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();
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                    // The overlay rebuilds on a new revision, so a refresh
104                    // while the menu is already open must bump it too.
105                    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}