@@ -15,14 +15,23 @@ use lsp_types::{
1515} ;
1616use squawk_linter:: Linter ;
1717use 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
1926pub 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 ) ]
248296struct 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
277327struct 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