1use crate::{
3 config_handle,
4 dot::{find::find_forward_wrapping, Cur, Dot, Range, TextObject},
5 editor::Action,
6 exec::{Addr, Address, IterBoundedChars},
7 fsys::InputFilter,
8 key::Input,
9 lsp::Coords,
10 ts::{LineIter, TsState},
11 util::normalize_line_endings,
12 MAX_NAME_LEN, UNNAMED_BUFFER,
13};
14use ad_event::Source;
15use std::{
16 cmp::min,
17 fs,
18 io::{self, ErrorKind},
19 path::{Path, PathBuf},
20 time::SystemTime,
21};
22use tracing::{debug, error};
23
24mod buffers;
25mod edit;
26mod internal;
27
28use edit::{Edit, EditLog, Kind, Txt};
29pub use internal::{Chars, GapBuffer, IdxChars, Slice, SliceIter};
30
31pub(crate) use buffers::{BufferId, Buffers};
32
33pub(crate) const DEFAULT_OUTPUT_BUFFER: &str = "+output";
34const HTTPS: &str = "https://";
35const HTTP: &str = "http://";
36
37#[derive(Debug, Clone, PartialEq, Eq)]
40pub(crate) enum ActionOutcome {
41 SetClipboard(String),
42 SetStatusMessage(String),
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub(crate) enum BufferKind {
48 File(PathBuf),
50 Directory(PathBuf),
52 Virtual(String),
54 Output(String),
56 Unnamed,
58 MiniBuffer,
60}
61
62impl Default for BufferKind {
63 fn default() -> Self {
64 Self::Unnamed
65 }
66}
67
68impl BufferKind {
69 fn display_name(&self) -> String {
70 match self {
71 BufferKind::File(p) => p.display().to_string(),
72 BufferKind::Directory(p) => p.display().to_string(),
73 BufferKind::Virtual(s) => s.clone(),
74 BufferKind::Output(s) => s.clone(),
75 BufferKind::Unnamed => UNNAMED_BUFFER.to_string(),
76 BufferKind::MiniBuffer => "".to_string(),
77 }
78 }
79
80 fn path(&self) -> Option<&Path> {
82 match &self {
83 BufferKind::File(p) => Some(p.as_ref()),
84 _ => None,
85 }
86 }
87
88 fn dir(&self) -> Option<&Path> {
90 match &self {
91 BufferKind::File(p) => p.parent(),
92 BufferKind::Directory(p) => Some(p.as_ref()),
93 BufferKind::Output(s) => Path::new(s).parent(),
94 _ => None,
95 }
96 }
97
98 pub(crate) fn is_file(&self) -> bool {
99 matches!(self, Self::File(_))
100 }
101
102 pub(crate) fn is_dir(&self) -> bool {
103 matches!(self, Self::Directory(_))
104 }
105
106 pub fn output_file_key(&self, cwd: &Path) -> String {
109 let path = self.dir().unwrap_or(cwd);
110 format!("{}/{DEFAULT_OUTPUT_BUFFER}", path.display())
111 }
112
113 fn try_kind_and_content_from_path(path: PathBuf) -> io::Result<(Self, String)> {
114 match path.metadata() {
115 Ok(m) if m.is_dir() => {
116 let mut raw_entries = Vec::new();
117 for entry in path.read_dir()? {
118 let p = entry?.path();
119 let mut s = p.strip_prefix(&path).unwrap_or(&p).display().to_string();
120 if p.metadata().map(|m| m.is_dir()).unwrap_or_default() {
121 s.push('/');
122 }
123 raw_entries.push(s);
124 }
125 raw_entries.sort_unstable();
126
127 let mut raw = format!("{}\n\n..\n", path.display());
128 raw.push_str(&raw_entries.join("\n"));
129
130 Ok((Self::Directory(path), raw))
131 }
132
133 _ => {
134 let mut raw = match fs::read_to_string(&path) {
135 Ok(contents) => normalize_line_endings(contents),
136 Err(e) if e.kind() == ErrorKind::NotFound => String::new(),
137 Err(e) => return Err(e),
138 };
139
140 if raw.ends_with('\n') {
141 raw.pop();
142 }
143
144 Ok((Self::File(path), raw))
145 }
146 }
147 }
148}
149
150#[derive(Debug)]
152pub struct Buffer {
153 pub(crate) id: usize,
154 pub(crate) kind: BufferKind,
155 pub(crate) dot: Dot,
156 pub(crate) xdot: Dot,
157 pub(crate) txt: GapBuffer,
158 pub(crate) cached_rx: usize,
159 pub(crate) last_save: SystemTime,
160 pub(crate) dirty: bool,
161 pub(crate) input_filter: Option<InputFilter>,
162 pub(crate) ts_state: Option<TsState>,
163 edit_log: EditLog,
164}
165
166impl Buffer {
167 pub fn new_from_canonical_file_path(id: usize, path: PathBuf) -> io::Result<Self> {
169 let (kind, raw) = BufferKind::try_kind_and_content_from_path(path.clone())?;
170 let mut b = Self {
171 id,
172 kind,
173 dot: Dot::default(),
174 xdot: Dot::default(),
175 txt: GapBuffer::from(raw),
176 cached_rx: 0,
177 last_save: SystemTime::now(),
178 dirty: false,
179 edit_log: EditLog::default(),
180 input_filter: None,
181 ts_state: None,
182 };
183
184 let cfg = config_handle!();
185 if let Some(lang) = cfg.ts_lang_for_buffer(&b) {
186 match TsState::try_new(
187 lang,
188 &cfg.tree_sitter.parser_dir,
189 &cfg.tree_sitter.syntax_query_dir,
190 &b.txt,
191 ) {
192 Ok(state) => b.ts_state = Some(state),
193 Err(msg) => error!("unable to initialise tree-sitter: {msg}"),
194 }
195 }
196
197 Ok(b)
198 }
199
200 pub(crate) fn state_changed_on_disk(&self) -> Result<bool, String> {
201 fn inner(p: &Path, last_save: SystemTime) -> io::Result<bool> {
202 let modified = p.metadata()?.modified()?;
203 Ok(modified > last_save)
204 }
205
206 let path = match &self.kind {
207 BufferKind::File(p) => p,
208 _ => return Ok(false),
209 };
210
211 match inner(path, self.last_save) {
212 Ok(modified) => Ok(modified),
213 Err(e) if e.kind() == ErrorKind::NotFound => Ok(false),
214 Err(e) => Err(format!("Error checking file state: {e}")),
215 }
216 }
217
218 pub(crate) fn save_to_disk_at(&mut self, path: PathBuf, force: bool) -> String {
219 if !self.dirty {
220 return "Nothing to save".to_string();
221 }
222
223 if !force {
224 match self.state_changed_on_disk() {
225 Ok(false) => (),
226 Ok(true) => return "File modified on disk, use :w! to force".to_string(),
227 Err(s) => return s,
228 }
229 }
230
231 let contents = self.contents();
232 let n_lines = self.len_lines();
233 let display_path = match path.canonicalize() {
234 Ok(cp) => cp.display().to_string(),
235 Err(_) => path.display().to_string(),
236 };
237 let n_bytes = contents.len();
238
239 match fs::write(path, contents) {
240 Ok(_) => {
241 self.dirty = false;
242 self.last_save = SystemTime::now();
243 format!("\"{display_path}\" {n_lines}L {n_bytes}B written")
244 }
245 Err(e) => format!("Unable to save buffer: {e}"),
246 }
247 }
248
249 pub(super) fn reload_from_disk(&mut self) -> String {
250 let path = match &self.kind {
251 BufferKind::File(p) | BufferKind::Directory(p) => p,
252 _ => return "Buffer is not backed by a file on disk".to_string(),
253 };
254
255 debug!(id=%self.id, path=%path.as_os_str().to_string_lossy(), "reloading buffer state from disk");
256 let raw = match BufferKind::try_kind_and_content_from_path(path.to_path_buf()) {
257 Ok((_, raw)) => raw,
258 Err(e) => return format!("Error reloading buffer: {e}"),
259 };
260
261 let n_chars = raw.len();
262 self.txt = GapBuffer::from(raw);
263 self.dot.clamp_idx(n_chars);
264 self.xdot.clamp_idx(n_chars);
265 self.edit_log.clear();
266 self.dirty = false;
267 self.last_save = SystemTime::now();
268
269 let n_lines = self.txt.len_lines();
270 let n_bytes = self.txt.len();
271 debug!(%n_bytes, "reloaded buffer content");
272
273 let display_path = match path.canonicalize() {
274 Ok(cp) => cp.display().to_string(),
275 Err(_) => path.display().to_string(),
276 };
277
278 format!("\"{display_path}\" {n_lines}L {n_bytes}B loaded")
279 }
280
281 pub(super) fn new_minibuffer() -> Self {
282 Self {
283 id: usize::MAX,
284 kind: BufferKind::MiniBuffer,
285 dot: Default::default(),
286 xdot: Default::default(),
287 txt: GapBuffer::from(""),
288 cached_rx: 0,
289 last_save: SystemTime::now(),
290 dirty: false,
291 edit_log: Default::default(),
292 input_filter: None,
293 ts_state: None,
294 }
295 }
296
297 pub fn new_unnamed(id: usize, content: impl Into<String>) -> Self {
299 Self {
300 id,
301 kind: BufferKind::Unnamed,
302 dot: Dot::default(),
303 xdot: Dot::default(),
304 txt: GapBuffer::from(normalize_line_endings(content.into())),
305 cached_rx: 0,
306 last_save: SystemTime::now(),
307 dirty: false,
308 edit_log: EditLog::default(),
309 input_filter: None,
310 ts_state: None,
311 }
312 }
313
314 pub fn new_virtual(id: usize, name: impl Into<String>, content: impl Into<String>) -> Self {
319 let mut content = normalize_line_endings(content.into());
320 if content.ends_with('\n') {
321 content.pop();
322 }
323
324 Self {
325 id,
326 kind: BufferKind::Virtual(name.into()),
327 dot: Dot::default(),
328 xdot: Dot::default(),
329 txt: GapBuffer::from(content),
330 cached_rx: 0,
331 last_save: SystemTime::now(),
332 dirty: false,
333 edit_log: EditLog::default(),
334 input_filter: None,
335 ts_state: None,
336 }
337 }
338
339 pub(super) fn new_output(id: usize, name: String, content: String) -> Self {
342 Self {
343 id,
344 kind: BufferKind::Output(name),
345 dot: Dot::default(),
346 xdot: Dot::default(),
347 txt: GapBuffer::from(normalize_line_endings(content)),
348 cached_rx: 0,
349 last_save: SystemTime::now(),
350 dirty: false,
351 edit_log: EditLog::default(),
352 input_filter: None,
353 ts_state: None,
354 }
355 }
356
357 pub fn display_name(&self) -> String {
359 let s = self.kind.display_name();
360
361 s[0..min(MAX_NAME_LEN, s.len())].to_string()
362 }
363
364 pub fn full_name(&self) -> &str {
366 match &self.kind {
367 BufferKind::File(p) => p.to_str().expect("valid unicode"),
368 BufferKind::Directory(p) => p.to_str().expect("valid unicode"),
369 BufferKind::Virtual(s) => s,
370 BufferKind::Output(s) => s,
371 BufferKind::Unnamed => UNNAMED_BUFFER,
372 BufferKind::MiniBuffer => "*mini-buffer*",
373 }
374 }
375
376 pub fn dir(&self) -> Option<&Path> {
378 self.kind.dir()
379 }
380
381 pub fn path(&self) -> Option<&Path> {
383 self.kind.path()
384 }
385
386 pub fn output_file_key(&self, cwd: &Path) -> String {
389 self.kind.output_file_key(cwd)
390 }
391
392 pub fn is_unnamed(&self) -> bool {
394 self.kind == BufferKind::Unnamed
395 }
396
397 pub fn contents(&self) -> Vec<u8> {
399 let mut contents: Vec<u8> = self.txt.bytes();
400 contents.push(b'\n');
401
402 contents
403 }
404
405 pub fn str_contents(&self) -> String {
407 let mut s = self.txt.to_string();
408 s.push('\n');
409
410 s
411 }
412
413 pub(crate) fn pretty_print_ts_tree(&self) -> Option<String> {
414 self.ts_state.as_ref().map(|ts| ts.pretty_print_tree())
415 }
416
417 pub(crate) fn string_lines(&self) -> Vec<String> {
418 self.txt
419 .iter_lines()
420 .map(|l| {
421 let mut s = l.to_string();
422 if s.ends_with('\n') {
423 s.pop();
424 }
425 s
426 })
427 .collect()
428 }
429
430 pub fn update_ts_state(&mut self, from: usize, n_rows: usize) {
431 if let Some(ts) = self.ts_state.as_mut() {
432 ts.update(&self.txt, from, n_rows);
433 }
434 }
435
436 pub fn iter_tokenized_lines_from(
437 &self,
438 line: usize,
439 load_exec_range: Option<(bool, Range)>,
440 ) -> LineIter<'_> {
441 match self.ts_state.as_ref() {
442 Some(ts) => {
443 ts.iter_tokenized_lines_from(line, &self.txt, self.dot.as_range(), load_exec_range)
444 }
445 None => LineIter::new(
446 line,
447 &self.txt,
448 self.dot.as_range(),
449 load_exec_range,
450 &[],
451 &[],
452 ),
453 }
454 }
455
456 pub fn dot_contents(&self) -> String {
458 self.dot.content(self)
459 }
460
461 pub fn addr(&self) -> String {
463 self.dot.addr(self)
464 }
465
466 pub fn xdot_contents(&self) -> String {
470 self.xdot.content(self)
471 }
472
473 pub fn xaddr(&self) -> String {
477 self.xdot.addr(self)
478 }
479
480 #[inline]
482 pub fn len_lines(&self) -> usize {
483 self.txt.len_lines()
484 }
485
486 #[inline]
488 pub fn len_chars(&self) -> usize {
489 self.txt.len_chars()
490 }
491
492 #[inline]
498 pub fn is_empty(&self) -> bool {
499 self.txt.len_chars() == 0
500 }
501
502 pub(crate) fn debug_edit_log(&self) -> Vec<String> {
503 self.edit_log.debug_edits(self)
504 }
505
506 pub(crate) fn x_from_rx(&self, y: usize) -> usize {
507 self.x_from_provided_rx(y, self.cached_rx)
508 }
509
510 pub(crate) fn x_from_provided_rx(&self, y: usize, buf_rx: usize) -> usize {
511 if self.is_empty() {
512 return 0;
513 }
514
515 let mut rx = 0;
516 let mut cx = 0;
517 let tabstop = config_handle!().tabstop;
518
519 for c in self.txt.line(y).chars() {
520 if c == '\n' {
521 break;
522 }
523
524 if c == '\t' {
525 rx += (tabstop - 1) - (rx % tabstop);
526 }
527 rx += 1;
528
529 if rx > buf_rx {
530 break;
531 }
532 cx += 1;
533 }
534
535 cx
536 }
537
538 pub fn line(&self, y: usize) -> Option<Slice<'_>> {
540 if y >= self.len_lines() {
541 None
542 } else {
543 Some(self.txt.line(y))
544 }
545 }
546
547 pub(crate) fn try_expand_delimited(&mut self) {
550 let current_index = match self.dot {
551 Dot::Cur { c: Cur { idx } } => idx,
552 Dot::Range { .. } => return,
553 };
554
555 let prev = if current_index == 0 {
556 None
557 } else {
558 self.txt.get_char(current_index - 1)
559 };
560 let next = self.txt.get_char(current_index + 1);
561
562 let (l, r) = match (prev, next) {
563 (Some('\n'), _) | (_, Some('\n')) => ('\n', '\n'),
564 (Some('('), _) | (_, Some(')')) => ('(', ')'),
565 (Some('['), _) | (_, Some(']')) => ('[', ']'),
566 (Some('{'), _) | (_, Some('}')) => ('{', '}'),
567 (Some('<'), _) | (_, Some('>')) => ('<', '>'),
568 (Some('"'), _) | (_, Some('"')) => ('"', '"'),
569 (Some('\''), _) | (_, Some('\'')) => ('\'', '\''),
570 (Some(' '), _) | (_, Some(' ')) => (' ', ' '),
571
572 _ => return self.expand_cur_dot(),
573 };
574
575 self.set_dot(TextObject::Delimited(l, r), 1);
576 }
577
578 pub(crate) fn expand_cur_dot(&mut self) {
583 let current_index = match self.dot {
584 Dot::Cur { c: Cur { idx } } => idx,
585 Dot::Range { .. } => return,
586 };
587
588 if let Some(dot) = self.try_expand_known(current_index) {
589 self.dot = dot;
590 return;
591 }
592
593 let (mut from, mut to) = (current_index, current_index);
595 for (i, ch) in self.iter_between(current_index, self.txt.len_chars()) {
596 if !(ch == '_' || ch.is_alphanumeric()) {
597 break;
598 }
599 to = i;
600 }
601
602 for (i, ch) in self.rev_iter_between(current_index, 0) {
603 if !(ch == '_' || ch.is_alphanumeric()) {
604 break;
605 }
606 from = i;
607 }
608
609 self.dot = Dot::from_char_indices(from, to);
610 }
611
612 fn try_expand_known(&self, current_index: usize) -> Option<Dot> {
618 let (mut from, mut to) = (current_index, current_index);
619 let mut colon: Option<usize> = None;
620 let n_chars = self.txt.len_chars();
621
622 let is_file_char = |ch: char| ch.is_alphanumeric() || "._-+/:@".contains(ch);
623 let is_addr_char = |ch: char| "+-/$.#,;?".contains(ch);
624 let is_url_char = |ch: char| "?&=".contains(ch);
625 let has_url_prefix = |i: usize| {
626 let http = i > 4 && i + 3 <= n_chars && self.txt.slice(i - 4, i + 3) == HTTP;
627 let https = i > 5 && i + 3 <= n_chars && self.txt.slice(i - 5, i + 3) == HTTPS;
628
629 http || https
630 };
631
632 for (i, ch) in self.iter_between(current_index, self.txt.len_chars()) {
635 if !is_file_char(ch) {
636 break;
637 }
638 if ch == ':' && !has_url_prefix(i) {
639 colon = Some(i);
640 break;
641 }
642 to = i;
643 }
644
645 for (i, ch) in self.rev_iter_between(current_index, 0) {
646 if !(is_file_char(ch) || is_url_char(ch) || is_addr_char(ch)) {
647 break;
648 }
649 if colon.is_none() && ch == ':' && !has_url_prefix(i) {
650 colon = Some(i);
651 }
652 from = i;
653 }
654
655 if let Some(ix) = colon {
657 to = ix;
658 for (_, ch) in self.iter_between(ix + 1, self.txt.len_chars()) {
659 if ch.is_whitespace() || "()[]{}<>;".contains(ch) {
660 break;
661 }
662 to += 1;
663 }
664 }
665
666 let dot_content = self.txt.slice(from, to + 1).to_string();
667
668 if dot_content.starts_with(HTTP) || dot_content.starts_with(HTTPS) {
670 if to < self.txt.len_chars() {
671 for (_, ch) in self.iter_between(to + 1, self.txt.len_chars()) {
672 if ch.is_whitespace() || "()[]{}<>;".contains(ch) {
673 break;
674 }
675 to += 1;
676 }
677 }
678
679 if dot_content.ends_with('.') {
680 to -= 1;
681 }
682
683 return Some(Dot::from_char_indices(from, to));
684 }
685
686 let dot = Dot::from_char_indices(from, to);
687
688 let fname = match dot_content.split_once(':') {
690 Some((fname, _)) => fname,
691 None => &dot_content,
692 };
693
694 let path = Path::new(fname);
695 if path.is_absolute() && path.exists() {
696 return Some(dot);
697 } else if let Some(dir) = self.dir() {
698 if dir.join(path).exists() {
699 return Some(dot);
700 }
701 }
702
703 None
705 }
706
707 pub(crate) fn sign_col_dims(&self) -> (usize, usize) {
708 let w_lnum = n_digits(self.len_lines());
709 let w_sgncol = w_lnum + 2;
710
711 (w_lnum, w_sgncol)
712 }
713
714 pub(crate) fn append(&mut self, s: String, source: Source) {
715 let dot = self.dot;
716 self.set_dot(TextObject::BufferEnd, 1);
717 self.handle_action(Action::InsertString { s }, source);
718 self.dot = dot;
719 self.dot.clamp_idx(self.txt.len_chars());
720 self.xdot.clamp_idx(self.txt.len_chars());
721 }
722
723 pub(crate) fn handle_action(&mut self, a: Action, source: Source) -> Option<ActionOutcome> {
725 match a {
726 Action::Delete => {
727 let (c, deleted) = self.delete_dot(self.dot, Some(source));
728 self.dot = Dot::Cur { c };
729 self.dot.clamp_idx(self.txt.len_chars());
730 self.xdot.clamp_idx(self.txt.len_chars());
731 return deleted.map(ActionOutcome::SetClipboard);
732 }
733 Action::InsertChar { c } => {
734 let (c, _) = self.insert_char(self.dot, c, Some(source));
735 self.dot = Dot::Cur { c };
736 self.dot.clamp_idx(self.txt.len_chars());
737 self.xdot.clamp_idx(self.txt.len_chars());
738 return None;
739 }
740 Action::InsertString { s } => {
741 let (c, _) = self.insert_string(self.dot, s, Some(source));
742 self.dot = Dot::Cur { c };
743 self.dot.clamp_idx(self.txt.len_chars());
744 self.xdot.clamp_idx(self.txt.len_chars());
745 return None;
746 }
747
748 Action::Redo => return self.redo(),
749 Action::Undo => return self.undo(),
750
751 Action::DotCollapseFirst => self.dot = self.dot.collapse_to_first_cur(),
752 Action::DotCollapseLast => self.dot = self.dot.collapse_to_last_cur(),
753 Action::DotExtendBackward(tobj, count) => self.extend_dot_backward(tobj, count),
754 Action::DotExtendForward(tobj, count) => self.extend_dot_forward(tobj, count),
755 Action::DotFlip => self.dot.flip(),
756 Action::DotSet(t, count) => self.set_dot(t, count),
757 Action::DotSetFromCoords { coords } => self.set_dot_from_coords(coords),
758
759 Action::RawInput { i } => return self.handle_raw_input(i),
760
761 _ => (),
762 }
763
764 None
765 }
766
767 fn handle_raw_input(&mut self, k: Input) -> Option<ActionOutcome> {
768 let (match_indent, expand_tab, tabstop) = {
769 let conf = config_handle!();
770 (conf.match_indent, conf.expand_tab, conf.tabstop)
771 };
772
773 match k {
774 Input::Return => {
775 let mut s = "\n".to_string();
776 if match_indent {
777 let cur = self.dot.first_cur();
778 let y = self.txt.char_to_line(cur.idx);
779 let line = self.txt.line(y).to_string();
780 s.push_str(
781 &line
782 .find(|c: char| !c.is_whitespace())
783 .map(|ix| line.split_at(ix).0.to_string())
784 .unwrap_or_default(),
785 );
786 }
787
788 let c = self.insert_string(self.dot, s, Some(Source::Keyboard)).0;
789
790 self.dot = Dot::Cur { c };
791 return None;
792 }
793
794 Input::Tab => {
795 let (c, _) = if expand_tab {
796 self.insert_string(self.dot, " ".repeat(tabstop), Some(Source::Keyboard))
797 } else {
798 self.insert_char(self.dot, '\t', Some(Source::Keyboard))
799 };
800
801 self.dot = Dot::Cur { c };
802 return None;
803 }
804
805 Input::Char(ch) => {
806 let (c, _) = self.insert_char(self.dot, ch, Some(Source::Keyboard));
807 self.dot = Dot::Cur { c };
808 return None;
809 }
810
811 Input::Arrow(arr) => self.set_dot(TextObject::Arr(arr), 1),
812
813 _ => (),
814 }
815
816 None
817 }
818
819 pub(crate) fn set_dot(&mut self, t: TextObject, n: usize) {
821 for _ in 0..n {
822 t.set_dot(self);
823 }
824 self.dot.clamp_idx(self.txt.len_chars());
825 self.xdot.clamp_idx(self.txt.len_chars());
826 }
827
828 fn set_dot_from_coords(&mut self, coords: Coords) {
829 let mut addr: Addr = coords.as_addr(self);
830 self.dot = self.map_addr(&mut addr);
831 self.dot.clamp_idx(self.txt.len_chars());
832 self.xdot.clamp_idx(self.txt.len_chars());
833 }
834
835 fn extend_dot_forward(&mut self, t: TextObject, n: usize) {
837 for _ in 0..n {
838 t.extend_dot_forward(self);
839 }
840 self.dot.clamp_idx(self.txt.len_chars());
841 self.xdot.clamp_idx(self.txt.len_chars());
842 }
843
844 fn extend_dot_backward(&mut self, t: TextObject, n: usize) {
846 for _ in 0..n {
847 t.extend_dot_backward(self);
848 }
849 self.dot.clamp_idx(self.txt.len_chars());
850 self.xdot.clamp_idx(self.txt.len_chars());
851 }
852
853 pub(crate) fn new_edit_log_transaction(&mut self) {
854 self.edit_log.new_transaction()
855 }
856
857 fn undo(&mut self) -> Option<ActionOutcome> {
858 match self.edit_log.undo() {
859 Some(edits) => {
860 self.edit_log.paused = true;
861 for edit in edits.into_iter() {
862 self.apply_edit(edit);
863 }
864 self.edit_log.paused = false;
865 self.dirty = !self.edit_log.is_empty();
866 None
867 }
868 None => Some(ActionOutcome::SetStatusMessage(
869 "Nothing to undo".to_string(),
870 )),
871 }
872 }
873
874 fn redo(&mut self) -> Option<ActionOutcome> {
875 match self.edit_log.redo() {
876 Some(edits) => {
877 self.edit_log.paused = true;
878 for edit in edits.into_iter() {
879 self.apply_edit(edit);
880 }
881 self.edit_log.paused = false;
882 None
883 }
884 None => Some(ActionOutcome::SetStatusMessage(
885 "Nothing to redo".to_string(),
886 )),
887 }
888 }
889
890 fn apply_edit(&mut self, Edit { kind, cur, txt }: Edit) {
891 let new_cur = match (kind, txt) {
892 (Kind::Insert, Txt::Char(c)) => self.insert_char(Dot::Cur { c: cur }, c, None).0,
893 (Kind::Insert, Txt::String(s)) => self.insert_string(Dot::Cur { c: cur }, s, None).0,
894 (Kind::Delete, Txt::Char(_)) => self.delete_dot(Dot::Cur { c: cur }, None).0,
895 (Kind::Delete, Txt::String(s)) => {
896 let start_idx = cur.idx;
897 let end_idx = (start_idx + s.chars().count()).saturating_sub(1);
898 let end = Cur { idx: end_idx };
899 self.delete_dot(
900 Dot::Range {
901 r: Range::from_cursors(cur, end, true),
902 }
903 .collapse_null_range(),
904 None,
905 )
906 .0
907 }
908 };
909
910 self.dot = Dot::Cur { c: new_cur };
911 }
912
913 fn mark_dirty(&mut self) {
916 self.dirty = self.kind.is_file();
917 }
918
919 pub(crate) fn notify_load(&self, source: Source) -> bool {
921 match self.input_filter.as_ref() {
922 Some(f) => {
923 let (ch_from, ch_to) = self.dot.as_char_indices();
924 let txt = self.dot.content(self);
925 f.notify_load(source, ch_from, ch_to, &txt);
926 true
927 }
928 None => false,
929 }
930 }
931
932 pub(crate) fn notify_execute(&self, source: Source, arg: Option<(Range, String)>) -> bool {
934 match self.input_filter.as_ref() {
935 Some(f) => {
936 let (ch_from, ch_to) = self.dot.as_char_indices();
937 let txt = self.dot.content(self);
938 f.notify_execute(source, ch_from, ch_to, &txt, arg);
939 true
940 }
941 None => false,
942 }
943 }
944
945 fn insert_char(&mut self, dot: Dot, ch: char, source: Option<Source>) -> (Cur, Option<String>) {
946 let ch = if ch == '\r' { '\n' } else { ch };
947 let (cur, deleted) = match dot {
948 Dot::Cur { c } => (c, None),
949 Dot::Range { r } => self.delete_range(r, source),
950 };
951
952 let idx = cur.idx;
953
954 if let Some(ts) = self.ts_state.as_mut() {
955 if let Some(s) = deleted.as_ref() {
956 let len = s.chars().count();
957 let ch_old_end = min(dot.first_cur().idx + len, self.txt.len_chars());
958 ts.edit(idx, ch_old_end, idx, &self.txt);
959 }
960 }
961
962 self.txt.insert_char(idx, ch);
963
964 if let (Some(source), Some(f)) = (source, self.input_filter.as_ref()) {
965 f.notify_insert(source, idx, idx + 1, &ch.to_string());
966 }
967
968 self.edit_log.insert_char(cur, ch);
969
970 if let Some(ts) = self.ts_state.as_mut() {
971 ts.edit(idx, idx, idx + 1, &self.txt);
972 }
973
974 self.mark_dirty();
975
976 (Cur { idx: idx + 1 }, deleted)
977 }
978
979 fn insert_string(
980 &mut self,
981 dot: Dot,
982 s: String,
983 source: Option<Source>,
984 ) -> (Cur, Option<String>) {
985 let s = normalize_line_endings(s);
986 let len = s.chars().count();
987 let (mut cur, deleted) = match dot {
988 Dot::Cur { c } => (c, None),
989 Dot::Range { r } => self.delete_range(r, source),
990 };
991
992 let idx = cur.idx;
993
994 if let Some(ts) = self.ts_state.as_mut() {
995 if let Some(s) = deleted.as_ref() {
996 let len = s.chars().count();
997 let ch_old_end = min(dot.first_cur().idx + len, self.txt.len_chars());
998 ts.edit(idx, ch_old_end, idx, &self.txt);
999 }
1000 }
1001
1002 if !s.is_empty() {
1006 self.txt.insert_str(idx, &s);
1007
1008 if let (Some(source), Some(f)) = (source, self.input_filter.as_ref()) {
1009 f.notify_insert(source, idx, idx + len, &s);
1010 }
1011
1012 self.edit_log.insert_string(cur, s);
1013 cur.idx += len;
1014 }
1015
1016 if let Some(ts) = self.ts_state.as_mut() {
1017 ts.edit(idx, idx, idx + len, &self.txt);
1018 }
1019
1020 self.mark_dirty();
1021
1022 (cur, deleted)
1023 }
1024
1025 fn delete_dot(&mut self, dot: Dot, source: Option<Source>) -> (Cur, Option<String>) {
1026 let (cur, deleted) = match dot {
1027 Dot::Cur { c } => (self.delete_cur(c, source), None),
1028 Dot::Range { r } => self.delete_range(r, source),
1029 };
1030
1031 if let Some(ts) = self.ts_state.as_mut() {
1032 let len = deleted.as_ref().map(|s| s.chars().count()).unwrap_or(1);
1033 let ch_old_end = min(dot.first_cur().idx + len, self.txt.len_chars());
1034 ts.edit(cur.idx, ch_old_end, cur.idx, &self.txt);
1035 }
1036
1037 (cur, deleted)
1038 }
1039
1040 fn delete_cur(&mut self, cur: Cur, source: Option<Source>) -> Cur {
1041 let idx = cur.idx;
1042 if idx < self.txt.len_chars() {
1043 let ch = self.txt.char(idx);
1044 self.txt.remove_char(idx);
1045
1046 if let (Some(source), Some(f)) = (source, self.input_filter.as_ref()) {
1047 f.notify_delete(source, idx, idx + 1);
1048 }
1049
1050 self.edit_log.delete_char(cur, ch);
1051 self.mark_dirty();
1052 }
1053
1054 cur
1055 }
1056
1057 fn delete_range(&mut self, r: Range, source: Option<Source>) -> (Cur, Option<String>) {
1058 let (from, to) = if r.start.idx != r.end.idx {
1059 (r.start.idx, min(r.end.idx + 1, self.txt.len_chars()))
1060 } else {
1061 return (r.start, None);
1062 };
1063
1064 let s = self.txt.slice(from, to).to_string();
1065 self.txt.remove_range(from, to);
1066
1067 if let (Some(source), Some(f)) = (source, self.input_filter.as_ref()) {
1068 f.notify_delete(source, from, to);
1069 }
1070
1071 self.edit_log.delete_string(r.start, s.clone());
1072 self.mark_dirty();
1073
1074 (r.start, Some(s))
1075 }
1076
1077 pub(crate) fn find_forward(&mut self, s: &str) {
1078 if let Some(dot) = find_forward_wrapping(&s, self) {
1079 self.dot = dot;
1080 }
1081 }
1082}
1083
1084fn n_digits(mut n: usize) -> usize {
1085 if n == 0 {
1086 return 1;
1087 }
1088
1089 let mut digits = 0;
1090 while n != 0 {
1091 digits += 1;
1092 n /= 10;
1093 }
1094
1095 digits
1096}
1097
1098#[cfg(test)]
1099pub(crate) mod tests {
1100 use super::*;
1101 use crate::key::Arrow;
1102 use edit::tests::{del_c, del_s, in_c, in_s};
1103 use simple_test_case::test_case;
1104 use std::env;
1105
1106 const LINE_1: &str = "This is a test";
1107 const LINE_2: &str = "involving multiple lines";
1108
1109 #[test_case(0, 1; "n0")]
1110 #[test_case(5, 1; "n5")]
1111 #[test_case(10, 2; "n10")]
1112 #[test_case(13, 2; "n13")]
1113 #[test_case(731, 3; "n731")]
1114 #[test_case(930, 3; "n930")]
1115 #[test]
1116 fn n_digits_works(n: usize, digits: usize) {
1117 assert_eq!(n_digits(n), digits);
1118 }
1119
1120 pub fn buffer_from_lines(lines: &[&str]) -> Buffer {
1121 let mut b = Buffer::new_unnamed(0, "");
1122 let s = lines.join("\n");
1123
1124 for c in s.chars() {
1125 b.handle_action(Action::InsertChar { c }, Source::Keyboard);
1126 }
1127
1128 b
1129 }
1130
1131 fn simple_initial_buffer() -> Buffer {
1132 buffer_from_lines(&[LINE_1, LINE_2])
1133 }
1134
1135 #[test]
1136 fn simple_insert_works() {
1137 let b = simple_initial_buffer();
1138 let c = Cur::from_yx(1, LINE_2.len(), &b);
1139 let lines = b.string_lines();
1140
1141 assert_eq!(lines.len(), 2);
1142 assert_eq!(lines[0], LINE_1);
1143 assert_eq!(lines[1], LINE_2);
1144 assert_eq!(b.dot, Dot::Cur { c });
1145 assert_eq!(
1146 b.edit_log.edits,
1147 vec![vec![in_s(0, &format!("{LINE_1}\n{LINE_2}"))]]
1148 );
1149 }
1150
1151 #[test]
1152 fn insert_with_moving_dot_works() {
1153 let mut b = Buffer::new_unnamed(0, "");
1154
1155 for c in "hello w".chars() {
1157 b.handle_action(Action::InsertChar { c }, Source::Keyboard);
1158 }
1159
1160 b.handle_action(
1162 Action::DotSet(TextObject::Arr(Arrow::Left), 2),
1163 Source::Keyboard,
1164 );
1165 b.handle_action(Action::InsertChar { c: ',' }, Source::Keyboard);
1166
1167 b.handle_action(Action::DotSet(TextObject::LineEnd, 1), Source::Keyboard);
1169 for c in "orld!".chars() {
1170 b.handle_action(Action::InsertChar { c }, Source::Keyboard);
1171 }
1172
1173 assert_eq!(b.txt.to_string(), "hello, world!");
1175 }
1176
1177 #[test_case(
1178 Action::InsertChar { c: 'x' },
1179 in_c(LINE_1.len() + 1, 'x');
1180 "char"
1181 )]
1182 #[test_case(
1183 Action::InsertString { s: "x".to_string() },
1184 in_s(LINE_1.len() + 1, "x");
1185 "string"
1186 )]
1187 #[test]
1188 fn insert_w_range_dot_works(a: Action, edit: Edit) {
1189 let mut b = simple_initial_buffer();
1190 b.handle_action(Action::DotSet(TextObject::Line, 1), Source::Keyboard);
1191
1192 let outcome = b.handle_action(a, Source::Keyboard);
1193 assert_eq!(outcome, None);
1194
1195 let lines = b.string_lines();
1196 assert_eq!(lines.len(), 2);
1197
1198 let c = Cur::from_yx(1, 1, &b);
1199 assert_eq!(b.dot, Dot::Cur { c });
1200
1201 assert_eq!(lines[0], LINE_1);
1202 assert_eq!(lines[1], "x");
1203 assert_eq!(
1204 b.edit_log.edits,
1205 vec![vec![
1206 in_s(0, &format!("{LINE_1}\n{LINE_2}")),
1207 del_s(LINE_1.len() + 1, LINE_2),
1208 edit,
1209 ]]
1210 );
1211 }
1212
1213 #[test]
1214 fn move_forward_at_end_of_buffer_is_fine() {
1215 let mut b = Buffer::new_unnamed(0, "");
1216 b.handle_raw_input(Input::Arrow(Arrow::Right));
1217
1218 let c = Cur { idx: 0 };
1219 assert_eq!(b.dot, Dot::Cur { c });
1220 }
1221
1222 #[test]
1223 fn delete_in_empty_buffer_is_fine() {
1224 let mut b = Buffer::new_unnamed(0, "");
1225 b.handle_action(Action::Delete, Source::Keyboard);
1226 let c = Cur { idx: 0 };
1227 let lines = b.string_lines();
1228
1229 assert_eq!(b.dot, Dot::Cur { c });
1230 assert_eq!(lines.len(), 1);
1231 assert_eq!(lines[0], "");
1232 assert!(b.edit_log.edits.is_empty());
1233 }
1234
1235 #[test]
1236 fn simple_delete_works() {
1237 let mut b = simple_initial_buffer();
1238 b.handle_action(
1239 Action::DotSet(TextObject::Arr(Arrow::Left), 1),
1240 Source::Keyboard,
1241 );
1242 b.handle_action(Action::Delete, Source::Keyboard);
1243
1244 let c = Cur::from_yx(1, LINE_2.len() - 1, &b);
1245 let lines = b.string_lines();
1246
1247 assert_eq!(b.dot, Dot::Cur { c });
1248 assert_eq!(lines.len(), 2);
1249 assert_eq!(lines[0], LINE_1);
1250 assert_eq!(lines[1], "involving multiple line");
1251 assert_eq!(
1252 b.edit_log.edits,
1253 vec![vec![
1254 in_s(0, &format!("{LINE_1}\n{LINE_2}")),
1255 del_c(LINE_1.len() + 24, 's')
1256 ]]
1257 );
1258 }
1259
1260 #[test]
1261 fn delete_range_works() {
1262 let mut b = simple_initial_buffer();
1263 b.handle_action(Action::DotSet(TextObject::Line, 1), Source::Keyboard);
1264 b.handle_action(Action::Delete, Source::Keyboard);
1265
1266 let c = Cur::from_yx(1, 0, &b);
1267 let lines = b.string_lines();
1268
1269 assert_eq!(b.dot, Dot::Cur { c });
1270 assert_eq!(lines.len(), 2);
1271 assert_eq!(lines[0], LINE_1);
1272 assert_eq!(lines[1], "");
1273 assert_eq!(
1274 b.edit_log.edits,
1275 vec![vec![
1276 in_s(0, &format!("{LINE_1}\n{LINE_2}")),
1277 del_s(LINE_1.len() + 1, "involving multiple lines")
1278 ]]
1279 );
1280 }
1281
1282 #[test]
1283 fn delete_undo_works() {
1284 let mut b = simple_initial_buffer();
1285 let original_lines = b.string_lines();
1286 b.new_edit_log_transaction();
1287
1288 b.handle_action(
1289 Action::DotExtendBackward(TextObject::Word, 1),
1290 Source::Keyboard,
1291 );
1292 b.handle_action(Action::Delete, Source::Keyboard);
1293
1294 b.set_dot(TextObject::BufferStart, 1);
1295 b.handle_action(
1296 Action::DotExtendForward(TextObject::Word, 1),
1297 Source::Keyboard,
1298 );
1299 b.handle_action(Action::Delete, Source::Keyboard);
1300
1301 b.handle_action(Action::Undo, Source::Keyboard);
1302
1303 let lines = b.string_lines();
1304
1305 assert_eq!(lines, original_lines);
1306 }
1307
1308 fn c(idx: usize) -> Cur {
1309 Cur { idx }
1310 }
1311
1312 #[test]
1313 fn undo_string_insert_works() {
1314 let initial_content = "foo foo foo\n";
1315 let mut b = Buffer::new_unnamed(0, initial_content);
1316
1317 b.insert_string(Dot::Cur { c: c(0) }, "bar".to_string(), None);
1318 b.handle_action(Action::Undo, Source::Keyboard);
1319
1320 assert_eq!(b.string_lines(), vec!["foo foo foo", ""]);
1321 }
1322
1323 #[test]
1324 fn undo_string_delete_works() {
1325 let initial_content = "foo foo foo\n";
1326 let mut b = Buffer::new_unnamed(0, initial_content);
1327
1328 let r = Range::from_cursors(c(0), c(2), true);
1329 b.delete_dot(Dot::Range { r }, None);
1330 b.handle_action(Action::Undo, Source::Keyboard);
1331
1332 assert_eq!(b.string_lines(), vec!["foo foo foo", ""]);
1333 }
1334
1335 #[test]
1336 fn undo_string_insert_and_delete_works() {
1337 let initial_content = "foo foo foo\n";
1338 let mut b = Buffer::new_unnamed(0, initial_content);
1339
1340 let r = Range::from_cursors(c(0), c(2), true);
1341 b.delete_dot(Dot::Range { r }, None);
1342 b.insert_string(Dot::Cur { c: c(0) }, "bar".to_string(), None);
1343
1344 assert_eq!(b.string_lines(), vec!["bar foo foo", ""]);
1345
1346 b.handle_action(Action::Undo, Source::Keyboard);
1347 b.handle_action(Action::Undo, Source::Keyboard);
1348
1349 assert_eq!(b.string_lines(), vec!["foo foo foo", ""]);
1350 }
1351
1352 #[test_case("foo", None; "unknown format")]
1354 #[test_case("someFunc()", None; "camel case function call")]
1355 #[test_case("some_func()", None; "snake case function call")]
1356 #[test_case("not_a_real_file.rs", None; "file that does not exist")]
1357 #[test_case("README.md", Some("README.md"); "file that exists")]
1358 #[test_case("README.md:12,19", Some("README.md:12,19"); "file that exists with addr")]
1359 #[test_case("README.md:12:19", Some("README.md:12:19"); "file that exists with addr containing colon")]
1360 #[test_case("/dev/null", Some("/dev/null"); "file that exists abs path")]
1361 #[test_case("/dev/null:12-+#", Some("/dev/null:12-+#"); "file that exists abs path with addr")]
1362 #[test_case("http://example.com", Some("http://example.com"); "http url")]
1363 #[test_case("http://example.com/some/path", Some("http://example.com/some/path"); "http url with path")]
1364 #[test_case("http://example.com?foo=1", Some("http://example.com?foo=1"); "http url with query string")]
1365 #[test_case("http://example.com?foo=1&bar=2", Some("http://example.com?foo=1&bar=2"); "http url with multi query string")]
1366 #[test]
1367 fn try_expand_known_works(s: &str, expected: Option<&str>) {
1368 let cwd = env::current_dir().unwrap().display().to_string();
1369 for (l, r) in [(" ", " "), ("(", ")"), ("[", "]"), ("<", ">"), ("{", "}")] {
1371 let b = Buffer::new_output(
1372 0,
1373 format!("{cwd}/+output"),
1374 format!("abc_123 {l}{s}{r}\tmore text"),
1375 );
1376
1377 for i in 0..s.len() {
1379 let dot = b.try_expand_known(9 + i);
1380 let maybe_content = dot.map(|d| d.content(&b));
1381 assert_eq!(
1382 maybe_content.as_deref(),
1383 expected,
1384 "failed at offset={i} with lr=({l:?}, {r:?})"
1385 )
1386 }
1387 }
1388 }
1389
1390 #[test_case("\r", "\n"; "CR")]
1391 #[test_case("\n", "\n"; "LF")]
1392 #[test_case("\r\n", "\n"; "CRLF")]
1393 #[test_case("foo\rbar", "foo\nbar"; "text either side of CR")]
1394 #[test_case("foo\nbar", "foo\nbar"; "text either side of LF")]
1395 #[test_case("foo\r\nbar", "foo\nbar"; "text either side of CRLF")]
1396 #[test_case("foo\rbar\nbaz\r\nquux", "foo\nbar\nbaz\nquux"; "mixed line endings")]
1397 #[test]
1398 fn normalizes_line_endings_insert_string(s: &str, expected: &str) {
1399 let mut b = Buffer::new_virtual(0, "test", "");
1400 b.insert_string(Dot::Cur { c: c(0) }, s.to_string(), None);
1401 assert_eq!(b.str_contents(), format!("{expected}\n"));
1403 }
1404
1405 #[test_case('\r', "\n"; "CR")]
1406 #[test_case('\n', "\n"; "LF")]
1407 #[test_case('a', "a"; "ascii")]
1408 #[test]
1409 fn normalizes_line_endings_insert_char(ch: char, expected: &str) {
1410 let mut b = Buffer::new_virtual(0, "test", "");
1411 b.insert_char(Dot::Cur { c: c(0) }, ch, None);
1412 assert_eq!(b.str_contents(), format!("{expected}\n"));
1414 }
1415
1416 #[test]
1419 fn insert_string_reducing_buffer_len_works_with_ts_state() {
1420 let mut b = Buffer::new_virtual(0, "test", "fn main() {}");
1421 b.ts_state = Some(
1422 TsState::try_new_from_language("rust", tree_sitter_rust::LANGUAGE.into(), "", &b.txt)
1423 .unwrap(),
1424 );
1425
1426 b.set_dot(TextObject::BufferStart, 1);
1427 b.extend_dot_forward(TextObject::BufferEnd, 1);
1428
1429 b.handle_action(
1430 Action::InsertString {
1431 s: "bar".to_owned(),
1432 },
1433 Source::Fsys,
1434 );
1435
1436 assert_eq!(b.txt.to_string(), "bar");
1437 assert_eq!(b.dot, Dot::Cur { c: Cur { idx: 3 } });
1438 }
1439
1440 #[test]
1441 fn insert_char_reducing_buffer_len_works_with_ts_state() {
1442 let mut b = Buffer::new_virtual(0, "test", "fn main() {}");
1443 b.ts_state = Some(
1444 TsState::try_new_from_language("rust", tree_sitter_rust::LANGUAGE.into(), "", &b.txt)
1445 .unwrap(),
1446 );
1447
1448 b.set_dot(TextObject::BufferStart, 1);
1449 b.extend_dot_forward(TextObject::BufferEnd, 1);
1450
1451 b.handle_action(Action::InsertChar { c: 'a' }, Source::Fsys);
1452
1453 assert_eq!(b.txt.to_string(), "a");
1454 assert_eq!(b.dot, Dot::Cur { c: Cur { idx: 1 } });
1455 }
1456
1457 #[test]
1458 fn match_indent_works() {
1459 let mut b = Buffer::new_virtual(0, "test", " foo");
1460 b.set_dot(TextObject::BufferEnd, 1);
1461 b.handle_raw_input(Input::Return);
1462 assert_eq!(b.txt.to_string(), " foo\n ");
1463 }
1464}