From 341708f54394c68b61c081de84e7d48359c4d0e0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 19 May 2015 12:29:53 +0200 Subject: [PATCH 01/50] Refactor readline tests. They now use a real QLineEdit and verify a lot more. See #660, #678. --- tests/misc/test_readline.py | 338 ++++++++++++++++++++++++------------ 1 file changed, 225 insertions(+), 113 deletions(-) diff --git a/tests/misc/test_readline.py b/tests/misc/test_readline.py index 523c6f579..da2d05821 100644 --- a/tests/misc/test_readline.py +++ b/tests/misc/test_readline.py @@ -21,144 +21,256 @@ # pylint: disable=protected-access +import re import inspect -from unittest import mock -from PyQt5.QtWidgets import QLineEdit +from PyQt5.QtWidgets import QLineEdit, QApplication import pytest from qutebrowser.misc import readline +# Some functions aren't 100% readline compatible: +# https://github.com/The-Compiler/qutebrowser/issues/678 +# Those are marked with fixme and have another value marked with '# wrong' +# which marks the current behavior. + +fixme = pytest.mark.xfail(reason='readline compatibility - see #678') + + +class LineEdit(QLineEdit): + + """QLineEdit with some methods to make testing easier.""" + + def _get_index(self, haystack, needle): + """Get the index of a char (needle) in a string (haystack). + + Return: + The position where needle was found, or None if it wasn't found. + """ + try: + return haystack.index(needle) + except ValueError: + return None + + def set_aug_text(self, text): + """Set a text with markers for selected text and | as cursor.""" + real_text = re.sub('[<>|]', '', text) + self.setText(real_text) + + cursor_pos = self._get_index(text, '|') + sel_start_pos = self._get_index(text, '<') + sel_end_pos = self._get_index(text, '>') + + if sel_start_pos is not None and sel_end_pos is None: + raise ValueError("< given without >!") + if sel_start_pos is None and sel_end_pos is not None: + raise ValueError("> given without !") + self.setCursorPosition(cursor_pos) + elif sel_start_pos is not None: + if sel_start_pos > sel_end_pos: + raise ValueError("< given after >!") + sel_len = sel_end_pos - sel_start_pos - 1 + self.setSelection(sel_start_pos, sel_len) + + def aug_text(self): + """Get a text with markers for selected text and | as cursor.""" + text = self.text() + chars = list(text) + cur_pos = self.cursorPosition() + assert cur_pos >= 0 + chars.insert(cur_pos, '|') + if self.hasSelectedText(): + selected_text = self.selectedText() + sel_start = self.selectionStart() + sel_end = sel_start + len(selected_text) + assert sel_start > 0 + assert sel_end > 0 + assert sel_end > sel_start + assert cur_pos == sel_end + assert text[sel_start:sel_end] == selected_text + chars.insert(sel_start, '<') + chars.insert(sel_end + 1, '>') + return ''.join(chars) + + @pytest.fixture -def mocked_qapp(monkeypatch, stubs): - """Fixture that mocks readline.QApplication and returns it.""" - stub = stubs.FakeQApplication() - monkeypatch.setattr('qutebrowser.misc.readline.QApplication', stub) - return stub +def lineedit(qtbot, monkeypatch): + """Fixture providing a LineEdit.""" + le = LineEdit() + qtbot.add_widget(le) + monkeypatch.setattr(QApplication.instance(), 'focusWidget', lambda: le) + return le -class TestNoneWidget: - - """Test if there are no exceptions when the widget is None.""" - - def test_none(self, mocked_qapp): - """Call each rl_* method with a None focusWidget.""" - self.bridge = readline.ReadlineBridge() - mocked_qapp.focusWidget = mock.Mock(return_value=None) - for name, method in inspect.getmembers(self.bridge, inspect.ismethod): - if name.startswith('rl_'): - method() +@pytest.fixture +def bridge(): + """Fixture providing a ReadlineBridge.""" + return readline.ReadlineBridge() -class TestReadlineBridgeTest: +def test_none(bridge, qtbot): + """Call each rl_* method with a None focusWidget.""" + assert QApplication.instance().focusWidget() is None + for name, method in inspect.getmembers(bridge, inspect.ismethod): + if name.startswith('rl_'): + method() - """Tests for readline bridge.""" - @pytest.fixture(autouse=True) - def setup(self): - self.qle = mock.Mock() - self.qle.__class__ = QLineEdit - self.bridge = readline.ReadlineBridge() +@pytest.mark.parametrize('text, expected', [('fbar', 'fo|obar'), + ('|foobar', '|foobar')]) +def test_rl_backward_char(text, expected, lineedit, bridge): + """Test rl_backward_char.""" + lineedit.set_aug_text(text) + bridge.rl_backward_char() + assert lineedit.aug_text() == expected - def _set_selected_text(self, text): - """Set the value the fake QLineEdit should return for selectedText.""" - self.qle.configure_mock(**{'selectedText.return_value': text}) - def test_rl_backward_char(self, mocked_qapp): - """Test rl_backward_char.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_backward_char() - self.qle.cursorBackward.assert_called_with(False) +@pytest.mark.parametrize('text, expected', [('fbar', 'foob|ar'), + ('foobar|', 'foobar|')]) +def test_rl_forward_char(text, expected, lineedit, bridge): + """Test rl_forward_char.""" + lineedit.set_aug_text(text) + bridge.rl_forward_char() + assert lineedit.aug_text() == expected - def test_rl_forward_char(self, mocked_qapp): - """Test rl_forward_char.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_forward_char() - self.qle.cursorForward.assert_called_with(False) - def test_rl_backward_word(self, mocked_qapp): - """Test rl_backward_word.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_backward_word() - self.qle.cursorWordBackward.assert_called_with(False) +@pytest.mark.parametrize('text, expected', [('one o', 'one |two'), + ('two', '|one two'), + ('|one two', '|one two')]) +def test_rl_backward_word(text, expected, lineedit, bridge): + """Test rl_backward_word.""" + lineedit.set_aug_text(text) + bridge.rl_backward_word() + assert lineedit.aug_text() == expected - def test_rl_forward_word(self, mocked_qapp): - """Test rl_forward_word.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_forward_word() - self.qle.cursorWordForward.assert_called_with(False) - def test_rl_beginning_of_line(self, mocked_qapp): - """Test rl_beginning_of_line.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_beginning_of_line() - self.qle.home.assert_called_with(False) +@pytest.mark.parametrize('text, expected', [ + fixme(('ne two', 'one| two')), + ('ne two', 'one |two'), # wrong + fixme((' two', 'one two|')), + (' two', 'one |two'), # wrong + ('one t', 'one two|') +]) +def test_rl_forward_word(text, expected, lineedit, bridge): + """Test rl_forward_word.""" + lineedit.set_aug_text(text) + bridge.rl_forward_word() + assert lineedit.aug_text() == expected - def test_rl_end_of_line(self, mocked_qapp): - """Test rl_end_of_line.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_end_of_line() - self.qle.end.assert_called_with(False) - def test_rl_delete_char(self, mocked_qapp): - """Test rl_delete_char.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_delete_char() - self.qle.del_.assert_called_with() +def test_rl_beginning_of_line(lineedit, bridge): + """Test rl_beginning_of_line.""" + lineedit.set_aug_text('fbar') + bridge.rl_beginning_of_line() + assert lineedit.aug_text() == '|foobar' - def test_rl_backward_delete_char(self, mocked_qapp): - """Test rl_backward_delete_char.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_backward_delete_char() - self.qle.backspace.assert_called_with() - def test_rl_unix_line_discard(self, mocked_qapp): - """Set a selected text, delete it, see if it comes back with yank.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self._set_selected_text("delete test") - self.bridge.rl_unix_line_discard() - self.qle.home.assert_called_with(True) - assert self.bridge._deleted[self.qle] == "delete test" - self.qle.del_.assert_called_with() - self.bridge.rl_yank() - self.qle.insert.assert_called_with("delete test") +def test_rl_end_of_line(lineedit, bridge): + """Test rl_end_of_line.""" + lineedit.set_aug_text('fbar') + bridge.rl_end_of_line() + assert lineedit.aug_text() == 'foobar|' - def test_rl_kill_line(self, mocked_qapp): - """Set a selected text, delete it, see if it comes back with yank.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self._set_selected_text("delete test") - self.bridge.rl_kill_line() - self.qle.end.assert_called_with(True) - assert self.bridge._deleted[self.qle] == "delete test" - self.qle.del_.assert_called_with() - self.bridge.rl_yank() - self.qle.insert.assert_called_with("delete test") - def test_rl_unix_word_rubout(self, mocked_qapp): - """Set a selected text, delete it, see if it comes back with yank.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self._set_selected_text("delete test") - self.bridge.rl_unix_word_rubout() - self.qle.cursorWordBackward.assert_called_with(True) - assert self.bridge._deleted[self.qle] == "delete test" - self.qle.del_.assert_called_with() - self.bridge.rl_yank() - self.qle.insert.assert_called_with("delete test") +@pytest.mark.parametrize('text, expected', [('foo|bar', 'foo|ar'), + ('foobar|', 'foobar|'), + ('|foobar', '|oobar'), + ('fbar', 'f|bar')]) +def test_rl_delete_char(text, expected, lineedit, bridge): + """Test rl_delete_char.""" + lineedit.set_aug_text(text) + bridge.rl_delete_char() + assert lineedit.aug_text() == expected - def test_rl_kill_word(self, mocked_qapp): - """Set a selected text, delete it, see if it comes back with yank.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self._set_selected_text("delete test") - self.bridge.rl_kill_word() - self.qle.cursorWordForward.assert_called_with(True) - assert self.bridge._deleted[self.qle] == "delete test" - self.qle.del_.assert_called_with() - self.bridge.rl_yank() - self.qle.insert.assert_called_with("delete test") - def test_rl_yank_no_text(self, mocked_qapp): - """Test yank without having deleted anything.""" - mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) - self.bridge.rl_yank() - assert not self.qle.insert.called +@pytest.mark.parametrize('text, expected', [('foo|bar', 'fo|bar'), + ('foobar|', 'fooba|'), + ('|foobar', '|foobar'), + ('fbar', 'f|bar')]) +def test_rl_backward_delete_char(text, expected, lineedit, bridge): + """Test rl_backward_delete_char.""" + lineedit.set_aug_text(text) + bridge.rl_backward_delete_char() + assert lineedit.aug_text() == expected + + +@pytest.mark.parametrize('text, deleted, rest', [ + ('delete this| test', 'delete this', '| test'), + fixme(('delete test', 'delete this', '| test')), + ('delete test', 'delete ', '|this test'), # wrong + fixme(('fbar', 'foo', '|bar')), + ('fbar', 'f', '|oobar'), # wrong +]) +def test_rl_unix_line_discard(lineedit, bridge, text, deleted, rest): + """Delete from the cursor to the beginning of the line and yank back.""" + lineedit.set_aug_text(text) + bridge.rl_unix_line_discard() + assert bridge._deleted[lineedit] == deleted + assert lineedit.aug_text() == rest + lineedit.clear() + bridge.rl_yank() + assert lineedit.aug_text() == deleted + '|' + + +@pytest.mark.parametrize('text, deleted, rest', [ + ('test |delete this', 'delete this', 'test |'), + fixme(('delete this', 'test delete this', 'test |')), + ('delete this', 'test delete this', '|'), # wrong +]) +def test_rl_kill_line(lineedit, bridge, text, deleted, rest): + """Delete from the cursor to the end of line and yank back.""" + lineedit.set_aug_text(text) + bridge.rl_kill_line() + assert bridge._deleted[lineedit] == deleted + assert lineedit.aug_text() == rest + lineedit.clear() + bridge.rl_yank() + assert lineedit.aug_text() == deleted + '|' + + +@pytest.mark.parametrize('text, deleted, rest', [ + ('test delete|foobar', 'delete', 'test |foobar'), + ('test delete |foobar', 'delete ', 'test |foobar'), + fixme(('test delfoobar', 'delete', 'test |foobar')), + ('test delfoobar', 'del', 'test |ete foobar'), # wrong +]) +def test_rl_unix_word_rubout(lineedit, bridge, text, deleted, rest): + """Delete to word beginning and see if it comes back with yank.""" + lineedit.set_aug_text(text) + bridge.rl_unix_word_rubout() + assert bridge._deleted[lineedit] == deleted + assert lineedit.aug_text() == rest + lineedit.clear() + bridge.rl_yank() + assert lineedit.aug_text() == deleted + '|' + + +@pytest.mark.parametrize('text, deleted, rest', [ + fixme(('test foobar| delete', ' delete', 'test foobar|')), + ('test foobar| delete', ' ', 'test foobar|delete'), # wrong + fixme(('test foo|delete bar', 'delete', 'test foo| bar')), + ('test foo|delete bar', 'delete ', 'test foo|bar'), # wrong + fixme(('test foo delete', ' delete', 'test foobar|')), + ('test foodelete', 'bardelete', 'test foo|'), # wrong +]) +def test_rl_kill_word(lineedit, bridge, text, deleted, rest): + """Delete to word end and see if it comes back with yank.""" + lineedit.set_aug_text(text) + bridge.rl_kill_word() + assert bridge._deleted[lineedit] == deleted + assert lineedit.aug_text() == rest + lineedit.clear() + bridge.rl_yank() + assert lineedit.aug_text() == deleted + '|' + + +def test_rl_yank_no_text(lineedit, bridge): + """Test yank without having deleted anything.""" + lineedit.clear() + bridge.rl_yank() + assert lineedit.aug_text() == '|' From f1c0781a4c648cf16dcc4b3e8acc508741fe56cd Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 09:09:10 +0200 Subject: [PATCH 02/50] Use sip.SIP_VERSION_STR to get sip version. --- qutebrowser/utils/version.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 827762af4..19dae311a 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -127,18 +127,8 @@ def _module_versions(): A list of lines with version info. """ lines = [] - try: - import sipconfig # pylint: disable=import-error,unused-variable - except ImportError: - lines.append('SIP: ?') - else: - try: - lines.append('SIP: {}'.format( - sipconfig.Configuration().sip_version_str)) - except (AttributeError, TypeError): - log.misc.exception("Error while getting SIP version") - lines.append('SIP: ?') modules = collections.OrderedDict([ + ('sip', ['SIP_VERSION_STR']), ('colorlog', []), ('colorama', ['VERSION', '__version__']), ('pypeg2', ['__version__']), From 6c2fe3417ef79c3c3a1f0fc8e554a80518d7aa48 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 13:38:13 +0200 Subject: [PATCH 03/50] Accept numpad-enter as return in default bindings. See https://bbs.archlinux.org/viewtopic.php?pid=1523326#p1523326 --- CHANGELOG.asciidoc | 2 +- qutebrowser/config/configdata.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 96e6482ea..30bd50cee 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -45,7 +45,7 @@ Changed - `:spawn` now shows the command being executed in the statusbar, use `-q`/`--quiet` for the old behavior. - The `content -> geolocation` and `notifications` settings now support a `true` value to always allow those. However, this is *not recommended*. - New bindings `` (rapid), `` (foreground) and `` (background) to switch hint modes while hinting. -- `` is now accepted as an additional alias for ``/`` +- `` and numpad-enter are now bound by default for bindings where `` was bound. - `:hint tab` and `F` now respect the `background-tabs` setting. To enforce a foreground tab (what `F` did before), use `:hint tab-fg` or `;f`. - `:scroll` now takes a direction argument (`up`/`down`/`left`/`right`/`top`/`bottom`/`page-up`/`page-down`) instead of two pixel arguments (`dx`/`dy`). The old form still works but is deprecated. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9df909676..9deadc1bd 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1125,6 +1125,12 @@ KEY_SECTION_DESC = { ""), } +# Keys which are similiar to Return and should be bound by default where Return +# is bound. + +RETURN_KEYS = ['', '', '', '', '', + ''] + KEY_DATA = collections.OrderedDict([ ('!normal', collections.OrderedDict([ @@ -1243,8 +1249,8 @@ KEY_DATA = collections.OrderedDict([ ('stop', ['']), ('print', ['']), ('open qute:settings', ['Ss']), - ('follow-selected', ['']), - ('follow-selected -t', ['']), + ('follow-selected', RETURN_KEYS), + ('follow-selected -t', ['', '']), ])), ('insert', collections.OrderedDict([ @@ -1252,7 +1258,7 @@ KEY_DATA = collections.OrderedDict([ ])), ('hint', collections.OrderedDict([ - ('follow-hint', ['', '', '']), + ('follow-hint', RETURN_KEYS), ('hint --rapid links tab-bg', ['']), ('hint links', ['']), ('hint all tab-bg', ['']), @@ -1265,13 +1271,11 @@ KEY_DATA = collections.OrderedDict([ ('command-history-next', ['']), ('completion-item-prev', ['', '']), ('completion-item-next', ['', '']), - ('command-accept', ['', '', '', - '']), + ('command-accept', RETURN_KEYS), ])), ('prompt', collections.OrderedDict([ - ('prompt-accept', ['', '', '', - '']), + ('prompt-accept', RETURN_KEYS), ('prompt-yes', ['y']), ('prompt-no', ['n']), ])), @@ -1312,7 +1316,7 @@ KEY_DATA = collections.OrderedDict([ ('move-to-start-of-document', ['gg']), ('move-to-end-of-document', ['G']), ('yank-selected -p', ['Y']), - ('yank-selected', ['y', '', '']), + ('yank-selected', ['y'] + RETURN_KEYS), ('scroll left', ['H']), ('scroll down', ['J']), ('scroll up', ['K']), From b5a70dbdec4916c21493e558aaf618eca3668c61 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 13:43:40 +0200 Subject: [PATCH 04/50] Spelling fix. --- qutebrowser/config/configdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9deadc1bd..e95a54ea8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1125,7 +1125,7 @@ KEY_SECTION_DESC = { ""), } -# Keys which are similiar to Return and should be bound by default where Return +# Keys which are similar to Return and should be bound by default where Return # is bound. RETURN_KEYS = ['', '', '', '', '', From e22ef776f9609af9dcff966b8ca2f695cf910ca3 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Mon, 1 Jun 2015 19:02:07 +0200 Subject: [PATCH 05/50] Fix crash when executing "qutebrowser :set". Fixes #720. See #721. --- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index ba5a5c725..d54b22d0c 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -296,7 +296,7 @@ class TabbedBrowser(tabwidget.TabWidget): newtab: True to open URL in a new tab, False otherwise. """ qtutils.ensure_valid(url) - if newtab: + if newtab or self.currentWidget() is None: self.tabopen(url, background=False) else: self.currentWidget().openurl(url) From dc59ed4d73604a7683c84cdee0b262889ee6d31c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 19:03:28 +0200 Subject: [PATCH 06/50] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 63d803d8f..d42a895ef 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -140,8 +140,8 @@ Contributors, sorted by the number of commits in descending order: * Claude * Artur Shaik * Antoni Boucher -* ZDarian * Martin Tournoij +* ZDarian * Peter Vilim * John ShaggyTwoDope Jenkins * Jimmy From 131f3450072528041a6b7b1dcde210940ad37bba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 19:04:07 +0200 Subject: [PATCH 07/50] Update changelog. --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 30bd50cee..aaf69e22a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -79,6 +79,7 @@ Fixed - Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug) - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". +- Fixed exception when starting qutebrowser with `:set` as argument. https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- From 1bf036d1bad7549a482c1bdec43a46beabcba398 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 22:27:15 +0200 Subject: [PATCH 08/50] Add setting for the webpage bg color to use. Fixes #719. --- CHANGELOG.asciidoc | 1 + qutebrowser/browser/webview.py | 13 +++++++++++++ qutebrowser/config/configdata.py | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index aaf69e22a..92129b3e1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -37,6 +37,7 @@ Added - New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is. - New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`). - New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar. +- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one. Changed ~~~~~~~ diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 06176064d..584c7f268 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -24,6 +24,7 @@ import itertools import functools from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl +from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage @@ -108,6 +109,7 @@ class WebView(QWebView): self.search_flags = 0 self.selection_enabled = False self.init_neighborlist() + self._set_bg_color() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) # For some reason, this signal doesn't get disconnected automatically @@ -181,6 +183,15 @@ class WebView(QWebView): self.load_status = val self.load_status_changed.emit(val.name) + def _set_bg_color(self): + """Set the webpage background color as configured.""" + col = config.get('colors', 'webpage.bg') + palette = self.palette() + if col is None: + col = self.style().standardPalette().color(QPalette.Base) + palette.setColor(QPalette.Base, col) + self.setPalette(palette) + @pyqtSlot(str, str) def on_config_changed(self, section, option): """Reinitialize the zoom neighborlist if related config changed.""" @@ -195,6 +206,8 @@ class WebView(QWebView): self.setContextMenuPolicy(Qt.PreventContextMenu) else: self.setContextMenuPolicy(Qt.DefaultContextMenu) + elif section == 'colors' and option == 'webpage.bg': + self._set_bg_color() def init_neighborlist(self): """Initialize the _zoom neighborlist.""" diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index e95a54ea8..a3bbe2d48 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -959,6 +959,11 @@ def data(readonly=False): SettingValue(typ.QtColor(), 'red'), "Background color for downloads with errors."), + ('webpage.bg', + SettingValue(typ.QtColor(none_ok=True), 'white'), + "Background color for webpages if unset (or empty to use the " + "theme's color)"), + readonly=readonly )), From e767f7c0b81cc5b86d945243fed3c2a16391881d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 22:29:49 +0200 Subject: [PATCH 09/50] Regenerate docs. --- doc/help/settings.asciidoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 073ad2d63..fc4802232 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -221,6 +221,7 @@ |<>|Color gradient end for downloads. |<>|Color gradient interpolation system for downloads. |<>|Background color for downloads with errors. +|<>|Background color for webpages if unset (or empty to use the theme's color) |============== .Quick reference for section ``fonts'' @@ -1752,6 +1753,12 @@ Background color for downloads with errors. Default: +pass:[red]+ +[[colors-webpage.bg]] +=== webpage.bg +Background color for webpages if unset (or empty to use the theme's color) + +Default: +pass:[white]+ + == fonts Fonts used for the UI, with optional style/weight/size. From 592ace18d4aca6928fef2c269e46ee15fd492882 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 22:32:11 +0200 Subject: [PATCH 10/50] Skip test which might be responsible for segfaults. --- tests/mainwindow/statusbar/test_progress.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index 07e93e0e5..a0d066808 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -44,6 +44,9 @@ def progress_widget(qtbot, monkeypatch, config_stub): return widget +@pytest.mark.xfail( + reason='Blacklisted because it could cause random segfaults - see ' + 'https://github.com/hackebrot/qutebrowser/issues/22', run=False) def test_load_started(progress_widget): """Ensure the Progress widget reacts properly when the page starts loading. From d8e58b58869f696fafd0b314740afc94af918e2b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jun 2015 22:45:40 +0200 Subject: [PATCH 11/50] Fix some typos. --- qutebrowser/commands/userscripts.py | 4 ++-- qutebrowser/config/configtypes.py | 2 +- qutebrowser/misc/crashdialog.py | 2 +- qutebrowser/misc/miscwidgets.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 85c511165..76e9f94a6 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -148,7 +148,7 @@ class _BaseUserscriptRunner(QObject): def run(self, cmd, *args, env=None): """Run the userscript given. - Needs to be overridden by superclasses. + Needs to be overridden by subclasses. Args: cmd: The command to be started. @@ -160,7 +160,7 @@ class _BaseUserscriptRunner(QObject): def on_proc_finished(self): """Called when the process has finished. - Needs to be overridden by superclasses. + Needs to be overridden by subclasses. """ raise NotImplementedError diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index b9c760116..55d782202 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -693,7 +693,7 @@ class FontFamily(Font): class QtFont(Font): - """A Font which gets converted to q QFont.""" + """A Font which gets converted to a QFont.""" def transform(self, value): if not value: diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 79a425fa9..dbb473b4e 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -183,7 +183,7 @@ class _CrashDialog(QDialog): def _init_text(self): """Initialize the main text to be displayed on an exception. - Should be extended by superclass to set the actual text.""" + Should be extended by subclasses to set the actual text.""" self._lbl = QLabel(wordWrap=True, openExternalLinks=True, textInteractionFlags=Qt.LinksAccessibleByMouse) self._vbox.addWidget(self._lbl) diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index 74dd8f92e..04ced01c2 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -77,7 +77,7 @@ class CommandLineEdit(QLineEdit): def __on_cursor_position_changed(self, _old, new): """Prevent the cursor moving to the prompt. - We use __ here to avoid accidentally overriding it in superclasses. + We use __ here to avoid accidentally overriding it in subclasses. """ if new < self._promptlen: self.setCursorPosition(self._promptlen) From 36803cba06f18d6dfe79eca7846f656af208f03a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Jun 2015 20:51:06 +0200 Subject: [PATCH 12/50] Switch from flake8 to pytest-{mccabe,flakes,pep8}. --- .flake8 | 13 ----- .gitignore | 1 + CONTRIBUTING.asciidoc | 4 +- MANIFEST.in | 1 - qutebrowser/app.py | 2 +- qutebrowser/browser/network/networkmanager.py | 2 +- qutebrowser/commands/command.py | 2 +- qutebrowser/misc/crashsignal.py | 2 +- qutebrowser/misc/split.py | 2 +- tox.ini | 50 ++++++++++++++++--- 10 files changed, 52 insertions(+), 27 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 29d9c8be4..000000000 --- a/.flake8 +++ /dev/null @@ -1,13 +0,0 @@ -# vim: ft=dosini fileencoding=utf-8: - -[flake8] -# E265: Block comment should start with '#' -# E501: Line too long -# F841: unused variable -# F401: Unused import -# E402: module level import not at top of file -# E266: too many leading '#' for block comment -# W503: line break before binary operator -ignore=E265,E501,F841,F401,E402,E266,W503 -max_complexity = 12 -exclude=resources.py diff --git a/.gitignore b/.gitignore index f3ff3652a..a4c699d38 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__ /htmlcov /.tox /testresults.html +/.cache diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index 7b0fe6087..ed76f5152 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -93,7 +93,9 @@ Currently, the following tools will be invoked when you run `tox`: * Unit tests using the Python https://docs.python.org/3.4/library/unittest.html[unittest] framework -* https://pypi.python.org/pypi/flake8/[flake8] +* https://pypi.python.org/pypi/pyflakes[pyflakes] via https://pypi.python.org/pypi/pytest-flakes[pytest-flakes] +* https://pypi.python.org/pypi/pep8[pep8] via https://pypi.python.org/pypi/pytest-pep8[pytest-pep8] +* https://pypi.python.org/pypi/mccabe[mccabe] via https://pypi.python.org/pypi/pytest-mccabe[pytest-mccabe] * https://github.com/GreenSteam/pep257/[pep257] * http://pylint.org/[pylint] * https://pypi.python.org/pypi/pyroma/[pyroma] diff --git a/MANIFEST.in b/MANIFEST.in index 7ecd44de2..4092f81c5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -28,7 +28,6 @@ include doc/qutebrowser.1.asciidoc prune tests exclude qutebrowser.rcc exclude .coveragerc -exclude .flake8 exclude .pylintrc exclude .eslintrc exclude doc/help diff --git a/qutebrowser/app.py b/qutebrowser/app.py index d125d1d1a..2e8f7ea6a 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -606,7 +606,7 @@ class Quitter: # event loop, so we can shut down immediately. self._shutdown(status) - def _shutdown(self, status): # noqa + def _shutdown(self, status): """Second stage of shutdown.""" log.destroy.debug("Stage 2 of shutting down...") if qApp is None: diff --git a/qutebrowser/browser/network/networkmanager.py b/qutebrowser/browser/network/networkmanager.py index 3b4c71ed0..a3fc76baf 100644 --- a/qutebrowser/browser/network/networkmanager.py +++ b/qutebrowser/browser/network/networkmanager.py @@ -181,7 +181,7 @@ class NetworkManager(QNetworkAccessManager): request.deleteLater() self.shutting_down.emit() - if SSL_AVAILABLE: # noqa + if SSL_AVAILABLE: # pragma: no mccabe @pyqtSlot('QNetworkReply*', 'QList') def on_ssl_errors(self, reply, errors): """Decide if SSL errors should be ignored or not. diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index d55597d9d..4bda15f8a 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -420,7 +420,7 @@ class Command: value = self._type_conv[param.name](value) return name, value - def _get_call_args(self, win_id): # noqa + def _get_call_args(self, win_id): """Get arguments for a function call. Args: diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 7598f3312..2e336bc1e 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -190,7 +190,7 @@ class CrashHandler(QObject): objects = "" return ExceptionInfo(pages, cmd_history, objects) - def exception_hook(self, exctype, excvalue, tb): # noqa + def exception_hook(self, exctype, excvalue, tb): """Handle uncaught python exceptions. It'll try very hard to write all open tabs to a file, and then exit diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index b763d8246..a7bbeea6e 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -55,7 +55,7 @@ class ShellLexer: self.token = '' self.state = ' ' - def __iter__(self): # noqa + def __iter__(self): # pragma: no mccabe """Read a raw token from the input stream.""" # pylint: disable=too-many-branches,too-many-statements self.reset() diff --git a/tox.ini b/tox.ini index f39af4ee3..0b14e4e74 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = unittests,misc,pep257,flake8,pylint,pyroma,check-manifest +envlist = unittests,misc,pep257,pyflakes,pep8,mccabe,pylint,pyroma,check-manifest [testenv] basepython = python3 @@ -74,16 +74,40 @@ passenv = LANG # D402: First line should not be function's signature (false-positives) commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_content_disposition).*\.py' -[testenv:flake8] -skip_install = true +[testenv:pyflakes] +# https://github.com/fschulze/pytest-flakes/issues/6 +setenv = LANG=en_US.UTF-8 deps = -r{toxinidir}/requirements.txt - pyflakes==0.8.1 - pep8==1.5.7 # rq.filter: <1.6.0 - flake8==2.4.0 + py==1.4.27 + pytest==2.7.1 + pyflakes==0.9.0 + pytest-flakes==0.2 commands = {[testenv:mkvenv]commands} - {envdir}/bin/flake8 scripts tests qutebrowser --config=.flake8 + {envpython} -m py.test -q --flakes -m flakes + +[testenv:pep8] +deps = + -r{toxinidir}/requirements.txt + py==1.4.27 + pytest==2.7.1 + pep8==1.6.2 + pytest-pep8==1.0.6 +commands = + {[testenv:mkvenv]commands} + {envpython} -m py.test -q --pep8 -m pep8 + +[testenv:mccabe] +deps = + -r{toxinidir}/requirements.txt + py==1.4.27 + pytest==2.7.1 + mccabe==0.3 + pytest-mccabe==0.1 +commands = + {[testenv:mkvenv]commands} + {envpython} -m py.test -q --mccabe -m mccabe [testenv:pyroma] skip_install = true @@ -129,3 +153,15 @@ commands = norecursedirs = .tox .venv markers = gui: Tests using the GUI (e.g. spawning widgets) +flakes-ignore = + UnusedImport + UnusedVariable + resources.py ALL +pep8ignore = + E265 # Block comment should start with '#' + E501 # Line too long + E402 # module level import not at top of file + E266 # too many leading '#' for block comment + W503 # line break before binary operator + resources.py ALL +mccabe-complexity = 12 From f0c58b58dd2dbbd14025bbaa5e74ab91c2f6e0d7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Jun 2015 20:51:48 +0200 Subject: [PATCH 13/50] Mention pytest in CONTRIBUTING. --- CONTRIBUTING.asciidoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc index ed76f5152..1975a9d7c 100644 --- a/CONTRIBUTING.asciidoc +++ b/CONTRIBUTING.asciidoc @@ -91,8 +91,7 @@ unittests and several linters/checkers. Currently, the following tools will be invoked when you run `tox`: -* Unit tests using the Python -https://docs.python.org/3.4/library/unittest.html[unittest] framework +* Unit tests using https://www.pytest.org[pytest]. * https://pypi.python.org/pypi/pyflakes[pyflakes] via https://pypi.python.org/pypi/pytest-flakes[pytest-flakes] * https://pypi.python.org/pypi/pep8[pep8] via https://pypi.python.org/pypi/pytest-pep8[pytest-pep8] * https://pypi.python.org/pypi/mccabe[mccabe] via https://pypi.python.org/pypi/pytest-mccabe[pytest-mccabe] From e780efb3d97aa901e05e1226c8f21de9f0f53cc5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 3 Jun 2015 15:03:04 +0200 Subject: [PATCH 14/50] Handle javascript in qute:settings more gracefully. Fixes #727. --- qutebrowser/browser/network/qutescheme.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/network/qutescheme.py b/qutebrowser/browser/network/qutescheme.py index 0432fa6fc..48b3dbd5f 100644 --- a/qutebrowser/browser/network/qutescheme.py +++ b/qutebrowser/browser/network/qutescheme.py @@ -34,6 +34,7 @@ import configparser from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtNetwork import QNetworkReply +from PyQt5.QtWebKit import QWebSettings import qutebrowser from qutebrowser.browser.network import schemehandler, networkreply @@ -96,6 +97,12 @@ class JSBridge(QObject): @pyqtSlot(int, str, str, str) def set(self, win_id, sectname, optname, value): """Slot to set a setting from qute:settings.""" + # https://github.com/The-Compiler/qutebrowser/issues/727 + if (sectname, optname == 'content', 'allow-javascript' and + value == 'false'): + message.error(win_id, "Refusing to disable javascript via " + "qute:settings as it needs javascript support.") + return try: objreg.get('config').set('conf', sectname, optname, value) except (configexc.Error, configparser.Error) as e: @@ -172,10 +179,18 @@ def qute_help(win_id, request): def qute_settings(win_id, _request): """Handler for qute:settings. View/change qute configuration.""" - config_getter = functools.partial(objreg.get('config').get, raw=True) - html = jinja.env.get_template('settings.html').render( - win_id=win_id, title='settings', config=configdata, - confget=config_getter) + if not QWebSettings.globalSettings().testAttribute( + QWebSettings.JavascriptEnabled): + # https://github.com/The-Compiler/qutebrowser/issues/727 + template = jinja.env.get_template('pre.html') + html = template.render( + title='Failed to open qute:settings.', + content="qute:settings needs javascript enabled to work.") + else: + config_getter = functools.partial(objreg.get('config').get, raw=True) + html = jinja.env.get_template('settings.html').render( + win_id=win_id, title='settings', config=configdata, + confget=config_getter) return html.encode('UTF-8', errors='xmlcharrefreplace') From 0132bea42bb023139b773c331115ac00ad93f081 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 4 Jun 2015 12:20:43 +0200 Subject: [PATCH 15/50] Add --domain to yank to yank only the domain ... As I want to copy only the domain fairly frequently. I also changed the message in the statusline to show the actual text being copied, which I find helpful. But if you disagree, then just undo it (it's not that important or anything). --- doc/help/commands.asciidoc | 3 ++- qutebrowser/browser/commands.py | 13 ++++++++++--- qutebrowser/config/configdata.py | 2 ++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 6cc2ee9db..e3dd81e62 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -642,13 +642,14 @@ Save open pages and quit. [[yank]] === yank -Syntax: +:yank [*--title*] [*--sel*]+ +Syntax: +:yank [*--title*] [*--sel*] [*--domain*]+ Yank the current URL/title 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. [[zoom]] === zoom diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 6585abed6..9cb2a9991 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -696,19 +696,26 @@ class CommandDispatcher: frame.scroll(dx, dy) @cmdutils.register(instance='command-dispatcher', scope='window') - def yank(self, title=False, sel=False): + def yank(self, title=False, sel=False, domain=False): """Yank the current URL/title to the clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. title: Yank the title instead of the URL. + domain: Yank only the scheme & domain. """ clipboard = QApplication.clipboard() if title: s = self._tabbed_browser.page_title(self._current_index()) + what = 'title' + elif domain: + s = '{}://{}'.format(self._current_url().scheme(), + self._current_url().host()) + what = 'domain' else: s = self._current_url().toString( QUrl.FullyEncoded | QUrl.RemovePassword) + what = 'URL' if sel and clipboard.supportsSelection(): mode = QClipboard.Selection target = "primary selection" @@ -717,8 +724,8 @@ class CommandDispatcher: target = "clipboard" log.misc.debug("Yanking to {}: '{}'".format(target, s)) clipboard.setText(s, mode) - what = 'Title' if title else 'URL' - message.info(self._win_id, "{} yanked to {}".format(what, target)) + message.info(self._win_id, "Yanked {} to {}: {}".format( + what, target, s)) @cmdutils.register(instance='command-dispatcher', scope='window', count='count') diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a3bbe2d48..ad269d212 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1204,6 +1204,8 @@ KEY_DATA = collections.OrderedDict([ ('yank -s', ['yY']), ('yank -t', ['yt']), ('yank -ts', ['yT']), + ('yank -d', ['yd']), + ('yank -ds', ['yD']), ('paste', ['pp']), ('paste -s', ['pP']), ('paste -t', ['Pp']), From d60d4d756ce4e5cb45908d9940372e771d0b37dd Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Thu, 4 Jun 2015 13:20:39 +0200 Subject: [PATCH 16/50] Also yank port number --- doc/help/commands.asciidoc | 2 +- qutebrowser/browser/commands.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index e3dd81e62..104478c10 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -649,7 +649,7 @@ Yank the current URL/title 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. +* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number. [[zoom]] === zoom diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9cb2a9991..9bfe81a10 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -702,15 +702,17 @@ class CommandDispatcher: Args: sel: Use the primary selection instead of the clipboard. title: Yank the title instead of the URL. - domain: Yank only the scheme & domain. + domain: Yank only the scheme, domain, and port number. """ clipboard = QApplication.clipboard() if title: s = self._tabbed_browser.page_title(self._current_index()) what = 'title' elif domain: - s = '{}://{}'.format(self._current_url().scheme(), - self._current_url().host()) + port = self._current_url().port() + s = '{}://{}{}'.format(self._current_url().scheme(), + self._current_url().host(), + ':' + str(port) if port > -1 else '') what = 'domain' else: s = self._current_url().toString( From 9ec6e6da80eeacd6c127768bd3a41a965d7ab21c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:13:20 +0200 Subject: [PATCH 17/50] Fix exit status codes to be 0-based. --- CHANGELOG.asciidoc | 1 + qutebrowser/utils/usertypes.py | 2 +- tests/utils/usertypes/test_enum.py | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 92129b3e1..6bd22ced2 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,6 +81,7 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. +- Fixed exit status codes (successful exit was 1 instead of 0). https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 5d19ad515..c82a54596 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -242,7 +242,7 @@ Completion = enum('Completion', ['command', 'section', 'option', 'value', # Exit statuses for errors. Needs to be an int for sys.exit. Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init', - 'err_config', 'err_key_config'], is_int=True) + 'err_config', 'err_key_config'], is_int=True, start=0) class Question(QObject): diff --git a/tests/utils/usertypes/test_enum.py b/tests/utils/usertypes/test_enum.py index e1443b7be..7298b2861 100644 --- a/tests/utils/usertypes/test_enum.py +++ b/tests/utils/usertypes/test_enum.py @@ -54,3 +54,9 @@ def test_start(): e = usertypes.enum('Enum', ['three', 'four'], start=3) assert e.three.value == 3 assert e.four.value == 4 + + +def test_exit(): + """Make sure the exit status enum is correct.""" + assert usertypes.Exit.ok == 0 + assert usertypes.Exit.reserved == 1 From f41acc8fb5c00e9ba5c14dc3a3ec9fc335528c7e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:14:56 +0200 Subject: [PATCH 18/50] Remove changelog entry as it's a recent regression --- CHANGELOG.asciidoc | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 6bd22ced2..92129b3e1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,7 +81,6 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. -- Fixed exit status codes (successful exit was 1 instead of 0). https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- From 4a909aa028bdc79424b48b24d4a908f8186058f1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 4 Jun 2015 15:25:36 +0200 Subject: [PATCH 19/50] Use pylint's built-in checker to check for CRLF. --- .pylintrc | 1 - scripts/pylint_checkers/crlf.py | 45 --------------------------------- tox.ini | 4 +-- 3 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 scripts/pylint_checkers/crlf.py diff --git a/.pylintrc b/.pylintrc index 2cc56909d..a4abb32a0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,7 +4,6 @@ ignore=resources.py extension-pkg-whitelist=PyQt5,sip load-plugins=pylint_checkers.config, - pylint_checkers.crlf, pylint_checkers.modeline, pylint_checkers.openencoding, pylint_checkers.settrace diff --git a/scripts/pylint_checkers/crlf.py b/scripts/pylint_checkers/crlf.py deleted file mode 100644 index a77f8b9e0..000000000 --- a/scripts/pylint_checkers/crlf.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright 2014-2015 Florian Bruhin (The Compiler) -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# 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 . - -"""Checker for CRLF in files.""" - -from pylint import interfaces, checkers - - -class CrlfChecker(checkers.BaseChecker): - - """Check for CRLF in files.""" - - __implements__ = interfaces.IRawChecker - - name = 'crlf' - msgs = {'W9001': ('Uses CRLFs', 'crlf', None)} - options = () - priority = -1 - - def process_module(self, node): - """Process the module.""" - for (lineno, line) in enumerate(node.file_stream): - if b'\r\n' in line: - self.add_message('crlf', line=lineno) - return - - -def register(linter): - """Register the checker.""" - linter.register_checker(CrlfChecker(linter)) diff --git a/tox.ini b/tox.ini index 0b14e4e74..cff331d21 100644 --- a/tox.ini +++ b/tox.ini @@ -61,8 +61,8 @@ deps = six==1.9.0 commands = {[testenv:mkvenv]commands} - {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no - {envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no + {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF + {envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF [testenv:pep257] skip_install = true From 05fe68ccab1a6f64819c903a6edb424be758e3fc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 06:39:37 +0200 Subject: [PATCH 20/50] Regenerate authors --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index d42a895ef..3a701fd76 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -138,9 +138,9 @@ Contributors, sorted by the number of commits in descending order: * Raphael Pierzina * Joel Torstensson * Claude +* Martin Tournoij * Artur Shaik * Antoni Boucher -* Martin Tournoij * ZDarian * Peter Vilim * John ShaggyTwoDope Jenkins From 622938e3d3e39fb5222d6c3271b654165f404130 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 07:16:33 +0200 Subject: [PATCH 21/50] Fix completion performance with shrink=True. Before, the completion was shrinked every time any item was removed/added to the completion (rowsRemoved/rowsInserted signals), which was >3000 times when completing history. Also, the signals got connected multiple times if setting the same model, which made the situation worse. Fixes #734. --- CHANGELOG.asciidoc | 1 + qutebrowser/completion/completer.py | 2 +- qutebrowser/completion/completionwidget.py | 13 +++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 92129b3e1..c2daf1efe 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -81,6 +81,7 @@ Fixed - Fixed handling of keybindings containing Ctrl/Meta on OS X. - Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...". - Fixed exception when starting qutebrowser with `:set` as argument. +- Fixed horrible completion performance when the `shrink` option was set. https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1] ----------------------------------------------------------------------- diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 2fd1858ca..197c62ce1 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -272,7 +272,7 @@ class Completer(QObject): pattern = parts[self._cursor_part].strip() except IndexError: pattern = '' - self._model().set_pattern(pattern) + completion.set_pattern(pattern) log.completion.debug( "New completion for {}: {}, with pattern '{}'".format( diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index aa2fd31da..0bd6b04c9 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -201,8 +201,17 @@ class CompletionView(QTreeView): for i in range(model.rowCount()): self.expand(model.index(i, 0)) self._resize_columns() - model.rowsRemoved.connect(self.maybe_resize_completion) - model.rowsInserted.connect(self.maybe_resize_completion) + self.maybe_resize_completion() + + def set_pattern(self, pattern): + """Set the completion pattern for the current model. + + Called from on_update_completion(). + + Args: + pattern: The filter pattern to set (what the user entered). + """ + self.model().set_pattern(pattern) self.maybe_resize_completion() @pyqtSlot() From 7102459c8148f1f356b47231638d48c4d90161e8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 11:15:18 +0200 Subject: [PATCH 22/50] Rename _get_modeman() to instance(). --- qutebrowser/keyinput/modeman.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index fc70ac76b..e64522204 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -97,25 +97,25 @@ def init(win_id, parent): return modeman -def _get_modeman(win_id): +def instance(win_id): """Get a modemanager object.""" return objreg.get('mode-manager', scope='window', window=win_id) def enter(win_id, mode, reason=None, only_if_normal=False): """Enter the mode 'mode'.""" - _get_modeman(win_id).enter(mode, reason, only_if_normal) + instance(win_id).enter(mode, reason, only_if_normal) def leave(win_id, mode, reason=None): """Leave the mode 'mode'.""" - _get_modeman(win_id).leave(mode, reason) + instance(win_id).leave(mode, reason) def maybe_leave(win_id, mode, reason=None): """Convenience method to leave 'mode' without exceptions.""" try: - _get_modeman(win_id).leave(mode, reason) + instance(win_id).leave(mode, reason) except NotInModeError as e: # This is rather likely to happen, so we only log to debug log. log.modes.debug("{} (leave reason: {})".format(e, reason)) From 728f06e797d9c9cf002979b2b365748913bb3c32 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 11:15:22 +0200 Subject: [PATCH 23/50] Close context menu if another mode was entered. Fixes #735. --- qutebrowser/browser/webview.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index 584c7f268..ec82d2567 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -620,6 +620,7 @@ class WebView(QWebView): """Save a reference to the context menu so we can close it.""" menu = self.page().createStandardContextMenu() self.shutting_down.connect(menu.close) + modeman.instance(self.win_id).entered.connect(menu.close) menu.exec_(e.globalPos()) def wheelEvent(self, e): From 57ddd8e95e5d8ff8ad0473e84e58094045e57d6a Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 14:24:43 +0200 Subject: [PATCH 24/50] Always handle the key, even if it's bound. This fixes #716, which sufficiently annoyed me to make this quick fix. It's not a great fix, but it's not worse than what we had already, and the current behaviour is very surprising IMHO. --- qutebrowser/keyinput/basekeyparser.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index b52a39824..ce9719f31 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -162,12 +162,6 @@ class BaseKeyParser(QObject): key = e.key() self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt)) - if key == Qt.Key_Escape: - self._debug_log("Escape pressed, discarding '{}'.".format( - self._keystring)) - self._keystring = '' - return self.Match.none - if len(txt) == 1: category = unicodedata.category(txt) is_control_char = (category == 'Cc') @@ -303,6 +297,15 @@ class BaseKeyParser(QObject): True if the event was handled, False otherwise. """ handled = self._handle_special_key(e) + + # Special case for . See: + # https://github.com/The-Compiler/qutebrowser/issues/716 + if e.key() == Qt.Key_Escape: + self._debug_log("Escape pressed, discarding '{}'.".format( + self._keystring)) + self._keystring = '' + self.keystring_updated.emit(self._keystring) + if handled or not self._supports_chains: return handled match = self._handle_single_key(e) From fa65f345ac36bbb3e075d682d6c072af9a8bc347 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 15:10:41 +0200 Subject: [PATCH 25/50] Perhaps fix it more properly after all :-) --- doc/help/commands.asciidoc | 5 +++++ qutebrowser/config/configdata.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 14 ++++++-------- qutebrowser/keyinput/modeman.py | 5 +++++ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 104478c10..c14f23b81 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -685,6 +685,7 @@ How many steps to zoom out. [options="header",width="75%",cols="25%,75%"] |============== |Command|Description +|<>|Clear the currently entered key chain. |<>|Execute the command currently in the commandline. |<>|Go forward in the commandline history. |<>|Go back in the commandline history. @@ -739,6 +740,10 @@ How many steps to zoom out. |<>|Toggle caret selection mode. |<>|Yank the selected text to the clipboard or primary selection. |============== +[[clear-keychain]] +=== clear-keychain +Clear the currently entered key chain. + [[command-accept]] === command-accept Execute the command currently in the commandline. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index ad269d212..aa7a9c8c8 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1143,7 +1143,7 @@ KEY_DATA = collections.OrderedDict([ ])), ('normal', collections.OrderedDict([ - ('search', ['']), + ('search ;; clear-keychain', ['']), ('set-cmd-text -s :open', ['o']), ('set-cmd-text :open {url}', ['go']), ('set-cmd-text -s :open -t', ['O']), diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index ce9719f31..487575127 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -298,14 +298,6 @@ class BaseKeyParser(QObject): """ handled = self._handle_special_key(e) - # Special case for . See: - # https://github.com/The-Compiler/qutebrowser/issues/716 - if e.key() == Qt.Key_Escape: - self._debug_log("Escape pressed, discarding '{}'.".format( - self._keystring)) - self._keystring = '' - self.keystring_updated.emit(self._keystring) - if handled or not self._supports_chains: return handled match = self._handle_single_key(e) @@ -362,3 +354,9 @@ class BaseKeyParser(QObject): "defined!") if mode == self._modename: self.read_config() + + def clear_keystring(self): + """Clear the currently entered key sequence.""" + self._debug_log("discarding keystring '{}'.".format(self._keystring)) + self._keystring = '' + self.keystring_updated.emit(self._keystring) diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index e64522204..686741044 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -332,3 +332,8 @@ class ModeManager(QObject): return self._eventFilter_keypress(event) else: return self._eventFilter_keyrelease(event) + + @cmdutils.register(instance='mode-manager', scope='window', hide=True) + def clear_keychain(self): + """Clear the currently entered key chain.""" + self._handlers[self.mode].__self__.clear_keystring() From b55e22b5c335104b80266bc761461bd0285aa201 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 15:29:09 +0200 Subject: [PATCH 26/50] Refactor key mode/parser handling in modeman. --- qutebrowser/keyinput/basekeyparser.py | 3 ++ qutebrowser/keyinput/keyparser.py | 1 + qutebrowser/keyinput/modeman.py | 53 +++++++++---------------- qutebrowser/keyinput/modeparsers.py | 2 + qutebrowser/mainwindow/statusbar/bar.py | 14 +++---- 5 files changed, 32 insertions(+), 41 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index b52a39824..1c2c9ab86 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -49,6 +49,8 @@ class BaseKeyParser(QObject): special: execute() was called via a special key binding do_log: Whether to log keypresses or not. + passthrough: Whether unbound keys should be passed through with this + handler. Attributes: bindings: Bound key bindings @@ -69,6 +71,7 @@ class BaseKeyParser(QObject): keystring_updated = pyqtSignal(str) do_log = True + passthrough = False Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous', 'other', 'none']) diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py index bef364e66..46f179fdb 100644 --- a/qutebrowser/keyinput/keyparser.py +++ b/qutebrowser/keyinput/keyparser.py @@ -55,6 +55,7 @@ class PassthroughKeyParser(CommandKeyParser): """ do_log = False + passthrough = True def __init__(self, win_id, mode, parent=None, warn=True): """Constructor. diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index e64522204..357a5ffc9 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -84,16 +84,8 @@ def init(win_id, parent): modeman.destroyed.connect( functools.partial(objreg.delete, 'keyparsers', scope='window', window=win_id)) - modeman.register(KM.normal, keyparsers[KM.normal].handle) - modeman.register(KM.hint, keyparsers[KM.hint].handle) - modeman.register(KM.insert, keyparsers[KM.insert].handle, passthrough=True) - modeman.register(KM.passthrough, keyparsers[KM.passthrough].handle, - passthrough=True) - modeman.register(KM.command, keyparsers[KM.command].handle, - passthrough=True) - modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True) - modeman.register(KM.yesno, keyparsers[KM.yesno].handle) - modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True) + for mode, parser in keyparsers.items(): + modeman.register(mode, parser) return modeman @@ -126,10 +118,9 @@ class ModeManager(QObject): """Manager for keyboard modes. Attributes: - passthrough: A list of modes in which to pass through events. mode: The mode we're currently in. _win_id: The window ID of this ModeManager - _handlers: A dictionary of modes and their handlers. + _parsers: A dictionary of modes and their keyparsers. _forward_unbound_keys: If we should forward unbound keys. _releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was passed through, so the release event should as @@ -151,8 +142,7 @@ class ModeManager(QObject): def __init__(self, win_id, parent=None): super().__init__(parent) self._win_id = win_id - self._handlers = {} - self.passthrough = [] + self._parsers = {} self.mode = usertypes.KeyMode.normal self._releaseevents_to_pass = set() self._forward_unbound_keys = config.get( @@ -160,8 +150,7 @@ class ModeManager(QObject): objreg.get('config').changed.connect(self.set_forward_unbound_keys) def __repr__(self): - return utils.get_repr(self, mode=self.mode, - passthrough=self.passthrough) + return utils.get_repr(self, mode=self.mode) def _eventFilter_keypress(self, event): """Handle filtering of KeyPress events. @@ -173,11 +162,11 @@ class ModeManager(QObject): True if event should be filtered, False otherwise. """ curmode = self.mode - handler = self._handlers[curmode] + parser = self._parsers[curmode] if curmode != usertypes.KeyMode.insert: - log.modes.debug("got keypress in mode {} - calling handler " - "{}".format(curmode, utils.qualname(handler))) - handled = handler(event) if handler is not None else False + log.modes.debug("got keypress in mode {} - delegating to " + "{}".format(curmode, utils.qualname(parser))) + handled = parser.handle(event) is_non_alnum = bool(event.modifiers()) or not event.text().strip() focus_widget = QApplication.instance().focusWidget() @@ -187,7 +176,7 @@ class ModeManager(QObject): filter_this = True elif is_tab and not isinstance(focus_widget, QWebView): filter_this = True - elif (curmode in self.passthrough or + elif (parser.passthrough or self._forward_unbound_keys == 'all' or (self._forward_unbound_keys == 'auto' and is_non_alnum)): filter_this = False @@ -202,8 +191,8 @@ class ModeManager(QObject): "passthrough: {}, is_non_alnum: {}, is_tab {} --> " "filter: {} (focused: {!r})".format( handled, self._forward_unbound_keys, - curmode in self.passthrough, is_non_alnum, - is_tab, filter_this, focus_widget)) + parser.passthrough, is_non_alnum, is_tab, + filter_this, focus_widget)) return filter_this def _eventFilter_keyrelease(self, event): @@ -226,20 +215,16 @@ class ModeManager(QObject): log.modes.debug("filter: {}".format(filter_this)) return filter_this - def register(self, mode, handler, passthrough=False): + def register(self, mode, parser): """Register a new mode. Args: mode: The name of the mode. - handler: Handler for keyPressEvents. - passthrough: Whether to pass key bindings in this mode through to - the widgets. + parser: The KeyParser which should be used. """ - if not isinstance(mode, usertypes.KeyMode): - raise TypeError("Mode {} is no KeyMode member!".format(mode)) - self._handlers[mode] = handler - if passthrough: - self.passthrough.append(mode) + assert isinstance(mode, usertypes.KeyMode) + assert parser is not None + self._parsers[mode] = parser def enter(self, mode, reason=None, only_if_normal=False): """Enter a new mode. @@ -253,8 +238,8 @@ class ModeManager(QObject): raise TypeError("Mode {} is no KeyMode member!".format(mode)) log.modes.debug("Entering mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) - if mode not in self._handlers: - raise ValueError("No handler for mode {}".format(mode)) + if mode not in self._parsers: + raise ValueError("No keyparser for mode {}".format(mode)) prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno) if self.mode == mode or (self.mode in prompt_modes and mode in prompt_modes): diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 8d47de0c1..d16734ed0 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -224,6 +224,8 @@ class CaretKeyParser(keyparser.CommandKeyParser): """KeyParser for caret mode.""" + passthrough = True + def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 5f633eaaa..bc828a261 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -469,9 +469,9 @@ class StatusBar(QWidget): @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" - mode_manager = objreg.get('mode-manager', scope='window', - window=self._win_id) - if mode in mode_manager.passthrough: + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + if keyparsers[mode].passthrough: self._set_mode_text(mode.name) if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret): self.set_mode_active(mode, True) @@ -479,10 +479,10 @@ class StatusBar(QWidget): @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): """Clear marked mode.""" - mode_manager = objreg.get('mode-manager', scope='window', - window=self._win_id) - if old_mode in mode_manager.passthrough: - if new_mode in mode_manager.passthrough: + keyparsers = objreg.get('keyparsers', scope='window', + window=self._win_id) + if keyparsers[old_mode].passthrough: + if keyparsers[new_mode].passthrough: self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') From dfada850e0ebcbc1f9a847bbf964deed27ef17d4 Mon Sep 17 00:00:00 2001 From: Martin Tournoij Date: Fri, 5 Jun 2015 16:52:33 +0200 Subject: [PATCH 27/50] Update code after refactor, and add migration --- qutebrowser/config/configdata.py | 2 ++ qutebrowser/keyinput/modeman.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index aa7a9c8c8..f9b0470c7 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1354,4 +1354,6 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^scroll 0 -50$'), r'scroll up'), (re.compile(r'^scroll 50 0$'), r'scroll right'), (re.compile(r'^scroll ([-\d]+ [-\d]+)$'), r'scroll-px \1'), + + (re.compile(r'^search$'), r'search ;; clear-keychain'), ] diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 5b03bd9e0..6906a8720 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -321,4 +321,4 @@ class ModeManager(QObject): @cmdutils.register(instance='mode-manager', scope='window', hide=True) def clear_keychain(self): """Clear the currently entered key chain.""" - self._handlers[self.mode].__self__.clear_keystring() + self._parsers[self.mode].clear_keystring() From e38169433ebe69d082fade285789c790a1645525 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:26:49 +0200 Subject: [PATCH 28/50] tox: Update pytest-flakes to 1.0.0. Upstream changelog: - Fix issue #6 - support PEP263 for source file encoding. - Clarified license to be MIT like pytest-pep8 from which this is derived. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index cff331d21..93f00d40a 100644 --- a/tox.ini +++ b/tox.ini @@ -82,7 +82,7 @@ deps = py==1.4.27 pytest==2.7.1 pyflakes==0.9.0 - pytest-flakes==0.2 + pytest-flakes==1.0.0 commands = {[testenv:mkvenv]commands} {envpython} -m py.test -q --flakes -m flakes From 6ec8bbaca55f812bd1a32d78bea99f63272e155a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:27:59 +0200 Subject: [PATCH 29/50] tox: Update pytest-mock to 0.6.0. Upstream changelog: - Two new auxiliary methods, spy and stub. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 93f00d40a..0c4393277 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = pytest==2.7.1 pytest-capturelog==0.7 pytest-qt==1.3.0 - pytest-mock==0.5 + pytest-mock==0.6.0 pytest-html==1.3.1 # We don't use {[testenv:mkvenv]commands} here because that seems to be broken # on Ubuntu Trusty. From d3f7d9319a346993a8401d81c7f2b69c55b8fa99 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:29:00 +0200 Subject: [PATCH 30/50] tox: Update py to 1.4.28. Upstream changelog: - fix issue64 -- dirpath regression when "abs=True" is passed. Thanks Gilles Dartiguelongue. --- tox.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 0c4393277..b24842ff4 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/pl passenv = DISPLAY XAUTHORITY HOME deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pytest-capturelog==0.7 pytest-qt==1.3.0 @@ -79,7 +79,7 @@ commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D2 setenv = LANG=en_US.UTF-8 deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pyflakes==0.9.0 pytest-flakes==1.0.0 @@ -90,7 +90,7 @@ commands = [testenv:pep8] deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 pep8==1.6.2 pytest-pep8==1.0.6 @@ -101,7 +101,7 @@ commands = [testenv:mccabe] deps = -r{toxinidir}/requirements.txt - py==1.4.27 + py==1.4.28 pytest==2.7.1 mccabe==0.3 pytest-mccabe==0.1 From 80010996618372aa69764cd670a79f6f13848cdb Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:45:32 +0200 Subject: [PATCH 31/50] Adjust tests. --- tests/config/test_config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index d5fab2ed1..3fff0ea66 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -197,8 +197,10 @@ class TestKeyConfigParser: ('download-page', 'download'), ('cancel-download', 'download-cancel'), - ('search ""', 'search'), - ("search ''", 'search'), + ('search ""', 'search ;; clear-keychain'), + ("search ''", 'search ;; clear-keychain'), + ("search", 'search ;; clear-keychain'), + ("search ;; foobar", None), ('search "foo"', None), ('set-cmd-text "foo bar"', 'set-cmd-text foo bar'), From 5fb23f1373dd00db62224127ed0d22ab15c5944b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:45:38 +0200 Subject: [PATCH 32/50] Also migrate older search calls. --- qutebrowser/config/configdata.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index f9b0470c7..d2c360682 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1340,8 +1340,8 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^download-page$'), r'download'), (re.compile(r'^cancel-download$'), r'download-cancel'), - (re.compile(r'^search ""$'), r'search'), - (re.compile(r"^search ''$"), r'search'), + (re.compile(r"""^search (''|"")$"""), r'search ;; clear-keychain'), + (re.compile(r'^search$'), r'search ;; clear-keychain'), (re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'), (re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'), @@ -1354,6 +1354,4 @@ CHANGED_KEY_COMMANDS = [ (re.compile(r'^scroll 0 -50$'), r'scroll up'), (re.compile(r'^scroll 50 0$'), r'scroll right'), (re.compile(r'^scroll ([-\d]+ [-\d]+)$'), r'scroll-px \1'), - - (re.compile(r'^search$'), r'search ;; clear-keychain'), ] From d3e85ad9824d06bf27d90b6b35c6a54b02ca6c56 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:50:00 +0200 Subject: [PATCH 33/50] Update docs. --- doc/help/commands.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index c14f23b81..5a6ce06bf 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -685,7 +685,7 @@ How many steps to zoom out. [options="header",width="75%",cols="25%,75%"] |============== |Command|Description -|<>|Clear the currently entered key chain. +|<>|Clear the currently entered key chain. |<>|Execute the command currently in the commandline. |<>|Go forward in the commandline history. |<>|Go back in the commandline history. From 015de0e6dbd0171acbc881427adf03695480254f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:51:33 +0200 Subject: [PATCH 34/50] misc_checks: Check spelling case-insensitively. --- scripts/misc_checks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index 4e2c185f5..36bc28079 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -87,7 +87,7 @@ def check_spelling(target): continue for line in f: for w in words: - if re.search(w, line) and fn not in seen[w]: + if re.search(w, line, re.I) and fn not in seen[w]: print("Found '{}' in {}!".format(w, fn)) seen[w].append(fn) ok = False From 2459f14f6fe431f6b8bc0d8aa8ebcc4d6c7b16ad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 17:53:16 +0200 Subject: [PATCH 35/50] Update changelog. --- CHANGELOG.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c2daf1efe..92cc4cd24 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -38,6 +38,7 @@ Added - New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`). - New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar. - New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one. +- New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `` by default, in addition to clearing search). Changed ~~~~~~~ From fd75f77108330beea10d78c3907fa59fa9fcbe8f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 5 Jun 2015 18:18:29 +0200 Subject: [PATCH 36/50] Fix spell checker to check all files. --- scripts/misc_checks.py | 35 ++++++++++++++--------------------- tox.ini | 4 ++-- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index 36bc28079..c6033cf3b 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -35,9 +35,13 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from scripts import utils -def _py_files(target): +def _py_files(): """Iterate over all python files and yield filenames.""" - for (dirpath, _dirnames, filenames) in os.walk(target): + for (dirpath, _dirnames, filenames) in os.walk('.'): + parts = dirpath.split(os.sep) + if len(parts) >= 2 and parts[1].startswith('.'): + # ignore hidden dirs + continue for name in (e for e in filenames if e.endswith('.py')): yield os.path.join(dirpath, name) @@ -64,7 +68,7 @@ def check_git(): return status -def check_spelling(target): +def check_spelling(): """Check commonly misspelled words.""" # Words which I often misspell words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully', @@ -81,9 +85,9 @@ def check_spelling(target): seen = collections.defaultdict(list) try: ok = True - for fn in _py_files(target): + for fn in _py_files(): with tokenize.open(fn) as f: - if fn == os.path.join('scripts', 'misc_checks.py'): + if fn == os.path.join('.', 'scripts', 'misc_checks.py'): continue for line in f: for w in words: @@ -98,11 +102,11 @@ def check_spelling(target): return None -def check_vcs_conflict(target): +def check_vcs_conflict(): """Check VCS conflict markers.""" try: ok = True - for fn in _py_files(target): + for fn in _py_files(): with tokenize.open(fn) as f: for line in f: if any(line.startswith(c * 7) for c in '<>=|'): @@ -120,25 +124,14 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('checker', choices=('git', 'vcs', 'spelling'), help="Which checker to run.") - parser.add_argument('target', help="What to check", nargs='*') args = parser.parse_args() if args.checker == 'git': ok = check_git() - return 0 if ok else 1 elif args.checker == 'vcs': - is_ok = True - for target in args.target: - ok = check_vcs_conflict(target) - if not ok: - is_ok = False - return 0 if is_ok else 1 + ok = check_vcs_conflict() elif args.checker == 'spelling': - is_ok = True - for target in args.target: - ok = check_spelling(target) - if not ok: - is_ok = False - return 0 if is_ok else 1 + ok = check_spelling() + return 0 if ok else 1 if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index b24842ff4..7717dd8ec 100644 --- a/tox.ini +++ b/tox.ini @@ -46,8 +46,8 @@ commands = [testenv:misc] commands = {envpython} scripts/misc_checks.py git - {envpython} scripts/misc_checks.py vcs qutebrowser scripts tests - {envpython} scripts/misc_checks.py spelling qutebrowser scripts tests + {envpython} scripts/misc_checks.py vcs + {envpython} scripts/misc_checks.py spelling [testenv:pylint] skip_install = true From def41e70bf69af1bc8abf92f632f0c6e6b99c18a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:11:16 +0200 Subject: [PATCH 37/50] Fix some spelling mistakes. --- qutebrowser/browser/webelem.py | 2 +- qutebrowser/browser/webpage.py | 4 ++-- qutebrowser/browser/webview.py | 2 +- qutebrowser/config/configdata.py | 2 +- qutebrowser/config/configtypes.py | 2 +- qutebrowser/keyinput/basekeyparser.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- qutebrowser/utils/log.py | 2 +- qutebrowser/utils/usertypes.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 59fea9897..263289e6b 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -312,7 +312,7 @@ def javascript_escape(text): def get_child_frames(startframe): """Get all children recursively of a given QWebFrame. - Loosly based on http://blog.nextgenetics.net/?e=64 + Loosely based on http://blog.nextgenetics.net/?e=64 Args: startframe: The QWebFrame to start with. diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 8e430efcb..15659f56f 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -109,7 +109,7 @@ class BrowserPage(QWebPage): def _handle_errorpage(self, info, errpage): """Display an error page if needed. - Loosly based on Helpviewer/HelpBrowserWV.py from eric5 + Loosely based on Helpviewer/HelpBrowserWV.py from eric5 (line 260 @ 5d937eb378dd) Args: @@ -178,7 +178,7 @@ class BrowserPage(QWebPage): def _handle_multiple_files(self, info, files): """Handle uploading of multiple files. - Loosly based on Helpviewer/HelpBrowserWV.py from eric5. + Loosely based on Helpviewer/HelpBrowserWV.py from eric5. Args: info: The ChooseMultipleFilesExtensionOption instance. diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index ec82d2567..5a4fc3b69 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -163,7 +163,7 @@ class WebView(QWebView): return utils.get_repr(self, tab_id=self.tab_id, url=url) def __del__(self): - # Explicitely releasing the page here seems to prevent some segfaults + # Explicitly releasing the page here seems to prevent some segfaults # when quitting. # Copied from: # https://code.google.com/p/webscraping/source/browse/webkit.py#325 diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index d2c360682..9cf7115f7 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -464,7 +464,7 @@ def data(readonly=False): ('last-close', SettingValue(typ.LastClose(), 'ignore'), - "Behaviour when the last tab is closed."), + "Behavior when the last tab is closed."), ('hide-auto', SettingValue(typ.Bool(), 'false'), diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 55d782202..748748d42 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1312,7 +1312,7 @@ class SelectOnRemove(BaseType): class LastClose(BaseType): - """Behaviour when the last tab is closed.""" + """Behavior when the last tab is closed.""" valid_values = ValidValues(('ignore', "Don't do anything."), ('blank', "Load a blank page."), diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index d19e7ad9a..11b965c27 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -195,7 +195,7 @@ class BaseKeyParser(QObject): self._keystring = '' self.execute(binding, self.Type.chain, count) elif match == self.Match.ambiguous: - self._debug_log("Ambigious match for '{}'.".format( + self._debug_log("Ambiguous match for '{}'.".format( self._keystring)) self._handle_ambiguous_match(binding, count) elif match == self.Match.partial: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index d54b22d0c..7b4d84b7b 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -332,7 +332,7 @@ class TabbedBrowser(tabwidget.TabWidget): the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current. - - Explicitely opened tabs are at the very right. + - Explicitly opened tabs are at the very right. Return: The opened WebView instance. diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index d156c6be1..1f1071673 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -377,7 +377,7 @@ class RAMHandler(logging.Handler): """Logging handler which keeps the messages in a deque in RAM. - Loosly based on logging.BufferingHandler which is unsuitable because it + Loosely based on logging.BufferingHandler which is unsuitable because it uses a simple list rather than a deque. Attributes: diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index c82a54596..13a2e1cfb 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -73,7 +73,7 @@ class NeighborList(collections.abc.Sequence): Args: items: The list of items to iterate in. _default: The initially selected value. - _mode: Behaviour when the first/last item is reached. + _mode: Behavior when the first/last item is reached. Modes.block: Stay on the selected item Modes.wrap: Wrap around to the other end Modes.exception: Raise an IndexError. From 5a73ad0c190e67daebd24128fdf3c775f05fb8e6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:11:25 +0200 Subject: [PATCH 38/50] Improve spell-checker case-sensitivity. This only checks case-insensitively for the first char, so things like "QMouseEvent" don't trigger the check. --- scripts/misc_checks.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index c6033cf3b..dac0fe017 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -71,16 +71,17 @@ def check_git(): def check_spelling(): """Check commonly misspelled words.""" # Words which I often misspell - words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully', - 'occur[^r .]', 'seperator', 'explicitely', 'resetted', - 'auxillary', 'accidentaly', 'ambigious', 'loosly', - 'initialis', 'convienence', 'similiar', 'uncommited', - 'reproducable'} + words = {'[Bb]ehaviour', '[Qq]uitted', 'Ll]ikelyhood', '[Ss]ucessfully', + '[Oo]ccur[^r .]', '[Ss]eperator', '[Ee]xplicitely', '[Rr]esetted', + '[Aa]uxillary', '[Aa]ccidentaly', '[Aa]mbigious', '[Ll]oosly', + '[Ii]nitialis', '[Cc]onvienence', '[Ss]imiliar', '[Uu]ncommited', + '[Rr]eproducable'} # Words which look better when splitted, but might need some fine tuning. - words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence', - 'normalmode', 'eventloops', 'sizehint', 'statemachine', - 'metaobject', 'logrecord', 'filetype'} + words |= {'[Kk]eystrings', '[Ww]ebelements', '[Mm]ouseevent', + '[Kk]eysequence', '[Nn]ormalmode', '[Ee]ventloops', + '[Ss]izehint', '[Ss]tatemachine', '[Mm]etaobject', + '[Ll]ogrecord', '[Ff]iletype'} seen = collections.defaultdict(list) try: @@ -91,8 +92,8 @@ def check_spelling(): continue for line in f: for w in words: - if re.search(w, line, re.I) and fn not in seen[w]: - print("Found '{}' in {}!".format(w, fn)) + if re.search(w, line) and fn not in seen[w]: + print('Found "{}" in {}!'.format(w, fn)) seen[w].append(fn) ok = False print() From a0e5a3e8eebbe33e80238fb1eaad73afeb1e1d45 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:13:19 +0200 Subject: [PATCH 39/50] tox: Update pytest-qt to 1.4.0. Upstream changelog: - Messages sent by qDebug, qWarning, qCritical are captured and displayed when tests fail, similar to pytest-catchlog. Also, tests can be configured to automatically fail if an unexpected message is generated. (See docs). - New method waitSignals: will block untill all signals given are triggered, see docs (thanks @The-Compiler for idea and complete PR). - New parameter raising to waitSignals and waitSignals: when True (defaults to False) will raise a qtbot.SignalTimeoutError exception when timeout is reached, see docs (thanks again to @The-Compiler for idea and complete PR). - pytest-qt now requires pytest version >= 2.7. Internal changes to improve memory management - QApplication.exit() is no longer called at the end of the test session and the QApplication instance is not garbage collected anymore; - QtBot no longer receives a QApplication as a parameter in the constructor, always referencing QApplication.instance() now; this avoids keeping an extra reference in the qtbot instances. - deleteLater is called on widgets added in QtBot.addWidget at the end of each test; - QApplication.processEvents() is called at the end of each test to make sure widgets are cleaned up; --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7717dd8ec..9b1e0821a 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,7 @@ deps = py==1.4.28 pytest==2.7.1 pytest-capturelog==0.7 - pytest-qt==1.3.0 + pytest-qt==1.4.0 pytest-mock==0.6.0 pytest-html==1.3.1 # We don't use {[testenv:mkvenv]commands} here because that seems to be broken From 5310c60d586be3e8abc4b39dcbd05952c816333d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:24:24 +0200 Subject: [PATCH 40/50] Remove unused import. --- qutebrowser/keyinput/basekeyparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 11b965c27..56b9cfaac 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -23,7 +23,7 @@ import re import functools import unicodedata -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject +from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from qutebrowser.config import config from qutebrowser.utils import usertypes, log, utils, objreg From 2117b2afc6ad369bdfee2eaa26d5e5dde6270414 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 01:25:10 +0200 Subject: [PATCH 41/50] Revert "Skip test which might be responsible for segfaults." This reverts commit 592ace18d4aca6928fef2c269e46ee15fd492882. --- tests/mainwindow/statusbar/test_progress.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index a0d066808..07e93e0e5 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -44,9 +44,6 @@ def progress_widget(qtbot, monkeypatch, config_stub): return widget -@pytest.mark.xfail( - reason='Blacklisted because it could cause random segfaults - see ' - 'https://github.com/hackebrot/qutebrowser/issues/22', run=False) def test_load_started(progress_widget): """Ensure the Progress widget reacts properly when the page starts loading. From aa4cb2927d054b11821715f5575be55d3d5aab15 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:29:20 +0200 Subject: [PATCH 42/50] Fix TestHideQtWarning tests for pytest 1.4.0. pytest captures the Qt logging messages, so we can't use qWarning to test. --- tests/utils/test_log.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index a09a354ee..eadaa7470 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -27,7 +27,6 @@ import itertools import sys import pytest -from PyQt5.QtCore import qWarning from qutebrowser.utils import log @@ -230,33 +229,37 @@ class TestHideQtWarning: """Tests for hide_qt_warning/QtWarningFilter.""" - def test_unfiltered(self, caplog): + @pytest.fixture() + def logger(self): + return logging.getLogger('qt-tests') + + def test_unfiltered(self, logger, caplog): """Test a message which is not filtered.""" with log.hide_qt_warning("World", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello World") + logger.warning("Hello World") assert len(caplog.records()) == 1 record = caplog.records()[0] assert record.levelname == 'WARNING' assert record.message == "Hello World" - def test_filtered_exact(self, caplog): + def test_filtered_exact(self, logger, caplog): """Test a message which is filtered (exact match).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello") + logger.warning("Hello") assert not caplog.records() - def test_filtered_start(self, caplog): + def test_filtered_start(self, logger, caplog): """Test a message which is filtered (match at line start).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning("Hello World") + logger.warning("Hello World") assert not caplog.records() - def test_filtered_whitespace(self, caplog): + def test_filtered_whitespace(self, logger, caplog): """Test a message which is filtered (match with whitespace).""" with log.hide_qt_warning("Hello", logger='qt-tests'): with caplog.atLevel(logging.WARNING, logger='qt-tests'): - qWarning(" Hello World ") + logger.warning(" Hello World ") assert not caplog.records() From e86a79740a88230826f742af9ce01a292b379302 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:30:36 +0200 Subject: [PATCH 43/50] Use raising=True for QtBot.waitSignal. --- tests/javascript/conftest.py | 4 ++-- tests/utils/test_log.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/javascript/conftest.py b/tests/javascript/conftest.py index d97b38625..442ad73cb 100644 --- a/tests/javascript/conftest.py +++ b/tests/javascript/conftest.py @@ -80,7 +80,7 @@ class JSTester: def scroll_anchor(self, name): """Scroll the main frame to the given anchor.""" page = self.webview.page() - with self._qtbot.waitSignal(page.scrollRequested): + with self._qtbot.waitSignal(page.scrollRequested, raising=True): page.mainFrame().scrollToAnchor(name) def load(self, path, **kwargs): @@ -92,7 +92,7 @@ class JSTester: **kwargs: Passed to jinja's template.render(). """ template = self._jinja_env.get_template(path) - with self._qtbot.waitSignal(self.webview.loadFinished): + with self._qtbot.waitSignal(self.webview.loadFinished, raising=True): self.webview.setHtml(template.render(**kwargs)) def run_file(self, filename): diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index eadaa7470..03575ea5b 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -213,7 +213,7 @@ class TestInitLog: @pytest.fixture def args(self): - """Fixture providing an argparse namespace.""" + """Fixture providing an argparse namespace for init_log.""" return argparse.Namespace(debug=True, loglevel=logging.DEBUG, color=True, loglines=10, logfilter="") From d887623377c098063d3feaf162bc41cac1642301 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 02:34:19 +0200 Subject: [PATCH 44/50] Make tests fail on unexpected Qt messages. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 9b1e0821a..870ee72fd 100644 --- a/tox.ini +++ b/tox.ini @@ -165,3 +165,5 @@ pep8ignore = W503 # line break before binary operator resources.py ALL mccabe-complexity = 12 +qt_log_level_fail = WARNING +qt_log_ignore = ^SpellCheck: .* From e98a05e53ddbadef07647589f13ea481c9a6c7e3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:38:58 +0200 Subject: [PATCH 45/50] Fix scroll_anchor in javascript tests. It seems scrollRequested doesn't actually get emitted. --- tests/javascript/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/javascript/conftest.py b/tests/javascript/conftest.py index 442ad73cb..85b56a577 100644 --- a/tests/javascript/conftest.py +++ b/tests/javascript/conftest.py @@ -80,8 +80,10 @@ class JSTester: def scroll_anchor(self, name): """Scroll the main frame to the given anchor.""" page = self.webview.page() - with self._qtbot.waitSignal(page.scrollRequested, raising=True): - page.mainFrame().scrollToAnchor(name) + old_pos = page.mainFrame().scrollPosition() + page.mainFrame().scrollToAnchor(name) + new_pos = page.mainFrame().scrollPosition() + assert old_pos != new_pos def load(self, path, **kwargs): """Load and display the given test data. From a82b0d007dc9bf62eced30b17f3efcaccd2899b6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:46:47 +0200 Subject: [PATCH 46/50] Enforce a Qt with SSL support. --- qutebrowser/browser/network/networkmanager.py | 155 ++++++++---------- qutebrowser/misc/earlyinit.py | 14 ++ qutebrowser/utils/version.py | 12 +- 3 files changed, 84 insertions(+), 97 deletions(-) diff --git a/qutebrowser/browser/network/networkmanager.py b/qutebrowser/browser/network/networkmanager.py index a3fc76baf..aaf5f951b 100644 --- a/qutebrowser/browser/network/networkmanager.py +++ b/qutebrowser/browser/network/networkmanager.py @@ -23,14 +23,8 @@ import collections from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication, QUrl) -from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslError - -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - SSL_AVAILABLE = False -else: - SSL_AVAILABLE = QSslSocket.supportsSsl() +from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError, + QSslSocket) from qutebrowser.config import config from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils, @@ -46,13 +40,12 @@ _proxy_auth_cache = {} def init(): """Disable insecure SSL ciphers on old Qt versions.""" - if SSL_AVAILABLE: - if not qtutils.version_check('5.3.0'): - # Disable weak SSL ciphers. - # See https://codereview.qt-project.org/#/c/75943/ - good_ciphers = [c for c in QSslSocket.supportedCiphers() - if c.usedBits() >= 128] - QSslSocket.setDefaultCiphers(good_ciphers) + if not qtutils.version_check('5.3.0'): + # Disable weak SSL ciphers. + # See https://codereview.qt-project.org/#/c/75943/ + good_ciphers = [c for c in QSslSocket.supportedCiphers() + if c.usedBits() >= 128] + QSslSocket.setDefaultCiphers(good_ciphers) class SslError(QSslError): @@ -107,10 +100,9 @@ class NetworkManager(QNetworkAccessManager): } self._set_cookiejar() self._set_cache() - if SSL_AVAILABLE: - self.sslErrors.connect(self.on_ssl_errors) - self._rejected_ssl_errors = collections.defaultdict(list) - self._accepted_ssl_errors = collections.defaultdict(list) + self.sslErrors.connect(self.on_ssl_errors) + self._rejected_ssl_errors = collections.defaultdict(list) + self._accepted_ssl_errors = collections.defaultdict(list) self.authenticationRequired.connect(self.on_authentication_required) self.proxyAuthenticationRequired.connect( self.on_proxy_authentication_required) @@ -181,76 +173,67 @@ class NetworkManager(QNetworkAccessManager): request.deleteLater() self.shutting_down.emit() - if SSL_AVAILABLE: # pragma: no mccabe - @pyqtSlot('QNetworkReply*', 'QList') - def on_ssl_errors(self, reply, errors): - """Decide if SSL errors should be ignored or not. + @pyqtSlot('QNetworkReply*', 'QList') + def on_ssl_errors(self, reply, errors): # pragma: no mccabe + """Decide if SSL errors should be ignored or not. - This slot is called on SSL/TLS errors by the self.sslErrors signal. + This slot is called on SSL/TLS errors by the self.sslErrors signal. - Args: - reply: The QNetworkReply that is encountering the errors. - errors: A list of errors. - """ - errors = [SslError(e) for e in errors] - ssl_strict = config.get('network', 'ssl-strict') - if ssl_strict == 'ask': - try: - host_tpl = urlutils.host_tuple(reply.url()) - except ValueError: - host_tpl = None - is_accepted = False - is_rejected = False - else: - is_accepted = set(errors).issubset( - self._accepted_ssl_errors[host_tpl]) - is_rejected = set(errors).issubset( - self._rejected_ssl_errors[host_tpl]) - if is_accepted: - reply.ignoreSslErrors() - elif is_rejected: - pass - else: - err_string = '\n'.join('- ' + err.errorString() for err in - errors) - answer = self._ask('SSL errors - continue?\n{}'.format( - err_string), mode=usertypes.PromptMode.yesno, - owner=reply) - if answer: - reply.ignoreSslErrors() - d = self._accepted_ssl_errors - else: - d = self._rejected_ssl_errors - if host_tpl is not None: - d[host_tpl] += errors - elif ssl_strict: + Args: + reply: The QNetworkReply that is encountering the errors. + errors: A list of errors. + """ + errors = [SslError(e) for e in errors] + ssl_strict = config.get('network', 'ssl-strict') + if ssl_strict == 'ask': + try: + host_tpl = urlutils.host_tuple(reply.url()) + except ValueError: + host_tpl = None + is_accepted = False + is_rejected = False + else: + is_accepted = set(errors).issubset( + self._accepted_ssl_errors[host_tpl]) + is_rejected = set(errors).issubset( + self._rejected_ssl_errors[host_tpl]) + if is_accepted: + reply.ignoreSslErrors() + elif is_rejected: pass else: - for err in errors: - # FIXME we might want to use warn here (non-fatal error) - # https://github.com/The-Compiler/qutebrowser/issues/114 - message.error(self._win_id, - 'SSL error: {}'.format(err.errorString())) - reply.ignoreSslErrors() + err_string = '\n'.join('- ' + err.errorString() for err in + errors) + answer = self._ask('SSL errors - continue?\n{}'.format( + err_string), mode=usertypes.PromptMode.yesno, + owner=reply) + if answer: + reply.ignoreSslErrors() + d = self._accepted_ssl_errors + else: + d = self._rejected_ssl_errors + if host_tpl is not None: + d[host_tpl] += errors + elif ssl_strict: + pass + else: + for err in errors: + # FIXME we might want to use warn here (non-fatal error) + # https://github.com/The-Compiler/qutebrowser/issues/114 + message.error(self._win_id, + 'SSL error: {}'.format(err.errorString())) + reply.ignoreSslErrors() - @pyqtSlot(QUrl) - def clear_rejected_ssl_errors(self, url): - """Clear the rejected SSL errors on a reload. + @pyqtSlot(QUrl) + def clear_rejected_ssl_errors(self, url): + """Clear the rejected SSL errors on a reload. - Args: - url: The URL to remove. - """ - try: - del self._rejected_ssl_errors[url] - except KeyError: - pass - else: - @pyqtSlot(QUrl) - def clear_rejected_ssl_errors(self, _url): - """Clear the rejected SSL errors on a reload. - - Does nothing because SSL is unavailable. - """ + Args: + url: The URL to remove. + """ + try: + del self._rejected_ssl_errors[url] + except KeyError: pass @pyqtSlot('QNetworkReply', 'QAuthenticator') @@ -334,11 +317,7 @@ class NetworkManager(QNetworkAccessManager): A QNetworkReply. """ scheme = req.url().scheme() - if scheme == 'https' and not SSL_AVAILABLE: - return networkreply.ErrorNetworkReply( - req, "SSL is not supported by the installed Qt library!", - QNetworkReply.ProtocolUnknownError, self) - elif scheme in self._scheme_handlers: + if scheme in self._scheme_handlers: return self._scheme_handlers[scheme].createRequest( op, req, outgoing_data) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 3bc214389..43806df58 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -213,6 +213,19 @@ def check_qt_version(): _die(text) +def check_ssl_support(): + """Check if SSL support is available.""" + try: + from PyQt5.QtNetwork import QSslSocket + except ImportError: + ok = False + else: + ok = QSslSocket.supportsSsl() + if not ok: + text = "Fatal error: Your Qt is built without SSL support." + _die(text) + + def check_libraries(): """Check if all needed Python libraries are installed.""" modules = { @@ -288,6 +301,7 @@ def earlyinit(args): # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. check_qt_version() + check_ssl_support() remove_inputhook() check_libraries() init_log(args) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 19dae311a..add7e4c84 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -29,10 +29,7 @@ import collections from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion from PyQt5.QtWebKit import qWebKitVersion -try: - from PyQt5.QtNetwork import QSslSocket -except ImportError: - QSslSocket = None +from PyQt5.QtNetwork import QSslSocket import qutebrowser from qutebrowser.utils import log, utils @@ -199,16 +196,13 @@ def version(): 'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()), 'PyQt: {}'.format(PYQT_VERSION_STR), ] + lines += _module_versions() - if QSslSocket is not None and QSslSocket.supportsSsl(): - ssl_version = QSslSocket.sslLibraryVersionString() - else: - ssl_version = 'unavailable' lines += [ 'Webkit: {}'.format(qWebKitVersion()), 'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')), - 'SSL: {}'.format(ssl_version), + 'SSL: {}'.format(QSslSocket.sslLibraryVersionString()), '', 'Frozen: {}'.format(hasattr(sys, 'frozen')), 'Platform: {}, {}'.format(platform.platform(), From 37750b9e30ced50d27c7f775b7c9ce22d687f6b7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 10:47:53 +0200 Subject: [PATCH 47/50] Regenerate docs. --- doc/help/settings.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index fc4802232..1a99fc9ad 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -99,7 +99,7 @@ |<>|Which tab to select when the focused tab is removed. |<>|How new tabs are positioned. |<>|How new tabs opened explicitly are positioned. -|<>|Behaviour when the last tab is closed. +|<>|Behavior when the last tab is closed. |<>|Hide the tab bar if only one tab is open. |<>|Always hide the tab bar. |<>|Whether to wrap when changing tabs. @@ -911,7 +911,7 @@ Default: +pass:[last]+ [[tabs-last-close]] === last-close -Behaviour when the last tab is closed. +Behavior when the last tab is closed. Valid values: From 83f7cf84a9af99bfad52bc4e07aff157fae70804 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 11:14:14 +0200 Subject: [PATCH 48/50] tests: Set progress widget geometry. This hopefully fixes this warning on Windows: QWindowsWindow::setGeometryDp: Unable to set geometry 113x16+192+124 on QWidgetWindow/'ProgressClassWindow'. Resulting geometry: 124x16+192+124 (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 0x0, maximum size: 16777215x16777215). --- tests/mainwindow/statusbar/test_progress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index 07e93e0e5..b8b03cd29 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -39,6 +39,7 @@ def progress_widget(qtbot, monkeypatch, config_stub): 'qutebrowser.mainwindow.statusbar.progress.style.config', config_stub) widget = Progress() qtbot.add_widget(widget) + widget.setGeometry(200, 200, 200, 200) assert not widget.isVisible() assert not widget.isTextVisible() return widget From 6b94dc5279791788d32f427b98b4e149cc14e566 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 17:20:52 +0200 Subject: [PATCH 49/50] Add continue to default next-regexes. --- qutebrowser/config/configdata.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9cf7115f7..e1d24a610 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -740,7 +740,8 @@ def data(readonly=False): ('next-regexes', SettingValue(typ.RegexList(flags=re.IGNORECASE), - r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b'), + r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,' + r'\bcontinue\b'), "A comma-separated list of regexes to use for 'next' links."), ('prev-regexes', From 525d3ee4c99fd72b42af876828d7f392eeb0683f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 7 Jun 2015 17:38:27 +0200 Subject: [PATCH 50/50] Regenerate docs --- doc/help/settings.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 1a99fc9ad..af2453378 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1434,7 +1434,7 @@ Default: +pass:[true]+ === next-regexes A comma-separated list of regexes to use for 'next' links. -Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b]+ +Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,\bcontinue\b]+ [[hints-prev-regexes]] === prev-regexes