Merge branch 'master' into es6ify-js

This commit is contained in:
plexigras 2017-11-02 16:44:25 +01:00 committed by GitHub
commit 6e624bcd3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 272 additions and 115 deletions

View File

@ -51,7 +51,7 @@ matrix:
env: TESTENV=eslint env: TESTENV=eslint
language: node_js language: node_js
python: null python: null
node_js: node node_js: "lts/*"
fast_finish: true fast_finish: true
cache: cache:

View File

@ -34,6 +34,8 @@ Added
widget. widget.
- `:edit-url` now handles the `--private` and `--related` flags, which have the - `:edit-url` now handles the `--private` and `--related` flags, which have the
same effect they have with `:open`. same effect they have with `:open`.
- New `{line}` and `{column}` replacements for `editor.command` to position the
cursor correctly.
Changed Changed
~~~~~~~ ~~~~~~~
@ -76,6 +78,8 @@ Fixed
~~~~~ ~~~~~
- Handle accessing a locked sqlite database gracefully - Handle accessing a locked sqlite database gracefully
- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a
window.
v1.0.2 v1.0.2
------ ------

View File

@ -152,7 +152,7 @@ For QtWebEngine:
`:set spellcheck.languages "['en-US', 'pl-PL']"` `:set spellcheck.languages "['en-US', 'pl-PL']"`
How do I use Tor with qutebrowser?:: How do I use Tor with qutebrowser?::
Start tor on your machine, and do `:set network proxy socks://localhost:9050/` Start tor on your machine, and do `:set content.proxy socks://localhost:9050/`
in qutebrowser. Note this won't give you the same amount of fingerprinting in qutebrowser. Note this won't give you the same amount of fingerprinting
protection that the Tor Browser does, but it's useful to be able to access protection that the Tor Browser does, but it's useful to be able to access
`.onion` sites. `.onion` sites.
@ -162,7 +162,7 @@ Why does J move to the next (right) tab, and K to the previous (left) one?::
and qutebrowser's keybindings are designed to be compatible with dwb's. and qutebrowser's keybindings are designed to be compatible with dwb's.
The rationale behind it is that J is "down" in vim, and K is "up", which The rationale behind it is that J is "down" in vim, and K is "up", which
corresponds nicely to "next"/"previous". It also makes much more sense with corresponds nicely to "next"/"previous". It also makes much more sense with
vertical tabs (e.g. `:set tabs position left`). vertical tabs (e.g. `:set tabs.position left`).
What's the difference between insert and passthrough mode?:: What's the difference between insert and passthrough mode?::
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to They are quite similar, but insert mode has some bindings (like `Ctrl-e` to

View File

@ -1961,7 +1961,14 @@ Default: +pass:[-1]+
[[editor.command]] [[editor.command]]
=== editor.command === editor.command
The editor (and arguments) to use for the `open-editor` command. The editor (and arguments) to use for the `open-editor` command.
`{}` gets replaced by the filename of the file to be edited. Several placeholders are supported. Placeholders are substituted by the
respective value when executing the command.
`{file}` gets replaced by the filename of the file to be edited.
`{line}` gets replaced by the line in which the caret is found in the text.
`{column}` gets replaced by the column in which the caret is found in the text.
`{line0}` same as `{line}`, but starting from index 0.
`{column0}` same as `{column}`, but starting from index 0.
Type: <<types,ShellCommand>> Type: <<types,ShellCommand>>

View File

@ -660,9 +660,9 @@ class Quitter:
try: try:
args, cwd = self._get_restart_args(pages, session, override_args) args, cwd = self._get_restart_args(pages, session, override_args)
if cwd is None: if cwd is None:
subprocess.Popen(args) subprocess.run(args)
else: else:
subprocess.Popen(args, cwd=cwd) subprocess.run(args, cwd=cwd)
except OSError: except OSError:
log.destroy.exception("Failed to restart") log.destroy.exception("Failed to restart")
return False return False

View File

@ -1618,10 +1618,12 @@ class CommandDispatcher:
return return
assert isinstance(text, str), text assert isinstance(text, str), text
caret_position = elem.caret_position()
ed = editor.ExternalEditor(self._tabbed_browser) ed = editor.ExternalEditor(self._tabbed_browser)
ed.editing_finished.connect(functools.partial( ed.editing_finished.connect(functools.partial(
self.on_editing_finished, elem)) self.on_editing_finished, elem))
ed.edit(text) ed.edit(text, caret_position)
@cmdutils.register(instance='command-dispatcher', hide=True, @cmdutils.register(instance='command-dispatcher', hide=True,
scope='window') scope='window')

View File

@ -47,6 +47,7 @@ class WebEngineElement(webelem.AbstractWebElement):
'class_name': str, 'class_name': str,
'rects': list, 'rects': list,
'attributes': dict, 'attributes': dict,
'caret_position': int,
} }
assert set(js_dict.keys()).issubset(js_dict_types.keys()) assert set(js_dict.keys()).issubset(js_dict_types.keys())
for name, typ in js_dict_types.items(): for name, typ in js_dict_types.items():
@ -132,6 +133,10 @@ class WebEngineElement(webelem.AbstractWebElement):
def set_value(self, value): def set_value(self, value):
self._js_call('set_value', value) self._js_call('set_value', value)
def caret_position(self):
"""Get the text caret position for the current element."""
return self._js_dict.get('caret_position', 0)
def insert_text(self, text): def insert_text(self, text):
if not self.is_editable(strict=True): if not self.is_editable(strict=True):
raise webelem.Error("Element is not editable!") raise webelem.Error("Element is not editable!")

