Skip to main content

gpui_base/input/base/
kind.rs

1//! Compile-time input modes.
2//!
3//! The three input states are one engine seen through a mode marker, one per
4//! state:
5//!
6//! ```ignore
7//! pub type InputState    = InputBaseState<InputMode>;
8//! pub type TextareaState = InputBaseState<TextareaMode>;
9//! pub type EditorState   = InputBaseState<EditorMode>;
10//! ```
11//!
12//! A method that only makes sense for one mode lives in that mode's `impl`
13//! block, so it does not exist on the others: `InputState` has no `auto_grow`
14//! or `soft_wrap`, `TextareaState` has no `masked` or `line_number`, and only
15//! `EditorState` performs code actions. Methods shared by the two multi-line
16//! modes go on [`MultiLineMode`]. Reaching for the wrong one is a compile
17//! error rather than a debug assertion.
18//!
19//! [`super::LayoutMode`] carries the same distinction at runtime, since the
20//! engine branches on it while editing. The marker only decides which API is
21//! reachable.
22
23use std::cell::RefCell;
24use std::rc::Rc;
25
26use gpui::{Div, Entity, Stateful, Window};
27use ropey::Rope;
28
29use super::decorations::DecorationCollections;
30use super::lsp::{ContextMenuContent, HoverDefinition, InlineCompletion};
31use crate::input::{HighlightStyleResolver, InputEdit, InputHighlighter, TextDecoration};
32use crate::input::{HoverPopoverState, Lsp};
33use gpui::Task;
34
35use super::InputBaseState;
36
37/// A single-line text field: the mode of [`crate::input::InputState`].
38pub struct InputMode;
39
40/// Ordinary multi-line text: the mode of [`crate::input::TextareaState`].
41pub struct TextareaMode;
42
43/// Source code, with language features: the mode of [`crate::input::EditorState`].
44pub struct EditorMode;
45
46mod sealed {
47    pub trait Sealed {}
48}
49
50impl sealed::Sealed for InputMode {}
51impl sealed::Sealed for TextareaMode {}
52impl sealed::Sealed for EditorMode {}
53
54/// The modes whose layout spans more than one line: [`TextareaMode`] and
55/// [`EditorMode`].
56///
57/// Soft wrap, wrapping indent and the search session are meaningless in a
58/// single-line field, but shared by the two multi-line modes. Bounding an
59/// `impl` block on this trait puts those methods on both without writing them
60/// twice, and keeps them off [`InputMode`].
61pub trait MultiLineMode: InputModeKind {}
62
63impl MultiLineMode for TextareaMode {}
64impl MultiLineMode for EditorMode {}
65
66/// What the renderer may read out of a mode's extra state.
67///
68/// Kept apart from [`InputModeKind`] on purpose. This trait is *data*: the
69/// renderer is generic over the mode, so it cannot name `EditorExtras` and
70/// reach its fields directly, and these are the accessors it goes through
71/// instead. Every one of them has an empty answer, which is what a plain input
72/// and a textarea give.
73///
74/// [`InputModeKind`] is *behavior*: points where the engine hands control back
75/// during an edit. Adding a field an editor renders belongs here and leaves
76/// the engine's callbacks alone.
77pub trait InputExtras: Default + 'static {
78    /// Decoration ranges to paint, innermost collection first.
79    fn decoration_layers(&self) -> Vec<&[TextDecoration]> {
80        Vec::new()
81    }
82
83    /// Semantic-token styles for a visible range, when an LSP supplies them.
84    fn semantic_token_styles(
85        &self,
86        _text: &Rope,
87        _range: &std::ops::Range<usize>,
88        _resolver: &dyn HighlightStyleResolver,
89    ) -> Vec<(std::ops::Range<usize>, gpui::HighlightStyle)> {
90        Vec::new()
91    }
92
93    /// Document colours to paint as swatches, when an LSP supplies them.
94    fn document_color_swatches(
95        &self,
96        _text: &Rope,
97        _range: &std::ops::Range<usize>,
98    ) -> Vec<(std::ops::Range<usize>, gpui::Hsla)> {
99        Vec::new()
100    }
101
102    /// The symbol range the hover popover is anchored to.
103    fn hover_symbol_range(&self) -> Option<std::ops::Range<usize>> {
104        None
105    }
106
107    /// The inline completion to paint as ghost text.
108    fn inline_completion_item(&self) -> Option<&lsp_types::InlineCompletionItem> {
109        None
110    }
111
112    /// What this mode can offer its context menu: go-to-definition, code actions.
113    fn context_menu_capabilities(&self) -> (bool, bool) {
114        (false, false)
115    }
116}
117
118/// A mode with nothing extra to render.
119impl InputExtras for () {}
120
121/// Hooks the shared engine calls back into for mode-specific work.
122///
123/// The engine's render path is generic over the mode, so it cannot name a
124/// specific state type. This hook is the seam: each implementation is written
125/// for one concrete mode, so inside it `Entity<InputBaseState<Self>>` is that
126/// mode's own state type.
127/// Sealed: the engine branches on a closed set of runtime modes, so the
128/// markers are a closed set too. The three above are all of them.
129pub trait InputModeKind: sealed::Sealed + Sized + 'static {
130    /// Whether this kind of input spans more than one line.
131    ///
132    /// The kind decides this, not the layout: [`super::LayoutMode`] carries
133    /// how many rows to show and how to grow, which is a different question
134    /// from whether the input is a text field or a document. Deriving it from
135    /// the layout let the two disagree — an auto-growing textarea capped at
136    /// one row used to report itself as single-line.
137    const MULTI_LINE: bool;
138
139    /// Whether this kind of input is a source-code editor.
140    const CODE_EDITOR: bool = false;
141
142    /// State only this mode needs.
143    ///
144    /// The engine is shared, but its parts are not: a single-line field has no
145    /// use for an LSP client or a search session, and an editor has no use for
146    /// number stepping. Keeping those here means a form full of text fields
147    /// does not carry an editor's worth of machinery.
148    type Extras: InputExtras;
149
150    /// Drives the syntax highlighter after the text changed.
151    ///
152    /// Only a code editor has one. The engine's edit path is generic over the
153    /// mode, so it dispatches here, where `Self` is concrete and the highlighter
154    /// can be handed this mode's own context.
155    fn drive_highlighter(
156        _highlighter: &Rc<RefCell<Option<Box<dyn InputHighlighter>>>>,
157        _edit: InputEdit,
158        _text: &Rope,
159        _folding: bool,
160        _window: &mut Window,
161        _cx: &mut gpui::Context<InputBaseState<Self>>,
162    ) {
163    }
164
165    /// The range highlighted while Cmd-hovering a symbol, with its style.
166    fn hover_definition_style(
167        _state: &InputBaseState<Self>,
168        _cx: &gpui::App,
169    ) -> Option<(std::ops::Range<usize>, gpui::HighlightStyle)> {
170        None
171    }
172
173    /// The hitbox for Cmd-hover, when the mode supports go-to-definition.
174    fn hover_definition_hitbox(
175        _state: &InputBaseState<Self>,
176        _window: &mut Window,
177        _cx: &gpui::App,
178    ) -> Option<gpui::Hitbox> {
179        None
180    }
181
182    /// Drops cached language-server results, e.g. after the text is replaced.
183    fn reset_language_features(_state: &mut InputBaseState<Self>) {}
184
185    /// Drops decorations and hover state when the text is replaced wholesale.
186    fn reset_annotations(_state: &mut InputBaseState<Self>) {}
187
188    /// Slides decoration ranges along with an edit.
189    fn adjust_annotations(
190        _state: &mut InputBaseState<Self>,
191        _range: &std::ops::Range<usize>,
192        _new_len: usize,
193    ) {
194    }
195
196    /// Refreshes language-server state after the text changed.
197    fn refresh_language_features(
198        _state: &mut InputBaseState<Self>,
199        _window: &mut Window,
200        _cx: &mut gpui::Context<InputBaseState<Self>>,
201    ) {
202    }
203
204    /// Takes the pending inline completion, when Tab should accept it.
205    fn accept_inline_completion(
206        _state: &mut InputBaseState<Self>,
207        _window: &mut Window,
208        _cx: &mut gpui::Context<InputBaseState<Self>>,
209    ) -> bool {
210        false
211    }
212
213    /// Whether an inline completion is waiting to be accepted.
214    fn has_inline_completion(_state: &InputBaseState<Self>) -> bool {
215        false
216    }
217
218    /// Reacts to a click, for Cmd-click go-to-definition.
219    fn on_click(
220        _state: &mut InputBaseState<Self>,
221        _event: &gpui::MouseDownEvent,
222        _offset: usize,
223        _window: &mut Window,
224        _cx: &mut gpui::Context<InputBaseState<Self>>,
225    ) -> bool {
226        false
227    }
228
229    /// Drops hover state when the pointer leaves or focus moves.
230    fn clear_hover_state(
231        _state: &mut InputBaseState<Self>,
232        _cx: &mut gpui::Context<InputBaseState<Self>>,
233    ) {
234    }
235
236    /// Offers freshly typed text to the completion engine.
237    fn on_text_typed(
238        _state: &mut InputBaseState<Self>,
239        _range: &std::ops::Range<usize>,
240        _text: &str,
241        _window: &mut Window,
242        _cx: &mut gpui::Context<InputBaseState<Self>>,
243    ) {
244    }
245
246    /// Drops any inline completion after the text or cursor moved.
247    fn clear_inline_completion(
248        _state: &mut InputBaseState<Self>,
249        _cx: &mut gpui::Context<InputBaseState<Self>>,
250    ) {
251    }
252
253    /// Closes any open completion or code-action menu.
254    fn hide_context_menu(
255        _state: &mut InputBaseState<Self>,
256        _cx: &mut gpui::Context<InputBaseState<Self>>,
257    ) {
258    }
259
260    /// Whether a completion or code-action menu is currently open.
261    fn is_context_menu_open(_state: &InputBaseState<Self>, _cx: &gpui::App) -> bool {
262        false
263    }
264
265    /// Lets an open menu consume the action first. Returns true when it did.
266    fn handle_context_menu_action(
267        _state: &mut InputBaseState<Self>,
268        _action: Box<dyn gpui::Action>,
269        _window: &mut Window,
270        _cx: &mut gpui::Context<InputBaseState<Self>>,
271    ) -> bool {
272        false
273    }
274
275    /// Highlights the symbol under the pointer for go-to-definition.
276    ///
277    /// Separate from [`Self::on_mouse_move`]: this runs on paths that have no
278    /// mouse event to hand over, such as opening the context menu.
279    fn on_hover_definition(
280        _state: &mut InputBaseState<Self>,
281        _offset: usize,
282        _window: &mut Window,
283        _cx: &mut gpui::Context<InputBaseState<Self>>,
284    ) {
285    }
286
287    /// Reacts to the pointer moving, for the hover popover.
288    fn on_mouse_move(
289        _state: &mut InputBaseState<Self>,
290        _offset: usize,
291        _event: &gpui::MouseMoveEvent,
292        _window: &mut Window,
293        _cx: &mut gpui::Context<InputBaseState<Self>>,
294    ) {
295    }
296
297    /// Registers the actions that only this mode handles.
298    fn register_actions(
299        element: Stateful<Div>,
300        _entity: &Entity<InputBaseState<Self>>,
301        _window: &mut Window,
302    ) -> Stateful<Div> {
303        element
304    }
305}
306
307impl InputModeKind for InputMode {
308    const MULTI_LINE: bool = false;
309
310    /// A single-line field needs nothing beyond the shared engine. Masking,
311    /// validation and number stepping live there: together they are ~120 bytes
312    /// and their access sites sit inside the shared edit path, so separating
313    /// them would cost more in dispatch than it saves.
314    type Extras = ();
315}
316impl InputModeKind for TextareaMode {
317    const MULTI_LINE: bool = true;
318
319    /// Ordinary multi-line text needs nothing beyond the shared engine.
320    type Extras = ();
321}
322// `EditorMode`'s implementation lives with the editor code, next to the
323// language features it dispatches to.
324
325/// What a code editor adds on top of multi-line text: language features.
326pub struct EditorExtras {
327    pub(crate) lsp: Lsp,
328    pub(crate) decorations: DecorationCollections,
329    pub(crate) inline_completion: InlineCompletion,
330    pub(crate) context_menu_content: ContextMenuContent,
331    pub(crate) hover_popover: Option<HoverPopoverState>,
332    pub(crate) hover_definition: HoverDefinition,
333    pub(crate) context_menu_task: Task<anyhow::Result<()>>,
334}
335
336impl Default for EditorExtras {
337    fn default() -> Self {
338        Self {
339            lsp: Lsp::default(),
340            decorations: DecorationCollections::default(),
341            inline_completion: InlineCompletion::default(),
342            context_menu_content: ContextMenuContent::default(),
343            hover_popover: None,
344            hover_definition: HoverDefinition::default(),
345            context_menu_task: Task::ready(Ok(())),
346        }
347    }
348}