Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve default keybindings #1399

Open
imsnif opened this issue May 10, 2022 · 47 comments
Open

Improve default keybindings #1399

imsnif opened this issue May 10, 2022 · 47 comments
Labels
ease of use Issues where zellij has clunky or difficult to use behaviours
Milestone

Comments

@imsnif
Copy link
Member

imsnif commented May 10, 2022

The default Zellij keybindings are great. They allow a modal approach that is discoverable and easy to pick up (especially when combined with the status-bar). However, they have two main downsides:

  1. They collide with many terminal applications - and while we provide an escape hatch (at runtime with LOCKED mode or by changing the configuration), this is not a good enough solution.
  2. Alt doesn't work on mac, and while all alt-shortcuts are available in a more involved way through ctrl (and mac users can map option to alt) mac users still miss out.

We should think up and implement a default keybinding scheme that solves both of these issues while still keeping the discoverability and easy-of-use of the current keybindings - even if it involves changing modes around or upending existing app assumptions Zellij has.

To be clear - anything we come up with should still allow the usage of the current scheme as a "classic mode" in order not to break anyone's workflow. We will keep supporting this "classic mode" forever.

@imsnif imsnif added the ease of use Issues where zellij has clunky or difficult to use behaviours label May 10, 2022
@imsnif imsnif added this to the Roadmap milestone May 10, 2022
@guerinoni
Copy link

I'm still searching how can use this on my new MacBook 😂
Please help with keybinding on macOS :D

@donut
Copy link

donut commented Aug 15, 2022

In macOS with iTerm, I was able to get option + <arrow keys> working, but not option with any other keys.

You can find the setting in iTerm under

[Preferences > Advanced > Terminal > When you press an arrow key or other function that transmits the modifiers, should ⌥ be translated to Meta? > No]

But I really need proper option support. Although, I would like a VIM mode that worked with things like :vpslit and ctrl+w hjkl for splitting and movement. That's probably too much.

@rhoen
Copy link

rhoen commented Aug 19, 2022

Plus one to this issue.

As a mac user myself who was interested in checking out Zellij as an alternative to tmux, I have felt a clear communication that Zellij is not meant for me. If the Zellij community wants this app to be used by mac users, than this problem needs to be addressed. Zellij advertises itself as being out-of-the-box ready, and yet it displays impossible to use short cuts (alt key) to mac users right on the home screen.

@samholmes
Copy link

samholmes commented Sep 7, 2022

Here's a gist of a set of keybinding which I think may solve this issue to a certain extent: https://gist.github.com/samholmes/70bc8b553a2ae888239705904f4fd01c

Vim-Style

The keybindings draw heavy influence from Vim in that Normal mode is actually "Insert" mode and Pane mode is actually "Normal*" mode. This is because Pane is where all the other modes are reachable from with single key motions as well as main navigation actions. In Vim, users always start from Normal mode, not Insert, so this is also reflected in the config. I'll use an asterisk to denote when I mean the renamed Pane more (Normal*).

By defaulting to a vim-style normal mode, we have a lot quicker access to movement and other actions (commands) without requiring modifiers. This really makes it easier to choose cross-platform bindings (avoiding Alt on macOS). While in Normal* mode, the user is able to be swift and the tool can enable more complex gestures as the tool evolves. For example, dt to delete tab or dp to delete pane, etc. The bindings could expand to be more combinatorial and configurable perhaps like lf CLI.

With an Insert mode, the user can pop-in a pane (with either i or Return/Enter keys), enter a command., and press Esc to pop-out back to Normal* mode. This is a very fast way to navigate Zellij, and will inspire a lot more utility as plugins are added for more complex usage. While in Insert mode, Esc is the only keybinding, so it's unlikely to be causing a collision, however, this still runs into the issue where Esc is needed in while in Insert mode.

Therefore, in order to unbind the Esc key while in Insert mode, the user must enter Locked mode which is conveniently a binding as Ctrl+Space. When the user is in Locked mode, the panes are hidden to be visually obvious. To exit Locked mode, the user can press Ctrl+Space again to go back to Normal* mode.

Tab Mode

With a focused Normal* mode, Tab mode is not really needed as it is. All tab actions are bound to commands using the Ctrl modifier. Here are a few (see gist for full list):

  • Ctrl: 'n' – New tab
  • Ctrl: 'h' – Focus tab to the left
  • Ctrl: 'l' – Focus tab to the right

I chose this setup mainly because it's not glaringly obvious, visually, whether a user is in Tab mode. Perhaps if the Tabs were highlighted instead of the panes, this would make a Tab mode more useful. Though, I feel like tabs are too important to be at distance from Normal* with another move (though I could be wrong).

Solving Escape with an Integration Standard

So far, there is now way to delegate the Escape key to a program while in Insert mode which leaves us with the Locked mode workaround. However, I wonder if it's possible to send the Escape key sequence to the shell program running in the pane and have it return a character sequence back through stderr or some other stream to signal that it is handling it. This would mean that a program could acquire the control over Escape if it has integrated support for this standard. Most programs will not support this standard and so the user will have to fallback to using Locked mode, however programs may adopt this standard over time, especially if it is picked up by other terminal multiplexers. I'm not certain if this will be feasible, but I imagine one could discover if it is possible for programs to consider to signal to the parent program that it is acting on the Escape key.

EDIT: I added a Tab mode as an alternative option than tab actions from Normal*.

@samholmes
Copy link

samholmes commented Sep 16, 2022

Less Modes

I've since been playing with a particular configuration which uniquely simplifies the modes and UX for the tool. It doesn't try to emulate Vim as much as my previous post and removes two modes: Lock and Tab. Here is some explanation on how this was acheived.

