<feed xmlns='http://www.w3.org/2005/Atom'>
<title>slosh.git/tests/test_dump_config.py, branch docs-new</title>
<subtitle>an opinionated terminal multiplexer in C with programmable pane chrome</subtitle>
<id>https://git.sl0p.foo/slosh.git/atom/tests/test_dump_config.py?h=docs-new</id>
<link rel='self' href='https://git.sl0p.foo/slosh.git/atom/tests/test_dump_config.py?h=docs-new'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/'/>
<updated>2026-08-18T19:26:00Z</updated>
<entry>
<title>slosh: rename the project from sl0ppty</title>
<updated>2026-08-18T19:26:00Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-18T19:26:00Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=b9e53cace4eb75263077df8623b011e66f5f2241'/>
<id>urn:sha1:b9e53cace4eb75263077df8623b011e66f5f2241</id>
<content type='text'>
sl0p + pty was error-prone to type and the zero made it worse; slosh
(slop + sh) is one syllable, impossible to typo, and slosh.foo is ours.

Mechanical rename everywhere it appears: binary, env vars (SLOSH,
SLOSH_SESSION, SLOSH_BIN, SLOSH_CONFIG), socket dir (/tmp/slosh-&lt;uid&gt;,
$XDG_RUNTIME_DIR/slosh), config path (~/.config/slosh), header guards,
src/sl0ppty.h -&gt; src/slosh.h, contrib/sl0ppty-dev -&gt; contrib/slosh-dev,
the driving-slosh skill, docs and tests. The sl0ppi fork references and
the sl0p theme stay: one is history, the other is the brand.

The rename surfaced a latent bug in test_osc5577: the hello-reply
answer always appeared on screen twice (ECHOCTL echo plus cat -v), but
a seven-character implementation name happened to wrap the second copy
across the 60-column boundary, so counting substrings in wrapped text
found one and passed for the wrong reason. A five-character name moved
the wrap and exposed it. The loop guard itself is correct -- with echo
off before the request there is exactly one reply -- so the test now
counts on de-wrapped text and tolerates the pane's own echo.
</content>
</entry>
<entry>
<title>sl0ppty: ruff format the Python</title>
<updated>2026-08-17T12:36:59Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-17T12:36:59Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=c0022776d0912042819e4ea330cf7491a8196b45'/>
<id>urn:sha1:c0022776d0912042819e4ea330cf7491a8196b45</id>
<content type='text'>
The bumpy one, again. Every `.py` outside `vendor/` plus the three `contrib/`
scripts that are Python with no extension: 60 files, 6,714 lines rewritten, no
behaviour changed. Nothing here is worth reading -- the next commit adds it to
`.git-blame-ignore-revs` so `git blame` walks past it.

It is a bigger reformat than the C was, 60% of the lines against 17%, and for a
reason worth writing down rather than tuning away: ruff and black break a call that
does not fit onto one argument per line, where this code aligned the continuation
under the opening paren. The test suite is thousands of `check("some prose", cond,
detail)` calls, so nearly every one of them changed shape and the suite grew 3,308
lines. Line length was the one lever that moved it -- 120 columns would have been a
40% reformat and no growth at all -- and it was left at 88, because picking an
unusual width to dodge a one-time diff is paying forever to avoid paying once.

Import sorting came with it. Two files had `import os, pty, uuid, select, struct,
sys, termios, time, fcntl, signal` on one line and now have ten.

Verified after, not before, because a formatter that alters behaviour would be
worse than none:

- `make retest`, 1801 checks green -- and this reformats the suite that does the
  checking, including `harness.py`, which every one of those files imports.
- `make test-live`, the tty tests the fast suite skips. They matter here more than
  usual: `live_input.py` and `live_m0.py` are two of the files whose comma-imports
  were split, and nothing in the fast suite would have noticed if that broke them.
- All 60 files still compile, `make fmt` twice in a row changes nothing the second
  time, and `ruff check --select I` has nothing left to say.
