aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-31 21:18:40 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-31 21:18:40 +0300
commit8cf0e9b35b00f9dd91270fcd3931015d61ffd8a0 (patch)
tree2b76b158344fdbe0de17c6abe98564a262266d2f /src/app/input
parent859185b491c5a80e888ba8f9dd213d2c35baa034 (diff)
fix: confirm implicit worktree group close
refs #369
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/modal.rs64
-rw-r--r--src/app/input/mouse.rs36
-rw-r--r--src/app/input/navigate.rs26
3 files changed, 96 insertions, 30 deletions
diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs
index 6cde98b5..e85e9bb1 100644
--- a/src/app/input/modal.rs
+++ b/src/app/input/modal.rs
@@ -695,12 +695,13 @@ pub(super) fn apply_context_menu_action(
state.selected = ws_idx;
state.active = Some(ws_idx);
state.switch_tab(tab_idx);
- state.close_tab();
- state.mode = if state.active.is_some() {
- Mode::Terminal
- } else {
- Mode::Navigate
- };
+ if !state.close_tab() {
+ state.mode = if state.active.is_some() {
+ Mode::Terminal
+ } else {
+ Mode::Navigate
+ };
+ }
}
(ContextMenuKind::Pane { pane_id, .. }, Some("Rename pane")) => {
open_rename_pane(state, pane_id);
@@ -732,12 +733,13 @@ pub(super) fn apply_context_menu_action(
state.mode = Mode::Terminal;
}
(ContextMenuKind::Pane { .. }, Some("Close pane")) => {
- state.close_pane();
- state.mode = if state.active.is_some() {
- Mode::Terminal
- } else {
- Mode::Navigate
- };
+ if !state.close_pane() {
+ state.mode = if state.active.is_some() {
+ Mode::Terminal
+ } else {
+ Mode::Navigate
+ };
+ }
}
_ => leave_modal(state),
}
@@ -1260,4 +1262,42 @@ mod tests {
assert!(state.workspaces.is_empty());
assert_eq!(state.mode, Mode::Navigate);
}
+
+ #[test]
+ fn context_menu_close_pane_last_parent_group_pane_keeps_confirmation_mode() {
+ let mut state = state_with_workspaces(&["main", "issue"]);
+ state.active = Some(0);
+ state.selected = 1;
+ state.workspaces[0].worktree_space = Some(crate::workspace::WorktreeSpaceMembership {
+ key: "repo-key".into(),
+ label: "herdr".into(),
+ repo_root: "/repo/herdr".into(),
+ checkout_path: "/repo/herdr".into(),
+ is_linked_worktree: false,
+ });
+ state.workspaces[1].worktree_space = Some(crate::workspace::WorktreeSpaceMembership {
+ key: "repo-key".into(),
+ label: "herdr".into(),
+ repo_root: "/repo/herdr".into(),
+ checkout_path: "/repo/herdr-issue".into(),
+ is_linked_worktree: true,
+ });
+ let pane_id = state.workspaces[0].tabs[0].root_pane;
+ let menu = ContextMenuState {
+ kind: ContextMenuKind::Pane {
+ pane_id,
+ has_manual_label: false,
+ },
+ x: 0,
+ y: 0,
+ list: MenuListState::new(4),
+ };
+ let mut terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new();
+
+ apply_context_menu_action(&mut state, &mut terminal_runtimes, menu, 4);
+
+ assert_eq!(state.selected, 0);
+ assert_eq!(state.mode, Mode::ConfirmClose);
+ assert_eq!(state.workspaces.len(), 2);
+ }
}
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index e3ad0bc5..cc03c8f6 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -145,14 +145,13 @@ impl AppState {
match mouse.kind {
MouseEventKind::ScrollUp => {
if let Some(open) = &mut self.worktree_open {
- open.selected = open.selected.saturating_sub(1);
+ open.select_previous_filtered();
}
return None;
}
MouseEventKind::ScrollDown => {
if let Some(open) = &mut self.worktree_open {
- open.selected =
- (open.selected + 1).min(open.entries.len().saturating_sub(1));
+ open.select_next_filtered();
}
return None;
}
@@ -237,21 +236,28 @@ impl AppState {
self.screen_rect(),
open.entries.len(),
) {
- let max_rows = inner.height.saturating_sub(4) as usize;
- let start = crate::ui::open_existing_worktree_visible_start(
- open.selected,
- max_rows,
- );
+ let filtered = open.filtered_indices();
+ let max_rows =
+ crate::ui::open_existing_worktree_max_visible_rows(inner);
+ let start =
+ crate::ui::open_existing_worktree_visible_start(open, max_rows);
+ if mouse.row == inner.y.saturating_add(1)
+ && mouse.column >= inner.x
+ && mouse.column < inner.x.saturating_add(inner.width)
+ {
+ if let Some(open) = &mut self.worktree_open {
+ open.search_focused = true;
+ }
+ return None;
+ }
let row_idx = if rect_contains(inner, mouse.column, mouse.row) {
mouse
.row
- .checked_sub(inner.y.saturating_add(2))
+ .checked_sub(inner.y.saturating_add(3))
.map(usize::from)
+ .map(|row| row / 2)
.filter(|row| *row < max_rows)
- .and_then(|row| {
- let entry_idx = start + row;
- (entry_idx < open.entries.len()).then_some(entry_idx)
- })
+ .and_then(|row| filtered.get(start + row).copied())
} else {
None
};
@@ -1657,6 +1663,8 @@ mod tests {
},
],
selected: 0,
+ query: String::new(),
+ search_focused: false,
error: None,
}
}
@@ -1810,7 +1818,7 @@ mod tests {
app.handle_mouse(mouse(
MouseEventKind::Down(MouseButton::Left),
inner.x + 1,
- inner.y + 3,
+ inner.y + 5,
));
assert_eq!(app.state.worktree_open.as_ref().unwrap().selected, 1);
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index 8d09c00a..b6edefaf 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -761,8 +761,9 @@ pub(super) fn execute_navigate_action_in_context(
leave_navigate_mode(state);
}
NavigateAction::CloseTab => {
- state.close_tab();
- leave_navigate_mode(state);
+ if !state.close_tab() {
+ leave_navigate_mode(state);
+ }
}
NavigateAction::RenamePane => {
if let Some(pane_id) = state
@@ -786,8 +787,9 @@ pub(super) fn execute_navigate_action_in_context(
leave_navigate_mode(state);
}
NavigateAction::ClosePane => {
- state.close_pane();
- leave_navigate_mode(state);
+ if !state.close_pane() {
+ leave_navigate_mode(state);
+ }
}
NavigateAction::EditScrollback => {}
NavigateAction::CopyMode => state.enter_copy_mode(terminal_runtimes),
@@ -1756,6 +1758,22 @@ last_pane = "prefix+tab"
assert_eq!(state.mode, Mode::Terminal);
}
+ #[test]
+ fn prefix_close_pane_last_parent_group_pane_opens_confirmation() {
+ let mut state = state_with_workspaces(&["main", "issue"]);
+ mark_worktree_space_member(&mut state, 0, "repo-key");
+ mark_worktree_space_member(&mut state, 1, "repo-key");
+ state.selected = 1;
+ state.active = Some(0);
+ state.mode = Mode::Navigate;
+
+ execute_navigate_action(&mut state, NavigateAction::ClosePane);
+
+ assert_eq!(state.selected, 0);
+ assert_eq!(state.mode, Mode::ConfirmClose);
+ assert_eq!(state.workspaces.len(), 2);
+ }
+
#[tokio::test]
async fn custom_command_runs_from_prefix_key_in_navigate_mode() {
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();