In order to remove Lock mode, we must remove the need for lock mode. The need for lock mode is to turn off keybindings that would interfere with the use of a shell program within the pane. To eliminate this problem, there is only one single binding in Normal mode: Ctrl+Space which is the Null control character (\x00) for my terminal configuration (Alacritty). I've never needed to send the Null character to any program (as far as I'm aware), so I believe this evades any conflicts. If I need the Null control character for any reason, Lock mode could be re-introduced for this special case, or there could be a command to send the null character while in Pane mode.

Tab mode is removed completely in favor of a single mode to control tabs and panes (Pane mode in the config file). Instead of requiring an extra mode, the user can hold control while in Pane mode for tab navigation using hl or arrow keys. This is convenient because Ctrl is required in the primary mode switch binding (Ctrl+Space). Navigating while in the default mode (Pane) is very easy and muscle memory is built fairly quickly after minimal use.

While in any Zellij mode other than Normal, Rename, or SearchInput, the spacebar will change back to normal. This makes it so easy to get back into Normal mode from nearly anywhere to insert commands. The spacebar is a way to clear the Zellij control seamlessly.

By having less modes, it is very easy to build that muscle memory and use the tool swiftly. The two things to remember are Ctrl+Space to activate Zellij and Space to exit.

Here's the config.yaml for a Lock-less and Tab-less configuration.

#
# Lock-less and Tab-less:
#
keybinds:
  normal:
    - unbind: true
    # Switch Modes (only one switch)
    - action: [TogglePaneFrames, SwitchToMode: Pane]
      key: [Char: "\x00"]
  locked:
    - unbind: true
  pane:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " ", Char: "\n"]
    # Switch Modes
    - action: [SwitchToMode: Resize]
      key: [ Char: 'r']
    - action: [SwitchToMode: Scroll]
      key: [ Char: 's']
    - action: [SwitchToMode: Move]
      key: [ Char: 'm']
    - action: [SwitchToMode: Session]
      key: [ Char: 'o']
    - action: [SwitchToMode: EnterSearch, SearchInput: [0]]
      key: [Char: '/']
    - action: [Quit]
      key: [Ctrl: 'q']
    # Nav
    - action: [MoveFocus: Left]
      key: [Char: 'h', Left]
    - action: [MoveFocus: Right]
      key: [Char: 'l', Right]
    - action: [MoveFocus: Down]
      key: [Char: 'j', Down]
    - action: [MoveFocus: Up]
      key: [Char: 'k', Up]
    # Mode Actions
    - action: [ NewTab: ,]
      key: [Char: 'n']
    - action: [NewPane: ]
      key: [Char: 'K']
    - action: [NewPane: Down]
      key: [Char: 'J']
    - action: [NewPane: Right]
      key: [Char: 'L']
    - action: [NewPane: Left]
      key: [Char: 'H']
    - action: [SwitchToMode: RenamePane, PaneNameInput: [0]]
      key: [Char: 'c']
    - action: [CloseFocus]
      key: [Ctrl: 'd']
    - action: [CloseTab]
      key: [Ctrl: 'D']
    # Tab Actions
    - action: [ NewTab: , TogglePaneFrames, SwitchToMode: Normal]
      key: [Ctrl: 'n']
    - action: [GoToPreviousTab]
      key: [Ctrl: 'h']
    - action: [GoToNextTab]
      key: [Ctrl: 'l']
    - action: [SwitchToMode: RenameTab, TabNameInput: [0]]
      key: [Ctrl: 'c']
    - action: [ToggleActiveSyncTab]
      key: [Ctrl: 's']
    - action: [GoToTab: 1]
      key: [Ctrl: '1']
    - action: [GoToTab: 2]
      key: [Ctrl: '2']
    - action: [GoToTab: 3]
      key: [Ctrl: '3']
    - action: [GoToTab: 4]
      key: [Ctrl: '4']
    - action: [GoToTab: 5]
      key: [Ctrl: '5']
    - action: [GoToTab: 6]
      key: [Ctrl: '6']
    - action: [GoToTab: 7]
      key: [Ctrl: '7']
    - action: [GoToTab: 8]
      key: [Ctrl: '8']
    - action: [GoToTab: 9]
      key: [Ctrl: '9']
    # Toggles Mode Actions
    - action: [ToggleFocusFullscreen]
      key: [Char: 'f']
    - action: [TogglePaneFrames]
      key: [Char: 'z']
    - action: [ToggleFloatingPanes]
      key: [Char: 'w']
    - action: [TogglePaneEmbedOrFloating]
      key: [Char: 'e']
  tab:
    # Common:
    - unbind: true
  move:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Pane]
      key: [Esc]
    # Mode Actions
    - action: [MovePane: ]
      key: [Char: 'n', Char: "\t"]
    - action: [MovePane: Left]
      key: [Char: 'h', Left]
    - action: [MovePane: Down]
      key: [Char: 'j', Down]
    - action: [MovePane: Up]
      key: [Char: 'k', Up,]
    - action: [MovePane: Right]
      key: [Char: 'l', Right]
  renametab:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Pane]
      key: [Char: "\n"]
    - action: [UndoRenameTab , SwitchToMode: Pane]
      key: [Esc]
  renamepane:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Pane]
      key: [Char: "\n"]
    - action: [UndoRenamePane , SwitchToMode: Pane]
      key: [Esc]
  resize:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Pane]
      key: [Esc]
    # Mode Actions
    - action: [Resize: Left]
      key: [Char: 'h', Left]
    - action: [Resize: Down]
      key: [Char: 'j', Down]
    - action: [Resize: Up]
      key: [Char: 'k', Up,]
    - action: [Resize: Right]
      key: [Char: 'l', Right]
    - action: [Resize: Increase]
      key: [Char: '=', Char: '+', Char: 'i']
    - action: [Resize: Decrease]
      key: [Char: '-', Char: 'n'] # n for "narrow"
  scroll:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [ScrollToBottom, SwitchToMode: Pane]
      key: [Esc]
    - action: [SwitchToMode: EnterSearch, SearchInput: [0]]
      key: [Char: '/']
    - action: [EditScrollback, SwitchToMode: Normal]
      key: [Char: 'e']
    # Nav
    - action: [ScrollDown]
      key: [Char: 'j', Down]
    - action: [ScrollUp]
      key: [Char: 'k', Up]
    - action: [PageScrollDown]
      key: [Ctrl: 'f', PageDown, Right, Char: 'l']
    - action: [PageScrollUp]
      key: [Ctrl: 'b', PageUp, Left, Char: 'h']
    - action: [HalfPageScrollDown]
      key: [Char: 'd']
    - action: [HalfPageScrollUp]
      key: [Char: 'u']
  entersearch:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Search]
      key: [Char: "\n"]
    - action: [SearchInput: [27], SwitchToMode: Pane]
      key: [Esc]
  search:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [ScrollToBottom, SearchInput: [27], SwitchToMode: Pane]
      key: [Esc]
    - action: [SwitchToMode: EnterSearch]
      key: [Char: '/']
    # Nav
    - action: [ScrollDown]
      key: [Char: 'j', Down]
    - action: [ScrollUp]
      key: [Char: 'k', Up]
    - action: [PageScrollDown]
      key: [Ctrl: 'f', PageDown, Right, Char: 'l']
    - action: [PageScrollUp]
      key: [Ctrl: 'b', PageUp, Left, Char: 'h']
    - action: [HalfPageScrollDown]
      key: [Char: 'd']
    - action: [HalfPageScrollUp]
      key: [Char: 'u']
    - action: [Search: Down]
      key: [Char: 'n']
    - action: [Search: Up]
      key: [Char: 'N']
    # Actions
    - action: [SearchToggleOption: CaseSensitivity]
      key: [Char: 'c']
    - action: [SearchToggleOption: Wrap]
      key: [Char: 'w']
    - action: [SearchToggleOption: WholeWord]
      key: [Char: 'o']
  session:
    # Common:
    - unbind: true
    - action: [ TogglePaneFrames, SwitchToMode: Normal]
      key: [ Char: " "]
    # Switch Modes
    - action: [SwitchToMode: Pane]
      key: [Esc]
    # Actions
    - action: [Detach]
      key: [Char: 'd']

