aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_shader_repl.py (follow)
Commit message (Collapse)AuthorAge
* slosh: rename the project from sl0pptyuser2026-08-18
| | | | | | | | | | | | | | | | | | | | | 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-<uid>, $XDG_RUNTIME_DIR/slosh), config path (~/.config/slosh), header guards, src/sl0ppty.h -> src/slosh.h, contrib/sl0ppty-dev -> 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.
* sl0ppty: ruff format the Pythonuser2026-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* sl0ppty: the prompt speaks config, not a dialect of ituser2026-08-16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | chrome> include "contrib/chrome/heartbeat.kdl" heartbeat.kdl: 1 chrome, 0 content chrome> shaders { ...> tint where="chrome" color="#ff5fd7" amount="abs(sin(t / 5))" ...> dim where="content" amount=80 ...> } 1 chrome, 1 content Both of those were errors an hour ago, and the second one was my fault twice over: `:paste` printed `include "..."` and printing something you cannot type back is a prompt lying about its own output. Pasting it answered `unexpected argument for include`, and `load "..."` answered the same about `load`. So the grammar is now the config's, all of it: an entry, several with `;`, an entry with its own `where=`, a whole `shaders { }` block over as many lines as it takes, `include "f.kdl"`, `load f`. There is a test that reads `:paste`'s block off the screen, clears the pane, types it back line by line and compares the cells -- round-tripping is the property, not a nice-to-have. **The text is a document.** What it says replaces both chains, exactly as naming `shaders { }` in a config replaces the block rather than adding to it, and an entry's `where=` decides which rect it lands on. That drops a rule I invented last week -- "one chain, one rect", which refused an entry that named the other one -- because it made the prompt's mode a law rather than a default and made a two-rect block, the thing `:paste` prints, impossible to paste back. The prompt now keeps the document and sends all of it, so a line aimed at the frame leaves the contents alone, and the reply says `1 chrome, 1 content` rather than `ok`: with routing visible, the counts are the answer worth reading. `route_entries` is the one place routing is decided now -- config file, preset file, typed line, same function. `config_parse_chain_doc` takes the wrapper or the bare entries, because both are things a config contains and therefore both are things somebody will paste. Still nothing in the prompt parses KDL. A file goes by path; a pasted block goes via a temporary file, since an OSC payload cannot carry a newline -- a control byte ends the sequence, by the spec -- and joining lines with `;` would need exactly the comment-and-quote scanner I refused to write. The only syntax the script looks at is counting braces to know whether you have finished typing, and an empty line gives up on that. One hint, for the mistake that started this: when the session says `unknown shader`, the prompt adds `(a file? :load <path>, or include "<path>")`. After the fact rather than before, so a plugin's shader name is never second-guessed. Verified: `tests/test_shader_repl.py` is 41 checks -- every input shape, the multi-line paste and giving up on one, the round trip, the hint -- and `tests/test_inband_shaders.py` gained the document rule in both directions, including that a document naming only the frame takes the contents pass off. Full clean suite green.
* sl0ppty: `:load` a preset, without a second KDL readeruser2026-08-16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | chrome> :load sine-comet sine-comet.kdl: 1 chrome, 0 content chrome> :paste // sine-comet.kdl include "/home/user/dev/sl0ppty/contrib/chrome/sine-comet.kdl" A path, or the bare name of anything in `chrome/` or `shaders/` beside the script, from any directory. Entries land wherever their own `where=` says, so one file can fill both chains and a preset goes where its author put it. Tab completes filenames after `:load` and vocabulary everywhere else. **The prompt does not parse the file.** I started writing the scanner -- find the `shaders {` block, count braces, strip `//` outside quoted strings so that `color="#ff0033"` and `t / 8` survive -- and stopped, because that is a second reader of a format that already has one, and the two would disagree exactly where comments meet strings. Instead the session gets the path and reads it with the parser it already has: `config_parse_chain_file`, built on the same `parse_shader_entry` a config file goes through. Anything that is not a top-level `shaders { }` block is ignored, so a whole `config.kdl` can be handed over and only its shaders apply. The alternative was regenerating all 45 presets into something easier to read from Python. That would have cost them what makes them useful: each one is a config fragment today -- pasteable, `include`-able, checkable with `sl0ppty --check`, and loaded as a config by `tests/test_chrome_presets.py`. A format only the prompt can read would be a fourth thing to keep in step. `:paste` after a load answers with an `include` line rather than the file's thirty: the file is on disk, it is already the block, and reprinting it scrolled the pane you were looking at -- which is how I noticed. Both chains are built beside the live ones and swapped in together, so a file that goes wrong half way leaves the pane as it was rather than wearing the half that parsed. Each compiled expression is matched to the chain whose shader points at it before either is installed; freeing a program the other chain still uses is the one bug here that would not appear until the next repaint. Verified: nine checks in `tests/test_shader_repl.py` -- a preset by bare name, a file with both rects going to both chains, the three ways to be wrong (no such file, no `shaders { }` block, no argument), what `:paste` says afterwards, and tab completion. Then all 45 shipped presets loaded by name in one session: none refused.
* sl0ppty: PI is 180user2026-08-16
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Angles here are degrees, and pi in an angle language is not a number -- it is a half turn. So `PI` is 180, `TAU` is 360, and a formula written the radian way ports across as written: sin(TAU * x / cols) one cycle over the pane's width sin(TAU * p * 3 / perim) three lobes fitted exactly to a border PI / 2 a quarter turn PI / 6 thirty degrees, exactly That last line is the argument. A numeric pi would be 3 in this language, and `3.14159 / 6` in integers is 0 -- so radians cannot express a sixth of a turn here at all, while degrees give it as a whole number. The pun is not a compromise; it is the more precise reading of the two. Which is why there is no `deg2rad` or `rad2deg` beside them, and asking for one says `unknown function`. There is one angle unit. A conversion could only lose the angle (`deg2rad(90)` is 1, and `sin(1)` is nearly nothing) or invent a second scale for the language to disagree with itself about. Spelled-out numbers rather than variables: they read no environment, carry no dependency, and fold into the program like the literals they are. The browser preview learned the same two, from the same table. Verified: 12 cases in `tests/expr_test.c`, including that `deg2rad` and `rad2deg` are refused; the cross-check agrees with the compiler on 700 evaluations, now including `sin(TAU * x / cols)` and friends. Two smaller things ride along, both from the repl. `PI`/`TAU` tab-complete, and its drift test grew a third table to check -- it already caught `sin`/`cos` missing before I did. And it now checks the script *parses* before starting a pane that runs it: I broke the indentation of one line here, and the failure arrived as `IndexError: list index out of range` from a pane that died before its prompt, which is a long way from "line 212".
* sl0ppty: a prompt with a memoryuser2026-08-16
`contrib/shader-repl` imports readline, which is most of it: editing, up/down, ctrl-r, and history kept between runs in `$XDG_DATA_HOME/sl0ppty/shader-repl.history`. Chains only -- a `:` command is taken back out of the history it would otherwise fill, because pressing up four times past `:quit` and `:paste` to reach the line you were working on is the annoyance history exists to remove. Tab completes the commands, the nine shader names, the property keys, the two rects, the three channels, and the expression language's own variables and functions. `:help` prints the lot at once, which is the same list from the same place. That list is a claim about what exists, so `tests/test_shader_repl.py` checks it against `src/shader.c` and `src/expr.c` and fails if either grows a name it does not have -- verified by removing `spotlight` and watching it fail. Then it types every name it offers at a real prompt and requires `ok` from a real session, because a name that completes and does not parse is a lie with a keystroke attached. History across two runs, a tab completion, and `:help` are driven the same way, through a pty. Two things that cost me time and are now written down where they bite: keys go into a pane escaped (`\r`, `\t`, `\e[A`) because `raw` takes the rest of one command line and a literal newline ends the command instead of arriving; and Enter is CR, which is what a terminal sends and what readline waits for. Also: **a bare argument on a shader entry is now a complaint.** Every parameter is a property, so `tint color="#000000" spotlight` is not a pass with a spare word, it is a missing `;` -- KDL reads one node, the second name does nothing, and the session used to answer `ok`. It now says `unexpected argument for tint: spotlight (separate entries with ';')`. Found by watching the prompt get used.