aboutsummaryrefslogtreecommitdiff
path: root/src/app
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-11 23:31:36 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-11 23:34:20 +0300
commitfe693f613a23452e68589a4337863f217cc57930 (patch)
tree8d60f6c5b0d8b31426c6623cc8b0c2415d481000 /src/app
parent84c0d003ddb19a8e95e7ad5206771a663913605b (diff)
feat: add optional pane border labels
Diffstat (limited to 'src/app')
-rw-r--r--src/app/api.rs42
-rw-r--r--src/app/config_io.rs13
-rw-r--r--src/app/creation.rs1
-rw-r--r--src/app/input/mod.rs5
-rw-r--r--src/app/input/modal.rs53
-rw-r--r--src/app/input/mouse.rs16
-rw-r--r--src/app/input/navigate.rs16
-rw-r--r--src/app/input/settings.rs43
-rw-r--r--src/app/mod.rs54
-rw-r--r--src/app/state.rs46
10 files changed, 276 insertions, 13 deletions
diff --git a/src/app/api.rs b/src/app/api.rs
index c675aa6f..46429ffe 100644
--- a/src/app/api.rs
+++ b/src/app/api.rs
@@ -927,6 +927,48 @@ impl App {
result: ResponseResult::PaneInfo { pane },
}
}
+ Method::PaneRename(params) => {
+ let Some((ws_idx, pane_id)) = self.parse_pane_id(&params.pane_id) else {
+ return serde_json::to_string(&ErrorResponse {
+ id: request.id,
+ error: ErrorBody {
+ code: "pane_not_found".into(),
+ message: format!("pane {} not found", params.pane_id),
+ },
+ })
+ .unwrap();
+ };
+ let Some(ws) = self.state.workspaces.get_mut(ws_idx) else {
+ return serde_json::to_string(&ErrorResponse {
+ id: request.id,
+ error: ErrorBody {
+ code: "pane_not_found".into(),
+ message: format!("pane {} not found", params.pane_id),
+ },
+ })
+ .unwrap();
+ };
+ let Some(pane_state) = ws.pane_state_mut(pane_id) else {
+ return serde_json::to_string(&ErrorResponse {
+ id: request.id,
+ error: ErrorBody {
+ code: "pane_not_found".into(),
+ message: format!("pane {} not found", params.pane_id),
+ },
+ })
+ .unwrap();
+ };
+ match params.label.map(|label| label.trim().to_string()) {
+ Some(label) if !label.is_empty() => pane_state.set_manual_label(label),
+ _ => pane_state.clear_manual_label(),
+ }
+ self.state.mark_session_dirty();
+ let pane = self.pane_info(ws_idx, pane_id).unwrap();
+ SuccessResponse {
+ id: request.id,
+ result: ResponseResult::PaneInfo { pane },
+ }
+ }
Method::PaneRead(params) => {
let Some((ws_idx, pane_id)) = self.parse_pane_id(&params.pane_id) else {
return serde_json::to_string(&ErrorResponse {
diff --git a/src/app/config_io.rs b/src/app/config_io.rs
index a9199561..0bf04831 100644
--- a/src/app/config_io.rs
+++ b/src/app/config_io.rs
@@ -72,6 +72,19 @@ impl App {
}
}
+ pub(super) fn save_agent_border_labels(&mut self, enabled: bool) {
+ if self.update_config_file("agent border labels", |content| {
+ crate::config::upsert_section_bool(
+ content,
+ "ui",
+ "show_agent_labels_on_pane_borders",
+ enabled,
+ )
+ }) {
+ self.apply_config_from_disk(false);
+ }
+ }
+
pub(super) fn save_agent_panel_scope(&mut self, scope: crate::app::state::AgentPanelScope) {
let value = match scope {
crate::app::state::AgentPanelScope::CurrentWorkspace => {
diff --git a/src/app/creation.rs b/src/app/creation.rs
index 4e38084c..51dac1e8 100644
--- a/src/app/creation.rs
+++ b/src/app/creation.rs
@@ -245,6 +245,7 @@ impl App {
cwd: runtime
.and_then(|rt| rt.cwd())
.map(|cwd| cwd.display().to_string()),
+ label: pane.manual_label.clone(),
agent: pane.effective_agent_label().map(str::to_string),
agent_status: pane_agent_status(pane.state, pane.seen),
revision: 0,
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs
index aa607c78..575b373d 100644
--- a/src/app/input/mod.rs
+++ b/src/app/input/mod.rs
@@ -63,7 +63,7 @@ impl App {
Mode::Onboarding => self.handle_onboarding_key(key),
Mode::ReleaseNotes => self.handle_release_notes_key(key),
Mode::Navigate => unreachable!(),
- Mode::RenameWorkspace | Mode::RenameTab => {
+ Mode::RenameWorkspace | Mode::RenameTab | Mode::RenamePane => {
handle_rename_key(&mut self.state, key)
}
Mode::Resize => handle_resize_key(&mut self.state, key),
@@ -159,6 +159,9 @@ impl App {
SettingsAction::SaveTheme(name) => self.save_theme(&name),
SettingsAction::SaveSound(enabled) => self.save_sound(enabled),
SettingsAction::SaveToastDelivery(delivery) => self.save_toast_delivery(delivery),
+ SettingsAction::SaveAgentBorderLabels(enabled) => {
+ self.save_agent_border_labels(enabled)
+ }
}
}
if self.state.agent_panel_scope != previous_agent_panel_scope {
diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs
index a76576bf..367e7e84 100644
--- a/src/app/input/modal.rs
+++ b/src/app/input/modal.rs
@@ -164,6 +164,7 @@ pub(crate) fn handle_keybind_help_key(state: &mut AppState, key: KeyEvent) {
pub(super) fn open_rename_workspace(state: &mut AppState, ws_idx: usize) {
state.selected = ws_idx;
+ state.rename_pane_target = None;
state.name_input = state.workspaces[ws_idx].display_name();
state.name_input_replace_on_type = false;
state.mode = Mode::RenameWorkspace;
@@ -172,6 +173,7 @@ pub(super) fn open_rename_workspace(state: &mut AppState, ws_idx: usize) {
pub(super) fn open_rename_active_tab(state: &mut AppState, replace_on_type: bool) {
state.creating_new_tab = false;
state.requested_new_tab_name = None;
+ state.rename_pane_target = None;
if let Some(ws) = state.active.and_then(|i| state.workspaces.get(i)) {
if let Some(name) = ws.active_tab_display_name() {
state.name_input = name;
@@ -181,6 +183,21 @@ pub(super) fn open_rename_active_tab(state: &mut AppState, replace_on_type: bool
}
}
+pub(super) fn open_rename_pane(state: &mut AppState, pane_id: crate::layout::PaneId) {
+ let Some(ws) = state.active.and_then(|i| state.workspaces.get(i)) else {
+ return;
+ };
+ let Some(pane) = ws.pane_state(pane_id) else {
+ return;
+ };
+ state.creating_new_tab = false;
+ state.requested_new_tab_name = None;
+ state.rename_pane_target = Some(pane_id);
+ state.name_input = pane.manual_label.clone().unwrap_or_default();
+ state.name_input_replace_on_type = pane.manual_label.is_none();
+ state.mode = Mode::RenamePane;
+}
+
fn next_new_tab_default_name(state: &AppState) -> String {
state
.active
@@ -192,6 +209,7 @@ fn next_new_tab_default_name(state: &AppState) -> String {
pub(super) fn open_new_tab_dialog(state: &mut AppState) {
state.creating_new_tab = true;
state.requested_new_tab_name = None;
+ state.rename_pane_target = None;
state.name_input = next_new_tab_default_name(state);
state.name_input_replace_on_type = true;
state.mode = Mode::RenameTab;
@@ -295,9 +313,21 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) {
}
}
}
+ Mode::RenamePane => {
+ if let (Some(ws_idx), Some(pane_id)) = (state.active, state.rename_pane_target)
+ {
+ if let Some(ws) = state.workspaces.get_mut(ws_idx) {
+ if let Some(pane) = ws.pane_state_mut(pane_id) {
+ pane.set_manual_label(new_name);
+ state.mark_session_dirty();
+ }
+ }
+ }
+ }
_ => {}
}
state.creating_new_tab = false;
+ state.rename_pane_target = None;
state.name_input.clear();
state.name_input_replace_on_type = false;
leave_modal(state);
@@ -309,6 +339,7 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) {
ModalAction::Cancel => {
state.creating_new_tab = false;
state.requested_new_tab_name = None;
+ state.rename_pane_target = None;
state.name_input.clear();
state.name_input_replace_on_type = false;
leave_modal(state);
@@ -432,19 +463,33 @@ pub(super) fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuS
Mode::Navigate
};
}
- (ContextMenuKind::Pane, Some("Split vertical")) => {
+ (ContextMenuKind::Pane { pane_id, .. }, Some("Rename pane")) => {
+ open_rename_pane(state, pane_id);
+ }
+ (ContextMenuKind::Pane { pane_id, .. }, Some("Clear pane name")) => {
+ if let Some(ws_idx) = state.active {
+ if let Some(ws) = state.workspaces.get_mut(ws_idx) {
+ if let Some(pane) = ws.pane_state_mut(pane_id) {
+ pane.clear_manual_label();
+ state.mark_session_dirty();
+ }
+ }
+ }
+ state.mode = Mode::Terminal;
+ }
+ (ContextMenuKind::Pane { .. }, Some("Split vertical")) => {
state.split_pane(Direction::Horizontal);
state.mode = Mode::Terminal;
}
- (ContextMenuKind::Pane, Some("Split horizontal")) => {
+ (ContextMenuKind::Pane { .. }, Some("Split horizontal")) => {
state.split_pane(Direction::Vertical);
state.mode = Mode::Terminal;
}
- (ContextMenuKind::Pane, Some("Fullscreen")) => {
+ (ContextMenuKind::Pane { .. }, Some("Fullscreen")) => {
state.toggle_fullscreen();
state.mode = Mode::Terminal;
}
- (ContextMenuKind::Pane, Some("Close pane")) => {
+ (ContextMenuKind::Pane { .. }, Some("Close pane")) => {
state.close_pane();
state.mode = if state.active.is_some() {
Mode::Terminal
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index 4a074a2e..f7997807 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -137,7 +137,10 @@ impl AppState {
return None;
}
- if matches!(self.mode, Mode::RenameWorkspace | Mode::RenameTab) {
+ if matches!(
+ self.mode,
+ Mode::RenameWorkspace | Mode::RenameTab | Mode::RenamePane
+ ) {
let action = self
.rename_modal_inner()
.map(crate::ui::rename_button_rects)
@@ -678,8 +681,17 @@ impl AppState {
MouseEventKind::Down(MouseButton::Right) if !in_sidebar => {
if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() {
self.focus_pane(info.id);
+ let has_manual_label = self
+ .active
+ .and_then(|ws_idx| self.workspaces.get(ws_idx))
+ .and_then(|ws| ws.pane_state(info.id))
+ .and_then(|pane| pane.manual_label.as_ref())
+ .is_some();
self.context_menu = Some(ContextMenuState {
- kind: ContextMenuKind::Pane,
+ kind: ContextMenuKind::Pane {
+ pane_id: info.id,
+ has_manual_label,
+ },
x: mouse.column,
y: mouse.row,
list: MenuListState::new(0),
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index 3bc8da78..bb88c46b 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -368,6 +368,7 @@ pub(crate) enum NavigateAction {
PreviousTab,
NextTab,
CloseTab,
+ RenamePane,
FocusPaneLeft,
FocusPaneDown,
FocusPaneUp,
@@ -432,6 +433,12 @@ fn navigate_action_for_key(state: &AppState, key: &KeyEvent) -> Option<NavigateA
{
return Some(NavigateAction::CloseTab);
}
+ if kb
+ .rename_pane
+ .is_some_and(|(code, mods)| key_matches(key, code, mods))
+ {
+ return Some(NavigateAction::RenamePane);
+ }
if key_matches(key, kb.split_vertical.0, kb.split_vertical.1) {
return Some(NavigateAction::SplitVertical);
}
@@ -508,6 +515,15 @@ pub(super) fn execute_navigate_action(state: &mut AppState, action: NavigateActi
state.close_tab();
leave_navigate_mode(state);
}
+ NavigateAction::RenamePane => {
+ if let Some(pane_id) = state
+ .active
+ .and_then(|ws_idx| state.workspaces.get(ws_idx))
+ .and_then(|ws| ws.focused_pane_id())
+ {
+ super::modal::open_rename_pane(state, pane_id);
+ }
+ }
NavigateAction::FocusPaneLeft => state.navigate_pane(NavDirection::Left),
NavigateAction::FocusPaneDown => state.navigate_pane(NavDirection::Down),
NavigateAction::FocusPaneUp => state.navigate_pane(NavDirection::Up),
diff --git a/src/app/input/settings.rs b/src/app/input/settings.rs
index 67144a28..9e89e515 100644
--- a/src/app/input/settings.rs
+++ b/src/app/input/settings.rs
@@ -16,6 +16,7 @@ pub(super) enum SettingsAction {
SaveTheme(String),
SaveSound(bool),
SaveToastDelivery(ToastDelivery),
+ SaveAgentBorderLabels(bool),
}
impl App {
@@ -25,6 +26,9 @@ impl App {
SettingsAction::SaveTheme(name) => self.save_theme(&name),
SettingsAction::SaveSound(enabled) => self.save_sound(enabled),
SettingsAction::SaveToastDelivery(delivery) => self.save_toast_delivery(delivery),
+ SettingsAction::SaveAgentBorderLabels(enabled) => {
+ self.save_agent_border_labels(enabled)
+ }
}
}
}
@@ -157,6 +161,30 @@ pub(super) fn update_settings_state(state: &mut AppState, key: KeyEvent) -> Opti
state.settings.list.selected = usize::from(!state.sound_enabled());
}
KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => {
+ state.settings.section = SettingsSection::PaneLabels;
+ state.settings.list.selected = usize::from(!state.agent_border_labels_enabled());
+ }
+ _ => {
+ if let Some(super::modal::ModalAction::Close) =
+ super::modal::modal_action_from_key(&key, super::modal::SETTINGS_ACTIONS)
+ {
+ cancel_settings(state);
+ }
+ }
+ },
+ SettingsSection::PaneLabels => match key.code {
+ KeyCode::Up | KeyCode::Char('k') | KeyCode::Down | KeyCode::Char('j') => {
+ state.settings.list.selected = 1 - state.settings.list.selected.min(1);
+ }
+ KeyCode::Enter | KeyCode::Char(' ') => {
+ let enabled = state.settings.list.selected == 0;
+ return Some(SettingsAction::SaveAgentBorderLabels(enabled));
+ }
+ KeyCode::BackTab | KeyCode::Left | KeyCode::Char('h') => {
+ state.settings.section = SettingsSection::Toast;
+ state.settings.list.selected = toast_delivery_index(state.toast_delivery());
+ }
+ KeyCode::Tab | KeyCode::Right | KeyCode::Char('l') => {
state.settings.section = SettingsSection::Theme;
state.settings.list.selected = current_theme_index(&state.theme_name);
}
@@ -252,6 +280,14 @@ impl AppState {
None
}
}
+ SettingsSection::PaneLabels => {
+ let list_y = area.y + 3;
+ if row >= list_y && row < list_y + 2 {
+ Some((row - list_y) as usize)
+ } else {
+ None
+ }
+ }
}
}
@@ -264,6 +300,9 @@ impl AppState {
SettingsSection::Theme => current_theme_index(&self.theme_name),
SettingsSection::Sound => usize::from(!self.sound_enabled()),
SettingsSection::Toast => toast_delivery_index(self.toast_delivery()),
+ SettingsSection::PaneLabels => {
+ usize::from(!self.agent_border_labels_enabled())
+ }
});
return None;
}
@@ -282,6 +321,10 @@ impl AppState {
let delivery = toast_delivery_for_index(idx);
Some(SettingsAction::SaveToastDelivery(delivery))
}
+ SettingsSection::PaneLabels => {
+ let enabled = idx == 0;
+ Some(SettingsAction::SaveAgentBorderLabels(enabled))
+ }
};
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 60e38f5b..43d15bec 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -292,6 +292,7 @@ impl App {
request_clipboard_write: None,
creating_new_tab: false,
requested_new_tab_name: None,
+ rename_pane_target: None,
request_complete_onboarding: false,
name_input: String::new(),
name_input_replace_on_type: false,
@@ -344,6 +345,7 @@ impl App {
sidebar_section_split,
agent_panel_scope,
confirm_close: config.ui.confirm_close,
+ show_agent_labels_on_pane_borders: config.ui.show_agent_labels_on_pane_borders,
pane_scrollback_limit_bytes: config.advanced.scrollback_limit_bytes,
accent: crate::config::parse_color(&config.ui.accent),
sound: config.ui.sound.clone(),
@@ -649,6 +651,8 @@ impl App {
self.state.sidebar_width = config.ui.sidebar_width;
}
self.state.confirm_close = config.ui.confirm_close;
+ self.state.show_agent_labels_on_pane_borders =
+ config.ui.show_agent_labels_on_pane_borders;
self.state.agent_panel_scope =
agent_panel_scope_from_config(config.ui.agent_panel_scope);
self.state.agent_panel_scroll = 0;
@@ -809,7 +813,7 @@ impl App {
Mode::Navigate => {
self.handle_navigate_key(key);
}
- Mode::RenameWorkspace | Mode::RenameTab => {
+ Mode::RenameWorkspace | Mode::RenameTab | Mode::RenamePane => {
input::handle_rename_key(&mut self.state, key_event);
}
Mode::Resize => {
@@ -1482,6 +1486,54 @@ mod tests {
assert!(app.state.should_quit);
}
+ #[test]
+ fn pane_rename_request_sets_and_clears_manual_label() {
+ let mut app = test_app();
+ let workspace = Workspace::test_new("api-pane-rename");
+ let pane = workspace.tabs[0].root_pane;
+ app.state.workspaces = vec![workspace];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+
+ let pane_id = app.pane_info(0, pane).unwrap().pane_id;
+ let response = app.handle_api_request(crate::api::schema::Request {
+ id: "req_pane_rename".into(),
+ method: crate::api::schema::Method::PaneRename(crate::api::schema::PaneRenameParams {
+ pane_id: pane_id.clone(),
+ label: Some("reviewer".into()),
+ }),
+ });
+ let response: serde_json::Value = serde_json::from_str(&response).unwrap();
+
+ assert_eq!(response["result"]["type"], "pane_info");
+ assert_eq!(response["result"]["pane"]["label"], "reviewer");
+ assert_eq!(
+ app.state.workspaces[0]
+ .pane_state(pane)
+ .unwrap()
+ .manual_label
+ .as_deref(),
+ Some("reviewer")
+ );
+
+ let response = app.handle_api_request(crate::api::schema::Request {
+ id: "req_pane_rename_clear".into(),
+ method: crate::api::schema::Method::PaneRename(crate::api::schema::PaneRenameParams {
+ pane_id,
+ label: None,
+ }),
+ });
+ let response: serde_json::Value = serde_json::from_str(&response).unwrap();
+
+ assert_eq!(response["result"]["type"], "pane_info");
+ assert!(response["result"]["pane"].get("label").is_none());
+ assert!(app.state.workspaces[0]
+ .pane_state(pane)
+ .unwrap()
+ .manual_label
+ .is_none());
+ }
+
#[tokio::test]
async fn pane_split_request_targets_pane_in_background_tab() {
let _guard = config_env_lock().lock().unwrap();
diff --git a/src/app/state.rs b/src/app/state.rs
index 134d86bd..dd081298 100644
--- a/src/app/state.rs
+++ b/src/app/state.rs
@@ -383,6 +383,7 @@ pub enum Mode {
Terminal,
RenameWorkspace,
RenameTab,
+ RenamePane,
Resize,
ConfirmClose,
ContextMenu,
@@ -408,16 +409,18 @@ pub enum SettingsSection {
Theme,
Sound,
Toast,
+ PaneLabels,
}
impl SettingsSection {
- pub const ALL: &[Self] = &[Self::Theme, Self::Sound, Self::Toast];
+ pub const ALL: &[Self] = &[Self::Theme, Self::Sound, Self::Toast, Self::PaneLabels];
pub fn label(self) -> &'static str {
match self {
Self::Theme => "theme",
Self::Sound => "sound",
Self::Toast => "toasts",
+ Self::PaneLabels => "pane labels",
}
}
}
@@ -554,9 +557,17 @@ pub(crate) struct TabPressState {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContextMenuKind {
- Workspace { ws_idx: usize },
- Tab { ws_idx: usize, tab_idx: usize },
- Pane,
+ Workspace {
+ ws_idx: usize,
+ },
+ Tab {
+ ws_idx: usize,
+ tab_idx: usize,
+ },
+ Pane {
+ pane_id: PaneId,
+ has_manual_label: bool,
+ },
}
/// Right-click context menu state.
@@ -572,7 +583,22 @@ impl ContextMenuState {
match self.kind {
ContextMenuKind::Workspace { .. } => &["Rename", "Close"],
ContextMenuKind::Tab { .. } => &["New tab", "Rename", "Close"],
- ContextMenuKind::Pane => &[
+ ContextMenuKind::Pane {
+ has_manual_label: true,
+ ..
+ } => &[
+ "Rename pane",
+ "Clear pane name",
+ "Split vertical",
+ "Split horizontal",
+ "Fullscreen",
+ "Close pane",
+ ],
+ ContextMenuKind::Pane {
+ has_manual_label: false,
+ ..
+ } => &[
+ "Rename pane",
"Split vertical",
"Split horizontal",
"Fullscreen",
@@ -645,6 +671,7 @@ pub struct AppState {
pub request_clipboard_write: Option<Vec<u8>>,
pub creating_new_tab: bool,
pub requested_new_tab_name: Option<String>,
+ pub rename_pane_target: Option<PaneId>,
pub request_complete_onboarding: bool,
pub name_input: String,
pub name_input_replace_on_type: bool,
@@ -683,6 +710,7 @@ pub struct AppState {
pub sidebar_section_split: f32,
pub agent_panel_scope: AgentPanelScope,
pub confirm_close: bool,
+ pub show_agent_labels_on_pane_borders: bool,
pub pane_scrollback_limit_bytes: usize,
#[allow(dead_code)] // kept for backward compat; palette.accent is the source of truth
pub accent: Color,
@@ -719,6 +747,10 @@ impl AppState {
self.toast_config.delivery
}
+ pub fn agent_border_labels_enabled(&self) -> bool {
+ self.show_agent_labels_on_pane_borders
+ }
+
pub fn is_prefix(&self, key: &crossterm::event::KeyEvent) -> bool {
key_matches(key, self.prefix_code, self.prefix_mods)
}
@@ -797,6 +829,7 @@ impl AppState {
request_clipboard_write: None,
creating_new_tab: false,
requested_new_tab_name: None,
+ rename_pane_target: None,
request_complete_onboarding: false,
name_input: String::new(),
name_input_replace_on_type: false,
@@ -844,6 +877,7 @@ impl AppState {
sidebar_section_split: 0.5,
agent_panel_scope: AgentPanelScope::AllWorkspaces,
confirm_close: true,
+ show_agent_labels_on_pane_borders: false,
pane_scrollback_limit_bytes: crate::config::DEFAULT_SCROLLBACK_LIMIT_BYTES,
accent: Color::Cyan,
sound: SoundConfig {
@@ -877,6 +911,8 @@ impl AppState {
next_tab_label: None,
close_tab: None,
close_tab_label: None,
+ rename_pane: None,
+ rename_pane_label: None,
focus_pane_left: None,
focus_pane_left_label: None,
focus_pane_down: None,