aboutsummaryrefslogtreecommitdiff
path: root/src/app/input/navigate.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-29 18:09:25 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-29 18:09:25 +0300
commit1a4e94e57e181379e2ed42d63a49fbc56ed1dba4 (patch)
tree0319544374f1149a405259070b90df9d62854789 /src/app/input/navigate.rs
parenta937f7e85bbf8780c6030c57f48dab2168bca932 (diff)
refactor: route tui mutations through runtime authority
Diffstat (limited to 'src/app/input/navigate.rs')
-rw-r--r--src/app/input/navigate.rs672
1 files changed, 656 insertions, 16 deletions
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index 7072f659..c6ad3e16 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -56,12 +56,7 @@ impl App {
self.launch_focused_scrollback_editor();
finish_action_context(&mut self.state, ActionContext::Prefix, previous_mode);
} else {
- execute_navigate_action_in_context(
- &mut self.state,
- &mut self.terminal_runtimes,
- action,
- ActionContext::Prefix,
- );
+ self.execute_tui_navigate_action(action, ActionContext::Prefix);
}
self.selection_autoscroll_deadline = None;
return;
@@ -84,7 +79,8 @@ impl App {
return;
}
- if handle_navigate_reserved_key(&mut self.state, raw_key) {
+ if let Some(action) = navigate_reserved_action_for_key(&self.state, raw_key) {
+ self.execute_tui_navigate_action(action, ActionContext::Navigate);
return;
}
@@ -92,12 +88,7 @@ impl App {
if action == NavigateAction::EditScrollback {
self.launch_focused_scrollback_editor();
} else {
- execute_navigate_action_in_context(
- &mut self.state,
- &mut self.terminal_runtimes,
- action,
- ActionContext::Navigate,
- );
+ self.execute_tui_navigate_action(action, ActionContext::Navigate);
}
self.selection_autoscroll_deadline = None;
return;
@@ -108,6 +99,595 @@ impl App {
}
}
+ pub(super) fn execute_tui_navigate_action(
+ &mut self,
+ action: NavigateAction,
+ context: ActionContext,
+ ) {
+ let previous_mode = self.state.mode;
+ match action {
+ NavigateAction::NewWorkspace => {
+ self.dispatch_tui_api_request(
+ "tui.key.workspace.create",
+ crate::api::schema::Method::WorkspaceCreate(
+ crate::api::schema::WorkspaceCreateParams {
+ cwd: None,
+ focus: true,
+ label: None,
+ env: Default::default(),
+ },
+ ),
+ );
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::NewWorktree => {
+ if let Some(ws_idx) = workspace_action_target(&self.state, context).filter(|idx| {
+ workspace_can_start_worktree_action(&self.state, &self.terminal_runtimes, *idx)
+ }) {
+ self.state.request_new_linked_worktree = Some(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::OpenWorktree => {
+ if let Some(ws_idx) = workspace_action_target(&self.state, context).filter(|idx| {
+ workspace_can_start_worktree_action(&self.state, &self.terminal_runtimes, *idx)
+ }) {
+ self.state.request_open_existing_worktree = Some(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::RemoveWorktree => {
+ if let Some(ws_idx) = workspace_action_target(&self.state, context) {
+ self.state.request_remove_linked_worktree = Some(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::RenameWorkspace => {
+ if let Some(ws_idx) = workspace_action_target(&self.state, context) {
+ super::modal::open_rename_workspace(
+ &mut self.state,
+ &self.terminal_runtimes,
+ ws_idx,
+ );
+ }
+ }
+ NavigateAction::CloseWorkspace => {
+ if let Some(ws_idx) = workspace_action_target(&self.state, context) {
+ self.state.selected = ws_idx;
+ if self.state.confirm_close {
+ super::modal::open_confirm_close(&mut self.state);
+ } else {
+ self.close_workspace_idx_via_api(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ }
+ NavigateAction::SwitchWorkspace(idx) => {
+ if let Some(ws_idx) = self.state.workspace_at_visible_position(idx) {
+ self.focus_workspace_idx_via_api(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::SwitchTab(idx) => {
+ if self
+ .state
+ .active
+ .and_then(|ws_idx| self.state.workspaces.get(ws_idx))
+ .is_some_and(|ws| idx < ws.tabs.len())
+ {
+ self.focus_tab_idx_via_api(idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::FocusAgent(idx) => {
+ if let Some((ws_idx, pane_id)) = self.agent_entry_target(idx) {
+ self.focus_pane_internal_via_api(ws_idx, pane_id);
+ self.state.ensure_agent_panel_entry_visible(idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::WorkspacePicker => {
+ self.state.mobile_switcher_scroll = 0;
+ self.state.mode = Mode::Navigate;
+ }
+ NavigateAction::PreviousWorkspace => {
+ if let Some(ws_idx) = self.relative_visible_workspace(-1) {
+ self.focus_workspace_idx_via_api(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::NextWorkspace => {
+ if let Some(ws_idx) = self.relative_visible_workspace(1) {
+ self.focus_workspace_idx_via_api(ws_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::PreviousAgent => {
+ if let Some((idx, ws_idx, pane_id)) = self.relative_agent_entry(false) {
+ self.focus_pane_internal_via_api(ws_idx, pane_id);
+ self.state.ensure_agent_panel_entry_visible(idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::NextAgent => {
+ if let Some((idx, ws_idx, pane_id)) = self.relative_agent_entry(true) {
+ self.focus_pane_internal_via_api(ws_idx, pane_id);
+ self.state.ensure_agent_panel_entry_visible(idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::NewTab => {
+ if self.state.active.is_some() {
+ if self.state.prompt_new_tab_name {
+ super::modal::open_new_tab_dialog(&mut self.state);
+ } else {
+ self.dispatch_tui_api_request(
+ "tui.key.tab.create",
+ crate::api::schema::Method::TabCreate(
+ crate::api::schema::TabCreateParams {
+ workspace_id: None,
+ cwd: None,
+ focus: true,
+ label: None,
+ env: Default::default(),
+ },
+ ),
+ );
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ }
+ NavigateAction::RenameTab => {
+ super::modal::open_rename_active_tab(&mut self.state, false)
+ }
+ NavigateAction::PreviousTab => {
+ if let Some(tab_idx) = self.relative_tab(-1) {
+ self.focus_tab_idx_via_api(tab_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::NextTab => {
+ if let Some(tab_idx) = self.relative_tab(1) {
+ self.focus_tab_idx_via_api(tab_idx);
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::CloseTab => {
+ self.close_active_tab_via_api();
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::RenamePane => {
+ if let Some(pane_id) = self
+ .state
+ .active
+ .and_then(|ws_idx| self.state.workspaces.get(ws_idx))
+ .and_then(|ws| ws.focused_pane_id())
+ {
+ super::modal::open_rename_pane(&mut self.state, pane_id);
+ }
+ }
+ NavigateAction::FocusPaneLeft => self.focus_pane_direction_via_api(NavDirection::Left),
+ NavigateAction::FocusPaneDown => self.focus_pane_direction_via_api(NavDirection::Down),
+ NavigateAction::FocusPaneUp => self.focus_pane_direction_via_api(NavDirection::Up),
+ NavigateAction::FocusPaneRight => {
+ self.focus_pane_direction_via_api(NavDirection::Right)
+ }
+ NavigateAction::SwapPaneLeft => {
+ self.swap_pane_direction_via_api(NavDirection::Left);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::SwapPaneDown => {
+ self.swap_pane_direction_via_api(NavDirection::Down);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::SwapPaneUp => {
+ self.swap_pane_direction_via_api(NavDirection::Up);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::SwapPaneRight => {
+ self.swap_pane_direction_via_api(NavDirection::Right);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::SplitVertical => {
+ self.split_focused_pane_via_api(crate::api::schema::SplitDirection::Right);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::SplitHorizontal => {
+ self.split_focused_pane_via_api(crate::api::schema::SplitDirection::Down);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::ClosePane => {
+ self.close_focused_pane_via_api();
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::EditScrollback => {}
+ NavigateAction::CopyMode => self.state.enter_copy_mode(&self.terminal_runtimes),
+ NavigateAction::Zoom => {
+ self.zoom_focused_pane_via_api();
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::EnterResizeMode => self.state.mode = Mode::Resize,
+ NavigateAction::ToggleSidebar => {
+ self.state.sidebar_collapsed = !self.state.sidebar_collapsed;
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::CyclePaneNext => {
+ self.cycle_pane_via_api(false);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::CyclePanePrevious => {
+ self.cycle_pane_via_api(true);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::LastPane => {
+ self.last_pane_via_api();
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::Help => super::modal::open_keybind_help(&mut self.state),
+ NavigateAction::Settings => super::settings::open_settings(&mut self.state),
+ NavigateAction::ReloadConfig => {
+ self.dispatch_tui_api_request(
+ "tui.server.reload_config",
+ crate::api::schema::Method::ServerReloadConfig(
+ crate::api::schema::EmptyParams::default(),
+ ),
+ );
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::OpenNotificationTarget => {
+ self.focus_toast_target_via_api();
+ if self.state.mode == Mode::Navigate {
+ leave_navigate_mode(&mut self.state);
+ }
+ }
+ NavigateAction::Detach => {
+ super::modal::request_detach(&mut self.state);
+ leave_navigate_mode(&mut self.state);
+ }
+ NavigateAction::OpenNavigator => {
+ self.state.open_navigator_from(&self.terminal_runtimes)
+ }
+ }
+
+ finish_action_context(&mut self.state, context, previous_mode);
+ }
+
+ pub(crate) fn focus_workspace_idx_via_api(&mut self, ws_idx: usize) {
+ let workspace_id = self.public_workspace_id(ws_idx);
+ self.dispatch_tui_api_request(
+ "tui.workspace.focus",
+ crate::api::schema::Method::WorkspaceFocus(crate::api::schema::WorkspaceTarget {
+ workspace_id,
+ }),
+ );
+ }
+
+ pub(crate) fn close_workspace_idx_via_api(&mut self, ws_idx: usize) {
+ let workspace_id = self.public_workspace_id(ws_idx);
+ self.dispatch_tui_api_request(
+ "tui.workspace.close",
+ crate::api::schema::Method::WorkspaceClose(crate::api::schema::WorkspaceTarget {
+ workspace_id,
+ }),
+ );
+ }
+
+ pub(crate) fn move_workspace_via_api(&mut self, source_ws_idx: usize, insert_idx: usize) {
+ let workspace_id = self.public_workspace_id(source_ws_idx);
+ self.dispatch_tui_api_request(
+ "tui.workspace.move",
+ crate::api::schema::Method::WorkspaceMove(crate::api::schema::WorkspaceMoveParams {
+ workspace_id,
+ insert_index: insert_idx,
+ }),
+ );
+ }
+
+ pub(crate) fn focus_tab_idx_via_api(&mut self, tab_idx: usize) {
+ let Some(ws_idx) = self.state.active else {
+ return;
+ };
+ let Some(tab_id) = self.public_tab_id(ws_idx, tab_idx) else {
+ return;
+ };
+ self.dispatch_tui_api_request(
+ "tui.tab.focus",
+ crate::api::schema::Method::TabFocus(crate::api::schema::TabTarget { tab_id }),
+ );
+ }
+
+ pub(crate) fn close_active_tab_via_api(&mut self) {
+ let Some(ws_idx) = self.state.active else {
+ return;
+ };
+ let tab_idx = self.state.workspaces[ws_idx].active_tab_index();
+ let Some(tab_id) = self.public_tab_id(ws_idx, tab_idx) else {
+ return;
+ };
+ self.dispatch_tui_api_request(
+ "tui.tab.close",
+ crate::api::schema::Method::TabClose(crate::api::schema::TabTarget { tab_id }),
+ );
+ }
+
+ pub(crate) fn move_tab_via_api(
+ &mut self,
+ ws_idx: usize,
+ source_tab_idx: usize,
+ insert_idx: usize,
+ ) {
+ let Some(tab_id) = self.public_tab_id(ws_idx, source_tab_idx) else {
+ return;
+ };
+ self.dispatch_tui_api_request(
+ "tui.tab.move",
+ crate::api::schema::Method::TabMove(crate::api::schema::TabMoveParams {
+ tab_id,
+ insert_index: insert_idx,
+ }),
+ );
+ }
+
+ pub(crate) fn focus_pane_internal_via_api(
+ &mut self,
+ ws_idx: usize,
+ pane_id: crate::layout::PaneId,
+ ) {
+ let Some(pane_id) = self.public_pane_id(ws_idx, pane_id) else {
+ return;
+ };
+ self.dispatch_tui_api_request(
+ "tui.pane.focus",
+ crate::api::schema::Method::PaneFocus(crate::api::schema::PaneTarget { pane_id }),
+ );
+ }
+
+ pub(crate) fn focus_pane_direction_via_api(&mut self, direction: NavDirection) {
+ if let Some((ws_idx, target)) = self.directional_pane_target_from_view(direction) {
+ self.focus_pane_internal_via_api(ws_idx, target);
+ return;
+ }
+ self.dispatch_tui_api_request(
+ "tui.pane.focus_direction",
+ crate::api::schema::Method::PaneFocusDirection(
+ crate::api::schema::PaneFocusDirectionParams {
+ pane_id: None,
+ direction: api_pane_direction(direction),
+ },
+ ),
+ );
+ }
+
+ pub(crate) fn swap_pane_direction_via_api(&mut self, direction: NavDirection) {
+ if let Some((ws_idx, source, target)) = self.directional_pane_swap_from_view(direction) {
+ let source_pane_id = self.public_pane_id(ws_idx, source);
+ let target_pane_id = self.public_pane_id(ws_idx, target);
+ if let (Some(source_pane_id), Some(target_pane_id)) = (source_pane_id, target_pane_id) {
+ self.dispatch_tui_api_request(
+ "tui.pane.swap_exact",
+ crate::api::schema::Method::PaneSwap(crate::api::schema::PaneSwapParams {
+ pane_id: None,
+ direction: None,
+ source_pane_id: Some(source_pane_id),
+ target_pane_id: Some(target_pane_id),
+ }),
+ );
+ return;
+ }
+ }
+ self.dispatch_tui_api_request(
+ "tui.pane.swap",
+ crate::api::schema::Method::PaneSwap(crate::api::schema::PaneSwapParams {
+ pane_id: None,
+ direction: Some(api_pane_direction(direction)),
+ source_pane_id: None,
+ target_pane_id: None,
+ }),
+ );
+ }
+
+ pub(crate) fn split_focused_pane_via_api(
+ &mut self,
+ direction: crate::api::schema::SplitDirection,
+ ) {
+ self.dispatch_tui_api_request(
+ "tui.pane.split",
+ crate::api::schema::Method::PaneSplit(crate::api::schema::PaneSplitParams {
+ workspace_id: None,
+ target_pane_id: None,
+ direction,
+ ratio: None,
+ cwd: None,
+ focus: true,
+ env: Default::default(),
+ }),
+ );
+ }
+
+ pub(crate) fn close_focused_pane_via_api(&mut self) {
+ let Some((ws_idx, pane_id)) = self.focused_pane_target() else {
+ return;
+ };
+ let Some(pane_id) = self.public_pane_id(ws_idx, pane_id) else {
+ return;
+ };
+ self.dispatch_tui_api_request(
+ "tui.pane.close",
+ crate::api::schema::Method::PaneClose(crate::api::schema::PaneTarget { pane_id }),
+ );
+ }
+
+ pub(crate) fn zoom_focused_pane_via_api(&mut self) {
+ self.dispatch_tui_api_request(
+ "tui.pane.zoom",
+ crate::api::schema::Method::PaneZoom(crate::api::schema::PaneZoomParams {
+ pane_id: None,
+ mode: crate::api::schema::PaneZoomMode::Toggle,
+ }),
+ );
+ }
+
+ pub(crate) fn set_split_ratio_via_api(&mut self, path: Vec<bool>, ratio: f32) {
+ self.dispatch_tui_api_request(
+ "tui.layout.set_split_ratio",
+ crate::api::schema::Method::LayoutSetSplitRatio(
+ crate::api::schema::LayoutSetSplitRatioParams {
+ tab_id: None,
+ pane_id: None,
+ path,
+ ratio,
+ },
+ ),
+ );
+ }
+
+ pub(crate) fn cycle_pane_via_api(&mut self, reverse: bool) {
+ let Some((ws_idx, pane_id)) = self.focused_pane_target() else {
+ return;
+ };
+ let Some(tab) = self.state.workspaces[ws_idx].active_tab() else {
+ return;
+ };
+ let ids = tab.layout.pane_ids();
+ let Some(pos) = ids.iter().position(|id| *id == pane_id) else {
+ return;
+ };
+ let target = if reverse {
+ ids[(pos + ids.len() - 1) % ids.len()]
+ } else {
+ ids[(pos + 1) % ids.len()]
+ };
+ self.focus_pane_internal_via_api(ws_idx, target);
+ }
+
+ pub(crate) fn last_pane_via_api(&mut self) {
+ let Some(target) = self.state.previous_pane_focus.clone() else {
+ return;
+ };
+ let Some((ws_idx, _tab_idx)) = self.state.pane_focus_target_indices(&target) else {
+ self.state.previous_pane_focus = None;
+ return;
+ };
+ if self.state.current_pane_focus_target().as_ref() == Some(&target) {
+ self.state.previous_pane_focus = None;
+ return;
+ }
+ self.focus_pane_internal_via_api(ws_idx, target.pane_id);
+ }
+
+ pub(crate) fn focus_toast_target_via_api(&mut self) {
+ let Some(target) = self
+ .state
+ .toast
+ .as_ref()
+ .and_then(|toast| toast.target.clone())
+ else {
+ return;
+ };
+ let Some(ws_idx) = self
+ .state
+ .workspaces
+ .iter()
+ .position(|workspace| workspace.id == target.workspace_id)
+ else {
+ return;
+ };
+ self.focus_pane_internal_via_api(ws_idx, target.pane_id);
+ self.state.toast = None;
+ self.state.mode = Mode::Terminal;
+ }
+
+ fn focused_pane_target(&self) -> Option<(usize, crate::layout::PaneId)> {
+ let ws_idx = self.state.active?;
+ let pane_id = self.state.workspaces.get(ws_idx)?.focused_pane_id()?;
+ Some((ws_idx, pane_id))
+ }
+
+ fn directional_pane_target_from_view(
+ &self,
+ direction: NavDirection,
+ ) -> Option<(usize, crate::layout::PaneId)> {
+ let ws_idx = self.state.active?;
+ let focused = self
+ .state
+ .view
+ .pane_infos
+ .iter()
+ .find(|pane| pane.is_focused)?;
+ let target =
+ crate::layout::find_in_direction(focused, direction, &self.state.view.pane_infos)?;
+ Some((ws_idx, target))
+ }
+
+ fn directional_pane_swap_from_view(
+ &self,
+ direction: NavDirection,
+ ) -> Option<(usize, crate::layout::PaneId, crate::layout::PaneId)> {
+ let ws_idx = self.state.active?;
+ let focused = self
+ .state
+ .view
+ .pane_infos
+ .iter()
+ .find(|pane| pane.is_focused)?;
+ let target =
+ crate::layout::find_in_direction(focused, direction, &self.state.view.pane_infos)?;
+ Some((ws_idx, focused.id, target))
+ }
+
+ fn relative_visible_workspace(&self, delta: isize) -> Option<usize> {
+ let order = self.state.visible_workspace_order();
+ if order.is_empty() {
+ return None;
+ }
+ let current = self.state.active.unwrap_or(self.state.selected);
+ let current_pos = order.iter().position(|idx| *idx == current).unwrap_or(0);
+ let next = (current_pos as isize + delta).rem_euclid(order.len() as isize) as usize;
+ order.get(next).copied()
+ }
+
+ fn relative_tab(&self, delta: isize) -> Option<usize> {
+ let ws = self
+ .state
+ .active
+ .and_then(|ws_idx| self.state.workspaces.get(ws_idx))?;
+ if ws.tabs.is_empty() {
+ return None;
+ }
+ Some((ws.active_tab as isize + delta).rem_euclid(ws.tabs.len() as isize) as usize)
+ }
+
+ fn agent_entry_target(&self, idx: usize) -> Option<(usize, crate::layout::PaneId)> {
+ let entries = crate::ui::agent_panel_entries(&self.state);
+ let target = entries.get(idx)?;
+ Some((target.ws_idx, target.pane_id))
+ }
+
+ fn relative_agent_entry(&self, forward: bool) -> Option<(usize, usize, crate::layout::PaneId)> {
+ let entries = crate::ui::agent_panel_entries(&self.state);
+ if entries.is_empty() {
+ return None;
+ }
+ let focused = self
+ .state
+ .active
+ .and_then(|idx| self.state.workspaces.get(idx))
+ .and_then(crate::workspace::Workspace::focused_pane_id);
+ let current_idx = entries
+ .iter()
+ .position(|entry| Some(entry.pane_id) == focused)
+ .unwrap_or(0);
+ let next_idx = if forward {
+ (current_idx + 1) % entries.len()
+ } else if current_idx == 0 {
+ entries.len() - 1
+ } else {
+ current_idx - 1
+ };
+ let target = entries.get(next_idx)?;
+ Some((next_idx, target.ws_idx, target.pane_id))
+ }
+
fn pass_through_key_to_focused_pane(&mut self, key: TerminalKey) -> bool {
let Some(ws_idx) = self.state.active else {
return false;
@@ -540,6 +1120,66 @@ pub(super) fn handle_navigate_reserved_key(state: &mut AppState, key: TerminalKe
false
}
+fn navigate_reserved_action_for_key(state: &AppState, key: TerminalKey) -> Option<NavigateAction> {
+ let (code, modifiers) = crate::config::normalize_key_combo((key.code, key.modifiers));
+ if modifiers.is_empty() {
+ match code {
+ KeyCode::Enter => {
+ return (!state.workspaces.is_empty()).then_some(NavigateAction::SwitchWorkspace(
+ state
+ .visible_workspace_order()
+ .iter()
+ .position(|idx| *idx == state.selected)
+ .unwrap_or(state.selected),
+ ));
+ }
+ KeyCode::Char(c @ '1'..='9') => {
+ return Some(NavigateAction::SwitchWorkspace(
+ (c as usize) - ('1' as usize),
+ ));
+ }
+ KeyCode::Tab => return Some(NavigateAction::CyclePaneNext),
+ KeyCode::BackTab => return Some(NavigateAction::CyclePanePrevious),
+ KeyCode::Left => return Some(NavigateAction::FocusPaneLeft),
+ KeyCode::Right => return Some(NavigateAction::FocusPaneRight),
+ _ => {}
+ }
+ }
+
+ if state.keybinds.navigate.workspace_up.matches_direct_key(key)
+ || state
+ .keybinds
+ .navigate
+ .workspace_down
+ .matches_direct_key(key)
+ {
+ return None;
+ }
+ if state.keybinds.navigate.pane_left.matches_direct_key(key) {
+ return Some(NavigateAction::FocusPaneLeft);
+ }
+ if state.keybinds.navigate.pane_down.matches_direct_key(key) {
+ return Some(NavigateAction::FocusPaneDown);
+ }
+ if state.keybinds.navigate.pane_up.matches_direct_key(key) {
+ return Some(NavigateAction::FocusPaneUp);
+ }
+ if state.keybinds.navigate.pane_right.matches_direct_key(key) {
+ return Some(NavigateAction::FocusPaneRight);
+ }
+
+ None
+}
+
+pub(super) fn api_pane_direction(direction: NavDirection) -> crate::api::schema::PaneDirection {
+ match direction {
+ NavDirection::Left => crate::api::schema::PaneDirection::Left,
+ NavDirection::Right => crate::api::schema::PaneDirection::Right,
+ NavDirection::Up => crate::api::schema::PaneDirection::Up,
+ NavDirection::Down => crate::api::schema::PaneDirection::Down,
+ }
+}
+
#[allow(dead_code)] // exercised in input unit tests; production uses App::handle_navigate_key
pub(crate) fn handle_navigate_key(state: &mut AppState, key: KeyEvent) {
let mut terminal_runtimes = TerminalRuntimeRegistry::new();
@@ -1698,7 +2338,7 @@ last_pane = "prefix+tab"
app.handle_navigate_key(TerminalKey::new(KeyCode::Char('n'), KeyModifiers::SHIFT));
- assert!(app.state.request_new_workspace);
+ assert_eq!(app.state.workspaces.len(), 2);
assert_eq!(app.state.mode, Mode::Terminal);
}
@@ -1719,7 +2359,7 @@ last_pane = "prefix+tab"
app.handle_navigate_key(TerminalKey::new(KeyCode::Char('N'), KeyModifiers::empty()));
- assert!(app.state.request_new_workspace);
+ assert_eq!(app.state.workspaces.len(), 2);
assert_eq!(app.state.mode, Mode::Terminal);
}
@@ -1760,7 +2400,7 @@ last_pane = "prefix+tab"
app.handle_navigate_key(TerminalKey::new(KeyCode::Char('R'), KeyModifiers::empty()));
- assert!(app.state.request_reload_config);
+ assert!(!app.state.request_reload_config);
assert_eq!(app.state.mode, Mode::Terminal);
}