gpui_component/input/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::{
7 popovers::{CodeActionItem, CodeActionMenu, ContextMenu},
8 InputState, ToggleCodeActions,
9};
10
11pub trait CodeActionProvider {
12 fn id(&self) -> SharedString;
14
15 fn code_actions(
21 &self,
22 state: Entity<InputState>,
23 range: Range<usize>,
24 window: &mut Window,
25 cx: &mut App,
26 ) -> Task<Result<Vec<CodeAction>>>;
27
28 fn perform_code_action(
30 &self,
31 state: Entity<InputState>,
32 action: CodeAction,
33 push_to_history: bool,
34 window: &mut Window,
35 cx: &mut App,
36 ) -> Task<Result<()>>;
37}
38
39impl InputState {
40 pub(crate) fn on_action_toggle_code_actions(
41 &mut self,
42 _: &ToggleCodeActions,
43 window: &mut Window,
44 cx: &mut Context<Self>,
45 ) {
46 self.handle_code_action_trigger(window, cx)
47 }
48
49 pub(crate) fn handle_code_action_trigger(
51 &mut self,
52 window: &mut Window,
53 cx: &mut Context<Self>,
54 ) {
55 let providers = self.lsp.code_action_providers.clone();
56 let menu = match self.context_menu.as_ref() {
57 Some(ContextMenu::CodeAction(menu)) => Some(menu),
58 _ => None,
59 };
60
61 let menu = match menu {
62 Some(menu) => menu.clone(),
63 None => {
64 let menu = CodeActionMenu::new(cx.entity(), window, cx);
65 self.context_menu = Some(ContextMenu::CodeAction(menu.clone()));
66 menu
67 }
68 };
69
70 let range = self.selected_range.start..self.selected_range.end;
71
72 let state = cx.entity();
73 self._context_menu_task = cx.spawn_in(window, async move |editor, cx| {
74 let mut provider_responses = vec![];
75 _ = cx.update(|window, cx| {
76 for provider in providers {
77 let task = provider.code_actions(state.clone(), range.clone(), window, cx);
78 provider_responses.push((provider.id(), task));
79 }
80 });
81
82 let mut code_actions: Vec<CodeActionItem> = vec![];
83 for (provider_id, provider_responses) in provider_responses {
84 if let Some(responses) = provider_responses.await.ok() {
85 code_actions.extend(responses.into_iter().map(|action| CodeActionItem {
86 provider_id: provider_id.clone(),
87 action,
88 }))
89 }
90 }
91
92 if code_actions.is_empty() {
93 _ = menu.update(cx, |menu, cx| {
94 menu.hide(cx);
95 cx.notify();
96 });
97
98 return Ok(());
99 }
100 editor
101 .update_in(cx, |editor, window, cx| {
102 if !editor.focus_handle.is_focused(window) {
103 return;
104 }
105
106 _ = menu.update(cx, |menu, cx| {
107 menu.show(editor.cursor(), code_actions, window, cx);
108 });
109
110 cx.notify();
111 })
112 .ok();
113
114 Ok(())
115 });
116 }
117
118 pub(crate) fn perform_code_action(
119 &mut self,
120 item: &CodeActionItem,
121 window: &mut Window,
122 cx: &mut Context<Self>,
123 ) {
124 let providers = self.lsp.code_action_providers.clone();
125 let Some(provider) = providers
126 .iter()
127 .find(|provider| provider.id() == item.provider_id)
128 else {
129 return;
130 };
131
132 let state = cx.entity();
133 let task = provider.perform_code_action(state, item.action.clone(), true, window, cx);
134
135 cx.spawn_in(window, async move |_, _| {
136 let _ = task.await;
137 })
138 .detach();
139 }
140}