</content>
</entry>
<entry>
<title>sl0ppty: double-click a word to select it</title>
<updated>2026-08-17T11:53:57Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-17T11:53:57Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=2f9135d2810cfb38ccdd4f07e895c66430d066fe'/>
<id>urn:sha1:2f9135d2810cfb38ccdd4f07e895c66430d066fe</id>
<content type='text'>
`pane_select_word()` was written and never wired to anything: no declaration, no
caller, no way to reach it. This is the rest of it -- the gesture, the setting, and
the tests.

**The word is defined by what it stops at, not by what it is made of.** Anything
that is not blank and not a listed separator belongs to a word, so CJK, accented
letters and emoji need no table and come out whole; whitespace always separates,
listed or not, because a double-click that selected the line would be a different
gesture arrived at by surprise. Wide glyphs continue the run they are in rather than
each being their own word -- otherwise every CJK character is a word.

`word_separators` defaults to `"'`()[]{}&lt;&gt;;,|`, and what it leaves *out* is the
decision: `/ . - _ : = @ # % ? &amp; + ~ $` stay inside words, so a path, a dotted
name, a snake_case identifier, a key=value pair, a URL and `src/app.c:1234` are
each one thing you double-clicked to copy whole. `:` is the difference between
copying an error location and copying half of one, which is why kitty keeps it too.

The gesture reuses what selecting already has: the press starts a selection at the
cell -- which anchors a drag out of the double-click -- then widens it to the word,
and the release path that already copies a drag copies this. So the word *is* the
copy, in the one place that rule lives, and the highlight is the copied span rather
than an approximation of it. Tracked per cell as well as per pane, so clicking two
different words quickly selects the second rather than everything between them.
A program that asked for the mouse still keeps its clicks, with shift as the escape
hatch, exactly as the drag has it.

**One thing this needed on the way: quoted values were dumped unescaped.** The
default separator set opens with a quote, and `cb_add(&amp;b, "key \"%s\"\n", ...)`
wrote it raw -- so `--dump-config` produced a file that no longer parsed, breaking
the property the dump exists for. The escaping loop already existed inside
`cb_chord`, for the same reason and with the reasoning written down; it is now
`cb_qval`/`cb_qstr` and every string setting goes through it: the marks, `shell`,
`editor`, `shader_dir`, `project_roots`, `project_layout`. Latent for all of those,
load-bearing for this one.

**And `--dump-config` does not do what four comments and the docs said it did.** It
calls `config_dump_defaults()`: every setting with its *default*, never the value
your config gives it. The function name was the honest one. Corrected in
`docs/config.md`, `config/config.kdl`, `main.c`, `config.c`, `app.c` and the test's
own docstring, rather than changing what the flag does -- starting a config from it
is the documented use, and `--check` is what reads yours. There is no way to ask for
the effective config; that is a gap, not a lie, and it can be a flag when somebody
wants it.

Verified: 22 checks in `tests/test_clipboard.py`. An error location, an identifier
stopping at a bracket, a URL, a word inside quotes and a run of CJK; the highlight
being exactly as wide as the copy; a separator, a blank cell and a single click each
leaving the clipboard alone; two clicks on different cells not being a double; two
slow clicks not being one; the separator set honoured from a config, including a
quote and a backslash carried through KDL escaping; a word in a scrolled pane coming
from the row on screen; and a mouse-tracking program keeping its double-clicks until
shift takes them back. Three of those failed first because the test asked for the
answer twice -- a second `worded()` call is a second pair of clicks -- and one
because it mapped string indices to columns without counting wide glyphs as two.
</content>
</entry>
<entry>
<title>sl0ppty: every complaint, and the chords that produced eleven of them</title>
<updated>2026-08-15T22:25:27Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-15T22:24:41Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=25fb74894a826777d2f9dd10148b304bc8d02ceb'/>
<id>urn:sha1:25fb74894a826777d2f9dd10148b304bc8d02ceb</id>
<content type='text'>
Reported as "C-a is not a valid prefix in my config.kdl", which it was
not, and neither were ten other lines of the same file -- a file written
by `sl0ppty --dump-config`.