@har7an
Copy link
Contributor

har7an commented Sep 19, 2022

Alternative VIM bindings

I like your proposal, @samholmes, and it inspired me to give this issue a go myself. You can find the keybindings I came up with attached here: zellij_vim.zip

As proposed here, it greatly reduces the amount of keybinding collisions and offers all zellij features without having to rely on the Alt key. However, Alt is still used for the default quick-navigation keybindings (Alt + <hjkl> and Alt + <+|->) in zellij "Normal" input mode.

Notes on the bindings:

zellij input mode equivalent vim mode
Normal Input
Pane Normal

All other zellij input modes are accessible from the Pane zellij input mode. Generally the "Pane" input mode has become pretty large now, mostly because I added all sorts of goodies to it:

  • Enter "Search" mode directly via '/'
  • Scroll Down/Up via 'Ctrl + <e|y>'
  • Scroll to bottom via 'G'

None of these are displayed in the status bar, though. Also, due to overlaps in the keybinding (The cosmetic additions to display keybindings in the status bar), some keybindings (toggle pane frames/floating panes) aren't displayed, either.

Workarounds for the current state of zellij

There's only a single one I needed so far!

Displaying bindings in the status bar

As you will notice, the status bar (as it is currently implemented) is very picky about when it displays a keybinding hint. That is because it matches for sequences of actions exactly, i.e. it will only display the hint for "New" in Pane mode, when the order of actions in the config is exactly [NewPane: , SwitchToMode: Normal,]. When we adopt a vim-like approach we would of course like to remain in the mode we have previously been, so we do not want SwitchToMode: Normal (Which, for me, is the equivalent to vims 'Input' mode).

My workaround for this limitation: Where appropriate I keep the default binding in the config and use a capital letter to display in the status bar, whereas the real command is bound to the non-capitalized letter instead. In the config, it looks like this:

- action: [NewPane: , SwitchToMode: Normal,]
  key: [Char: 'N',]
- action: [NewPane: ,]
  key: [Char: 'n',]

So the UI tells me to press N, whereas really I have to press n to get the desired effect.

