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