Skip to main content

gpui_base/input/editor/
mod.rs

1use gpui::{App, Div, Entity, InteractiveElement as _, IntoElement, RenderOnce, Stateful, Window};
2
3use super::{EditorMode, InputBaseState, InputModeKind};
4
5/// State for source-code editing.
6///
7/// This is the shared editing engine in its code-editor kind. Languages, line
8/// numbers, folding, indent guides, diagnostics, decorations, and the LSP
9/// providers exist on this kind only, so an ordinary input or textarea never
10/// exposes them.
11pub type EditorState = InputBaseState<EditorMode>;
12
13impl InputModeKind for EditorMode {
14    const MULTI_LINE: bool = true;
15    const CODE_EDITOR: bool = true;
16
17    type Extras = super::EditorExtras;
18
19    fn hover_definition_style(
20        state: &InputBaseState<Self>,
21        _cx: &App,
22    ) -> Option<(std::ops::Range<usize>, gpui::HighlightStyle)> {
23        state.hover_definition_style()
24    }
25
26    fn hover_definition_hitbox(
27        state: &InputBaseState<Self>,
28        window: &mut Window,
29        _cx: &App,
30    ) -> Option<gpui::Hitbox> {
31        state.hover_definition_hitbox(window)
32    }
33
34    fn reset_language_features(state: &mut InputBaseState<Self>) {
35        state.extras.lsp.reset();
36    }
37
38    fn reset_annotations(state: &mut InputBaseState<Self>) {
39        state.extras.hover_popover = None;
40        state.extras.decorations.clear();
41        state.extras.range_decorations.clear();
42    }
43
44    fn editing_syntax_context(state: &InputBaseState<Self>, offset: usize) -> super::SyntaxContext {
45        state.syntax_context_at(offset)
46    }
47
48    fn adjust_annotations(
49        state: &mut InputBaseState<Self>,
50        range: &std::ops::Range<usize>,
51        new_len: usize,
52    ) {
53        state.extras.decorations.adjust_for_edit(range, new_len);
54        state
55            .extras
56            .range_decorations
57            .adjust_for_edit(range, new_len);
58    }
59
60    fn refresh_language_features(
61        state: &mut InputBaseState<Self>,
62        window: &mut Window,
63        cx: &mut gpui::Context<InputBaseState<Self>>,
64    ) {
65        let text = state.text().clone();
66        state.extras.lsp.update(&text, window, cx);
67    }
68
69    fn accept_inline_completion(
70        state: &mut InputBaseState<Self>,
71        window: &mut Window,
72        cx: &mut gpui::Context<InputBaseState<Self>>,
73    ) -> bool {
74        state.accept_inline_completion(window, cx)
75    }
76
77    fn has_inline_completion(state: &InputBaseState<Self>) -> bool {
78        state.has_inline_completion()
79    }
80
81    fn on_click(
82        state: &mut InputBaseState<Self>,
83        event: &gpui::MouseDownEvent,
84        offset: usize,
85        window: &mut Window,
86        cx: &mut gpui::Context<InputBaseState<Self>>,
87    ) -> bool {
88        state.handle_click_hover_definition(event, offset, window, cx)
89    }
90
91    fn clear_hover_state(
92        state: &mut InputBaseState<Self>,
93        cx: &mut gpui::Context<InputBaseState<Self>>,
94    ) {
95        state.clear_hover_state(cx);
96    }
97
98    fn on_text_typed(
99        state: &mut InputBaseState<Self>,
100        range: &std::ops::Range<usize>,
101        text: &str,
102        window: &mut Window,
103        cx: &mut gpui::Context<InputBaseState<Self>>,
104    ) {
105        state.handle_completion_trigger(range, text, window, cx);
106    }
107
108    fn clear_inline_completion(
109        state: &mut InputBaseState<Self>,
110        cx: &mut gpui::Context<InputBaseState<Self>>,
111    ) {
112        state.clear_inline_completion(cx);
113    }
114
115    fn hide_context_menu(
116        state: &mut InputBaseState<Self>,
117        cx: &mut gpui::Context<InputBaseState<Self>>,
118    ) {
119        state.hide_context_menu(cx);
120    }
121
122    fn is_context_menu_open(state: &InputBaseState<Self>, cx: &App) -> bool {
123        state.is_context_menu_open(cx)
124    }
125
126    fn handle_context_menu_action(
127        state: &mut InputBaseState<Self>,
128        action: Box<dyn gpui::Action>,
129        window: &mut Window,
130        cx: &mut gpui::Context<InputBaseState<Self>>,
131    ) -> bool {
132        state.handle_action_for_context_menu(action, window, cx)
133    }
134
135    fn on_hover_definition(
136        state: &mut InputBaseState<Self>,
137        offset: usize,
138        window: &mut Window,
139        cx: &mut gpui::Context<InputBaseState<Self>>,
140    ) {
141        state.handle_hover_definition(offset, window, cx);
142    }
143
144    fn on_mouse_move(
145        state: &mut InputBaseState<Self>,
146        offset: usize,
147        event: &gpui::MouseMoveEvent,
148        window: &mut Window,
149        cx: &mut gpui::Context<InputBaseState<Self>>,
150    ) {
151        state.handle_mouse_move(offset, event, window, cx);
152    }
153
154    fn drive_highlighter(
155        highlighter: &std::rc::Rc<std::cell::RefCell<Option<Box<dyn super::InputHighlighter>>>>,
156        edit: super::InputEdit,
157        text: &ropey::Rope,
158        folding: bool,
159        window: &mut Window,
160        cx: &mut gpui::Context<InputBaseState<Self>>,
161    ) {
162        let mut highlighter = highlighter.borrow_mut();
163        let Some(highlighter) = highlighter.as_mut() else {
164            return;
165        };
166        highlighter.update(Some(edit), text, folding, window, cx);
167    }
168
169    fn drive_highlighter_batch(
170        highlighter: &std::rc::Rc<std::cell::RefCell<Option<Box<dyn super::InputHighlighter>>>>,
171        edits: &[(super::InputEdit, ropey::Rope)],
172        folding: bool,
173        window: &mut Window,
174        cx: &mut gpui::Context<InputBaseState<Self>>,
175    ) {
176        let mut highlighter = highlighter.borrow_mut();
177        let Some(highlighter) = highlighter.as_mut() else {
178            return;
179        };
180        highlighter.update_batch(edits, folding, window, cx);
181    }
182
183    fn register_actions(
184        element: Stateful<Div>,
185        entity: &Entity<InputBaseState<Self>>,
186        window: &mut Window,
187    ) -> Stateful<Div> {
188        element
189            .key_context("Input mode=editor")
190            .on_action(window.listener_for(entity, InputBaseState::on_action_toggle_code_actions))
191            .on_action(window.listener_for(entity, InputBaseState::on_action_show_completions))
192            .on_action(window.listener_for(entity, InputBaseState::on_action_go_to_definition))
193    }
194}
195
196impl EditorState {
197    /// The LSP providers and their cached results.
198    ///
199    /// This exists on the editor alone: an ordinary input or textarea has no
200    /// language server, and no field to reach one through.
201    pub fn lsp(&self) -> &super::Lsp {
202        &self.extras.lsp
203    }
204
205    /// The LSP providers, mutably. Configure the providers through this.
206    pub fn lsp_mut(&mut self) -> &mut super::Lsp {
207        &mut self.extras.lsp
208    }
209
210    /// Syntax context at `offset`, or `Code` when the language has no provider.
211    pub(crate) fn syntax_context_at(&self, offset: usize) -> super::SyntaxContext {
212        self.mode.syntax_context_at(&self.text, offset)
213    }
214}
215
216/// An unstyled source-code editor.
217#[derive(IntoElement)]
218pub struct Editor {
219    state: Entity<EditorState>,
220}
221
222impl Editor {
223    pub fn new(state: &Entity<EditorState>) -> Self {
224        Self {
225            state: state.clone(),
226        }
227    }
228}
229
230impl RenderOnce for Editor {
231    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
232        self.state
233    }
234}
235
236/// What a code editor exposes to the renderer. See [`crate::input::InputExtras`].
237impl crate::input::InputExtras for super::EditorExtras {
238    fn decoration_layers(&self) -> Vec<&[super::TextDecoration]> {
239        self.decorations.iter().collect()
240    }
241
242    fn range_decorations(&self, ranges: &[std::ops::Range<usize>]) -> Vec<&super::RangeDecoration> {
243        self.range_decorations.intersecting(ranges)
244    }
245
246    fn semantic_token_styles(
247        &self,
248        text: &ropey::Rope,
249        range: &std::ops::Range<usize>,
250        resolver: &dyn crate::input::HighlightStyleResolver,
251    ) -> Vec<(std::ops::Range<usize>, gpui::HighlightStyle)> {
252        self.lsp.semantic_tokens_for_range(text, range, resolver)
253    }
254
255    fn document_color_swatches(
256        &self,
257        text: &ropey::Rope,
258        range: &std::ops::Range<usize>,
259    ) -> Vec<(std::ops::Range<usize>, gpui::Hsla)> {
260        self.lsp.document_colors_for_range(text, range)
261    }
262
263    fn hover_symbol_range(&self) -> Option<std::ops::Range<usize>> {
264        self.hover_popover
265            .as_ref()
266            .map(|session| session.symbol_range.clone())
267    }
268
269    fn inline_completion_item(&self) -> Option<&lsp_types::InlineCompletionItem> {
270        self.inline_completion.item.as_ref()
271    }
272
273    fn context_menu_capabilities(&self) -> (bool, bool) {
274        (
275            self.lsp.definition_provider.is_some(),
276            !self.lsp.code_action_providers.is_empty(),
277        )
278    }
279}