* fix: keep single-pane tabs when serializing, stop the busy-spin on IPC disconnect, and make client-less CLI actions either work or fail loudly
* fix: some tab closing/ordering issues
* test: integration coverage for OSC 133 selection, scroll retention, paste-read opt-in, theme actions, and input fragmentation
* rendering-width regression tests
* fix: forward alt+mouse events into a guest pane the client is descended into
* fix: alt + [] delay in non-kitty
* allow disabling mouse hover tips while keeping the other effects
* more osc133 actions
* fix: base mode race when changing keybinding presets
* fix compilation warnings
* make releases drafts and remove diagnostic logging
* add pr
A saved / resurrected session had tabs always in the order of
creation, even if they were subsequently reorder. This patch fixes
this by sorting the tabs_to_proces in get_layout_metadata() before
they are processed.
When AddClient arrives while background plugins are already loaded,
the add_client loop now checks if a (plugin_id, client_id) pair
already exists in the plugin map before creating a new instance.
This eliminates the race condition where load_background_plugin and
add_client both create instances for the same plugin.
Fixes#5177
When ECH replaces a wide character with spaces, the trailing padding cell
must keep the same style as the replacement cell. Otherwise, wide
characters can appear split in half, with one cell using the pane
background and the other falling back to the terminal default background.
Preserve the background style for the padding cell.
* fix(clipboard): don't kill copy_command processes that own the selection
The reaping added in #4298 kills the copy_command process if it is still
running one second after the copied text was written to its stdin. X11 and
Wayland clipboard helpers are supposed to keep running: they own the
selection and serve it to whoever pastes it, so killing them empties the
clipboard.
Wait for the process instead of polling it with a deadline. Commands that
exit on their own are still reaped as soon as they do, so the zombies #4298
was about don't come back.
* docs(changelog): add PR
* fix(scroll): keep scroll position when leaving scroll mode
Leaving Scroll mode for Normal or Locked snapped the active pane back to
the bottom. Switching panes requires leaving Scroll mode, so that reset
is what prevented keeping a pane scrolled while working in another.
It is also redundant: the scroll keymap already binds an explicit
ScrollToBottom on exit, so users who want to follow live output keep it,
while a plain exit now preserves the scroll position. Output is still
buffered while a pane is scrolled and replayed when scroll is cleared,
so the buffer-overwrite protection is unaffected.
* fix(scroll): re-enter scroll mode when focusing a scrolled pane
Focusing a scrolled pane now switches the client into Scroll mode, and
focusing an unscrolled one returns it to Normal. Only the Normal<->Scroll
pair is synced, so a client in another mode is left alone.
* fix(scroll): re-enter scroll mode on focus-last-pane too
Cover the FocusLastPane action added in #4241 with the same focus-to-scroll
sync as the other focus moves, so landing on a scrolled pane via focus-last
behaves like landing on it via MoveFocus.
* fix(scroll): sync the default mode with scroll, cover mouse focus
Follow imsnif's review on #5299. The focus sync now switches between Scroll
and the configured default mode read from default_mode_info instead of a
hardcoded Normal, so unlock-first sessions (default Locked) return to Locked
rather than Normal. It also fires on mouse focus changes (click, click-through,
focus-follows-mouse) by hooking the existing active-pane diff in
handle_mouse_event, not only keyboard focus moves.
Tests parameterize the focus-sync assertion over the default mode (Normal and
Locked) and add a mouse-click variant.
* cover some more cases
* sync also on mouse scroll
* rustfmt
---------
Co-authored-by: Aram Drevekenin <aram@poor.dev>
When `stacked_pane_list` is on and the user adds a stacked pane, the classic
in-grid stack path first collapses the pre-existing pane to `Fixed(1)` and
then `focus_pane` fires `reapply_pane_frames`, which sends `resize_pty!` to
the collapsed pane at 1 row. The stack-list groupify pass (invoked later
during `render`) then extracts the pane to `suppressed_panes` and resizes it
back to the visible size, issuing a second `resize_pty!`. The two rapid
WINCH events cause the shell (nushell, pwsh, bash, …) inside the pane to
redraw its prompt twice, overwriting previously-visible rows of output.
The grid data is preserved in `lines_above`, but the visible viewport ends
up mostly blank — the user sees only the pre-existing rows that had already
scrolled off, giving the impression of a truncated buffer.
Sync stacked-pane-list mode right after the classic mutation, before
`focus_pane`. `groupify_all` moves the collapsed member into
`suppressed_panes` first, so the subsequent `reapply_pane_frames` (which
only iterates `tiled_panes`) never sends a 1-row `resize_pty!` to that
shell. The pane is then resized once to the visible size via
`resize_stack_list_hidden_members`.
Regression test asserts that after adding a stacked pane in this mode, the
pre-existing pane is in `suppressed_panes` and the new visible member has
non-fixed row dimensions — proving groupify happened before any classic
`reapply_pane_frames` step.
Co-authored-by: divens <divens.dev@gmail.com>
Co-authored-by: Aram Drevekenin <aram@poor.dev>
* fix(input): realign raw-byte buffer with the keyboard parser to stop stale bytes eating keystrokes
After a paste (e.g. via a clipboard manager like maccy), zellij's
current_buffer can hold up to a few stale bytes that termwiz's
InputParser has already consumed into events. Those stale bytes are then
attached to the raw payload of subsequent KeyEvent messages, desyncing
each keystroke from the bytes that produced it: keys go missing, come
out garbled, or drop into vi command mode.
The root cause is that parse_with_consumed reports consumption relative
to its internal self.buf, while stdin_handler drains a separately
accumulated current_buffer. The two can drift apart whenever a read
leaves a partial sequence that the parser holds under maybe_more.
Reconcile the two after every parse: trim current_buffer to the
parser's buffered length, so no byte the parser has already decoded is
ever forwarded with a later key event. Add InputParser::buffered_len to
the vendored termwiz for that purpose.
* fix: remove parsed bytes from front rather than back
* rustfmt
---------
Co-authored-by: Aram Drevekenin <aram@poor.dev>
* initial chrome
* mobile state
* feat(mobile-web): implement browser→server actions (fit toggle, single-pane fullscreen, pane/tab focus) with fullscreen-truth reconciliation
* panning
* remove mobile plugin
* fix first load race
* improve web handshake
* rustfmt
* cleanups
* moar cleanups
* add bundle asset check to ci
* fix(mobile-web): tab-scope co-presence, yield single-pane to desktop clients, fix render-mode desync
* make mobile use no-ui fullscreen
* client side welcome screen for mobile
* attach web clients to first tab on tiles surface
* fix: opening new pane in mobile single mode no longer exits mobile single mode
* mobile chrome light theme support
* fix server races and flaky tests
* rustfmt
* fix flakes
* add pr
* upgrade insta, rand, sha2, strum, thiserror, tokio-tungstenite, toml, which; drop unused semver and typetag
* upgrade sysinfo to 0.39.5, bump toolchain (msrv) to 1.95.0
* remote legacy yaml config converter and thus drop serde_yaml
* upgrade unicode-width to 0.2.2
* fix(tests): run ipc roundtrip test on a larger stack
`find_executable` used to short-circuit on the first exists()-and-is_file
match and only consult PATHEXT if no bare file was present. That returned
the wrong file for tools whose Windows installer ships both a Unix-style
launcher and a `.bat` alongside it. The reported case is Composer, whose
setup places bare `composer` (a Unix launcher, invalid PE on Windows) next
to `composer.bat`. The bare file was resolved and handed to CreateProcessW,
which rejected it with ERROR_BAD_EXE_FORMAT.
On Windows, when the candidate has no user-supplied extension, walk PATHEXT
first and only fall back to the bare match if nothing matches. This mirrors
cmd.exe's and powershell's resolution.
* fix(grid): scroll the region when a line wraps at its bottom margin
Wrapping at the bottom margin of a DECSTBM scroll region used to move
the cursor below the region instead of scrolling it, after which no
subsequent output could scroll through the region (or reach scrollback
for top-anchored regions) anymore.
line_wrap() now mirrors add_canonical_line(): the region scrolls up one
line, the cursor stays on its bottom row, and the new row is a wrapped
(non-canonical) row. Top-anchored partial regions preserve the
scrolled-off line in scrollback (#4941), other regions discard it, and
the alternate screen never transfers.
* docs(changelog): line wrap scroll region fix
* fix(grid): keep hyperlink tracking aligned when wrap scrolls an inner region
When a line wraps at the bottom of a scroll region that does not start
at the top of the screen, the region's rows shift up but the hyperlink
tracker's recorded positions did not, so a URL wrapping there lost its
clickable anchors. Offset the tracked positions for the rows inside the
region, the way the full-screen and top-anchored paths already do via
offset_cursor_lines.
* refactor shared logic and adjust changelog
---------
Co-authored-by: Aram Drevekenin <aram@poor.dev>