@@ -2,9 +2,10 @@ use std::{ffi::OsStr, path::PathBuf, time::Duration};
2
2
3
3
use lsp_textdocument:: FullTextDocument ;
4
4
use serde:: Deserialize ;
5
- use tokio:: fs ;
5
+ use tokio:: { fs , time :: timeout } ;
6
6
use tower_lsp:: lsp_types:: {
7
- CompletionItem , CompletionItemKind , CompletionResponse , DiagnosticSeverity , Range , Url ,
7
+ CompletionItem , CompletionItemKind , CompletionResponse , DiagnosticSeverity , InlayHint ,
8
+ InlayHintKind , Range , Url ,
8
9
} ;
9
10
use tower_lsp:: { jsonrpc:: Result , lsp_types:: Diagnostic } ;
10
11
@@ -17,7 +18,7 @@ pub(crate) enum IdeCheck {
17
18
Hint ( IdeCheckHint ) ,
18
19
}
19
20
20
- #[ derive( Debug , Deserialize , PartialEq ) ]
21
+ #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
21
22
pub ( crate ) struct IdeCheckDiagnostic {
22
23
pub message : String ,
23
24
pub severity : IdeDiagnosticSeverity ,
@@ -38,11 +39,63 @@ impl IdeCheckDiagnostic {
38
39
}
39
40
}
40
41
41
- #[ derive( Debug , Deserialize , PartialEq ) ]
42
+ #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
42
43
pub ( crate ) struct IdeCheckHint {
43
44
pub position : IdeSpan ,
44
45
pub typename : String ,
45
46
}
47
+ impl IdeCheckHint {
48
+ pub fn to_inlay_hint ( & self , doc : & FullTextDocument ) -> InlayHint {
49
+ InlayHint {
50
+ position : doc. position_at ( self . position . end ) ,
51
+ label : tower_lsp:: lsp_types:: InlayHintLabel :: String ( format ! ( ": {}" , & self . typename) ) ,
52
+ kind : Some ( InlayHintKind :: TYPE ) ,
53
+ text_edits : None ,
54
+ tooltip : None ,
55
+ padding_left : None ,
56
+ padding_right : None ,
57
+ data : None ,
58
+ }
59
+ }
60
+ }
61
+
62
+ #[ derive( Debug , PartialEq ) ]
63
+ pub ( crate ) struct IdeCheckResponse {
64
+ pub diagnostics : Vec < IdeCheckDiagnostic > ,
65
+ pub inlay_hints : Vec < IdeCheckHint > ,
66
+ }
67
+ impl IdeCheckResponse {
68
+ pub fn from_compiler_response ( value : & CompilerResponse ) -> Self {
69
+ let ide_checks: Vec < IdeCheck > = value
70
+ . stdout
71
+ . lines ( )
72
+ . filter_map ( |l| serde_json:: from_slice ( l. as_bytes ( ) ) . ok ( ) )
73
+ . collect ( ) ;
74
+
75
+ let diagnostics = ide_checks
76
+ . iter ( )
77
+ . filter_map ( |c| match c {
78
+ IdeCheck :: Diagnostic ( d) => Some ( d) ,
79
+ IdeCheck :: Hint ( _) => None ,
80
+ } )
81
+ . cloned ( )
82
+ . collect :: < Vec < _ > > ( ) ;
83
+
84
+ let inlay_hints = ide_checks
85
+ . iter ( )
86
+ . filter_map ( |c| match c {
87
+ IdeCheck :: Diagnostic ( _) => None ,
88
+ IdeCheck :: Hint ( h) => Some ( h) ,
89
+ } )
90
+ . cloned ( )
91
+ . collect :: < Vec < _ > > ( ) ;
92
+
93
+ Self {
94
+ diagnostics,
95
+ inlay_hints,
96
+ }
97
+ }
98
+ }
46
99
47
100
#[ derive( Deserialize ) ]
48
101
pub ( crate ) struct IdeComplete {
@@ -82,7 +135,7 @@ impl From<IdeComplete> for CompletionResponse {
82
135
}
83
136
}
84
137
85
- #[ derive( Debug , Deserialize , PartialEq ) ]
138
+ #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
86
139
pub ( crate ) enum IdeDiagnosticSeverity {
87
140
Error ,
88
141
Warning ,
@@ -113,13 +166,12 @@ pub(crate) struct IdeHover {
113
166
pub hover : String ,
114
167
pub span : Option < IdeSpan > ,
115
168
}
116
- #[ derive( Debug , Deserialize , PartialEq ) ]
169
+ #[ derive( Clone , Debug , Deserialize , PartialEq ) ]
117
170
pub ( crate ) struct IdeSpan {
118
171
pub end : u32 ,
119
172
pub start : u32 ,
120
173
}
121
174
122
- #[ allow( dead_code) ]
123
175
#[ derive( Clone , Debug , Deserialize ) ]
124
176
#[ serde( default , rename_all = "camelCase" ) ]
125
177
pub ( crate ) struct IdeSettings {
@@ -141,7 +193,6 @@ impl Default for IdeSettings {
141
193
}
142
194
}
143
195
144
- #[ allow( dead_code) ]
145
196
#[ derive( Clone , Debug , Deserialize ) ]
146
197
#[ serde( default , rename_all = "camelCase" ) ]
147
198
pub ( crate ) struct IdeSettingsHints {
@@ -155,6 +206,7 @@ impl Default for IdeSettingsHints {
155
206
}
156
207
}
157
208
209
+ #[ derive( Debug ) ]
158
210
pub ( crate ) struct CompilerResponse {
159
211
pub cmdline : String ,
160
212
pub stdout : String ,
@@ -167,8 +219,6 @@ pub(crate) async fn run_compiler(
167
219
settings : IdeSettings ,
168
220
uri : & Url ,
169
221
) -> Result < CompilerResponse > {
170
- // TODO: support allowErrors and label options like vscode-nushell-lang?
171
-
172
222
let max_number_of_problems = format ! ( "{}" , settings. max_number_of_problems) ;
173
223
let max_number_of_problems_flag = OsStr :: new ( & max_number_of_problems) ;
174
224
if flags. contains ( & OsStr :: new ( "--ide-check" ) ) {
@@ -210,16 +260,27 @@ pub(crate) async fn run_compiler(
210
260
211
261
let cmdline = format ! ( "nu {flags:?}" ) ;
212
262
213
- // TODO: honour max_nushell_invocation_time like vscode-nushell-lang
214
- // TODO: call settings.nushell_executable_path
215
-
216
263
// TODO: call nushell Rust code directly instead of via separate process,
217
264
// https://github.com/jokeyrhyme/nuls/issues/7
218
- let output = tokio:: process:: Command :: new ( "nu" )
219
- . args ( flags)
220
- . output ( )
221
- . await
222
- . map_err ( |e| map_err_to_internal_error ( e, format ! ( "`{cmdline}` failed" ) ) ) ?;
265
+ let output = timeout (
266
+ settings. max_nushell_invocation_time ,
267
+ tokio:: process:: Command :: new ( settings. nushell_executable_path )
268
+ . args ( flags)
269
+ . output ( ) ,
270
+ )
271
+ . await
272
+ . map_err ( |e| {
273
+ map_err_to_internal_error (
274
+ e,
275
+ format ! (
276
+ "`{cmdline}` timeout, {:?} elapsed" ,
277
+ & settings. max_nushell_invocation_time
278
+ ) ,
279
+ )
280
+ } ) ?
281
+ . map_err ( |e| map_err_to_internal_error ( e, format ! ( "`{cmdline}` failed" ) ) ) ?;
282
+ // intentionally skip checking the ExitStatus, we always want stdout regardless
283
+
223
284
let stdout = String :: from_utf8 ( output. stdout ) . map_err ( |e| {
224
285
map_err_to_parse_error ( e, format ! ( "`{cmdline}` did not return valid UTF-8" ) )
225
286
} ) ?;
@@ -285,7 +346,7 @@ mod tests {
285
346
}
286
347
287
348
#[ tokio:: test]
288
- async fn completion_ok ( ) {
349
+ async fn run_compiler_for_completion_ok ( ) {
289
350
let output = run_compiler (
290
351
"wh" ,
291
352
vec ! [ OsStr :: new( "--ide-complete" ) , OsStr :: new( & format!( "{}" , 2 ) ) ] ,
@@ -315,4 +376,44 @@ mod tests {
315
376
unreachable ! ( ) ;
316
377
}
317
378
}
379
+
380
+ #[ tokio:: test]
381
+ async fn run_compiler_for_diagnostic_ok ( ) {
382
+ let doc = FullTextDocument :: new (
383
+ String :: from ( "nushell" ) ,
384
+ 1 ,
385
+ String :: from (
386
+ "
387
+ let foo = ['one', 'two', 'three']
388
+ ls ||
389
+ " ,
390
+ ) ,
391
+ ) ;
392
+ let uri = Url :: parse ( "file:///foo.nu" ) . expect ( "unable to parse test URL" ) ;
393
+ let output = run_compiler (
394
+ doc. get_content ( None ) ,
395
+ vec ! [ OsStr :: new( "--ide-check" ) ] ,
396
+ IdeSettings :: default ( ) ,
397
+ & uri,
398
+ )
399
+ . await
400
+ . expect ( "unable to run `nu --ide-check ...`" ) ;
401
+
402
+ let got = IdeCheckResponse :: from_compiler_response ( & output) ;
403
+
404
+ assert_eq ! (
405
+ got,
406
+ IdeCheckResponse {
407
+ diagnostics: vec![ IdeCheckDiagnostic {
408
+ message: String :: from( "The '||' operator is not supported in Nushell" ) ,
409
+ severity: IdeDiagnosticSeverity :: Error ,
410
+ span: IdeSpan { end: 72 , start: 70 }
411
+ } ] ,
412
+ inlay_hints: vec![ IdeCheckHint {
413
+ position: IdeSpan { end: 24 , start: 21 } ,
414
+ typename: String :: from( "list<string>" )
415
+ } ] ,
416
+ }
417
+ ) ;
418
+ }
318
419
}
0 commit comments