gpui_component/input/lsp/
code_actions.rs

1use 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    /// The id for this CodeAction.
13    fn id(&self) -> SharedString;
14
15    /// Fetches code actions for the specified range.
16    ///
17    /// textDocument/codeAction
18    ///
19    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction
20    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    /// Performs the specified code action.
29    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    /// Show code actions for the cursor.
50    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}