parent
8dcb75a5c0
commit
550e9f8cc0
|
|
@ -7,6 +7,7 @@
|
|||
- Added macOS support for the `HERDR_AGENT=<agent>` foreground-process hint, allowing agents hidden behind host-visible wrappers such as `nono` to use the named agent's screen manifest. (#679)
|
||||
|
||||
### Fixed
|
||||
- New panes, tabs, layouts, and workspaces using `new_cwd = "follow"` now inherit the foreground process-group leader's working directory instead of an unrelated helper process directory. (#1472)
|
||||
- Background update checks, captured plugin commands, and workspace Git probes no longer flash console windows on Windows. (#1468)
|
||||
- Live handoff now preserves installed plugins and no longer lets the next plugin installation overwrite the existing registry. (#893)
|
||||
- `herdr wait agent-status` now returns `pane_not_found` promptly when its target pane closes instead of waiting for the full timeout. (#1439)
|
||||
|
|
|
|||
26
src/pane.rs
26
src/pane.rs
|
|
@ -2713,6 +2713,22 @@ impl PaneRuntime {
|
|||
(pid > 0).then_some(pid)
|
||||
}
|
||||
|
||||
pub fn follow_cwd(&self) -> Option<std::path::PathBuf> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let leader_cwd = self
|
||||
.io
|
||||
.foreground_process_group_id()
|
||||
.and_then(usable_process_cwd);
|
||||
leader_cwd.or_else(|| self.cwd())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
self.cwd()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current working directory of the process group controlling the pane PTY.
|
||||
pub fn foreground_cwd(&self) -> Option<std::path::PathBuf> {
|
||||
#[cfg(unix)]
|
||||
|
|
@ -2816,6 +2832,16 @@ impl PaneRuntime {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn follow_cwd_falls_back_to_reported_pane_cwd_without_foreground_group() {
|
||||
let (runtime, _rx) = PaneRuntime::test_with_channel(80, 24);
|
||||
let cwd = std::env::temp_dir();
|
||||
*runtime.reported_cwd.lock().unwrap() = Some(cwd.clone());
|
||||
|
||||
assert_eq!(runtime.follow_cwd(), Some(cwd));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_liveness_treats_reaped_direct_child_as_gone() {
|
||||
assert!(!process_alive_for_shutdown(42, 42, true, |_| true));
|
||||
|
|
|
|||
|
|
@ -453,6 +453,10 @@ impl TerminalRuntime {
|
|||
self.0.cwd()
|
||||
}
|
||||
|
||||
pub fn follow_cwd(&self) -> Option<std::path::PathBuf> {
|
||||
self.0.follow_cwd()
|
||||
}
|
||||
|
||||
pub fn foreground_cwd(&self) -> Option<std::path::PathBuf> {
|
||||
self.0.foreground_cwd()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -578,7 +578,14 @@ impl Tab {
|
|||
terminals: &HashMap<TerminalId, TerminalState>,
|
||||
terminal_runtimes: &TerminalRuntimeRegistry,
|
||||
) -> Option<PathBuf> {
|
||||
self.foreground_cwd_for_pane(pane_id, terminal_runtimes)
|
||||
.or_else(|| self.cwd_for_pane(pane_id, terminals, terminal_runtimes))
|
||||
let terminal_id = self.terminal_id(pane_id)?;
|
||||
terminal_runtimes
|
||||
.get(terminal_id)
|
||||
.and_then(|runtime| runtime.follow_cwd())
|
||||
.or_else(|| {
|
||||
terminals
|
||||
.get(terminal_id)
|
||||
.map(|terminal| terminal.cwd.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -895,6 +895,144 @@ fn pane_info_reports_foreground_cwd_without_changing_pane_cwd() {
|
|||
cleanup_spawned_herdr(child, base);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn new_terminal_cwd_follow_ignores_nonleader_group_member_cwd() {
|
||||
let _lock = test_lock();
|
||||
let base = unique_test_dir();
|
||||
let helper_cwd = base.join("plugin-cache");
|
||||
let helper_marker = base.join("helper-cwd-ready");
|
||||
let marker = base.join("helper-ready");
|
||||
let leader_pid_file = base.join("leader.pid");
|
||||
let helper_pid_file = base.join("helper.pid");
|
||||
fs::create_dir_all(&helper_cwd).unwrap();
|
||||
let config_home = base.join("config");
|
||||
let runtime_dir = base.join("runtime");
|
||||
let socket_path = runtime_dir.join("herdr.sock");
|
||||
|
||||
let child = spawn_herdr_with_shell(&config_home, &runtime_dir, &socket_path, "/bin/bash");
|
||||
wait_for_socket(&socket_path, Duration::from_secs(5));
|
||||
|
||||
let created = send_request(
|
||||
&socket_path,
|
||||
&format!(
|
||||
r#"{{"id":"member_ws","method":"workspace.create","params":{{"cwd":"{}","focus":true}}}}"#,
|
||||
base.display()
|
||||
),
|
||||
);
|
||||
let pane_id = created["result"]["root_pane"]["pane_id"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let workspace_id = created["result"]["workspace"]["workspace_id"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let command = format!(
|
||||
"/bin/sh -c 'printf %s $$ > {}; (cd {} && touch {} && sleep 30) & printf %s $! > {}; while [ ! -e {} ]; do sleep 0.01; done; touch {}; wait'",
|
||||
leader_pid_file.display(),
|
||||
helper_cwd.display(),
|
||||
helper_marker.display(),
|
||||
helper_pid_file.display(),
|
||||
helper_marker.display(),
|
||||
marker.display()
|
||||
);
|
||||
let send_text = send_request(
|
||||
&socket_path,
|
||||
&serde_json::json!({
|
||||
"id": "member_send",
|
||||
"method": "pane.send_text",
|
||||
"params": {
|
||||
"pane_id": pane_id,
|
||||
"text": command,
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(send_text["result"]["type"], "ok");
|
||||
let send_enter = send_request(
|
||||
&socket_path,
|
||||
&format!(
|
||||
r#"{{"id":"member_enter","method":"pane.send_keys","params":{{"pane_id":"{}","keys":["Enter"]}}}}"#,
|
||||
pane_id
|
||||
),
|
||||
);
|
||||
assert_eq!(send_enter["result"]["type"], "ok");
|
||||
wait_for_path(&marker, Duration::from_secs(5));
|
||||
|
||||
let leader_pid: u32 = fs::read_to_string(&leader_pid_file)
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
let helper_pid: u32 = fs::read_to_string(&helper_pid_file)
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
unsafe { libc::getpgid(leader_pid as libc::pid_t) },
|
||||
leader_pid as libc::pid_t
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { libc::getpgid(helper_pid as libc::pid_t) },
|
||||
leader_pid as libc::pid_t
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_link(format!("/proc/{leader_pid}/cwd")).unwrap(),
|
||||
base
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read_link(format!("/proc/{helper_pid}/cwd")).unwrap(),
|
||||
helper_cwd
|
||||
);
|
||||
|
||||
let pane = send_request(
|
||||
&socket_path,
|
||||
&format!(
|
||||
r#"{{"id":"member_pane","method":"pane.get","params":{{"pane_id":"{}"}}}}"#,
|
||||
pane_id
|
||||
),
|
||||
);
|
||||
assert_eq!(pane["result"]["pane"]["cwd"], base.display().to_string());
|
||||
assert_eq!(
|
||||
pane["result"]["pane"]["foreground_cwd"],
|
||||
helper_cwd.display().to_string()
|
||||
);
|
||||
|
||||
let split = send_request(
|
||||
&socket_path,
|
||||
&serde_json::json!({
|
||||
"id": "member_split",
|
||||
"method": "pane.split",
|
||||
"params": {
|
||||
"target_pane_id": pane_id,
|
||||
"direction": "right",
|
||||
"focus": false,
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(split["result"]["pane"]["cwd"], base.display().to_string());
|
||||
|
||||
let tab = send_request(
|
||||
&socket_path,
|
||||
&serde_json::json!({
|
||||
"id": "member_tab",
|
||||
"method": "tab.create",
|
||||
"params": {
|
||||
"workspace_id": workspace_id,
|
||||
"focus": false,
|
||||
},
|
||||
})
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
tab["result"]["root_pane"]["cwd"],
|
||||
base.display().to_string()
|
||||
);
|
||||
|
||||
cleanup_spawned_herdr(child, base);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[test]
|
||||
fn agent_start_creates_named_terminal_over_socket() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue