zrd_core/
engine.rs

1//! Core editor engine with platform-agnostic business logic
2
3use crate::{BufferPosition, EditorAction, EditorState};
4use std::fs;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::time::{Duration, Instant};
8
9pub struct EditorEngine {
10    state: EditorState,
11    undo_stack: Vec<EditorState>,
12    redo_stack: Vec<EditorState>,
13    last_edit_time: Option<Instant>,
14}
15
16const UNDO_CHUNK_DURATION: Duration = Duration::from_millis(500);
17
18impl EditorEngine {
19    pub fn new() -> Self {
20        Self {
21            state: EditorState::new(),
22            undo_stack: Vec::new(),
23            redo_stack: Vec::new(),
24            last_edit_time: None,
25        }
26    }
27
28    pub fn state(&self) -> &EditorState {
29        &self.state
30    }
31
32    pub fn state_mut(&mut self) -> &mut EditorState {
33        &mut self.state
34    }
35
36    fn should_push_undo_state(&self) -> bool {
37        if let Some(last_time) = self.last_edit_time {
38            Instant::now().duration_since(last_time) > UNDO_CHUNK_DURATION
39        } else {
40            true
41        }
42    }
43
44    fn push_undo_state(&mut self) {
45        if !self.should_push_undo_state() {
46            return;
47        }
48        self.undo_stack.push(self.state.clone_for_undo());
49        self.redo_stack.clear();
50    }
51
52    fn mark_edit_time(&mut self) {
53        self.last_edit_time = Some(Instant::now());
54    }
55
56    pub fn handle_action(&mut self, action: EditorAction) {
57        match action {
58            EditorAction::TypeCharacter(c) => self.type_character(c),
59            EditorAction::TypeString(s) => self.type_string(&s),
60            EditorAction::Backspace => self.backspace(),
61            EditorAction::Delete => self.delete(),
62            EditorAction::Newline => self.newline(),
63            EditorAction::MoveLeft => self.move_left(),
64            EditorAction::MoveRight => self.move_right(),
65            EditorAction::MoveUp => self.move_up(),
66            EditorAction::MoveDown => self.move_down(),
67            EditorAction::MoveToBeginningOfLine => self.move_to_line_start(),
68            EditorAction::MoveToEndOfLine => self.move_to_line_end(),
69            EditorAction::MoveWordLeft => self.move_word_left(),
70            EditorAction::MoveWordRight => self.move_word_right(),
71            EditorAction::Undo => self.undo(),
72            EditorAction::Redo => self.redo(),
73            EditorAction::DeleteLine => self.delete_line(),
74            EditorAction::DeleteToBeginningOfLine => self.delete_to_beginning_of_line(),
75            EditorAction::DeleteToEndOfLine => self.delete_to_end_of_line(),
76            EditorAction::DeleteWordLeft => self.delete_word_left(),
77            EditorAction::DeleteWordRight => self.delete_word_right(),
78            EditorAction::MoveLineUp => self.move_line_up(),
79            EditorAction::MoveLineDown => self.move_line_down(),
80            EditorAction::Tab => self.tab(),
81            EditorAction::Outdent => self.outdent(),
82            EditorAction::SelectLeft => self.select_left(),
83            EditorAction::SelectRight => self.select_right(),
84            EditorAction::SelectUp => self.select_up(),
85            EditorAction::SelectDown => self.select_down(),
86            EditorAction::SelectWordLeft => self.select_word_left(),
87            EditorAction::SelectWordRight => self.select_word_right(),
88            EditorAction::SelectAll => self.select_all(),
89            EditorAction::IncreaseFontSize => {
90                self.state.font_size = (self.state.font_size + 2.0).min(72.0);
91            }
92            EditorAction::DecreaseFontSize => {
93                self.state.font_size = (self.state.font_size - 2.0).max(8.0);
94            }
95            EditorAction::ResetFontSize => {
96                self.state.font_size = 14.0;
97            }
98            EditorAction::Cut | EditorAction::Copy | EditorAction::Paste(_) => {
99                // Clipboard operations need platform-specific handling
100            }
101            EditorAction::Quit => {
102                // Handled by platform-specific code
103            }
104            EditorAction::SetCursorPosition { row, column } => {
105                self.set_cursor_position(row, column)
106            }
107            EditorAction::StartSelection { row, column } => self.start_selection(row, column),
108            EditorAction::ExtendSelection { row, column } => self.extend_selection(row, column),
109        }
110    }
111
112    fn selection_range(&self) -> Option<(BufferPosition, BufferPosition)> {
113        self.state.selection_anchor.map(|anchor| {
114            if anchor.row < self.state.cursor.row
115                || (anchor.row == self.state.cursor.row && anchor.column < self.state.cursor.column)
116            {
117                (anchor, self.state.cursor)
118            } else {
119                (self.state.cursor, anchor)
120            }
121        })
122    }
123
124    fn clear_selection(&mut self) {
125        self.state.selection_anchor = None;
126    }
127
128    fn delete_selection(&mut self) {
129        if let Some((start, end)) = self.selection_range() {
130            self.delete_range(start, end);
131            self.state.cursor = start;
132            self.clear_selection();
133        }
134    }
135
136    fn delete_range(&mut self, start: BufferPosition, end: BufferPosition) {
137        if start.row == end.row {
138            let line = &mut self.state.lines[start.row];
139            line.replace_range(start.column..end.column, "");
140        } else {
141            let first_part = self.state.lines[start.row][..start.column].to_string();
142            let last_part = self.state.lines[end.row][end.column..].to_string();
143            self.state.lines[start.row] = first_part + &last_part;
144            self.state.lines.drain((start.row + 1)..=(end.row));
145        }
146    }
147
148    fn type_character(&mut self, c: char) {
149        self.push_undo_state();
150        self.mark_edit_time();
151        self.delete_selection();
152
153        if c == '\n' {
154            let line = self.state.lines[self.state.cursor.row].clone();
155            let (before, after) = line.split_at(self.state.cursor.column);
156            self.state.lines[self.state.cursor.row] = before.to_string();
157            self.state
158                .lines
159                .insert(self.state.cursor.row + 1, after.to_string());
160            self.state.cursor = BufferPosition::new(self.state.cursor.row + 1, 0);
161        } else {
162            self.state.lines[self.state.cursor.row].insert(self.state.cursor.column, c);
163            self.state.cursor.column += c.len_utf8();
164        }
165    }
166
167    fn type_string(&mut self, s: &str) {
168        self.push_undo_state();
169        self.mark_edit_time();
170        self.delete_selection();
171
172        for c in s.chars() {
173            if c == '\n' {
174                let line = self.state.lines[self.state.cursor.row].clone();
175                let (before, after) = line.split_at(self.state.cursor.column);
176                self.state.lines[self.state.cursor.row] = before.to_string();
177                self.state
178                    .lines
179                    .insert(self.state.cursor.row + 1, after.to_string());
180                self.state.cursor = BufferPosition::new(self.state.cursor.row + 1, 0);
181            } else {
182                self.state.lines[self.state.cursor.row].insert(self.state.cursor.column, c);
183                self.state.cursor.column += c.len_utf8();
184            }
185        }
186    }
187
188    fn backspace(&mut self) {
189        self.push_undo_state();
190        self.mark_edit_time();
191
192        if let Some((start, end)) = self.selection_range() {
193            self.delete_range(start, end);
194            self.state.cursor = start;
195            self.clear_selection();
196        } else if self.state.cursor.column > 0 {
197            let line = &self.state.lines[self.state.cursor.row];
198            let before = &line[..self.state.cursor.column];
199            if let Some((last_char_start, _)) = before.char_indices().last() {
200                self.state.lines[self.state.cursor.row].remove(last_char_start);
201                self.state.cursor.column = last_char_start;
202            }
203        } else if self.state.cursor.row > 0 {
204            let current_line = self.state.lines.remove(self.state.cursor.row);
205            self.state.cursor.row -= 1;
206            self.state.cursor.column = self.state.lines[self.state.cursor.row].len();
207            self.state.lines[self.state.cursor.row].push_str(&current_line);
208        }
209    }
210
211    fn delete(&mut self) {
212        self.push_undo_state();
213        self.mark_edit_time();
214
215        if let Some((start, end)) = self.selection_range() {
216            self.delete_range(start, end);
217            self.state.cursor = start;
218            self.clear_selection();
219        } else {
220            let line_len = self.state.lines[self.state.cursor.row].len();
221            if self.state.cursor.column < line_len {
222                self.state.lines[self.state.cursor.row].remove(self.state.cursor.column);
223            } else if self.state.cursor.row + 1 < self.state.lines.len() {
224                let next_line = self.state.lines.remove(self.state.cursor.row + 1);
225                self.state.lines[self.state.cursor.row].push_str(&next_line);
226            }
227        }
228    }
229
230    fn detect_list_pattern(line: &str) -> Option<(String, usize, bool)> {
231        let trimmed = line.trim_start();
232        let indent_len = line.len() - trimmed.len();
233
234        if let Some(rest) = trimmed.strip_prefix("- [ ] ") {
235            return Some(("- [ ] ".to_string(), indent_len + 6, rest.is_empty()));
236        }
237        if let Some(rest) = trimmed.strip_prefix("- [x] ") {
238            return Some(("- [ ] ".to_string(), indent_len + 6, rest.is_empty()));
239        }
240        if let Some(rest) = trimmed.strip_prefix("- [X] ") {
241            return Some(("- [ ] ".to_string(), indent_len + 6, rest.is_empty()));
242        }
243        if let Some(rest) = trimmed.strip_prefix("- ") {
244            return Some(("- ".to_string(), indent_len + 2, rest.is_empty()));
245        }
246        if let Some(rest) = trimmed.strip_prefix("* ") {
247            return Some(("* ".to_string(), indent_len + 2, rest.is_empty()));
248        }
249        if let Some(rest) = trimmed.strip_prefix("+ ") {
250            return Some(("+ ".to_string(), indent_len + 2, rest.is_empty()));
251        }
252
253        if let Some(number_end) = trimmed.find(". ") {
254            if let Ok(num) = trimmed[..number_end].parse::<usize>() {
255                let rest = &trimmed[number_end + 2..];
256                let next_num = num + 1;
257                let pattern = format!("{}. ", next_num);
258                return Some((pattern, indent_len + number_end + 2, rest.is_empty()));
259            }
260        }
261
262        None
263    }
264
265    fn newline(&mut self) {
266        self.push_undo_state();
267        self.last_edit_time = None;
268        self.delete_selection();
269
270        let line = self.state.lines[self.state.cursor.row].clone();
271
272        if let Some((pattern, pattern_len, is_empty)) = Self::detect_list_pattern(&line) {
273            if is_empty {
274                let before_pattern = &line[..line.len() - pattern_len];
275                self.state.lines[self.state.cursor.row] = before_pattern.to_string();
276                self.state
277                    .lines
278                    .insert(self.state.cursor.row + 1, String::new());
279                self.state.cursor = BufferPosition::new(self.state.cursor.row + 1, 0);
280            } else {
281                let (before, after) = line.split_at(self.state.cursor.column);
282                self.state.lines[self.state.cursor.row] = before.to_string();
283                self.state
284                    .lines
285                    .insert(self.state.cursor.row + 1, pattern.clone() + after);
286                self.state.cursor = BufferPosition::new(self.state.cursor.row + 1, pattern.len());
287            }
288        } else {
289            let (before, after) = line.split_at(self.state.cursor.column);
290            self.state.lines[self.state.cursor.row] = before.to_string();
291            self.state
292                .lines
293                .insert(self.state.cursor.row + 1, after.to_string());
294            self.state.cursor = BufferPosition::new(self.state.cursor.row + 1, 0);
295        }
296    }
297
298    fn move_left(&mut self) {
299        self.clear_selection();
300        if self.state.cursor.column > 0 {
301            let line = &self.state.lines[self.state.cursor.row];
302            let before = &line[..self.state.cursor.column];
303            if let Some(prev_char) = before.chars().last() {
304                self.state.cursor.column -= prev_char.len_utf8();
305            }
306        } else if self.state.cursor.row > 0 {
307            self.state.cursor.row -= 1;
308            self.state.cursor.column = self.state.lines[self.state.cursor.row].len();
309        }
310    }
311
312    fn move_right(&mut self) {
313        self.clear_selection();
314        let line_len = self.state.lines[self.state.cursor.row].len();
315        if self.state.cursor.column < line_len {
316            let after = &self.state.lines[self.state.cursor.row][self.state.cursor.column..];
317            if let Some(next_char) = after.chars().next() {
318                self.state.cursor.column += next_char.len_utf8();
319            }
320        } else if self.state.cursor.row + 1 < self.state.lines.len() {
321            self.state.cursor.row += 1;
322            self.state.cursor.column = 0;
323        }
324    }
325
326    fn move_up(&mut self) {
327        self.clear_selection();
328        if self.state.cursor.row > 0 {
329            self.state.cursor.row -= 1;
330            let line_len = self.state.lines[self.state.cursor.row].len();
331            self.state.cursor.column = self.state.cursor.column.min(line_len);
332        }
333    }
334
335    fn move_down(&mut self) {
336        self.clear_selection();
337        if self.state.cursor.row + 1 < self.state.lines.len() {
338            self.state.cursor.row += 1;
339            let line_len = self.state.lines[self.state.cursor.row].len();
340            self.state.cursor.column = self.state.cursor.column.min(line_len);
341        }
342    }
343
344    fn move_to_line_start(&mut self) {
345        self.clear_selection();
346        self.state.cursor.column = 0;
347    }
348
349    fn move_to_line_end(&mut self) {
350        self.clear_selection();
351        self.state.cursor.column = self.state.lines[self.state.cursor.row].len();
352    }
353
354    fn move_word_left(&mut self) {
355        self.clear_selection();
356
357        if self.state.cursor.column == 0 {
358            if self.state.cursor.row > 0 {
359                self.state.cursor.row -= 1;
360                self.state.cursor.column = self.state.lines[self.state.cursor.row].len();
361            }
362            return;
363        }
364
365        let line = &self.state.lines[self.state.cursor.row];
366        let mut pos = self.state.cursor.column;
367
368        // Skip whitespace
369        while pos > 0
370            && line
371                .chars()
372                .nth(pos - 1)
373                .map_or(false, |c| c.is_whitespace())
374        {
375            pos -= 1;
376        }
377
378        // Skip word characters
379        while pos > 0 {
380            let ch = line.chars().nth(pos - 1);
381            if ch.map_or(false, |c| !c.is_alphanumeric() && c != '_') {
382                break;
383            }
384            pos -= 1;
385        }
386
387        self.state.cursor.column = pos;
388    }
389
390    fn move_word_right(&mut self) {
391        self.clear_selection();
392
393        let line = &self.state.lines[self.state.cursor.row];
394
395        if self.state.cursor.column >= line.len() {
396            if self.state.cursor.row < self.state.lines.len() - 1 {
397                self.state.cursor.row += 1;
398                self.state.cursor.column = 0;
399            }
400            return;
401        }
402
403        let mut pos = self.state.cursor.column;
404
405        // Skip current word
406        while pos < line.len() {
407            let ch = line.chars().nth(pos);
408            if ch.map_or(false, |c| !c.is_alphanumeric() && c != '_') {
409                break;
410            }
411            pos += 1;
412        }
413
414        // Skip whitespace
415        while pos < line.len() && line.chars().nth(pos).map_or(false, |c| c.is_whitespace()) {
416            pos += 1;
417        }
418
419        self.state.cursor.column = pos;
420    }
421
422    fn undo(&mut self) {
423        if let Some(prev_state) = self.undo_stack.pop() {
424            self.redo_stack.push(self.state.clone_for_undo());
425            self.state = prev_state;
426            self.last_edit_time = None;
427        }
428    }
429
430    fn redo(&mut self) {
431        if let Some(next_state) = self.redo_stack.pop() {
432            self.undo_stack.push(self.state.clone_for_undo());
433            self.state = next_state;
434            self.last_edit_time = None;
435        }
436    }
437
438    fn delete_line(&mut self) {
439        self.push_undo_state();
440        self.last_edit_time = None;
441
442        if self.state.lines.len() == 1 {
443            self.state.lines[0].clear();
444            self.state.cursor = BufferPosition::zero();
445        } else if self.state.cursor.row < self.state.lines.len() - 1 {
446            self.state.lines.remove(self.state.cursor.row);
447            self.state.cursor.column = 0;
448        } else {
449            self.state.lines.remove(self.state.cursor.row);
450            self.state.cursor.row -= 1;
451            self.state.cursor.column = 0;
452        }
453        self.clear_selection();
454    }
455
456    fn delete_to_beginning_of_line(&mut self) {
457        self.push_undo_state();
458        self.last_edit_time = None;
459        self.state.lines[self.state.cursor.row].replace_range(..self.state.cursor.column, "");
460        self.state.cursor.column = 0;
461    }
462
463    fn delete_to_end_of_line(&mut self) {
464        self.push_undo_state();
465        self.last_edit_time = None;
466        self.state.lines[self.state.cursor.row].replace_range(self.state.cursor.column.., "");
467    }
468
469    fn delete_word_left(&mut self) {
470        let start_pos = self.state.cursor;
471        self.move_word_left();
472        let end_pos = self.state.cursor;
473
474        if start_pos.row == end_pos.row {
475            self.push_undo_state();
476            self.last_edit_time = None;
477            self.state.lines[end_pos.row].replace_range(end_pos.column..start_pos.column, "");
478        }
479    }
480
481    fn delete_word_right(&mut self) {
482        let start_pos = self.state.cursor;
483        self.move_word_right();
484        let end_pos = self.state.cursor;
485
486        if start_pos.row == end_pos.row {
487            self.push_undo_state();
488            self.last_edit_time = None;
489            self.state.cursor = start_pos;
490            self.state.lines[start_pos.row].replace_range(start_pos.column..end_pos.column, "");
491        }
492    }
493
494    fn move_line_up(&mut self) {
495        if self.state.cursor.row == 0 {
496            return;
497        }
498        self.push_undo_state();
499        self.last_edit_time = None;
500        self.state
501            .lines
502            .swap(self.state.cursor.row, self.state.cursor.row - 1);
503        self.state.cursor.row -= 1;
504    }
505
506    fn move_line_down(&mut self) {
507        if self.state.cursor.row + 1 >= self.state.lines.len() {
508            return;
509        }
510        self.push_undo_state();
511        self.last_edit_time = None;
512        self.state
513            .lines
514            .swap(self.state.cursor.row, self.state.cursor.row + 1);
515        self.state.cursor.row += 1;
516    }
517
518    fn tab(&mut self) {
519        self.push_undo_state();
520        self.last_edit_time = None;
521
522        if let Some((start, end)) = self.selection_range() {
523            for row in start.row..=end.row {
524                self.state.lines[row].insert_str(0, "    ");
525            }
526            self.state.selection_anchor = Some(BufferPosition::new(start.row, start.column + 4));
527            self.state.cursor = BufferPosition::new(end.row, end.column + 4);
528        } else {
529            self.state.lines[self.state.cursor.row].insert_str(self.state.cursor.column, "    ");
530            self.state.cursor.column += 4;
531        }
532    }
533
534    fn outdent(&mut self) {
535        self.push_undo_state();
536        self.last_edit_time = None;
537
538        if let Some((start, end)) = self.selection_range() {
539            for row in start.row..=end.row {
540                let spaces_to_remove = self.state.lines[row]
541                    .chars()
542                    .take(4)
543                    .take_while(|&c| c == ' ')
544                    .count();
545                if spaces_to_remove > 0 {
546                    self.state.lines[row].replace_range(..spaces_to_remove, "");
547                }
548            }
549            let new_start_col = start.column.saturating_sub(4);
550            let new_end_col = end.column.saturating_sub(4);
551            self.state.selection_anchor = Some(BufferPosition::new(start.row, new_start_col));
552            self.state.cursor = BufferPosition::new(end.row, new_end_col);
553        } else {
554            let spaces_to_remove = self.state.lines[self.state.cursor.row]
555                .chars()
556                .take(4)
557                .take_while(|&c| c == ' ')
558                .count();
559            if spaces_to_remove > 0 {
560                self.state.lines[self.state.cursor.row].replace_range(..spaces_to_remove, "");
561                self.state.cursor.column =
562                    self.state.cursor.column.saturating_sub(spaces_to_remove);
563            }
564        }
565    }
566
567    fn select_left(&mut self) {
568        if self.state.selection_anchor.is_none() {
569            self.state.selection_anchor = Some(self.state.cursor);
570        }
571        if self.state.cursor.column > 0 {
572            let line = &self.state.lines[self.state.cursor.row];
573            let before = &line[..self.state.cursor.column];
574            if let Some(prev_char) = before.chars().last() {
575                self.state.cursor.column -= prev_char.len_utf8();
576            }
577        } else if self.state.cursor.row > 0 {
578            self.state.cursor.row -= 1;
579            self.state.cursor.column = self.state.lines[self.state.cursor.row].len();
580        }
581    }
582
583    fn select_right(&mut self) {
584        if self.state.selection_anchor.is_none() {
585            self.state.selection_anchor = Some(self.state.cursor);
586        }
587        let line_len = self.state.lines[self.state.cursor.row].len();
588        if self.state.cursor.column < line_len {
589            let after = &self.state.lines[self.state.cursor.row][self.state.cursor.column..];
590            if let Some(next_char) = after.chars().next() {
591                self.state.cursor.column += next_char.len_utf8();
592            }
593        } else if self.state.cursor.row + 1 < self.state.lines.len() {
594            self.state.cursor.row += 1;
595            self.state.cursor.column = 0;
596        }
597    }
598
599    fn select_up(&mut self) {
600        if self.state.selection_anchor.is_none() {
601            self.state.selection_anchor = Some(self.state.cursor);
602        }
603        if self.state.cursor.row > 0 {
604            self.state.cursor.row -= 1;
605            let line_len = self.state.lines[self.state.cursor.row].len();
606            self.state.cursor.column = self.state.cursor.column.min(line_len);
607        }
608    }
609
610    fn select_down(&mut self) {
611        if self.state.selection_anchor.is_none() {
612            self.state.selection_anchor = Some(self.state.cursor);
613        }
614        if self.state.cursor.row + 1 < self.state.lines.len() {
615            self.state.cursor.row += 1;
616            let line_len = self.state.lines[self.state.cursor.row].len();
617            self.state.cursor.column = self.state.cursor.column.min(line_len);
618        }
619    }
620
621    fn select_word_left(&mut self) {
622        if self.state.selection_anchor.is_none() {
623            self.state.selection_anchor = Some(self.state.cursor);
624        }
625        self.move_word_left();
626    }
627
628    fn select_word_right(&mut self) {
629        if self.state.selection_anchor.is_none() {
630            self.state.selection_anchor = Some(self.state.cursor);
631        }
632        self.move_word_right();
633    }
634
635    fn select_all(&mut self) {
636        self.state.selection_anchor = Some(BufferPosition::zero());
637        let last_row = self.state.lines.len().saturating_sub(1);
638        let last_col = self.state.lines[last_row].len();
639        self.state.cursor = BufferPosition::new(last_row, last_col);
640    }
641
642    /// Set cursor to specific position, clamping to valid bounds
643    fn set_cursor_position(&mut self, row: usize, column: usize) {
644        self.clear_selection();
645        let row = row.min(self.state.lines.len().saturating_sub(1));
646        let column = column.min(self.state.lines[row].len());
647        self.state.cursor = BufferPosition::new(row, column);
648    }
649
650    /// Start a new selection at position
651    fn start_selection(&mut self, row: usize, column: usize) {
652        let row = row.min(self.state.lines.len().saturating_sub(1));
653        let column = column.min(self.state.lines[row].len());
654        self.state.cursor = BufferPosition::new(row, column);
655        self.state.selection_anchor = Some(self.state.cursor);
656    }
657
658    /// Extend selection to position
659    fn extend_selection(&mut self, row: usize, column: usize) {
660        if self.state.selection_anchor.is_none() {
661            self.state.selection_anchor = Some(self.state.cursor);
662        }
663        let row = row.min(self.state.lines.len().saturating_sub(1));
664        let column = column.min(self.state.lines[row].len());
665        self.state.cursor = BufferPosition::new(row, column);
666    }
667
668    /// Load editor state from a file
669    pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
670        let content = fs::read_to_string(path)?;
671        self.state.lines = if content.is_empty() {
672            vec![String::new()]
673        } else {
674            content.lines().map(|s| s.to_string()).collect()
675        };
676        self.state.cursor = BufferPosition::zero();
677        self.state.selection_anchor = None;
678        self.undo_stack.clear();
679        self.redo_stack.clear();
680        self.last_edit_time = None;
681        Ok(())
682    }
683
684    /// Save editor state to a file
685    pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
686        let content = self.state.lines.join("\n");
687        if let Some(parent) = path.as_ref().parent() {
688            fs::create_dir_all(parent)?;
689        }
690        fs::write(path, content)
691    }
692
693    /// Get default config file path
694    pub fn default_file_path() -> PathBuf {
695        let home = std::env::var("HOME")
696            .or_else(|_| std::env::var("USERPROFILE"))
697            .unwrap_or_else(|_| ".".to_string());
698        PathBuf::from(home)
699            .join(".config")
700            .join("zrd")
701            .join("default.txt")
702    }
703}
704
705impl Default for EditorEngine {
706    fn default() -> Self {
707        Self::new()
708    }
709}