perl_lsp/lib.rs
1//! Perl Language Server Protocol Runtime
2//!
3//! This crate provides the production-ready LSP server runtime for Perl, implementing the
4//! Language Server Protocol specification for seamless integration with editors like VSCode,
5//! Neovim, Emacs, and any LSP-compatible client.
6//!
7//! The runtime handles all protocol communication, message framing, state management, and
8//! feature dispatching, while delegating parsing and analysis to the `perl_parser` engine.
9//!
10//! # Quick Start
11//!
12//! ## Running the Server
13//!
14//! The simplest way to start the server is via the binary:
15//!
16//! ```bash
17//! # Install from source
18//! cargo install --path crates/perllsp
19//!
20//! # Run in stdio mode (default)
21//! perllsp --stdio
22//!
23//! # Check health status
24//! perllsp --health
25//!
26//! # Show version information
27//! perllsp --version
28//! ```
29//!
30//! ## Programmatic Usage
31//!
32//! Use the runtime as a library to embed LSP support:
33//!
34//! ```no_run
35//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
36//! perl_lsp::run_stdio()?;
37//! # Ok(())
38//! # }
39//! ```
40//!
41//! ## Custom Server Configuration
42//!
43//! For advanced use cases, create a server instance directly:
44//!
45//! ```no_run
46//! use perl_lsp::LspServer;
47//!
48//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
49//! let mut server = LspServer::new();
50//! server.run()?;
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! # Architecture
56//!
57//! The runtime is organized into distinct layers for maintainability and extensibility:
58//!
59//! ## Protocol Layer
60//!
61//! - **[`protocol`]**: JSON-RPC message types and LSP protocol definitions
62//! - **[`transport`]**: Message framing and transport (stdio, TCP, WebSocket)
63//! - **[`dispatch`]**: Request routing and method dispatch logic
64//!
65//! ## State Management
66//!
67//! - **[`state`]**: Document and workspace state management
68//! - **[`textdoc`]**: Text document synchronization and version tracking
69//! - **[`runtime`]**: Server lifecycle and initialization management
70//!
71//! ## Feature Providers
72//!
73//! The [`features`] module contains all LSP capability implementations:
74//!
75//! - **Completion**: Context-aware code completion with type inference
76//! - **Hover**: Symbol information and documentation on hover
77//! - **Definition**: Go-to-definition with cross-file support
78//! - **References**: Find all references across workspace
79//! - **Rename**: Symbol renaming with conflict detection
80//! - **Diagnostics**: Real-time syntax and semantic validation
81//! - **Formatting**: Native code formatting with optional perltidy compatibility
82//! - **Semantic Tokens**: Fine-grained syntax highlighting
83//! - **Code Actions**: Quick fixes and refactoring suggestions
84//! - **Code Lens**: Inline actionable metadata
85//! - **Inlay Hints**: Parameter names and type information
86//! - **Call Hierarchy**: Function call navigation
87//! - **Type Hierarchy**: Class inheritance navigation
88//! - **Document Links**: File and URL navigation
89//! - **Folding**: Code folding for blocks and regions
90//! - **Selection Range**: Smart text selection expansion
91//! - **Signature Help**: Function signature assistance
92//!
93//! ## Utilities
94//!
95//! - **[`convert`]**: Conversions between [`perl_parser`] types and [`lsp_types`]
96//! - **[`util`]**: URI handling, UTF-16 conversion, and position mapping
97//! - **[`fallback`]**: Text-based fallback when parsing fails
98//! - **[`cancellation`]**: Request cancellation infrastructure
99//! - **[`diagnostics_catalog`]**: Stable diagnostic codes
100//!
101//! ## Request Handling
102//!
103//! - **[`handlers`]**: Request and notification handlers
104//! - **[`server`]**: Public server API and message processing loop
105//!
106//! # Protocol Support
107//!
108//! The server implements LSP 3.17 specification features:
109//!
110//! ## Lifecycle Methods
111//!
112//! - `initialize` - Server initialization and capability negotiation
113//! - `initialized` - Post-initialization notification
114//! - `shutdown` - Graceful server shutdown
115//! - `exit` - Server process termination
116//!
117//! ## Document Synchronization
118//!
119//! - `textDocument/didOpen` - Document opened in editor
120//! - `textDocument/didChange` - Document content changed
121//! - `textDocument/didSave` - Document saved to disk
122//! - `textDocument/didClose` - Document closed in editor
123//!
124//! ## Language Features
125//!
126//! See [`features`] module documentation for complete feature list and capabilities.
127//!
128//! # Communication Modes
129//!
130//! ## Stdio Mode (Default)
131//!
132//! Standard input/output transport for editor integration:
133//!
134//! ```bash
135//! perllsp --stdio
136//! ```
137//!
138//! Editors configure this mode in their LSP client settings:
139//!
140//! ### VSCode Configuration
141//!
142//! ```json
143//! {
144//! "perl.lsp.command": "perllsp",
145//! "perl.lsp.args": ["--stdio"]
146//! }
147//! ```
148//!
149//! ### Neovim Configuration
150//!
151//! ```lua
152//! require'lspconfig'.perl.setup{
153//! cmd = { "perllsp", "--stdio" }
154//! }
155//! ```
156//!
157//! ## Socket Mode
158//!
159//! TCP socket transport for remote or debugging scenarios:
160//!
161//! ```bash
162//! perllsp --socket --port 9257
163//! ```
164//!
165//! Connect via TCP socket from any LSP client supporting network transport.
166//!
167//! # Features Module
168//!
169//! All LSP capabilities are implemented in the [`features`] module, organized by category:
170//!
171//! ```text
172//! features/
173//! ├── completion.rs # Code completion
174//! ├── diagnostics.rs # Real-time validation
175//! ├── formatting.rs # Code formatting
176//! ├── hover.rs # Hover information
177//! ├── references.rs # Find references
178//! ├── rename.rs # Symbol renaming
179//! ├── semantic_tokens.rs # Syntax highlighting
180//! ├── code_actions.rs # Quick fixes
181//! ├── code_lens_provider.rs # Code lens
182//! ├── inlay_hints.rs # Inline hints
183//! └── ...
184//! ```
185//!
186//! # State Management
187//!
188//! The server maintains state across requests:
189//!
190//! - **Document State**: Parsed ASTs, symbol tables, and version tracking
191//! - **Workspace Index**: Cross-file symbol resolution and references
192//! - **Configuration**: Client-provided settings and capabilities
193//! - **Diagnostics**: Cached validation results for incremental updates
194//!
195//! # Error Handling
196//!
197//! The runtime implements comprehensive error recovery:
198//!
199//! - **Parse Errors**: Graceful degradation to text-based features
200//! - **Protocol Errors**: JSON-RPC error responses with diagnostic codes
201//! - **Cancellation**: Request cancellation for long-running operations
202//! - **Fallback**: Text-based completion/hover when parsing fails
203//!
204//! # Performance Optimizations
205//!
206//! - **Incremental Parsing**: Reuse unchanged AST nodes on edits
207//! - **Lazy Indexing**: Index files on-demand rather than at startup
208//! - **Caching**: Cache parse results and symbol tables
209//! - **Cancellation**: Cancel stale requests when new ones arrive
210//! - **Streaming**: Stream large responses to avoid blocking
211//!
212//! # Integration with perl-parser
213//!
214//! The runtime delegates all parsing and analysis to `perl_parser`:
215//!
216//! ```text
217//! use perl_parser::Parser;
218//! use perl_lsp::convert::to_lsp_diagnostic;
219//!
220//! let code = "my $x = 1;";
221//! let mut parser = Parser::new(code);
222//! let ast = parser.parse()?;
223//!
224//! // Convert parser errors to LSP diagnostics
225//! let diagnostics: Vec<_> = parser.errors()
226//! .iter()
227//! .map(|e| to_lsp_diagnostic(e, &ast))
228//! .collect();
229//! ```
230//!
231//! # Testing
232//!
233//! The runtime includes comprehensive test coverage:
234//!
235//! ```bash
236//! # Run all LSP runtime tests
237//! cargo test -p perl-lsp-rs
238//!
239//! # Run with adaptive threading for resource-constrained environments
240//! RUST_TEST_THREADS=2 cargo test -p perl-lsp-rs
241//!
242//! # Test specific feature
243//! cargo test -p perl-lsp-rs completion
244//! ```
245//!
246//! # Logging and Diagnostics
247//!
248//! Enable logging for debugging:
249//!
250//! ```bash
251//! perllsp --stdio --log
252//! ```
253//!
254//! Logs are written to stderr, separate from LSP protocol communication on stdout/stdin.
255//!
256//! # Client Capabilities
257//!
258//! The server adapts to client capabilities negotiated during initialization:
259//!
260//! - **Dynamic Registration**: Supports dynamic capability registration
261//! - **Workspace Folders**: Multi-root workspace support
262//! - **Configuration**: Client-side configuration changes
263//! - **Pull Diagnostics**: Diagnostic pull model (LSP 3.17+)
264//!
265//! # Security Considerations
266//!
267//! - **Sandboxed Execution**: No arbitrary code execution
268//! - **Path Validation**: URI validation and path sanitization
269//! - **Resource Limits**: Memory and time budgets for operations
270//! - **Input Validation**: Strict protocol message validation
271//!
272//! # Migration from perl-parser
273//!
274//! This crate was extracted from `perl_parser` to separate LSP runtime concerns
275//! from parsing engine logic. The migration maintains API compatibility while
276//! improving modularity.
277//!
278//! Legacy code can continue using `perl_parser::lsp_server::LspServer` with
279//! deprecation warnings pointing to this crate.
280//!
281//! # Related Crates
282//!
283//! - `perl_parser`: Parsing engine and analysis infrastructure
284//! - `perl_lexer`: Context-aware Perl tokenizer
285//! - `perl_corpus`: Comprehensive test corpus
286//! - `perl_dap`: Debug Adapter Protocol implementation
287//!
288//! # Documentation
289//!
290//! - **LSP Guide**: See `docs/reference/LSP_IMPLEMENTATION_GUIDE.md` in the repository
291//! - **Capability Policy**: See `docs/reference/LSP_CAPABILITY_POLICY.md`
292//! - **Commands Reference**: See `docs/reference/COMMANDS_REFERENCE.md`
293//!
294//! # Usage Example
295//!
296//! Complete example of running the LSP server:
297//!
298//! ```no_run
299//! use perl_lsp::LspServer;
300//!
301//! let mut server = LspServer::new();
302//!
303//! match server.run() {
304//! Ok(()) => println!("Server exited cleanly"),
305//! Err(e) => eprintln!("Server error: {}", e),
306//! }
307//! ```
308
309#![deny(unsafe_code)]
310#![cfg_attr(test, allow(clippy::print_stderr, clippy::print_stdout))]
311#![warn(missing_docs)]
312#![allow(
313 // Migrated from perl-parser - these patterns are acceptable in LSP runtime code
314 clippy::collapsible_match,
315 clippy::only_used_in_recursion,
316 clippy::while_let_loop,
317 clippy::needless_range_loop,
318 clippy::for_kv_map,
319 clippy::arc_with_non_send_sync,
320 clippy::mutable_key_type,
321 clippy::new_without_default,
322 clippy::if_same_then_else
323)]
324
325// Module declarations - migrated from perl-parser
326pub mod cancellation;
327pub mod cli;
328pub mod convert;
329pub mod diagnostics_catalog;
330pub mod dispatch;
331pub(crate) mod documentation_targets;
332pub mod execute_command;
333pub mod fallback;
334pub mod features;
335pub mod handlers;
336pub mod protocol;
337pub mod ripr_facts_emitter;
338pub mod runtime;
339pub mod security;
340pub mod server;
341pub mod state;
342pub mod textdoc;
343pub mod transport;
344pub mod util;
345
346// Re-exports for key types
347pub use cli::run_cli;
348pub use protocol::{JsonRpcError, JsonRpcId, JsonRpcRequest, JsonRpcResponse};
349pub use server::LspServer;
350
351/// DAP bridge adapter re-export
352#[cfg(feature = "dap-phase1")]
353pub use perl_dap::BridgeAdapter;
354
355// Wave F re-exports: absorbed capability-map crate
356pub use perl_lsp_rs_core::capability_map;
357
358// =============================================================================
359// Internal compatibility re-exports (crate-internal, not API surface)
360// =============================================================================
361// These re-exports allow migrated code to use `crate::...` paths for engine
362// pieces while we incrementally update paths to `perl_parser::...`
363
364/// Parser re-export for migrated code
365pub(crate) use perl_parser::Parser;
366
367/// Position utilities re-export
368pub(crate) mod position {
369 pub use perl_parser::position::*;
370}
371
372/// Declaration types re-export
373pub(crate) mod declaration {
374 pub use perl_parser::declaration::*;
375}
376
377/// Workspace index re-export
378pub(crate) mod workspace_index {
379 pub use perl_parser::workspace_index::*;
380}
381
382/// Symbol types re-export
383pub(crate) mod symbol {
384 pub use perl_parser::symbol::*;
385}
386
387/// AST types re-export
388pub(crate) mod ast {
389 pub use perl_parser::ast::*;
390}
391
392/// Feature re-exports for old intra-crate paths
393pub(crate) mod code_actions_enhanced {
394 #[allow(unused_imports)]
395 pub use crate::features::code_actions_enhanced::*;
396}
397
398pub(crate) mod code_lens_provider {
399 pub use crate::features::code_lens_provider::*;
400}
401
402pub(crate) mod diagnostics {
403 #[allow(unused_imports)]
404 pub use crate::features::diagnostics::*;
405}
406
407// More feature re-exports for runtime imports
408pub(crate) mod inlay_hints {
409 pub use crate::features::inlay_hints::*;
410}
411
412pub(crate) mod document_links {
413 pub use crate::features::document_links::*;
414}
415
416pub(crate) mod lsp_document_link {
417 pub use crate::features::lsp_document_link::*;
418}
419
420pub(crate) mod linked_editing {
421 pub use crate::features::linked_editing::*;
422}
423
424pub(crate) mod code_actions_pragmas {
425 pub use crate::features::code_actions_pragmas::*;
426}
427
428// Engine re-exports for runtime
429pub(crate) mod perl_critic {
430 pub use perl_lsp_rs_core::tooling::perl_critic::*;
431}
432
433pub(crate) mod semantic {
434 pub use perl_parser::semantic::*;
435}
436
437pub(crate) mod error {
438 pub use perl_parser::error::*;
439}
440
441pub(crate) mod completion {
442 pub use crate::features::completion::*;
443}
444
445pub(crate) mod on_type_formatting {
446 pub use crate::features::on_type_formatting::*;
447}
448
449pub(crate) mod inline_completions {
450 pub use crate::features::inline_completions::*;
451}
452
453pub(crate) mod type_hierarchy {
454 pub use crate::features::type_hierarchy::*;
455}
456
457// Re-export SourceLocation at crate root for convenience
458pub(crate) use perl_parser::ast::SourceLocation;
459
460// Engine modules needed by runtime
461pub(crate) mod type_inference {
462 pub use perl_parser::type_inference::*;
463}
464
465pub(crate) mod builtin_signatures {
466 pub use perl_parser::builtin_signatures::*;
467}
468
469pub(crate) mod semantic_tokens {
470 pub use crate::features::semantic_tokens::*;
471}
472
473pub(crate) mod call_hierarchy_provider;
474
475// Parser module re-export for tests using crate::parser::Parser
476pub(crate) mod parser {
477 #[allow(unused_imports)]
478 pub use perl_parser::parser::*;
479}
480
481// Folding re-export
482pub(crate) mod folding {
483 pub use crate::features::folding::*;
484}
485
486// References re-export
487pub(crate) mod references {
488 #[allow(unused_imports)]
489 pub use crate::features::references::*;
490}
491
492// Rename re-export
493pub(crate) mod rename {
494 #[allow(unused_imports)]
495 pub use crate::features::rename::*;
496}
497
498// Signature help re-export
499pub(crate) mod signature_help {
500 #[allow(unused_imports)]
501 pub use crate::features::signature_help::*;
502}
503
504/// Run the LSP server in stdio mode.
505///
506/// This is the main entry point for the LSP server. It reads JSON-RPC messages from stdin
507/// and writes responses to stdout, following the Language Server Protocol specification.
508///
509/// # Errors
510///
511/// Returns an error if:
512/// - The transport layer fails to initialize
513/// - Message framing or parsing fails
514/// - The server encounters an unrecoverable error
515///
516/// # Example
517///
518/// ```no_run
519/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
520/// perl_lsp::run_stdio()?;
521/// # Ok(())
522/// # }
523/// ```
524pub fn run_stdio() -> Result<(), Box<dyn std::error::Error>> {
525 let server = LspServer::new();
526 server.run().map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
527}