Proposal for a solution: In order to avoid touching the status bar code, it would suffice to add a new Key variant that wraps either Char, or, even better, the Key enum itself (Which would create a recursive enum, see discussion here for the consequences). This can be implemented to display as the inner Char, but not have any effect otherwise. In other words: We could add a new variant and call it, say, UI, which wraps a Char (UI(Char)), and cannot ever be constructed by means of pressing keys on a keyboard (because it doesn't have a matching key). Instead, it is used solely for the purpose of keeping the status bar populated while adding custom keybindings.

Thoughts on the keybindings

  • I chose 'F1' to go from vim input (zellij "Normal") to vim normal (zellij "Pane") because I don't need that button for anything else and it's sufficiently close to Esc, which is what real vim uses. Along with the default Alt + <*> keybindings, that's the only binding left in normal mode. It's be pretty cool if one day that could be changed to Ctrl + <SPACE> or something similar.
  • Locking is 'F1', too, because I didn't feel like spending an ASCII char for it. This also prevents me from accidentally (un)locking the session.
  • zellij "Resize" mode is bound to 'Ctrl + w' in accordance with resizing in vim. It adds '>' as alias for 'l' and '<' as an alias for 'h'.
  • zellij "Move" mode is bound to 'W' due to a compromise. In regular vim, moving works with the Ctrl + w modifier, but that is also used as prefix for resizing splits (i.e. zellij panes, see 'Resize' mode explanation above). So I went with 'W', because I thought in this mode you shift panes around (Hence it's 'Shift + w', i.e. 'W'). Most importantly, this keeps the separate "Move" mode and hence also displays the keybinding hints there.
  • zellij "Search" mode is bound to 's' mostly for the sake of completeness. Maybe you won't need it because simple scrolling works in "Pane" mode (linewise via Ctrl + <e|y>) and entering search is bound to /, too. If you use the "Pane" mode bindings and don't need this mode anyway, you can also drop it.
  • zellij "Session" mode is bound to 'z' (mostly because that's how you suspend commands in bash/zsh)
  • Quitting is bound to 'Z' due to the 'ZZ' binding in vim
  • Use ENTER to leave the most recent mode. I.e. use ENTER to go from "Tab" to "Pane" mode, and hit it again to go from "Pane" to "Normal" mode. Alternatively, use Esc to switch from any zellij mode (except "Normal") to "Pane" mode, and use i to switch from any mode to "Normal" mode.

This maintains all the existing zellij input modes, which is probably helpful for new users to learn how the application works. Also, as stated before, this is needed to display the keybinding hints in the status bar.

Since it always stays in the previous mode, one can now chain actions much more nicely. For example, I can now seamlessly create new panes (via n, r or d), move focus (with <hjkl>) and either rename (c) or close (x) them without having to switch modes all the time. This makes manually "preparing" tab layouts blazing fast.

Generally I think zellij doesn't need a lot of work (code-wise) to adopt even complex alternative key-binding schemes. However, it would probably be neat if the user were allowed to add/remove custom input modes to their liking. That, however, will take a lot of work to implement, especially in the status bar for displaying custom keybindings.

Problems with this keybinding proposal

Due to the additional level of indirection, performing certain actions (Such as quickly moving a pane and going back to "Normal" mode) will require at least one additional keystroke, unless the user binds actions they use frequently to custom keys in e.g. "Pane" mode. However, users often finding themselves preparing a layout by hand or renaming panes/tabs in batches will need much less inputs to achieve their goals with this keymap.

Also, this being inspired by vim, it is likely rather complicated/not intuitive for most users. However, this is offset to a certain degree by the status bar showing key hints.

@imsnif
Copy link
Member Author

imsnif commented Sep 19, 2022

Just so stress - while I'm very happy this discussion is going on, whatever we end up going with must be at least (and hopefully more) as discoverable as the current keybinding scheme.

IMO this would require a change in the plugin(s) - which means it lies behind some technical debt - but I'm open to being surprised on this one if anyone has good ideas on how to do this without.

@samholmes
Copy link

@imsnif A menu in the bottom right corner like Helix's keybindings menu would be a wonderful way to make all bindings discoverable whichever mode you're in.

@imsnif
Copy link
Member Author

imsnif commented Sep 19, 2022

@samholmes - I get what you're saying. I do think something like that will help greatly with discoverability, but I think in our context it will be overkill. I don't want the user to have to read so much just to use the tool.

The intention in Zellij's UI was that at a quick glance you know exactly what you can do in each mode by reading its tips and in general by looking at the mode names. This has degraded a bit as we added new features, which is why I think we need a new approach.

An example I'm toying with is making the modes only applicable to the hjkl/arrows and thus needing much less of them (just pane/tab/move/resize) and then defaulting to the last mode and having some sort of UI-explained toggle that would make this behaviour clear.

This sort of approach can be applied to the other actions as well (eg. a secondary "mode-set" of session operations, pane operations, etc. on the second line of the UI where the tips are now). Just a general half-baked idea to give an example of the direction I'm thinking in.

@samholmes
Copy link

@imsnif When a user has built the muscle-memory for a command, no discoverability is needed not even a subtle one. Only when a user is struggling to remember or is completely new to the tool is discoverability important UX. So, simply hide/show the HUD using ? keybinding.

@imsnif
Copy link
Member Author

imsnif commented Sep 19, 2022

I agree with everything you said up until the last phrase :) For the reasons I mentioned above.

@samholmes
Copy link

samholmes commented Sep 19, 2022

I don't want the user to have to read so much just to use the tool.

The user doesn't need to read a lot to use the tool, only when to discover commands of which they are unaware or cannot remember.

The intention in Zellij's UI was that at a quick glance you know exactly what you can do in each mode by reading its tips and in general by looking at the mode names. This has degraded a bit as we added new features, which is why I think we need a new approach.

A single line doesn't scale for discoverability. A hud will scale for discoverability (especially if it's scrollable; hopefully this wont be necessary though). If you're goal is to have a UI tool for discoverability, then use the right UI tool. If the user is not trying to discover, but then they may either be either "searching" or "using". Of course if they're using, no tips are need, so UI should be gone if its invasive (though the HUD is not all that invasive; leave it up to the user to decide), and this can be handled with ?. If they're "searching" then documentation is ideal for this user story.

I hope that makes sense.

EDIT: Case in point, do you read the commands after learning them, or do you learn the commands and then never look at the toolbar? Only for the commands you don't know or cannot remember do you need to look at the toolbar.

@imsnif
Copy link
Member Author

imsnif commented Sep 19, 2022

Thanks for your thoughts @samholmes - my opinion on this remains as stated.

@Zykino
Copy link
Contributor

Zykino commented Oct 4, 2022

I think my 2cts can come in this issue (even if I did not read all keybindings proposals):

Make Zellij auto lock itself when "certain apps" are loaded/used.

Maybe the lock should be different and a Ctrl+g could unlock Zellij for only one keybinding for example, a bit like tmux prefixes when you are in an application.
I think of this since I like this "classics" keybindings but you will always have clash with whatever the shame you come up with. I think in the default list we can add all modal editors (vi, vim, kakoune, helix) and maybe other apps. In kakoune I want to use Ctlr+n to select the following entry and in helix I want to use Alt+arrows more. Both are also used by Zellij.

This list should be easy to add/remove programs from the user config. Maybe not override the default autoLock list but addAutoLock and removeAutoLock configurations options should be exposed.

@barrykp
Copy link

barrykp commented Oct 24, 2022

I tried out zellij today and this problem is what will prevent me from adopting it. The keybindings overlap too much with my text editor (vim), and some of them are intercepted by the operating system (MacOS makes alt + anything painful to use in a terminal). And the locking mechanism isn't a very good solution.

Screen and tmux both use a single command-prefix binding that minimizes overlap with other applications. My opinion is that that should be an available option.

@imsnif
Copy link
Member Author

imsnif commented Oct 24, 2022

Screen and tmux both use a single command-prefix binding that minimizes overlap with other applications. My opinion is that that should be an available option.

It is. If you like you can remove all keybindings from the config and just stay with tmux mode.
You can of course also remap the keybindings that collide with your setup.

I agree that the defaults should be better, but we do have options. :)

@barrykp
Copy link

barrykp commented Oct 24, 2022

Thank you for the fast response!

It is. If you like you can remove all keybindings from the config and just stay with tmux mode. You can of course also remap the keybindings that collide with your setup.

OK I can see that the default tmux prefix has some associated functionality, which is nice, but it doesn't solve the main problem, which is that all of the other bindings are still in effect.

I didn't create a config file (there's nothing in ~/.config) or bind any keys myself. How do I unbind all keybindings? Does enabling tmux mode automatically do this? I don't see it as an option in the Documentation :(

If this is a simple config change then I will definitely try it out. If it's not a simple "use prefix/tmux mode", then I vote to make it simple that way.

@imsnif
Copy link
Member Author

imsnif commented Oct 27, 2022

Hey @barrykp - I get what you're saying. My solution suggests unbinding all keys except the tmux ones (that's the quickest way, you can also be more creative if you like).

I agree the previous docs left something to be desired though. We've just introduced a new configuration format that makes this much easier and I updated the docs.

The TLDR for what you want is to use the default config and do something like:

keybinds clear-defaults=true {
    // delete everything here
    tmux {
        // ...
    }
}

For more info: https://zellij.dev/documentation/keybindings.html

Personally, I think this is a significantly inferior experience to the default keybindings with some tweaks to avoid those that collide for you in your workflow, but to each their own :)

@barrykp
Copy link

barrykp commented Oct 27, 2022

Thank you for the explanation (and updating the documentation). It looks like I will have to do an update to take advantage of it. I will give it a try!

Personally, I think this is a significantly inferior experience to the default keybindings with some tweaks to avoid those that collide for you in your workflow, but to each their own :)

This can be a lot of work. My OS (macOS) interferes with some keybindings. My terminal application (iterm) might interfere with some (it might now, but at least I don't have to check). The shell I use (bash) uses emacs-style keybindings (especially important for searching). And my editor is vim. Practically all of the default keybindings collide with something. And finding alternatives isn't easy. Choosing is hard, especially when you can't be sure if some app doesn't use it. Personally, it's easier to remember that the multiplexer occupies exactly one keybinding, than to try and cater for everything I might want to do in a terminal in the config.

But it's good to support different options for different preferences.

@daniel-heater-imprivata

I am trying to map @samholmes "less' mode to KDL.. How do I bind 'Ctrl+Space'? I've tried the several mappings from here Null control character

Also, I found that I had 'Ctrl+Space' on my Mac mapped to "select the previous input source" which changed my keyboard language. I unmapped it, but I can't remember if that was a default or if I added it

@samholmes
Copy link

samholmes commented Nov 14, 2022

In my alacratty.yaml:

key_bindings:
  - { key: Space,     mods: Control,   chars: "\x00"                        } # Ctrl + Space

@AndyJado
Copy link

AndyJado commented Dec 5, 2022

I've been using revolver mode for zj modals, namely stuff over modal in ctrl + g`, so that I hit this one key and I get all modes within instinct.

Anyway, ALT in macos is a real pain, since we've lost cmd in the first place..

@wongjiahau
Copy link

wongjiahau commented Jan 31, 2023

Why not follow Tmux, where every command starts with Ctrl-b (which is respected by many famous shell apps, including Vim, which also works on Mac)?

In my current Vim+Tmux workflow, I can easily switch between Shell and Vim using Ctrl-b o.

However, I cannot do the same in Zellij; I have to press Ctrl-g Ctrl-p Right-Arrow Enter to go from Vim to Shell and Ctrl-p Left-Arrow Enter Ctrl-g to go back to Vim from Shell.

Because of this, I did not switch to Zellij, the current classic mode requires too many combos to accomplish something simple.

@imsnif
Copy link
Member Author

imsnif commented Jan 31, 2023

@wongjiahau

Why not follow Tmux, where every command starts with Ctrl-b (which is respected by many famous shell apps, including Vim, which also works on Mac)?

Because it is extremely cumbersome. :)

However, I cannot do the same in Zellij; I have to press Ctrl-g Ctrl-p Right-Arrow Enter to go from Vim to Shell and Ctrl-p Left-Arrow Enter Ctrl-g to go back to Vim from Shell.

You can get Zellij to behave exactly like tmux with a little bit of config (namely using clear-defaults=true on the main keybinds node and only leaving the tmux mode keys).

@rumpelsepp
Copy link

You can get Zellij to behave exactly like tmux with a little bit of config (namely using clear-defaults=true on the main keybinds node and only leaving the tmux mode keys).

Would it be complicated to provide a config option (e.g. tmux-emulation-mode or similar) which does exactly that what you're saying? Then people could just flip that switch.

@imsnif
Copy link
Member Author

imsnif commented Jan 31, 2023

Just to clarify: Zellij supports the tmux keybindings by default. What we're talking about is disabling all the other keybindings on top of that.

That being said, I'm happy to provide this as a config/cli option. If you'd like to open an issue about it, that'll be great.

@rumpelsepp
Copy link

Yeah I got that. Having a config option for this would be easier and might also reduce support queries; I opened #2125. :)

@wongjiahau
Copy link

