Also make it possible to use multiple variables in one argument.
This commit is contained in:
Jan Verbeek 2016-08-07 13:14:46 +02:00
commit ebfe23c376
28 changed files with 467 additions and 235 deletions

View File

@ -44,6 +44,17 @@ Changed
(i.e. to open it at the position it would be opened if it was a clicked link)
- `:download-open` and `:prompt-open-download` now have an optional `cmdline`
argument to pass a commandline to open the download with.
- `:yank` now has a position argument to select what to yank instead of using
flags.
- Replacements like `{url}` can now also be used in the middle of an argument.
Consequently, commands taking another command (`:later`, `:repeat` and
`:bind`) now don't immediately evaluate variables.
Removed
~~~~~~~
- The `:yank-selected` command got merged into `:yank` as `:yank selection`
and thus removed.
v0.8.3 (unreleased)
-------------------
@ -52,6 +63,7 @@ Fixed
~~~~~
- Fixed crash when doing `:<space><enter>`, another corner-case introduced in v0.8.0
- Fixed `:open-editor` (`<Ctrl-e>`) on Windows
v0.8.2
------

View File

@ -146,13 +146,13 @@ Contributors, sorted by the number of commits in descending order:
* Lamar Pavel
* Bruno Oliveira
* Alexander Cogneau
* Marshall Lochbaum
* Felix Van der Jeugt
* Jakub Klinkovský
* Martin Tournoij
* Marshall Lochbaum
* Jan Verbeek
* Raphael Pierzina
* Joel Torstensson
* Jan Verbeek
* Patric Schmitz
* Tarcisio Fedrizzi
* Claude

View File

@ -65,8 +65,7 @@
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|<<view-source,view-source>>|Show the source of the current page.
|<<wq,wq>>|Save open pages and quit.
|<<yank,yank>>|Yank the current URL/title to the clipboard or primary selection.
|<<yank-selected,yank-selected>>|Yank the selected text to the clipboard or primary selection.
|<<yank,yank>>|Yank something to the clipboard or primary selection.
|<<zoom,zoom>>|Set the zoom level for the current tab.
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
|<<zoom-out,zoom-out>>|Decrease the zoom level for the current tab.
@ -110,6 +109,7 @@ Bind a key to a command.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
* This command does not replace variables like +\{url\}+.
[[bookmark-add]]
=== bookmark-add
@ -424,6 +424,7 @@ Execute a command after some time.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
* This command does not replace variables like +\{url\}+.
[[messages]]
=== messages
@ -579,6 +580,7 @@ Repeat a given command.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
* This command does not replace variables like +\{url\}+.
[[report]]
=== report
@ -840,25 +842,25 @@ Save open pages and quit.
[[yank]]
=== yank
Syntax: +:yank [*--title*] [*--sel*] [*--domain*] [*--pretty*]+
Syntax: +:yank [*--sel*] [*--keep*] ['what']+
Yank the current URL/title to the clipboard or primary selection.
Yank something to the clipboard or primary selection.
==== optional arguments
* +*-t*+, +*--title*+: Yank the title instead of the URL.
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number.
* +*-p*+, +*--pretty*+: Yank the URL in pretty decoded form.
==== positional arguments
* +'what'+: What to yank.
- `url`: The current URL.
- `pretty-url`: The URL in pretty decoded form.
- `title`: The current page's title.
- `domain`: The current scheme, domain, and port number.
- `selection`: The selection under the cursor.
[[yank-selected]]
=== yank-selected
Syntax: +:yank-selected [*--sel*] [*--keep*]+
Yank the selected text to the clipboard or primary selection.
==== optional arguments
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
* +*-k*+, +*--keep*+: If given, stay in visual mode after yanking.
* +*-k*+, +*--keep*+: Stay in visual mode after yanking the selection.
[[zoom]]
=== zoom

View File

@ -1,2 +1,2 @@
pip==8.1.2
setuptools==25.1.4
setuptools==25.1.6

View File

@ -3,4 +3,4 @@
pluggy==0.3.1
py==1.4.31
tox==2.3.1
virtualenv==15.0.2
virtualenv==15.0.3

View File

@ -645,30 +645,44 @@ class CommandDispatcher:
"representation.")
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False, domain=False, pretty=False):
"""Yank the current URL/title to the clipboard or primary selection.
@cmdutils.argument('what', choices=['selection', 'url', 'pretty-url',
'title', 'domain'])
def yank(self, what='url', sel=False, keep=False):
"""Yank something to the clipboard or primary selection.
Args:
what: What to yank.
- `url`: The current URL.
- `pretty-url`: The URL in pretty decoded form.
- `title`: The current page's title.
- `domain`: The current scheme, domain, and port number.
- `selection`: The selection under the cursor.
sel: Use the primary selection instead of the clipboard.
title: Yank the title instead of the URL.
domain: Yank only the scheme, domain, and port number.
pretty: Yank the URL in pretty decoded form.
keep: Stay in visual mode after yanking the selection.
"""
if title:
if what == 'title':
s = self._tabbed_browser.page_title(self._current_index())
what = 'title'
elif domain:
elif what == 'domain':
port = self._current_url().port()
s = '{}://{}{}'.format(self._current_url().scheme(),
self._current_url().host(),
':' + str(port) if port > -1 else '')
what = 'domain'
else:
elif what in ['url', 'pretty-url']:
flags = QUrl.RemovePassword
if not pretty:
if what != 'pretty-url':
flags |= QUrl.FullyEncoded
s = self._current_url().toString(flags)
what = 'URL'
what = 'URL' # For printing
elif what == 'selection':
caret = self._current_widget().caret
s = caret.selection()
if not caret.has_selection() or not s:
message.info(self._win_id, "Nothing to yank")
return
else: # pragma: no cover
raise ValueError("Invalid value {!r} for `what'.".format(what))
if sel and utils.supports_selection():
target = "primary selection"
@ -677,8 +691,15 @@ class CommandDispatcher:
target = "clipboard"
utils.set_clipboard(s, selection=sel)
message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s))
if what != 'selection':
message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s))
else:
message.info(self._win_id, "{} {} yanked to {}".format(
len(s), "char" if len(s) == 1 else "chars", target))
if not keep:
modeman.maybe_leave(self._win_id, KeyMode.caret,
"yank selected")
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -982,7 +1003,7 @@ class CommandDispatcher:
self._tabbed_browser.setUpdatesEnabled(True)
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
maxsplit=0, no_replace_variables=True)
def spawn(self, cmdline, userscript=False, verbose=False, detach=False):
"""Spawn a command in a shell.
@ -1755,31 +1776,6 @@ class CommandDispatcher:
"""Move the cursor or selection to the end of the document."""
self._current_widget().caret.move_to_end_of_document()
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank_selected(self, sel=False, keep=False):
"""Yank the selected text to the clipboard or primary selection.
Args:
sel: Use the primary selection instead of the clipboard.
keep: If given, stay in visual mode after yanking.
"""
caret = self._current_widget().caret
s = caret.selection()
if not caret.has_selection() or len(s) == 0:
message.info(self._win_id, "Nothing to yank")
return
if sel and utils.supports_selection():
target = "primary selection"
else:
sel = False
target = "clipboard"
utils.set_clipboard(s, sel)
message.info(self._win_id, "{} {} yanked to {}".format(
len(s), "char" if len(s) == 1 else "chars", target))
if not keep:
modeman.maybe_leave(self._win_id, KeyMode.caret, "yank selected")
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window')
def toggle_selection(self):

