@@ -2,8 +2,8 @@ use futures_util::{stream::FuturesOrdered, FutureExt};
22use helix_lsp:: {
33 block_on,
44 lsp:: {
5- self , CodeAction , CodeActionOrCommand , CodeActionTriggerKind , DiagnosticSeverity ,
6- NumberOrString ,
5+ self , CodeAction , CodeActionKind , CodeActionOrCommand , CodeActionTriggerKind ,
6+ DiagnosticSeverity , NumberOrString ,
77 } ,
88 util:: { diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range} ,
99 Client , LanguageServerId , OffsetEncoding ,
@@ -23,7 +23,7 @@ use helix_view::{
2323 editor:: Action ,
2424 handlers:: lsp:: SignatureHelpInvoked ,
2525 theme:: Style ,
26- Document , View ,
26+ Document , DocumentId , View ,
2727} ;
2828
2929use crate :: {
@@ -587,6 +587,11 @@ struct CodeActionOrCommandItem {
587587 language_server_id : LanguageServerId ,
588588}
589589
590+ struct CodeActionItem {
591+ lsp_item : lsp:: CodeAction ,
592+ language_server_id : LanguageServerId ,
593+ }
594+
590595impl ui:: menu:: Item for CodeActionOrCommandItem {
591596 type Data = ( ) ;
592597 fn format ( & self , _data : & Self :: Data ) -> Row < ' _ > {
@@ -659,34 +664,8 @@ pub fn code_action(cx: &mut Context) {
659664
660665 let selection_range = doc. selection ( view. id ) . primary ( ) ;
661666
662- let mut seen_language_servers = HashSet :: new ( ) ;
663-
664- let mut futures: FuturesOrdered < _ > = doc
665- . language_servers_with_feature ( LanguageServerFeature :: CodeAction )
666- . filter ( |ls| seen_language_servers. insert ( ls. id ( ) ) )
667- // TODO this should probably already been filtered in something like "language_servers_with_feature"
668- . filter_map ( |language_server| {
669- let offset_encoding = language_server. offset_encoding ( ) ;
670- let language_server_id = language_server. id ( ) ;
671- let range = range_to_lsp_range ( doc. text ( ) , selection_range, offset_encoding) ;
672- // Filter and convert overlapping diagnostics
673- let code_action_context = lsp:: CodeActionContext {
674- diagnostics : doc
675- . diagnostics ( )
676- . iter ( )
677- . filter ( |& diag| {
678- selection_range
679- . overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
680- } )
681- . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
682- . collect ( ) ,
683- only : None ,
684- trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
685- } ;
686- let code_action_request =
687- language_server. code_actions ( doc. identifier ( ) , range, code_action_context) ?;
688- Some ( ( code_action_request, language_server_id) )
689- } )
667+ let mut futures: FuturesOrdered < _ > = code_actions_for_range ( doc, selection_range, None )
668+ . into_iter ( )
690669 . map ( |( request, ls_id) | async move {
691670 let Some ( mut actions) = request. await ? else {
692671 return anyhow:: Ok ( Vec :: new ( ) ) ;
@@ -788,19 +767,12 @@ pub fn code_action(cx: &mut Context) {
788767 }
789768 lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
790769 log:: debug!( "code action: {:?}" , code_action) ;
791- // we support lsp "codeAction/resolve" for `edit` and `command` fields
792- let mut resolved_code_action = None ;
793- if code_action. edit . is_none ( ) || code_action. command . is_none ( ) {
794- if let Some ( future) = language_server. resolve_code_action ( code_action) {
795- if let Ok ( code_action) = helix_lsp:: block_on ( future) {
796- resolved_code_action = Some ( code_action) ;
797- }
798- }
799- }
800770 let resolved_code_action =
801- resolved_code_action . as_ref ( ) . unwrap_or ( code_action) ;
771+ resolve_code_action ( code_action, language_server ) ;
802772
803- if let Some ( ref workspace_edit) = resolved_code_action. edit {
773+ if let Some ( ref workspace_edit) =
774+ resolved_code_action. as_ref ( ) . unwrap_or ( code_action) . edit
775+ {
804776 let _ = editor. apply_workspace_edit ( offset_encoding, workspace_edit) ;
805777 }
806778
@@ -825,6 +797,155 @@ pub fn code_action(cx: &mut Context) {
825797 } ) ;
826798}
827799
800+ pub fn code_actions_for_range (
801+ doc : & Document ,
802+ range : helix_core:: Range ,
803+ only : Option < Vec < CodeActionKind > > ,
804+ ) -> Vec < (
805+ impl Future < Output = Result < Option < Vec < CodeActionOrCommand > > , helix_lsp:: Error > > ,
806+ LanguageServerId ,
807+ ) > {
808+ let mut seen_language_servers = HashSet :: new ( ) ;
809+
810+ doc. language_servers_with_feature ( LanguageServerFeature :: CodeAction )
811+ . filter ( |ls| seen_language_servers. insert ( ls. id ( ) ) )
812+ // TODO this should probably already been filtered in something like "language_servers_with_feature"
813+ . filter_map ( |language_server| {
814+ let offset_encoding = language_server. offset_encoding ( ) ;
815+ let language_server_id = language_server. id ( ) ;
816+ let lsp_range = range_to_lsp_range ( doc. text ( ) , range, offset_encoding) ;
817+ // Filter and convert overlapping diagnostics
818+ let code_action_context = lsp:: CodeActionContext {
819+ diagnostics : doc
820+ . diagnostics ( )
821+ . iter ( )
822+ . filter ( |& diag| {
823+ range. overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
824+ } )
825+ . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
826+ . collect ( ) ,
827+ only : only. clone ( ) ,
828+ trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
829+ } ;
830+ let code_action_request =
831+ language_server. code_actions ( doc. identifier ( ) , lsp_range, code_action_context) ?;
832+ Some ( ( code_action_request, language_server_id) )
833+ } )
834+ . collect :: < Vec < _ > > ( )
835+ }
836+
837+ /// Will apply the code actions on save from the language config for each language server
838+ pub fn code_actions_on_save ( cx : & mut compositor:: Context , doc_id : & DocumentId ) {
839+ let doc = doc ! ( cx. editor, doc_id) ;
840+
841+ let code_actions_on_save_cfg = doc
842+ . language_config ( )
843+ . and_then ( |c| c. code_actions_on_save . clone ( ) ) ;
844+
845+ if let Some ( code_actions_on_save_cfg) = code_actions_on_save_cfg {
846+ for code_action_kind in code_actions_on_save_cfg
847+ . into_iter ( )
848+ . map ( CodeActionKind :: from)
849+ {
850+ log:: debug!( "Attempting code action on save {:?}" , code_action_kind) ;
851+ let doc = doc ! ( cx. editor, doc_id) ;
852+ let full_range = helix_core:: Range :: new ( 0 , doc. text ( ) . len_chars ( ) ) ;
853+ let code_actions: Vec < CodeActionItem > =
854+ code_actions_for_range ( doc, full_range, Some ( vec ! [ code_action_kind. clone( ) ] ) )
855+ . into_iter ( )
856+ . filter_map ( |( future, language_server_id) | {
857+ if let Ok ( Some ( mut actions) ) = helix_lsp:: block_on ( future) {
858+ // Retain only enabled code actions that do not have commands.
859+ //
860+ // Commands are deprecated and are not supported because they apply
861+ // workspace edits asynchronously and there is currently no mechanism
862+ // to handle waiting for the workspace edits to be applied before moving
863+ // on to the next code action (or auto-format).
864+ actions. retain ( |action| {
865+ matches ! (
866+ action,
867+ CodeActionOrCommand :: CodeAction ( CodeAction {
868+ disabled: None ,
869+ command: None ,
870+ ..
871+ } )
872+ )
873+ } ) ;
874+
875+ // Use the first matching code action
876+ if let Some ( lsp_item) = actions. first ( ) {
877+ return match lsp_item {
878+ CodeActionOrCommand :: CodeAction ( code_action) => {
879+ Some ( CodeActionItem {
880+ lsp_item : code_action. clone ( ) ,
881+ language_server_id,
882+ } )
883+ }
884+ _ => None ,
885+ } ;
886+ }
887+ }
888+ None
889+ } )
890+ . collect ( ) ;
891+
892+ if code_actions. is_empty ( ) {
893+ log:: debug!( "Code action on save not found {:?}" , code_action_kind) ;
894+ cx. editor
895+ . set_error ( format ! ( "Code Action not found: {:?}" , code_action_kind) ) ;
896+ }
897+
898+ for code_action in code_actions {
899+ log:: debug!(
900+ "Applying code action on save {:?} for language server {:?}" ,
901+ code_action. lsp_item,
902+ code_action. language_server_id
903+ ) ;
904+ let Some ( language_server) = cx
905+ . editor
906+ . language_server_by_id ( code_action. language_server_id )
907+ else {
908+ log:: error!(
909+ "Language server disappeared {:?}" ,
910+ code_action. language_server_id
911+ ) ;
912+ continue ;
913+ } ;
914+
915+ let offset_encoding = language_server. offset_encoding ( ) ;
916+
917+ let resolved_code_action =
918+ resolve_code_action ( & code_action. lsp_item , language_server) ;
919+
920+ if let Some ( ref workspace_edit) = resolved_code_action
921+ . as_ref ( )
922+ . unwrap_or ( & code_action. lsp_item )
923+ . edit
924+ {
925+ let _ = cx
926+ . editor
927+ . apply_workspace_edit_sync ( offset_encoding, workspace_edit) ;
928+ }
929+ }
930+ }
931+ }
932+ }
933+
934+ pub fn resolve_code_action (
935+ code_action : & CodeAction ,
936+ language_server : & Client ,
937+ ) -> Option < CodeAction > {
938+ let mut resolved_code_action = None ;
939+ if code_action. edit . is_none ( ) || code_action. command . is_none ( ) {
940+ if let Some ( future) = language_server. resolve_code_action ( code_action) {
941+ if let Ok ( code_action) = helix_lsp:: block_on ( future) {
942+ resolved_code_action = Some ( code_action) ;
943+ }
944+ }
945+ }
946+ resolved_code_action
947+ }
948+
828949#[ derive( Debug ) ]
829950pub struct ApplyEditError {
830951 pub kind : ApplyEditErrorKind ,
0 commit comments