pgcli icon indicating copy to clipboard operation
pgcli copied to clipboard

[Feature] Allow to configure additional vi mode Escape mapping

Open fogine opened this issue 7 years ago • 13 comments

Description

GNU read line allows to globally map jj key sequence to ESC for quick insert mode -> command mode transition.
I'm not aware of any global configuration for prompt_toolkit.

Your environment

> pgcli --version  

Version: 1.7.0

> uname -a  

Linux myuser 4.11.9-1-ARCH #1 SMP PREEMPT Wed Jul 5 18:23:08 CEST 2017 x86_64 GNU/Linux

Currently I've patched the key_bindings.py like so:

diff --git a/pgcli/key_bindings.py b/pgcli/key_bindings.py                                                                                                                                                                                    
index 646e6f7..b623c62 100644
--- a/pgcli/key_bindings.py
+++ b/pgcli/key_bindings.py
@@ -2,7 +2,8 @@ import logging
 from prompt_toolkit.enums import EditingMode
 from prompt_toolkit.keys import Keys
 from prompt_toolkit.key_binding.manager import KeyBindingManager
-from prompt_toolkit.filters import Condition
+from prompt_toolkit.key_binding.input_processor import KeyPress
+from prompt_toolkit.filters import Condition, ViInsertMode
 from .filters import HasSelectedCompletion
 
 _logger = logging.getLogger(__name__)
@@ -22,6 +23,14 @@ def pgcli_bindings(get_vi_mode_enabled, set_vi_mode_enabled):
         enable_search=True,
         enable_abort_and_exit_bindings=True)
 
