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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ The dock is a narrow Herdr pane rather than an extension of the native
sidebar. That keeps the plugin on Herdr's public plugin surface, compatible
with upstream Herdr, and independently installable.

Docks survive Herdr server restarts: toggling a dock records where it was
open, and after `herdr server stop` and a fresh start a startup hook re-opens
those docks — without stealing focus and at their saved width. The plain shell
pane Herdr leaves in the dock's old slot is not plugin-owned after the
restart, so close it once manually. Opt out with `[dock]
restore_on_startup = false` in the plugin config
(`herdr plugin config-dir herdr-context`).

The goal is simple: **know where the project stands — files, changes,
conversations — without leaving the tab.**

Expand Down Expand Up @@ -204,6 +212,7 @@ warning in the dock.
```toml
[dock]
initial_width = 40 # 24..60
restore_on_startup = true # re-open docks after a Herdr server restart

[ui]
display_mode = "ascii" # ascii, unicode, or nerd
Expand Down
7 changes: 7 additions & 0 deletions herdr-plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,10 @@ id = "toggle"
title = "herdr-context: toggle dock"
contexts = ["workspace", "tab", "pane"]
command = ["./target/release/herdr-context", "toggle"]

[[startup]]
command = ["./target/release/herdr-context", "restore"]

[[events]]
on = "pane.exited"
command = ["./target/release/herdr-context", "on-event"]
17 changes: 16 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,27 @@ impl ConfigLoad {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DockConfig {
initial_width: u16,
restore_on_startup: bool,
}

impl DockConfig {
#[must_use]
pub const fn initial_width(self) -> u16 {
self.initial_width
}

/// Whether the restore startup hook re-opens docks after a Herdr restart.
#[must_use]
pub const fn restore_on_startup(self) -> bool {
self.restore_on_startup
}
}

impl Default for DockConfig {
fn default() -> Self {
Self {
initial_width: DEFAULT_DOCK_WIDTH,
restore_on_startup: true,
}
}
}
Expand Down Expand Up @@ -684,7 +692,7 @@ fn parse_config(value: &toml::Value) -> ConfigLoad {
if let Some(table) = optional_table(root, "dock", &mut warnings) {
warn_unknown_fields(
table,
&["initial_width"],
&["initial_width", "restore_on_startup"],
"dock.unknown_field",
&mut warnings,
);
Expand All @@ -696,6 +704,13 @@ fn parse_config(value: &toml::Value) -> ConfigLoad {
&mut warnings,
)
.unwrap_or(DEFAULT_DOCK_WIDTH);

config.dock.restore_on_startup = parse_bool(
table.get("restore_on_startup"),
"dock.restore_on_startup",
&mut warnings,
)
.unwrap_or(true);
}

if let Some(table) = optional_table(root, "ui", &mut warnings) {
Expand Down
10 changes: 8 additions & 2 deletions src/host/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ impl HostClient for CommandHostClient {
let mut origin_pane_id = OsString::from("HERDR_CONTEXT_ORIGIN_PANE_ID=");
origin_pane_id.push(request.origin_pane_id().as_str());
let pane_cwd = self.plugin_root.as_deref().unwrap_or_else(|| request.cwd());
let args = vec![
let mut args = vec![
OsString::from("plugin"),
OsString::from("pane"),
OsString::from("open"),
Expand All @@ -518,8 +518,14 @@ impl HostClient for CommandHostClient {
origin_cwd,
OsString::from("--env"),
origin_pane_id,
OsString::from("--focus"),
];
if request.focus() {
args.push(OsString::from("--focus"));
} else {
// Herdr focuses a newly opened pane by default; the explicit
// negation is what keeps restore from stealing focus.
args.push(OsString::from("--no-focus"));
}
let result = self.invoke(args)?;
expect_type(&result, "plugin_pane_opened")?;
let pane_id = required_string(&result, "/plugin_pane/pane/pane_id", "opened pane id")?;
Expand Down
Loading
Loading