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