View File

@ -75,13 +75,13 @@ class Command:
deprecated: False, or a string to describe why a command is deprecated.
desc: The description of the command.
handler: The handler function to call.
completion: Completions to use for arguments, as a list of strings.
debug: Whether this is a debugging command (only shown with --debug).
parser: The ArgumentParser to use to parse this command.
flags_with_args: A list of flags which take an argument.
no_cmd_split: If true, ';;' to split sub-commands is ignored.
backend: Which backend the command works with (or None if it works with
both)
no_replace_variables: Don't replace variables like {url}
_qute_args: The saved data from @cmdutils.argument
_needs_js: Whether the command needs javascript enabled
_modes: The modes the command can be executed in.
@ -95,7 +95,7 @@ class Command:
hide=False, modes=None, not_modes=None, needs_js=False,
debug=False, ignore_args=False, deprecated=False,
no_cmd_split=False, star_args_optional=False, scope='global',
backend=None):
backend=None, no_replace_variables=False):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-locals
if modes is not None and not_modes is not None:
@ -127,6 +127,7 @@ class Command:
self.handler = handler
self.no_cmd_split = no_cmd_split
self.backend = backend
self.no_replace_variables = no_replace_variables
self.docparser = docutils.DocstringParser(handler)
self.parser = argparser.ArgumentParser(
@ -148,13 +149,7 @@ class Command:
self._qute_args = getattr(self.handler, 'qute_args', {})
self.handler.qute_args = None
args = self._inspect_func()
self.completion = []
for arg in args:
arg_completion = self.get_arg_info(arg).completion
if arg_completion is not None:
self.completion.append(arg_completion)
self._inspect_func()
def _check_prerequisites(self, win_id):
"""Check if the command is permitted to run currently.
@ -208,6 +203,11 @@ class Command:
"""Get an ArgInfo tuple for the given inspect.Parameter."""
return self._qute_args.get(param.name, ArgInfo())
def get_pos_arg_info(self, pos):
"""Get an ArgInfo tuple for the given positional parameter."""
name = self.pos_args[pos][0]
return self._qute_args.get(name, ArgInfo())
def _inspect_special_param(self, param):
"""Check if the given parameter is a special one.

View File

@ -52,29 +52,28 @@ def replace_variables(win_id, arglist):
args = []
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if '{url}' in arglist:
if any('{url}' in arg for arg in arglist):
url = _current_url(tabbed_browser).toString(QUrl.FullyEncoded |
QUrl.RemovePassword)
if '{url:pretty}' in arglist:
if any('{url:pretty}' in arg for arg in arglist):
pretty_url = _current_url(tabbed_browser).toString(QUrl.RemovePassword)
try:
if '{clipboard}' in arglist:
if any('{clipboard}' in arg for arg in arglist):
clipboard = utils.get_clipboard()
if '{primary}' in arglist:
if any('{primary}' in arg for arg in arglist):
primary = utils.get_clipboard(selection=True)
except utils.ClipboardEmptyError as e:
raise cmdexc.CommandError(e)
for arg in arglist:
if arg == '{url}':
args.append(url)
elif arg == '{url:pretty}':
args.append(pretty_url)
elif arg == '{clipboard}':
args.append(clipboard)
elif arg == '{primary}':
args.append(primary)
else:
args.append(arg)
if '{url}' in arg:
arg = arg.replace('{url}', url)
if '{url:pretty}' in arg:
arg = arg.replace('{url:pretty}', pretty_url)
if '{clipboard}' in arg:
arg = arg.replace('{clipboard}', clipboard)
if '{primary}' in arg:
arg = arg.replace('{primary}', primary)
args.append(arg)
return args
@ -290,7 +289,10 @@ class CommandRunner(QObject):
window=self._win_id)
cur_mode = mode_manager.mode
args = replace_variables(self._win_id, result.args)
if result.cmd.no_replace_variables:
args = result.args
else:
args = replace_variables(self._win_id, result.args)
if count is not None:
if result.count is not None:
raise cmdexc.CommandMetaError("Got count via command and "

View File

@ -204,25 +204,18 @@ class Completer(QObject):
return sortfilter.CompletionFilterModel(source=model, parent=self)
# delegate completion to command
try:
completions = cmdutils.cmd_dict[parts[0]].completion
cmd = cmdutils.cmd_dict[parts[0]]
except KeyError:
# entering an unknown command
return None
if completions is None:
# command without any available completions
return None
dbg_completions = [c.name for c in completions]
try:
idx = cursor_part - 1
completion = completions[idx]
completion = cmd.get_pos_arg_info(idx).completion
except IndexError:
# More arguments than completions
log.completion.debug("completions: {}".format(
', '.join(dbg_completions)))
# user provided more positional arguments than the command takes
return None
if completion is None:
return None
dbg_completions[idx] = '*' + dbg_completions[idx] + '*'
log.completion.debug("completions: {}".format(
', '.join(dbg_completions)))
model = self._get_completion_model(completion, parts, cursor_part)
return model

View File

@ -27,8 +27,7 @@ Module attributes:
import functools
from qutebrowser.completion.models import (miscmodels, urlmodel, configmodel,
base)
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.utils import objreg, usertypes, log, debug
from qutebrowser.config import configdata
@ -115,13 +114,6 @@ def init_session_completion():
_instances[usertypes.Completion.sessions] = model
def _init_empty_completion():
"""Initialize empty completion model."""
log.completion.debug("Initializing empty completion.")
if usertypes.Completion.empty not in _instances:
_instances[usertypes.Completion.empty] = base.BaseCompletionModel()
INITIALIZERS = {
usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion,
@ -133,7 +125,6 @@ INITIALIZERS = {
usertypes.Completion.quickmark_by_name: init_quickmark_completions,
usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion,
usertypes.Completion.empty: _init_empty_completion,
}

View File

@ -135,8 +135,8 @@ class CompletionFilterModel(QSortFilterProxyModel):
for col in self.srcmodel.columns_to_filter:
idx = self.srcmodel.index(row, col, parent)
if not idx.isValid():
# No entries in parent model
if not idx.isValid(): # pragma: no cover
# this is a sanity check not hit by any test case
continue
data = self.srcmodel.data(idx)
if not data:

View File

@ -1506,12 +1506,12 @@ KEY_DATA = collections.OrderedDict([
('enter-mode jump_mark', ["'"]),
('yank', ['yy']),
('yank -s', ['yY']),
('yank -t', ['yt']),
('yank -ts', ['yT']),
('yank -d', ['yd']),
('yank -ds', ['yD']),
('yank -p', ['yp']),
('yank -ps', ['yP']),
('yank title', ['yt']),
('yank title -s', ['yT']),
('yank domain', ['yd']),
('yank domain -s', ['yD']),
('yank pretty-url', ['yp']),
('yank pretty-url -s', ['yP']),
('open {clipboard}', ['pp']),
('open {primary}', ['pP']),
('open -t {clipboard}', ['Pp']),
@ -1638,8 +1638,8 @@ KEY_DATA = collections.OrderedDict([
('move-to-end-of-line', ['$']),
('move-to-start-of-document', ['gg']),
('move-to-end-of-document', ['G']),
('yank-selected -p', ['Y']),
('yank-selected', ['y'] + RETURN_KEYS),
('yank selection -s', ['Y']),
('yank selection', ['y'] + RETURN_KEYS),
('scroll left', ['H']),
('scroll down', ['J']),
('scroll up', ['K']),
@ -1678,6 +1678,15 @@ CHANGED_KEY_COMMANDS = [
(re.compile(r'^hint links fill "([^"]*)"$'), r'hint links fill \1'),
(re.compile(r'^yank -t(\S+)'), r'yank title -\1'),
(re.compile(r'^yank -t'), r'yank title'),
(re.compile(r'^yank -d(\S+)'), r'yank domain -\1'),
(re.compile(r'^yank -d'), r'yank domain'),
(re.compile(r'^yank -p(\S+)'), r'yank pretty-url -\1'),
(re.compile(r'^yank -p'), r'yank pretty-url'),
(re.compile(r'^yank-selected -p'), r'yank selection -s'),
(re.compile(r'^yank-selected'), r'yank selection'),
(re.compile(r'^paste$'), r'open {clipboard}'),
(re.compile(r'^paste -([twb])$'), r'open -\1 {clipboard}'),
(re.compile(r'^paste -([twb])s$'), r'open -\1 {primary}'),

View File

@ -150,9 +150,9 @@ class KeyConfigParser(QObject):
data = str(self)
f.write(data)
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True)
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True,
no_replace_variables=True)
@cmdutils.argument('win_id', win_id=True)
@cmdutils.argument('key', completion=usertypes.Completion.empty)
@cmdutils.argument('command', completion=usertypes.Completion.command)
def bind(self, key, win_id, command=None, *, mode='normal', force=False):
"""Bind a key to a command.
@ -361,12 +361,12 @@ class KeyConfigParser(QObject):
raise KeyConfigError("Got command '{}' without getting a "
"section!".format(line))
else:
self._validate_command(line)
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
if rgx.match(line):
line = rgx.sub(repl, line)
self._mark_config_dirty()
break
self._validate_command(line)
self._cur_command = line
def _read_keybinding(self, line):

View File

@ -23,8 +23,8 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize
from PyQt5.QtWidgets import QSizePolicy
from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.commands import cmdexc, cmdutils, runners
from qutebrowser.misc import cmdhistory, split
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.misc import cmdhistory
from qutebrowser.misc import miscwidgets as misc
from qutebrowser.utils import usertypes, log, objreg
@ -108,10 +108,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
space: If given, a space is added to the end.
append: If given, the text is appended to the current text.
"""
args = split.simple_split(text)
args = runners.replace_variables(self._win_id, args)
text = ' '.join(args)
if space:
text += ' '
if append:

View File

@ -35,8 +35,8 @@ class ExternalEditor(QObject):
Attributes:
_text: The current text before the editor is opened.
_oshandle: The OS level handle to the tmpfile.
_filehandle: The file handle to the tmpfile.
_file: The file handle as tempfile.NamedTemporaryFile. Note that this
handle will be closed after the initial file has been created.
_proc: The GUIProcess of the editor.
_win_id: The window ID the ExternalEditor is associated with.
"""
@ -46,20 +46,18 @@ class ExternalEditor(QObject):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._text = None
self._oshandle = None
self._filename = None
self._file = None
self._proc = None
self._win_id = win_id
def _cleanup(self):
"""Clean up temporary files after the editor closed."""
if self._oshandle is None or self._filename is None:
if self._file is None:
# Could not create initial file.
return
try:
os.close(self._oshandle)
if self._proc.exit_status() != QProcess.CrashExit:
os.remove(self._filename)
os.remove(self._file.name)
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
@ -82,7 +80,7 @@ class ExternalEditor(QObject):
return
encoding = config.get('general', 'editor-encoding')
try:
with open(self._filename, 'r', encoding=encoding) as f:
with open(self._file.name, 'r', encoding=encoding) as f:
text = f.read()
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
@ -108,13 +106,18 @@ class ExternalEditor(QObject):
if self._text is not None:
raise ValueError("Already editing a file!")
self._text = text
encoding = config.get('general', 'editor-encoding')
try:
self._oshandle, self._filename = tempfile.mkstemp(
text=True, prefix='qutebrowser-editor-')
if text:
encoding = config.get('general', 'editor-encoding')
with open(self._filename, 'w', encoding=encoding) as f:
f.write(text)
# Close while the external process is running, as otherwise systems
# with exclusive write access (e.g. Windows) may fail to update
# the file from the external editor, see
# https://github.com/The-Compiler/qutebrowser/issues/1767
with tempfile.NamedTemporaryFile(
mode='w', prefix='qutebrowser-editor-', encoding=encoding,
delete=False) as fobj:
if text:
fobj.write(text)
self._file = fobj
except OSError as e:
message.error(self._win_id, "Failed to create initial file: "
"{}".format(e))
@ -125,6 +128,6 @@ class ExternalEditor(QObject):
self._proc.error.connect(self.on_proc_error)
editor = config.get('general', 'editor')
executable = editor[0]
args = [arg.replace('{}', self._filename) for arg in editor[1:]]
args = [arg.replace('{}', self._file.name) for arg in editor[1:]]
log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
self._proc.start(executable, args)

View File

@ -39,7 +39,7 @@ from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
@cmdutils.register(maxsplit=1, no_cmd_split=True)
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('win_id', win_id=True)
def later(ms: int, command, win_id):
"""Execute a command after some time.
@ -69,7 +69,7 @@ def later(ms: int, command, win_id):
raise
@cmdutils.register(maxsplit=1, no_cmd_split=True)
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
@cmdutils.argument('win_id', win_id=True)
def repeat(times: int, command, win_id):
"""Repeat a given command.

View File

@ -238,8 +238,7 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
# Available command completions
Completion = enum('Completion', ['command', 'section', 'option', 'value',
'helptopic', 'quickmark_by_name',
'bookmark_by_url', 'url', 'tab', 'sessions',
'empty'])
'bookmark_by_url', 'url', 'tab', 'sessions'])
# Exit statuses for errors. Needs to be an int for sys.exit.

View File

@ -150,12 +150,14 @@ PERFECT_FILES = [
('tests/unit/completion/test_models.py',
'qutebrowser/completion/models/base.py'),
('tests/unit/completion/test_sortfilter.py',
'qutebrowser/completion/models/sortfilter.py'),
]
# 100% coverage because of end2end tests, but no perfect unit tests yet.
WHITELISTED_FILES = []
WHITELISTED_FILES = ['qutebrowser/browser/webkit/webkitinspector.py']
class Skipped(Exception):

View File

@ -239,7 +239,8 @@ def _get_command_doc_notes(cmd):
Yield:
Strings which should be added to the docs.
"""
if cmd.maxsplit is not None or cmd.no_cmd_split:
if (cmd.maxsplit is not None or cmd.no_cmd_split or
cmd.no_replace_variables and cmd.name != "spawn"):
yield ""
yield "==== note"
if cmd.maxsplit is not None:
@ -248,6 +249,8 @@ def _get_command_doc_notes(cmd):
if cmd.no_cmd_split:
yield ("* With this command, +;;+ is interpreted literally "
"instead of splitting off a second command.")
if cmd.no_replace_variables and cmd.name != "spawn":
yield r"* This command does not replace variables like +\{url\}+."
def _get_action_metavar(action, nargs=1):

View File

@ -10,7 +10,7 @@ Feature: Caret mode
Scenario: Selecting the entire document
When I run :toggle-selection
And I run :move-to-end-of-document
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain:
one two three
eins zwei drei
@ -23,14 +23,14 @@ Feature: Caret mode
And I run :move-to-start-of-document
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one"
Scenario: Moving to end and to start of document (with selection)
When I run :move-to-end-of-document
And I run :toggle-selection
And I run :move-to-start-of-document
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain:
one two three
eins zwei drei
@ -43,7 +43,7 @@ Feature: Caret mode
Scenario: Selecting a block
When I run :toggle-selection
And I run :move-to-end-of-next-block
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain:
one two three
eins zwei drei
@ -53,7 +53,7 @@ Feature: Caret mode
And I run :toggle-selection
And I run :move-to-end-of-prev-block
And I run :move-to-prev-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain:
drei
@ -64,14 +64,14 @@ Feature: Caret mode
And I run :move-to-end-of-prev-block
And I run :toggle-selection
And I run :move-to-prev-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "drei"
Scenario: Moving back to the start of previous block (with selection)
When I run :move-to-end-of-next-block with count 2
And I run :toggle-selection
And I run :move-to-start-of-prev-block
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain:
eins zwei drei
@ -82,20 +82,20 @@ Feature: Caret mode
And I run :move-to-start-of-prev-block
And I run :toggle-selection
And I run :move-to-next-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "eins "
Scenario: Moving to the start of next block (with selection)
When I run :toggle-selection
And I run :move-to-start-of-next-block
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three\n"
Scenario: Moving to the start of next block
When I run :move-to-start-of-next-block
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "eins"
# line
@ -103,20 +103,20 @@ Feature: Caret mode
Scenario: Selecting a line
When I run :toggle-selection
And I run :move-to-end-of-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three"
Scenario: Moving and selecting a line
When I run :move-to-next-line
And I run :toggle-selection
And I run :move-to-end-of-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "eins zwei drei"
Scenario: Selecting next line
When I run :toggle-selection
And I run :move-to-next-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three\n"
Scenario: Moving to end and to start of line
@ -124,21 +124,21 @@ Feature: Caret mode
And I run :move-to-start-of-line
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one"
Scenario: Selecting a line (backwards)
When I run :move-to-end-of-line
And I run :toggle-selection
When I run :move-to-start-of-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three"
Scenario: Selecting previous line
When I run :move-to-next-line
And I run :toggle-selection
When I run :move-to-prev-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three\n"
Scenario: Moving to previous line
@ -146,7 +146,7 @@ Feature: Caret mode
When I run :move-to-prev-line
And I run :toggle-selection
When I run :move-to-next-line
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one two three\n"
# word
@ -154,35 +154,35 @@ Feature: Caret mode
Scenario: Selecting a word
When I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one"
Scenario: Moving to end and selecting a word
When I run :move-to-end-of-word
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain " two"
Scenario: Moving to next word and selecting a word
When I run :move-to-next-word
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "two"
Scenario: Moving to next word and selecting until next word
When I run :move-to-next-word
And I run :toggle-selection
And I run :move-to-next-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "two "
Scenario: Moving to previous word and selecting a word
When I run :move-to-end-of-word
And I run :toggle-selection
And I run :move-to-prev-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one"
Scenario: Moving to previous word
@ -190,7 +190,7 @@ Feature: Caret mode
And I run :move-to-prev-word
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "one"
# char
@ -198,21 +198,21 @@ Feature: Caret mode
Scenario: Selecting a char
When I run :toggle-selection
And I run :move-to-next-char
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "o"
Scenario: Moving and selecting a char
When I run :move-to-next-char
And I run :toggle-selection
And I run :move-to-next-char
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "n"
Scenario: Selecting previous char
When I run :move-to-end-of-word
And I run :toggle-selection
And I run :move-to-prev-char
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "e"
Scenario: Moving to previous char
@ -220,41 +220,41 @@ Feature: Caret mode
And I run :move-to-prev-char
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "e"
# :yank-selected
# :yank selection
Scenario: :yank-selected without selection
When I run :yank-selected
Scenario: :yank selection without selection
When I run :yank selection
Then the message "Nothing to yank" should be shown.
Scenario: :yank-selected message
Scenario: :yank selection message
When I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected
And I run :yank selection
Then the message "3 chars yanked to clipboard" should be shown.
Scenario: :yank-selected message with one char
Scenario: :yank selection message with one char
When I run :toggle-selection
And I run :move-to-next-char
And I run :yank-selected
And I run :yank selection
Then the message "1 char yanked to clipboard" should be shown.
Scenario: :yank-selected with primary selection
Scenario: :yank selection with primary selection
When selection is supported
And I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected --sel
And I run :yank selection --sel
Then the message "3 chars yanked to primary selection" should be shown.
And the primary selection should contain "one"
Scenario: :yank-selected with --keep
Scenario: :yank selection with --keep
When I run :toggle-selection
And I run :move-to-end-of-word
And I run :yank-selected --keep
And I run :yank selection --keep
And I run :move-to-end-of-word
And I run :yank-selected --keep
And I run :yank selection --keep
Then the message "3 chars yanked to clipboard" should be shown.
And the message "7 chars yanked to clipboard" should be shown.
And the clipboard should contain "one two"
@ -265,7 +265,7 @@ Feature: Caret mode
When I run :toggle-selection
And I run :move-to-end-of-word
And I run :drop-selection
And I run :yank-selected
And I run :yank selection
Then the message "Nothing to yank" should be shown.
# :follow-selected

View File

@ -9,19 +9,19 @@ Feature: Searching on a page
Scenario: Searching text
When I run :search foo
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "foo"
Scenario: Searching twice
When I run :search foo
And I run :search bar
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Bar"
Scenario: Searching with --reverse
When I set general -> ignore-case to true
And I run :search -r foo
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Foo"
Scenario: Searching without matches
@ -32,13 +32,13 @@ Feature: Searching on a page
Scenario: Searching with / and spaces at the end (issue 874)
When I run :set-cmd-text -s /space
And I run :command-accept
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "space "
Scenario: Searching with / and slash in search term (issue 507)
When I run :set-cmd-text -s //slash
And I run :command-accept
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "/slash"
# This doesn't work because this is QtWebKit behavior.
@ -52,25 +52,25 @@ Feature: Searching on a page
Scenario: Searching text with ignore-case = true
When I set general -> ignore-case to true
And I run :search bar
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Bar"
Scenario: Searching text with ignore-case = false
When I set general -> ignore-case to false
And I run :search bar
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "bar"
Scenario: Searching text with ignore-case = smart (lower-case)
When I set general -> ignore-case to smart
And I run :search bar
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Bar"
Scenario: Searching text with ignore-case = smart (upper-case)
When I set general -> ignore-case to smart
And I run :search Foo
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Foo" # even though foo was first
## :search-next
@ -79,21 +79,21 @@ Feature: Searching on a page
When I set general -> ignore-case to true
And I run :search foo
And I run :search-next
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Foo"
Scenario: Jumping to next match with count
When I set general -> ignore-case to true
And I run :search baz
And I run :search-next with count 2
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "BAZ"
Scenario: Jumping to next match with --reverse
When I set general -> ignore-case to true
And I run :search --reverse foo
And I run :search-next
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "foo"
Scenario: Jumping to next match without search
@ -107,7 +107,7 @@ Feature: Searching on a page
And I run :search foo
And I run :tab-prev
And I run :search-next
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "foo"
## :search-prev
@ -117,7 +117,7 @@ Feature: Searching on a page
And I run :search foo
And I run :search-next
And I run :search-prev
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "foo"
Scenario: Jumping to previous match with count
@ -126,7 +126,7 @@ Feature: Searching on a page
And I run :search-next
And I run :search-next
And I run :search-prev with count 2
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "baz"
Scenario: Jumping to previous match with --reverse
@ -134,7 +134,7 @@ Feature: Searching on a page
And I run :search --reverse foo
And I run :search-next
And I run :search-prev
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Foo"
Scenario: Jumping to previous match without search
@ -149,14 +149,14 @@ Feature: Searching on a page
When I run :search foo
And I run :search-next
And I run :search-next
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "foo"
Scenario: Wrapping around page with --reverse
When I run :search --reverse foo
And I run :search-next
And I run :search-next
And I run :yank-selected
And I run :yank selection
Then the clipboard should contain "Foo"
# TODO: wrapping message with scrolling

View File

@ -23,13 +23,13 @@ Feature: Yanking and pasting.
Scenario: Yanking title to clipboard
When I open data/title.html
And I wait for regex "Changing title for idx \d to 'Test title'" in the log
And I run :yank --title
And I run :yank title
Then the message "Yanked title to clipboard: Test title" should be shown
And the clipboard should contain "Test title"
Scenario: Yanking domain to clipboard
When I open data/title.html
And I run :yank --domain
And I run :yank domain
Then the message "Yanked domain to clipboard: http://localhost:(port)" should be shown
And the clipboard should contain "http://localhost:(port)"
@ -41,7 +41,7 @@ Feature: Yanking and pasting.
Scenario: Yanking pretty decoded URL
When I open data/title with spaces.html
And I run :yank --pretty
And I run :yank pretty-url
Then the message "Yanked URL to clipboard: http://localhost:(port)/data/title with spaces.html" should be shown
And the clipboard should contain "http://localhost:(port)/data/title with spaces.html"

View File

@ -54,7 +54,7 @@ def test_insert_mode(file_name, source, input_text, auto_insert, quteproc):
quteproc.send_cmd(':enter-mode caret')
quteproc.send_cmd(':toggle-selection')
quteproc.send_cmd(':move-to-prev-word')
quteproc.send_cmd(':yank-selected')
quteproc.send_cmd(':yank selection')
expected_message = '{} chars yanked to clipboard'.format(len(input_text))
quteproc.mark_expected(category='message',

View File

@ -291,6 +291,21 @@ class TestRegister:
else:
assert cmd._get_call_args(win_id=0) == ([expected], {})
def test_pos_arg_info(self):
@cmdutils.register()
@cmdutils.argument('foo', choices=('a', 'b'))
@cmdutils.argument('bar', choices=('x', 'y'))
@cmdutils.argument('opt')
def fun(foo, bar, opt=False):
"""Blah."""
pass
cmd = cmdutils.cmd_dict['fun']
assert cmd.get_pos_arg_info(0) == command.ArgInfo(choices=('a', 'b'))
assert cmd.get_pos_arg_info(1) == command.ArgInfo(choices=('x', 'y'))
with pytest.raises(IndexError):
cmd.get_pos_arg_info(2)
class TestArgument:

View File

@ -27,6 +27,7 @@ from PyQt5.QtGui import QStandardItemModel
from qutebrowser.completion import completer
from qutebrowser.utils import usertypes
from qutebrowser.commands import command, cmdutils
class FakeCompletionModel(QStandardItemModel):
@ -91,24 +92,48 @@ def instances(monkeypatch):
@pytest.fixture(autouse=True)
def cmdutils_patch(monkeypatch, stubs):
"""Patch the cmdutils module to provide fake commands."""
@cmdutils.argument('section_', completion=usertypes.Completion.section)
@cmdutils.argument('option', completion=usertypes.Completion.option)
@cmdutils.argument('value', completion=usertypes.Completion.value)
def set_command(section_=None, option=None, value=None):
"""docstring!"""
pass
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
def show_help(tab=False, bg=False, window=False, topic=None):
"""docstring!"""
pass
@cmdutils.argument('url', completion=usertypes.Completion.url)
@cmdutils.argument('count', count=True)
def openurl(url=None, implicit=False, bg=False, tab=False, window=False,
count=None):
"""docstring!"""
pass
@cmdutils.argument('win_id', win_id=True)
@cmdutils.argument('command', completion=usertypes.Completion.command)
def bind(key, win_id, command=None, *, mode='normal', force=False):
"""docstring!"""
# pylint: disable=unused-variable
pass
def tab_detach():
"""docstring!"""
pass
cmds = {
'set': [usertypes.Completion.section, usertypes.Completion.option,
usertypes.Completion.value],
'help': [usertypes.Completion.helptopic],
'quickmark-load': [usertypes.Completion.quickmark_by_name],
'bookmark-load': [usertypes.Completion.bookmark_by_url],
'open': [usertypes.Completion.url],
'buffer': [usertypes.Completion.tab],
'session-load': [usertypes.Completion.sessions],
'bind': [usertypes.Completion.empty, usertypes.Completion.command],
'tab-detach': None,
'set': set_command,
'help': show_help,
'open': openurl,
'bind': bind,
'tab-detach': tab_detach,
}
cmd_utils = stubs.FakeCmdUtils({
name: stubs.FakeCommand(completion=compl)
for name, compl in cmds.items()
name: command.Command(name=name, handler=fn)
for name, fn in cmds.items()
})
monkeypatch.setattr('qutebrowser.completion.completer.cmdutils',
cmd_utils)
monkeypatch.setattr('qutebrowser.completion.completer.cmdutils', cmd_utils)
def _set_cmd_prompt(cmd, txt):
@ -143,21 +168,17 @@ def _validate_cmd_prompt(cmd, txt):
(':set general ignore-case |', usertypes.Completion.value),
(':set general huh |', None),
(':help |', usertypes.Completion.helptopic),
(':quickmark-load |', usertypes.Completion.quickmark_by_name),
(':bookmark-load |', usertypes.Completion.bookmark_by_url),
(':help |', usertypes.Completion.helptopic),
(':open |', usertypes.Completion.url),
(':buffer |', usertypes.Completion.tab),
(':session-load |', usertypes.Completion.sessions),
(':bind |', usertypes.Completion.empty),
(':bind |', None),
(':bind <c-x> |', usertypes.Completion.command),
(':bind <c-x> foo|', usertypes.Completion.command),
(':bind <c-x>| foo', usertypes.Completion.empty),
(':bind <c-x>| foo', None),
(':set| general ', usertypes.Completion.command),
(':|set general ', usertypes.Completion.command),
(':set gene|ral ignore-case', usertypes.Completion.section),
(':|', usertypes.Completion.command),
(': |', usertypes.Completion.command),
(':bookmark-load |', usertypes.Completion.bookmark_by_url),
('/|', None),
(':open -t|', None),
(':open --tab|', None),

View File

@ -21,9 +21,44 @@
import pytest
from PyQt5.QtCore import Qt
from qutebrowser.completion.models import base, sortfilter
def _create_model(data):
"""Create a completion model populated with the given data.
data: A list of lists, where each sub-list represents a category, each
tuple in the sub-list represents an item, and each value in the
tuple represents the item data for that column
"""
model = base.BaseCompletionModel()
for catdata in data:
cat = model.new_category('')
for itemdata in catdata:
model.new_item(cat, *itemdata)
return model
def _extract_model_data(model):
"""Express a model's data as a list for easier comparison.
Return: A list of lists, where each sub-list represents a category, each
tuple in the sub-list represents an item, and each value in the
tuple represents the item data for that column
"""
data = []
for i in range(0, model.rowCount()):
cat_idx = model.index(i, 0)
row = []
for j in range(0, model.rowCount(cat_idx)):
row.append((model.data(cat_idx.child(j, 0)),
model.data(cat_idx.child(j, 1)),
model.data(cat_idx.child(j, 2))))
data.append(row)
return data
@pytest.mark.parametrize('pattern, data, expected', [
('foo', 'barfoobar', True),
('foo', 'barFOObar', True),
@ -46,3 +81,145 @@ def test_filter_accepts_row(pattern, data, expected):
row_count = filter_model.rowCount(idx)
assert row_count == (1 if expected else 0)
@pytest.mark.parametrize('tree, first, last', [
([[('Aa',)]], 'Aa', 'Aa'),
([[('Aa',)], [('Ba',)]], 'Aa', 'Ba'),
([[('Aa',), ('Ab',), ('Ac',)], [('Ba',), ('Bb',)], [('Ca',)]],
'Aa', 'Ca'),
([[], [('Ba',)]], 'Ba', 'Ba'),
([[], [], [('Ca',)]], 'Ca', 'Ca'),
([[], [], [('Ca',), ('Cb',)]], 'Ca', 'Cb'),
([[('Aa',)], []], 'Aa', 'Aa'),
([[('Aa',)], []], 'Aa', 'Aa'),
([[('Aa',)], [], []], 'Aa', 'Aa'),
([[('Aa',)], [], [('Ca',)]], 'Aa', 'Ca'),
([[], []], None, None),
])
def test_first_last_item(tree, first, last):
"""Test that first() and last() return indexes to the first and last items.
Args:
tree: Each list represents a completion category, with each string
being an item under that category.
first: text of the first item
last: text of the last item
"""
model = _create_model(tree)
filter_model = sortfilter.CompletionFilterModel(model)
assert filter_model.data(filter_model.first_item()) == first
assert filter_model.data(filter_model.last_item()) == last
def test_set_source_model():
"""Ensure setSourceModel sets source_model and clears the pattern."""
model1 = base.BaseCompletionModel()
model2 = base.BaseCompletionModel()
filter_model = sortfilter.CompletionFilterModel(model1)
filter_model.set_pattern('foo')
# sourceModel() is cached as srcmodel, so make sure both match
assert filter_model.srcmodel is model1
assert filter_model.sourceModel() is model1
assert filter_model.pattern == 'foo'
filter_model.setSourceModel(model2)
assert filter_model.srcmodel is model2
assert filter_model.sourceModel() is model2
assert not filter_model.pattern
@pytest.mark.parametrize('tree, expected', [
([[('Aa',)]], 1),
([[('Aa',)], [('Ba',)]], 2),
([[('Aa',), ('Ab',), ('Ac',)], [('Ba',), ('Bb',)], [('Ca',)]], 6),
([[], [('Ba',)]], 1),
([[], [], [('Ca',)]], 1),
([[], [], [('Ca',), ('Cb',)]], 2),
([[('Aa',)], []], 1),
([[('Aa',)], []], 1),
([[('Aa',)], [], []], 1),
([[('Aa',)], [], [('Ca',)]], 2),
])
def test_count(tree, expected):
model = _create_model(tree)
filter_model = sortfilter.CompletionFilterModel(model)
assert filter_model.count() == expected
@pytest.mark.parametrize('pattern, dumb_sort, filter_cols, before, after', [
('foo', None, [0],
[[('foo', '', ''), ('bar', '', '')]],
[[('foo', '', '')]]),
('foo', None, [0],
[[('foob', '', ''), ('fooc', '', ''), ('fooa', '', '')]],
[[('fooa', '', ''), ('foob', '', ''), ('fooc', '', '')]]),
('foo', None, [0],
[[('foo', '', '')], [('bar', '', '')]],
[[('foo', '', '')], []]),
# prefer foobar as it starts with the pattern
('foo', None, [0],
[[('barfoo', '', ''), ('foobar', '', '')]],
[[('foobar', '', ''), ('barfoo', '', '')]]),
# however, don't rearrange categories
('foo', None, [0],
[[('barfoo', '', '')], [('foobar', '', '')]],
[[('barfoo', '', '')], [('foobar', '', '')]]),
('foo', None, [1],
[[('foo', 'bar', ''), ('bar', 'foo', '')]],
[[('bar', 'foo', '')]]),
('foo', None, [0, 1],
[[('foo', 'bar', ''), ('bar', 'foo', ''), ('bar', 'bar', '')]],
[[('foo', 'bar', ''), ('bar', 'foo', '')]]),
('foo', None, [0, 1, 2],
[[('foo', '', ''), ('bar', '')]],
[[('foo', '', '')]]),
# the fourth column is the sort role, which overrides data-based sorting
('', None, [0],
[[('two', '', '', 2), ('one', '', '', 1), ('three', '', '', 3)]],
[[('one', '', ''), ('two', '', ''), ('three', '', '')]]),
('', Qt.AscendingOrder, [0],
[[('two', '', '', 2), ('one', '', '', 1), ('three', '', '', 3)]],
[[('one', '', ''), ('two', '', ''), ('three', '', '')]]),
('', Qt.DescendingOrder, [0],
[[('two', '', '', 2), ('one', '', '', 1), ('three', '', '', 3)]],
[[('three', '', ''), ('two', '', ''), ('one', '', '')]]),
])
def test_set_pattern(pattern, dumb_sort, filter_cols, before, after):
"""Validate the filtering and sorting results of set_pattern."""
model = _create_model(before)
model.DUMB_SORT = dumb_sort
model.columns_to_filter = filter_cols
filter_model = sortfilter.CompletionFilterModel(model)
filter_model.set_pattern(pattern)
actual = _extract_model_data(filter_model)
assert actual == after
def test_sort():
"""Ensure that a sort argument passed to sort overrides DUMB_SORT.
While test_set_pattern above covers most of the sorting logic, this
particular case is easier to test separately.
"""
model = _create_model([[('B', '', '', 1),
('C', '', '', 2),
('A', '', '', 0)]])
filter_model = sortfilter.CompletionFilterModel(model)
filter_model.sort(0, Qt.AscendingOrder)
actual = _extract_model_data(filter_model)
assert actual == [[('A', '', ''), ('B', '', ''), ('C', '', '')]]
filter_model.sort(0, Qt.DescendingOrder)
actual = _extract_model_data(filter_model)
assert actual == [[('C', '', ''), ('B', '', ''), ('A', '', '')]]

View File

@ -287,6 +287,17 @@ class TestKeyConfigParser:
('hint links fill ":open -t {hint-url}"',
'hint links fill :open -t {hint-url}'),
('yank-selected', 'yank selection'),
('yank-selected --sel', 'yank selection --sel'),
('yank-selected -p', 'yank selection -s'),
('yank -t', 'yank title'),
('yank -ts', 'yank title -s'),
('yank -d', 'yank domain'),
('yank -ds', 'yank domain -s'),
('yank -p', 'yank pretty-url'),
('yank -ps', 'yank pretty-url -s'),
('paste', 'open {clipboard}'),
('paste -t', 'open -t {clipboard}'),
('paste -ws', 'open -w {primary}'),

View File

@ -69,14 +69,14 @@ class TestArg:
config_stub.data['general']['editor'] = ['bin', 'foo', '{}', 'bar']
editor.edit("")
editor._proc._proc.start.assert_called_with(
"bin", ["foo", editor._filename, "bar"])
"bin", ["foo", editor._file.name, "bar"])
def test_placeholder_inline(self, config_stub, editor):
"""Test starting editor with placeholder arg inside of another arg."""
config_stub.data['general']['editor'] = ['bin', 'foo{}', 'bar']
editor.edit("")
editor._proc._proc.start.assert_called_with(
"bin", ["foo" + editor._filename, "bar"])
"bin", ["foo" + editor._file.name, "bar"])
class TestFileHandling:
@ -86,7 +86,7 @@ class TestFileHandling:
def test_ok(self, editor):
"""Test file handling when closing with an exit status == 0."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
assert os.path.basename(filename).startswith('qutebrowser-editor-')
editor._proc.finished.emit(0, QProcess.NormalExit)
@ -95,7 +95,7 @@ class TestFileHandling:
def test_error(self, editor):
"""Test file handling when closing with an exit status != 0."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
editor._proc._proc.exitStatus = mock.Mock(
@ -109,7 +109,7 @@ class TestFileHandling:
def test_crash(self, editor):
"""Test file handling when closing with a crash."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
editor._proc._proc.exitStatus = mock.Mock(
@ -125,7 +125,7 @@ class TestFileHandling:
def test_unreadable(self, message_mock, editor):
"""Test file handling when closing with an unreadable file."""
editor.edit("")
filename = editor._filename
filename = editor._file.name
assert os.path.exists(filename)
os.chmod(filename, 0o077)
editor._proc.finished.emit(0, QProcess.NormalExit)
@ -160,10 +160,10 @@ def test_modify(editor, initial_text, edited_text):
"""Test if inputs get modified correctly."""
editor.edit(initial_text)
with open(editor._filename, 'r', encoding='utf-8') as f:
with open(editor._file.name, 'r', encoding='utf-8') as f:
assert f.read() == initial_text
with open(editor._filename, 'w', encoding='utf-8') as f:
with open(editor._file.name, 'w', encoding='utf-8') as f:
f.write(edited_text)
editor._proc.finished.emit(0, QProcess.NormalExit)