gpui_base/input/editor/lsp/
code_actions.rs1use anyhow::Result;
2use gpui::{App, Context, Entity, SharedString, Task, Window};
3use lsp_types::CodeAction;
4use std::ops::Range;
5
6use crate::input::{EditorMode, EditorState, InputBaseState, ToggleCodeActions};
7
8pub trait CodeActionProvider {
9 fn id(&self) -> SharedString;
11
12 fn code_actions(
18 &self,
19 state: Entity<EditorState>,
20 range: Range<usize>,
21 window: &mut Window,
22 cx: &mut App,
23 ) -> Task<Result<Vec<CodeAction>>>;
24
25 fn perform_code_action(
27 &self,
28 state: Entity<EditorState>,
29 action: CodeAction,
30 push_to_history: bool,
31 window: &mut Window,
32 cx: &mut App,
33 ) -> Task<Result<()>>;
34}
35
36#[derive(Clone, Debug)]
37pub struct CodeActionItem {
38 pub provider_id: SharedString,
39 pub action: CodeAction,
40}
41
42impl InputBaseState<EditorMode> {
43 pub(crate) fn on_action_toggle_code_actions(
44 &mut self,
45 _: &ToggleCodeActions,
46 window: &mut Window,
47 cx: &mut Context<Self>,
48 ) {
49 self.handle_code_action_trigger(window, cx)
50 }
51
52 pub(crate) fn handle_code_action_trigger(
54 &mut self,
55 window: &mut Window,
56 cx: &mut Context<Self>,
57 ) {
58 let providers = self.extras.lsp.code_action_providers.clone();
59 let range = self.selected_range();
60
61 let state = cx.entity();
62 self.extras.context_menu_task = cx.spawn_in(window, async move |editor, cx| {
63 let mut provider_responses = vec![];
64 _ = cx.update(|window, cx| {
65 for provider in providers {
66 let task = provider.code_actions(state.clone(), range.clone(), window, cx);
67 provider_responses.push((provider.id(), task));
68 }
69 });
70
71 let mut code_actions: Vec<CodeActionItem> = vec![];
72 for (provider_id, provider_responses) in provider_responses {
73 if let Some(responses) = provider_responses.await.ok() {
74 code_actions.extend(responses.into_iter().map(|action| CodeActionItem {
75 provider_id: provider_id.clone(),
76 action,
77 }))
78 }
79 }
80
81 if code_actions.is_empty() {
82 editor.update(cx, |editor, cx| {
83 editor.extras.context_menu_content.code_action.open = false;
84 editor.extras.context_menu_content.code_action.items.clear();
85 editor.extras.context_menu_content.code_action.bump();
86 cx.notify();
87 })?;
88 return Ok(());
89 }
90 editor
91 .update_in(cx, |editor, window, cx| {
92 if !editor.focus_handle.is_focused(window) {
93 return;
94 }
95
96 editor.extras.context_menu_content.code_action.items = code_actions;
97 editor.extras.context_menu_content.code_action.open = !editor
98 .extras
99 .context_menu_content
100 .code_action
101 .items
102 .is_empty();
103 editor.extras.context_menu_content.code_action.bump();
106
107 cx.notify();
108 })
109 .ok();
110
111 Ok(())
112 });
113 }
114
115 pub fn perform_code_action(
116 &mut self,
117 item: &CodeActionItem,
118 window: &mut Window,
119 cx: &mut Context<Self>,
120 ) {
121 let providers = self.extras.lsp.code_action_providers.clone();
122 let Some(provider) = providers
123 .iter()
124 .find(|provider| provider.id() == item.provider_id)
125 else {
126 return;
127 };
128
129 let state = cx.entity();
130 let task = provider.perform_code_action(state, item.action.clone(), true, window, cx);
131
132 cx.spawn_in(window, async move |_, _| {
133 let _ = task.await;
134 })
135 .detach();
136 }
137}