Myah MitchellandAram Drevekenin 0591ca5432 feat(nested-sessions): report a guest's input mode and key bindings to its host (#5589)
* refactor(plugin-api): share the keybinding protobuf conversions

The conversion between a keybinding table and its protobuf form was written
out four times: once each way for the ModeUpdate and InitialKeybinds events.
Collect them into `keybinds_to_protobuf` and `keybinds_from_protobuf`.

Decoding now drops a binding whose key is missing instead of unwrapping it,
so a malformed or foreign payload can no longer panic the conversion, and a
mode whose key fails to convert costs only that binding rather than every
binding in the mode.

* feat(nested-sessions): carry input mode and keybindings over the protocol

A session hosting a nested session has no way to learn what that session is
doing, so its plugins cannot show hints for the session the user is actually
typing into. Add the three messages that carry it:

- GuestModeUpdate, sent by a guest whenever its input mode changes, so a host
  always knows which of the guest's bindings currently apply.
- RequestGuestKeybinds and GuestKeybindsUpdate, a query and its answer, for
  the far larger keybinding table. Keeping the table behind a query means it
  is fetched once by whoever wants it rather than pushed at every host.

The table travels as an encoded plugin-API payload in an opaque bytes field.
Describing keybindings natively here would mean restating the whole Action
schema in this contract, which the codegen compiles with only its own include
path. Reusing the existing payload also means a host and a guest built from
different Zellij versions exchange whatever both understand and silently drop
the rest, which is the behavior this wire needs.

An input mode is written under the name InputMode::from_str reads, so adding a
mode needs no change to this contract and an unknown mode is rejected rather
than misread.

Peers advertise the new HintReporting capability, so each side can tell
whether the other can take part before asking it anything.

* feat(plugin-api): expose nested session mode and keybinding events

Plugins running in a host session can see that a pane holds a nested guest
session, but they have no way to learn what that guest is doing. A status bar
in the host therefore keeps showing the host's own hints while the user is
descended into the guest, which is the wrong keybinding table for the keys the
user is actually pressing.

Add two events and one command covering that gap:

  - Event::NestedSessionModeUpdate reports a guest's input mode as it changes.
  - Event::NestedSessionKeybinds delivers a guest's full keybinding table.
  - PluginCommand::RequestNestedSessionKeybinds asks a guest pane for that
    table, which arrives asynchronously as the event above.

Both events carry the pane the guest runs in and the guest's session name, so
a plugin watching several nested panes can tell them apart. Keybindings are
requested rather than pushed because the table is large and most plugins never
need it.

* feat(nested-sessions): report guest input mode and keybindings to the host

Wire the nested session hint events end to end.

A guest announces the HintReporting capability during the handshake and, from
then on, emits a GuestModeUpdate frame whenever its input mode changes. The
mode is reported on every change rather than only while the host is descended,
so the host already holds the right mode the moment focus arrives and the
status bar does not lag behind the descent.

The host turns each such frame into Event::NestedSessionModeUpdate. It answers
PluginCommand::RequestNestedSessionKeybinds by writing a RequestGuestKeybinds
frame into the guest pane; the guest replies with its keybinding table plus its
current mode, and the host forwards the table as Event::NestedSessionKeybinds.

Requests to guests that did not advertise HintReporting are dropped rather than
written, and frames from panes whose guest is not currently live are ignored,
matching how the other guest-to-host frames are handled.

Both events and the command require the ReadApplicationState permission, the
same gate ModeUpdate uses.

* test(nested-sessions): cover guest mode and keybinding reporting

Unit tests over the contract check that every input mode survives the round
trip under its own name, that a mode name the reading side does not know is
rejected rather than guessed at, and that a keybinding table keeps its modes,
keys and actions across the wire. The undecodable and empty payload cases are
covered too, since a host and a guest may be built from different versions.

Integration tests drive two real sessions bridged through a pane: both sides
advertise hint reporting during the handshake, the guest answers a keybinding
request with a populated table plus its current mode, and the guest reports
each mode change while the user is descended into it.

* test(nested-sessions): drop unknown capabilities without losing known ones

A peer built from a newer Zellij can advertise capabilities this side has
never heard of, so pin down that they are ignored individually rather than
invalidating the whole advertisement.

* feat(nested-sessions): carry the guest's base mode alongside its input mode

A host plugin that only knows a guest's current mode cannot tell which of the
guest's bindings are inherited from its base mode. Zellij's own hint rendering
leans on that distinction: bindings the base mode already advertises are
inherited by every other mode through `shared_except` groups, so repeating them
in each mode is noise. Without the guest's base mode a host plugin either
suppresses the wrong bindings or suppresses none.

A guest with a non-Normal base mode, such as an unlock-first config, is where
this shows: assuming Normal there hides the wrong half of its bindings.

The base mode rides along on the existing mode message, so a guest that has not
switched modes yet still reports one, resolved the way the rest of Screen
resolves it (the configured default mode is the base). It stays optional on the
wire, so a name the reading side does not recognize costs only the
inherited-binding distinction rather than the whole message.

* feat(nested-sessions): report the guest's mode when the handshake completes

A guest only pushed its input mode when that mode changed, so a host that
had just picked up a nested session learned nothing about it until the user
happened to switch modes inside it. A host plugin has no other way to tell
which of its panes holds a session worth asking for keybindings, which left
it unable to act on the feature at all in the common case of a guest sitting
in its starting mode.

Send the report once on contact as well, and reuse the same client mode
lookup the keybinding answer already did.

* docs(changelog): note the nested session hint reporting events

* fix(nested-sessions): drop guest keybindings on control characters