+    @key_binding_manager.registry.add_binding('j', 'j', filter=ViInsertMode())
+    def _(event):
+        """
+        Typing 'jj' in Insert mode, should go back to navigation mode.
+        """
+        _logger.debug('Detected jj keys.')
+        event.cli.input_processor.feed(KeyPress(Keys.Escape))
+
     @key_binding_manager.registry.add_binding(Keys.F2)
     def _(event):
         """

fogine avatar Jul 25 '17 13:07 fogine

Can we do a more general solution to that -- to enable any arbitrary key bindings (in vi mode or not) from a config file? PtPython allows it but its config file is a python file, so you can simply put your mappings as they are like in the proposed solution. In pgcli's case I am not sure where would be the place of these mappings though.

I'll be happy to implement it when I have time as soon as we agree on how would the user specify their key bindings.

arturbalabanov avatar Apr 27 '18 12:04 arturbalabanov

The new version uses prompt_toolkit 2.0, which no longer uses KeyBindingManager. Any idea on how to hack key_bindings.py to allow for custom vi mode key bindings? Or does the 2.0 version of promt_toolkit have a readline-style configuration for vi mode?

cswingle avatar Oct 06 '18 16:10 cswingle

Answering my own question, here's a patch that adds a jk binding to switch to vi normal/navigation mode. It's got no testing to see if the user is even in vi mode, so use at your own risk.

diff --git a/pgcli/key_bindings.py b/pgcli/key_bindings.py
index f1eaaa39..0e21904c 100644
--- a/pgcli/key_bindings.py
+++ b/pgcli/key_bindings.py
@@ -4,6 +4,7 @@ import logging
 from prompt_toolkit.enums import EditingMode
 from prompt_toolkit.key_binding import KeyBindings
 from prompt_toolkit.filters import completion_is_selected
+from prompt_toolkit.key_binding.vi_state import InputMode

 _logger = logging.getLogger(__name__)

@@ -12,6 +13,12 @@ def pgcli_bindings(pgcli):
     """Custom key bindings for pgcli."""
     kb = KeyBindings()

+    @kb.add('j', 'k')
+    def _(event):
+        """vi Normal mode."""
+        _logger.debug('Detected jk keystroke.')
+        event.cli.vi_state.input_mode = InputMode.NAVIGATION
+
     @kb.add('f2')
     def _(event):
         """Enable/Disable SmartCompletion Mode."""

cswingle avatar Oct 06 '18 17:10 cswingle

@jonathanslenders , do you have any advice here?

j-bennet avatar Oct 06 '18 21:10 j-bennet

Any updates on the progress of this feature? The above patch seems to work as a temporary fix.

I found this project a few hours ago and I am loving it!

aste4 avatar Nov 24 '19 00:11 aste4

Would maintainers be interested in merging this patch?

rightaway avatar Apr 22 '20 11:04 rightaway

Providing a way to override specific features in pgcli is one thing, but overriding how vi keybindings are handled in pgcli seems sufficiently obscure that I'm not sure it is worth adding complexity to the code base.

I wonder if we can have a prompt-toolkit level configuration that will apply to all apps that use prompt-toolkit as readline replacement. @jonathanslenders Thoughts?

amjith avatar Apr 22 '20 16:04 amjith

This is a feature that can be configured in ptpython.

Ideally if you have a Python configuration file, it should be possible to define a function in that file (lets call it def custom_bindings(), which returns a prompt_toolkit.key_binding.Keybindings object.

These key bindings can then be merged into the main pgcli key bindings like this: https://python-prompt-toolkit.readthedocs.io/en/master/pages/advanced_topics/key_bindings.html#merging-key-bindings

The configuration function should then look something like this:

from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.key_binding.input_processor import KeyPress
from prompt_toolkit.filters import vi_insert_mode

def create_key_bindings():
    bindings = KeyBindings()

    @bindings.add('j', 'j', filter=vi_insert_mode)
    def esc(event):
        event.cli.input_processor.feed(KeyPress(Keys.Escape))

    return bindings

In the ptpython config, you can see that the key bindings are registered in a KeyBindings object that we already have. It was a decision I made back then, but I think it's not really better. Something else you can see in the ptpython config is that the key bindings have access to the "repl" object. That's the main application object. That's useful, because then the custom bindings can change about anything to the application.

One question maybe for everyone: would it make sense to have a global .prompt_toolkit.config.py file or something like this, were these custom bindings could be configured for every prompt_toolkit application. Similar to .inputrc for all GNU readline applications? (edit: of course, key bindings in the global configuration file don't have access to application specific objects.)

jonathanslenders avatar Apr 22 '20 20:04 jonathanslenders

@jonathanslenders

One question maybe for everyone: would it make sense to have a global .prompt_toolkit.config.py file or something like this, were these custom bindings could be configured for every prompt_toolkit application. Similar to .inputrc for all GNU readline applications?

For my part that would be optimal, and I think it's pretty likely that anyone going to the trouble to configure a vi_insert_mode key combination for one application is going to want it for every application using prompt_toolkit, just like the way it works with readline.

cswingle avatar Apr 22 '20 20:04 cswingle

Yes, global configuration would be great as I patch multiple projects that use prompt_toolkit, eg. mycli as well..

fogine avatar Apr 22 '20 22:04 fogine

@jonathanslenders This function would be in its own standalone file?

And in which file do I put the merging function from the link?

rightaway avatar Apr 23 '20 06:04 rightaway

Could anyone please explain how to get this to work?

rightaway avatar May 06 '20 07:05 rightaway

You'll need to edit key_binding.py

After modifying the above examples (btw thanks everyone for sharing those snippets), the following worked for me: The changes appear to be necessary due to updates to the prompt-toolkit library.

diff --git a/pgcli/key_bindings.py b/pgcli/key_bindings.py
index 23174b6..9437a83 100644
--- a/pgcli/key_bindings.py
+++ b/pgcli/key_bindings.py
@@ -1,7 +1,10 @@
 import logging
 from prompt_toolkit.enums import EditingMode
+from prompt_toolkit.keys import Keys
 from prompt_toolkit.key_binding import KeyBindings
+from prompt_toolkit.key_binding.key_processor import KeyPress
 from prompt_toolkit.filters import (
+    ViInsertMode,
     completion_is_selected,
     is_searching,
     has_completions,
@@ -124,4 +127,12 @@ def pgcli_bindings(pgcli):
         """Move down in history."""
         event.current_buffer.history_forward(count=event.arg)

+    @kb.add("k", "j", filter=ViInsertMode())
+    def _(event):
+        """
+        Typing 'kj' in Insert mode, should go back to navigation mode.
+        """
+        _logger.debug('Detected kj keys.')
+        event.cli.key_processor.feed(KeyPress(Keys.Escape))
+
     return kb

caticoa3 avatar Jul 09 '20 00:07 caticoa3