1use std::default::Default;
8use std::ops::Range;
9
10use bitflags::bitflags;
11use embedder_traits::{EmbedderMsg, ScriptToEmbedderChan};
12use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
13use script_bindings::codegen::GenericBindings::MouseEventBinding::MouseEventMethods;
14use script_bindings::codegen::GenericBindings::UIEventBinding::UIEventMethods;
15use script_bindings::match_domstring_ascii;
16use script_bindings::trace::CustomTraceable;
17use servo_base::generic_channel::GenericCallback;
18use servo_base::id::WebViewId;
19use servo_base::text::{Utf8CodeUnits, Utf16CodeUnits};
20use servo_base::{Rope, RopeIndex, RopeMovement, RopeSlice};
21
22use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::bindings::str::DOMString;
27use crate::dom::compositionevent::CompositionEvent;
28use crate::dom::event::Event;
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::inputevent::InputEvent;
31use crate::dom::keyboardevent::KeyboardEvent;
32use crate::dom::mouseevent::MouseEvent;
33use crate::dom::types::{ClipboardEvent, UIEvent};
34use crate::drag_data_store::Kind;
35
36pub trait ClipboardProvider {
39 fn get_text(&mut self) -> Result<String, String>;
41 fn set_text(&mut self, _: String);
43}
44
45#[derive(MallocSizeOf)]
46pub(crate) struct EmbedderClipboardProvider {
47 pub embedder_sender: ScriptToEmbedderChan,
48 pub webview_id: WebViewId,
49}
50
51impl ClipboardProvider for EmbedderClipboardProvider {
52 fn get_text(&mut self) -> Result<String, String> {
53 let (callback, rx) = GenericCallback::new_blocking().unwrap();
54 self.embedder_sender
55 .send(EmbedderMsg::GetClipboardText(self.webview_id, callback))
56 .unwrap();
57 rx.recv().unwrap()
58 }
59 fn set_text(&mut self, s: String) {
60 self.embedder_sender
61 .send(EmbedderMsg::SetClipboardText(self.webview_id, s))
62 .unwrap();
63 }
64}
65
66#[derive(Clone, Copy, PartialEq)]
67pub enum Selection {
68 Selected,
69 NotSelected,
70}
71
72#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
73pub enum SelectionDirection {
74 Forward,
75 Backward,
76 None,
77}
78
79impl From<DOMString> for SelectionDirection {
80 fn from(direction: DOMString) -> SelectionDirection {
81 match_domstring_ascii!(direction,
82 "forward" => SelectionDirection::Forward,
83 "backward" => SelectionDirection::Backward,
84 _ => SelectionDirection::None,
85 )
86 }
87}
88
89impl From<SelectionDirection> for DOMString {
90 fn from(direction: SelectionDirection) -> DOMString {
91 match direction {
92 SelectionDirection::Forward => DOMString::from("forward"),
93 SelectionDirection::Backward => DOMString::from("backward"),
94 SelectionDirection::None => DOMString::from("none"),
95 }
96 }
97}
98
99#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
100pub enum Lines {
101 Single,
102 Multiple,
103}
104
105impl Lines {
106 fn normalize(&self, contents: impl Into<String>) -> String {
107 let contents = contents.into().replace("\r\n", "\n");
108 match self {
109 Self::Multiple => {
110 contents.replace("\r", "\n")
112 },
113 Lines::Single => contents.replace(['\r', '\n'], " "),
117 }
118 }
119}
120
121#[derive(Clone, Copy, PartialEq)]
122pub(crate) struct SelectionState {
123 start: RopeIndex,
124 end: RopeIndex,
125 direction: SelectionDirection,
126}
127
128#[derive(JSTraceable, MallocSizeOf)]
130pub struct TextInput<T: ClipboardProvider> {
131 #[no_trace]
132 rope: Rope,
133
134 mode: Lines,
139
140 #[no_trace]
142 edit_point: RopeIndex,
143
144 #[no_trace]
147 selection_origin: Option<RopeIndex>,
148 selection_direction: SelectionDirection,
149
150 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
151 clipboard_provider: T,
152
153 max_length: Option<Utf16CodeUnits>,
157 min_length: Option<Utf16CodeUnits>,
158
159 was_last_change_by_set_content: bool,
161
162 currently_dragging: bool,
164}
165
166#[derive(Clone, Copy, PartialEq)]
167pub enum IsComposing {
168 Composing,
169 NotComposing,
170}
171
172impl From<IsComposing> for bool {
173 fn from(is_composing: IsComposing) -> Self {
174 match is_composing {
175 IsComposing::Composing => true,
176 IsComposing::NotComposing => false,
177 }
178 }
179}
180
181#[derive(Clone, Copy, PartialEq)]
183pub enum InputType {
184 InsertText,
185 InsertLineBreak,
186 InsertFromPaste,
187 InsertCompositionText,
188 DeleteByCut,
189 DeleteContentBackward,
190 DeleteContentForward,
191 Nothing,
192}
193
194impl InputType {
195 fn as_str(&self) -> &str {
196 match *self {
197 InputType::InsertText => "insertText",
198 InputType::InsertLineBreak => "insertLineBreak",
199 InputType::InsertFromPaste => "insertFromPaste",
200 InputType::InsertCompositionText => "insertCompositionText",
201 InputType::DeleteByCut => "deleteByCut",
202 InputType::DeleteContentBackward => "deleteContentBackward",
203 InputType::DeleteContentForward => "deleteContentForward",
204 InputType::Nothing => "",
205 }
206 }
207}
208
209pub enum KeyReaction {
211 TriggerDefaultAction,
212 DispatchInput(Option<String>, IsComposing, InputType),
213 RedrawSelection,
214 Nothing,
215}
216
217bitflags! {
218 #[derive(Clone, Copy)]
221 pub struct ClipboardEventFlags: u8 {
222 const QueueInputEvent = 1 << 0;
223 const FireClipboardChangedEvent = 1 << 1;
224 }
225}
226
227pub struct ClipboardEventReaction {
228 pub flags: ClipboardEventFlags,
229 pub text: Option<String>,
230 pub input_type: InputType,
231}
232
233impl ClipboardEventReaction {
234 fn new(flags: ClipboardEventFlags) -> Self {
235 Self {
236 flags,
237 text: None,
238 input_type: InputType::Nothing,
239 }
240 }
241
242 fn with_text(mut self, text: String) -> Self {
243 self.text = Some(text);
244 self
245 }
246
247 fn with_input_type(mut self, input_type: InputType) -> Self {
248 self.input_type = input_type;
249 self
250 }
251
252 fn empty() -> Self {
253 Self::new(ClipboardEventFlags::empty())
254 }
255}
256
257#[derive(Clone, Copy, Eq, PartialEq)]
259pub enum Direction {
260 Forward,
261 Backward,
262}
263
264#[cfg(target_os = "macos")]
266pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
267#[cfg(not(target_os = "macos"))]
268pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
269
270fn len_of_first_n_code_units(text: &DOMString, n: Utf16CodeUnits) -> Utf8CodeUnits {
274 let mut utf8_len = Utf8CodeUnits::zero();
275 let mut utf16_len = Utf16CodeUnits::zero();
276 for c in text.str().chars() {
277 utf16_len += Utf16CodeUnits(c.len_utf16());
278 if utf16_len > n {
279 break;
280 }
281 utf8_len += Utf8CodeUnits(c.len_utf8());
282 }
283 utf8_len
284}
285
286impl<T: ClipboardProvider> TextInput<T> {
287 pub fn new(lines: Lines, initial: DOMString, clipboard_provider: T) -> TextInput<T> {
289 Self {
290 rope: Rope::new(initial),
291 mode: lines,
292 edit_point: Default::default(),
293 selection_origin: None,
294 clipboard_provider,
295 max_length: Default::default(),
296 min_length: Default::default(),
297 selection_direction: SelectionDirection::None,
298 was_last_change_by_set_content: true,
299 currently_dragging: Default::default(),
300 }
301 }
302
303 pub fn edit_point(&self) -> RopeIndex {
304 self.edit_point
305 }
306
307 pub fn selection_origin(&self) -> Option<RopeIndex> {
308 self.selection_origin
309 }
310
311 pub fn selection_origin_or_edit_point(&self) -> RopeIndex {
314 self.selection_origin.unwrap_or(self.edit_point)
315 }
316
317 pub fn selection_direction(&self) -> SelectionDirection {
318 self.selection_direction
319 }
320
321 pub fn set_max_length(&mut self, length: Option<Utf16CodeUnits>) {
322 self.max_length = length;
323 }
324
325 pub fn set_min_length(&mut self, length: Option<Utf16CodeUnits>) {
326 self.min_length = length;
327 }
328
329 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
331 self.was_last_change_by_set_content
332 }
333
334 fn delete_selection(&mut self) -> bool {
337 if self.selection_start() == self.selection_end() {
338 return false;
339 }
340 self.replace_selection(&DOMString::new());
341 true
342 }
343
344 pub fn delete_unit_or_selection(&mut self, unit: RopeMovement, direction: Direction) -> bool {
349 if !self.has_uncollapsed_selection() {
350 let amount = match direction {
351 Direction::Forward => 1,
352 Direction::Backward => -1,
353 };
354 self.modify_selection(amount, unit);
355 }
356 self.delete_selection()
357 }
358
359 pub fn insert<S: Into<String>>(&mut self, string: S) {
362 if self.selection_origin.is_none() {
363 self.selection_origin = Some(self.edit_point);
364 }
365 self.replace_selection(&DOMString::from(string.into()));
366 }
367
368 pub fn selection_start(&self) -> RopeIndex {
371 match self.selection_direction {
372 SelectionDirection::None | SelectionDirection::Forward => {
373 self.selection_origin_or_edit_point()
374 },
375 SelectionDirection::Backward => self.edit_point,
376 }
377 }
378
379 pub(crate) fn selection_start_utf16(&self) -> Utf16CodeUnits {
380 self.rope.index_to_utf16_offset(self.selection_start())
381 }
382
383 fn selection_start_offset(&self) -> Utf8CodeUnits {
385 self.rope.index_to_utf8_offset(self.selection_start())
386 }
387
388 pub fn selection_end(&self) -> RopeIndex {
391 match self.selection_direction {
392 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
393 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
394 }
395 }
396
397 pub(crate) fn selection_end_utf16(&self) -> Utf16CodeUnits {
398 self.rope.index_to_utf16_offset(self.selection_end())
399 }
400
401 pub fn selection_end_offset(&self) -> Utf8CodeUnits {
403 self.rope.index_to_utf8_offset(self.selection_end())
404 }
405
406 #[inline]
409 pub(crate) fn has_uncollapsed_selection(&self) -> bool {
410 self.selection_origin
411 .is_some_and(|selection_origin| selection_origin != self.edit_point)
412 }
413
414 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<Utf8CodeUnits> {
418 self.selection_start_offset()..self.selection_end_offset()
419 }
420
421 pub(crate) fn sorted_selection_character_offsets_range(&self) -> Range<usize> {
425 self.rope.index_to_character_offset(self.selection_start())..
426 self.rope.index_to_character_offset(self.selection_end())
427 }
428
429 pub(crate) fn selection_state(&self) -> SelectionState {
431 SelectionState {
432 start: self.selection_start(),
433 end: self.selection_end(),
434 direction: self.selection_direction,
435 }
436 }
437
438 fn assert_ok_selection(&self) {
440 debug!(
441 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
442 self.edit_point, self.selection_origin, self.selection_direction
443 );
444
445 debug_assert_eq!(self.edit_point, self.rope.normalize_index(self.edit_point));
446 if let Some(selection_origin) = self.selection_origin {
447 debug_assert_eq!(
448 selection_origin,
449 self.rope.normalize_index(selection_origin)
450 );
451 match self.selection_direction {
452 SelectionDirection::None | SelectionDirection::Forward => {
453 debug_assert!(selection_origin <= self.edit_point)
454 },
455 SelectionDirection::Backward => debug_assert!(self.edit_point <= selection_origin),
456 }
457 }
458 }
459
460 fn selection_slice(&self) -> RopeSlice<'_> {
461 self.rope
462 .slice(Some(self.selection_start()), Some(self.selection_end()))
463 }
464
465 pub(crate) fn get_selection_text(&self) -> Option<String> {
466 let text: String = self.selection_slice().into();
467 if text.is_empty() {
468 return None;
469 }
470 Some(text)
471 }
472
473 fn selection_utf16_len(&self) -> Utf16CodeUnits {
475 Utf16CodeUnits(
476 self.selection_slice()
477 .chars()
478 .map(char::len_utf16)
479 .sum::<usize>(),
480 )
481 }
482
483 pub fn replace_selection(&mut self, insert: &DOMString) {
487 let string_to_insert = if let Some(max_length) = self.max_length {
488 let utf16_length_without_selection =
489 self.len_utf16().saturating_sub(self.selection_utf16_len());
490 let utf16_length_that_can_be_inserted =
491 max_length.saturating_sub(utf16_length_without_selection);
492 let Utf8CodeUnits(last_char_index) =
493 len_of_first_n_code_units(insert, utf16_length_that_can_be_inserted);
494 &insert.str()[..last_char_index]
495 } else {
496 &insert.str()
497 };
498 let string_to_insert = self.mode.normalize(string_to_insert);
499
500 let start = self.selection_start();
501 let end = self.selection_end();
502 let end_index_of_insertion = self.rope.replace_range(start..end, string_to_insert);
503
504 self.was_last_change_by_set_content = false;
505 self.clear_selection();
506 self.edit_point = end_index_of_insertion;
507 }
508
509 pub fn modify_edit_point(&mut self, amount: isize, movement: RopeMovement) {
510 if amount == 0 {
511 return;
512 }
513
514 if matches!(movement, RopeMovement::Line) || !self.has_uncollapsed_selection() {
517 self.clear_selection();
518 self.edit_point = self.rope.move_by(self.edit_point, movement, amount);
519 return;
520 }
521
522 let new_edit_point = if amount > 0 {
525 self.selection_end()
526 } else {
527 self.selection_start()
528 };
529 self.clear_selection();
530 self.edit_point = new_edit_point;
531 }
532
533 pub fn modify_selection(&mut self, amount: isize, movement: RopeMovement) {
534 let old_edit_point = self.edit_point;
535 self.edit_point = self.rope.move_by(old_edit_point, movement, amount);
536
537 if self.selection_origin.is_none() {
538 self.selection_origin = Some(old_edit_point);
539 }
540 self.update_selection_direction();
541 }
542
543 pub fn modify_selection_or_edit_point(
544 &mut self,
545 amount: isize,
546 movement: RopeMovement,
547 select: Selection,
548 ) {
549 match select {
550 Selection::Selected => self.modify_selection(amount, movement),
551 Selection::NotSelected => self.modify_edit_point(amount, movement),
552 }
553 self.assert_ok_selection();
554 }
555
556 fn update_selection_direction(&mut self) {
561 debug!(
562 "edit_point: {:?}, selection_origin: {:?}",
563 self.edit_point, self.selection_origin
564 );
565 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
566 SelectionDirection::Backward
567 } else {
568 SelectionDirection::Forward
569 }
570 }
571
572 pub fn handle_return(&mut self) -> KeyReaction {
574 match self.mode {
575 Lines::Multiple => {
576 self.insert('\n');
577 KeyReaction::DispatchInput(
578 None,
579 IsComposing::NotComposing,
580 InputType::InsertLineBreak,
581 )
582 },
583 Lines::Single => KeyReaction::TriggerDefaultAction,
584 }
585 }
586
587 pub fn select_all(&mut self) {
589 self.selection_origin = Some(RopeIndex::default());
590 self.edit_point = self.rope.last_index();
591 self.selection_direction = SelectionDirection::Forward;
592 self.assert_ok_selection();
593 }
594
595 pub fn clear_selection(&mut self) {
597 self.selection_origin = None;
598 self.selection_direction = SelectionDirection::None;
599 }
600
601 pub(crate) fn clear_selection_to_end(&mut self) {
603 self.clear_selection();
604 self.edit_point = self.rope.last_index();
605 }
606
607 pub(crate) fn clear_selection_to_start(&mut self) {
608 self.clear_selection();
609 self.edit_point = Default::default();
610 }
611
612 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
614 let key = event.key();
615 let mods = event.modifiers();
616 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
617 }
618
619 pub fn handle_keydown_aux(
622 &mut self,
623 key: Key,
624 mut mods: Modifiers,
625 macos: bool,
626 ) -> KeyReaction {
627 let maybe_select = if mods.contains(Modifiers::SHIFT) {
628 Selection::Selected
629 } else {
630 Selection::NotSelected
631 };
632
633 let alt_or_control = if macos {
634 Modifiers::ALT
635 } else {
636 Modifiers::CONTROL
637 };
638
639 mods.remove(Modifiers::SHIFT);
640 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
641 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
642 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
643 KeyReaction::RedrawSelection
644 })
645 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
646 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
647 KeyReaction::RedrawSelection
648 })
649 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
650 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
651 KeyReaction::RedrawSelection
652 })
653 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
654 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
655 KeyReaction::RedrawSelection
656 })
657 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
658 self.modify_selection_or_edit_point(-1, RopeMovement::LineStartOrEnd, maybe_select);
659 KeyReaction::RedrawSelection
660 })
661 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
662 self.modify_selection_or_edit_point(1, RopeMovement::LineStartOrEnd, maybe_select);
663 KeyReaction::RedrawSelection
664 })
665 .shortcut(CMD_OR_CONTROL, 'A', || {
666 self.select_all();
667 KeyReaction::RedrawSelection
668 })
669 .shortcut(CMD_OR_CONTROL, 'X', || {
670 if let Some(text) = self.get_selection_text() {
671 self.clipboard_provider.set_text(text);
672 self.delete_selection();
673 }
674 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::DeleteByCut)
675 })
676 .shortcut(CMD_OR_CONTROL, 'C', || {
677 if let Some(text) = self.get_selection_text() {
679 self.clipboard_provider.set_text(text);
680 }
681 KeyReaction::DispatchInput(None, IsComposing::NotComposing, InputType::Nothing)
682 })
683 .shortcut(CMD_OR_CONTROL, 'V', || {
684 if let Ok(text_content) = self.clipboard_provider.get_text() {
685 self.insert(&text_content);
686 KeyReaction::DispatchInput(
687 Some(text_content),
688 IsComposing::NotComposing,
689 InputType::InsertFromPaste,
690 )
691 } else {
692 KeyReaction::DispatchInput(
693 Some("".to_string()),
694 IsComposing::NotComposing,
695 InputType::InsertFromPaste,
696 )
697 }
698 })
699 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
700 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Forward) {
701 KeyReaction::DispatchInput(
702 None,
703 IsComposing::NotComposing,
704 InputType::DeleteContentForward,
705 )
706 } else {
707 KeyReaction::Nothing
708 }
709 })
710 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
711 if self.delete_unit_or_selection(RopeMovement::Grapheme, Direction::Backward) {
712 KeyReaction::DispatchInput(
713 None,
714 IsComposing::NotComposing,
715 InputType::DeleteContentBackward,
716 )
717 } else {
718 KeyReaction::Nothing
719 }
720 })
721 .shortcut(alt_or_control, Key::Named(NamedKey::Backspace), || {
722 if self.delete_unit_or_selection(RopeMovement::Word, Direction::Backward) {
723 KeyReaction::DispatchInput(
724 None,
725 IsComposing::NotComposing,
726 InputType::DeleteContentBackward,
727 )
728 } else {
729 KeyReaction::Nothing
730 }
731 })
732 .optional_shortcut(
733 macos,
734 Modifiers::META,
735 Key::Named(NamedKey::ArrowLeft),
736 || {
737 self.modify_selection_or_edit_point(
738 -1,
739 RopeMovement::LineStartOrEnd,
740 maybe_select,
741 );
742 KeyReaction::RedrawSelection
743 },
744 )
745 .optional_shortcut(
746 macos,
747 Modifiers::META,
748 Key::Named(NamedKey::ArrowRight),
749 || {
750 self.modify_selection_or_edit_point(
751 1,
752 RopeMovement::LineStartOrEnd,
753 maybe_select,
754 );
755 KeyReaction::RedrawSelection
756 },
757 )
758 .optional_shortcut(
759 macos,
760 Modifiers::META,
761 Key::Named(NamedKey::ArrowUp),
762 || {
763 self.modify_selection_or_edit_point(
764 -1,
765 RopeMovement::RopeStartOrEnd,
766 maybe_select,
767 );
768 KeyReaction::RedrawSelection
769 },
770 )
771 .optional_shortcut(
772 macos,
773 Modifiers::META,
774 Key::Named(NamedKey::ArrowDown),
775 || {
776 self.modify_selection_or_edit_point(
777 1,
778 RopeMovement::RopeStartOrEnd,
779 maybe_select,
780 );
781 KeyReaction::RedrawSelection
782 },
783 )
784 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowLeft), || {
785 self.modify_selection_or_edit_point(-1, RopeMovement::Word, maybe_select);
786 KeyReaction::RedrawSelection
787 })
788 .shortcut(alt_or_control, Key::Named(NamedKey::ArrowRight), || {
789 self.modify_selection_or_edit_point(1, RopeMovement::Word, maybe_select);
790 KeyReaction::RedrawSelection
791 })
792 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
793 self.modify_selection_or_edit_point(-1, RopeMovement::Grapheme, maybe_select);
794 KeyReaction::RedrawSelection
795 })
796 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
797 self.modify_selection_or_edit_point(1, RopeMovement::Grapheme, maybe_select);
798 KeyReaction::RedrawSelection
799 })
800 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
801 self.modify_selection_or_edit_point(-1, RopeMovement::Line, maybe_select);
802 KeyReaction::RedrawSelection
803 })
804 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
805 self.modify_selection_or_edit_point(1, RopeMovement::Line, maybe_select);
806 KeyReaction::RedrawSelection
807 })
808 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
809 self.handle_return()
810 })
811 .optional_shortcut(
812 macos,
813 Modifiers::empty(),
814 Key::Named(NamedKey::Home),
815 || {
816 self.modify_selection_or_edit_point(
817 -1,
818 RopeMovement::RopeStartOrEnd,
819 maybe_select,
820 );
821 KeyReaction::RedrawSelection
822 },
823 )
824 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
825 self.modify_selection_or_edit_point(1, RopeMovement::RopeStartOrEnd, maybe_select);
826 KeyReaction::RedrawSelection
827 })
828 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
829 self.modify_selection_or_edit_point(-28, RopeMovement::Line, maybe_select);
830 KeyReaction::RedrawSelection
831 })
832 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
833 self.modify_selection_or_edit_point(28, RopeMovement::Line, maybe_select);
834 KeyReaction::RedrawSelection
835 })
836 .otherwise(|| {
837 if let Key::Character(ref character) = key {
838 self.insert(character);
839 return KeyReaction::DispatchInput(
840 Some(character.to_string()),
841 IsComposing::NotComposing,
842 InputType::InsertText,
843 );
844 }
845 if matches!(key, Key::Named(NamedKey::Process)) {
846 return KeyReaction::DispatchInput(
847 None,
848 IsComposing::Composing,
849 InputType::Nothing,
850 );
851 }
852 KeyReaction::Nothing
853 })
854 .unwrap()
855 }
856
857 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
858 let insertion = event.data().str();
859 if insertion.is_empty() {
860 self.clear_selection();
861 return KeyReaction::RedrawSelection;
862 }
863
864 self.insert(insertion.to_string());
865 KeyReaction::DispatchInput(
866 Some(insertion.to_string()),
867 IsComposing::NotComposing,
868 InputType::InsertCompositionText,
869 )
870 }
871
872 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
873 let insertion = event.data().str();
874 if insertion.is_empty() {
875 return KeyReaction::Nothing;
876 }
877
878 let start = self.selection_start_offset();
879 let insertion = insertion.to_string();
880 self.insert(insertion.clone());
881 self.set_selection_range_utf8(
882 start,
883 start + event.data().len_utf8(),
884 SelectionDirection::Forward,
885 );
886 KeyReaction::DispatchInput(
887 Some(insertion),
888 IsComposing::Composing,
889 InputType::InsertCompositionText,
890 )
891 }
892
893 fn edit_point_for_mouse_event(&self, event: &MouseEvent) -> RopeIndex {
894 event
895 .dom_offset_for_selection()
896 .map(|character_offset| {
897 self.rope.move_by(
898 Default::default(),
899 RopeMovement::Character,
900 character_offset.0 as isize,
901 )
902 })
903 .unwrap_or_else(|| self.rope.last_index())
904 }
905
906 pub(crate) fn handle_mouse_event(&mut self, mouse_event: &MouseEvent) -> bool {
909 let event_type = mouse_event.upcast::<Event>().type_();
912 if event_type == atom!("mouseup") || mouse_event.Buttons() & 1 != 1 {
913 self.currently_dragging = false;
914 }
915
916 if event_type == atom!("mousedown") {
917 return self.handle_mousedown(mouse_event);
918 }
919
920 if event_type == atom!("mousemove") && self.currently_dragging {
921 self.edit_point = self.edit_point_for_mouse_event(mouse_event);
922 self.update_selection_direction();
923 return true;
924 }
925
926 false
927 }
928
929 fn handle_mousedown(&mut self, mouse_event: &MouseEvent) -> bool {
934 assert_eq!(mouse_event.upcast::<Event>().type_(), atom!("mousedown"));
935
936 if mouse_event.Button() != 0 {
943 return false;
944 }
945
946 self.currently_dragging = true;
947 match mouse_event.upcast::<UIEvent>().Detail() {
948 3 => {
949 let word_boundaries = self.rope.line_boundaries(self.edit_point);
950 self.edit_point = word_boundaries.end;
951 self.selection_origin = Some(word_boundaries.start);
952 self.update_selection_direction();
953 true
954 },
955 2 => {
956 let word_boundaries = self.rope.relevant_word_boundaries(self.edit_point);
957 self.edit_point = word_boundaries.end;
958 self.selection_origin = Some(word_boundaries.start);
959 self.update_selection_direction();
960 true
961 },
962 1 => {
963 self.clear_selection();
964 self.edit_point = self.edit_point_for_mouse_event(mouse_event);
965 self.selection_origin = Some(self.edit_point);
966 self.update_selection_direction();
967 true
968 },
969 _ => {
970 false
974 },
975 }
976 }
977
978 pub(crate) fn is_empty(&self) -> bool {
980 self.rope.is_empty()
981 }
982
983 pub(crate) fn len_utf16(&self) -> Utf16CodeUnits {
985 self.rope.len_utf16()
986 }
987
988 pub fn get_content(&self) -> DOMString {
990 self.rope.contents().into()
991 }
992
993 pub fn set_content(&mut self, content: DOMString) {
1000 self.rope = Rope::new(
1001 content
1002 .str()
1003 .to_string()
1004 .replace("\r\n", "\n")
1005 .replace("\r", "\n"),
1006 );
1007 self.was_last_change_by_set_content = true;
1008
1009 self.edit_point = self.rope.normalize_index(self.edit_point());
1010 self.selection_origin = self
1011 .selection_origin
1012 .map(|selection_origin| self.rope.normalize_index(selection_origin));
1013 }
1014
1015 pub fn set_selection_range_utf16(
1016 &mut self,
1017 start: Utf16CodeUnits,
1018 end: Utf16CodeUnits,
1019 direction: SelectionDirection,
1020 ) {
1021 self.set_selection_range_utf8(
1022 self.rope.utf16_offset_to_utf8_offset(start),
1023 self.rope.utf16_offset_to_utf8_offset(end),
1024 direction,
1025 );
1026 }
1027
1028 pub fn set_selection_range_utf8(
1029 &mut self,
1030 mut start: Utf8CodeUnits,
1031 mut end: Utf8CodeUnits,
1032 direction: SelectionDirection,
1033 ) {
1034 let text_end = self.get_content().len_utf8();
1035 if end > text_end {
1036 end = text_end;
1037 }
1038 if start > end {
1039 start = end;
1040 }
1041
1042 self.selection_direction = direction;
1043
1044 match direction {
1045 SelectionDirection::None | SelectionDirection::Forward => {
1046 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(start));
1047 self.edit_point = self.rope.utf8_offset_to_rope_index(end);
1048 },
1049 SelectionDirection::Backward => {
1050 self.selection_origin = Some(self.rope.utf8_offset_to_rope_index(end));
1051 self.edit_point = self.rope.utf8_offset_to_rope_index(start);
1052 },
1053 }
1054
1055 self.assert_ok_selection();
1056 }
1057
1058 pub(crate) fn handle_clipboard_event(
1066 &mut self,
1067 clipboard_event: &ClipboardEvent,
1068 ) -> ClipboardEventReaction {
1069 let event = clipboard_event.upcast::<Event>();
1070 if !event.IsTrusted() {
1071 return ClipboardEventReaction::empty();
1072 }
1073
1074 if event.DefaultPrevented() {
1077 return ClipboardEventReaction::empty();
1080 }
1081
1082 let event_type = event.Type();
1083 match_domstring_ascii!(event_type,
1084 "copy" => {
1085 let selection = self.get_selection_text();
1087
1088 if let Some(text) = selection {
1090 self.clipboard_provider.set_text(text);
1091 }
1092
1093 ClipboardEventReaction::new(ClipboardEventFlags::FireClipboardChangedEvent)
1095 },
1096 "cut" => {
1097 let selection = self.get_selection_text();
1099
1100 let Some(text) = selection else {
1102 return ClipboardEventReaction::empty();
1104 };
1105
1106 self.clipboard_provider.set_text(text);
1108
1109 self.delete_selection();
1111
1112 ClipboardEventReaction::new(
1115 ClipboardEventFlags::FireClipboardChangedEvent |
1116 ClipboardEventFlags::QueueInputEvent,
1117 )
1118 .with_input_type(InputType::DeleteByCut)
1119 },
1120 "paste" => {
1121 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1123 return ClipboardEventReaction::empty();
1124 };
1125 let Some(drag_data_store) = data_transfer.data_store() else {
1126 return ClipboardEventReaction::empty();
1127 };
1128
1129 let Some(text_content) =
1139 drag_data_store
1140 .iter_item_list()
1141 .find_map(|item| match item {
1142 Kind::Text { data, .. } => Some(data.to_string()),
1143 _ => None,
1144 })
1145 else {
1146 return ClipboardEventReaction::empty();
1147 };
1148 if text_content.is_empty() {
1149 return ClipboardEventReaction::empty();
1150 }
1151
1152 self.insert(&text_content);
1153
1154 ClipboardEventReaction::new(ClipboardEventFlags::QueueInputEvent)
1157 .with_text(text_content)
1158 .with_input_type(InputType::InsertFromPaste)
1159 },
1160 _ => ClipboardEventReaction::empty(),)
1161 }
1162
1163 pub(crate) fn queue_input_event(
1165 &self,
1166 target: &EventTarget,
1167 data: Option<String>,
1168 is_composing: IsComposing,
1169 input_type: InputType,
1170 ) {
1171 let global = target.global();
1172 let target = Trusted::new(target);
1173 global.task_manager().user_interaction_task_source().queue(
1174 task!(fire_input_event: move |cx| {
1175 let target = target.root();
1176 let global = target.global();
1177 let window = global.as_window();
1178 let event = InputEvent::new(
1179 cx,
1180 window,
1181 None,
1182 atom!("input"),
1183 true,
1184 false,
1185 Some(window),
1186 0,
1187 data.map(DOMString::from),
1188 is_composing.into(),
1189 input_type.as_str().into(),
1190 );
1191 let event = event.upcast::<Event>();
1192 event.set_composed(true);
1193 event.fire(cx, &target);
1194 }),
1195 );
1196 }
1197}