Skip to main content

mq_edit/document/
buffer.rs

1use std::path::{Path, PathBuf};
2
3use miette::Result;
4use mq_markdown::{Markdown, Node};
5use unicode_width::UnicodeWidthChar;
6
7use super::history::{EditAction, EditHistory};
8use super::{Cursor, CursorMovement, DocumentType, FileType, LineMap};
9
10/// Document buffer that manages content editing for any file type
11#[derive(Debug, Clone)]
12pub struct DocumentBuffer {
13    /// Document-specific data (AST for Markdown, language info for Code)
14    document_type: DocumentType,
15    /// File type classification
16    file_type: FileType,
17    /// Path to the file (if loaded from disk)
18    file_path: Option<PathBuf>,
19    /// Current cursor position
20    cursor: Cursor,
21    /// Content as lines (cached for performance)
22    lines: Vec<String>,
23    /// Whether the buffer has been modified
24    modified: bool,
25    /// Edit history for undo/redo
26    history: EditHistory,
27    /// Flag to suppress history recording during undo/redo operations
28    recording: bool,
29}
30
31impl DocumentBuffer {
32    /// Create a new empty buffer (defaults to Markdown)
33    pub fn new() -> Self {
34        let document_type = DocumentType::new_markdown("").unwrap_or_else(|_| {
35            // Create empty markdown if parsing fails
36            DocumentType::new_markdown(" ").expect("Failed to create empty markdown")
37        });
38
39        Self {
40            document_type,
41            file_type: FileType::Markdown,
42            file_path: None,
43            cursor: Cursor::new(),
44            lines: vec![String::new()],
45            modified: false,
46            history: EditHistory::new(),
47            recording: true,
48        }
49    }
50
51    /// Create buffer from file
52    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
53        let path = path.as_ref();
54        let content = std::fs::read_to_string(path)
55            .map_err(|e| miette::miette!("Failed to read file: {}", e))?;
56
57        // Detect file type from extension
58        let file_type = FileType::from_path(path);
59
60        // Create appropriate document type
61        let document_type = match &file_type {
62            FileType::Markdown => DocumentType::new_markdown(&content)?,
63            FileType::Code(lang) => DocumentType::new_code(lang.clone()),
64            FileType::PlainText => DocumentType::new_plain_text(),
65        };
66
67        let lines = Self::extract_lines(&content);
68
69        Ok(Self {
70            document_type,
71            file_type,
72            file_path: Some(path.to_path_buf()),
73            cursor: Cursor::new(),
74            lines,
75            modified: false,
76            history: EditHistory::new(),
77            recording: true,
78        })
79    }
80
81    /// Create buffer from string (defaults to Markdown)
82    pub fn from_string(content: &str) -> Result<Self> {
83        let document_type = DocumentType::new_markdown(content)?;
84        let lines = Self::extract_lines(content);
85
86        Ok(Self {
87            document_type,
88            file_type: FileType::Markdown,
89            file_path: None,
90            cursor: Cursor::new(),
91            lines,
92            modified: false,
93            history: EditHistory::new(),
94            recording: true,
95        })
96    }
97
98    /// Extract lines from content
99    fn extract_lines(content: &str) -> Vec<String> {
100        if content.is_empty() {
101            vec![String::new()]
102        } else {
103            content.lines().map(|s| s.to_string()).collect()
104        }
105    }
106
107    /// Get the current cursor position
108    pub fn cursor(&self) -> &Cursor {
109        &self.cursor
110    }
111
112    /// Get mutable cursor
113    pub fn cursor_mut(&mut self) -> &mut Cursor {
114        &mut self.cursor
115    }
116
117    /// Get the file path
118    pub fn file_path(&self) -> Option<&Path> {
119        self.file_path.as_deref()
120    }
121
122    /// Check if buffer has been modified
123    pub fn is_modified(&self) -> bool {
124        self.modified
125    }
126
127    /// Get the file type
128    pub fn file_type(&self) -> &FileType {
129        &self.file_type
130    }
131
132    /// Get the document type
133    pub fn document_type(&self) -> &DocumentType {
134        &self.document_type
135    }
136
137    /// Get the underlying Markdown AST (returns None if not a Markdown document)
138    pub fn markdown(&self) -> Option<&Markdown> {
139        self.document_type.markdown_ast()
140    }
141
142    /// Get the LineMap (returns None if not a Markdown document)
143    pub fn line_map(&self) -> Option<&LineMap> {
144        self.document_type.line_map()
145    }
146
147    /// Get all lines
148    pub fn lines(&self) -> &[String] {
149        &self.lines
150    }
151
152    /// Get a specific line
153    pub fn line(&self, index: usize) -> Option<&str> {
154        self.lines.get(index).map(|s| s.as_str())
155    }
156
157    /// Get total line count
158    pub fn line_count(&self) -> usize {
159        self.lines.len()
160    }
161
162    /// Get the node at current cursor position (only for Markdown documents)
163    pub fn node_at_cursor(&self) -> Option<&Node> {
164        match &self.document_type {
165            DocumentType::Markdown { ast, line_map } => {
166                line_map.get_node_at_line(ast, self.cursor.line)
167            }
168            _ => None,
169        }
170    }
171
172    /// Convert character position to byte position for a given line
173    /// Returns the byte offset in the line string for the given character position
174    fn char_to_byte_idx(&self, line_idx: usize, char_pos: usize) -> usize {
175        if let Some(line) = self.line(line_idx) {
176            line.char_indices()
177                .nth(char_pos)
178                .map(|(byte_idx, _)| byte_idx)
179                .unwrap_or(line.len())
180        } else {
181            0
182        }
183    }
184
185    /// Get the character count (not byte count) for a line
186    fn line_char_count(&self, line_idx: usize) -> usize {
187        self.line(line_idx).map(|s| s.chars().count()).unwrap_or(0)
188    }
189
190    /// Move cursor
191    pub fn move_cursor(&mut self, movement: CursorMovement) {
192        match movement {
193            CursorMovement::Up => {
194                if self.cursor.line > 0 {
195                    self.cursor.line -= 1;
196                    self.clamp_cursor_column();
197                }
198            }
199            CursorMovement::Down => {
200                if self.cursor.line + 1 < self.line_count() {
201                    self.cursor.line += 1;
202                    self.clamp_cursor_column();
203                }
204            }
205            CursorMovement::Left => {
206                if self.cursor.column > 0 {
207                    self.cursor.column -= 1;
208                    self.cursor.update_desired_column();
209                } else if self.cursor.line > 0 {
210                    // Move to end of previous line
211                    self.cursor.line -= 1;
212                    self.cursor.column = self.line_char_count(self.cursor.line);
213                    self.cursor.update_desired_column();
214                }
215            }
216            CursorMovement::Right => {
217                let line_len = self.line_char_count(self.cursor.line);
218                if self.cursor.column < line_len {
219                    self.cursor.column += 1;
220                    self.cursor.update_desired_column();
221                } else if self.cursor.line + 1 < self.line_count() {
222                    // Move to start of next line
223                    self.cursor.line += 1;
224                    self.cursor.column = 0;
225                    self.cursor.update_desired_column();
226                }
227            }
228            CursorMovement::StartOfLine => {
229                self.cursor.column = 0;
230                self.cursor.update_desired_column();
231            }
232            CursorMovement::EndOfLine => {
233                self.cursor.column = self.line_char_count(self.cursor.line);
234                self.cursor.update_desired_column();
235            }
236            CursorMovement::PageUp => {
237                self.cursor.line = self.cursor.line.saturating_sub(20);
238                self.clamp_cursor_column();
239            }
240            CursorMovement::PageDown => {
241                self.cursor.line = (self.cursor.line + 20).min(self.line_count().saturating_sub(1));
242                self.clamp_cursor_column();
243            }
244            CursorMovement::StartOfDocument => {
245                self.cursor.line = 0;
246                self.cursor.column = 0;
247                self.cursor.update_desired_column();
248            }
249            CursorMovement::EndOfDocument => {
250                self.cursor.line = self.line_count().saturating_sub(1);
251                self.cursor.column = self.line_char_count(self.cursor.line);
252                self.cursor.update_desired_column();
253            }
254        }
255    }
256
257    /// Clamp cursor column to line length (preserving desired column for up/down movement)
258    fn clamp_cursor_column(&mut self) {
259        let line_len = self.line_char_count(self.cursor.line);
260        self.cursor.column = self.cursor.desired_column.min(line_len);
261    }
262
263    /// Insert a character at cursor position
264    pub fn insert_char(&mut self, c: char) {
265        if self.recording {
266            let cursor_before = self.cursor;
267            self.history.push(
268                EditAction::InsertChar {
269                    line: self.cursor.line,
270                    column: self.cursor.column,
271                    c,
272                },
273                cursor_before,
274            );
275        }
276        let byte_idx = self.char_to_byte_idx(self.cursor.line, self.cursor.column);
277        if let Some(line) = self.lines.get_mut(self.cursor.line) {
278            line.insert(byte_idx, c);
279            self.cursor.column += 1;
280            self.cursor.update_desired_column();
281            self.modified = true;
282            self.rebuild_document();
283        }
284    }
285
286    /// Insert a string at cursor position (useful for IME/paste operations)
287    pub fn insert_str(&mut self, s: &str) {
288        if s.is_empty() {
289            return;
290        }
291
292        if self.recording {
293            let cursor_before = self.cursor;
294            self.history.push(
295                EditAction::InsertStr {
296                    line: self.cursor.line,
297                    column: self.cursor.column,
298                    text: s.to_string(),
299                },
300                cursor_before,
301            );
302        }
303
304        // Split the string by newlines
305        let lines_to_insert: Vec<&str> = s.split('\n').collect();
306
307        if lines_to_insert.len() == 1 {
308            // Single line insertion (most common for IME)
309            let byte_idx = self.char_to_byte_idx(self.cursor.line, self.cursor.column);
310            if let Some(line) = self.lines.get_mut(self.cursor.line) {
311                line.insert_str(byte_idx, s);
312                // Update cursor by character count, not byte count
313                self.cursor.column += s.chars().count();
314                self.cursor.update_desired_column();
315                self.modified = true;
316                self.rebuild_document();
317            }
318        } else {
319            // Multi-line insertion
320            let byte_idx = self.char_to_byte_idx(self.cursor.line, self.cursor.column);
321            if let Some(current_line) = self.lines.get_mut(self.cursor.line) {
322                // Split current line at cursor
323                let rest = current_line.split_off(byte_idx);
324
325                // Append first line of paste to current line
326                current_line.push_str(lines_to_insert[0]);
327
328                // Insert middle lines
329                for (i, &line) in lines_to_insert[1..lines_to_insert.len() - 1]
330                    .iter()
331                    .enumerate()
332                {
333                    self.lines
334                        .insert(self.cursor.line + 1 + i, line.to_string());
335                }
336
337                // Insert last line and append rest of original line
338                let last_line_idx = self.cursor.line + lines_to_insert.len() - 1;
339                let mut last_line = lines_to_insert.last().unwrap().to_string();
340                let new_cursor_column = last_line.chars().count();
341                last_line.push_str(&rest);
342                self.lines.insert(last_line_idx, last_line);
343
344                // Update cursor position (use character count)
345                self.cursor.line = last_line_idx;
346                self.cursor.column = new_cursor_column;
347                self.cursor.update_desired_column();
348                self.modified = true;
349                self.rebuild_document();
350            }
351        }
352    }
353
354    /// Delete a range of characters on the current line (from start_col to current cursor position)
355    /// Moves cursor to start_col after deletion
356    pub fn delete_range(&mut self, start_col: usize) {
357        let end_col = self.cursor.column;
358        if start_col >= end_col {
359            return;
360        }
361
362        let line_idx = self.cursor.line;
363        if line_idx >= self.lines.len() {
364            return;
365        }
366
367        // Calculate byte indices first (before mutable borrow)
368        let start_byte = self.char_to_byte_idx(line_idx, start_col);
369        let end_byte = self.char_to_byte_idx(line_idx, end_col);
370
371        if end_byte <= self.lines[line_idx].len() {
372            if self.recording {
373                let cursor_before = self.cursor;
374                let deleted = self.lines[line_idx][start_byte..end_byte].to_string();
375                self.history.push(
376                    EditAction::DeleteRange {
377                        line: line_idx,
378                        start_col,
379                        deleted,
380                    },
381                    cursor_before,
382                );
383            }
384            self.lines[line_idx].replace_range(start_byte..end_byte, "");
385            self.cursor.column = start_col;
386            self.cursor.update_desired_column();
387            self.modified = true;
388            self.rebuild_document();
389        }
390    }
391
392    /// Delete character at cursor (backspace)
393    pub fn delete_char(&mut self) {
394        if self.cursor.column > 0 {
395            let cursor_before = self.cursor;
396            self.cursor.column -= 1;
397            let byte_idx = self.char_to_byte_idx(self.cursor.line, self.cursor.column);
398            if let Some(line) = self.lines.get_mut(self.cursor.line) {
399                // Find the character at byte_idx and remove it
400                if let Some((_, ch)) = line[byte_idx..].char_indices().next() {
401                    if self.recording {
402                        self.history.push(
403                            EditAction::DeleteChar {
404                                line: self.cursor.line,
405                                column: self.cursor.column,
406                                deleted: ch,
407                            },
408                            cursor_before,
409                        );
410                    }
411                    line.replace_range(byte_idx..byte_idx + ch.len_utf8(), "");
412                }
413                self.cursor.update_desired_column();
414                self.modified = true;
415                self.rebuild_document();
416            }
417        } else if self.cursor.line > 0 {
418            if self.recording {
419                let cursor_before = self.cursor;
420                self.history.push(
421                    EditAction::JoinLines {
422                        line: self.cursor.line,
423                        column: self.lines[self.cursor.line - 1].chars().count(),
424                    },
425                    cursor_before,
426                );
427            }
428            // Join with previous line
429            let current_line = self.lines.remove(self.cursor.line);
430            self.cursor.line -= 1;
431            // Use character count, not byte length
432            self.cursor.column = self.lines[self.cursor.line].chars().count();
433            self.lines[self.cursor.line].push_str(&current_line);
434            self.cursor.update_desired_column();
435            self.modified = true;
436            self.rebuild_document();
437        }
438    }
439
440    /// Insert newline at cursor
441    pub fn insert_newline(&mut self) {
442        if self.recording {
443            let cursor_before = self.cursor;
444            self.history.push(
445                EditAction::InsertNewline {
446                    line: self.cursor.line,
447                    column: self.cursor.column,
448                },
449                cursor_before,
450            );
451        }
452        let byte_idx = self.char_to_byte_idx(self.cursor.line, self.cursor.column);
453        if let Some(line) = self.lines.get_mut(self.cursor.line) {
454            let rest = line.split_off(byte_idx);
455            self.cursor.line += 1;
456            self.lines.insert(self.cursor.line, rest);
457            self.cursor.column = 0;
458            self.cursor.update_desired_column();
459            self.modified = true;
460            self.rebuild_document();
461        }
462    }
463
464    /// Rebuild document-specific structures from current lines content
465    fn rebuild_document(&mut self) {
466        let content = self.lines.join("\n");
467        // Rebuild document type (for Markdown, this reparses the AST)
468        let _ = self.document_type.rebuild(&content);
469        // If rebuilding fails, keep the old document state (better than crashing)
470    }
471
472    /// Undo the last edit action
473    pub fn undo(&mut self) {
474        if let Some(entry) = self.history.undo() {
475            self.recording = false;
476            self.apply_reverse(&entry.action);
477            self.cursor = entry.cursor_before;
478            self.cursor.update_desired_column();
479            self.modified = true;
480            self.rebuild_document();
481            self.recording = true;
482        }
483    }
484
485    /// Redo the last undone edit action
486    pub fn redo(&mut self) {
487        if let Some(entry) = self.history.redo() {
488            self.recording = false;
489            self.apply_forward(&entry.action);
490            self.modified = true;
491            self.rebuild_document();
492            self.recording = true;
493        }
494    }
495
496    /// Apply the reverse of an edit action (for undo)
497    fn apply_reverse(&mut self, action: &EditAction) {
498        match action {
499            EditAction::InsertChar { line, column, .. } => {
500                // Reverse of insert char: delete the char at that position
501                let byte_idx = self.char_to_byte_idx(*line, *column);
502                if let Some(line_content) = self.lines.get_mut(*line)
503                    && let Some((_, ch)) = line_content[byte_idx..].char_indices().next()
504                {
505                    line_content.replace_range(byte_idx..byte_idx + ch.len_utf8(), "");
506                }
507            }
508            EditAction::InsertStr { line, column, text } => {
509                // Reverse of insert str: remove the inserted text
510                let inserted_lines: Vec<&str> = text.split('\n').collect();
511                if inserted_lines.len() == 1 {
512                    let byte_idx = self.char_to_byte_idx(*line, *column);
513                    if let Some(line_content) = self.lines.get_mut(*line) {
514                        let end_byte = byte_idx + text.len();
515                        if end_byte <= line_content.len() {
516                            line_content.replace_range(byte_idx..end_byte, "");
517                        }
518                    }
519                } else {
520                    // Multi-line: reconstruct the original line
521                    let first_line_byte = self.char_to_byte_idx(*line, *column);
522                    let last_inserted_line_idx = *line + inserted_lines.len() - 1;
523                    let last_inserted_chars = inserted_lines.last().unwrap().chars().count();
524                    let last_line_rest_byte =
525                        self.char_to_byte_idx(last_inserted_line_idx, last_inserted_chars);
526
527                    // Get the part before insertion on the first line
528                    let before = self.lines[*line][..first_line_byte].to_string();
529                    // Get the part after insertion on the last line
530                    let after =
531                        self.lines[last_inserted_line_idx][last_line_rest_byte..].to_string();
532
533                    // Remove the inserted lines (keep the first line)
534                    for _ in 0..inserted_lines.len() - 1 {
535                        if *line + 1 < self.lines.len() {
536                            self.lines.remove(*line + 1);
537                        }
538                    }
539
540                    // Reconstruct the original line
541                    self.lines[*line] = format!("{}{}", before, after);
542                }
543            }
544            EditAction::InsertNewline { line, column } => {
545                // Reverse of insert newline: join line+1 back into line at column
546                if *line + 1 < self.lines.len() {
547                    let next_line = self.lines.remove(*line + 1);
548                    let byte_idx = self.char_to_byte_idx(*line, *column);
549                    if let Some(line_content) = self.lines.get_mut(*line) {
550                        // Truncate at column and append next line
551                        line_content.truncate(byte_idx);
552                        line_content.push_str(&next_line);
553                    }
554                }
555            }
556            EditAction::DeleteChar {
557                line,
558                column,
559                deleted,
560            } => {
561                // Reverse of delete char: re-insert the deleted char
562                let byte_idx = self.char_to_byte_idx(*line, *column);
563                if let Some(line_content) = self.lines.get_mut(*line) {
564                    line_content.insert(byte_idx, *deleted);
565                }
566            }
567            EditAction::JoinLines { line, column } => {
568                // Reverse of join lines: split the line back
569                let byte_idx = self.char_to_byte_idx(*line - 1, *column);
570                if let Some(prev_line) = self.lines.get_mut(*line - 1) {
571                    let rest = prev_line.split_off(byte_idx);
572                    self.lines.insert(*line, rest);
573                }
574            }
575            EditAction::DeleteRange {
576                line,
577                start_col,
578                deleted,
579            } => {
580                // Reverse of delete range: re-insert the deleted text
581                let byte_idx = self.char_to_byte_idx(*line, *start_col);
582                if let Some(line_content) = self.lines.get_mut(*line) {
583                    line_content.insert_str(byte_idx, deleted);
584                }
585            }
586            EditAction::ReplaceAt {
587                line,
588                column,
589                old_text,
590                new_text,
591            } => {
592                // Reverse of replace: replace new_text back with old_text
593                let byte_idx = self.char_to_byte_idx(*line, *column);
594                if let Some(line_content) = self.lines.get_mut(*line) {
595                    let end_byte = byte_idx + new_text.len();
596                    if end_byte <= line_content.len() {
597                        line_content.replace_range(byte_idx..end_byte, old_text);
598                    }
599                }
600            }
601        }
602    }
603
604    /// Apply an edit action forward (for redo)
605    fn apply_forward(&mut self, action: &EditAction) {
606        match action {
607            EditAction::InsertChar { line, column, c } => {
608                let byte_idx = self.char_to_byte_idx(*line, *column);
609                if let Some(line_content) = self.lines.get_mut(*line) {
610                    line_content.insert(byte_idx, *c);
611                }
612                self.cursor.line = *line;
613                self.cursor.column = *column + 1;
614                self.cursor.update_desired_column();
615            }
616            EditAction::InsertStr { line, column, text } => {
617                // Re-apply the insert_str logic
618                let inserted_lines: Vec<&str> = text.split('\n').collect();
619                if inserted_lines.len() == 1 {
620                    let byte_idx = self.char_to_byte_idx(*line, *column);
621                    if let Some(line_content) = self.lines.get_mut(*line) {
622                        line_content.insert_str(byte_idx, text);
623                    }
624                    self.cursor.line = *line;
625                    self.cursor.column = *column + text.chars().count();
626                } else {
627                    let byte_idx = self.char_to_byte_idx(*line, *column);
628                    if let Some(current_line) = self.lines.get_mut(*line) {
629                        let rest = current_line.split_off(byte_idx);
630                        current_line.push_str(inserted_lines[0]);
631
632                        for (i, &il) in inserted_lines[1..inserted_lines.len() - 1]
633                            .iter()
634                            .enumerate()
635                        {
636                            self.lines.insert(*line + 1 + i, il.to_string());
637                        }
638
639                        let last_line_idx = *line + inserted_lines.len() - 1;
640                        let mut last_line = inserted_lines.last().unwrap().to_string();
641                        let new_cursor_column = last_line.chars().count();
642                        last_line.push_str(&rest);
643                        self.lines.insert(last_line_idx, last_line);
644
645                        self.cursor.line = last_line_idx;
646                        self.cursor.column = new_cursor_column;
647                    }
648                }
649                self.cursor.update_desired_column();
650            }
651            EditAction::InsertNewline { line, column } => {
652                let byte_idx = self.char_to_byte_idx(*line, *column);
653                if let Some(line_content) = self.lines.get_mut(*line) {
654                    let rest = line_content.split_off(byte_idx);
655                    self.lines.insert(*line + 1, rest);
656                }
657                self.cursor.line = *line + 1;
658                self.cursor.column = 0;
659                self.cursor.update_desired_column();
660            }
661            EditAction::DeleteChar {
662                line,
663                column,
664                deleted,
665            } => {
666                let byte_idx = self.char_to_byte_idx(*line, *column);
667                if let Some(line_content) = self.lines.get_mut(*line) {
668                    line_content.replace_range(byte_idx..byte_idx + deleted.len_utf8(), "");
669                }
670                self.cursor.line = *line;
671                self.cursor.column = *column;
672                self.cursor.update_desired_column();
673            }
674            EditAction::JoinLines { line, column } => {
675                let current_line = self.lines.remove(*line);
676                self.lines[*line - 1].push_str(&current_line);
677                self.cursor.line = *line - 1;
678                self.cursor.column = *column;
679                self.cursor.update_desired_column();
680            }
681            EditAction::DeleteRange {
682                line,
683                start_col,
684                deleted,
685            } => {
686                let start_byte = self.char_to_byte_idx(*line, *start_col);
687                let end_byte = start_byte + deleted.len();
688                if end_byte <= self.lines[*line].len() {
689                    self.lines[*line].replace_range(start_byte..end_byte, "");
690                }
691                self.cursor.line = *line;
692                self.cursor.column = *start_col;
693                self.cursor.update_desired_column();
694            }
695            EditAction::ReplaceAt {
696                line,
697                column,
698                old_text,
699                new_text,
700            } => {
701                let byte_idx = self.char_to_byte_idx(*line, *column);
702                if let Some(line_content) = self.lines.get_mut(*line) {
703                    let end_byte = byte_idx + old_text.len();
704                    if end_byte <= line_content.len() {
705                        line_content.replace_range(byte_idx..end_byte, new_text);
706                    }
707                }
708            }
709        }
710    }
711
712    /// Save buffer to file
713    pub fn save(&mut self) -> Result<()> {
714        if let Some(path) = &self.file_path {
715            let content = self.lines.join("\n");
716            std::fs::write(path, content)
717                .map_err(|e| miette::miette!("Failed to write file: {}", e))?;
718            self.modified = false;
719            Ok(())
720        } else {
721            Err(miette::miette!("No file path set"))
722        }
723    }
724
725    /// Save buffer to a specific file
726    pub fn save_as(&mut self, path: impl AsRef<Path>) -> Result<()> {
727        let content = self.lines.join("\n");
728        std::fs::write(path.as_ref(), content)
729            .map_err(|e| miette::miette!("Failed to write file: {}", e))?;
730        self.file_path = Some(path.as_ref().to_path_buf());
731        self.modified = false;
732        Ok(())
733    }
734
735    /// Get buffer content as string
736    pub fn content(&self) -> String {
737        self.lines.join("\n")
738    }
739
740    /// Get the start column of the word at cursor position
741    /// This is useful for code completion to determine how much text to replace
742    pub fn word_start_column(&self, line: usize, column: usize) -> usize {
743        if let Some(line_content) = self.line(line) {
744            let chars: Vec<char> = line_content.chars().collect();
745            if column == 0 || chars.is_empty() {
746                return column;
747            }
748
749            // Search backwards from current position to find word start
750            let mut start = column.min(chars.len());
751            while start > 0 {
752                let c = chars[start - 1];
753                // Word characters: alphanumeric and underscore
754                if c.is_alphanumeric() || c == '_' {
755                    start -= 1;
756                } else {
757                    break;
758                }
759            }
760            start
761        } else {
762            column
763        }
764    }
765
766    /// Calculate the display width from the start of a line to a given column position
767    /// This accounts for wide characters (e.g., CJK characters, emoji) that take 2 columns
768    pub fn display_width_to_column(&self, line: usize, column: usize) -> usize {
769        if let Some(line_content) = self.line(line) {
770            line_content
771                .chars()
772                .take(column)
773                .map(|c| c.width().unwrap_or(0))
774                .sum()
775        } else {
776            0
777        }
778    }
779
780    /// Find all occurrences of a query string in the buffer
781    /// Returns a list of (line, column) positions (0-indexed)
782    pub fn find_all(&self, query: &str) -> Vec<(usize, usize)> {
783        if query.is_empty() {
784            return Vec::new();
785        }
786
787        let mut results = Vec::new();
788        for (line_idx, line) in self.lines.iter().enumerate() {
789            let mut search_start = 0;
790            while let Some(byte_pos) = line[search_start..].find(query) {
791                let abs_byte_pos = search_start + byte_pos;
792                // Convert byte position to character position
793                let char_pos = line[..abs_byte_pos].chars().count();
794                results.push((line_idx, char_pos));
795                // Move past this match
796                search_start = abs_byte_pos + query.len();
797            }
798        }
799        results
800    }
801
802    /// Find the next occurrence of a query string starting from a position
803    /// Returns the (line, column) position or None if not found
804    pub fn find_next(
805        &self,
806        query: &str,
807        from_line: usize,
808        from_column: usize,
809    ) -> Option<(usize, usize)> {
810        if query.is_empty() {
811            return None;
812        }
813
814        // Search from current line
815        for (line_idx, line) in self.lines.iter().enumerate().skip(from_line) {
816            let start_col = if line_idx == from_line {
817                // Start after current column position
818                self.char_to_byte_idx(line_idx, from_column + 1)
819            } else {
820                0
821            };
822
823            if start_col < line.len()
824                && let Some(byte_pos) = line[start_col..].find(query)
825            {
826                let abs_byte_pos = start_col + byte_pos;
827                let char_pos = line[..abs_byte_pos].chars().count();
828                return Some((line_idx, char_pos));
829            }
830        }
831
832        // Wrap around to beginning
833        for (line_idx, line) in self.lines.iter().enumerate().take(from_line + 1) {
834            let end_col = if line_idx == from_line {
835                self.char_to_byte_idx(line_idx, from_column)
836            } else {
837                line.len()
838            };
839
840            if let Some(byte_pos) = line[..end_col].find(query) {
841                let char_pos = line[..byte_pos].chars().count();
842                return Some((line_idx, char_pos));
843            }
844        }
845
846        None
847    }
848
849    /// Find the previous occurrence of a query string starting from a position
850    /// Returns the (line, column) position or None if not found
851    pub fn find_prev(
852        &self,
853        query: &str,
854        from_line: usize,
855        from_column: usize,
856    ) -> Option<(usize, usize)> {
857        if query.is_empty() {
858            return None;
859        }
860
861        // Search backwards from current line
862        for line_idx in (0..=from_line).rev() {
863            let line = &self.lines[line_idx];
864            let end_col = if line_idx == from_line {
865                self.char_to_byte_idx(line_idx, from_column)
866            } else {
867                line.len()
868            };
869
870            // Find the last occurrence before end_col
871            if let Some(byte_pos) = line[..end_col].rfind(query) {
872                let char_pos = line[..byte_pos].chars().count();
873                return Some((line_idx, char_pos));
874            }
875        }
876
877        // Wrap around to end
878        for line_idx in (from_line..self.lines.len()).rev() {
879            let line = &self.lines[line_idx];
880            let start_col = if line_idx == from_line {
881                self.char_to_byte_idx(line_idx, from_column + 1)
882            } else {
883                0
884            };
885
886            if start_col < line.len()
887                && let Some(byte_pos) = line[start_col..].rfind(query)
888            {
889                let abs_byte_pos = start_col + byte_pos;
890                let char_pos = line[..abs_byte_pos].chars().count();
891                return Some((line_idx, char_pos));
892            }
893        }
894
895        None
896    }
897
898    /// Replace text at a specific position
899    /// Returns true if replacement was successful
900    pub fn replace_at(
901        &mut self,
902        line: usize,
903        column: usize,
904        old_text: &str,
905        new_text: &str,
906    ) -> bool {
907        if line >= self.lines.len() {
908            return false;
909        }
910
911        let byte_idx = self.char_to_byte_idx(line, column);
912        let line_content = &self.lines[line];
913
914        // Verify the old text exists at this position
915        if byte_idx + old_text.len() > line_content.len() {
916            return false;
917        }
918        if &line_content[byte_idx..byte_idx + old_text.len()] != old_text {
919            return false;
920        }
921
922        if self.recording {
923            let cursor_before = self.cursor;
924            self.history.push(
925                EditAction::ReplaceAt {
926                    line,
927                    column,
928                    old_text: old_text.to_string(),
929                    new_text: new_text.to_string(),
930                },
931                cursor_before,
932            );
933        }
934
935        // Perform the replacement
936        let new_line = format!(
937            "{}{}{}",
938            &line_content[..byte_idx],
939            new_text,
940            &line_content[byte_idx + old_text.len()..]
941        );
942        self.lines[line] = new_line;
943        self.modified = true;
944        self.rebuild_document();
945        true
946    }
947
948    /// Replace all occurrences of old_text with new_text
949    /// Returns the number of replacements made
950    pub fn replace_all(&mut self, old_text: &str, new_text: &str) -> usize {
951        if old_text.is_empty() {
952            return 0;
953        }
954
955        let mut count = 0;
956        for line_idx in 0..self.lines.len() {
957            let line = &self.lines[line_idx];
958            if line.contains(old_text) {
959                let new_line = line.replace(old_text, new_text);
960                count += line.matches(old_text).count();
961                self.lines[line_idx] = new_line;
962            }
963        }
964
965        if count > 0 {
966            self.modified = true;
967            self.rebuild_document();
968        }
969
970        count
971    }
972}
973
974impl Default for DocumentBuffer {
975    fn default() -> Self {
976        Self::new()
977    }
978}
979
980#[cfg(test)]
981mod tests {
982    use super::*;
983
984    #[test]
985    fn test_empty_buffer() {
986        let buffer = DocumentBuffer::new();
987        assert_eq!(buffer.line_count(), 1);
988        assert_eq!(buffer.cursor().line, 0);
989        assert_eq!(buffer.cursor().column, 0);
990        assert!(!buffer.is_modified());
991    }
992
993    #[test]
994    fn test_from_string() {
995        let buffer = DocumentBuffer::from_string("# Hello\n\nWorld").unwrap();
996        assert_eq!(buffer.line_count(), 3);
997        assert_eq!(buffer.line(0), Some("# Hello"));
998        assert_eq!(buffer.line(1), Some(""));
999        assert_eq!(buffer.line(2), Some("World"));
1000    }
1001
1002    #[test]
1003    fn test_cursor_movement() {
1004        let mut buffer = DocumentBuffer::from_string("Line 1\nLine 2\nLine 3").unwrap();
1005
1006        buffer.move_cursor(CursorMovement::Down);
1007        assert_eq!(buffer.cursor().line, 1);
1008
1009        buffer.move_cursor(CursorMovement::Right);
1010        assert_eq!(buffer.cursor().column, 1);
1011
1012        buffer.move_cursor(CursorMovement::EndOfLine);
1013        assert_eq!(buffer.cursor().column, 6); // "Line 2".len()
1014    }
1015
1016    #[test]
1017    fn test_insert_char() {
1018        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1019        buffer.cursor_mut().column = 5;
1020        buffer.insert_char('!');
1021
1022        assert_eq!(buffer.line(0), Some("Hello!"));
1023        assert_eq!(buffer.cursor().column, 6);
1024        assert!(buffer.is_modified());
1025    }
1026
1027    #[test]
1028    fn test_delete_char() {
1029        let mut buffer = DocumentBuffer::from_string("Hello!").unwrap();
1030        buffer.cursor_mut().column = 6;
1031        buffer.delete_char();
1032
1033        assert_eq!(buffer.line(0), Some("Hello"));
1034        assert_eq!(buffer.cursor().column, 5);
1035        assert!(buffer.is_modified());
1036    }
1037
1038    #[test]
1039    fn test_insert_newline() {
1040        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1041        buffer.cursor_mut().column = 2;
1042        buffer.insert_newline();
1043
1044        assert_eq!(buffer.line_count(), 2);
1045        assert_eq!(buffer.line(0), Some("He"));
1046        assert_eq!(buffer.line(1), Some("llo"));
1047        assert_eq!(buffer.cursor().line, 1);
1048        assert_eq!(buffer.cursor().column, 0);
1049    }
1050
1051    #[test]
1052    fn test_insert_str_single_line() {
1053        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1054        buffer.cursor_mut().column = 5;
1055        buffer.insert_str(" World");
1056
1057        assert_eq!(buffer.line(0), Some("Hello World"));
1058        assert_eq!(buffer.cursor().column, 11);
1059        assert!(buffer.is_modified());
1060    }
1061
1062    #[test]
1063    fn test_insert_str_empty() {
1064        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1065        buffer.cursor_mut().column = 5;
1066        buffer.insert_str("");
1067
1068        assert_eq!(buffer.line(0), Some("Hello"));
1069        assert_eq!(buffer.cursor().column, 5);
1070    }
1071
1072    #[test]
1073    fn test_insert_str_multi_line() {
1074        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1075        buffer.cursor_mut().column = 2;
1076        buffer.insert_str("XXX\nYYY\nZZZ");
1077
1078        assert_eq!(buffer.line_count(), 3);
1079        assert_eq!(buffer.line(0), Some("HeXXX"));
1080        assert_eq!(buffer.line(1), Some("YYY"));
1081        assert_eq!(buffer.line(2), Some("ZZZllo"));
1082        assert_eq!(buffer.cursor().line, 2);
1083        assert_eq!(buffer.cursor().column, 3);
1084        assert!(buffer.is_modified());
1085    }
1086
1087    #[test]
1088    fn test_insert_str_japanese() {
1089        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1090        buffer.cursor_mut().column = 5;
1091        buffer.insert_str("こんにちは");
1092
1093        assert_eq!(buffer.line(0), Some("Helloこんにちは"));
1094        assert_eq!(buffer.cursor().column, 10); // 5 + "こんにちは".chars().count()
1095        assert!(buffer.is_modified());
1096    }
1097
1098    #[test]
1099    fn test_insert_char_japanese() {
1100        let mut buffer = DocumentBuffer::from_string("Hello").unwrap();
1101        buffer.cursor_mut().column = 5;
1102        buffer.insert_char('あ');
1103        buffer.insert_char('い');
1104
1105        assert_eq!(buffer.line(0), Some("Helloあい"));
1106        assert_eq!(buffer.cursor().column, 7); // 5 + 2
1107        assert!(buffer.is_modified());
1108    }
1109
1110    #[test]
1111    fn test_delete_char_japanese() {
1112        let mut buffer = DocumentBuffer::from_string("こんにちは世界").unwrap();
1113        buffer.cursor_mut().column = 7; // After "世界"
1114        buffer.delete_char();
1115
1116        assert_eq!(buffer.line(0), Some("こんにちは世"));
1117        assert_eq!(buffer.cursor().column, 6);
1118    }
1119
1120    #[test]
1121    fn test_cursor_movement_japanese() {
1122        let mut buffer = DocumentBuffer::from_string("こんにちは").unwrap();
1123
1124        // Move to end
1125        buffer.move_cursor(CursorMovement::EndOfLine);
1126        assert_eq!(buffer.cursor().column, 5);
1127
1128        // Move left through Japanese characters
1129        buffer.move_cursor(CursorMovement::Left);
1130        assert_eq!(buffer.cursor().column, 4);
1131        buffer.move_cursor(CursorMovement::Left);
1132        assert_eq!(buffer.cursor().column, 3);
1133    }
1134
1135    #[test]
1136    fn test_insert_str_mixed_content() {
1137        let mut buffer = DocumentBuffer::from_string("Hello世界").unwrap();
1138        buffer.cursor_mut().column = 5; // After "Hello"
1139        buffer.insert_str("ありがとう");
1140
1141        assert_eq!(buffer.line(0), Some("Helloありがとう世界"));
1142        assert_eq!(buffer.cursor().column, 10); // 5 + 5
1143    }
1144
1145    #[test]
1146    fn test_word_start_column() {
1147        let buffer = DocumentBuffer::from_string("hello world").unwrap();
1148        // In the middle of "hello"
1149        assert_eq!(buffer.word_start_column(0, 3), 0);
1150        // At the end of "hello"
1151        assert_eq!(buffer.word_start_column(0, 5), 0);
1152        // At the space
1153        assert_eq!(buffer.word_start_column(0, 6), 6);
1154        // In the middle of "world"
1155        assert_eq!(buffer.word_start_column(0, 8), 6);
1156        // At the end of "world"
1157        assert_eq!(buffer.word_start_column(0, 11), 6);
1158    }
1159
1160    #[test]
1161    fn test_word_start_column_with_symbols() {
1162        let buffer = DocumentBuffer::from_string("self.method()").unwrap();
1163        // After "self"
1164        assert_eq!(buffer.word_start_column(0, 4), 0);
1165        // At the dot
1166        assert_eq!(buffer.word_start_column(0, 5), 5);
1167        // In "method"
1168        assert_eq!(buffer.word_start_column(0, 8), 5);
1169        // After "method"
1170        assert_eq!(buffer.word_start_column(0, 11), 5);
1171    }
1172
1173    #[test]
1174    fn test_word_start_column_with_underscore() {
1175        let buffer = DocumentBuffer::from_string("my_variable = 10").unwrap();
1176        // In "my_variable"
1177        assert_eq!(buffer.word_start_column(0, 5), 0);
1178        assert_eq!(buffer.word_start_column(0, 11), 0);
1179    }
1180
1181    #[test]
1182    fn test_word_start_column_at_start() {
1183        let buffer = DocumentBuffer::from_string("hello").unwrap();
1184        assert_eq!(buffer.word_start_column(0, 0), 0);
1185    }
1186
1187    #[test]
1188    fn test_display_width_ascii() {
1189        let buffer = DocumentBuffer::from_string("hello world").unwrap();
1190        // ASCII characters have width 1
1191        assert_eq!(buffer.display_width_to_column(0, 0), 0);
1192        assert_eq!(buffer.display_width_to_column(0, 5), 5);
1193        assert_eq!(buffer.display_width_to_column(0, 11), 11);
1194    }
1195
1196    #[test]
1197    fn test_display_width_japanese() {
1198        let buffer = DocumentBuffer::from_string("こんにちは").unwrap();
1199        // Japanese characters typically have width 2
1200        assert_eq!(buffer.display_width_to_column(0, 0), 0);
1201        assert_eq!(buffer.display_width_to_column(0, 1), 2); // After 'こ'
1202        assert_eq!(buffer.display_width_to_column(0, 2), 4); // After 'ん'
1203        assert_eq!(buffer.display_width_to_column(0, 5), 10); // After 'は'
1204    }
1205
1206    #[test]
1207    fn test_display_width_mixed() {
1208        let buffer = DocumentBuffer::from_string("Hello世界").unwrap();
1209        // 'H' 'e' 'l' 'l' 'o' = 5 columns, '世' '界' = 4 columns
1210        assert_eq!(buffer.display_width_to_column(0, 5), 5); // After "Hello"
1211        assert_eq!(buffer.display_width_to_column(0, 6), 7); // After "Hello世"
1212        assert_eq!(buffer.display_width_to_column(0, 7), 9); // After "Hello世界"
1213    }
1214}