From 2bcf46194bd9fef5f623ae2699c2910447aebe2d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 May 2014 10:49:19 +0200 Subject: [PATCH] Add readline-like shortcuts --- TODO | 1 - qutebrowser/app.py | 2 + qutebrowser/config/configdata.py | 25 ++++++ qutebrowser/utils/readline.py | 129 +++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 qutebrowser/utils/readline.py diff --git a/TODO b/TODO index 808384bd8..25f2455fb 100644 --- a/TODO +++ b/TODO @@ -58,7 +58,6 @@ Improvements / minor features - Close tabs on right click - Up/Down not for history - search highlighting -- readline like shortcuts (like C-w) for command prompt - max height for completion (be smaller if possible) - tab should directly insert word and space if there's only one option - vertical tabbar diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 72d212d92..52bf403b9 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -66,6 +66,7 @@ from qutebrowser.browser.cookies import CookieJar from qutebrowser.utils.message import MessageBridge from qutebrowser.utils.misc import (dotted_getattr, get_standard_dir, actute_warning) +from qutebrowser.utils.readline import ReadlineBridge from qutebrowser.utils.debug import set_trace # pylint: disable=unused-import @@ -252,6 +253,7 @@ class QuteBrowser(QApplication): self.setApplicationName("qutebrowser") self.setApplicationVersion(qutebrowser.__version__) self.messagebridge = MessageBridge() + self.rl_bridge = ReadlineBridge() def _handle_segfault(self): """Handle a segfault from a previous run.""" diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a090db6c1..b287ca8f2 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -690,6 +690,9 @@ DATA = OrderedDict([ ('', 'leave-mode'), )), + # FIXME we should probably have a common section for input modes with a + # text field. + ('keybind.command', sect.ValueList( types.KeyBindingName(), types.KeyBinding(), ('', 'leave-mode'), @@ -699,6 +702,17 @@ DATA = OrderedDict([ ('', 'completion-item-prev'), ('', 'completion-item-next'), ('', 'command-accept'), + ('', 'rl-backward-char'), + ('', 'rl-forward-char'), + ('', 'rl-backward-word'), + ('', 'rl-forward-word'), + ('', 'rl-beginning-of-line'), + ('', 'rl-end-of-line'), + ('', 'rl-unix-line-discard'), + ('', 'rl-kill-line'), + ('', 'rl-kill-word'), + ('', 'rl-unix-word-rubout'), + ('', 'rl-yank'), )), ('keybind.prompt', sect.ValueList( @@ -708,6 +722,17 @@ DATA = OrderedDict([ ('', 'prompt-accept'), ('y', 'prompt-yes'), ('n', 'prompt-no'), + ('', 'rl-backward-char'), + ('', 'rl-forward-char'), + ('', 'rl-backward-word'), + ('', 'rl-forward-word'), + ('', 'rl-beginning-of-line'), + ('', 'rl-end-of-line'), + ('', 'rl-unix-line-discard'), + ('', 'rl-kill-line'), + ('', 'rl-kill-word'), + ('', 'rl-unix-word-rubout'), + ('', 'rl-yank'), )), ('aliases', sect.ValueList( diff --git a/qutebrowser/utils/readline.py b/qutebrowser/utils/readline.py new file mode 100644 index 000000000..0f768d1eb --- /dev/null +++ b/qutebrowser/utils/readline.py @@ -0,0 +1,129 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Bridge to provide readline-like shortcuts for QLineEdits.""" + +from PyQt5.QtWidgets import QApplication, QLineEdit + +import qutebrowser.commands.utils as cmd +import qutebrowser.keyinput.modeman as modeman + + +class ReadlineBridge: + + """Bridge which provides readline-like commands for the current QLineEdit. + + Attributes: + deleted: Mapping from widgets to their last deleted text. + """ + + def __init__(self): + self.deleted = {} + + @property + def widget(self): + """Get the currently active QLineEdit.""" + w = QApplication.instance().focusWidget() + if isinstance(w, QLineEdit): + return w + else: + return None + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_backward_char(self): + """Readline: Move back a character.""" + if self.widget is None: + return + self.widget.cursorBackward(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_forward_char(self): + """Readline: Move forward a character.""" + if self.widget is None: + return + self.widget.cursorForward(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_backward_word(self): + """Readline: Move back to the start of the current or previous word.""" + if self.widget is None: + return + self.widget.cursorWordBackward(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_forward_word(self): + """Readline: Move forward to the end of the next word.""" + if self.widget is None: + return + self.widget.cursorWordForward(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_beginning_of_line(self): + """Readline: Move to the start of the current line.""" + if self.widget is None: + return + self.widget.home(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_end_of_line(self): + """Readline: Move to the end of the line.""" + if self.widget is None: + return + self.widget.end(False) + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_unix_line_discard(self): + """Readline: Kill backward from cursor to the beginning of the line.""" + if self.widget is None: + return + self.widget.home(True) + self.deleted[self.widget] = self.widget.selectedText() + self.widget.del_() + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_kill_line(self): + """Readline: Kill the text from point to the end of the line.""" + if self.widget is None: + return + self.widget.home(True) + self.deleted[self.widget] = self.widget.selectedText() + self.widget.del_() + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_unix_word_rubout(self): + """Readline: Kill the word behind point.""" + if self.widget is None: + return + self.widget.cursorWordBackward(True) + self.deleted[self.widget] = self.widget.selectedText() + self.widget.del_() + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_kill_word(self): + """Readline: Kill from point to the end of the current word.""" + if self.widget is None: + return + self.widget.cursorWordForward(True) + self.deleted[self.widget] = self.widget.selectedText() + self.widget.del_() + + @cmd.register(instance='rl_bridge', hide=True, modes=['command', 'prompt']) + def rl_yank(self): + """Readline: Yank the top of the kill ring into the buffer at point.""" + if self.widget is None or self.widget not in self.deleted: + return + self.widget.insert(self.deleted[self.widget])