wez / wezterm

A GPU-accelerated cross-platform terminal emulator and multiplexer written by @wez and implemented in Rust
https://wezfurlong.org/wezterm/
Other
16.61k stars 742 forks source link

Disable some keybinds, but only inside a terminal editor #1417

Closed eugenesvk closed 2 years ago

eugenesvk commented 2 years ago

Is your feature request related to a problem? Please describe.

Let's say I've mapped Ctrlw to close a buffer in a terminal editor. However, in WezTerm this closes a tab and is also a very useful shortcut, so I'd like to retain both, but only close a tab when it's not an editor (this types of conflicts also happens with many other keybinds, the above is just one example)

Describe the solution you'd like

I'd like to have a second layer of WezTerm keybinds that would work only inside a tab with a terminal editor running

I understand there is both a global disable_default_key_bindings = true, and local {key="m", mods="CMD", action="DisableDefaultAssignment"}, options for disabling keybinds and allowing he tab-app to take over, but then I don't understand how to make key such assignments context-specific, there doesn't seem to be a field {key="m",process="terminal_editor.exe"...} or something easier that would apply to a group of keybinds rather then to each individual ones

Describe alternatives you've considered

Couldn't think of a good alternative

Additional context

wez commented 2 years ago

Probing the executable is not guaranteed to be accurate or even succeed, and it has overhead that may also involve IPC on some systems, so it's not suitable to bake directly into the input matching layer where we need to be acting with the lowest latency.

You can however put the logic in your config in lua.

With the latest nightly (needed to get the process name in lua), I would suggest using https://wezfurlong.org/wezterm/config/lua/wezterm/action_callback.html to run a lua callback for your key binding, and in there use pane:get_foreground_process_name() to deduce whether an editor is running (caveat: for local panes only).

window:perform_action() can be used to perform key assignment style actions.

Something like this untested config sounds like it would do what you're asking for:

local wezterm = require 'wezterm';

-- Change this to match your heuristics for whether the executable path is an editor
function is_an_editor(name)
   return name:find("vim")
end

return {
  keys = {
    {
      mods = "CTRL",
      key = "w",
      action = wezterm.action_callback(function(win, pane)
        local proc_name = pane:get_foreground_process_name()
        if is_an_editor(proc_name) then
           window:perform_action({SendKey={key="w", mods="CTRL"}})
        else
           window:perform_action(wezterm.action{CloseCurrentPane={confirm=true}})
        end
      end)
    },
  }
}
eugenesvk commented 2 years ago

Probing the executable is not guaranteed to be accurate or even succeed, and it has overhead that may also involve IPC on some systems, so it's not suitable to bake directly into the input matching layer where we need to be acting with the lowest latency

Ok, makes sense that it might not be good on a per-key level, but what about

something easier that would apply to a group of keybinds rather then to each individual ones

These groups/sets of keybinds could be limited to a pane, so you don't need to probe the executable on every keybind, but just probe a paneID to decide, whether to apply a keybind or not Then, to switch the group/set in a pane, you'd need to probe once per some event (e.g. a simple keybind to switch to another group) or something

I checked your example (launch cmd.exe in a single tab, launch an editor inside), and unfortunately it reports the top-level cmd.exe process instead of the child editor process I launch

wez commented 2 years ago

I checked your example (launch cmd.exe in a single tab, launch an editor inside), and unfortunately it reports the top-level cmd.exe process instead of the child editor process I launch

a38757ef3 should improve that

eugenesvk commented 2 years ago

Thanks, will try that later! By the way, would it be easier or at least better to instead pass your awesome LocalProcessInfo object? It has this nice property of supporting the name field, which strips the path out and allows for a more reliable match (what if process path has vim in there? guess I need to parse path to separate the executable)

(also, it would've worked even if the top process were returned (cmd.exe in the example above) since I could've done the recursion myself (just like I did based on your suggestion in another issue on checking before a tab is closed))

wez commented 2 years ago

The name field may be truncated (eg: Linux limits it to 16 characters), or rewritten at runtime depending on the system, or be the name used to invoke the program through a symlink, so it's not a great candidate for process matching.

