feat: add theme and language selection to code editor with language server connection support

This commit is contained in:
ngc2207 2025-01-10 19:13:52 +08:00
parent 937330ac3d
commit 1aeb472495
3 changed files with 91 additions and 49 deletions

View File

@ -3,16 +3,33 @@ import {
DEFAULT_EDITOR_THEME, DEFAULT_EDITOR_THEME,
DEFAULT_EDITOR_LANGUAGE, DEFAULT_EDITOR_LANGUAGE,
} from "@/config"; } from "@/config";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Palette } from "lucide-react";
import * as monaco from "monaco-editor"; import * as monaco from "monaco-editor";
import { highlighter } from "@/lib/shiki"; import { highlighter } from "@/lib/shiki";
import { Bot, CodeXml } from "lucide-react"; import { Bot, CodeXml } from "lucide-react";
import { shikiToMonaco } from "@shikijs/monaco"; import { shikiToMonaco } from "@shikijs/monaco";
import { connectToLanguageServer } from "@/lib/lsp";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { SUPPORTED_EDITOR_THEMES } from "@/constants/themes";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { DiffEditor, Editor, Monaco, loader } from "@monaco-editor/react"; import { DiffEditor, Editor, Monaco, loader } from "@monaco-editor/react";
import { SUPPORTED_EDITOR_LANGUAGES_CONFIG } from "@/constants/languages";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import { connectToLanguageServer, SUPPORTED_LSP_LANGUAGES } from "@/lib/lsp";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
self.MonacoEnvironment = {
getWorker(_, _label) {
return new editorWorker();
},
};
loader.config({ monaco }); loader.config({ monaco });
function App() { function App() {
@ -25,9 +42,12 @@ function App() {
if (webSocketRef.current) { if (webSocketRef.current) {
webSocketRef.current.close(); webSocketRef.current.close();
} }
connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket; if (language in SUPPORTED_LSP_LANGUAGES) {
}); connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket;
});
}
return () => { return () => {
if (webSocketRef.current) { if (webSocketRef.current) {
@ -40,32 +60,66 @@ function App() {
<div className="h-screen bg-[#282c34] dark overflow-hidden"> <div className="h-screen bg-[#282c34] dark overflow-hidden">
<Tabs defaultValue="code-editor" className="h-full mt-2"> <Tabs defaultValue="code-editor" className="h-full mt-2">
<ScrollArea className="border-b border-[#3e4452]"> <ScrollArea className="border-b border-[#3e4452]">
<TabsList className="gap-1 bg-transparent px-4 mb-2"> <div className="flex items-center justify-between">
<TabsTrigger <TabsList className="gap-1 bg-transparent px-4 mb-2">
value="code-editor" <TabsTrigger
className="rounded-full data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-none" value="code-editor"
> className="rounded-full data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-none"
<CodeXml >
className="-ms-0.5 me-1.5 opacity-60" <CodeXml
size={16} className="-ms-0.5 me-1.5 opacity-60"
strokeWidth={2} size={16}
aria-hidden="true" strokeWidth={2}
/> aria-hidden="true"
/>
</TabsTrigger>
<TabsTrigger </TabsTrigger>
value="diff-editor" <TabsTrigger
className="500 rounded-full data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-none" value="diff-editor"
> className="500 rounded-full data-[state=active]:bg-primary data-[state=active]:text-primary-foreground data-[state=active]:shadow-none"
<Bot >
className="-ms-0.5 me-1.5 opacity-60" <Bot
size={16} className="-ms-0.5 me-1.5 opacity-60"
strokeWidth={2} size={16}
aria-hidden="true" strokeWidth={2}
/> aria-hidden="true"
AI />
</TabsTrigger> AI
</TabsList> </TabsTrigger>
</TabsList>
<div className="flex items-center gap-x-2 pr-4 mb-2">
<Select value={theme} onValueChange={setTheme}>
<SelectTrigger className="relative ps-9 w-40 bg-foreground">
<div className="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center ps-3 text-muted-foreground/80 group-has-[[disabled]]:opacity-50">
<Palette size={16} strokeWidth={2} aria-hidden="true" />
</div>
<SelectValue placeholder="Select Theme" />
</SelectTrigger>
<SelectContent>
{SUPPORTED_EDITOR_THEMES.map((theme) => (
<SelectItem key={theme.id} value={theme.id}>
<span className="truncate">{theme.label}</span>
</SelectItem>
))}
</SelectContent>
</Select>
<Select value={language} onValueChange={setLanguage}>
<SelectTrigger className="[&>span]:flex [&>span]:items-center [&>span]:gap-2 [&>span_svg]:shrink-0 [&>span_svg]:text-muted-foreground/80 w-36 bg-foreground">
<SelectValue placeholder="Select Language" />
</SelectTrigger>
<SelectContent className="[&_*[role=option]>span>svg]:shrink-0 [&_*[role=option]>span>svg]:text-muted-foreground/80 [&_*[role=option]>span]:end-2 [&_*[role=option]>span]:start-auto [&_*[role=option]>span]:flex [&_*[role=option]>span]:items-center [&_*[role=option]>span]:gap-2 [&_*[role=option]]:pe-8 [&_*[role=option]]:ps-2">
{Object.values(SUPPORTED_EDITOR_LANGUAGES_CONFIG).map(
(language) => (
<SelectItem key={language.id} value={language.id}>
<language.icon size={16} aria-hidden="true" />
<span className="truncate">{language.label}</span>
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
</div>
<ScrollBar orientation="horizontal" /> <ScrollBar orientation="horizontal" />
</ScrollArea> </ScrollArea>
<TabsContent value="code-editor" className="h-full mt-0"> <TabsContent value="code-editor" className="h-full mt-0">
@ -98,9 +152,11 @@ function App() {
"*" "*"
); );
} }
connectToLanguageServer(language).then((webSocket) => { if (language in SUPPORTED_LSP_LANGUAGES) {
webSocketRef.current = webSocket; connectToLanguageServer(language).then((webSocket) => {
}); webSocketRef.current = webSocket;
});
}
}} }}
onChange={(value) => { onChange={(value) => {
if (value !== undefined) { if (value !== undefined) {

View File

@ -28,15 +28,6 @@ using namespace std;
int main() { int main() {
cout << "Hello, World!"; cout << "Hello, World!";
return 0; return 0;
}`,
},
java: {
path: "playground/Main.java",
language: "java",
value: `public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}`, }`,
}, },
}; };

View File

@ -1,6 +1,6 @@
import { COriginal, CplusplusOriginal, JavaOriginal } from "devicons-react"; import { COriginal, CplusplusOriginal } from "devicons-react";
export const SUPPORTED_EDITOR_LANGUAGES = ["c", "cpp", "java"]; export const SUPPORTED_EDITOR_LANGUAGES = ["c", "cpp"];
export const SUPPORTED_EDITOR_LANGUAGES_CONFIG = { export const SUPPORTED_EDITOR_LANGUAGES_CONFIG = {
c: { c: {
@ -13,11 +13,6 @@ export const SUPPORTED_EDITOR_LANGUAGES_CONFIG = {
label: "C++", label: "C++",
icon: CplusplusOriginal, icon: CplusplusOriginal,
}, },
java: {
id: "java",
label: "Java",
icon: JavaOriginal,
},
}; };
export type SupportedEditorLanguage = export type SupportedEditorLanguage =