pgcli
pgcli copied to clipboard
[Feature] Allow to configure additional vi mode Escape mapping
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):
"""
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.
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?
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."""
@jonathanslenders , do you have any advice here?
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!
Would maintainers be interested in merging this patch?
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?
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
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.
Yes, global configuration would be great as I patch multiple projects that use prompt_toolkit, eg. mycli
as well..
@jonathanslenders This function would be in its own standalone file?
And in which file do I put the merging function from the link?
Could anyone please explain how to get this to work?
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