Using the executable image path is unambiguous.

If you're worried about matching directory structure, my recommendation would be to extract the "basename" from the executable path and match on that. https://github.com/Donearm/scripts/blob/master/lib/basename.lua looks like the right kind of thing, but you'd probably want to augment it for windows backslashes and not just posix forward slashes.

would it be easier or at least better to instead pass your awesome LocalProcessInfo object?

I'd considered doing that, but decided against it:

eugenesvk commented 2 years ago

Thanks for such a thoughtful response, makes sense to get the least amount of info in the most performant way (and it's also not a big deal to parse path, though thanks for the basename tip)

My immediate issue with handling of Ctrlw in a fail-safe manner is resolved thanks to your quick fixes!, though it might be worth it keeping the issue open until a more general solution can be found that would allow you to, e.g., have a WezTerm leader key (+a bunch of keybinds with a leader key) and vim leader key (and its own bunch of keybinds) bound to the same key, but act without a conflict; and likewise apply a rule to a bunch of keybinds without having to change each via this callback mechanism

P.S.

or rewritten at runtime depending on the system, or be the name used to invoke the program through a symlink, so it's not a great candidate for process matching.

:) funny that you mention this symlink behavior, the current fix also shows the path to a scoop's shim instead of the path to the real exe

wez commented 2 years ago

I've added this function to the examples in the docs:

-- Equivalent to POSIX basename(3)
-- Given "/foo/bar" returns "bar"
-- Given "c:\\foo\\bar" returns "bar"
function basename(s)
  return string.gsub(s, "(.*[/\\])(.*)", "%2")
end
eugenesvk commented 2 years ago

I've added my code to the discussions in case anyone is interested https://github.com/wez/wezterm/discussions/1458

Though it might not be truly x-platform, come to think of it, the \ is a valid name character on *nix, so I guess the proper way to do that is only look for \ or / on Windows, where both can be path separators, but only / elsewhere

eugenesvk commented 2 years ago

By the way, is it possible to define the callback function separately and pass it a couple of custom arguments (like the value of mods and key besides the default win and pane)? Then I could use the same function for a couple of close pane keybinds without having to copy&paste the full inlined function only to change win:perform_action( {SendKey={mods="CTRL",key="w"}} , pane)

wez commented 2 years ago

You can define a function that takes those parameters and call that from inside the callback:

function mything(win, pane, something)
   -- something
end

...

wezterm.action_callback(function(win, pane)
   mything(win, pane, "foobar")
end)
eugenesvk commented 2 years ago

Oh, that's the piece I was missing, thanks! (tried to pass args to function(win, pane))

eugenesvk commented 2 years ago

@wez given that the current solution is limited (e.g, doesn't work with the the great multiplexer and requires proc polling every time), should this maybe stay open until a better solution is implemented?

wez commented 2 years ago

As I mentioned in my first comment, this feature request is not suitable to bake into the keymapping layer of wezterm; it is complex and couples keybindings to something that can have unpredictable IPC latency and be a source of non-determinism. The process polling you mentioned is a limitation of Windows itself and there's nothing that wezterm can do to solve that--fundamentally, what you are asking for is not something that the operating system can provide. Coupling an implementation to those shaky foundations is a recipe for support headaches that I do not want to take on.

My general approach for resolving this kind of thing is to expose sufficient scripting ability for users to encode their custom workflow. The trade-off being made here is the scope of the engineering problem: it is too difficult to build exactly what you have asked for and for it to be reliable and supportable across the wide user population of the software. However, by exposing some primitives and a scripting language it is, relatively speaking, an easier engineering project for you to set up something that works for your specific workflow in your config file.

eugenesvk commented 2 years ago

Yout first comment was about my naive suggestion of baking process checks into every keybind. But what about, e.g., adding the functionality to manually switch between groups of keybinds tied to a pane so that after I've launched an editor I could switch to an alternative keybind group that disables most of the terminal's keybinds for this pane and avoid any conflicts), that wouldn't require any process checks at all (not even callbacks) and thus not be OS-limited?

github-actions[bot] commented 1 year ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.