Skip to main content

gpui_base/input/editor/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::{EditorMode, EditorState, InputBaseState, ToggleCodeActions};
7
8pub trait CodeActionProvider {
9    /// The id for this CodeAction.
10    fn id(&self) -> SharedString;
11
12    /// Fetches code actions for the specified range.
13    ///
14    /// textDocument/codeAction
15    ///
16    /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction
17    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    /// Performs the specified code action.
26    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    /// Show code actions for the cursor.
53    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}