Skip to content
25 changes: 25 additions & 0 deletions app/src/search/slash_command_menu/static_commands/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ pub static RENAME_TAB: LazyLock<StaticCommand> = LazyLock::new(|| StaticCommand
argument: Some(Argument::required().with_hint_text("<tab name>")),
});

pub static RENAME_PANE: LazyLock<StaticCommand> = LazyLock::new(|| StaticCommand {
name: "/rename-pane",
description: "Rename the current pane",
icon_path: "bundled/svg/pencil-line.svg",
availability: Availability::ALWAYS,
auto_enter_ai_mode: false,
argument: Some(Argument::required().with_hint_text("<pane name>")),
});

Comment thread
psh4607 marked this conversation as resolved.
pub static FORK: LazyLock<StaticCommand> = LazyLock::new(|| {
let hint_text = "<optional prompt to send in forked conversation>";
StaticCommand {
Expand Down Expand Up @@ -526,6 +535,7 @@ fn all_commands() -> Vec<StaticCommand> {
NEW.clone(),
PLAN.clone(),
RENAME_TAB.clone(),
RENAME_PANE.clone(),
USAGE,
CONVERSATIONS,
EXPORT_TO_CLIPBOARD,
Expand Down Expand Up @@ -650,6 +660,21 @@ mod tests {
assert_eq!(argument.hint_text, Some("<tab name>"));
}

#[test]
fn rename_pane_command_requires_argument() {
let command = COMMAND_REGISTRY
.get_command_with_name(RENAME_PANE.name)
.expect("expected /rename-pane to be registered");
let argument = command
.argument
.as_ref()
.expect("expected /rename-pane to require an argument");

assert!(!argument.is_optional);
assert!(!argument.should_execute_on_selection);
assert_eq!(argument.hint_text, Some("<pane name>"));
}

#[test]
fn strip_command_prefix_matches_orchestrate() {
let result = strip_command_prefix("/orchestrate deploy services", "/orchestrate");
Expand Down
14 changes: 14 additions & 0 deletions app/src/terminal/input/slash_commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,20 @@ impl Input {

ctx.dispatch_typed_action(&WorkspaceAction::SetActiveTabName(name.to_owned()));
}
rename_pane if command.name == commands::RENAME_PANE.name => {
let Some(name) = argument
.map(|name| name.trim())
.filter(|name| !name.is_empty())
else {
show_error_toast(
"Please provide a pane name after /rename-pane".to_owned(),
ctx,
);
return true;
};

ctx.dispatch_typed_action(&WorkspaceAction::SetActivePaneName(name.to_owned()));
}
create_env if command.name == commands::CREATE_ENVIRONMENT.name => {
// If the user included args after the slash command, treat them as repo paths/URLs.
let repos = argument
Expand Down
2 changes: 2 additions & 0 deletions app/src/util/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ pub enum CustomAction {
GoToLine,
ToggleGlobalSearch,
ToggleConversationListView,
RenamePane,
}

lazy_static! {
Expand Down Expand Up @@ -441,6 +442,7 @@ pub fn custom_tag_to_keystroke(custom: CustomTag) -> Option<Keystroke> {
| CustomAction::SplitPaneUp
| CustomAction::ConfigureKeybindings
| CustomAction::RenameTab
| CustomAction::RenamePane
| CustomAction::CloseTab
| CustomAction::CloseOtherTabs
| CustomAction::CloseTabsRight
Expand Down
4 changes: 4 additions & 0 deletions app/src/workspace/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ pub enum WorkspaceAction {
RenamePane(PaneViewLocator),
ResetPaneName(PaneViewLocator),
RenameActiveTab,
RenameActivePane,
SetActiveTabName(String),
SetActivePaneName(String),
ToggleTabRightClickMenu {
tab_index: usize,
anchor: TabContextMenuAnchor,
Expand Down Expand Up @@ -720,7 +722,9 @@ impl WorkspaceAction {
| RenamePane(_)
| ResetPaneName(_)
| RenameActiveTab
| RenameActivePane
| SetActiveTabName(_)
| SetActivePaneName(_)
| CloseTab(_)
| CloseActiveTab
| CloseOtherTabs(_)
Expand Down
9 changes: 9 additions & 0 deletions app/src/workspace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,15 @@ pub fn init(app: &mut AppContext) {
.with_custom_action(CustomAction::RenameTab)
.with_context_predicate(id!("Workspace"))]);

app.register_editable_bindings([EditableBinding::new(
"workspace:rename_active_pane",
"Rename the current pane",
WorkspaceAction::RenameActivePane,
)
.with_group(bindings::BindingGroup::Settings.as_str())
.with_custom_action(CustomAction::RenamePane)
.with_context_predicate(id!("Workspace"))]);

app.register_editable_bindings([
EditableBinding::new(
"workspace:terminate_app",
Expand Down
56 changes: 56 additions & 0 deletions app/src/workspace/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5242,6 +5242,60 @@ impl Workspace {
ctx.notify();
}

pub fn rename_active_pane(&mut self, ctx: &mut ViewContext<Self>) {
let use_vertical_tabs =
FeatureFlag::VerticalTabs.is_enabled() && *TabSettings::as_ref(ctx).use_vertical_tabs;
if !use_vertical_tabs {
Comment thread
psh4607 marked this conversation as resolved.
Outdated
let message =
"Pane renaming via shortcut requires Vertical Tabs. Use `/rename-pane <name>` instead, or enable Vertical Tabs in Settings."
.to_string();
self.toast_stack.update(ctx, |view, ctx| {
view.add_ephemeral_toast(DismissibleToast::default(message), ctx);
});
return;
}
if !self.vertical_tabs_panel_open {
Comment thread
psh4607 marked this conversation as resolved.
Outdated
self.vertical_tabs_panel_open = true;
}
let active_pane_group = self.active_tab_pane_group().clone();
let pane_group_id = active_pane_group.id();
let pane_id = active_pane_group.as_ref(ctx).focused_pane_id(ctx);
self.rename_pane(
Comment thread
psh4607 marked this conversation as resolved.
Outdated
Comment thread
psh4607 marked this conversation as resolved.
Outdated
PaneViewLocator {
pane_group_id,
pane_id,
},
ctx,
);
}

fn set_active_pane_name(&mut self, name: &str, ctx: &mut ViewContext<Self>) {
Comment thread
psh4607 marked this conversation as resolved.
let active_pane_group = self.active_tab_pane_group().clone();
let pane_group_id = active_pane_group.id();
let pane_id = active_pane_group.as_ref(ctx).focused_pane_id(ctx);

if self.current_workspace_state.is_any_pane_being_renamed() {
self.current_workspace_state.clear_pane_being_renamed();
self.clear_pane_name_editor(ctx);
}

let title = name.trim();
if title.is_empty() {
ctx.notify();
return;
}
self.set_custom_pane_name(
PaneViewLocator {
pane_group_id,
pane_id,
},
title.to_owned(),
ctx,
);
Comment thread
psh4607 marked this conversation as resolved.
ctx.dispatch_global_action("workspace:save_app", ());
ctx.notify();
}

pub fn list_tab_pane_groups(&self, app: &AppContext) -> Vec<TabPaneGroupIdentifiers> {
self.tabs
.iter()
Expand Down Expand Up @@ -19676,7 +19730,9 @@ impl TypedActionView for Workspace {
RenamePane(locator) => self.rename_pane(*locator, ctx),
ResetPaneName(locator) => self.clear_pane_name(*locator, ctx),
RenameActiveTab => self.rename_tab(self.active_tab_index, ctx),
RenameActivePane => self.rename_active_pane(ctx),
SetActiveTabName(name) => self.set_active_tab_name(name, ctx),
SetActivePaneName(name) => self.set_active_pane_name(name, ctx),
ToggleTabRightClickMenu { tab_index, anchor } => {
self.toggle_tab_right_click_menu(*tab_index, *anchor, ctx)
}
Expand Down