View File

@ -126,6 +126,14 @@ class WebKitElement(webelem.AbstractWebElement):
value = javascript.string_escape(value) value = javascript.string_escape(value)
self._elem.evaluateJavaScript("this.value='{}'".format(value)) self._elem.evaluateJavaScript("this.value='{}'".format(value))
def caret_position(self):
"""Get the text caret position for the current element."""
self._check_vanished()
pos = self._elem.evaluateJavaScript('this.selectionStart')
if pos is None:
return 0
return int(pos)
def insert_text(self, text): def insert_text(self, text):
self._check_vanished() self._check_vanished()
if not self.is_editable(strict=True): if not self.is_editable(strict=True):

View File

@ -766,11 +766,11 @@ editor.command:
type: type:
name: ShellCommand name: ShellCommand
placeholder: true placeholder: true
default: ["gvim", "-f", "{}"] default: ["gvim", "-f", "{file}", "-c", "normal {line}G{column0}l"]
desc: >- desc: >-
The editor (and arguments) to use for the `open-editor` command. The editor (and arguments) to use for the `open-editor` command.
`{}` gets replaced by the filename of the file to be edited. `{file}` gets replaced by the filename of the file to be edited.
editor.encoding: editor.encoding:
type: Encoding type: Encoding

View File

@ -755,6 +755,6 @@ def get_diff():
lexer = pygments.lexers.DiffLexer() lexer = pygments.lexers.DiffLexer()
formatter = pygments.formatters.HtmlFormatter( formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table', full=True, linenos='table',
title='Config diff') title='Diffing pre-1.0 default config with pre-1.0 modified config')
# pylint: enable=no-member # pylint: enable=no-member
return pygments.highlight(conf_diff + key_diff, lexer, formatter) return pygments.highlight(conf_diff + key_diff, lexer, formatter)

View File

@ -1341,9 +1341,12 @@ class ShellCommand(List):
if not value: if not value:
return value return value
if self.placeholder and '{}' not in ' '.join(value): if (self.placeholder and
'{}' not in ' '.join(value) and
'{file}' not in ' '.join(value)):
raise configexc.ValidationError(value, "needs to contain a " raise configexc.ValidationError(value, "needs to contain a "
"{}-placeholder.") "{}-placeholder or a "
"{file}-placeholder.")
return value return value

View File

@ -48,11 +48,26 @@ window._qutebrowser.webelem = (function() {
const id = elements.length; const id = elements.length;
elements[id] = elem; elements[id] = elem;
// InvalidStateError will be thrown if elem doesn't have selectionStart
let caret_position = 0;
try {
caret_position = elem.selectionStart;
} catch (err) {
if (err instanceof DOMException &&
err.name === "InvalidStateError") {
// nothing to do, caret_position is already 0
} else {
// not the droid we're looking for
throw err;
}
}
const out = { const out = {
"id": id, "id": id,
"value": elem.value, "value": elem.value,
"outer_xml": elem.outerHTML, "outer_xml": elem.outerHTML,
"rects": [], // Gets filled up later "rects": [], // Gets filled up later
"caret_position": caret_position,
}; };
// https://github.com/qutebrowser/qutebrowser/issues/2569 // https://github.com/qutebrowser/qutebrowser/issues/2569

View File

@ -259,13 +259,14 @@ class TabbedBrowser(tabwidget.TabWidget):
def tab_close_prompt_if_pinned(self, tab, force, yes_action): def tab_close_prompt_if_pinned(self, tab, force, yes_action):
"""Helper method for tab_close. """Helper method for tab_close.
If tab is pinned, prompt. If everything is good, run yes_action. If tab is pinned, prompt. If not, run yes_action.
If tab is destroyed, abort question.
""" """
if tab.data.pinned and not force: if tab.data.pinned and not force:
message.confirm_async( message.confirm_async(
title='Pinned Tab', title='Pinned Tab',
text="Are you sure you want to close a pinned tab?", text="Are you sure you want to close a pinned tab?",
yes_action=yes_action, default=False) yes_action=yes_action, default=False, abort_on=[tab.destroyed])
else: else:
yes_action() yes_action()

View File

@ -96,11 +96,12 @@ class ExternalEditor(QObject):
def on_proc_error(self, _err): def on_proc_error(self, _err):
self._cleanup() self._cleanup()
def edit(self, text): def edit(self, text, caret_position=0):
"""Edit a given text. """Edit a given text.
Args: Args:
text: The initial text to edit. text: The initial text to edit.
caret_position: The position of the caret in the text.
""" """
if self._filename is not None: if self._filename is not None:
raise ValueError("Already editing a file!") raise ValueError("Already editing a file!")
@ -121,7 +122,9 @@ class ExternalEditor(QObject):
return return
self._remove_file = True self._remove_file = True
self._start_editor()
line, column = self._calc_line_and_column(text, caret_position)
self._start_editor(line=line, column=column)
def edit_file(self, filename): def edit_file(self, filename):
"""Edit the file with the given filename.""" """Edit the file with the given filename."""
@ -129,13 +132,82 @@ class ExternalEditor(QObject):
self._remove_file = False self._remove_file = False
self._start_editor() self._start_editor()
def _start_editor(self): def _start_editor(self, line=1, column=1):
"""Start the editor with the file opened as self._filename.""" """Start the editor with the file opened as self._filename.
Args:
line: the line number to pass to the editor
column: the column number to pass to the editor
"""
self._proc = guiprocess.GUIProcess(what='editor', parent=self) self._proc = guiprocess.GUIProcess(what='editor', parent=self)
self._proc.finished.connect(self.on_proc_closed) self._proc.finished.connect(self.on_proc_closed)
self._proc.error.connect(self.on_proc_error) self._proc.error.connect(self.on_proc_error)
editor = config.val.editor.command editor = config.val.editor.command
executable = editor[0] executable = editor[0]
args = [arg.replace('{}', self._filename) for arg in editor[1:]]
args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]]
log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
self._proc.start(executable, args) self._proc.start(executable, args)
def _calc_line_and_column(self, text, caret_position):
r"""Calculate line and column numbers given a text and caret position.
Both line and column are 1-based indexes, because that's what most
editors use as line and column starting index. By "most" we mean at
least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets,
visual studio, QtCreator and so on.
To find the line we just count how many newlines there are before the
caret and add 1.
To find the column we calculate the difference between the caret and
the last newline before the caret.
For example in the text `aaa\nbb|bbb` (| represents the caret):
caret_position = 6
text[:caret_position] = `aaa\nbb`
text[:caret_position].count('\n') = 1
caret_position - text[:caret_position].rfind('\n') = 3
Thus line, column = 2, 3, and the caret is indeed in the second
line, third column
Args:
text: the text for which the numbers must be calculated
caret_position: the position of the caret in the text
Return:
A (line, column) tuple of (int, int)
"""
line = text[:caret_position].count('\n') + 1
column = caret_position - text[:caret_position].rfind('\n')
return line, column
def _sub_placeholder(self, arg, line, column):
"""Substitute a single placeholder.
If the `arg` input to this function is a valid placeholder it will
be substituted with the appropriate value, otherwise it will be left
unchanged.
Args:
arg: an argument of editor.command.
line: the previously-calculated line number for the text caret.
column: the previously-calculated column number for the text caret.
Return:
The substituted placeholder or the original argument.
"""
replacements = {
'{}': self._filename,
'{file}': self._filename,
'{line}': str(line),
'{line0}': str(line-1),
'{column}': str(column),
'{column0}': str(column-1)
}
for old, new in replacements.items():
arg = arg.replace(old, new)
return arg

