Skip to content

Commit 598a733

Browse files
authored
server: incremental sync (#583)
now we're storing the document state in memory on the server and doing incremental sync. also cleanup the extension a bit so focus is kept when opening the syntax tree or the tokens
1 parent 1078214 commit 598a733

File tree

3 files changed

+368
-82
lines changed

3 files changed

+368
-82
lines changed

crates/squawk_server/src/lib.rs

Lines changed: 112 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,23 @@ use lsp_types::{
1515
};
1616
use squawk_linter::Linter;
1717
use squawk_syntax::{Parse, SourceFile};
18+
use std::collections::HashMap;
19+
mod lsp_utils;
20+
21+
struct DocumentState {
22+
content: String,
23+
version: i32,
24+
}
1825

1926
pub fn run() -> Result<()> {
2027
info!("Starting Squawk LSP server");
2128

2229
let (connection, io_threads) = Connection::stdio();
2330

2431
let server_capabilities = serde_json::to_value(&ServerCapabilities {
25-
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
32+
text_document_sync: Some(TextDocumentSyncCapability::Kind(
33+
TextDocumentSyncKind::INCREMENTAL,
34+
)),
2635
// definition_provider: Some(OneOf::Left(true)),
2736
..Default::default()
2837
})
@@ -48,6 +57,8 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
4857
let client_name = init_params.client_info.map(|x| x.name);
4958
info!("Client name: {client_name:?}");
5059

60+
let mut documents: HashMap<Url, DocumentState> = HashMap::new();
61+
5162
for msg in &connection.receiver {
5263
match msg {
5364
Message::Request(req) => {
@@ -63,10 +74,10 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
6374
handle_goto_definition(&connection, req)?;
6475
}
6576
"squawk/syntaxTree" => {
66-
handle_syntax_tree(&connection, req)?;
77+
handle_syntax_tree(&connection, req, &documents)?;
6778
}
6879
"squawk/tokens" => {
69-
handle_tokens(&connection, req)?;
80+
handle_tokens(&connection, req, &documents)?;
7081
}
7182
_ => {
7283
info!("Ignoring unhandled request: {}", req.method);
@@ -78,13 +89,19 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
7889
}
7990
Message::Notification(notif) => {
8091
info!("Received notification: method={}", notif.method);
81-
82-
if notif.method == DidOpenTextDocument::METHOD {
83-
handle_did_open(&connection, notif)?;
84-
} else if notif.method == DidChangeTextDocument::METHOD {
85-
handle_did_change(&connection, notif)?;
86-
} else if notif.method == DidCloseTextDocument::METHOD {
87-
handle_did_close(&connection, notif)?;
92+
match notif.method.as_ref() {
93+
DidOpenTextDocument::METHOD => {
94+
handle_did_open(&connection, notif, &mut documents)?;
95+
}
96+
DidChangeTextDocument::METHOD => {
97+
handle_did_change(&connection, notif, &mut documents)?;
98+
}
99+
DidCloseTextDocument::METHOD => {
100+
handle_did_close(&connection, notif, &mut documents)?;
101+
}
102+
_ => {
103+
info!("Ignoring unhandled notification: {}", notif.method);
104+
}
88105
}
89106
}
90107
}
@@ -97,10 +114,7 @@ fn handle_goto_definition(connection: &Connection, req: lsp_server::Request) ->
97114

98115
let location = Location {
99116
uri: params.text_document_position_params.text_document.uri,
100-
range: Range {
101-
start: Position::new(1, 2),
102-
end: Position::new(1, 3),
103-
},
117+
range: Range::new(Position::new(1, 2), Position::new(1, 3)),
104118
};
105119

106120
let result = GotoDefinitionResponse::Scalar(location);
@@ -114,33 +128,83 @@ fn handle_goto_definition(connection: &Connection, req: lsp_server::Request) ->
114128
Ok(())
115129
}
116130

117-
fn handle_did_open(connection: &Connection, notif: lsp_server::Notification) -> Result<()> {
131+
fn publish_diagnostics(
132+
connection: &Connection,
133+
uri: Url,
134+
version: i32,
135+
diagnostics: Vec<Diagnostic>,
136+
) -> Result<()> {
137+
let publish_params = PublishDiagnosticsParams {
138+
uri,
139+
diagnostics,
140+
version: Some(version),
141+
};
142+
143+
let notification = Notification {
144+
method: PublishDiagnostics::METHOD.to_owned(),
145+
params: serde_json::to_value(publish_params)?,
146+
};
147+
148+
connection
149+
.sender
150+
.send(Message::Notification(notification))?;
151+
Ok(())
152+
}
153+
154+
fn handle_did_open(
155+
connection: &Connection,
156+
notif: lsp_server::Notification,
157+
documents: &mut HashMap<Url, DocumentState>,
158+
) -> Result<()> {
118159
let params: DidOpenTextDocumentParams = serde_json::from_value(notif.params)?;
119160
let uri = params.text_document.uri;
120161
let content = params.text_document.text;
121162
let version = params.text_document.version;
122163

123-
lint(connection, uri, &content, version)?;
164+
documents.insert(uri.clone(), DocumentState { content, version });
165+
166+
let content = documents.get(&uri).map_or("", |doc| &doc.content);
167+
168+
// TODO: we need a better setup for "run func when input changed"
169+
let diagnostics = lint(content);
170+
publish_diagnostics(connection, uri, version, diagnostics)?;
124171

125172
Ok(())
126173
}
127174

128-
fn handle_did_change(connection: &Connection, notif: lsp_server::Notification) -> Result<()> {
175+
fn handle_did_change(
176+
connection: &Connection,
177+
notif: lsp_server::Notification,
178+
documents: &mut HashMap<Url, DocumentState>,
179+
) -> Result<()> {
129180
let params: DidChangeTextDocumentParams = serde_json::from_value(notif.params)?;
130181
let uri = params.text_document.uri;
131182
let version = params.text_document.version;
132183

133-
if let Some(change) = params.content_changes.last() {
134-
lint(connection, uri, &change.text, version)?;
135-
}
184+
let Some(doc_state) = documents.get_mut(&uri) else {
185+
return Ok(());
186+
};
187+
188+
doc_state.content =
189+
lsp_utils::apply_incremental_changes(&doc_state.content, params.content_changes);
190+
doc_state.version = version;
191+
192+
let diagnostics = lint(&doc_state.content);
193+
publish_diagnostics(connection, uri, version, diagnostics)?;
136194

137195
Ok(())
138196
}
139197

140-
fn handle_did_close(connection: &Connection, notif: lsp_server::Notification) -> Result<()> {
198+
fn handle_did_close(
199+
connection: &Connection,
200+
notif: lsp_server::Notification,
201+
documents: &mut HashMap<Url, DocumentState>,
202+
) -> Result<()> {
141203
let params: DidCloseTextDocumentParams = serde_json::from_value(notif.params)?;
142204
let uri = params.text_document.uri;
143205

206+
documents.remove(&uri);
207+
144208
let publish_params = PublishDiagnosticsParams {
145209
uri,
146210
diagnostics: vec![],
@@ -159,14 +223,14 @@ fn handle_did_close(connection: &Connection, notif: lsp_server::Notification) ->
159223
Ok(())
160224
}
161225

162-
fn lint(connection: &Connection, uri: lsp_types::Url, content: &str, version: i32) -> Result<()> {
226+
fn lint(content: &str) -> Vec<Diagnostic> {
163227
let parse: Parse<SourceFile> = SourceFile::parse(content);
164228
let parse_errors = parse.errors();
165229
let mut linter = Linter::with_all_rules();
166230
let violations = linter.lint(parse, content);
167231
let line_index = LineIndex::new(content);
168232

169-
let mut diagnostics = vec![];
233+
let mut diagnostics = Vec::with_capacity(violations.len() + parse_errors.len());
170234

171235
for error in parse_errors {
172236
let range_start = error.range().start();
@@ -179,10 +243,10 @@ fn lint(connection: &Connection, uri: lsp_types::Url, content: &str, version: i3
179243
}
180244

181245
let diagnostic = Diagnostic {
182-
range: Range {
183-
start: Position::new(start_line_col.line, start_line_col.col),
184-
end: Position::new(end_line_col.line, end_line_col.col),
185-
},
246+
range: Range::new(
247+
Position::new(start_line_col.line, start_line_col.col),
248+
Position::new(end_line_col.line, end_line_col.col),
249+
),
186250
severity: Some(DiagnosticSeverity::ERROR),
187251
code: Some(lsp_types::NumberOrString::String(
188252
"syntax-error".to_string(),
@@ -208,10 +272,10 @@ fn lint(connection: &Connection, uri: lsp_types::Url, content: &str, version: i3
208272
}
209273

210274
let diagnostic = Diagnostic {
211-
range: Range {
212-
start: Position::new(start_line_col.line, start_line_col.col),
213-
end: Position::new(end_line_col.line, end_line_col.col),
214-
},
275+
range: Range::new(
276+
Position::new(start_line_col.line, start_line_col.col),
277+
Position::new(end_line_col.line, end_line_col.col),
278+
),
215279
severity: Some(DiagnosticSeverity::WARNING),
216280
code: Some(lsp_types::NumberOrString::String(
217281
violation.code.to_string(),
@@ -225,42 +289,28 @@ fn lint(connection: &Connection, uri: lsp_types::Url, content: &str, version: i3
225289
};
226290
diagnostics.push(diagnostic);
227291
}
228-
229-
let publish_params = PublishDiagnosticsParams {
230-
uri,
231-
diagnostics,
232-
version: Some(version),
233-
};
234-
235-
let notification = Notification {
236-
method: PublishDiagnostics::METHOD.to_owned(),
237-
params: serde_json::to_value(publish_params)?,
238-
};
239-
240-
connection
241-
.sender
242-
.send(Message::Notification(notification))?;
243-
244-
Ok(())
292+
diagnostics
245293
}
246294

247295
#[derive(serde::Deserialize)]
248296
struct SyntaxTreeParams {
249297
#[serde(rename = "textDocument")]
250298
text_document: lsp_types::TextDocumentIdentifier,
251-
// TODO: once we start storing the text doc on the server we won't need to
252-
// send the content across the wire
253-
text: String,
254299
}
255300

256-
fn handle_syntax_tree(connection: &Connection, req: lsp_server::Request) -> Result<()> {
301+
fn handle_syntax_tree(
302+
connection: &Connection,
303+
req: lsp_server::Request,
304+
documents: &HashMap<Url, DocumentState>,
305+
) -> Result<()> {
257306
let params: SyntaxTreeParams = serde_json::from_value(req.params)?;
258307
let uri = params.text_document.uri;
259-
let content = params.text;
260308

261309
info!("Generating syntax tree for: {}", uri);
262310

263-
let parse: Parse<SourceFile> = SourceFile::parse(&content);
311+
let content = documents.get(&uri).map_or("", |doc| &doc.content);
312+
313+
let parse: Parse<SourceFile> = SourceFile::parse(content);
264314
let syntax_tree = format!("{:#?}", parse.syntax_node());
265315

266316
let resp = Response {
@@ -277,19 +327,21 @@ fn handle_syntax_tree(connection: &Connection, req: lsp_server::Request) -> Resu
277327
struct TokensParams {
278328
#[serde(rename = "textDocument")]
279329
text_document: lsp_types::TextDocumentIdentifier,
280-
// TODO: once we start storing the text doc on the server we won't need to
281-
// send the content across the wire
282-
text: String,
283330
}
284331

285-
fn handle_tokens(connection: &Connection, req: lsp_server::Request) -> Result<()> {
332+
fn handle_tokens(
333+
connection: &Connection,
334+
req: lsp_server::Request,
335+
documents: &HashMap<Url, DocumentState>,
336+
) -> Result<()> {
286337
let params: TokensParams = serde_json::from_value(req.params)?;
287338
let uri = params.text_document.uri;
288-
let content = params.text;
289339

290340
info!("Generating tokens for: {}", uri);
291341

292-
let tokens = squawk_lexer::tokenize(&content);
342+
let content = documents.get(&uri).map_or("", |doc| &doc.content);
343+
344+
let tokens = squawk_lexer::tokenize(content);
293345

294346
let mut output = Vec::new();
295347
let mut char_pos = 0;

0 commit comments

Comments
 (0)