| Commit message (Collapse) | Author | Age |
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The option was only read when a client connected, so changing it needed a new
session -- inconsistent with gap/padding/title_alignment, which apply live, and
the kind of inconsistency that reads as "my config change did nothing".
Threaded through update_frame_layout, the existing vehicle for the fork's
pane_frames options: Tab -> tiled/floating containers -> each pane's Style,
including suppressed panes so a hidden stack member is styled correctly when it
swaps back into view.
Verified by editing config.kdl against a running session and watching
1:Approve flip to [Approve] and back, then confirming a click still resolves
afterwards -- the button widths change with the style, so a stale layout would
have silently mis-targeted clicks.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Reference for programs wanting to drive the bar: the OSC 5577 verbs, escaping
rules, click reports, limits, rendering behaviour, persistence, and the security
model.
Both worked examples were run against a real session rather than written from
memory, which caught four bugs in my own samples:
- the Python reader kept a trailing buffer window, so a processed report was
re-matched and every click fired twice
- it used tty.setraw, which also disables signals, leaving the sample unkillable
with Ctrl-C
- it exited via traceback and left the bar behind; both samples now clear it on
exit
- the shell reader echoed the report to the screen, and an `stty -echo` did not
help because bash's `read -n` saves and restores terminal settings around each
call. `read -s` is the fix.
Also verified and documented two behaviours I would otherwise have guessed at: a
BEL terminator works as well as ST, and the bar is not drawn when pane frames are
off (it lives in the frame row), reappearing when they return.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
First slice of the status/action frame: one row of program-supplied text in a
pane's bottom frame row.
printf '\033]5577;1;status;building 3/7\033\\'
printf '\033]5577;1;clear\033\\'
Rendered in the EXISTING frame row rather than in reserved rows. Reserving would
shrink the pane's content area and resize its PTY whenever the bar appeared or
disappeared, firing SIGWINCH at the program that asked for it -- the same churn
that made the stack-list picker use a fixed cap. The frame row is already a text
surface with a precedence chain, so this cost no geometry work at all.
State lives on Grid like the window title, so it survives re-renders, resizes
and detach/reattach with no draining. Session resurrection is NOT covered yet.
Versioned from the start, and unknown versions or verbs are ignored, so the
planned button verbs cannot be half-interpreted by an older build. Text is the
remaining params rejoined with ';', which means a single text field needs no
escaping -- the trick OSC 0/2 already uses for titles.
Treated as untrusted, because it is: an in-band sequence is emitted just as
readily by `cat` of a hostile file as by a cooperating program. Control
characters are stripped -- a raw ESC could otherwise repaint anything on screen,
a newline would corrupt the frame row -- and the length is capped, since the
string is retained for the life of the pane.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Upstream's mouse_hover_effects governs two unrelated things: the frame highlight
on hover, and the "Ctrl <MouseScroll> or <drag borders> to resize" hint. Its own
comment in default.kdl admits as much. So silencing a beginner reminder you have
outgrown also costs you a highlight that stays useful indefinitely.
mouse_hover_help_text (default true) gates only the hint. One line in
execute_send_to_terminal; the highlight is set in execute_update_hover and is
untouched.
Config-file only, deliberately. The option has no protobuf slot, so it does not
cross the client/server IPC contract -- the roundtrip test asserts exactly that,
with the fixture set to None and a comment explaining why. It still works from
the config file because the server parses the file itself and Options::merge
keeps the file value when the client sends None; a CLI flag would be silently
ignored.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Upstream's action focuses an already-running plugin, so a chord bound to it can
only ever open one -- there is no way to dismiss with the same key.
This cannot be fixed in a plugin. Keybindings are resolved into actions in
route.rs before a key reaches a pane, so the launching chord never arrives at
the plugin. intercept_key_presses does not help either: the intercept lives in
screen.rs and only sees keys that already became Action::Write, i.e. keys
destined for a pane. Confirmed empirically -- an intercepting build received
Ctrl-u but never Ctrl-;.
Closes only when the plugin is the focused pane in the client's ACTIVE tab, so
the chord cannot close whatever pane the user happens to be on. A plugin that
exists but is hidden behind a floating-layer toggle, or is focused in a
different tab, is revealed or brought over as before.
Defaults to false, so every existing binding is unaffected. Config-only, with no
protobuf representation: defaulted at both boundaries and dropped when an action
crosses the plugin API, so no prost regeneration is needed.
The 24 snapshot lines are the new field appearing in serialised keybind dumps.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The pane finder was living in ~/dev with no version control at all, while
path-depending on this checkout's zellij-tile -- so the plugin and the binary
it must stay API-compatible with were maintained in two unrelated places, one
of them unversioned.
Its own workspace root rather than a member of zellij's: a non-root member has
its [profile] ignored, and opt-level="z" + lto + strip are what keep the wasm
small, which matters because the keybinding loads it with skip_plugin_cache and
recompiles on every launch. Staying independent also means carrying this crate
touches no upstream-tracked file, so it cannot conflict on a rebase.
install.sh no longer assumes ./target: this checkout's .cargo/config.toml sets
a shared build.target-dir, so the artifact lands in the repo's target dir
instead. It now asks cargo metadata where the build actually went, and fails
loudly rather than silently installing a stale wasm.
The zellij-tile path dep becomes relative, so the crate builds from any
checkout rather than only from /home/user/lol/zellij.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`hide_default_pane_names` is a frame-rendering option, but it was applied
inside current_title() -- which is also what fills PaneInfo.title. Every plugin
that lists panes therefore received an empty string for any pane whose program
had never set a title.
For the bundled session manager that is untidy. For a fuzzy pane finder it is
broken: the rows are identical AND unsearchable, so the plugin's whole purpose
stops working.
The blanking moves to the frame, which already had a blank_title concept, and
current_title() goes back to reporting a real title to everything that asks.
Frames still show nothing for an unnamed pane, named panes still show their
name, and PaneInfo reports "Pane #N" again.
Changes a value rather than adding a field, so it needs no protobuf
regeneration.
Only observable without a shell that sets a title: verified by disabling the
zsh hook, where the pane finder previously listed three identical blank rows
and now lists Pane #1/#2/#3. That masking is also why this went unnoticed --
and why I flagged it twice without confirming it was actually reachable.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The boxed entries read as clutter: every entry bracketed in vertical bars, and
the active one marked by a "> " that sits outside its own cell.
`stack_list_style "minimal"` drops both. The active entry is shown by
inverting it instead, which needs no glyph and no column, and the padding is
moved inside the styled run so the inverted block covers the whole cell rather
than leaving unstyled gaps at its edges. Four fewer columns of decoration per
entry, so more fit per row in grid mode.
Uses ribbon_selected rather than text_selected. Themes give ribbon_selected
real contrast because it marks the selected item elsewhere in zellij's UI --
gruber yellow here, white-on-blue in the TempleOS theme -- whereas
text_selected's background is 40,40,40 against a 24,24,24 terminal and read as
essentially no highlight when tried.
Decoration width comes from the style itself, so Tab's column arithmetic and
the renderer cannot disagree about how wide an entry is. Default stays boxed,
so nothing changes for anyone who does not ask.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every bug this feature has had came from the same place: it remembered the swap
layout position that was active before collapsing, and replayed it to expand.
Remembered positions stop applying when the pane count changes, are only stable
while nothing is inserted, and a failed apply left the state claiming a
transition that never happened.
Both directions now resolve fresh. Collapsing picks the configured layout, or a
synthesized stack if that one is missing OR cannot currently apply -- a
`stacked` layout with min_panes=4 and three panes open used to be found, fail
its constraint, and give up. Expanding picks BASE when it still applies, since
that reproduces the user's actual layout, and otherwise a synthesized grid.
Both chains end in an unconstrained layout, so neither can come up empty.
The only remaining state is one bool, flipped only after a layout was applied.
Also stops consulting is_tiled_damaged. It is meant to mean "the user
rearranged panes by hand" but is equally set when a relayout finds nothing
applicable, which happens on the first pane added to a tab with no usable swap
layout and then never clears -- it disabled this feature in four distinct
situations. It protected little regardless: expanding applies BASE or a grid,
so a manual arrangement does not survive a round trip either way.
Synthesized layouts are created once and reused; the test asserts the layout
list does not grow across repeated collapse/expand cycles, since they are now
appended lazily in both directions.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`stack_list_layout "grid"` packs picker entries several per row, which turns
stack_list_max_rows from a count into a space budget: eight panes in a wide
terminal now fit on one row instead of spending three and hiding five.
The entry renderer already drew each entry as a single chunk at its own x/y,
sized by its geom and centring its own label, so a grid needed no rendering
changes at all -- only the loop assigning every entry the same x and the full
width. Column count is derived from the widest label rather than configured, so
it adapts to the titles; one column is the floor and reproduces the previous
full-width rows byte for byte.
The row -> member mapping now has a column axis, and it had already caused one
wrong-pane bug when two consumers disagreed. So the layout is computed once,
by one function, into explicit cells; the renderer, the reserved-row budget and
the mouse hit-test all read those same cells rather than each doing their own
arithmetic. Windowing is by row, so a grid scrolls a whole row at a time and
degrades to the old per-member window when there is one column.
Cells are separated by a two-column gap: without it the focused entry's "> "
marker sits flush against the previous cell and reads as belonging to it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The cap made the picker a scrolling viewport -- everything stays reachable by
moving focus -- but there was no way to see the whole stack at once.
Clicking "+N more" expands it to every member; the indicator becomes "- fewer"
and collapses it again. Collapse gets its own label because an invisible way
back out is not an affordance.
Hangs off the indicator row because that row is already reserved, already
rendered, and resolves to no member, so it is free real estate. It is also why
this needs no new keybinding: mouse handling is entirely server-side, while
Action is a protobuf enum we cannot extend without protoc.
A click rather than hover, as expanding changes the reserved row count and so
resizes the visible pane's PTY -- acceptable once per deliberate click, not on
every mouse move.
All three consumers of the row -> member mapping (renderer, reserved-row
budget, mouse hit-test) now go through stack_list_window_for, so expansion
cannot desync them the way the cap desynced the hit-test in 844617b4.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A stack reserves one row per member plus a padding row, so a deep stack spends
most of the screen listing itself. `stack_list_max_rows` caps that; the list
windows around the focused member and reports the rest as "+N more".
The indicator goes in the padding row the list already reserves, so it costs
no extra row and never displaces a real entry. Entries keep their real rank
rather than renumbering from 1, otherwise the numbering stops meaning anything
once the window has scrolled. The window slides on ordinary focus movement,
which is also why this needs no new keybinding -- Action is a protobuf enum
and the prost assets cannot be regenerated here (no protoc).
A fixed cap rather than hover-to-expand on purpose: reserved_top_rows feeds
pane geometry, so growing the list on hover would resize the pane's PTY and
reflow whatever is running in it on every mouse move. A real expand/collapse
has to be an overlay that reserves nothing, which is a much larger change to
the render path.
reserved_top_rows and the renderer both go through stack_list_window(); if
they disagreed the list would draw into rows the geometry never reserved, i.e.
over the pane beneath it.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Splitting a window into thirds is great until the window is 70 columns wide
and each pane gets 23. `responsive_min_cols` collapses the tab into the swap
layout named by `responsive_layout` when it gets narrower than that, and
restores the previously active layout when it grows back.
Selects a swap layout BY NAME rather than adding a size-aware
LayoutConstraint: that type crosses both the plugin protobuf API and the
client/server contract, and the committed prost assets cannot be regenerated
here (no protoc).
Restoring uses a 10-column hysteresis band. Without a dead band, dragging a
window edge that lands exactly on the threshold flips the layout on every
motion event, and every flip resizes every PTY in the tab.
"Did the user rearrange panes by hand" is judged from the damaged flag as
sampled BEFORE each resize, not the live one: resize_whole_tab's own
auto-layout pass sets that flag itself and clears it again only if it found a
layout that fits, so on a terminal too narrow for any of them it latches true
although nothing was rearranged. And once collapsed, restore stays reachable
regardless of the flag, so damage occurring while collapsed cannot strand the
tab in the narrow layout.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Entering RenameTab left the existing name in place and appended to it, so
renaming a tab meant clearing it by hand first. The first printable keystroke
now REPLACES it, the way a text field with its contents selected behaves.
Armed when a client enters RenameTab and disarmed when it leaves, so a
keystroke that arrives later cannot wipe a name the user was not editing.
Backspace and delete deliberately do NOT replace: reaching for them means
editing the existing name, not discarding it.
The flag is taken out of the set BEFORE borrowing the active tab, since
get_active_tab_mut borrows self mutably and every branch consumes the flag
anyway.
Applies to renames from any entry point - the keybind and the sl0pstatus
double-click-a-tab both go through Screen::change_mode.
Pane rename is deliberately left alone for now; say the word and it can get
the same treatment.
Baselines held: zellij-server 1441/30.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Dragging showed only a green border on the target. The swap is now applied as
you drag, so the real layout IS the preview and releasing just keeps it.
Dragging elsewhere undoes it and previews the new arrangement; dragging off all
panes restores the original.
Because swap_panes is symmetric, applying it twice is the identity, so 'undo
the preview' is just the same call again. Drop needs no work at all now - the
swap already happened - which also removes the old drop-time swap.
THE TRAP, which a naive version hits immediately: once the swap is applied the
dragged pane sits under the cursor, so the next motion computes the dragged
pane as its own target, undoes the swap, which puts the original pane back
under the cursor, which re-applies it - oscillating on every motion event and
spamming both PTYs with resizes.
Avoided by treating two cases as 'the layout already shows what you would get'
and doing nothing:
- the cursor is over the dragged pane, wherever the preview has moved it;
- the cursor is over the pane it is currently swapped with, which after a
preview sits at the dragged pane's original position.
So a swap is only re-applied when the cursor reaches a THIRD pane, which also
keeps PTY resizes down to one pair per target change rather than per event.
The dragged pane is kept highlighted rather than the target, so it stays
identifiable while the layout moves around it.
Note: the state machine lives in the executor and needs a real Tab, so it is
reasoned through in the commit and README rather than unit-tested; the
decision-level tests cover only which action is produced.
Baselines held: zellij-server 1441/30.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Dragging a pane by its title was blind until release. The pane a drop would
land on is now highlighted with the theme's frame_highlight colour.
Reuses the existing hover highlight rather than adding a second highlighting
mechanism, but sets tab.mouse_hover_pane_id directly instead of going through
execute_update_hover, which is gated on the mouse_hover_effects option. During
a drag this is essential feedback rather than decoration, so it must not be
switchable off.
The pane being dragged is deliberately NOT highlighted when hovered: dropping
onto itself is a no-op, so showing no highlight is the honest signal that
releasing there changes nothing.
The highlight is cleared on drop, so a completed drag does not leave a pane
looking hovered.
Baselines held: zellij-server 1441/30.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Dragging a tiled pane by its title bar and dropping it on another pane swaps
the two. Previously the only way to rearrange panes was MOVE mode.
The underlying operation already existed: the directional move_pane_* functions
are all the same geometry swap, differing only in how they pick the other pane.
Extracted as TiledPanes::swap_panes(a, b) so a pane can be swapped with an
arbitrary target - the one a drag was dropped onto - which also de-duplicates
what was four copies of the same swap.
Gesture: the title row EXCLUDING its corners. Corners stay resize handles, so
two-axis resize from the top is preserved. Plain top-edge resize is gone and
costs nothing: every boundary is shared, so the boundary above a pane is the
bottom edge of the pane above and can be dragged from there. A pane at the top
of the viewport has the screen edge above it, which was never resizable.
Ordering in determine_mouse_action matters three ways:
- an in-progress drag is checked FIRST, so motion cannot be reinterpreted as
text selection or a resize;
- the drag check sits AFTER the double-click check, so the second click of a
rename is not swallowed;
- and BEFORE the resize branch, which would otherwise claim the title row.
Two behaviours had to be preserved deliberately:
- Dropping a pane on itself is a no-op, which is what keeps a plain click and
a double-click working on the title bar.
- Starting a drag focuses the pane. Pressing a title used to start a resize,
which focused it as a side effect; without this a plain click on the title
would do nothing at all.
Stacked panes are excluded from the handle. Their title row is the affordance
for focusing/expanding a stack member, and intercepting it broke
focus_stacked_pane_under_flexible_pane_with_the_mouse - caught by the suite,
not by hand.
Baselines held: zellij-server 1440/30 (the 30 are pre-existing).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`ui { pane_frames { hide_default_pane_names true } }`, default false.
A pane now shows no title at all until something actually names it: the user
renaming it (including by double-clicking the title row) or the running
program setting a title. Upstream always shows an auto-generated 'Pane #N'.
Implemented by tracking whether pane_title is still the constructor's
placeholder, NOT by matching the string: a program that legitimately titles
itself 'Pane #3' must not be mistaken for an unnamed pane. set_title clears
the flag, since something then chose that title.
current_title reads the option at render time rather than baking an empty
title in at construction, so it hot-reloads and turning the option back off
restores the placeholder on existing panes.
Display precedence is unchanged and now explicit in the code:
user-given name > title set by the running program > placeholder
render_terminal_title already fell back to an empty string rather than the
placeholder, so the terminal WINDOW title is unaffected.
Empty titles were already handled by the frame renderer
(render_title_left_side returns None for an empty title), including the
sl0p centred/right alignment path.
Baselines held: zellij-server 1436/30, zellij-utils 436/5.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`ui { pane_frames { inactive_grayscale 0-100 } }`, default 0 (off).
Real colour transformation, not the SGR 'faint' attribute zellij already uses
for frames: faint is terminal-dependent, several terminals only apply it to
the 16 ANSI colours, and it does not remove colour. Each cell's colour is
resolved to RGB and blended toward its own Rec.709 luminance, so an unfocused
pane keeps its brightness but drains of colour.
Colour resolution covers the cases that actually occur: RgbCode directly,
ColorIndex via eightbit_to_rgb, and NamedColor by mapping to its 0-15 index.
Cells with NO explicit colour are deliberately left alone - they render in the
terminal default, which is already unsaturated, and rewriting them would mean
emitting an explicit colour for nearly every cell for no visible gain.
Two non-obvious pieces were needed to make it actually work:
- The output buffer only emits CHANGED cells, so an idle pane would keep its
old colours indefinitely after losing focus. Pane::refresh_for_focus_change
forces a full repaint, called from ActivePanes::insert (the single place
focus changes). It is a no-op when the feature is off, so it costs nothing
by default.
- Terminal panes render through the SHARED multi-client path. Focus is per
client, so once unfocused panes are desaturated two clients may need
different output for the same pane. Both tiled and floating panes now fall
back to per-client rendering when grayscale is enabled - the reason this is
opt-in.
Known limitation: sixel/kitty image chunks are not colour-transformed and stay
in colour.
Hot-reloads like the other sl0p options. Tests cover the transform: full
strength yields a true neutral grey, partial sits between original and grey,
indexed and named colours resolve rather than being skipped, luminance is
perceptual (green brighter than blue) not a flat average, already-grey is
unchanged, and non-colour codes are untouched.
Baselines held: zellij-server 1436/30, zellij-utils 436/5.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Double-clicking the top edge of a pane's frame focuses that pane and switches
the client into RenamePane mode.
Everything needed already existed; this is wiring, not new machinery:
- grid.rs already tracks repeated clicks (Click, 400ms threshold). It is now
pub and reused rather than inventing a second notion of 'double click'
that could drift from the one used for text selection. A frame click never
reaches the grid, so Tab tracks frame clicks per client separately.
- pane.position_is_on_frame() / get_edge_at_position() already give the hit
edge, and mouse_handler already computes both.
- Screen::change_mode() already calls store_pane_name() when entering
RenamePane, so issuing ChangeMode(RenamePane) does the whole setup exactly
as the keybind path does. No rename logic is duplicated.
Placement in determine_mouse_action matters twice:
- AFTER the floating 'start moving' branch, so floating panes keep dragging
by their title (that is their move handle). Tiled panes only for now.
- BEFORE the resize branch, which would otherwise consume the click as a
top-edge resize drag.
Only PaneEdge::Top renames; corners stay resize handles. The pane is focused
first because RenamePane acts on the ACTIVE pane, so renaming an unfocused
pane would otherwise rename the wrong one.
Tests cover all three: double-click on Top renames, single click on Top still
resizes, and double-click on any other edge or corner does not rename.
Baselines held: zellij-server 1424/30 (3 new tests), zellij-utils 435/5.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Closes the frameless-mode hole documented in the README.
In frameless mode (pane_frame_style "titles" or "none") the cells around a
pane's content are owned by the inter-pane boundary renderer, not the pane.
The ghost-fix commit correctly stopped panes from BLANKING that region -
blanking it erased the boundaries and broke three tests - but nothing stopped
them from CREATING it: padding still inset the content, so the vacated cells
had no owner and kept whatever was drawn there before.
Half the job done in each direction produced exactly the ghosting the blanking
was meant to prevent, just in a mode we do not use by default.
Fix: a pane that is not drawing a frame is not inset at all - no gap, no
padding, no moat, nothing unowned. Pane::set_frame() already received the
right boolean from both the tiled and floating containers and was throwing it
away; it now records it.
Config values are preserved, they simply take effect only with full frames.
README updated to describe this as a defined limit rather than a known bug.
Baselines held: zellij-server 1421/30, zellij-utils 435/5.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
`zellij --version` was identical to upstream, so there was no way to tell a
fork binary from a stock one.
Uses BUILD METADATA (+sl0p1) rather than a prerelease (-sl0p1): cargo ignores
build metadata when matching version requirements, so the intra-workspace
`zellij-utils = "0.45.0"` dependencies still resolve. The prerelease form was
tried first and fails to resolve - it would require updating every member
manifest to depend on the exact prerelease.
Checked against the places VERSION is actually consumed:
- shared::version_number() yields 4500 for both 0.45.0 and 0.45.0+sl0p1, so
terminal version reporting is byte-identical, and still orders below 0.46.0.
- client/server compatibility keys off CLIENT_SERVER_CONTRACT_VERSION, not
this, so existing sessions are unaffected.
- ZELLIJ_PLUGIN_ARTIFACT_DIR is keyed by VERSION, so the bundled wasm plugins
are recompiled once on first run after a bump. One-time, harmless.
Baselines held: zellij-server 1421/30, zellij-utils 435/5.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Anyone cloning this repo got stock Zellij's README and no indication the
binary behaves differently, with the new options discoverable only from
commit messages.
Documents the four config options, which changes apply live vs need a new
session, the fastdev build profile, and the known rough edges.
Two corrections while writing it:
- The frameless-mode caveat is stated honestly. `padding` DOES still inset
content when pane frames are off, but the vacated cells are not repainted
because ui/boundaries.rs owns that region, so stale glyphs are expected on
resize. That is a real limitation, not the deliberate design the first
draft claimed. gap/padding are documented as full-frame only for now.
- Deduplicated the borderless-pane comment in both panes, which scripted
patching had left repeated three times.
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
| |
* add name section in readme
* Adjust wording
---------
Co-authored-by: Aram Drevekenin <aram@poor.dev>
|
| | |
|
| | |
|
| | |
|
| |
|
| |
Updated the quick launch commands to include xonsh.
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* chore(assets): new readme animation
* chore(assets): increase animation resolution
* chore(assets): improve animation
* chore(assets): improve animation
* docs(readme): some cleanups and clarifications
* docs(readme): fix links and wording
* chore(version): bump development version
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
* xtask: Implement a new build system
xtask is a cargo alias that is used to extend the cargo build system
with custom commands. For an introduction to xtask, see here:
https://github.com/matklad/cargo-xtask/
The idea is that instead of writing makefiles, xtask requires no
additional dependencies except `cargo` and `rustc`, which must be
available to build the project anyway.
This commit provides a basic implementation of the `build` and `test`
subcommands.
* xtask/deps: Add 'which'
* xtask/test: Handle error when cargo not found
* xtask/flags: Add more commands
to perform different useful tasks. Includes:
- clippy
- format
- "make" (composite)
- "install" (composite)
Also add more options to `build` to selectively compile plugins or leave
them out entirely.
* xtask/main: Return error when cargo not found
* xtask/build: Add more subtasks
- `wasm_opt_plugins` and
- `manpage`
that perform other build commands. Add thorough documentation on what
each of these does and also handle the new `build` cli flags
appropriately.
* xtask/clippy: Add job to run clippy
* xtask/format: Add job to run rustfmt
* xtask/pipeline: Add composite commands
that perform multiple atomic xtask commands sequentially in a pipeline
sort of fashion.
* xtask/deps: Pin dependencies
* xtask/main: Integrate new jobs
and add documentation.
* xtask: Implement 'dist'
which performs an 'install' and copies the resulting zellij binary along
with some other assets to a `target/dist` folder.
* cargo: Update xflags version
* xtask: Measure task time, update tty title
* xtask: Update various tasks
* xtask: wasm-opt plugins in release builds
automatically.
* xtask/build: Copy debug plugins to assets folder
* xtask: Add 'run' subcommand
* xtask: Add arbitrary args to test and run
* xtask: Rearrange CLI commands in help
* xtask: Add deprecation notice
* docs: Replace `cargo make` with `xtask`
* github: Use `xtask` in workflows.
* xtask: Add support for CI commands
* xtask: Streamline error handling
* github: Use new xtask commands in CI
* xtask: Add 'publish' job
* xtask/publish: Add retry when publish fails
* xtask: Apply rustfmt
* xtask: Refine 'make' deprecation warning
* xtask: add task to build manpage
* contributing: Fix e2e commands
* xtask/run: Add missing `--`
to pass all arguments following `xtask run` directly to the zellij
binary being run.
* xtask: Stay in invocation dir
and make all tasks that need it change to the project root dir
themselves.
* xtask/run: Add `--data-dir` flag
which will allow very quick iterations when not changing the plugins
between builds.
* xtask/ci: Install dependencies without asking
* utils: Allow including plugins from target folder
* utils/assets: Reduce asset map complexity
* utils/consts: Update asset map docs
* xtask: Fix plugin includes
* xtask/test: Build plugins first
because the zellij binary needs to include the plugins.
* xtask/test: Fix formatting
* xtask: Add notice on how to disable it
|
| | |
|