View File

@ -151,12 +151,14 @@ def _git_str_subprocess(gitpath):
return None return None
try: try:
# https://stackoverflow.com/questions/21017300/21017394#21017394 # https://stackoverflow.com/questions/21017300/21017394#21017394
commit_hash = subprocess.check_output( commit_hash = subprocess.run(
['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'], ['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'],
cwd=gitpath).decode('UTF-8').strip() cwd=gitpath, check=True,
date = subprocess.check_output( stdout=subprocess.PIPE).stdout.decode('UTF-8').strip()
date = subprocess.run(
['git', 'show', '-s', '--format=%ci', 'HEAD'], ['git', 'show', '-s', '--format=%ci', 'HEAD'],
cwd=gitpath).decode('UTF-8').strip() cwd=gitpath, check=True,
stdout=subprocess.PIPE).stdout.decode('UTF-8').strip()
return '{} ({})'.format(commit_hash, date) return '{} ({})'.format(commit_hash, date)
except (subprocess.CalledProcessError, OSError): except (subprocess.CalledProcessError, OSError):
return None return None

View File

@ -224,7 +224,7 @@ class AsciiDoc:
return self._args.asciidoc return self._args.asciidoc
try: try:
subprocess.call(['asciidoc'], stdout=subprocess.DEVNULL, subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
except OSError: except OSError:
pass pass
@ -232,7 +232,7 @@ class AsciiDoc:
return ['asciidoc'] return ['asciidoc']
try: try:
subprocess.call(['asciidoc.py'], stdout=subprocess.DEVNULL, subprocess.run(['asciidoc.py'], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
except OSError: except OSError:
pass pass
@ -258,7 +258,7 @@ class AsciiDoc:
try: try:
env = os.environ.copy() env = os.environ.copy()
env['HOME'] = self._homedir env['HOME'] = self._homedir
subprocess.check_call(cmdline, env=env) subprocess.run(cmdline, check=True, env=env)
except (subprocess.CalledProcessError, OSError) as e: except (subprocess.CalledProcessError, OSError) as e:
self._failed = True self._failed = True
utils.print_col(str(e), 'red') utils.print_col(str(e), 'red')

View File

@ -50,7 +50,7 @@ def call_script(name, *args, python=sys.executable):
python: The python interpreter to use. python: The python interpreter to use.
""" """
path = os.path.join(os.path.dirname(__file__), os.pardir, name) path = os.path.join(os.path.dirname(__file__), os.pardir, name)
subprocess.check_call([python, path] + list(args)) subprocess.run([python, path] + list(args), check=True)
def call_tox(toxenv, *args, python=sys.executable): def call_tox(toxenv, *args, python=sys.executable):
@ -64,9 +64,9 @@ def call_tox(toxenv, *args, python=sys.executable):
env = os.environ.copy() env = os.environ.copy()
env['PYTHON'] = python env['PYTHON'] = python
env['PATH'] = os.environ['PATH'] + os.pathsep + os.path.dirname(python) env['PATH'] = os.environ['PATH'] + os.pathsep + os.path.dirname(python)
subprocess.check_call( subprocess.run(
[sys.executable, '-m', 'tox', '-vv', '-e', toxenv] + list(args), [sys.executable, '-m', 'tox', '-vv', '-e', toxenv] + list(args),
env=env) env=env, check=True)
def run_asciidoc2html(args): def run_asciidoc2html(args):
@ -89,8 +89,9 @@ def _maybe_remove(path):
def smoke_test(executable): def smoke_test(executable):
"""Try starting the given qutebrowser executable.""" """Try starting the given qutebrowser executable."""
subprocess.check_call([executable, '--no-err-windows', '--nowindow', subprocess.run([executable, '--no-err-windows', '--nowindow',
'--temp-basedir', 'about:blank', ':later 500 quit']) '--temp-basedir', 'about:blank', ':later 500 quit'],
check=True)
def patch_mac_app(): def patch_mac_app():
@ -178,7 +179,7 @@ def build_mac():
utils.print_title("Patching .app") utils.print_title("Patching .app")
patch_mac_app() patch_mac_app()
utils.print_title("Building .dmg") utils.print_title("Building .dmg")
subprocess.check_call(['make', '-f', 'scripts/dev/Makefile-dmg']) subprocess.run(['make', '-f', 'scripts/dev/Makefile-dmg'], check=True)
dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__) dmg_name = 'qutebrowser-{}.dmg'.format(qutebrowser.__version__)
os.rename('qutebrowser.dmg', dmg_name) os.rename('qutebrowser.dmg', dmg_name)
@ -187,14 +188,14 @@ def build_mac():
try: try:
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
subprocess.check_call(['hdiutil', 'attach', dmg_name, subprocess.run(['hdiutil', 'attach', dmg_name,
'-mountpoint', tmpdir]) '-mountpoint', tmpdir], check=True)
try: try:
binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents', binary = os.path.join(tmpdir, 'qutebrowser.app', 'Contents',
'MacOS', 'qutebrowser') 'MacOS', 'qutebrowser')
smoke_test(binary) smoke_test(binary)
finally: finally:
subprocess.call(['hdiutil', 'detach', tmpdir]) subprocess.run(['hdiutil', 'detach', tmpdir])
except PermissionError as e: except PermissionError as e:
print("Failed to remove tempdir: {}".format(e)) print("Failed to remove tempdir: {}".format(e))
@ -242,13 +243,13 @@ def build_windows():
patch_windows(out_64) patch_windows(out_64)
utils.print_title("Building installers") utils.print_title("Building installers")
subprocess.check_call(['makensis.exe', subprocess.run(['makensis.exe',
'/DVERSION={}'.format(qutebrowser.__version__), '/DVERSION={}'.format(qutebrowser.__version__),
'misc/qutebrowser.nsi']) 'misc/qutebrowser.nsi'], check=True)
subprocess.check_call(['makensis.exe', subprocess.run(['makensis.exe',
'/DX64', '/DX64',
'/DVERSION={}'.format(qutebrowser.__version__), '/DVERSION={}'.format(qutebrowser.__version__),
'misc/qutebrowser.nsi']) 'misc/qutebrowser.nsi'], check=True)
name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__)
name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__)
@ -292,12 +293,12 @@ def build_sdist():
_maybe_remove('dist') _maybe_remove('dist')
subprocess.check_call([sys.executable, 'setup.py', 'sdist']) subprocess.run([sys.executable, 'setup.py', 'sdist'], check=True)
dist_files = os.listdir(os.path.abspath('dist')) dist_files = os.listdir(os.path.abspath('dist'))
assert len(dist_files) == 1 assert len(dist_files) == 1
dist_file = os.path.join('dist', dist_files[0]) dist_file = os.path.join('dist', dist_files[0])
subprocess.check_call(['gpg', '--detach-sign', '-a', dist_file]) subprocess.run(['gpg', '--detach-sign', '-a', dist_file], check=True)
tar = tarfile.open(dist_file) tar = tarfile.open(dist_file)
by_ext = collections.defaultdict(list) by_ext = collections.defaultdict(list)
@ -366,7 +367,7 @@ def github_upload(artifacts, tag):
def pypi_upload(artifacts): def pypi_upload(artifacts):
"""Upload the given artifacts to PyPI using twine.""" """Upload the given artifacts to PyPI using twine."""
filenames = [a[0] for a in artifacts] filenames = [a[0] for a in artifacts]
subprocess.check_call(['twine', 'upload'] + filenames) subprocess.run(['twine', 'upload'] + filenames, check=True)
def main(): def main():

