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

Bind/run multiple actions at once #914

Open
YaLTeR opened this issue Jan 1, 2025 · 8 comments
Open

Bind/run multiple actions at once #914

YaLTeR opened this issue Jan 1, 2025 · 8 comments
Labels
enhancement New feature or request

Comments

@YaLTeR
Copy link
Owner

YaLTeR commented Jan 1, 2025

Allow binds to run several actions sequentially, e.g.:

Mod+G {
    focus-column-right
    consume-or-expel-window-left
}

And, more importantly, add some way to run several actions as a batch through IPC and niri msg action. This will allow doing several actions as one atomic sequence and with no delay in-between, which is currently impossible.

Here the binds and the IPC part should be straightforward, but I'm not sure how to parse the arguments in niri msg action. Maybe clap offers something?

Another question is what to do if only one action in the sequence fails a check like allow-when-locked. Here I guess we ignore just the action that fails the check, same as what would happen if you were to run several niri msg action commands one after another.

@YaLTeR YaLTeR added the enhancement New feature or request label Jan 1, 2025
@YaLTeR
Copy link
Owner Author

YaLTeR commented Jan 2, 2025

Idea from @calops for CLI:

you could also just have an option to read actions from stdin, where you can just put all of them separated with newlines

Though, I'm not sure if you can run a clap parser on stdin to parse the arguments.

@calops
Copy link
Contributor

calops commented Jan 2, 2025

You should be able to run a clap parser on any string manually. It would have to be a subset of the global parser but it sounds doable.

@bbb651
Copy link
Contributor

bbb651 commented Jan 12, 2025

Clap doesn't support it currently, someone mentioned this as a workaround:

I resorted to collecting std::env::args() into a Vec<String>, iterated over .split("--"), provided the zeroth argument (program name) at index 0 for each split and fed those to MagicalArgs::from_iter. It is bad in the sense that it abolutely requires the presence of -- between subcommands.

In hindsight, it is a rather obvious workaround that would have saved me half a day of frustration. Retains all the nice stuff of structopt and scales until the limits of linux command lines are reached. If only clap/structopt could parse partially and yield the remainder instead of failing with a hardcoded unkown argument error...

This breaks -- and presumably breaks clap_complete (which we don't have yet anyway), but otherwise seems like pretty good workaround.

@calops
Copy link
Contributor

calops commented Jan 12, 2025

This issue only seems to relate to chaining commands in the program arguments. You can still use the exact same workaround you're talking about with the contents of stdin instead of splitting on --. You would still need to tokenize each line but that's fairly simple. We probably don't need anything but handling quotes and then splitting on spaces (or maybe IFS).

@bbb651
Copy link
Contributor

bbb651 commented Jan 18, 2025

I started working on this, I'm currently ignoring SwitchActions and changing Bind to always hold a Vec of actions (I'll look if this has any meaningful performance/memory impact in the end, it feels like a enum of single and multiple actions or SmallVec<1> should be a lot better).

The hotkey overlay is pretty annoying to deal with, when looking for certain actions should it ignore multi-action binds entirely for now since it can't display them? Or prefer single actions over multi-actions, and display only the relevant action for multi-action binds?

For cli, clap doesn't support custom subcommand parsing, it's pretty much the same problem as #965, currently the best workaround I can think of is adding:

#[derive(Subcommand)]
pub enum Msg {
    // ...
    /// Perform an action.
    Action {
        #[command(subcommand)]
        action: Option<Action>,
        #[clap(skip)]
        actions: Vec<Action>,
    },
    // ...
}

To both make clap parse the action and make room to marshal actions, and treat it like a union. Parsing in main will look something like this (doesn't compile yet):

            Sub::Msg { mut msg, json } => {
                if let Msg::Action { action: None, .. } = msg {
                    let stdin = io::stdin();
                    let actions = stdin
                        .map(BufReader::read_line)
                        .map(Action::try_parse_from)
                        .collect();
                    msg = Msg::Action {
                        action: None,
                        actions,
                    }
                }
                handle_msg(msg, json)?;
                return Ok(());
            }

@YaLTeR
Copy link
Owner Author

YaLTeR commented Jan 19, 2025

The hotkey overlay is pretty annoying to deal with, when looking for certain actions should it ignore multi-action binds entirely for now since it can't display them? Or prefer single actions over multi-actions, and display only the relevant action for multi-action binds?

I say ignore multi-action binds for the hotkey overlay.

To both make clap parse the action and make room to marshal actions, and treat it like a union.

That Msg in src/cli.rs is purely for the clap CLI itself, isn't it? Why add a field if it's skipped for clap?

@YaLTeR
Copy link
Owner Author

YaLTeR commented Jan 19, 2025

Maybe instead of that field, add a separate Actions enum variant that does this?

@dyfrgi
Copy link

dyfrgi commented Feb 22, 2025

By giving a couple of options for what to do when one action fails a check, this could be used to replace all the actions like move-window-down-or-to-workspace-down. If we were writing bash, it would be move-window-down || move-window-to-workspace-down. In KDL, that would probably need to be expressed via an or node with children:

Mod+Ctrl+J { or { move-window-down; move-window-to-workspace-down; } }

One nice thing about this is that it should compose well - it could be nested. That would let someone express a pretty complex action. A name like one-of might be better than or. That also allows all-of, or even two-of, though I don't know why someone would want that.

It is possible to use an infix style in KDL, though, it's just... weird. move-window-down or move-window-to-workspace-down winds up being parsed as a Node with two Arguments. & and | are not a non-identifier character, so I suppose you could even write it as move-window-down || move-window-to-workspace-down. It's a bit of an abuse of the syntax, though, and might make writing the parser code a little tricky. I haven't looked at knuffel enough to say whether it could do this in any sort of reasonable way. I don't think that it's worth it.

I suppose another way to do it would be to give the top-level node a property:

Mod+Ctrl+J chaining=or {
  move-window-down
  move-window-to-workspace-down
}

or to have an infix node called or:

Mod+Ctrl+J {
  move-window-down
  or
  move-window-to-workspace-down
}

which could also be expressed shorter:

Mod+Ctrl+J { move-window-down; or; move-window-to-workspace-down; }

I am sure that there's also demand for the "and" mode. Personally, I don't think I'd use the example from the ticket (focus-column-right; consume-or-expel-window-left) unless it would stop after the first one if it couldn't move the focus right. I want my actions to do a thing or not do a thing, not half do a thing.

So far as IPC goes, it would also enable some new things to return the result of an action via IPC. I considered the idea of directly using shell for it, if niri msg action focus-right exited non-zero when there's nothing to focus to the right. niri msg action focus-column-right || niri msg action focus-monitor-right.

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

4 participants