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

Binding modes/binding submaps #846

Open
YaLTeR opened this issue Dec 8, 2024 · 3 comments
Open

Binding modes/binding submaps #846

YaLTeR opened this issue Dec 8, 2024 · 3 comments
Labels
enhancement New feature or request

Comments

@YaLTeR
Copy link
Owner

YaLTeR commented Dec 8, 2024

The ability to configure multiple binding modes with separate binds.

An example use-case is a "launcher" mode, where e.g. you press Super+D, then instead of the normal binds, you have binds to run applications, like F for firefox, N for nautilus, T for terminal, etc. So you press Super+D+F to run firefox.

There's a WIP implementation for binding modes: #532. The author's GitHub account had disappeared, but I remember about the PR and will get to it at some point.

@YaLTeR YaLTeR added the enhancement New feature or request label Dec 8, 2024
@mastoca
Copy link

mastoca commented Dec 8, 2024

xmonad's approach for subkeys is interesting which I used in many ways. for example I use it to bring up the gridSelect module

       ...
        ^++^ subKeys
          "Grid Select (Super-g followed by a key)"
          [ ("M1-g g", addName "grid select some bookmarks" $ spawnSelected' myBookmarkGrid),
     ...

@bbb651
Copy link
Contributor

bbb651 commented Jan 14, 2025

In my opinion instead of binding modes we should allow bindings to have multiple keys (like in @mastoca's example). This is more intuitive and matches other existing software (e.g. vim/neovim, vscode, zed, emacs, ghostty). They are both identical in functionally since they just represent a state machine, but multikey syntax lets bindings be local which is good for managing and sharing configs, and hands off the task of building the state machine to niri.

I've started working on this, I've done the parsing and the syntax works really nicely by (ab)using arguments:

Mod+G G cooldown=150 {
    // ...
}
Code
        let keys =
            iter::once(node.node_name.parse::<Key>().map_err(|e| {
                DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind"))
            }))
            .chain(node.arguments.iter().map(|val| {
                match &*val.literal {
                    knuffel::ast::Literal::String(s) => Ok(s),
                    _ => Err(DecodeError::unexpected(
                        &val.literal,
                        "argument type",
                        "expected argument to be a string",
                    )),
                }?
                .parse::<Key>()
                .map_err(|e| {
                    DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind"))
                })
            }))
            .collect::<Result<Vec<Key>, DecodeError<_>>>()?;

This requires reworking bindings to a state machine, I've currently going with a naive approach of storing the index of the "active key" for each bind incrementing it when a key matches (going from longest to shortest sequence to allow bindings that end with the entire sequence of keys of another bind) and resetting all the indices once a bind is triggered, we can switch to a trie if this turns out to be a performance issue.
I currently need to refactor everything that touches mods_with_binds to use the state machine.

I'm not sure what should happen when an unrelated key is pressed while a multikey binding is ongoing, should it:

  • Reset binding state, send the key?
  • Reset binding state, supress the key?
  • Keep binding state, send the key?
  • Keep binding state, suppress the key? Probably not, easy to get stuck in a binding

We also need to think about how this interacts with release binds.

Edit: I thought about it a bit more and this doesn't allow expressing modal bindings very well, you can simulate an insert mode with I Escape set to noop if we go with the "keep binding state, send key" option, but now we have to make a bind for every single key, whereas binding modes can feasibility have an option per mode to disable input. It also means binding sequences cannot start while binding sequences are already in progress (so the binding L doesn't trigger while inside I Escape) but that probably makes sense anyways. The biggest limitation is that you cannot trigger a binding inside such a mode without exiting that mode... I don't think they are both mutually exclusive but it feels a bit redundant if it's strictly less powerful

@YaLTeR
Copy link
Owner Author

YaLTeR commented Jan 15, 2025

Hmm. What I don't like about this approach is that it's infectious in a way: you put one multi-key bind somewhere in your config (and forget about it), and suddenly pressing the first key just stops doing anything, or starts working with a big latency (the timeout). On the other hand, binding modes are much more explicit about this. You can also be explicit about the unrelated keys, since probably in different cases it makes sense to do different things about them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants