@@ -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,132 @@ 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
+ match language_server. code_actions (
738
+ doc. identifier ( ) ,
739
+ lsp_range,
740
+ // Filter and convert overlapping diagnostics
741
+ lsp:: CodeActionContext {
742
+ diagnostics : doc
743
+ . diagnostics ( )
744
+ . iter ( )
745
+ . filter ( |& diag| {
746
+ range
747
+ . overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
748
+ } )
749
+ . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
750
+ . collect ( ) ,
751
+ only : None ,
752
+ trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
753
+ } ,
754
+ ) {
755
+ Some ( request) => Some ( ( request, language_server. id ( ) ) ) ,
756
+ None => None ,
757
+ }
758
+ } )
759
+ . collect :: < Vec < _ > > ( )
760
+ }
761
+
762
+ pub fn code_actions_on_save (
763
+ doc : & mut Document ,
764
+ ) -> Option <
765
+ FuturesUnordered < impl Future < Output = Result < Vec < CodeActionOrCommandItem > , anyhow:: Error > > > ,
766
+ > {
767
+ let code_actions_on_save_cfg = doc
768
+ . language_config ( )
769
+ . map ( |c| c. code_actions_on_save . clone ( ) ) ?;
770
+
771
+ if code_actions_on_save_cfg. is_empty ( ) {
772
+ return None ;
773
+ }
774
+
775
+ let full_range = Range :: new ( 0 , doc. text ( ) . len_chars ( ) ) ;
776
+
777
+ Some (
778
+ code_actions_for_range ( doc, full_range)
779
+ . into_iter ( )
780
+ . map ( |( request, ls_id) | {
781
+ let code_actions_on_save = code_actions_on_save_cfg. clone ( ) ;
782
+ async move {
783
+ log:: debug!( "Configured code actions on save {:?}" , code_actions_on_save) ;
784
+ let json = request. await ?;
785
+ let response: Option < helix_lsp:: lsp:: CodeActionResponse > =
786
+ serde_json:: from_value ( json) ?;
787
+ let available_code_actions = match response {
788
+ Some ( value) => value,
789
+ None => helix_lsp:: lsp:: CodeActionResponse :: default ( ) ,
790
+ } ;
791
+ log:: debug!( "Available code actions {:?}" , available_code_actions) ;
792
+
793
+ let code_actions: Vec < CodeActionOrCommand > = available_code_actions
794
+ . into_iter ( )
795
+ . filter ( |action| match action {
796
+ helix_lsp:: lsp:: CodeActionOrCommand :: CodeAction ( x)
797
+ if x. disabled . is_none ( ) =>
798
+ {
799
+ match & x. kind {
800
+ Some ( kind) => code_actions_on_save. get ( kind. as_str ( ) ) . is_some ( ) ,
801
+ None => false ,
802
+ }
803
+ }
804
+ _ => false ,
805
+ } )
806
+ . collect ( ) ;
807
+
808
+ Ok ( code_actions
809
+ . into_iter ( )
810
+ . map ( |lsp_item| CodeActionOrCommandItem {
811
+ lsp_item,
812
+ language_server_id : ls_id,
813
+ } )
814
+ . collect ( ) )
815
+ }
816
+ } )
817
+ . collect ( ) ,
818
+ )
819
+ }
820
+
821
+ pub fn apply_code_action ( editor : & mut Editor , code_action_item : & CodeActionOrCommandItem ) {
822
+ let Some ( language_server) = editor. language_server_by_id ( code_action_item. language_server_id ) else {
823
+ editor. set_error ( "Language Server disappeared" ) ;
824
+ return ;
825
+ } ;
826
+
827
+ let offset_encoding = language_server. offset_encoding ( ) ;
828
+
829
+ match code_action_item. lsp_item . clone ( ) {
830
+ lsp:: CodeActionOrCommand :: Command ( command) => {
831
+ log:: debug!( "code action command: {:?}" , command) ;
832
+ execute_lsp_command ( editor, code_action_item. language_server_id , command) ;
833
+ }
834
+ lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
835
+ log:: debug!( "code action: {:?}" , code_action) ;
836
+ if let Some ( ref workspace_edit) = code_action. edit {
837
+ log:: debug!( "edit: {:?}" , workspace_edit) ;
838
+ let _ = apply_workspace_edit ( editor, offset_encoding, workspace_edit) ;
839
+ }
840
+
841
+ // if code action provides both edit and command first the edit
842
+ // should be applied and then the command
843
+ if let Some ( command) = code_action. command {
844
+ execute_lsp_command ( editor, code_action_item. language_server_id , command) ;
845
+ }
846
+ }
847
+ }
848
+ }
849
+
773
850
impl ui:: menu:: Item for lsp:: Command {
774
851
type Data = ( ) ;
775
852
fn format ( & self , _data : & Self :: Data ) -> Row {
0 commit comments