Add :rl-rubout and :rl-filename-rubout

Closes #4561
This commit is contained in:
Florian Bruhin 2022-03-31 17:46:34 +02:00
parent ebdd791b74
commit ab65c542a0
7 changed files with 136 additions and 39 deletions

View File

@ -19,6 +19,15 @@ breaking changes (such as renamed commands) can happen in minor releases.
v2.5.0 (unreleased)
-------------------
Deprecated
~~~~~~~~~~
- The `:rl-unix-word-rubout` command (`<Ctrl-W>` in command/prompt modes) has
been deprecated. Use `:rl-rubout " "` instead.
- The `:rl-unix-filename-rubout` command has been deprecated. Use either
`:rl-rubout "/ "` (classic readline behavior) or `:rl-filename-rubout` (using
OS path separator and ignoring spaces) instead.
Changed
~~~~~~~
@ -78,6 +87,11 @@ Added
current tab.
- New `editor.remove_file` setting which can be set to `False` to keep all
temporary editor files after closing the external editor.
- New `:rl-rubout` command replacing `:rl-unix-word-rubout` (and optionally
`:rl-unix-filename-rubout`), taking a delimiter as argument.
- New `:rl-filename-rubout` command, using the OS path separator and ignoring
spaces. The command also gets shown in the suggested commands for a download
filename prompt now.
Fixed
~~~~~

View File

