Language Server Protocol (LSP) implementation for Lex
This crate provides language server capabilities for the Lex format, enabling rich editor
support in any LSP-compatible editor .
Position Encoding — read before touching any Position
In this server, `Position.character` is a **UTF-8 byte offset** into the line —
NOT a UTF-16 code unit nor a `char` count. This is the
load-bearing convention every position↔text conversion here must honor.
It originates upstream: lex-core's `byte_to_position` computes
`column = byte_offset - line_start`, and `to_lsp_position` forwards that value to
LSP unchanged. Every consumer of a `Position` in this crate must therefore read
`.character` back as a byte offset to stay consistent.
Practical consequence: to take the text up to a caret, slice on the byte offset
— never
`line.chars.take`.
A `char`-based count silently over-reads past the caret on any line containing
multi-byte characters . See `slice_text_by_range`
in `server.rs` for the canonical byte-offset slicing routine, including the
multi-byte-boundary guards.
Caveat — not yet uniform: `indent_level_from_position` in `server.rs` still uses
the `chars.take` form. It is currently harmless there , but it does not yet
follow this convention and is a separate cleanup, not a reason to copy the pattern.
Design Decision: tower-lsp
After evaluating the Rust LSP ecosystem, we chose tower-lsp as our framework:
Considered Options:
1. tower-lsp: High-level async framework built on Tower
2. lsp-server: Low-level sync library from rust-analyzer
3. async-lsp: Low-level async with full Tower integration
Why tower-lsp:
- Best balance of ease-of-use and functionality for a new LSP project
- Strong ecosystem support with extensive documentation and examples
- Modern async/await patterns ideal for Lex's structured parsing needs
- Built-in LSP 3.18 support with proposed features
- Active community with production usage in many language servers
- Good integration with Rust async ecosystem
Trade-offs:
- Less flexible than async-lsp for custom Tower layers
- Requires &self for
Feature Set
Lex is a structured document format, not a programming language. LSP features are selected
to optimize document authoring and navigation workflows:
Core Features:
a. Syntax Highlighting
1. Semantic Tokens
Architecture
The server follows a layered architecture:
LSP Layer :
- Handles JSON-RPC communication
- Protocol handshaking and capability negotiation
- Request/response routing
Server Layer :
- Implements LanguageServer trait
- Manages document state and parsing
- Coordinates feature implementations
- Very thing, mostly calls the the feature layers over lex-parser
- Thin tests just asserting the right things are being called and returned
Feature Layer:
- Each feature operates on Lex AST
- Stateless transformations where possible
- All logic and dense unit tests
Testing Strategy
Following Lex project conventions:
- Use official sample files from specs/ for all tests
- Use lexplore loader for consistent test data
- Use ast_assertions library for AST validation
- Test each feature in isolation and integration
- Test against kitchensink and trifecta fixtures
Non-Features
The following LSP features are intentionally excluded as they don't apply to document formats:
- Code Lens: Not applicable to documents
- Type Hierarchy: No type system
- Implementation: No interfaces/implementations
- Moniker: For cross-repo linking, not needed
- Linked Editing Range: For paired tags
- Diagnostics: we don't have a clear vision for how that would work.
Error Handling and Robustness
The server is designed to be highly robust and crash-resistant, following these principles:
1. No Panics:
- We strictly avoid `unwrap` and `expect` in production code paths.
- All potential failure points return `Result`.
- Errors are propagated up the stack and handled gracefully.
2. Graceful Degradation:
- If a feature fails , we log the error and return
an empty result or `None` rather than crashing the server.
- This ensures that a bug in one feature doesn't bring down the entire editor experience.
3. Error Propagation:
- The `lex-parser` crate returns `Result` types for all parsing operations.
- The `lexd-lsp` server maps these internal errors to appropriate LSP error codes
when communicating with the client.
4. Property-Based Testing:
- We use `proptest` to fuzz the server with random inputs
to uncover edge cases and ensure stability under unexpected conditions.
Usage
This crate provides both a library and binary:
Library:
```rust
use LexLanguageServer;
use Server;
async
```
Binary:
$ lexd-lsp
Starts the language server on stdin/stdout for editor integration.