View File

@ -285,8 +285,8 @@ def main_check():
print(msg.text) print(msg.text)
print() print()
filters = ','.join('qutebrowser/' + msg.filename for msg in messages) filters = ','.join('qutebrowser/' + msg.filename for msg in messages)
subprocess.check_call([sys.executable, '-m', 'coverage', 'report', subprocess.run([sys.executable, '-m', 'coverage', 'report',
'--show-missing', '--include', filters]) '--show-missing', '--include', filters], check=True)
print() print()
print("To debug this, run 'tox -e py36-pyqt59-cov' " print("To debug this, run 'tox -e py36-pyqt59-cov' "
"(or py35-pyqt59-cov) locally and check htmlcov/index.html") "(or py35-pyqt59-cov) locally and check htmlcov/index.html")
@ -312,9 +312,9 @@ def main_check_all():
for test_file, src_file in PERFECT_FILES: for test_file, src_file in PERFECT_FILES:
if test_file is None: if test_file is None:
continue continue
subprocess.check_call( subprocess.run(
[sys.executable, '-m', 'pytest', '--cov', 'qutebrowser', [sys.executable, '-m', 'pytest', '--cov', 'qutebrowser',
'--cov-report', 'xml', test_file]) '--cov-report', 'xml', test_file], check=True)
with open('coverage.xml', encoding='utf-8') as f: with open('coverage.xml', encoding='utf-8') as f:
messages = check(f, [(test_file, src_file)]) messages = check(f, [(test_file, src_file)])
os.remove('coverage.xml') os.remove('coverage.xml')

