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 self.cancel_pending_pastes_for_buffer(id);
51
52 if let Some((_, preview_id)) = self.active_window().preview {
55 if preview_id == id {
56 self.active_window_mut().preview = None;
57 }
58 }
59
60 if let Some((wait_id, _)) = self.active_window_mut().wait_tracking.remove(&id) {
62 self.active_window_mut().completed_waits.push(wait_id);
63 }
64
65 self.active_window().save_file_state_on_close(id);
67
68 if let Err(e) = self.delete_buffer_recovery(id) {
70 tracing::debug!("Failed to delete buffer recovery on close: {}", e);
71 }
72
73 if let Some(terminal_id) = self.active_window_mut().terminal_buffers.remove(&id) {
75 self.active_window_mut().terminal_manager.close(terminal_id);
77 self.active_window_mut()
81 .terminal_explicit_titles
82 .remove(&id);
83 self.active_window_mut().terminal_fg_cache.remove(&id);
84
85 let backing_file = self
91 .active_window_mut()
92 .terminal_backing_files
93 .remove(&terminal_id);
94 if let Some(ref path) = backing_file {
95 self.retain_closed_terminal_backing(path);
96 }
97 if let Some(log_file) = self
99 .active_window_mut()
100 .terminal_log_files
101 .remove(&terminal_id)
102 {
103 if backing_file.as_ref() != Some(&log_file) {
104 #[allow(clippy::let_underscore_must_use)]
106 let _ = self.authority.filesystem.remove_file(&log_file);
107 }
108 }
109
110 self.active_window_mut().terminal_mode_resume.remove(&id);
112
113 if self.active_window().terminal_mode {
115 self.active_window_mut().terminal_mode = false;
116 self.active_window_mut().key_context =
117 crate::input::keybindings::KeyContext::Normal;
118 }
119 }
120
121 let active_split = self
125 .windows
126 .get(&self.active_window)
127 .and_then(|w| w.buffers.splits())
128 .map(|(mgr, _)| mgr)
129 .expect("active window must have a populated split layout")
130 .active_split();
131
132 let replacement_target: Option<crate::view::split::TabTarget> = self
133 .windows
134 .get(&self.active_window)
135 .and_then(|w| w.buffers.splits())
136 .map(|(_, vs)| vs)
137 .expect("active window must have a populated split layout")
138 .get(&active_split)
139 .and_then(|vs| {
140 use crate::view::split::TabTarget;
141 vs.focus_history.iter().rev().find_map(|t| match t {
142 TabTarget::Buffer(bid) if *bid == id => None, TabTarget::Buffer(bid) => {
144 let hidden = self
146 .active_window()
147 .buffer_metadata
148 .get(bid)
149 .map(|m| m.hidden_from_tabs)
150 .unwrap_or(false);
151 if hidden
152 || !self
153 .windows
154 .get(&self.active_window)
155 .map(|w| &w.buffers)
156 .expect("active window present")
157 .contains_key(bid)
158 {
159 None
160 } else {
161 Some(*t)
162 }
163 }
164 TabTarget::Group(leaf) => {
165 if self.active_window().grouped_subtrees.contains_key(leaf) {
167 Some(*t)
168 } else {
169 None
170 }
171 }
172 })
173 });
174
175 let fallback_buffer: Option<BufferId> = self.buffers().find_id(|bid, _| {
178 bid != id
179 && !self
180 .active_window()
181 .buffer_metadata
182 .get(&bid)
183 .map(|m| m.hidden_from_tabs)
184 .unwrap_or(false)
185 });
186
187 let closing_active = self.active_buffer() == id;
190
191 let return_to_group = match replacement_target {
198 Some(crate::view::split::TabTarget::Group(leaf)) => Some(leaf),
199 _ => None,
200 };
201
202 let direct_replacement = match replacement_target {
203 Some(crate::view::split::TabTarget::Buffer(bid)) => Some(bid),
204 _ => None,
205 };
206
207 let already_keyed = return_to_group.and_then(|_| {
215 self.windows
216 .get(&self.active_window)
217 .and_then(|w| w.buffers.splits())
218 .map(|(_, vs)| vs)
219 .expect("active window must have a populated split layout")
220 .get(&active_split)?
221 .keyed_states
222 .keys()
223 .find(|&&bid| bid != id)
224 .copied()
225 });
226
227 let any_remaining = return_to_group.and_then(|_| {
231 self.windows
232 .get(&self.active_window)
233 .map(|w| &w.buffers)
234 .expect("active window present")
235 .find_id(|bid, _| bid != id)
236 });
237
238 let (replacement_buffer, created_empty_buffer) = match direct_replacement
239 .or(already_keyed)
240 .or(fallback_buffer)
241 .or(any_remaining)
242 {
243 Some(bid) => (bid, false),
244 None => {
245 let new_id = self.new_buffer();
251 if !self
252 .config
253 .editor
254 .auto_create_empty_buffer_on_last_buffer_close
255 {
256 if let Some(meta) = self.active_window_mut().buffer_metadata.get_mut(&new_id) {
257 meta.hidden_from_tabs = true;
258 meta.synthetic_placeholder = true;
259 }
260 }
261 (new_id, true)
262 }
263 };
264
265 if closing_active {
269 self.set_active_buffer(replacement_buffer);
270
271 let hidden = self
280 .active_window()
281 .buffer_metadata
282 .get(&replacement_buffer)
283 .is_some_and(|m| m.hidden_from_tabs);
284 if return_to_group.is_some() && hidden {
285 use crate::view::split::TabTarget;
286 if let Some(vs) = self
287 .windows
288 .get_mut(&self.active_window)
289 .and_then(|w| w.split_view_states_mut())
290 .expect("active window must have a populated split layout")
291 .get_mut(&active_split)
292 {
293 vs.open_buffers
294 .retain(|t| *t != TabTarget::Buffer(replacement_buffer));
295 vs.focus_history
296 .retain(|t| *t != TabTarget::Buffer(replacement_buffer));
297 }
298 }
299 }
300
301 let splits_to_update = self
307 .windows
308 .get(&self.active_window)
309 .and_then(|w| w.buffers.splits())
310 .map(|(mgr, _)| mgr)
311 .expect("active window must have a populated split layout")
312 .splits_for_buffer(id);
313 for split_id in splits_to_update {
314 self.active_window_mut()
315 .set_pane_buffer(split_id, replacement_buffer);
316 }
317
318 self.windows
319 .get_mut(&self.active_window)
320 .map(|w| &mut w.buffers)
321 .expect("active window present")
322 .remove(&id);
323 self.detach_buffer_from_all_windows(id);
324 self.active_window_mut().event_logs.remove(&id);
325 self.active_window_mut().seen_byte_ranges.remove(&id);
326 self.active_window_mut().buffer_metadata.remove(&id);
327 self.active_window_mut().status_bar_values.remove(&id);
328 if let Some((request_id, _, _)) = self
329 .active_window_mut()
330 .semantic_tokens_in_flight
331 .remove(&id)
332 {
333 self.active_window_mut()
334 .pending_semantic_token_requests
335 .remove(&request_id);
336 }
337 if let Some((request_id, _, _, _)) = self
338 .active_window_mut()
339 .semantic_tokens_range_in_flight
340 .remove(&id)
341 {
342 self.active_window_mut()
343 .pending_semantic_token_range_requests
344 .remove(&request_id);
345 }
346 self.active_window_mut()
347 .semantic_tokens_range_last_request
348 .remove(&id);
349 self.active_window_mut()
350 .semantic_tokens_range_applied
351 .remove(&id);
352 self.active_window_mut()
353 .semantic_tokens_full_debounce
354 .remove(&id);
355
356 self.panel_ids_mut().retain(|_, &mut buf_id| buf_id != id);
360
361 for view_state in self
363 .windows
364 .get_mut(&self.active_window)
365 .and_then(|w| w.split_view_states_mut())
366 .expect("active window must have a populated split layout")
367 .values_mut()
368 {
369 view_state.remove_buffer(id);
370 view_state.remove_from_history(id);
371 }
372
373 if closing_active {
374 if created_empty_buffer && self.config.file_explorer.auto_open_on_last_buffer_close {
375 self.focus_file_explorer();
376 }
377 if let Some(group_leaf) = return_to_group {
378 self.activate_group_tab(active_split, group_leaf);
379 }
380 }
381
382 self.plugin_manager.read().unwrap().run_hook(
387 "buffer_closed",
388 fresh_core::hooks::HookArgs::BufferClosed { buffer_id: id },
389 );
390
391 Ok(())
392 }
393
394 pub fn switch_buffer(&mut self, id: BufferId) {
396 if self
397 .windows
398 .get(&self.active_window)
399 .map(|w| &w.buffers)
400 .expect("active window present")
401 .contains_key(&id)
402 && id != self.active_buffer()
403 {
404 self.active_window_mut()
406 .position_history
407 .commit_pending_movement();
408
409 let cursors = self.active_cursors();
411 let position = cursors.primary().position;
412 let anchor = cursors.primary().anchor;
413 let buffer_id = self.active_buffer();
414 let ph = &mut self.active_window_mut().position_history;
415 ph.record_movement(buffer_id, position, anchor);
416 ph.commit_pending_movement();
417
418 self.set_active_buffer(id);
419 }
420 }
421
422 pub fn close_tab(&mut self) {
431 let active_split = self
436 .windows
437 .get(&self.active_window)
438 .and_then(|w| w.buffers.splits())
439 .map(|(mgr, _)| mgr)
440 .expect("active window must have a populated split layout")
441 .active_split();
442 if let Some(group_leaf_id) = self
443 .windows
444 .get(&self.active_window)
445 .and_then(|w| w.buffers.splits())
446 .map(|(_, vs)| vs)
447 .expect("active window must have a populated split layout")
448 .get(&active_split)
449 .and_then(|vs| vs.active_group_tab)
450 {
451 self.close_buffer_group_by_leaf(group_leaf_id);
452 self.set_status_message(t!("buffer.tab_closed").to_string());
453 return;
454 }
455
456 let buffer_id = self.active_buffer();
460 self.close_tab_in_split(buffer_id, active_split);
461 }
462
463 pub fn close_tab_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
473 if self.active_window().terminal_mode && self.active_window().is_terminal_buffer(buffer_id)
475 {
476 self.active_window_mut().terminal_mode = false;
477 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
478 }
479
480 let buffer_in_other_splits = self
482 .windows
483 .get(&self.active_window)
484 .and_then(|w| w.buffers.splits())
485 .map(|(_, vs)| vs)
486 .expect("active window must have a populated split layout")
487 .iter()
488 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
489 .count();
490
491 let split_tabs = self
493 .windows
494 .get(&self.active_window)
495 .and_then(|w| w.buffers.splits())
496 .map(|(_, vs)| vs)
497 .expect("active window must have a populated split layout")
498 .get(&split_id)
499 .map(|vs| vs.buffer_tab_ids_vec())
500 .unwrap_or_default();
501
502 let is_last_viewport = buffer_in_other_splits == 0;
503
504 if is_last_viewport {
505 if let Some(state) = self
507 .windows
508 .get(&self.active_window)
509 .map(|w| &w.buffers)
510 .expect("active window present")
511 .get(&buffer_id)
512 {
513 if state.buffer.is_modified() {
514 let name = self.get_buffer_display_name(buffer_id);
516 let save_key = t!("prompt.key.save").to_string();
517 let discard_key = t!("prompt.key.discard").to_string();
518 let cancel_key = t!("prompt.key.cancel").to_string();
519 self.start_prompt(
520 t!(
521 "prompt.buffer_modified",
522 name = name,
523 save_key = save_key,
524 discard_key = discard_key,
525 cancel_key = cancel_key
526 )
527 .to_string(),
528 PromptType::ConfirmCloseBuffer { buffer_id },
529 );
530 return false;
531 }
532 }
533 let has_other_splits = self
540 .windows
541 .get(&self.active_window)
542 .and_then(|w| w.buffers.splits())
543 .map(|(mgr, _)| mgr)
544 .expect("active window must have a populated split layout")
545 .root()
546 .count_leaves()
547 > 1;
548 if split_tabs.len() <= 1 && has_other_splits {
549 self.handle_close_split(split_id.into());
550 if let Err(e) = self.close_buffer(buffer_id) {
553 tracing::debug!(
554 "close_tab_in_split: buffer cleanup after split close failed: {}",
555 e
556 );
557 }
558 self.set_status_message(t!("buffer.tab_closed").to_string());
559 return true;
560 }
561 if let Err(e) = self.close_buffer(buffer_id) {
562 self.set_status_message(t!("file.cannot_close", error = e.to_string()).to_string());
563 } else {
564 self.set_status_message(t!("buffer.tab_closed").to_string());
565 }
566 } else {
567 use crate::view::split::TabTarget;
568
569 let targets: Vec<TabTarget> = self
576 .windows
577 .get(&self.active_window)
578 .and_then(|w| w.buffers.splits())
579 .map(|(_, vs)| vs)
580 .expect("active window must have a populated split layout")
581 .get(&split_id)
582 .map(|vs| vs.open_buffers.clone())
583 .unwrap_or_default();
584
585 let closing = TabTarget::Buffer(buffer_id);
586 let closing_idx = targets.iter().position(|t| *t == closing).unwrap_or(0);
587 let has_other_tab = targets.iter().any(|t| *t != closing);
588
589 if !has_other_tab {
590 self.handle_close_split(split_id.into());
592 return true;
593 }
594
595 let replacement = if closing_idx > 0 {
600 targets[closing_idx - 1]
601 } else {
602 *targets
604 .iter()
605 .find(|t| **t != closing)
606 .expect("has_other_tab")
607 };
608
609 if let Some(view_state) = self
611 .windows
612 .get_mut(&self.active_window)
613 .and_then(|w| w.split_view_states_mut())
614 .expect("active window must have a populated split layout")
615 .get_mut(&split_id)
616 {
617 view_state.remove_buffer(buffer_id);
618 }
619
620 match replacement {
623 TabTarget::Buffer(replacement_buffer) => {
624 self.windows
625 .get_mut(&self.active_window)
626 .and_then(|w| w.split_manager_mut())
627 .expect("active window must have a populated split layout")
628 .set_split_buffer(split_id, replacement_buffer);
629 }
630 TabTarget::Group(group_leaf) => {
631 self.activate_group_tab(split_id, group_leaf);
632 }
633 }
634
635 self.set_status_message(t!("buffer.tab_closed").to_string());
636 }
637 true
638 }
639
640 pub fn close_other_tabs_in_split(&mut self, keep_buffer_id: BufferId, split_id: LeafId) {
642 let split_tabs = self
644 .windows
645 .get(&self.active_window)
646 .and_then(|w| w.buffers.splits())
647 .map(|(_, vs)| vs)
648 .expect("active window must have a populated split layout")
649 .get(&split_id)
650 .map(|vs| vs.buffer_tab_ids_vec())
651 .unwrap_or_default();
652
653 let tabs_to_close: Vec<_> = split_tabs
655 .iter()
656 .filter(|&&id| id != keep_buffer_id)
657 .copied()
658 .collect();
659
660 let mut closed = 0;
661 let mut skipped_modified = 0;
662 for buffer_id in tabs_to_close {
663 if self.close_tab_in_split_silent(buffer_id, split_id) {
664 closed += 1;
665 } else {
666 skipped_modified += 1;
667 }
668 }
669
670 self.windows
672 .get_mut(&self.active_window)
673 .and_then(|w| w.split_manager_mut())
674 .expect("active window must have a populated split layout")
675 .set_split_buffer(split_id, keep_buffer_id);
676
677 self.set_batch_close_status_message(closed, skipped_modified);
678 }
679
680 pub fn close_tabs_to_right_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
682 let split_tabs = self
684 .windows
685 .get(&self.active_window)
686 .and_then(|w| w.buffers.splits())
687 .map(|(_, vs)| vs)
688 .expect("active window must have a populated split layout")
689 .get(&split_id)
690 .map(|vs| vs.buffer_tab_ids_vec())
691 .unwrap_or_default();
692
693 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
695 return;
696 };
697
698 let tabs_to_close: Vec<_> = split_tabs.iter().skip(target_idx + 1).copied().collect();
700
701 let mut closed = 0;
702 let mut skipped_modified = 0;
703 for buf_id in tabs_to_close {
704 if self.close_tab_in_split_silent(buf_id, split_id) {
705 closed += 1;
706 } else {
707 skipped_modified += 1;
708 }
709 }
710
711 self.set_batch_close_status_message(closed, skipped_modified);
712 }
713
714 pub fn close_tabs_to_left_in_split(&mut self, buffer_id: BufferId, split_id: LeafId) {
716 let split_tabs = self
718 .windows
719 .get(&self.active_window)
720 .and_then(|w| w.buffers.splits())
721 .map(|(_, vs)| vs)
722 .expect("active window must have a populated split layout")
723 .get(&split_id)
724 .map(|vs| vs.buffer_tab_ids_vec())
725 .unwrap_or_default();
726
727 let Some(target_idx) = split_tabs.iter().position(|&id| id == buffer_id) else {
729 return;
730 };
731
732 let tabs_to_close: Vec<_> = split_tabs.iter().take(target_idx).copied().collect();
734
735 let mut closed = 0;
736 let mut skipped_modified = 0;
737 for buf_id in tabs_to_close {
738 if self.close_tab_in_split_silent(buf_id, split_id) {
739 closed += 1;
740 } else {
741 skipped_modified += 1;
742 }
743 }
744
745 self.set_batch_close_status_message(closed, skipped_modified);
746 }
747
748 pub fn close_all_tabs_in_split(&mut self, split_id: LeafId) {
750 let split_tabs = self
752 .windows
753 .get(&self.active_window)
754 .and_then(|w| w.buffers.splits())
755 .map(|(_, vs)| vs)
756 .expect("active window must have a populated split layout")
757 .get(&split_id)
758 .map(|vs| vs.buffer_tab_ids_vec())
759 .unwrap_or_default();
760
761 let mut closed = 0;
762 let mut skipped_modified = 0;
763
764 for buffer_id in split_tabs {
766 if self.close_tab_in_split_silent(buffer_id, split_id) {
767 closed += 1;
768 } else {
769 skipped_modified += 1;
770 }
771 }
772
773 self.set_batch_close_status_message(closed, skipped_modified);
774 }
775
776 fn set_batch_close_status_message(&mut self, closed: usize, skipped_modified: usize) {
778 let message = match (closed, skipped_modified) {
779 (0, 0) => t!("buffer.no_tabs_to_close").to_string(),
780 (0, n) => t!("buffer.skipped_modified", count = n).to_string(),
781 (n, 0) => t!("buffer.closed_tabs", count = n).to_string(),
782 (c, s) => t!("buffer.closed_tabs_skipped", closed = c, skipped = s).to_string(),
783 };
784 self.set_status_message(message);
785 }
786
787 fn close_tab_in_split_silent(&mut self, buffer_id: BufferId, split_id: LeafId) -> bool {
791 if self.active_window().terminal_mode && self.active_window().is_terminal_buffer(buffer_id)
793 {
794 self.active_window_mut().terminal_mode = false;
795 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
796 }
797
798 let buffer_in_other_splits = self
800 .windows
801 .get(&self.active_window)
802 .and_then(|w| w.buffers.splits())
803 .map(|(_, vs)| vs)
804 .expect("active window must have a populated split layout")
805 .iter()
806 .filter(|(&sid, view_state)| sid != split_id && view_state.has_buffer(buffer_id))
807 .count();
808
809 let split_tabs = self
811 .windows
812 .get(&self.active_window)
813 .and_then(|w| w.buffers.splits())
814 .map(|(_, vs)| vs)
815 .expect("active window must have a populated split layout")
816 .get(&split_id)
817 .map(|vs| vs.buffer_tab_ids_vec())
818 .unwrap_or_default();
819
820 let is_last_viewport = buffer_in_other_splits == 0;
821
822 if is_last_viewport {
823 if let Some(state) = self
826 .windows
827 .get(&self.active_window)
828 .map(|w| &w.buffers)
829 .expect("active window present")
830 .get(&buffer_id)
831 {
832 if state.buffer.is_modified() {
833 return false;
835 }
836 }
837 if let Err(e) = self.close_buffer(buffer_id) {
838 tracing::warn!("Failed to close buffer: {}", e);
839 }
840 true
841 } else {
842 if split_tabs.len() <= 1 {
844 self.handle_close_split(split_id.into());
846 return true;
847 }
848
849 let current_idx = split_tabs
851 .iter()
852 .position(|&id| id == buffer_id)
853 .unwrap_or(0);
854 let replacement_idx = if current_idx > 0 { current_idx - 1 } else { 1 };
855 let replacement_buffer = split_tabs.get(replacement_idx).copied();
856
857 if let Some(view_state) = self
859 .windows
860 .get_mut(&self.active_window)
861 .and_then(|w| w.split_view_states_mut())
862 .expect("active window must have a populated split layout")
863 .get_mut(&split_id)
864 {
865 view_state.remove_buffer(buffer_id);
866 }
867
868 if let Some(replacement) = replacement_buffer {
871 self.active_window_mut()
872 .set_pane_buffer(split_id, replacement);
873 }
874 true
875 }
876 }
877
878 pub fn next_buffer(&mut self) {
880 self.cycle_tab(1);
881 }
882
883 pub fn prev_buffer(&mut self) {
885 self.cycle_tab(-1);
886 }
887
888 fn cycle_tab(&mut self, direction: i32) {
891 use crate::view::split::TabTarget;
892
893 let active_split = self
894 .windows
895 .get(&self.active_window)
896 .and_then(|w| w.buffers.splits())
897 .map(|(mgr, _)| mgr)
898 .expect("active window must have a populated split layout")
899 .active_split();
900 let Some(view_state) = self
901 .windows
902 .get(&self.active_window)
903 .and_then(|w| w.buffers.splits())
904 .map(|(_, vs)| vs)
905 .expect("active window must have a populated split layout")
906 .get(&active_split)
907 else {
908 return;
909 };
910
911 let targets: Vec<TabTarget> = view_state
913 .open_buffers
914 .iter()
915 .copied()
916 .filter(|t| match t {
917 TabTarget::Buffer(id) => !self
918 .active_window()
919 .buffer_metadata
920 .get(id)
921 .map(|m| m.hidden_from_tabs)
922 .unwrap_or(false),
923 TabTarget::Group(_) => true,
924 })
925 .collect();
926
927 if targets.len() < 2 {
928 return;
929 }
930
931 let current_target = view_state.active_target();
932 let Some(idx) = targets.iter().position(|t| *t == current_target) else {
933 return;
934 };
935
936 let next_idx = if direction > 0 {
937 (idx + 1) % targets.len()
938 } else if idx == 0 {
939 targets.len() - 1
940 } else {
941 idx - 1
942 };
943
944 if targets[next_idx] == current_target {
945 return;
946 }
947
948 self.active_window_mut()
950 .position_history
951 .commit_pending_movement();
952 let cursors = self.active_cursors();
953 let position = cursors.primary().position;
954 let anchor = cursors.primary().anchor;
955 let buffer_id = self.active_buffer();
956 let ph = &mut self.active_window_mut().position_history;
957 ph.record_movement(buffer_id, position, anchor);
958 ph.commit_pending_movement();
959
960 self.active_window_mut()
968 .animate_tab_switch(active_split, direction.signum());
969
970 match targets[next_idx] {
971 TabTarget::Buffer(buffer_id) => {
972 self.set_active_buffer(buffer_id);
973 }
974 TabTarget::Group(group_leaf_id) => {
975 self.activate_group_tab(active_split, group_leaf_id);
976 }
977 }
978 }
979
980 pub fn navigate_back(&mut self) {
982 self.active_window_mut().in_navigation = true;
984
985 self.active_window_mut()
987 .position_history
988 .commit_pending_movement();
989
990 if self.active_window_mut().position_history.can_go_back()
993 && !self.active_window_mut().position_history.can_go_forward()
994 {
995 let cursors = self.active_cursors();
996 let position = cursors.primary().position;
997 let anchor = cursors.primary().anchor;
998 let buffer_id = self.active_buffer();
999 let ph = &mut self.active_window_mut().position_history;
1000 ph.record_movement(buffer_id, position, anchor);
1001 ph.commit_pending_movement();
1002 }
1003
1004 if let Some(entry) = self.active_window_mut().position_history.back() {
1006 let target_buffer = entry.buffer_id;
1007 let target_position = entry.position;
1008 let target_anchor = entry.anchor;
1009
1010 if self
1012 .windows
1013 .get(&self.active_window)
1014 .map(|w| &w.buffers)
1015 .expect("active window present")
1016 .contains_key(&target_buffer)
1017 {
1018 self.set_active_buffer(target_buffer);
1019
1020 let cursors = self.active_cursors();
1022 let cursor_id = cursors.primary_id();
1023 let old_position = cursors.primary().position;
1024 let old_anchor = cursors.primary().anchor;
1025 let old_sticky_column = cursors.primary().sticky_column;
1026 let event = Event::MoveCursor {
1027 cursor_id,
1028 old_position,
1029 new_position: target_position,
1030 old_anchor,
1031 new_anchor: target_anchor,
1032 old_sticky_column,
1033 new_sticky_column: 0, };
1035 let split_id = self
1036 .windows
1037 .get(&self.active_window)
1038 .and_then(|w| w.buffers.splits())
1039 .map(|(mgr, _)| mgr)
1040 .expect("active window must have a populated split layout")
1041 .active_split();
1042 self.active_window_mut()
1043 .apply_event_to_buffer(target_buffer, split_id, &event);
1044 self.active_window_mut()
1048 .ensure_active_cursor_visible_for_navigation(true);
1049 }
1050 }
1051
1052 self.active_window_mut().in_navigation = false;
1054 }
1055
1056 pub fn navigate_forward(&mut self) {
1058 self.active_window_mut().in_navigation = true;
1060
1061 if let Some(entry) = self.active_window_mut().position_history.forward() {
1062 let target_buffer = entry.buffer_id;
1063 let target_position = entry.position;
1064 let target_anchor = entry.anchor;
1065
1066 if self
1068 .windows
1069 .get(&self.active_window)
1070 .map(|w| &w.buffers)
1071 .expect("active window present")
1072 .contains_key(&target_buffer)
1073 {
1074 self.set_active_buffer(target_buffer);
1075
1076 let cursors = self.active_cursors();
1078 let cursor_id = cursors.primary_id();
1079 let old_position = cursors.primary().position;
1080 let old_anchor = cursors.primary().anchor;
1081 let old_sticky_column = cursors.primary().sticky_column;
1082 let event = Event::MoveCursor {
1083 cursor_id,
1084 old_position,
1085 new_position: target_position,
1086 old_anchor,
1087 new_anchor: target_anchor,
1088 old_sticky_column,
1089 new_sticky_column: 0, };
1091 let split_id = self
1092 .windows
1093 .get(&self.active_window)
1094 .and_then(|w| w.buffers.splits())
1095 .map(|(mgr, _)| mgr)
1096 .expect("active window must have a populated split layout")
1097 .active_split();
1098 self.active_window_mut()
1099 .apply_event_to_buffer(target_buffer, split_id, &event);
1100 self.active_window_mut()
1104 .ensure_active_cursor_visible_for_navigation(true);
1105 }
1106 }
1107
1108 self.active_window_mut().in_navigation = false;
1110 }
1111
1112 fn retain_closed_terminal_backing(&self, path: &std::path::Path) {
1118 use std::time::{SystemTime, UNIX_EPOCH};
1119 let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
1120 return;
1121 };
1122 let Some(parent) = path.parent() else {
1123 return;
1124 };
1125 let epoch_ms = SystemTime::now()
1126 .duration_since(UNIX_EPOCH)
1127 .map(|d| d.as_millis())
1128 .unwrap_or(0);
1129 let retained = parent.join(format!("{stem}-closed-{epoch_ms}.txt"));
1130 #[allow(clippy::let_underscore_must_use)]
1131 let _ = self.authority.filesystem.rename(path, &retained);
1132 self.gc_retained_terminal_backings(parent);
1133 }
1134
1135 fn gc_retained_terminal_backings(&self, dir: &std::path::Path) {
1140 const MAX_RETAINED: usize = 200;
1141 let Ok(entries) = self.authority.filesystem.read_dir(dir) else {
1142 return;
1143 };
1144 let mut retained: Vec<(u128, std::path::PathBuf)> = entries
1145 .into_iter()
1146 .filter_map(|e| {
1147 let rest = e.name.strip_suffix(".txt")?;
1148 let idx = rest.rfind("-closed-")?;
1149 let epoch: u128 = rest[idx + "-closed-".len()..].parse().ok()?;
1150 Some((epoch, e.path))
1151 })
1152 .collect();
1153 if retained.len() <= MAX_RETAINED {
1154 return;
1155 }
1156 retained.sort_by_key(|(epoch, _)| *epoch);
1157 let remove_count = retained.len() - MAX_RETAINED;
1158 for (_, p) in retained.into_iter().take(remove_count) {
1159 #[allow(clippy::let_underscore_must_use)]
1160 let _ = self.authority.filesystem.remove_file(&p);
1161 }
1162 }
1163}