Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion crates/cli/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,9 @@ pub enum SessionTitleMenuAction {
PlaybookTerminalMode,
SplitHorizontal,
SplitVertical,
/// Fill the screen with this pane (or restore the split layout when
/// something is already zoomed) — the menu twin of the zoom chord.
Zoom,
CloseSplit,
Archive,
/// Fork-only: merge the fork's result into its parent and archive it.
Expand Down Expand Up @@ -1810,12 +1813,13 @@ impl OperatorTitleMenuAction {
}

impl SessionTitleMenuAction {
pub const ALL: [Self; 10] = [
pub const ALL: [Self; 11] = [
Self::Rename,
Self::Fork,
Self::PlaybookTerminalMode,
Self::SplitHorizontal,
Self::SplitVertical,
Self::Zoom,
Self::CloseSplit,
Self::Archive,
Self::Merge,
Expand All @@ -1831,6 +1835,7 @@ impl SessionTitleMenuAction {
Self::PlaybookTerminalMode => "playbook mode",
Self::SplitHorizontal => "split horizontal",
Self::SplitVertical => "split vertical",
Self::Zoom => "zoom",
Self::CloseSplit => "close split",
Self::Archive => "archive",
Self::Merge => "merge and archive",
Expand Down Expand Up @@ -33729,6 +33734,76 @@ mod tests {
server.abort();
}

/// The menu's zoom row is the mouse twin of the zoom chord. The chord
/// picks its target from the focused pane, which a mouse user may have
/// left on the list — the row must zoom the view the menu hangs off
/// anyway.
#[tokio::test]
async fn session_menu_zoom_row_zooms_the_view_pane() {
use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
let (mut app, _dir, server) = captured_app().await;
app.main_windows = MainWindowTree::single(1, Selection::Session("s1".into()));
app.active_window_id = 1;
app.focus = PaneFocus::List;

let backend = ratatui::backend::TestBackend::new(100, 30);
let mut term = ratatui::Terminal::new(backend).expect("terminal");
term.draw(|f| crate::ui::render(f, &mut app)).expect("render");

let pane = app
.layout
.main_window_areas
.first()
.copied()
.expect("single pane registered");
let (bx, _, by) = crate::ui::view_close_button_range(pane.area);
let click = |kind, col, row| MouseEvent {
kind,
column: col,
row,
modifiers: crossterm::event::KeyModifiers::empty(),
};

app.on_mouse(click(MouseEventKind::Down(MouseButton::Left), bx + 1, by))
.await;
app.on_mouse(click(MouseEventKind::Up(MouseButton::Left), bx + 1, by))
.await;
let menu = app
.session_title_menu
.clone()
.expect("clicking the actions button opens the session menu");
term.draw(|f| crate::ui::render(f, &mut app))
.expect("render open menu");
assert!(
rendered_text(term.backend().buffer()).contains("zoom"),
"the menu should offer zoom"
);

let zoom_idx = SessionTitleMenuAction::ALL
.iter()
.position(|action| *action == SessionTitleMenuAction::Zoom)
.expect("zoom menu row");
let item_row = menu.area.y + 1 + zoom_idx as u16;
let item_col = menu.area.x + 2;
assert_eq!(
menu.item_at(item_col, item_row),
Some(SessionTitleMenuAction::Zoom)
);
app.on_mouse(click(MouseEventKind::Down(MouseButton::Left), item_col, item_row))
.await;
app.on_mouse(click(MouseEventKind::Up(MouseButton::Left), item_col, item_row))
.await;

assert_eq!(app.zoom, ZoomMode::View, "the menu row zooms the view pane");
assert_eq!(app.focus, PaneFocus::View, "zooming follows focus to the view");
assert!(
app.session_title_menu.is_none(),
"acting on a row closes the menu"
);

server.abort();
}

#[test]
fn session_menu_only_activates_merge_for_forks() {
let area = ratatui::layout::Rect::new(10, 5, 34, 11);
Expand Down
10 changes: 10 additions & 0 deletions crates/cli/src/app/session_title_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ impl App {
SessionTitleMenuAction::SplitVertical => {
self.split_active_window(WindowSplitDirection::Below)
}
SessionTitleMenuAction::Zoom => {
// The chord derives its target from the focused pane; a menu
// click can land while focus is still the list, so pin the
// target to the view this menu hangs off. Unzoom keeps
// whatever focus the user had.
if matches!(self.zoom, ZoomMode::None) {
self.focus = PaneFocus::View;
}
self.run_action(crate::keymap::KeyAction::ToggleZoom).await;
}
SessionTitleMenuAction::CloseSplit => self.delete_active_window(),
SessionTitleMenuAction::Archive => {
if self
Expand Down
2 changes: 2 additions & 0 deletions crates/cli/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8171,6 +8171,8 @@ fn session_title_menu_action_label(
(SessionTitleMenuAction::SplitHorizontal, Profile::Vim) => Some("C-w v"),
(SessionTitleMenuAction::SplitVertical, Profile::Emacs) => Some("C-x 2"),
(SessionTitleMenuAction::SplitVertical, Profile::Vim) => Some("C-w s"),
(SessionTitleMenuAction::Zoom, Profile::Emacs) => Some("C-x z"),
(SessionTitleMenuAction::Zoom, Profile::Vim) => Some("z"),
(SessionTitleMenuAction::CloseSplit, Profile::Emacs) => Some("C-x 0"),
(SessionTitleMenuAction::CloseSplit, Profile::Vim) => Some("C-w c"),
(SessionTitleMenuAction::Archive, Profile::Emacs)
Expand Down
Loading