feat: connect NWScript language server to Monaco via WebSocket

Add the forked nwscript-ee-language-server as a git submodule and wire
it up to the editor through a WebSocket-based LSP bridge:

- Backend: lsp.service.ts spawns the language server in --stdio mode
  and bridges JSON-RPC messages between WebSocket and stdin/stdout
- Backend: /ws/lsp upgrade handler in index.ts
- Frontend: LspClient class using vscode-ws-jsonrpc for JSON-RPC over
  WebSocket, with Monaco providers for completions, hover, and
  diagnostics
- Frontend: useLspClient/useLspDocument hooks integrated into
  MonacoEditor component
This commit is contained in:
plenarius
2026-04-20 19:41:05 -04:00
parent 64908098cd
commit b7177a8fd7
9 changed files with 589 additions and 3 deletions
@@ -1,6 +1,7 @@
import { useRef, useCallback } from "react";
import { useRef, useCallback, useState } from "react";
import { Editor as ReactMonacoEditor, type OnMount } from "@monaco-editor/react";
import type { editor } from "monaco-editor";
import { useLspClient, useLspDocument } from "../../hooks/useLspClient.js";
interface MonacoEditorProps {
filePath: string;
@@ -229,10 +230,16 @@ export function MonacoEditor({
onChange,
}: MonacoEditorProps) {
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const [monacoRef, setMonacoRef] = useState<typeof import("monaco-editor") | null>(null);
const resolvedLang = language ?? languageFromPath(filePath);
useLspClient(monacoRef);
useLspDocument(editorRef.current, filePath, resolvedLang);
const handleMount: OnMount = useCallback(
(editorInstance, monaco) => {
editorRef.current = editorInstance;
setMonacoRef(monaco as unknown as typeof import("monaco-editor"));
registerNWScript(monaco);
defineForgeTheme(monaco);
monaco.editor.setTheme("forge-dark");