1use rust_i18n::t;
13
14use crate::model::event::{BufferId, Event, LeafId};
15use crate::view::prompt::PromptType;
16
17use super::Editor;
18
19impl Editor {
20 pub fn close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {
22 if let Some(state) = self
24 .windows
25 .get(&self.active_window)
26 .map(|w| &w.buffers)
27 .expect("active window present")
28 .get(&id)
29 {
30 if state.buffer.is_modified() {
31 return Err(anyhow::anyhow!("Buffer has unsaved changes"));
32 }
33 }
34 self.close_buffer_internal(id)
35 }
36
37 pub fn force_close_buffer(&mut self, id: BufferId) -> anyhow::Result<()> {
40 self.close_buffer_internal(id)
41 }
42
43 fn close_buffer_internal(&mut self, id: BufferId) -> anyhow::Result<()> {
45 if let Some((_, preview_id)) = self.active_window().preview {
48 if preview_id == id {
49 self.active_window_mut().preview = None;
50 }
51 }
52
53 if let Some((wait_id, _)) = self.active_window_mut().wait_tracking.remove(&id) {
55 self.active_window_mut().completed_waits.push(wait_id);
56 }
57
58 self.active_window().save_file_state_on_close(id);
60
61 if let Err(e) = self.delete_buffer_recovery(id) {
63 tracing::debug!("Failed to delete buffer recovery on close: {}", e);
64 }
65
66 if let Some(terminal_id) = self.active_window_mut().terminal_buffers.remove(&id) {
68 self.active_window_mut().terminal_manager.close(terminal_id);
70
71 let backing_file = self
77 .active_window_mut()
78 .terminal_backing_files
79 .remove(&terminal_id);
80 if let Some(ref path) = backing_file {
81 self.retain_closed_terminal_backing(path);
82 }
83 if let Some(log_file) = self
85 .active_window_mut()
86 .terminal_log_files
87 .remove(&terminal_id)
88 {
89 if backing_file.as_ref() != Some(&log_file) {
90 #[allow(clippy::let_underscore_must_use)]
92 let _ = self.authority.filesystem.remove_file(&log_file);
93 }
94 }
95
96 self.active_window_mut().terminal_mode_resume.remove(&id);
98
99 if self.active_window().terminal_mode {
101 self.active_window_mut().terminal_mode = false;
102 self.active_window_mut().key_context =
103 crate::input::keybindings::KeyContext::Normal;
104 }
105 }
106
107 let active_split = self
111 .windows
112 .get(&self.active_window)
113 .and_then(|w| w.buffers.splits())
114 .map(|(mgr, _)| mgr)
115 .expect("active window must have a populated split layout")
116 .active_split();
117
118 let replacement_target: Option<crate::view::split::TabTarget> = self
119 .windows
120 .get(&self.active_window)
121 .and_then(|w| w.buffers.splits())
122 .map(|(_, vs)| vs)
123 .expect("active window must have a populated split layout")
124 .get(&active_split)
125 .and_then(|vs| {
126 use crate::view::split::TabTarget;
127 vs.focus_history.iter().rev().find_map(|t| match t {
128 TabTarget::Buffer(bid) if *bid == id => None, TabTarget::Buffer(bid) => {
130 let hidden = self
132 .active_window()
133 .buffer_metadata
134 .get(bid)
135 .map(|m| m.hidden_from_tabs)
136 .unwrap_or(false);
137 if hidden
138 || !self
139 .windows
140 .get(&self.active_window)
141 .map(|w| &w.buffers)
142 .expect("active window present")
143 .contains_key(bid)
144 {
145 None
146 } else {
147 Some(*t)
148 }
149 }
150 TabTarget::Group(leaf) => {
151 if self.active_window().grouped_subtrees.contains_key(leaf) {
153 Some(*t)
154 } else {
155 None
156 }
157 }
158 })
159 });
160
161 let fallback_buffer: Option<BufferId> = self.buffers().find_id(|bid, _| {
164 bid != id
165 && !self
166 .active_window()
167 .buffer_metadata
168 .get(&bid)
169 .map(|m| m.hidden_from_tabs)
170 .unwrap_or(false)
171 });
172
173 let closing_active = self.active_buffer() == id;
176
177 let return_to_group = match replacement_target {
184 Some(crate::view::split::TabTarget::Group(leaf)) => Some(leaf),
185 _ => None,
186 };
187
188 let direct_replacement = match replacement_target {
189 Some(crate::view::split::TabTarget::Buffer(bid)) => Some(bid),
190 _ => None,
191 };
192
193 let already_keyed = return_to_group.and_then(|_| {
201 self.windows
202 .get(&self.active_window)
203 .and_then(|w| w.buffers.splits())
204 .map(|(_, vs)| vs)
205 .expect("active window must have a populated split layout")
206 .get(&active_split)?
207 .keyed_states
208 .keys()
209 .find(|&&bid| bid != id)
210 .copied()
211 });
212
213 let any_remaining = return_to_group.and_then(|_| {
217 self.windows
218 .get(&self.active_window)
219 .map(|w| &w.buffers)
220 .expect("active window present")
221 .find_id(|bid, _| bid != id)
222 });
223
224 let (replacement_buffer, created_empty_buffer) = match direct_replacement
225 .or(already_keyed)
226 .or(fallback_buffer)
227 .or(any_remaining)
228 {
229 Some(bid) => (bid, false),
230 None => {
231 let new_id = self.new_buffer();
237 if !self
238 .config
239 .editor
240 .auto_create_empty_buffer_on_last_buffer_close
241 {
242 if let Some(meta) = self.active_window_mut().buffer_metadata.get_mut(&new_id) {
243 meta.hidden_from_tabs = true;
244 meta.synthetic_placeholder = true;
245 }
246 }
247 (new_id, true)
248 }
249 };
250
251 if closing_active {
255 self.set_active_buffer(replacement_buffer);
256
257 let hidden = self
266 .active_window()
267 .buffer_metadata
268 .get(&replacement_buffer)
269 .is_some_and(|m| m.hidden_from_tabs);
270 if return_to_group.is_some() && hidden {
271 use crate::view::split::TabTarget;
272 if let Some(vs) = self
273 .windows
274 .get_mut(&self.active_window)
275 .and_then(|w| w.split_view_states_mut())
276 .expect("active window must have a populated split layout")
277 .get_mut(&active_split)
278 {
279 vs.open_buffers
280 .retain(|t| *t != TabTarget::Buffer(replacement_buffer));
281 vs.focus_history
282 .retain(|t| *t != TabTarget::Buffer(replacement_buffer));
283 }
284 }
285 }
286
287 let splits_to_update = self
293 .windows
294 .get(&self.active_window)
295 .and_then(|w| w.buffers.splits())
296 .map(|(mgr, _)| mgr)
297 .expect("active window must have a populated split layout")
298 .splits_for_buffer(id);
299 for split_id in splits_to_update {
300 self.active_window_mut()
301 .set_pane_buffer(split_id, replacement_buffer);
302 }
303
304 self.windows
305 .get_mut(&self.active_window)
306 .map(|w| &mut w.buffers)
307 .expect("active window present")
308 .remove(&id);
309 self.detach_buffer_from_all_windows(id);
310 self.active_window_mut().event_logs.remove(&id);
311 self.active_window_mut().seen_byte_ranges.remove(&id);
312 self.active_window_mut().buffer_metadata.remove(&id);
313 self.active_window_mut().status_bar_values.remove(&id);
314 if let Some((request_id, _, _)) = self
315 .active_window_mut()
316 .semantic_tokens_in_flight
317 .remove(&id)
318 {
319 self.active_window_mut()
320 .pending_semantic_token_requests
321 .remove(&request_id);
322 }
323 if let Some((request_id, _, _, _)) = self
324 .active_window_mut()
325 .semantic_tokens_range_in_flight
326 .remove(&id)
327 {
328 self.active_window_mut()
329 .pending_semantic_token_range_requests
330 .remove(&request_id);
331 }
332 self.active_window_mut()
333 .semantic_tokens_range_last_request
334 .remove(&id);
335 self.active_window_mut()
336 .semantic_tokens_range_applied
337 .remove(&id);
338 self.active_window_mut()
339 .semantic_tokens_full_debounce
340 .remove(&id);
341
342 self.panel_ids_mut().retain(|_, &mut buf_id| buf_id != id);
346
347 for view_state in self
349 .windows
350 .get_mut(&self.active_window)
351 .and_then(|w| w.split_view_states_mut())
352 .expect("active window must have a populated split layout")
353 .values_mut()
354 {
355 view_state.remove_buffer(id);
356 view_state.remove_from_history(id);
357 }
358
359 if closing_active {
360 if created_empty_buffer && self.config.file_explorer.auto_open_on_last_buffer_close {
361 self.focus_file_explorer();
362 }
363 if let Some(group_leaf) = return_to_group {
364 self.activate_group_tab(active_split, group_leaf);
365 }
366 }
367
368 self.plugin_manager.read().unwrap().run_hook(
373 "buffer_closed",
374 fresh_core::hooks::HookArgs::BufferClosed { buffer_id: id },
375 );
376
377 Ok(())
378 }
379
380 pub fn switch_buffer(&mut self, id: BufferId) {
382 if self
383 .windows
384 .get(&self.active_window)
385 .map(|w| &w.buffers)
386 .expect("active window present")
387 .contains_key(&id)
388 && id != self.active_buffer()
389 {
390 self.active_window_mut()
392 .position_history
393 .commit_pending_movement();
394
395 let cursors = self.active_cursors();
397 let position = cursors.primary().position;
398 let anchor = cursors.primary().anchor;
399 let buffer_id = self.active_buffer();
400 let ph = &mut self.active_window_mut().position_history;
401 ph.record_movement(buffer_id, position, anchor);
402 ph.commit_pending_movement();
403
404 self.set_active_buffer(id);
405 }
406 }
407
408 pub fn close_tab(&mut self) {
417 let active_split = self
422 .windows
423 .get(&self.active_window)
424 .and_then(|w| w.buffers.splits())
425 .map(|(mgr, _)| mgr)
426 .expect("active window must have a populated split layout")
427 .active_split();
428 if let Some(group_leaf_id) = self
429 .windows
430 .get(&self.active_window)
431 .and_then(|w| w.buffers.splits())
432 .map(|(_, vs)| vs)
433 .expect("active window must have a populated split layout")
434 .get(&active_split)
435 .and_then(|vs| vs.active_group_tab)
436 {
437 self.close_buffer_group_by_leaf(group_leaf_id);
438 self.set_status_message(t!("buffer.tab_closed").to_string());
439 return;
440 }
441
442 let buffer_id = self.active_buffer();
446 self.close_tab_in_split(buffer_id, active_split);
447 }
448
449 pub fn close_tab_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
459 if self.active_window().terminal_mode && self.active_window().is_terminal_buffer(buffer_id)
461 {
462 self.active_window_mut().terminal_mode = false;
463 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
464 }
465
466 let buffer_in_other_splits = self
468 .windows
469 .get(&self.active_window)
470 .and_then(|w| w.buffers.splits())
471 .map(|(_, vs)| vs)
472 .expect("active window must have a populated split layout")
473 .iter()
474 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
475 .count();
476
477 let split_tabs = self
479 .windows
480 .get(&self.active_window)
481 .and_then(|w| w.buffers.splits())
482 .map(|(_, vs)| vs)
483 .expect("active window must have a populated split layout")
484 .get(&split_id)
485 .map(|vs| vs.buffer_tab_ids_vec())
486 .unwrap_or_default();
487
488 let is_last_viewport = buffer_in_other_splits == 0;
489
490 if is_last_viewport {
491 if let Some(state) = self
493 .windows
494 .get(&self.active_window)
495 .map(|w| &w.buffers)
496 .expect("active window present")
497 .get(&buffer_id)
498 {
499 if state.buffer.is_modified() {
500 let name = self.get_buffer_display_name(buffer_id);
502 let save_key = t!("prompt.key.save").to_string();
503 let discard_key = t!("prompt.key.discard").to_string();
504 let cancel_key = t!("prompt.key.cancel").to_string();
505 self.start_prompt(
506 t!(
507 "prompt.buffer_modified",
508 name = name,
509 save_key = save_key,
510 discard_key = discard_key,
511 cancel_key = cancel_key
512 )
513 .to_string(),
514 PromptType::ConfirmCloseBuffer { buffer_id },
515 );
516 return false;
517 }
518 }
519 let has_other_splits = self
526 .windows
527 .get(&self.active_window)
528 .and_then(|w| w.buffers.splits())
529 .map(|(mgr, _)| mgr)
530 .expect("active window must have a populated split layout")
531 .root()
532 .count_leaves()
533 > 1;
534 if split_tabs.len() <= 1 && has_other_splits {
535 self.handle_close_split(split_id.into());
536 if let Err(e) = self.close_buffer(buffer_id) {
539 tracing::debug!(
540 "close_tab_in_split: buffer cleanup after split close failed: {}",
541 e
542 );
543 }
544 self.set_status_message(t!("buffer.tab_closed").to_string());
545 return true;
546 }
547 if let Err(e) = self.close_buffer(buffer_id) {
548 self.set_status_message(t!("file.cannot_close", error = e.to_string()).to_string());
549 } else {
550 self.set_status_message(t!("buffer.tab_closed").to_string());
551 }
552 } else {
553 use crate::view::split::TabTarget;
554
555 let targets: Vec<TabTarget> = self
562 .windows
563 .get(&self.active_window)
564 .and_then(|w| w.buffers.splits())
565 .map(|(_, vs)| vs)
566 .expect("active window must have a populated split layout")
567 .get(&split_id)
568 .map(|vs| vs.open_buffers.clone())
569 .unwrap_or_default();
570
571 let closing = TabTarget::Buffer(buffer_id);
572 let closing_idx = targets.iter().position(|t| *t == closing).unwrap_or(0);
573 let has_other_tab = targets.iter().any(|t| *t != closing);
574
575 if !has_other_tab {
576 self.handle_close_split(split_id.into());
578 return true;
579 }
580
581 let replacement = if closing_idx > 0 {
586 targets[closing_idx - 1]
587 } else {
588 *targets
590 .iter()
591 .find(|t| **t != closing)
592 .expect("has_other_tab")
593 };
594
595 if let Some(view_state) = self
597 .windows
598 .get_mut(&self.active_window)
599 .and_then(|w| w.split_view_states_mut())
600 .expect("active window must have a populated split layout")
601 .get_mut(&split_id)
602 {
603 view_state.remove_buffer(buffer_id);
604 }
605
606 match replacement {
609 TabTarget::Buffer(replacement_buffer) => {
610 self.windows
611 .get_mut(&self.active_window)
612 .and_then(|w| w.split_manager_mut())
613 .expect("active window must have a populated split layout")
614 .set_split_buffer(split_id, replacement_buffer);
615 }
616 TabTarget::Group(group_leaf) => {
617 self.activate_group_tab(split_id, group_leaf);
618 }
619 }
620
621 self.set_status_message(t!("buffer.tab_closed").to_string());
622 }
623 true
624 }
625
626 pub fn close_other_tabs_in_split(&mut self, keep_buffer_id: BufferId, split_id: LeafId) {
628 let split_tabs = self
630 .windows
631 .get(&self.active_window)
632 .and_then(|w| w.buffers.splits())
633 .map(|(_, vs)| vs)
634 .expect("active window must have a populated split layout")
635 .get(&split_id)
636 .map(|vs| vs.buffer_tab_ids_vec())
637 .unwrap_or_default();
638
639 let tabs_to_close: Vec<_> = split_tabs
641 .iter()
642 .filter(|&&id| id != keep_buffer_id)
643 .copied()
644 .collect();
645
646 let mut closed = 0;
647 let mut skipped_modified = 0;
648 for buffer_id in tabs_to_close {
649 if self.close_tab_in_split_silent(buffer_id, split_id) {
650 closed += 1;
651 } else {
652 skipped_modified += 1;
653 }
654 }
655
656 self.windows
658 .get_mut(&self.active_window)
659 .and_then(|w| w.split_manager_mut())
660 .expect("active window must have a populated split layout")
661 .set_split_buffer(split_id, keep_buffer_id);
662
663 self.set_batch_close_status_message(closed, skipped_modified);
664 }
665
666 pub fn close_tabs_to_right_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
668 let split_tabs = self
670 .windows
671 .get(&self.active_window)
672 .and_then(|w| w.buffers.splits())
673 .map(|(_, vs)| vs)
674 .expect("active window must have a populated split layout")
675 .get(&split_id)
676 .map(|vs| vs.buffer_tab_ids_vec())
677 .unwrap_or_default();
678
679 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
681 return;
682 };
683
684 let tabs_to_close: Vec<_> = split_tabs.iter().skip(target_idx + 1).copied().collect();
686
687 let mut closed = 0;
688 let mut skipped_modified = 0;
689 for buf_id in tabs_to_close {
690 if self.close_tab_in_split_silent(buf_id, split_id) {
691 closed += 1;
692 } else {
693 skipped_modified += 1;
694 }
695 }
696
697 self.set_batch_close_status_message(closed, skipped_modified);
698 }
699
700 pub fn close_tabs_to_left_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
702 let split_tabs = self
704 .windows
705 .get(&self.active_window)
706 .and_then(|w| w.buffers.splits())
707 .map(|(_, vs)| vs)
708 .expect("active window must have a populated split layout")
709 .get(&split_id)
710 .map(|vs| vs.buffer_tab_ids_vec())
711 .unwrap_or_default();
712
713 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
715 return;
716 };
717
718 let tabs_to_close: Vec<_> = split_tabs.iter().take(target_idx).copied().collect();
720
721 let mut closed = 0;
722 let mut skipped_modified = 0;
723 for buf_id in tabs_to_close {
724 if self.close_tab_in_split_silent(buf_id, split_id) {
725 closed += 1;
726 } else {
727 skipped_modified += 1;
728 }
729 }
730
731 self.set_batch_close_status_message(closed, skipped_modified);
732 }
733
734 pub fn close_all_tabs_in_split(&mut self, split_id: LeafId) {
736 let split_tabs = self
738 .windows
739 .get(&self.active_window)
740 .and_then(|w| w.buffers.splits())
741 .map(|(_, vs)| vs)
742 .expect("active window must have a populated split layout")
743 .get(&split_id)
744 .map(|vs| vs.buffer_tab_ids_vec())
745 .unwrap_or_default();
746
747 let mut closed = 0;
748 let mut skipped_modified = 0;
749
750 for buffer_id in split_tabs {
752 if self.close_tab_in_split_silent(buffer_id, split_id) {
753 closed += 1;
754 } else {
755 skipped_modified += 1;
756 }
757 }
758
759 self.set_batch_close_status_message(closed, skipped_modified);
760 }
761
762 fn set_batch_close_status_message(&mut self, closed: usize, skipped_modified: usize) {
764 let message = match (closed, skipped_modified) {
765 (0, 0) => t!("buffer.no_tabs_to_close").to_string(),
766 (0, n) => t!("buffer.skipped_modified", count = n).to_string(),
767 (n, 0) => t!("buffer.closed_tabs", count = n).to_string(),
768 (c, s) => t!("buffer.closed_tabs_skipped", closed = c, skipped = s).to_string(),
769 };
770 self.set_status_message(message);
771 }
772
773 fn close_tab_in_split_silent(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
777 if self.active_window().terminal_mode && self.active_window().is_terminal_buffer(buffer_id)
779 {
780 self.active_window_mut().terminal_mode = false;
781 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
782 }
783
784 let buffer_in_other_splits = self
786 .windows
787 .get(&self.active_window)
788 .and_then(|w| w.buffers.splits())
789 .map(|(_, vs)| vs)
790 .expect("active window must have a populated split layout")
791 .iter()
792 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
793 .count();
794
795 let split_tabs = self
797 .windows
798 .get(&self.active_window)
799 .and_then(|w| w.buffers.splits())
800 .map(|(_, vs)| vs)
801 .expect("active window must have a populated split layout")
802 .get(&split_id)
803 .map(|vs| vs.buffer_tab_ids_vec())
804 .unwrap_or_default();
805
806 let is_last_viewport = buffer_in_other_splits == 0;
807
808 if is_last_viewport {
809 if let Some(state) = self
812 .windows
813 .get(&self.active_window)
814 .map(|w| &w.buffers)
815 .expect("active window present")
816 .get(&buffer_id)
817 {
818 if state.buffer.is_modified() {
819 return false;
821 }
822 }
823 if let Err(e) = self.close_buffer(buffer_id) {
824 tracing::warn!("Failed to close buffer: {}", e);
825 }
826 true
827 } else {
828 if split_tabs.len() <= 1 {
830 self.handle_close_split(split_id.into());
832 return true;
833 }
834
835 let current_idx = split_tabs
837 .iter()
838 .position(|&id| id == buffer_id)
839 .unwrap_or(0);
840 let replacement_idx = if current_idx > 0 { current_idx - 1 } else { 1 };
841 let replacement_buffer = split_tabs.get(replacement_idx).copied();
842
843 if let Some(view_state) = self
845 .windows
846 .get_mut(&self.active_window)
847 .and_then(|w| w.split_view_states_mut())
848 .expect("active window must have a populated split layout")
849 .get_mut(&split_id)
850 {
851 view_state.remove_buffer(buffer_id);
852 }
853
854 if let Some(replacement) = replacement_buffer {
857 self.active_window_mut()
858 .set_pane_buffer(split_id, replacement);
859 }
860 true
861 }
862 }
863
864 pub fn next_buffer(&mut self) {
866 self.cycle_tab(1);
867 }
868
869 pub fn prev_buffer(&mut self) {
871 self.cycle_tab(-1);
872 }
873
874 fn cycle_tab(&mut self, direction: i32) {
877 use crate::view::split::TabTarget;
878
879 let active_split = self
880 .windows
881 .get(&self.active_window)
882 .and_then(|w| w.buffers.splits())
883 .map(|(mgr, _)| mgr)
884 .expect("active window must have a populated split layout")
885 .active_split();
886 let Some(view_state) = self
887 .windows
888 .get(&self.active_window)
889 .and_then(|w| w.buffers.splits())
890 .map(|(_, vs)| vs)
891 .expect("active window must have a populated split layout")
892 .get(&active_split)
893 else {
894 return;
895 };
896
897 let targets: Vec<TabTarget> = view_state
899 .open_buffers
900 .iter()
901 .copied()
902 .filter(|t| match t {
903 TabTarget::Buffer(id) => !self
904 .active_window()
905 .buffer_metadata
906 .get(id)
907 .map(|m| m.hidden_from_tabs)
908 .unwrap_or(false),
909 TabTarget::Group(_) => true,
910 })
911 .collect();
912
913 if targets.len() < 2 {
914 return;
915 }
916
917 let current_target = view_state.active_target();
918 let Some(idx) = targets.iter().position(|t| *t == current_target) else {
919 return;
920 };
921
922 let next_idx = if direction > 0 {
923 (idx + 1) % targets.len()
924 } else if idx == 0 {
925 targets.len() - 1
926 } else {
927 idx - 1
928 };
929
930 if targets[next_idx] == current_target {
931 return;
932 }
933
934 self.active_window_mut()
936 .position_history
937 .commit_pending_movement();
938 let cursors = self.active_cursors();
939 let position = cursors.primary().position;
940 let anchor = cursors.primary().anchor;
941 let buffer_id = self.active_buffer();
942 let ph = &mut self.active_window_mut().position_history;
943 ph.record_movement(buffer_id, position, anchor);
944 ph.commit_pending_movement();
945
946 self.active_window_mut()
954 .animate_tab_switch(active_split, direction.signum());
955
956 match targets[next_idx] {
957 TabTarget::Buffer(buffer_id) => {
958 self.set_active_buffer(buffer_id);
959 }
960 TabTarget::Group(group_leaf_id) => {
961 self.activate_group_tab(active_split, group_leaf_id);
962 }
963 }
964 }
965
966 pub fn navigate_back(&mut self) {
968 self.active_window_mut().in_navigation = true;
970
971 self.active_window_mut()
973 .position_history
974 .commit_pending_movement();
975
976 if self.active_window_mut().position_history.can_go_back()
979 && !self.active_window_mut().position_history.can_go_forward()
980 {
981 let cursors = self.active_cursors();
982 let position = cursors.primary().position;
983 let anchor = cursors.primary().anchor;
984 let buffer_id = self.active_buffer();
985 let ph = &mut self.active_window_mut().position_history;
986 ph.record_movement(buffer_id, position, anchor);
987 ph.commit_pending_movement();
988 }
989
990 if let Some(entry) = self.active_window_mut().position_history.back() {
992 let target_buffer = entry.buffer_id;
993 let target_position = entry.position;
994 let target_anchor = entry.anchor;
995
996 if self
998 .windows
999 .get(&self.active_window)
1000 .map(|w| &w.buffers)
1001 .expect("active window present")
1002 .contains_key(&target_buffer)
1003 {
1004 self.set_active_buffer(target_buffer);
1005
1006 let cursors = self.active_cursors();
1008 let cursor_id = cursors.primary_id();
1009 let old_position = cursors.primary().position;
1010 let old_anchor = cursors.primary().anchor;
1011 let old_sticky_column = cursors.primary().sticky_column;
1012 let event = Event::MoveCursor {
1013 cursor_id,
1014 old_position,
1015 new_position: target_position,
1016 old_anchor,
1017 new_anchor: target_anchor,
1018 old_sticky_column,
1019 new_sticky_column: 0, };
1021 let split_id = self
1022 .windows
1023 .get(&self.active_window)
1024 .and_then(|w| w.buffers.splits())
1025 .map(|(mgr, _)| mgr)
1026 .expect("active window must have a populated split layout")
1027 .active_split();
1028 self.active_window_mut()
1029 .apply_event_to_buffer(target_buffer, split_id, &event);
1030 self.active_window_mut()
1034 .ensure_active_cursor_visible_for_navigation(true);
1035 }
1036 }
1037
1038 self.active_window_mut().in_navigation = false;
1040 }
1041
1042 pub fn navigate_forward(&mut self) {
1044 self.active_window_mut().in_navigation = true;
1046
1047 if let Some(entry) = self.active_window_mut().position_history.forward() {
1048 let target_buffer = entry.buffer_id;
1049 let target_position = entry.position;
1050 let target_anchor = entry.anchor;
1051
1052 if self
1054 .windows
1055 .get(&self.active_window)
1056 .map(|w| &w.buffers)
1057 .expect("active window present")
1058 .contains_key(&target_buffer)
1059 {
1060 self.set_active_buffer(target_buffer);
1061
1062 let cursors = self.active_cursors();
1064 let cursor_id = cursors.primary_id();
1065 let old_position = cursors.primary().position;
1066 let old_anchor = cursors.primary().anchor;
1067 let old_sticky_column = cursors.primary().sticky_column;
1068 let event = Event::MoveCursor {
1069 cursor_id,
1070 old_position,
1071 new_position: target_position,
1072 old_anchor,
1073 new_anchor: target_anchor,
1074 old_sticky_column,
1075 new_sticky_column: 0, };
1077 let split_id = self
1078 .windows
1079 .get(&self.active_window)
1080 .and_then(|w| w.buffers.splits())
1081 .map(|(mgr, _)| mgr)
1082 .expect("active window must have a populated split layout")
1083 .active_split();
1084 self.active_window_mut()
1085 .apply_event_to_buffer(target_buffer, split_id, &event);
1086 self.active_window_mut()
1090 .ensure_active_cursor_visible_for_navigation(true);
1091 }
1092 }
1093
1094 self.active_window_mut().in_navigation = false;
1096 }
1097
1098 fn retain_closed_terminal_backing(&self, path: &std::path::Path) {
1104 use std::time::{SystemTime, UNIX_EPOCH};
1105 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
1106 return;
1107 };
1108 let Some(parent) = path.parent() else {
1109 return;
1110 };
1111 let epoch_ms = SystemTime::now()
1112 .duration_since(UNIX_EPOCH)
1113 .map(|d| d.as_millis())
1114 .unwrap_or(0);
1115 let retained = parent.join(format!("{stem}-closed-{epoch_ms}.txt"));
1116 #[allow(clippy::let_underscore_must_use)]
1117 let _ = self.authority.filesystem.rename(path, &retained);
1118 self.gc_retained_terminal_backings(parent);
1119 }
1120
1121 fn gc_retained_terminal_backings(&self, dir: &std::path::Path) {
1126 const MAX_RETAINED: usize = 200;
1127 let Ok(entries) = self.authority.filesystem.read_dir(dir) else {
1128 return;
1129 };
1130 let mut retained: Vec<(u128, std::path::PathBuf)> = entries
1131 .into_iter()
1132 .filter_map(|e| {
1133 let rest = e.name.strip_suffix(".txt")?;
1134 let idx = rest.rfind("-closed-")?;
1135 let epoch: u128 = rest[idx + "-closed-".len()..].parse().ok()?;
1136 Some((epoch, e.path))
1137 })
1138 .collect();
1139 if retained.len() <= MAX_RETAINED {
1140 return;
1141 }
1142 retained.sort_by_key(|(epoch, _)| *epoch);
1143 let remove_count = retained.len() - MAX_RETAINED;
1144 for (_, p) in retained.into_iter().take(remove_count) {
1145 #[allow(clippy::let_underscore_must_use)]
1146 let _ = self.authority.filesystem.remove_file(&p);
1147 }
1148 }
1149}