The loader now collects every complaint instead of keeping the first.
Each one carries the file and the line it happened at, so
"line 14: bad binding" became "config.kdl:116: bad key: ←", and a
complaint from an included file names that file. `err` still means the
first message, so a session's one status line behaves as before; the list
is what a linter needs, and `--check` (next commit) is that linter. Both
changes live in the same regions of config.c, which is why they are one
commit.

Then what the list found.

`--dump-config` is documented as "a file you could have written", and
`--dump-config &gt; ~/.config/sl0ppty/config.kdl` as a supported way to
start one. It wrote every chord in the *cheatsheet's* notation:

    prefix "C-a"
    bind "?" "help"
    bind "←" "focus-left"
    bind "S-tab" "prev-tab"

and the parser only spoke the config's, so eleven of the file's own lines
came back as complaints and the session ran on the defaults those lines
were describing. Two sessions looked identical for the worst possible
reason, which is why the test asserting exactly that never noticed.

Both halves were wrong, so both are fixed.

The parser accepts what the sheet prints: `C-` `M-` `S-` as well as
`ctrl+ alt+ shift+`, the arrow glyphs as well as `left`/`right`, and a
character that carries its own shift -- `?` is shift+slash, `H` is
shift+h, `|` is shift+backslash. config.kdl has claimed for months that
`?` and `S-slash` were both writable; only one of them was.

A punctuation binding registers *both* encodings, because whether a
terminal reports `?` as shift+slash or as a bare byte is up to the
terminal and not to what the config meant. The shipped default has bound
`?` twice by hand for exactly this reason since it was written; this is
that, done once, in the one place that knows the character implied the
shift. A capital letter needs no such thing -- the decoder reports `H` as
shift+h everywhere.

And the dumper writes the canonical config spelling: `ctrl+a`,
`shift+slash`, `left`, `shift+tab`. `shift+` spelled out rather than
folded back into `?` or `H`, because both are accepted on the way in and
one spelling on the way out keeps a dump of a dump the same dump. A key
with no spelling the parser would accept is written as an empty chord
rather than as its display form: writing the display form is what caused
this, and a dump that does not load is worse than a dump missing a line.

The test that should have caught it now does: a session started from the
dump is asked to reload and must have nothing to say. Looking at the
screen was never going to find this, because the screen was right.
</content>
</entry>
<entry>
<title>sl0ppty: render the config instead of shipping a copy of it</title>
<updated>2026-08-15T14:22:27Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-15T14:22:27Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/slosh.git/commit/?id=95b9b0414a2a8a3b1ec8e91d530c8b7119901257'/>
<id>urn:sha1:95b9b0414a2a8a3b1ec8e91d530c8b7119901257</id>
<content type='text'>
`C-a e` on a machine with no config opened an empty buffer, which tells you
nothing about what you can set. The obvious fix -- embed config/config.kdl and
write it out -- trades one problem for a worse one: a checked-in copy of the
defaults is a second source of truth, and it drifts. Ours already had. Four
theme colours existed in the code and not in that file, and nothing said so.

So the file is generated. `sl0ppty --dump-config` renders every setting with
the value it currently holds -- geometry, behaviour, all 48 theme colours, the
state shader chains, and every binding including the leader-less ones -- and
`C-a e` seeds a missing config with exactly that, creating the directories on
the way (a fresh container has no ~/.config either).

config/config.kdl stays, and is now explicitly the *prose*: what a setting is
for, what it cost to get right, which ones are opinions. Values are generated,
essays are written, and the file says so at the top.

Three things keep the two honest:

- the theme colours became one file-scope table that the parser and the
  renderer both walk. 48 names that used to exist in one place and be typed
  again in another now exist once.
- the dump must parse back to itself byte-for-byte. That is what caught
  `bind \ split-cols` rendering unquoted -- valid-looking output that would
  not have survived being read back.
- a test scans config.c for `kdl_child(root, "...")`, which is the parser's
  own vocabulary, and fails if a key is missing from the dump. Add a knob,
  forget to render it, and the suite tells you rather than a user.

It never overwrites a config that exists: fopen(..., "wx") loses the race
rather than an edit, and there is a test for that too, because it is the one
thing here that must never go wrong.
</content>
</entry>
</feed>
