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