feat: integrate monaco-languageclient v10 with NWScript LSP

Replace hand-rolled LSP client (lspClient.ts, useLspClient.ts) with
monaco-languageclient v10 extended mode using @typefox/monaco-editor-react.
NWScript TextMate grammar from the LSP submodule provides syntax highlighting.
Full LSP features: completion, hover, diagnostics, go-to-definition, signature
help — all wired through WebSocket to the nwscript-language-server.

LSP server patches: fix workspaceFolders null assertion crash, handle missing
workspace/configuration gracefully, derive rootPath from rootUri when null,
guard tokenizer getRawTokenContent against undefined tokens.

Backend fixes: WebSocket routing changed to noServer mode so /ws, /ws/lsp,
and /ws/terminal/* don't conflict. TLK index loaded at startup (41,927 entries
from nwn-haks/layonara.tlk.json). Workspace routes get proper try/catch.
writeConfig creates parent directories. setupClone ensures workspace structure.

Frontend: GffEditor and AreaEditor rewritten with inline styles and TLK
resolution for CExoLocString fields. EditorTabs rewritten with lucide icons.
Tab content hydrates from API on refresh. Setup wizard gets friendly error
messages. SimpleEditor/SimpleDiffEditor for non-LSP editor uses. Vite config
updated for monaco-vscode-api compatibility.
This commit is contained in:
plenarius
2026-04-21 05:23:52 -04:00
parent cbe51a6e67
commit f39f1d818b
62 changed files with 9355 additions and 1137 deletions
+47 -15
View File
@@ -37,7 +37,7 @@ The Forge container manages sibling containers via the Docker socket (Portainer
- **Icons**: lucide-react (SVG icons throughout)
- **Fonts**: Self-hosted variable fonts via @fontsource-variable (Manrope, Alegreya, JetBrains Mono)
- **Code editor**: Monaco Editor with NWScript Monarch tokenizer
- **NWScript LSP**: Forked `layonara/nwscript-ee-language-server` connected via WebSocket JSON-RPC
- **NWScript LSP**: Forked `layonara/nwscript-ee-language-server` connected via `monaco-languageclient` WebSocket. Server patched for Forge compatibility (see LSP server patches below).
- **Terminal**: xterm.js with child_process.spawn shell sessions
- **Docker API**: dockerode
- **Git**: simple-git (named import: `import { simpleGit } from "simple-git"`)
@@ -290,14 +290,18 @@ Each Gitea repo has a push mirror configured to sync to the corresponding GitHub
- Frontend uses `"moduleResolution": "bundler"` and `"noImplicitAny": false` in tsconfig to avoid strict-mode issues from subagent-generated code
- Frontend build script is `"build": "vite build"` (skips `tsc -b` which fights with bundler resolution)
### Tested End-to-End
- Setup wizard flow against live Gitea — fork detection, token validation, clone all 3 repos (including 5.5GB nwn-haks), workspace initialization
- NWScript LSP with real `.nss` files — completion, hover, syntax highlighting all working via `monaco-languageclient`
- TLK resolution in GFF visual editors — 41,927 entries from `layonara.tlk.json`
### Not Yet Tested End-to-End
- Full setup wizard flow against live Gitea (the UI renders but hasn't been walked through with real forking/cloning)
- Module build → server start → connect with NWN client
- Toolset temp0/ watcher with real GFF files
- Hot-reload pipeline with a real nasher build
- Push mirrors actually triggering GitHub CI
- LSP with real `.nss` files (the bridge architecture is built but URI mapping may need tuning)
### Remaining UI Work
@@ -430,23 +434,51 @@ docker build -t layonara-builder builder/ # ~45 seconds
docker build -t layonara-forge . # ~2 minutes
```
## Current State (as of UI overhaul session end)
## Current State (as of LSP integration session)
- All code is local — nothing has been pushed to any remote for the layonara-forge repo itself
- Frontend build passes clean (no TS errors, no lint issues)
- All 8 pages styled with consistent design system (cards, icons, tokens)
- Setup wizard has 4-phase indicator, path inputs, status dots
- Routes are code-split (10 chunks)
- 28 frontend files modified in the UI overhaul
- The `layonara/neverwinter.nim` and `layonara/nwscript-ee-language-server` forks are on GitHub with changes pushed
- `plenarius/layonara_nwn` has a release workflow added and v0.1.1 release published
- Setup wizard integration-tested against live Gitea (fork, clone all 3 repos verified)
- **monaco-languageclient v10** extended mode fully integrated — replaced hand-rolled LSP client
- **NWScript TextMate grammar** from submodule providing syntax highlighting (replaces Monarch tokenizer)
- **NWScript LSP** working end-to-end: completion, hover, diagnostics, go-to-definition, signature help
- **LSP server patched**: 4 crash bugs fixed (workspaceFolders null, workspace/configuration, rootPath null, tokenizer null guard)
- **TLK index** loaded at startup (41,927 entries), resolves localized string references in GFF editors
- GFF editors rewritten with inline styles, TLK lookup for CExoLocString fields
- EditorTabs rewritten with inline styles + lucide icons
- Tab content hydrates from API on refresh (localStorage persists tab list, API fetches content)
- Setup wizard error display improved with friendly error mapping
- Backend: WebSocket routing fixed (noServer mode), workspace routes have try/catch, writeConfig creates directories
- Docker: `git` added to production Dockerfile
### Editor Stack
| Component | Package | Purpose |
|-----------|---------|---------|
| Editor wrapper | `@typefox/monaco-editor-react` | React component wrapping MonacoEditorReactComp |
| LSP client | `monaco-languageclient` v10.7.0 | Extended mode with VS Code services |
| VS Code API | `@codingame/monaco-vscode-api` v25.1.2 | TextMate, themes, editor services |
| Grammar | `nwscript-extension/` | Local VS Code extension with `.tmLanguage.json` from submodule |
| Theme | Default Dark Modern | VS Code built-in (Forge Dark theme created but token colors need tuning) |
| LSP server | `lsp/nwscript-language-server/server/out/server.js` | Forked NWScript LSP, `--stdio` mode |
| LSP bridge | `packages/backend/src/services/lsp.service.ts` | WebSocket ↔ stdio pipe |
| Compiler | `lsp/.../server/resources/compiler/linux/nwn_script_comp` | Pre-built binary for diagnostics |
### Key Architecture Notes
- `monaco-editor` is overridden to `@codingame/monaco-vscode-editor-api` via npm overrides in root `package.json`
- The `MonacoVscodeApiWrapper` must be initialized once per app lifecycle — `MonacoEditorReactComp` handles this
- `SimpleEditor` and `SimpleDiffEditor` (for Server SQL console and Toolset diff viewer) also use `MonacoEditorReactComp` without a language client
- NWScript extension registered via `@codingame/monaco-vscode-api/extensions` `registerExtension` pattern
- Vite config requires `resolve.dedupe: ['vscode']`, `worker.format: 'es'`, and `@codingame/esbuild-import-meta-url-plugin`
- WebSocket routing: `/ws` (event broadcast), `/ws/lsp` (LSP bridge), `/ws/terminal/:id` (terminal) — all via `noServer` mode with manual upgrade handling
## Priority for Next Session
1. **Integration test the setup wizard** against live Gitea (fork, clone, build cycle) — needs `git` installed or Docker environment
2. **Test module build → server start → NWN client connection**
3. **Test Toolset temp0/ sync** with real GFF files
4. **Polish remaining components** (GFF editors, CommitDialog, FileExplorer, EditorTabs, SearchPanel — still use Tailwind classes)
1. **Forge Dark theme** — created at `nwscript-extension/themes/forge-dark.json` but token colors don't apply (theme loads but syntax highlighting loses color). Needs investigation into how VS Code theme contributions interact with TextMate scopes in the `@codingame` stack. Consider starting from Default Dark Modern and customizing editor chrome colors via `userConfiguration` instead.
2. **Polish remaining components** — CommitDialog, FileExplorer, SearchPanel still use Tailwind classes for layout. ItemEditor, CreatureEditor, DialogEditor GFF editors also need inline style conversion.
3. **Test module build → server start → NWN client connection** — requires Docker socket access
4. **Test Toolset temp0/ sync** with real GFF files
5. **Test light mode** visually
6. **Set up Gitea backup** on xandrial
7. **Button nesting warning** — EditorTabs has `<button>` inside `<button>` (close button inside tab button). Change outer to `<div>` with click handler.