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    }
42
43    fn editing_syntax_context(state: &InputBaseState<Self>, offset: usize) -> super::SyntaxContext {
44        state.syntax_context_at(offset)
45    }
46
47    fn adjust_annotations(
48        state: &mut InputBaseState<Self>,
49        range: &std::ops::Range<usize>,
50        new_len: usize,
51    ) {
52        state.extras.decorations.adjust_for_edit(range, new_len);
53    }
54
55    fn refresh_language_features(
56        state: &mut InputBaseState<Self>,
57        window: &mut Window,
58        cx: &mut gpui::Context<InputBaseState<Self>>,
59    ) {
60        let text = state.text().clone();
61        state.extras.lsp.update(&text, window, cx);
62    }
63
64    fn accept_inline_completion(
65        state: &mut InputBaseState<Self>,
66        window: &mut Window,
67        cx: &mut gpui::Context<InputBaseState<Self>>,
68    ) -> bool {
69        state.accept_inline_completion(window, cx)
70    }
71
72    fn has_inline_completion(state: &InputBaseState<Self>) -> bool {
73        state.has_inline_completion()
74    }
75
76    fn on_click(
77        state: &mut InputBaseState<Self>,
78        event: &gpui::MouseDownEvent,
79        offset: usize,
80        window: &mut Window,
81        cx: &mut gpui::Context<InputBaseState<Self>>,
82    ) -> bool {
83        state.handle_click_hover_definition(event, offset, window, cx)
84    }
85
86    fn clear_hover_state(
87        state: &mut InputBaseState<Self>,
88        cx: &mut gpui::Context<InputBaseState<Self>>,
89    ) {
90        state.clear_hover_state(cx);
91    }
92
93    fn on_text_typed(
94        state: &mut InputBaseState<Self>,
95        range: &std::ops::Range<usize>,
96        text: &str,
97        window: &mut Window,
98        cx: &mut gpui::Context<InputBaseState<Self>>,
99    ) {
100        state.handle_completion_trigger(range, text, window, cx);
101    }
102
103    fn clear_inline_completion(
104        state: &mut InputBaseState<Self>,
105        cx: &mut gpui::Context<InputBaseState<Self>>,
106    ) {
107        state.clear_inline_completion(cx);
108    }
109
110    fn hide_context_menu(
111        state: &mut InputBaseState<Self>,
112        cx: &mut gpui::Context<InputBaseState<Self>>,
113    ) {
114        state.hide_context_menu(cx);
115    }
116
117    fn is_context_menu_open(state: &InputBaseState<Self>, cx: &App) -> bool {
118        state.is_context_menu_open(cx)
119    }
120
121    fn handle_context_menu_action(
122        state: &mut InputBaseState<Self>,
123        action: Box<dyn gpui::Action>,
124        window: &mut Window,
125        cx: &mut gpui::Context<InputBaseState<Self>>,
126    ) -> bool {
127        state.handle_action_for_context_menu(action, window, cx)
128    }
129
130    fn on_hover_definition(
131        state: &mut InputBaseState<Self>,
132        offset: usize,
133        window: &mut Window,
134        cx: &mut gpui::Context<InputBaseState<Self>>,
135    ) {
136        state.handle_hover_definition(offset, window, cx);
137    }
138
139    fn on_mouse_move(
140        state: &mut InputBaseState<Self>,
141        offset: usize,
142        event: &gpui::MouseMoveEvent,
143        window: &mut Window,
144        cx: &mut gpui::Context<InputBaseState<Self>>,
145    ) {
146        state.handle_mouse_move(offset, event, window, cx);
147    }
148
149    fn drive_highlighter(
150        highlighter: &std::rc::Rc<std::cell::RefCell<Option<Box<dyn super::InputHighlighter>>>>,
151        edit: super::InputEdit,
152        text: &ropey::Rope,
153        folding: bool,
154        window: &mut Window,
155        cx: &mut gpui::Context<InputBaseState<Self>>,
156    ) {
157        let mut highlighter = highlighter.borrow_mut();
158        let Some(highlighter) = highlighter.as_mut() else {
159            return;
160        };
161        highlighter.update(Some(edit), text, folding, window, cx);
162    }
163
164    fn register_actions(
165        element: Stateful<Div>,
166        entity: &Entity<InputBaseState<Self>>,
167        window: &mut Window,
168    ) -> Stateful<Div> {
169        element
170            .on_action(window.listener_for(entity, InputBaseState::on_action_toggle_code_actions))
171            .on_action(window.listener_for(entity, InputBaseState::on_action_go_to_definition))
172    }
173}
174
175impl EditorState {
176    /// The LSP providers and their cached results.
177    ///
178    /// This exists on the editor alone: an ordinary input or textarea has no
179    /// language server, and no field to reach one through.
180    pub fn lsp(&self) -> &super::Lsp {
181        &self.extras.lsp
182    }
183
184    /// The LSP providers, mutably. Configure the providers through this.
185    pub fn lsp_mut(&mut self) -> &mut super::Lsp {
186        &mut self.extras.lsp
187    }
188
189    /// Syntax context at `offset`, or `Code` when the language has no provider.
190    pub(crate) fn syntax_context_at(&self, offset: usize) -> super::SyntaxContext {
191        self.mode.syntax_context_at(&self.text, offset)
192    }
193}
194
195/// An unstyled source-code editor.
196#[derive(IntoElement)]
197pub struct Editor {
198    state: Entity<EditorState>,
199}
200
201impl Editor {
202    pub fn new(state: &Entity<EditorState>) -> Self {
203        Self {
204            state: state.clone(),
205        }
206    }
207}
208
209impl RenderOnce for Editor {
210    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
211        self.state
212    }
213}
214
215/// What a code editor exposes to the renderer. See [`crate::input::InputExtras`].
216impl crate::input::InputExtras for super::EditorExtras {
217    fn decoration_layers(&self) -> Vec<&[super::TextDecoration]> {
218        self.decorations.iter().collect()
219    }
220
221    fn semantic_token_styles(
222        &self,
223        text: &ropey::Rope,
224        range: &std::ops::Range<usize>,
225        resolver: &dyn crate::input::HighlightStyleResolver,
226    ) -> Vec<(std::ops::Range<usize>, gpui::HighlightStyle)> {
227        self.lsp.semantic_tokens_for_range(text, range, resolver)
228    }
229
230    fn document_color_swatches(
231        &self,
232        text: &ropey::Rope,
233        range: &std::ops::Range<usize>,
234    ) -> Vec<(std::ops::Range<usize>, gpui::Hsla)> {
235        self.lsp.document_colors_for_range(text, range)
236    }
237
238    fn hover_symbol_range(&self) -> Option<std::ops::Range<usize>> {
239        self.hover_popover
240            .as_ref()
241            .map(|session| session.symbol_range.clone())
242    }
243
244    fn inline_completion_item(&self) -> Option<&lsp_types::InlineCompletionItem> {
245        self.inline_completion.item.as_ref()
246    }
247
248    fn context_menu_capabilities(&self) -> (bool, bool) {
249        (
250            self.lsp.definition_provider.is_some(),
251            !self.lsp.code_action_providers.is_empty(),
252        )
253    }
254}