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