@@ -18,7 +18,7 @@ use tui::{
18
18
use super :: { align_view, push_jump, Align , Context , Editor , Open } ;
19
19
20
20
use helix_core:: {
21
- path, syntax:: LanguageServerFeature , text_annotations:: InlineAnnotation , Selection ,
21
+ path, syntax:: LanguageServerFeature , text_annotations:: InlineAnnotation , Range , Selection ,
22
22
} ;
23
23
use helix_view:: {
24
24
document:: { DocumentInlayHints , DocumentInlayHintsId , Mode } ,
@@ -542,7 +542,8 @@ pub fn workspace_diagnostics_picker(cx: &mut Context) {
542
542
cx. push_layer ( Box :: new ( overlaid ( picker) ) ) ;
543
543
}
544
544
545
- struct CodeActionOrCommandItem {
545
+ #[ derive( Debug ) ]
546
+ pub struct CodeActionOrCommandItem {
546
547
lsp_item : lsp:: CodeActionOrCommand ,
547
548
language_server_id : usize ,
548
549
}
@@ -619,34 +620,8 @@ pub fn code_action(cx: &mut Context) {
619
620
620
621
let selection_range = doc. selection ( view. id ) . primary ( ) ;
621
622
622
- let mut seen_language_servers = HashSet :: new ( ) ;
623
-
624
- let mut futures: FuturesUnordered < _ > = doc
625
- . language_servers_with_feature ( LanguageServerFeature :: CodeAction )
626
- . filter ( |ls| seen_language_servers. insert ( ls. id ( ) ) )
627
- // TODO this should probably already been filtered in something like "language_servers_with_feature"
628
- . filter_map ( |language_server| {
629
- let offset_encoding = language_server. offset_encoding ( ) ;
630
- let language_server_id = language_server. id ( ) ;
631
- let range = range_to_lsp_range ( doc. text ( ) , selection_range, offset_encoding) ;
632
- // Filter and convert overlapping diagnostics
633
- let code_action_context = lsp:: CodeActionContext {
634
- diagnostics : doc
635
- . diagnostics ( )
636
- . iter ( )
637
- . filter ( |& diag| {
638
- selection_range
639
- . overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
640
- } )
641
- . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
642
- . collect ( ) ,
643
- only : None ,
644
- trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
645
- } ;
646
- let code_action_request =
647
- language_server. code_actions ( doc. identifier ( ) , range, code_action_context) ?;
648
- Some ( ( code_action_request, language_server_id) )
649
- } )
623
+ let mut futures: FuturesUnordered < _ > = code_actions_for_range ( doc, selection_range)
624
+ . into_iter ( )
650
625
. map ( |( request, ls_id) | async move {
651
626
let json = request. await ?;
652
627
let response: Option < lsp:: CodeActionResponse > = serde_json:: from_value ( json) ?;
@@ -734,31 +709,7 @@ pub fn code_action(cx: &mut Context) {
734
709
735
710
// always present here
736
711
let action = action. unwrap ( ) ;
737
- let Some ( language_server) = editor. language_server_by_id ( action. language_server_id ) else {
738
- editor. set_error ( "Language Server disappeared" ) ;
739
- return ;
740
- } ;
741
- let offset_encoding = language_server. offset_encoding ( ) ;
742
-
743
- match & action. lsp_item {
744
- lsp:: CodeActionOrCommand :: Command ( command) => {
745
- log:: debug!( "code action command: {:?}" , command) ;
746
- execute_lsp_command ( editor, action. language_server_id , command. clone ( ) ) ;
747
- }
748
- lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
749
- log:: debug!( "code action: {:?}" , code_action) ;
750
- if let Some ( ref workspace_edit) = code_action. edit {
751
- log:: debug!( "edit: {:?}" , workspace_edit) ;
752
- let _ = apply_workspace_edit ( editor, offset_encoding, workspace_edit) ;
753
- }
754
-
755
- // if code action provides both edit and command first the edit
756
- // should be applied and then the command
757
- if let Some ( command) = & code_action. command {
758
- execute_lsp_command ( editor, action. language_server_id , command. clone ( ) ) ;
759
- }
760
- }
761
- }
712
+ apply_code_action ( editor, action) ;
762
713
} ) ;
763
714
picker. move_down ( ) ; // pre-select the first item
764
715
@@ -770,6 +721,131 @@ pub fn code_action(cx: &mut Context) {
770
721
} ) ;
771
722
}
772
723
724
+ pub fn code_actions_for_range (
725
+ doc : & mut Document ,
726
+ range : helix_core:: Range ,
727
+ ) -> Vec < ( impl Future < Output = Result < Value , helix_lsp:: Error > > , usize ) > {
728
+ let mut seen_language_servers = HashSet :: new ( ) ;
729
+
730
+ doc. language_servers_with_feature ( LanguageServerFeature :: CodeAction )
731
+ . filter ( |ls| seen_language_servers. insert ( ls. id ( ) ) )
732
+ // TODO this should probably already been filtered in something like "language_servers_with_feature"
733
+ . filter_map ( |language_server| {
734
+ let offset_encoding = language_server. offset_encoding ( ) ;
735
+ let lsp_range = range_to_lsp_range ( doc. text ( ) , range, offset_encoding) ;
736
+
737
+ language_server
738
+ . code_actions (
739
+ doc. identifier ( ) ,
740
+ lsp_range,
741
+ // Filter and convert overlapping diagnostics
742
+ lsp:: CodeActionContext {
743
+ diagnostics : doc
744
+ . diagnostics ( )
745
+ . iter ( )
746
+ . filter ( |& diag| {
747
+ range. overlaps ( & helix_core:: Range :: new (
748
+ diag. range . start ,
749
+ diag. range . end ,
750
+ ) )
751
+ } )
752
+ . map ( |diag| {
753
+ diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding)
754
+ } )
755
+ . collect ( ) ,
756
+ only : None ,
757
+ trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
758
+ } ,
759
+ )
760
+ . map ( |request| ( request, language_server. id ( ) ) )
761
+ } )
762
+ . collect :: < Vec < _ > > ( )
763
+ }
764
+
765
+ pub fn code_actions_on_save (
766
+ doc : & mut Document ,
767
+ ) -> Option <
768
+ FuturesUnordered < impl Future < Output = Result < Vec < CodeActionOrCommandItem > , anyhow:: Error > > > ,
769
+ > {
770
+ let code_actions_on_save_cfg = doc
771
+ . language_config ( )
772
+ . map ( |c| c. code_actions_on_save . clone ( ) ) ?;
773
+
774
+ if code_actions_on_save_cfg. is_empty ( ) {
775
+ return None ;
776
+ }
777
+
778
+ let full_range = Range :: new ( 0 , doc. text ( ) . len_chars ( ) ) ;
779
+
780
+ Some (
781
+ code_actions_for_range ( doc, full_range)
782
+ . into_iter ( )
783
+ . map ( |( request, ls_id) | {
784
+ let code_actions_on_save = code_actions_on_save_cfg. clone ( ) ;
785
+ async move {
786
+ log:: debug!( "Configured code actions on save {:?}" , code_actions_on_save) ;
787
+ let json = request. await ?;
788
+ let response: Option < helix_lsp:: lsp:: CodeActionResponse > =
789
+ serde_json:: from_value ( json) ?;
790
+ let available_code_actions = match response {
791
+ Some ( value) => value,
792
+ None => helix_lsp:: lsp:: CodeActionResponse :: default ( ) ,
793
+ } ;
794
+ log:: debug!( "Available code actions {:?}" , available_code_actions) ;
795
+
796
+ Ok ( available_code_actions
797
+ . into_iter ( )
798
+ . filter ( |action| match action {
799
+ helix_lsp:: lsp:: CodeActionOrCommand :: CodeAction ( x)
800
+ if x. disabled . is_none ( ) =>
801
+ {
802
+ match & x. kind {
803
+ Some ( kind) => code_actions_on_save. get ( kind. as_str ( ) ) . is_some ( ) ,
804
+ None => false ,
805
+ }
806
+ }
807
+ _ => false ,
808
+ } )
809
+ . map ( |lsp_item| CodeActionOrCommandItem {
810
+ lsp_item,
811
+ language_server_id : ls_id,
812
+ } )
813
+ . collect ( ) )
814
+ }
815
+ } )
816
+ . collect ( ) ,
817
+ )
818
+ }
819
+
820
+ pub fn apply_code_action ( editor : & mut Editor , code_action_item : & CodeActionOrCommandItem ) {
821
+ let Some ( language_server) = editor. language_server_by_id ( code_action_item. language_server_id ) else {
822
+ editor. set_error ( "Language Server disappeared" ) ;
823
+ return ;
824
+ } ;
825
+
826
+ let offset_encoding = language_server. offset_encoding ( ) ;
827
+
828
+ match code_action_item. lsp_item . clone ( ) {
829
+ lsp:: CodeActionOrCommand :: Command ( command) => {
830
+ log:: debug!( "code action command: {:?}" , command) ;
831
+ execute_lsp_command ( editor, code_action_item. language_server_id , command) ;
832
+ }
833
+ lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
834
+ log:: debug!( "code action: {:?}" , code_action) ;
835
+ if let Some ( ref workspace_edit) = code_action. edit {
836
+ log:: debug!( "edit: {:?}" , workspace_edit) ;
837
+ let _ = apply_workspace_edit ( editor, offset_encoding, workspace_edit) ;
838
+ }
839
+
840
+ // if code action provides both edit and command first the edit
841
+ // should be applied and then the command
842
+ if let Some ( command) = code_action. command {
843
+ execute_lsp_command ( editor, code_action_item. language_server_id , command) ;
844
+ }
845
+ }
846
+ }
847
+ }
848
+
773
849
impl ui:: menu:: Item for lsp:: Command {
774
850
type Data = ( ) ;
775
851
fn format ( & self , _data : & Self :: Data ) -> Row {
0 commit comments