1use crate::{
2 buffer::{Buffer, BufferKind, Cur, WELCOME_SQUIRREL},
3 config::Config,
4 dot::TextObject,
5 lsp::LspManagerHandle,
6 ziplist,
7 ziplist::{Position, ZipList},
8};
9use ad_event::Source;
10use std::{
11 collections::VecDeque,
12 io::{self, ErrorKind},
13 mem,
14 path::Path,
15 sync::{Arc, RwLock},
16};
17
18#[cfg(test)]
19use crate::lsp::Req;
20#[cfg(test)]
21use std::sync::mpsc::Sender;
22
23const MAX_JUMPS: usize = 100;
24
25pub type BufferId = usize;
27
28#[derive(Debug)]
31pub struct Buffers {
32 next_id: BufferId,
33 inner: ZipList<Buffer>,
34 jump_list: JumpList,
35 lsp_handle: Arc<LspManagerHandle>,
36 config: Arc<RwLock<Config>>,
37}
38
39impl Buffers {
40 pub fn new(lsp_handle: Arc<LspManagerHandle>, config: Arc<RwLock<Config>>) -> Self {
41 Self {
42 next_id: 1,
43 inner: ziplist![Buffer::new_unnamed(0, "", config.clone())],
44 jump_list: JumpList::default(),
45 lsp_handle,
46 config,
47 }
48 }
49
50 #[cfg(test)]
51 pub(crate) fn new_with_raw_sender(tx_req: Sender<Req>, config: Arc<RwLock<Config>>) -> Self {
52 Self {
53 next_id: 1,
54 inner: ziplist![Buffer::new_unnamed(0, "", config.clone())],
55 jump_list: JumpList::default(),
56 lsp_handle: Arc::new(LspManagerHandle::new_stubbed(tx_req)),
57 config,
58 }
59 }
60
61 #[cfg(test)]
62 pub(crate) fn new_stubbed(
63 ids: &[usize],
64 tx_req: Sender<Req>,
65 config: Arc<RwLock<Config>>,
66 ) -> Self {
67 Self {
68 next_id: ids.last().unwrap() + 1,
69 inner: ZipList::try_from_iter(
70 ids.iter()
71 .map(|i| Buffer::new_virtual(*i, "", "", config.clone())),
72 )
73 .unwrap(),
74 jump_list: JumpList::default(),
75 lsp_handle: Arc::new(LspManagerHandle::new_stubbed(tx_req)),
76 config,
77 }
78 }
79
80 pub fn open_or_focus<P: AsRef<Path>>(
82 &mut self,
83 path: P,
84 retain_empty_unnamed: bool,
85 ) -> io::Result<Option<BufferId>> {
86 let path = match path.as_ref().canonicalize() {
87 Ok(p) => p,
88 Err(e) if e.kind() == ErrorKind::NotFound => path.as_ref().to_path_buf(),
89 Err(e) => return Err(e),
90 };
91
92 if self.active().kind.is_dir() && path.metadata().map(|m| m.is_dir()).unwrap_or_default() {
96 let b = self.active_mut();
97 b.kind = BufferKind::Directory(path);
98 b.reload_from_disk();
99 b.set_dot(TextObject::BufferStart, 1);
100 return Ok(None);
101 }
102
103 let existing_id = self.with_path(&path).map(|b| b.id);
106 if let Some(existing_id) = existing_id {
107 self.notify_lsp_changes_if_dirty();
108 self.record_jump_position();
109 self.inner.focus_element_by(|b| b.id == existing_id);
110 return Ok(None);
111 }
112
113 let id = self.next_id;
115 self.next_id += 1;
116 let mut b = Buffer::new_from_canonical_file_path(id, path, self.config.clone())?;
117
118 if !retain_empty_unnamed && self.is_empty_squirrel() {
121 mem::swap(&mut self.inner.focus, &mut b);
122 } else {
123 self.record_jump_position();
124 self.push_buffer(b);
125 }
126
127 self.lsp_handle.document_opened(self);
128
129 Ok(Some(id))
130 }
131
132 pub fn ensure_file_is_open<P: AsRef<Path>>(&mut self, path: P) {
133 let p = match path.as_ref().canonicalize() {
134 Ok(p) => p,
135 Err(_) => return,
136 };
137
138 if self.with_path(&p).is_some() {
139 return;
140 }
141
142 let id = self.next_id;
143 self.next_id += 1;
144
145 if let Ok(b) = Buffer::new_from_canonical_file_path(id, p, self.config.clone()) {
146 self.lsp_handle.document_opened(self);
147 self.inner.insert_at(Position::Tail, b);
148 }
149 }
150
151 #[inline]
156 fn notify_lsp_changes_if_dirty(&self) {
157 if self.inner.focus.dirty {
158 self.lsp_handle.document_changed(&self.inner.focus);
159 }
160 }
161
162 pub fn next(&mut self) {
163 self.notify_lsp_changes_if_dirty();
164 self.inner.focus_down();
165 }
166
167 pub fn previous(&mut self) {
168 self.notify_lsp_changes_if_dirty();
169 self.inner.focus_up();
170 }
171
172 pub fn iter(&self) -> impl Iterator<Item = &Buffer> {
173 self.inner.iter().map(|(_, b)| b)
174 }
175
176 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Buffer> {
177 self.inner.iter_mut().map(|(_, b)| b)
178 }
179
180 pub fn close_buffer(&mut self, id: BufferId) {
181 let removed = self.inner.remove_where_with_default(
182 |b| b.id == id,
183 || Buffer::new_unnamed(self.next_id, "", self.config.clone()),
184 );
185 self.jump_list.clear_for_buffer(id);
186
187 if let Some(b) = removed {
188 self.lsp_handle.document_closed(&b);
189 self.next_id += 1; }
191 }
192
193 fn push_buffer(&mut self, buf: Buffer) {
194 self.inner.insert(buf);
195 }
196
197 pub(crate) fn open_virtual(&mut self, name: String, content: String) -> BufferId {
198 let existing_id = self
199 .inner
200 .iter()
201 .find(|(_, b)| match &b.kind {
202 BufferKind::Virtual(s) => s == &name,
203 _ => false,
204 })
205 .map(|(_, b)| b.id);
206
207 if let Some(id) = existing_id {
208 self.focus_id(id);
209 self.inner.focus.txt = content.into();
210 let n = self.inner.focus.txt.len_chars();
211 self.inner.focus.dot.clamp_idx(n);
212 self.inner.focus.xdot.clamp_idx(n);
213 return id;
214 }
215
216 let id = self.next_id;
217 let buf = Buffer::new_virtual(id, name, content, self.config.clone());
218 self.record_jump_position();
219 self.push_buffer(buf);
220 self.next_id += 1;
221
222 id
223 }
224
225 pub(crate) fn as_buffer_list(&self) -> Vec<String> {
227 let mut entries: Vec<(usize, String)> = self
228 .inner
229 .iter()
230 .map(|(focused, b)| {
231 (
232 b.id,
233 format!(
234 "{:<4} {} {}",
235 b.id,
236 if focused { '*' } else { ' ' },
237 b.full_name()
238 ),
239 )
240 })
241 .collect();
242 entries.sort_by_key(|e| e.0);
243
244 entries.into_iter().map(|(_, s)| s).collect()
245 }
246
247 pub(crate) fn contains_bufid(&self, id: BufferId) -> bool {
248 self.inner.iter().any(|(_, b)| b.id == id)
249 }
250
251 pub(crate) fn focus_id(&mut self, id: BufferId) -> Option<BufferId> {
252 if !self.contains_bufid(id) || self.active().id == id {
253 return None;
254 }
255 self.notify_lsp_changes_if_dirty();
256 self.record_jump_position();
257 self.inner.focus_element_by(|b| b.id == id);
258
259 Some(id)
260 }
261
262 pub(crate) fn focus_id_silent(&mut self, id: BufferId) {
264 self.notify_lsp_changes_if_dirty();
265 self.inner.focus_element_by(|b| b.id == id);
266 }
267
268 pub(crate) fn with_id(&self, id: BufferId) -> Option<&Buffer> {
269 self.inner.iter().find(|(_, b)| b.id == id).map(|(_, b)| b)
270 }
271
272 pub(crate) fn with_id_mut(&mut self, id: BufferId) -> Option<&mut Buffer> {
273 self.inner
274 .iter_mut()
275 .find(|(_, b)| b.id == id)
276 .map(|(_, b)| b)
277 }
278
279 pub(crate) fn with_path<P: AsRef<Path>>(&self, path: P) -> Option<&Buffer> {
280 let path = path.as_ref();
281 self.inner
282 .iter()
283 .find(|(_, b)| match &b.kind {
284 BufferKind::File(p) | BufferKind::Directory(p) => p == path,
285 _ => false,
286 })
287 .map(|(_, b)| b)
288 }
289
290 pub fn dirty_buffers(&self) -> Vec<String> {
291 self.inner
292 .iter()
293 .filter(|(_, b)| b.dirty && b.kind.is_file())
294 .map(|(_, b)| b.full_name().to_string())
295 .collect()
296 }
297
298 #[inline]
299 pub fn active(&self) -> &Buffer {
300 &self.inner.focus
301 }
302
303 #[inline]
304 pub fn active_mut(&mut self) -> &mut Buffer {
305 &mut self.inner.focus
306 }
307
308 pub fn record_jump_position(&mut self) {
309 self.jump_list
310 .push(self.inner.focus.id, self.inner.focus.dot.active_cur());
311 }
312
313 fn jump(&mut self, bufid: BufferId, mut cur: Cur) -> (BufferId, BufferId) {
314 self.notify_lsp_changes_if_dirty();
315 let prev_id = self.inner.focus.id;
316 self.inner.focus_element_by(|b| b.id == bufid);
317 let new_id = self.inner.focus.id;
318 if new_id == bufid {
319 cur.clamp_idx(self.inner.focus.txt.len_chars());
322 self.inner.focus.dot = cur.into();
323 }
324
325 (prev_id, new_id)
326 }
327
328 pub fn jump_list_forward(&mut self) -> Option<(BufferId, BufferId)> {
329 if let Some((bufid, cur)) = self.jump_list.forward() {
330 Some(self.jump(bufid, cur))
331 } else {
332 None
333 }
334 }
335
336 pub fn jump_list_backward(&mut self) -> Option<(BufferId, BufferId)> {
337 let (bufid, cur) = (self.inner.focus.id, self.inner.focus.dot.active_cur());
338 if let Some((bufid, cur)) = self.jump_list.backward(bufid, cur) {
339 Some(self.jump(bufid, cur))
340 } else {
341 None
342 }
343 }
344
345 #[inline]
346 pub fn len(&self) -> usize {
347 self.inner.len()
348 }
349
350 #[inline]
353 pub fn is_empty_squirrel(&self) -> bool {
354 self.inner.len() == 1
355 && self.inner.focus.is_unnamed()
356 && (self.inner.focus.txt.is_empty() || self.inner.focus.txt == WELCOME_SQUIRREL)
357 }
358
359 pub(crate) fn write_output_for_buffer(&mut self, id: usize, s: String, cwd: &Path) -> BufferId {
361 let key = match self.with_id(id) {
362 Some(b) => b.output_file_key(cwd),
363 None => format!("{}/DEFAULT_OUTPUT_BUFFER", cwd.display()),
364 };
365
366 let k = BufferKind::Output(key.clone());
367 match self.inner.iter_mut().find(|(_, b)| b.kind == k) {
368 Some((_, b)) => {
369 b.append(s, Source::Fsys);
370
371 b.id
372 }
373
374 None => {
375 let id = self.next_id;
376 self.next_id += 1;
377 let b = Buffer::new_output(id, key, s, self.config.clone());
378 self.record_jump_position();
379 self.inner.insert(b);
380
381 id
382 }
383 }
384 }
385}
386
387#[derive(Debug, Clone, PartialEq, Eq)]
388struct JumpList {
389 idx: usize,
390 jumps: VecDeque<(BufferId, Cur)>,
391}
392
393impl Default for JumpList {
394 fn default() -> Self {
395 Self {
396 idx: 0,
397 jumps: VecDeque::with_capacity(MAX_JUMPS),
398 }
399 }
400}
401
402impl JumpList {
403 fn push(&mut self, id: BufferId, cur: Cur) {
404 self.jumps.truncate(self.idx);
405 let jump = (id, cur);
406
407 if self.jumps.back() == Some(&jump) {
408 return;
409 }
410 if self.jumps.len() == MAX_JUMPS {
411 self.jumps.pop_front();
412 }
413
414 self.jumps.push_back(jump);
415 self.idx = self.jumps.len();
416 }
417
418 fn forward(&mut self) -> Option<(BufferId, Cur)> {
419 if self.idx + 1 >= self.jumps.len() {
420 return None;
421 }
422
423 self.idx += 1;
424 self.jumps.get(self.idx).copied()
425 }
426
427 fn backward(&mut self, id: BufferId, cur: Cur) -> Option<(BufferId, Cur)> {
428 if self.idx == 0 {
429 return None;
430 }
431
432 if self.idx == self.jumps.len() {
436 self.push(id, cur);
437 self.idx -= 1;
438 }
439
440 self.idx -= 1;
441 self.jumps.get(self.idx).copied()
442 }
443
444 fn clear_for_buffer(&mut self, id: BufferId) {
446 self.jumps.retain(|j| j.0 != id);
447 self.idx = self.jumps.len();
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn jump_backward_clamps_stale_cursor_positions() {
457 let (tx, _rx) = std::sync::mpsc::channel();
458 let config = Arc::new(RwLock::new(Config::default()));
459 let mut buffers = Buffers::new_with_raw_sender(tx, config.clone());
460
461 let initial_content = "This is a test buffer with enough content to demonstrate the bug.";
462 buffers.inner.focus.txt = initial_content.into();
463
464 buffers.inner.focus.set_dot_from_cursor(60);
465 assert_eq!(buffers.inner.focus.dot.active_cur().idx, 60);
466
467 buffers.record_jump_position();
468
469 assert_eq!(buffers.inner.focus.txt.len_chars(), 65);
470 let new_len = 30;
471 buffers
472 .inner
473 .focus
474 .txt
475 .remove_range(new_len, buffers.inner.focus.txt.len_chars());
476 assert_eq!(buffers.inner.focus.txt.len_chars(), new_len);
477
478 buffers.inner.focus.set_dot_from_cursor(0);
479 let result = buffers.jump_list_backward();
480
481 assert!(result.is_some());
482
483 let cur = buffers.inner.focus.dot.active_cur();
484 let (_y, _x) = cur.as_yx(&buffers.inner.focus);
485
486 assert!(
487 cur.idx <= buffers.inner.focus.txt.len_chars(),
488 "cursor index {} exceeds buffer length {}",
489 cur.idx,
490 buffers.inner.focus.txt.len_chars()
491 );
492 }
493}