fm/modes/menu/
input.rs

1use unicode_segmentation::UnicodeSegmentation;
2
3/// Holds the chars typed by the user and the cursor position.
4/// Methods allow mutation of this content and movement of the cursor.
5#[derive(Clone, Default)]
6pub struct Input {
7    /// The input typed by the user
8    chars: Vec<String>,
9    /// The index of the cursor in that string
10    cursor_index: usize,
11}
12
13impl Input {
14    /// Empty the content and move the cursor to start.
15    pub fn reset(&mut self) {
16        self.chars.clear();
17        self.cursor_index = 0;
18    }
19
20    /// Current index of the cursor
21    #[must_use]
22    pub fn index(&self) -> usize {
23        self.cursor_index
24    }
25
26    #[must_use]
27    pub fn len(&self) -> usize {
28        self.chars.len()
29    }
30
31    #[must_use]
32    pub fn is_empty(&self) -> bool {
33        self.chars.is_empty()
34    }
35
36    /// Returns the content typed by the user as a String.
37    #[must_use]
38    pub fn string(&self) -> String {
39        self.chars.join("")
40    }
41
42    /// Returns a string of * for every char typed.
43    #[must_use]
44    pub fn password(&self) -> String {
45        "*".repeat(self.len())
46    }
47
48    /// Insert an utf-8 char into the input at cursor index.
49    pub fn insert(&mut self, c: char) {
50        self.chars.insert(self.cursor_index, String::from(c));
51        self.cursor_index += 1;
52    }
53
54    /// Move the cursor to the start
55    pub fn cursor_start(&mut self) {
56        self.cursor_index = 0;
57    }
58
59    /// Move the cursor to the end
60    pub fn cursor_end(&mut self) {
61        self.cursor_index = self.len();
62    }
63
64    /// Move the cursor left if possible
65    pub fn cursor_left(&mut self) {
66        if self.cursor_index > 0 {
67            self.cursor_index -= 1;
68        }
69    }
70
71    /// Move the cursor right if possible
72    pub fn cursor_right(&mut self) {
73        if self.cursor_index < self.len() {
74            self.cursor_index += 1;
75        }
76    }
77
78    /// Move the cursor to the given index, limited to the length of the input.
79    ///
80    /// Used when the user click on the input string.
81    pub fn cursor_move(&mut self, index: usize) {
82        if index <= self.len() {
83            self.cursor_index = index
84        } else {
85            self.cursor_end()
86        }
87    }
88
89    /// Backspace, delete the char under the cursor and move left
90    pub fn delete_char_left(&mut self) {
91        if self.cursor_index > 0 && !self.chars.is_empty() {
92            self.chars.remove(self.cursor_index - 1);
93            self.cursor_index -= 1;
94        }
95    }
96
97    /// Delete all chars right to the cursor
98    pub fn delete_chars_right(&mut self) {
99        self.chars = self
100            .chars
101            .iter()
102            .take(self.cursor_index)
103            .map(std::string::ToString::to_string)
104            .collect();
105    }
106
107    pub fn delete_line(&mut self) {
108        self.chars = vec![];
109        self.cursor_index = 0;
110    }
111
112    /// Deletes left symbols until a separator is reached or the start of the line
113    pub fn delete_left(&mut self) {
114        while self.cursor_index > 0 {
115            self.chars.remove(self.cursor_index.saturating_sub(1));
116            self.cursor_index -= 1;
117            if self.is_empty() || is_separator(&self.chars[self.cursor_index.saturating_sub(1)]) {
118                break;
119            }
120        }
121    }
122
123    /// Replace the content with the new content.
124    /// Put the cursor at the end.
125    ///
126    /// To avoid splitting graphemes at wrong place, the new content is read
127    /// as Unicode Graphemes with
128    /// ```rust
129    /// unicode_segmentation::UnicodeSegmentation::graphemes(content, true)
130    /// ```
131    /// See [`UnicodeSegmentation`] for more information.
132    pub fn replace(&mut self, content: &str) {
133        self.chars = UnicodeSegmentation::graphemes(content, true)
134            .collect::<Vec<&str>>()
135            .iter()
136            .map(|s| (*s).to_string())
137            .collect();
138        self.cursor_end();
139    }
140}
141
142fn is_separator(word: &str) -> bool {
143    matches!(
144        word,
145        " " | "/"
146            | "\\"
147            | "."
148            | ","
149            | ";"
150            | "!"
151            | "?"
152            | "%"
153            | "_"
154            | "-"
155            | "+"
156            | "*"
157            | "("
158            | ")"
159            | "{"
160            | "}"
161            | "["
162            | "]"
163    )
164}