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