Just to clarify: Zellij supports the tmux keybindings by default. What we're talking about is disabling all the other keybindings on top of that.

That being said, I'm happy to provide this as a config/cli option. If you'd like to open an issue about it, that'll be great.

@imsnif The tmux keybindings does not work when I'm in Ctrl-g which means it's still not helpful, as I still have to lock/unlock Zellij so that it does not clash with my Vim bindings.

@wongjiahau
Copy link

@wongjiahau

Why not follow Tmux, where every command starts with Ctrl-b (which is respected by many famous shell apps, including Vim, which also works on Mac)?

Because it is extremely cumbersome. :)

However, I cannot do the same in Zellij; I have to press Ctrl-g Ctrl-p Right-Arrow Enter to go from Vim to Shell and Ctrl-p Left-Arrow Enter Ctrl-g to go back to Vim from Shell.

You can get Zellij to behave exactly like tmux with a little bit of config (namely using clear-defaults=true on the main keybinds node and only leaving the tmux mode keys).

@imsnif Sorry but where exactly should I put clear-defaults=true? I'm unfamiliar with KDL, and got parse errors after trying to place clear-defaults=true to the supposed main keybinds node.

@naosense
Copy link
Contributor

Hey @wongjiahau , add this configuration at the top of the config file
image

@Crocmagnon
Copy link

Hello! Just chiming in after seeing the roadmap :)

Please also don't forget non-QWERTY layouts, such as common AZERTY, QWERTZ and their macOS variants 🙏🏻

@max-sixty
Copy link

max-sixty commented Mar 4, 2023

FYI if anyone would like a config consistent with the suggestion above, I've put mine here: https://gist.github.com/max-sixty/6be7225ddc0a9cecb7203d5f7fb4c8d3

From the comments at the top:


This Zellij config offers a more modal experience than the default. There are very few keybindings in the "modal mode", and we hit C-space to enter a different mode where we can navigate.

The only keybindings in the normal mode are:

  • C-space to go into the tmux mode
  • C-n for a new pane
  • C-t for a new tab
  • A-[ & A-] to go to the previous/next tab
  • S-arrow to navigate between panes

@bmikaili
Copy link

So I skimmed over the options above but it's still unclear to me what the suggested way of using this for macos users is? Is there one recommended config so macOS users can use alt+ keybindings?

@mslalith
Copy link

Try using Kitty terminal emulator on macOS, you should be able to use Alt keybindings. Although I only tried Alt + n 😉

@utkarshgupta137
Copy link

Alacritty also supports this with the option_as_alt config since v0.12
Bonus: If you want to use Cmd shortcuts, just forward them to alt:

        { key: Key1, mods: Command, chars: "\x1b1" }

@milanglacier
Copy link

milanglacier commented May 16, 2023

This is some excerpt of my configs which I am so satisfied, these configs perfectly solves some of the painful points for the default keybindings of zellij (at least to me):

  1. you can never send a real control g to the terminal (does not need to be Control g, but whatever the lock mode key).
  2. nested zellij session is painful where so many keys are blocked by the upper level zellij session since you can never send a real control g to the inner level to lock the inner session.

Note that I changed all the Control prefix to alt prefix, i.e. A-g for lock mode, A-t for tab mode, A-p to pane mode, etc.

    locked {
        bind "Alt g" { SwitchToMode "Normal"; }
    }

    normal {
        bind "Alt g" { SwitchToMode "Locked"; }
    }

    pane {
        bind "Alt p" { Write 27 7024; SwitchToMode "Normal"; } // send a real Alt+p to the terminal
    }

    move {

        bind "Alt m" { Write 27 7021; SwitchToMode "Normal"; } // send a real Alt+m to the terminal
    }

    session {
        bind "Alt o" { Write 27 7023; SwitchToMode "Normal"; } // send a real Alt+o to the terminal
    }

    tab {
        bind "Alt t" { Write 27 7028; SwitchToMode "Normal"; } // send a real Alt+t to the terminal
    }


    shared_except "locked" {
        bind "Ctrl a" { SwitchToMode "Tmux"; }
        bind "Alt p" { SwitchToMode "Pane"; }
        bind "Alt r" { SwitchToMode "Resize"; }
        bind "Alt s" { SwitchToMode "Scroll"; }
        bind "Alt o" { SwitchToMode "Session"; }
        bind "Alt t" { SwitchToMode "Tab"; }
        bind "Alt m" { SwitchToMode "Move"; }
        bind "Alt g" { Write 27 7015; SwitchToMode "Normal"; } // send a real Alt+g to the terminal
    }

    shared_except "normal" "locked" {
        bind "Enter" "Esc" { SwitchToMode "Normal"; }
    }

In this setting, in any mode other than lock mode and normal mode, press A-g (I use A-g instead of C-g as my lock mode key) will send a real A-g to the terminal, which also makes lock the inner level zellij session possible, all you need to do is to press Alt-t Alt-g or Alt-m Alt-g (i.e. one more key stroke).

You also do not need to enter the lock mode of the zellij to send a real A-t/A-m (i.e. mode prefix key) into the terminal, just press A-t, A-m, etc. twice. This also makes entering the pane/move/tab mode of the inner level zellij session easier, you do not need to lock the outer level zellij session, just press A-t A-t twice to enter it.

@utkarshgupta137
Copy link

I agree, I really think alt keys are the way to go. Most major terminals like Alacritty, WezTerm, iTerm2, & Kitty support it on macOS too.
Most TUIs I've tried rarely use alt keys for any functionality. On macOS at least, the option keys were unused inside the terminal.
This is my config & I'm really satisfied with Zellij now: https://github.com/utkarshgupta137/nix-config/blob/main/assets/zellij/config.kdl

@milanglacier
Copy link

milanglacier commented May 17, 2023

