1use gpui::TextAlign;
6use gpui::{
7 Action, App, AppContext, Bounds, ClipboardItem, Context, Edges, Entity, EntityInputHandler,
8 EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyBinding,
9 KeyDownEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _,
10 Pixels, Point, Render, ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription,
11 UTF16Selection, Window, actions, div, point, prelude::FluentBuilder as _, px,
12};
13use ropey::{Rope, RopeSlice};
14use serde::Deserialize;
15use std::borrow::Cow;
16use std::cell::Cell;
17use std::ops::Range;
18use std::rc::Rc;
19use sum_tree::Bias;
20use unicode_segmentation::*;
21
22use super::{
23 DiagnosticSet, DisplayMap, InputContextMenuCapabilities, InputEditorStyle,
24 InputHighlighterFactory, MASK_CHAR, MaskPattern, NativeMenu, NumberStep, WrappingIndent,
25 blink_cursor::BlinkCursor,
26 change::Change,
27 element::{EditorScrollbar, EditorScrollbarSnapshot, TextElement},
28 kind::InputModeKind,
29 mask_pattern::normalize_number_input,
30 mode::LayoutMode,
31 undo_manager::{EditIntent, UndoManager},
32};
33use crate::actions::{SelectDown, SelectLeft, SelectRight, SelectUp};
34use crate::input::blink_cursor::CURSOR_WIDTH;
35use crate::input::movement::MoveDirection;
36use crate::input::{
37 InputExtras as _, Position, RopeExt as _, Selection, element::RIGHT_MARGIN, layout::LastLayout,
38};
39use crate::{AutoScroll, StepAction};
40
41#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
42#[action(namespace = input, no_json)]
43pub struct Enter {
44 pub secondary: bool,
46 pub shift: bool,
48}
49
50impl Enter {
51 pub fn is_primary(action: &dyn Action) -> bool {
54 action.partial_eq(&Enter {
55 secondary: false,
56 shift: false,
57 }) || action.partial_eq(&Enter {
58 secondary: false,
59 shift: true,
60 })
61 }
62}
63
64actions!(
65 input,
66 [
67 Backspace,
68 Delete,
69 DeleteToBeginningOfLine,
70 DeleteToEndOfLine,
71 DeleteToPreviousWordStart,
72 DeleteToNextWordEnd,
73 Indent,
74 Outdent,
75 IndentInline,
76 OutdentInline,
77 MoveUp,
78 MoveDown,
79 MoveLeft,
80 MoveRight,
81 MoveHome,
82 MoveEnd,
83 MovePageUp,
84 MovePageDown,
85 SelectAll,
86 SelectToStartOfLine,
87 SelectToEndOfLine,
88 SelectToStart,
89 SelectToEnd,
90 SelectToPreviousWordStart,
91 SelectToNextWordEnd,
92 ShowCharacterPalette,
93 Copy,
94 Cut,
95 Paste,
96 Undo,
97 Redo,
98 MoveToStartOfLine,
99 MoveToEndOfLine,
100 MoveToStart,
101 MoveToEnd,
102 MoveToPreviousWord,
103 MoveToNextWord,
104 Escape,
105 ToggleCodeActions,
106 Search,
107 Replace,
108 GoToDefinition,
109 ]
110);
111
112#[derive(Clone)]
113pub enum InputEvent {
114 Change,
115 PressEnter { secondary: bool, shift: bool },
116 Focus,
117 Blur,
118}
119
120pub(super) const CONTEXT: &str = "Input";
121
122pub(crate) fn init(cx: &mut App) {
123 cx.bind_keys([
124 KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
125 KeyBinding::new("shift-backspace", Backspace, Some(CONTEXT)),
126 #[cfg(target_os = "macos")]
127 KeyBinding::new("ctrl-backspace", Backspace, Some(CONTEXT)),
128 KeyBinding::new("delete", Delete, Some(CONTEXT)),
129 KeyBinding::new("shift-delete", Delete, Some(CONTEXT)),
130 #[cfg(target_os = "macos")]
131 KeyBinding::new("cmd-backspace", DeleteToBeginningOfLine, Some(CONTEXT)),
132 #[cfg(target_os = "macos")]
133 KeyBinding::new("cmd-delete", DeleteToEndOfLine, Some(CONTEXT)),
134 #[cfg(target_os = "macos")]
135 KeyBinding::new("alt-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
136 #[cfg(not(target_os = "macos"))]
137 KeyBinding::new("ctrl-backspace", DeleteToPreviousWordStart, Some(CONTEXT)),
138 #[cfg(target_os = "macos")]
139 KeyBinding::new("alt-delete", DeleteToNextWordEnd, Some(CONTEXT)),
140 #[cfg(not(target_os = "macos"))]
141 KeyBinding::new("ctrl-delete", DeleteToNextWordEnd, Some(CONTEXT)),
142 KeyBinding::new(
143 "enter",
144 Enter {
145 secondary: false,
146 shift: false,
147 },
148 Some(CONTEXT),
149 ),
150 KeyBinding::new(
151 "shift-enter",
152 Enter {
153 secondary: false,
154 shift: true,
155 },
156 Some(CONTEXT),
157 ),
158 KeyBinding::new(
159 "secondary-enter",
160 Enter {
161 secondary: true,
162 shift: false,
163 },
164 Some(CONTEXT),
165 ),
166 KeyBinding::new("escape", Escape, Some(CONTEXT)),
167 KeyBinding::new("up", MoveUp, Some(CONTEXT)),
168 KeyBinding::new("down", MoveDown, Some(CONTEXT)),
169 KeyBinding::new("left", MoveLeft, Some(CONTEXT)),
170 KeyBinding::new("right", MoveRight, Some(CONTEXT)),
171 KeyBinding::new("pageup", MovePageUp, Some(CONTEXT)),
172 KeyBinding::new("pagedown", MovePageDown, Some(CONTEXT)),
173 KeyBinding::new("tab", IndentInline, Some(CONTEXT)),
174 KeyBinding::new("shift-tab", OutdentInline, Some(CONTEXT)),
175 #[cfg(target_os = "macos")]
176 KeyBinding::new("cmd-]", Indent, Some(CONTEXT)),
177 #[cfg(not(target_os = "macos"))]
178 KeyBinding::new("ctrl-]", Indent, Some(CONTEXT)),
179 #[cfg(target_os = "macos")]
180 KeyBinding::new("cmd-[", Outdent, Some(CONTEXT)),
181 #[cfg(not(target_os = "macos"))]
182 KeyBinding::new("ctrl-[", Outdent, Some(CONTEXT)),
183 KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
184 KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
185 KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
186 KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
187 KeyBinding::new("home", MoveHome, Some(CONTEXT)),
188 KeyBinding::new("end", MoveEnd, Some(CONTEXT)),
189 KeyBinding::new("shift-home", SelectToStartOfLine, Some(CONTEXT)),
190 KeyBinding::new("shift-end", SelectToEndOfLine, Some(CONTEXT)),
191 #[cfg(target_os = "macos")]
192 KeyBinding::new("ctrl-shift-a", SelectToStartOfLine, Some(CONTEXT)),
193 #[cfg(target_os = "macos")]
194 KeyBinding::new("ctrl-shift-e", SelectToEndOfLine, Some(CONTEXT)),
195 #[cfg(target_os = "macos")]
196 KeyBinding::new("shift-cmd-left", SelectToStartOfLine, Some(CONTEXT)),
197 #[cfg(target_os = "macos")]
198 KeyBinding::new("shift-cmd-right", SelectToEndOfLine, Some(CONTEXT)),
199 #[cfg(target_os = "macos")]
200 KeyBinding::new("alt-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
201 #[cfg(not(target_os = "macos"))]
202 KeyBinding::new("ctrl-shift-left", SelectToPreviousWordStart, Some(CONTEXT)),
203 #[cfg(target_os = "macos")]
204 KeyBinding::new("alt-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
205 #[cfg(not(target_os = "macos"))]
206 KeyBinding::new("ctrl-shift-right", SelectToNextWordEnd, Some(CONTEXT)),
207 #[cfg(target_os = "macos")]
208 KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
209 #[cfg(target_os = "macos")]
210 KeyBinding::new("cmd-a", SelectAll, Some(CONTEXT)),
211 #[cfg(not(target_os = "macos"))]
212 KeyBinding::new("ctrl-a", SelectAll, Some(CONTEXT)),
213 #[cfg(target_os = "macos")]
214 KeyBinding::new("cmd-c", Copy, Some(CONTEXT)),
215 #[cfg(not(target_os = "macos"))]
216 KeyBinding::new("ctrl-c", Copy, Some(CONTEXT)),
217 #[cfg(target_os = "macos")]
218 KeyBinding::new("cmd-x", Cut, Some(CONTEXT)),
219 #[cfg(not(target_os = "macos"))]
220 KeyBinding::new("ctrl-x", Cut, Some(CONTEXT)),
221 #[cfg(target_os = "macos")]
222 KeyBinding::new("cmd-v", Paste, Some(CONTEXT)),
223 #[cfg(not(target_os = "macos"))]
224 KeyBinding::new("ctrl-v", Paste, Some(CONTEXT)),
225 #[cfg(target_os = "macos")]
226 KeyBinding::new("ctrl-a", MoveHome, Some(CONTEXT)),
227 #[cfg(target_os = "macos")]
228 KeyBinding::new("cmd-left", MoveHome, Some(CONTEXT)),
229 #[cfg(target_os = "macos")]
230 KeyBinding::new("ctrl-e", MoveEnd, Some(CONTEXT)),
231 #[cfg(target_os = "macos")]
232 KeyBinding::new("cmd-right", MoveEnd, Some(CONTEXT)),
233 #[cfg(target_os = "macos")]
234 KeyBinding::new("cmd-z", Undo, Some(CONTEXT)),
235 #[cfg(target_os = "macos")]
236 KeyBinding::new("cmd-shift-z", Redo, Some(CONTEXT)),
237 #[cfg(target_os = "macos")]
238 KeyBinding::new("cmd-up", MoveToStart, Some(CONTEXT)),
239 #[cfg(target_os = "macos")]
240 KeyBinding::new("cmd-down", MoveToEnd, Some(CONTEXT)),
241 #[cfg(target_os = "macos")]
242 KeyBinding::new("alt-left", MoveToPreviousWord, Some(CONTEXT)),
243 #[cfg(target_os = "macos")]
244 KeyBinding::new("alt-right", MoveToNextWord, Some(CONTEXT)),
245 #[cfg(not(target_os = "macos"))]
246 KeyBinding::new("ctrl-left", MoveToPreviousWord, Some(CONTEXT)),
247 #[cfg(not(target_os = "macos"))]
248 KeyBinding::new("ctrl-right", MoveToNextWord, Some(CONTEXT)),
249 #[cfg(target_os = "macos")]
250 KeyBinding::new("cmd-shift-up", SelectToStart, Some(CONTEXT)),
251 #[cfg(target_os = "macos")]
252 KeyBinding::new("cmd-shift-down", SelectToEnd, Some(CONTEXT)),
253 #[cfg(not(target_os = "macos"))]
254 KeyBinding::new("ctrl-z", Undo, Some(CONTEXT)),
255 #[cfg(not(target_os = "macos"))]
256 KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)),
257 #[cfg(target_os = "macos")]
258 KeyBinding::new("cmd-.", ToggleCodeActions, Some(CONTEXT)),
259 #[cfg(not(target_os = "macos"))]
260 KeyBinding::new("ctrl-.", ToggleCodeActions, Some(CONTEXT)),
261 #[cfg(target_os = "macos")]
262 KeyBinding::new("cmd-f", Search, Some(CONTEXT)),
263 #[cfg(not(target_os = "macos"))]
264 KeyBinding::new("ctrl-f", Search, Some(CONTEXT)),
265 #[cfg(target_os = "macos")]
266 KeyBinding::new("cmd-shift-f", Replace, Some(CONTEXT)),
267 #[cfg(not(target_os = "macos"))]
268 KeyBinding::new("ctrl-h", Replace, Some(CONTEXT)),
269 ]);
270}
271
272pub struct InputBaseState<M: InputModeKind> {
283 pub(crate) extras: M::Extras,
285 pub(super) focus_handle: FocusHandle,
286 pub(super) mode: LayoutMode,
287 pub(super) text: Rope,
288 pub(super) display_map: DisplayMap,
289 pub(super) undo_manager: UndoManager,
290 pub(super) search_session: super::SearchSession,
291 pub(super) searchable: bool,
292 pub(super) replaceable: bool,
293 pub(super) soft_wrap: bool,
294 pub(super) wrapping_indent: WrappingIndent,
295 pub(super) scroll_beyond_last_line: Option<usize>,
296 pub(super) cursor_surrounding_lines: Option<usize>,
297 pub(super) blink_cursor: Entity<BlinkCursor>,
298 pub(super) loading: bool,
299 pub(super) selected_range: Selection,
304 pub(super) selected_word_range: Option<Selection>,
306 pub(super) selection_reversed: bool,
307 pub(super) ime_marked_range: Option<Selection>,
309 pub(super) last_layout: Option<LastLayout>,
310 pub(super) last_cursor: Option<usize>,
311 pub(super) input_bounds: Bounds<Pixels>,
313 pub(super) last_bounds: Option<Bounds<Pixels>>,
315 pub(super) last_selected_range: Option<Selection>,
316 pub(super) selecting: bool,
317 pub(crate) disabled: bool,
318 pub(crate) readonly: bool,
319 pub(crate) text_align: TextAlign,
320 pub(super) masked: bool,
321 pub(super) clean_on_escape: bool,
322 pub(super) submit_on_enter: bool,
323 pub(super) show_whitespaces: bool,
324 pub(crate) cursor_line_end_affinity: bool,
326 pub(super) pattern: Option<regex::Regex>,
327 pub(super) validate: Option<Box<dyn Fn(&str, &mut App) -> bool + 'static>>,
328 pub(crate) number_step: Option<NumberStep>,
331 pub(crate) number_min: Option<f64>,
333 pub(crate) number_max: Option<f64>,
335 pub(crate) scroll_handle: ScrollHandle,
336 pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
338 pub(crate) scroll_size: gpui::Size<Pixels>,
340 pub(super) editor_scrollbar_snapshot: Cell<Option<EditorScrollbarSnapshot>>,
341 pub(super) editor_paddings: Edges<Pixels>,
342 pub(super) editor_style: InputEditorStyle,
347 projected_editor_style: InputEditorStyle,
352
353 pub(crate) mask_pattern: MaskPattern,
355 pub(super) mask_pattern_set: bool,
359 pub(super) placeholder: SharedString,
360
361 pub(super) diagnostic_popover: Option<Rc<crate::input::DiagnosticEntry>>,
363
364 context_menu_handler: Option<
365 Rc<dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App)>,
366 >,
367 pending_context_menu: Option<(Point<Pixels>, usize)>,
368
369 pub(super) enable_context_menu: bool,
372
373 pub(super) completion_inserting: bool,
375 pub(super) overlay_action_handler: Option<
376 Rc<
377 dyn Fn(
378 super::InputOverlayKind,
379 Box<dyn Action>,
380 &mut Window,
381 &mut Context<InputBaseState<M>>,
382 ) -> bool,
383 >,
384 >,
385
386 _pending_update: bool,
390 pub(super) silent_replace_text: bool,
392 pub(super) emit_events: bool,
394
395 pub(super) preferred_column: Option<(Pixels, usize)>,
400 _subscriptions: Vec<Subscription>,
401
402 pub(super) auto_scroll: AutoScroll,
403}
404
405#[derive(Clone)]
410pub struct InputPresentation {
411 focus_handle: FocusHandle,
412 disabled: bool,
413 readonly: bool,
414 loading: bool,
415 masked: bool,
416 multi_line: bool,
417 code_editor: bool,
418 text_align: TextAlign,
419 placeholder: SharedString,
420 mask_placeholder: Option<String>,
421}
422
423impl InputPresentation {
424 pub fn focus_handle(&self) -> &FocusHandle {
425 &self.focus_handle
426 }
427
428 pub fn is_disabled(&self) -> bool {
429 self.disabled
430 }
431
432 pub fn is_readonly(&self) -> bool {
433 self.readonly
434 }
435
436 pub fn is_editable(&self) -> bool {
440 !self.disabled && !self.readonly
441 }
442
443 pub fn is_loading(&self) -> bool {
444 self.loading
445 }
446
447 pub fn is_masked(&self) -> bool {
448 self.masked
449 }
450
451 pub fn is_multi_line(&self) -> bool {
452 self.multi_line
453 }
454
455 pub fn is_code_editor(&self) -> bool {
456 self.code_editor
457 }
458
459 pub fn text_align(&self) -> TextAlign {
460 self.text_align
461 }
462
463 pub fn placeholder(&self) -> &SharedString {
464 &self.placeholder
465 }
466
467 pub fn mask_placeholder(&self) -> Option<&str> {
469 self.mask_placeholder.as_deref()
470 }
471}
472
473impl<M: InputModeKind> EventEmitter<InputEvent> for InputBaseState<M> {}
474
475impl<M: InputModeKind> InputBaseState<M> {
476 #[doc(hidden)]
477 pub fn cursor_layout(&self) -> Option<(Bounds<Pixels>, Pixels)> {
478 let layout = self.last_layout.as_ref()?;
479 Some((layout.cursor_bounds?, layout.line_height))
480 }
481
482 pub fn input_bounds(&self) -> Bounds<Pixels> {
483 self.input_bounds
484 }
485
486 pub fn text_bounds(&self) -> Option<Bounds<Pixels>> {
487 self.last_bounds
488 }
489
490 pub fn diagnostic_popover(&self) -> Option<Rc<crate::input::DiagnosticEntry>> {
491 self.diagnostic_popover.clone()
492 }
493
494 pub fn presentation(&self) -> InputPresentation {
495 InputPresentation {
496 focus_handle: self.focus_handle.clone(),
497 disabled: self.disabled,
498 readonly: self.readonly,
499 loading: self.loading,
500 masked: self.masked,
501 multi_line: self.is_multi_line(),
502 code_editor: self.is_code_editor(),
503 text_align: self.text_align,
504 placeholder: self.placeholder.clone(),
505 mask_placeholder: self.mask_pattern.placeholder(),
506 }
507 }
508
509 #[inline]
514 pub(crate) fn shows_scrollbar(&self) -> bool {
522 self.is_multi_line()
523 }
524
525 pub fn is_multi_line(&self) -> bool {
526 M::MULTI_LINE
527 }
528
529 #[inline]
531 pub fn is_single_line(&self) -> bool {
532 !M::MULTI_LINE
533 }
534
535 #[inline]
537 pub fn is_code_editor(&self) -> bool {
538 M::CODE_EDITOR
539 }
540
541 pub fn is_copyable(&self) -> bool {
545 !self.selected_range.is_empty() && !self.masked
546 }
547
548 pub fn context_menu_capabilities(&self) -> InputContextMenuCapabilities {
549 let (go_to_definition, code_actions) = self.extras.context_menu_capabilities();
550 InputContextMenuCapabilities::new()
551 .disabled(self.disabled)
552 .readonly(self.readonly)
553 .code_editor(self.is_code_editor())
554 .selection(!self.selected_range.is_empty())
555 .masked(self.masked)
556 .go_to_definition(go_to_definition)
557 .code_actions(code_actions)
558 }
559
560 pub fn set_text_align(&mut self, text_align: TextAlign, cx: &mut Context<Self>) {
561 if !self.is_single_line() || self.text_align == text_align {
562 return;
563 }
564
565 self.text_align = text_align;
566 cx.notify();
567 }
568
569 pub fn toggle_masked(&mut self, _: &mut Window, cx: &mut Context<Self>) {
575 self.masked = !self.masked;
576 cx.notify();
577 }
578
579 pub fn on_context_menu(
580 &mut self,
581 handler: Rc<
582 dyn Fn(NativeMenu, InputContextMenuCapabilities, Point<Pixels>, &mut Window, &mut App),
583 >,
584 ) {
585 self.context_menu_handler = Some(handler);
586 }
587
588 fn new_in_mode(window: &mut Window, cx: &mut Context<Self>) -> Self {
590 let focus_handle = cx.focus_handle().tab_stop(true);
591 let blink_cursor = cx.new(|_| BlinkCursor::new());
592 let undo_manager = UndoManager::new();
593
594 let _subscriptions = vec![
595 cx.observe(&blink_cursor, |_, _, cx| cx.notify()),
597 cx.observe_window_activation(window, |input, window, cx| {
599 if window.is_window_active() {
600 let focus_handle = input.focus_handle.clone();
601 if focus_handle.is_focused(window) {
602 input.blink_cursor.update(cx, |blink_cursor, cx| {
603 blink_cursor.start(cx);
604 });
605 }
606 }
607 }),
608 cx.on_focus(&focus_handle, window, Self::on_focus),
609 cx.on_blur(&focus_handle, window, Self::on_blur),
610 ];
611
612 let text_style = window.text_style();
613
614 Self {
615 extras: M::Extras::default(),
616 focus_handle: focus_handle.clone(),
617 text: "".into(),
618 display_map: DisplayMap::new(text_style.font(), window.rem_size(), None),
619 search_session: super::SearchSession::default(),
620 searchable: false,
621 replaceable: true,
622 soft_wrap: true,
623 wrapping_indent: WrappingIndent::default(),
624 scroll_beyond_last_line: None,
625 cursor_surrounding_lines: None,
626 blink_cursor,
627 undo_manager,
628 selected_range: Selection::default(),
629 selected_word_range: None,
630 selection_reversed: false,
631 ime_marked_range: None,
632 input_bounds: Bounds::default(),
633 selecting: false,
634 disabled: false,
635 readonly: false,
636 text_align: TextAlign::Left,
637 masked: false,
638 clean_on_escape: false,
639 submit_on_enter: false,
640 show_whitespaces: false,
641 loading: false,
642 pattern: None,
643 validate: None,
644 number_step: Some(NumberStep::Fixed(1.)),
645 number_min: None,
646 number_max: None,
647 mode: LayoutMode::default(),
648 last_layout: None,
649 last_bounds: None,
650 last_selected_range: None,
651 last_cursor: None,
652 scroll_handle: ScrollHandle::new(),
653 scroll_size: gpui::size(px(0.), px(0.)),
654 editor_scrollbar_snapshot: Cell::new(None),
655 editor_paddings: Edges::default(),
656 deferred_scroll_offset: None,
657 preferred_column: None,
658 placeholder: SharedString::default(),
659 mask_pattern: MaskPattern::default(),
660 mask_pattern_set: false,
661 editor_style: InputEditorStyle::default(),
662 projected_editor_style: InputEditorStyle::default(),
663 diagnostic_popover: None,
664 context_menu_handler: None,
665 pending_context_menu: None,
666 enable_context_menu: true,
667 completion_inserting: false,
668 overlay_action_handler: None,
669 silent_replace_text: false,
670 emit_events: true,
671 _subscriptions,
672 _pending_update: false,
673 cursor_line_end_affinity: false,
674 auto_scroll: AutoScroll::default(),
675 }
676 }
677
678 pub fn context_menu(mut self, enable: bool) -> Self {
683 self.enable_context_menu = enable;
684 self
685 }
686
687 pub fn set_context_menu_enabled(&mut self, enabled: bool) {
688 self.enable_context_menu = enabled;
689 }
690
691 #[doc(hidden)]
693 pub fn replaceable(mut self, allow: bool) -> Self {
694 self.replaceable = allow;
695 self
696 }
697
698 pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
700 self.placeholder = placeholder.into();
701 self
702 }
703
704 pub fn set_highlighter(
706 &mut self,
707 new_language: impl Into<SharedString>,
708 cx: &mut Context<Self>,
709 ) {
710 match &mut self.mode {
711 LayoutMode::CodeEditor {
712 language,
713 highlighter,
714 ..
715 } => {
716 *language = new_language.into();
717 *highlighter.borrow_mut() = None;
718 }
719 _ => {}
720 }
721 cx.notify();
722 }
723
724 fn reset_highlighter(&mut self, cx: &mut Context<Self>) {
725 match &mut self.mode {
726 LayoutMode::CodeEditor { highlighter, .. } => {
727 *highlighter.borrow_mut() = None;
728 }
729 _ => {}
730 }
731 cx.notify();
732 }
733
734 pub fn set_highlighter_factory(
736 &mut self,
737 factory: InputHighlighterFactory,
738 cx: &mut Context<Self>,
739 ) {
740 self.mode.set_highlighter_factory(factory);
741 self._pending_update = true;
742 cx.notify();
743 }
744
745 pub fn ensure_highlighter_factory(&mut self, factory: InputHighlighterFactory) {
747 self.mode.ensure_highlighter_factory(factory);
748 }
749
750 pub fn set_editor_style(&mut self, style: InputEditorStyle) {
751 self.editor_style = style.clone();
752 self.projected_editor_style = style;
753 }
754
755 #[doc(hidden)]
757 pub fn set_editor_paddings(&mut self, paddings: Edges<Pixels>) {
758 self.editor_paddings = paddings;
759 }
760
761 pub fn apply_highlighter_fold_candidates(
762 &mut self,
763 candidates: Vec<crate::input::FoldRange>,
764 cx: &mut Context<Self>,
765 ) {
766 if self.mode.is_folding() {
767 self.display_map.set_fold_candidates(candidates);
768 }
769 cx.notify();
770 }
771
772 #[inline]
773 pub fn diagnostics(&self) -> Option<&DiagnosticSet> {
774 self.mode.diagnostics()
775 }
776
777 #[inline]
778 pub fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
779 self.mode.diagnostics_mut()
780 }
781
782 pub fn set_placeholder(
784 &mut self,
785 placeholder: impl Into<SharedString>,
786 _: &mut Window,
787 cx: &mut Context<Self>,
788 ) {
789 self.placeholder = placeholder.into();
790 cx.notify();
791 }
792
793 pub(super) fn line_and_position_for_offset(
801 &self,
802 offset: usize,
803 ) -> (usize, usize, Option<Point<Pixels>>) {
804 let Some(last_layout) = &self.last_layout else {
805 return (0, 0, None);
806 };
807 let line_height = last_layout.line_height;
808
809 let mut y_offset = last_layout.visible_top;
810 for (vi, line) in last_layout.lines.iter().enumerate() {
811 let prev_lines_offset = last_layout.visible_line_byte_offsets[vi];
812 let local_offset = offset.saturating_sub(prev_lines_offset);
813 if let Some(pos) = line.position_for_index(local_offset, last_layout, false) {
814 let sub_line_index = (pos.y / line_height) as usize;
815 let adjusted_pos = point(pos.x + last_layout.line_number_width, pos.y + y_offset);
816 return (vi, sub_line_index, Some(adjusted_pos));
817 }
818
819 y_offset += line.size(line_height).height;
820 }
821 (0, 0, None)
822 }
823
824 pub fn set_value(
831 &mut self,
832 value: impl Into<SharedString>,
833 window: &mut Window,
834 cx: &mut Context<Self>,
835 ) {
836 self.undo_manager.set_ignoring(true);
837 self.emit_events = false;
838 self.replace_text(value, window, cx);
839 self.undo_manager.set_ignoring(false);
840 self.emit_events = true;
841
842 self.reset_selection();
843 self.reset_lsp_state();
844 self.reset_scroll_to_start();
845
846 self.undo_manager.clear();
847 cx.notify();
848 }
849
850 pub fn replace_all(
860 &mut self,
861 text: impl Into<SharedString>,
862 window: &mut Window,
863 cx: &mut Context<Self>,
864 ) {
865 self.replace_text(text, window, cx);
866 self.reset_selection();
867 self.reset_lsp_state();
868 self.reset_scroll_to_start();
869
870 cx.notify();
871 }
872
873 fn with_edits_allowed(&mut self, f: impl FnOnce(&mut Self)) {
878 let (was_disabled, was_readonly) = (self.disabled, self.readonly);
879 (self.disabled, self.readonly) = (false, false);
880 f(self);
881 (self.disabled, self.readonly) = (was_disabled, was_readonly);
882 }
883
884 pub fn insert(
888 &mut self,
889 text: impl Into<SharedString>,
890 window: &mut Window,
891 cx: &mut Context<Self>,
892 ) {
893 let text: SharedString = text.into();
894 self.with_edits_allowed(|this| {
895 this.undo_manager.pending_intent = Some(EditIntent::Atomic);
896 let range_utf16 = this.range_to_utf16(&(this.cursor()..this.cursor()));
897 this.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
898 this.selected_range = (this.selected_range.end..this.selected_range.end).into();
899 });
900 }
901
902 pub fn replace(
906 &mut self,
907 text: impl Into<SharedString>,
908 window: &mut Window,
909 cx: &mut Context<Self>,
910 ) {
911 let text: SharedString = text.into();
912 self.with_edits_allowed(|this| {
913 this.undo_manager.pending_intent = Some(EditIntent::Atomic);
914 this.replace_text_in_range_silent(None, &text, window, cx);
915 this.selected_range = (this.selected_range.end..this.selected_range.end).into();
916 });
917 }
918
919 fn replace_text(
920 &mut self,
921 text: impl Into<SharedString>,
922 window: &mut Window,
923 cx: &mut Context<Self>,
924 ) {
925 let text: SharedString = text.into();
926 self.with_edits_allowed(|this| {
927 this.undo_manager.pending_intent = Some(EditIntent::Atomic);
928 let range = 0..this.text.chars().map(|c| c.len_utf16()).sum();
929 this.replace_text_in_range_silent(Some(range), &text, window, cx);
930 this.reset_highlighter(cx);
931 });
932 }
933
934 fn reset_selection(&mut self) {
935 if self.is_single_line() {
939 let end = self.text.len();
940 self.selected_range = (end..end).into();
941 } else {
942 self.selected_range.clear();
943 }
944 }
945
946 fn reset_lsp_state(&mut self) {
947 if self.is_code_editor() {
948 self._pending_update = true;
949 M::reset_language_features(self);
950 }
951 }
952
953 fn reset_scroll_to_start(&mut self) {
954 self.scroll_handle.set_offset(point(px(0.), px(0.)));
958 if self.is_single_line() {
959 self.deferred_scroll_offset = Some(point(px(0.), px(0.)));
960 }
961 }
962
963 #[allow(unused)]
967 pub(crate) fn disabled(mut self, disabled: bool) -> Self {
968 self.disabled = disabled;
969 self
970 }
971
972 pub fn set_disabled(&mut self, disabled: bool, cx: &mut Context<Self>) {
973 if self.disabled == disabled {
974 return;
975 }
976
977 self.disabled = disabled;
978 cx.notify();
979 }
980
981 #[allow(unused)]
989 pub(crate) fn readonly(mut self, readonly: bool) -> Self {
990 self.readonly = readonly;
991 self
992 }
993
994 pub fn set_readonly(&mut self, readonly: bool, cx: &mut Context<Self>) {
995 if self.readonly == readonly {
996 return;
997 }
998
999 self.readonly = readonly;
1000 if readonly {
1001 self.search_session.replace_mode = false;
1002 }
1003 cx.notify();
1004 }
1005
1006 pub fn is_editable(&self) -> bool {
1011 !self.disabled && !self.readonly
1012 }
1013
1014 pub fn clean_on_escape(mut self) -> Self {
1016 self.clean_on_escape = true;
1017 self
1018 }
1019
1020 pub fn set_clean_on_escape(&mut self, clean: bool) {
1021 self.clean_on_escape = clean;
1022 }
1023
1024 #[doc(hidden)]
1029 pub fn submit_on_enter(mut self, submit: bool) -> Self {
1030 self.submit_on_enter = submit;
1031 self
1032 }
1033
1034 pub fn set_submit_on_enter(&mut self, submit: bool, cx: &mut Context<Self>) {
1035 self.submit_on_enter = submit;
1036 cx.notify();
1037 }
1038
1039 #[doc(hidden)]
1041 pub fn show_whitespaces(mut self, show: bool) -> Self {
1042 self.show_whitespaces = show;
1043 self
1044 }
1045
1046 pub fn set_show_whitespaces(&mut self, show: bool, _: &mut Window, cx: &mut Context<Self>) {
1048 self.show_whitespaces = show;
1049 cx.notify();
1050 }
1051
1052 pub fn scroll_beyond_last_line(mut self, rows: Option<usize>) -> Self {
1062 self.scroll_beyond_last_line = rows;
1063 self
1064 }
1065
1066 pub fn set_scroll_beyond_last_line(
1068 &mut self,
1069 rows: Option<usize>,
1070 _: &mut Window,
1071 cx: &mut Context<Self>,
1072 ) {
1073 if self.scroll_beyond_last_line == rows {
1074 return;
1075 }
1076 self.scroll_beyond_last_line = rows;
1077 cx.notify();
1078 }
1079
1080 pub fn cursor_surrounding_lines(mut self, lines: Option<usize>) -> Self {
1091 self.cursor_surrounding_lines = lines;
1092 self
1093 }
1094
1095 pub fn set_cursor_surrounding_lines(
1097 &mut self,
1098 lines: Option<usize>,
1099 _: &mut Window,
1100 cx: &mut Context<Self>,
1101 ) {
1102 if self.cursor_surrounding_lines == lines {
1103 return;
1104 }
1105 self.cursor_surrounding_lines = lines;
1106 cx.notify();
1107 }
1108
1109 pub fn default_value(mut self, value: impl Into<SharedString>) -> Self {
1111 let text: SharedString = value.into();
1112 self.text = Rope::from(self.normalize_input(&text).as_ref());
1113 if let Some(diagnostics) = self.mode.diagnostics_mut() {
1114 diagnostics.reset(&self.text)
1115 }
1116 self._pending_update = true;
1119 self
1120 }
1121
1122 pub fn value(&self) -> SharedString {
1127 SharedString::new(self.text.to_string())
1128 }
1129
1130 pub fn selected_value(&self) -> SharedString {
1136 SharedString::new(self.selected_text().to_string())
1137 }
1138
1139 pub fn unmask_value(&self) -> SharedString {
1141 self.mask_pattern.unmask(&self.text.to_string()).into()
1142 }
1143
1144 #[doc(hidden)]
1150 pub fn prepare(&mut self, _: &mut Window, _: &mut Context<Self>) {}
1151
1152 pub fn text(&self) -> &Rope {
1157 &self.text
1158 }
1159
1160 pub fn cursor_position(&self) -> Position {
1162 let offset = self.cursor();
1163 self.text.offset_to_position(offset)
1164 }
1165
1166 pub fn set_cursor_position(
1170 &mut self,
1171 position: impl Into<Position>,
1172 window: &mut Window,
1173 cx: &mut Context<Self>,
1174 ) {
1175 let position: Position = position.into();
1176 let offset = self.text.position_to_offset(&position);
1177
1178 self.move_to(offset, None, cx);
1179 self.update_preferred_column();
1180 self.focus(window, cx);
1181 }
1182
1183 pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
1185 self.focus_handle.focus(window, cx);
1186 self.blink_cursor.update(cx, |cursor, cx| {
1187 cursor.start(cx);
1188 });
1189 }
1190
1191 pub fn refresh(&mut self, cx: &mut Context<Self>) {
1205 self._pending_update = true;
1206 cx.notify();
1207 }
1208
1209 pub(super) fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
1210 self.undo_manager.break_transaction_coalescing();
1211 self.select_to(self.previous_boundary(self.cursor()), cx);
1212 }
1213
1214 pub(super) fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
1215 self.undo_manager.break_transaction_coalescing();
1216 self.select_to(self.next_boundary(self.cursor()), cx);
1217 }
1218
1219 pub(super) fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
1220 if self.is_single_line() {
1221 return;
1222 }
1223 self.undo_manager.break_transaction_coalescing();
1224 let offset = self.start_of_line().saturating_sub(1);
1225 self.select_to(self.previous_boundary(offset), cx);
1226 }
1227
1228 pub(super) fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
1229 if self.is_single_line() {
1230 return;
1231 }
1232 self.undo_manager.break_transaction_coalescing();
1233 let offset = (self.end_of_line() + 1).min(self.text.len());
1234 self.select_to(self.next_boundary(offset), cx);
1235 }
1236
1237 pub(super) fn on_action_select_all(
1238 &mut self,
1239 _: &SelectAll,
1240 window: &mut Window,
1241 cx: &mut Context<Self>,
1242 ) {
1243 self.select_all(window, cx);
1244 }
1245
1246 pub(super) fn select_to_start(
1247 &mut self,
1248 _: &SelectToStart,
1249 _: &mut Window,
1250 cx: &mut Context<Self>,
1251 ) {
1252 self.undo_manager.break_transaction_coalescing();
1253 self.select_to(0, cx);
1254 }
1255
1256 pub(super) fn select_to_end(
1257 &mut self,
1258 _: &SelectToEnd,
1259 _: &mut Window,
1260 cx: &mut Context<Self>,
1261 ) {
1262 self.undo_manager.break_transaction_coalescing();
1263 let end = self.text.len();
1264 self.select_to(end, cx);
1265 }
1266
1267 pub(super) fn select_to_start_of_line(
1268 &mut self,
1269 _: &SelectToStartOfLine,
1270 _: &mut Window,
1271 cx: &mut Context<Self>,
1272 ) {
1273 self.undo_manager.break_transaction_coalescing();
1274 let offset = self.start_of_line();
1275 self.select_to(offset, cx);
1276 }
1277
1278 pub(super) fn select_to_end_of_line(
1279 &mut self,
1280 _: &SelectToEndOfLine,
1281 _: &mut Window,
1282 cx: &mut Context<Self>,
1283 ) {
1284 self.undo_manager.break_transaction_coalescing();
1285 let offset = self.end_of_line();
1286 self.select_to_with_affinity(offset, true, cx);
1288 }
1289
1290 pub(super) fn select_to_previous_word(
1291 &mut self,
1292 _: &SelectToPreviousWordStart,
1293 _: &mut Window,
1294 cx: &mut Context<Self>,
1295 ) {
1296 self.undo_manager.break_transaction_coalescing();
1297 let offset = self.previous_start_of_word();
1298 self.select_to(offset, cx);
1299 }
1300
1301 pub(super) fn select_to_next_word(
1302 &mut self,
1303 _: &SelectToNextWordEnd,
1304 _: &mut Window,
1305 cx: &mut Context<Self>,
1306 ) {
1307 self.undo_manager.break_transaction_coalescing();
1308 let offset = self.next_end_of_word();
1309 self.select_to(offset, cx);
1310 }
1311
1312 pub(super) fn previous_start_of_word(&mut self) -> usize {
1314 if self.masked {
1315 return 0;
1319 }
1320
1321 let offset = self.selected_range.start;
1322 let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1323 let left_part = self.text.slice(0..offset).to_string();
1325
1326 UnicodeSegmentation::split_word_bound_indices(left_part.as_str())
1327 .rfind(|(_, s)| !s.trim_start().is_empty())
1328 .map(|(i, _)| i)
1329 .unwrap_or(0)
1330 }
1331
1332 pub(super) fn next_end_of_word(&mut self) -> usize {
1334 if self.masked {
1335 return self.text.len();
1337 }
1338
1339 let offset = self.cursor();
1340 let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
1341 let right_part = self.text.slice(offset..self.text.len()).to_string();
1342
1343 UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
1344 .find(|(_, s)| !s.trim_start().is_empty())
1345 .map(|(i, s)| offset + i + s.len())
1346 .unwrap_or(self.text.len())
1347 }
1348
1349 pub(super) fn start_of_line(&self) -> usize {
1354 if self.is_single_line() {
1355 return 0;
1356 }
1357
1358 let row = self.text.offset_to_point(self.cursor()).row;
1359 let logical_start = self.text.line_start_offset(row);
1360
1361 if self.soft_wrap && self.is_code_editor() {
1362 let wrap_point = self.display_map.offset_to_wrap_display_point_with_affinity(
1363 self.cursor(),
1364 self.cursor_line_end_affinity,
1365 );
1366 if let Some(line) = self.display_map.line(row)
1367 && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1368 {
1369 let visual_start = logical_start + range.start;
1370 if self.cursor() != visual_start {
1371 return visual_start;
1372 }
1373 }
1374 }
1375
1376 logical_start
1377 }
1378
1379 pub(super) fn end_of_line(&self) -> usize {
1384 if self.is_single_line() {
1385 return self.text.len();
1386 }
1387
1388 let row = self.text.offset_to_point(self.cursor()).row;
1389 let logical_start = self.text.line_start_offset(row);
1390 let logical_end = self.text.line_end_offset(row);
1391
1392 if self.soft_wrap && self.is_code_editor() {
1393 let wrap_point = self.display_map.offset_to_wrap_display_point_with_affinity(
1397 self.cursor(),
1398 self.cursor_line_end_affinity,
1399 );
1400 if let Some(line) = self.display_map.line(row)
1401 && let Some(range) = line.wrapped_lines.get(wrap_point.local_row)
1402 {
1403 let visual_end = logical_start + range.end;
1404 if self.cursor() != visual_end {
1405 return visual_end;
1406 }
1407 }
1408 }
1409
1410 logical_end
1411 }
1412
1413 pub(super) fn start_of_line_of_selection(
1417 &mut self,
1418 window: &mut Window,
1419 cx: &mut Context<Self>,
1420 ) -> usize {
1421 if self.is_single_line() {
1422 return 0;
1423 }
1424
1425 let mut offset =
1426 self.previous_boundary(self.selected_range.start.min(self.selected_range.end));
1427 if self.text.char_at(offset) == Some('\r') {
1428 offset += 1;
1429 }
1430
1431 let line = self
1432 .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
1433 .unwrap_or_default()
1434 .rfind('\n')
1435 .map(|i| i + 1)
1436 .unwrap_or(0);
1437 line
1438 }
1439
1440 pub(super) fn indent_of_next_line(&mut self) -> String {
1444 if self.is_single_line() {
1445 return "".into();
1446 }
1447
1448 let mut current_indent = String::new();
1449 let mut next_indent = String::new();
1450 let current_line_start_pos = self.start_of_line();
1451 let next_line_start_pos = self.end_of_line();
1452 for c in self.text.slice(current_line_start_pos..).chars() {
1453 if !c.is_whitespace() {
1454 break;
1455 }
1456 if c == '\n' || c == '\r' {
1457 break;
1458 }
1459 current_indent.push(c);
1460 }
1461
1462 for c in self.text.slice(next_line_start_pos..).chars() {
1463 if !c.is_whitespace() {
1464 break;
1465 }
1466 if c == '\n' || c == '\r' {
1467 break;
1468 }
1469 next_indent.push(c);
1470 }
1471
1472 if next_indent.len() > current_indent.len() {
1473 return next_indent;
1474 } else {
1475 return current_indent;
1476 }
1477 }
1478
1479 pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
1480 let intent = if self.selected_range.is_empty() {
1481 self.select_to(self.previous_boundary(self.cursor()), cx);
1482 EditIntent::Backspace
1483 } else {
1484 EditIntent::Atomic
1485 };
1486 self.undo_manager.pending_intent = Some(intent);
1487 self.replace_text_in_range(None, "", window, cx);
1488 self.pause_blink_cursor(cx);
1489 }
1490
1491 pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
1492 let intent = if self.selected_range.is_empty() {
1493 self.select_to(self.next_boundary(self.cursor()), cx);
1494 EditIntent::DeleteForward
1495 } else {
1496 EditIntent::Atomic
1497 };
1498 self.undo_manager.pending_intent = Some(intent);
1499 self.replace_text_in_range(None, "", window, cx);
1500 self.pause_blink_cursor(cx);
1501 }
1502
1503 pub(super) fn delete_to_beginning_of_line(
1504 &mut self,
1505 _: &DeleteToBeginningOfLine,
1506 window: &mut Window,
1507 cx: &mut Context<Self>,
1508 ) {
1509 if !self.selected_range.is_empty() {
1510 self.replace_text_in_range(None, "", window, cx);
1511 self.pause_blink_cursor(cx);
1512 return;
1513 }
1514
1515 let mut offset = self.start_of_line();
1516 if offset == self.cursor() {
1517 offset = offset.saturating_sub(1);
1518 }
1519 self.replace_text_in_range_silent(
1520 Some(self.range_to_utf16(&(offset..self.cursor()))),
1521 "",
1522 window,
1523 cx,
1524 );
1525 self.pause_blink_cursor(cx);
1526 }
1527
1528 pub(super) fn delete_to_end_of_line(
1529 &mut self,
1530 _: &DeleteToEndOfLine,
1531 window: &mut Window,
1532 cx: &mut Context<Self>,
1533 ) {
1534 if !self.selected_range.is_empty() {
1535 self.replace_text_in_range(None, "", window, cx);
1536 self.pause_blink_cursor(cx);
1537 return;
1538 }
1539
1540 let mut offset = self.end_of_line();
1541 if offset == self.cursor() {
1542 offset = (offset + 1).clamp(0, self.text.len());
1543 }
1544 self.replace_text_in_range_silent(
1545 Some(self.range_to_utf16(&(self.cursor()..offset))),
1546 "",
1547 window,
1548 cx,
1549 );
1550 self.pause_blink_cursor(cx);
1551 }
1552
1553 pub(super) fn delete_previous_word(
1554 &mut self,
1555 _: &DeleteToPreviousWordStart,
1556 window: &mut Window,
1557 cx: &mut Context<Self>,
1558 ) {
1559 if !self.selected_range.is_empty() {
1560 self.replace_text_in_range(None, "", window, cx);
1561 self.pause_blink_cursor(cx);
1562 return;
1563 }
1564
1565 let offset = self.previous_start_of_word();
1566 self.replace_text_in_range_silent(
1567 Some(self.range_to_utf16(&(offset..self.cursor()))),
1568 "",
1569 window,
1570 cx,
1571 );
1572 self.pause_blink_cursor(cx);
1573 }
1574
1575 pub(super) fn delete_next_word(
1576 &mut self,
1577 _: &DeleteToNextWordEnd,
1578 window: &mut Window,
1579 cx: &mut Context<Self>,
1580 ) {
1581 if !self.selected_range.is_empty() {
1582 self.replace_text_in_range(None, "", window, cx);
1583 self.pause_blink_cursor(cx);
1584 return;
1585 }
1586
1587 let offset = self.next_end_of_word();
1588 self.replace_text_in_range_silent(
1589 Some(self.range_to_utf16(&(self.cursor()..offset))),
1590 "",
1591 window,
1592 cx,
1593 );
1594 self.pause_blink_cursor(cx);
1595 }
1596
1597 pub(super) fn enter(&mut self, action: &Enter, window: &mut Window, cx: &mut Context<Self>) {
1598 if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1599 return;
1600 }
1601
1602 if M::has_inline_completion(self) {
1604 M::clear_inline_completion(self, cx);
1605 }
1606
1607 let insert_newline = self.is_multi_line() && (!self.submit_on_enter || action.shift);
1612
1613 if insert_newline {
1614 let indent = if self.is_code_editor() {
1616 self.indent_of_next_line()
1617 } else {
1618 "".to_string()
1619 };
1620
1621 let new_line_text = format!("\n{}", indent);
1623 self.replace_text_in_range_silent(None, &new_line_text, window, cx);
1624 self.pause_blink_cursor(cx);
1625 } else {
1626 self.undo_manager.break_transaction_coalescing();
1629 cx.propagate();
1630 }
1631
1632 cx.emit(InputEvent::PressEnter {
1633 secondary: action.secondary,
1634 shift: action.shift,
1635 });
1636 }
1637
1638 pub fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1639 self.replace_text("", window, cx);
1640 self.selected_range = (0..0).into();
1641 self.scroll_to(0, None, cx);
1642 }
1643
1644 pub(super) fn escape(&mut self, action: &Escape, window: &mut Window, cx: &mut Context<Self>) {
1645 if M::handle_context_menu_action(self, Box::new(action.clone()), window, cx) {
1646 return;
1647 }
1648
1649 if M::has_inline_completion(self) {
1651 M::clear_inline_completion(self, cx);
1652 return; }
1654
1655 if self.ime_marked_range.is_some() {
1656 self.unmark_text(window, cx);
1657 }
1658
1659 if self.clean_on_escape {
1660 return self.clean(window, cx);
1661 }
1662
1663 cx.propagate();
1664 }
1665
1666 pub(crate) fn handle_right_click_menu(
1668 &mut self,
1669 position: Point<Pixels>,
1670 offset: usize,
1671 window: &mut Window,
1672 cx: &mut Context<Self>,
1673 ) {
1674 if self.disabled {
1675 return;
1676 }
1677 if crate::GlobalState::is_in_deferred_context(cx) {
1678 return;
1679 }
1680
1681 if !self.selected_range.contains(offset) {
1682 self.move_to(offset, None, cx);
1683 }
1684
1685 if self.is_code_editor() {
1686 M::on_hover_definition(self, offset, window, cx);
1687 }
1688
1689 if let Some(handler) = self.context_menu_handler.clone() {
1690 let capabilities = self.context_menu_capabilities();
1691 cx.defer_in(window, move |_, window, cx| {
1692 handler(NativeMenu::new(), capabilities, position, window, cx);
1693 });
1694 }
1695 }
1696
1697 pub(super) fn on_mouse_down(
1698 &mut self,
1699 event: &MouseDownEvent,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) {
1703 self.undo_manager.break_transaction_coalescing();
1704 crate::global_state::GlobalState::suppress_text_selection(cx);
1707
1708 M::clear_inline_completion(self, cx);
1710
1711 if let Some(ime_marked_range) = &self.ime_marked_range {
1714 if ime_marked_range.len() == 0 {
1715 self.ime_marked_range = None;
1716 }
1717 }
1718
1719 self.selecting = true;
1720 let (offset, line_end_affinity) = self.index_for_mouse_position(event.position);
1721
1722 if M::on_click(self, event, offset, window, cx) {
1723 return;
1724 }
1725
1726 if event.button == MouseButton::Left && event.click_count >= 3 {
1728 self.select_line(offset, window, cx);
1729 return;
1730 }
1731
1732 if event.button == MouseButton::Left && event.click_count == 2 {
1734 self.select_word(offset, window, cx);
1735 return;
1736 }
1737
1738 if event.button == MouseButton::Right {
1740 if self.enable_context_menu {
1741 if !self.selected_range.contains(offset) {
1742 self.move_to(offset, None, cx);
1743 }
1744 self.pending_context_menu = Some((event.position, offset));
1745 }
1746 return;
1747 }
1748
1749 if event.modifiers.shift {
1750 self.select_to_with_affinity(offset, line_end_affinity, cx);
1751 } else {
1752 self.move_to_with_affinity(offset, None, line_end_affinity, cx)
1753 }
1754 }
1755
1756 pub(super) fn on_mouse_up(
1757 &mut self,
1758 event: &MouseUpEvent,
1759 window: &mut Window,
1760 cx: &mut Context<Self>,
1761 ) {
1762 if event.button == MouseButton::Right {
1763 if let Some((position, offset)) = self.pending_context_menu.take() {
1764 self.handle_right_click_menu(position, offset, window, cx);
1765 }
1766 }
1767 if self.selected_range.is_empty() {
1768 self.selection_reversed = false;
1769 }
1770 self.selecting = false;
1771 self.selected_word_range = None;
1772 self.auto_scroll.stop();
1773 }
1774
1775 pub(super) fn on_mouse_move(
1776 &mut self,
1777 event: &MouseMoveEvent,
1778 window: &mut Window,
1779 cx: &mut Context<Self>,
1780 ) {
1781 let within_bounds = self
1783 .last_bounds
1784 .as_ref()
1785 .map(|bounds| bounds.contains(&event.position))
1786 .unwrap_or(false);
1787
1788 if !within_bounds {
1789 M::clear_hover_state(self, cx);
1791 return;
1792 }
1793
1794 let (offset, _) = self.index_for_mouse_position(event.position);
1796 M::on_mouse_move(self, offset, event, window, cx);
1797
1798 if self.is_code_editor() {
1799 if let Some(diagnostic) = self
1800 .mode
1801 .diagnostics()
1802 .and_then(|set| set.for_offset(offset))
1803 {
1804 self.diagnostic_popover = Some(Rc::new(diagnostic.clone()));
1805 cx.notify();
1806 } else {
1807 self.diagnostic_popover = None;
1808 }
1809 }
1810 }
1811
1812 pub(super) fn on_scroll_wheel(
1813 &mut self,
1814 event: &ScrollWheelEvent,
1815 window: &mut Window,
1816 cx: &mut Context<Self>,
1817 ) {
1818 let line_height = self
1819 .last_layout
1820 .as_ref()
1821 .map(|layout| layout.line_height)
1822 .unwrap_or(window.line_height());
1823 let delta = event.delta.pixel_delta(line_height);
1824
1825 let old_offset = self.scroll_handle.offset();
1826 self.update_scroll_offset(Some(old_offset + delta), cx);
1827
1828 if self.scroll_handle.offset() != old_offset {
1830 cx.stop_propagation();
1831 }
1832
1833 self.diagnostic_popover = None;
1834 }
1835
1836 pub(super) fn update_scroll_offset(
1837 &mut self,
1838 offset: Option<Point<Pixels>>,
1839 cx: &mut Context<Self>,
1840 ) {
1841 let mut offset = offset.unwrap_or(self.scroll_handle.offset());
1842 let safe_x_offset = if self.text_align == TextAlign::Left {
1844 px(0.)
1845 } else {
1846 -CURSOR_WIDTH
1847 };
1848
1849 let safe_y_range =
1850 (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
1851 let safe_x_range = (-self.scroll_size.width + self.input_bounds.size.width + safe_x_offset)
1852 .min(safe_x_offset)..px(0.);
1853
1854 offset.y = if self.is_single_line() {
1855 px(0.)
1856 } else {
1857 offset.y.clamp(safe_y_range.start, safe_y_range.end)
1858 };
1859 offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);
1860 self.scroll_handle.set_offset(offset);
1861 cx.notify();
1862 }
1863
1864 pub(crate) fn scroll_to(
1868 &mut self,
1869 offset: usize,
1870 direction: Option<MoveDirection>,
1871 cx: &mut Context<Self>,
1872 ) {
1873 let Some(last_layout) = self.last_layout.as_ref() else {
1874 return;
1875 };
1876 let Some(bounds) = self.last_bounds.as_ref() else {
1877 return;
1878 };
1879
1880 let mut scroll_offset = self.scroll_handle.offset();
1881 let was_offset = scroll_offset;
1882 let line_height = last_layout.line_height;
1883
1884 let point = self.text.offset_to_point(offset);
1885
1886 let row = point.row;
1887
1888 let mut row_offset_y = line_height * self.display_map.buffer_line_to_display_row(row);
1890
1891 let safety_margin = match last_layout.text_align {
1894 TextAlign::Left => RIGHT_MARGIN,
1895 TextAlign::Right => px(0.),
1896 TextAlign::Center => CURSOR_WIDTH,
1897 };
1898 if let Some(line) = last_layout
1899 .lines
1900 .get(row.saturating_sub(last_layout.visible_range.start))
1901 {
1902 if let Some(pos) = line.position_for_index(point.column, last_layout, false) {
1904 let bounds_width = bounds.size.width - last_layout.line_number_width;
1905 let col_offset_x = pos.x;
1906 row_offset_y += pos.y;
1907 if col_offset_x - safety_margin < -scroll_offset.x {
1908 scroll_offset.x = -col_offset_x + safety_margin;
1910 } else if col_offset_x + safety_margin > -scroll_offset.x + bounds_width {
1911 scroll_offset.x = -(col_offset_x - bounds_width + safety_margin);
1912 }
1913 }
1914 }
1915
1916 let edge_height = if direction.is_some() && self.is_code_editor() {
1921 super::element::cursor_surrounding_padding(
1922 self.mode.is_auto_grow(),
1923 self.cursor_surrounding_lines,
1924 last_layout.visible_range.len(),
1925 line_height,
1926 )
1927 } else {
1928 line_height
1929 };
1930 if row_offset_y - edge_height + line_height < -scroll_offset.y {
1931 scroll_offset.y = -row_offset_y + edge_height - line_height;
1933 } else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
1934 scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
1936 }
1937
1938 if direction == Some(MoveDirection::Up) {
1940 scroll_offset.y = scroll_offset.y.max(was_offset.y);
1941 } else if direction == Some(MoveDirection::Down) {
1942 scroll_offset.y = scroll_offset.y.min(was_offset.y);
1943 }
1944
1945 let safe_y_min = (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.));
1949 scroll_offset.x = scroll_offset.x.min(px(0.));
1950 scroll_offset.y = scroll_offset.y.clamp(safe_y_min, px(0.));
1951 self.deferred_scroll_offset = Some(scroll_offset);
1952 cx.notify();
1953 }
1954
1955 pub(super) fn show_character_palette(
1956 &mut self,
1957 _: &ShowCharacterPalette,
1958 window: &mut Window,
1959 _: &mut Context<Self>,
1960 ) {
1961 window.show_character_palette();
1962 }
1963
1964 pub(super) fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
1965 if !self.is_copyable() {
1966 return;
1967 }
1968
1969 let selected_text = self.text.slice(self.selected_range).to_string();
1970 cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
1971 }
1972
1973 pub(super) fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
1974 if !self.is_copyable() {
1975 return;
1976 }
1977
1978 let selected_text = self.text.slice(self.selected_range).to_string();
1979 cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
1980
1981 self.undo_manager.pending_intent = Some(EditIntent::Atomic);
1982 self.replace_text_in_range_silent(None, "", window, cx);
1983 }
1984
1985 pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
1986 if let Some(clipboard) = cx.read_from_clipboard() {
1987 let new_text = clipboard.text().unwrap_or_default();
1988 self.undo_manager.pending_intent = Some(EditIntent::Atomic);
1989 self.replace_text_in_range_silent(None, &new_text, window, cx);
1990 self.scroll_to(self.cursor(), None, cx);
1991 }
1992 }
1993
1994 fn push_history(
1995 &mut self,
1996 text: &Rope,
1997 range: &Range<usize>,
1998 new_text: &str,
1999 requested_intent: Option<EditIntent>,
2000 selection_before: Selection,
2001 selection_after: Option<Selection>,
2002 ) {
2003 if self.undo_manager.is_ignoring() {
2004 return;
2005 }
2006
2007 let range =
2008 text.clip_offset(range.start, Bias::Left)..text.clip_offset(range.end, Bias::Right);
2009 let old_text = text.slice(range.clone()).to_string();
2010 let new_range = range.start..range.start + new_text.len();
2011
2012 let intent = requested_intent.unwrap_or_else(|| {
2013 if range.is_empty()
2014 && old_text.is_empty()
2015 && !new_text.is_empty()
2016 && !new_text.contains(['\n', '\r'])
2017 {
2018 EditIntent::Typing
2019 } else {
2020 EditIntent::Atomic
2021 }
2022 });
2023
2024 let selection_before = match intent {
2025 EditIntent::Backspace => Selection::new(range.end, range.end),
2026 EditIntent::DeleteForward => Selection::new(range.start, range.start),
2027 EditIntent::Typing | EditIntent::Atomic => selection_before,
2028 };
2029 let selection_after =
2030 selection_after.unwrap_or_else(|| Selection::new(new_range.end, new_range.end));
2031
2032 self.undo_manager.record_transaction(
2033 Change::new(
2034 range,
2035 &old_text,
2036 new_range,
2037 new_text,
2038 selection_before,
2039 selection_after,
2040 ),
2041 intent,
2042 );
2043 }
2044
2045 pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
2046 self.undo_manager.set_ignoring(true);
2047 if let Some(changes) = self.undo_manager.undo() {
2048 let selection = changes.last().unwrap().selection_before;
2049 for change in &changes {
2050 let range_utf16 = self.range_to_utf16(&change.new_range.into());
2051 self.replace_text_in_range_silent(Some(range_utf16), &change.old_text, window, cx);
2052 }
2053 self.selected_range = selection;
2054 }
2055 self.undo_manager.set_ignoring(false);
2056 }
2057
2058 pub(super) fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
2059 self.undo_manager.set_ignoring(true);
2060 if let Some(changes) = self.undo_manager.redo() {
2061 let selection = changes.last().unwrap().selection_after;
2062 for change in &changes {
2063 let range_utf16 = self.range_to_utf16(&change.old_range.into());
2064 self.replace_text_in_range_silent(Some(range_utf16), &change.new_text, window, cx);
2065 }
2066 self.selected_range = selection;
2067 }
2068 self.undo_manager.set_ignoring(false);
2069 }
2070
2071 pub fn cursor(&self) -> usize {
2075 if let Some(ime_marked_range) = &self.ime_marked_range {
2076 return ime_marked_range.end;
2077 }
2078
2079 if self.selection_reversed {
2080 self.selected_range.start
2081 } else {
2082 self.selected_range.end
2083 }
2084 }
2085
2086 pub fn visible_row_range(&self) -> Option<std::ops::Range<usize>> {
2088 self.last_layout.as_ref().map(|l| l.visible_range.clone())
2089 }
2090
2091 pub fn scroll_offset(&self) -> gpui::Point<gpui::Pixels> {
2093 self.scroll_handle.offset()
2094 }
2095
2096 pub fn set_scroll_offset(&mut self, offset: gpui::Point<gpui::Pixels>, cx: &mut Context<Self>) {
2100 self.deferred_scroll_offset = Some(offset);
2101 cx.notify();
2102 }
2103
2104 pub fn line_height(&self) -> Option<gpui::Pixels> {
2106 self.last_layout.as_ref().map(|l| l.line_height)
2107 }
2108
2109 pub fn selected_range(&self) -> std::ops::Range<usize> {
2115 self.selected_range.into()
2116 }
2117
2118 pub fn select_all(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2119 self.undo_manager.break_transaction_coalescing();
2120 self.selected_range = (0..self.text.len()).into();
2121 cx.notify();
2122 }
2123
2124 pub fn set_selected_range(&mut self, range: Range<usize>, cx: &mut Context<Self>) {
2129 let end_bias = if range.start == range.end {
2130 Bias::Left
2131 } else {
2132 Bias::Right
2133 };
2134 let start = self.text.clip_offset(range.start, Bias::Left);
2135 let end = self.text.clip_offset(range.end, end_bias);
2136
2137 self.move_to(start, None, cx);
2138 self.selection_reversed = false;
2139 self.selected_word_range = None;
2140 self.select_to(end, cx);
2141 }
2142
2143 pub(crate) fn index_for_mouse_position(&self, position: Point<Pixels>) -> (usize, bool) {
2151 if self.text.len() == 0 {
2153 return (0, false);
2154 }
2155
2156 let (Some(bounds), Some(last_layout)) =
2157 (self.last_bounds.as_ref(), self.last_layout.as_ref())
2158 else {
2159 return (0, false);
2160 };
2161
2162 let line_height = last_layout.line_height;
2163 let line_number_width = last_layout.line_number_width;
2164
2165 let inner_position = position - bounds.origin - point(line_number_width, px(0.));
2176
2177 let mut y_offset = last_layout.visible_top;
2178
2179 for (vi, (line_layout, _buffer_line)) in last_layout
2181 .lines
2182 .iter()
2183 .zip(last_layout.visible_buffer_lines.iter())
2184 .enumerate()
2185 {
2186 let line_start_offset = last_layout.visible_line_byte_offsets[vi];
2187
2188 let line_origin = point(px(0.), y_offset);
2190 let pos = inner_position - line_origin;
2191
2192 if self.is_single_line() {
2194 let local_index = line_layout.closest_index_for_x(pos.x, last_layout);
2195 return (self.resolve_index(line_start_offset + local_index), false);
2197 }
2198
2199 if let Some((local_index, line_end_affinity)) =
2201 line_layout.closest_index_for_position(pos, last_layout)
2202 {
2203 return (
2204 self.resolve_index(line_start_offset + local_index),
2205 line_end_affinity,
2206 );
2207 } else if pos.y < px(0.) {
2208 return (self.resolve_index(line_start_offset), false);
2210 }
2211
2212 y_offset += line_layout.size(line_height).height;
2213 }
2214
2215 (self.text.len(), false)
2217 }
2218
2219 fn resolve_index(&self, index: usize) -> usize {
2222 if self.masked {
2223 self.text.char_index_to_offset(index / MASK_CHAR.len_utf8())
2224 } else {
2225 index.min(self.text.len())
2226 }
2227 }
2228
2229 pub(crate) fn select_to(&mut self, offset: usize, cx: &mut Context<Self>) {
2236 self.select_to_with_affinity(offset, false, cx);
2237 }
2238
2239 pub(crate) fn select_to_with_affinity(
2246 &mut self,
2247 offset: usize,
2248 line_end_affinity: bool,
2249 cx: &mut Context<Self>,
2250 ) {
2251 M::clear_inline_completion(self, cx);
2252
2253 self.cursor_line_end_affinity = line_end_affinity;
2254 let offset = offset.clamp(0, self.text.len());
2255 if self.selection_reversed {
2256 self.selected_range.start = offset
2257 } else {
2258 self.selected_range.end = offset
2259 };
2260
2261 if self.selected_range.end < self.selected_range.start {
2262 self.selection_reversed = !self.selection_reversed;
2263 self.selected_range = (self.selected_range.end..self.selected_range.start).into();
2264 }
2265
2266 if let Some(word_range) = self.selected_word_range.as_ref() {
2268 if self.selected_range.start > word_range.start {
2269 self.selected_range.start = word_range.start;
2270 }
2271 if self.selected_range.end < word_range.end {
2272 self.selected_range.end = word_range.end;
2273 }
2274 }
2275 if self.selected_range.is_empty() {
2276 self.update_preferred_column();
2277 }
2278 cx.notify()
2279 }
2280
2281 pub fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2283 self.undo_manager.break_transaction_coalescing();
2284 let offset = self.cursor();
2285 self.selected_range = (offset..offset).into();
2286 cx.notify()
2287 }
2288
2289 #[inline]
2290 pub(super) fn offset_from_utf16(&self, offset: usize) -> usize {
2291 self.text.offset_utf16_to_offset(offset)
2292 }
2293
2294 #[inline]
2295 pub(super) fn offset_to_utf16(&self, offset: usize) -> usize {
2296 self.text.offset_to_offset_utf16(offset)
2297 }
2298
2299 #[inline]
2300 pub(crate) fn range_to_utf16(&self, range: &Range<usize>) -> Range<usize> {
2301 self.offset_to_utf16(range.start)..self.offset_to_utf16(range.end)
2302 }
2303
2304 #[inline]
2305 pub(super) fn range_from_utf16(&self, range_utf16: &Range<usize>) -> Range<usize> {
2306 self.offset_from_utf16(range_utf16.start)..self.offset_from_utf16(range_utf16.end)
2307 }
2308
2309 fn clamp_offset_to_visible_backward(&self, offset: usize) -> usize {
2312 let line = self.text.offset_to_point(offset).row;
2313 if self.display_map.is_buffer_line_hidden(line) {
2314 for fold in self.display_map.folded_ranges() {
2315 if line > fold.start_line && line <= fold.end_line {
2316 return self.text.line_end_offset(fold.start_line);
2317 }
2318 }
2319 }
2320 offset
2321 }
2322
2323 fn clamp_offset_to_visible_forward(&self, offset: usize) -> usize {
2326 let line = self.text.offset_to_point(offset).row;
2327 if self.display_map.is_buffer_line_hidden(line) {
2328 for fold in self.display_map.folded_ranges() {
2329 if line > fold.start_line && line <= fold.end_line {
2330 return self.text.line_start_offset(fold.end_line);
2331 }
2332 }
2333 }
2334 offset
2335 }
2336
2337 pub(super) fn previous_boundary(&self, offset: usize) -> usize {
2338 let mut offset = self.text.clip_offset(offset.saturating_sub(1), Bias::Left);
2339 if let Some(ch) = self.text.char_at(offset) {
2340 if ch == '\r' {
2341 offset -= 1;
2342 }
2343 }
2344
2345 self.clamp_offset_to_visible_backward(offset)
2346 }
2347
2348 pub(super) fn next_boundary(&self, offset: usize) -> usize {
2349 let mut offset = self.text.clip_offset(offset + 1, Bias::Right);
2350 if let Some(ch) = self.text.char_at(offset) {
2351 if ch == '\r' {
2352 offset += 1;
2353 }
2354 }
2355
2356 self.clamp_offset_to_visible_forward(offset)
2357 }
2358
2359 pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
2361 (self.focus_handle.is_focused(window) || M::is_context_menu_open(self, cx))
2362 && !self.disabled
2363 && self.blink_cursor.read(cx).visible()
2364 && window.is_window_active()
2365 }
2366
2367 fn on_focus(&mut self, _: &mut Window, cx: &mut Context<Self>) {
2368 self.blink_cursor.update(cx, |cursor, cx| {
2369 cursor.start(cx);
2370 });
2371 cx.emit(InputEvent::Focus);
2372 }
2373
2374 fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2375 if M::is_context_menu_open(self, cx) {
2376 return;
2377 }
2378
2379 self.undo_manager.break_transaction_coalescing();
2380
2381 M::clear_hover_state(self, cx);
2385 self.diagnostic_popover = None;
2386 M::clear_inline_completion(self, cx);
2387 self.blink_cursor.update(cx, |cursor, cx| {
2388 cursor.stop(cx);
2389 });
2390 self.clamp_number_value(window, cx);
2391 cx.emit(InputEvent::Blur);
2392 cx.notify();
2393 }
2394
2395 fn clamp_number_value(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2400 if !self.is_single_line() {
2401 return;
2402 }
2403 if !matches!(self.mask_pattern, MaskPattern::Number { .. }) {
2404 return;
2405 }
2406 if self.number_min.is_none() && self.number_max.is_none() {
2407 return;
2408 }
2409
2410 let Ok(value) = self.unmask_value().parse::<f64>() else {
2411 return;
2412 };
2413
2414 let clamped = match (self.number_min, self.number_max) {
2415 (Some(min), _) if value < min => min,
2416 (_, Some(max)) if value > max => max,
2417 _ => return,
2418 };
2419
2420 let new_text = clamped.to_string();
2423 if !self.is_valid_input(&new_text, cx) {
2424 return;
2425 }
2426
2427 let range = self.range_to_utf16(&(0..self.text.len()));
2428 self.replace_text_in_range_silent(Some(range), &new_text, window, cx);
2429 }
2430
2431 pub(super) fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
2432 self.blink_cursor.update(cx, |cursor, cx| {
2433 cursor.pause(cx);
2434 });
2435 }
2436
2437 pub(super) fn on_key_down(&mut self, _: &KeyDownEvent, _: &mut Window, cx: &mut Context<Self>) {
2438 self.pause_blink_cursor(cx);
2439 }
2440
2441 pub(super) fn on_drag_move(
2442 &mut self,
2443 event: &MouseMoveEvent,
2444 window: &mut Window,
2445 cx: &mut Context<Self>,
2446 ) {
2447 if self.text.len() == 0 {
2448 return;
2449 }
2450
2451 if self.last_layout.is_none() {
2452 return;
2453 }
2454
2455 if !self.focus_handle.is_focused(window) {
2456 return;
2457 }
2458
2459 if !self.selecting {
2460 return;
2461 }
2462
2463 self.auto_scroll.last_drag_position = Some(event.position);
2464 let (offset, line_end_affinity) = self.index_for_mouse_position(event.position);
2465 self.select_to_with_affinity(offset, line_end_affinity, cx);
2466
2467 if !self.is_single_line() {
2468 let delta = AutoScroll::compute_delta(event.position.y, self.input_bounds);
2469 let scroll_delta = delta.map(|d| -d);
2471 self.auto_scroll.set(scroll_delta, cx, |delta, state, cx| {
2472 let current = state.scroll_handle.offset();
2473 state.update_scroll_offset(Some(point(current.x, current.y + delta)), cx);
2474 if let Some(pos) = state.auto_scroll.last_drag_position {
2475 let (offset, line_end_affinity) = state.index_for_mouse_position(pos);
2476 state.select_to_with_affinity(offset, line_end_affinity, cx);
2477 }
2478 });
2479 }
2480 }
2481
2482 fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
2488 let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
2489 normalize_number_input(new_text)
2490 } else {
2491 Cow::Borrowed(new_text)
2492 };
2493
2494 if self.is_single_line() && normalized.contains(['\n', '\r']) {
2495 Cow::Owned(normalized.replace(['\n', '\r'], ""))
2496 } else {
2497 normalized
2498 }
2499 }
2500
2501 pub(crate) fn is_valid_input(&self, new_text: &str, cx: &mut Context<Self>) -> bool {
2502 if new_text.is_empty() {
2503 return true;
2504 }
2505
2506 if let Some(validate) = &self.validate {
2507 if !validate(new_text, cx) {
2508 return false;
2509 }
2510 }
2511
2512 if !self.mask_pattern.is_valid(new_text) {
2513 return false;
2514 }
2515
2516 let Some(pattern) = &self.pattern else {
2517 return true;
2518 };
2519
2520 pattern.is_match(new_text)
2521 }
2522
2523 pub fn mask_pattern(mut self, pattern: impl Into<MaskPattern>) -> Self {
2533 self.mask_pattern = pattern.into();
2534 self.mask_pattern_set = true;
2535 if let Some(placeholder) = self.mask_pattern.placeholder() {
2536 self.placeholder = placeholder.into();
2537 }
2538 self
2539 }
2540
2541 pub fn set_mask_pattern(
2542 &mut self,
2543 pattern: impl Into<MaskPattern>,
2544 _: &mut Window,
2545 cx: &mut Context<Self>,
2546 ) {
2547 self.mask_pattern = pattern.into();
2548 self.mask_pattern_set = true;
2549 if let Some(placeholder) = self.mask_pattern.placeholder() {
2550 self.placeholder = placeholder.into();
2551 }
2552 cx.notify();
2553 }
2554
2555 pub fn ensure_number_mask(&mut self) {
2557 if self.mask_pattern_set {
2558 return;
2559 }
2560 self.mask_pattern = MaskPattern::Number {
2561 separator: None,
2562 fraction: None,
2563 };
2564 }
2565
2566 pub(super) fn set_input_bounds(&mut self, new_bounds: Bounds<Pixels>, cx: &mut Context<Self>) {
2567 let wrap_width_changed = self.input_bounds.size.width != new_bounds.size.width;
2568 self.input_bounds = new_bounds;
2569
2570 if let Some(last_layout) = self.last_layout.as_ref() {
2572 if wrap_width_changed {
2573 let wrap_width = if !self.soft_wrap {
2574 None
2576 } else {
2577 last_layout.wrap_width
2578 };
2579
2580 self.display_map.on_layout_changed(wrap_width, cx);
2581 if self.is_multi_line() {
2582 self.mode.update_auto_grow(&self.display_map);
2583 }
2584 cx.notify();
2585 }
2586 }
2587 }
2588
2589 pub fn selected_text(&self) -> RopeSlice<'_> {
2594 let range_utf16 = self.range_to_utf16(&self.selected_range.into());
2595 let range = self.range_from_utf16(&range_utf16);
2596 self.text.slice(range)
2597 }
2598
2599 pub fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
2603 let Some(last_layout) = self.last_layout.as_ref() else {
2604 return None;
2605 };
2606
2607 let Some(last_bounds) = self.last_bounds else {
2608 return None;
2609 };
2610
2611 let (_, _, start_pos) = self.line_and_position_for_offset(range.start);
2612 let (_, _, end_pos) = self.line_and_position_for_offset(range.end);
2613
2614 let Some(start_pos) = start_pos else {
2615 return None;
2616 };
2617 let Some(end_pos) = end_pos else {
2618 return None;
2619 };
2620
2621 Some(Bounds::from_corners(
2622 last_bounds.origin + start_pos,
2623 last_bounds.origin + end_pos + point(px(0.), last_layout.line_height),
2624 ))
2625 }
2626
2627 pub(crate) fn replace_text_in_range_silent(
2631 &mut self,
2632 range_utf16: Option<Range<usize>>,
2633 new_text: &str,
2634 window: &mut Window,
2635 cx: &mut Context<Self>,
2636 ) {
2637 self.silent_replace_text = true;
2638 self.replace_text_in_range(range_utf16, new_text, window, cx);
2639 self.silent_replace_text = false;
2640 }
2641
2642 fn update_fold_candidates(&mut self) {
2645 if !self.mode.is_folding() {
2646 return;
2647 }
2648
2649 let Some(highlighter_rc) = self.mode.highlighter() else {
2650 return;
2651 };
2652
2653 let highlighter = highlighter_rc.borrow();
2654 let Some(highlighter) = highlighter.as_ref() else {
2655 return;
2656 };
2657
2658 let fold_ranges = highlighter.fold_ranges(&self.text);
2659 self.display_map.set_fold_candidates(fold_ranges);
2660 }
2661
2662 fn update_fold_candidates_incremental(&mut self, edit_range: &Range<usize>, new_text: &str) {
2665 if !self.mode.is_folding() {
2666 return;
2667 }
2668
2669 let Some(highlighter_rc) = self.mode.highlighter() else {
2670 return;
2671 };
2672
2673 let highlighter = highlighter_rc.borrow();
2674 let Some(highlighter) = highlighter.as_ref() else {
2675 return;
2676 };
2677
2678 let new_end = edit_range.start + new_text.len();
2680 self.display_map.update_fold_candidates_for_edit(
2681 |range, text| highlighter.fold_ranges_for_edit(range, text),
2682 edit_range.start..new_end,
2683 &self.text,
2684 );
2685 }
2686}
2687
2688impl<M: InputModeKind> EntityInputHandler for InputBaseState<M> {
2689 fn text_for_range(
2690 &mut self,
2691 range_utf16: Range<usize>,
2692 adjusted_range: &mut Option<Range<usize>>,
2693 _window: &mut Window,
2694 _cx: &mut Context<Self>,
2695 ) -> Option<String> {
2696 let range = self.range_from_utf16(&range_utf16);
2697 adjusted_range.replace(self.range_to_utf16(&range));
2698 Some(self.text.slice(range).to_string())
2699 }
2700
2701 fn selected_text_range(
2702 &mut self,
2703 _ignore_disabled_input: bool,
2704 _window: &mut Window,
2705 _cx: &mut Context<Self>,
2706 ) -> Option<UTF16Selection> {
2707 Some(UTF16Selection {
2708 range: self.range_to_utf16(&self.selected_range.into()),
2709 reversed: false,
2710 })
2711 }
2712
2713 fn marked_text_range(
2714 &self,
2715 _window: &mut Window,
2716 _cx: &mut Context<Self>,
2717 ) -> Option<Range<usize>> {
2718 self.ime_marked_range
2719 .map(|range| self.range_to_utf16(&range.into()))
2720 }
2721
2722 fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
2723 self.ime_marked_range = None;
2724 self.undo_manager.commit_transaction();
2725 }
2726
2727 fn replace_text_in_range(
2732 &mut self,
2733 range_utf16: Option<Range<usize>>,
2734 new_text: &str,
2735 window: &mut Window,
2736 cx: &mut Context<Self>,
2737 ) {
2738 let requested_intent = self.undo_manager.pending_intent.take();
2739 if !self.is_editable() {
2740 return;
2741 }
2742 let selection_before = self.selected_range;
2743
2744 if self.blink_cursor.read(cx).visible() {
2745 self.pause_blink_cursor(cx);
2746 }
2747
2748 let new_text = self.normalize_input(new_text);
2752 let new_text: &str = &new_text;
2753
2754 let range = range_utf16
2755 .as_ref()
2756 .map(|range_utf16| self.range_from_utf16(range_utf16))
2757 .or(self.ime_marked_range.map(|range| {
2758 let range = self.range_to_utf16(&(range.start..range.end));
2759 self.range_from_utf16(&range)
2760 }))
2761 .unwrap_or(self.selected_range.into());
2762
2763 let old_text = self.text.clone();
2764 self.text.replace(range.clone(), new_text);
2765
2766 let mut new_offset = (range.start + new_text.len()).min(self.text.len());
2767
2768 let mut mask_changed = false;
2771
2772 if self.is_single_line() {
2773 let pending_text = self.text.to_string();
2774 if !self.is_valid_input(&pending_text, cx)
2780 && self.is_valid_input(&old_text.to_string(), cx)
2781 {
2782 self.text = old_text;
2783 return;
2784 }
2785
2786 if !self.mask_pattern.is_none() {
2787 let mask_text = self.mask_pattern.mask(&pending_text);
2788 mask_changed = mask_text.as_str() != pending_text;
2789 self.text = Rope::from(mask_text.as_str());
2790 let new_text_len =
2791 (new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
2792 new_offset = (range.start + new_text_len).min(mask_text.len());
2793 }
2794 }
2795
2796 if mask_changed {
2797 M::reset_annotations(self);
2800 } else {
2801 M::adjust_annotations(self, &range, new_text.len());
2802 }
2803 if mask_changed {
2804 self.push_history(
2808 &old_text,
2809 &(0..old_text.len()),
2810 &self.text.to_string(),
2811 Some(EditIntent::Atomic),
2812 selection_before,
2813 Some(Selection::new(new_offset, new_offset)),
2814 );
2815 } else {
2816 self.push_history(
2817 &old_text,
2818 &range,
2819 &new_text,
2820 requested_intent,
2821 selection_before,
2822 None,
2823 );
2824 }
2825 self.undo_manager.commit_transaction();
2831 if let Some(diagnostics) = self.mode.diagnostics_mut() {
2832 diagnostics.reset(&self.text)
2833 }
2834 self.display_map
2836 .adjust_folds_for_edit(&old_text, &range, new_text);
2837 self.display_map
2838 .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
2839
2840 self.mode.update_highlighter::<M>(
2841 super::mode::HighlighterUpdate {
2842 selected_range: &range,
2843 old_text: &old_text,
2844 new_text: &self.text,
2845 change_text: &new_text,
2846 force: true,
2847 },
2848 window,
2849 cx,
2850 );
2851
2852 self.update_fold_candidates_incremental(&range, new_text);
2853 M::refresh_language_features(self, window, cx);
2854 self.selected_range = (new_offset..new_offset).into();
2855 self.ime_marked_range.take();
2856 self.update_preferred_column();
2857 self.update_search(cx);
2858 if self.is_multi_line() {
2859 self.mode.update_auto_grow(&self.display_map);
2860 }
2861 if !self.silent_replace_text {
2862 M::on_text_typed(self, &range, &new_text, window, cx);
2863 }
2864 if self.emit_events {
2865 cx.emit(InputEvent::Change);
2866 }
2867 cx.notify();
2868 }
2869
2870 fn replace_and_mark_text_in_range(
2872 &mut self,
2873 range_utf16: Option<Range<usize>>,
2874 new_text: &str,
2875 new_selected_range_utf16: Option<Range<usize>>,
2876 window: &mut Window,
2877 cx: &mut Context<Self>,
2878 ) {
2879 let requested_intent = self.undo_manager.pending_intent.take();
2880 if !self.is_editable() {
2881 return;
2882 }
2883 let selection_before = self.selected_range;
2884
2885 let starts_composition = self.ime_marked_range.is_none();
2886 if starts_composition {
2887 self.undo_manager.begin_transaction();
2888 }
2889
2890 M::reset_language_features(self);
2891
2892 let new_text = self.normalize_input(new_text);
2894 let new_text: &str = &new_text;
2895
2896 let range = range_utf16
2897 .as_ref()
2898 .map(|range_utf16| self.range_from_utf16(range_utf16))
2899 .or(self.ime_marked_range.map(|range| {
2900 let range = self.range_to_utf16(&(range.start..range.end));
2901 self.range_from_utf16(&range)
2902 }))
2903 .unwrap_or(self.selected_range.into());
2904
2905 let old_text = self.text.clone();
2906 self.text.replace(range.clone(), new_text);
2907
2908 if self.is_single_line() {
2909 let pending_text = self.text.to_string();
2910 if !self.is_valid_input(&pending_text, cx)
2912 && self.is_valid_input(&old_text.to_string(), cx)
2913 {
2914 self.text = old_text;
2915 if starts_composition {
2916 self.undo_manager.commit_transaction();
2917 }
2918 return;
2919 }
2920 }
2921
2922 M::adjust_annotations(self, &range, new_text.len());
2923 if let Some(diagnostics) = self.mode.diagnostics_mut() {
2924 diagnostics.reset(&self.text)
2925 }
2926 self.display_map
2928 .adjust_folds_for_edit(&old_text, &range, new_text);
2929 self.display_map
2930 .on_text_changed(&self.text, &range, &Rope::from(new_text), cx);
2931
2932 self.mode.update_highlighter::<M>(
2933 super::mode::HighlighterUpdate {
2934 selected_range: &range,
2935 old_text: &old_text,
2936 new_text: &self.text,
2937 change_text: &new_text,
2938 force: true,
2939 },
2940 window,
2941 cx,
2942 );
2943
2944 self.update_fold_candidates_incremental(&range, new_text);
2945 M::refresh_language_features(self, window, cx);
2946 if new_text.is_empty() {
2947 self.selected_range = (range.start..range.start).into();
2949 self.ime_marked_range = None;
2950 } else {
2951 self.ime_marked_range = Some((range.start..range.start + new_text.len()).into());
2952 self.selected_range = new_selected_range_utf16
2953 .as_ref()
2954 .map(|range_utf16| {
2955 let new_text = Rope::from(new_text);
2956 range.start + new_text.offset_utf16_to_offset(range_utf16.start)
2957 ..range.start + new_text.offset_utf16_to_offset(range_utf16.end)
2958 })
2959 .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len())
2960 .into();
2961 }
2962 if self.is_multi_line() {
2963 self.mode.update_auto_grow(&self.display_map);
2964 }
2965 self.push_history(
2966 &old_text,
2967 &range,
2968 new_text,
2969 requested_intent,
2970 selection_before,
2971 Some(self.selected_range),
2972 );
2973 if new_text.is_empty() {
2974 self.undo_manager.commit_transaction();
2975 }
2976 cx.notify();
2977 }
2978
2979 fn bounds_for_range(
2981 &mut self,
2982 range_utf16: Range<usize>,
2983 bounds: Bounds<Pixels>,
2984 _window: &mut Window,
2985 _cx: &mut Context<Self>,
2986 ) -> Option<Bounds<Pixels>> {
2987 let last_layout = self.last_layout.as_ref()?;
2988 let line_height = last_layout.line_height;
2989 let line_number_width = last_layout.line_number_width;
2990 let range = self.range_from_utf16(&range_utf16);
2991
2992 let mut start_origin = None;
2993 let mut end_origin = None;
2994 let line_number_origin = point(line_number_width, px(0.));
2995 let mut y_offset = last_layout.visible_top;
2996
2997 for (vi, line) in last_layout.lines.iter().enumerate() {
2998 if start_origin.is_some() && end_origin.is_some() {
2999 break;
3000 }
3001
3002 let index_offset = last_layout.visible_line_byte_offsets[vi];
3003
3004 if start_origin.is_none() {
3005 if let Some(p) = line.position_for_index(
3006 range.start.saturating_sub(index_offset),
3007 last_layout,
3008 false,
3009 ) {
3010 start_origin = Some(p + point(px(0.), y_offset));
3011 }
3012 }
3013
3014 if end_origin.is_none() {
3015 if let Some(p) = line.position_for_index(
3016 range.end.saturating_sub(index_offset),
3017 last_layout,
3018 false,
3019 ) {
3020 end_origin = Some(p + point(px(0.), y_offset));
3021 }
3022 }
3023
3024 y_offset += line.size(line_height).height;
3025 }
3026
3027 let start_origin = start_origin.unwrap_or_default();
3028 let mut end_origin = end_origin.unwrap_or_default();
3029 end_origin.y = start_origin.y;
3031
3032 Some(Bounds::from_corners(
3033 bounds.origin + line_number_origin + start_origin,
3034 bounds.origin + line_number_origin + point(end_origin.x, end_origin.y + line_height),
3036 ))
3037 }
3038
3039 fn character_index_for_point(
3040 &mut self,
3041 point: gpui::Point<Pixels>,
3042 _window: &mut Window,
3043 _cx: &mut Context<Self>,
3044 ) -> Option<usize> {
3045 let last_layout = self.last_layout.as_ref()?;
3046 let line_point = self.last_bounds?.localize(&point)?;
3047
3048 for (vi, line) in last_layout.lines.iter().enumerate() {
3049 let offset = last_layout.visible_line_byte_offsets[vi];
3050 if let Some(utf8_index) = line.index_for_position(line_point, last_layout) {
3051 return Some(self.offset_to_utf16(offset + utf8_index));
3052 }
3053 }
3054
3055 None
3056 }
3057}
3058
3059impl<M: InputModeKind> Focusable for InputBaseState<M> {
3060 fn focus_handle(&self, _cx: &App) -> FocusHandle {
3061 self.focus_handle.clone()
3062 }
3063}
3064
3065impl<M: InputModeKind> Render for InputBaseState<M> {
3066 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3067 self.editor_style = self
3070 .projected_editor_style
3071 .resolved(&crate::Theme::global(cx).tokens);
3072 let entity = cx.entity();
3073 if self._pending_update {
3074 self.mode.update_highlighter::<M>(
3075 super::mode::HighlighterUpdate {
3076 selected_range: &(0..0),
3077 old_text: &self.text,
3078 new_text: &self.text,
3079 change_text: "",
3080 force: false,
3081 },
3082 window,
3083 cx,
3084 );
3085
3086 self.update_fold_candidates();
3087 M::refresh_language_features(self, window, cx);
3088 self._pending_update = false;
3089 }
3090
3091 let element = div()
3092 .id("input-state")
3093 .key_context(CONTEXT)
3094 .track_focus(&self.focus_handle)
3095 .when(self.is_editable(), |this| {
3096 this.on_action(window.listener_for(&entity, InputBaseState::backspace))
3097 .on_action(window.listener_for(&entity, InputBaseState::delete))
3098 .on_action(
3099 window.listener_for(&entity, InputBaseState::delete_to_beginning_of_line),
3100 )
3101 .on_action(window.listener_for(&entity, InputBaseState::delete_to_end_of_line))
3102 .on_action(window.listener_for(&entity, InputBaseState::delete_previous_word))
3103 .on_action(window.listener_for(&entity, InputBaseState::delete_next_word))
3104 .on_action(window.listener_for(&entity, InputBaseState::enter))
3105 .on_action(window.listener_for(&entity, InputBaseState::escape))
3106 .on_action(window.listener_for(&entity, InputBaseState::paste))
3107 .on_action(window.listener_for(&entity, InputBaseState::cut))
3108 .on_action(window.listener_for(&entity, InputBaseState::undo))
3109 .on_action(window.listener_for(&entity, InputBaseState::redo))
3110 .when(self.is_multi_line(), |this| {
3111 this.on_action(window.listener_for(&entity, InputBaseState::indent_inline))
3112 .on_action(window.listener_for(&entity, InputBaseState::outdent_inline))
3113 .on_action(window.listener_for(&entity, InputBaseState::indent_block))
3114 .on_action(window.listener_for(&entity, InputBaseState::outdent_block))
3115 })
3116 })
3117 .on_action(window.listener_for(&entity, InputBaseState::left))
3118 .on_action(window.listener_for(&entity, InputBaseState::right))
3119 .on_action(window.listener_for(&entity, InputBaseState::select_left))
3120 .on_action(window.listener_for(&entity, InputBaseState::select_right))
3121 .when(self.is_multi_line(), |this| {
3122 this.on_action(window.listener_for(&entity, InputBaseState::up))
3123 .on_action(window.listener_for(&entity, InputBaseState::down))
3124 .on_action(window.listener_for(&entity, InputBaseState::select_up))
3125 .on_action(window.listener_for(&entity, InputBaseState::select_down))
3126 .on_action(window.listener_for(&entity, InputBaseState::page_up))
3127 .on_action(window.listener_for(&entity, InputBaseState::page_down))
3128 })
3129 .on_action(window.listener_for(&entity, InputBaseState::on_action_select_all))
3130 .on_action(window.listener_for(&entity, InputBaseState::select_to_start_of_line))
3131 .on_action(window.listener_for(&entity, InputBaseState::select_to_end_of_line))
3132 .on_action(window.listener_for(&entity, InputBaseState::select_to_previous_word))
3133 .on_action(window.listener_for(&entity, InputBaseState::select_to_next_word))
3134 .on_action(window.listener_for(&entity, InputBaseState::home))
3135 .on_action(window.listener_for(&entity, InputBaseState::end))
3136 .on_action(window.listener_for(&entity, InputBaseState::move_to_start))
3137 .on_action(window.listener_for(&entity, InputBaseState::move_to_end))
3138 .on_action(window.listener_for(&entity, InputBaseState::move_to_previous_word))
3139 .on_action(window.listener_for(&entity, InputBaseState::move_to_next_word))
3140 .on_action(window.listener_for(&entity, InputBaseState::select_to_start))
3141 .on_action(window.listener_for(&entity, InputBaseState::select_to_end))
3142 .on_action(window.listener_for(&entity, InputBaseState::show_character_palette))
3143 .on_action(window.listener_for(&entity, InputBaseState::copy))
3144 .on_action(window.listener_for(&entity, InputBaseState::on_action_search))
3145 .on_action(window.listener_for(&entity, InputBaseState::on_action_replace))
3146 .on_key_down(window.listener_for(&entity, InputBaseState::on_key_down))
3147 .on_mouse_down(
3148 MouseButton::Left,
3149 window.listener_for(&entity, InputBaseState::on_mouse_down),
3150 )
3151 .on_mouse_down(
3152 MouseButton::Right,
3153 window.listener_for(&entity, InputBaseState::on_mouse_down),
3154 )
3155 .on_mouse_up(
3156 MouseButton::Left,
3157 window.listener_for(&entity, InputBaseState::on_mouse_up),
3158 )
3159 .on_mouse_up(
3160 MouseButton::Right,
3161 window.listener_for(&entity, InputBaseState::on_mouse_up),
3162 )
3163 .on_mouse_move(window.listener_for(&entity, InputBaseState::on_mouse_move))
3164 .on_scroll_wheel(window.listener_for(&entity, InputBaseState::on_scroll_wheel))
3165 .when(!self.disabled, |this| this.cursor_text())
3166 .flex_1()
3167 .when(self.is_multi_line(), |this| this.h_full())
3168 .flex_grow_1()
3169 .overflow_x_hidden()
3170 .when(self.is_multi_line(), |this| {
3171 this.pt(self.editor_paddings.top)
3172 .pr(self.editor_paddings.right)
3173 .pb(self.editor_paddings.bottom)
3174 .pl(self.editor_paddings.left)
3175 })
3176 .child(TextElement::new(entity.clone()).placeholder(self.placeholder.clone()))
3177 .when(self.shows_scrollbar(), |this| {
3178 this.child(EditorScrollbar::new(entity.clone()))
3179 });
3180
3181 M::register_actions(element, &entity, window)
3184 }
3185}
3186
3187#[cfg(test)]
3188mod tests {
3189 use super::*;
3190
3191 use crate::theme::Theme;
3192 use gpui::{TestAppContext, VisualTestContext};
3193
3194 use crate::input::{EditorMode, InputMode, TextareaMode};
3195
3196 struct TestRoot<M: InputModeKind>(Entity<InputBaseState<M>>);
3197
3198 impl<M: InputModeKind> Render for TestRoot<M> {
3199 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3200 div().size_full().child(self.0.clone())
3201 }
3202 }
3203
3204 struct InputView<M: InputModeKind> {
3205 input: Entity<InputBaseState<M>>,
3206 window_handle: gpui::WindowHandle<TestRoot<M>>,
3207 }
3208
3209 impl<M: InputModeKind> InputView<M> {
3211 fn build_with(
3212 cx: &mut TestAppContext,
3213 make: impl FnOnce(&mut Window, &mut Context<InputBaseState<M>>) -> InputBaseState<M>
3214 + 'static,
3215 ) -> Self {
3216 let mut input: Option<Entity<InputBaseState<M>>> = None;
3217
3218 let window = cx.update(|cx| {
3219 cx.open_window(Default::default(), |window, cx| {
3220 cx.set_global(Theme::default());
3222 super::super::init(cx);
3224
3225 input = Some(cx.new(|cx| make(window, cx)));
3226
3227 cx.new(|_| TestRoot(input.clone().unwrap()))
3228 })
3229 .unwrap()
3230 });
3231
3232 Self {
3233 input: input.clone().unwrap(),
3234 window_handle: window,
3235 }
3236 }
3237 }
3238
3239 impl InputView<EditorMode> {
3240 fn new(cx: &mut TestAppContext) -> Self {
3242 Self::build_editor(cx, |state| state)
3243 }
3244
3245 fn build_editor(
3246 cx: &mut TestAppContext,
3247 f: impl FnOnce(InputBaseState<EditorMode>) -> InputBaseState<EditorMode> + 'static,
3248 ) -> Self {
3249 Self::build_with(cx, move |window, cx| {
3250 f(crate::input::EditorState::new(window, cx).language("sql"))
3251 })
3252 }
3253 }
3254
3255 impl InputView<TextareaMode> {
3256 fn build_textarea(
3257 cx: &mut TestAppContext,
3258 f: impl FnOnce(InputBaseState<TextareaMode>) -> InputBaseState<TextareaMode> + 'static,
3259 ) -> Self {
3260 Self::build_with(cx, move |window, cx| {
3261 f(crate::input::TextareaState::new(window, cx))
3262 })
3263 }
3264 }
3265
3266 impl InputView<InputMode> {
3267 fn build(
3269 cx: &mut TestAppContext,
3270 f: impl FnOnce(InputBaseState<InputMode>) -> InputBaseState<InputMode> + 'static,
3271 ) -> Self {
3272 Self::build_with(cx, move |window, cx| {
3273 f(crate::input::InputState::new(window, cx))
3274 })
3275 }
3276 }
3277
3278 #[gpui::test]
3279 fn only_a_multi_line_input_paints_scrollbars(cx: &mut TestAppContext) {
3280 cx.update(crate::init);
3281
3282 let single = InputView::build(cx, |state| state);
3286 single
3287 .input
3288 .update(cx, |state, _| assert!(!state.shows_scrollbar()));
3289
3290 let multi = InputView::build_textarea(cx, |state| state);
3291 multi
3292 .input
3293 .update(cx, |state, _| assert!(state.shows_scrollbar()));
3294 }
3295
3296 #[gpui::test]
3297 fn context_menu_handler_is_deferred_and_respects_disabled(cx: &mut TestAppContext) {
3298 use std::{cell::Cell, rc::Rc};
3299 cx.update(crate::init);
3300 let input_view = InputView::new(cx);
3301 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3302 let input = input_view.input;
3303 let calls = Rc::new(Cell::new(0usize));
3304 let items = Rc::new(Cell::new(0usize));
3305
3306 cx.update(|window, cx| {
3307 input.update(cx, |state, cx| {
3308 let calls2 = calls.clone();
3309 let items2 = items.clone();
3310 state.on_context_menu(Rc::new(move |menu, _, _, _, _| {
3311 calls2.set(calls2.get() + 1);
3312 items2.set(menu.items.len());
3313 }));
3314 state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
3315 })
3316 });
3317 assert_eq!(calls.get(), 1);
3318 assert_eq!(items.get(), 0);
3319
3320 cx.update(|window, cx| {
3321 input.update(cx, |state, cx| {
3322 state.disabled = true;
3323 state.handle_right_click_menu(point(px(0.), px(0.)), 0, window, cx);
3324 })
3325 });
3326 assert_eq!(calls.get(), 1);
3327 }
3328
3329 #[gpui::test]
3330 fn test_readonly_rejects_user_edits_only(cx: &mut TestAppContext) {
3331 let input_view = InputView::new(cx);
3332 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3333 let input = input_view.input;
3334
3335 cx.update(|window, cx| {
3336 input.update(cx, |state, cx| {
3337 state.set_value("hello", window, cx);
3338 state.set_readonly(true, cx);
3339 });
3340 });
3341
3342 cx.update(|_, cx| {
3343 input.read_with(cx, |state, _| {
3344 assert!(!state.is_editable());
3345 assert!(!state.is_replaceable());
3346 });
3347 });
3348
3349 cx.update(|window, cx| {
3351 input.update(cx, |state, cx| {
3352 state.replace_text_in_range(None, " world", window, cx);
3353 state.replace_and_mark_text_in_range(None, "γ", None, window, cx);
3354 });
3355 });
3356 cx.update(|_, cx| {
3357 input.read_with(cx, |state, _| assert_eq!(state.value(), "hello"));
3358 });
3359
3360 cx.update(|window, cx| {
3362 input.update(cx, |state, cx| {
3363 state.insert(" world", window, cx);
3364 state.set_value("changed", window, cx);
3365 });
3366 });
3367 cx.update(|_, cx| {
3368 input.read_with(cx, |state, _| assert_eq!(state.value(), "changed"));
3369 });
3370
3371 cx.update(|window, cx| {
3374 input.update(cx, |state, cx| {
3375 state.set_readonly(false, cx);
3376 state.replace_text_in_range(None, "!", window, cx);
3377 });
3378 });
3379 cx.update(|_, cx| {
3380 input.read_with(cx, |state, _| {
3381 assert!(state.is_editable());
3382 assert_eq!(state.value(), "!changed");
3383 });
3384 });
3385 }
3386
3387 #[gpui::test]
3393 fn test_scroll_to_eob_does_not_overshoot_safe_range(cx: &mut TestAppContext) {
3394 let input_view = InputView::new(cx);
3395 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3396 let input = input_view.input;
3397
3398 cx.update(|window, cx| {
3400 input.update(cx, |state, cx| {
3401 state.set_scroll_beyond_last_line(Some(1), window, cx);
3402 state.set_cursor_surrounding_lines(Some(1), window, cx);
3403 let text: String = (1..=50)
3404 .map(|i| format!("line {i}"))
3405 .collect::<Vec<_>>()
3406 .join("\n");
3407 state.set_value(text, window, cx);
3408 });
3409 });
3410 cx.run_until_parked();
3411
3412 cx.update(|_, cx| {
3415 input.read_with(cx, |state, _| {
3416 assert!(
3417 state.scroll_size.height > px(0.),
3418 "scroll_size not populated by initial paint"
3419 );
3420 assert!(
3421 state.input_bounds.size.height > px(0.),
3422 "input_bounds not populated by initial paint"
3423 );
3424 });
3425 });
3426
3427 cx.update(|_, cx| {
3432 input.update(cx, |state, cx| {
3433 let end = state.text.len();
3434 state.move_to(end, Some(MoveDirection::Down), cx);
3435
3436 let deferred = state
3437 .deferred_scroll_offset
3438 .expect("scroll_to should populate deferred_scroll_offset");
3439 let safe_y_min =
3440 (-state.scroll_size.height + state.input_bounds.size.height).min(px(0.));
3441
3442 assert!(
3443 deferred.y >= safe_y_min,
3444 "deferred_scroll_offset.y = {:?} below safe_y_min = {:?} \
3445 β paint would jitter (Bug C regression)",
3446 deferred.y,
3447 safe_y_min,
3448 );
3449 });
3450 });
3451 }
3452
3453 #[gpui::test]
3454 fn test_number_step(cx: &mut TestAppContext) {
3455 let input = InputView::build(cx, |state| state).input;
3456
3457 cx.update(|cx| {
3458 input.update(cx, |_state, cx| {
3459 assert_eq!(
3460 NumberStep::from(5.).value(123., StepAction::Increment, cx),
3461 5.
3462 );
3463
3464 let step = NumberStep::by_value(|value, action, _cx| {
3467 let below = match action {
3468 StepAction::Increment => value < 1.0,
3469 StepAction::Decrement => value <= 1.0,
3470 };
3471 if below { 0.1 } else { 0.5 }
3472 });
3473 assert_eq!(step.value(0.5, StepAction::Increment, cx), 0.1);
3474 assert_eq!(step.value(1.0, StepAction::Increment, cx), 0.5);
3475 assert_eq!(step.value(1.0, StepAction::Decrement, cx), 0.1);
3476 assert_eq!(step.value(2.0, StepAction::Decrement, cx), 0.5);
3477 });
3478 });
3479 }
3480
3481 #[gpui::test]
3482 fn test_number_input_normalization(cx: &mut TestAppContext) {
3483 let input_view = InputView::build(cx, |state| {
3484 state.mask_pattern(MaskPattern::Number {
3485 separator: None,
3486 fraction: None,
3487 })
3488 });
3489 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3490 let input = input_view.input;
3491
3492 cx.update(|window, cx| {
3496 input.update(cx, |state, cx| {
3497 state.replace_text_in_range(None, "12γ5", window, cx);
3498 });
3499 });
3500 cx.run_until_parked();
3501 cx.update(|_, cx| {
3502 input.read_with(cx, |state, _| {
3503 assert_eq!(state.value(), "12.5");
3504 let cursor: Range<usize> = state.selected_range.into();
3505 assert_eq!(cursor, 4..4);
3506 });
3507 });
3508
3509 cx.update(|window, cx| {
3511 input.update(cx, |state, cx| {
3512 state.replace_text_in_range(None, "abc", window, cx);
3513 });
3514 });
3515 cx.run_until_parked();
3516 cx.update(|_, cx| {
3517 input.read_with(cx, |state, _| {
3518 assert_eq!(state.value(), "12.5");
3519 });
3520 });
3521
3522 cx.update(|window, cx| {
3525 input.update(cx, |state, cx| {
3526 let range = state.range_to_utf16(&(0..state.text.len()));
3527 state.replace_text_in_range(Some(range), "γ", window, cx);
3528 });
3529 });
3530 cx.run_until_parked();
3531 cx.update(|_, cx| {
3532 input.read_with(cx, |state, _| {
3533 assert_eq!(state.value(), ".");
3534 let cursor: Range<usize> = state.selected_range.into();
3535 assert_eq!(cursor, 1..1);
3536 });
3537 });
3538 }
3539
3540 #[gpui::test]
3541 fn test_number_input_normalization_with_separator(cx: &mut TestAppContext) {
3542 let input_view = InputView::build(cx, |state| {
3543 state.mask_pattern(MaskPattern::Number {
3544 separator: Some(','),
3545 fraction: Some(2),
3546 })
3547 });
3548 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3549 let input = input_view.input;
3550
3551 cx.update(|window, cx| {
3552 input.update(cx, |state, cx| {
3553 state.replace_text_in_range(None, "1234", window, cx);
3554 });
3555 });
3556 cx.run_until_parked();
3557 cx.update(|_, cx| {
3558 input.read_with(cx, |state, _| {
3559 assert_eq!(state.value(), "1,234");
3560 assert_eq!(state.unmask_value(), "1234");
3561 });
3562 });
3563 }
3564
3565 #[gpui::test]
3566 fn test_number_input_clamp_on_blur(cx: &mut TestAppContext) {
3567 let input_view = InputView::build(cx, |state| {
3568 state
3569 .mask_pattern(MaskPattern::Number {
3570 separator: None,
3571 fraction: None,
3572 })
3573 .min(10.)
3574 .max(100.)
3575 });
3576 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3577 let input = input_view.input;
3578
3579 cx.update(|window, cx| {
3581 input.update(cx, |state, cx| {
3582 state.replace_text_in_range(None, "1000", window, cx);
3583 assert_eq!(state.value(), "1000");
3584 state.clamp_number_value(window, cx);
3585 assert_eq!(state.value(), "100");
3586
3587 let range = state.range_to_utf16(&(0..state.text.len()));
3588 state.replace_text_in_range(Some(range), "1", window, cx);
3589 assert_eq!(state.value(), "1");
3590 state.clamp_number_value(window, cx);
3591 assert_eq!(state.value(), "10");
3592 });
3593 });
3594 }
3595
3596 #[gpui::test]
3597 fn test_number_input_undo_with_mask(cx: &mut TestAppContext) {
3598 let input_view = InputView::build(cx, |state| {
3599 state.mask_pattern(MaskPattern::Number {
3600 separator: Some(','),
3601 fraction: None,
3602 })
3603 });
3604 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3605 let input = input_view.input;
3606
3607 cx.update(|window, cx| {
3610 input.update(cx, |state, cx| {
3611 state.replace_text_in_range(None, "1234", window, cx);
3612 assert_eq!(state.value(), "1,234");
3613 state.replace_text_in_range(None, "5", window, cx);
3614 assert_eq!(state.value(), "12,345");
3615
3616 state.undo(&Undo, window, cx);
3620 assert_eq!(state.value(), "1,234");
3621 state.undo(&Undo, window, cx);
3622 assert_eq!(state.value(), "");
3623 state.redo(&Redo, window, cx);
3624 assert_eq!(state.value(), "1,234");
3625 state.redo(&Redo, window, cx);
3626 assert_eq!(state.value(), "12,345");
3627 });
3628 });
3629 }
3630
3631 #[gpui::test]
3632 fn test_undo_manager_coalesces_adjacent_typing_transactions(cx: &mut TestAppContext) {
3633 let input_view = InputView::build(cx, |state| state);
3634 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3635 let input = input_view.input;
3636
3637 cx.update(|window, cx| {
3638 input.update(cx, |state, cx| {
3639 state.replace_text_in_range(None, "a", window, cx);
3640 state.replace_text_in_range(None, "b", window, cx);
3641 assert_eq!(state.value(), "ab");
3642
3643 state.undo(&Undo, window, cx);
3644 assert_eq!(state.value(), "");
3645 });
3646 });
3647 }
3648
3649 #[gpui::test]
3650 fn test_undo_manager_cursor_movement_splits_typing(cx: &mut TestAppContext) {
3651 let input_view = InputView::build(cx, |state| state);
3652 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3653 let input = input_view.input;
3654
3655 cx.update(|window, cx| {
3656 input.update(cx, |state, cx| {
3657 state.replace_text_in_range(None, "a", window, cx);
3658 state.replace_text_in_range(None, "b", window, cx);
3659 state.left(&MoveLeft, window, cx);
3660 state.replace_text_in_range(None, "x", window, cx);
3661 assert_eq!(state.value(), "axb");
3662
3663 state.undo(&Undo, window, cx);
3664 assert_eq!(state.value(), "ab");
3665 state.undo(&Undo, window, cx);
3666 assert_eq!(state.value(), "");
3667 });
3668 });
3669 }
3670
3671 #[gpui::test]
3672 fn test_undo_manager_splits_backward_and_forward_delete(cx: &mut TestAppContext) {
3673 let input_view = InputView::build(cx, |state| state);
3674 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3675 let input = input_view.input;
3676
3677 cx.update(|window, cx| {
3678 input.update(cx, |state, cx| {
3679 state.set_value("abcd", window, cx);
3680 state.set_selected_range(2..2, cx);
3681 state.backspace(&Backspace, window, cx);
3682 state.delete(&Delete, window, cx);
3683 assert_eq!(state.value(), "ad");
3684
3685 state.undo(&Undo, window, cx);
3686 assert_eq!(state.value(), "acd");
3687 state.undo(&Undo, window, cx);
3688 assert_eq!(state.value(), "abcd");
3689 });
3690 });
3691 }
3692
3693 #[gpui::test]
3694 fn test_undo_manager_coalesces_directional_character_deletes(cx: &mut TestAppContext) {
3695 let input_view = InputView::build(cx, |state| state);
3696 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3697 let input = input_view.input;
3698
3699 cx.update(|window, cx| {
3700 input.update(cx, |state, cx| {
3701 state.set_value("abcd", window, cx);
3702 state.backspace(&Backspace, window, cx);
3703 state.backspace(&Backspace, window, cx);
3704 assert_eq!(state.value(), "ab");
3705 state.undo(&Undo, window, cx);
3706 assert_eq!(state.value(), "abcd");
3707 assert_eq!(state.selected_range(), 4..4);
3708
3709 state.set_value("abcd", window, cx);
3710 state.set_selected_range(1..1, cx);
3711 state.delete(&Delete, window, cx);
3712 state.delete(&Delete, window, cx);
3713 assert_eq!(state.value(), "ad");
3714 state.undo(&Undo, window, cx);
3715 assert_eq!(state.value(), "abcd");
3716 assert_eq!(state.selected_range(), 1..1);
3717 });
3718 });
3719 }
3720
3721 #[gpui::test]
3722 fn test_undo_manager_atomic_paste_isolated_from_typing(cx: &mut TestAppContext) {
3723 let input_view = InputView::build(cx, |state| state);
3724 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3725 let input = input_view.input;
3726
3727 cx.update(|window, cx| {
3728 cx.write_to_clipboard(ClipboardItem::new_string("P".to_string()));
3729 input.update(cx, |state, cx| {
3730 state.replace_text_in_range(None, "a", window, cx);
3731 state.paste(&Paste, window, cx);
3732 state.replace_text_in_range(None, "b", window, cx);
3733 assert_eq!(state.value(), "aPb");
3734
3735 state.undo(&Undo, window, cx);
3736 assert_eq!(state.value(), "aP");
3737 state.undo(&Undo, window, cx);
3738 assert_eq!(state.value(), "a");
3739 state.undo(&Undo, window, cx);
3740 assert_eq!(state.value(), "");
3741 });
3742 });
3743 }
3744
3745 #[gpui::test]
3746 fn test_undo_manager_programmatic_insert_is_atomic(cx: &mut TestAppContext) {
3747 let input_view = InputView::build(cx, |state| state);
3748 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3749 let input = input_view.input;
3750
3751 cx.update(|window, cx| {
3752 input.update(cx, |state, cx| {
3753 state.replace_text_in_range(None, "a", window, cx);
3754 state.insert("P", window, cx);
3755 state.replace_text_in_range(None, "b", window, cx);
3756 assert_eq!(state.value(), "aPb");
3757
3758 state.undo(&Undo, window, cx);
3759 assert_eq!(state.value(), "aP");
3760 state.undo(&Undo, window, cx);
3761 assert_eq!(state.value(), "a");
3762 });
3763 });
3764 }
3765
3766 #[gpui::test]
3767 fn test_undo_manager_selection_round_trip_splits_typing(cx: &mut TestAppContext) {
3768 let input_view = InputView::build(cx, |state| state);
3769 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3770 let input = input_view.input;
3771
3772 cx.update(|window, cx| {
3773 input.update(cx, |state, cx| {
3774 state.replace_text_in_range(None, "a", window, cx);
3775 state.replace_text_in_range(None, "b", window, cx);
3776 state.select_all(window, cx);
3777 state.unselect(window, cx);
3778 state.replace_text_in_range(None, "c", window, cx);
3779
3780 state.undo(&Undo, window, cx);
3781 assert_eq!(state.value(), "ab");
3782 state.undo(&Undo, window, cx);
3783 assert_eq!(state.value(), "");
3784 });
3785 });
3786 }
3787
3788 #[gpui::test]
3789 fn test_undo_manager_enter_is_atomic(cx: &mut TestAppContext) {
3790 let input_view = InputView::build_textarea(cx, |state| state);
3791 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3792 let input = input_view.input;
3793
3794 cx.update(|window, cx| {
3795 input.update(cx, |state, cx| {
3796 state.replace_text_in_range(None, "a", window, cx);
3797 state.enter(
3798 &Enter {
3799 secondary: false,
3800 shift: false,
3801 },
3802 window,
3803 cx,
3804 );
3805 state.replace_text_in_range(None, "b", window, cx);
3806
3807 state.undo(&Undo, window, cx);
3808 assert_eq!(state.value(), "a\n");
3809 state.undo(&Undo, window, cx);
3810 assert_eq!(state.value(), "a");
3811 state.undo(&Undo, window, cx);
3812 assert_eq!(state.value(), "");
3813 });
3814 });
3815 }
3816
3817 #[gpui::test]
3818 fn test_undo_manager_single_line_return_commits_the_typing_session(cx: &mut TestAppContext) {
3819 let input_view = InputView::build(cx, |state| state);
3820 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3821 let input = input_view.input;
3822
3823 cx.update(|window, cx| {
3824 input.update(cx, |state, cx| {
3825 for part in ["a", "b", "c"] {
3826 state.replace_text_in_range(None, part, window, cx);
3827 }
3828 state.enter(
3829 &Enter {
3830 secondary: false,
3831 shift: false,
3832 },
3833 window,
3834 cx,
3835 );
3836 for part in ["d", "e", "f"] {
3837 state.replace_text_in_range(None, part, window, cx);
3838 }
3839 assert_eq!(state.value(), "abcdef");
3840
3841 state.undo(&Undo, window, cx);
3842 assert_eq!(state.value(), "abc");
3843 state.undo(&Undo, window, cx);
3844 assert_eq!(state.value(), "");
3845 });
3846 });
3847 }
3848
3849 #[gpui::test]
3850 fn test_undo_manager_submit_on_enter_commits_the_textarea_session(cx: &mut TestAppContext) {
3851 let input_view = InputView::build_textarea(cx, |state| state.submit_on_enter(true));
3852 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3853 let input = input_view.input;
3854
3855 cx.update(|window, cx| {
3856 input.update(cx, |state, cx| {
3857 state.replace_text_in_range(None, "before submit", window, cx);
3858 state.enter(
3859 &Enter {
3860 secondary: false,
3861 shift: false,
3862 },
3863 window,
3864 cx,
3865 );
3866 state.replace_text_in_range(None, " after submit", window, cx);
3867 assert_eq!(state.value(), "before submit after submit");
3868
3869 state.undo(&Undo, window, cx);
3870 assert_eq!(state.value(), "before submit");
3871 state.undo(&Undo, window, cx);
3872 assert_eq!(state.value(), "");
3873 });
3874 });
3875 }
3876
3877 #[gpui::test]
3878 fn test_undo_manager_blur_commits_the_typing_session(cx: &mut TestAppContext) {
3879 let input_view = InputView::build(cx, |state| state);
3880 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3881 let input = input_view.input;
3882
3883 cx.update(|window, cx| {
3884 input.update(cx, |state, cx| {
3885 state.replace_text_in_range(None, "before blur", window, cx);
3886 state.on_blur(window, cx);
3887 state.on_focus(window, cx);
3888 state.replace_text_in_range(None, " after focus", window, cx);
3889 assert_eq!(state.value(), "before blur after focus");
3890
3891 state.undo(&Undo, window, cx);
3892 assert_eq!(state.value(), "before blur");
3893 state.undo(&Undo, window, cx);
3894 assert_eq!(state.value(), "");
3895 });
3896 });
3897 }
3898
3899 #[gpui::test]
3900 fn test_undo_manager_keeps_rapid_lines_in_distinct_transactions(cx: &mut TestAppContext) {
3901 let input_view = InputView::build_textarea(cx, |state| state);
3902 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3903 let input = input_view.input;
3904
3905 cx.update(|window, cx| {
3906 input.update(cx, |state, cx| {
3907 let enter = Enter {
3908 secondary: false,
3909 shift: false,
3910 };
3911 state.replace_text_in_range(None, "a", window, cx);
3912 state.enter(&enter, window, cx);
3913 state.replace_text_in_range(None, "b", window, cx);
3914 state.enter(&enter, window, cx);
3915 state.replace_text_in_range(None, "c", window, cx);
3916 assert_eq!(state.value(), "a\nb\nc");
3917
3918 for expected in ["a\nb\n", "a\nb", "a\n", "a", ""] {
3919 state.undo(&Undo, window, cx);
3920 assert_eq!(state.value(), expected);
3921 }
3922 });
3923 });
3924 }
3925
3926 #[gpui::test]
3927 fn test_undo_manager_coalesces_long_unicode_typing_without_a_timer(cx: &mut TestAppContext) {
3928 let input_view = InputView::build_textarea(cx, |state| state);
3929 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3930 let input = input_view.input;
3931 let parts = [
3932 "The ",
3933 "quick ",
3934 "brown fox, ",
3935 "δ½ ε₯½οΌδΈη ",
3936 "π¦ jumps over 13 lazy dogs.",
3937 ];
3938 let expected = parts.concat();
3939
3940 cx.update(|window, cx| {
3941 input.update(cx, |state, cx| {
3942 for part in parts {
3943 state.replace_text_in_range(None, part, window, cx);
3944 }
3945 assert_eq!(state.value(), expected);
3946
3947 state.undo(&Undo, window, cx);
3948 assert_eq!(state.value(), "");
3949 state.redo(&Redo, window, cx);
3950 assert_eq!(state.value(), expected);
3951 });
3952 });
3953 }
3954
3955 #[gpui::test]
3956 fn test_undo_manager_long_multiline_sequence_has_structural_boundaries(
3957 cx: &mut TestAppContext,
3958 ) {
3959 let input_view = InputView::build_textarea(cx, |state| state);
3960 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
3961 let input = input_view.input;
3962 let enter = Enter {
3963 secondary: false,
3964 shift: false,
3965 };
3966
3967 cx.update(|window, cx| {
3968 input.update(cx, |state, cx| {
3969 for (index, line) in [
3970 "first line with punctuation!",
3971 "第δΊθ‘ε
ε« Unicode π¦",
3972 "third line has several words",
3973 ]
3974 .into_iter()
3975 .enumerate()
3976 {
3977 for chunk in line.split_inclusive(' ') {
3978 state.replace_text_in_range(None, chunk, window, cx);
3979 }
3980 if index < 2 {
3981 state.enter(&enter, window, cx);
3982 }
3983 }
3984
3985 assert_eq!(
3986 state.value(),
3987 "first line with punctuation!\n第δΊθ‘ε
ε« Unicode π¦\nthird line has several words"
3988 );
3989 state.undo(&Undo, window, cx);
3990 assert_eq!(
3991 state.value(),
3992 "first line with punctuation!\n第δΊθ‘ε
ε« Unicode π¦\n"
3993 );
3994 state.undo(&Undo, window, cx);
3995 assert_eq!(
3996 state.value(),
3997 "first line with punctuation!\n第δΊθ‘ε
ε« Unicode π¦"
3998 );
3999 state.undo(&Undo, window, cx);
4000 assert_eq!(state.value(), "first line with punctuation!\n");
4001 });
4002 });
4003 }
4004
4005 #[gpui::test]
4006 fn test_masked_input_keeps_its_value_out_of_the_clipboard(cx: &mut TestAppContext) {
4007 let input_view = InputView::build(cx, |state| state);
4008 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4009 let input = input_view.input;
4010
4011 cx.update(|window, cx| {
4012 input.update(cx, |state, cx| {
4013 state.set_value("hunter2", window, cx);
4014 state.set_masked(true, window, cx);
4015 state.select_all(window, cx);
4016 cx.write_to_clipboard(ClipboardItem::new_string("sentinel".into()));
4017
4018 state.copy(&Copy, window, cx);
4019 assert_eq!(
4020 cx.read_from_clipboard().and_then(|item| item.text()),
4021 Some("sentinel".to_string())
4022 );
4023
4024 state.cut(&Cut, window, cx);
4026 assert_eq!(state.value(), "hunter2");
4027 assert_eq!(
4028 cx.read_from_clipboard().and_then(|item| item.text()),
4029 Some("sentinel".to_string())
4030 );
4031
4032 state.set_masked(false, window, cx);
4034 state.copy(&Copy, window, cx);
4035 assert_eq!(
4036 cx.read_from_clipboard().and_then(|item| item.text()),
4037 Some("hunter2".to_string())
4038 );
4039 });
4040 });
4041 }
4042
4043 #[gpui::test]
4044 fn test_masked_input_collapses_word_boundaries(cx: &mut TestAppContext) {
4045 let input_view = InputView::build(cx, |state| state);
4046 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4047 let input = input_view.input;
4048
4049 cx.update(|window, cx| {
4050 input.update(cx, |state, cx| {
4051 state.set_value("aaa bbb ccc", window, cx);
4052 state.set_masked(true, window, cx);
4053 state.set_selected_range(7..7, cx);
4054
4055 state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4058 assert_eq!(state.value(), " ccc");
4059 assert_eq!(state.selected_range(), 0..0);
4060
4061 state.delete_next_word(&DeleteToNextWordEnd, window, cx);
4062 assert_eq!(state.value(), "");
4063
4064 state.set_value("aaa bbb ccc", window, cx);
4066 state.select_word(9, window, cx);
4067 assert_eq!(state.selected_range(), 0..11);
4068
4069 state.set_masked(false, window, cx);
4071 state.set_value("aaa bbb ccc", window, cx);
4072 state.set_selected_range(11..11, cx);
4073 state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4074 assert_eq!(state.value(), "aaa bbb ");
4075
4076 state.set_value("aaa bbb ccc", window, cx);
4077 state.select_word(9, window, cx);
4078 assert_eq!(state.selected_range(), 8..11);
4079 });
4080 });
4081 }
4082
4083 #[gpui::test]
4084 fn test_masked_input_disables_the_copy_context_menu_items(cx: &mut TestAppContext) {
4085 let input_view = InputView::build(cx, |state| state);
4086 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4087 let input = input_view.input;
4088
4089 cx.update(|window, cx| {
4090 input.update(cx, |state, cx| {
4091 state.set_value("hunter2", window, cx);
4092 state.select_all(window, cx);
4093 assert!(state.context_menu_capabilities().is_copyable());
4094
4095 state.set_masked(true, window, cx);
4096 let capabilities = state.context_menu_capabilities();
4097 assert!(capabilities.is_masked());
4098 assert!(capabilities.has_selection());
4099 assert!(!capabilities.is_copyable());
4100 });
4101 });
4102 }
4103
4104 #[gpui::test]
4105 fn test_undo_manager_cut_and_repeated_pastes_are_distinct_transactions(
4106 cx: &mut TestAppContext,
4107 ) {
4108 let input_view = InputView::build(cx, |state| state);
4109 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4110 let input = input_view.input;
4111
4112 cx.update(|window, cx| {
4113 input.update(cx, |state, cx| {
4114 state.replace_text_in_range(None, "alpha beta gamma", window, cx);
4115 state.set_selected_range(6..10, cx);
4116 state.cut(&Cut, window, cx);
4117 assert_eq!(state.value(), "alpha gamma");
4118
4119 state.paste(&Paste, window, cx);
4120 state.paste(&Paste, window, cx);
4121 assert_eq!(state.value(), "alpha betabeta gamma");
4122
4123 state.undo(&Undo, window, cx);
4124 assert_eq!(state.value(), "alpha beta gamma");
4125 state.undo(&Undo, window, cx);
4126 assert_eq!(state.value(), "alpha gamma");
4127 state.undo(&Undo, window, cx);
4128 assert_eq!(state.value(), "alpha beta gamma");
4129 });
4130 });
4131 }
4132
4133 #[gpui::test]
4134 fn test_undo_manager_word_and_line_deletes_do_not_coalesce(cx: &mut TestAppContext) {
4135 let input_view = InputView::build_textarea(cx, |state| state);
4136 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4137 let input = input_view.input;
4138
4139 cx.update(|window, cx| {
4140 input.update(cx, |state, cx| {
4141 state.set_value("one two three\nfour five", window, cx);
4142 state.set_selected_range(13..13, cx);
4143 state.delete_previous_word(&DeleteToPreviousWordStart, window, cx);
4144 assert_eq!(state.value(), "one two \nfour five");
4145 state.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
4146 assert_eq!(state.value(), "one two four five");
4147
4148 state.undo(&Undo, window, cx);
4149 assert_eq!(state.value(), "one two \nfour five");
4150 state.undo(&Undo, window, cx);
4151 assert_eq!(state.value(), "one two three\nfour five");
4152 });
4153 });
4154 }
4155
4156 #[gpui::test]
4157 fn test_undo_manager_multiline_replacement_is_one_atomic_transaction(cx: &mut TestAppContext) {
4158 let input_view = InputView::build_textarea(cx, |state| state);
4159 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4160 let input = input_view.input;
4161
4162 cx.update(|window, cx| {
4163 input.update(cx, |state, cx| {
4164 state.replace_text_in_range(None, "before", window, cx);
4165 state.set_selected_range(0..6, cx);
4166 state.replace_text_in_range(None, "line one\nline two\n第δΈθ‘", window, cx);
4167 assert_eq!(state.value(), "line one\nline two\n第δΈθ‘");
4168
4169 state.undo(&Undo, window, cx);
4170 assert_eq!(state.value(), "before");
4171 assert_eq!(state.selected_range(), 0..6);
4172 state.redo(&Redo, window, cx);
4173 assert_eq!(state.value(), "line one\nline two\n第δΈθ‘");
4174 });
4175 });
4176 }
4177
4178 #[gpui::test]
4179 fn test_undo_manager_composition_isolated_from_long_typing(cx: &mut TestAppContext) {
4180 let input_view = InputView::build(cx, |state| state);
4181 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4182 let input = input_view.input;
4183
4184 cx.update(|window, cx| {
4185 input.update(cx, |state, cx| {
4186 state.replace_text_in_range(None, "prefix ", window, cx);
4187 state.replace_and_mark_text_in_range(None, "n", None, window, cx);
4188 state.replace_and_mark_text_in_range(None, "ni", None, window, cx);
4189 state.replace_and_mark_text_in_range(None, "δ½ ", None, window, cx);
4190 state.unmark_text(window, cx);
4191 state.replace_text_in_range(None, " suffix", window, cx);
4192 assert_eq!(state.value(), "prefix δ½ suffix");
4193
4194 state.undo(&Undo, window, cx);
4195 assert_eq!(state.value(), "prefix δ½ ");
4196 state.undo(&Undo, window, cx);
4197 assert_eq!(state.value(), "prefix ");
4198 state.undo(&Undo, window, cx);
4199 assert_eq!(state.value(), "");
4200 });
4201 });
4202 }
4203
4204 #[gpui::test]
4205 fn test_undo_manager_selected_replacement_is_atomic(cx: &mut TestAppContext) {
4206 let input_view = InputView::build(cx, |state| state);
4207 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4208 let input = input_view.input;
4209
4210 cx.update(|window, cx| {
4211 input.update(cx, |state, cx| {
4212 state.replace_text_in_range(None, "abc", window, cx);
4213 state.set_selected_range(1..2, cx);
4214 state.replace_text_in_range(None, "X", window, cx);
4215 state.replace_text_in_range(None, "z", window, cx);
4216 assert_eq!(state.value(), "aXzc");
4217
4218 state.undo(&Undo, window, cx);
4219 assert_eq!(state.value(), "aXc");
4220 state.undo(&Undo, window, cx);
4221 assert_eq!(state.value(), "abc");
4222 state.undo(&Undo, window, cx);
4223 assert_eq!(state.value(), "");
4224 });
4225 });
4226 }
4227
4228 #[gpui::test]
4229 fn test_number_input_leading_dot_editable(cx: &mut TestAppContext) {
4230 let input_view = InputView::build(cx, |state| {
4231 state.mask_pattern(MaskPattern::Number {
4232 separator: None,
4233 fraction: None,
4234 })
4235 });
4236 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4237 let input = input_view.input;
4238
4239 cx.update(|window, cx| {
4240 input.update(cx, |state, cx| {
4241 state.replace_text_in_range(None, "1.2", window, cx);
4242
4243 let range = state.range_to_utf16(&(0..1));
4247 state.replace_text_in_range(Some(range), "", window, cx);
4248 assert_eq!(state.value(), ".2");
4249 let cursor: Range<usize> = state.selected_range.into();
4250 assert_eq!(cursor, 0..0);
4251
4252 state.replace_text_in_range(Some(0..0), "3", window, cx);
4254 assert_eq!(state.value(), "3.2");
4255 });
4256 });
4257 }
4258
4259 #[gpui::test]
4260 fn test_number_input_escape_invalid_text(cx: &mut TestAppContext) {
4261 let input_view = InputView::build(cx, |state| {
4264 state
4265 .mask_pattern(MaskPattern::Number {
4266 separator: None,
4267 fraction: None,
4268 })
4269 .default_value("1,234")
4270 });
4271 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4272 let input = input_view.input;
4273
4274 cx.update(|window, cx| {
4275 input.update(cx, |state, cx| {
4276 let range = state.range_to_utf16(&(4..5));
4280 state.replace_text_in_range(Some(range), "", window, cx);
4281 assert_eq!(state.value(), "1,23");
4282
4283 let range = state.range_to_utf16(&(1..2));
4285 state.replace_text_in_range(Some(range), "", window, cx);
4286 assert_eq!(state.value(), "123");
4287 state.replace_text_in_range(None, "a", window, cx);
4288 assert_eq!(state.value(), "123");
4289 });
4290 });
4291 }
4292
4293 #[gpui::test]
4297 fn test_set_value_single_line_caret_at_end_view_at_start(cx: &mut TestAppContext) {
4298 let input_view = InputView::build(cx, |state| state);
4299 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4300 let input = input_view.input;
4301
4302 let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
4304 let len = value.len();
4305
4306 cx.update(|window, cx| {
4309 input.update(cx, |state, cx| {
4310 state.set_value(value.clone(), window, cx);
4311
4312 assert_eq!(
4313 state.selected_range,
4314 Selection::new(len, len),
4315 "single-line caret should be at the end after set_value"
4316 );
4317 assert_eq!(
4318 state.deferred_scroll_offset,
4319 Some(point(px(0.), px(0.))),
4320 "the view should be forced back to the start"
4321 );
4322 });
4323 });
4324
4325 cx.run_until_parked();
4328 cx.update(|_, cx| {
4329 input.read_with(cx, |state, _| {
4330 assert!(
4331 state.scroll_size.width > state.input_bounds.size.width,
4332 "value must overflow the input width or this test is vacuous"
4333 );
4334 assert_eq!(
4335 state.scroll_handle.offset().x,
4336 px(0.),
4337 "long value should display from its start, not its tail"
4338 );
4339 });
4340 });
4341 }
4342
4343 #[gpui::test]
4347 fn test_replace_all_single_line(cx: &mut TestAppContext) {
4348 let input_view = InputView::build(cx, |state| state);
4349 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4350 let input = input_view.input;
4351
4352 let value = format!("https://example.com/v1/users?{}", "x=1&".repeat(120));
4354 let len = value.len();
4355
4356 cx.update(|window, cx| {
4360 input.update(cx, |state, cx| {
4361 state.set_value("hello", window, cx);
4362 state.replace_all(value.clone(), window, cx);
4363 assert_eq!(state.value(), value);
4364 assert_eq!(
4365 state.selected_range,
4366 Selection::new(len, len),
4367 "single-line caret should be at the end after replace_all"
4368 );
4369 assert_eq!(
4370 state.scroll_handle.offset(),
4371 point(px(0.), px(0.)),
4372 "the scroll offset should be reset to the start"
4373 );
4374 assert_eq!(
4375 state.deferred_scroll_offset,
4376 Some(point(px(0.), px(0.))),
4377 "single-line should set a deferred scroll offset to keep the start visible"
4378 );
4379 });
4380 });
4381
4382 cx.run_until_parked();
4385 cx.update(|_, cx| {
4386 input.read_with(cx, |state, _| {
4387 assert!(
4388 state.scroll_size.width > state.input_bounds.size.width,
4389 "value must overflow the input width or this test is vacuous"
4390 );
4391 assert_eq!(
4392 state.scroll_handle.offset().x,
4393 px(0.),
4394 "long value should display from its start, not its tail"
4395 );
4396 });
4397 });
4398 }
4399
4400 #[gpui::test]
4401 fn test_single_line_removes_newlines(cx: &mut TestAppContext) {
4402 let input_view = InputView::build(cx, |state| state.default_value("default\nvalue"));
4403 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4404 let input = input_view.input;
4405
4406 cx.update(|window, cx| {
4407 input.update(cx, |state, cx| {
4408 assert_eq!(state.value(), "defaultvalue");
4409
4410 state.set_value("first\nsecond\r\nthird\rfourth", window, cx);
4411 assert_eq!(state.value(), "firstsecondthirdfourth");
4412
4413 state.set_value("", window, cx);
4414 state.insert("a\nb", window, cx);
4415 assert_eq!(state.value(), "ab");
4416 });
4417
4418 cx.write_to_clipboard(ClipboardItem::new_string("a\r\nb\nc\rd".to_string()));
4419 input.update(cx, |state, cx| {
4420 state.set_value("", window, cx);
4421 state.paste(&Paste, window, cx);
4422 assert_eq!(state.value(), "abcd");
4423 });
4424 });
4425
4426 cx.run_until_parked();
4427 }
4428
4429 #[gpui::test]
4433 fn test_replace_all_multi_line(cx: &mut TestAppContext) {
4434 let input_view = InputView::build_textarea(cx, |state| state);
4435 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4436 let input = input_view.input;
4437
4438 cx.update(|window, cx| {
4439 input.update(cx, |state, cx| {
4440 state.set_value("foo\nbar", window, cx);
4441 state.replace_all("baz\nqux", window, cx);
4442 assert_eq!(state.value(), "baz\nqux");
4443 assert_eq!(
4444 state.selected_range,
4445 Selection::new(0, 0),
4446 "multi-line selection should be cleared after replace_all"
4447 );
4448 assert_eq!(
4449 state.scroll_handle.offset(),
4450 point(px(0.), px(0.)),
4451 "the scroll offset should be reset to the start"
4452 );
4453 assert!(
4454 state.deferred_scroll_offset.is_none(),
4455 "multi-line should not set a deferred scroll offset"
4456 );
4457 });
4458 });
4459 }
4460
4461 #[gpui::test]
4464 fn test_replace_all_preserves_undo_history(cx: &mut TestAppContext) {
4465 let input_view = InputView::build(cx, |state| state);
4466 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4467 let input = input_view.input;
4468
4469 cx.update(|window, cx| {
4470 input.update(cx, |state, cx| {
4471 state.set_value("first", window, cx);
4473 assert!(
4474 !state.undo_manager.has_undos(),
4475 "history should be empty after set_value"
4476 );
4477
4478 state.replace_all("second", window, cx);
4480 assert_eq!(state.value(), "second");
4481 assert!(
4482 state.undo_manager.has_undos(),
4483 "replace_all should record an undo step"
4484 );
4485
4486 state.undo(&Undo, window, cx);
4488 assert_eq!(state.value(), "first");
4489
4490 state.redo(&Redo, window, cx);
4492 assert_eq!(state.value(), "second");
4493 });
4494 });
4495 }
4496
4497 #[gpui::test]
4500 fn test_replace_all_code_editor(cx: &mut TestAppContext) {
4501 let input_view = InputView::new(cx);
4502 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4503 let input = input_view.input;
4504
4505 cx.update(|window, cx| {
4506 input.update(cx, |state, cx| {
4507 state.set_value("select 1", window, cx);
4509 state._pending_update = false;
4510
4511 state.replace_all("select 2", window, cx);
4512 assert_eq!(state.value(), "select 2");
4513 assert!(
4514 state._pending_update,
4515 "replace_all on a code editor should request a pending update"
4516 );
4517 });
4518 });
4519 }
4520
4521 #[gpui::test]
4522 fn test_set_selected_range(cx: &mut TestAppContext) {
4523 let input_view = InputView::build(cx, |state| state.default_value("hello world"));
4524 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4525 let input = input_view.input;
4526
4527 cx.update(|_, cx| {
4528 input.update(cx, |s, cx| {
4529 s.set_selected_range(0..5, cx);
4530 assert_eq!(s.selected_range(), 0..5);
4531 assert_eq!(s.selected_text().to_string(), "hello");
4532
4533 s.set_selected_range(6..11, cx);
4534 assert_eq!(s.selected_text().to_string(), "world");
4535
4536 s.set_selected_range(100..100, cx);
4538 assert_eq!(s.selected_range(), 11..11);
4539 });
4540 });
4541 }
4542
4543 #[gpui::test]
4544 fn test_set_selected_range_clips_to_utf8_boundaries(cx: &mut TestAppContext) {
4545 let input_view = InputView::build(cx, |state| state.default_value("Γ©x"));
4546 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4547 let input = input_view.input;
4548
4549 cx.update(|window, cx| {
4550 input.update(cx, |state, cx| {
4551 state.set_selected_range(0..1, cx);
4552 assert_eq!(state.selected_range(), 0..2);
4553 state.copy(&Copy, window, cx);
4554
4555 state.set_selected_range(1..1, cx);
4556 assert_eq!(state.selected_range(), 0..0);
4557 });
4558 });
4559 }
4560
4561 #[gpui::test]
4562 fn test_ime_selection_is_relative_to_replacement_start(cx: &mut TestAppContext) {
4563 let input_view = InputView::build(cx, |state| state.default_value("δ½ ε₯½ "));
4564 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4565 let input = input_view.input;
4566
4567 cx.update(|window, cx| {
4568 input.update(cx, |state, cx| {
4569 state.set_selected_range(7..7, cx);
4570 state.replace_and_mark_text_in_range(None, "s", Some(1..1), window, cx);
4571 state.replace_and_mark_text_in_range(None, "sh", Some(2..2), window, cx);
4572
4573 assert_eq!(state.value(), "δ½ ε₯½ sh");
4574 assert_eq!(state.selected_range(), 9..9);
4575 assert_eq!(state.ime_marked_range, Some((7..9).into()));
4576 });
4577 });
4578 }
4579
4580 #[gpui::test]
4581 fn test_undo_manager_composition_is_one_undo_group(cx: &mut TestAppContext) {
4582 let input_view = InputView::build(cx, |state| state);
4583 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4584 let input = input_view.input;
4585
4586 cx.update(|window, cx| {
4587 input.update(cx, |state, cx| {
4588 state.set_value("a", window, cx);
4589 state.replace_and_mark_text_in_range(None, "s", None, window, cx);
4590 state.replace_and_mark_text_in_range(None, "sh", None, window, cx);
4591 state.replace_text_in_range(None, "ζ―", window, cx);
4592 assert_eq!(state.value(), "aζ―");
4593
4594 state.undo(&Undo, window, cx);
4595 assert_eq!(state.value(), "a");
4596 state.redo(&Redo, window, cx);
4597 assert_eq!(state.value(), "aζ―");
4598 });
4599 });
4600 }
4601
4602 #[gpui::test]
4603 fn test_undo_manager_consecutive_compositions_are_separate_groups(cx: &mut TestAppContext) {
4604 let input_view = InputView::build(cx, |state| state);
4605 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4606 let input = input_view.input;
4607
4608 cx.update(|window, cx| {
4609 input.update(cx, |state, cx| {
4610 state.replace_and_mark_text_in_range(None, "j", None, window, cx);
4612 state.replace_and_mark_text_in_range(None, "jin", None, window, cx);
4613 state.replace_text_in_range(None, "δ»ε€©", window, cx);
4614 state.replace_and_mark_text_in_range(None, "w", None, window, cx);
4616 state.replace_and_mark_text_in_range(None, "wo", None, window, cx);
4617 state.replace_text_in_range(None, "ζ们", window, cx);
4618 assert_eq!(state.value(), "δ»ε€©ζ们");
4619 assert_eq!(state.selected_range(), 12..12);
4620
4621 state.undo(&Undo, window, cx);
4622 assert_eq!(state.value(), "δ»ε€©");
4623 assert_eq!(state.selected_range(), 6..6);
4624
4625 state.undo(&Undo, window, cx);
4626 assert_eq!(state.value(), "");
4627 assert_eq!(state.selected_range(), 0..0);
4628
4629 state.redo(&Redo, window, cx);
4630 assert_eq!(state.value(), "δ»ε€©");
4631 assert_eq!(state.selected_range(), 6..6);
4632
4633 state.redo(&Redo, window, cx);
4634 assert_eq!(state.value(), "δ»ε€©ζ们");
4635 assert_eq!(state.selected_range(), 12..12);
4636 });
4637 });
4638 }
4639
4640 #[gpui::test]
4641 fn test_undo_manager_typing_after_composition_is_a_separate_group(cx: &mut TestAppContext) {
4642 let input_view = InputView::build(cx, |state| state);
4643 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4644 let input = input_view.input;
4645
4646 cx.update(|window, cx| {
4647 input.update(cx, |state, cx| {
4648 state.replace_and_mark_text_in_range(None, "n", None, window, cx);
4649 state.replace_text_in_range(None, "δ½ ", window, cx);
4650 state.undo_manager.pending_intent = Some(EditIntent::Typing);
4651 state.replace_text_in_range(None, "a", window, cx);
4652 state.undo_manager.pending_intent = Some(EditIntent::Typing);
4653 state.replace_text_in_range(None, "b", window, cx);
4654 assert_eq!(state.value(), "δ½ ab");
4655
4656 state.undo(&Undo, window, cx);
4657 assert_eq!(state.value(), "δ½ ");
4658
4659 state.undo(&Undo, window, cx);
4660 assert_eq!(state.value(), "");
4661 });
4662 });
4663 }
4664
4665 #[gpui::test]
4666 fn test_undo_manager_composition_cancel_leaves_no_entry(cx: &mut TestAppContext) {
4667 let input_view = InputView::build(cx, |state| state);
4668 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4669 let input = input_view.input;
4670
4671 cx.update(|window, cx| {
4672 input.update(cx, |state, cx| {
4673 state.set_value("a", window, cx);
4674 state.replace_and_mark_text_in_range(None, "s", None, window, cx);
4675 state.replace_and_mark_text_in_range(None, "", None, window, cx);
4676
4677 assert_eq!(state.value(), "a");
4678 assert!(!state.undo_manager.has_undos());
4679 });
4680 });
4681 }
4682
4683 #[gpui::test]
4684 fn test_undo_manager_selection_restored_by_undo_and_redo(cx: &mut TestAppContext) {
4685 let input_view = InputView::build(cx, |state| state);
4686 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4687 let input = input_view.input;
4688
4689 cx.update(|window, cx| {
4690 input.update(cx, |state, cx| {
4691 state.set_value("abc", window, cx);
4692 state.set_selected_range(1..2, cx);
4693 state.replace_text_in_range(None, "X", window, cx);
4694
4695 state.undo(&Undo, window, cx);
4696 assert_eq!(state.value(), "abc");
4697 assert_eq!(state.selected_range(), 1..2);
4698
4699 state.redo(&Redo, window, cx);
4700 assert_eq!(state.value(), "aXc");
4701 assert_eq!(state.selected_range(), 2..2);
4702 });
4703 });
4704 }
4705
4706 #[gpui::test]
4707 fn test_undo_manager_forward_delete_restores_cursor(cx: &mut TestAppContext) {
4708 let input_view = InputView::build(cx, |state| state);
4709 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4710 let input = input_view.input;
4711
4712 cx.update(|window, cx| {
4713 input.update(cx, |state, cx| {
4714 state.set_value("abc", window, cx);
4715 state.set_selected_range(1..1, cx);
4716 state.delete(&Delete, window, cx);
4717
4718 state.undo(&Undo, window, cx);
4719 assert_eq!(state.value(), "abc");
4720 assert_eq!(state.selected_range(), 1..1);
4721 });
4722 });
4723 }
4724
4725 #[gpui::test]
4726 fn test_undo_manager_selection_movement_preserves_redo(cx: &mut TestAppContext) {
4727 let input_view = InputView::build(cx, |state| state);
4728 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4729 let input = input_view.input;
4730
4731 cx.update(|window, cx| {
4732 input.update(cx, |state, cx| {
4733 state.replace_text_in_range(None, "ab", window, cx);
4734 state.undo(&Undo, window, cx);
4735 state.set_selected_range(0..0, cx);
4736 state.redo(&Redo, window, cx);
4737 assert_eq!(state.value(), "ab");
4738
4739 state.undo(&Undo, window, cx);
4740 state.replace_text_in_range(None, "x", window, cx);
4741 state.redo(&Redo, window, cx);
4742 assert_eq!(state.value(), "x");
4743 });
4744 });
4745 }
4746
4747 #[gpui::test]
4748 fn test_undo_manager_noop_edit_preserves_redo(cx: &mut TestAppContext) {
4749 let input_view = InputView::build(cx, |state| state);
4750 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4751 let input = input_view.input;
4752
4753 cx.update(|window, cx| {
4754 input.update(cx, |state, cx| {
4755 state.replace_text_in_range(None, "a", window, cx);
4756 state.undo(&Undo, window, cx);
4757 state.backspace(&Backspace, window, cx);
4758 state.redo(&Redo, window, cx);
4759 assert_eq!(state.value(), "a");
4760 });
4761 });
4762 }
4763
4764 #[gpui::test]
4765 fn test_undo_manager_noop_edit_breaks_coalescing_without_clearing_history(
4766 cx: &mut TestAppContext,
4767 ) {
4768 let input_view = InputView::build(cx, |state| state);
4769 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4770 let input = input_view.input;
4771
4772 cx.update(|window, cx| {
4773 input.update(cx, |state, cx| {
4774 state.replace_text_in_range(None, "alpha", window, cx);
4775 state.replace_text_in_range(None, "", window, cx);
4776 state.replace_text_in_range(None, "beta", window, cx);
4777 assert_eq!(state.value(), "alphabeta");
4778
4779 state.undo(&Undo, window, cx);
4780 assert_eq!(state.value(), "alpha");
4781 state.undo(&Undo, window, cx);
4782 assert_eq!(state.value(), "");
4783 });
4784 });
4785 }
4786
4787 #[gpui::test]
4788 fn test_undo_manager_masked_redo_restores_actual_cursor(cx: &mut TestAppContext) {
4789 let input_view = InputView::build(cx, |state| {
4790 state.mask_pattern(MaskPattern::Number {
4791 separator: Some(','),
4792 fraction: None,
4793 })
4794 });
4795 let mut cx = VisualTestContext::from_window(input_view.window_handle.into(), cx);
4796 let input = input_view.input;
4797
4798 cx.update(|window, cx| {
4799 input.update(cx, |state, cx| {
4800 state.set_value("12345", window, cx);
4801 state.set_selected_range(2..2, cx);
4802 state.replace_text_in_range(None, "9", window, cx);
4803 let selection_after_edit = state.selected_range();
4804 assert_ne!(selection_after_edit.end, state.value().len());
4805
4806 state.undo(&Undo, window, cx);
4807 state.redo(&Redo, window, cx);
4808 assert_eq!(state.selected_range(), selection_after_edit);
4809 });
4810 });
4811 }
4812
4813 #[gpui::test]
4819 fn test_unfold_at(cx: &mut TestAppContext) {
4820 use crate::input::{FoldRange, Position};
4821
4822 let view = InputView::<EditorMode>::new(cx);
4823 let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4824 let input = view.input;
4825
4826 cx.update(|window, cx| {
4829 input.update(cx, |state, cx| {
4830 state.set_value("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl", window, cx);
4831 state.apply_highlighter_fold_candidates(
4832 vec![
4833 FoldRange::new(0, 5),
4834 FoldRange::new(2, 4),
4835 FoldRange::new(7, 10),
4836 ],
4837 cx,
4838 );
4839 state.display_map.set_folded(0, true);
4840 state.display_map.set_folded(2, true);
4841 state.display_map.set_folded(7, true);
4842 });
4843 });
4844
4845 for line in [0, 5] {
4848 cx.update(|_, cx| {
4849 input.update(cx, |state, cx| {
4850 assert!(!state.display_map.is_buffer_line_hidden(line));
4851 assert!(!state.unfold_at(Position::new(line as u32, 0), cx));
4852 });
4853 input.read_with(cx, |state, _| {
4854 assert!(state.display_map.is_folded_at(0));
4855 assert!(state.display_map.is_folded_at(2));
4856 assert!(state.display_map.is_folded_at(7));
4857 });
4858 });
4859 }
4860
4861 cx.update(|_, cx| {
4864 input.update(cx, |state, cx| {
4865 assert!(state.unfold_at(Position::new(3, 0), cx));
4866 });
4867 input.read_with(cx, |state, _| {
4868 assert!(!state.display_map.is_buffer_line_hidden(3));
4869 assert!(!state.display_map.is_folded_at(0));
4870 assert!(!state.display_map.is_folded_at(2));
4871 assert!(state.display_map.is_folded_at(7));
4872 assert!(state.display_map.is_fold_candidate(0));
4874 assert!(state.display_map.is_fold_candidate(2));
4875 });
4876 });
4877
4878 cx.update(|_, cx| {
4880 input.update(cx, |state, cx| {
4881 assert!(!state.unfold_at(Position::new(3, 0), cx));
4882 });
4883 });
4884 }
4885
4886 #[gpui::test]
4891 fn test_blur_keeps_decorations(cx: &mut TestAppContext) {
4892 let view = InputView::<EditorMode>::new(cx);
4893 let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4894 let input = view.input;
4895
4896 cx.update(|window, cx| {
4897 input.update(cx, |state, cx| {
4898 state.set_value("select 1", window, cx);
4899 let _collection = state.create_decorations_collection(
4900 vec![crate::input::TextDecoration::new(
4901 0..6,
4902 gpui::HighlightStyle {
4903 font_weight: Some(gpui::FontWeight::BOLD),
4904 ..Default::default()
4905 },
4906 )],
4907 cx,
4908 );
4909 state.present_hover(
4910 0..6,
4911 lsp_types::Hover {
4912 contents: lsp_types::HoverContents::Scalar(
4913 lsp_types::MarkedString::String("docs".into()),
4914 ),
4915 range: None,
4916 },
4917 cx,
4918 );
4919 assert!(state.hover_popover().is_some());
4920
4921 state.on_blur(window, cx);
4922
4923 assert!(
4924 state.hover_popover().is_none(),
4925 "blur should hide the hover popover"
4926 );
4927 let decorations = state.extras.decoration_layers();
4928 assert!(
4929 decorations.iter().any(|layer| !layer.is_empty()),
4930 "blur must not discard decorations"
4931 );
4932 });
4933 });
4934 }
4935
4936 #[gpui::test]
4941 fn test_kind_does_not_follow_the_row_count(cx: &mut TestAppContext) {
4942 let view = InputView::build_textarea(cx, |state| state.auto_grow(1, 1));
4943 let mut cx = VisualTestContext::from_window(view.window_handle.into(), cx);
4944 view.input.read_with(&mut cx, |state, _| {
4945 assert!(state.is_multi_line());
4946 assert!(!state.is_single_line());
4947 assert!(!state.is_code_editor());
4948 });
4949 }
4950
4951 #[gpui::test]
4956 fn test_soft_wrap_is_enabled_by_default(cx: &mut TestAppContext) {
4957 let textarea = InputView::build_textarea(cx, |state| state);
4958 let mut textarea_cx = VisualTestContext::from_window(textarea.window_handle.into(), cx);
4959 textarea
4960 .input
4961 .read_with(&mut textarea_cx, |state, _| assert!(state.soft_wrap));
4962
4963 let editor = InputView::<EditorMode>::new(cx);
4964 let mut editor_cx = VisualTestContext::from_window(editor.window_handle.into(), cx);
4965 editor
4966 .input
4967 .read_with(&mut editor_cx, |state, _| assert!(state.soft_wrap));
4968 }
4969}
4970
4971impl InputBaseState<crate::input::InputMode> {
4973 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
4975 Self::new_in_mode(window, cx)
4976 }
4977
4978 pub fn step_by(mut self, f: impl Fn(f64, StepAction, &mut App) -> f64 + 'static) -> Self {
4993 self.number_step = Some(NumberStep::by_value(f));
4994 self
4995 }
4996
4997 pub fn masked(mut self, masked: bool) -> Self {
4999 self.masked = masked;
5000 self
5001 }
5002
5003 pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
5005 self.masked = masked;
5006 cx.notify();
5007 }
5008
5009 pub fn pattern(mut self, pattern: regex::Regex) -> Self {
5011 self.pattern = Some(pattern);
5012 self
5013 }
5014
5015 pub fn set_pattern(
5017 &mut self,
5018 pattern: regex::Regex,
5019 _window: &mut Window,
5020 _cx: &mut Context<Self>,
5021 ) {
5022 self.pattern = Some(pattern);
5023 }
5024
5025 pub fn validate(mut self, f: impl Fn(&str, &mut App) -> bool + 'static) -> Self {
5027 self.validate = Some(Box::new(f));
5028 self
5029 }
5030
5031 pub fn set_validator(
5032 &mut self,
5033 validate: impl Fn(&str, &mut App) -> bool + 'static,
5034 _cx: &mut Context<Self>,
5035 ) {
5036 self.validate = Some(Box::new(validate));
5037 }
5038
5039 pub fn step(mut self, step: impl Into<NumberStep>) -> Self {
5049 self.number_step = Some(step.into());
5050 self
5051 }
5052
5053 pub fn min(mut self, min: f64) -> Self {
5059 self.number_min = Some(min);
5060 self
5061 }
5062
5063 pub fn max(mut self, max: f64) -> Self {
5069 self.number_max = Some(max);
5070 self
5071 }
5072
5073 pub fn set_step(
5078 &mut self,
5079 step: impl Into<Option<NumberStep>>,
5080 _: &mut Window,
5081 _: &mut Context<Self>,
5082 ) {
5083 self.number_step = step.into();
5084 }
5085
5086 pub fn set_min(&mut self, min: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
5088 self.number_min = min;
5089 }
5090
5091 pub fn set_max(&mut self, max: Option<f64>, _: &mut Window, _: &mut Context<Self>) {
5093 self.number_max = max;
5094 }
5095
5096 pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
5098 self.loading = loading;
5099 cx.notify();
5100 }
5101}
5102
5103impl<M: crate::input::MultiLineMode> InputBaseState<M> {
5106 #[doc(hidden)]
5108 pub fn searchable(mut self, searchable: bool) -> Self {
5109 self.searchable = searchable;
5110 self
5111 }
5112
5113 pub fn set_searchable(&mut self, searchable: bool, cx: &mut Context<Self>) {
5114 self.searchable = searchable;
5115 cx.notify();
5116 }
5117
5118 #[doc(hidden)]
5120 pub fn soft_wrap(mut self, wrap: bool) -> Self {
5121 self.soft_wrap = wrap;
5122 self
5123 }
5124
5125 pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
5127 self.soft_wrap = wrap;
5128 if wrap {
5129 let wrap_width = self
5130 .last_layout
5131 .as_ref()
5132 .and_then(|b| b.wrap_width)
5133 .unwrap_or(self.input_bounds.size.width);
5134
5135 self.display_map.on_layout_changed(Some(wrap_width), cx);
5136
5137 let mut offset = self.scroll_handle.offset();
5139 offset.x = px(0.);
5140 self.scroll_handle.set_offset(offset);
5141 } else {
5142 self.display_map.on_layout_changed(None, cx);
5143 }
5144 cx.notify();
5145 }
5146
5147 #[doc(hidden)]
5149 pub fn wrapping_indent(mut self, wrapping_indent: WrappingIndent) -> Self {
5150 self.wrapping_indent = wrapping_indent;
5151 self
5152 }
5153
5154 pub fn set_wrapping_indent(
5156 &mut self,
5157 wrapping_indent: WrappingIndent,
5158 _: &mut Window,
5159 cx: &mut Context<Self>,
5160 ) {
5161 self.wrapping_indent = wrapping_indent;
5162 self.display_map.set_wrapping_indent(wrapping_indent, cx);
5163 cx.notify();
5164 }
5165}
5166
5167impl InputBaseState<crate::input::TextareaMode> {
5169 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
5174 Self::new_in_mode(window, cx)
5175 }
5176
5177 pub fn set_auto_grow(&mut self, min_rows: usize, max_rows: usize, cx: &mut Context<Self>) {
5178 self.mode = LayoutMode::auto_grow(min_rows, max_rows.max(min_rows));
5179 cx.notify();
5180 }
5181
5182 #[doc(hidden)]
5188 pub fn rows(mut self, rows: usize) -> Self {
5189 match &mut self.mode {
5190 LayoutMode::PlainText { rows: r, .. } | LayoutMode::CodeEditor { rows: r, .. } => {
5191 *r = rows
5192 }
5193 LayoutMode::AutoGrow {
5194 max_rows: max_r,
5195 rows: r,
5196 ..
5197 } => {
5198 *r = rows;
5199 *max_r = rows;
5200 }
5201 }
5202 self
5203 }
5204
5205 pub fn set_rows(&mut self, rows: usize, cx: &mut Context<Self>) {
5206 match &mut self.mode {
5207 LayoutMode::PlainText { rows: value, .. }
5208 | LayoutMode::CodeEditor { rows: value, .. } => *value = rows,
5209 LayoutMode::AutoGrow {
5210 rows: value,
5211 max_rows,
5212 ..
5213 } => {
5214 *value = rows;
5215 *max_rows = rows;
5216 }
5217 }
5218 cx.notify();
5219 }
5220
5221 pub fn auto_grow(mut self, min_rows: usize, max_rows: usize) -> Self {
5223 self.mode = LayoutMode::auto_grow(min_rows, max_rows);
5224 self
5225 }
5226}
5227
5228impl InputBaseState<crate::input::EditorMode> {
5230 pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
5241 let mut state = Self::new_in_mode(window, cx);
5242 state.mode = LayoutMode::code_editor();
5243 state.searchable = true;
5244 state
5245 }
5246
5247 pub fn language(mut self, language: impl Into<SharedString>) -> Self {
5251 if let LayoutMode::CodeEditor {
5252 language: l,
5253 highlighter,
5254 ..
5255 } = &mut self.mode
5256 {
5257 *l = language.into();
5258 *highlighter.borrow_mut() = None;
5259 }
5260 self
5261 }
5262
5263 #[doc(hidden)]
5267 pub fn folding(mut self, folding: bool) -> Self {
5268 if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
5269 *f = folding;
5270 }
5271 self
5272 }
5273
5274 pub fn set_folding(&mut self, folding: bool, _: &mut Window, cx: &mut Context<Self>) {
5278 if let LayoutMode::CodeEditor { folding: f, .. } = &mut self.mode {
5279 *f = folding;
5280 }
5281 if !folding {
5282 self.display_map.clear_folds();
5283 }
5284 cx.notify();
5285 }
5286
5287 pub fn unfold_at(&mut self, position: impl Into<Position>, cx: &mut Context<Self>) -> bool {
5300 let offset = self.text.position_to_offset(&position.into());
5301 let line = self.text.offset_to_point(offset).row;
5302 let covering: Vec<usize> = self
5305 .display_map
5306 .folded_ranges()
5307 .iter()
5308 .filter(|fold| line > fold.start_line && line < fold.end_line)
5309 .map(|fold| fold.start_line)
5310 .collect();
5311 if covering.is_empty() {
5312 return false;
5313 }
5314
5315 for start_line in covering {
5316 self.display_map.set_folded(start_line, false);
5317 }
5318 cx.notify();
5319 true
5320 }
5321
5322 #[doc(hidden)]
5324 pub fn line_number(mut self, line_number: bool) -> Self {
5325 if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
5326 *l = line_number;
5327 }
5328 self
5329 }
5330
5331 pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
5333 if let LayoutMode::CodeEditor { line_number: l, .. } = &mut self.mode {
5334 *l = line_number;
5335 }
5336 cx.notify();
5337 }
5338}