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 PlainText {
17 multi_line: bool,
18 tab: TabSize,
19 rows: usize,
20 },
21 AutoGrow {
23 rows: usize,
24 min_rows: usize,
25 max_rows: usize,
26 },
27 CodeEditor {
29 multi_line: bool,
30 tab: TabSize,
31 rows: usize,
32 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 pub(super) fn plain_text() -> Self {
51 InputMode::PlainText {
52 multi_line: false,
53 tab: TabSize::default(),
54 rows: 1,
55 }
56 }
57
58 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 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 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 #[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 #[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 let mut selected_range = selected_range.clone();
221 selected_range.end = selected_range.end.min(text.len());
222
223 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}