1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 //! Common handling of keyboard input and state management for text input controls
6 
7 use clipboard_provider::ClipboardProvider;
8 use dom::bindings::str::DOMString;
9 use dom::keyboardevent::KeyboardEvent;
10 use msg::constellation_msg::{Key, KeyModifiers};
11 use std::borrow::ToOwned;
12 use std::cmp::{max, min};
13 use std::default::Default;
14 use std::ops::Range;
15 use std::usize;
16 use unicode_segmentation::UnicodeSegmentation;
17 
18 #[derive(Clone, Copy, PartialEq)]
19 pub enum Selection {
20     Selected,
21     NotSelected
22 }
23 
24 #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
25 pub enum SelectionDirection {
26     Forward,
27     Backward,
28     None,
29 }
30 
31 impl From<DOMString> for SelectionDirection {
from(direction: DOMString) -> SelectionDirection32     fn from(direction: DOMString) -> SelectionDirection {
33         match direction.as_ref() {
34             "forward" => SelectionDirection::Forward,
35             "backward" => SelectionDirection::Backward,
36             _ => SelectionDirection::None,
37         }
38     }
39 }
40 
41 impl From<SelectionDirection> for DOMString {
from(direction: SelectionDirection) -> DOMString42     fn from(direction: SelectionDirection) -> DOMString {
43         match direction {
44             SelectionDirection::Forward => DOMString::from("forward"),
45             SelectionDirection::Backward => DOMString::from("backward"),
46             SelectionDirection::None => DOMString::from("none"),
47         }
48     }
49 }
50 
51 #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
52 pub struct TextPoint {
53     /// 0-based line number
54     pub line: usize,
55     /// 0-based column number in UTF-8 bytes
56     pub index: usize,
57 }
58 
59 impl TextPoint {
60     /// Returns a TextPoint constrained to be a valid location within lines
constrain_to(&self, lines: &[DOMString]) -> TextPoint61     fn constrain_to(&self, lines: &[DOMString]) -> TextPoint {
62         let line = min(self.line, lines.len() - 1);
63 
64         TextPoint {
65             line,
66             index: min(self.index, lines[line].len()),
67         }
68     }
69 }
70 
71 #[derive(Clone, Copy, PartialEq)]
72 pub struct SelectionState {
73     start: TextPoint,
74     end: TextPoint,
75     direction: SelectionDirection,
76 }
77 
78 /// Encapsulated state for handling keyboard input in a single or multiline text input control.
79 #[derive(JSTraceable, MallocSizeOf)]
80 pub struct TextInput<T: ClipboardProvider> {
81     /// Current text input content, split across lines without trailing '\n'
82     lines: Vec<DOMString>,
83 
84     /// Current cursor input point
85     edit_point: TextPoint,
86 
87     /// The current selection goes from the selection_origin until the edit_point. Note that the
88     /// selection_origin may be after the edit_point, in the case of a backward selection.
89     selection_origin: Option<TextPoint>,
90     selection_direction: SelectionDirection,
91 
92     /// Is this a multiline input?
93     multiline: bool,
94 
95     #[ignore_malloc_size_of = "Can't easily measure this generic type"]
96     clipboard_provider: T,
97 
98     /// The maximum number of UTF-16 code units this text input is allowed to hold.
99     ///
100     /// <https://html.spec.whatwg.org/multipage/#attr-fe-maxlength>
101     max_length: Option<usize>,
102     min_length: Option<usize>,
103 }
104 
105 /// Resulting action to be taken by the owner of a text input that is handling an event.
106 pub enum KeyReaction {
107     TriggerDefaultAction,
108     DispatchInput,
109     RedrawSelection,
110     Nothing,
111 }
112 
113 impl Default for TextPoint {
default() -> TextPoint114     fn default() -> TextPoint {
115         TextPoint {
116             line: 0,
117             index: 0,
118         }
119     }
120 }
121 
122 /// Control whether this control should allow multiple lines.
123 #[derive(Eq, PartialEq)]
124 pub enum Lines {
125     Single,
126     Multiple,
127 }
128 
129 /// The direction in which to delete a character.
130 #[derive(Clone, Copy, Eq, PartialEq)]
131 pub enum Direction {
132     Forward,
133     Backward
134 }
135 
136 
137 /// Was the keyboard event accompanied by the standard control modifier,
138 /// i.e. cmd on Mac OS or ctrl on other platforms.
139 #[cfg(target_os = "macos")]
is_control_key(mods: KeyModifiers) -> bool140 fn is_control_key(mods: KeyModifiers) -> bool {
141     mods.contains(KeyModifiers::SUPER) && !mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT)
142 }
143 
144 #[cfg(not(target_os = "macos"))]
is_control_key(mods: KeyModifiers) -> bool145 fn is_control_key(mods: KeyModifiers) -> bool {
146     mods.contains(KeyModifiers::CONTROL) && !mods.contains(KeyModifiers::SUPER | KeyModifiers::ALT)
147 }
148 
149 /// The length in bytes of the first n characters in a UTF-8 string.
150 ///
151 /// If the string has fewer than n characters, returns the length of the whole string.
len_of_first_n_chars(text: &str, n: usize) -> usize152 fn len_of_first_n_chars(text: &str, n: usize) -> usize {
153     match text.char_indices().take(n).last() {
154         Some((index, ch)) => index + ch.len_utf8(),
155         None => 0
156     }
157 }
158 
159 /// The length in bytes of the first n code units a string when encoded in UTF-16.
160 ///
161 /// If the string is fewer than n code units, returns the length of the whole string.
len_of_first_n_code_units(text: &str, n: usize) -> usize162 fn len_of_first_n_code_units(text: &str, n: usize) -> usize {
163     let mut utf8_len = 0;
164     let mut utf16_len = 0;
165     for c in text.chars() {
166         utf16_len += c.len_utf16();
167         if utf16_len > n {
168             break;
169         }
170         utf8_len += c.len_utf8();
171     }
172     utf8_len
173 }
174 
175 impl<T: ClipboardProvider> TextInput<T> {
176     /// Instantiate a new text input control
new(lines: Lines, initial: DOMString, clipboard_provider: T, max_length: Option<usize>, min_length: Option<usize>, selection_direction: SelectionDirection) -> TextInput<T>177     pub fn new(lines: Lines, initial: DOMString,
178                clipboard_provider: T, max_length: Option<usize>,
179                min_length: Option<usize>,
180                selection_direction: SelectionDirection) -> TextInput<T> {
181         let mut i = TextInput {
182             lines: vec!(),
183             edit_point: Default::default(),
184             selection_origin: None,
185             multiline: lines == Lines::Multiple,
186             clipboard_provider: clipboard_provider,
187             max_length: max_length,
188             min_length: min_length,
189             selection_direction: selection_direction,
190         };
191         i.set_content(initial);
192         i
193     }
194 
edit_point(&self) -> TextPoint195     pub fn edit_point(&self) -> TextPoint {
196         self.edit_point
197     }
198 
selection_origin(&self) -> Option<TextPoint>199     pub fn selection_origin(&self) -> Option<TextPoint> {
200         self.selection_origin
201     }
202 
203     /// The selection origin, or the edit point if there is no selection. Note that the selection
204     /// origin may be after the edit point, in the case of a backward selection.
selection_origin_or_edit_point(&self) -> TextPoint205     pub fn selection_origin_or_edit_point(&self) -> TextPoint {
206         self.selection_origin.unwrap_or(self.edit_point)
207     }
208 
selection_direction(&self) -> SelectionDirection209     pub fn selection_direction(&self) -> SelectionDirection {
210         self.selection_direction
211     }
212 
set_max_length(&mut self, length: Option<usize>)213     pub fn set_max_length(&mut self, length: Option<usize>) {
214         self.max_length = length;
215     }
216 
set_min_length(&mut self, length: Option<usize>)217     pub fn set_min_length(&mut self, length: Option<usize>) {
218         self.min_length = length;
219     }
220 
221     /// Remove a character at the current editing point
delete_char(&mut self, dir: Direction)222     pub fn delete_char(&mut self, dir: Direction) {
223         if self.selection_origin.is_none() || self.selection_origin == Some(self.edit_point) {
224             self.adjust_horizontal_by_one(dir, Selection::Selected);
225         }
226         self.replace_selection(DOMString::new());
227     }
228 
229     /// Insert a character at the current editing point
insert_char(&mut self, ch: char)230     pub fn insert_char(&mut self, ch: char) {
231         self.insert_string(ch.to_string());
232     }
233 
234     /// Insert a string at the current editing point
insert_string<S: Into<String>>(&mut self, s: S)235     pub fn insert_string<S: Into<String>>(&mut self, s: S) {
236         if self.selection_origin.is_none() {
237             self.selection_origin = Some(self.edit_point);
238         }
239         self.replace_selection(DOMString::from(s.into()));
240     }
241 
242     /// The start of the selection (or the edit point, if there is no selection). Always less than
243     /// or equal to selection_end(), regardless of the selection direction.
selection_start(&self) -> TextPoint244     pub fn selection_start(&self) -> TextPoint {
245         match self.selection_direction {
246             SelectionDirection::None | SelectionDirection::Forward => self.selection_origin_or_edit_point(),
247             SelectionDirection::Backward => self.edit_point,
248         }
249     }
250 
251     /// The UTF-8 byte offset of the selection_start()
selection_start_offset(&self) -> usize252     pub fn selection_start_offset(&self) -> usize {
253         self.text_point_to_offset(&self.selection_start())
254     }
255 
256     /// The end of the selection (or the edit point, if there is no selection). Always greater
257     /// than or equal to selection_start(), regardless of the selection direction.
selection_end(&self) -> TextPoint258     pub fn selection_end(&self) -> TextPoint {
259         match self.selection_direction {
260             SelectionDirection::None | SelectionDirection::Forward => self.edit_point,
261             SelectionDirection::Backward => self.selection_origin_or_edit_point(),
262         }
263     }
264 
265     /// The UTF-8 byte offset of the selection_end()
selection_end_offset(&self) -> usize266     pub fn selection_end_offset(&self) -> usize {
267         self.text_point_to_offset(&self.selection_end())
268     }
269 
270     /// Whether or not there is an active selection (the selection may be zero-length)
271     #[inline]
has_selection(&self) -> bool272     pub fn has_selection(&self) -> bool {
273         self.selection_origin.is_some()
274     }
275 
276     /// Returns a tuple of (start, end) giving the bounds of the current selection. start is always
277     /// less than or equal to end.
sorted_selection_bounds(&self) -> (TextPoint, TextPoint)278     pub fn sorted_selection_bounds(&self) -> (TextPoint, TextPoint) {
279         (self.selection_start(), self.selection_end())
280     }
281 
282     /// Return the selection range as UTF-8 byte offsets from the start of the content.
283     ///
284     /// If there is no selection, returns an empty range at the edit point.
sorted_selection_offsets_range(&self) -> Range<usize>285     pub fn sorted_selection_offsets_range(&self) -> Range<usize> {
286         self.selection_start_offset() .. self.selection_end_offset()
287     }
288 
289     /// The state of the current selection. Can be used to compare whether selection state has changed.
selection_state(&self) -> SelectionState290     pub fn selection_state(&self) -> SelectionState {
291         SelectionState {
292             start: self.selection_start(),
293             end: self.selection_end(),
294             direction: self.selection_direction,
295         }
296     }
297 
298     // Check that the selection is valid.
assert_ok_selection(&self)299     fn assert_ok_selection(&self) {
300         if let Some(begin) = self.selection_origin {
301             debug_assert!(begin.line < self.lines.len());
302             debug_assert!(begin.index <= self.lines[begin.line].len());
303 
304             match self.selection_direction {
305                 SelectionDirection::None | SelectionDirection::Forward => {
306                     debug_assert!(begin <= self.edit_point)
307                 },
308 
309                 SelectionDirection::Backward => {
310                     debug_assert!(self.edit_point <= begin)
311                 },
312             }
313         }
314 
315         debug_assert!(self.edit_point.line < self.lines.len());
316         debug_assert!(self.edit_point.index <= self.lines[self.edit_point.line].len());
317     }
318 
get_selection_text(&self) -> Option<String>319     pub fn get_selection_text(&self) -> Option<String> {
320         let text = self.fold_selection_slices(String::new(), |s, slice| s.push_str(slice));
321         if text.is_empty() {
322             return None
323         }
324         Some(text)
325     }
326 
327     /// The length of the selected text in UTF-16 code units.
selection_utf16_len(&self) -> usize328     fn selection_utf16_len(&self) -> usize {
329         self.fold_selection_slices(0usize,
330             |len, slice| *len += slice.chars().map(char::len_utf16).sum::<usize>())
331     }
332 
333     /// Run the callback on a series of slices that, concatenated, make up the selected text.
334     ///
335     /// The accumulator `acc` can be mutated by the callback, and will be returned at the end.
fold_selection_slices<B, F: FnMut(&mut B, &str)>(&self, mut acc: B, mut f: F) -> B336     fn fold_selection_slices<B, F: FnMut(&mut B, &str)>(&self, mut acc: B, mut f: F) -> B {
337         if self.has_selection() {
338             let (start, end) = self.sorted_selection_bounds();
339 
340             if start.line == end.line {
341                 f(&mut acc, &self.lines[start.line][start.index..end.index])
342             } else {
343                 f(&mut acc, &self.lines[start.line][start.index..]);
344                 for line in &self.lines[start.line + 1 .. end.line] {
345                     f(&mut acc, "\n");
346                     f(&mut acc, line);
347                 }
348                 f(&mut acc, "\n");
349                 f(&mut acc, &self.lines[end.line][..end.index])
350             }
351         }
352 
353         acc
354     }
355 
replace_selection(&mut self, insert: DOMString)356     pub fn replace_selection(&mut self, insert: DOMString) {
357         if !self.has_selection() {
358             return
359         }
360 
361         let (start, end) = self.sorted_selection_bounds();
362 
363         let allowed_to_insert_count = if let Some(max_length) = self.max_length {
364             let len_after_selection_replaced = self.utf16_len() - self.selection_utf16_len();
365             if len_after_selection_replaced >= max_length {
366                 // If, after deleting the selection, the len is still greater than the max
367                 // length, then don't delete/insert anything
368                 return
369             }
370 
371             max_length - len_after_selection_replaced
372         } else {
373             usize::MAX
374         };
375 
376         let last_char_index = len_of_first_n_code_units(&*insert, allowed_to_insert_count);
377         let chars_to_insert = &insert[..last_char_index];
378 
379         self.clear_selection();
380 
381         let new_lines = {
382             let prefix = &self.lines[start.line][..start.index];
383             let suffix = &self.lines[end.line][end.index..];
384             let lines_prefix = &self.lines[..start.line];
385             let lines_suffix = &self.lines[end.line + 1..];
386 
387             let mut insert_lines = if self.multiline {
388                 chars_to_insert.split('\n').map(|s| DOMString::from(s)).collect()
389             } else {
390                 vec!(DOMString::from(chars_to_insert))
391             };
392 
393             // FIXME(ajeffrey): effecient append for DOMStrings
394             let mut new_line = prefix.to_owned();
395 
396             new_line.push_str(&insert_lines[0]);
397             insert_lines[0] = DOMString::from(new_line);
398 
399             let last_insert_lines_index = insert_lines.len() - 1;
400             self.edit_point.index = insert_lines[last_insert_lines_index].len();
401             self.edit_point.line = start.line + last_insert_lines_index;
402 
403             // FIXME(ajeffrey): effecient append for DOMStrings
404             insert_lines[last_insert_lines_index].push_str(suffix);
405 
406             let mut new_lines = vec!();
407             new_lines.extend_from_slice(lines_prefix);
408             new_lines.extend_from_slice(&insert_lines);
409             new_lines.extend_from_slice(lines_suffix);
410             new_lines
411         };
412 
413         self.lines = new_lines;
414         self.assert_ok_selection();
415     }
416 
417     /// Return the length in UTF-8 bytes of the current line under the editing point.
current_line_length(&self) -> usize418     pub fn current_line_length(&self) -> usize {
419         self.lines[self.edit_point.line].len()
420     }
421 
422     /// Adjust the editing point position by a given of lines. The resulting column is
423     /// as close to the original column position as possible.
adjust_vertical(&mut self, adjust: isize, select: Selection)424     pub fn adjust_vertical(&mut self, adjust: isize, select: Selection) {
425         if !self.multiline {
426             return;
427         }
428 
429         if select == Selection::Selected {
430             if self.selection_origin.is_none() {
431                 self.selection_origin = Some(self.edit_point);
432             }
433         } else {
434             self.clear_selection();
435         }
436 
437         assert!(self.edit_point.line < self.lines.len());
438 
439         let target_line: isize = self.edit_point.line as isize + adjust;
440 
441         if target_line < 0 {
442             self.edit_point.index = 0;
443             self.edit_point.line = 0;
444             return;
445         } else if target_line as usize >= self.lines.len() {
446             self.edit_point.line = self.lines.len() - 1;
447             self.edit_point.index = self.current_line_length();
448             return;
449         }
450 
451         let col = self.lines[self.edit_point.line][..self.edit_point.index].chars().count();
452         self.edit_point.line = target_line as usize;
453         self.edit_point.index = len_of_first_n_chars(&self.lines[self.edit_point.line], col);
454         self.assert_ok_selection();
455     }
456 
457     /// Adjust the editing point position by a given number of bytes. If the adjustment
458     /// requested is larger than is available in the current line, the editing point is
459     /// adjusted vertically and the process repeats with the remaining adjustment requested.
adjust_horizontal(&mut self, adjust: isize, select: Selection)460     pub fn adjust_horizontal(&mut self, adjust: isize, select: Selection) {
461         let direction = if adjust >= 0 { Direction::Forward } else { Direction::Backward };
462         if self.adjust_selection_for_horizontal_change(direction, select) {
463             return
464         }
465         self.perform_horizontal_adjustment(adjust, select);
466     }
467 
adjust_horizontal_by_one(&mut self, direction: Direction, select: Selection)468     pub fn adjust_horizontal_by_one(&mut self, direction: Direction, select: Selection) {
469         if self.adjust_selection_for_horizontal_change(direction, select) {
470             return
471         }
472         let adjust = {
473             let current_line = &self.lines[self.edit_point.line];
474             match direction {
475                 Direction::Forward => {
476                     match current_line[self.edit_point.index..].graphemes(true).next() {
477                         Some(c) => c.len() as isize,
478                         None => 1,  // Going to the next line is a "one byte" offset
479                     }
480                 }
481                 Direction::Backward => {
482                     match current_line[..self.edit_point.index].graphemes(true).next_back() {
483                         Some(c) => -(c.len() as isize),
484                         None => -1,  // Going to the previous line is a "one byte" offset
485                     }
486                 }
487             }
488         };
489         self.perform_horizontal_adjustment(adjust, select);
490     }
491 
492     /// Return whether to cancel the caret move
adjust_selection_for_horizontal_change(&mut self, adjust: Direction, select: Selection) -> bool493     fn adjust_selection_for_horizontal_change(&mut self, adjust: Direction, select: Selection)
494                                               -> bool {
495         if select == Selection::Selected {
496             if self.selection_origin.is_none() {
497                 self.selection_origin = Some(self.edit_point);
498             }
499 
500             self.selection_direction = match adjust {
501                 Direction::Backward => SelectionDirection::Backward,
502                 Direction::Forward => SelectionDirection::Forward,
503             };
504         } else {
505             if self.has_selection() {
506                 self.edit_point = match adjust {
507                     Direction::Backward => self.selection_start(),
508                     Direction::Forward => self.selection_end(),
509                 };
510                 self.clear_selection();
511                 return true
512             }
513         }
514         false
515     }
516 
perform_horizontal_adjustment(&mut self, adjust: isize, select: Selection)517     fn perform_horizontal_adjustment(&mut self, adjust: isize, select: Selection) {
518         if adjust < 0 {
519             let remaining = self.edit_point.index;
520             if adjust.abs() as usize > remaining && self.edit_point.line > 0 {
521                 self.adjust_vertical(-1, select);
522                 self.edit_point.index = self.current_line_length();
523                 self.adjust_horizontal(adjust + remaining as isize + 1, select);
524             } else {
525                 self.edit_point.index = max(0, self.edit_point.index as isize + adjust) as usize;
526             }
527         } else {
528             let remaining = self.current_line_length() - self.edit_point.index;
529             if adjust as usize > remaining && self.lines.len() > self.edit_point.line + 1 {
530                 self.adjust_vertical(1, select);
531                 self.edit_point.index = 0;
532                 // one shift is consumed by the change of line, hence the -1
533                 self.adjust_horizontal(adjust - remaining as isize - 1, select);
534             } else {
535                 self.edit_point.index = min(self.current_line_length(),
536                                             self.edit_point.index + adjust as usize);
537             }
538         }
539         self.assert_ok_selection();
540     }
541 
542     /// Deal with a newline input.
handle_return(&mut self) -> KeyReaction543     pub fn handle_return(&mut self) -> KeyReaction {
544         if !self.multiline {
545             KeyReaction::TriggerDefaultAction
546         } else {
547             self.insert_char('\n');
548             KeyReaction::DispatchInput
549         }
550     }
551 
552     /// Select all text in the input control.
select_all(&mut self)553     pub fn select_all(&mut self) {
554         self.selection_origin = Some(TextPoint {
555             line: 0,
556             index: 0,
557         });
558         let last_line = self.lines.len() - 1;
559         self.edit_point.line = last_line;
560         self.edit_point.index = self.lines[last_line].len();
561         self.assert_ok_selection();
562     }
563 
564     /// Remove the current selection.
clear_selection(&mut self)565     pub fn clear_selection(&mut self) {
566         self.selection_origin = None;
567         self.selection_direction = SelectionDirection::None;
568     }
569 
570     /// Remove the current selection and set the edit point to the end of the content.
clear_selection_to_limit(&mut self, direction: Direction)571     pub fn clear_selection_to_limit(&mut self, direction: Direction) {
572         self.clear_selection();
573         self.adjust_horizontal_to_limit(direction, Selection::NotSelected);
574     }
575 
adjust_horizontal_by_word(&mut self, direction: Direction, select: Selection)576     pub fn adjust_horizontal_by_word(&mut self, direction: Direction, select: Selection) {
577         if self.adjust_selection_for_horizontal_change(direction, select) {
578             return
579         }
580         let shift_increment: isize =  {
581             let input: &str;
582             match direction {
583                 Direction::Backward => {
584                     let remaining = self.edit_point.index;
585                     let current_line = self.edit_point.line;
586                     let mut newline_adjustment = 0;
587                     if remaining == 0 && current_line > 0 {
588                         input = &self
589                             .lines[current_line-1];
590                         newline_adjustment = 1;
591                     } else {
592                         input = &self
593                             .lines[current_line]
594                             [..remaining];
595                     }
596 
597                     let mut iter = input.split_word_bounds().rev();
598                     let mut shift_temp: isize = 0;
599                     loop {
600                         match iter.next() {
601                             None => break,
602                             Some(x) => {
603                                 shift_temp += - (x.len() as isize);
604                                 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
605                                     break;
606                                 }
607                             }
608                         }
609                     }
610                     shift_temp - newline_adjustment
611                 }
612                 Direction::Forward => {
613                     let remaining = self.current_line_length() - self.edit_point.index;
614                     let current_line = self.edit_point.line;
615                     let mut newline_adjustment = 0;
616                     if remaining == 0 && self.lines.len() > self.edit_point.line + 1 {
617                         input = &self
618                             .lines[current_line + 1];
619                         newline_adjustment = 1;
620                     } else {
621                         input = &self
622                             .lines[current_line]
623                             [self.edit_point.index..];
624                     }
625 
626                     let mut iter = input.split_word_bounds();
627                     let mut shift_temp: isize = 0;
628                     loop {
629                         match iter.next() {
630                             None => break,
631                             Some(x) => {
632                                 shift_temp += x.len() as isize;
633                                 if x.chars().any(|x| x.is_alphabetic() || x.is_numeric()) {
634                                     break;
635                                 }
636                             }
637                         }
638                     }
639                     shift_temp + newline_adjustment
640                 }
641             }
642         };
643 
644         self.adjust_horizontal(shift_increment, select);
645     }
646 
adjust_horizontal_to_line_end(&mut self, direction: Direction, select: Selection)647     pub fn adjust_horizontal_to_line_end(&mut self, direction: Direction, select: Selection) {
648         if self.adjust_selection_for_horizontal_change(direction, select) {
649             return
650         }
651         let shift: isize = {
652             let current_line = &self.lines[self.edit_point.line];
653             match direction {
654                 Direction::Backward => {
655                     - (current_line[..self.edit_point.index].len() as isize)
656                 },
657                 Direction::Forward => {
658                     current_line[self.edit_point.index..].len() as isize
659                 }
660             }
661         };
662         self.perform_horizontal_adjustment(shift, select);
663     }
664 
adjust_horizontal_to_limit(&mut self, direction: Direction, select: Selection)665     pub fn adjust_horizontal_to_limit(&mut self, direction: Direction, select: Selection) {
666         if self.adjust_selection_for_horizontal_change(direction, select) {
667             return
668         }
669         match direction {
670             Direction::Backward => {
671                 self.edit_point.line = 0;
672                 self.edit_point.index = 0;
673             },
674             Direction::Forward => {
675                 self.edit_point.line = &self.lines.len() - 1;
676                 self.edit_point.index = (&self.lines[&self.lines.len() - 1]).len();
677             }
678         }
679     }
680 
681     /// Process a given `KeyboardEvent` and return an action for the caller to execute.
handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction682     pub fn handle_keydown(&mut self, event: &KeyboardEvent) -> KeyReaction {
683         if let Some(key) = event.get_key() {
684             self.handle_keydown_aux(event.printable(), key, event.get_key_modifiers())
685         } else {
686             KeyReaction::Nothing
687         }
688     }
689 
handle_keydown_aux(&mut self, printable: Option<char>, key: Key, mods: KeyModifiers) -> KeyReaction690     pub fn handle_keydown_aux(&mut self,
691                               printable: Option<char>,
692                               key: Key,
693                               mods: KeyModifiers) -> KeyReaction {
694         let maybe_select = if mods.contains(KeyModifiers::SHIFT) {
695                 Selection::Selected
696             } else {
697                 Selection::NotSelected
698         };
699 
700         match (printable, key) {
701             (_, Key::B) if mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT) => {
702                 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
703                 KeyReaction::RedrawSelection
704             },
705             (_, Key::F) if mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT) => {
706                 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
707                 KeyReaction::RedrawSelection
708             },
709             (_, Key::A) if mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT) => {
710                 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
711                 KeyReaction::RedrawSelection
712             },
713             (_, Key::E) if mods.contains(KeyModifiers::CONTROL | KeyModifiers::ALT) => {
714                 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
715                 KeyReaction::RedrawSelection
716             },
717             #[cfg(target_os = "macos")]
718             (None, Key::A) if mods == KeyModifiers::CONTROL => {
719                 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
720                 KeyReaction::RedrawSelection
721             },
722             #[cfg(target_os = "macos")]
723             (None, Key::E) if mods == KeyModifiers::CONTROL => {
724                 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
725                 KeyReaction::RedrawSelection
726             },
727             (_, Key::A) if is_control_key(mods) => {
728                 self.select_all();
729                 KeyReaction::RedrawSelection
730             },
731             (_, Key::C) if is_control_key(mods) => {
732                 if let Some(text) = self.get_selection_text() {
733                     self.clipboard_provider.set_clipboard_contents(text);
734                 }
735                 KeyReaction::DispatchInput
736             },
737             (_, Key::V) if is_control_key(mods) => {
738                 let contents = self.clipboard_provider.clipboard_contents();
739                 self.insert_string(contents);
740                 KeyReaction::DispatchInput
741             },
742             (Some(c), _) => {
743                 self.insert_char(c);
744                 KeyReaction::DispatchInput
745             },
746             (None, Key::Delete) => {
747                 self.delete_char(Direction::Forward);
748                 KeyReaction::DispatchInput
749             },
750             (None, Key::Backspace) => {
751                 self.delete_char(Direction::Backward);
752                 KeyReaction::DispatchInput
753             },
754             #[cfg(target_os = "macos")]
755             (None, Key::Left) if mods.contains(KeyModifiers::SUPER) => {
756                 self.adjust_horizontal_to_line_end(Direction::Backward, maybe_select);
757                 KeyReaction::RedrawSelection
758             },
759             #[cfg(target_os = "macos")]
760             (None, Key::Right) if mods.contains(KeyModifiers::SUPER) => {
761                 self.adjust_horizontal_to_line_end(Direction::Forward, maybe_select);
762                 KeyReaction::RedrawSelection
763             },
764             #[cfg(target_os = "macos")]
765             (None, Key::Up) if mods.contains(KeyModifiers::SUPER) => {
766                 self.adjust_horizontal_to_limit(Direction::Backward, maybe_select);
767                 KeyReaction::RedrawSelection
768             },
769             #[cfg(target_os = "macos")]
770             (None, Key::Down) if mods.contains(KeyModifiers::SUPER) => {
771                 self.adjust_horizontal_to_limit(Direction::Forward, maybe_select);
772                 KeyReaction::RedrawSelection
773             },
774             (None, Key::Left) if mods.contains(KeyModifiers::ALT) => {
775                 self.adjust_horizontal_by_word(Direction::Backward, maybe_select);
776                 KeyReaction::RedrawSelection
777             },
778             (None, Key::Right) if mods.contains(KeyModifiers::ALT) => {
779                 self.adjust_horizontal_by_word(Direction::Forward, maybe_select);
780                 KeyReaction::RedrawSelection
781             },
782             (None, Key::Left) => {
783                 self.adjust_horizontal_by_one(Direction::Backward, maybe_select);
784                 KeyReaction::RedrawSelection
785             },
786             (None, Key::Right) => {
787                 self.adjust_horizontal_by_one(Direction::Forward, maybe_select);
788                 KeyReaction::RedrawSelection
789             },
790             (None, Key::Up) => {
791                 self.adjust_vertical(-1, maybe_select);
792                 KeyReaction::RedrawSelection
793             },
794             (None, Key::Down) => {
795                 self.adjust_vertical(1, maybe_select);
796                 KeyReaction::RedrawSelection
797             },
798             (None, Key::Enter) | (None, Key::KpEnter) => self.handle_return(),
799             (None, Key::Home) => {
800                 #[cfg(not(target_os = "macos"))]
801                 {
802                     self.edit_point.index = 0;
803                 }
804                 KeyReaction::RedrawSelection
805             },
806             (None, Key::End) => {
807                 #[cfg(not(target_os = "macos"))]
808                 {
809                     self.edit_point.index = self.current_line_length();
810                     self.assert_ok_selection();
811                 }
812                 KeyReaction::RedrawSelection
813             },
814             (None, Key::PageUp) => {
815                 self.adjust_vertical(-28, maybe_select);
816                 KeyReaction::RedrawSelection
817             },
818             (None, Key::PageDown) => {
819                 self.adjust_vertical(28, maybe_select);
820                 KeyReaction::RedrawSelection
821             },
822             _ => KeyReaction::Nothing,
823         }
824     }
825 
826     /// Whether the content is empty.
is_empty(&self) -> bool827     pub fn is_empty(&self) -> bool {
828         self.lines.len() <= 1 && self.lines.get(0).map_or(true, |line| line.is_empty())
829     }
830 
831     /// The length of the content in bytes.
len(&self) -> usize832     pub fn len(&self) -> usize {
833         self.lines.iter().fold(0, |m, l| {
834             m + l.len() + 1 // + 1 for the '\n'
835         }) - 1
836     }
837 
838     /// The length of the content in bytes.
utf16_len(&self) -> usize839     pub fn utf16_len(&self) -> usize {
840         self.lines.iter().fold(0, |m, l| {
841             m + l.chars().map(char::len_utf16).sum::<usize>() + 1 // + 1 for the '\n'
842         }) - 1
843     }
844 
845     /// The length of the content in chars.
char_count(&self) -> usize846     pub fn char_count(&self) -> usize {
847         self.lines.iter().fold(0, |m, l| {
848             m + l.chars().count() + 1 // + 1 for the '\n'
849         }) - 1
850     }
851 
852     /// Get the current contents of the text input. Multiple lines are joined by \n.
get_content(&self) -> DOMString853     pub fn get_content(&self) -> DOMString {
854         let mut content = "".to_owned();
855         for (i, line) in self.lines.iter().enumerate() {
856             content.push_str(&line);
857             if i < self.lines.len() - 1 {
858                 content.push('\n');
859             }
860         }
861         DOMString::from(content)
862     }
863 
864     /// Get a reference to the contents of a single-line text input. Panics if self is a multiline input.
single_line_content(&self) -> &DOMString865     pub fn single_line_content(&self) -> &DOMString {
866         assert!(!self.multiline);
867         &self.lines[0]
868     }
869 
870     /// Set the current contents of the text input. If this is control supports multiple lines,
871     /// any \n encountered will be stripped and force a new logical line.
set_content(&mut self, content: DOMString)872     pub fn set_content(&mut self, content: DOMString) {
873         self.lines = if self.multiline {
874             // https://html.spec.whatwg.org/multipage/#textarea-line-break-normalisation-transformation
875             content.replace("\r\n", "\n")
876                    .split(|c| c == '\n' || c == '\r')
877                    .map(DOMString::from)
878                    .collect()
879         } else {
880             vec!(content)
881         };
882 
883         self.edit_point = self.edit_point.constrain_to(&self.lines);
884 
885         if let Some(origin) = self.selection_origin {
886             self.selection_origin = Some(origin.constrain_to(&self.lines));
887         }
888         self.assert_ok_selection();
889     }
890 
891     /// Convert a TextPoint into a byte offset from the start of the content.
text_point_to_offset(&self, text_point: &TextPoint) -> usize892     fn text_point_to_offset(&self, text_point: &TextPoint) -> usize {
893         self.lines.iter().enumerate().fold(0, |acc, (i, val)| {
894             if i < text_point.line {
895                 acc + val.len() + 1 // +1 for the \n
896             } else {
897                 acc
898             }
899         }) + text_point.index
900     }
901 
902     /// Convert a byte offset from the start of the content into a TextPoint.
offset_to_text_point(&self, abs_point: usize) -> TextPoint903     fn offset_to_text_point(&self, abs_point: usize) -> TextPoint {
904         let mut index = abs_point;
905         let mut line = 0;
906         let last_line_idx = self.lines.len() - 1;
907         self.lines.iter().enumerate().fold(0, |acc, (i, val)| {
908             if i != last_line_idx {
909                 let line_end = val.len();
910                 let new_acc = acc + line_end + 1;
911                 if abs_point >= new_acc && index > line_end {
912                     index -= line_end + 1;
913                     line += 1;
914                 }
915                 new_acc
916             } else {
917                 acc
918             }
919         });
920 
921         TextPoint {
922             line: line, index: index
923         }
924     }
925 
set_selection_range(&mut self, start: u32, end: u32, direction: SelectionDirection)926     pub fn set_selection_range(&mut self, start: u32, end: u32, direction: SelectionDirection) {
927         let mut start = start as usize;
928         let mut end = end as usize;
929         let text_end = self.get_content().len();
930 
931         if end > text_end {
932             end = text_end;
933         }
934         if start > end {
935             start = end;
936         }
937 
938         self.selection_direction = direction;
939 
940         match direction {
941             SelectionDirection::None |
942             SelectionDirection::Forward => {
943                 self.selection_origin = Some(self.offset_to_text_point(start));
944                 self.edit_point = self.offset_to_text_point(end);
945             },
946             SelectionDirection::Backward => {
947                 self.selection_origin = Some(self.offset_to_text_point(end));
948                 self.edit_point = self.offset_to_text_point(start);
949             }
950         }
951         self.assert_ok_selection();
952     }
953 
set_edit_point_index(&mut self, index: usize)954     pub fn set_edit_point_index(&mut self, index: usize) {
955         let byte_size = self.lines[self.edit_point.line]
956             .graphemes(true)
957             .take(index)
958             .fold(0, |acc, x| acc + x.len());
959         self.edit_point.index = byte_size;
960     }
961 }
962