diff --git a/.appveyor.yml b/.appveyor.yml index 837ebb1d4..f2424fc94 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,7 +7,7 @@ environment: PYTHONUNBUFFERED: 1 PYTHON: C:\Python36\python.exe matrix: - - TESTENV: py36-pyqt59 + - TESTENV: py36-pyqt510 - TESTENV: pylint install: diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e29d2678d..d7da20300 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,3 +8,5 @@ tests/unit/completion/* @rcorre tests/unit/misc/test_sql.py @rcorre qutebrowser/config/configdata.yml @mschilli87 + +qutebrowser/javascript/caret.js @artur-shaik diff --git a/.gitignore b/.gitignore index 54a0dcae6..85233aa2f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ __pycache__ /.tox /testresults.html /.cache +/.pytest_cache /.testmondata /.hypothesis /.mypy_cache diff --git a/.travis.yml b/.travis.yml index 251842d06..d5ebb1dec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,17 @@ matrix: env: TESTENV=py35-pyqt59 - os: linux env: TESTENV=py36-pyqt59-cov + - os: linux + env: TESTENV=py36-pyqt510 + # We need a newer Xvfb as a WORKAROUND for: + # https://bugreports.qt.io/browse/QTBUG-64928 + sudo: required + addons: + apt: + sources: + - sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe" + packages: + - xvfb - os: osx env: TESTENV=py36 OSX=sierra osx_image: xcode9.2 diff --git a/MANIFEST.in b/MANIFEST.in index b20c2bc77..9dace6f98 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -8,7 +8,7 @@ graft icons graft doc/img graft misc/apparmor graft misc/userscripts -recursive-include scripts *.py *.sh +recursive-include scripts *.py *.sh *.js include qutebrowser/utils/testfile include qutebrowser/git-commit-id include LICENSE doc/* README.asciidoc diff --git a/README.asciidoc b/README.asciidoc index a625f317c..aed2f61e2 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -114,7 +114,7 @@ The following software and libraries are required to run qutebrowser: * http://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] * http://pygments.org/[pygments] -* http://pyyaml.org/wiki/PyYAML[PyYAML] +* https://github.com/yaml/pyyaml[PyYAML] * http://www.attrs.org/[attrs] The following libraries are optional: diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9a312dc0c..6fb47ae9d 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,7 +21,18 @@ v1.2.0 (unreleased) Added ~~~~~ +- QtWebEngine: Caret/visual mode is now supported. - A new `qute://bindings` page, opened by `:bind`, shows all keybindings. +- `:session-load` has a new `--delete` flag which deletes the + session after loading it. +- QtWebEngine: Retrying downloads is now supported with Qt 5.10 or newer. +- QtWebEngine: Hinting and other features inside same-origin frames is now + supported. +- New `cycle-inputs.js` script in `scripts/` which can be used with `:jseval -f` + to cycle through inputs. +- New `--no-last` flag for `:tab-focus` to not focus the last tab when focusing + the currently focused one. +- New `--edit` flag for `:view-source` to open the source in an external editor. Changed ~~~~~~~ @@ -31,19 +42,57 @@ Changed - Deleting a prefix (`:`, `/` or `?`) via backspace now leaves command mode. - Angular 1 elements now get hints assigned. - `:tab-only` with pinned tabs now still closes unpinned tabs. +- GreaseMonkey `@include` and `@exclude` now support + regex matches. With QtWebEngine and Qt 5.8 and newer, Qt handles the matching, + but similar functionality was added in Qt 5.11. +- The sqlite history now uses write-ahead logging which should be + a performance and stability improvement. +- The `url.incdec_segments` option now also can take `port` as possible segment. +- QtWebEngine: `:view-source` now uses Chromium's `view-source:` scheme. +- Tabs now show their full title as tooltip. +- When an editor is spawned with `:open-editor` and `:config-edit`, the changes + are now applied as soon as the file is saved in the editor. +- When there are multiple unknown keys in a autoconfig.yml, they now all get + reported in one error. +- New `tabs.mode_on_change` setting which replaces + `tabs.persist_mode_on_change`. It can now be set to `restore` which remembers + input modes (input/passthrough) per tab. Fixed ~~~~~ -- Improved fullscreen handling with Qt 5.10. +- QtWebEngine: Improved fullscreen handling with Qt 5.10. +- QtWebEngine: Hinting and scrolling now works properly on special + `view-source:` pages. +- QtWebKit: `:view-source` now displays a valid URL. +- URLs containing ampersands and other special chars are now shown + correctly when filtering them in the completion. +- `:bookmark-add "" foo` can now be used to save the current URL with a custom + title. +- `:spawn -o` now waits until the process has finished before trying to show the + output. Previously, it incorrectly showed the previous output immediately. +- QtWebEngine: Qt download objects are now cleaned up properly when a download + is removed. +- Suspended pages now should always load the correct page when being un-suspended. -v1.1.1 (unreleased) -------------------- +Removed +~~~~~~~ + +- `QUTE_SELECTED_HTML` is now not set for userscripts anymore except when called + via hints. +- The `qutebrowser_viewsource` userscript has been removed as `:view-source + --edit` can now be used. +- The `tabs.persist_mode_on_change` setting has been removed and replaced by + `tabs.mode_on_change`. + +v1.1.1 +------ Fixed ~~~~~ - The Makefile now actually works. +- Fixed crashes with Qt 5.10 when closing a tab before it finished loading. v1.1.0 ------ @@ -1124,7 +1173,7 @@ Added - New `:fake-key` command to send a fake keypress to a website or to qutebrowser. - New `--mhtml` argument for `:download` to download a page including all - ressources as MHTML file. + resources as MHTML file. - New option `tabs -> title-alignment` to change the alignment of tab titles. Changed @@ -1324,7 +1373,7 @@ Added - New argument `--no-err-windows` to suppress all error windows. - New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom). - New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is. -- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully. +- New flag `-v`/`--verbose` for `:spawn` to print information when the process started/exited successfully. - Many new color settings (foreground setting for every background setting). - 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. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index afbb752c5..a75034b95 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -375,7 +375,7 @@ The following logging levels are available for every logger: |error |There was an issue and some kind of operation was abandoned. |warning |There was an issue but the operation can continue running. |info |General informational messages. -|debug |Verbose debugging informations. +|debug |Verbose debugging information. |======================================================================= [[commands]] diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 0d94796a4..8bbc1e5d0 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -216,6 +216,29 @@ And then re-emerging qtwebengine with: + emerge -1 qtwebengine +Unable to view DRM content (Netflix, Spotify, etc.).:: + You will need to install `widevine` and set `qt.args` to point to it. + Qt 5.9 currently only supports widevine up to Chrome version 61. ++ +On Arch, simply install `qt5-webengine-widevine` from the AUR and run: ++ +---- +:set qt.args '["ppapi-widevine-path=/usr/lib/qt/plugins/ppapi/libwidevinecdmadapter.so"]' +:restart +---- ++ +For other distributions, download the chromium tarball and widevine-cdm zip from +https://aur.archlinux.org/packages/qt5-webengine-widevine/[the AUR page], +extract `libwidevinecdmadapter.so` and `libwidevinecdm.so` files, respectively, +and move them to the `ppapi` plugin directory in your Qt library directory (create it if it does not exist). ++ +Lastly, set your `qt.args` to point to that directory and restart qutebrowser: ++ +---- +:set qt.args '["ppapi-widevine-path=/usr/lib64/qt5/plugins/ppapi/libwidevinecdmadapter.so"]' +:restart +---- + My issue is not listed.:: If you experience any segfaults or crashes, you can report the issue in https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index be7f4d557..bd84417d7 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1,6 +1,7 @@ // DO NOT EDIT THIS FILE DIRECTLY! // It is autogenerated by running: // $ python3 scripts/dev/src2asciidoc.py +// vim: readonly: = Commands @@ -175,7 +176,8 @@ Save the current page as a bookmark, or a specific url. If no url and title are provided, then save the current page as a bookmark. If a url and title have been provided, then save the given url as a bookmark with the provided title. You can view all saved bookmarks on the link:qute://bookmarks[bookmarks page]. ==== positional arguments -* +'url'+: url to save as a bookmark. If None, use url of current page. +* +'url'+: url to save as a bookmark. If not given, use url of current page. + * +'title'+: title of the new bookmark. ==== optional arguments @@ -742,7 +744,13 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link - `next`: Open a _next_ link. - `up`: Go up a level in the current URL. - `increment`: Increment the last number in the URL. + Uses the + link:settings.html#url.incdec_segments[url.incdec_segments] + config option. - `decrement`: Decrement the last number in the URL. + Uses the + link:settings.html#url.incdec_segments[url.incdec_segments] + config option. @@ -1067,7 +1075,7 @@ Delete a session. [[session-load]] === session-load -Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] 'name'+ +Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] [*--delete*] 'name'+ Load a session. @@ -1079,6 +1087,7 @@ Load a session. * +*-t*+, +*--temp*+: Don't set the current session for :session-save. * +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline). +* +*-d*+, +*--delete*+: Delete the saved session once it has loaded. [[session-save]] === session-save @@ -1203,7 +1212,7 @@ The tab index to close [[tab-focus]] === tab-focus -Syntax: +:tab-focus ['index']+ +Syntax: +:tab-focus [*--no-last*] ['index']+ Select the tab given as argument/[count]. @@ -1215,6 +1224,9 @@ If neither count nor index are given, it behaves like tab-next. If both are give last tab. +==== optional arguments +* +*-n*+, +*--no-last*+: Whether to avoid focusing last tab if already focused. + ==== count The tab index to focus, starting with 1. @@ -1314,8 +1326,13 @@ Show version information. [[view-source]] === view-source +Syntax: +:view-source [*--edit*]+ + Show the source of the current page in a new tab. +==== optional arguments +* +*-e*+, +*--edit*+: Edit the source in the editor instead of opening a tab. + [[window-only]] === window-only Close all windows except for the current one. diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index a9b7b6ddf..bb43e1dfe 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -254,7 +254,7 @@ Getting the config directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you need to get the qutebrowser config directory, you can do so by reading -`config.configdir`. Similarily, you can get the qutebrowser data directory via +`config.configdir`. Similarly, you can get the qutebrowser data directory via `config.datadir`. This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path` @@ -366,6 +366,8 @@ You can use something like this to read colors from an `~/.Xresources` file: [source,python] ---- +import subprocess + def read_xresources(prefix): props = {} x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE) @@ -376,7 +378,7 @@ def read_xresources(prefix): return props xresources = read_xresources('*') -c.colors.statusbar.normal.bg = xresources['*background'] +c.colors.statusbar.normal.bg = xresources['*.background'] ---- Avoiding flake8 errors diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 53af8399d..53dce9e8e 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1,6 +1,7 @@ // DO NOT EDIT THIS FILE DIRECTLY! // It is autogenerated by running: // $ python3 scripts/dev/src2asciidoc.py +// vim: readonly: = Setting reference @@ -237,11 +238,11 @@ |<>|Padding (in pixels) for tab indicators. |<>|Width (in pixels) of the progress indicator (0 to disable). |<>|How to behave when the last tab is closed. +|<>|When switching tabs, what input mode is applied. |<>|Switch between tabs using the mouse wheel. |<>|Position of new tabs opened from another tab. |<>|Position of new tabs which aren't opened from another tab. |<>|Padding (in pixels) around text for tabs. -|<>|Stay in insert/passthrough mode when switching tabs. |<>|Shrink pinned tabs down to their contents. |<>|Position of the tab bar. |<>|Which tab to select when the focused tab is removed. @@ -1579,7 +1580,7 @@ Default: +pass:[true]+ [[content.headers.referer]] === content.headers.referer When to send the Referer header. -The Referer header tells websites from which website you were coming from when visting them. +The Referer header tells websites from which website you were coming from when visiting them. Type: <> @@ -2773,6 +2774,20 @@ Valid values: Default: +pass:[ignore]+ +[[tabs.mode_on_change]] +=== tabs.mode_on_change +When switching tabs, what input mode is applied. + +Type: <> + +Valid values: + + * +persist+: Retain the current mode. + * +restore+: Restore previously saved mode. + * +normal+: Always revert to normal mode. + +Default: +pass:[normal]+ + [[tabs.mousewheel_switching]] === tabs.mousewheel_switching Switch between tabs using the mouse wheel. @@ -2824,14 +2839,6 @@ Default: - +pass:[right]+: +pass:[5]+ - +pass:[top]+: +pass:[0]+ -[[tabs.persist_mode_on_change]] -=== tabs.persist_mode_on_change -Stay in insert/passthrough mode when switching tabs. - -Type: <> - -Default: +pass:[false]+ - [[tabs.pinned.shrink]] === tabs.pinned.shrink Shrink pinned tabs down to their contents. @@ -2993,6 +3000,7 @@ Type: <> Valid values: * +host+ + * +port+ * +path+ * +query+ * +anchor+ diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc index 5505eca93..00c89e884 100644 --- a/doc/stacktrace.asciidoc +++ b/doc/stacktrace.asciidoc @@ -37,7 +37,7 @@ is available in the repositories: Archlinux ^^^^^^^^^ -For Archlinux, no debug informations are provided. You can either compile Qt +For Archlinux, no debug information is provided. You can either compile Qt yourself (which will take a few hours even on a modern machine) or use debugging symbols compiled/packaged by me (x86_64 only). diff --git a/doc/userscripts.asciidoc b/doc/userscripts.asciidoc index 7f43e969b..c2f35b026 100644 --- a/doc/userscripts.asciidoc +++ b/doc/userscripts.asciidoc @@ -45,8 +45,6 @@ In `command` mode: - `QUTE_URL`: The current URL. - `QUTE_TITLE`: The title of the current page. - `QUTE_SELECTED_TEXT`: The text currently selected on the page. -- `QUTE_SELECTED_HTML` The HTML currently selected on the page (not supported - with QtWebEngine). In `hints` mode: diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index df7a12ed6..df27fc428 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -certifi==2017.11.5 +certifi==2018.1.18 chardet==3.0.4 -codecov==2.0.13 -coverage==4.4.2 +codecov==2.0.15 +coverage==4.5 idna==2.6 requests==2.18.4 urllib3==1.22 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 0ae43d663..5a43e66d1 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==17.4.0 flake8==3.5.0 -flake8-bugbear==17.12.0 +flake8-bugbear==18.2.0 flake8-builtins==1.0.post0 flake8-comprehensions==1.4.1 flake8-copyright==0.2.0 diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 54cacc3c3..bd9654b1b 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==38.4.0 +setuptools==38.5.0 six==1.11.0 wheel==0.30.0 diff --git a/misc/requirements/requirements-pylint-master.txt b/misc/requirements/requirements-pylint-master.txt index ce588c136..d23d2ea57 100644 --- a/misc/requirements/requirements-pylint-master.txt +++ b/misc/requirements/requirements-pylint-master.txt @@ -1,11 +1,11 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -e git+https://github.com/PyCQA/astroid.git#egg=astroid -certifi==2017.11.5 +certifi==2018.1.18 chardet==3.0.4 github3.py==0.9.6 idna==2.6 -isort==4.2.15 +isort==4.3.2 lazy-object-proxy==1.3.1 mccabe==0.6.1 -e git+https://github.com/PyCQA/pylint.git#egg=pylint diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 6267fc2b0..e86be34e2 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,14 +1,14 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==1.6.0 -certifi==2017.11.5 +astroid==1.6.1 +certifi==2018.1.18 chardet==3.0.4 github3.py==0.9.6 idna==2.6 -isort==4.2.15 +isort==4.3.2 lazy-object-proxy==1.3.1 mccabe==0.6.1 -pylint==1.8.1 +pylint==1.8.2 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 5a08f2f73..99ba1b7cc 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.9.2 -sip==4.19.6 +PyQt5==5.10 +sip==4.19.7 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 574dbdb28..eebd2945b 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,24 +5,25 @@ beautifulsoup4==4.6.0 cheroot==6.0.0 click==6.7 # colorama==0.3.9 -coverage==4.4.2 +coverage==4.5 EasyProcess==0.2.3 fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.44.16 +hypothesis==3.44.25 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 # MarkupSafe==1.0 +more-itertools==4.1.0 parse==1.8.2 parse-type==0.4.2 pluggy==0.6.0 py==1.5.2 py-cpuinfo==3.3.0 -pytest==3.3.1 # rq.filter: != 3.3.2 -pytest-bdd==2.19.0 +pytest==3.4.0 +pytest-bdd==2.20.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 pytest-faulthandler==1.3.1 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 174eeb7df..121689980 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -4,7 +4,7 @@ coverage Flask hunter hypothesis -pytest==3.3.1 +pytest pytest-bdd pytest-benchmark pytest-cov @@ -19,4 +19,3 @@ pytest-xvfb vulture #@ ignore: Jinja2, MarkupSafe, colorama -#@ filter: pytest != 3.3.2 diff --git a/misc/userscripts/qutebrowser_viewsource b/misc/userscripts/qutebrowser_viewsource deleted file mode 100755 index a8ad71de3..000000000 --- a/misc/userscripts/qutebrowser_viewsource +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2015 Zach-Button -# Copyright 2016-2017 Florian Bruhin (The Compiler) -# -# 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 . - -# -# This script fetches the unprocessed HTML source for a page and opens it in vim. -# :bind gf spawn --userscript qutebrowser_viewsource -# -# Caveat: Does not use authentication of any kind. Add it in if you want it to. -# - -path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html) - -curl "$QUTE_URL" > "$path" -urxvt -e vim "$path" - -rm "$path" diff --git a/pytest.ini b/pytest.ini index 4fefa0f77..89571aebc 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,5 @@ [pytest] +log_level = NOTSET addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median testpaths = tests markers = @@ -25,6 +26,7 @@ markers = this: Used to mark tests during development no_invalid_lines: Don't fail on unparseable lines in end2end tests issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478 + issue3572: Tests which are broken with QtWebEngine and Qt 5.10, https://github.com/qutebrowser/qutebrowser/issues/3572 fake_os: Fake utils.is_* to a fake operating system unicode_locale: Tests which need an unicode locale to work qt_log_level_fail = WARNING diff --git a/qutebrowser.py b/qutebrowser.py index a16bd9ac7..8dd81b01a 100755 --- a/qutebrowser.py +++ b/qutebrowser.py @@ -2,7 +2,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2015 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 1b1ec4c40..72afa0d5d 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version_info__ = (1, 1, 0) +__version_info__ = (1, 1, 1) __version__ = '.'.join(str(e) for e in __version_info__) __description__ = "A keyboard-driven, vim-like browser based on PyQt5." diff --git a/qutebrowser/__main__.py b/qutebrowser/__main__.py index 506039890..533cf6e67 100644 --- a/qutebrowser/__main__.py +++ b/qutebrowser/__main__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/app.py b/qutebrowser/app.py index e3d06391d..c87201931 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/__init__.py b/qutebrowser/browser/__init__.py index c5d5e6c92..b565801d3 100644 --- a/qutebrowser/browser/__init__.py +++ b/qutebrowser/browser/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 623d15717..fa35684b9 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 677ed7a97..ee4242f7a 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -94,21 +94,21 @@ class TabData: keep_icon: Whether the (e.g. cloned) icon should not be cleared on page load. inspector: The QWebInspector used for this webview. - viewing_source: Set if we're currently showing a source view. override_target: Override for open_target for fake clicks (like hints). Only used for QtWebKit. pinned: Flag to pin the tab. fullscreen: Whether the tab has a video shown fullscreen currently. netrc_used: Whether netrc authentication was performed. + input_mode: current input mode for the tab. """ keep_icon = attr.ib(False) - viewing_source = attr.ib(False) inspector = attr.ib(None) override_target = attr.ib(None) pinned = attr.ib(False) fullscreen = attr.ib(False) netrc_used = attr.ib(False) + input_mode = attr.ib(usertypes.KeyMode.normal) class AbstractAction: @@ -123,8 +123,9 @@ class AbstractAction: action_class = None action_base = None - def __init__(self): + def __init__(self, tab): self._widget = None + self._tab = tab def exit_fullscreen(self): """Exit the fullscreen mode.""" @@ -141,6 +142,10 @@ class AbstractAction: raise WebTabError("{} is not a valid web action!".format(name)) self._widget.triggerPageAction(member) + def show_source(self): + """Show the source of the current page in a new tab.""" + raise NotImplementedError + class AbstractPrinting: @@ -247,10 +252,10 @@ class AbstractZoom(QObject): _default_zoom_changed: Whether the zoom was changed from the default. """ - def __init__(self, win_id, parent=None): + def __init__(self, tab, parent=None): super().__init__(parent) + self._tab = tab self._widget = None - self._win_id = win_id self._default_zoom_changed = False self._init_neighborlist() config.instance.changed.connect(self._on_config_changed) @@ -326,10 +331,9 @@ class AbstractCaret(QObject): """Attribute of AbstractTab for caret browsing.""" - def __init__(self, win_id, tab, mode_manager, parent=None): + def __init__(self, tab, mode_manager, parent=None): super().__init__(parent) self._tab = tab - self._win_id = win_id self._widget = None self.selection_enabled = False mode_manager.entered.connect(self._on_mode_entered) @@ -392,10 +396,7 @@ class AbstractCaret(QObject): def drop_selection(self): raise NotImplementedError - def has_selection(self): - raise NotImplementedError - - def selection(self, html=False): + def selection(self, callback): raise NotImplementedError def follow_selected(self, *, tab=False): @@ -641,16 +642,6 @@ class AbstractTab(QWidget): tab_registry[self.tab_id] = self objreg.register('tab', self, registry=self.registry) - # self.history = AbstractHistory(self) - # self.scroller = AbstractScroller(self, parent=self) - # self.caret = AbstractCaret(win_id=win_id, tab=self, - # mode_manager=mode_manager, parent=self) - # self.zoom = AbstractZoom(win_id=win_id) - # self.search = AbstractSearch(parent=self) - # self.printing = AbstractPrinting() - # self.elements = AbstractElements(self) - # self.action = AbstractAction() - self.data = TabData() self._layout = miscwidgets.WrapperLayout(self) self._widget = None @@ -725,7 +716,6 @@ class AbstractTab(QWidget): def _on_load_started(self): self._progress = 0 self._has_ssl_errors = False - self.data.viewing_source = False self._set_load_status(usertypes.LoadStatus.loading) self.load_started.emit() @@ -751,6 +741,10 @@ class AbstractTab(QWidget): @pyqtSlot(bool) def _on_load_finished(self, ok): + if sip.isdeleted(self._widget): + # https://github.com/qutebrowser/qutebrowser/issues/3498 + return + sess_manager = objreg.get('session-manager') sess_manager.save_autosave() @@ -813,7 +807,7 @@ class AbstractTab(QWidget): raise NotImplementedError def dump_async(self, callback, *, plain=False): - """Dump the current page to a file ascync. + """Dump the current page's html asynchronously. The given callback will be called with the result when dumping is complete. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 2a43a1726..a59cdac25 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -26,12 +26,9 @@ import functools import typing from PyQt5.QtWidgets import QApplication, QTabBar, QDialog -from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery +from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery from PyQt5.QtGui import QKeyEvent from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog -import pygments -import pygments.lexers -import pygments.formatters from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.config import config, configdata @@ -638,7 +635,13 @@ class CommandDispatcher: - `next`: Open a _next_ link. - `up`: Go up a level in the current URL. - `increment`: Increment the last number in the URL. + Uses the + link:settings.html#url.incdec_segments[url.incdec_segments] + config option. - `decrement`: Decrement the last number in the URL. + Uses the + link:settings.html#url.incdec_segments[url.incdec_segments] + config option. tab: Open in a new tab. bg: Open in a background tab. @@ -849,14 +852,21 @@ class CommandDispatcher: s = self._yank_url(what) what = 'URL' # For printing elif what == 'selection': + def _selection_callback(s): + if not s: + message.info("Nothing to yank") + return + self._yank_to_target(s, sel, what, keep) + caret = self._current_widget().caret - s = caret.selection() - if not caret.has_selection() or not s: - message.info("Nothing to yank") - return + caret.selection(callback=_selection_callback) + return else: # pragma: no cover raise ValueError("Invalid value {!r} for `what'.".format(what)) + self._yank_to_target(s, sel, what, keep) + + def _yank_to_target(self, s, sel, what, keep): if sel and utils.supports_selection(): target = "primary selection" else: @@ -1099,7 +1109,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('index', choices=['last']) @cmdutils.argument('count', count=True) - def tab_focus(self, index: typing.Union[str, int] = None, count=None): + def tab_focus(self, index: typing.Union[str, int] = None, + count=None, no_last=False): """Select the tab given as argument/[count]. If neither count nor index are given, it behaves like tab-next. @@ -1111,13 +1122,14 @@ class CommandDispatcher: Negative indices count from the end, such that -1 is the last tab. count: The tab index to focus, starting with 1. + no_last: Whether to avoid focusing last tab if already focused. """ index = count if count is not None else index if index == 'last': self._tab_focus_last() return - elif index == self._current_index() + 1: + elif not no_last and index == self._current_index() + 1: self._tab_focus_last(show_error=False) return elif index is None: @@ -1207,9 +1219,29 @@ class CommandDispatcher: log.procs.debug("Executing {} with args {}, userscript={}".format( cmd, args, userscript)) + + @pyqtSlot() + def _on_proc_finished(): + if output: + tb = objreg.get('tabbed-browser', scope='window', + window='last-focused') + tb.openurl(QUrl('qute://spawn-output'), newtab=True) + if userscript: + def _selection_callback(s): + try: + runner = self._run_userscript(s, cmd, args, verbose) + runner.finished.connect(_on_proc_finished) + except cmdexc.CommandError as e: + message.error(str(e)) + # ~ expansion is handled by the userscript module. - self._run_userscript(cmd, *args, verbose=verbose) + # dirty hack for async call because of: + # https://bugreports.qt.io/browse/QTBUG-53134 + # until it fixed or blocked async call implemented: + # https://github.com/qutebrowser/qutebrowser/issues/3327 + caret = self._current_widget().caret + caret.selection(callback=_selection_callback) else: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, @@ -1218,18 +1250,14 @@ class CommandDispatcher: proc.start_detached(cmd, args) else: proc.start(cmd, args) - - if output: - tabbed_browser = objreg.get('tabbed-browser', scope='window', - window='last-focused') - tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) + proc.finished.connect(_on_proc_finished) @cmdutils.register(instance='command-dispatcher', scope='window') def home(self): """Open main startpage in current tab.""" self.openurl(config.val.url.start_pages[0]) - def _run_userscript(self, cmd, *args, verbose=False): + def _run_userscript(self, selection, cmd, args, verbose): """Run a userscript given as argument. Args: @@ -1239,21 +1267,15 @@ class CommandDispatcher: """ env = { 'QUTE_MODE': 'command', + 'QUTE_SELECTED_TEXT': selection, } idx = self._current_index() if idx != -1: env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx) - tab = self._tabbed_browser.currentWidget() - if tab is not None and tab.caret.has_selection(): - env['QUTE_SELECTED_TEXT'] = tab.caret.selection() - try: - env['QUTE_SELECTED_HTML'] = tab.caret.selection(html=True) - except browsertab.UnsupportedOperationError: - pass - # FIXME:qtwebengine: If tab is None, run_async will fail! + tab = self._tabbed_browser.currentWidget() try: url = self._tabbed_browser.current_url() @@ -1263,10 +1285,11 @@ class CommandDispatcher: env['QUTE_URL'] = url.toString(QUrl.FullyEncoded) try: - userscripts.run_async(tab, cmd, *args, win_id=self._win_id, - env=env, verbose=verbose) + runner = userscripts.run_async( + tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose) except userscripts.Error as e: raise cmdexc.CommandError(e) + return runner @cmdutils.register(instance='command-dispatcher', scope='window') def quickmark_save(self): @@ -1328,7 +1351,8 @@ class CommandDispatcher: link:qute://bookmarks[bookmarks page]. Args: - url: url to save as a bookmark. If None, use url of current page. + url: url to save as a bookmark. If not given, use url of current + page. title: title of the new bookmark. toggle: remove the bookmark instead of raising an error if it already exists. @@ -1337,7 +1361,7 @@ class CommandDispatcher: raise cmdexc.CommandError('Title must be provided if url has ' 'been provided') bookmark_manager = objreg.get('bookmark-manager') - if url is None: + if not url: url = self._current_url() else: try: @@ -1483,34 +1507,26 @@ class CommandDispatcher: ) @cmdutils.register(instance='command-dispatcher', scope='window') - def view_source(self): - """Show the source of the current page in a new tab.""" - tab = self._current_widget() - if tab.data.viewing_source: - raise cmdexc.CommandError("Already viewing source!") + def view_source(self, edit=False): + """Show the source of the current page in a new tab. + Args: + edit: Edit the source in the editor instead of opening a tab. + """ + tab = self._current_widget() try: current_url = self._current_url() except cmdexc.CommandError as e: message.error(str(e)) return + if current_url.scheme() == 'view-source': + raise cmdexc.CommandError("Already viewing source!") - def show_source_cb(source): - """Show source as soon as it's ready.""" - # WORKAROUND for https://github.com/PyCQA/pylint/issues/491 - # pylint: disable=no-member - lexer = pygments.lexers.HtmlLexer() - formatter = pygments.formatters.HtmlFormatter( - full=True, linenos='table', - title='Source for {}'.format(current_url.toDisplayString())) - # pylint: enable=no-member - highlighted = pygments.highlight(source, lexer, formatter) - - new_tab = self._tabbed_browser.tabopen() - new_tab.set_html(highlighted) - new_tab.data.viewing_source = True - - tab.dump_async(show_source_cb) + if edit: + ed = editor.ExternalEditor(self._tabbed_browser) + tab.dump_async(ed.edit) + else: + tab.action.show_source() @cmdutils.register(instance='command-dispatcher', scope='window', debug=True) @@ -1616,9 +1632,11 @@ class CommandDispatcher: caret_position = elem.caret_position() - ed = editor.ExternalEditor(self._tabbed_browser) - ed.editing_finished.connect(functools.partial( - self.on_editing_finished, elem)) + ed = editor.ExternalEditor(watch=True, parent=self._tabbed_browser) + ed.file_updated.connect(functools.partial( + self.on_file_updated, elem)) + ed.editing_finished.connect(lambda: mainwindow.raise_window( + objreg.last_focused_window(), alert=False)) ed.edit(text, caret_position) @cmdutils.register(instance='command-dispatcher', scope='window') @@ -1631,10 +1649,10 @@ class CommandDispatcher: tab = self._current_widget() tab.elements.find_focused(self._open_editor_cb) - def on_editing_finished(self, elem, text): + def on_file_updated(self, elem, text): """Write the editor text into the form field and clean up tempfile. - Callback for GUIProcess when the editor was closed. + Callback for GUIProcess when the edited text was updated. Args: elem: The WebElementWrapper which was modified. @@ -1647,8 +1665,6 @@ class CommandDispatcher: except webelem.Error as e: raise cmdexc.CommandError(str(e)) - mainwindow.raise_window(objreg.last_focused_window(), alert=False) - @cmdutils.register(instance='command-dispatcher', maxsplit=0, scope='window') def insert_text(self, text): @@ -2141,7 +2157,7 @@ class CommandDispatcher: ed = editor.ExternalEditor(self._tabbed_browser) # Passthrough for openurl args (e.g. -t, -b, -w) - ed.editing_finished.connect(functools.partial( + ed.file_updated.connect(functools.partial( self._open_if_changed, old_url=old_url, bg=bg, tab=tab, window=window, private=private, related=related)) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index c064d700e..12c96db7e 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index ac44d75d9..80da117d2 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index 9a82d6a93..fb064f6c1 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -23,7 +23,6 @@ import re import os import json import fnmatch -import functools import glob import attr @@ -135,7 +134,7 @@ class GreasemonkeyManager(QObject): Signals: scripts_reloaded: Emitted when scripts are reloaded from disk. Any cached or already-injected scripts should be - considered obselete. + considered obsolete. """ scripts_reloaded = pyqtSignal() @@ -178,10 +177,10 @@ class GreasemonkeyManager(QObject): elif script.run_at == 'document-idle': self._run_idle.append(script) else: - log.greasemonkey.warning("Script {} has invalid run-at " - "defined, defaulting to " - "document-end" - .format(script_path)) + if script.run_at: + log.greasemonkey.warning( + "Script {} has invalid run-at defined, " + "defaulting to document-end".format(script_path)) # Default as per # https://wiki.greasespot.net/Metadata_Block#.40run-at self._run_end.append(script) @@ -196,11 +195,23 @@ class GreasemonkeyManager(QObject): """ if url.scheme() not in self.greaseable_schemes: return MatchingScripts(url, [], [], []) - match = functools.partial(fnmatch.fnmatch, - url.toString(QUrl.FullyEncoded)) + + string_url = url.toString(QUrl.FullyEncoded) + + def _match(pattern): + # For include and exclude rules if they start and end with '/' they + # should be treated as a (ecma syntax) regular expression. + if pattern.startswith('/') and pattern.endswith('/'): + matches = re.search(pattern[1:-1], string_url, flags=re.I) + return matches is not None + + # Otherwise they are glob expressions. + return fnmatch.fnmatch(string_url, pattern) + tester = (lambda script: - any(match(pat) for pat in script.includes) and - not any(match(pat) for pat in script.excludes)) + any(_match(pat) for pat in script.includes) and + not any(_match(pat) for pat in script.excludes)) + return MatchingScripts( url, [script for script in self._run_start if tester(script)], diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 14cc2b574..3e0eddbeb 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 04bc1be15..85922f9e8 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -172,7 +172,7 @@ class WebHistory(sql.SqlTable): @pyqtSlot(QUrl, QUrl, str) def add_from_tab(self, url, requested_url, title): """Add a new history entry as slot, called from a BrowserTab.""" - if any(url.scheme() == 'data' or + if any(url.scheme() in ('data', 'view-source') or (url.scheme(), url.host()) == ('qute', 'back') for url in (url, requested_url)): return diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 9c583f4b3..608404eeb 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py index d08f191a8..b0053cbf1 100644 --- a/qutebrowser/browser/mouse.py +++ b/qutebrowser/browser/mouse.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index 8b406368b..257ce6fe0 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index a3f0813c8..95ff99390 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 2821a840d..96be78742 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -61,6 +61,9 @@ class ProxyFactory(QNetworkProxyFactory): """ proxy = config.val.content.proxy if proxy is configtypes.SYSTEM_PROXY: + # On Linux, use "export http_proxy=socks5://host:port" to manually + # set system proxy. + # ref. http://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery proxies = QNetworkProxyFactory.systemProxyForQuery(query) elif isinstance(proxy, pac.PACFetcher): proxies = proxy.resolve(query) diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index cd5bcac0a..5ce3d866e 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015 Daniel Schadt -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 378bc72b5..7a8c5aa64 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 08122958a..320e7873a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 1e2398783..5be2acd55 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py index 90b85c586..663aa67e7 100644 --- a/qutebrowser/browser/signalfilter.py +++ b/qutebrowser/browser/signalfilter.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 5e2c60dfb..2d357a8e8 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) -# Copyright 2015-2017 Antoni Boucher +# Copyright 2014-2018 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Antoni Boucher # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 30355ad1a..122e7d031 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/__init__.py b/qutebrowser/browser/webengine/__init__.py index 60d140540..2649645d3 100644 --- a/qutebrowser/browser/webengine/__init__.py +++ b/qutebrowser/browser/webengine/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/certificateerror.py b/qutebrowser/browser/webengine/certificateerror.py index c97e51b81..47953d4cc 100644 --- a/qutebrowser/browser/webengine/certificateerror.py +++ b/qutebrowser/browser/webengine/certificateerror.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index e34718791..480e8ee85 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/spell.py b/qutebrowser/browser/webengine/spell.py index 9166180d4..beebe4da7 100644 --- a/qutebrowser/browser/webengine/spell.py +++ b/qutebrowser/browser/webengine/spell.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Michal Siedlaczek +# Copyright 2017-2018 Michal Siedlaczek # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/tabhistory.py b/qutebrowser/browser/webengine/tabhistory.py index 5db6faeb1..81f0a3afd 100644 --- a/qutebrowser/browser/webengine/tabhistory.py +++ b/qutebrowser/browser/webengine/tabhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py index d467724d5..f5b6529d7 100644 --- a/qutebrowser/browser/webengine/webenginedownloads.py +++ b/qutebrowser/browser/webengine/webenginedownloads.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -45,6 +45,10 @@ class DownloadItem(downloads.AbstractDownloadItem): qt_item.downloadProgress.connect(self.stats.on_download_progress) qt_item.stateChanged.connect(self._on_state_changed) + # Ensure wrapped qt_item is deleted manually when the wrapper object + # is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373 + self.destroyed.connect(self._qt_item.deleteLater) + def _is_page_download(self): """Check if this item is a page (i.e. mhtml) download.""" return (self._qt_item.savePageFormat() != @@ -96,9 +100,15 @@ class DownloadItem(downloads.AbstractDownloadItem): self._qt_item.cancel() def retry(self): - # https://bugreports.qt.io/browse/QTBUG-56840 - raise downloads.UnsupportedOperationError( - "Retrying downloads is unsupported with QtWebEngine") + state = self._qt_item.state() + assert state == QWebEngineDownloadItem.DownloadInterrupted, state + + try: + self._qt_item.resume() + except AttributeError: + raise downloads.UnsupportedOperationError( + "Retrying downloads is unsupported with QtWebEngine on " + "Qt/PyQt < 5.10") def _get_open_filename(self): return self._filename diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index d6d74ebe4..127b71cf0 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webengineinspector.py b/qutebrowser/browser/webengine/webengineinspector.py index 8fa8bcb2d..b2a6ebb1e 100644 --- a/qutebrowser/browser/webengine/webengineinspector.py +++ b/qutebrowser/browser/webengine/webengineinspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 2e9aedd3e..ac583a671 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index f8b54e065..607499401 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 0dd6d565b..122c4e51e 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -21,6 +21,7 @@ import math import functools +import sys import html as html_utils import sip @@ -98,6 +99,19 @@ class WebEngineAction(browsertab.AbstractAction): """Save the current page.""" self._widget.triggerPageAction(QWebEnginePage.SavePage) + def show_source(self): + try: + self._widget.triggerPageAction(QWebEnginePage.ViewSource) + except AttributeError: + # Qt < 5.8 + tb = objreg.get('tabbed-browser', scope='window', + window=self._tab.win_id) + urlstr = self._tab.url().toString(QUrl.RemoveUserInfo) + # The original URL becomes the path of a view-source: URL + # (without a host), but query/fragment should stay. + url = QUrl('view-source:' + urlstr) + tb.tabopen(url, background=False, related=True) + class WebEnginePrinting(browsertab.AbstractPrinting): @@ -201,70 +215,87 @@ class WebEngineCaret(browsertab.AbstractCaret): @pyqtSlot(usertypes.KeyMode) def _on_mode_entered(self, mode): - pass + if mode != usertypes.KeyMode.caret: + return + + self._tab.run_js_async( + javascript.assemble('caret', 'setPlatform', sys.platform)) + self._js_call('setInitialCursor') @pyqtSlot(usertypes.KeyMode) def _on_mode_left(self): - pass + self.drop_selection() + self._js_call('disableCaret') def move_to_next_line(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveDown') def move_to_prev_line(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveUp') def move_to_next_char(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveRight') def move_to_prev_char(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveLeft') def move_to_end_of_word(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToEndOfWord') def move_to_next_word(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToNextWord') def move_to_prev_word(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToPreviousWord') def move_to_start_of_line(self): - log.stub() + self._js_call('moveToStartOfLine') def move_to_end_of_line(self): - log.stub() + self._js_call('moveToEndOfLine') def move_to_start_of_next_block(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToStartOfNextBlock') def move_to_start_of_prev_block(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToStartOfPrevBlock') def move_to_end_of_next_block(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToEndOfNextBlock') def move_to_end_of_prev_block(self, count=1): - log.stub() + for _ in range(count): + self._js_call('moveToEndOfPrevBlock') def move_to_start_of_document(self): - log.stub() + self._js_call('moveToStartOfDocument') def move_to_end_of_document(self): - log.stub() + self._js_call('moveToEndOfDocument') def toggle_selection(self): - log.stub() + self._js_call('toggleSelection') def drop_selection(self): - log.stub() + self._js_call('dropSelection') - def has_selection(self): - return self._widget.hasSelection() - - def selection(self, html=False): - if html: - raise browsertab.UnsupportedOperationError - return self._widget.selectedText() + def selection(self, callback): + # Not using selectedText() as WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-53134 + # Even on Qt 5.10 selectedText() seems to work poorly, see + # https://github.com/qutebrowser/qutebrowser/issues/3523 + self._tab.run_js_async(javascript.assemble('caret', 'getSelection'), + callback) def _follow_selected_cb(self, js_elem, tab=False): """Callback for javascript which clicks the selected element. @@ -308,6 +339,10 @@ class WebEngineCaret(browsertab.AbstractCaret): self._tab.run_js_async(js_code, lambda jsret: self._follow_selected_cb(jsret, tab)) + def _js_call(self, command): + self._tab.run_js_async( + javascript.assemble('caret', command)) + class WebEngineScroller(browsertab.AbstractScroller): @@ -557,13 +592,13 @@ class WebEngineTab(browsertab.AbstractTab): private=private) self.history = WebEngineHistory(self) self.scroller = WebEngineScroller(self, parent=self) - self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager, + self.caret = WebEngineCaret(mode_manager=mode_manager, tab=self, parent=self) - self.zoom = WebEngineZoom(win_id=win_id, parent=self) + self.zoom = WebEngineZoom(tab=self, parent=self) self.search = WebEngineSearch(parent=self) self.printing = WebEnginePrinting() - self.elements = WebEngineElements(self) - self.action = WebEngineAction() + self.elements = WebEngineElements(tab=self) + self.action = WebEngineAction(tab=self) self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebEngine @@ -577,9 +612,12 @@ class WebEngineTab(browsertab.AbstractTab): 'window._qutebrowser = window._qutebrowser || {};', utils.read_file('javascript/scroll.js'), utils.read_file('javascript/webelem.js'), + utils.read_file('javascript/caret.js'), ]) script = QWebEngineScript() - script.setInjectionPoint(QWebEngineScript.DocumentCreation) + # We can't use DocumentCreation here as WORKAROUND for + # https://bugreports.qt.io/browse/QTBUG-66011 + script.setInjectionPoint(QWebEngineScript.DocumentReady) script.setSourceCode(js_code) page = self._widget.page() @@ -597,6 +635,9 @@ class WebEngineTab(browsertab.AbstractTab): @pyqtSlot() def _restore_zoom(self): + if sip.isdeleted(self._widget): + # https://github.com/qutebrowser/qutebrowser/issues/3498 + return if self._saved_zoom is None: return self.zoom.set_factor(self._saved_zoom) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index b313fc36c..1b3c15f9e 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/__init__.py b/qutebrowser/browser/webkit/__init__.py index 93b53cdba..5100b7a53 100644 --- a/qutebrowser/browser/webkit/__init__.py +++ b/qutebrowser/browser/webkit/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/cache.py b/qutebrowser/browser/webkit/cache.py index 998545509..163612ce9 100644 --- a/qutebrowser/browser/webkit/cache.py +++ b/qutebrowser/browser/webkit/cache.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/certificateerror.py b/qutebrowser/browser/webkit/certificateerror.py index 23353f268..cad17fba5 100644 --- a/qutebrowser/browser/webkit/certificateerror.py +++ b/qutebrowser/browser/webkit/certificateerror.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/cookies.py b/qutebrowser/browser/webkit/cookies.py index f08e2e546..01b6842a0 100644 --- a/qutebrowser/browser/webkit/cookies.py +++ b/qutebrowser/browser/webkit/cookies.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/http.py b/qutebrowser/browser/webkit/http.py index 8997f4b0c..73e620015 100644 --- a/qutebrowser/browser/webkit/http.py +++ b/qutebrowser/browser/webkit/http.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 67c8a5b7a..ca807f705 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Daniel Schadt +# Copyright 2015-2018 Daniel Schadt # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/filescheme.py b/qutebrowser/browser/webkit/network/filescheme.py index a971e3257..d8c2c36b8 100644 --- a/qutebrowser/browser/webkit/network/filescheme.py +++ b/qutebrowser/browser/webkit/network/filescheme.py @@ -1,7 +1,7 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) -# Copyright 2015-2017 Antoni Boucher (antoyo) +# Copyright 2014-2018 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Antoni Boucher (antoyo) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 2f883b6be..50fb53b35 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/network/networkreply.py b/qutebrowser/browser/webkit/network/networkreply.py index 22263c96b..dc6bed5ed 100644 --- a/qutebrowser/browser/webkit/network/networkreply.py +++ b/qutebrowser/browser/webkit/network/networkreply.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # Based on the Eric5 helpviewer, # Copyright (c) 2009 - 2014 Detlev Offenbach diff --git a/qutebrowser/browser/webkit/network/schemehandler.py b/qutebrowser/browser/webkit/network/schemehandler.py index c6337efa3..c50194172 100644 --- a/qutebrowser/browser/webkit/network/schemehandler.py +++ b/qutebrowser/browser/webkit/network/schemehandler.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # Based on the Eric5 helpviewer, # Copyright (c) 2009 - 2014 Detlev Offenbach diff --git a/qutebrowser/browser/webkit/network/webkitqutescheme.py b/qutebrowser/browser/webkit/network/webkitqutescheme.py index 5413a4a8d..2ed8d5aa1 100644 --- a/qutebrowser/browser/webkit/network/webkitqutescheme.py +++ b/qutebrowser/browser/webkit/network/webkitqutescheme.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/rfc6266.py b/qutebrowser/browser/webkit/rfc6266.py index f83413ee2..139b4f9df 100644 --- a/qutebrowser/browser/webkit/rfc6266.py +++ b/qutebrowser/browser/webkit/rfc6266.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index d595a6e95..263bf6334 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 829052798..a7d4f4b3e 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkithistory.py b/qutebrowser/browser/webkit/webkithistory.py index 0edbb3fa3..2c719acd6 100644 --- a/qutebrowser/browser/webkit/webkithistory.py +++ b/qutebrowser/browser/webkit/webkithistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitinspector.py b/qutebrowser/browser/webkit/webkitinspector.py index 957d1b4d2..3098ab5af 100644 --- a/qutebrowser/browser/webkit/webkitinspector.py +++ b/qutebrowser/browser/webkit/webkitinspector.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index 3354a9486..ee01c40db 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 49b24d251..9c4d2bf66 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -23,6 +23,10 @@ import re import functools import xml.etree.ElementTree +import pygments +import pygments.lexers +import pygments.formatters + import sip from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF, QSize) @@ -50,6 +54,29 @@ class WebKitAction(browsertab.AbstractAction): """Save the current page.""" raise browsertab.UnsupportedOperationError + def show_source(self): + + def show_source_cb(source): + """Show source as soon as it's ready.""" + # WORKAROUND for https://github.com/PyCQA/pylint/issues/491 + # pylint: disable=no-member + lexer = pygments.lexers.HtmlLexer() + formatter = pygments.formatters.HtmlFormatter( + full=True, linenos='table') + # pylint: enable=no-member + highlighted = pygments.highlight(source, lexer, formatter) + + tb = objreg.get('tabbed-browser', scope='window', + window=self._tab.win_id) + new_tab = tb.tabopen(background=False, related=True) + # The original URL becomes the path of a view-source: URL + # (without a host), but query/fragment should stay. + url = QUrl('view-source:' + urlstr) + new_tab.set_html(highlighted, url) + + urlstr = self._tab.url().toString(QUrl.RemoveUserInfo) + self._tab.dump_async(show_source_cb) + class WebKitPrinting(browsertab.AbstractPrinting): @@ -161,7 +188,7 @@ class WebKitCaret(browsertab.AbstractCaret): settings = self._widget.settings() settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) - self.selection_enabled = bool(self.selection()) + self.selection_enabled = self._widget.hasSelection() if self._widget.isVisible(): # Sometimes the caret isn't immediately visible, but unfocusing @@ -174,7 +201,7 @@ class WebKitCaret(browsertab.AbstractCaret): # # Note: We can't use hasSelection() here, as that's always # true in caret mode. - if not self.selection(): + if not self.selection_enabled: self._widget.page().currentFrame().evaluateJavaScript( utils.read_file('javascript/position_caret.js')) @@ -327,23 +354,16 @@ class WebKitCaret(browsertab.AbstractCaret): def toggle_selection(self): self.selection_enabled = not self.selection_enabled mainwindow = objreg.get('main-window', scope='window', - window=self._win_id) + window=self._tab.win_id) mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True) def drop_selection(self): self._widget.triggerPageAction(QWebPage.MoveToNextChar) - def has_selection(self): - return self._widget.hasSelection() - - def selection(self, html=False): - if html: - return self._widget.selectedHtml() - return self._widget.selectedText() + def selection(self, callback): + callback(self._widget.selectedText()) def follow_selected(self, *, tab=False): - if not self.has_selection(): - return if QWebSettings.globalSettings().testAttribute( QWebSettings.JavascriptEnabled): if tab: @@ -351,7 +371,9 @@ class WebKitCaret(browsertab.AbstractCaret): self._tab.run_js_async( 'window.getSelection().anchorNode.parentNode.click()') else: - selection = self.selection(html=True) + selection = self._widget.selectedHtml() + if not selection: + return try: selected_element = xml.etree.ElementTree.fromstring( '{}'.format(selection)).find('a') @@ -615,13 +637,13 @@ class WebKitTab(browsertab.AbstractTab): self._make_private(widget) self.history = WebKitHistory(self) self.scroller = WebKitScroller(self, parent=self) - self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager, + self.caret = WebKitCaret(mode_manager=mode_manager, tab=self, parent=self) - self.zoom = WebKitZoom(win_id=win_id, parent=self) + self.zoom = WebKitZoom(tab=self, parent=self) self.search = WebKitSearch(parent=self) self.printing = WebKitPrinting() - self.elements = WebKitElements(self) - self.action = WebKitAction() + self.elements = WebKitElements(tab=self) + self.action = WebKitAction(tab=self) self._set_widget(widget) self._connect_signals() self.backend = usertypes.Backend.QtWebKit diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 89407fcdf..5c2bb4111 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index 4f1ff10c8..942e7265c 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/__init__.py b/qutebrowser/commands/__init__.py index 7bc59ae40..0bbc9852b 100644 --- a/qutebrowser/commands/__init__.py +++ b/qutebrowser/commands/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py index 9dfe841ce..707324ede 100644 --- a/qutebrowser/commands/argparser.py +++ b/qutebrowser/commands/argparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index bca2f0500..5d3ac2a89 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/cmdutils.py b/qutebrowser/commands/cmdutils.py index 2f7af2f9f..f9ce91b8f 100644 --- a/qutebrowser/commands/cmdutils.py +++ b/qutebrowser/commands/cmdutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -105,7 +105,8 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name else: assert isinstance(self._name, str), self._name name = self._name - log.commands.vdebug("Registering command {}".format(name)) + log.commands.vdebug("Registering command {} (from {}:{})".format( + name, func.__module__, func.__qualname__)) if name in cmd_dict: raise ValueError("{} is already registered!".format(name)) cmd = command.Command(name=name, instance=self._instance, diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index b4ff1cde8..fd3fcf20a 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -193,10 +193,10 @@ class Command: return False def _inspect_func(self): - """Inspect the function to get useful informations from it. + """Inspect the function to get useful information from it. Sets instance attributes (desc, type_conv, name_conv) based on the - informations. + information. Return: How many user-visible arguments the command has. diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 890a0275e..9fe53f07a 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index d86cb8ccf..5654fd809 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -446,3 +446,4 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False): runner.prepare_run(cmd_path, *args, env=env, verbose=verbose) tab.dump_async(runner.store_html) tab.dump_async(runner.store_text, plain=True) + return runner diff --git a/qutebrowser/completion/__init__.py b/qutebrowser/completion/__init__.py index 8b8b9d88d..2c9121699 100644 --- a/qutebrowser/completion/__init__.py +++ b/qutebrowser/completion/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 4e187750d..09b80ed12 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index b4f9c5a33..779906a83 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -203,9 +203,9 @@ class CompletionItemDelegate(QStyledItemDelegate): columns_to_filter = index.model().columns_to_filter(index) if index.column() in columns_to_filter and pattern: repl = r'\g<0>' - text = re.sub(re.escape(pattern).replace(r'\ ', r'|'), - repl, html.escape(self._opt.text), - flags=re.IGNORECASE) + pat = html.escape(re.escape(pattern)).replace(r'\ ', r'|') + txt = html.escape(self._opt.text) + text = re.sub(pat, repl, txt, flags=re.IGNORECASE) self._doc.setHtml(text) else: self._doc.setPlainText(self._opt.text) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 29f4f6653..740be75d9 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/__init__.py b/qutebrowser/completion/models/__init__.py index 5812545eb..7f62829ba 100644 --- a/qutebrowser/completion/models/__init__.py +++ b/qutebrowser/completion/models/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index aa4422d83..1c77e1d31 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Ryan Roden-Corrent (rcorre) +# Copyright 2017-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 445a57a66..c433dbc12 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/histcategory.py b/qutebrowser/completion/models/histcategory.py index 57a2aa936..a07f78143 100644 --- a/qutebrowser/completion/models/histcategory.py +++ b/qutebrowser/completion/models/histcategory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Ryan Roden-Corrent (rcorre) +# Copyright 2017-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 657ae97aa..13bc1e6b2 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Ryan Roden-Corrent (rcorre) +# Copyright 2017-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 22c9000c3..049d89295 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 1d7a075eb..bebf6d829 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/completion/models/util.py b/qutebrowser/completion/models/util.py index 40d5de8dc..c1b8b56f9 100644 --- a/qutebrowser/completion/models/util.py +++ b/qutebrowser/completion/models/util.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Ryan Roden-Corrent (rcorre) +# Copyright 2017-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/__init__.py b/qutebrowser/config/__init__.py index bf0bce0ec..e2c25cce8 100644 --- a/qutebrowser/config/__init__.py +++ b/qutebrowser/config/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index c170d0705..f30acb8ca 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index 93a1f7ce0..8bc2a9ed8 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -253,7 +253,7 @@ class ConfigCommands: Args: no_source: Don't re-source the config file after editing. """ - def on_editing_finished(): + def on_file_updated(): """Source the new config when editing finished. This can't use cmdexc.CommandError as it's run async. @@ -263,9 +263,9 @@ class ConfigCommands: except configexc.ConfigFileErrors as e: message.error(str(e)) - ed = editor.ExternalEditor(self._config) + ed = editor.ExternalEditor(watch=True, parent=self._config) if not no_source: - ed.editing_finished.connect(on_editing_finished) + ed.file_updated.connect(on_file_updated) filename = os.path.join(standarddir.config(), 'config.py') ed.edit_file(filename) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index b265ab8fc..3e0a6d8b1 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -54,7 +54,12 @@ class Option: @attr.s class Migrations: - """Nigrated options in configdata.yml.""" + """Nigrated options in configdata.yml. + + Attributes: + renamed: A dict mapping old option names to new names. + deleted: A list of option names which have been removed. + """ renamed = attr.ib(default=attr.Factory(dict)) deleted = attr.ib(default=attr.Factory(list)) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index a118a8b59..5e47eb84e 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -360,7 +360,7 @@ content.headers.referer: When to send the Referer header. The Referer header tells websites from which website you were coming from - when visting them. + when visiting them. content.headers.user_agent: default: null @@ -1251,10 +1251,15 @@ tabs.padding: type: Padding desc: Padding (in pixels) around text for tabs. -tabs.persist_mode_on_change: - default: false - type: Bool - desc: Stay in insert/passthrough mode when switching tabs. +tabs.mode_on_change: + default: normal + type: + name: String + valid_values: + - persist: "Retain the current mode." + - restore: "Restore previously saved mode." + - normal: "Always revert to normal mode." + desc: When switching tabs, what input mode is applied. tabs.position: default: top @@ -1418,7 +1423,7 @@ url.default_page: url.incdec_segments: type: name: FlagList - valid_values: [host, path, query, anchor] + valid_values: [host, port, path, query, anchor] default: [path, query] desc: URL segments where `:navigate increment/decrement` will search for a number. diff --git a/qutebrowser/config/configdiff.py b/qutebrowser/config/configdiff.py index 020be2c8c..9f8b70a26 100644 --- a/qutebrowser/config/configdiff.py +++ b/qutebrowser/config/configdiff.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 28b269dd5..0a4986efa 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 0e9572f55..692474075 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -166,25 +166,42 @@ class YamlConfig(QObject): self._dirty = False self._handle_migrations() + self._validate() def _handle_migrations(self): - """Handle unknown/renamed keys.""" + """Migrate older configs to the newest format.""" + # Simple renamed/deleted options for name in list(self._values): if name in configdata.MIGRATIONS.renamed: new_name = configdata.MIGRATIONS.renamed[name] log.config.debug("Renaming {} to {}".format(name, new_name)) self._values[new_name] = self._values[name] del self._values[name] + self._mark_changed() elif name in configdata.MIGRATIONS.deleted: log.config.debug("Removing {}".format(name)) del self._values[name] - elif name in configdata.DATA: - pass + self._mark_changed() + + # tabs.persist_mode_on_change got merged into tabs.mode_on_change + old = 'tabs.persist_mode_on_change' + new = 'tabs.mode_on_change' + if old in self._values: + if self._values[old]: + self._values[new] = 'persist' else: - desc = configexc.ConfigErrorDesc( - "While loading options", - "Unknown option {}".format(name)) - raise configexc.ConfigFileErrors('autoconfig.yml', [desc]) + self._values[new] = 'normal' + del self._values[old] + self._mark_changed() + + def _validate(self): + """Make sure all settings exist.""" + unknown = set(self._values) - set(configdata.DATA) + if unknown: + errors = [configexc.ConfigErrorDesc("While loading options", + "Unknown option {}".format(e)) + for e in sorted(unknown)] + raise configexc.ConfigFileErrors('autoconfig.yml', errors) def unset(self, name): """Remove the given option name if it's configured.""" diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 510245e2e..c7c9362b0 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 71c32f59e..26998d510 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 0e57af45f..fa8abb76f 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/html/back.html b/qutebrowser/html/back.html index 894427800..6128c41f7 100644 --- a/qutebrowser/html/back.html +++ b/qutebrowser/html/back.html @@ -27,7 +27,7 @@ function prepare_restore() { return; } - document.addEventListener("visibilitychange", go_back); + document.addEventListener("visibilitychange", go_back, {once: true}); } // there are three states diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index 2e4de98fc..1e6ee2a20 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -54,3 +54,5 @@ rules: function-paren-newline: "off" multiline-comment-style: "off" no-bitwise: "off" + no-ternary: "off" + max-lines: "off" diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js new file mode 100644 index 000000000..5088c3e2f --- /dev/null +++ b/qutebrowser/javascript/caret.js @@ -0,0 +1,1368 @@ +/* eslint-disable max-len, max-statements, complexity, +max-params, default-case, valid-jsdoc */ + +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * Copyright 2018 Florian Bruhin (The Compiler) + * + * 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 . + */ + +/** + * Ported chrome-caretbrowsing extension. + * https://cs.chromium.org/chromium/src/ui/accessibility/extensions/caretbrowsing/ + * + * The behavior is based on Mozilla's spec whenever possible: + * http://www.mozilla.org/access/keyboard/proposal + * + * The one exception is that Esc is used to escape out of a form control, + * rather than their proposed key (which doesn't seem to work in the + * latest Firefox anyway). + * + * Some details about how Chrome selection works, which will help in + * understanding the code: + * + * The Selection object (window.getSelection()) has four components that + * completely describe the state of the caret or selection: + * + * base and anchor: this is the start of the selection, the fixed point. + * extent and focus: this is the end of the selection, the part that + * moves when you hold down shift and press the left or right arrows. + * + * When the selection is a cursor, the base, anchor, extent, and focus are + * all the same. + * + * There's only one time when the base and anchor are not the same, or the + * extent and focus are not the same, and that's when the selection is in + * an ambiguous state - i.e. it's not clear which edge is the focus and which + * is the anchor. As an example, if you double-click to select a word, then + * the behavior is dependent on your next action. If you press Shift+Right, + * the right edge becomes the focus. But if you press Shift+Left, the left + * edge becomes the focus. + * + * When the selection is in an ambiguous state, the base and extent are set + * to the position where the mouse clicked, and the anchor and focus are set + * to the boundaries of the selection. + * + * The only way to set the selection and give it direction is to use + * the non-standard Selection.setBaseAndExtent method. If you try to use + * Selection.addRange(), the anchor will always be on the left and the focus + * will always be on the right, making it impossible to manipulate + * selections that move from right to left. + * + * Finally, Chrome will throw an exception if you try to set an invalid + * selection - a selection where the left and right edges are not the same, + * but it doesn't span any visible characters. A common example is that + * there are often many whitespace characters in the DOM that are not + * visible on the page; trying to select them will fail. Another example is + * any node that's invisible or not displayed. + * + * While there are probably many possible methods to determine what is + * selectable, this code uses the method of determining if there's a valid + * bounding box for the range or not - keep moving the cursor forwards until + * the range from the previous position and candidate next position has a + * valid bounding box. + */ + +"use strict"; + +window._qutebrowser.caret = (function() { + function isElementInViewport(node) { + let i; + let boundingRect = (node.getClientRects()[0] || + node.getBoundingClientRect()); + + if (boundingRect.width <= 1 && boundingRect.height <= 1) { + const rects = node.getClientRects(); + for (i = 0; i < rects.length; i++) { + if (rects[i].width > rects[0].height && + rects[i].height > rects[0].height) { + boundingRect = rects[i]; + } + } + } + if (boundingRect === undefined) { + return null; + } + if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { + return null; + } + if (boundingRect.width <= 1 || boundingRect.height <= 1) { + const children = node.children; + let visibleChildNode = false; + for (i = 0; i < children.length; ++i) { + boundingRect = (children[i].getClientRects()[0] || + children[i].getBoundingClientRect()); + if (boundingRect.width > 1 && boundingRect.height > 1) { + visibleChildNode = true; + break; + } + } + if (visibleChildNode === false) { + return null; + } + } + if (boundingRect.top + boundingRect.height < 10 || + boundingRect.left + boundingRect.width < -10) { + return null; + } + const computedStyle = window.getComputedStyle(node, null); + if (computedStyle.visibility !== "visible" || + computedStyle.display === "none" || + node.hasAttribute("disabled") || + parseInt(computedStyle.width, 10) === 0 || + parseInt(computedStyle.height, 10) === 0) { + return null; + } + return boundingRect.top >= -20; + } + + function positionCaret() { + const walker = document.createTreeWalker(document.body, -1); + let node; + const textNodes = []; + let el; + while ((node = walker.nextNode())) { + if (node.nodeType === 3 && node.nodeValue.trim() !== "") { + textNodes.push(node); + } + } + for (let i = 0; i < textNodes.length; i++) { + const element = textNodes[i].parentElement; + if (isElementInViewport(element)) { + el = element; + break; + } + } + if (el !== undefined) { + /* eslint-disable no-use-before-define */ + const start = new Cursor(el, 0, ""); + const end = new Cursor(el, 0, ""); + const nodesCrossed = []; + const result = TraverseUtil.getNextChar( + start, end, nodesCrossed, true); + if (result === null) { + return; + } + CaretBrowsing.setAndValidateSelection(start, start); + /* eslint-enable no-use-before-define */ + } + } + + /** + * Return whether a node is focusable. This includes nodes whose tabindex + * attribute is set to "-1" explicitly - these nodes are not in the tab + * order, but they should still be focused if the user navigates to them + * using linear or smart DOM navigation. + * + * Note that when the tabIndex property of an Element is -1, that doesn't + * tell us whether the tabIndex attribute is missing or set to "-1" explicitly, + * so we have to check the attribute. + * + * @param {Object} targetNode The node to check if it's focusable. + * @return {boolean} True if the node is focusable. + */ + function isFocusable(targetNode) { + if (!targetNode || typeof (targetNode.tabIndex) !== "number") { + return false; + } + + if (targetNode.tabIndex >= 0) { + return true; + } + + if (targetNode.hasAttribute && + targetNode.hasAttribute("tabindex") && + targetNode.getAttribute("tabindex") === "-1") { + return true; + } + + return false; + } + + const axs = {}; + + axs.dom = {}; + + axs.color = {}; + + axs.utils = {}; + + axs.dom.parentElement = function(node) { + if (!node) { + return null; + } + const composedNode = axs.dom.composedParentNode(node); + if (!composedNode) { + return null; + } + switch (composedNode.nodeType) { + case Node.ELEMENT_NODE: + return composedNode; + default: + return axs.dom.parentElement(composedNode); + } + }; + + axs.dom.shadowHost = function(node) { + if ("host" in node) { + return node.host; + } + return null; + }; + + axs.dom.composedParentNode = function(node) { + if (!node) { + return null; + } + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return axs.dom.shadowHost(node); + } + const parentNode = node.parentNode; + if (!parentNode) { + return null; + } + if (parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + return axs.dom.shadowHost(parentNode); + } + if (!parentNode.shadowRoot) { + return parentNode; + } + const points = node.getDestinationInsertionPoints(); + if (points.length > 0) { + return axs.dom.composedParentNode(points[points.length - 1]); + } + return null; + }; + + axs.color.Color = function(red, green, blue, alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + }; + + axs.color.parseColor = function(colorText) { + if (colorText === "transparent") { + return new axs.color.Color(0, 0, 0, 0); + } + let match = colorText.match(/^rgb\((\d+), (\d+), (\d+)\)$/); + if (match) { + const blue = parseInt(match[3], 10); + const green = parseInt(match[2], 10); + const red = parseInt(match[1], 10); + return new axs.color.Color(red, green, blue, 1); + } + match = colorText.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/); + if (match) { + const red = parseInt(match[1], 10); + const green = parseInt(match[2], 10); + const blue = parseInt(match[3], 10); + const alpha = parseFloat(match[4]); + return new axs.color.Color(red, green, blue, alpha); + } + return null; + }; + + axs.color.flattenColors = function(color1, color2) { + const colorAlpha = color1.alpha; + return new axs.color.Color( + ((1 - colorAlpha) * color2.red) + (colorAlpha * color1.red), + ((1 - colorAlpha) * color2.green) + (colorAlpha * color1.green), + ((1 - colorAlpha) * color2.blue) + (colorAlpha * color2.blue), + color1.alpha + (color2.alpha * (1 - color1.alpha))); + }; + + axs.utils.getParentBgColor = function(_el) { + let el = _el; + let el2 = el; + let iter = null; + el = []; + for (iter = null; (el2 = axs.dom.parentElement(el2));) { + const style = window.getComputedStyle(el2, null); + if (style) { + const color = axs.color.parseColor(style.backgroundColor); + if (color && + (style.opacity < 1 && + (color.alpha *= style.opacity), + color.alpha !== 0 && + (el.push(color), color.alpha === 1))) { + iter = !0; + break; + } + } + } + if (!iter) { + el.push(new axs.color.Color(255, 255, 255, 1)); + } + for (el2 = el.pop(); el.length;) { + iter = el.pop(); + el2 = axs.color.flattenColors(iter, el2); + } + return el2; + }; + + axs.utils.getFgColor = function(el, el2, color) { + let color2 = axs.color.parseColor(el.color); + if (!color2) { + return null; + } + if (color2.alpha < 1) { + color2 = axs.color.flattenColors(color2, color); + } + if (el.opacity < 1) { + const el3 = axs.utils.getParentBgColor(el2); + color2.alpha *= el.opacity; + color2 = axs.color.flattenColors(color2, el3); + } + return color2; + }; + + axs.utils.getBgColor = function(el, elParent) { + let color = axs.color.parseColor(el.backgroundColor); + if (!color) { + return null; + } + if (el.opacity < 1) { + color.alpha *= el.opacity; + } + if (color.alpha < 1) { + const bgColor = axs.utils.getParentBgColor(elParent); + if (bgColor === null) { + return null; + } + color = axs.color.flattenColors(color, bgColor); + } + return color; + }; + + axs.color.colorChannelToString = function(_color) { + const color = Math.round(_color); + if (color < 15) { + return `0${color.toString(16)}`; + } + return color.toString(16); + }; + + axs.color.colorToString = function(color) { + if (color.alpha === 1) { + const red = axs.color.colorChannelToString(color.red); + const green = axs.color.colorChannelToString(color.green); + const blue = axs.color.colorChannelToString(color.blue); + return `#${red}${green}${blue}`; + } + const arr = [color.red, color.green, color.blue, color.alpha].join(); + return `rgba(${arr})`; + }; + + /** + * A class to represent a cursor location in the document, + * like the start position or end position of a selection range. + * + * Later this may be extended to support "virtual text" for an object, + * like the ALT text for an image. + * + * Note: we cache the text of a particular node at the time we + * traverse into it. Later we should add support for dynamically + * reloading it. + * @param {Node} node The DOM node. + * @param {number} index The index of the character within the node. + * @param {string} text The cached text contents of the node. + * @constructor + */ + // eslint-disable-next-line func-style + const Cursor = function(node, index, text) { + this.node = node; + this.index = index; + this.text = text; + }; + + /** + * @return {Cursor} A new cursor pointing to the same location. + */ + Cursor.prototype.clone = function() { + return new Cursor(this.node, this.index, this.text); + }; + + /** + * Modify this cursor to point to the location that another cursor points to. + * @param {Cursor} otherCursor The cursor to copy from. + */ + Cursor.prototype.copyFrom = function(otherCursor) { + this.node = otherCursor.node; + this.index = otherCursor.index; + this.text = otherCursor.text; + }; + + /** + * Utility functions for stateless DOM traversal. + * @constructor + */ + const TraverseUtil = {}; + + /** + * Gets the text representation of a node. This allows us to substitute + * alt text, names, or titles for html elements that provide them. + * @param {Node} node A DOM node. + * @return {string} A text string representation of the node. + */ + TraverseUtil.getNodeText = function(node) { + if (node.constructor === Text) { + return node.data; + } + return ""; + }; + + /** + * Return true if a node should be treated as a leaf node, because + * its children are properties of the object that shouldn't be traversed. + * + * TODO(dmazzoni): replace this with a predicate that detects nodes with + * ARIA roles and other objects that have their own description. + * For now we just detect a couple of common cases. + * + * @param {Node} node A DOM node. + * @return {boolean} True if the node should be treated as a leaf node. + */ + TraverseUtil.treatAsLeafNode = function(node) { + return node.childNodes.length === 0 || + node.nodeName === "SELECT" || + node.nodeName === "OBJECT"; + }; + + /** + * Return true only if a single character is whitespace. + * From https://developer.mozilla.org/en/Whitespace_in_the_DOM, + * whitespace is defined as one of the characters + * "\t" TAB \u0009 + * "\n" LF \u000A + * "\r" CR \u000D + * " " SPC \u0020. + * + * @param {string} c A string containing a single character. + * @return {boolean} True if the character is whitespace, otherwise false. + */ + TraverseUtil.isWhitespace = function(ch) { + return (ch === " " || ch === "\n" || ch === "\r" || ch === "\t"); + }; + + /** + * Use the computed CSS style to figure out if this DOM node is currently + * visible. + * @param {Node} node A HTML DOM node. + * @return {boolean} Whether or not the html node is visible. + */ + TraverseUtil.isVisible = function(node) { + if (!node.style) { + return true; + } + const style = window.getComputedStyle(node, null); + return (Boolean(style) && + style.display !== "none" && + style.visibility !== "hidden"); + }; + + /** + * Use the class name to figure out if this DOM node should be traversed. + * @param {Node} node A HTML DOM node. + * @return {boolean} Whether or not the html node should be traversed. + */ + TraverseUtil.isSkipped = function(_node) { + let node = _node; + if (node.constructor === Text) { + node = node.parentElement; + } + if (node.className === "CaretBrowsing_Caret") { + return true; + } + return false; + }; + + /** + * Moves the cursor forwards until it has crossed exactly one character. + * @param {Cursor} cursor The cursor location where the search should start. + * On exit, the cursor will be immediately to the right of the + * character returned. + * @param {Array} nodesCrossed Any HTML nodes crossed between the + * initial and final cursor position will be pushed onto this array. + * @return {?string} The character found, or null if the bottom of the + * document has been reached. + */ + TraverseUtil.forwardsChar = function(cursor, nodesCrossed) { + for (;;) { + let childNode = null; + if (!TraverseUtil.treatAsLeafNode(cursor.node)) { + for (let i = cursor.index; + i < cursor.node.childNodes.length; + i++) { + const node = cursor.node.childNodes[i]; + if (TraverseUtil.isSkipped(node)) { + nodesCrossed.push(node); + } else if (TraverseUtil.isVisible(node)) { + childNode = node; + break; + } + } + } + if (childNode) { + cursor.node = childNode; + cursor.index = 0; + cursor.text = TraverseUtil.getNodeText(cursor.node); + if (cursor.node.constructor !== Text) { + nodesCrossed.push(cursor.node); + } + } else { + // Return the next character from this leaf node. + if (cursor.index < cursor.text.length) { + return cursor.text[cursor.index++]; + } + + // Move to the next sibling, going up the tree as necessary. + while (cursor.node !== null) { + // Try to move to the next sibling. + let siblingNode = null; + for (let node = cursor.node.nextSibling; + node !== null; + node = node.nextSibling) { + if (TraverseUtil.isSkipped(node)) { + nodesCrossed.push(node); + } else if (TraverseUtil.isVisible(node)) { + siblingNode = node; + break; + } + } + if (siblingNode) { + cursor.node = siblingNode; + cursor.text = TraverseUtil.getNodeText(siblingNode); + cursor.index = 0; + + if (cursor.node.constructor !== Text) { + nodesCrossed.push(cursor.node); + } + + break; + } + + // Otherwise, move to the parent. + const parentNode = cursor.node.parentNode; + if (parentNode && + parentNode.constructor !== HTMLBodyElement) { + cursor.node = cursor.node.parentNode; + cursor.text = null; + cursor.index = 0; + } else { + return null; + } + } + } + } + }; + + /** + * Finds the next character, starting from endCursor. Upon exit, startCursor + * and endCursor will surround the next character. If skipWhitespace is + * true, will skip until a real character is found. Otherwise, it will + * attempt to select all of the whitespace between the initial position + * of endCursor and the next non-whitespace character. + * @param {Cursor} startCursor On exit, points to the position before + * the char. + * @param {Cursor} endCursor The position to start searching for the next + * char. On exit, will point to the position past the char. + * @param {Array} nodesCrossed Any HTML nodes crossed between the + * initial and final cursor position will be pushed onto this array. + * @param {boolean} skipWhitespace If true, will keep scanning until a + * non-whitespace character is found. + * @return {?string} The next char, or null if the bottom of the + * document has been reached. + */ + TraverseUtil.getNextChar = function( + startCursor, endCursor, nodesCrossed, skipWhitespace) { + // Save the starting position and get the first character. + startCursor.copyFrom(endCursor); + let fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); + if (fChar === null) { + return null; + } + + // Keep track of whether the first character was whitespace. + const initialWhitespace = TraverseUtil.isWhitespace(fChar); + + // Keep scanning until we find a non-whitespace or non-skipped character. + while ((TraverseUtil.isWhitespace(fChar)) || + (TraverseUtil.isSkipped(endCursor.node))) { + fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); + if (fChar === null) { + return null; + } + } + if (skipWhitespace || !initialWhitespace) { + // If skipWhitepace is true, or if the first character we encountered + // was not whitespace, return that non-whitespace character. + startCursor.copyFrom(endCursor); + startCursor.index--; + return fChar; + } + + for (let i = 0; i < nodesCrossed.length; i++) { + if (TraverseUtil.isSkipped(nodesCrossed[i])) { + // We need to make sure that startCursor and endCursor aren't + // surrounding a skippable node. + endCursor.index--; + startCursor.copyFrom(endCursor); + startCursor.index--; + return " "; + } + } + // Otherwise, return all of the whitespace before that last character. + endCursor.index--; + return " "; + }; + + /** + * The class handling the Caret Browsing implementation in the page. + * Sets up communication with the background page, and then when caret + * browsing is enabled, response to various key events to move the caret + * or selection within the text content of the document. + * @constructor + */ + const CaretBrowsing = {}; + + /** + * Is caret browsing enabled? + * @type {boolean} + */ + CaretBrowsing.isEnabled = false; + + /** + * Keep it enabled even when flipped off (for the options page)? + * @type {boolean} + */ + CaretBrowsing.forceEnabled = false; + + /** + * What to do when the caret appears? + * @type {string} + */ + CaretBrowsing.onEnable = undefined; + + /** + * What to do when the caret jumps? + * @type {string} + */ + CaretBrowsing.onJump = undefined; + + /** + * Is this window / iframe focused? We won't show the caret if not, + * especially so that carets aren't shown in two iframes of the same + * tab. + * @type {boolean} + */ + CaretBrowsing.isWindowFocused = false; + + /** + * Is the caret actually visible? This is true only if isEnabled and + * isWindowFocused are both true. + * @type {boolean} + */ + CaretBrowsing.isCaretVisible = false; + + /** + * The actual caret element, an absolute-positioned flashing line. + * @type {Element} + */ + CaretBrowsing.caretElement = undefined; + + /** + * The x-position of the caret, in absolute pixels. + * @type {number} + */ + CaretBrowsing.caretX = 0; + + /** + * The y-position of the caret, in absolute pixels. + * @type {number} + */ + CaretBrowsing.caretY = 0; + + /** + * The width of the caret in pixels. + * @type {number} + */ + CaretBrowsing.caretWidth = 0; + + /** + * The height of the caret in pixels. + * @type {number} + */ + CaretBrowsing.caretHeight = 0; + + /** + * The foregroundc color. + * @type {string} + */ + CaretBrowsing.caretForeground = "#000"; + + /** + * The backgroundc color. + * @type {string} + */ + CaretBrowsing.caretBackground = "#fff"; + + /** + * Is the selection collapsed, i.e. are the start and end locations + * the same? If so, our blinking caret image is shown; otherwise + * the Chrome selection is shown. + * @type {boolean} + */ + CaretBrowsing.isSelectionCollapsed = false; + + /** + * The id returned by window.setInterval for our blink function, so + * we can cancel it when caret browsing is disabled. + * @type {number?} + */ + CaretBrowsing.blinkFunctionId = null; + + /** + * The desired x-coordinate to match when moving the caret up and down. + * To match the behavior as documented in Mozilla's caret browsing spec + * (http://www.mozilla.org/access/keyboard/proposal), we keep track of the + * initial x position when the user starts moving the caret up and down, + * so that the x position doesn't drift as you move throughout lines, but + * stays as close as possible to the initial position. This is reset when + * moving left or right or clicking. + * @type {number?} + */ + CaretBrowsing.targetX = null; + + /** + * A flag that flips on or off as the caret blinks. + * @type {boolean} + */ + CaretBrowsing.blinkFlag = true; + + /** + * Check if a node is a control that normally allows the user to interact + * with it using arrow keys. We won't override the arrow keys when such a + * control has focus, the user must press Escape to do caret browsing outside + * that control. + * @param {Node} node A node to check. + * @return {boolean} True if this node is a control that the user can + * interact with using arrow keys. + */ + CaretBrowsing.isControlThatNeedsArrowKeys = function(node) { + if (!node) { + return false; + } + + if (node === document.body || node !== document.activeElement) { + return false; + } + + if (node.constructor === HTMLSelectElement) { + return true; + } + + if (node.constructor === HTMLInputElement) { + switch (node.type) { + case "email": + case "number": + case "password": + case "search": + case "text": + case "tel": + case "url": + case "": + return true; // All of these are text boxes. + case "datetime": + case "datetime-local": + case "date": + case "month": + case "radio": + case "range": + case "week": + return true; // These are other input elements that use arrows. + } + } + + // Handle focusable ARIA controls. + if (node.getAttribute && isFocusable(node)) { + const role = node.getAttribute("role"); + switch (role) { + case "combobox": + case "grid": + case "gridcell": + case "listbox": + case "menu": + case "menubar": + case "menuitem": + case "menuitemcheckbox": + case "menuitemradio": + case "option": + case "radiogroup": + case "scrollbar": + case "slider": + case "spinbutton": + case "tab": + case "tablist": + case "textbox": + case "tree": + case "treegrid": + case "treeitem": + return true; + } + } + + return false; + }; + + CaretBrowsing.injectCaretStyles = function() { + const style = ".CaretBrowsing_Caret {" + + " position: absolute;" + + " z-index: 2147483647;" + + " min-height: 10px;" + + " background-color: #000;" + + "}"; + const node = document.createElement("style"); + node.innerHTML = style; + document.body.appendChild(node); + }; + + /** + * If there's no initial selection, set the cursor just before the + * first text character in the document. + */ + CaretBrowsing.setInitialCursor = function() { + const selectionRange = window.getSelection().toString().length; + if (selectionRange === 0) { + positionCaret(); + } + + CaretBrowsing.injectCaretStyles(); + CaretBrowsing.toggle(); + CaretBrowsing.initiated = true; + CaretBrowsing.selectionEnabled = selectionRange > 0; + }; + + /** + * Try to set the window's selection to be between the given start and end + * cursors, and return whether or not it was successful. + * @param {Cursor} start The start position. + * @param {Cursor} end The end position. + * @return {boolean} True if the selection was successfully set. + */ + CaretBrowsing.setAndValidateSelection = function(start, end) { + const sel = window.getSelection(); + sel.setBaseAndExtent(start.node, start.index, end.node, end.index); + + if (sel.rangeCount !== 1) { + return false; + } + + return (sel.anchorNode === start.node && + sel.anchorOffset === start.index && + sel.focusNode === end.node && + sel.focusOffset === end.index); + }; + + /** + * Set focus to a node if it's focusable. If it's an input element, + * select the text, otherwise it doesn't appear focused to the user. + * Every other control behaves normally if you just call focus() on it. + * @param {Node} node The node to focus. + * @return {boolean} True if the node was focused. + */ + CaretBrowsing.setFocusToNode = function(nodeArg) { + let node = nodeArg; + while (node && node !== document.body) { + if (isFocusable(node) && node.constructor !== HTMLIFrameElement) { + node.focus(); + if (node.constructor === HTMLInputElement && node.select) { + node.select(); + } + return true; + } + node = node.parentNode; + } + + return false; + }; + + /** + * Set the caret element's normal style, i.e. not when animating. + */ + CaretBrowsing.setCaretElementNormalStyle = function() { + const element = CaretBrowsing.caretElement; + element.className = "CaretBrowsing_Caret"; + if (CaretBrowsing.isSelectionCollapsed) { + element.style.opacity = "1.0"; + } else { + element.style.opacity = "0.0"; + } + element.style.left = `${CaretBrowsing.caretX}px`; + element.style.top = `${CaretBrowsing.caretY}px`; + element.style.width = `${CaretBrowsing.caretWidth}px`; + element.style.height = `${CaretBrowsing.caretHeight}px`; + element.style.color = CaretBrowsing.caretForeground; + }; + + /** + * Create the caret element. This assumes that caretX, caretY, + * caretWidth, and caretHeight have all been set. The caret is + * animated in so the user can find it when it first appears. + */ + CaretBrowsing.createCaretElement = function() { + const element = document.createElement("div"); + element.className = "CaretBrowsing_Caret"; + document.body.appendChild(element); + CaretBrowsing.caretElement = element; + CaretBrowsing.setCaretElementNormalStyle(); + }; + + /** + * Recreate the caret element, triggering any intro animation. + */ + CaretBrowsing.recreateCaretElement = function() { + if (CaretBrowsing.caretElement) { + window.clearInterval(CaretBrowsing.blinkFunctionId); + CaretBrowsing.caretElement.parentElement.removeChild( + CaretBrowsing.caretElement); + CaretBrowsing.caretElement = null; + CaretBrowsing.updateIsCaretVisible(); + } + }; + + /** + * Get the rectangle for a cursor position. This is tricky because + * you can't get the bounding rectangle of an empty range, so this function + * computes the rect by trying a range including one character earlier or + * later than the cursor position. + * @param {Cursor} cursor A single cursor position. + * @return {{left: number, top: number, width: number, height: number}} + * The bounding rectangle of the cursor. + */ + CaretBrowsing.getCursorRect = function(cursor) { + let node = cursor.node; + const index = cursor.index; + const rect = { + "left": 0, + "top": 0, + "width": 1, + "height": 0, + }; + if (node.constructor === Text) { + let left = index; + let right = index; + const max = node.data.length; + const newRange = document.createRange(); + while (left > 0 || right < max) { + if (left > 0) { + left--; + newRange.setStart(node, left); + newRange.setEnd(node, index); + const rangeRect = newRange.getBoundingClientRect(); + if (rangeRect && rangeRect.width && rangeRect.height) { + rect.left = rangeRect.right; + rect.top = rangeRect.top; + rect.height = rangeRect.height; + break; + } + } + if (right < max) { + right++; + newRange.setStart(node, index); + newRange.setEnd(node, right); + const rangeRect = newRange.getBoundingClientRect(); + if (rangeRect && rangeRect.width && rangeRect.height) { + rect.left = rangeRect.left; + rect.top = rangeRect.top; + rect.height = rangeRect.height; + break; + } + } + } + } else { + rect.height = node.offsetHeight; + while (node !== null) { + rect.left += node.offsetLeft; + rect.top += node.offsetTop; + node = node.offsetParent; + } + } + rect.left += window.pageXOffset; + rect.top += window.pageYOffset; + return rect; + }; + + /** + * Compute the new location of the caret or selection and update + * the element as needed. + * @param {boolean} scrollToSelection If true, will also scroll the page + * to the caret / selection location. + */ + CaretBrowsing.updateCaretOrSelection = + function(scrollToSelection) { + const sel = window.getSelection(); + if (sel.rangeCount === 0) { + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.style.opacity = "0.0"; + } + return; + } + + const range = sel.getRangeAt(0); + if (!range) { + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.style.opacity = "0.0"; + } + return; + } + + if (CaretBrowsing.isControlThatNeedsArrowKeys( + document.activeElement)) { + let node = document.activeElement; + CaretBrowsing.caretWidth = node.offsetWidth; + CaretBrowsing.caretHeight = node.offsetHeight; + CaretBrowsing.caretX = 0; + CaretBrowsing.caretY = 0; + while (node.offsetParent) { + CaretBrowsing.caretX += node.offsetLeft; + CaretBrowsing.caretY += node.offsetTop; + node = node.offsetParent; + } + CaretBrowsing.isSelectionCollapsed = false; + } else if (range.startOffset !== range.endOffset || + range.startContainer !== range.endContainer) { + const rect = range.getBoundingClientRect(); + if (!rect) { + return; + } + CaretBrowsing.caretX = rect.left + window.pageXOffset; + CaretBrowsing.caretY = rect.top + window.pageYOffset; + CaretBrowsing.caretWidth = rect.width; + CaretBrowsing.caretHeight = rect.height; + CaretBrowsing.isSelectionCollapsed = false; + } else { + const rect = CaretBrowsing.getCursorRect( + new Cursor(range.startContainer, + range.startOffset, + TraverseUtil.getNodeText(range.startContainer))); + CaretBrowsing.caretX = rect.left; + CaretBrowsing.caretY = rect.top; + CaretBrowsing.caretWidth = rect.width; + CaretBrowsing.caretHeight = rect.height; + CaretBrowsing.isSelectionCollapsed = true; + } + + if (CaretBrowsing.caretElement) { + const element = CaretBrowsing.caretElement; + if (CaretBrowsing.isSelectionCollapsed) { + element.style.opacity = "1.0"; + element.style.left = `${CaretBrowsing.caretX}px`; + element.style.top = `${CaretBrowsing.caretY}px`; + element.style.width = `${CaretBrowsing.caretWidth}px`; + element.style.height = `${CaretBrowsing.caretHeight}px`; + } else { + element.style.opacity = "0.0"; + } + } else { + CaretBrowsing.createCaretElement(); + } + + let elem = range.startContainer; + if (elem.constructor === Text) { + elem = elem.parentElement; + } + const style = window.getComputedStyle(elem); + const bg = axs.utils.getBgColor(style, elem); + const fg = axs.utils.getFgColor(style, elem, bg); + CaretBrowsing.caretBackground = axs.color.colorToString(bg); + CaretBrowsing.caretForeground = axs.color.colorToString(fg); + + if (scrollToSelection) { + // Scroll just to the "focus" position of the selection, + // the part the user is manipulating. + const rect = CaretBrowsing.getCursorRect( + new Cursor(sel.focusNode, sel.focusOffset, + TraverseUtil.getNodeText(sel.focusNode))); + + const yscroll = window.pageYOffset; + const pageHeight = window.innerHeight; + const caretY = rect.top; + const caretHeight = Math.min(rect.height, 30); + if (yscroll + pageHeight < caretY + caretHeight) { + window.scroll(0, (caretY + caretHeight - pageHeight + 100)); + } else if (caretY < yscroll) { + window.scroll(0, (caretY - 100)); + } + } + }; + + CaretBrowsing.move = function(direction, granularity) { + let action = "move"; + if (CaretBrowsing.selectionEnabled) { + action = "extend"; + } + window. + getSelection(). + modify(action, direction, granularity); + + if (CaretBrowsing.isWindows && + (direction === "forward" || + direction === "right") && + granularity === "word") { + CaretBrowsing.move("left", "character"); + } else { + window.setTimeout(() => { + CaretBrowsing.updateCaretOrSelection(true); + }, 0); + } + }; + + CaretBrowsing.moveToBlock = function(paragraph, boundary) { + let action = "move"; + if (CaretBrowsing.selectionEnabled) { + action = "extend"; + } + window. + getSelection(). + modify(action, paragraph, "paragraph"); + + window. + getSelection(). + modify(action, boundary, "paragraphboundary"); + + window.setTimeout(() => { + CaretBrowsing.updateCaretOrSelection(true); + }, 0); + }; + + CaretBrowsing.toggle = function(value) { + if (CaretBrowsing.forceEnabled) { + CaretBrowsing.recreateCaretElement(); + return; + } + + if (value === undefined) { + CaretBrowsing.isEnabled = !CaretBrowsing.isEnabled; + } else { + CaretBrowsing.isEnabled = value; + } + CaretBrowsing.updateIsCaretVisible(); + }; + + /** + * Event handler, called when the mouse is clicked. Chrome already + * sets the selection when the mouse is clicked, all we need to do is + * update our cursor. + * @param {Event} evt The DOM event. + * @return {boolean} True if the default action should be performed. + */ + CaretBrowsing.onClick = function() { + if (!CaretBrowsing.isEnabled) { + return true; + } + window.setTimeout(() => { + CaretBrowsing.targetX = null; + CaretBrowsing.updateCaretOrSelection(false); + }, 0); + return true; + }; + + /** + * Update whether or not the caret is visible, based on whether caret browsing + * is enabled and whether this window / iframe has focus. + */ + CaretBrowsing.updateIsCaretVisible = function() { + CaretBrowsing.isCaretVisible = + (CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused); + if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) { + CaretBrowsing.setInitialCursor(); + CaretBrowsing.updateCaretOrSelection(true); + } else if (!CaretBrowsing.isCaretVisible && + CaretBrowsing.caretElement) { + window.clearInterval(CaretBrowsing.blinkFunctionId); + if (CaretBrowsing.caretElement) { + CaretBrowsing.isSelectionCollapsed = false; + CaretBrowsing.caretElement.parentElement.removeChild( + CaretBrowsing.caretElement); + CaretBrowsing.caretElement = null; + } + } + }; + + CaretBrowsing.onWindowFocus = function() { + CaretBrowsing.isWindowFocused = true; + CaretBrowsing.updateIsCaretVisible(); + }; + + CaretBrowsing.onWindowBlur = function() { + CaretBrowsing.isWindowFocused = false; + CaretBrowsing.updateIsCaretVisible(); + }; + + CaretBrowsing.init = function() { + CaretBrowsing.isWindowFocused = document.hasFocus(); + + document.addEventListener("click", CaretBrowsing.onClick, false); + window.addEventListener("focus", CaretBrowsing.onWindowFocus, false); + window.addEventListener("blur", CaretBrowsing.onWindowBlur, false); + }; + + window.setTimeout(() => { + if (!window.caretBrowsingLoaded) { + window.caretBrowsingLoaded = true; + CaretBrowsing.init(); + + if (document.body && + document.body.getAttribute("caretbrowsing") === "on") { + CaretBrowsing.forceEnabled = true; + CaretBrowsing.isEnabled = true; + CaretBrowsing.updateIsCaretVisible(); + } + } + }, 0); + + const funcs = {}; + + funcs.setInitialCursor = () => { + if (!CaretBrowsing.initiated) { + CaretBrowsing.setInitialCursor(); + return; + } + + if (window.getSelection().toString().length === 0) { + positionCaret(); + } + CaretBrowsing.toggle(); + }; + + funcs.setPlatform = (platform) => { + CaretBrowsing.isWindows = platform.startsWith("win"); + }; + + funcs.disableCaret = () => { + CaretBrowsing.toggle(false); + }; + + funcs.toggle = () => { + CaretBrowsing.toggle(); + }; + + funcs.moveRight = () => { + CaretBrowsing.move("right", "character"); + }; + + funcs.moveLeft = () => { + CaretBrowsing.move("left", "character"); + }; + + funcs.moveDown = () => { + CaretBrowsing.move("forward", "line"); + }; + + funcs.moveUp = () => { + CaretBrowsing.move("backward", "line"); + }; + + funcs.moveToEndOfWord = () => { + funcs.moveToNextWord(); + funcs.moveLeft(); + }; + + funcs.moveToNextWord = () => { + CaretBrowsing.move("forward", "word"); + funcs.moveRight(); + }; + + funcs.moveToPreviousWord = () => { + CaretBrowsing.move("backward", "word"); + }; + + funcs.moveToStartOfLine = () => { + CaretBrowsing.move("left", "lineboundary"); + }; + + funcs.moveToEndOfLine = () => { + CaretBrowsing.move("right", "lineboundary"); + }; + + funcs.moveToStartOfNextBlock = () => { + CaretBrowsing.moveToBlock("forward", "backward"); + }; + + funcs.moveToStartOfPrevBlock = () => { + CaretBrowsing.moveToBlock("backward", "backward"); + }; + + funcs.moveToEndOfNextBlock = () => { + CaretBrowsing.moveToBlock("forward", "forward"); + }; + + funcs.moveToEndOfPrevBlock = () => { + CaretBrowsing.moveToBlock("backward", "forward"); + }; + + funcs.moveToStartOfDocument = () => { + CaretBrowsing.move("backward", "documentboundary"); + }; + + funcs.moveToEndOfDocument = () => { + CaretBrowsing.move("forward", "documentboundary"); + funcs.moveLeft(); + }; + + funcs.dropSelection = () => { + window.getSelection().removeAllRanges(); + }; + + funcs.getSelection = () => window.getSelection().toString(); + + funcs.toggleSelection = () => { + CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled; + }; + + return funcs; +})(); diff --git a/qutebrowser/javascript/webelem.js b/qutebrowser/javascript/webelem.js index 61b537504..d635de412 100644 --- a/qutebrowser/javascript/webelem.js +++ b/qutebrowser/javascript/webelem.js @@ -40,7 +40,54 @@ window._qutebrowser.webelem = (function() { const funcs = {}; const elements = []; - function serialize_elem(elem) { + function get_frame_offset(frame) { + if (frame === null) { + // Dummy object with zero offset + return { + "top": 0, + "right": 0, + "bottom": 0, + "left": 0, + "height": 0, + "width": 0, + }; + } + return frame.frameElement.getBoundingClientRect(); + } + + // Add an offset rect to a base rect, for use with frames + function add_offset_rect(base, offset) { + return { + "top": base.top + offset.top, + "left": base.left + offset.left, + "bottom": base.bottom + offset.top, + "right": base.right + offset.left, + "height": base.height, + "width": base.width, + }; + } + + function get_caret_position(elem, frame) { + // With older Chromium versions (and QtWebKit), InvalidStateError will + // be thrown if elem doesn't have selectionStart. + // With newer Chromium versions (>= Qt 5.10), we get null. + try { + return elem.selectionStart; + } catch (err) { + if (err instanceof (frame + ? frame.DOMException + : DOMException) && + err.name === "InvalidStateError") { + // nothing to do, caret_position is already null + } else { + // not the droid we're looking for + throw err; + } + } + return null; + } + + function serialize_elem(elem, frame = null) { if (!elem) { return null; } @@ -48,21 +95,7 @@ window._qutebrowser.webelem = (function() { const id = elements.length; elements[id] = elem; - // With older Chromium versions (and QtWebKit), InvalidStateError will - // be thrown if elem doesn't have selectionStart. - // With newer Chromium versions (>= Qt 5.10), we get null. - let caret_position = null; - try { - caret_position = elem.selectionStart; - } catch (err) { - if (err instanceof DOMException && - err.name === "InvalidStateError") { - // nothing to do, caret_position is already null - } else { - // not the droid we're looking for - throw err; - } - } + const caret_position = get_caret_position(elem, frame); const out = { "id": id, @@ -115,16 +148,13 @@ window._qutebrowser.webelem = (function() { out.attributes = attributes; const client_rects = elem.getClientRects(); + const frame_offset_rect = get_frame_offset(frame); + for (let k = 0; k < client_rects.length; ++k) { const rect = client_rects[k]; - out.rects.push({ - "top": rect.top, - "right": rect.right, - "bottom": rect.bottom, - "left": rect.left, - "height": rect.height, - "width": rect.width, - }); + out.rects.push( + add_offset_rect(rect, frame_offset_rect) + ); } // console.log(JSON.stringify(out)); @@ -132,9 +162,7 @@ window._qutebrowser.webelem = (function() { return out; } - function is_visible(elem) { - // FIXME:qtwebengine Handle frames and iframes - + function is_visible(elem, frame = null) { // Adopted from vimperator: // https://github.com/vimperator/vimperator-labs/blob/vimperator-3.14.0/common/content/hints.js#L259-L285 // FIXME:qtwebengine we might need something more sophisticated like @@ -142,7 +170,8 @@ window._qutebrowser.webelem = (function() { // https://github.com/1995eaton/chromium-vim/blob/1.2.85/content_scripts/dom.js#L74-L134 const win = elem.ownerDocument.defaultView; - let rect = elem.getBoundingClientRect(); + const offset_rect = get_frame_offset(frame); + let rect = add_offset_rect(elem.getBoundingClientRect(), offset_rect); if (!rect || rect.top > window.innerHeight || @@ -174,8 +203,20 @@ window._qutebrowser.webelem = (function() { return true; } + // Returns true if the iframe is accessible without + // cross domain errors, else false. + function iframe_same_domain(frame) { + try { + frame.document; // eslint-disable-line no-unused-expressions + return true; + } catch (err) { + return false; + } + } + funcs.find_css = (selector, only_visible) => { const elems = document.querySelectorAll(selector); + const subelem_frames = window.frames; const out = []; for (let i = 0; i < elems.length; ++i) { @@ -184,14 +225,70 @@ window._qutebrowser.webelem = (function() { } } + // Recurse into frames and add them + for (let i = 0; i < subelem_frames.length; i++) { + if (iframe_same_domain(subelem_frames[i])) { + const frame = subelem_frames[i]; + const subelems = frame.document. + querySelectorAll(selector); + for (let elem_num = 0; elem_num < subelems.length; ++elem_num) { + if (!only_visible || + is_visible(subelems[elem_num], frame)) { + out.push(serialize_elem(subelems[elem_num], frame)); + } + } + } + } + return out; }; + // Runs a function in a frame until the result is not null, then return + function run_frames(func) { + for (let i = 0; i < window.frames.length; ++i) { + const frame = window.frames[i]; + if (iframe_same_domain(frame)) { + const result = func(frame); + if (result) { + return result; + } + } + } + return null; + } + funcs.find_id = (id) => { const elem = document.getElementById(id); - return serialize_elem(elem); + if (elem) { + return serialize_elem(elem); + } + + const serialized_elem = run_frames((frame) => { + const element = frame.window.document.getElementById(id); + return serialize_elem(element, frame); + }); + + if (serialized_elem) { + return serialized_elem; + } + + return null; }; + // Check if elem is an iframe, and if so, return the result of func on it. + // If no iframes match, return null + function call_if_frame(elem, func) { + // Check if elem is a frame, and if so, call func on the window + if ("contentWindow" in elem) { + const frame = elem.contentWindow; + if (iframe_same_domain(frame) && + "frameElement" in elem.contentWindow) { + return func(frame); + } + } + return null; + } + funcs.find_focused = () => { const elem = document.activeElement; @@ -201,26 +298,52 @@ window._qutebrowser.webelem = (function() { return null; } + // Check if we got an iframe, and if so, recurse inside of it + const frame_elem = call_if_frame(elem, + (frame) => serialize_elem(frame.document.activeElement, frame)); + + if (frame_elem !== null) { + return frame_elem; + } return serialize_elem(elem); }; funcs.find_at_pos = (x, y) => { - // FIXME:qtwebengine - // If the element at the specified point belongs to another document - // (for example, an iframe's subdocument), the subdocument's parent - // element is returned (the iframe itself). - const elem = document.elementFromPoint(x, y); + + + // Check if we got an iframe, and if so, recurse inside of it + const frame_elem = call_if_frame(elem, + (frame) => { + // Subtract offsets due to being in an iframe + const frame_offset_rect = + frame.frameElement.getBoundingClientRect(); + return serialize_elem(frame.document. + elementFromPoint(x - frame_offset_rect.left, + y - frame_offset_rect.top), frame); + }); + + if (frame_elem !== null) { + return frame_elem; + } return serialize_elem(elem); }; // Function for returning a selection to python (so we can click it) funcs.find_selected_link = () => { const elem = window.getSelection().anchorNode; - if (!elem) { - return null; + if (elem) { + return serialize_elem(elem.parentNode); } - return serialize_elem(elem.parentNode); + + const serialized_frame_elem = run_frames((frame) => { + const node = frame.window.getSelection().anchorNode; + if (node) { + return serialize_elem(node.parentNode, frame); + } + return null; + }); + return serialized_frame_elem; }; funcs.set_value = (id, value) => { diff --git a/qutebrowser/keyinput/__init__.py b/qutebrowser/keyinput/__init__.py index c6b95b8a9..1cff4943b 100644 --- a/qutebrowser/keyinput/__init__.py +++ b/qutebrowser/keyinput/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index b435ac52c..89120f922 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/keyparser.py b/qutebrowser/keyinput/keyparser.py index 8fcb53035..8ae27412e 100644 --- a/qutebrowser/keyinput/keyparser.py +++ b/qutebrowser/keyinput/keyparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py index 9e3667590..08740d80d 100644 --- a/qutebrowser/keyinput/macros.py +++ b/qutebrowser/keyinput/macros.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Jan Verbeek (blyxxyz) +# Copyright 2016-2018 Jan Verbeek (blyxxyz) # # This file is part of qutebrowser. # diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index b8c46476f..e32830f50 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -29,7 +29,6 @@ from qutebrowser.keyinput import modeparsers, keyparser from qutebrowser.config import config from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import usertypes, log, objreg, utils -from qutebrowser.misc import objects @attr.s(frozen=True) @@ -267,10 +266,6 @@ class ModeManager(QObject): usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: raise cmdexc.CommandError( "Mode {} can't be entered manually!".format(mode)) - elif (m == usertypes.KeyMode.caret and - objects.backend == usertypes.Backend.QtWebEngine): - raise cmdexc.CommandError("Caret mode is not supported with " - "QtWebEngine yet.") self.enter(m, 'command') diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 7c2088133..b739d38a1 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/__init__.py b/qutebrowser/mainwindow/__init__.py index 43eb563a9..1b76e9b5a 100644 --- a/qutebrowser/mainwindow/__init__.py +++ b/qutebrowser/mainwindow/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index a5faf6dd1..235c15295 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -105,8 +105,7 @@ def raise_window(window, alert=True): QApplication.instance().alert(window) -# WORKAROUND for https://github.com/PyCQA/pylint/issues/1770 -def get_target_window(): # pylint: disable=inconsistent-return-statements +def get_target_window(): """Get the target window for new tabs, or None if none exist.""" try: win_mode = config.val.new_instance_open_target_window @@ -429,7 +428,6 @@ class MainWindow(QWidget): status = self._get_object('statusbar') keyparsers = self._get_object('keyparsers') completion_obj = self._get_object('completion') - tabs = self._get_object('tabbed-browser') cmd = self._get_object('status-command') message_bridge = self._get_object('message-bridge') mode_manager = self._get_object('mode-manager') @@ -449,7 +447,7 @@ class MainWindow(QWidget): status.keystring.setText) cmd.got_cmd[str].connect(self._commandrunner.run_safely) cmd.got_cmd[str, int].connect(self._commandrunner.run_safely) - cmd.returnPressed.connect(tabs.on_cmd_return_pressed) + cmd.returnPressed.connect(self.tabbed_browser.on_cmd_return_pressed) # key hint popup for mode, parser in keyparsers.items(): @@ -467,25 +465,31 @@ class MainWindow(QWidget): message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text) # statusbar - tabs.current_tab_changed.connect(status.on_tab_changed) + self.tabbed_browser.current_tab_changed.connect(status.on_tab_changed) - tabs.cur_progress.connect(status.prog.setValue) - tabs.cur_load_finished.connect(status.prog.hide) - tabs.cur_load_started.connect(status.prog.on_load_started) + self.tabbed_browser.cur_progress.connect(status.prog.setValue) + self.tabbed_browser.cur_load_finished.connect(status.prog.hide) + self.tabbed_browser.cur_load_started.connect( + status.prog.on_load_started) - tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc) - tabs.tab_index_changed.connect(status.tabindex.on_tab_index_changed) + self.tabbed_browser.cur_scroll_perc_changed.connect( + status.percentage.set_perc) + self.tabbed_browser.tab_index_changed.connect( + status.tabindex.on_tab_index_changed) - tabs.cur_url_changed.connect(status.url.set_url) - tabs.cur_url_changed.connect(functools.partial( - status.backforward.on_tab_cur_url_changed, tabs=tabs)) - tabs.cur_link_hovered.connect(status.url.set_hover_url) - tabs.cur_load_status_changed.connect(status.url.on_load_status_changed) - tabs.cur_fullscreen_requested.connect(self._on_fullscreen_requested) - tabs.cur_fullscreen_requested.connect(status.maybe_hide) + self.tabbed_browser.cur_url_changed.connect(status.url.set_url) + self.tabbed_browser.cur_url_changed.connect(functools.partial( + status.backforward.on_tab_cur_url_changed, + tabs=self.tabbed_browser)) + self.tabbed_browser.cur_link_hovered.connect(status.url.set_hover_url) + self.tabbed_browser.cur_load_status_changed.connect( + status.url.on_load_status_changed) + self.tabbed_browser.cur_fullscreen_requested.connect( + self._on_fullscreen_requested) + self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide) # command input / completion - mode_manager.left.connect(tabs.on_mode_left) + mode_manager.left.connect(self.tabbed_browser.on_mode_left) cmd.clear_completion_selection.connect( completion_obj.on_clear_completion_selection) cmd.hide_completion.connect(completion_obj.hide) diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 5e389e23c..43ddd5248 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 3d21a52e0..df4c45096 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/__init__.py b/qutebrowser/mainwindow/statusbar/__init__.py index eb3ed7193..eee7cf990 100644 --- a/qutebrowser/mainwindow/statusbar/__init__.py +++ b/qutebrowser/mainwindow/statusbar/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/backforward.py b/qutebrowser/mainwindow/statusbar/backforward.py index 3a566e105..b214b894d 100644 --- a/qutebrowser/mainwindow/statusbar/backforward.py +++ b/qutebrowser/mainwindow/statusbar/backforward.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index ae7a3954d..ae6832eb0 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 26ba802c0..681e20ac5 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -193,7 +193,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): if run: self.command_accept() - ed.editing_finished.connect(callback) + ed.file_updated.connect(callback) ed.edit(self.text()) @pyqtSlot(usertypes.KeyMode) diff --git a/qutebrowser/mainwindow/statusbar/keystring.py b/qutebrowser/mainwindow/statusbar/keystring.py index dd9825ab2..29dd3b790 100644 --- a/qutebrowser/mainwindow/statusbar/keystring.py +++ b/qutebrowser/mainwindow/statusbar/keystring.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/percentage.py b/qutebrowser/mainwindow/statusbar/percentage.py index 0d75e8163..a3f30e50d 100644 --- a/qutebrowser/mainwindow/statusbar/percentage.py +++ b/qutebrowser/mainwindow/statusbar/percentage.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/progress.py b/qutebrowser/mainwindow/statusbar/progress.py index c8707e0b1..930282771 100644 --- a/qutebrowser/mainwindow/statusbar/progress.py +++ b/qutebrowser/mainwindow/statusbar/progress.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/tabindex.py b/qutebrowser/mainwindow/statusbar/tabindex.py index 6a4cc987c..47a775f34 100644 --- a/qutebrowser/mainwindow/statusbar/tabindex.py +++ b/qutebrowser/mainwindow/statusbar/tabindex.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/text.py b/qutebrowser/mainwindow/statusbar/text.py index b232cedc8..0a57446f1 100644 --- a/qutebrowser/mainwindow/statusbar/text.py +++ b/qutebrowser/mainwindow/statusbar/text.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/textbase.py b/qutebrowser/mainwindow/statusbar/textbase.py index 0ae271191..5c157e52a 100644 --- a/qutebrowser/mainwindow/statusbar/textbase.py +++ b/qutebrowser/mainwindow/statusbar/textbase.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/statusbar/url.py b/qutebrowser/mainwindow/statusbar/url.py index a91c67550..f24e79834 100644 --- a/qutebrowser/mainwindow/statusbar/url.py +++ b/qutebrowser/mainwindow/statusbar/url.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index d8056d853..cd9f52b37 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -643,6 +643,9 @@ class TabbedBrowser(tabwidget.TabWidget): @pyqtSlot(int) def on_current_changed(self, idx): """Set last-focused-tab and leave hinting mode when focus changed.""" + mode_on_change = config.val.tabs.mode_on_change + modes_to_save = [usertypes.KeyMode.insert, + usertypes.KeyMode.passthrough] if idx == -1 or self.shutting_down: # closing the last tab (before quitting) or shutting down return @@ -651,17 +654,23 @@ class TabbedBrowser(tabwidget.TabWidget): log.webview.debug("on_current_changed got called with invalid " "index {}".format(idx)) return + if self._now_focused is not None and mode_on_change == 'restore': + current_mode = modeman.instance(self._win_id).mode + if current_mode not in modes_to_save: + current_mode = usertypes.KeyMode.normal + self._now_focused.data.input_mode = current_mode log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret] - if not config.val.tabs.persist_mode_on_change: - modes_to_leave += [usertypes.KeyMode.insert, - usertypes.KeyMode.passthrough] + if mode_on_change != 'persist': + modes_to_leave += modes_to_save for mode in modes_to_leave: modeman.leave(self._win_id, mode, 'tab changed', maybe=True) - + if mode_on_change == 'restore': + modeman.enter(self._win_id, tab.data.input_mode, + 'restore input mode for tab') if self._now_focused is not None: objreg.register('last-focused-tab', self._now_focused, update=True, scope='window', window=self._win_id) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 28cfac0fb..da1f2624b 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -148,7 +148,10 @@ class TabWidget(QTabWidget): fields['index'] = idx + 1 title = '' if fmt is None else fmt.format(**fields) - self.tabBar().setTabText(idx, title) + tabbar = self.tabBar() + + tabbar.setTabText(idx, title) + tabbar.setTabToolTip(idx, title) def get_tab_fields(self, idx): """Get the tab field data.""" diff --git a/qutebrowser/misc/__init__.py b/qutebrowser/misc/__init__.py index 03ad27aa8..2be43490a 100644 --- a/qutebrowser/misc/__init__.py +++ b/qutebrowser/misc/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/autoupdate.py b/qutebrowser/misc/autoupdate.py index 15a3b6670..d056d61b6 100644 --- a/qutebrowser/misc/autoupdate.py +++ b/qutebrowser/misc/autoupdate.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 8cc5b71b2..d6ac36d10 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -191,7 +191,7 @@ def _handle_nouveau_graphics(): text="

