Add new-instance-open-target.window = first-opened

Fixes #1060.

In the process of adding this, I also decided to rewrite
mainwindow.get_window() for clarity (and also because flake8 was warning
about complexity).

Also adds some tests to the new-instance-target mechanism, in particular
a specific test for the issue in question.
This commit is contained in:
Niklas Haas 2016-08-11 23:05:28 +02:00
parent 0058917b8d
commit c2cc28a72b
No known key found for this signature in database
GPG Key ID: 9A09076581B27402
8 changed files with 223 additions and 51 deletions

View File

@ -62,7 +62,7 @@ Changed
the most recently focused (instead of the last opened) window. This can be the most recently focused (instead of the last opened) window. This can be
configured with the new `new-instance-open-target.window` setting. configured with the new `new-instance-open-target.window` setting.
It can also be set to `last-visible` to show the pages in the most recently It can also be set to `last-visible` to show the pages in the most recently
visible window. visible window, or `first-opened` to use the first (oldest) available window.
- Word hints now are more clever about getting the element text from some elements. - Word hints now are more clever about getting the element text from some elements.
- Completions for `:help` and `:bind` now also show hidden commands - Completions for `:help` and `:bind` now also show hidden commands
- The `:buffer` completion now also filters using the first column (id). - The `:buffer` completion now also filters using the first column (id).

View File

