@@ -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,12 +23,12 @@ use helix_view::{
2323 editor:: Action ,
2424 handlers:: lsp:: SignatureHelpInvoked ,
2525 theme:: Style ,
26- Document , View ,
26+ Document , DocumentId , View ,
2727} ;
2828
2929use crate :: {
3030 compositor:: { self , Compositor } ,
31- job:: Callback ,
31+ job:: { self , Callback , Job } ,
3232 ui:: { self , overlay:: overlaid, FileLocation , Picker , Popup , PromptEvent } ,
3333} ;
3434
@@ -659,34 +659,8 @@ pub fn code_action(cx: &mut Context) {
659659
660660 let selection_range = doc. selection ( view. id ) . primary ( ) ;
661661
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- } )
662+ let mut futures: FuturesOrdered < _ > = code_actions_for_range ( doc, selection_range, None )
663+ . into_iter ( )
690664 . map ( |( request, ls_id) | async move {
691665 let Some ( mut actions) = request. await ? else {
692666 return anyhow:: Ok ( Vec :: new ( ) ) ;
@@ -788,19 +762,12 @@ pub fn code_action(cx: &mut Context) {
788762 }
789763 lsp:: CodeActionOrCommand :: CodeAction ( code_action) => {
790764 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- }
800765 let resolved_code_action =
801- resolved_code_action . as_ref ( ) . unwrap_or ( code_action) ;
766+ resolve_code_action_blocking ( code_action, language_server ) ;
802767
803- if let Some ( ref workspace_edit) = resolved_code_action. edit {
768+ if let Some ( ref workspace_edit) =
769+ resolved_code_action. as_ref ( ) . unwrap_or ( code_action) . edit
770+ {
804771 let _ = editor. apply_workspace_edit ( offset_encoding, workspace_edit) ;
805772 }
806773
@@ -825,6 +792,183 @@ pub fn code_action(cx: &mut Context) {
825792 } ) ;
826793}
827794
795+ // Extracting this to a type alias would require boxing this future
796+ #[ allow( clippy:: type_complexity) ]
797+ fn code_actions_for_range (
798+ doc : & Document ,
799+ range : helix_core:: Range ,
800+ only : Option < Vec < CodeActionKind > > ,
801+ ) -> Vec < (
802+ impl Future < Output = Result < Option < Vec < CodeActionOrCommand > > , helix_lsp:: Error > > ,
803+ LanguageServerId ,
804+ ) > {
805+ let mut seen_language_servers = HashSet :: new ( ) ;
806+
807+ doc. language_servers_with_feature ( LanguageServerFeature :: CodeAction )
808+ . filter ( |ls| seen_language_servers. insert ( ls. id ( ) ) )
809+ // TODO this should probably already been filtered in something like "language_servers_with_feature"
810+ . filter_map ( |language_server| {
811+ let offset_encoding = language_server. offset_encoding ( ) ;
812+ let language_server_id = language_server. id ( ) ;
813+ let lsp_range = range_to_lsp_range ( doc. text ( ) , range, offset_encoding) ;
814+ // Filter and convert overlapping diagnostics
815+ let code_action_context = lsp:: CodeActionContext {
816+ diagnostics : doc
817+ . diagnostics ( )
818+ . iter ( )
819+ . filter ( |& diag| {
820+ range. overlaps ( & helix_core:: Range :: new ( diag. range . start , diag. range . end ) )
821+ } )
822+ . map ( |diag| diagnostic_to_lsp_diagnostic ( doc. text ( ) , diag, offset_encoding) )
823+ . collect ( ) ,
824+ only : only. clone ( ) ,
825+ trigger_kind : Some ( CodeActionTriggerKind :: INVOKED ) ,
826+ } ;
827+ let code_action_request =
828+ language_server. code_actions ( doc. identifier ( ) , lsp_range, code_action_context) ?;
829+ Some ( ( code_action_request, language_server_id) )
830+ } )
831+ . collect :: < Vec < _ > > ( )
832+ }
833+
834+ pub fn code_actions_on_save (
835+ cx : & compositor:: Context ,
836+ doc_id : DocumentId ,
837+ make_format_job : Option < Job > ,
838+ ) -> Option < Job > {
839+ let doc = doc ! ( cx. editor, & doc_id) ;
840+
841+ let Some ( code_actions_on_save_cfg) = doc
842+ . language_config ( )
843+ . and_then ( |c| c. code_actions_on_save . clone ( ) )
844+ else {
845+ return make_format_job;
846+ } ;
847+
848+ let mut queued_job = make_format_job;
849+ for action_kind in code_actions_on_save_cfg
850+ . into_iter ( )
851+ // To run the actions in the configured order, build the chain of callbacks in reverse
852+ . rev ( )
853+ . map ( CodeActionKind :: from)
854+ {
855+ let call: job:: Callback = Callback :: Followup ( Box :: new ( move |editor| {
856+ let doc = doc ! ( editor, & doc_id) ;
857+ let full_range = helix_core:: Range :: new ( 0 , doc. text ( ) . len_chars ( ) ) ;
858+ if let Some ( ( actions, ls_id) ) =
859+ code_actions_for_range ( doc, full_range, Some ( vec ! [ action_kind] ) )
860+ . into_iter ( )
861+ . next ( )
862+ {
863+ let call = make_code_action_callback ( actions, ls_id, queued_job. take ( ) ) ;
864+ Some ( Job :: with_callback ( call) . wait_before_exiting ( ) )
865+ } else {
866+ queued_job. take ( )
867+ }
868+ } ) ) ;
869+ queued_job = Some ( Job :: with_callback ( async { Ok ( call) } ) . wait_before_exiting ( ) ) ;
870+ }
871+ queued_job
872+ }
873+
874+ async fn make_code_action_callback (
875+ actions : impl Future < Output = Result < Option < Vec < CodeActionOrCommand > > , helix_lsp:: Error > >
876+ + Send
877+ + Sync ,
878+ language_server_id : LanguageServerId ,
879+ queued : Option < Job > ,
880+ ) -> anyhow:: Result < job:: Callback > {
881+ let actions = actions. await ?;
882+
883+ let call: job:: Callback = Callback :: Followup ( Box :: new ( move |editor| {
884+ let Some ( actions) = actions else {
885+ return queued;
886+ } ;
887+
888+ let code_actions: Vec < _ > = actions
889+ . iter ( )
890+ . filter ( |action| {
891+ matches ! (
892+ action,
893+ CodeActionOrCommand :: CodeAction ( CodeAction { disabled: None , .. } )
894+ )
895+ } )
896+ . filter_map ( |action| match action {
897+ CodeActionOrCommand :: CodeAction ( code_action) => Some ( code_action) ,
898+ CodeActionOrCommand :: Command ( _) => None ,
899+ } )
900+ . collect ( ) ;
901+
902+ let next = if let Some ( code_action) = code_actions. first ( ) {
903+ let Some ( language_server) = editor. language_server_by_id ( language_server_id) else {
904+ return queued;
905+ } ;
906+ let Some ( resolve) = resolve_code_action ( code_action, language_server) else {
907+ return queued;
908+ } ;
909+ let callback = make_code_action_resolve_callback ( resolve, language_server_id, queued) ;
910+ Some ( Job :: with_callback ( callback) . wait_before_exiting ( ) )
911+ } else {
912+ queued
913+ } ;
914+
915+ next
916+ } ) ) ;
917+
918+ Ok ( call)
919+ }
920+
921+ async fn make_code_action_resolve_callback (
922+ resolve : impl Future < Output = Result < lsp:: CodeAction , helix_lsp:: Error > > + Send + Sync ,
923+ language_server_id : LanguageServerId ,
924+ queued : Option < Job > ,
925+ ) -> anyhow:: Result < job:: Callback > {
926+ let code_action = resolve. await ?;
927+
928+ let call: job:: Callback = Callback :: Followup ( Box :: new ( move |editor| {
929+ let Some ( language_server) = editor. language_server_by_id ( language_server_id) else {
930+ return queued;
931+ } ;
932+ let offset_encoding = language_server. offset_encoding ( ) ;
933+
934+ if let Some ( ref workspace_edit) = code_action. edit {
935+ if let Err ( e) = editor. apply_workspace_edit ( offset_encoding, workspace_edit) {
936+ log:: error!( "failed to apply workspace edit: {:?}" , e) ;
937+ }
938+ } ;
939+
940+ queued
941+ } ) ) ;
942+
943+ Ok ( call)
944+ }
945+
946+ fn resolve_code_action (
947+ code_action : & CodeAction ,
948+ language_server : & Client ,
949+ ) -> Option < impl Future < Output = Result < lsp:: CodeAction , helix_lsp:: Error > > > {
950+ if code_action. edit . is_none ( ) || code_action. command . is_none ( ) {
951+ language_server. resolve_code_action ( code_action)
952+ } else {
953+ None
954+ }
955+ }
956+
957+ pub fn resolve_code_action_blocking (
958+ code_action : & CodeAction ,
959+ language_server : & Client ,
960+ ) -> Option < CodeAction > {
961+ let mut resolved_code_action = None ;
962+ if code_action. edit . is_none ( ) || code_action. command . is_none ( ) {
963+ if let Some ( future) = language_server. resolve_code_action ( code_action) {
964+ if let Ok ( code_action) = helix_lsp:: block_on ( future) {
965+ resolved_code_action = Some ( code_action) ;
966+ }
967+ }
968+ }
969+ resolved_code_action
970+ }
971+
828972#[ derive( Debug ) ]
829973pub struct ApplyEditError {
830974 pub kind : ApplyEditErrorKind ,
0 commit comments