@ -1681,13 +1681,13 @@ How many steps to zoom out.
|<<rl-beginning-of-line,rl-beginning-of-line>>|Move to the start of the line.
|<<rl-delete-char,rl-delete-char>>|Delete the character after the cursor.
|<<rl-end-of-line,rl-end-of-line>>|Move to the end of the line.
|<<rl-filename-rubout,rl-filename-rubout>>|Delete backwards using the OS path separator as boundary.
|<<rl-forward-char,rl-forward-char>>|Move forward a character.
|<<rl-forward-word,rl-forward-word>>|Move forward to the end of the next word.
|<<rl-kill-line,rl-kill-line>>|Remove chars from the cursor to the end of the line.
|<<rl-kill-word,rl-kill-word>>|Remove chars from the cursor to the end of the current word.
|<<rl-unix-filename-rubout,rl-unix-filename-rubout>>|Remove chars from the cursor to the previous path separator.
|<<rl-rubout,rl-rubout>>|Delete backwards using the given characters as boundaries.
|<<rl-unix-line-discard,rl-unix-line-discard>>|Remove chars backward from the cursor to the beginning of the line.
|<<rl-unix-word-rubout,rl-unix-word-rubout>>|Remove chars from the cursor to the beginning of the word.
|<<rl-yank,rl-yank>>|Paste the most recently deleted text.
|<<selection-drop,selection-drop>>|Drop selection and keep selection mode enabled.
|<<selection-reverse,selection-reverse>>|Swap the stationary and moving end of the current selection.
@ -1939,6 +1939,12 @@ Move to the end of the line.
This acts like readline's end-of-line.
[[rl-filename-rubout]]
=== rl-filename-rubout
Delete backwards using the OS path separator as boundary.
For behavior that matches readline's `unix-filename-rubout` exactly, use `:rl-rubout "/ "` instead. This command uses the OS path seperator (i.e. `\` on Windows) and ignores spaces.
[[rl-forward-char]]
=== rl-forward-char
Move forward a character.
@ -1963,11 +1969,17 @@ Remove chars from the cursor to the end of the current word.
This acts like readline's kill-word.
[[rl-unix-filename-rubout]]
=== rl-unix-filename-rubout
Remove chars from the cursor to the previous path separator.
[[rl-rubout]]
=== rl-rubout
Syntax: +:rl-rubout 'delim'+
Delete backwards using the given characters as boundaries.
With " ", this acts like readline's `unix-word-rubout`. With " /", this acts like readline's `unix-filename-rubout`, but consider using `:rl-filename-rubout` instead: It uses the OS path seperator (i.e. `\` on Windows) and ignores spaces.
==== positional arguments
* +'delim'+: A string of characters (or a single character) until which text will be deleted.
This acts like readline's unix-filename-rubout.
[[rl-unix-line-discard]]
=== rl-unix-line-discard
@ -1975,12 +1987,6 @@ Remove chars backward from the cursor to the beginning of the line.
This acts like readline's unix-line-discard.
[[rl-unix-word-rubout]]
=== rl-unix-word-rubout
Remove chars from the cursor to the beginning of the word.
This acts like readline's unix-word-rubout. Whitespace is used as a word delimiter.
[[rl-yank]]
=== rl-yank
Paste the most recently deleted text.

View File

@ -524,9 +524,10 @@ Default:
* +pass:[&lt;Ctrl-Return&gt;]+: +pass:[command-accept --rapid]+
* +pass:[&lt;Ctrl-Shift-C&gt;]+: +pass:[completion-item-yank --sel]+
* +pass:[&lt;Ctrl-Shift-Tab&gt;]+: +pass:[completion-item-focus prev-category]+
* +pass:[&lt;Ctrl-Shift-W&gt;]+: +pass:[rl-filename-rubout]+
* +pass:[&lt;Ctrl-Tab&gt;]+: +pass:[completion-item-focus next-category]+
* +pass:[&lt;Ctrl-U&gt;]+: +pass:[rl-unix-line-discard]+
* +pass:[&lt;Ctrl-W&gt;]+: +pass:[rl-unix-word-rubout]+
* +pass:[&lt;Ctrl-W&gt;]+: +pass:[rl-rubout &quot; &quot;]+
* +pass:[&lt;Ctrl-Y&gt;]+: +pass:[rl-yank]+
* +pass:[&lt;Down&gt;]+: +pass:[completion-item-focus --history next]+
* +pass:[&lt;Escape&gt;]+: +pass:[mode-leave]+
@ -760,8 +761,9 @@ Default:
* +pass:[&lt;Ctrl-H&gt;]+: +pass:[rl-backward-delete-char]+
* +pass:[&lt;Ctrl-K&gt;]+: +pass:[rl-kill-line]+
* +pass:[&lt;Ctrl-P&gt;]+: +pass:[prompt-open-download --pdfjs]+
* +pass:[&lt;Ctrl-Shift-W&gt;]+: +pass:[rl-filename-rubout]+
* +pass:[&lt;Ctrl-U&gt;]+: +pass:[rl-unix-line-discard]+
* +pass:[&lt;Ctrl-W&gt;]+: +pass:[rl-unix-word-rubout]+
* +pass:[&lt;Ctrl-W&gt;]+: +pass:[rl-rubout &quot; &quot;]+
* +pass:[&lt;Ctrl-X&gt;]+: +pass:[prompt-open-download]+
* +pass:[&lt;Ctrl-Y&gt;]+: +pass:[rl-yank]+
* +pass:[&lt;Down&gt;]+: +pass:[prompt-item-focus next]+

View File

@ -19,6 +19,7 @@
"""Bridge to provide readline-like shortcuts for QLineEdits."""
import os
from typing import Iterable, Optional, MutableMapping
from PyQt5.QtWidgets import QApplication, QLineEdit
@ -90,8 +91,13 @@ class _ReadlineBridge:
def kill_line(self) -> None:
self._dispatch('end', mark=True, delete=True)
def _rubout(self, delim: Iterable[str]) -> None:
"""Delete backwards using the characters in delim as boundaries."""
def rubout(self, delim: Iterable[str]) -> None:
"""Delete backwards using the characters in delim as boundaries.
With delim=[' '], this acts like unix-word-rubout.
With delim=[' ', '/'], this acts like unix-filename-rubout.
With delim=[os.sep], this serves as a more useful filename-rubout.
"""
widget = self._widget()
if widget is None:
return
@ -115,12 +121,6 @@ class _ReadlineBridge:
self._deleted[widget] = widget.selectedText()
widget.del_()
def unix_word_rubout(self) -> None:
self._rubout([' '])
def unix_filename_rubout(self) -> None:
self._rubout([' ', '/'])
def backward_kill_word(self) -> None:
self._dispatch('cursorWordBackward', mark=True, delete=True)
@ -221,23 +221,54 @@ def rl_kill_line() -> None:
bridge.kill_line()
@_register()
@_register(deprecated="Use :rl-rubout ' ' instead.")
def rl_unix_word_rubout() -> None:
"""Remove chars from the cursor to the beginning of the word.
This acts like readline's unix-word-rubout. Whitespace is used as a
word delimiter.
"""
bridge.unix_word_rubout()
bridge.rubout([" "])
@_register()
@_register(
deprecated='Use :rl-filename-rubout or :rl-rubout " /" instead '
'(see their `:help` for details).'
)
def rl_unix_filename_rubout() -> None:
"""Remove chars from the cursor to the previous path separator.
This acts like readline's unix-filename-rubout.
"""
bridge.unix_filename_rubout()
bridge.rubout([" ", "/"])
@_register()
def rl_rubout(delim: str) -> None:
"""Delete backwards using the given characters as boundaries.
With " ", this acts like readline's `unix-word-rubout`.
With " /", this acts like readline's `unix-filename-rubout`, but consider
using `:rl-filename-rubout` instead: It uses the OS path seperator (i.e. `\\`
on Windows) and ignores spaces.
Args:
delim: A string of characters (or a single character) until which text
will be deleted.
"""
bridge.rubout(list(delim))
@_register()
def rl_filename_rubout() -> None:
"""Delete backwards using the OS path separator as boundary.
For behavior that matches readline's `unix-filename-rubout` exactly, use
`:rl-rubout "/ "` instead. This command uses the OS path seperator (i.e.
`\\` on Windows) and ignores spaces.
"""
bridge.rubout(os.sep)
@_register()

View File

@ -3764,7 +3764,8 @@ bindings.default:
<Ctrl-U>: rl-unix-line-discard
<Ctrl-K>: rl-kill-line
<Alt-D>: rl-kill-word
<Ctrl-W>: rl-unix-word-rubout
<Ctrl-W>: rl-rubout " "
<Ctrl-Shift-W>: rl-filename-rubout
<Alt-Backspace>: rl-backward-kill-word
<Ctrl-Y>: rl-yank
<Ctrl-?>: rl-delete-char
@ -3789,7 +3790,8 @@ bindings.default:
<Ctrl-U>: rl-unix-line-discard
<Ctrl-K>: rl-kill-line
<Alt-D>: rl-kill-word
<Ctrl-W>: rl-unix-word-rubout
<Ctrl-W>: rl-rubout " "
<Ctrl-Shift-W>: rl-filename-rubout
<Alt-Backspace>: rl-backward-kill-word
<Ctrl-?>: rl-delete-char
<Ctrl-H>: rl-backward-delete-char

View File

@ -831,6 +831,7 @@ class DownloadFilenamePrompt(FilenamePrompt):
cmds = [
('prompt-accept', 'Accept'),
('mode-leave', 'Abort'),
('rl-filename-rubout', "Go to parent directory"),
('prompt-open-download', "Open download"),
('prompt-open-download --pdfjs', "Open download via PDF.js"),
('prompt-yank', "Yank URL"),

View File

@ -17,6 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
import os
import re
import inspect
@ -94,18 +95,19 @@ class LineEdit(QLineEdit):
return ''.join(chars)
def _validate_deletion(lineedit, method, text, deleted, rest):
def _validate_deletion(lineedit, method, args, text, deleted, rest):
"""Run and validate a text deletion method on the ReadLine bridge.
Args:
lineedit: The LineEdit instance.
method: Reference to the method on the bridge to test.
args: Arguments to pass to the method.
text: The starting 'augmented' text (see LineEdit.set_aug_text)
deleted: The text that should be deleted when the method is invoked.
rest: The augmented text that should remain after method is invoked.
"""
lineedit.set_aug_text(text)
method()
method(*args)
assert readlinecommands.bridge._deleted[lineedit] == deleted
assert lineedit.aug_text() == rest
lineedit.clear()
@ -127,7 +129,9 @@ def test_none(qtbot):
assert QApplication.instance().focusWidget() is None
for name, method in inspect.getmembers(readlinecommands,
inspect.isfunction):
if name.startswith('rl_'):
if name == "rl_rubout":
method(delim=" ")
elif name.startswith('rl_'):
method()
@ -218,7 +222,8 @@ def test_rl_backward_delete_char(text, expected, lineedit):
])
def test_rl_unix_line_discard(lineedit, text, deleted, rest):
"""Delete from the cursor to the beginning of the line and yank back."""
_validate_deletion(lineedit, readlinecommands.rl_unix_line_discard,
_validate_deletion(lineedit,
readlinecommands.rl_unix_line_discard, [],
text, deleted, rest)
@ -230,7 +235,8 @@ def test_rl_unix_line_discard(lineedit, text, deleted, rest):
])
def test_rl_kill_line(lineedit, text, deleted, rest):
"""Delete from the cursor to the end of line and yank back."""
_validate_deletion(lineedit, readlinecommands.rl_kill_line,
_validate_deletion(lineedit,
readlinecommands.rl_kill_line, [],
text, deleted, rest)
@ -243,9 +249,14 @@ def test_rl_kill_line(lineedit, text, deleted, rest):
marks=fixme),
('test del<ete >foobar', 'del', 'test |ete foobar'), # wrong
])
def test_rl_unix_word_rubout(lineedit, text, deleted, rest):
@pytest.mark.parametrize("method, args", [
(readlinecommands.rl_unix_word_rubout, []), # deprecated
(readlinecommands.rl_rubout, [" "]), # equivalent
])
def test_rl_unix_word_rubout(lineedit, text, deleted, rest, method, args):
"""Delete to word beginning and see if it comes back with yank."""
_validate_deletion(lineedit, readlinecommands.rl_unix_word_rubout,
_validate_deletion(lineedit,
method, args,
text, deleted, rest)
@ -256,9 +267,37 @@ def test_rl_unix_word_rubout(lineedit, text, deleted, rest):
('open -t |github.com/foo/bar', '-t ', 'open |github.com/foo/bar'),
('open foo/bar.baz|', 'bar.baz', 'open foo/|'),
])
def test_rl_unix_filename_rubout(lineedit, text, deleted, rest):
@pytest.mark.parametrize("method, args", [
(readlinecommands.rl_unix_filename_rubout, []), # deprecated
(readlinecommands.rl_rubout, [" /"]), # equivalent
])
def test_rl_unix_filename_rubout(lineedit, text, deleted, rest, method, args):
"""Delete filename segment and see if it comes back with yank."""
_validate_deletion(lineedit, readlinecommands.rl_unix_filename_rubout,
_validate_deletion(lineedit,
method, args,
text, deleted, rest)
@pytest.mark.parametrize('os_sep, text, deleted, rest', [
pytest.param('/', 'path|', 'path', '|', marks=fixme),
('/', 'path|', 'ath', 'p|'), # wrong
('/', '/path|', 'path', '/|'),
('/', '/path/sub|', 'sub', '/path/|'),
('/', '/path/trailing/|', 'trailing/', '/path/|'),
('/', '/test/path with spaces|', 'path with spaces', '/test/|'),
('/', r'/test/path\backslashes\eww|', r'path\backslashes\eww', '/test/|'),
pytest.param('\\', 'path|', 'path', '|', marks=fixme),
('\\', 'path|', 'ath', 'p|'), # wrong
('\\', r'C:\path|', 'path', r'C:\|'),
('\\', r'C:\path\sub|', 'sub', r'C:\path\|'),
('\\', r'C:\test\path with spaces|', 'path with spaces', r'C:\test\|'),
('\\', r'C:\path\trailing\|', 'trailing\\', r'C:\path\|'),
])
def test_filename_rubout(os_sep, monkeypatch, lineedit, text, deleted, rest):
"""Delete filename segment and see if it comes back with yank."""
monkeypatch.setattr(os, "sep", os_sep)
_validate_deletion(lineedit,
readlinecommands.rl_filename_rubout, [],
text, deleted, rest)
@ -275,7 +314,8 @@ def test_rl_unix_filename_rubout(lineedit, text, deleted, rest):
])
def test_rl_kill_word(lineedit, text, deleted, rest):
"""Delete to word end and see if it comes back with yank."""
_validate_deletion(lineedit, readlinecommands.rl_kill_word,
_validate_deletion(lineedit,
readlinecommands.rl_kill_word, [],
text, deleted, rest)
@ -290,7 +330,8 @@ def test_rl_kill_word(lineedit, text, deleted, rest):
])
def test_rl_backward_kill_word(lineedit, text, deleted, rest):
"""Delete to word beginning and see if it comes back with yank."""
_validate_deletion(lineedit, readlinecommands.rl_backward_kill_word,
_validate_deletion(lineedit,
readlinecommands.rl_backward_kill_word, [],
text, deleted, rest)