I agree, I really think alt keys are the way to go. Most major terminals like Alacritty, WezTerm, iTerm2, & Kitty support it on macOS too.
Most TUIs I've tried rarely use alt keys for any functionality. On macOS at least, the option keys were unused inside the terminal.
This is my config & I'm really satisfied with Zellij now: https://github.com/utkarshgupta137/nix-config/blob/main/assets/zellij/config.kdl

The important thing is, you must have the ability to send any key sequence to the tty session running in the zellij session, which also includes the lock mode key.

Without this, you can’t lock the zellij session in the inner level zellij session (if you are running the nested zellij session), or if you just want to send the key sequence of the lock mode key to the tty. All of them cause lots of trouble.

The "Write" action documented in the zellij config requires some hack to make this possible. It takes me some time to figure out 27 7015 is the corrsponding byte sequence of Alt + g which the tty running in the zellij session can correctly interpret.

@utkarshgupta137
Copy link

I'm not sure where 7015 is coming from, but it should be pretty easy to figure out the bytes you need to achieve an input,

You need to refer to the ASCII table: https://en.wikipedia.org/wiki/ASCII#Control_code_chart
Look at the caret notation in this table, for eg., Ctrl + h corresponds to 8. So you could use Write 8 to pass Ctrl + h to the nested session.

For passing alt keys, we need to use the Escape key, which is 27 in the table. After that, if we want to send g, which has the value of 103, you can use Write 27 103.

I added bind "Alt Z" { Write 27 122; } (122 is z) to my config & I was able to lock the inner Zellij session using Alt + Z & the outer using Alt + z as usual.

@danielyrovas
Copy link

I'm gonna put my keybinds that I've setup here, because I believe they are intuitive and better than the default. They are based on the alt-centric layout under examples. I use WezTerm and I think by default the option key is mapped to Alt.

config.kdl
keybinds clear-defaults=true {
    shared_except "locked" "normal" {
        bind "Ctrl c" "Ctrl d" " Enter" "Esc" "Space" "Backspace" { SwitchToMode "normal"; }
    }
    pane {
        bind "h" "Left" { NewPane "Left"; SwitchToMode "normal"; }
        bind "l" "Right" { NewPane "Right"; SwitchToMode "normal"; }
        bind "j" "Down" { NewPane "Down"; SwitchToMode "normal"; }
        bind "k" "Up" { NewPane "Up"; SwitchToMode "normal"; }
        bind "p" { SwitchFocus; }
        bind "n" { NewPane; SwitchToMode "normal"; }
        bind "x" { CloseFocus; SwitchToMode "normal"; }
        bind "f" { ToggleFocusFullscreen; SwitchToMode "normal"; }
        bind "z" { TogglePaneFrames; SwitchToMode "normal"; }
        bind "r" { SwitchToMode "RenamePane"; PaneNameInput 0;}
        bind "w" { ToggleFloatingPanes; SwitchToMode "Normal"; }
        bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "Normal"; }
    }
    tab {
        bind "h" "Left" { GoToPreviousTab; }
        bind "l" "Right" { GoToNextTab; }
        bind "Alt n" "n" { NewTab; SwitchToMode "normal"; }
        bind "x" { CloseTab; SwitchToMode "normal"; }
        bind "s" { ToggleActiveSyncTab; }
        bind "r" { SwitchToMode "RenameTab"; TabNameInput 0; }
        bind "b" { BreakPane; SwitchToMode "Normal"; }
        bind "]" { BreakPaneRight; SwitchToMode "Normal"; }
        bind "[" { BreakPaneLeft; SwitchToMode "Normal"; }
        bind "1" { GoToTab 1; }
        bind "2" { GoToTab 2; }
        bind "3" { GoToTab 3; }
        bind "4" { GoToTab 4; }
        bind "5" { GoToTab 5; }
        bind "6" { GoToTab 6; }
        bind "7" { GoToTab 7; }
        bind "8" { GoToTab 8; }
        bind "9" { GoToTab 9; }
        bind "Tab" { ToggleTab; }
    }
    resize {
        bind "h" "Left" { Resize "Left"; }
        bind "j" "Down" { Resize "Down"; }
        bind "k" "Up" { Resize "Up"; }
        bind "l" "Right" { Resize "Right"; }
        bind "Alt =" { Resize "Increase"; }
        bind "Alt +" { Resize "Increase"; }
        bind "Alt -" { Resize "Decrease"; }
        bind "Alt n" { NewPane; }
    }
    move {
        bind "h" "Left" { MovePane "Left"; }
        bind "j" "Down" { MovePane "Down"; }
        bind "k" "Up" { MovePane "Up"; }
        bind "l" "Right" { MovePane "Right"; }
        bind "Alt n" { NewPane; }
    }
    scroll {
        bind "e" { EditScrollback; SwitchToMode "normal"; }
        bind "Alt c" { ScrollToBottom; SwitchToMode "normal"; }
        bind "j" "Down" { ScrollDown; }
        bind "k" "Up" { ScrollUp; }
        bind "Alt f" "PageDown" "Right" "l" { PageScrollDown; }
        bind "Alt b" "PageUp" "Left" "h" { PageScrollUp; }
        bind "d" { HalfPageScrollDown; }
        bind "u" { HalfPageScrollUp; }
        bind "s" { SwitchToMode "entersearch"; SearchInput 0; }
    }
    search {
        bind "Alt s" { SwitchToMode "normal"; }
        bind "s" { SwitchToMode "entersearch"; SearchInput 0; }
        bind "n" { Search "Down"; }
        bind "p" { Search "Up"; }
        bind "c" { SearchToggleOption "CaseSensitivity"; }
        bind "w" { SearchToggleOption "Wrap"; }
        bind "o" { SearchToggleOption "WholeWord"; }
    }
    entersearch {
        bind "Enter" { SwitchToMode "search"; }
        bind "Alt c" "Esc" { SearchInput 27; SwitchToMode "scroll"; }
    }
    session {
        bind "d" { Detach; }
        bind "q" { Quit; }
        bind "s" {
            LaunchOrFocusPlugin "zellij:session-manager" {
                floating true
                move_to_focused_tab true
            };
            SwitchToMode "Normal"
        }
    }
    renametab {
        bind "Esc" "Ctrl c" { UndoRenameTab; SwitchToMode "normal"; }
    }
    renamepane {
        bind "Esc" "Ctrl c" { UndoRenamePane; SwitchToMode "normal"; }
    }
    // locked {}
    // tmux {}
    // normal {}
    shared_except "locked" {
        bind "Alt f" { ToggleFocusFullscreen; SwitchToMode "normal"; }
        bind "Alt w" { ToggleFloatingPanes; SwitchToMode "normal"; }
        bind "Alt q" { Detach; }
        bind "Alt p" { SwitchToMode "pane"; }
        bind "Alt r" { SwitchToMode "resize"; }
        bind "Alt t" { SwitchToMode "tab"; }
        bind "Alt s" { SwitchToMode "scroll"; }
        bind "Alt m" { SwitchToMode "move"; }
        bind "Alt o" { SwitchToMode "session"; }
        bind "Alt n" { NewPane; }
        bind "Alt h" { MoveFocusOrTab "Left"; }
        bind "Alt l" { MoveFocusOrTab "Right"; }
        bind "Alt j" { MoveFocus "Down"; }
        bind "Alt k" { MoveFocus "Up"; }
        bind "Alt =" { Resize "Increase"; }
        bind "Alt -" { Resize "Decrease"; }
        bind "Alt [" { PreviousSwapLayout; }
        bind "Alt ]" { NextSwapLayout; }

        bind "Alt 1" { GoToTab 1; SwitchToMode "normal"; }
        bind "Alt 2" { GoToTab 2; SwitchToMode "normal"; }
        bind "Alt 3" { GoToTab 3; SwitchToMode "normal"; }
        bind "Alt 4" { GoToTab 4; SwitchToMode "normal"; }
        bind "Alt 5" { GoToTab 5; SwitchToMode "normal"; }
        bind "Alt 6" { GoToTab 6; SwitchToMode "normal"; }
        bind "Alt 7" { GoToTab 7; SwitchToMode "normal"; }
        bind "Alt 8" { GoToTab 8; SwitchToMode "normal"; }
        bind "Alt 9" { GoToTab 9; SwitchToMode "normal"; }
    }
}

