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#[derive(Debug, Clone)]
12pub struct DocumentBuffer {
13 document_type: DocumentType,
15 file_type: FileType,
17 file_path: Option<PathBuf>,
19 cursor: Cursor,
21 lines: Vec<String>,
23 modified: bool,
25 history: EditHistory,
27 recording: bool,
29}
30
31impl DocumentBuffer {
32 pub fn new() -> Self {
34 let document_type = DocumentType::new_markdown("").unwrap_or_else(|_| {
35 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 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 let file_type = FileType::from_path(path);
59
60 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 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 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 pub fn cursor(&self) -> &Cursor {
109 &self.cursor
110 }
111
112 pub fn cursor_mut(&mut self) -> &mut Cursor {
114 &mut self.cursor
115 }
116
117 pub fn file_path(&self) -> Option<&Path> {
119 self.file_path.as_deref()
120 }
121
122 pub fn is_modified(&self) -> bool {
124 self.modified
125 }
126
127 pub fn file_type(&self) -> &FileType {
129 &self.file_type
130 }
131
132 pub fn document_type(&self) -> &DocumentType {
134 &self.document_type
135 }
136
137 pub fn markdown(&self) -> Option<&Markdown> {
139 self.document_type.markdown_ast()
140 }
141
142 pub fn line_map(&self) -> Option<&LineMap> {
144 self.document_type.line_map()
145 }
146
147 pub fn lines(&self) -> &[String] {
149 &self.lines
150 }
151
152 pub fn line(&self, index: usize) -> Option<&str> {
154 self.lines.get(index).map(|s| s.as_str())
155 }
156
157 pub fn line_count(&self) -> usize {
159 self.lines.len()
160 }
161
162 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 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 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 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 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 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 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 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 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 let lines_to_insert: Vec<&str> = s.split('\n').collect();
306
307 if lines_to_insert.len() == 1 {
308 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 self.cursor.column += s.chars().count();
314 self.cursor.update_desired_column();
315 self.modified = true;
316 self.rebuild_document();
317 }
318 } else {
319 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 let rest = current_line.split_off(byte_idx);
324
325 current_line.push_str(lines_to_insert[0]);
327
328 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 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 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 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 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 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 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 let current_line = self.lines.remove(self.cursor.line);
430 self.cursor.line -= 1;
431 self.cursor.column = self.lines[self.cursor.line].chars().count();
433 self.lines[self.cursor.line].push_str(¤t_line);
434 self.cursor.update_desired_column();
435 self.modified = true;
436 self.rebuild_document();
437 }
438 }
439
440 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 fn rebuild_document(&mut self) {
466 let content = self.lines.join("\n");
467 let _ = self.document_type.rebuild(&content);
469 }
471
472 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 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 fn apply_reverse(&mut self, action: &EditAction) {
498 match action {
499 EditAction::InsertChar { line, column, .. } => {
500 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 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 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 let before = self.lines[*line][..first_line_byte].to_string();
529 let after =
531 self.lines[last_inserted_line_idx][last_line_rest_byte..].to_string();
532
533 for _ in 0..inserted_lines.len() - 1 {
535 if *line + 1 < self.lines.len() {
536 self.lines.remove(*line + 1);
537 }
538 }
539
540 self.lines[*line] = format!("{}{}", before, after);
542 }
543 }
544 EditAction::InsertNewline { line, column } => {
545 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 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 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 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 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 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 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 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(¤t_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 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 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 pub fn content(&self) -> String {
737 self.lines.join("\n")
738 }
739
740 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 let mut start = column.min(chars.len());
751 while start > 0 {
752 let c = chars[start - 1];
753 if c.is_alphanumeric() || c == '_' {
755 start -= 1;
756 } else {
757 break;
758 }
759 }
760 start
761 } else {
762 column
763 }
764 }
765
766 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 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 let char_pos = line[..abs_byte_pos].chars().count();
794 results.push((line_idx, char_pos));
795 search_start = abs_byte_pos + query.len();
797 }
798 }
799 results
800 }
801
802 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 for (line_idx, line) in self.lines.iter().enumerate().skip(from_line) {
816 let start_col = if line_idx == from_line {
817 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 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 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 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 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 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 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 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 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 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); }
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); 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); 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; 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 buffer.move_cursor(CursorMovement::EndOfLine);
1126 assert_eq!(buffer.cursor().column, 5);
1127
1128 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; buffer.insert_str("ありがとう");
1140
1141 assert_eq!(buffer.line(0), Some("Helloありがとう世界"));
1142 assert_eq!(buffer.cursor().column, 10); }
1144
1145 #[test]
1146 fn test_word_start_column() {
1147 let buffer = DocumentBuffer::from_string("hello world").unwrap();
1148 assert_eq!(buffer.word_start_column(0, 3), 0);
1150 assert_eq!(buffer.word_start_column(0, 5), 0);
1152 assert_eq!(buffer.word_start_column(0, 6), 6);
1154 assert_eq!(buffer.word_start_column(0, 8), 6);
1156 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 assert_eq!(buffer.word_start_column(0, 4), 0);
1165 assert_eq!(buffer.word_start_column(0, 5), 5);
1167 assert_eq!(buffer.word_start_column(0, 8), 5);
1169 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 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 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 assert_eq!(buffer.display_width_to_column(0, 0), 0);
1201 assert_eq!(buffer.display_width_to_column(0, 1), 2); assert_eq!(buffer.display_width_to_column(0, 2), 4); assert_eq!(buffer.display_width_to_column(0, 5), 10); }
1205
1206 #[test]
1207 fn test_display_width_mixed() {
1208 let buffer = DocumentBuffer::from_string("Hello世界").unwrap();
1209 assert_eq!(buffer.display_width_to_column(0, 5), 5); assert_eq!(buffer.display_width_to_column(0, 6), 7); assert_eq!(buffer.display_width_to_column(0, 7), 9); }
1214}