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

Map keychains in passthrough-keymodes #1044

Open
gboehl opened this issue Oct 21, 2015 · 8 comments · May be fixed by #3683
Open

Map keychains in passthrough-keymodes #1044

gboehl opened this issue Oct 21, 2015 · 8 comments · May be fixed by #3683
Labels
component: keyinput Issues related to processing keypresses. priority: 3 - wishlist Issues which are not important and/or where it's unclear whether they're feasible.

Comments

@gboehl
Copy link

gboehl commented Oct 21, 2015

Is there any way to map a sequence such as 'jk' to 'leave-mode'? Unfortunately, only combinations are supported.

Thanks for the excellent work!

@The-Compiler The-Compiler changed the title Map key sequence against <Esc> or 'leave-mode' Map keychains in passthrough-keymodes Oct 22, 2015
@The-Compiler The-Compiler added component: keyinput Issues related to processing keypresses. priority: 3 - wishlist Issues which are not important and/or where it's unclear whether they're feasible. labels Oct 22, 2015
@The-Compiler
Copy link
Member

This isn't possible right now, sorry - but let's add it to the TODO 😉

#319 is somewhat related.

@MichaelMackus
Copy link

👍

@The-Compiler
Copy link
Member

@MichaelMackus https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments 😉

@The-Compiler
Copy link
Member

Partial work on this from @MichaelMackus in #3075:

From 77b7d2287fe906229412255a06afdbe1b4564f02 Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Sat, 30 Sep 2017 11:30:09 -0700
Subject: [PATCH 1/6] Add base KeyChainParser for passthrough modes

