Skip to content
This repository has been archived by the owner on Apr 1, 2020. It is now read-only.

Synchronizing VimScript keybindings. #20

Closed
jordwalke opened this issue Dec 4, 2016 · 25 comments
Closed

Synchronizing VimScript keybindings. #20

jordwalke opened this issue Dec 4, 2016 · 25 comments

Comments

@jordwalke
Copy link

jordwalke commented Dec 4, 2016

For UI interactions such as the popup (command-p) window (and canceling source editor popup menus), how should people configure keyboard mappings? How do they configure these, while also configuring Vim keyboard mapping (for insert/command mode). For example, it might make sense to apply all the cmap mappings while the "command P" window is open.
If cmap doesn't make sense to integrate with, then perhaps a new "mode" for mappings makes sense. But in that case, would users configure 90% of their mappings in Vim, and then 10% of them in ~/.oni/config.json? It would be nice if keyboard mappings could be specified entirely in one place.

Edit: Summarizing thoughts, tradeoffs, observations:

  • No one wants to write VimScript (how can I blame them).
  • Everyone knows JS/JSON so it is preferable.
  • But we don't want a ton of startup time overhead to wait for a JS engine to warm up or wait for latency to cross the language bridge several times (if we can avoid it).
  • We also don't want half our editor config in VimScript and half in JS/JSON long term so if there are two ways to do something it would be nice if there was a migration path to have everything in one language, without paying any performance costs (a suggested approach is ahead of time compiling JS/JSON to VimScript - which also lets you use that same config in any build of Vim).
@bryphe
Copy link
Member

bryphe commented Dec 4, 2016

Good question, it's just hard coded in a poor way right now. My initial thinking was to have a ~/.oni/keybindings.json, and create commands for these, like 'oni.menu.nextItem' with JSON bindings, and get to the point where 99% of the mappings could be done in the json file.

It does have the downside of having two places for bindings, and being foreign to power users. To bridge the gap, there are a few options I was thinking about:

  • Add a way to execute commands from VimScript, like: OniCommand("oni.menu.nextItem") that could be part of key mappings
  • Add some information about the UI state in VimScript, sort of like how pumvisible() works

Long-term, my vision for this project is to make modal editing through neovim more friendly and attractive to users of VSCode/Atom/etc, without having to dive into creating a _vimrc/init.vim... but still retaining much of the flexibility.

Thanks for calling out some ideas here and trying out the app, great to have your feedback!

@jordwalke
Copy link
Author

Long-term, my vision for this project is to make modal editing through neovim more friendly and attractive to users of VSCode/Atom/etc,

Where do I sign?

@jordwalke
Copy link
Author

jordwalke commented Dec 4, 2016

One thought: If there were an efficient way to invoke VimScript commands directly from JavaScript, then I'd be okay with half my configuration being in VimScript, and the other half being in JS because I know that I can eventually port my VimScript setup to JavaScript (including keyboard mappings).

Bonus: The JS configuration can be made to spit out regular VimScript, and then the configuration can be loaded directly from VimScript for better use in regular (non-Oni) Vim (which would also help keep startup performance fast - if computing the pure VimScript configuration ahead of time (and caching it)).

The result is that there would be a path to converting all your vimscript config/bindings to JS without any downsides.

@bryphe
Copy link
Member

bryphe commented Dec 5, 2016

Good point. I believe there would be an efficient way to invoke VimScript commands over the msgpack RPC API, and there's already an entry point via the neovim/node-client. Need to validate the latency though beforehand and make sure that it is actually efficient. Regarding efficiency, eventually it'd be nice to run some input benchmarks, like this article... There is definitely more typing latency in Oni than GVim at the moment for me at the moment, but that's sort of a tangent.

I like the idea of spitting out regular VimScript from json - things like inoremap/noremap could be emitted, so there wouldn't be any additional latency, and it would be relatively straightforward to implement. It gives us both the benefits of built-in performance and portability so that the config can still move freely. It also maps well to configuration options - moving some options like linenumbers, colorscheme, etc from init.vim to a configuration.json could be implemented in the exact same way.

This sounds like the direction we should move forward in - really gives us the best of both worlds.

This is great feedback, thanks for thinking about this!

@Bretley
Copy link
Contributor

Bretley commented Dec 6, 2016

For keybindings then, would it be feasible to make a keybinding interpreter/generator so that people can choose to live in their configuration environment of choice? if there was a way to convert vim keybindings into the JSON/JS usable form and vice verse, people can live in their configuration environment of choice. For most people, it would probably be the init.vim, at least until oni attracts new users on its own.

@jordwalke
Copy link
Author

