-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Concept
Replace the TypeScript LSP server (src/server/) with a native Rust binary. The VS Code extension host (TypeScript shell) stays as-is, since the vscode module only runs in Node.js. All language intelligence moves to Rust.
Motivation
- Native parsing speed and lower memory footprint as workspace size grows
cargo fuzzfor parser robustness without any extra tooling- Stronger type guarantees for the IEC 61131-3 type system (Rust enums vs TS interfaces)
- Foundation for incremental re-analysis if scale demands it later
Suggested Approach
Keep in TypeScript (unchanged)
src/extension.ts- activation, command registrationsrc/client/lsp-client.ts- spawns the Rust binary as a stdio child process, wiresvscode-languageclient
Rewrite in Rust (one crate per concern)
controlforge-syntax- lexer + parser producing a typed ASTcontrolforge-hir- typed IR + symbol index; simple full re-index on file changecontrolforge-lsp-tower-lspserver, implements all current providers (completion, definition, rename, formatting, diagnostics, hover). Stdio transport, spawned by the TS shell.
Stack rationale
tower-lsp - the standard Rust LSP framework. Async, well maintained, no real alternative worth considering.
logos - compile-time DFA lexer generator. Low complexity cost; less code to maintain than a hand-rolled lexer and harder to get wrong. Justified.
rowan (lossless CST) - preserves whitespace and comments, needed for a correct formatting provider. The main tradeoff: more complex than a simple typed AST, with a meaningful learning curve. Justified long-term but not required upfront. Start with a typed AST; migrate to rowan when formatting becomes a focus.
salsa (incremental computation) - not needed at migration time. See scale analysis below. If ever required, ra-salsa (rust-analyzer's actively maintained fork) is the best option.
On scale and performance
ST projects rarely reach the scale where naive full re-index on save becomes perceptible:
| Project size | Files | Lines | TS LSP | Rust (no salsa) |
|---|---|---|---|---|
| Typical machine | 20-50 | 5k-20k | Fine | Fine |
| Large OEM | 100-300 | 50k-150k | Sluggish on save | Fine |
| Massive platform | 500+ | 300k+ | Noticeable lag | Fine |
| Extreme | 1000+ | 500k+ | Poor | Consider salsa |
Native Rust re-parses ~100x faster than TypeScript. Even a naive full re-index on every save is imperceptible up to ~500 files. The vast majority of ControlForge users are in the 10-100 file range.
Distribution
- CI produces platform binaries:
linux-x64,win32-x64,darwin-x64,darwin-arm64 - Bundled in
.vsixunderbin/- binary resolver inlsp-client.tspicks correct one at runtime - Highest ongoing maintenance cost of the migration; automate with
cargo-xtaskor ajustrelease script
Migration Path
- Stand up
controlforge-syntaxcrate withlogoslexer + typed AST, port parser tests first - Stand up
controlforge-lspwithtower-lsp, initially returning empty results - validate spawn/stdio wiring end-to-end - Port providers one by one; run existing unit tests against the new server via the LSP protocol
- Stand up
controlforge-hirwith simple re-index on change - Delete
src/server/once feature parity is confirmed - Migrate to
rowanlossless CST when formatting provider becomes a focus
Risks
- Platform binary distribution adds CI complexity and release friction
- Contributor bar rises significantly - Rust knowledge required for all LSP work
- Rewrite cost is substantial; no user-visible features ship during migration
Decision Criteria
Not worth doing until at least one of:
- Measurable performance complaints on real workspaces
- A specific feature is blocked by the TypeScript architecture
- A Rust-fluent contributor is available to lead it