---
 qutebrowser/keyinput/keyparser.py   | 52 +++++++++++++++++++++++++++++++++++--
 qutebrowser/keyinput/modeparsers.py | 32 +++--------------------
 2 files changed, 53 insertions(+), 31 deletions(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index 8fcb53035c..76c099a106 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -21,12 +21,61 @@
 
 import traceback
 
+from PyQt5.QtCore import pyqtSlot
+
 from qutebrowser.keyinput.basekeyparser import BaseKeyParser
 from qutebrowser.utils import message, utils
 from qutebrowser.commands import runners, cmdexc
 
+class KeyChainParser(BaseKeyParser):
+    """KeyChainParser which implements chaining of multiple keys."""
+    def __init__(self, win_id, parent=None):
+        super().__init__(win_id, parent, supports_count=True,
+                         supports_chains=True)
+        self._partial_timer = usertypes.Timer(self, 'partial-match')
+        self._partial_timer.setSingleShot(True)
+
+    def __repr__(self):
+        return utils.get_repr(self)
+
+    def _handle_single_key(self, e):
+        """Override _handle_single_key to abort if the key is a startchar.
 
-class CommandKeyParser(BaseKeyParser):
+        Args:
+            e: the KeyPressEvent from Qt.
+
+        Return:
+            A self.Match member.
+        """
+        match = super()._handle_single_key(e)
+        if match == self.Match.partial:
+            timeout = config.val.input.partial_timeout
+            if timeout != 0:
+                self._partial_timer.setInterval(timeout)
+                self._partial_timer.timeout.connect(self._clear_partial_match)
+                self._partial_timer.start()
+        return match
+
+    @pyqtSlot()
+    def _clear_partial_match(self):
+        """Clear a partial keystring after a timeout."""
+        self._debug_log("Clearing partial keystring {}".format(
+            self._keystring))
+        self._keystring = ''
+        self.keystring_updated.emit(self._keystring)
+
+    @pyqtSlot()
+    def _stop_timers(self):
+        super()._stop_timers()
+        self._partial_timer.stop()
+        try:
+            self._partial_timer.timeout.disconnect(self._clear_partial_match)
+        except TypeError:
+            # no connections
+            pass
+
+
+class CommandKeyParser(KeyChainParser):
 
     """KeyChainParser for command bindings.
 
@@ -45,7 +94,6 @@ def execute(self, cmdstr, _keytype, count=None):
         except cmdexc.Error as e:
             message.error(str(e), stack=traceback.format_exc())
 
-
 class PassthroughKeyParser(CommandKeyParser):
 
     """KeyChainParser which passes through normal keys.
diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py
index 7aa8ca3716..dd809f90c3 100644
--- a/qutebrowser/keyinput/modeparsers.py
+++ b/qutebrowser/keyinput/modeparsers.py
@@ -39,18 +39,12 @@
 
 class NormalKeyParser(keyparser.CommandKeyParser):
 
-    """KeyParser for normal mode with added STARTCHARS detection and more.
-
-    Attributes:
-        _partial_timer: Timer to clear partial keypresses.
-    """
+    """KeyParser for normal mode with added STARTCHARS detection and more."""
 
     def __init__(self, win_id, parent=None):
         super().__init__(win_id, parent, supports_count=True,
                          supports_chains=True)
         self._read_config('normal')
-        self._partial_timer = usertypes.Timer(self, 'partial-match')
-        self._partial_timer.setSingleShot(True)
         self._inhibited = False
         self._inhibited_timer = usertypes.Timer(self, 'normal-inhibited')
         self._inhibited_timer.setSingleShot(True)
@@ -72,14 +66,8 @@ def _handle_single_key(self, e):
             self._debug_log("Ignoring key '{}', because the normal mode is "
                 "currently inhibited.".format(txt))
             return self.Match.none
-        match = super()._handle_single_key(e)
-        if match == self.Match.partial:
-            timeout = config.val.input.partial_timeout
-            if timeout != 0:
-                self._partial_timer.setInterval(timeout)
-                self._partial_timer.timeout.connect(self._clear_partial_match)
-                self._partial_timer.start()
-        return match
+
+        return super()._handle_single_key(e)
 
     def set_inhibited_timeout(self, timeout):
         if timeout != 0:
@@ -91,14 +79,6 @@ def set_inhibited_timeout(self, timeout):
             self._inhibited_timer.start()
 
     @pyqtSlot()
-    def _clear_partial_match(self):
-        """Clear a partial keystring after a timeout."""
-        self._debug_log("Clearing partial keystring {}".format(
-            self._keystring))
-        self._keystring = ''
-        self.keystring_updated.emit(self._keystring)
-
-    @pyqtSlot()
     def _clear_inhibited(self):
         """Reset inhibition state after a timeout."""
         self._debug_log("Releasing inhibition state of normal mode.")
@@ -107,12 +87,6 @@ def _clear_inhibited(self):
     @pyqtSlot()
     def _stop_timers(self):
         super()._stop_timers()
-        self._partial_timer.stop()
-        try:
-            self._partial_timer.timeout.disconnect(self._clear_partial_match)
-        except TypeError:
-            # no connections
-            pass
         self._inhibited_timer.stop()
         try:
             self._inhibited_timer.timeout.disconnect(self._clear_inhibited)

From ccf1616eb34d612ba86963b223f801b44c9572f3 Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Tue, 3 Oct 2017 10:15:58 -0700
Subject: [PATCH 2/6] Add arguments and imports

---
 qutebrowser/keyinput/keyparser.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index 76c099a106..99e7abaf9e 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -24,14 +24,14 @@
 from PyQt5.QtCore import pyqtSlot
 
 from qutebrowser.keyinput.basekeyparser import BaseKeyParser
-from qutebrowser.utils import message, utils
+from qutebrowser.utils import message, usertypes, utils
 from qutebrowser.commands import runners, cmdexc
+from qutebrowser.config import config
 
 class KeyChainParser(BaseKeyParser):
     """KeyChainParser which implements chaining of multiple keys."""
-    def __init__(self, win_id, parent=None):
-        super().__init__(win_id, parent, supports_count=True,
-                         supports_chains=True)
+    def __init__(self, win_id, parent=None, supports_count=True, supports_chains=True):
+        super().__init__(win_id, parent, supports_count=supports_count, supports_chains=supports_chains)
         self._partial_timer = usertypes.Timer(self, 'partial-match')
         self._partial_timer.setSingleShot(True)
 

From 8adf859220305a0d692bf2a3af41de2315185d0c Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Tue, 3 Oct 2017 10:17:16 -0700
Subject: [PATCH 3/6] Don't warn on keychains

---
 qutebrowser/keyinput/keyparser.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index 99e7abaf9e..c0fdf60586 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -32,6 +32,7 @@ class KeyChainParser(BaseKeyParser):
     """KeyChainParser which implements chaining of multiple keys."""
     def __init__(self, win_id, parent=None, supports_count=True, supports_chains=True):
         super().__init__(win_id, parent, supports_count=supports_count, supports_chains=supports_chains)
+        self._warn_on_keychains = False
         self._partial_timer = usertypes.Timer(self, 'partial-match')
         self._partial_timer.setSingleShot(True)
 
@@ -116,7 +117,6 @@ def __init__(self, win_id, mode, parent=None, warn=True):
             warn: Whether to warn if an ignored key was bound.
         """
         super().__init__(win_id, parent, supports_chains=False)
-        self._warn_on_keychains = warn
         self._read_config(mode)
         self._mode = mode
 

From 25321af5cb63244ce127bfc97bab25584b98b4ad Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Tue, 3 Oct 2017 16:00:28 -0700
Subject: [PATCH 4/6] Fix keychains in insert & command modes

---
 qutebrowser/keyinput/keyparser.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index c0fdf60586..5a8d0eb44a 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -32,7 +32,7 @@ class KeyChainParser(BaseKeyParser):
     """KeyChainParser which implements chaining of multiple keys."""
     def __init__(self, win_id, parent=None, supports_count=True, supports_chains=True):
         super().__init__(win_id, parent, supports_count=supports_count, supports_chains=supports_chains)
-        self._warn_on_keychains = False
+        self._warn_on_keychains = not supports_chains
         self._partial_timer = usertypes.Timer(self, 'partial-match')
         self._partial_timer.setSingleShot(True)
 
@@ -84,8 +84,8 @@ class CommandKeyParser(KeyChainParser):
         _commandrunner: CommandRunner instance.
     """
 
-    def __init__(self, win_id, parent=None, supports_count=None,
-                 supports_chains=False):
+    def __init__(self, win_id, parent=None, supports_count=True,
+                 supports_chains=True):
         super().__init__(win_id, parent, supports_count, supports_chains)
         self._commandrunner = runners.CommandRunner(win_id)
 
@@ -116,7 +116,7 @@ def __init__(self, win_id, mode, parent=None, warn=True):
             parent: Qt parent.
             warn: Whether to warn if an ignored key was bound.
         """
-        super().__init__(win_id, parent, supports_chains=False)
+        super().__init__(win_id, parent)
         self._read_config(mode)
         self._mode = mode
 

From 59ad095faca93984de7b58bebd7c7c865c4258e8 Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Tue, 3 Oct 2017 16:09:50 -0700
Subject: [PATCH 5/6] Remove supports_chains arg

Everything that extends KeyChainParser is assumed to support keychains
now. Also dropped the "warn" arg since it wasn't being used.
---
 qutebrowser/keyinput/keyparser.py   | 16 ++++++----------
 qutebrowser/keyinput/modeman.py     |  3 +--
 qutebrowser/keyinput/modeparsers.py | 15 +++++----------
 3 files changed, 12 insertions(+), 22 deletions(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index 5a8d0eb44a..6ebb91b860 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -30,9 +30,8 @@
 
 class KeyChainParser(BaseKeyParser):
     """KeyChainParser which implements chaining of multiple keys."""
-    def __init__(self, win_id, parent=None, supports_count=True, supports_chains=True):
-        super().__init__(win_id, parent, supports_count=supports_count, supports_chains=supports_chains)
-        self._warn_on_keychains = not supports_chains
+    def __init__(self, win_id, parent=None, supports_count=None):
+        super().__init__(win_id, parent, supports_count, supports_chains=True)
         self._partial_timer = usertypes.Timer(self, 'partial-match')
         self._partial_timer.setSingleShot(True)
 
@@ -84,9 +83,8 @@ class CommandKeyParser(KeyChainParser):
         _commandrunner: CommandRunner instance.
     """
 
-    def __init__(self, win_id, parent=None, supports_count=True,
-                 supports_chains=True):
-        super().__init__(win_id, parent, supports_count, supports_chains)
+    def __init__(self, win_id, parent=None, supports_count=True):
+        super().__init__(win_id, parent, supports_count)
         self._commandrunner = runners.CommandRunner(win_id)
 
     def execute(self, cmdstr, _keytype, count=None):
@@ -108,18 +106,16 @@ class PassthroughKeyParser(CommandKeyParser):
     do_log = False
     passthrough = True
 
-    def __init__(self, win_id, mode, parent=None, warn=True):
+    def __init__(self, win_id, mode, parent=None):
         """Constructor.
 
         Args:
             mode: The mode this keyparser is for.
             parent: Qt parent.
-            warn: Whether to warn if an ignored key was bound.
         """
         super().__init__(win_id, parent)
         self._read_config(mode)
         self._mode = mode
 
     def __repr__(self):
-        return utils.get_repr(self, mode=self._mode,
-                              warn=self._warn_on_keychains)
+        return utils.get_repr(self, mode=self._mode)
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index 6a6dd5459c..c5c091ad25 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -72,8 +72,7 @@ def init(win_id, parent):
         KM.passthrough: keyparser.PassthroughKeyParser(win_id, 'passthrough',
                                                        modeman),
         KM.command: keyparser.PassthroughKeyParser(win_id, 'command', modeman),
-        KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman,
-                                                  warn=False),
+        KM.prompt: keyparser.PassthroughKeyParser(win_id, 'prompt', modeman),
         KM.yesno: modeparsers.PromptKeyParser(win_id, modeman),
         KM.caret: modeparsers.CaretKeyParser(win_id, modeman),
         KM.set_mark: modeparsers.RegisterKeyParser(win_id, KM.set_mark,
diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py
index dd809f90c3..e09337bf53 100644
--- a/qutebrowser/keyinput/modeparsers.py
+++ b/qutebrowser/keyinput/modeparsers.py
@@ -42,8 +42,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
     """KeyParser for normal mode with added STARTCHARS detection and more."""
 
     def __init__(self, win_id, parent=None):
-        super().__init__(win_id, parent, supports_count=True,
-                         supports_chains=True)
+        super().__init__(win_id, parent, supports_count=True)
         self._read_config('normal')
         self._inhibited = False
         self._inhibited_timer = usertypes.Timer(self, 'normal-inhibited')
@@ -100,8 +99,7 @@ class PromptKeyParser(keyparser.CommandKeyParser):
     """KeyParser for yes/no prompts."""
 
     def __init__(self, win_id, parent=None):
-        super().__init__(win_id, parent, supports_count=False,
-                         supports_chains=True)
+        super().__init__(win_id, parent, supports_count=False)
         # We don't want an extra section for this in the config, so we just
         # abuse the prompt section.
         self._read_config('prompt')
@@ -120,8 +118,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
     """
 
     def __init__(self, win_id, parent=None):
-        super().__init__(win_id, parent, supports_count=False,
-                         supports_chains=True)
+        super().__init__(win_id, parent, supports_count=False)
         self._filtertext = ''
         self._last_press = LastPress.none
         self._read_config('hint')
@@ -236,8 +233,7 @@ class CaretKeyParser(keyparser.CommandKeyParser):
     passthrough = True
 
     def __init__(self, win_id, parent=None):
-        super().__init__(win_id, parent, supports_count=True,
-                         supports_chains=True)
+        super().__init__(win_id, parent, supports_count=True)
         self._read_config('caret')
 
 
@@ -251,8 +247,7 @@ class RegisterKeyParser(keyparser.CommandKeyParser):
     """
 
     def __init__(self, win_id, mode, parent=None):
-        super().__init__(win_id, parent, supports_count=False,
-                         supports_chains=False)
+        super().__init__(win_id, parent, supports_count=False)
         self._mode = mode
         self._read_config('register')
 

From 652d92b3b91ecb33ef6510f76c7b5d549d4e6a49 Mon Sep 17 00:00:00 2001
From: Michael Mackus <michaelmackus@gmail.com>
Date: Tue, 3 Oct 2017 16:53:58 -0700
Subject: [PATCH 6/6] Turn off count for passthrough modes

This was breaking number keys in input modes.
---
 qutebrowser/keyinput/keyparser.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py
index 6ebb91b860..fd55d47ec8 100644
--- a/qutebrowser/keyinput/keyparser.py
+++ b/qutebrowser/keyinput/keyparser.py
@@ -30,7 +30,7 @@
 
 class KeyChainParser(BaseKeyParser):
     """KeyChainParser which implements chaining of multiple keys."""
-    def __init__(self, win_id, parent=None, supports_count=None):
+    def __init__(self, win_id, parent=None, supports_count=False):
         super().__init__(win_id, parent, supports_count, supports_chains=True)
         self._partial_timer = usertypes.Timer(self, 'partial-match')
         self._partial_timer.setSingleShot(True)
@@ -83,7 +83,7 @@ class CommandKeyParser(KeyChainParser):
         _commandrunner: CommandRunner instance.
     """
 
-    def __init__(self, win_id, parent=None, supports_count=True):
+    def __init__(self, win_id, parent=None, supports_count=False):
         super().__init__(win_id, parent, supports_count)
         self._commandrunner = runners.CommandRunner(win_id)
 
@@ -113,7 +113,7 @@ def __init__(self, win_id, mode, parent=None):
             mode: The mode this keyparser is for.
             parent: Qt parent.
         """
-        super().__init__(win_id, parent)
+        super().__init__(win_id, parent, supports_count=False)
         self._read_config(mode)
         self._mode = mode

@The-Compiler
Copy link
Member

With #319 now done, you can :bind --mode=insert jk leave-mode - however, qutebrowser won't let you type j anymore either, so this isn't really done yet 😉

@bearcatsandor
Copy link

Just my 2 cents that this isn't fixed yet. The-Compiler's comment on March 4, 2018 is still correct. It will let you type 'j', but you have to press 'j' twice to get that letter, and nothing new is saved to key.conf when you bind it. (I wish we could up-vote issues on Github. I'd love to have this fixed. I just learned Python, so I may give it a shot.

@The-Compiler
Copy link
Member

@bearcatsandor You can upvote issues on GitHub, that's what reactions are for. Also, note that keys.conf is a thing of the past, see the configuring documentation for details. Finally, there already is a PR open for this at #3683 - help with testing/reviewing that would be welcome!

@bearcatsandor
Copy link

bearcatsandor commented Dec 16, 2019

Wow. That was a fast response. Thank you. I'll check all that out. For anyone that may have tried this and wants to revert, the command is unbind --mode=insert jk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: keyinput Issues related to processing keypresses. priority: 3 - wishlist Issues which are not important and/or where it's unclear whether they're feasible.
Projects
None yet
4 participants