| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Makes the fork installable and runnable beside a stock zellij: separate binary
name, config dir, cache dir, socket dir, log file and plugin URL scheme.
Everything user-visible is defined once in zellij-utils/src/branding.rs and
referenced from the ~15 places that used to hold a literal, rather than sed-ing
the tree -- upstream has 3300 commits of future rebases, and each touched
upstream line is a conflict forever. A conflict in branding.rs itself always
resolves the same way: keep our constant, take their code.
binary zellij -> sl0ppij
config dir ~/.config/zellij -> ~/.config/sl0ppij
system cfg /etc/zellij -> /etc/sl0ppij
cache/data ProjectDirs(Zellij) -> ProjectDirs(foo.sl0p.sl0ppij)
socket/tmp /tmp/zellij-$UID -> /tmp/sl0ppij-$UID
log zellij-log/zellij.log -> sl0ppij-log/sl0ppij.log
plugin urls zellij:tab-bar -> sl0ppij:tab-bar (zellij: still parses)
Two things deliberately NOT renamed:
- The cargo package stays `zellij`; only [[bin]] is renamed. Renaming the
package would ripple through every intra-workspace dep, the plugin API and
the 0.45.0+sl0p1 version trick for a name nobody sees.
- The $ZELLIJ* environment variables stay, because the ecosystem (shell
prompts, "am I in a multiplexer" checks, our own pi-ext) looks for them. We
now export $SL0PPIJ* alongside them, and those take precedence on read.
branding::import_compat_env() folds an inherited ZELLIJ_CONFIG_DIR into its
SL0PPIJ_ spelling at startup, before clap reads the environment -- clap can
only be told one variable name per argument.
Snapshots: 14 updated, all of them the plugin URL scheme or a session message.
The insta churn (assertion_line metadata, trailing newlines) that a bulk
--force-update introduces across 340+ other snapshots was reverted, so the diff
here is only what actually changed.
Tests: zellij-utils 471 passed, zellij-server 1499 passed. branding.rs carries
its own tests, including one asserting no shipped default path contains the
string "zellij".
Verified live: sl0ppij creates /tmp/sl0ppij-1000 next to an existing
/tmp/zellij-1000, and `sl0ppij list-sessions` reports none while a stock zellij
reports three running sessions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Implements the zellij half of sl0p/workspace-hints-proposal.md: a pane can carry
an opaque agent-facing hint for what it is FOR, so a project can ship a layout
that an agent is able to navigate instead of guessing.
zellij never interprets a purpose. The consuming agent owns the entire vocabulary,
which is what keeps this to one string threaded along the existing status_bar path
rather than a policy engine, and lets the vocabulary change without a rebuild.
Two ways in, because declared panes and agent-created panes are different:
layout: pane name="server" purpose="dev-server"
in-band: printf '\033]5577;1;purpose;agent:build\033\\'
The second exists because tagging at spawn time would mean threading a field
through 28 SpawnTerminal call sites, and a new action variant is impossible while
protobuf enums cannot be regenerated. Wrapping the command costs one printf.
A declared purpose outranks an in-band one and cannot be overridden or cleared by
it. OSC arrives from whatever the pane happens to be showing, so otherwise a
`cat hostile.txt` in the human's editor pane could relabel it as the agent's
scratch pane and invite an agent to run commands where the human is working. A
layout is written by a human in a file they can read; in-band data only fills a
blank.
Exposed on PaneListEntry (`list-panes --json`) rather than PaneInfo, which crosses
the plugin protobuf API we cannot regenerate; resolved in the screen thread where
panes live rather than via route.rs's per-pane PTY round-trips. Values are
sanitised to a bounded [A-Za-z0-9_.:/-] token at both entry points, because a
purpose is never rendered -- it is matched against and pasted into consumers'
shell and JSON pipelines.
Verified end to end rather than by inspection: purposes from a layout appear in
list-panes --json against the right pane ids, an agent-spawned pane tags itself,
a declared purpose survives an in-band override attempt, all of it comes back
after kill + resurrect, an unknown pane property is still a parse error, and a
layout with no purpose leaves dump-layout unchanged.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`status_bar_button_style "indexed"` renders `1:Approve 2:Reject` instead of
`[Approve] [Reject]`, so the bar doubles as a key legend for a harness that binds
alt+1..9 to the same actions.
A config option rather than a protocol field: whether the numbers mean anything
depends on what the USER has bound, not on what the program knows. It needs no
wire change and so no version bump, and the click report still carries the id --
never the index -- so a program is unaffected by which style is in force.
The style is threaded into layout_status_bar, the one function the renderer draws
from and the hit-test resolves against. That is what makes this safe: changing a
button's rendered width necessarily changes its clickable span too, verified by a
test asserting span width equals the drawn width and that every drawn column
resolves back, for both styles.
Also fixes the web client's Style construction to read the option from config
rather than defaulting it -- my first pass claimed that code had no pane_frames
config available, which was wrong; it uses it two lines further down.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Second slice of the status/action frame. A program declares buttons, and a click
is reported back to it:
printf '\033]5577;1;buttons;approve:Approve;reject:Reject\033\\'
-> click -> program's stdin receives OSC 5577 ; 1 ; click ; approve ST
The report is a FIXED format carrying only the id, never bytes the payload chose,
and ids are restricted to [A-Za-z0-9_-]. The sequence that creates a button is
in-band and untrusted -- `cat` of a hostile file emits it as readily as a
cooperating program -- so a click that could send arbitrary bytes would let
viewing a file inject into the stdin of whatever is running, a shell included.
Labels are '%'-escaped since the wire format is ';'-separated, and both the
button count and id length are capped because the list lives as long as the pane.
Positions come from one layout_status_bar function that the renderer draws from
and the hit-test resolves against. Computing that geometry twice is how the
stacked-pane picker once selected the wrong pane; a test asserts every drawn
column resolves back to the button drawn there, across 100 widths.
Two things the drive caught that unit tests could not:
- The first delivery path pushed onto grid.pending_messages_to_pty, which is
only drained when the PANE produces output -- so a click on an idle program sat
in the queue forever. The write now happens at the tab level, which owns the
senders.
- The hit-test derived the frame row from the pane's raw geometry, but this fork
draws the frame inset by gap_offset, so with `gap 1` every click missed by one
row. It now derives from the CONTENT rect, which is by definition the inside of
the drawn frame and therefore the same columns the renderer lays out.
Buttons serialise with the session like the status text, so a resurrected pane
comes back with its buttons intact.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The status bar survived detach/reattach (it lives on Grid) but not a
serialise/restore cycle, so a resurrected pane came back blank.
It now travels the whole per-pane serialisation pipeline, mirroring the fork's
existing default_fg/default_bg: pane -> PaneLayoutMetadata ->
PaneLayoutManifest -> TiledPaneLayout -> a status_bar property in the serialised
KDL, then back through the normal layout parser and applied by LayoutApplier
when the pane is recreated.
The sanitiser moved from grid.rs into zellij-utils so both entry points share
one definition. That matters: a layout file on disk is no more trustworthy than
the escape sequence that wrote it -- it may have been hand-edited, or copied from
somewhere else -- so restored text is stripped and length-capped on the way in
just like the OSC payload was.
Verified end to end rather than by unit test alone: set the bar, waited for a
serialisation pass, confirmed status_bar="..." in session-layout.kdl, killed the
session until list-sessions reported EXITED, then attached and watched it come
back from disk. The earlier attempt only proved detach/reattach because Quit had
left the session alive.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Collapsing and expanding a tab from the default layout stranded the status bar
halfway down the screen, and left the collapsed stack three rows tall.
A synthesized layout describes only where the user's panes go. Applied bare, it
laid out EVERY pane in the tab -- including the status bar, which became an
ordinary grid cell. Real swap layouts do not hit this because their `tab` node
is expanded with the default tab template, which carries the pinned plugin
slots; a layout built at runtime has to take those from BASE instead.
Synthesis now builds into the tab's own structure: the first non-plugin leaf
becomes the stack or grid and inherits its size, further content leaves are
dropped since the replacement accounts for every pane itself, and plugin panes
keep their slots and sizes.
Falls back to the bare replacement when there is no content slot at all -- a
layout of nothing but plugin panes would otherwise be returned unchanged,
silently dropping every user pane. Found by the test, not by the drive.
Only reproducible with a layout that has plugin panes, which is why the
quaddie.kdl drives never showed it and the default layout did immediately.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A tab restores into whatever layout was active before it collapsed, which for a
bare layout is the implicit BASE. BASE is pinned to ExactPanes of the count it
was written with, so adding one pane while collapsed left nothing to expand
into and the tab stayed stacked on every subsequent widening.
Two fixes. When the stored layout no longer applies, synthesize a grid to
spread the panes out rather than leaving them stacked -- a wide terminal is not
a state that retries, so failing here strands the tab for good.
And only consume the restore position once a layout has actually been applied.
It was cleared unconditionally, so after a failed restore the tab believed it
had expanded while visibly still collapsed, and never tried again -- which is
why this looked like "expand stopped working" rather than "expand did nothing
once".
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The collapse selects a swap layout by name, and a custom layout file only gets
swap layouts from a hand-written sibling "<layout>.swap.kdl". So for most
layouts there was nothing to select and the feature silently did nothing --
requiring a boilerplate file per layout to use it at all.
Now a missing layout is synthesized: every pane flattened into one stack, in
layout order. Appended rather than inserted, so it cannot shift the BASE layout
at position 0, which is what the collapse restores back to.
Synthesized lazily, at the first collapse, rather than up front: a stack sitting
in the swap cycle would let auto_layout apply it spontaneously when a pane is
added, stacking panes on a perfectly wide terminal.
Also scopes the damaged-layout guard. With no swap layouts, relayout finds
nothing applicable on the first pane added, gives up and marks the tab damaged
-- permanently, and with nothing to do with user intent. Honouring that flag
disabled the collapse for exactly the bare layouts this fallback targets, so it
is now only consulted when the tab has swap layouts the user actually defined.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A `children` placeholder inserts every pane it receives as a flat sibling.
That single hardcoded policy is why a layout can only express "N equal
columns", "N equal rows" or "one stack" without hand-writing a variant per
pane count -- the max_panes=5 / max_panes=8 / max_panes=12 ladders in a swap
layout file exist purely to work around it.
`pane arrangement="grid" { children; }` builds a tree for N panes instead.
Two to start: grid (ceil(sqrt(n)) columns, rows filled left to right) and
master-stack (one 60%-wide master, the rest beside it).
Generates a TiledPaneLayout rather than geometry, so everything downstream
works unchanged: split_space, the minimum-size feasibility check,
LayoutApplier, swap-layout damage tracking, and the responsive collapse. It
composes with that last one for free -- a grid is simply infeasible on a
narrow terminal, so it is skipped and the collapse takes over.
The arrangement builds ONE self-contained subtree rather than splicing a
variable number of nodes in among the placeholder's siblings, so it stays well
defined next to the pre-existing "children node with sibling nodes" TODO
rather than leaning on it.
Like default_fg/default_bg, the field has no protobuf representation, so it is
defaulted at both plugin-API boundaries and does not survive a round trip.
This is why it is a field rather than a LayoutConstraint variant: a new enum
variant would have no representation when converting TO protobuf, and the
committed prost assets cannot be regenerated here (no protoc).
The 48 snapshot updates are the new field appearing in TiledPaneLayout debug
dumps (324 lines, all `children_arrangement: None`). No snapshot content was
removed. The `expression:` metadata churn in those files is pre-existing drift
that regeneration swept up: the stored snapshots recorded the expression
before normalize_layout_debug was introduced.
|
|
|
Squashed import of upstream zellij at 0e6e4404 ("Support the Kitty Image
Protocol (#5428)"), the merge-base our fork was developed against. Upstream's
3311 commits are deliberately NOT replayed here; only our own fork commits are,
so the monorepo stays small and `git log sl0ppij/` shows sl0p work, not zellij's
entire history.
Upstream provenance is tracked in FORKS.md and can always be re-fetched from
https://github.com/zellij-org/zellij.
|