Skip to main content

lexd_lsp/
lib.rs

1//! Language Server Protocol (LSP) implementation for Lex
2//!
3//!     This crate provides language server capabilities for the Lex format, enabling rich editor
4//!     support in any LSP-compatible editor (VSCode, Neovim, Emacs, Sublime, etc.).
5//!
6//! Position Encoding — read before touching any `Position`
7//!
8//!     In this server, `Position.character` is a **UTF-8 byte offset** into the line —
9//!     NOT a UTF-16 code unit (the LSP default) nor a `char` count. This is the
10//!     load-bearing convention every position↔text conversion here must honor.
11//!
12//!     It originates upstream: lex-core's `SourceLocation::byte_to_position` computes
13//!     `column = byte_offset - line_start`, and `to_lsp_position` forwards that value to
14//!     LSP unchanged. Every consumer of a `Position` in this crate must therefore read
15//!     `.character` back as a byte offset to stay consistent.
16//!
17//!     Practical consequence: to take the text up to a caret, slice on the byte offset
18//!     (`line.get(..pos.character as usize)`, which returns `None` rather than panicking
19//!     on an out-of-bounds or mid-`char` index) — never
20//!     `line.chars().take(pos.character as usize)`.
21//!     A `char`-based count silently over-reads past the caret on any line containing
22//!     multi-byte characters (this caused a real bug in #740). See `slice_text_by_range`
23//!     in `server.rs` for the canonical byte-offset slicing routine, including the
24//!     multi-byte-boundary guards.
25//!
26//!     Caveat — not yet uniform: `indent_level_from_position` in `server.rs` still uses
27//!     the `chars().take()` form. It is currently harmless there (it only consumes leading
28//!     ASCII indentation, where byte and `char` counts coincide), but it does not yet
29//!     follow this convention and is a separate cleanup, not a reason to copy the pattern.
30//!
31//! Design Decision: tower-lsp
32//!
33//!     After evaluating the Rust LSP ecosystem, we chose tower-lsp as our framework:
34//!
35//!     Considered Options:
36//!         1. tower-lsp: High-level async framework built on Tower (~204K monthly downloads)
37//!         2. lsp-server: Low-level sync library from rust-analyzer (~309K monthly downloads)
38//!         3. async-lsp: Low-level async with full Tower integration (~135 GitHub stars)
39//!
40//!     Why tower-lsp:
41//!         - Best balance of ease-of-use and functionality for a new LSP project
42//!         - Strong ecosystem support with extensive documentation and examples
43//!         - Modern async/await patterns ideal for Lex's structured parsing needs
44//!         - Built-in LSP 3.18 support with proposed features
45//!         - Active community with production usage in many language servers
46//!         - Good integration with Rust async ecosystem (tokio, futures)
47//!
48//!     Trade-offs:
49//!         - Less flexible than async-lsp for custom Tower layers (acceptable for our needs)
50//!         - Requires &self for trait methods, forcing Arc<Mutex<>> for mutable state (standard pattern)
51//!         - Notification ordering is async (not an issue for initial feature set)
52//!
53//!     Future Migration Path:
54//!         If we later need precise notification ordering or custom middleware, we can migrate
55//!         to async-lsp with minimal disruption as both use similar async patterns.
56//!
57//! Feature Set
58//!
59//!     Lex is a structured document format, not a programming language. LSP features are selected
60//!     to optimize document authoring and navigation workflows:
61//!
62//!     Core Features:
63//!
64//!         a. Syntax Highlighting
65
66//!             1. Semantic Tokens (textDocument/semanticTokens/*):
67//!                 - Syntax highlighting for sessions, lists, definitions, annotations
68//!                 - Inline formatting: bold, italic, code, math
69//!                 - References, footnotes, citations
70//!                 - Verbatim blocks with language-specific highlighting
71//!             2. Document Symbols (textDocument/documentSymbol):
72//!                 - Hierarchical outline view of document structure
73//!                 - Sessions with nesting (1., 1.1., 1.1.1., etc.)
74//!                 - Definitions, annotations, lists as navigable symbols
75//!            3. Hover Information (textDocument/hover):
76//!                 - Preview footnote/citation content on hover
77//!                 - Show annotation metadata
78//!                 - Preview definition content when hovering over reference
79//!             4. Folding Ranges (textDocument/foldingRange):
80//!                 - Fold sessions and nested content
81//!                 - Fold list items with children
82//!                 - Fold annotations, definitions, verbatim blocks
83//!
84//!         b. Navigation
85//!
86//!             5. Go to Definition / Find References (textDocument/definition, textDocument/references):
87//!                 - Find all references to footnotes/citations
88//!                 - Jump from footnote reference [42] to annotation
89//!                 - Jump from citation [@spec2025] to bibliography entry
90//!                 - Jump from internal reference [TK-rootlist] to target
91//!             6. Document Links (textDocument/documentLink):
92//!                 - Clickable links in text
93//!                 - Verbatim block src parameters (images, includes)
94//!                 - External references
95//!
96//!         c. Editing
97//!
98//!             7. Document Formatting (textDocument/formatting, textDocument/rangeFormatting):
99//!                 - Fix indentation issues
100//!                 - Normalize blank lines
101//!                 - Align list markers //!         
102//!
103//!
104//!
105//! Architecture
106//!
107//!     The server follows a layered architecture:
108//!
109//!     LSP Layer (tower-lsp):
110//!         - Handles JSON-RPC communication
111//!         - Protocol handshaking and capability negotiation
112//!         - Request/response routing
113//!
114//!     Server Layer (this crate):
115//!         - Implements LanguageServer trait
116//!         - Manages document state and parsing
117//!         - Coordinates feature implementations
118//!         - Very thing, mostly calls the the feature layers over lex-parser
119//!         - Thin tests just asserting the right things are being called and returned
120//!
121//!     Feature Layer:
122//!         - Each feature operates on Lex AST
123//!         - Stateless transformations where possible
124//!         - All logic and dense unit tests
125//!
126//!
127//! Testing Strategy
128//!
129//!     Following Lex project conventions:
130//!         - Use official sample files from specs/ for all tests
131//!         - Use lexplore loader for consistent test data
132//!         - Use ast_assertions library for AST validation
133//!         - Test each feature in isolation and integration
134//!         - Test against kitchensink and trifecta fixtures
135//!
136//! Non-Features
137//!
138//!     The following LSP features are intentionally excluded as they don't apply to document formats:
139//!         - Code Lens: Not applicable to documents
140//!         - Type Hierarchy: No type system
141//!         - Implementation: No interfaces/implementations
142//!         - Moniker: For cross-repo linking, not needed
143//!         - Linked Editing Range: For paired tags (HTML/XML)
144//!         - Diagnostics: we don't have a clear vision for how that would work.
145//!
146//! Error Handling and Robustness
147//!
148//!     The server is designed to be highly robust and crash-resistant, following these principles:
149//!
150//!     1. No Panics:
151//!         - We strictly avoid `unwrap()` and `expect()` in production code paths.
152//!         - All potential failure points (parsing, serialization, IO) return `Result`.
153//!         - Errors are propagated up the stack and handled gracefully.
154//!
155//!     2. Graceful Degradation:
156//!         - If a feature fails (e.g., semantic tokens calculation), we log the error and return
157//!           an empty result or `None` rather than crashing the server.
158//!         - This ensures that a bug in one feature doesn't bring down the entire editor experience.
159//!
160//!     3. Error Propagation:
161//!         - The `lex-parser` crate returns `Result` types for all parsing operations.
162//!         - The `lexd-lsp` server maps these internal errors to appropriate LSP error codes
163//!           (e.g., `InternalError`, `InvalidRequest`) when communicating with the client.
164//!
165//!     4. Property-Based Testing:
166//!         - We use `proptest` to fuzz the server with random inputs (commands, document text)
167//!           to uncover edge cases and ensure stability under unexpected conditions.
168//!
169//! Usage
170//!
171//!     This crate provides both a library and binary:
172//!
173//!     Library:
174//!         ```rust
175//!         use lexd_lsp::LexLanguageServer;
176//!         use tower_lsp::Server;
177//!
178//!         #[tokio::main]
179//!         async fn main() {
180//!             let stdin = tokio::io::stdin();
181//!             let stdout = tokio::io::stdout();
182//!
183//!             let (service, socket) = LspService::new(|client| LexLanguageServer::new(client));
184//!             Server::new(stdin, stdout, socket).serve(service).await;
185//!         }
186//!         ```
187//!
188//!     Binary:
189//!         $ lexd-lsp
190//!         Starts the language server on stdin/stdout for editor integration.
191//!
192
193pub mod extension_dispatch;
194pub mod features;
195pub mod server;
196pub mod trust_prompt;
197
198pub use server::LexLanguageServer;