From d2ce0acfe5adcbcf54fca9e2cbb015366ae6cfe4 Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Sat, 11 Jan 2025 00:41:22 +0800 Subject: [PATCH] feat: refactor language server connection handling and improve undo/redo functionality in code editor --- code-editor/src/App.tsx | 196 ++++++++++++++++--------------------- code-editor/src/lib/lsp.ts | 62 ++++++++---- 2 files changed, 125 insertions(+), 133 deletions(-) diff --git a/code-editor/src/App.tsx b/code-editor/src/App.tsx index 7641025..6f287f0 100644 --- a/code-editor/src/App.tsx +++ b/code-editor/src/App.tsx @@ -3,6 +3,18 @@ import { DEFAULT_EDITOR_THEME, DEFAULT_EDITOR_LANGUAGE, } from "@/config"; +import { + Paintbrush, + Palette, + Redo2, + Undo2, +} from "lucide-react"; +import { + redoChange, + undoChange, + formatCode, + moveCursorToEnd, +} from "@/lib/editor"; import { Select, SelectContent, @@ -10,41 +22,26 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Copy, - FileDown, - Paintbrush, - Palette, - Redo2, - Undo2, -} 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 { 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"; -import { Button } from "@/components/ui/button"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; -import { - redoChange, - undoChange, - formatCode, - downloadCode, - copyCode, - moveCursorToEnd, -} from "@/lib/editor"; +import { CodeXml } from "lucide-react"; +import * as monaco from "monaco-editor"; +import { highlighter } from "@/lib/shiki"; +import { Button } from "@/components/ui/button"; +import { shikiToMonaco } from "@shikijs/monaco"; +import { useEffect, useRef, useState } from "react"; +import { SUPPORTED_EDITOR_THEMES } from "@/constants/themes"; +import { MonacoLanguageClient } from "monaco-languageclient"; +import { Editor, Monaco, loader } from "@monaco-editor/react"; +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; +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) { @@ -59,42 +56,45 @@ function App() { const [theme, setTheme] = useState(DEFAULT_EDITOR_THEME); const file = DEFAULT_FILES[language]; const webSocketRef = useRef(null); + const languageClientRef = useRef(null); const editorRef = useRef(null); useEffect(() => { - const closeWebSocket = () => { - if (webSocketRef.current) { - if ( - webSocketRef.current.readyState === WebSocket.OPEN || - webSocketRef.current.readyState === WebSocket.CONNECTING - ) { - webSocketRef.current.onclose = () => { - webSocketRef.current = null; - }; - webSocketRef.current.close(); - } else { - webSocketRef.current = null; - } + const handleLanguageServer = async () => { + if (languageClientRef.current && language in SUPPORTED_LSP_LANGUAGES) { + await languageClientRef.current.stop(); + languageClientRef.current = null; } - }; - - const connectWebSocket = async () => { - closeWebSocket(); - - if (editorRef.current && language in SUPPORTED_LSP_LANGUAGES) { - try { - const webSocket = await connectToLanguageServer(language); - webSocket.onopen = () => { + if ( + webSocketRef.current && + webSocketRef.current.readyState === WebSocket.OPEN + ) { + webSocketRef.current.close(); + webSocketRef.current = null; + } + if (language in SUPPORTED_LSP_LANGUAGES) { + connectToLanguageServer(language) + .then(([webSocket, languageClient]) => { webSocketRef.current = webSocket; - }; - } catch (error) { - console.error("Failed to connect to language server", error); - } + languageClientRef.current = languageClient; + }) + .catch((error) => { + console.error(error); + }); } }; - connectWebSocket(); - return closeWebSocket; + if (editorRef.current) { + const value = editorRef.current.getModel()?.getValue(); + if (value !== undefined) { + window.parent.postMessage( + { type: "editor-info", language, code: value }, + "*" + ); + } + moveCursorToEnd(editorRef.current); + handleLanguageServer(); + } }, [language]); return ( @@ -153,24 +153,6 @@ function App() {
- - - - - - - 重做 - - - @@ -189,6 +171,24 @@ function App() { + + + + + + + 重做 + + + @@ -207,42 +207,6 @@ function App() { - - - - - - - 复制 - - - - - - - - - - 下载 - - -
{ + moveCursorToEnd(editor); + connectToLanguageServer(language) + .then(([webSocket, languageClient]) => { webSocketRef.current = webSocket; + languageClientRef.current = languageClient; + }) + .catch((error) => { + console.error(error); }); - } }} onChange={(value) => { if (value !== undefined) { diff --git a/code-editor/src/lib/lsp.ts b/code-editor/src/lib/lsp.ts index cdbfcd6..7ab3bbe 100644 --- a/code-editor/src/lib/lsp.ts +++ b/code-editor/src/lib/lsp.ts @@ -57,26 +57,50 @@ function createLanguageClient( }); } -function createWebSocket(url: string) { - const webSocket = new WebSocket(url); - webSocket.onopen = () => { - const socket = toSocket(webSocket); - const reader = new WebSocketMessageReader(socket); - const writer = new WebSocketMessageWriter(socket); - const languageClient = createLanguageClient({ - reader, - writer, - }); - languageClient.start(); - reader.onClose(() => languageClient.stop()); - }; - return webSocket; -} +// function createWebSocket(url: string) { +// const webSocket = new WebSocket(url); +// webSocket.onopen = () => { +// const socket = toSocket(webSocket); +// const reader = new WebSocketMessageReader(socket); +// const writer = new WebSocketMessageWriter(socket); +// const languageClient = createLanguageClient({ +// reader, +// writer, +// }); +// languageClient.start(); +// reader.onClose(() => languageClient.stop()); +// }; +// return webSocket; +// } export async function connectToLanguageServer( language: string -): Promise { - const { hostname, port, path } = SUPPORTED_LSP_LANGUAGES[language]; - const url = createUrl(hostname, port, path); - return createWebSocket(url); +): Promise<[WebSocket, MonacoLanguageClient]> { + return new Promise((resolve, reject) => { + if (!(language in SUPPORTED_LSP_LANGUAGES)) { + reject(new Error("Unsupported language")); + return; + } + const { hostname, port, path } = SUPPORTED_LSP_LANGUAGES[language]; + const url = createUrl(hostname, port, path); + const webSocket = new WebSocket(url); + webSocket.onopen = () => { + const socket = toSocket(webSocket); + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + const languageClient = createLanguageClient({ + reader, + writer, + }); + languageClient.start(); + reader.onClose(() => languageClient.stop()); + resolve([webSocket, languageClient]); + }; + webSocket.onerror = (error) => { + reject(error); + }; + webSocket.onclose = () => { + reject(new Error("WebSocket closed")); + }; + }); }