View File

@ -24,7 +24,8 @@ import sys
import subprocess import subprocess
import os import os
code = subprocess.call(['git', '--no-pager', 'diff', '--exit-code', '--stat']) code = subprocess.run(['git', '--no-pager', 'diff',
'--exit-code', '--stat']).returncode
if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false': if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false':
if code != 0: if code != 0:
@ -42,6 +43,6 @@ if code != 0:
if 'TRAVIS' in os.environ: if 'TRAVIS' in os.environ:
print() print()
print("travis_fold:start:gitdiff") print("travis_fold:start:gitdiff")
subprocess.call(['git', '--no-pager', 'diff']) subprocess.run(['git', '--no-pager', 'diff'])
print("travis_fold:end:gitdiff") print("travis_fold:end:gitdiff")
sys.exit(code) sys.exit(code)

View File

@ -23,4 +23,4 @@
import subprocess import subprocess
with open('qutebrowser/resources.py', 'w', encoding='utf-8') as f: with open('qutebrowser/resources.py', 'w', encoding='utf-8') as f:
subprocess.check_call(['pyrcc5', 'qutebrowser.rcc'], stdout=f) subprocess.run(['pyrcc5', 'qutebrowser.rcc'], stdout=f, check=True)

View File

@ -84,7 +84,8 @@ def parse_coredumpctl_line(line):
def get_info(pid): def get_info(pid):
"""Get and parse "coredumpctl info" output for the given PID.""" """Get and parse "coredumpctl info" output for the given PID."""
data = {} data = {}
output = subprocess.check_output(['coredumpctl', 'info', str(pid)]) output = subprocess.run(['coredumpctl', 'info', str(pid)], check=True,
stdout=subprocess.PIPE).stdout
output = output.decode('utf-8') output = output.decode('utf-8')
for line in output.split('\n'): for line in output.split('\n'):
if not line.strip(): if not line.strip():
@ -117,12 +118,12 @@ def dump_infos_gdb(parsed):
"""Dump all needed infos for the given crash using gdb.""" """Dump all needed infos for the given crash using gdb."""
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
coredump = os.path.join(tempdir, 'dump') coredump = os.path.join(tempdir, 'dump')
subprocess.check_call(['coredumpctl', 'dump', '-o', coredump, subprocess.run(['coredumpctl', 'dump', '-o', coredump,
str(parsed.pid)]) str(parsed.pid)], check=True)
subprocess.check_call(['gdb', parsed.exe, coredump, subprocess.run(['gdb', parsed.exe, coredump,
'-ex', 'info threads', '-ex', 'info threads',
'-ex', 'thread apply all bt full', '-ex', 'thread apply all bt full',
'-ex', 'quit']) '-ex', 'quit'], check=True)
def dump_infos(parsed): def dump_infos(parsed):
@ -143,7 +144,7 @@ def check_prerequisites():
"""Check if coredumpctl/gdb are installed.""" """Check if coredumpctl/gdb are installed."""
for binary in ['coredumpctl', 'gdb']: for binary in ['coredumpctl', 'gdb']:
try: try:
subprocess.check_call([binary, '--version']) subprocess.run([binary, '--version'], check=True)
except FileNotFoundError: except FileNotFoundError:
print("{} is needed to run this script!".format(binary), print("{} is needed to run this script!".format(binary),
file=sys.stderr) file=sys.stderr)
@ -158,7 +159,8 @@ def main():
action='store_true') action='store_true')
args = parser.parse_args() args = parser.parse_args()
coredumps = subprocess.check_output(['coredumpctl', 'list']) coredumps = subprocess.run(['coredumpctl', 'list'], check=True,
stdout=subprocess.PIPE).stdout
lines = coredumps.decode('utf-8').split('\n') lines = coredumps.decode('utf-8').split('\n')
for line in lines[1:]: for line in lines[1:]:
if not line.strip(): if not line.strip():

View File

@ -62,7 +62,8 @@ def check_git():
print() print()
return False return False
untracked = [] untracked = []
gitst = subprocess.check_output(['git', 'status', '--porcelain']) gitst = subprocess.run(['git', 'status', '--porcelain'], check=True,
stdout=subprocess.PIPE).stdout
gitst = gitst.decode('UTF-8').strip() gitst = gitst.decode('UTF-8').strip()
for line in gitst.splitlines(): for line in gitst.splitlines():
s, name = line.split(maxsplit=1) s, name = line.split(maxsplit=1)

View File

