1use crate::buffer::{GapBuffer, TextBuffer};
2use crate::syntax_highlighter::SyntaxHighlighter;
3use gpui::*;
4
5#[derive(Clone)]
6pub struct EditorConfig {
7 pub line_height: Pixels,
8 pub font_size: Pixels,
9 pub gutter_width: Pixels,
10 pub gutter_padding: Pixels,
11 pub text_color: Rgba,
12 pub line_number_color: Rgba,
13 pub gutter_bg_color: Rgba,
14 pub editor_bg_color: Rgba,
15 pub active_line_bg_color: Rgba,
16 pub font_family: SharedString,
17}
18
19impl Default for EditorConfig {
20 fn default() -> Self {
21 Self {
22 line_height: px(20.0),
23 font_size: px(14.0),
24 gutter_width: px(50.0),
25 gutter_padding: px(10.0),
26 text_color: rgb(0xcccccc),
27 line_number_color: rgb(0x666666),
28 gutter_bg_color: rgb(0x252525),
29 editor_bg_color: rgb(0x1e1e1e),
30 active_line_bg_color: rgb(0x2a2a2a),
31 font_family: "Monaco".into(),
32 }
33 }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct CursorPosition {
38 pub row: usize,
39 pub col: usize,
40}
41
42impl CursorPosition {
43 pub fn new(row: usize, col: usize) -> Self {
44 Self { row, col }
45 }
46}
47
48#[derive(Clone)]
49pub struct Editor {
50 id: ElementId,
51 buffer: GapBuffer,
52 config: EditorConfig,
53 cursor_position: CursorPosition,
54 goal_column: Option<usize>,
55 selection_anchor: Option<CursorPosition>,
56 syntax_highlighter: SyntaxHighlighter,
57 language: String,
58 current_theme: String,
59}
60
61impl Editor {
62 pub fn new(id: impl Into<ElementId>, lines: Vec<String>) -> Self {
63 let id = id.into();
64 let syntax_highlighter = SyntaxHighlighter::new();
65
66 let full_text = lines.join("\n");
68 let language = syntax_highlighter
69 .detect_language(&full_text, Some("rs"))
70 .unwrap_or_else(|| "Rust".to_string());
71
72 Self {
73 id,
74 buffer: GapBuffer::from_lines(lines),
75 config: EditorConfig::default(),
76 cursor_position: CursorPosition { row: 0, col: 0 },
77 goal_column: None,
78 selection_anchor: None,
79 syntax_highlighter,
80 language,
81 current_theme: String::new(),
82 }
83 }
84
85 pub fn id(&self) -> &ElementId {
86 &self.id
87 }
88
89 pub fn config(&self) -> &EditorConfig {
90 &self.config
91 }
92
93 pub fn config_mut(&mut self) -> &mut EditorConfig {
94 &mut self.config
95 }
96
97 pub fn set_config(&mut self, config: EditorConfig) {
98 self.config = config;
99 }
100
101 pub fn cursor_position(&self) -> CursorPosition {
102 self.cursor_position
103 }
104
105 pub fn set_cursor_position(&mut self, position: CursorPosition) {
106 self.cursor_position = position;
107 self.goal_column = None;
109 }
110
111 pub fn get_cursor_position(&self) -> CursorPosition {
112 self.cursor_position
113 }
114
115 pub fn clear_selection(&mut self) {
116 self.selection_anchor = None;
117 self.goal_column = None;
119 }
120
121 pub fn get_buffer(&self) -> &GapBuffer {
122 &self.buffer
123 }
124
125 pub fn get_buffer_mut(&mut self) -> &mut GapBuffer {
126 &mut self.buffer
127 }
128
129 pub fn language(&self) -> &str {
130 &self.language
131 }
132
133 pub fn set_language(&mut self, language: String) {
134 self.language = language;
135 }
136
137 pub fn current_theme(&self) -> &str {
138 &self.current_theme
139 }
140
141 pub fn set_theme(&mut self, theme: &str) {
142 self.current_theme = theme.to_string();
143 self.syntax_highlighter.set_theme(theme);
144 self.config.editor_bg_color = self.syntax_highlighter.get_theme_background().into();
146 self.config.text_color = self.syntax_highlighter.get_theme_foreground().into();
147 self.config.gutter_bg_color = self.syntax_highlighter.get_theme_gutter_background().into();
148 self.config.active_line_bg_color =
149 self.syntax_highlighter.get_theme_line_highlight().into();
150 }
151
152 pub fn update_buffer(&mut self, lines: Vec<String>) {
153 self.buffer = GapBuffer::from_lines(lines);
154 self.syntax_highlighter.reset_state();
156 }
157
158 pub fn update_line(&mut self, line_index: usize, new_content: String) {
160 if line_index < self.buffer.line_count() {
162 let line_len = self.buffer.line_len(line_index);
164
165 let start_pos = self.buffer.cursor_to_position(line_index, 0);
167 let end_pos = self.buffer.cursor_to_position(line_index, line_len);
168 self.buffer.delete_range(start_pos, end_pos);
169
170 self.buffer.insert_at(line_index, 0, &new_content);
172
173 self.syntax_highlighter
175 .clear_state_from_line(line_index, &self.language);
176 }
177 }
178
179 pub fn highlight_line(
181 &mut self,
182 line: &str,
183 line_index: usize,
184 font_family: SharedString,
185 font_size: f32,
186 ) -> Vec<TextRun> {
187 self.syntax_highlighter.highlight_line(
188 line,
189 &self.language,
190 line_index,
191 font_family,
192 font_size,
193 )
194 }
195
196 pub fn move_left(&mut self, shift_held: bool) {
198 if shift_held && self.selection_anchor.is_none() {
199 self.selection_anchor = Some(self.cursor_position);
200 } else if !shift_held {
201 self.selection_anchor = None;
202 }
203
204 self.goal_column = None;
206
207 if self.cursor_position.col > 0 {
208 self.cursor_position.col -= 1;
209 } else if self.cursor_position.row > 0 {
210 self.cursor_position.row -= 1;
211 self.cursor_position.col = self.buffer.line_len(self.cursor_position.row);
212 }
213 }
214
215 pub fn move_right(&mut self, shift_held: bool) {
216 if shift_held && self.selection_anchor.is_none() {
217 self.selection_anchor = Some(self.cursor_position);
218 } else if !shift_held {
219 self.selection_anchor = None;
220 }
221
222 self.goal_column = None;
224
225 let current_line_len = self.buffer.line_len(self.cursor_position.row);
226
227 if self.cursor_position.col < current_line_len {
228 self.cursor_position.col += 1;
229 } else if self.cursor_position.row < self.buffer.line_count().saturating_sub(1) {
230 self.cursor_position.row += 1;
232 self.cursor_position.col = 0;
233 }
234 }
235
236 pub fn move_up(&mut self, shift_held: bool) {
237 if shift_held && self.selection_anchor.is_none() {
238 self.selection_anchor = Some(self.cursor_position);
239 } else if !shift_held {
240 self.selection_anchor = None;
241 }
242
243 if self.cursor_position.row > 0 {
244 if self.goal_column.is_none() {
246 self.goal_column = Some(self.cursor_position.col);
247 }
248
249 self.cursor_position.row -= 1;
250
251 let line_len = self.buffer.line_len(self.cursor_position.row);
253 self.cursor_position.col = self
254 .goal_column
255 .unwrap_or(self.cursor_position.col)
256 .min(line_len);
257 }
258 }
259
260 pub fn move_down(&mut self, shift_held: bool) {
261 if shift_held && self.selection_anchor.is_none() {
262 self.selection_anchor = Some(self.cursor_position);
263 } else if !shift_held {
264 self.selection_anchor = None;
265 }
266
267 if self.cursor_position.row < self.buffer.line_count().saturating_sub(1) {
268 if self.goal_column.is_none() {
270 self.goal_column = Some(self.cursor_position.col);
271 }
272
273 self.cursor_position.row += 1;
274
275 let line_len = self.buffer.line_len(self.cursor_position.row);
277 self.cursor_position.col = self
278 .goal_column
279 .unwrap_or(self.cursor_position.col)
280 .min(line_len);
281 }
282 }
283
284 pub fn select_all(&mut self) {
285 self.goal_column = None;
287
288 self.selection_anchor = Some(CursorPosition { row: 0, col: 0 });
290
291 let last_row = self.buffer.line_count().saturating_sub(1);
293 let last_col = self.buffer.line_len(last_row);
294 self.cursor_position = CursorPosition {
295 row: last_row,
296 col: last_col,
297 };
298 }
299
300 pub fn has_selection(&self) -> bool {
301 self.selection_anchor.is_some()
302 }
303
304 pub fn get_selection_range(&self) -> Option<(CursorPosition, CursorPosition)> {
305 self.selection_anchor.map(|anchor| {
306 if anchor.row < self.cursor_position.row
308 || (anchor.row == self.cursor_position.row && anchor.col < self.cursor_position.col)
309 {
310 (anchor, self.cursor_position)
311 } else {
312 (self.cursor_position, anchor)
313 }
314 })
315 }
316
317 pub fn delete_selection(&mut self) -> bool {
318 if let Some((start, end)) = self.get_selection_range() {
319 let start_pos = self.buffer.cursor_to_position(start.row, start.col);
321 let end_pos = self.buffer.cursor_to_position(end.row, end.col);
322
323 self.buffer.delete_range(start_pos, end_pos);
325
326 self.cursor_position = start;
328 self.selection_anchor = None;
329 self.goal_column = None;
330
331 self.syntax_highlighter
333 .clear_state_from_line(start.row, &self.language);
334
335 true
336 } else {
337 false
338 }
339 }
340
341 pub fn get_selected_text(&self) -> String {
342 if let Some((start, end)) = self.get_selection_range() {
343 let start_pos = self.buffer.cursor_to_position(start.row, start.col);
345 let end_pos = self.buffer.cursor_to_position(end.row, end.col);
346
347 let text = self.buffer.to_string();
349
350 let chars: Vec<char> = text.chars().collect();
352 if start_pos <= chars.len() && end_pos <= chars.len() && start_pos <= end_pos {
353 chars[start_pos..end_pos].iter().collect()
354 } else {
355 String::new()
356 }
357 } else {
358 String::new()
359 }
360 }
361
362 pub fn insert_char(&mut self, ch: char) {
363 self.delete_selection();
365
366 self.buffer.insert_at(
368 self.cursor_position.row,
369 self.cursor_position.col,
370 &ch.to_string(),
371 );
372 self.cursor_position.col += 1;
373 self.goal_column = None;
374
375 self.syntax_highlighter
377 .clear_state_from_line(self.cursor_position.row, &self.language);
378 }
379
380 pub fn insert_newline(&mut self) {
381 self.delete_selection();
383
384 self.buffer
386 .insert_at(self.cursor_position.row, self.cursor_position.col, "\n");
387 self.cursor_position.row += 1;
388 self.cursor_position.col = 0;
389 self.goal_column = None;
390
391 self.syntax_highlighter
393 .clear_state_from_line(self.cursor_position.row - 1, &self.language);
394 }
395
396 pub fn backspace(&mut self) {
397 if self.selection_anchor.is_some() {
399 self.delete_selection();
400 return;
401 }
402
403 self.buffer
405 .backspace_at(self.cursor_position.row, self.cursor_position.col);
406
407 if self.cursor_position.col > 0 {
408 self.cursor_position.col -= 1;
409 self.syntax_highlighter
411 .clear_state_from_line(self.cursor_position.row, &self.language);
412 } else if self.cursor_position.row > 0 {
413 self.cursor_position.row -= 1;
415 let line_len = self.buffer.line_len(self.cursor_position.row);
416 self.cursor_position.col = line_len;
417 self.syntax_highlighter
419 .clear_state_from_line(self.cursor_position.row, &self.language);
420 }
421
422 self.goal_column = None;
423 }
424
425 pub fn delete(&mut self) {
426 if self.selection_anchor.is_some() {
428 self.delete_selection();
429 return;
430 }
431
432 self.buffer
434 .delete_at(self.cursor_position.row, self.cursor_position.col);
435
436 self.syntax_highlighter
438 .clear_state_from_line(self.cursor_position.row, &self.language);
439
440 self.goal_column = None;
441 }
442}