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