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 _;
12
13#[derive(Debug, Copy, Clone)]
14pub struct TabSize {
15 pub tab_size: usize,
17 pub hard_tabs: bool,
19}
20
21impl Default for TabSize {
22 fn default() -> Self {
23 Self {
24 tab_size: 2,
25 hard_tabs: false,
26 }
27 }
28}
29
30impl TabSize {
31 pub(super) fn to_string(&self) -> SharedString {
32 if self.hard_tabs {
33 "\t".into()
34 } else {
35 " ".repeat(self.tab_size).into()
36 }
37 }
38}
39
40#[derive(Default, Clone)]
41pub enum InputMode {
42 #[default]
43 SingleLine,
44 MultiLine {
45 tab: TabSize,
46 rows: usize,
47 },
48 AutoGrow {
49 rows: usize,
50 min_rows: usize,
51 max_rows: usize,
52 },
53 CodeEditor {
54 tab: TabSize,
55 rows: usize,
56 line_number: bool,
58 language: SharedString,
59 highlighter: Rc<RefCell<Option<SyntaxHighlighter>>>,
60 diagnostics: DiagnosticSet,
61 },
62}
63
64#[allow(unused)]
65impl InputMode {
66 #[inline]
67 pub(super) fn is_single_line(&self) -> bool {
68 matches!(self, InputMode::SingleLine)
69 }
70
71 #[inline]
72 pub(super) fn is_code_editor(&self) -> bool {
73 matches!(self, InputMode::CodeEditor { .. })
74 }
75
76 #[inline]
77 pub(super) fn is_auto_grow(&self) -> bool {
78 matches!(self, InputMode::AutoGrow { .. })
79 }
80
81 #[inline]
82 pub(super) fn is_multi_line(&self) -> bool {
83 matches!(
84 self,
85 InputMode::MultiLine { .. } | InputMode::AutoGrow { .. } | InputMode::CodeEditor { .. }
86 )
87 }
88
89 pub(super) fn set_rows(&mut self, new_rows: usize) {
90 match self {
91 InputMode::MultiLine { rows, .. } => {
92 *rows = new_rows;
93 }
94 InputMode::CodeEditor { rows, .. } => {
95 *rows = new_rows;
96 }
97 InputMode::AutoGrow {
98 rows,
99 min_rows,
100 max_rows,
101 } => {
102 *rows = new_rows.clamp(*min_rows, *max_rows);
103 }
104 _ => {}
105 }
106 }
107
108 pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) {
109 if self.is_single_line() {
110 return;
111 }
112
113 let wrapped_lines = text_wrapper.len();
114 self.set_rows(wrapped_lines);
115 }
116
117 pub(super) fn rows(&self) -> usize {
119 match self {
120 InputMode::MultiLine { rows, .. } => *rows,
121 InputMode::CodeEditor { rows, .. } => *rows,
122 InputMode::AutoGrow { rows, .. } => *rows,
123 _ => 1,
124 }
125 .max(1)
126 }
127
128 #[allow(unused)]
130 pub(super) fn min_rows(&self) -> usize {
131 match self {
132 InputMode::MultiLine { .. } | InputMode::CodeEditor { .. } => 1,
133 InputMode::AutoGrow { min_rows, .. } => *min_rows,
134 _ => 1,
135 }
136 .max(1)
137 }
138
139 #[allow(unused)]
140 pub(super) fn max_rows(&self) -> usize {
141 match self {
142 InputMode::MultiLine { .. } | InputMode::CodeEditor { .. } => usize::MAX,
143 InputMode::AutoGrow { max_rows, .. } => *max_rows,
144 _ => 1,
145 }
146 }
147
148 #[allow(unused)]
150 #[inline]
151 pub(super) fn line_number(&self) -> bool {
152 match self {
153 InputMode::CodeEditor { line_number, .. } => *line_number,
154 _ => false,
155 }
156 }
157
158 #[inline]
159 pub(super) fn tab_size(&self) -> Option<&TabSize> {
160 match self {
161 InputMode::MultiLine { tab, .. } => Some(tab),
162 InputMode::CodeEditor { tab, .. } => Some(tab),
163 _ => None,
164 }
165 }
166
167 pub(super) fn update_highlighter(
168 &mut self,
169 selected_range: &Range<usize>,
170 text: &Rope,
171 new_text: &str,
172 force: bool,
173 cx: &mut App,
174 ) {
175 match &self {
176 InputMode::CodeEditor {
177 language,
178 highlighter,
179 ..
180 } => {
181 if !force && highlighter.borrow().is_some() {
182 return;
183 }
184
185 let mut highlighter = highlighter.borrow_mut();
186 if highlighter.is_none() {
187 let new_highlighter = SyntaxHighlighter::new(language);
188 highlighter.replace(new_highlighter);
189 }
190
191 let Some(highlighter) = highlighter.as_mut() else {
192 return;
193 };
194
195 let mut selected_range = selected_range.clone();
197 selected_range.end = selected_range.end.min(text.len());
198
199 let changed_len = new_text.len() as isize - selected_range.len() as isize;
204 let new_end = (selected_range.end as isize + changed_len) as usize;
205
206 let start_pos = text.offset_to_point(selected_range.start);
207 let old_end_pos = text.offset_to_point(selected_range.end);
208 let new_end_pos = text.offset_to_point(new_end);
209
210 let edit = InputEdit {
211 start_byte: selected_range.start,
212 old_end_byte: selected_range.end,
213 new_end_byte: new_end,
214 start_position: start_pos,
215 old_end_position: old_end_pos,
216 new_end_position: new_end_pos,
217 };
218
219 highlighter.update(Some(edit), text);
220 }
221 _ => {}
222 }
223 }
224
225 #[allow(unused)]
226 pub(super) fn diagnostics(&self) -> Option<&DiagnosticSet> {
227 match self {
228 InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),
229 _ => None,
230 }
231 }
232
233 pub(super) fn diagnostics_mut(&mut self) -> Option<&mut DiagnosticSet> {
234 match self {
235 InputMode::CodeEditor { diagnostics, .. } => Some(diagnostics),
236 _ => None,
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::TabSize;
244
245 #[test]
246 fn test_tab_size() {
247 let tab = TabSize {
248 tab_size: 2,
249 hard_tabs: false,
250 };
251 assert_eq!(tab.to_string(), " ");
252 let tab = TabSize {
253 tab_size: 4,
254 hard_tabs: false,
255 };
256 assert_eq!(tab.to_string(), " ");
257
258 let tab = TabSize {
259 tab_size: 2,
260 hard_tabs: true,
261 };
262 assert_eq!(tab.to_string(), "\t");
263 let tab = TabSize {
264 tab_size: 4,
265 hard_tabs: true,
266 };
267 assert_eq!(tab.to_string(), "\t");
268 }
269}