diff options
author | Florian Bruhin <me@the-compiler.org> | 2021-03-03 16:12:45 +0100 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2021-03-03 16:12:45 +0100 |
commit | 610bcc7d8399fab45bdf27b310ffc0726fca30b1 (patch) | |
tree | ec122bd41614038274674831eb416051d3446fd2 | |
parent | ef945c42f75e7a13fd0191f265fbcdebcb0a9fd7 (diff) | |
parent | 7d5acd7239170c478ea9cf696a0f59b91f7fd53d (diff) | |
download | qutebrowser-610bcc7d8399fab45bdf27b310ffc0726fca30b1.tar.gz qutebrowser-610bcc7d8399fab45bdf27b310ffc0726fca30b1.zip |
Merge remote-tracking branch 'origin/pr/6116'
-rw-r--r-- | qutebrowser/browser/shared.py | 67 | ||||
-rw-r--r-- | qutebrowser/config/configdata.yml | 4 | ||||
-rw-r--r-- | qutebrowser/misc/guiprocess.py | 5 | ||||
-rwxr-xr-x | scripts/dev/run_vulture.py | 1 | ||||
-rw-r--r-- | tests/end2end/features/editor.feature | 49 | ||||
-rw-r--r-- | tests/end2end/features/test_editor_bdd.py | 37 | ||||
-rw-r--r-- | tests/unit/misc/test_guiprocess.py | 2 |
7 files changed, 116 insertions, 49 deletions
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 78b475835..94332ffcb 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -23,7 +23,7 @@ import os import sys import html import netrc -from typing import Callable, Mapping, List +from typing import Callable, Mapping, List, Optional import tempfile from PyQt5.QtCore import QUrl @@ -360,23 +360,60 @@ def choose_file(multiple: bool) -> List[str]: A list of selected file paths, or empty list if no file is selected. If multiple is False, the return value will have at most 1 item. """ - handle = tempfile.NamedTemporaryFile(prefix='qutebrowser-fileselect-', delete=False) - handle.close() - tmpfilename = handle.name - with utils.cleanup_file(tmpfilename): - if multiple: - command = config.val.fileselect.multiple_files.command - else: - command = config.val.fileselect.single_file.command + if multiple: + command = config.val.fileselect.multiple_files.command + else: + command = config.val.fileselect.single_file.command + use_tmp_file = any('{}' in arg for arg in command[1:]) + if use_tmp_file: + handle = tempfile.NamedTemporaryFile( + prefix='qutebrowser-fileselect-', + delete=False, + ) + handle.close() + tmpfilename = handle.name + with utils.cleanup_file(tmpfilename): + command = ( + command[:1] + + [arg.replace('{}', tmpfilename) for arg in command[1:]] + ) + return _execute_fileselect_command( + command=command, + multiple=multiple, + tmpfilename=tmpfilename, + ) + else: + return _execute_fileselect_command( + command=command, + multiple=multiple, + ) - proc = guiprocess.GUIProcess(what='choose-file') - proc.start(command[0], - [arg.replace('{}', tmpfilename) for arg in command[1:]]) - loop = qtutils.EventLoop() - proc.finished.connect(lambda _code, _status: loop.exit()) - loop.exec() +def _execute_fileselect_command( + command: List[str], + multiple: bool, + tmpfilename: Optional[str] = None +) -> List[str]: + """Execute external command to choose file. + Args: + multiple: Should selecting multiple files be allowed. + tmpfilename: Path to the temporary file if used, otherwise None. + + Return: + A list of selected file paths, or empty list if no file is selected. + If multiple is False, the return value will have at most 1 item. + """ + proc = guiprocess.GUIProcess(what='choose-file') + proc.start(command[0], command[1:]) + + loop = qtutils.EventLoop() + proc.finished.connect(lambda _code, _status: loop.exit()) + loop.exec() + + if tmpfilename is None: + selected_files = proc.final_stdout.splitlines() + else: try: with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f: selected_files = f.read().splitlines() diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index b4805665b..d93aa1e4b 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1259,7 +1259,7 @@ fileselect.handler: fileselect.single_file.command: type: name: ShellCommand - placeholder: true + placeholder: false completions: - ['["xterm", "-e", "ranger", "--choosefile={}"]', "Ranger in xterm"] - ['["xterm", "-e", "vifm", "--choose-files", "{}"]', "vifm in xterm"] @@ -1276,7 +1276,7 @@ fileselect.single_file.command: fileselect.multiple_files.command: type: name: ShellCommand - placeholder: true + placeholder: false completions: - ['["xterm", "-e", "ranger", "--choosefiles={}"]', "Ranger in xterm"] - ['["xterm", "-e", "vifm", "--choose-files", "{}"]', "vifm in xterm"] diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 1273b227e..79c84c346 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -61,6 +61,9 @@ class GUIProcess(QObject): self.cmd = None self.args = None + self.final_stdout: str = "" + self.final_stderr: str = "" + self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) @@ -125,6 +128,8 @@ class GUIProcess(QObject): log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) + self.final_stdout = stdout + self.final_stderr = stderr def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 8895da55f..612b88637 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -61,6 +61,7 @@ def whitelist_generator(): # noqa: C901 yield 'scripts.utils.bg_colors' yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' yield 'qutebrowser.misc.throttle.Throttle.set_delay' + yield 'qutebrowser.misc.guiprocess.GUIProcess.final_stderr' # Qt attributes yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 52756422c..47cb1230a 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -6,14 +6,14 @@ Feature: Opening external editors Scenario: Editing a URL When I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url Then data/numbers/2.txt should be loaded Scenario: Editing a URL with -t When I run :tab-only And I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -t Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -24,7 +24,7 @@ Feature: Opening external editors When I set tabs.new_position.related to prev And I open data/numbers/1.txt And I run :tab-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -rt Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -34,7 +34,7 @@ Feature: Opening external editors Scenario: Editing a URL with -b When I run :tab-only And I open data/numbers/1.txt - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -b Then data/numbers/2.txt should be loaded And the following tabs should be open: @@ -45,7 +45,7 @@ Feature: Opening external editors When I run :window-only And I open data/numbers/1.txt in a new tab And I run :tab-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -w Then data/numbers/2.txt should be loaded And the session should look like: @@ -65,7 +65,7 @@ Feature: Opening external editors When I open data/numbers/1.txt in a new tab And I run :tab-only And I run :window-only - And I set up a fake editor replacing "1.txt" by "2.txt" + And I setup a fake editor replacing "1.txt" by "2.txt" And I run :edit-url -p Then data/numbers/2.txt should be loaded And the session should look like: @@ -90,13 +90,13 @@ Feature: Opening external editors Scenario: Editing a URL with invalid URL When I set url.auto_search to never And I open data/hello.txt - And I set up a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" + And I setup a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" And I run :edit-url Then the error "Invalid URL" should be shown Scenario: Spawning an editor successfully Given I have a fresh instance - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -105,7 +105,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor in normal mode - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -119,7 +119,7 @@ Feature: Opening external editors # There's no guarantee that the tab gets deleted... @posix Scenario: Spawning an editor and closing the tab - When I set up a fake editor that writes "foobar" on save + When I setup a fake editor that writes "foobar" on save And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -134,7 +134,7 @@ Feature: Opening external editors # Could not get signals working on Windows @posix Scenario: Spawning an editor and saving - When I set up a fake editor that writes "foobar" on save + When I setup a fake editor that writes "foobar" on save And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -145,7 +145,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor in caret mode - When I set up a fake editor returning "foobar" + When I setup a fake editor returning "foobar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -159,7 +159,7 @@ Feature: Opening external editors Then the javascript message "text: foobar" should be logged Scenario: Spawning an editor with existing text - When I set up a fake editor replacing "foo" by "bar" + When I setup a fake editor replacing "foo" by "bar" And I open data/editor.html And I run :click-element id qute-textarea And I wait for "Entering mode KeyMode.insert (reason: clicking input)" in the log @@ -173,20 +173,20 @@ Feature: Opening external editors Scenario: Edit a command and run it When I run :set-cmd-text :message-info foo - And I set up a fake editor replacing "foo" by "bar" + And I setup a fake editor replacing "foo" by "bar" And I run :edit-command --run Then the message "bar" should be shown And "Leaving mode KeyMode.command (reason: cmd accept)" should be logged Scenario: Edit a command and omit the start char - When I set up a fake editor returning "message-info foo" + When I setup a fake editor returning "message-info foo" And I run :edit-command Then the error "command must start with one of :/?" should be shown And "Leaving mode KeyMode.command *" should not be logged Scenario: Edit a command to be empty When I run :set-cmd-text : - When I set up a fake editor returning empty text + When I setup a fake editor returning empty text And I run :edit-command Then the error "command must start with one of :/?" should be shown And "Leaving mode KeyMode.command *" should not be logged @@ -194,13 +194,20 @@ Feature: Opening external editors ## select single file Scenario: Select one file with single command - When I set up a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt" + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file + And I open data/fileselect.html + And I run :click-element id single_file + Then the javascript message "Files: 1.txt" should be logged + + Scenario: Select one file with single command that writes to stdout + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to stdout And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with single command - When I set up a fake "single_file" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" + When I setup a fake single_file fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file + And I open data/fileselect.html And I run :click-element id single_file Then the javascript message "Files: 1.txt" should be logged @@ -209,13 +216,15 @@ Feature: Opening external editors ## select multiple files Scenario: Select one file with multiple command - When I set up a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt" + When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt" and writes to a temporary file + And I open data/fileselect.html And I run :click-element id multiple_files Then the javascript message "Files: 1.txt" should be logged Scenario: Select two files with multiple command - When I set up a fake "multiple_files" fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" + When I setup a fake multiple_files fileselector selecting "tests/end2end/data/numbers/1.txt tests/end2end/data/numbers/2.txt" and writes to a temporary file + And I open data/fileselect.html And I run :click-element id multiple_files Then the javascript message "Files: 1.txt, 2.txt" should be logged diff --git a/tests/end2end/features/test_editor_bdd.py b/tests/end2end/features/test_editor_bdd.py index 445691bee..40f77a0f7 100644 --- a/tests/end2end/features/test_editor_bdd.py +++ b/tests/end2end/features/test_editor_bdd.py @@ -32,7 +32,7 @@ bdd.scenarios('editor.feature') from qutebrowser.utils import utils -@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by ' +@bdd.when(bdd.parsers.parse('I setup a fake editor replacing "{text}" by ' '"{replacement}"')) def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement): """Set up editor.command to a small python script doing a replacement.""" @@ -53,7 +53,7 @@ def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement): quteproc.set_setting('editor.command', editor) -@bdd.when(bdd.parsers.parse('I set up a fake editor returning "{text}"')) +@bdd.when(bdd.parsers.parse('I setup a fake editor returning "{text}"')) def set_up_editor(quteproc, tmpdir, text): """Set up editor.command to a small python script inserting a text.""" script = tmpdir / 'script.py' @@ -67,7 +67,7 @@ def set_up_editor(quteproc, tmpdir, text): quteproc.set_setting('editor.command', editor) -@bdd.when(bdd.parsers.parse('I set up a fake editor returning empty text')) +@bdd.when(bdd.parsers.parse('I setup a fake editor returning empty text')) def set_up_editor_empty(quteproc, tmpdir): """Set up editor.command to a small python script inserting empty text.""" set_up_editor(quteproc, tmpdir, "") @@ -107,7 +107,7 @@ def editor_pid_watcher(tmpdir): return EditorPidWatcher(tmpdir) -@bdd.when(bdd.parsers.parse('I set up a fake editor that writes "{text}" on ' +@bdd.when(bdd.parsers.parse('I setup a fake editor that writes "{text}" on ' 'save')) def set_up_editor_wait(quteproc, tmpdir, text, editor_pid_watcher): """Set up editor.command to a small python script inserting a text.""" @@ -180,18 +180,31 @@ def save_editor_wait(tmpdir): os.kill(pid, signal.SIGUSR2) -@bdd.when(bdd.parsers.parse('I set up a fake "{kind}" fileselector ' - 'selecting "{files}"')) -def set_up_fileselector(quteproc, py_proc, kind, files): +@bdd.when(bdd.parsers.parse('I setup a fake {kind} fileselector ' + 'selecting "{files}" and writes to {output_type}')) +def set_up_fileselector(quteproc, py_proc, kind, files, output_type): """Set up fileselect.xxx.command to select the file(s).""" cmd, args = py_proc(r""" import os import sys - tmp_file = sys.argv[1] - with open(tmp_file, 'w') as f: - for selected_file in sys.argv[2:]: - f.write(os.path.abspath(selected_file) + "\n") + tmp_file = None + for i, arg in enumerate(sys.argv): + if arg.startswith('--file='): + tmp_file = arg[len('--file='):] + sys.argv.pop(i) + break + selected_files = sys.argv[1:] + if tmp_file is None: + for selected_file in selected_files: + print(os.path.abspath(selected_file)) + else: + with open(tmp_file, 'w') as f: + for selected_file in selected_files: + f.write(os.path.abspath(selected_file) + '\n') """) - fileselect_cmd = json.dumps([cmd, *args, '{}', *files.split(' ')]) + args += files.split(' ') + if output_type == "a temporary file": + args += ['--file={}'] + fileselect_cmd = json.dumps([cmd, *args]) quteproc.set_setting('fileselect.handler', 'external') quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd) diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index a2acad1ac..9e1b3916c 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -127,9 +127,11 @@ def test_start_output_message(proc, qtbot, caplog, message_mock, py_proc, if stdout_msg is not None: assert stdout_msg.level == usertypes.MessageLevel.info assert stdout_msg.text == 'stdout text' + assert proc.final_stdout.strip() == "stdout text", proc.final_stdout if stderr_msg is not None: assert stderr_msg.level == usertypes.MessageLevel.error assert stderr_msg.text == 'stderr text' + assert proc.final_stderr.strip() == "stderr text", proc.final_stderr def test_start_env(monkeypatch, qtbot, py_proc): |