@JokerQyou
Copy link

The default Ctrl+t for tab conflicts with any terminal emulator with tab support, like Konsole, Yakuake.

@towry
Copy link

towry commented Dec 6, 2023

The default Ctrl+t for tab conflicts with any terminal emulator with tab support, like Konsole, Yakuake.

You can use keybinds clear-defaults=true { to clear the default key mappings and create your own.

@oredaze
Copy link

oredaze commented Jan 6, 2024

While I don't think defaults are all that important for people who use advanced tools like zellij (because they should be smart enough to configure), I still feel like pointing out that Alt is much more sensible than Ctrl because most things don't use that key, so no conflicts. Yes MAC users may have problems, but I consider the Ctrl conflicts a bigger friction point.

Maybe ask MAC users at first run how to deal with it, like use a MAC specific sample config file.

@kristiangronberg
Copy link

I'm on a Mac and will try to use the following setup:

  • Karabiner with "Complex Modification": Change Spacebar to left_shift. I'm then changing the left_shift to be left_option instead.
  • Then in Alacritty do I set: option_as_alt = "OnlyLeft"

With this do I have Alt on Space (when holding down spacebar) and will see if I can remap the Zellij Ctrl keys to Alt. This could "fail" if it turns out that it is too annoying not to have the possibility of holding down spacebar to get a lot of spaces...

Apart from the keybinding dilemma (on a mac :) ) is this a really nice project. Thank you!

@Zykino
Copy link
Contributor

Zykino commented Mar 30, 2024

Not sure about all your Mac stuffs, but at least an Alt based setup is possible: https://github.com/zellij-org/zellij/blob/main/example/alt-centered-config.kdl (Take care the file is old and the config may have changed since, prefer to dump the config, see: https://zellij.dev/documentation/configuration)

@kristiangronberg
Copy link

Thank you @Zykino . I will use alt-centered config as inspiration.

@kristiangronberg
Copy link

kristiangronberg commented Mar 30, 2024

I took the advise from the alt-centered config and ended up with something which worked. The "shared_excerpt" did end up like this:

    shared_except "locked" {
        bind "Alt g" { SwitchToMode "Locked"; }
        bind "Alt d" { Detach; }
        bind "Alt q" { Quit; }
        bind "Alt n" { NewPane; }
        bind "Alt w" { CloseFocus; SwitchToMode "Normal"; }
        bind "Alt f" { ToggleFocusFullscreen; SwitchToMode "Normal"; }
        bind "Alt h" "Alt Left" { MoveFocusOrTab "Left"; }
        bind "Alt l" "Alt Right" { MoveFocusOrTab "Right"; }
        bind "Alt j" "Alt Down" { MoveFocus "Down"; }
        bind "Alt k" "Alt Up" { MoveFocus "Up"; }
        bind "Alt =" "Alt +" { Resize "Increase"; }
        bind "Alt -" { Resize "Decrease"; }
        bind "Alt [" { PreviousSwapLayout; }
        bind "Alt ]" { NextSwapLayout; }
    }

I think an improvement this the Mac keybinding could be to use, what is commonly called as, the "Hyper" key. It is a combination of all the modifier keys. Since all the modifiers keys are rarely (or never) used together, can this "Hyper" key then become another virtual modifier key. There is an app for this https://hyperkey.app . I think a lot of people are using Karabiner Elements for this as well.

If it was possible to use a "Hyper" key, then could we leave the "Option" key alone, which I think would be the better long term solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ease of use Issues where zellij has clunky or difficult to use behaviours
Projects
None yet
Development

No branches or pull requests