T29: the foreground is its group and the executable its leader runs, so exec herdr from a shell is followed and a program that did not read is tried again; a relative herdr socket lies in the client's folder; on Linux an executable an update replaced still names itself herdr
This commit is contained in:
4 files changed
+119
-28
No files matched your search
@@ -92,4 +92,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
|
|||||||
- 2026-10-15: a release's macOS disk image comes from the Release workflow, dispatched once the draft holds the Windows and Linux builds. It runs on `macos-signing`, the Mac mini's runner in the owner's login session, whose keychain holds the Developer ID identity; at the owner's choice that runner moved from AgentCompany alone to the owner's level, so every repository reaches it. The job reads the App Store Connect key from the owner's user-level secrets `APPLE_API_ISSUER`, `APPLE_API_KEY_ID` and `APPLE_API_KEY_P8`, writes the .p8 into the runner's temp folder for notarytool alone, and puts the stapled image on the draft through `scripts/release-asset.sh` with the job's own token. One notarization covers the image and the app inside it; the image carries the ticket, and the app copied out finds its own online on its first open.
|
- 2026-10-15: a release's macOS disk image comes from the Release workflow, dispatched once the draft holds the Windows and Linux builds. It runs on `macos-signing`, the Mac mini's runner in the owner's login session, whose keychain holds the Developer ID identity; at the owner's choice that runner moved from AgentCompany alone to the owner's level, so every repository reaches it. The job reads the App Store Connect key from the owner's user-level secrets `APPLE_API_ISSUER`, `APPLE_API_KEY_ID` and `APPLE_API_KEY_P8`, writes the .p8 into the runner's temp folder for notarytool alone, and puts the stapled image on the draft through `scripts/release-asset.sh` with the job's own token. One notarization covers the image and the app inside it; the image carries the ticket, and the app copied out finds its own online on its first open.
|
||||||
- 2026-10-15: on Omarchy 4 (Linux) ELYTerminal follows the system's theme. Omarchy renders the current theme's `colors.toml` through its own resolver into `~/.local/state/omarchy/current/theme/ghostty.conf`, under `$HOME` whatever `XDG_STATE_HOME` says, so ELYTerminal reads that file and wears what Ghostty wears there: its color lines go through the strict parser the bundled themes use, and the rest, such as the glass block, pass by. Omarchy writes no `cursor-text`, so the background stands in, as in Ghostty and Omarchy's kitty template. ELYTerminal reads the file twice a second, since Omarchy signals only Alacritty, kitty and Ghostty, by name, and wears it whenever its text changes and parses; a check that fails is logged once while it lasts and tried again, so a file made readable again is worn though nothing else about it changed. The Omarchy theme is the default unless that file is surely absent, so a folder that cannot be read makes a fresh install wear it and the start name the error; another pick stops following. At start a missing file means no Omarchy unless the settings wear it, a file that cannot be read stops the start, and one that does not parse stops it only when worn, else it is logged and no Omarchy card shows. While running, a missing file is a switch in progress, and any other failure keeps the last theme. Omarchy 3's `~/.config/omarchy/current` gets no shim.
|
- 2026-10-15: on Omarchy 4 (Linux) ELYTerminal follows the system's theme. Omarchy renders the current theme's `colors.toml` through its own resolver into `~/.local/state/omarchy/current/theme/ghostty.conf`, under `$HOME` whatever `XDG_STATE_HOME` says, so ELYTerminal reads that file and wears what Ghostty wears there: its color lines go through the strict parser the bundled themes use, and the rest, such as the glass block, pass by. Omarchy writes no `cursor-text`, so the background stands in, as in Ghostty and Omarchy's kitty template. ELYTerminal reads the file twice a second, since Omarchy signals only Alacritty, kitty and Ghostty, by name, and wears it whenever its text changes and parses; a check that fails is logged once while it lasts and tried again, so a file made readable again is worn though nothing else about it changed. The Omarchy theme is the default unless that file is surely absent, so a folder that cannot be read makes a fresh install wear it and the start name the error; another pick stops following. At start a missing file means no Omarchy unless the settings wear it, a file that cannot be read stops the start, and one that does not parse stops it only when worn, else it is logged and no Omarchy card shows. While running, a missing file is a switch in progress, and any other failure keeps the last theme. Omarchy 3's `~/.config/omarchy/current` gets no shim.
|
||||||
- 2026-10-16: on Hyprland 0.56 a window blurs only while Hyprland's own `decoration:blur:enabled` is on: its `shouldBlur` reads that option first, then the window's `no_blur`, RGBX and opaque rules, then the blur region a surface sets through `ext_background_effect_v1`, which Hyprland offers with the blur capability whatever the option says. Omarchy 4.0.4's default `looknfeel.lua` turns the option off, so on Omarchy no window blurs until the user turns it on; ELYTerminal leaves the compositor's settings alone. On the owner's Omarchy the log named `ext_background_effect_v1` and the pane's region, and the wallpaper behind the window blurred with the option on and showed sharp with it off.
|
- 2026-10-16: on Hyprland 0.56 a window blurs only while Hyprland's own `decoration:blur:enabled` is on: its `shouldBlur` reads that option first, then the window's `no_blur`, RGBX and opaque rules, then the blur region a surface sets through `ext_background_effect_v1`, which Hyprland offers with the blur capability whatever the option says. Omarchy 4.0.4's default `looknfeel.lua` turns the option off, so on Omarchy no window blurs until the user turns it on; ELYTerminal leaves the compositor's settings alone. On the owner's Omarchy the log named `ext_background_effect_v1` and the pane's region, and the wallpaper behind the window blurred with the option on and showed sharp with it off.
|
||||||
- 2026-10-16: herdr 0.9 keeps its panes' OSC 7 and writes only a title, notifications, OSC 52 and bells to the terminal it runs in, so no terminal hears a folder from inside it; herdr's server knows each pane's `cwd`, the folder its shell reported, else the shell's own. On macOS and Linux each session asks twice a second which process group leads its pty's foreground (`tcgetpgrp` on a copy of the master). When that leader's executable is `herdr` and its arguments start herdr's terminal client (no command, `session attach NAME` or `client`), ELYTerminal reads the client's arguments and variables (`/proc` on Linux; `KERN_PROCARGS2` on macOS, which hides only the variables of Apple's own programs) and finds its socket as herdr 0.9 does: `--session NAME`, then `HERDR_SOCKET_PATH`, then `HERDR_SESSION`, under `$XDG_CONFIG_HOME/herdr`, else `~/.config/herdr`. It asks that socket for `session.snapshot` twice a second, a connection each, since herdr closes one after its reply, and reports the focused pane's `cwd` as the session's folder when it changes and again each time herdr takes the foreground. Focus belongs to herdr's server, so clients of one server follow one focus; a client of another machine's server (`--remote`, `--machine`) is logged, not followed. A failure is logged once while it lasts. Windows has none of this: there herdr is a beta on named pipes.
|
- 2026-10-16: herdr 0.9 keeps its panes' OSC 7 and writes only a title, notifications, OSC 52 and bells to the terminal it runs in, so no terminal hears a folder from inside it; herdr's server knows each pane's `cwd`, the folder its shell reported, else the shell's own. On macOS and Linux each session asks twice a second which process group leads its pty's foreground (`tcgetpgrp` on a copy of the master) and what executable that group's leader runs: an exec keeps the group and swaps the executable, so `exec herdr` from a shell is a new program, and one that did not read is tried again; on Linux the ` (deleted)` mark an update leaves on a running executable is set aside. When that executable is `herdr` and its arguments start herdr's terminal client (no command, `session attach NAME` or `client`), ELYTerminal reads the client's arguments and variables (`/proc` on Linux; `KERN_PROCARGS2` on macOS, which hides only the variables of Apple's own programs) and finds its socket as herdr 0.9 does: `--session NAME`, then `HERDR_SOCKET_PATH`, then `HERDR_SESSION`, under `$XDG_CONFIG_HOME/herdr`, else `~/.config/herdr`; a relative socket lies in the client's folder, as herdr reads it (`/proc/PID/cwd`; `proc_pidinfo` on macOS). It asks that socket for `session.snapshot` twice a second, a connection each, since herdr closes one after its reply, and reports the focused pane's `cwd` as the session's folder when it changes and again each time herdr takes the foreground. Focus belongs to herdr's server, so clients of one server follow one focus; a client of another machine's server (`--remote`, `--machine`) is logged, not followed. A failure is logged once while it lasts. Windows has none of this: there herdr is a beta on named pipes.
|
||||||
+95
-25
@@ -28,37 +28,60 @@ type Invocation = (Vec<String>, HashMap<String, OsString>);
|
|||||||
/// Follows the pane herdr's server has focused while herdr's client runs in the foreground.
|
/// Follows the pane herdr's server has focused while herdr's client runs in the foreground.
|
||||||
pub fn follow(cx: &mut Context<TerminalView>) -> Task<()> {
|
pub fn follow(cx: &mut Context<TerminalView>) -> Task<()> {
|
||||||
cx.spawn(async move |view, cx| {
|
cx.spawn(async move |view, cx| {
|
||||||
let (mut group, mut socket, mut shown, mut failed) = (None, None, None, String::new());
|
let mut seen: Option<(i32, Option<PathBuf>)> = None;
|
||||||
|
let (mut socket, mut shown, mut retry, mut failed) = (None, None, false, String::new());
|
||||||
loop {
|
loop {
|
||||||
cx.background_executor().timer(CHECK).await;
|
cx.background_executor().timer(CHECK).await;
|
||||||
let Ok((pid, now)) = view.update(cx, |view, _| (view.pty.pid, view.pty.foreground()))
|
let Ok((pid, group)) = view.update(cx, |view, _| (view.pty.pid, view.pty.foreground()))
|
||||||
else {
|
else {
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
let now = match now {
|
let group = match group {
|
||||||
Ok(now) => Some(now),
|
Ok(group) => Some(group),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
if group.is_some() {
|
if seen.is_some() {
|
||||||
log::debug!("terminal {pid}: its foreground does not read: {error}");
|
log::debug!("terminal {pid}: its foreground does not read: {error}");
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if now != group {
|
// An exec keeps the group and swaps the program, so the two name the foreground.
|
||||||
group = now;
|
let program = match group {
|
||||||
|
Some(group) => {
|
||||||
|
let exe = cx
|
||||||
|
.background_executor()
|
||||||
|
.spawn(async move { path(group).ok() })
|
||||||
|
.await;
|
||||||
|
Some((group, exe))
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if program != seen {
|
||||||
|
if let Some((group, None)) = &program {
|
||||||
|
log::debug!(
|
||||||
|
"terminal {pid}: process {group}, another user's or gone, does not read"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
seen = program.clone();
|
||||||
shown = None;
|
shown = None;
|
||||||
|
retry = true;
|
||||||
failed.clear();
|
failed.clear();
|
||||||
if socket.take().is_some() {
|
if socket.take().is_some() {
|
||||||
log::info!("terminal {pid}: herdr left the foreground");
|
log::info!("terminal {pid}: herdr left the foreground");
|
||||||
}
|
}
|
||||||
let found = match now {
|
}
|
||||||
Some(now) => {
|
if retry {
|
||||||
|
let found = match program {
|
||||||
|
Some((group, Some(exe)))
|
||||||
|
if exe.file_name().is_some_and(|name| name == "herdr") =>
|
||||||
|
{
|
||||||
cx.background_executor()
|
cx.background_executor()
|
||||||
.spawn(async move { client(now) })
|
.spawn(async move { client(group) })
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
_ => Ok(None),
|
||||||
};
|
};
|
||||||
|
retry = found.is_err();
|
||||||
socket = found.unwrap_or_else(|error| {
|
socket = found.unwrap_or_else(|error| {
|
||||||
failure(&mut failed, pid, &error);
|
failure(&mut failed, pid, &error);
|
||||||
None
|
None
|
||||||
@@ -130,22 +153,20 @@ fn starting(error: &anyhow::Error) -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The socket of the herdr server whose terminal client leads process group `group`, if one does.
|
/// The socket of the herdr server whose terminal client leads process group `group`, if it is one.
|
||||||
fn client(group: i32) -> anyhow::Result<Option<PathBuf>> {
|
fn client(group: i32) -> anyhow::Result<Option<PathBuf>> {
|
||||||
let path = match path(group) {
|
|
||||||
Ok(path) => path,
|
|
||||||
// Another user's program, such as sudo, or one already gone.
|
|
||||||
Err(error) => {
|
|
||||||
log::debug!("terminal: process {group} does not read: {error}");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if path.file_name().is_none_or(|name| name != "herdr") {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let (args, env) =
|
let (args, env) =
|
||||||
launch(group).with_context(|| format!("herdr: process {group} does not read"))?;
|
launch(group).with_context(|| format!("herdr: process {group} does not read"))?;
|
||||||
follows(&args, &env)
|
let Some(socket) = follows(&args, &env)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
if socket.is_absolute() {
|
||||||
|
return Ok(Some(socket));
|
||||||
|
}
|
||||||
|
// herdr reads a relative socket from its client's folder.
|
||||||
|
let folder =
|
||||||
|
cwd(group).with_context(|| format!("herdr: process {group}'s folder does not read"))?;
|
||||||
|
Ok(Some(folder.join(socket)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The socket a herdr process with `args` and `env` shows, if it is herdr's terminal client.
|
/// The socket a herdr process with `args` and `env` shows, if it is herdr's terminal client.
|
||||||
@@ -305,7 +326,17 @@ pub(super) fn focused(reply: &str) -> anyhow::Result<Option<PathBuf>> {
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn path(pid: i32) -> io::Result<PathBuf> {
|
fn path(pid: i32) -> io::Result<PathBuf> {
|
||||||
std::fs::read_link(format!("/proc/{pid}/exe"))
|
std::fs::read_link(format!("/proc/{pid}/exe")).map(installed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The path of an executable the kernel marks ` (deleted)`, as an update that replaced it leaves it.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(super) fn installed(path: PathBuf) -> PathBuf {
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
match path.as_os_str().as_bytes().strip_suffix(b" (deleted)") {
|
||||||
|
Some(kept) => PathBuf::from(OsStr::from_bytes(kept)),
|
||||||
|
None => path,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -317,6 +348,11 @@ fn launch(pid: i32) -> io::Result<Invocation> {
|
|||||||
Ok((args, env))
|
Ok((args, env))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn cwd(pid: i32) -> io::Result<PathBuf> {
|
||||||
|
std::fs::read_link(format!("/proc/{pid}/cwd"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Each NUL-ended string in `bytes`.
|
/// Each NUL-ended string in `bytes`.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn strings(bytes: &[u8]) -> impl Iterator<Item = &[u8]> {
|
fn strings(bytes: &[u8]) -> impl Iterator<Item = &[u8]> {
|
||||||
@@ -373,6 +409,40 @@ fn launch(pid: i32) -> io::Result<Invocation> {
|
|||||||
procargs(&buffer)
|
procargs(&buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn cwd(pid: i32) -> io::Result<PathBuf> {
|
||||||
|
// SAFETY: the struct is plain C data, valid zeroed.
|
||||||
|
let mut info: libc::proc_vnodepathinfo = unsafe { std::mem::zeroed() };
|
||||||
|
let size = std::mem::size_of::<libc::proc_vnodepathinfo>() as libc::c_int;
|
||||||
|
// SAFETY: `info` holds the size given.
|
||||||
|
let read = unsafe {
|
||||||
|
libc::proc_pidinfo(
|
||||||
|
pid,
|
||||||
|
libc::PROC_PIDVNODEPATHINFO,
|
||||||
|
0,
|
||||||
|
(&raw mut info).cast(),
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if read <= 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
if read != size {
|
||||||
|
return Err(io::Error::other(format!(
|
||||||
|
"proc_pidinfo read {read} of {size} bytes"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let path = info
|
||||||
|
.pvi_cdir
|
||||||
|
.vip_path
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|byte| *byte as u8);
|
||||||
|
Ok(PathBuf::from(OsString::from_vec(
|
||||||
|
path.take_while(|byte| *byte != 0).collect(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
/// `KERN_PROCARGS2`'s layout: argc, the program's path, NUL padding, the arguments, the variables.
|
/// `KERN_PROCARGS2`'s layout: argc, the program's path, NUL padding, the arguments, the variables.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub(super) fn procargs(buffer: &[u8]) -> io::Result<Invocation> {
|
pub(super) fn procargs(buffer: &[u8]) -> io::Result<Invocation> {
|
||||||
|
|||||||
+22
-1
@@ -38,6 +38,8 @@ fn a_client_finds_its_server_as_herdr_does() {
|
|||||||
let socket = env(&[("HOME", "/h"), ("HERDR_SOCKET_PATH", "/s.sock")]);
|
let socket = env(&[("HOME", "/h"), ("HERDR_SOCKET_PATH", "/s.sock")]);
|
||||||
assert_eq!(found("herdr", &socket), PathBuf::from("/s.sock"));
|
assert_eq!(found("herdr", &socket), PathBuf::from("/s.sock"));
|
||||||
assert_eq!(found("herdr --session work", &socket), work);
|
assert_eq!(found("herdr --session work", &socket), work);
|
||||||
|
let relative = env(&[("HOME", "/h"), ("HERDR_SOCKET_PATH", "herdr.sock")]);
|
||||||
|
assert_eq!(found("herdr", &relative), PathBuf::from("herdr.sock"));
|
||||||
let named = env(&[("HOME", "/h"), ("HERDR_SESSION", "work")]);
|
let named = env(&[("HOME", "/h"), ("HERDR_SESSION", "work")]);
|
||||||
assert_eq!(found("herdr", &named), work);
|
assert_eq!(found("herdr", &named), work);
|
||||||
assert!(follows(&args("herdr"), &env(&[])).is_err());
|
assert!(follows(&args("herdr"), &env(&[])).is_err());
|
||||||
@@ -120,7 +122,7 @@ fn herdr_answers_over_its_socket() {
|
|||||||
|
|
||||||
/// macOS hides the variables of its own programs, such as `/bin/sleep`, so the test reads itself.
|
/// macOS hides the variables of its own programs, such as `/bin/sleep`, so the test reads itself.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_process_reads_with_its_path_arguments_and_variables() {
|
fn a_process_reads_with_its_path_arguments_variables_and_folder() {
|
||||||
let pid = std::process::id() as i32;
|
let pid = std::process::id() as i32;
|
||||||
let read = path(pid).expect("its path");
|
let read = path(pid).expect("its path");
|
||||||
let (args, env) = launch(pid).expect("its arguments");
|
let (args, env) = launch(pid).expect("its arguments");
|
||||||
@@ -128,6 +130,25 @@ fn a_process_reads_with_its_path_arguments_and_variables() {
|
|||||||
assert_eq!(fs::canonicalize(read).ok(), fs::canonicalize(exe).ok());
|
assert_eq!(fs::canonicalize(read).ok(), fs::canonicalize(exe).ok());
|
||||||
assert_eq!(args, std::env::args().collect::<Vec<_>>());
|
assert_eq!(args, std::env::args().collect::<Vec<_>>());
|
||||||
assert_eq!(env.get("HOME"), std::env::var_os("HOME").as_ref());
|
assert_eq!(env.get("HOME"), std::env::var_os("HOME").as_ref());
|
||||||
|
let folder = std::env::current_dir().expect("this test's folder");
|
||||||
|
assert_eq!(
|
||||||
|
fs::canonicalize(cwd(pid).expect("its folder")).ok(),
|
||||||
|
fs::canonicalize(folder).ok()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn an_updated_herdr_still_names_itself() {
|
||||||
|
let deleted = PathBuf::from("/home/u/.local/bin/herdr (deleted)");
|
||||||
|
assert_eq!(
|
||||||
|
installed(deleted),
|
||||||
|
PathBuf::from("/home/u/.local/bin/herdr")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
installed("/usr/bin/herdr".into()),
|
||||||
|
PathBuf::from("/usr/bin/herdr")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil
|
|||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| PTY through alacritty's event loop; OSC 7 scanned on the reader | T04 | `term::pty`, `term::tap` tests; round 3 failed at the cap, its fix stands on a red-green test | Pass |
|
| PTY through alacritty's event loop; OSC 7 scanned on the reader | T04 | `term::pty`, `term::tap` tests; round 3 failed at the cap, its fix stands on a red-green test | Pass |
|
||||||
| Shell integration for zsh, bash, fish, PowerShell; a shell without it reports nothing and the panels say so | T05 | `term::shell` tests on macOS and Linux | Pass |
|
| Shell integration for zsh, bash, fish, PowerShell; a shell without it reports nothing and the panels say so | T05 | `term::shell` tests on macOS and Linux | Pass |
|
||||||
| Inside herdr, the folder of herdr's focused pane, read from its socket, on macOS and Linux | T29 | `term::herdr` tests: the socket found as herdr 0.9 finds it, only the terminal client followed and a remote one refused, the snapshot read, a socket round trip, a process's path, arguments and variables; `the_foreground_is_the_program_s_own_group`; on X11 with herdr 0.9.5 run in the session: its start showed the focused pane's folder, a cd in the pane moved the panel, the focus moved to a pane in `/etc` and back moved it each time, and the client's end gave the panel back to bash's report; on macOS under `shot`, herdr 0.9.5 as the program, its pane's cd moved the panel | Pass |
|
| Inside herdr, the folder of herdr's focused pane, read from its socket, on macOS and Linux | T29 | `term::herdr` tests: the socket found as herdr 0.9 finds it, only the terminal client followed and a remote one refused, the snapshot read, a socket round trip, a process's path, arguments and variables; `the_foreground_is_the_program_s_own_group`; on X11 with herdr 0.9.5 run in the session: its start showed the focused pane's folder, a cd in the pane moved the panel, the focus moved to a pane in `/etc` and back moved it each time, and the client's end gave the panel back to bash's report, then `exec herdr` from that bash, with `HERDR_SOCKET_PATH` relative to its folder, was followed through the socket there; on macOS under `shot`, herdr 0.9.5 as the program and again by `exec` from `sh` with a relative socket, its pane's cd moved the panel | Pass |
|
||||||
| Colors, styles, five underlines, wide characters, combining marks | T06 | `term::frame`, `term::ink`, `term::paint` tests | Pass |
|
| Colors, styles, five underlines, wide characters, combining marks | T06 | `term::frame`, `term::ink`, `term::paint` tests | Pass |
|
||||||
| Box drawing, blocks and powerline shapes as geometry | T07 | `term::boxes` tests | Pass |
|
| Box drawing, blocks and powerline shapes as geometry | T07 | `term::boxes` tests | Pass |
|
||||||
| JetBrains Mono, any installed font, Nerd Font icons | T06, T17 | `tests/fonts.rs` | Pass |
|
| JetBrains Mono, any installed font, Nerd Font icons | T06, T17 | `tests/fonts.rs` | Pass |
|
||||||
|
|||||||
Reference in new issue
Block a user