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.start..self.selected_range.end;
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 cx.notify();
86 })?;
87 return Ok(());
88 }
89 editor
90 .update_in(cx, |editor, window, cx| {
91 if !editor.focus_handle.is_focused(window) {
92 return;
93 }
94
95 editor.extras.context_menu_content.code_action.items = code_actions;
96 editor.extras.context_menu_content.code_action.open = !editor
97 .extras
98 .context_menu_content
99 .code_action
100 .items
101 .is_empty();
102
103 cx.notify();
104 })
105 .ok();
106
107 Ok(())
108 });
109 }
110
111 pub fn perform_code_action(
112 &mut self,
113 item: &CodeActionItem,
114 window: &mut Window,
115 cx: &mut Context<Self>,
116 ) {
117 let providers = self.extras.lsp.code_action_providers.clone();
118 let Some(provider) = providers
119 .iter()
120 .find(|provider| provider.id() == item.provider_id)
121 else {
122 return;
123 };
124
125 let state = cx.entity();
126 let task = provider.perform_code_action(state, item.action.clone(), true, window, cx);
127
128 cx.spawn_in(window, async move |_, _| {
129 let _ = task.await;
130 })
131 .detach();
132 }
133}