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