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_LANGUAGE,
} from "@/config";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Palette } from "lucide-react";
import * as monaco from "monaco-editor";
import { highlighter } from "@/lib/shiki";
import { Bot, CodeXml } from "lucide-react";
import { shikiToMonaco } from "@shikijs/monaco";
import { connectToLanguageServer } from "@/lib/lsp";
import { useEffect, useRef, useState } from "react";
import { SUPPORTED_EDITOR_THEMES } from "@/constants/themes";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
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";
self.MonacoEnvironment = {
getWorker(_, _label) {
return new editorWorker();
},
};
loader.config({ monaco });
function App() {
@ -25,9 +42,12 @@ function App() {
if (webSocketRef.current) {
webSocketRef.current.close();
}
connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket;
});
if (language in SUPPORTED_LSP_LANGUAGES) {
connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket;
});
}
return () => {
if (webSocketRef.current) {
@ -40,32 +60,66 @@ function App() {
<div className="h-screen bg-[#282c34] dark overflow-hidden">
<Tabs defaultValue="code-editor" className="h-full mt-2">
<ScrollArea className="border-b border-[#3e4452]">
<TabsList className="gap-1 bg-transparent px-4 mb-2">
<TabsTrigger
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"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
</TabsTrigger>
<TabsTrigger
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"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
AI
</TabsTrigger>
</TabsList>
<div className="flex items-center justify-between">
<TabsList className="gap-1 bg-transparent px-4 mb-2">
<TabsTrigger
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"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
</TabsTrigger>
<TabsTrigger
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"
size={16}
strokeWidth={2}
aria-hidden="true"
/>
AI
</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" />
</ScrollArea>
<TabsContent value="code-editor" className="h-full mt-0">
@ -98,9 +152,11 @@ function App() {
"*"
);
}
connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket;
});
if (language in SUPPORTED_LSP_LANGUAGES) {
connectToLanguageServer(language).then((webSocket) => {
webSocketRef.current = webSocket;
});
}
}}
onChange={(value) => {
if (value !== undefined) {

View File

@ -28,15 +28,6 @@ using namespace std;
int main() {
cout << "Hello, World!";
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 = {
c: {
@ -13,11 +13,6 @@ export const SUPPORTED_EDITOR_LANGUAGES_CONFIG = {
label: "C++",
icon: CplusplusOriginal,
},
java: {
id: "java",
label: "Java",
icon: JavaOriginal,
},
};
export type SupportedEditorLanguage =