Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/next/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- The plugin marketplace now discovers valid manifests at repository roots and subdirectories, groups multiple plugins under each repository, and publishes their versions and exact default-branch commits.

### Fixed
- Prefix-mode keybindings can use Escape as the RHS key (for example `copy_mode = "prefix+esc"`, matching tmux `bind Escape copy-mode`). Unbound Escape still cancels prefix mode.
- Configs containing the retired Herdr-written `ui.agent_panel_scope` setting no longer report it as an unknown key after upgrades. (#2292)
- Claude Code confirmation prompts using `Enter to confirm · Esc to cancel` now report `blocked` instead of `idle`. (#2268)
- Sidebar agent lists keep scrolling when differently sized clients are attached to the same session. (#2255, thanks @aiworkflowpro)
Expand Down
2 changes: 1 addition & 1 deletion docs/next/website/src/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ next_tab = ["prefix+n", "ctrl+alt+]"]

Optional actions are unset by default. Bind them with `prefix+` for prefix-mode behavior, or use an explicit modified chord when you intentionally want a direct shortcut.

Key strings accept plain keys, modifier combinations such as `ctrl+a`, `shift+n`, `alt+1`, `cmd+k`, and special keys such as `enter`, `tab`, `esc`, `left`, `right`, `up`, and `down`. Named punctuation such as `minus`, `comma`, `ampersand`, `plus`, and `backtick` is also accepted. Plain direct printable keys such as `n` are unsafe because they intercept typing; use `prefix+n` unless you intentionally want a direct binding. The `navigate_workspace_*` and `navigate_pane_*` fields are navigate-mode-only and may use plain keys such as `j` or `k`; they must not use `prefix+`, `esc`, `enter`, `tab`, `shift+tab`, `left`, `right`, or unmodified `1` through `9`. Left and right arrows are permanent aliases for pane-left and pane-right navigation. These navigate-mode shortcuts are independent from general action bindings such as `focus_pane_down = "prefix+j"`; when both use the same key, the navigate-mode shortcut wins while navigate mode is open. Alt, Cmd/Super, and punctuation with modifiers depend on your terminal and tmux settings.
Key strings accept plain keys, modifier combinations such as `ctrl+a`, `shift+n`, `alt+1`, `cmd+k`, and special keys such as `enter`, `tab`, `esc`/`escape`, `left`, `right`, `up`, and `down`. Named punctuation such as `minus`, `comma`, `ampersand`, `plus`, and `backtick` is also accepted. Plain direct printable keys such as `n` are unsafe because they intercept typing; use `prefix+n` unless you intentionally want a direct binding. Escape may be used as a prefix-mode RHS key (for example `copy_mode = "prefix+esc"`, like tmux `bind Escape copy-mode`); when Escape is not bound, it still cancels prefix mode. The `navigate_workspace_*` and `navigate_pane_*` fields are navigate-mode-only and may use plain keys such as `j` or `k`; they must not use `prefix+`, `esc`, `enter`, `tab`, `shift+tab`, `left`, `right`, or unmodified `1` through `9`. Left and right arrows are permanent aliases for pane-left and pane-right navigation. These navigate-mode shortcuts are independent from general action bindings such as `focus_pane_down = "prefix+j"`; when both use the same key, the navigate-mode shortcut wins while navigate mode is open. Alt, Cmd/Super, and punctuation with modifiers depend on your terminal and tmux settings.

If you have old custom keybindings and want the new defaults, run `herdr config reset-keys`. Herdr backs up `config.toml`, removes `[keys]` and `[[keys.command]]`, and uses built-in v2 defaults after restart or `herdr server reload-config`.

Expand Down
2 changes: 1 addition & 1 deletion docs/next/website/src/content/docs/ja/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ next_tab = ["prefix+n", "ctrl+alt+]"]

任意のアクションはデフォルトでは未設定です。プレフィックスモードの挙動には `prefix+` でバインドし、意図的に直接ショートカットにする場合は明示的な修飾キーコードを使ってください。

キー文字列には、通常のキー、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` のような修飾キーの組み合わせ、`enter`、`tab`、`esc`、`left`、`right`、`up`、`down` のような特殊キーが使えます。`minus`、`comma`、`ampersand`、`plus`、`backtick` のような名前付き記号も使えます。`n` のような通常の印字可能キーの直接バインドは入力を妨げるため危険です。意図的に直接バインドするのでなければ `prefix+n` を使ってください。`navigate_workspace_*` と `navigate_pane_*` のフィールドはナビゲートモード専用で、`j` や `k` のような通常のキーを使えます。これらには `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`、修飾なしの `1` から `9` は使えません。左右の矢印キーは、左ペインと右ペインへのナビゲーションの恒久的なエイリアスです。これらのナビゲートモードショートカットは、`focus_pane_down = "prefix+j"` のような一般アクションのバインドから独立しています。両方に同じキーが使われている場合、ナビゲートモードが開いている間はナビゲートモードのショートカットが優先されます。Alt、Cmd/Super、修飾キー付き記号は、ターミナルと tmux の設定に依存します。
キー文字列には、通常のキー、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` のような修飾キーの組み合わせ、`enter`、`tab`、`esc`/`escape`、`left`、`right`、`up`、`down` のような特殊キーが使えます。`minus`、`comma`、`ampersand`、`plus`、`backtick` のような名前付き記号も使えます。`n` のような通常の印字可能キーの直接バインドは入力を妨げるため危険です。意図的に直接バインドするのでなければ `prefix+n` を使ってください。Escape はプレフィックスモードの右側キーとしても使えます(例: `copy_mode = "prefix+esc"`。tmux の `bind Escape copy-mode` に相当)。バインドされていない場合、Escape はこれまでどおりプレフィックスモードを解除します。`navigate_workspace_*` と `navigate_pane_*` のフィールドはナビゲートモード専用で、`j` や `k` のような通常のキーを使えます。これらには `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`、修飾なしの `1` から `9` は使えません。左右の矢印キーは、左ペインと右ペインへのナビゲーションの恒久的なエイリアスです。これらのナビゲートモードショートカットは、`focus_pane_down = "prefix+j"` のような一般アクションのバインドから独立しています。両方に同じキーが使われている場合、ナビゲートモードが開いている間はナビゲートモードのショートカットが優先されます。Alt、Cmd/Super、修飾キー付き記号は、ターミナルと tmux の設定に依存します。

古いカスタムキーバインドを使っていて新しいデフォルトが欲しい場合は、`herdr config reset-keys` を実行してください。Herdr は `config.toml` をバックアップし、`[keys]` と `[[keys.command]]` を削除し、再起動または `herdr server reload-config` の後に組み込みの v2 デフォルトを使います。

Expand Down
2 changes: 1 addition & 1 deletion docs/next/website/src/content/docs/zh-cn/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ next_tab = ["prefix+n", "ctrl+alt+]"]

可选动作默认不设置。使用 `prefix+` 可获得前缀模式行为;当你确实需要直接快捷键时,请使用显式的修饰组合键。

按键字符串支持普通按键、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` 等修饰组合键,以及 `enter`、`tab`、`esc`、`left`、`right`、`up`、`down` 等特殊键。也支持 `minus`、`comma`、`ampersand`、`plus`、`backtick` 等命名标点。直接绑定 `n` 这样的普通可打印键并不安全,因为它会拦截输入;除非你有意设置直接绑定,否则请使用 `prefix+n`。`navigate_workspace_*` 和 `navigate_pane_*` 字段仅在导航模式中生效,可以使用 `j` 或 `k` 等普通按键;它们不得使用 `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`,也不得使用无修饰键的 `1` 到 `9`。左右方向键是向左和向右导航窗格的永久别名。这些导航模式快捷键独立于 `focus_pane_down = "prefix+j"` 等通用动作绑定;两者使用同一按键时,打开导航模式后,导航模式快捷键优先。Alt、Cmd/Super 以及带修饰键的标点取决于你的终端和 tmux 设置。
按键字符串支持普通按键、`ctrl+a`、`shift+n`、`alt+1`、`cmd+k` 等修饰组合键,以及 `enter`、`tab`、`esc`/`escape`、`left`、`right`、`up`、`down` 等特殊键。也支持 `minus`、`comma`、`ampersand`、`plus`、`backtick` 等命名标点。直接绑定 `n` 这样的普通可打印键并不安全,因为它会拦截输入;除非你有意设置直接绑定,否则请使用 `prefix+n`。Escape 可以用作前缀模式的右侧按键(例如 `copy_mode = "prefix+esc"`,类似 tmux 的 `bind Escape copy-mode`);未绑定时,Escape 仍会取消前缀模式。`navigate_workspace_*` 和 `navigate_pane_*` 字段仅在导航模式中生效,可以使用 `j` 或 `k` 等普通按键;它们不得使用 `prefix+`、`esc`、`enter`、`tab`、`shift+tab`、`left`、`right`,也不得使用无修饰键的 `1` 到 `9`。左右方向键是向左和向右导航窗格的永久别名。这些导航模式快捷键独立于 `focus_pane_down = "prefix+j"` 等通用动作绑定;两者使用同一按键时,打开导航模式后,导航模式快捷键优先。Alt、Cmd/Super 以及带修饰键的标点取决于你的终端和 tmux 设置。

如果你已有旧的自定义按键绑定,并想使用新的默认值,请运行 `herdr config reset-keys`。Herdr 会备份 `config.toml`,移除 `[keys]` 和 `[[keys.command]]`,并在重启或执行 `herdr server reload-config` 后使用内置的 v2 默认值。

Expand Down
80 changes: 75 additions & 5 deletions src/app/input/navigate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ impl App {
return;
}

if key.code == KeyCode::Esc {
leave_command_mode(&mut self.state);
return;
}

if let Some(action) =
non_indexed_action_for_key(&self.state, &raw_key, BindingDispatch::Prefix)
{
Expand Down Expand Up @@ -3033,6 +3028,81 @@ navigate_pane_down = "ctrl+j"
assert_eq!(app.state.mode, Mode::Terminal);
}

#[tokio::test]
async fn unbound_prefix_escape_exits_prefix_mode() {
let mut app = app_with_test_workspaces(&["test"]);
app.state.mode = Mode::Terminal;

app.handle_key(TerminalKey::new(
app.state.prefix_code,
app.state.prefix_mods,
))
.await;
assert_eq!(app.state.mode, Mode::Prefix);

app.handle_key(TerminalKey::new(KeyCode::Esc, KeyModifiers::empty()))
.await;

assert_eq!(app.state.mode, Mode::Terminal);
assert!(app.state.copy_mode.is_none());
}

#[tokio::test]
async fn prefix_escape_copy_mode_binding_enters_copy_mode() {
let mut app = app_with_test_workspaces(&["test"]);
app.state.view.pane_infos = app.state.workspaces[0].tabs[0]
.layout
.panes(ratatui::layout::Rect::new(0, 0, 80, 24));
let config: Config = toml::from_str(
r#"
[keys]
copy_mode = "prefix+esc"
"#,
)
.unwrap();
app.state.keybinds.copy_mode = config.keybinds().copy_mode;
app.state.mode = Mode::Terminal;

app.handle_key(TerminalKey::new(
app.state.prefix_code,
app.state.prefix_mods,
))
.await;
app.handle_key(TerminalKey::new(KeyCode::Esc, KeyModifiers::empty()))
.await;

assert_eq!(app.state.mode, Mode::Copy);
assert!(app.state.copy_mode.is_some());
}

#[tokio::test]
async fn prefix_escape_alias_copy_mode_binding_enters_copy_mode() {
let mut app = app_with_test_workspaces(&["test"]);
app.state.view.pane_infos = app.state.workspaces[0].tabs[0]
.layout
.panes(ratatui::layout::Rect::new(0, 0, 80, 24));
let config: Config = toml::from_str(
r#"
[keys]
copy_mode = "prefix+escape"
"#,
)
.unwrap();
app.state.keybinds.copy_mode = config.keybinds().copy_mode;
app.state.mode = Mode::Terminal;

app.handle_key(TerminalKey::new(
app.state.prefix_code,
app.state.prefix_mods,
))
.await;
app.handle_key(TerminalKey::new(KeyCode::Esc, KeyModifiers::empty()))
.await;

assert_eq!(app.state.mode, Mode::Copy);
assert!(app.state.copy_mode.is_some());
}

#[tokio::test]
async fn prefix_help_matches_enhanced_shifted_question_mark() {
let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
Expand Down
27 changes: 27 additions & 0 deletions src/config/keybinds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,33 @@ next_tab = "prefix+n"
);
}

#[test]
fn copy_mode_accepts_prefix_escape_aliases() {
for raw in ["prefix+esc", "prefix+escape", "prefix+Esc", "prefix+Escape"] {
let config: Config = toml::from_str(&format!(
r#"
[keys]
copy_mode = "{raw}"
"#
))
.unwrap();
let kb = config.keybinds();
assert!(
config.collect_diagnostics().is_empty(),
"unexpected diagnostics for {raw:?}: {:?}",
config.collect_diagnostics()
);
assert_eq!(
binding_triggers(&kb.copy_mode),
vec![BindingTrigger::Prefix((
KeyCode::Esc,
KeyModifiers::empty()
))],
"failed for {raw:?}"
);
}
}

#[test]
fn back_and_forth_keybinds_are_unset_by_default() {
let kb = Config::default().keybinds();
Expand Down
Loading