1use std::borrow::ToOwned;
8use std::cmp::min;
9use std::default::Default;
10use std::ops::{Add, AddAssign, Range};
11
12use bitflags::bitflags;
13use keyboard_types::{Key, KeyState, Modifiers, NamedKey, ShortcutMatcher};
14use unicode_segmentation::UnicodeSegmentation;
15
16use crate::clipboard_provider::ClipboardProvider;
17use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::str::DOMString;
20use crate::dom::compositionevent::CompositionEvent;
21use crate::dom::event::Event;
22use crate::dom::keyboardevent::KeyboardEvent;
23use crate::dom::types::ClipboardEvent;
24use crate::drag_data_store::Kind;
25
26#[derive(Clone, Copy, PartialEq)]
27pub enum Selection {
28 Selected,
29 NotSelected,
30}
31
32#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
33pub enum SelectionDirection {
34 Forward,
35 Backward,
36 None,
37}
38
39#[derive(Clone, Copy, Debug, Eq, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
40pub struct UTF8Bytes(pub usize);
41
42impl UTF8Bytes {
43 pub fn zero() -> UTF8Bytes {
44 UTF8Bytes(0)
45 }
46
47 pub fn one() -> UTF8Bytes {
48 UTF8Bytes(1)
49 }
50
51 pub(crate) fn unwrap_range(byte_range: Range<UTF8Bytes>) -> Range<usize> {
52 byte_range.start.0..byte_range.end.0
53 }
54
55 pub(crate) fn saturating_sub(self, other: UTF8Bytes) -> UTF8Bytes {
56 if self > other {
57 UTF8Bytes(self.0 - other.0)
58 } else {
59 UTF8Bytes::zero()
60 }
61 }
62}
63
64impl Add for UTF8Bytes {
65 type Output = UTF8Bytes;
66
67 fn add(self, other: UTF8Bytes) -> UTF8Bytes {
68 UTF8Bytes(self.0 + other.0)
69 }
70}
71
72impl AddAssign for UTF8Bytes {
73 fn add_assign(&mut self, other: UTF8Bytes) {
74 *self = UTF8Bytes(self.0 + other.0)
75 }
76}
77
78trait StrExt {
79 fn len_utf8(&self) -> UTF8Bytes;
80}
81impl StrExt for str {
82 fn len_utf8(&self) -> UTF8Bytes {
83 UTF8Bytes(self.len())
84 }
85}
86
87#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
88pub struct UTF16CodeUnits(pub usize);
89
90impl UTF16CodeUnits {
91 pub fn zero() -> UTF16CodeUnits {
92 UTF16CodeUnits(0)
93 }
94
95 pub fn one() -> UTF16CodeUnits {
96 UTF16CodeUnits(1)
97 }
98
99 pub(crate) fn saturating_sub(self, other: UTF16CodeUnits) -> UTF16CodeUnits {
100 if self > other {
101 UTF16CodeUnits(self.0 - other.0)
102 } else {
103 UTF16CodeUnits::zero()
104 }
105 }
106}
107
108impl Add for UTF16CodeUnits {
109 type Output = UTF16CodeUnits;
110
111 fn add(self, other: UTF16CodeUnits) -> UTF16CodeUnits {
112 UTF16CodeUnits(self.0 + other.0)
113 }
114}
115
116impl AddAssign for UTF16CodeUnits {
117 fn add_assign(&mut self, other: UTF16CodeUnits) {
118 *self = UTF16CodeUnits(self.0 + other.0)
119 }
120}
121
122impl From<DOMString> for SelectionDirection {
123 fn from(direction: DOMString) -> SelectionDirection {
124 match direction.as_ref() {
125 "forward" => SelectionDirection::Forward,
126 "backward" => SelectionDirection::Backward,
127 _ => SelectionDirection::None,
128 }
129 }
130}
131
132impl From<SelectionDirection> for DOMString {
133 fn from(direction: SelectionDirection) -> DOMString {
134 match direction {
135 SelectionDirection::Forward => DOMString::from("forward"),
136 SelectionDirection::Backward => DOMString::from("backward"),
137 SelectionDirection::None => DOMString::from("none"),
138 }
139 }
140}
141
142#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
143pub struct TextPoint {
144 pub line: usize,
146 pub index: UTF8Bytes,
148}
149
150impl TextPoint {
151 fn constrain_to(&self, lines: &[DOMString]) -> TextPoint {
153 let line = min(self.line, lines.len() - 1);
154
155 TextPoint {
156 line,
157 index: min(self.index, lines[line].len_utf8()),
158 }
159 }
160}
161
162#[derive(Clone, Copy, PartialEq)]
163pub(crate) struct SelectionState {
164 start: TextPoint,
165 end: TextPoint,
166 direction: SelectionDirection,
167}
168
169#[derive(JSTraceable, MallocSizeOf)]
171pub struct TextInput<T: ClipboardProvider> {
172 lines: Vec<DOMString>,
174
175 edit_point: TextPoint,
177
178 selection_origin: Option<TextPoint>,
181 selection_direction: SelectionDirection,
182
183 multiline: bool,
185
186 #[ignore_malloc_size_of = "Can't easily measure this generic type"]
187 clipboard_provider: T,
188
189 max_length: Option<UTF16CodeUnits>,
193 min_length: Option<UTF16CodeUnits>,
194
195 was_last_change_by_set_content: bool,
197}
198
199pub enum KeyReaction {
201 TriggerDefaultAction,
202 DispatchInput,
203 RedrawSelection,
204 Nothing,
205}
206
207bitflags! {
208 pub struct ClipboardEventReaction: u8 {
211 const QueueInputEvent = 1 << 0;
212 const FireClipboardChangedEvent = 1 << 1;
213 }
214}
215
216impl Default for TextPoint {
217 fn default() -> TextPoint {
218 TextPoint {
219 line: 0,
220 index: UTF8Bytes::zero(),
221 }
222 }
223}
224
225#[derive(Eq, PartialEq)]
227pub enum Lines {
228 Single,
229 Multiple,
230}
231
232#[derive(Clone, Copy, Eq, PartialEq)]
234pub enum Direction {
235 Forward,
236 Backward,
237}
238
239#[cfg(target_os = "macos")]
241pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::META;
242#[cfg(not(target_os = "macos"))]
243pub(crate) const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL;
244
245fn len_of_first_n_chars(text: &str, n: usize) -> UTF8Bytes {
250 match text.char_indices().take(n).last() {
251 Some((index, ch)) => UTF8Bytes(index + ch.len_utf8()),
252 None => UTF8Bytes::zero(),
253 }
254}
255
256fn len_of_first_n_code_units(text: &str, n: UTF16CodeUnits) -> UTF8Bytes {
260 let mut utf8_len = UTF8Bytes::zero();
261 let mut utf16_len = UTF16CodeUnits::zero();
262 for c in text.chars() {
263 utf16_len += UTF16CodeUnits(c.len_utf16());
264 if utf16_len > n {
265 break;
266 }
267 utf8_len += UTF8Bytes(c.len_utf8());
268 }
269 utf8_len
270}
271
272impl<T: ClipboardProvider> TextInput<T> {
273 pub fn new(
275 lines: Lines,
276 initial: DOMString,
277 clipboard_provider: T,
278 max_length: Option<UTF16CodeUnits>,
279 min_length: Option<UTF16CodeUnits>,
280 selection_direction: SelectionDirection,
281 ) -> TextInput<T> {
282 let mut i = TextInput {
283 lines: vec![],
284 edit_point: Default::default(),
285 selection_origin: None,
286 multiline: lines == Lines::Multiple,
287 clipboard_provider,
288 max_length,
289 min_length,
290 selection_direction,
291 was_last_change_by_set_content: true,
292 };
293 i.set_content(initial);
294 i
295 }
296
297 pub fn edit_point(&self) -> TextPoint {
298 self.edit_point
299 }
300
301 pub fn selection_origin(&self) -> Option<TextPoint> {
302 self.selection_origin
303 }
304
305 pub fn selection_origin_or_edit_point(&self) -> TextPoint {
308 self.selection_origin.unwrap_or(self.edit_point)
309 }
310
311 pub fn selection_direction(&self) -> SelectionDirection {
312 self.selection_direction
313 }
314
315 pub(crate) fn set_max_length(&mut self, length: Option<UTF16CodeUnits>) {
316 self.max_length = length;
317 }
318
319 pub(crate) fn set_min_length(&mut self, length: Option<UTF16CodeUnits>) {
320 self.min_length = length;
321 }
322
323 pub(crate) fn was_last_change_by_set_content(&self) -> bool {
325 self.was_last_change_by_set_content
326 }
327
328 pub fn delete_char(&mut self, dir: Direction) -> bool {
332 if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) {
333 self.adjust_horizontal_by_one(dir, Selection::Selected);
334 }
335 if self.selection_start() == self.selection_end() {
336 false
337 } else {
338 self.replace_selection(DOMString::new());
339 true
340 }
341 }
342
343 pub fn insert_char(&mut self, ch: char) {
345 self.insert_string(ch.to_string());
346 }
347
348 pub fn insert_string<S: Into<String>>(&mut self, s: S) {
351 if self.selection_origin.is_none() {
352 self.selection_origin = Some(self.edit_point);
353 }
354 self.replace_selection(DOMString::from(s.into()));
355 }
356
357 pub fn selection_start(&self) -> TextPoint {
360 match self.selection_direction {
361 SelectionDirection::None | SelectionDirection::Forward => {
362 self.selection_origin_or_edit_point()
363 },
364 SelectionDirection::Backward => self.edit_point,
365 }
366 }
367
368 pub fn selection_start_offset(&self) -> UTF8Bytes {
370 self.text_point_to_offset(&self.selection_start())
371 }
372
373 pub fn selection_end(&self) -> TextPoint {
376 match self.selection_direction {
377 SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
378 SelectionDirection::Backward => self.selection_origin_or_edit_point(),
379 }
380 }
381
382 pub fn selection_end_offset(&self) -> UTF8Bytes {
384 self.text_point_to_offset(&self.selection_end())
385 }
386
387 #[inline]
389 pub(crate) fn has_selection(&self) -> bool {
390 self.selection_origin.is_some()
391 }
392
393 pub fn sorted_selection_bounds(&self) -> (TextPoint, TextPoint) {
396 (self.selection_start(), self.selection_end())
397 }
398
399 pub(crate) fn sorted_selection_offsets_range(&self) -> Range<UTF8Bytes> {
403 self.selection_start_offset()..self.selection_end_offset()
404 }
405
406 pub(crate) fn selection_state(&self) -> SelectionState {
408 SelectionState {
409 start: self.selection_start(),
410 end: self.selection_end(),
411 direction: self.selection_direction,
412 }
413 }
414
415 fn assert_ok_selection(&self) {
417 debug!(
418 "edit_point: {:?}, selection_origin: {:?}, direction: {:?}",
419 self.edit_point, self.selection_origin, self.selection_direction
420 );
421 if let Some(begin) = self.selection_origin {
422 debug_assert!(begin.line < self.lines.len());
423 debug_assert!(begin.index <= self.lines[begin.line].len_utf8());
424
425 match self.selection_direction {
426 SelectionDirection::None | SelectionDirection::Forward => {
427 debug_assert!(begin <= self.edit_point)
428 },
429
430 SelectionDirection::Backward => debug_assert!(self.edit_point <= begin),
431 }
432 }
433
434 debug_assert!(self.edit_point.line < self.lines.len());
435 debug_assert!(self.edit_point.index <= self.lines[self.edit_point.line].len_utf8());
436 }
437
438 pub(crate) fn get_selection_text(&self) -> Option<String> {
439 let text = self.fold_selection_slices(String::new(), |s, slice| s.push_str(slice));
440 if text.is_empty() {
441 return None;
442 }
443 Some(text)
444 }
445
446 fn selection_utf16_len(&self) -> UTF16CodeUnits {
448 self.fold_selection_slices(UTF16CodeUnits::zero(), |len, slice| {
449 *len += UTF16CodeUnits(slice.chars().map(char::len_utf16).sum::<usize>())
450 })
451 }
452
453 fn fold_selection_slices<B, F: FnMut(&mut B, &str)>(&self, mut acc: B, mut f: F) -> B {
457 if self.has_selection() {
458 let (start, end) = self.sorted_selection_bounds();
459 let UTF8Bytes(start_offset) = start.index;
460 let UTF8Bytes(end_offset) = end.index;
461
462 if start.line == end.line {
463 f(&mut acc, &self.lines[start.line][start_offset..end_offset])
464 } else {
465 f(&mut acc, &self.lines[start.line][start_offset..]);
466 for line in &self.lines[start.line + 1..end.line] {
467 f(&mut acc, "\n");
468 f(&mut acc, line);
469 }
470 f(&mut acc, "\n");
471 f(&mut acc, &self.lines[end.line][..end_offset])
472 }
473 }
474
475 acc
476 }
477
478 pub fn replace_selection(&mut self, insert: DOMString) {
479 if !self.has_selection() {
480 return;
481 }
482
483 let allowed_to_insert_count = if let Some(max_length) = self.max_length {
484 let len_after_selection_replaced =
485 self.utf16_len().saturating_sub(self.selection_utf16_len());
486 max_length.saturating_sub(len_after_selection_replaced)
487 } else {
488 UTF16CodeUnits(usize::MAX)
489 };
490
491 let UTF8Bytes(last_char_index) =
492 len_of_first_n_code_units(&insert, allowed_to_insert_count);
493 let to_insert = &insert[..last_char_index];
494
495 let (start, end) = self.sorted_selection_bounds();
496 let UTF8Bytes(start_offset) = start.index;
497 let UTF8Bytes(end_offset) = end.index;
498
499 let new_lines = {
500 let prefix = &self.lines[start.line][..start_offset];
501 let suffix = &self.lines[end.line][end_offset..];
502 let lines_prefix = &self.lines[..start.line];
503 let lines_suffix = &self.lines[end.line + 1..];
504
505 let mut insert_lines = if self.multiline {
506 to_insert.split('\n').map(DOMString::from).collect()
507 } else {
508 vec![DOMString::from(to_insert)]
509 };
510
511 let mut new_line = prefix.to_owned();
513
514 new_line.push_str(&insert_lines[0]);
515 insert_lines[0] = DOMString::from(new_line);
516
517 let last_insert_lines_index = insert_lines.len() - 1;
518 self.edit_point.index = insert_lines[last_insert_lines_index].len_utf8();
519 self.edit_point.line = start.line + last_insert_lines_index;
520
521 insert_lines[last_insert_lines_index].push_str(suffix);
523
524 let mut new_lines = vec![];
525 new_lines.extend_from_slice(lines_prefix);
526 new_lines.extend_from_slice(&insert_lines);
527 new_lines.extend_from_slice(lines_suffix);
528 new_lines
529 };
530
531 self.lines = new_lines;
532 self.was_last_change_by_set_content = false;
533 self.clear_selection();
534 self.assert_ok_selection();
535 }
536
537 pub fn current_line_length(&self) -> UTF8Bytes {
539 self.lines[self.edit_point.line].len_utf8()
540 }
541
542 pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) {
545 if !self.multiline {
546 return;
547 }
548
549 if select == Selection::Selected {
550 if self.selection_origin.is_none() {
551 self.selection_origin = Some(self.edit_point);
552 }
553 } else {
554 self.clear_selection();
555 }
556
557 assert!(self.edit_point.line < self.lines.len());
558
559 let target_line: isize = self.edit_point.line as isize + adjust;
560
561 if target_line < 0 {
562 self.edit_point.line = 0;
563 self.edit_point.index = UTF8Bytes::zero();
564 if self.selection_origin.is_some() &&
565 (self.selection_direction == SelectionDirection::None ||
566 self.selection_direction == SelectionDirection::Forward)
567 {
568 self.selection_origin = Some(TextPoint {
569 line: 0,
570 index: UTF8Bytes::zero(),
571 });
572 }
573 return;
574 } else if target_line as usize >= self.lines.len() {
575 self.edit_point.line = self.lines.len() - 1;
576 self.edit_point.index = self.current_line_length();
577 if self.selection_origin.is_some() &&
578 (self.selection_direction == SelectionDirection::Backward)
579 {
580 self.selection_origin = Some(self.edit_point);
581 }
582 return;
583 }
584
585 let UTF8Bytes(edit_index) = self.edit_point.index;
586 let col = self.lines[self.edit_point.line][..edit_index]
587 .chars()
588 .count();
589 self.edit_point.line = target_line as usize;
590 self.edit_point.index = len_of_first_n_chars(&self.lines[self.edit_point.line], col);
592 if let Some(origin) = self.selection_origin {
593 if ((self.selection_direction == SelectionDirection::None ||
594 self.selection_direction == SelectionDirection::Forward) &&
595 self.edit_point <= origin) ||
596 (self.selection_direction == SelectionDirection::Backward &&
597 origin <= self.edit_point)
598 {
599 self.selection_origin = Some(self.edit_point);
600 }
601 }
602 self.assert_ok_selection();
603 }
604
605 pub fn adjust_horizontal(
609 &mut self,
610 adjust: UTF8Bytes,
611 direction: Direction,
612 select: Selection,
613 ) {
614 if self.adjust_selection_for_horizontal_change(direction, select) {
615 return;
616 }
617 self.perform_horizontal_adjustment(adjust, direction, select);
618 }
619
620 pub fn adjust_horizontal_by_one(&mut self, direction: Direction, select: Selection) {
624 if self.adjust_selection_for_horizontal_change(direction, select) {
625 return;
626 }
627 let adjust = {
628 let current_line = &self.lines[self.edit_point.line];
629 let UTF8Bytes(current_offset) = self.edit_point.index;
630 let next_ch = match direction {
631 Direction::Forward => current_line[current_offset..].graphemes(true).next(),
632 Direction::Backward => current_line[..current_offset].graphemes(true).next_back(),
633 };
634 match next_ch {
635 Some(c) => UTF8Bytes(c.len()),
636 None => UTF8Bytes::one(), }
638 };
639 self.perform_horizontal_adjustment(adjust, direction, select);
640 }
641
642 fn adjust_selection_for_horizontal_change(
644 &mut self,
645 adjust: Direction,
646 select: Selection,
647 ) -> bool {
648 if select == Selection::Selected {
649 if self.selection_origin.is_none() {
650 self.selection_origin = Some(self.edit_point);
651 }
652 } else if self.has_selection() {
653 self.edit_point = match adjust {
654 Direction::Backward => self.selection_start(),
655 Direction::Forward => self.selection_end(),
656 };
657 self.clear_selection();
658 return true;
659 }
660 false
661 }
662
663 fn update_selection_direction(&mut self) {
668 debug!(
669 "edit_point: {:?}, selection_origin: {:?}",
670 self.edit_point, self.selection_origin
671 );
672 self.selection_direction = if Some(self.edit_point) < self.selection_origin {
673 SelectionDirection::Backward
674 } else {
675 SelectionDirection::Forward
676 }
677 }
678
679 fn perform_horizontal_adjustment(
680 &mut self,
681 adjust: UTF8Bytes,
682 direction: Direction,
683 select: Selection,
684 ) {
685 match direction {
686 Direction::Backward => {
687 let remaining = self.edit_point.index;
688 if adjust > remaining && self.edit_point.line > 0 {
689 let selection_origin_temp = self.selection_origin;
693 self.adjust_vertical(-1, select);
694 self.edit_point.index = self.current_line_length();
695 self.selection_origin = selection_origin_temp;
697 self.adjust_horizontal(
699 adjust.saturating_sub(remaining + UTF8Bytes::one()),
700 direction,
701 select,
702 );
703 } else {
704 self.edit_point.index = remaining.saturating_sub(adjust);
705 }
706 },
707 Direction::Forward => {
708 let remaining = self
709 .current_line_length()
710 .saturating_sub(self.edit_point.index);
711 if adjust > remaining && self.lines.len() > self.edit_point.line + 1 {
712 self.adjust_vertical(1, select);
713 self.edit_point.index = UTF8Bytes::zero();
714 self.adjust_horizontal(
716 adjust.saturating_sub(remaining + UTF8Bytes::one()),
717 direction,
718 select,
719 );
720 } else {
721 self.edit_point.index =
722 min(self.current_line_length(), self.edit_point.index + adjust);
723 }
724 },
725 };
726 self.update_selection_direction();
727 self.assert_ok_selection();
728 }
729
730 pub fn handle_return(&mut self) -> KeyReaction {
732 if !self.multiline {
733 KeyReaction::TriggerDefaultAction
734 } else {
735 self.insert_char('\n');
736 KeyReaction::DispatchInput
737 }
738 }
739
740 pub fn select_all(&mut self) {
742 self.selection_origin = Some(TextPoint {
743 line: 0,
744 index: UTF8Bytes::zero(),
745 });
746 let last_line = self.lines.len() - 1;
747 self.edit_point.line = last_line;
748 self.edit_point.index = self.lines[last_line].len_utf8();
749 self.selection_direction = SelectionDirection::Forward;
750 self.assert_ok_selection();
751 }
752
753 pub fn clear_selection(&mut self) {
755 self.selection_origin = None;
756 self.selection_direction = SelectionDirection::None;
757 }
758
759 pub(crate) fn clear_selection_to_limit(&mut self, direction: Direction) {
761 self.clear_selection();
762 self.adjust_horizontal_to_limit(direction, Selection::NotSelected);
763 }
764
765 pub fn adjust_horizontal_by_word(&mut self, direction: Direction, select: Selection) {
766 if self.adjust_selection_for_horizontal_change(direction, select) {
767 return;
768 }
769 let shift_increment: UTF8Bytes = {
770 let current_index = self.edit_point.index;
771 let current_line = self.edit_point.line;
772 let mut newline_adjustment = UTF8Bytes::zero();
773 let mut shift_temp = UTF8Bytes::zero();
774 match direction {
775 Direction::Backward => {
776 let input: &str;
777 if current_index == UTF8Bytes::zero() && current_line > 0 {
778 input = &self.lines[current_line - 1];
779 newline_adjustment = UTF8Bytes::one();
780 } else {
781 let UTF8Bytes(remaining) = current_index;
782 input = &self.lines[current_line][..remaining];
783 }
784
785 let mut iter = input.split_word_bounds().rev();
786 loop {
787 match iter.next() {
788 None => break,
789 Some(x) => {
790 shift_temp += UTF8Bytes(x.len());
791 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
792 break;
793 }
794 },
795 }
796 }
797 },
798 Direction::Forward => {
799 let input: &str;
800 let remaining = self.current_line_length().saturating_sub(current_index);
801 if remaining == UTF8Bytes::zero() && self.lines.len() > self.edit_point.line + 1
802 {
803 input = &self.lines[current_line + 1];
804 newline_adjustment = UTF8Bytes::one();
805 } else {
806 let UTF8Bytes(current_offset) = current_index;
807 input = &self.lines[current_line][current_offset..];
808 }
809
810 let mut iter = input.split_word_bounds();
811 loop {
812 match iter.next() {
813 None => break,
814 Some(x) => {
815 shift_temp += UTF8Bytes(x.len());
816 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
817 break;
818 }
819 },
820 }
821 }
822 },
823 };
824
825 shift_temp + newline_adjustment
826 };
827
828 self.adjust_horizontal(shift_increment, direction, select);
829 }
830
831 pub fn adjust_horizontal_to_line_end(&mut self, direction: Direction, select: Selection) {
832 if self.adjust_selection_for_horizontal_change(direction, select) {
833 return;
834 }
835 let shift: usize = {
836 let current_line = &self.lines[self.edit_point.line];
837 let UTF8Bytes(current_offset) = self.edit_point.index;
838 match direction {
839 Direction::Backward => current_line[..current_offset].len(),
840 Direction::Forward => current_line[current_offset..].len(),
841 }
842 };
843 self.perform_horizontal_adjustment(UTF8Bytes(shift), direction, select);
844 }
845
846 pub(crate) fn adjust_horizontal_to_limit(&mut self, direction: Direction, select: Selection) {
847 if self.adjust_selection_for_horizontal_change(direction, select) {
848 return;
849 }
850 match direction {
851 Direction::Backward => {
852 self.edit_point.line = 0;
853 self.edit_point.index = UTF8Bytes::zero();
854 },
855 Direction::Forward => {
856 self.edit_point.line = &self.lines.len() - 1;
857 self.edit_point.index = (self.lines[&self.lines.len() - 1]).len_utf8();
858 },
859 }
860 }
861
862 pub(crate) fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
864 let key = event.key();
865 let mods = event.modifiers();
866 self.handle_keydown_aux(key, mods, cfg!(target_os = "macos"))
867 }
868
869 pub fn handle_keydown_aux(
872 &mut self,
873 key: Key,
874 mut mods: Modifiers,
875 macos: bool,
876 ) -> KeyReaction {
877 let maybe_select = if mods.contains(Modifiers::SHIFT) {
878 Selection::Selected
879 } else {
880 Selection::NotSelected
881 };
882 mods.remove(Modifiers::SHIFT);
883 ShortcutMatcher::new(KeyState::Down, key.clone(), mods)
884 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'B', || {
885 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
886 KeyReaction::RedrawSelection
887 })
888 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'F', || {
889 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
890 KeyReaction::RedrawSelection
891 })
892 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'A', || {
893 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
894 KeyReaction::RedrawSelection
895 })
896 .shortcut(Modifiers::CONTROL | Modifiers::ALT, 'E', || {
897 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
898 KeyReaction::RedrawSelection
899 })
900 .optional_shortcut(macos, Modifiers::CONTROL, 'A', || {
901 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
902 KeyReaction::RedrawSelection
903 })
904 .optional_shortcut(macos, Modifiers::CONTROL, 'E', || {
905 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
906 KeyReaction::RedrawSelection
907 })
908 .shortcut(CMD_OR_CONTROL, 'A', || {
909 self.select_all();
910 KeyReaction::RedrawSelection
911 })
912 .shortcut(CMD_OR_CONTROL, 'X', || {
913 if let Some(text) = self.get_selection_text() {
914 self.clipboard_provider.set_text(text);
915 self.delete_char(Direction::Backward);
916 }
917 KeyReaction::DispatchInput
918 })
919 .shortcut(CMD_OR_CONTROL, 'C', || {
920 if let Some(text) = self.get_selection_text() {
922 self.clipboard_provider.set_text(text);
923 }
924 KeyReaction::DispatchInput
925 })
926 .shortcut(CMD_OR_CONTROL, 'V', || {
927 if let Ok(text_content) = self.clipboard_provider.get_text() {
928 self.insert_string(text_content);
929 }
930 KeyReaction::DispatchInput
931 })
932 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Delete), || {
933 if self.delete_char(Direction::Forward) {
934 KeyReaction::DispatchInput
935 } else {
936 KeyReaction::Nothing
937 }
938 })
939 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Backspace), || {
940 if self.delete_char(Direction::Backward) {
941 KeyReaction::DispatchInput
942 } else {
943 KeyReaction::Nothing
944 }
945 })
946 .optional_shortcut(
947 macos,
948 Modifiers::META,
949 Key::Named(NamedKey::ArrowLeft),
950 || {
951 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
952 KeyReaction::RedrawSelection
953 },
954 )
955 .optional_shortcut(
956 macos,
957 Modifiers::META,
958 Key::Named(NamedKey::ArrowRight),
959 || {
960 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
961 KeyReaction::RedrawSelection
962 },
963 )
964 .optional_shortcut(
965 macos,
966 Modifiers::META,
967 Key::Named(NamedKey::ArrowUp),
968 || {
969 self.adjust_horizontal_to_limit(Direction::Backward, maybe_select);
970 KeyReaction::RedrawSelection
971 },
972 )
973 .optional_shortcut(
974 macos,
975 Modifiers::META,
976 Key::Named(NamedKey::ArrowDown),
977 || {
978 self.adjust_horizontal_to_limit(Direction::Forward, maybe_select);
979 KeyReaction::RedrawSelection
980 },
981 )
982 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowLeft), || {
983 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
984 KeyReaction::RedrawSelection
985 })
986 .shortcut(Modifiers::ALT, Key::Named(NamedKey::ArrowRight), || {
987 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
988 KeyReaction::RedrawSelection
989 })
990 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowLeft), || {
991 self.adjust_horizontal_by_one(Direction::Backward, maybe_select);
992 KeyReaction::RedrawSelection
993 })
994 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowRight), || {
995 self.adjust_horizontal_by_one(Direction::Forward, maybe_select);
996 KeyReaction::RedrawSelection
997 })
998 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowUp), || {
999 self.adjust_vertical(-1, maybe_select);
1000 KeyReaction::RedrawSelection
1001 })
1002 .shortcut(Modifiers::empty(), Key::Named(NamedKey::ArrowDown), || {
1003 self.adjust_vertical(1, maybe_select);
1004 KeyReaction::RedrawSelection
1005 })
1006 .shortcut(Modifiers::empty(), Key::Named(NamedKey::Enter), || {
1007 self.handle_return()
1008 })
1009 .optional_shortcut(
1010 macos,
1011 Modifiers::empty(),
1012 Key::Named(NamedKey::Home),
1013 || {
1014 self.edit_point.index = UTF8Bytes::zero();
1015 KeyReaction::RedrawSelection
1016 },
1017 )
1018 .optional_shortcut(macos, Modifiers::empty(), Key::Named(NamedKey::End), || {
1019 self.edit_point.index = self.current_line_length();
1020 self.assert_ok_selection();
1021 KeyReaction::RedrawSelection
1022 })
1023 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageUp), || {
1024 self.adjust_vertical(-28, maybe_select);
1025 KeyReaction::RedrawSelection
1026 })
1027 .shortcut(Modifiers::empty(), Key::Named(NamedKey::PageDown), || {
1028 self.adjust_vertical(28, maybe_select);
1029 KeyReaction::RedrawSelection
1030 })
1031 .otherwise(|| {
1032 if let Key::Character(ref c) = key {
1033 self.insert_string(c.as_str());
1034 return KeyReaction::DispatchInput;
1035 }
1036 if matches!(key, Key::Named(NamedKey::Process)) {
1037 return KeyReaction::DispatchInput;
1038 }
1039 KeyReaction::Nothing
1040 })
1041 .unwrap()
1042 }
1043
1044 pub(crate) fn handle_compositionend(&mut self, event: &CompositionEvent) -> KeyReaction {
1045 self.insert_string(event.data());
1046 KeyReaction::DispatchInput
1047 }
1048
1049 pub(crate) fn handle_compositionupdate(&mut self, event: &CompositionEvent) -> KeyReaction {
1050 let start = self.selection_start_offset().0;
1051 self.insert_string(event.data());
1052 self.set_selection_range(
1053 start as u32,
1054 (start + event.data().len_utf8().0) as u32,
1055 SelectionDirection::Forward,
1056 );
1057 KeyReaction::DispatchInput
1058 }
1059
1060 pub(crate) fn is_empty(&self) -> bool {
1062 self.lines.len() <= 1 && self.lines.first().is_none_or(|line| line.is_empty())
1063 }
1064
1065 pub(crate) fn len_utf8(&self) -> UTF8Bytes {
1067 self.lines
1068 .iter()
1069 .fold(UTF8Bytes::zero(), |m, l| {
1070 m + l.len_utf8() + UTF8Bytes::one() })
1072 .saturating_sub(UTF8Bytes::one())
1073 }
1074
1075 pub(crate) fn utf16_len(&self) -> UTF16CodeUnits {
1077 self.lines
1078 .iter()
1079 .fold(UTF16CodeUnits::zero(), |m, l| {
1080 m + UTF16CodeUnits(l.chars().map(char::len_utf16).sum::<usize>() + 1)
1081 })
1083 .saturating_sub(UTF16CodeUnits::one())
1084 }
1085
1086 pub(crate) fn char_count(&self) -> usize {
1088 self.lines.iter().fold(0, |m, l| {
1089 m + l.chars().count() + 1 }) - 1
1091 }
1092
1093 pub fn get_content(&self) -> DOMString {
1095 let mut content = "".to_owned();
1096 for (i, line) in self.lines.iter().enumerate() {
1097 content.push_str(line);
1098 if i < self.lines.len() - 1 {
1099 content.push('\n');
1100 }
1101 }
1102 DOMString::from(content)
1103 }
1104
1105 pub(crate) fn single_line_content(&self) -> &DOMString {
1107 assert!(!self.multiline);
1108 &self.lines[0]
1109 }
1110
1111 pub fn set_content(&mut self, content: DOMString) {
1114 self.lines = if self.multiline {
1115 content
1117 .replace("\r\n", "\n")
1118 .split(['\n', '\r'])
1119 .map(DOMString::from)
1120 .collect()
1121 } else {
1122 vec![content]
1123 };
1124
1125 self.was_last_change_by_set_content = true;
1126 self.edit_point = self.edit_point.constrain_to(&self.lines);
1127
1128 if let Some(origin) = self.selection_origin {
1129 self.selection_origin = Some(origin.constrain_to(&self.lines));
1130 }
1131 self.assert_ok_selection();
1132 }
1133
1134 fn text_point_to_offset(&self, text_point: &TextPoint) -> UTF8Bytes {
1136 self.lines
1137 .iter()
1138 .enumerate()
1139 .fold(UTF8Bytes::zero(), |acc, (i, val)| {
1140 if i < text_point.line {
1141 acc + val.len_utf8() + UTF8Bytes::one() } else {
1143 acc
1144 }
1145 }) +
1146 text_point.index
1147 }
1148
1149 fn offset_to_text_point(&self, abs_point: UTF8Bytes) -> TextPoint {
1151 let mut index = abs_point;
1152 let mut line = 0;
1153 let last_line_idx = self.lines.len() - 1;
1154 self.lines
1155 .iter()
1156 .enumerate()
1157 .fold(UTF8Bytes::zero(), |acc, (i, val)| {
1158 if i != last_line_idx {
1159 let line_end = val.len_utf8();
1160 let new_acc = acc + line_end + UTF8Bytes::one();
1161 if abs_point >= new_acc && index > line_end {
1162 index = index.saturating_sub(line_end + UTF8Bytes::one());
1163 line += 1;
1164 }
1165 new_acc
1166 } else {
1167 acc
1168 }
1169 });
1170
1171 TextPoint { line, index }
1172 }
1173
1174 pub fn set_selection_range(&mut self, start: u32, end: u32, direction: SelectionDirection) {
1175 let mut start = UTF8Bytes(start as usize);
1176 let mut end = UTF8Bytes(end as usize);
1177 let text_end = self.get_content().len_utf8();
1178
1179 if end > text_end {
1180 end = text_end;
1181 }
1182 if start > end {
1183 start = end;
1184 }
1185
1186 self.selection_direction = direction;
1187
1188 match direction {
1189 SelectionDirection::None | SelectionDirection::Forward => {
1190 self.selection_origin = Some(self.offset_to_text_point(start));
1191 self.edit_point = self.offset_to_text_point(end);
1192 },
1193 SelectionDirection::Backward => {
1194 self.selection_origin = Some(self.offset_to_text_point(end));
1195 self.edit_point = self.offset_to_text_point(start);
1196 },
1197 }
1198 self.assert_ok_selection();
1199 }
1200
1201 pub fn set_edit_point_index(&mut self, index: usize) {
1203 let byte_offset = self.lines[self.edit_point.line]
1204 .graphemes(true)
1205 .take(index)
1206 .fold(UTF8Bytes::zero(), |acc, x| acc + x.len_utf8());
1207 self.edit_point.index = byte_offset;
1208 }
1209
1210 pub(crate) fn handle_clipboard_event(
1218 &mut self,
1219 clipboard_event: &ClipboardEvent,
1220 ) -> ClipboardEventReaction {
1221 let event = clipboard_event.upcast::<Event>();
1222 if !event.IsTrusted() {
1223 return ClipboardEventReaction::empty();
1224 }
1225
1226 if event.DefaultPrevented() {
1229 return ClipboardEventReaction::empty();
1232 }
1233
1234 match event.Type().str() {
1235 "copy" => {
1236 let selection = self.get_selection_text();
1238
1239 if let Some(text) = selection {
1241 self.clipboard_provider.set_text(text);
1242 }
1243
1244 ClipboardEventReaction::FireClipboardChangedEvent
1246 },
1247 "cut" => {
1248 let selection = self.get_selection_text();
1250
1251 let Some(text) = selection else {
1253 return ClipboardEventReaction::empty();
1255 };
1256
1257 self.clipboard_provider.set_text(text);
1259
1260 self.delete_char(Direction::Backward);
1262
1263 ClipboardEventReaction::FireClipboardChangedEvent |
1266 ClipboardEventReaction::QueueInputEvent
1267 },
1268 "paste" => {
1269 let Some(data_transfer) = clipboard_event.get_clipboard_data() else {
1271 return ClipboardEventReaction::empty();
1272 };
1273 let Some(drag_data_store) = data_transfer.data_store() else {
1274 return ClipboardEventReaction::empty();
1275 };
1276
1277 let Some(text_content) =
1287 drag_data_store
1288 .iter_item_list()
1289 .find_map(|item| match item {
1290 Kind::Text { data, .. } => Some(data.to_string()),
1291 _ => None,
1292 })
1293 else {
1294 return ClipboardEventReaction::empty();
1295 };
1296 if text_content.is_empty() {
1297 return ClipboardEventReaction::empty();
1298 }
1299
1300 self.insert_string(text_content);
1301
1302 ClipboardEventReaction::QueueInputEvent
1305 },
1306 _ => ClipboardEventReaction::empty(),
1307 }
1308 }
1309}