diff options
| author | user <user@clank> | 2026-08-14 22:06:27 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 22:06:27 +0200 |
| commit | 360ee83a9bae4d06db1ab7747ef984cd7292883d (patch) | |
| tree | df0c65da1753f288d875483538b3f6517628d289 | |
| parent | sl0ppty: walk the row, not screen_at() (diff) | |
| download | slosh-360ee83a9bae4d06db1ab7747ef984cd7292883d.tar.gz slosh-360ee83a9bae4d06db1ab7747ef984cd7292883d.tar.xz slosh-360ee83a9bae4d06db1ab7747ef984cd7292883d.zip | |
sl0ppty: expand ~ in a path that a config file wrote
`cwd="~/dev/api"` is in the README, in config/layout.example.kdl, and did
nothing. `~` is a shell thing, and no shell has touched a path that arrives
from a layout file -- so it went to chdir() literally, failed, and left the
pane in whatever directory the session happened to be started from. The pane
still ran, so nothing looked wrong until you typed `ls`.
path_expand() handles `~` and `~/...` against $HOME and returns everything
else untouched, including `~user`, which needs the password database and is
not worth linking it in for -- returned unchanged rather than half-expanded
into something wrong.
Applied where a layout's cwd is read, which is one place because a pane
inherits its tab's. Found while documenting a shader directory that was
about to have exactly the same bug.
| -rw-r--r-- | src/app.c | 7 | ||||
| -rw-r--r-- | src/path.c | 25 | ||||
| -rw-r--r-- | src/sl0ppty.h | 6 | ||||
| -rw-r--r-- | tests/test_layout_files.py | 29 |
4 files changed, 66 insertions, 1 deletions
@@ -1583,7 +1583,12 @@ uint32_t app_current_tab_id(app_t *a) { return a->ntabs ? cur(a)->id : 0; } */ static node_t *build_pane(app_t *a, const kdl_node_t *node, const char *cwd) { - const char *node_cwd = kdl_prop(node, "cwd", cwd); + /* Expanded once here, so both the pane spawned from this node and every + * child that inherits the value get the same real directory. The buffer + * outlives the recursion below it: children finish before we return. */ + char cwdbuf[1024]; + const char *node_cwd = + path_expand(kdl_prop(node, "cwd", cwd), cwdbuf, sizeof cwdbuf); /* a split: children, in order, in one direction */ size_t kids = 0; diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..adf824b --- /dev/null +++ b/src/path.c @@ -0,0 +1,25 @@ +/* Paths as people write them. + * + * `~` is a shell thing: by the time a path reaches us from a config or layout + * file no shell has touched it, so `cwd="~/dev/api"` was being handed to + * chdir() literally, failing, and leaving the pane in whatever directory the + * session happened to be in. Silently \u2014 which is the worst way for a path to + * be wrong, because the pane still starts and still works. + */ +#include "sl0ppty.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +const char *path_expand(const char *path, char *buf, size_t cap) { + if (!path) return NULL; + /* `~` and `~/...` only. `~user` needs the password database and is not + * worth linking it in for; it is returned unchanged, which is exactly what + * it did before, rather than being half-expanded into something wrong. */ + if (path[0] != '~' || (path[1] && path[1] != '/')) return path; + const char *home = getenv("HOME"); + if (!home || !*home) return path; + snprintf(buf, cap, "%s%s", home, path + 1); + return buf; +} diff --git a/src/sl0ppty.h b/src/sl0ppty.h index 2b7d905..96f8542 100644 --- a/src/sl0ppty.h +++ b/src/sl0ppty.h @@ -95,6 +95,12 @@ char *screen_dump(screen_t *s); /* The same screen as JSON: rows of text, style runs, cursor, hit-list. */ char *screen_dump_json(screen_t *s); +/* ---- paths -------------------------------------------------------------- */ + +/* Expand a leading `~`, using `buf` when it has to. Returns `path` itself when + * there is nothing to do, so `buf` only has to outlive the result's use. */ +const char *path_expand(const char *path, char *buf, size_t cap); + /* ---- pty --------------------------------------------------------------- */ typedef struct { diff --git a/tests/test_layout_files.py b/tests/test_layout_files.py index 4adfbef..f396b07 100644 --- a/tests/test_layout_files.py +++ b/tests/test_layout_files.py @@ -142,6 +142,34 @@ def test_cwd(): os.unlink(s._layout_path) +def test_cwd_expands_a_tilde(): + """`~` is a shell thing, and no shell has touched a path in a layout file. + + Unexpanded it reached chdir() literally, failed, and left the pane in + whatever directory the session was started from — with the pane running, + so nothing looked wrong until you typed `ls`.""" + home = tempfile.mkdtemp(prefix="sl0ppty-home-") + text = """ + layout { + tab name="t" { + pane cwd="~" command="pwd; cat" + pane cwd="~/sub" command="pwd; cat" + } + } + """ + os.mkdir(os.path.join(home, "sub")) + with session_with(text, cols=76, rows=14, env={"HOME": home}) as s: + s.settle() + panes = s.panes() + snap = s.snapshot() + check("~ is the home directory", + home in snap.pane_text(panes[0]), repr(snap.pane_text(panes[0]))) + check("and ~/sub is under it", + os.path.join(home, "sub") in snap.pane_text(panes[1]), + repr(snap.pane_text(panes[1]))) + os.unlink(s._layout_path) + + def test_apply_over_the_api(): """How `sl0ppi up` will add a project tab to a running session.""" with Session(SH, cols=70, rows=14) as s: @@ -206,6 +234,7 @@ if __name__ == "__main__": test_suspended() test_declared_purposes_are_locked() test_cwd() + test_cwd_expands_a_tilde() test_apply_over_the_api() test_bad_layouts() sys.exit(report()) |
