1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! LSP (Language Server Protocol) Client Architecture
//!
//! This module implements a full-featured LSP client for the Fresh editor.
//! It supports multiple concurrent language servers, async I/O, and robust
//! error handling with automatic server restarts.
//!
//! # Architecture Overview
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────────────┐
//! │ Editor (App) │
//! │ │
//! │ ┌─────────────────────────────────────────────────────────────────┐ │
//! │ │ BufferMetadata │ │
//! │ │ - lsp_opened_with: HashSet<u64> (tracks didOpen per server) │ │
//! │ └─────────────────────────────────────────────────────────────────┘ │
//! │ │ │
//! │ ▼ │
//! │ ┌─────────────────────────────────────────────────────────────────┐ │
//! │ │ with_lsp_for_buffer() helper │ │
//! │ │ - Ensures didOpen is sent before any request │ │
//! │ │ - Lazy text fetching (only if didOpen needed) │ │
//! │ │ - Per-server-instance tracking via handle IDs │ │
//! │ └─────────────────────────────────────────────────────────────────┘ │
//! │ │ │
//! └──────────────────────────────┼──────────────────────────────────────────┘
//! │
//! ▼
//! ┌─────────────────────────────────────────────────────────────────────────┐
//! │ LspManager │
//! │ │
//! │ - One manager per editor instance │
//! │ - Manages multiple language servers (one per language) │
//! │ - Handles server lifecycle (spawn, restart, shutdown) │
//! │ - Restart throttling with exponential backoff │
//! │ - Manual start/stop support via command palette │
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ LspHandle │ │ LspHandle │ │ LspHandle │ ... │
//! │ │ (rust) │ │ (typescript) │ │ (python) │ │
//! │ │ id: 1 │ │ id: 2 │ │ id: 3 │ │
//! │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
//! └─────────┼─────────────────┼─────────────────┼───────────────────────────┘
//! │ │ │
//! │ tokio channels │ │
//! ▼ ▼ ▼
//! ┌─────────────────────────────────────────────────────────────────────────┐
//! │ Tokio Runtime (async tasks) │
//! │ │
//! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
//! │ │ LspTask │ │ LspTask │ │ LspTask │ │
//! │ │ (rust- │ │ (typescript- │ │ (python- │ │
//! │ │ analyzer) │ │ language- │ │ lsp) │ │
//! │ │ │ │ server) │ │ │ │
//! │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
//! └─────────┼─────────────────┼─────────────────┼───────────────────────────┘
//! │ │ │
//! │ stdin/stdout │ │
//! ▼ ▼ ▼
//! ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
//! │ rust-analyzer │ │ typescript- │ │ pylsp │
//! │ (subprocess) │ │ language-server │ │ (subprocess) │
//! └─────────────────┘ └─────────────────┘ └─────────────────┘
//! ```
//!
//! # Module Structure
//!
//! - **`manager`**: [`LspManager`] - Manages multiple language servers, handles
//! spawning/restarting, routes requests by language. Includes restart throttling
//! with exponential backoff to prevent server crash loops.
//!
//! - **`async_handler`**: [`LspHandle`] and `LspTask` - The async LSP client
//! implementation. `LspHandle` is a sync handle that sends commands via tokio
//! channels. `LspTask` runs in a separate tokio task, managing the server
//! subprocess and JSON-RPC I/O. Each handle has a unique `id` for tracking.
//!
//! - **`diagnostics`**: Converts LSP diagnostics to editor overlays (colored
//! underlines for errors, warnings, etc.).
//!
//! # Message Flow
//!
//! ## Outgoing Requests (Editor → Server)
//!
//! 1. Editor calls a request method (e.g., `request_hover()`)
//! 2. `with_lsp_for_buffer()` helper ensures `didOpen` was sent to this server instance
//! 3. If needed, fetches buffer text and sends `didOpen` first
//! 4. Request is sent via `LspHandle` through tokio channel
//! 5. `LspTask` serializes to JSON-RPC and writes to server stdin
//! 6. Response is parsed and sent back through `AsyncBridge`
//!
//! ## Incoming Notifications (Server → Editor)
//!
//! 1. `LspTask` reads from server stdout
//! 2. Parses JSON-RPC message
//! 3. For notifications (diagnostics, progress, etc.), sends via `AsyncBridge`
//! 4. Editor's main loop receives and processes the notification
//!
//! # Document Synchronization
//!
//! The LSP protocol requires `textDocument/didOpen` before any other document
//! operations. We track this per-buffer, per-server-instance:
//!
//! - Each `LspHandle` has a unique `id` (monotonically increasing)
//! - `BufferMetadata.lsp_opened_with` is a `HashSet<u64>` of handle IDs
//! - Before any request, we check if the current handle's ID is in the set
//! - If not, we send `didOpen` first, then add the ID to the set
//! - This handles: multiple servers per buffer, server restarts (new ID)
//!
//! # Error Handling
//!
//! - **Server crashes**: Automatic restart with exponential backoff
//! - **Too many restarts**: Server enters cooldown, user notified
//! - **Request timeout**: Logged, doesn't block editor
//! - **Capability checks**: Some features check server capabilities before sending
//! (e.g., pull diagnostics only if `diagnosticProvider` is advertised)
// Re-export for public API (used by tests)
pub use crateLspServerConfig;