@@ -5,28 +5,42 @@ use std::{
5
5
task:: { Context , Poll } ,
6
6
} ;
7
7
8
+ use acvm:: Language ;
8
9
use async_lsp:: {
9
- router:: Router , AnyEvent , AnyNotification , AnyRequest , Error , LspService , ResponseError ,
10
+ router:: Router , AnyEvent , AnyNotification , AnyRequest , ClientSocket , Error , LanguageClient ,
11
+ LspService , ResponseError ,
10
12
} ;
11
13
use lsp_types:: {
12
- notification, request, DidChangeConfigurationParams , DidChangeTextDocumentParams ,
13
- DidCloseTextDocumentParams , DidOpenTextDocumentParams , InitializeParams , InitializeResult ,
14
- InitializedParams , ServerCapabilities ,
14
+ notification, request, Diagnostic , DiagnosticSeverity , DidChangeConfigurationParams ,
15
+ DidChangeTextDocumentParams , DidCloseTextDocumentParams , DidOpenTextDocumentParams ,
16
+ DidSaveTextDocumentParams , InitializeParams , InitializeResult , InitializedParams ,
17
+ PublishDiagnosticsParams , Range , ServerCapabilities , TextDocumentSyncOptions ,
15
18
} ;
19
+ use noirc_driver:: Driver ;
20
+ use noirc_errors:: { DiagnosticKind , FileDiagnostic } ;
21
+ use noirc_frontend:: graph:: CrateType ;
16
22
use serde_json:: Value as JsonValue ;
17
23
use tower:: Service ;
18
24
19
25
// State for the LSP gets implemented on this struct and is internal to the implementation
20
- #[ derive( Debug , Default ) ]
21
- struct LspState ;
26
+ #[ derive( Debug ) ]
27
+ struct LspState {
28
+ client : ClientSocket ,
29
+ }
30
+
31
+ impl LspState {
32
+ fn new ( client : & ClientSocket ) -> Self {
33
+ Self { client : client. clone ( ) }
34
+ }
35
+ }
22
36
23
37
pub struct NargoLspService {
24
38
router : Router < LspState > ,
25
39
}
26
40
27
41
impl NargoLspService {
28
- pub fn new ( ) -> Self {
29
- let state = LspState :: default ( ) ;
42
+ pub fn new ( client : & ClientSocket ) -> Self {
43
+ let state = LspState :: new ( client ) ;
30
44
let mut router = Router :: new ( state) ;
31
45
router
32
46
. request :: < request:: Initialize , _ > ( on_initialize)
@@ -36,17 +50,12 @@ impl NargoLspService {
36
50
. notification :: < notification:: DidOpenTextDocument > ( on_did_open_text_document)
37
51
. notification :: < notification:: DidChangeTextDocument > ( on_did_change_text_document)
38
52
. notification :: < notification:: DidCloseTextDocument > ( on_did_close_text_document)
53
+ . notification :: < notification:: DidSaveTextDocument > ( on_did_save_text_document)
39
54
. notification :: < notification:: Exit > ( on_exit) ;
40
55
Self { router }
41
56
}
42
57
}
43
58
44
- impl Default for NargoLspService {
45
- fn default ( ) -> Self {
46
- Self :: new ( )
47
- }
48
- }
49
-
50
59
// This trait implemented as a passthrough to the router, which makes
51
60
// our `NargoLspService` a normal Service as far as Tower is concerned.
52
61
impl Service < AnyRequest > for NargoLspService {
@@ -90,8 +99,14 @@ fn on_initialize(
90
99
_params : InitializeParams ,
91
100
) -> impl Future < Output = Result < InitializeResult , ResponseError > > {
92
101
async {
102
+ let text_document_sync = TextDocumentSyncOptions {
103
+ save : Some ( true . into ( ) ) ,
104
+ ..TextDocumentSyncOptions :: default ( )
105
+ } ;
106
+
93
107
Ok ( InitializeResult {
94
108
capabilities : ServerCapabilities {
109
+ text_document_sync : Some ( text_document_sync. into ( ) ) ,
95
110
// Add capabilities before this spread when adding support for one
96
111
..ServerCapabilities :: default ( )
97
112
} ,
@@ -142,22 +157,99 @@ fn on_did_close_text_document(
142
157
ControlFlow :: Continue ( ( ) )
143
158
}
144
159
160
+ fn on_did_save_text_document (
161
+ state : & mut LspState ,
162
+ params : DidSaveTextDocumentParams ,
163
+ ) -> ControlFlow < Result < ( ) , async_lsp:: Error > > {
164
+ // TODO: Requiring `Language` and `is_opcode_supported` to construct a driver makes for some real stinky code
165
+ // The driver should not require knowledge of the backend; instead should be implemented as an independent pass (in nargo?)
166
+ let mut driver = Driver :: new ( & Language :: R1CS , Box :: new ( |_op| false ) ) ;
167
+
168
+ let file_path = & params. text_document . uri . to_file_path ( ) . unwrap ( ) ;
169
+
170
+ driver. create_local_crate ( file_path, CrateType :: Binary ) ;
171
+
172
+ let mut diagnostics = Vec :: new ( ) ;
173
+
174
+ let file_diagnostics = match driver. check_crate ( false ) {
175
+ Ok ( warnings) => warnings,
176
+ Err ( errors_and_warnings) => errors_and_warnings,
177
+ } ;
178
+
179
+ if !file_diagnostics. is_empty ( ) {
180
+ let fm = driver. file_manager ( ) ;
181
+ let files = fm. as_simple_files ( ) ;
182
+
183
+ for FileDiagnostic { file_id, diagnostic } in file_diagnostics {
184
+ // TODO: This file_id never be 0 because the "path" where it maps is the directory, not a file
185
+ if file_id. as_usize ( ) != 0 {
186
+ continue ;
187
+ }
188
+
189
+ let mut range = Range :: default ( ) ;
190
+
191
+ // TODO: Should this be the first item in secondaries? Should we bail when we find a range?
192
+ for sec in diagnostic. secondaries {
193
+ // TODO: Codespan ranges are often (always?) off by some amount of characters
194
+ if let Ok ( codespan_range) =
195
+ codespan_lsp:: byte_span_to_range ( files, file_id. as_usize ( ) , sec. span . into ( ) )
196
+ {
197
+ // We have to manually attach each because the codespan_lsp restricts lsp-types to the wrong version range
198
+ range. start . line = codespan_range. start . line ;
199
+ range. start . character = codespan_range. start . character ;
200
+ range. end . line = codespan_range. end . line ;
201
+ range. end . character = codespan_range. end . character ;
202
+ }
203
+ }
204
+ let severity = match diagnostic. kind {
205
+ DiagnosticKind :: Error => Some ( DiagnosticSeverity :: ERROR ) ,
206
+ DiagnosticKind :: Warning => Some ( DiagnosticSeverity :: WARNING ) ,
207
+ } ;
208
+ diagnostics. push ( Diagnostic {
209
+ range,
210
+ severity,
211
+ message : diagnostic. message ,
212
+ ..Diagnostic :: default ( )
213
+ } )
214
+ }
215
+ }
216
+
217
+ let _ = state. client . publish_diagnostics ( PublishDiagnosticsParams {
218
+ uri : params. text_document . uri ,
219
+ version : None ,
220
+ diagnostics,
221
+ } ) ;
222
+
223
+ ControlFlow :: Continue ( ( ) )
224
+ }
225
+
145
226
fn on_exit ( _state : & mut LspState , _params : ( ) ) -> ControlFlow < Result < ( ) , async_lsp:: Error > > {
146
227
ControlFlow :: Continue ( ( ) )
147
228
}
148
229
149
230
#[ cfg( test) ]
150
231
mod lsp_tests {
232
+ use lsp_types:: TextDocumentSyncCapability ;
151
233
use tokio:: test;
152
234
153
235
use super :: * ;
154
236
155
237
#[ test]
156
238
async fn test_on_initialize ( ) {
157
- let mut state = LspState :: default ( ) ;
239
+ // Not available in published release yet
240
+ let client = ClientSocket :: new_closed ( ) ;
241
+ let mut state = LspState :: new ( & client) ;
158
242
let params = InitializeParams :: default ( ) ;
159
243
let response = on_initialize ( & mut state, params) . await . unwrap ( ) ;
160
- assert_eq ! ( response. capabilities, ServerCapabilities :: default ( ) ) ;
244
+ assert ! ( matches!(
245
+ response. capabilities,
246
+ ServerCapabilities {
247
+ text_document_sync: Some ( TextDocumentSyncCapability :: Options (
248
+ TextDocumentSyncOptions { save: Some ( _) , .. }
249
+ ) ) ,
250
+ ..
251
+ }
252
+ ) ) ;
161
253
assert ! ( response. server_info. is_none( ) ) ;
162
254
}
163
255
}
0 commit comments