jordwalke commented Dec 6, 2016

@bert88sta That's a good idea. I'd love to see some examples of what the bidirectional conversion might look like. One challenge is that there may be complex logic in determining what bindings are generated. For example, let's take the case where we use JS to configure via a file myKeyBindings.js

let something = reallyComplicatedExpression ();
let other = reallySlowExpression ();
module.exports = {
  'insertMode': {
     'ctrl-l': something,
     'ctrl-h': other
  }
};

The idea I had was that this file could use arbitrary JS expressions / dependencies to determine the keybindings. That isn't something that's easy to convert into VimScript. It's similar to how many are using CSS in JS. You could take this raw JS object that is returned and turn it into pure VimScript.

The benefit is that we can run myKeyBindings.js ahead of time, convert it to VimScript, and cache the result so that not only are not waiting on a JS engine at startup time. Furthermore, the resulting VimScript is more lightweight than the original myKeyBindings.js because none of the expressions are evaluated.

The same could be done of color schemes, and possibly many other forms of configuration.

When given the full power of the language (JS) to author configuration, it's very expressive and powerful, but it is then impossible to create a bidirectional mapping between VimScript and JS. However, if the results are rendered as regular VimScript, it makes the appeal of bidirectional mapping smaller. The generated VimScript config could work in any existing Vim configuration (even without NeoVim). Yes, it must be authored in JS, but it works in all versions of Vim, and works fast at startup time because all the expensive stuff has been precomputed.

I think it's worth Oni coming up with some conventions early on about which things must be precomputed, and which things can be computed at runtime/startup time. You can put a hard line in the sand that anything that blocks the initial rendering/config of Vim must not have huge JS dependencies or huge computations. That could help keep the "new window" operation blazing fast, while allowing more expensive plugins to be loaded lazily, and asynchronously. I feel it's important to get this right early on before people start making render blocking heavy plugins.

@Bretley
Copy link
Contributor

Bretley commented Dec 6, 2016

@jordwalke

However, if the results are rendered as regular VimScript, it makes the appeal of bidirectional mapping smaller.

I don't follow.

@jordwalke
Copy link
Author

However, if the results are rendered as regular VimScript, it makes the appeal of bidirectional mapping smaller.

It's only my personal opinion, but if you can render to plain VimScript, then the desire to be able to convert from JS to VimScript, and from VimScript to JS seamlessly seems less appealing.
If you can render from JS to VimScript ahead of time, then you can always just convert any JS config to plain VimScript, so that it can play well with other non-Oni based Vims.

@bryphe bryphe mentioned this issue Dec 7, 2016
@Bretley
Copy link
Contributor

Bretley commented Dec 8, 2016

A place to start might be comparing the output of :map to a list of oni keybindings and just looking for conflicts

@Bretley
Copy link
Contributor

Bretley commented Dec 9, 2016

@extr0py : As a start, I'm going to implement actual variables for keys globally... where would I define this? in config.ts? Oni.d.ts?

Any help would be appreciated

@bryphe
Copy link
Member

bryphe commented Dec 10, 2016

Thanks @bert88sta ! Are you thinking like the actual letter keys, or something higher level like commands?