Nothing a guest says is authenticated. Its frames travel in the terminal
stream of the pane it runs in, so any program with that pane can write them,
including a keybinding table claiming a binding on a raw control character.
A host that draws those bindings puts their keys on its own screen, where a
control character is a terminal escape in the host's output rather than a
key anyone could press.

Drop them where the guest's payload is decoded, so no host plugin inherits
the problem. Zellij spells the control keys that do exist as their own
BareKey variants, so a Char holding one describes no binding worth keeping.

* test(nested-sessions): expect hint reporting in the announce ack

The host now advertises HintReporting alongside NestedControl when it
acknowledges a guest, so the assertion on the exact capability list had to
follow.

* fix(plugin-api): drop only what has no protobuf form when writing keybindings

keybinds_to_protobuf failed the whole table on the first key or mode it could
not represent, so one unrepresentable binding cost the caller every binding:
a ModeUpdate that failed to convert entirely, and a nested session's answer
that reached its host as an empty table indistinguishable from a session with
no bindings at all.

Drop the individual mode, key or action instead, which is what the reading
half already does and what the writing half already did for actions. A table
now survives the trip between builds minus whatever they do not have in
common.

* fix(nested-sessions): keep a host's view of the guest fresh across a reconfigure

A reconfigure rewrites a session's keybindings and base mode without going
through change_mode, and keybindings are otherwise only ever sent in answer
to a request. A host that had already asked once therefore kept drawing the
table the guest had before its config was reloaded, with no way of noticing
it had gone stale, and would not have picked up the new base mode until the
user happened to switch modes inside the guest.

Report both from reconfigure, next to the shortcut broadcast that already
happens there. Answer a keybinding request from the requesting client's own
table too, since a reconfigure with write_to_disk unset rebinds only the
client that asked: the mode in the same reply already came from that client,
so the two halves now describe the same thing.

* fix: adjust protocol + API

* Update CHANGELOG.md

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-09-24 14:43:03 +02:00
2026-07-27 18:24:37 +02:00
2026-08-05 15:32:53 +02:00
2022-03-02 12:12:26 +01:00
2026-08-20 11:12:34 +02:00
2026-08-20 11:12:34 +02:00
2020-10-27 11:11:10 +01:00
2026-09-22 09:17:47 +02:00
2026-09-14 14:43:41 +02:00


logo
Zellij

demo

[Installation] [Screencasts & Tutorials] [Configuration] [Layouts] [FAQ]

Discord Chat Matrix Chat Zellij documentation


Sponsored by     gresearch logo

What is this?

Zellij is a workspace aimed at developers, ops-oriented people and anyone who loves the terminal. Similar programs are sometimes called "Terminal Multiplexers".

Zellij is designed around the philosophy that one must not sacrifice simplicity for power, taking pride in its great experience out of the box as well as the advanced features it places at its users' fingertips.

Zellij is geared toward beginner and power users alike - allowing deep customizability, personal automation through layouts, true multiplayer collaboration, unique UX features such as floating and stacked panes, and a plugin system allowing one to create plugins in any language that compiles to WebAssembly.

Zellij includes a built-in web-client, making a terminal optional.

You can get started by installing Zellij and checking out the Screencasts & Tutorials.

For more details about our future plans, read about upcoming features in our roadmap.

How do I install it?

The easiest way to install Zellij is through a package for your OS.

If one is not available for your OS, you could download a prebuilt binary from the latest release and place it in your $PATH. If you'd like, we could automatically choose one for you.

You can also install (compile) with cargo:

cargo install --locked zellij

Try Zellij without installing

bash/zsh:

bash <(curl -L https://zellij.dev/launch)

fish/xonsh:

bash -c 'bash <(curl -L https://zellij.dev/launch)'

Installing from main

Installing Zellij from the main branch is not recommended. This branch represents pre-release code, is constantly being worked on and may contain broken or unusable features. In addition, using it may corrupt the cache for future versions, forcing users to clear it before they can use the officially released version.

That being said - no-one will stop you from using it (and bug reports involving new features are greatly appreciated), but please consider using the latest release instead as detailed at the top of this section.

How do I start a development environment?

  • Clone the project
  • In the project folder, for debug builds run: cargo xtask run
  • To run all tests: cargo xtask test

For more build commands, see CONTRIBUTING.md.

Configuration

For configuring Zellij, please see the Configuration Documentation.

About issues in this repository

Issues in this repository, whether open or closed, do not necessarily indicate a problem or a bug in the software. They only indicate that the reporter wanted to communicate their experiences or thoughts to the maintainers. The Zellij maintainers do their best to go over and reply to all issue reports, but unfortunately cannot promise these will always be dealt with or even read. Your understanding is appreciated.

Roadmap

Presented here is the project roadmap, divided into three main sections.

These are issues that are either being actively worked on or are planned for the near future.

If you'll click on the image, you'll be led to an SVG version of it on the website where you can directly click on every issue

roadmap

Origin of the Name

From Wikipedia, the free encyclopedia

Zellij (Arabic: الزليج, romanized: zillīj; also spelled zillij or zellige) is a style of mosaic tilework made from individually hand-chiseled tile pieces. The pieces were typically of different colours and fitted together to form various patterns on the basis of tessellations, most notably elaborate Islamic geometric motifs such as radiating star patterns composed of various polygons. This form of Islamic art is one of the main characteristics of architecture in the western Islamic world. It is found in the architecture of Morocco, the architecture of Algeria, early Islamic sites in Tunisia, and in the historic monuments of al-Andalus (in the Iberian Peninsula).

License

MIT

Sponsored by

S
Description
A terminal workspace with batteries included
Readme
302 MiB
Languages
Rust 98.3%
JavaScript 1.6%