@ -116,9 +116,11 @@ def main():
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
pip_bin = os.path.join(tmpdir, 'bin', 'pip') pip_bin = os.path.join(tmpdir, 'bin', 'pip')
subprocess.check_call(['virtualenv', tmpdir]) subprocess.run(['virtualenv', tmpdir], check=True)
subprocess.check_call([pip_bin, 'install', '-r', filename]) subprocess.run([pip_bin, 'install', '-r', filename], check=True)
reqs = subprocess.check_output([pip_bin, 'freeze']).decode('utf-8') reqs = subprocess.run([pip_bin, 'freeze'], check=True,
stdout=subprocess.PIPE
).stdout.decode('utf-8')
with open(filename, 'r', encoding='utf-8') as f: with open(filename, 'r', encoding='utf-8') as f:
comments = read_comments(f) comments = read_comments(f)

View File

@ -76,14 +76,15 @@ def main():
pass pass
elif args.profile_tool == 'gprof2dot': elif args.profile_tool == 'gprof2dot':
# yep, shell=True. I know what I'm doing. # yep, shell=True. I know what I'm doing.
subprocess.call('gprof2dot -f pstats {} | dot -Tpng | feh -F -'.format( subprocess.run(
'gprof2dot -f pstats {} | dot -Tpng | feh -F -'.format(
shlex.quote(profilefile)), shell=True) shlex.quote(profilefile)), shell=True)
elif args.profile_tool == 'kcachegrind': elif args.profile_tool == 'kcachegrind':
callgraphfile = os.path.join(tempdir, 'callgraph') callgraphfile = os.path.join(tempdir, 'callgraph')
subprocess.call(['pyprof2calltree', '-k', '-i', profilefile, subprocess.run(['pyprof2calltree', '-k', '-i', profilefile,
'-o', callgraphfile]) '-o', callgraphfile])
elif args.profile_tool == 'snakeviz': elif args.profile_tool == 'snakeviz':
subprocess.call(['snakeviz', profilefile]) subprocess.run(['snakeviz', profilefile])
shutil.rmtree(tempdir) shutil.rmtree(tempdir)

View File

@ -73,7 +73,7 @@ def main():
env = os.environ.copy() env = os.environ.copy()
env['PYTHONPATH'] = os.pathsep.join(pythonpath) env['PYTHONPATH'] = os.pathsep.join(pythonpath)
ret = subprocess.call(['pylint'] + args, env=env) ret = subprocess.run(['pylint'] + args, env=env).returncode
return ret return ret

View File

@ -92,22 +92,22 @@ def main():
utils.print_bold("==== {} ====".format(page)) utils.print_bold("==== {} ====".format(page))
if test_harfbuzz: if test_harfbuzz:
print("With system harfbuzz:") print("With system harfbuzz:")
ret = subprocess.call([sys.executable, '-c', SCRIPT, page]) ret = subprocess.run([sys.executable, '-c', SCRIPT, page]).returncode
print_ret(ret) print_ret(ret)
retvals.append(ret) retvals.append(ret)
if test_harfbuzz: if test_harfbuzz:
print("With QT_HARFBUZZ=old:") print("With QT_HARFBUZZ=old:")
env = dict(os.environ) env = dict(os.environ)
env['QT_HARFBUZZ'] = 'old' env['QT_HARFBUZZ'] = 'old'
ret = subprocess.call([sys.executable, '-c', SCRIPT, page], ret = subprocess.run([sys.executable, '-c', SCRIPT, page],
env=env) env=env).returncode
print_ret(ret) print_ret(ret)
retvals.append(ret) retvals.append(ret)
print("With QT_HARFBUZZ=new:") print("With QT_HARFBUZZ=new:")
env = dict(os.environ) env = dict(os.environ)
env['QT_HARFBUZZ'] = 'new' env['QT_HARFBUZZ'] = 'new'
ret = subprocess.call([sys.executable, '-c', SCRIPT, page], ret = subprocess.run([sys.executable, '-c', SCRIPT, page],
env=env) env=env).returncode
print_ret(ret) print_ret(ret)
retvals.append(ret) retvals.append(ret)
if all(r == 0 for r in retvals): if all(r == 0 for r in retvals):

View File

@ -529,9 +529,9 @@ def regenerate_cheatsheet():
] ]
for filename, x, y in files: for filename, x, y in files:
subprocess.check_call(['inkscape', '-e', filename, '-b', 'white', subprocess.run(['inkscape', '-e', filename, '-b', 'white',
'-w', str(x), '-h', str(y), '-w', str(x), '-h', str(y),
'misc/cheatsheet.svg']) 'misc/cheatsheet.svg'], check=True)
def main(): def main():

View File

@ -46,12 +46,14 @@ def run_py(executable, *code):
f.write('\n'.join(code)) f.write('\n'.join(code))
cmd = [executable, filename] cmd = [executable, filename]
try: try:
ret = subprocess.check_output(cmd, universal_newlines=True) ret = subprocess.run(cmd, universal_newlines=True, check=True,
stdout=subprocess.PIPE).stdout
finally: finally:
os.remove(filename) os.remove(filename)
else: else:
cmd = [executable, '-c', '\n'.join(code)] cmd = [executable, '-c', '\n'.join(code)]
ret = subprocess.check_output(cmd, universal_newlines=True) ret = subprocess.run(cmd, universal_newlines=True, check=True,
stdout=subprocess.PIPE).stdout
return ret.rstrip() return ret.rstrip()

View File