If its the former, we could have a Keys enum that maps to the letter value. If it's the latter, I think we might need to introduce a new concept like 'Commandand reference it likeCommand.autoCompleteNext`.

For key handling, I'm picturing we'll have a couple pieces:

  1. A VIM-command like OniExecute that lets us run Oni-facing commands - like :OniExecute("editor.menu.next"). This means we can do things like imaps and nmaps and have them push back to our VIM layer.
  2. Have NeovimInstance listen for the OniExecute messages and emit them
  3. Have the 'Services' like Format, QuickOpen, etc listen to these commands and react (like send a ui action or something). Some of the existing concepts that are baked into index.tsx, like the menus or autocompletion, should probably get their own editor 'service' to manage the state

That's at least what I was thinking - hope that can help get you started. Thanks for looking at it!

@Bretley
Copy link
Contributor

Bretley commented Dec 10, 2016

@extr0py : I was just thinking of this suggestion

  1. Generalize functions that do things consistently ( UI.genericNext() instead of UI.nextCompletion() or UI.nextFile etc..). By offloading the logic into some boilerplate functions, the index code gets cleaned up.

  2. Because of 1, we can have general-case keybindings, as well as cleaning up some of the logic in browser/src/index. If we got keybindings 1 to 1 with generic functions, we can avoid nasty unclear code and make people like json config since it will be simple.

As for the OniCommand thing, I'll get on that since (unlike TS) I can actually write vimL. Assuming it's efficient to use it, keeping neovim in charge of the things neovim does best is a pretty solid Idea.

@bryphe
Copy link
Member

bryphe commented Dec 10, 2016

The generic functions sound great, makes sense to generalize those.

Regarding the command execution, this file is a good place to start:
https://github.com/extr0py/oni/blob/master/vim/core/oni-core-interop/plugin/init.vim

This is all the special interop logic - the extra things we send over the wire that Neovim doesn't send out of the box. So might be a good place to define that function.

@Bretley
Copy link
Contributor

Bretley commented Dec 10, 2016

alright, so the question becomes how do we add the state info for neovim to get a hold of?
I made the OniExecute function and replaced the "keybinding" in index with one in the init.vim, but as of right now, vim can't know anything about the UI state.

@bryphe
Copy link
Member

bryphe commented Dec 10, 2016

Hmm, ya, we could always send information down to the Neovim layer by executing a command (NeovimInstance has a command method that would be good for this). So we could notify Neovim when a menu is up, autocomplete is up, etc, and set some global variable, like g:oni_ui_menu or something.

We'd probably need to do the following:

  • Define a handler in the default's init.vim to listen for these commands
    • Populate some global variable
  • Call the handler from NeovimInstance.command when the UI state changes

That way, the Vim key bindings can apply depending on the oni state. I think it'd also be good to declare a general global variable like g:is_oni so that users could differentiate in their init.vim if they needed to

@kopischke
Copy link

I think it'd also be good to declare a general global variable like g:is_oni so that users could differentiate in their init.vim if they needed to.

Good idea, but the convention seems to be to respond to has('gui_<name>'): MacVim has gui_macvim, VimR has gui_vimr (see https://github.com/qvacua/vimr/wiki/VimR-MacVim, “Distinguishing VimR and MacVim in vimrc” section).

@Bretley
Copy link
Contributor

Bretley commented Dec 14, 2016

@extr0py:

Is it safe to assume that there's only going to be one "list" up at a time? A.K.A there won't be multiple UI instances that require c-p to navigate simultaneously? If not this is going to be complicated and slow.

@bryphe
Copy link
Member

bryphe commented Dec 14, 2016

@kopischke - good point, thanks! I opened issue #94 to track setting gui_oni to follow that convention.

@bert88sta - IMO, it would be a poor user experience to have multiple menus up, so we should avoid that. And it seems like it might also be unclear to the user what the result of might be in that case, so I don't believe there'd be a case to have multiple menus / list up at one time.

@Bretley
Copy link
Contributor

Bretley commented Dec 14, 2016

@extr0py 👍

That makes my life a bit easier when it comes to the vim <-> oni communication.

@jordwalke
Copy link
Author

jordwalke commented Dec 15, 2016

Could someone summarize the overall architecture for vim keybindings? Is anyone else concerned about impacting the gloriously fast startup time of Vim, by putting large, user configurable JS bundles in the critical load path? This is why I suggested that a more constrained approach be taken for a subset of editor functionality (colorschemes, key bindings, and syntax highlighting), which allows that configuration to compiled ahead of time to VimScript. I understand that no one wants to build that ahead of time converter right now, but it would be nice to take into account the architecture's future potential to be able to do such a thing. Namely - would it require a JavaScript interpreter to know which map commands to execute/send to neovim? I believe that we could even adopt Atom's keybindings json configuration scheme.

@jasonszhao
Copy link
Contributor

Another consequence of the current architecture is that the accelerator option in Electron menus doesn't work.

I was playing with the source, and I was disappointed that adding the accelerator line doesn't do anything (this is modified from Menu.js):

{
    label: 'Undo',
    accelerator: 'CmdOrCtrl+Z',
    click: (item, focusedWindow) => executeVimCommand("u")
}

@keforbes
Copy link
Collaborator

@jasonszhao, I think that's a consequence of #223, due to the discussion in #216.

@badosu
Copy link
Collaborator

badosu commented Nov 23, 2017

@bryphe I guess this is done already, or is there anything to address on this issue?

@bryphe
Copy link
Member

bryphe commented Feb 5, 2018

@badosu - sorry for the late reply! Just going through items now.

@bryphe I guess this is done already, or is there anything to address on this issue?

Yes, although we still have further work to refine our input model, I believe we can close this and tracking remaining issues.

I drafted up some of the motivation and architecture behind our current input model here: Architecture: Input Management, and we still have several issues tracking enhancements (chorded key bindings, more robust resolution, config improvements)

@bryphe
Copy link
Member

bryphe commented Feb 5, 2018

I'll close this out, and we can continue to track the outstanding work in the other issues:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants