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 .
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 `lex-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:
$ lex-lsp
Starts the language server on stdin/stdout for editor integration.