@ -230,8 +230,10 @@ def data(readonly=False):
('new-instance-open-target.window', ('new-instance-open-target.window',
SettingValue(typ.String( SettingValue(typ.String(
valid_values=typ.ValidValues( valid_values=typ.ValidValues(
('last-opened', "Open new tabs in the last opened " ('first-opened', "Open new tabs in the first (oldest) "
"window."), "opened window."),
('last-opened', "Open new tabs in the last (newest) "
"opened window."),
('last-focused', "Open new tabs in the most recently " ('last-focused', "Open new tabs in the most recently "
"focused window."), "focused window."),
('last-visible', "Open new tabs in the most recently " ('last-visible', "Open new tabs in the most recently "

View File

@ -54,44 +54,61 @@ def get_window(via_ipc, force_window=False, force_tab=False,
""" """
if force_window and force_tab: if force_window and force_tab:
raise ValueError("force_window and force_tab are mutually exclusive!") raise ValueError("force_window and force_tab are mutually exclusive!")
if not via_ipc: if not via_ipc:
# Initial main window # Initial main window
return 0 return 0
window_to_raise = None
open_target = config.get('general', 'new-instance-open-target')
# Apply any target overrides, ordered by precedence
if force_target is not None: if force_target is not None:
open_target = force_target open_target = force_target
else: if force_window:
open_target = config.get('general', 'new-instance-open-target') open_target = 'window'
if (open_target == 'window' or force_window) and not force_tab: if force_tab and open_target == 'window':
open_target = 'tab' # for lack of a better default
window = None
raise_window = False
# Try to find the existing tab target if opening in a tab
if open_target != 'window':
window = get_target_window()
raise_window = open_target not in ['tab-silent', 'tab-bg-silent']
# Otherwise, or if no window was found, create a new one
if window is None:
window = MainWindow() window = MainWindow()
window.show() window.show()
win_id = window.win_id raise_window = True
window_to_raise = window
else: if raise_window:
try: window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
win_mode = config.get('general', 'new-instance-open-target.window') window.setWindowState(window.windowState() | Qt.WindowActive)
if win_mode == 'last-focused': window.raise_()
window = objreg.last_focused_window() window.activateWindow()
elif win_mode == 'last-opened': QApplication.instance().alert(window)
window = objreg.last_window()
elif win_mode == 'last-visible': return window.win_id
window = objreg.last_visible_window()
except objreg.NoWindow:
# There is no window left, so we open a new one def get_target_window():
window = MainWindow() """Get the target window for new tabs, or None if none exist."""
window.show() try:
win_id = window.win_id win_mode = config.get('general', 'new-instance-open-target.window')
window_to_raise = window if win_mode == 'last-focused':
win_id = window.win_id return objreg.last_focused_window()
if open_target not in ['tab-silent', 'tab-bg-silent']: elif win_mode == 'first-opened':
window_to_raise = window return objreg.window_by_index(0)
if window_to_raise is not None: elif win_mode == 'last-opened':
window_to_raise.setWindowState( return objreg.window_by_index(-1)
window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) elif win_mode == 'last-visible':
window_to_raise.raise_() return objreg.last_visible_window()
window_to_raise.activateWindow() else:
QApplication.instance().alert(window_to_raise) raise ValueError("Invalid win_mode {}".format(win_mode))
return win_id except objreg.NoWindow:
return None
class MainWindow(QWidget): class MainWindow(QWidget):

View File

@ -298,13 +298,13 @@ def last_focused_window():
try: try:
return get('last-focused-main-window') return get('last-focused-main-window')
except KeyError: except KeyError:
return last_window() return window_by_index(-1)
def last_window(): def window_by_index(idx):
"""Get the last opened window object.""" """Get the Nth opened window object."""
if not window_registry: if not window_registry:
raise NoWindow() raise NoWindow()
else: else:
key = sorted(window_registry)[-1] key = sorted(window_registry)[idx]
return window_registry[key] return window_registry[key]

View File

@ -156,15 +156,18 @@ def open_path(quteproc, path):
"""Open a URL. """Open a URL.
If used like "When I open ... in a new tab", the URL is opened in a new If used like "When I open ... in a new tab", the URL is opened in a new
tab. With "... in a new window", it's opened in a new window. tab. With "... in a new window", it's opened in a new window. With
"... as a URL", it's opened according to new-instance-open-target.
""" """
new_tab = False new_tab = False
new_window = False new_window = False
as_url = False
wait = True wait = True
new_tab_suffix = ' in a new tab' new_tab_suffix = ' in a new tab'
new_window_suffix = ' in a new window' new_window_suffix = ' in a new window'
do_not_wait_suffix = ' without waiting' do_not_wait_suffix = ' without waiting'
as_url_suffix = ' as a URL'
if path.endswith(new_tab_suffix): if path.endswith(new_tab_suffix):
path = path[:-len(new_tab_suffix)] path = path[:-len(new_tab_suffix)]
@ -172,12 +175,16 @@ def open_path(quteproc, path):
elif path.endswith(new_window_suffix): elif path.endswith(new_window_suffix):
path = path[:-len(new_window_suffix)] path = path[:-len(new_window_suffix)]
new_window = True new_window = True
elif path.endswith(as_url_suffix):
path = path[:-len(as_url_suffix)]
as_url = True
if path.endswith(do_not_wait_suffix): if path.endswith(do_not_wait_suffix):
path = path[:-len(do_not_wait_suffix)] path = path[:-len(do_not_wait_suffix)]
wait = False wait = False
quteproc.open_path(path, new_tab=new_tab, new_window=new_window, wait=wait) quteproc.open_path(path, new_tab=new_tab, new_window=new_window,
as_url=as_url, wait=wait)
@bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}")) @bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}"))

View File

@ -0,0 +1,108 @@
Feature: Invoking a new process
Simulate what happens when running qutebrowser with an existing instance
Background:
Given I clean up open tabs
Scenario: Using new-instance-open-target = tab
When I set general -> new-instance-open-target to tab
And I open data/title.html
And I open data/search.html as a URL
Then the following tabs should be open:
- data/title.html
- data/search.html (active)
Scenario: Using new-instance-open-target = tab-bg
When I set general -> new-instance-open-target to tab-bg
And I open data/title.html
And I open data/search.html as a URL
Then the following tabs should be open:
- data/title.html (active)
- data/search.html
Scenario: Using new-instance-open-target = window
When I set general -> new-instance-open-target to window
And I open data/title.html
And I open data/search.html as a URL
Then the session should look like:
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
- tabs:
- history:
- url: http://localhost:*/data/search.html
Scenario: Using new-instance-open-target.window = last-opened
When I set general -> new-instance-open-target to tab
And I set general -> new-instance-open-target.window to last-opened
And I open data/title.html
And I open data/search.html in a new window
And I open data/hello.txt as a URL
Then the session should look like:
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
- tabs:
- history:
- url: http://localhost:*/data/search.html
- history:
- url: http://localhost:*/data/hello.txt
Scenario: Using new-instance-open-target.window = first-opened
When I set general -> new-instance-open-target to tab
And I set general -> new-instance-open-target.window to first-opened
And I open data/title.html
And I open data/search.html in a new window
And I open data/hello.txt as a URL
Then the session should look like:
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
- history:
- url: http://localhost:*/data/hello.txt
- tabs:
- history:
- url: http://localhost:*/data/search.html
# issue #1060
Scenario: Using target.window = first-opened after tab-detach
When I set general -> new-instance-open-target to tab
And I set general -> new-instance-open-target.window to first-opened
And I open data/title.html
And I open data/search.html in a new tab
And I run :tab-detach
And I open data/hello.txt as a URL
Then the session should look like:
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
- history:
- url: http://localhost:*/data/hello.txt
- tabs:
- history:
- url: http://localhost:*/data/search.html
Scenario: Opening a new qutebrowser instance with no parameters
When I set general -> new-instance-open-target to tab
And I set general -> startpage to about:blank
And I open data/title.html
And I spawn a new window
And I wait until about:blank is loaded
Then the session should look like:
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
- tabs:
- history:
- url: about:blank

View File

@ -0,0 +1,30 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
import pytest_bdd as bdd
bdd.scenarios('invoke.feature')
@bdd.when(bdd.parsers.parse("I spawn a new window"))
def invoke_with(quteproc):
"""Spawn a new window via IPC call."""
quteproc.log_summary("Create a new window")
quteproc.send_ipc([], target_arg='window')
quteproc.wait_for(category='init', module='app',
function='_open_startpage', message='Opening startpage')

View File

@ -365,6 +365,15 @@ class QuteProc(testprocess.Process):
finally: finally:
super().after_test() super().after_test()
def send_ipc(self, commands, target_arg=''):
"""Send a raw command to the running IPC socket."""
time.sleep(self._delay / 1000)
assert self._ipc_socket is not None
ipc.send_to_running_instance(self._ipc_socket, commands, target_arg)
self.wait_for(category='ipc', module='ipc', function='on_ready_read',
message='Read from socket *')
def send_cmd(self, command, count=None, invalid=False, *, escape=True): def send_cmd(self, command, count=None, invalid=False, *, escape=True):
"""Send a command to the running qutebrowser instance. """Send a command to the running qutebrowser instance.
@ -379,18 +388,13 @@ class QuteProc(testprocess.Process):
summary += ' (count {})'.format(count) summary += ' (count {})'.format(count)
self.log_summary(summary) self.log_summary(summary)
assert self._ipc_socket is not None
time.sleep(self._delay / 1000)
if escape: if escape:
command = command.replace('\\', r'\\') command = command.replace('\\', r'\\')
if count is not None: if count is not None:
command = ':{}:{}'.format(count, command.lstrip(':')) command = ':{}:{}'.format(count, command.lstrip(':'))
ipc.send_to_running_instance(self._ipc_socket, [command], self.send_ipc([command])
target_arg='')
if not invalid: if not invalid:
self.wait_for(category='commands', module='command', self.wait_for(category='commands', module='command',
function='run', message='command called: *') function='run', message='command called: *')
@ -418,18 +422,22 @@ class QuteProc(testprocess.Process):
yield yield
self.set_setting(sect, opt, old_value) self.set_setting(sect, opt, old_value)
def open_path(self, path, *, new_tab=False, new_window=False, port=None, def open_path(self, path, *, new_tab=False, new_window=False, as_url=False,
https=False, wait=True): port=None, https=False, wait=True):
"""Open the given path on the local webserver in qutebrowser.""" """Open the given path on the local webserver in qutebrowser."""
url = self.path_to_url(path, port=port, https=https) url = self.path_to_url(path, port=port, https=https)
self.open_url(url, new_tab=new_tab, new_window=new_window, wait=wait) self.open_url(url, new_tab=new_tab, new_window=new_window,
as_url=as_url, wait=wait)
def open_url(self, url, *, new_tab=False, new_window=False, wait=True): def open_url(self, url, *, new_tab=False, new_window=False, as_url=False,
wait=True):
"""Open the given url in qutebrowser.""" """Open the given url in qutebrowser."""
if new_tab and new_window: if new_tab and new_window:
raise ValueError("new_tab and new_window given!") raise ValueError("new_tab and new_window given!")
if new_tab: if as_url:
self.send_cmd(url, invalid=True)
elif new_tab:
self.send_cmd(':open -t ' + url) self.send_cmd(':open -t ' + url)
elif new_window: elif new_window:
self.send_cmd(':open -w ' + url) self.send_cmd(':open -w ' + url)