1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Buffer-list management: `:bn`/`:bp`/`:bd`. Cycles and deletes
//! against the MRU `opened_paths` and the sleeping snapshot map; the
//! actual file-open work lives in [`super::open`].
use anyhow::Result;
use crate::buffer_ref::BufferRef;
use crate::editor::Buffer;
use super::{App, Toast, root_cause};
impl App {
/// `:bn` / `:bp` — cycle through `opened_paths`. Same semantics
/// as vim's `:bnext` / `:bprev`: forward wraps to the start, back
/// wraps to the end. No-op when there's only one buffer.
pub fn buffer_cycle(&mut self, forward: bool) -> Result<()> {
if self.opened_paths.len() <= 1 {
self.toast = Toast::info("only one buffer");
return Ok(());
}
let current_ref = self.active_ref();
let len = self.opened_paths.len();
let idx = self
.opened_paths
.iter()
.position(|r| r == ¤t_ref)
.unwrap_or(0);
let target_idx = if forward {
(idx + 1) % len
} else {
(idx + len - 1) % len
};
let target = self.opened_paths[target_idx].clone();
self.switch_to_buffer(target)
}
/// `:bd` / `:bd!` — drop the current buffer from MRU and
/// sleeping, then switch to the most-recent remaining buffer
/// (falling back to a fresh scratch). Refuses on dirty without
/// `force`. The deleted buffer is *not* stashed — its content
/// is gone, same as vim's `:bd`.
pub fn buffer_delete(&mut self, force: bool) -> Result<()> {
if !force && self.buffer.dirty {
self.toast = Toast::error("unsaved changes (use :bd!)");
return Ok(());
}
let current_ref = self.active_ref();
// Pick a successor before mutating state — the most-recent
// entry that *isn't* the one we're deleting.
let target = self
.opened_paths
.iter()
.rev()
.find(|r| *r != ¤t_ref)
.cloned();
// Drop the deleted buffer from all bookkeeping.
self.opened_paths.retain(|r| r != ¤t_ref);
self.sleeping.remove(¤t_ref);
self.lsp.detach_current();
match target {
Some(BufferRef::Scratch) => {
let restored = match self.sleeping.remove(&BufferRef::Scratch) {
Some(b) => b.thaw(),
None => Buffer::new(),
};
self.install_buffer(restored);
self.open_gen = self.open_gen.wrapping_add(1);
self.lsp.set_last_synced_version(self.buffer.version);
self.record_opened(BufferRef::Scratch);
self.toast = Toast::info("deleted, [scratch]");
Ok(())
}
Some(BufferRef::File(path)) => {
// Restore from sleeping when available; otherwise
// re-read disk. Both paths set up LSP/highlighter.
if let Some(b) = self.sleeping.remove(&BufferRef::File(path.clone())) {
self.install_buffer(b.thaw());
self.open_gen = self.open_gen.wrapping_add(1);
self.lsp.set_last_synced_version(self.buffer.version);
self.record_opened(BufferRef::File(path.clone()));
self.spawn_highlighter_worker(&path);
self.spawn_lsp_worker(&path);
self.toast = Toast::info(format!("deleted, restored {}", path.display()));
} else {
// Successor isn't in sleeping (rare — would mean
// it was evicted by MRU cap while being in the
// picker). Fresh-load from disk.
let loaded = match Buffer::load(&path) {
Ok(b) => b,
Err(e) => {
self.install_buffer(Buffer::new());
self.open_gen = self.open_gen.wrapping_add(1);
self.record_opened(BufferRef::Scratch);
self.toast = Toast::error(format!(
"deleted; failed to open {}: {} — using scratch",
path.display(),
root_cause(&e)
));
return Ok(());
}
};
self.install_buffer(loaded);
self.record_opened(BufferRef::File(path.clone()));
self.open_gen = self.open_gen.wrapping_add(1);
self.lsp.set_last_synced_version(self.buffer.version);
self.spawn_highlighter_worker(&path);
self.spawn_lsp_worker(&path);
self.toast = Toast::info(format!("deleted, opened {}", path.display()));
}
Ok(())
}
None => {
// Nothing left — start a fresh scratch.
self.install_buffer(Buffer::new());
self.open_gen = self.open_gen.wrapping_add(1);
self.record_opened(BufferRef::Scratch);
self.toast = Toast::info("deleted, [scratch]");
Ok(())
}
}
}
}