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