There are two ways to fix this:

" "

Forcing software rendering

" "

This allows you to use the newer QtWebEngine backend (based " - "on Chromium) but could have noticable performance impact " + "on Chromium) but could have noticeable performance impact " "(depending on your hardware). " "This sets the qt.force_software_rendering = True option " "(if you have a config.py file, you'll need to set this " diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index da71b278d..50330ef88 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The-Compiler) +# Copyright 2014-2018 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/cmdhistory.py b/qutebrowser/misc/cmdhistory.py index a24f6b816..9fa273c1c 100644 --- a/qutebrowser/misc/cmdhistory.py +++ b/qutebrowser/misc/cmdhistory.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index b5ca8dd78..661c7b805 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 9ef0e573c..4d395b8ec 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index b8d63ad58..5224c31cc 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 10530eecf..c78d0848d 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The-Compiler) +# Copyright 2014-2018 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 553c700e7..154660001 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -22,7 +22,8 @@ import os import tempfile -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QProcess +from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QProcess, + QFileSystemWatcher) from qutebrowser.config import config from qutebrowser.utils import message, log @@ -39,19 +40,36 @@ class ExternalEditor(QObject): _remove_file: Whether the file should be removed when the editor is closed. _proc: The GUIProcess of the editor. + _watcher: A QFileSystemWatcher to watch the edited file for changes. + Only set if watch=True. + + Signals: + file_updated: The text in the edited file was updated. + arg: The new text. + editing_finished: The editor process was closed. """ - editing_finished = pyqtSignal(str) + file_updated = pyqtSignal(str) + editing_finished = pyqtSignal() - def __init__(self, parent=None): + def __init__(self, parent=None, watch=False): super().__init__(parent) self._filename = None self._proc = None self._remove_file = None + self._watcher = QFileSystemWatcher(parent=self) if watch else None + self._content = None def _cleanup(self): """Clean up temporary files after the editor closed.""" assert self._remove_file is not None + + watched_files = self._watcher.files() if self._watcher else [] + if watched_files: + failed = self._watcher.removePaths(watched_files) + if failed: + log.procs.error("Failed to unwatch paths: {}".format(failed)) + if self._filename is None or not self._remove_file: # Could not create initial file. return @@ -65,7 +83,7 @@ class ExternalEditor(QObject): message.error("Failed to delete tempfile... ({})".format(e)) @pyqtSlot(int, QProcess.ExitStatus) - def on_proc_closed(self, exitcode, exitstatus): + def on_proc_closed(self, _exitcode, exitstatus): """Write the editor text into the form field and clean up tempfile. Callback for QProcess when the editor was closed. @@ -75,22 +93,10 @@ class ExternalEditor(QObject): # No error/cleanup here, since we already handle this in # on_proc_error. return - try: - if exitcode != 0: - return - encoding = config.val.editor.encoding - try: - with open(self._filename, 'r', encoding=encoding) as f: - text = f.read() - except OSError as e: - # NOTE: Do not replace this with "raise CommandError" as it's - # executed async. - message.error("Failed to read back edited file: {}".format(e)) - return - log.procs.debug("Read back: {}".format(text)) - self.editing_finished.emit(text) - finally: - self._cleanup() + # do a final read to make sure we don't miss the last signal + self._on_file_changed(self._filename) + self.editing_finished.emit() + self._cleanup() @pyqtSlot(QProcess.ProcessError) def on_proc_error(self, _err): @@ -128,6 +134,21 @@ class ExternalEditor(QObject): line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) + @pyqtSlot(str) + def _on_file_changed(self, path): + try: + with open(path, 'r', encoding=config.val.editor.encoding) as f: + text = f.read() + except OSError as e: + # NOTE: Do not replace this with "raise CommandError" as it's + # executed async. + message.error("Failed to read back edited file: {}".format(e)) + return + log.procs.debug("Read back: {}".format(text)) + if self._content != text: + self._content = text + self.file_updated.emit(text) + def edit_file(self, filename): """Edit the file with the given filename.""" self._filename = filename @@ -147,6 +168,13 @@ class ExternalEditor(QObject): editor = config.val.editor.command executable = editor[0] + if self._watcher: + ok = self._watcher.addPath(self._filename) + if not ok: + log.procs.error("Failed to watch path: {}" + .format(self._filename)) + self._watcher.fileChanged.connect(self._on_file_changed) + args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) self._proc.start(executable, args) diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 384c3ae30..52dc352a7 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index 4d33d487e..b0b41af76 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index c9f982365..8af5ba6bf 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 46d1d3f24..fad58da2d 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index 155cbd1b0..6e50edb9b 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py index 5d3e9b5ca..2e868e27c 100644 --- a/qutebrowser/misc/miscwidgets.py +++ b/qutebrowser/misc/miscwidgets.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/msgbox.py b/qutebrowser/misc/msgbox.py index 459ab8b61..053534158 100644 --- a/qutebrowser/misc/msgbox.py +++ b/qutebrowser/misc/msgbox.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index 60d764620..d6c116eab 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2017 Florian Bruhin (The Compiler) +# Copyright 2017-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/pastebin.py b/qutebrowser/misc/pastebin.py index 9609481b6..d022fc526 100644 --- a/qutebrowser/misc/pastebin.py +++ b/qutebrowser/misc/pastebin.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/readline.py b/qutebrowser/misc/readline.py index 51ce6bb94..3846b77e0 100644 --- a/qutebrowser/misc/readline.py +++ b/qutebrowser/misc/readline.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index 02001902c..0d79c97db 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -77,7 +77,7 @@ class Saveable: Args: is_exit: Whether we're currently exiting qutebrowser. explicit: Whether the user explicitly requested this save. - silent: Don't write informations to log. + silent: Don't write information to log. force: Force saving, no matter what. """ if (self._config_opt is not None and @@ -157,7 +157,7 @@ class SaveManager(QObject): Args: is_exit: Whether we're currently exiting qutebrowser. explicit: Whether this save operation was triggered explicitly. - silent: Don't write informations to log. Used to reduce log spam + silent: Don't write information to log. Used to reduce log spam when autosaving. force: Force saving, no matter what. """ diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index b46b5a26a..a8a652dbb 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -457,7 +457,8 @@ class SessionManager(QObject): @cmdutils.register(instance='session-manager') @cmdutils.argument('name', completion=miscmodels.session) - def session_load(self, name, clear=False, temp=False, force=False): + def session_load(self, name, clear=False, temp=False, force=False, + delete=False): """Load a session. Args: @@ -466,6 +467,7 @@ class SessionManager(QObject): temp: Don't set the current session for :session-save. force: Force loading internal sessions (starting with an underline). + delete: Delete the saved session once it has loaded. """ if name.startswith('_') and not force: raise cmdexc.CommandError("{} is an internal session, use --force " @@ -482,6 +484,17 @@ class SessionManager(QObject): if clear: for win in old_windows: win.close() + if delete: + try: + self.delete(name) + except SessionError as e: + log.sessions.exception("Error while deleting session!") + raise cmdexc.CommandError( + "Error while deleting session: {}" + .format(e)) + else: + log.sessions.debug( + "Loaded & deleted session {}.".format(name)) @cmdutils.register(instance='session-manager') @cmdutils.argument('name', completion=miscmodels.session) diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index 2ae8b3792..31738fbcc 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index 00527e7fe..9b09fb132 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Ryan Roden-Corrent (rcorre) +# Copyright 2016-2018 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # @@ -115,6 +115,11 @@ def init(db_path): raise SqliteError("Failed to open sqlite database at {}: {}" .format(db_path, error.text()), error) + # Enable write-ahead-logging and reduce disk write frequency + # see https://sqlite.org/pragma.html and issues #2930 and #3507 + Query("PRAGMA journal_mode=WAL").run() + Query("PRAGMA synchronous=NORMAL").run() + def close(): """Close the SQL connection.""" diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 4054f85bb..c0d83b7eb 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index fc113a5d4..815ffd5a7 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/__init__.py b/qutebrowser/utils/__init__.py index c28a40b10..16069f3ae 100644 --- a/qutebrowser/utils/__init__.py +++ b/qutebrowser/utils/__init__.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 813b02e7a..06bdd2909 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index e54effe8d..9ae2039ac 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/error.py b/qutebrowser/utils/error.py index 0d045bf19..b9abcdfe1 100644 --- a/qutebrowser/utils/error.py +++ b/qutebrowser/utils/error.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2015-2017 Florian Bruhin (The Compiler) +# Copyright 2015-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index f536fed1f..335b1b983 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2016-2017 Florian Bruhin (The Compiler) +# Copyright 2016-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 802fc5fff..d4ce3368f 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 1a65b6eb8..d6cae1312 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 32395b8bd..49fb021a4 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index f65e4efab..8d44a9eb5 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 87978274f..8a42fb073 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 64f7eb9a8..40f1fa966 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 97e03c072..2ed466dd1 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -537,7 +537,7 @@ def incdec_number(url, incdec, count=1, segments=None): incdec: Either 'increment' or 'decrement' count: The number to increment or decrement by segments: A set of URL segments to search. Valid segments are: - 'host', 'path', 'query', 'anchor'. + 'host', 'port', 'path', 'query', 'anchor'. Default: {'path', 'query'} Return: @@ -550,7 +550,7 @@ def incdec_number(url, incdec, count=1, segments=None): if segments is None: segments = {'path', 'query'} - valid_segments = {'host', 'path', 'query', 'anchor'} + valid_segments = {'host', 'port', 'path', 'query', 'anchor'} if segments - valid_segments: extra_elements = segments - valid_segments raise IncDecError("Invalid segments: {}".format( @@ -561,6 +561,8 @@ def incdec_number(url, incdec, count=1, segments=None): # Order as they appear in a URL segment_modifiers = [ ('host', url.host, url.setHost), + ('port', lambda: str(url.port()) if url.port() > 0 else '', + lambda x: url.setPort(int(x))), ('path', url.path, url.setPath), ('query', url.query, url.setQuery), ('anchor', url.fragment, url.setFragment), diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index aad685d07..dc57e708c 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index f29db578e..079866920 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 47cbf6eff..aff7ed07b 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -1,6 +1,6 @@ # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Utilities to show various version informations.""" +"""Utilities to show various version information.""" import re import sys @@ -167,7 +167,7 @@ def _git_str_subprocess(gitpath): def _release_info(): - """Try to gather distribution release informations. + """Try to gather distribution release information. Return: list of (filename, content) tuples. @@ -335,7 +335,7 @@ def _uptime() -> datetime.timedelta: def version(): - """Return a string with various version informations.""" + """Return a string with various version information.""" lines = ["qutebrowser v{}".format(qutebrowser.__version__)] gitver = _git_str() if gitver is not None: diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 01af53693..1827dc2a3 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: -# Copyright 2014-2017 Florian Bruhin (The Compiler) +# Copyright 2014-2018 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # diff --git a/scripts/cycle-inputs.js b/scripts/cycle-inputs.js new file mode 100644 index 000000000..bb667bda7 --- /dev/null +++ b/scripts/cycle-inputs.js @@ -0,0 +1,46 @@ +/* Cycle text boxes. + * works with the types defined in 'types'. + * Note: Does not work for