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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use lsp_server::{Message, Response};
use lsp_types::InitializeParams;
use std::error::Error;
use tokio::sync::oneshot;
use crate::context;
use super::connection::AsyncConnection;
use super::message_processor::ServerMessageProcessor;
/// LSP Server manages the entire server lifecycle
pub(super) struct LspServer {
pub(super) connection: AsyncConnection,
pub(super) server_context: context::ServerContext,
pub(super) processor: ServerMessageProcessor,
}
impl LspServer {
/// Create a new LSP server instance
pub(super) fn new(
connection: AsyncConnection,
params: &InitializeParams,
init_rx: oneshot::Receiver<()>,
) -> Self {
let server_context = context::ServerContext::new(
lsp_server::Connection {
sender: connection.connection.sender.clone(),
receiver: connection.connection.receiver.clone(),
},
params.capabilities.clone(),
);
Self {
connection,
server_context,
processor: ServerMessageProcessor::new(init_rx),
}
}
/// Run the main server loop
pub(super) async fn run(mut self) -> Result<(), Box<dyn Error + Sync + Send>> {
// First, wait for initialization to complete while handling allowed messages
self.wait_for_initialization().await?;
// Process all pending messages after initialization
if self
.processor
.process_pending_messages(&mut self.connection, &mut self.server_context)
.await?
{
self.server_context.close().await;
return Ok(()); // Shutdown requested during pending message processing
}
// Now focus on normal message processing
while let Some(msg) = self.connection.recv().await {
if self
.processor
.process_message(msg, &mut self.connection, &mut self.server_context)
.await?
{
break; // Shutdown requested
}
}
self.server_context.close().await;
Ok(())
}
/// Wait for initialization to complete while handling initialization-allowed messages
async fn wait_for_initialization(&mut self) -> Result<(), Box<dyn Error + Sync + Send>> {
loop {
// Check if initialization is complete
if self.processor.check_initialization_complete()? {
break; // Initialization completed
}
// Use a short timeout to check for messages during initialization
match tokio::time::timeout(
tokio::time::Duration::from_millis(50),
self.connection.recv(),
)
.await
{
Ok(Some(msg)) => {
// Process message if allowed during initialization, otherwise queue it
if self.processor.can_process_during_init(&msg) {
self.processor
.handle_message(msg, &mut self.connection, &mut self.server_context)
.await?;
} else {
match msg {
Message::Request(request) => {
if should_fail_fast_request_during_init(&request.method) {
// During startup, fail fast for editor data requests instead
// of queueing them behind full workspace initialization.
// Clients will re-request after initialization and avoid
// perceived 10-20s startup request stalls.
let response = Response::new_err(
request.id,
lsp_server::ErrorCode::ContentModified as i32,
"server initializing".to_owned(),
);
self.connection.send(response.into())?;
} else {
// Preserve one-shot/critical request semantics by
// deferring them until initialization completes.
self.processor
.pending_messages
.push(Message::Request(request));
}
}
other => {
self.processor.pending_messages.push(other);
}
}
}
}
Ok(None) => {
// Connection closed during initialization
return Ok(());
}
Err(_) => {
// Timeout - continue checking for initialization completion
continue;
}
}
}
Ok(())
}
}
fn should_fail_fast_request_during_init(method: &str) -> bool {
matches!(
method,
"textDocument/hover"
| "textDocument/completion"
| "textDocument/documentSymbol"
| "textDocument/foldingRange"
| "textDocument/documentColor"
| "textDocument/documentLink"
| "textDocument/codeLens"
| "textDocument/inlayHint"
| "textDocument/semanticTokens/full"
| "textDocument/diagnostic"
| "workspace/diagnostic"
| "workspace/symbol"
| "gluals/annotator"
| "gluals/gmodScriptedClasses"
| "gluals/gmodScriptedClassesV2"
| "gluals/docSearch"
| "gluals/hoverExpand"
| "emmy/annotator"
)
}