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