Skip to main content

gpui_component/input/
mode.rs

1use std::rc::Rc;
2use std::{cell::RefCell, ops::Range};
3
4use gpui::{App, SharedString};
5use ropey::Rope;
6use tree_sitter::InputEdit;
7
8use super::text_wrapper::TextWrapper;
9use crate::highlighter::DiagnosticSet;
10use crate::highlighter::SyntaxHighlighter;
11use crate::input::{RopeExt as _, TabSize};
12
13#[derive(Clone)]
14pub(crate) enum InputMode {
15    /// A plain text input mode.
16    PlainText {
17        multi_line: bool,
18        tab: TabSize,
19        rows: usize,
20    },
21    /// An auto grow input mode.
22    AutoGrow {
23        rows: usize,
24        min_rows: usize,
25        max_rows: usize,
26    },
27    /// A code editor input mode.
28    CodeEditor {
29        multi_line: bool,
30        tab: TabSize,
31        rows: usize,
32        /// Show line number
33        line_number: bool,
34        language: SharedString,
35        indent_guides: bool,
36        highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,
37        diagnostics: DiagnosticSet,
38    },
39}
40
41impl Default for InputMode {
42    fn default() -> Self {
43        InputMode::plain_text()
44    }
45}
46
47#[allow(unused)]
48impl InputMode {
49    /// Create a plain input mode with default settings.
50    pub(super) fn plain_text() -> Self {
51        InputMode::PlainText {
52            multi_line: false,
53            tab: TabSize::default(),
54            rows: 1,
55        }
56    }
57
58    /// Create a code editor input mode with default settings.
59    pub(super) fn code_editor(language: impl Into<SharedString>) -> Self {
60        InputMode::CodeEditor {
61            rows: 2,
62            multi_line: true,
63            tab: TabSize::default(),
64            language: language.into(),
65            highlighter: Rc::new(RefCell::new(None)),
66            line_number: true,
67            indent_guides: true,
68            diagnostics: DiagnosticSet::new(&Rope::new()),
69        }
70    }
71
72    /// Create an auto grow input mode with given min and max rows.
73    pub(super) fn auto_grow(min_rows: usize, max_rows: usize) -> Self {
74        InputMode::AutoGrow {
75            rows: min_rows,
76            min_rows,
77            max_rows,
78        }
79    }
80
81    pub(super) fn multi_line(mut self, multi_line: bool) -> Self {
82        match &mut self {
83            InputMode::PlainText { multi_line: ml, .. } => *ml = multi_line,
84            InputMode::CodeEditor { multi_line: ml, .. } => *ml = multi_line,
85            InputMode::AutoGrow { .. } => {}
86        }
87        self
88    }
89
90    #[inline]
91    pub(super) fn is_single_line(&self) -> bool {
92        !self.is_multi_line()
93    }
94
95    #[inline]
96    pub(super) fn is_code_editor(&self) -> bool {
97        matches!(self, InputMode::CodeEditor { .. })
98    }
99
100    #[inline]
101    pub(super) fn is_auto_grow(&self) -> bool {
102        matches!(self, InputMode::AutoGrow { .. })
103    }
104
105    #[inline]
106    pub(super) fn is_multi_line(&self) -> bool {
107        match self {
108            InputMode::PlainText { multi_line, .. } => *multi_line,
109            InputMode::CodeEditor { multi_line, .. } => *multi_line,
110            InputMode::AutoGrow { max_rows, .. } => *max_rows > 1,
111        }
112    }
113
114    pub(super) fn set_rows(&mut self, new_rows: usize) {
115        match self {
116            InputMode::PlainText { rows, .. } => {
117                *rows = new_rows;
118            }
119            InputMode::CodeEditor { rows, .. } => {
120                *rows = new_rows;
121            }
122            InputMode::AutoGrow {
123                rows,
124                min_rows,
125                max_rows,
126            } => {
127                *rows = new_rows.clamp(*min_rows, *max_rows);
128            }
129        }
130    }
131
132    pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) {
133        if self.is_single_line() {
134            return;
135        }
136
137        let wrapped_lines = text_wrapper.len();
138        self.set_rows(wrapped_lines);
139    }
140
141    /// At least 1 row be return.
142    pub(super) fn rows(&self) -> usize {
143        if !self.is_multi_line() {
144            return 1;
145        }
146
147        match self {
148            InputMode::PlainText { rows, .. } => *rows,
149            InputMode::CodeEditor { rows, .. } => *rows,
150            InputMode::AutoGrow { rows, .. } => *rows,
151        }
152        .max(1)
153    }
154
155    /// At least 1 row be return.
156    #[allow(unused)]
157    pub(super) fn min_rows(&self) -> usize {
158        match self {
159            InputMode::AutoGrow { min_rows, .. } => *min_rows,
160            _ => 1,
161        }
162        .max(1)
163    }
164
165    #[allow(unused)]
166    pub(super) fn max_rows(&self) -> usize {
167        if !self.is_multi_line() {
168            return 1;
169        }
170
171        match self {
172            InputMode::AutoGrow { max_rows, .. } => *max_rows,
173            _ => usize::MAX,
174        }
175    }
176
177    /// Return false if the mode is not [`InputMode::CodeEditor`].
178    #[allow(unused)]
179    #[inline]
180    pub(super) fn line_number(&self) -> bool {
181        match self {
182            InputMode::CodeEditor {
183                line_number,
184                multi_line,
185                ..
186            } => *line_number && *multi_line,
187            _ => false,
188        }
189    }
190
191    pub(super) fn update_highlighter(
192        &mut self,
193        selected_range: &Range<usize>,
194        text: &Rope,
195        new_text: &str,
196        force: bool,
197        cx: &mut App,
198    ) {
199        match &self {
200            InputMode::CodeEditor {
201                language,
202                highlighter,
203                ..
204            } => {
205                if !force && highlighter.borrow().is_some() {
206                    return;
207                }
208
209                let mut highlighter = highlighter.borrow_mut();
210                if highlighter.is_none() {
211                    let new_highlighter = SyntaxHighlighter::new(language);
212                    highlighter.replace(new_highlighter);
213                }
214
215                let Some(highlighter) = highlighter.as_mut() else {
216                    return;
217                };
218
219                // When full text changed, the selected_range may be out of bound (The before version).
220                let mut selected_range = selected_range.clone();
221                selected_range.end = selected_range.end.min(text.len());
222
223                // If insert a chart, this is 1.
224                // If backspace or delete, this is -1.
225                // If selected to delete, this is the length of the selected text.
226                // let changed_len = new_text.len() as isize - selected_range.len() as isize;
227                let changed_len = new_text.len() as isize - selected_range.len() as isize;
228                let new_end = (selected_range.end as isize + changed_len) as usize;
229
230                let start_pos = text.offset_to_point(selected_range.start);
231                let old_end_pos = text.offset_to_point(selected_range.end);
232                let new_end_pos = text.offset_to_point(new_end);
233
234                let edit = InputEdit {
235                    start_byte: selected_range.start,
236                    old_end_byte: selected_range.end,
237                    new_end_byte: new_end,
238                    start_position: start_pos,
239                    old_end_position: old_end_pos,
240                    new_end_position: new_end_pos,
241                };
242
243                highlighter.update(Some(edit), text);
244            }
245            _ => {}
246        }
247    }
248
249    #[allow(unused)]
250    pub(super) fn diagnostics(&self) -> Option<&DiagnosticSet> {
251        match self {
252            InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),
253            _ => None,
254        }
255    }
256
257    pub(super) fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
258        match self {
259            InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),
260            _ => None,
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use ropey::Rope;
268
269    use crate::{
270        highlighter::DiagnosticSet,
271        input::{TabSize, mode::InputMode},
272    };
273
274    #[test]
275    fn test_code_editor() {
276        let mode = InputMode::code_editor("rust");
277        assert_eq!(mode.is_code_editor(), true);
278        assert_eq!(mode.is_multi_line(), true);
279        assert_eq!(mode.is_single_line(), false);
280        assert_eq!(mode.line_number(), true);
281        assert_eq!(mode.has_indent_guides(), true);
282        assert_eq!(mode.max_rows(), usize::MAX);
283        assert_eq!(mode.min_rows(), 1);
284
285        let mode = InputMode::CodeEditor {
286            multi_line: false,
287            line_number: true,
288            indent_guides: true,
289            rows: 0,
290            tab: Default::default(),
291            language: "rust".into(),
292            highlighter: Default::default(),
293            diagnostics: DiagnosticSet::new(&Rope::new()),
294        };
295        assert_eq!(mode.is_code_editor(), true);
296        assert_eq!(mode.is_multi_line(), false);
297        assert_eq!(mode.is_single_line(), true);
298        assert_eq!(mode.line_number(), false);
299        assert_eq!(mode.has_indent_guides(), false);
300        assert_eq!(mode.max_rows(), 1);
301        assert_eq!(mode.min_rows(), 1);
302    }
303
304    #[test]
305    fn test_plain() {
306        let mode = InputMode::PlainText {
307            multi_line: true,
308            tab: TabSize::default(),
309            rows: 5,
310        };
311        assert_eq!(mode.is_code_editor(), false);
312        assert_eq!(mode.is_multi_line(), true);
313        assert_eq!(mode.is_single_line(), false);
314        assert_eq!(mode.line_number(), false);
315        assert_eq!(mode.rows(), 5);
316        assert_eq!(mode.max_rows(), usize::MAX);
317        assert_eq!(mode.min_rows(), 1);
318
319        let mode = InputMode::plain_text();
320        assert_eq!(mode.is_code_editor(), false);
321        assert_eq!(mode.is_multi_line(), false);
322        assert_eq!(mode.is_single_line(), true);
323        assert_eq!(mode.line_number(), false);
324        assert_eq!(mode.max_rows(), 1);
325        assert_eq!(mode.min_rows(), 1);
326    }
327
328    #[test]
329    fn test_auto_grow() {
330        let mut mode = InputMode::auto_grow(2, 5);
331        assert_eq!(mode.is_code_editor(), false);
332        assert_eq!(mode.is_multi_line(), true);
333        assert_eq!(mode.is_single_line(), false);
334        assert_eq!(mode.line_number(), false);
335        assert_eq!(mode.rows(), 2);
336        assert_eq!(mode.max_rows(), 5);
337        assert_eq!(mode.min_rows(), 2);
338
339        mode.set_rows(4);
340        assert_eq!(mode.rows(), 4);
341
342        mode.set_rows(1);
343        assert_eq!(mode.rows(), 2);
344
345        mode.set_rows(10);
346        assert_eq!(mode.rows(), 5);
347    }
348}