@@ -14,7 +14,10 @@ use crate::{
14
14
ui:: { self , overlay:: overlayed, FileLocation , FilePicker , Popup , PromptEvent } ,
15
15
} ;
16
16
17
- use std:: borrow:: Cow ;
17
+ use std:: {
18
+ borrow:: Cow ,
19
+ sync:: { Arc , Mutex } ,
20
+ } ;
18
21
19
22
// TODO extend this to support multiple language servers
20
23
#[ macro_export]
@@ -30,6 +33,19 @@ macro_rules! language_server {
30
33
} ;
31
34
}
32
35
36
+ #[ macro_export]
37
+ macro_rules! language_server_by_id {
38
+ ( $editor: expr, $id: expr) => {
39
+ match $editor. language_servers. get_by_id( $id) {
40
+ Some ( language_server) => language_server,
41
+ None => {
42
+ $editor. set_status( "Language server not active for current buffer" ) ;
43
+ return ;
44
+ }
45
+ }
46
+ } ;
47
+ }
48
+
33
49
fn location_to_file_location ( location : & lsp:: Location ) -> FileLocation {
34
50
let path = location. uri . to_file_path ( ) . unwrap ( ) ;
35
51
let line = Some ( (
@@ -179,9 +195,9 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
179
195
)
180
196
}
181
197
182
- impl ui:: menu:: Item for lsp:: CodeActionOrCommand {
198
+ impl ui:: menu:: Item for ( lsp:: CodeActionOrCommand , OffsetEncoding ) {
183
199
fn label ( & self ) -> & str {
184
- match self {
200
+ match & self . 0 {
185
201
lsp:: CodeActionOrCommand :: CodeAction ( action) => action. title . as_str ( ) ,
186
202
lsp:: CodeActionOrCommand :: Command ( command) => command. title . as_str ( ) ,
187
203
}
@@ -191,84 +207,117 @@ impl ui::menu::Item for lsp::CodeActionOrCommand {
191
207
pub fn code_action ( cx : & mut Context ) {
192
208
let ( view, doc) = current ! ( cx. editor) ;
193
209
194
- let language_server = language_server ! ( cx. editor, doc) ;
195
-
196
210
let selection_range = doc. selection ( view. id ) . primary ( ) ;
197
- let offset_encoding = language_server. offset_encoding ( ) ;
198
211
199
- let range = range_to_lsp_range ( doc. text ( ) , selection_range, offset_encoding) ;
200
-
201
- let future = language_server. code_actions (
202
- doc. identifier ( ) ,
203
- range,
204
- // Filter and convert overlapping diagnostics
205
- lsp:: CodeActionContext {
206
- diagnostics : doc
207
- . diagnostics ( )
208
- . iter ( )
209
- . filter ( |& diag| {
210
- selection_range
211
- . overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
212
- } )
213
- . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
214
- . collect ( ) ,
215
- only : None ,
216
- } ,
217
- ) ;
212
+ // this ensures, that a previously opened menu that doesn't have anything to do with this command will be replaced with a new menu
213
+ let code_actions_menu_open = Arc :: new ( Mutex :: new ( false ) ) ;
218
214
219
- cx. callback (
220
- future,
221
- move |editor, compositor, response : Option < lsp:: CodeActionResponse > | {
222
- let actions = match response {
223
- Some ( a) => a,
224
- None => return ,
225
- } ;
226
- if actions. is_empty ( ) {
227
- editor. set_status ( "No code actions available" ) ;
228
- return ;
229
- }
215
+ let mut requests = Vec :: new ( ) ;
216
+
217
+ for language_server in doc. language_servers ( ) {
218
+ let offset_encoding = language_server. offset_encoding ( ) ;
219
+ let range = range_to_lsp_range ( doc. text ( ) , selection_range, offset_encoding) ;
220
+
221
+ requests. push ( (
222
+ language_server. code_actions (
223
+ doc. identifier ( ) ,
224
+ range,
225
+ // Filter and convert overlapping diagnostics
226
+ lsp:: CodeActionContext {
227
+ diagnostics : doc
228
+ . diagnostics ( )
229
+ . iter ( )
230
+ . filter ( |& diag| {
231
+ selection_range
232
+ . overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
233
+ } )
234
+ . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
235
+ . collect ( ) ,
236
+ only : None ,
237
+ } ,
238
+ ) ,
239
+ offset_encoding,
240
+ language_server. id ( ) ,
241
+ ) ) ;
242
+ }
243
+
244
+ for ( future, offset_encoding, lsp_id) in requests {
245
+ let code_actions_menu_open = code_actions_menu_open. clone ( ) ;
230
246
231
- let mut picker = ui:: Menu :: new ( actions, move |editor, code_action, event| {
232
- if event != PromptEvent :: Validate {
247
+ cx. callback (
248
+ future,
249
+ move |editor, compositor, response : Option < lsp:: CodeActionResponse > | {
250
+ let actions = match response {
251
+ Some ( a) => a
252
+ . into_iter ( )
253
+ . map ( |a| ( a, offset_encoding) )
254
+ . collect :: < Vec < _ > > ( ) ,
255
+ None => return ,
256
+ } ;
257
+
258
+ let mut code_actions_menu_open = code_actions_menu_open. lock ( ) . unwrap ( ) ;
259
+
260
+ if actions. is_empty ( ) && !* code_actions_menu_open {
261
+ editor. set_status ( "No code actions available" ) ;
233
262
return ;
234
263
}
235
264
236
- // always present here
237
- let code_action = code_action. unwrap ( ) ;
265
+ let code_actions_menu = compositor. find_id :: < Popup <
266
+ ui:: Menu < ( lsp:: CodeActionOrCommand , OffsetEncoding ) > ,
267
+ > > ( "code-action" ) ;
238
268
239
- match code_action {
240
- lsp:: CodeActionOrCommand :: Command ( command) => {
241
- log:: debug!( "code action command: {:?}" , command) ;
242
- execute_lsp_command ( editor, command. clone ( ) ) ;
243
- }
244
- lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
245
- log:: debug!( "code action: {:?}" , code_action) ;
246
- if let Some ( ref workspace_edit) = code_action. edit {
247
- log:: debug!( "edit: {:?}" , workspace_edit) ;
248
- apply_workspace_edit ( editor, offset_encoding, workspace_edit) ;
269
+ if !* code_actions_menu_open || code_actions_menu. is_none ( ) {
270
+ let mut picker = ui:: Menu :: new ( actions, move |editor, code_action, event| {
271
+ if event != PromptEvent :: Validate {
272
+ return ;
249
273
}
250
274
251
- // if code action provides both edit and command first the edit
252
- // should be applied and then the command
253
- if let Some ( command) = & code_action. command {
254
- execute_lsp_command ( editor, command. clone ( ) ) ;
275
+ // always present here
276
+ let code_action = code_action. unwrap ( ) ;
277
+
278
+ match code_action {
279
+ ( lsp:: CodeActionOrCommand :: Command ( command) , _encoding) => {
280
+ log:: debug!( "code action command: {:?}" , command) ;
281
+ execute_lsp_command ( editor, lsp_id, command. clone ( ) ) ;
282
+ }
283
+ (
284
+ lsp:: CodeActionOrCommand :: CodeAction ( code_action) ,
285
+ offset_encoding,
286
+ ) => {
287
+ log:: debug!( "code action: {:?}" , code_action) ;
288
+ if let Some ( ref workspace_edit) = code_action. edit {
289
+ log:: debug!( "edit: {:?}" , workspace_edit) ;
290
+ apply_workspace_edit ( editor, * offset_encoding, workspace_edit) ;
291
+ }
292
+
293
+ // if code action provides both edit and command first the edit
294
+ // should be applied and then the command
295
+ if let Some ( command) = & code_action. command {
296
+ execute_lsp_command ( editor, lsp_id, command. clone ( ) ) ;
297
+ }
298
+ }
255
299
}
256
- }
257
- }
258
- } ) ;
259
- picker. move_down ( ) ; // pre-select the first item
300
+ } ) ;
301
+ picker. move_down ( ) ; // pre-select the first item
260
302
261
- let popup = Popup :: new ( "code-action" , picker) . margin ( helix_view:: graphics:: Margin {
262
- vertical : 1 ,
263
- horizontal : 1 ,
264
- } ) ;
265
- compositor. replace_or_push ( "code-action" , popup) ;
266
- } ,
267
- )
303
+ let popup = Popup :: new ( "code-action" , picker)
304
+ . margin ( helix_view:: graphics:: Margin {
305
+ vertical : 1 ,
306
+ horizontal : 1 ,
307
+ } )
308
+ . auto_close ( true ) ;
309
+ compositor. replace_or_push ( "code-action" , popup) ;
310
+ * code_actions_menu_open = true ;
311
+ } else if let Some ( code_actions_menu) = code_actions_menu {
312
+ let picker = code_actions_menu. contents_mut ( ) ;
313
+ picker. add_options ( actions)
314
+ }
315
+ } ,
316
+ )
317
+ }
268
318
}
269
- pub fn execute_lsp_command ( editor : & mut Editor , cmd : lsp:: Command ) {
270
- let doc = doc ! ( editor) ;
271
- let language_server = language_server ! ( editor, doc) ;
319
+ pub fn execute_lsp_command ( editor : & mut Editor , language_server_id : usize , cmd : lsp:: Command ) {
320
+ let language_server = language_server_by_id ! ( editor, language_server_id) ;
272
321
273
322
// the command is executed on the server and communicated back
274
323
// to the client asynchronously using workspace edits
0 commit comments