@ -51,12 +51,14 @@ def _git_str():
return None return None
try: try:
# https://stackoverflow.com/questions/21017300/21017394#21017394 # https://stackoverflow.com/questions/21017300/21017394#21017394
commit_hash = subprocess.check_output( commit_hash = subprocess.run(
['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'], ['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'],
cwd=BASEDIR).decode('UTF-8').strip() cwd=BASEDIR, check=True,
date = subprocess.check_output( stdout=subprocess.PIPE).stdout.decode('UTF-8').strip()
date = subprocess.run(
['git', 'show', '-s', '--format=%ci', 'HEAD'], ['git', 'show', '-s', '--format=%ci', 'HEAD'],
cwd=BASEDIR).decode('UTF-8').strip() cwd=BASEDIR, check=True,
stdout=subprocess.PIPE).stdout.decode('UTF-8').strip()
return '{} ({})'.format(commit_hash, date) return '{} ({})'.format(commit_hash, date)
except (subprocess.CalledProcessError, OSError): except (subprocess.CalledProcessError, OSError):
return None return None

View File

@ -47,10 +47,10 @@ def update_documentation():
return return
try: try:
subprocess.call(['asciidoc'], stdout=subprocess.DEVNULL, subprocess.run(['asciidoc'], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL)
except OSError: except OSError:
pytest.skip("Docs outdated and asciidoc unavailable!") pytest.skip("Docs outdated and asciidoc unavailable!")
update_script = os.path.join(script_path, 'asciidoc2html.py') update_script = os.path.join(script_path, 'asciidoc2html.py')
subprocess.call([sys.executable, update_script]) subprocess.run([sys.executable, update_script])

View File

@ -259,14 +259,13 @@ def test_command_on_start(request, quteproc_new):
def test_launching_with_python2(): def test_launching_with_python2():
try: try:
proc = subprocess.Popen(['python2', '-m', 'qutebrowser', proc = subprocess.run(['python2', '-m', 'qutebrowser',
'--no-err-windows'], stderr=subprocess.PIPE) '--no-err-windows'], stderr=subprocess.PIPE)
except FileNotFoundError: except FileNotFoundError:
pytest.skip("python2 not found") pytest.skip("python2 not found")
_stdout, stderr = proc.communicate()
assert proc.returncode == 1 assert proc.returncode == 1
error = "At least Python 3.5 is required to run qutebrowser" error = "At least Python 3.5 is required to run qutebrowser"
assert stderr.decode('ascii').startswith(error) assert proc.stderr.decode('ascii').startswith(error)
def test_initial_private_browsing(request, quteproc_new): def test_initial_private_browsing(request, quteproc_new):

View File

@ -60,8 +60,9 @@ class TestSet:
@pytest.mark.parametrize('option, old_value, inp, new_value', [ @pytest.mark.parametrize('option, old_value, inp, new_value', [
('url.auto_search', 'naive', 'dns', 'dns'), ('url.auto_search', 'naive', 'dns', 'dns'),
# https://github.com/qutebrowser/qutebrowser/issues/2962 # https://github.com/qutebrowser/qutebrowser/issues/2962
('editor.command', ['gvim', '-f', '{}'], '[emacs, "{}"]', ('editor.command',
['emacs', '{}']), ['gvim', '-f', '{file}', '-c', 'normal {line}G{column0}l'],
'[emacs, "{}"]', ['emacs', '{}']),
]) ])
def test_set_simple(self, monkeypatch, commands, config_stub, def test_set_simple(self, monkeypatch, commands, config_stub,
temp, option, old_value, inp, new_value): temp, option, old_value, inp, new_value):

View File

@ -1815,9 +1815,12 @@ class TestShellCommand:
@pytest.mark.parametrize('kwargs, val, expected', [ @pytest.mark.parametrize('kwargs, val, expected', [
({}, '[foobar]', ['foobar']), ({}, '[foobar]', ['foobar']),
({'placeholder': '{}'}, '[foo, "{}", bar]', ['foo', '{}', 'bar']), ({'placeholder': True}, '[foo, "{}", bar]', ['foo', '{}', 'bar']),
({'placeholder': '{}'}, '["foo{}bar"]', ['foo{}bar']), ({'placeholder': True}, '["foo{}bar"]', ['foo{}bar']),
({'placeholder': '{}'}, '[foo, "bar {}"]', ['foo', 'bar {}']), ({'placeholder': True}, '[foo, "bar {}"]', ['foo', 'bar {}']),
({'placeholder': True}, '[f, "{file}", b]', ['f', '{file}', 'b']),
({'placeholder': True}, '["f{file}b"]', ['f{file}b']),
({'placeholder': True}, '[f, "b {file}"]', ['f', 'b {file}']),
]) ])
def test_valid(self, klass, kwargs, val, expected): def test_valid(self, klass, kwargs, val, expected):
cmd = klass(**kwargs) cmd = klass(**kwargs)
@ -1825,8 +1828,15 @@ class TestShellCommand:
assert cmd.to_py(expected) == expected assert cmd.to_py(expected) == expected
@pytest.mark.parametrize('kwargs, val', [ @pytest.mark.parametrize('kwargs, val', [
({'placeholder': '{}'}, '[foo, bar]'), ({'placeholder': True}, '[foo, bar]'),
({'placeholder': '{}'}, '[foo, "{", "}", bar'), ({'placeholder': True}, '[foo, "{", "}", bar'),
({'placeholder': True}, '[foo, bar]'),
({'placeholder': True}, '[foo, "{fi", "le}", bar'),
# Like valid ones but with wrong placeholder
({'placeholder': True}, '[f, "{wrong}", b]'),
({'placeholder': True}, '["f{wrong}b"]'),
({'placeholder': True}, '[f, "b {wrong}"]'),
]) ])
def test_from_str_invalid(self, klass, kwargs, val): def test_from_str_invalid(self, klass, kwargs, val):
with pytest.raises(configexc.ValidationError): with pytest.raises(configexc.ValidationError):

View File

@ -36,15 +36,14 @@ TEXT = (r"At least Python 3.5 is required to run qutebrowser, but it's "
def test_python2(): def test_python2():
"""Run checkpyver with python 2.""" """Run checkpyver with python 2."""
try: try:
proc = subprocess.Popen( proc = subprocess.run(
['python2', checkpyver.__file__, '--no-err-windows'], ['python2', checkpyver.__file__, '--no-err-windows'],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
except FileNotFoundError: except FileNotFoundError:
pytest.skip("python2 not found") pytest.skip("python2 not found")
assert not stdout assert not proc.stdout
stderr = stderr.decode('utf-8') stderr = proc.stderr.decode('utf-8')
assert re.match(TEXT, stderr), stderr assert re.match(TEXT, stderr), stderr
assert proc.returncode == 1 assert proc.returncode == 1

View File

@ -177,3 +177,18 @@ def test_modify(qtbot, editor, initial_text, edited_text):
editor._proc.finished.emit(0, QProcess.NormalExit) editor._proc.finished.emit(0, QProcess.NormalExit)
assert blocker.args == [edited_text] assert blocker.args == [edited_text]
@pytest.mark.parametrize('text, caret_position, result', [
('', 0, (1, 1)),
('a', 0, (1, 1)),
('a\nb', 1, (1, 2)),
('a\nb', 2, (2, 1)),
('a\nb', 3, (2, 2)),
('a\nbb\nccc', 4, (2, 3)),
('a\nbb\nccc', 5, (3, 1)),
('a\nbb\nccc', 8, (3, 4)),
])
def test_calculation(editor, text, caret_position, result):
"""Test calculation for line and column given text and caret_position."""
assert editor._calc_line_and_column(text, caret_position) == result

View File

@ -555,8 +555,9 @@ def test_no_qapplication(qapp, tmpdir):
pyfile = tmpdir / 'sub.py' pyfile = tmpdir / 'sub.py'
pyfile.write_text(textwrap.dedent(sub_code), encoding='ascii') pyfile.write_text(textwrap.dedent(sub_code), encoding='ascii')
output = subprocess.check_output([sys.executable, str(pyfile)] + sys.path, output = subprocess.run([sys.executable, str(pyfile)] + sys.path,
universal_newlines=True) universal_newlines=True,
check=True, stdout=subprocess.PIPE).stdout
sub_locations = json.loads(output) sub_locations = json.loads(output)
standarddir._init_dirs() standarddir._init_dirs()

View File

@ -299,8 +299,8 @@ class TestGitStr:
def _has_git(): def _has_git():
"""Check if git is installed.""" """Check if git is installed."""
try: try:
subprocess.check_call(['git', '--version'], stdout=subprocess.DEVNULL, subprocess.run(['git', '--version'], stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL) stderr=subprocess.DEVNULL, check=True)
except (OSError, subprocess.CalledProcessError): except (OSError, subprocess.CalledProcessError):
return False return False
else: else:
@ -337,12 +337,13 @@ class TestGitStrSubprocess:
# If we don't call this with shell=True it might fail under # If we don't call this with shell=True it might fail under
# some environments on Windows... # some environments on Windows...
# http://bugs.python.org/issue24493 # http://bugs.python.org/issue24493
subprocess.check_call( subprocess.run(
'git -C "{}" {}'.format(tmpdir, ' '.join(args)), 'git -C "{}" {}'.format(tmpdir, ' '.join(args)),
env=env, shell=True) env=env, check=True, shell=True)
else: else:
subprocess.check_call( subprocess.run(
['git', '-C', str(tmpdir)] + list(args), env=env) ['git', '-C', str(tmpdir)] + list(args),
check=True, env=env)
(tmpdir / 'file').write_text("Hello World!", encoding='utf-8') (tmpdir / 'file').write_text("Hello World!", encoding='utf-8')
_git('init') _git('init')
@ -368,14 +369,14 @@ class TestGitStrSubprocess:
subprocess.CalledProcessError(1, 'foobar') subprocess.CalledProcessError(1, 'foobar')
]) ])
def test_exception(self, exc, mocker, tmpdir): def test_exception(self, exc, mocker, tmpdir):
"""Test with subprocess.check_output raising an exception. """Test with subprocess.run raising an exception.
Args: Args:
exc: The exception to raise. exc: The exception to raise.
""" """
m = mocker.patch('qutebrowser.utils.version.os') m = mocker.patch('qutebrowser.utils.version.os')
m.path.isdir.return_value = True m.path.isdir.return_value = True
mocker.patch('qutebrowser.utils.version.subprocess.check_output', mocker.patch('qutebrowser.utils.version.subprocess.run',
side_effect=exc) side_effect=exc)
ret = version._git_str_subprocess(str(tmpdir)) ret = version._git_str_subprocess(str(tmpdir))
assert ret is None assert ret is None