Merge branch 'master' into jay/cache-tabsize
This commit is contained in:
commit
f6cc9d53b8
@ -13,6 +13,7 @@ include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include LICENSE doc/* README.asciidoc
|
||||
include misc/qutebrowser.desktop
|
||||
include misc/qutebrowser.appdata.xml
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
include qutebrowser.py
|
||||
|
@ -15,19 +15,74 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.0.2 (unreleased)
|
||||
v1.1.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Fixes
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Fixed workaround for black screens with Nvidia cards
|
||||
- New `{current_url}` field for `window.title_format` and `tabs.title.format`.
|
||||
- New `colors.statusbar.passthrough.fg`/`.bg` settings.
|
||||
- New `completion.delay` and `completion.min_chars` settings to update the
|
||||
completion less often.
|
||||
- New `completion.use_best_match` setting to automatically use the best-matching
|
||||
command in the completion.
|
||||
- New `:tab-give` and `:tab-take` commands, to give tabs to another window, or
|
||||
take them from another window.
|
||||
- New `config.source(...)` method for `config.py` to source another file.
|
||||
- New `keyhint.radius` option to configure the edge rounding for the key hint
|
||||
widget.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- More consistent sizing for favicons with vertical tabs.
|
||||
- Using `:home` on pinned tabs is now prevented.
|
||||
- Fix crash with unknown file types loaded via qute://help
|
||||
- Scrolling performance improvements
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:tab-detach` has been deprecated, as `:tab-give` without argument can be used
|
||||
instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
||||
commands have been removed.
|
||||
|
||||
v1.0.3 (unreleased)
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Handle accessing a locked sqlite database gracefully
|
||||
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix workaround for black screens or crashes with Nvidia cards
|
||||
- Handle a filesystem going read-only gracefully
|
||||
- Fix crash when setting `fonts.monospace`
|
||||
- Fix list options not being modifyable via `.append()` in `config.py`
|
||||
- Mark the content.notifications setting as QtWebKit only correctly
|
||||
- Fix wrong rendering of keys like `<back>` in the completion
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Nicer error messages and other minor improvements
|
||||
|
||||
v1.0.1
|
||||
------
|
||||
|
||||
Fixes
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
@ -65,6 +120,9 @@ Major changes
|
||||
the entire browsing history. The default for
|
||||
`completion.web_history_max_items` got changed to `-1` (unlimited). If the
|
||||
completion is too slow on your machine, try setting it to a few 1000 items.
|
||||
- Up/Down now navigates through the command history instead of selecting
|
||||
completion items. Either use Tab to cycle through the completion, or
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#migrating-older-configurations[restore the old behavior].
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
@ -681,7 +681,6 @@ qutebrowser release
|
||||
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
* Update changelog (remove *(unreleased)*).
|
||||
* Run tests again.
|
||||
* Commit.
|
||||
|
||||
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
|
||||
@ -691,9 +690,9 @@ qutebrowser release
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* Linux: Run `python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* Linux: Run `git checkout v1.$x.$y && python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server: Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist.
|
||||
|
@ -83,13 +83,14 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
||||
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|
||||
|<<tab-close,tab-close>>|Close the current/[count]th tab.
|
||||
|<<tab-detach,tab-detach>>|Detach the current tab to its own window.
|
||||
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|
||||
|<<tab-give,tab-give>>|Give the current tab to a new or existing window if win_id given.
|
||||
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|
||||
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|
||||
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|
||||
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
|
||||
|<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|
||||
|<<tab-take,tab-take>>|Take a tab from another window.
|
||||
|<<unbind,unbind>>|Unbind a keychain.
|
||||
|<<undo,undo>>|Re-open a closed tab.
|
||||
|<<version,version>>|Show version information.
|
||||
@ -946,10 +947,6 @@ Close the current/[count]th tab.
|
||||
==== count
|
||||
The tab index to close
|
||||
|
||||
[[tab-detach]]
|
||||
=== tab-detach
|
||||
Detach the current tab to its own window.
|
||||
|
||||
[[tab-focus]]
|
||||
=== tab-focus
|
||||
Syntax: +:tab-focus ['index']+
|
||||
@ -967,6 +964,17 @@ If neither count nor index are given, it behaves like tab-next. If both are give
|
||||
==== count
|
||||
The tab index to focus, starting with 1.
|
||||
|
||||
[[tab-give]]
|
||||
=== tab-give
|
||||
Syntax: +:tab-give ['win-id']+
|
||||
|
||||
Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
||||
==== positional arguments
|
||||
* +'win-id'+: The window ID of the window to give the current tab to.
|
||||
|
||||
[[tab-move]]
|
||||
=== tab-move
|
||||
Syntax: +:tab-move ['index']+
|
||||
@ -1019,6 +1027,16 @@ Switch to the previous tab, or switch [count] tabs back.
|
||||
==== count
|
||||
How many tabs to switch back.
|
||||
|
||||
[[tab-take]]
|
||||
=== tab-take
|
||||
Syntax: +:tab-take 'index'+
|
||||
|
||||
Take a tab from another window.
|
||||
|
||||
==== positional arguments
|
||||
* +'index'+: The [win_id/]index of the tab to take. Or a substring in which case the closest match will be taken.
|
||||
|
||||
|
||||
[[unbind]]
|
||||
=== unbind
|
||||
Syntax: +:unbind [*--mode* 'mode'] 'key'+
|
||||
|
@ -18,8 +18,9 @@ the old defaults.
|
||||
Other changes in default settings:
|
||||
|
||||
- `<Up>` and `<Down>` in the completion now navigate through command history
|
||||
instead of selecting completion items. You can get back the old behavior by
|
||||
doing:
|
||||
instead of selecting completion items. Use `<Tab>`/`<Shift-Tab>` to cycle
|
||||
through the completion instead.
|
||||
You can get back the old behavior by doing:
|
||||
+
|
||||
----
|
||||
:bind -f -m command <Up> completion-item-focus prev
|
||||
@ -237,6 +238,9 @@ that via `import utils` as well.
|
||||
While it's in some cases possible to import code from the qutebrowser
|
||||
installation, doing so is unsupported and discouraged.
|
||||
|
||||
To read config data from a different file with `c` and `config` available, you
|
||||
can use `config.source('otherfile.py')` in your `config.py`.
|
||||
|
||||
Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -346,15 +350,38 @@ def bind_chained(key, *commands):
|
||||
bind_chained('<Escape>', 'clear-keychain', 'search')
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reading colors from Xresources
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 integration which complains about `c` and `config` being undefined, you can use:
|
||||
You can use something like this to read colors from an `~/.Xresources` file:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
c = c # noqa: F821
|
||||
config = config # noqa: F821
|
||||
def read_xresources(prefix):
|
||||
props = {}
|
||||
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
|
||||
lines = x.stdout.decode().split('\n')
|
||||
for line in filter(lambda l : l.startswith(prefix), lines):
|
||||
prop, _, value = line.partition(':\t')
|
||||
props[prop] = value
|
||||
return props
|
||||
|
||||
xresources = read_xresources('*')
|
||||
c.colors.statusbar.normal.bg = xresources['*background']
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 and pylint integration, it may have some
|
||||
complaints about invalid names, undefined variables, or missing docstrings.
|
||||
You can silence those with:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
c = c # noqa: F821 pylint: disable=E0602,C0103
|
||||
config = config # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
||||
For type annotation support (note that those imports aren't guaranteed to be
|
||||
@ -362,8 +389,9 @@ stable across qutebrowser versions):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
# pylint: disable=C0111
|
||||
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
||||
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
||||
config = config # type: ConfigAPI # noqa: F821
|
||||
c = c # type: ConfigContainer # noqa: F821
|
||||
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
|
||||
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
|
||||
----
|
||||
|
@ -70,6 +70,8 @@
|
||||
|<<colors.statusbar.insert.fg,colors.statusbar.insert.fg>>|Foreground color of the statusbar in insert mode.
|
||||
|<<colors.statusbar.normal.bg,colors.statusbar.normal.bg>>|Background color of the statusbar.
|
||||
|<<colors.statusbar.normal.fg,colors.statusbar.normal.fg>>|Foreground color of the statusbar.
|
||||
|<<colors.statusbar.passthrough.bg,colors.statusbar.passthrough.bg>>|Background color of the statusbar in passthrough mode.
|
||||
|<<colors.statusbar.passthrough.fg,colors.statusbar.passthrough.fg>>|Foreground color of the statusbar in passthrough mode.
|
||||
|<<colors.statusbar.private.bg,colors.statusbar.private.bg>>|Background color of the statusbar in private browsing mode.
|
||||
|<<colors.statusbar.private.fg,colors.statusbar.private.fg>>|Foreground color of the statusbar in private browsing mode.
|
||||
|<<colors.statusbar.progress.bg,colors.statusbar.progress.bg>>|Background color of the progress bar.
|
||||
@ -94,13 +96,16 @@
|
||||
|<<colors.tabs.selected.odd.fg,colors.tabs.selected.odd.fg>>|Foreground color of selected odd tabs.
|
||||
|<<colors.webpage.bg,colors.webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color)
|
||||
|<<completion.cmd_history_max_items,completion.cmd_history_max_items>>|How many commands to save in the command history.
|
||||
|<<completion.delay,completion.delay>>|Delay in ms before updating completions after typing a character.
|
||||
|<<completion.height,completion.height>>|The height of the completion, in px or as percentage of the window.
|
||||
|<<completion.min_chars,completion.min_chars>>|Minimum amount of characters needed to update completions.
|
||||
|<<completion.quick,completion.quick>>|Move on to the next part when there's only one possible completion left.
|
||||
|<<completion.scrollbar.padding,completion.scrollbar.padding>>|Padding of scrollbar handle in the completion window (in px).
|
||||
|<<completion.scrollbar.width,completion.scrollbar.width>>|Width of the scrollbar in the completion window (in px).
|
||||
|<<completion.show,completion.show>>|When to show the autocompletion window.
|
||||
|<<completion.shrink,completion.shrink>>|Shrink the completion to be smaller than the configured size if there are no scrollbars.
|
||||
|<<completion.timestamp_format,completion.timestamp_format>>|How to format timestamps (e.g. for the history completion).
|
||||
|<<completion.use_best_match,completion.use_best_match>>|Whether to execute the best-matching command on a partial match.
|
||||
|<<completion.web_history_max_items,completion.web_history_max_items>>|How many URLs to show in the web history.
|
||||
|<<confirm_quit,confirm_quit>>|Whether quitting the application requires a confirmation.
|
||||
|<<content.cache.appcache,content.cache.appcache>>|Whether support for the HTML 5 web application cache feature is enabled.
|
||||
@ -204,6 +209,7 @@
|
||||
|<<input.spatial_navigation,input.spatial_navigation>>|Enable Spatial Navigation.
|
||||
|<<keyhint.blacklist,keyhint.blacklist>>|Keychains that shouldn't be shown in the keyhint dialog.
|
||||
|<<keyhint.delay,keyhint.delay>>|Time from pressing a key to seeing the keyhint dialog (ms).
|
||||
|<<keyhint.radius,keyhint.radius>>|The rounding radius for the edges of the keyhint dialog.
|
||||
|<<messages.timeout,messages.timeout>>|Time (in ms) to show messages in the statusbar for.
|
||||
|<<messages.unfocused,messages.unfocused>>|Show messages in unfocused windows.
|
||||
|<<new_instance_open_target,new_instance_open_target>>|How to open links in an existing instance if a new one is launched.
|
||||
@ -1093,6 +1099,22 @@ Type: <<types,QssColor>>
|
||||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors.statusbar.passthrough.bg]]
|
||||
=== colors.statusbar.passthrough.bg
|
||||
Background color of the statusbar in passthrough mode.
|
||||
|
||||
Type: <<types,QssColor>>
|
||||
|
||||
Default: +pass:[darkblue]+
|
||||
|
||||
[[colors.statusbar.passthrough.fg]]
|
||||
=== colors.statusbar.passthrough.fg
|
||||
Foreground color of the statusbar in passthrough mode.
|
||||
|
||||
Type: <<types,QssColor>>
|
||||
|
||||
Default: +pass:[white]+
|
||||
|
||||
[[colors.statusbar.private.bg]]
|
||||
=== colors.statusbar.private.bg
|
||||
Background color of the statusbar in private browsing mode.
|
||||
@ -1293,6 +1315,14 @@ Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[100]+
|
||||
|
||||
[[completion.delay]]
|
||||
=== completion.delay
|
||||
Delay in ms before updating completions after typing a character.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[0]+
|
||||
|
||||
[[completion.height]]
|
||||
=== completion.height
|
||||
The height of the completion, in px or as percentage of the window.
|
||||
@ -1301,6 +1331,14 @@ Type: <<types,PercOrInt>>
|
||||
|
||||
Default: +pass:[50%]+
|
||||
|
||||
[[completion.min_chars]]
|
||||
=== completion.min_chars
|
||||
Minimum amount of characters needed to update completions.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[1]+
|
||||
|
||||
[[completion.quick]]
|
||||
=== completion.quick
|
||||
Move on to the next part when there's only one possible completion left.
|
||||
@ -1355,6 +1393,14 @@ Type: <<types,TimestampTemplate>>
|
||||
|
||||
Default: +pass:[%Y-%m-%d]+
|
||||
|
||||
[[completion.use_best_match]]
|
||||
=== completion.use_best_match
|
||||
Whether to execute the best-matching command on a partial match.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[completion.web_history_max_items]]
|
||||
=== completion.web_history_max_items
|
||||
How many URLs to show in the web history.
|
||||
@ -2369,6 +2415,14 @@ Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[500]+
|
||||
|
||||
[[keyhint.radius]]
|
||||
=== keyhint.radius
|
||||
The rounding radius for the edges of the keyhint dialog.
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[6]+
|
||||
|
||||
[[messages.timeout]]
|
||||
=== messages.timeout
|
||||
Time (in ms) to show messages in the statusbar for.
|
||||
@ -2792,6 +2846,7 @@ The following placeholders are defined:
|
||||
* `{host}`: The host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : The url of the current web page.
|
||||
|
||||
|
||||
Type: <<types,FormatString>>
|
||||
@ -2928,6 +2983,7 @@ The following placeholders are defined:
|
||||
* `{host}`: The host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : The url of the current web page.
|
||||
|
||||
|
||||
Type: <<types,FormatString>>
|
||||
|
@ -41,21 +41,17 @@ Debian Stretch / Ubuntu 17.04 and newer
|
||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml,attr} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
|
||||
----
|
||||
|
||||
Get the qutebrowser package from the
|
||||
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
||||
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
|
||||
|
||||
(If you are using debian testing you can just use the python3-pypeg2 package from the repos)
|
||||
|
||||
Install the packages:
|
||||
|
||||
----
|
||||
# dpkg -i python3-pypeg2_*_all.deb
|
||||
# dpkg -i qutebrowser_*_all.deb
|
||||
# apt install ./python3-pypeg2_*_all.deb
|
||||
# apt install ./qutebrowser_*_all.deb
|
||||
----
|
||||
|
||||
Some additional hints:
|
||||
@ -66,7 +62,7 @@ Some additional hints:
|
||||
`:help` command:
|
||||
+
|
||||
----
|
||||
# apt-get install --no-install-recommends asciidoc source-highlight
|
||||
# apt install --no-install-recommends asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
@ -76,7 +72,7 @@ $ python3 scripts/asciidoc2html.py
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
On Fedora
|
||||
@ -221,6 +217,10 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrows
|
||||
mailinglist] to get notified on new releases). You can install a newer version
|
||||
without uninstalling the older one.
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -261,6 +261,10 @@ Note that you'll need to upgrade to new versions manually (subscribe to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
|
||||
mailinglist] to get notified on new releases).
|
||||
|
||||
The binary release ships with a QtWebEngine built without proprietary codec
|
||||
support. To get support for e.g. h264/h265 videos, you'll need to build
|
||||
QtWebEngine from source yourself with support for that enabled.
|
||||
|
||||
This binary is also available through the
|
||||
https://caskroom.github.io/[Homebrew Cask] package manager:
|
||||
|
||||
@ -355,6 +359,18 @@ also typically means you'll be using an older release of QtWebEngine.
|
||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
|
||||
Building the docs
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
To build the documentation, install `asciidoc` (note that LaTeX which comes as
|
||||
optional/recommended dependency with some distributions is not required).
|
||||
|
||||
Then, run:
|
||||
|
||||
----
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
Creating a wrapper script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
43
misc/qutebrowser.appdata.xml
Normal file
43
misc/qutebrowser.appdata.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017 suve <veg@svgames.pl> -->
|
||||
<component type="desktop">
|
||||
<id>org.qutebrowser.qutebrowser</id>
|
||||
<metadata_license>CC-BY-SA-3.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<name>qutebrowser</name>
|
||||
<summary>A keyboard-driven web browser</summary>
|
||||
<description>
|
||||
<p>
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI.
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
|
||||
and is based on Python and PyQt5.
|
||||
</p>
|
||||
</description>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
<category>WebBrowser</category>
|
||||
</categories>
|
||||
<provides>
|
||||
<binary>qutebrowser</binary>
|
||||
</provides>
|
||||
<launchable type="desktop-id">qutebrowser.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/main.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/downloads.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/completion.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/hints.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://www.qutebrowser.org</url>
|
||||
<url type="faq">https://qutebrowser.org/doc/faq.html</url>
|
||||
<url type="help">https://qutebrowser.org/doc/help/</url>
|
||||
<url type="bugtracker">https://github.com/qutebrowser/qutebrowser/issues/</url>
|
||||
<url type="donation">https://github.com/qutebrowser/qutebrowser#donating</url>
|
||||
</component>
|
@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==36.5.0
|
||||
setuptools==36.6.0
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
|
@ -11,7 +11,7 @@ fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.6
|
||||
hunter==2.0.1
|
||||
hypothesis==3.32.0
|
||||
hypothesis==3.33.0
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.9.6
|
||||
Mako==1.0.7
|
||||
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 0, 1)
|
||||
__version_info__ = (1, 0, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
@ -353,6 +353,10 @@ class CommandDispatcher:
|
||||
Return:
|
||||
A list of URLs that can be opened.
|
||||
"""
|
||||
if isinstance(url, QUrl):
|
||||
yield url
|
||||
return
|
||||
|
||||
force_search = False
|
||||
urllist = [u for u in url.split('\n') if u.strip()]
|
||||
if (len(urllist) > 1 and not urlutils.is_url(urllist[0]) and
|
||||
@ -514,14 +518,54 @@ class CommandDispatcher:
|
||||
return newtab
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_detach(self):
|
||||
"""Detach the current tab to its own window."""
|
||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
||||
def tab_take(self, index):
|
||||
"""Take a tab from another window.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to take. Or a substring
|
||||
in which case the closest match will be taken.
|
||||
"""
|
||||
tabbed_browser, tab = self._resolve_buffer_index(index)
|
||||
|
||||
if tabbed_browser is self._tabbed_browser:
|
||||
raise cmdexc.CommandError("Can't take a tab from the same window")
|
||||
|
||||
self._open(tab.url(), tab=True)
|
||||
tabbed_browser.close_tab(tab, add_undo=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('win_id', completion=miscmodels.window)
|
||||
def tab_give(self, win_id: int = None):
|
||||
"""Give the current tab to a new or existing window if win_id given.
|
||||
|
||||
If no win_id is given, the tab will get detached into a new window.
|
||||
|
||||
Args:
|
||||
win_id: The window ID of the window to give the current tab to.
|
||||
"""
|
||||
if win_id == self._win_id:
|
||||
raise cmdexc.CommandError("Can't give a tab to the same window")
|
||||
|
||||
if win_id is None:
|
||||
if self._count() < 2:
|
||||
raise cmdexc.CommandError("Cannot detach one tab.")
|
||||
url = self._current_url()
|
||||
self._open(url, window=True)
|
||||
cur_widget = self._current_widget()
|
||||
self._tabbed_browser.close_tab(cur_widget, add_undo=False)
|
||||
raise cmdexc.CommandError("Cannot detach from a window with "
|
||||
"only one tab")
|
||||
|
||||
tabbed_browser = self._new_tabbed_browser(
|
||||
private=self._tabbed_browser.private)
|
||||
else:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
|
||||
tabbed_browser.tabopen(self._current_url())
|
||||
self._tabbed_browser.close_tab(self._current_widget(), add_undo=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window', deprecated='Use :tab-give instead!')
|
||||
def tab_detach(self):
|
||||
"""Deprecated way to detach a tab."""
|
||||
self.tab_give()
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
@ -972,63 +1016,13 @@ class CommandDispatcher:
|
||||
else:
|
||||
raise cmdexc.CommandError("Last tab")
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated="Use :open {clipboard}")
|
||||
def paste(self, sel=False, tab=False, bg=False, window=False):
|
||||
"""Open a page from the clipboard.
|
||||
|
||||
If the pasted text contains newlines, each line gets opened in its own
|
||||
tab.
|
||||
def _resolve_buffer_index(self, index):
|
||||
"""Resolve a buffer index to the tabbedbrowser and tab.
|
||||
|
||||
Args:
|
||||
sel: Use the primary selection instead of the clipboard.
|
||||
tab: Open in a new tab.
|
||||
bg: Open in a background tab.
|
||||
window: Open in new window.
|
||||
"""
|
||||
force_search = False
|
||||
if not utils.supports_selection():
|
||||
sel = False
|
||||
try:
|
||||
text = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
text_urls = [u for u in text.split('\n') if u.strip()]
|
||||
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
|
||||
urlutils.get_path_if_valid(
|
||||
text_urls[0], check_exists=True) is None):
|
||||
force_search = True
|
||||
text_urls = [text]
|
||||
for i, text_url in enumerate(text_urls):
|
||||
if not window and i > 0:
|
||||
tab = False
|
||||
bg = True
|
||||
try:
|
||||
url = urlutils.fuzzy_url(text_url, force_search=force_search)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
self._open(url, tab, bg, window)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def buffer(self, index=None, count=None):
|
||||
"""Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary when index is given. If both index and
|
||||
count are given, use count.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to focus. Or a substring
|
||||
index: The [win_id/]index of the tab to be selected. Or a substring
|
||||
in which case the closest match will be focused.
|
||||
count: The tab index to focus, starting with 1.
|
||||
"""
|
||||
if count is not None:
|
||||
index_parts = [count]
|
||||
elif index is None:
|
||||
raise cmdexc.CommandError("buffer: Either a count or the argument "
|
||||
"index must be specified.")
|
||||
else:
|
||||
index_parts = index.split('/', 1)
|
||||
|
||||
try:
|
||||
@ -1066,10 +1060,35 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(
|
||||
"There's no tab with index {}!".format(idx))
|
||||
|
||||
window = objreg.window_registry[win_id]
|
||||
return (tabbed_browser, tabbed_browser.widget(idx-1))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def buffer(self, index=None, count=None):
|
||||
"""Select tab by index or url/title best match.
|
||||
|
||||
Focuses window if necessary when index is given. If both index and
|
||||
count are given, use count.
|
||||
|
||||
Args:
|
||||
index: The [win_id/]index of the tab to focus. Or a substring
|
||||
in which case the closest match will be focused.
|
||||
count: The tab index to focus, starting with 1.
|
||||
"""
|
||||
if count is None and index is None:
|
||||
raise cmdexc.CommandError("buffer: Either a count or the argument "
|
||||
"index must be specified.")
|
||||
|
||||
if count is not None:
|
||||
index = str(count)
|
||||
|
||||
tabbed_browser, tab = self._resolve_buffer_index(index)
|
||||
|
||||
window = tabbed_browser.window()
|
||||
window.activateWindow()
|
||||
window.raise_()
|
||||
tabbed_browser.setCurrentIndex(idx-1)
|
||||
tabbed_browser.setCurrentWidget(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('index', choices=['last'])
|
||||
@ -1195,7 +1214,7 @@ class CommandDispatcher:
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
self._current_widget().openurl(config.val.url.start_pages[0])
|
||||
self.openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, cmd, *args, verbose=False):
|
||||
"""Run a userscript given as argument.
|
||||
@ -1624,14 +1643,6 @@ class CommandDispatcher:
|
||||
except webelem.Error as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher',
|
||||
deprecated="Use :insert-text {primary}",
|
||||
modes=[KeyMode.insert], hide=True, scope='window',
|
||||
backend=usertypes.Backend.QtWebKit)
|
||||
def paste_primary(self):
|
||||
"""Paste the primary selection at cursor position."""
|
||||
self.insert_text(utils.get_clipboard(selection=True, fallback=True))
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
||||
scope='window')
|
||||
def insert_text(self, text):
|
||||
|
@ -29,8 +29,9 @@ import os
|
||||
import time
|
||||
import urllib.parse
|
||||
import textwrap
|
||||
import pkg_resources
|
||||
import mimetypes
|
||||
|
||||
import pkg_resources
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
@ -323,8 +324,10 @@ def qute_help(url):
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
if not urlpath.endswith('.html'):
|
||||
mimetype, _encoding = mimetypes.guess_type(urlpath)
|
||||
assert mimetype is not None, url
|
||||
return mimetype, utils.read_file(path, binary=True)
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
|
@ -36,7 +36,8 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import spell
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.utils import utils, standarddir, javascript, qtutils, message
|
||||
from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
|
||||
message, log)
|
||||
|
||||
# The default QWebEngineProfile
|
||||
default_profile = None
|
||||
@ -145,6 +146,7 @@ class DictionaryLanguageSetter(DefaultProfileSetter):
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DictionaryLanguageSetter!")
|
||||
filenames = [self._find_installed(code) for code in value]
|
||||
log.config.debug("Found dicts: {}".format(filenames))
|
||||
super()._set([f for f in filenames if f], settings)
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ import functools
|
||||
import html as html_utils
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QUrl, QTimer
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@ -293,6 +293,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(tab, parent)
|
||||
self._args = objreg.get('args')
|
||||
self._pos_perc = (0, 0)
|
||||
self._pos_px = QPoint()
|
||||
self._at_bottom = False
|
||||
@ -307,39 +308,31 @@ class WebEngineScroller(browsertab.AbstractScroller):
|
||||
for _ in range(min(count, 5000)):
|
||||
self._tab.key_press(key, modifier)
|
||||
|
||||
@pyqtSlot()
|
||||
def _update_pos(self):
|
||||
@pyqtSlot(QPointF)
|
||||
def _update_pos(self, pos):
|
||||
"""Update the scroll position attributes when it changed."""
|
||||
def update_pos_cb(jsret):
|
||||
"""Callback after getting scroll position via JS."""
|
||||
if jsret is None:
|
||||
# This can happen when the callback would get called after
|
||||
# shutting down a tab
|
||||
return
|
||||
log.webview.vdebug(jsret)
|
||||
assert isinstance(jsret, dict), jsret
|
||||
self._pos_px = QPoint(jsret['px']['x'], jsret['px']['y'])
|
||||
self._pos_px = pos.toPoint()
|
||||
contents_size = self._widget.page().contentsSize()
|
||||
|
||||
dx = jsret['scroll']['width'] - jsret['inner']['width']
|
||||
if dx == 0:
|
||||
scrollable_x = contents_size.width() - self._widget.width()
|
||||
if scrollable_x == 0:
|
||||
perc_x = 0
|
||||
else:
|
||||
perc_x = min(100, round(100 / dx * jsret['px']['x']))
|
||||
perc_x = min(100, round(100 / scrollable_x * pos.x()))
|
||||
|
||||
dy = jsret['scroll']['height'] - jsret['inner']['height']
|
||||
if dy == 0:
|
||||
scrollable_y = contents_size.height() - self._widget.height()
|
||||
if scrollable_y == 0:
|
||||
perc_y = 0
|
||||
else:
|
||||
perc_y = min(100, round(100 / dy * jsret['px']['y']))
|
||||
perc_y = min(100, round(100 / scrollable_y * pos.y()))
|
||||
|
||||
self._at_bottom = math.ceil(jsret['px']['y']) >= dy
|
||||
self._at_bottom = math.ceil(pos.y()) >= scrollable_y
|
||||
|
||||
if (self._pos_perc != (perc_x, perc_y) or
|
||||
'no-scroll-filtering' in self._args.debug_flags):
|
||||
self._pos_perc = perc_x, perc_y
|
||||
|
||||
self.perc_changed.emit(*self._pos_perc)
|
||||
|
||||
js_code = javascript.assemble('scroll', 'pos')
|
||||
self._tab.run_js_async(js_code, update_pos_cb)
|
||||
|
||||
def pos_px(self):
|
||||
return self._pos_px
|
||||
|
||||
|
@ -422,12 +422,13 @@ class WebKitScroller(browsertab.AbstractScroller):
|
||||
else:
|
||||
for val, orientation in [(x, Qt.Horizontal), (y, Qt.Vertical)]:
|
||||
if val is not None:
|
||||
val = qtutils.check_overflow(val, 'int', fatal=False)
|
||||
frame = self._widget.page().mainFrame()
|
||||
m = frame.scrollBarMaximum(orientation)
|
||||
if m == 0:
|
||||
maximum = frame.scrollBarMaximum(orientation)
|
||||
if maximum == 0:
|
||||
continue
|
||||
frame.setScrollBarValue(orientation, int(m * val / 100))
|
||||
pos = int(maximum * val / 100)
|
||||
pos = qtutils.check_overflow(pos, 'int', fatal=False)
|
||||
frame.setScrollBarValue(orientation, pos)
|
||||
|
||||
def _key_press(self, key, count=1, getter_name=None, direction=None):
|
||||
frame = self._widget.page().mainFrame()
|
||||
|
@ -214,12 +214,12 @@ class CommandParser:
|
||||
Return:
|
||||
cmdstr modified to the matching completion or unmodified
|
||||
"""
|
||||
matches = []
|
||||
for valid_command in cmdutils.cmd_dict:
|
||||
if valid_command.find(cmdstr) == 0:
|
||||
matches.append(valid_command)
|
||||
matches = [cmd for cmd in sorted(cmdutils.cmd_dict, key=len)
|
||||
if cmdstr in cmd]
|
||||
if len(matches) == 1:
|
||||
cmdstr = matches[0]
|
||||
elif len(matches) > 1 and config.val.completion.use_best_match:
|
||||
cmdstr = matches[0]
|
||||
return cmdstr
|
||||
|
||||
def _split_args(self, cmd, argstr, keep):
|
||||
|
@ -196,14 +196,25 @@ class Completer(QObject):
|
||||
|
||||
For performance reasons we don't want to block here, instead we do this
|
||||
in the background.
|
||||
|
||||
We delay the update only if we've already input some text and ignore
|
||||
updates if the text is shorter than completion.min_chars (unless we're
|
||||
hitting backspace in which case updates won't be ignored).
|
||||
"""
|
||||
if (self._cmd.cursorPosition() == self._last_cursor_pos and
|
||||
_cmd, _sep, rest = self._cmd.text().partition(' ')
|
||||
input_length = len(rest)
|
||||
if (0 < input_length < config.val.completion.min_chars and
|
||||
self._cmd.cursorPosition() > self._last_cursor_pos):
|
||||
log.completion.debug("Ignoring update because the length of "
|
||||
"the text is less than completion.min_chars.")
|
||||
elif (self._cmd.cursorPosition() == self._last_cursor_pos and
|
||||
self._cmd.text() == self._last_text):
|
||||
log.completion.debug("Ignoring update because there were no "
|
||||
"changes.")
|
||||
else:
|
||||
log.completion.debug("Scheduling completion update.")
|
||||
self._timer.start()
|
||||
start_delay = config.val.completion.delay if self._last_text else 0
|
||||
self._timer.start(start_delay)
|
||||
self._last_cursor_pos = self._cmd.cursorPosition()
|
||||
self._last_text = self._cmd.text()
|
||||
|
||||
|
@ -202,7 +202,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
if index.column() in columns_to_filter and pattern:
|
||||
repl = r'<span class="highlight">\g<0></span>'
|
||||
text = re.sub(re.escape(pattern).replace(r'\ ', r'|'),
|
||||
repl, self._opt.text, flags=re.IGNORECASE)
|
||||
repl, html.escape(self._opt.text),
|
||||
flags=re.IGNORECASE)
|
||||
self._doc.setHtml(text)
|
||||
else:
|
||||
self._doc.setPlainText(self._opt.text)
|
||||
|
@ -122,3 +122,22 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
model.add_category(cat)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
def window(*, info=None): # pylint: disable=unused-argument
|
||||
"""A model to complete on all open windows."""
|
||||
model = completionmodel.CompletionModel(column_widths=(6, 30, 64))
|
||||
|
||||
windows = []
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
tab_titles = (tab.title() for tab in tabbed_browser.widgets())
|
||||
windows.append(("{}".format(win_id),
|
||||
objreg.window_registry[win_id].windowTitle(),
|
||||
", ".join(tab_titles)))
|
||||
|
||||
model.add_category(listcategory.ListCategory("Windows", windows))
|
||||
|
||||
return model
|
||||
|
@ -75,6 +75,10 @@ class ConfigCommands:
|
||||
tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
|
||||
return
|
||||
|
||||
if option.endswith('!'):
|
||||
raise cmdexc.CommandError("Toggling values was moved to the "
|
||||
":config-cycle command")
|
||||
|
||||
if option.endswith('?') and option != '?':
|
||||
self._print_value(option[:-1])
|
||||
return
|
||||
|
@ -678,6 +678,25 @@ completion.web_history_max_items:
|
||||
|
||||
0: no history / -1: unlimited
|
||||
|
||||
completion.delay:
|
||||
default: 0
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
desc: Delay in ms before updating completions after typing a character.
|
||||
|
||||
completion.min_chars:
|
||||
default: 1
|
||||
type:
|
||||
name: Int
|
||||
minval: 1
|
||||
desc: Minimum amount of characters needed to update completions.
|
||||
|
||||
completion.use_best_match:
|
||||
type: Bool
|
||||
default: false
|
||||
desc: Whether to execute the best-matching command on a partial match.
|
||||
|
||||
## downloads
|
||||
|
||||
downloads.location.directory:
|
||||
@ -959,6 +978,13 @@ keyhint.blacklist:
|
||||
Globs are supported, so `;*` will blacklist all keychains starting with `;`.
|
||||
Use `*` to disable keyhints.
|
||||
|
||||
keyhint.radius:
|
||||
type:
|
||||
name: Int
|
||||
minval: 0
|
||||
default: 6
|
||||
desc: The rounding radius for the edges of the keyhint dialog.
|
||||
|
||||
# emacs: '
|
||||
|
||||
keyhint.delay:
|
||||
@ -1224,6 +1250,7 @@ tabs.title.format:
|
||||
- scroll_pos
|
||||
- host
|
||||
- private
|
||||
- current_url
|
||||
none_ok: true
|
||||
desc: |
|
||||
The format to use for the tab title.
|
||||
@ -1239,6 +1266,7 @@ tabs.title.format:
|
||||
* `{host}`: The host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : The url of the current web page.
|
||||
|
||||
tabs.title.format_pinned:
|
||||
default: '{index}'
|
||||
@ -1254,6 +1282,7 @@ tabs.title.format_pinned:
|
||||
- scroll_pos
|
||||
- host
|
||||
- private
|
||||
- current_url
|
||||
none_ok: true
|
||||
desc: The format to use for the tab title for pinned tabs. The same placeholders
|
||||
like for `tabs.title.format` are defined.
|
||||
@ -1371,6 +1400,7 @@ window.title_format:
|
||||
- host
|
||||
- backend
|
||||
- private
|
||||
- current_url
|
||||
default: '{perc}{title}{title_sep}qutebrowser'
|
||||
desc: |
|
||||
The format to use for the window title.
|
||||
@ -1385,6 +1415,7 @@ window.title_format:
|
||||
* `{host}`: The host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : The url of the current web page.
|
||||
|
||||
## zoom
|
||||
|
||||
@ -1469,16 +1500,6 @@ colors.completion.category.border.bottom:
|
||||
type: QssColor
|
||||
desc: Bottom border color of the completion widget category headers.
|
||||
|
||||
colors.statusbar.insert.fg:
|
||||
default: white
|
||||
type: QssColor
|
||||
desc: Foreground color of the statusbar in insert mode.
|
||||
|
||||
colors.statusbar.insert.bg:
|
||||
default: darkgreen
|
||||
type: QssColor
|
||||
desc: Background color of the statusbar in insert mode.
|
||||
|
||||
colors.completion.item.selected.fg:
|
||||
default: black
|
||||
type: QtColor
|
||||
@ -1668,6 +1689,26 @@ colors.statusbar.normal.bg:
|
||||
type: QssColor
|
||||
desc: Background color of the statusbar.
|
||||
|
||||
colors.statusbar.insert.fg:
|
||||
default: white
|
||||
type: QssColor
|
||||
desc: Foreground color of the statusbar in insert mode.
|
||||
|
||||
colors.statusbar.insert.bg:
|
||||
default: darkgreen
|
||||
type: QssColor
|
||||
desc: Background color of the statusbar in insert mode.
|
||||
|
||||
colors.statusbar.passthrough.fg:
|
||||
default: white
|
||||
type: QssColor
|
||||
desc: Foreground color of the statusbar in passthrough mode.
|
||||
|
||||
colors.statusbar.passthrough.bg:
|
||||
default: darkblue
|
||||
type: QssColor
|
||||
desc: Background color of the statusbar in passthrough mode.
|
||||
|
||||
colors.statusbar.private.fg:
|
||||
default: white
|
||||
type: QssColor
|
||||
|
@ -259,6 +259,16 @@ class ConfigAPI:
|
||||
with self._handle_error('unbinding', key):
|
||||
self._keyconfig.unbind(key, mode=mode)
|
||||
|
||||
def source(self, filename):
|
||||
"""Read the given config file from disk."""
|
||||
if not os.path.isabs(filename):
|
||||
filename = str(self.configdir / filename)
|
||||
|
||||
try:
|
||||
read_config_py(filename)
|
||||
except configexc.ConfigFileErrors as e:
|
||||
self.errors += e.errors
|
||||
|
||||
|
||||
class ConfigPyWriter:
|
||||
|
||||
|
@ -104,7 +104,9 @@ def _update_monospace_fonts():
|
||||
continue
|
||||
elif not isinstance(opt.typ, configtypes.Font):
|
||||
continue
|
||||
elif not config.instance.get_obj(name).endswith(' monospace'):
|
||||
|
||||
value = config.instance.get_obj(name)
|
||||
if value is None or not value.endswith(' monospace'):
|
||||
continue
|
||||
|
||||
config.instance.changed.emit(name)
|
||||
|
@ -71,32 +71,5 @@ window._qutebrowser.scroll = (function() {
|
||||
window.scrollBy(dx, dy);
|
||||
};
|
||||
|
||||
funcs.pos = function() {
|
||||
var pos = {
|
||||
"px": {"x": window.scrollX, "y": window.scrollY},
|
||||
"scroll": {
|
||||
"width": Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.documentElement.offsetWidth
|
||||
),
|
||||
"height": Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.documentElement.offsetHeight
|
||||
),
|
||||
},
|
||||
"inner": {
|
||||
"width": window.innerWidth,
|
||||
"height": window.innerHeight,
|
||||
},
|
||||
};
|
||||
|
||||
// console.log(JSON.stringify(pos));
|
||||
return pos;
|
||||
};
|
||||
|
||||
return funcs;
|
||||
})();
|
||||
|
@ -388,20 +388,6 @@ class PromptContainer(QWidget):
|
||||
message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE)
|
||||
question.done()
|
||||
|
||||
@cmdutils.register(instance='prompt-container', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.yesno],
|
||||
deprecated='Use :prompt-accept yes instead!')
|
||||
def prompt_yes(self):
|
||||
"""Answer yes to a yes/no prompt."""
|
||||
self.prompt_accept('yes')
|
||||
|
||||
@cmdutils.register(instance='prompt-container', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.yesno],
|
||||
deprecated='Use :prompt-accept no instead!')
|
||||
def prompt_no(self):
|
||||
"""Answer no to a yes/no prompt."""
|
||||
self.prompt_accept('no')
|
||||
|
||||
@cmdutils.register(instance='prompt-container', hide=True, scope='window',
|
||||
modes=[usertypes.KeyMode.prompt], maxsplit=0)
|
||||
def prompt_open_download(self, cmdline: str = None):
|
||||
|
@ -43,6 +43,7 @@ class ColorFlags:
|
||||
command: If we're currently in command mode.
|
||||
mode: The current caret mode (CaretMode.off/.on/.selection).
|
||||
private: Whether this window is in private browsing mode.
|
||||
passthrough: If we're currently in passthrough-mode.
|
||||
"""
|
||||
|
||||
CaretMode = usertypes.enum('CaretMode', ['off', 'on', 'selection'])
|
||||
@ -51,6 +52,7 @@ class ColorFlags:
|
||||
command = attr.ib(False)
|
||||
caret = attr.ib(CaretMode.off)
|
||||
private = attr.ib(False)
|
||||
passthrough = attr.ib(False)
|
||||
|
||||
def to_stringlist(self):
|
||||
"""Get a string list of set flags used in the stylesheet.
|
||||
@ -66,6 +68,8 @@ class ColorFlags:
|
||||
strings.append('command')
|
||||
if self.private:
|
||||
strings.append('private')
|
||||
if self.passthrough:
|
||||
strings.append('passthrough')
|
||||
|
||||
if self.private and self.command:
|
||||
strings.append('private-command')
|
||||
@ -88,6 +92,7 @@ def _generate_stylesheet():
|
||||
('prompt', 'prompts'),
|
||||
('insert', 'statusbar.insert'),
|
||||
('command', 'statusbar.command'),
|
||||
('passthrough', 'statusbar.passthrough'),
|
||||
('private-command', 'statusbar.command.private'),
|
||||
]
|
||||
stylesheet = """
|
||||
@ -244,6 +249,9 @@ class StatusBar(QWidget):
|
||||
if mode == usertypes.KeyMode.insert:
|
||||
log.statusbar.debug("Setting insert flag to {}".format(val))
|
||||
self._color_flags.insert = val
|
||||
if mode == usertypes.KeyMode.passthrough:
|
||||
log.statusbar.debug("Setting passthrough flag to {}".format(val))
|
||||
self._color_flags.passthrough = val
|
||||
if mode == usertypes.KeyMode.command:
|
||||
log.statusbar.debug("Setting command flag to {}".format(val))
|
||||
self._color_flags.command = val
|
||||
@ -307,7 +315,8 @@ class StatusBar(QWidget):
|
||||
usertypes.KeyMode.command,
|
||||
usertypes.KeyMode.caret,
|
||||
usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
usertypes.KeyMode.yesno,
|
||||
usertypes.KeyMode.passthrough]:
|
||||
self.set_mode_active(mode, True)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode, usertypes.KeyMode)
|
||||
@ -324,7 +333,8 @@ class StatusBar(QWidget):
|
||||
usertypes.KeyMode.command,
|
||||
usertypes.KeyMode.caret,
|
||||
usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
usertypes.KeyMode.yesno,
|
||||
usertypes.KeyMode.passthrough]:
|
||||
self.set_mode_active(old_mode, False)
|
||||
|
||||
@pyqtSlot(browsertab.AbstractTab)
|
||||
|
@ -173,8 +173,18 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
widgets.append(widget)
|
||||
return widgets
|
||||
|
||||
def _update_window_title(self):
|
||||
"""Change the window title to match the current tab."""
|
||||
def _update_window_title(self, field=None):
|
||||
"""Change the window title to match the current tab.
|
||||
|
||||
Args:
|
||||
idx: The tab index to update.
|
||||
field: A field name which was updated. If given, the title
|
||||
is only set if the given field is in the template.
|
||||
"""
|
||||
title_format = config.val.window.title_format
|
||||
if field is not None and ('{' + field + '}') not in title_format:
|
||||
return
|
||||
|
||||
idx = self.currentIndex()
|
||||
if idx == -1:
|
||||
# (e.g. last tab removed)
|
||||
@ -183,7 +193,6 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
fields = self.get_tab_fields(idx)
|
||||
fields['id'] = self._win_id
|
||||
|
||||
title_format = config.val.window.title_format
|
||||
title = title_format.format(**fields)
|
||||
self.window().setWindowTitle(title)
|
||||
|
||||
@ -696,8 +705,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
log.webview.debug("Not updating scroll position because index is "
|
||||
"-1")
|
||||
return
|
||||
self._update_window_title()
|
||||
self._update_tab_title(idx)
|
||||
self._update_window_title('scroll_pos')
|
||||
self._update_tab_title(idx, 'scroll_pos')
|
||||
|
||||
def _on_renderer_process_terminated(self, tab, status, code):
|
||||
"""Show an error when a renderer process terminated."""
|
||||
|
@ -121,21 +121,29 @@ class TabWidget(QTabWidget):
|
||||
"""Get the tab title user data."""
|
||||
return self.tabBar().page_title(idx)
|
||||
|
||||
def _update_tab_title(self, idx):
|
||||
"""Update the tab text for the given tab."""
|
||||
def _update_tab_title(self, idx, field=None):
|
||||
"""Update the tab text for the given tab.
|
||||
|
||||
Args:
|
||||
idx: The tab index to update.
|
||||
field: A field name which was updated. If given, the title
|
||||
is only set if the given field is in the template.
|
||||
"""
|
||||
tab = self.widget(idx)
|
||||
if tab.data.pinned:
|
||||
fmt = config.val.tabs.title.format_pinned
|
||||
else:
|
||||
fmt = config.val.tabs.title.format
|
||||
|
||||
if (field is not None and
|
||||
(fmt is None or ('{' + field + '}') not in fmt)):
|
||||
return
|
||||
|
||||
fields = self.get_tab_fields(idx)
|
||||
fields['title'] = fields['title'].replace('&', '&&')
|
||||
fields['index'] = idx + 1
|
||||
|
||||
fmt = config.val.tabs.title.format
|
||||
fmt_pinned = config.val.tabs.title.format_pinned
|
||||
|
||||
if tab.data.pinned:
|
||||
title = '' if fmt_pinned is None else fmt_pinned.format(**fields)
|
||||
else:
|
||||
title = '' if fmt is None else fmt.format(**fields)
|
||||
|
||||
self.tabBar().setTabText(idx, title)
|
||||
|
||||
def get_tab_fields(self, idx):
|
||||
@ -164,6 +172,11 @@ class TabWidget(QTabWidget):
|
||||
except qtutils.QtValueError:
|
||||
fields['host'] = ''
|
||||
|
||||
try:
|
||||
fields['current_url'] = self.tab_url(idx).url()
|
||||
except qtutils.QtValueError:
|
||||
fields['current_url'] = ''
|
||||
|
||||
y = tab.scroller.pos_perc()[1]
|
||||
if y is None:
|
||||
scroll_pos = '???'
|
||||
@ -659,7 +672,7 @@ class TabBarStyle(QCommonStyle):
|
||||
icon_state = (QIcon.On if opt.state & QStyle.State_Selected
|
||||
else QIcon.Off)
|
||||
icon = opt.icon.pixmap(opt.iconSize, icon_mode, icon_state)
|
||||
p.drawPixmap(layouts.icon.x(), layouts.icon.y(), icon)
|
||||
self._style.drawItemPixmap(p, layouts.icon, Qt.AlignCenter, icon)
|
||||
|
||||
def drawControl(self, element, opt, p, widget=None):
|
||||
"""Override drawControl to draw odd tabs in a different color.
|
||||
@ -826,8 +839,7 @@ class TabBarStyle(QCommonStyle):
|
||||
else QIcon.Off)
|
||||
# reserve space for favicon when tab bar is vertical (issue #1968)
|
||||
position = config.val.tabs.position
|
||||
if (opt.icon.isNull() and
|
||||
position in [QTabWidget.East, QTabWidget.West] and
|
||||
if (position in [QTabWidget.East, QTabWidget.West] and
|
||||
config.val.tabs.favicons.show):
|
||||
tab_icon_size = icon_size
|
||||
else:
|
||||
@ -835,6 +847,7 @@ class TabBarStyle(QCommonStyle):
|
||||
tab_icon_size = QSize(
|
||||
min(actual_size.width(), icon_size.width()),
|
||||
min(actual_size.height(), icon_size.height()))
|
||||
|
||||
icon_top = text_rect.center().y() + 1 - tab_icon_size.height() / 2
|
||||
icon_rect = QRect(QPoint(text_rect.left(), icon_top), tab_icon_size)
|
||||
icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect)
|
||||
|
@ -49,7 +49,7 @@ def check_python_version():
|
||||
# pylint: disable=bad-builtin
|
||||
version_str = '.'.join(map(str, sys.version_info[:3]))
|
||||
text = ("At least Python 3.5 is required to run qutebrowser, but " +
|
||||
version_str + " is installed!\n")
|
||||
"it's running with " + version_str + ".\n")
|
||||
if Tk and '--no-err-windows' not in sys.argv: # pragma: no cover
|
||||
root = Tk()
|
||||
root.withdraw()
|
||||
|
@ -54,9 +54,9 @@ class KeyHintView(QLabel):
|
||||
background-color: {{ conf.colors.keyhint.bg }};
|
||||
padding: 6px;
|
||||
{% if conf.statusbar.position == 'top' %}
|
||||
border-bottom-right-radius: 6px;
|
||||
border-bottom-right-radius: {{ conf.keyhint.radius }}px;
|
||||
{% else %}
|
||||
border-top-right-radius: 6px;
|
||||
border-top-right-radius: {{ conf.keyhint.radius }}px;
|
||||
{% endif %}
|
||||
}
|
||||
"""
|
||||
|
@ -65,13 +65,12 @@ class SqliteError(SqlError):
|
||||
log.sql.debug("error code: {}".format(error.nativeErrorCode()))
|
||||
|
||||
# https://sqlite.org/rescode.html
|
||||
environmental_errors = [
|
||||
# SQLITE_LOCKED,
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2930
|
||||
'9',
|
||||
# SQLITE_FULL,
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3004
|
||||
'13',
|
||||
environmental_errors = [
|
||||
'5', # SQLITE_BUSY ("database is locked")
|
||||
'8', # SQLITE_READONLY
|
||||
'13', # SQLITE_FULL
|
||||
]
|
||||
self.environmental = error.nativeErrorCode() in environmental_errors
|
||||
|
||||
|
@ -159,7 +159,8 @@ def debug_flag_error(flag):
|
||||
debug-exit: Turn on debugging of late exit.
|
||||
pdb-postmortem: Drop into pdb on exceptions.
|
||||
"""
|
||||
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history']
|
||||
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
|
||||
'no-scroll-filtering']
|
||||
|
||||
if flag in valid_flags:
|
||||
return flag
|
||||
|
@ -20,6 +20,7 @@
|
||||
"""Utilities to get and initialize data/config paths."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import os.path
|
||||
import contextlib
|
||||
@ -106,6 +107,10 @@ def _init_data(args):
|
||||
if utils.is_windows:
|
||||
app_data_path = _writable_location(QStandardPaths.AppDataLocation)
|
||||
path = os.path.join(app_data_path, 'data')
|
||||
elif sys.platform.startswith('haiku'):
|
||||
# HaikuOS returns an empty value for AppDataLocation
|
||||
config_path = _writable_location(QStandardPaths.ConfigLocation)
|
||||
path = os.path.join(config_path, 'data')
|
||||
else:
|
||||
path = _writable_location(typ)
|
||||
_create(path)
|
||||
|
@ -882,7 +882,7 @@ def yaml_load(f):
|
||||
end = datetime.datetime.now()
|
||||
|
||||
delta = (end - start).total_seconds()
|
||||
deadline = 3 if 'CI' in os.environ else 1
|
||||
deadline = 3 if 'CI' in os.environ else 2
|
||||
if delta > deadline: # pragma: no cover
|
||||
log.misc.warning(
|
||||
"YAML load took unusually long, please report this at "
|
||||
|
@ -150,13 +150,14 @@ def _git_str_subprocess(gitpath):
|
||||
if not os.path.isdir(os.path.join(gitpath, ".git")):
|
||||
return None
|
||||
try:
|
||||
cid = subprocess.check_output(
|
||||
['git', 'describe', '--tags', '--dirty', '--always'],
|
||||
# https://stackoverflow.com/questions/21017300/21017394#21017394
|
||||
commit_hash = subprocess.check_output(
|
||||
['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'],
|
||||
cwd=gitpath).decode('UTF-8').strip()
|
||||
date = subprocess.check_output(
|
||||
['git', 'show', '-s', '--format=%ci', 'HEAD'],
|
||||
cwd=gitpath).decode('UTF-8').strip()
|
||||
return '{} ({})'.format(cid, date)
|
||||
return '{} ({})'.format(commit_hash, date)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return None
|
||||
|
||||
|
@ -20,8 +20,7 @@
|
||||
"""Custom astroid checker for config calls."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import pathlib
|
||||
|
||||
import yaml
|
||||
import astroid
|
||||
@ -30,6 +29,7 @@ from pylint.checkers import utils
|
||||
|
||||
|
||||
OPTIONS = None
|
||||
FAILED_LOAD = False
|
||||
|
||||
|
||||
class ConfigChecker(checkers.BaseChecker):
|
||||
@ -44,6 +44,7 @@ class ConfigChecker(checkers.BaseChecker):
|
||||
None),
|
||||
}
|
||||
priority = -1
|
||||
printed_warning = False
|
||||
|
||||
@utils.check_messages('bad-config-option')
|
||||
def visit_attribute(self, node):
|
||||
@ -58,6 +59,13 @@ class ConfigChecker(checkers.BaseChecker):
|
||||
|
||||
def _check_config(self, node, name):
|
||||
"""Check that we're accessing proper config options."""
|
||||
if FAILED_LOAD:
|
||||
if not ConfigChecker.printed_warning:
|
||||
print("[WARN] Could not find configdata.yml. Please run "
|
||||
"pylint from qutebrowser root.", file=sys.stderr)
|
||||
print("Skipping some checks...", file=sys.stderr)
|
||||
ConfigChecker.printed_warning = True
|
||||
return
|
||||
if name not in OPTIONS:
|
||||
self.add_message('bad-config-option', node=node, args=name)
|
||||
|
||||
@ -66,6 +74,11 @@ def register(linter):
|
||||
"""Register this checker."""
|
||||
linter.register_checker(ConfigChecker(linter))
|
||||
global OPTIONS
|
||||
yaml_file = os.path.join('qutebrowser', 'config', 'configdata.yml')
|
||||
with open(yaml_file, 'r', encoding='utf-8') as f:
|
||||
global FAILED_LOAD
|
||||
yaml_file = pathlib.Path('qutebrowser') / 'config' / 'configdata.yml'
|
||||
if not yaml_file.exists():
|
||||
OPTIONS = None
|
||||
FAILED_LOAD = True
|
||||
return
|
||||
with yaml_file.open(mode='r', encoding='utf-8') as f:
|
||||
OPTIONS = list(yaml.load(f))
|
||||
|
68
scripts/dev/standardpaths_tester.py
Normal file
68
scripts/dev/standardpaths_tester.py
Normal file
@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Show various QStandardPath paths."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import (QT_VERSION_STR, PYQT_VERSION_STR, qVersion,
|
||||
QStandardPaths, QCoreApplication)
|
||||
|
||||
|
||||
def print_header():
|
||||
"""Show system information."""
|
||||
print("Python {}".format(sys.version))
|
||||
print("os.name: {}".format(os.name))
|
||||
print("sys.platform: {}".format(sys.platform))
|
||||
print()
|
||||
|
||||
print("Qt {}, compiled {}".format(qVersion(), QT_VERSION_STR))
|
||||
print("PyQt {}".format(PYQT_VERSION_STR))
|
||||
print()
|
||||
|
||||
|
||||
def print_paths():
|
||||
for name, obj in vars(QStandardPaths).items():
|
||||
if isinstance(obj, QStandardPaths.StandardLocation):
|
||||
location = QStandardPaths.writableLocation(obj)
|
||||
print("{:25} {}".format(name, location))
|
||||
|
||||
|
||||
def main():
|
||||
print_header()
|
||||
|
||||
print("No QApplication")
|
||||
print("===============")
|
||||
print()
|
||||
print_paths()
|
||||
|
||||
app = QCoreApplication(sys.argv)
|
||||
app.setApplicationName("qapp_name")
|
||||
|
||||
print()
|
||||
print("With QApplication")
|
||||
print("=================")
|
||||
print()
|
||||
print_paths()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -50,13 +50,14 @@ def _git_str():
|
||||
if not os.path.isdir(os.path.join(BASEDIR, ".git")):
|
||||
return None
|
||||
try:
|
||||
cid = subprocess.check_output(
|
||||
['git', 'describe', '--tags', '--dirty', '--always'],
|
||||
# https://stackoverflow.com/questions/21017300/21017394#21017394
|
||||
commit_hash = subprocess.check_output(
|
||||
['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'],
|
||||
cwd=BASEDIR).decode('UTF-8').strip()
|
||||
date = subprocess.check_output(
|
||||
['git', 'show', '-s', '--format=%ci', 'HEAD'],
|
||||
cwd=BASEDIR).decode('UTF-8').strip()
|
||||
return '{} ({})'.format(cid, date)
|
||||
return '{} ({})'.format(commit_hash, date)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return None
|
||||
|
||||
|
@ -99,6 +99,8 @@ Feature: Page history
|
||||
Then the page should contain the plaintext "3.txt"
|
||||
Then the page should contain the plaintext "4.txt"
|
||||
|
||||
# Hangs a lot on AppVeyor
|
||||
@posix
|
||||
Scenario: Listing history with qute:history redirect
|
||||
When I open data/numbers/3.txt
|
||||
And I open data/numbers/4.txt
|
||||
|
@ -74,12 +74,12 @@ Feature: Invoking a new process
|
||||
|
||||
# issue #1060
|
||||
|
||||
Scenario: Using target_window = first-opened after tab-detach
|
||||
Scenario: Using target_window = first-opened after tab-give
|
||||
When I set new_instance_open_target to tab
|
||||
And I set new_instance_open_target_window to first-opened
|
||||
And I open data/title.html
|
||||
And I open data/search.html in a new tab
|
||||
And I run :tab-detach
|
||||
And I run :tab-give
|
||||
And I wait until data/search.html is loaded
|
||||
And I open data/hello.txt as a URL
|
||||
Then the session should look like:
|
||||
|
@ -387,22 +387,6 @@ Feature: Prompts
|
||||
Then the javascript message "confirm reply: true" should be logged
|
||||
And the error "No default value was set for this question!" should be shown
|
||||
|
||||
Scenario: Javascript confirm with deprecated :prompt-yes command
|
||||
When I open data/prompt/jsconfirm.html
|
||||
And I run :click-element id button
|
||||
And I wait for a prompt
|
||||
And I run :prompt-yes
|
||||
Then the javascript message "confirm reply: true" should be logged
|
||||
And the warning "prompt-yes is deprecated - Use :prompt-accept yes instead!" should be shown
|
||||
|
||||
Scenario: Javascript confirm with deprecated :prompt-no command
|
||||
When I open data/prompt/jsconfirm.html
|
||||
And I run :click-element id button
|
||||
And I wait for a prompt
|
||||
And I run :prompt-no
|
||||
Then the javascript message "confirm reply: false" should be logged
|
||||
And the warning "prompt-no is deprecated - Use :prompt-accept no instead!" should be shown
|
||||
|
||||
# Other
|
||||
|
||||
@qtwebengine_skip
|
||||
|
@ -638,28 +638,6 @@ Feature: Tab management
|
||||
And I run :tab-clone
|
||||
Then no crash should happen
|
||||
|
||||
# :tab-detach
|
||||
|
||||
Scenario: Detaching a tab
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-detach
|
||||
And I wait until data/numbers/2.txt is loaded
|
||||
Then the session should look like:
|
||||
windows:
|
||||
- tabs:
|
||||
- history:
|
||||
- url: about:blank
|
||||
- url: http://localhost:*/data/numbers/1.txt
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/2.txt
|
||||
|
||||
Scenario: Detach tab from window with only one tab
|
||||
When I open data/hello.txt
|
||||
And I run :tab-detach
|
||||
Then the error "Cannot detach one tab." should be shown
|
||||
|
||||
# :undo
|
||||
|
||||
Scenario: Undo without any closed tabs
|
||||
@ -1010,6 +988,76 @@ Feature: Tab management
|
||||
And I run :buffer "1/2/3"
|
||||
Then the error "No matching tab for: 1/2/3" should be shown
|
||||
|
||||
# :tab-take
|
||||
|
||||
@xfail_norun # Needs qutewm
|
||||
Scenario: Take a tab from another window
|
||||
Given I have a fresh instance
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new window
|
||||
And I run :tab-take 0/1
|
||||
Then the session should look like:
|
||||
windows:
|
||||
- tabs:
|
||||
- history:
|
||||
- url: about:blank
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/2.txt
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/1.txt
|
||||
|
||||
Scenario: Take a tab from the same window
|
||||
Given I have a fresh instance
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-take 0/1
|
||||
Then the error "Can't take a tab from the same window" should be shown
|
||||
|
||||
# :tab-give
|
||||
|
||||
@xfail_norun # Needs qutewm
|
||||
Scenario: Give a tab to another window
|
||||
Given I have a fresh instance
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new window
|
||||
And I run :tab-give 0
|
||||
Then the session should look like:
|
||||
windows:
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/1.txt
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/2.txt
|
||||
- tabs:
|
||||
- history:
|
||||
- url: about:blank
|
||||
|
||||
Scenario: Give a tab to the same window
|
||||
Given I have a fresh instance
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-give 0
|
||||
Then the error "Can't give a tab to the same window" should be shown
|
||||
|
||||
Scenario: Give a tab to a new window
|
||||
When I open data/numbers/1.txt
|
||||
And I open data/numbers/2.txt in a new tab
|
||||
And I run :tab-give
|
||||
And I wait until data/numbers/2.txt is loaded
|
||||
Then the session should look like:
|
||||
windows:
|
||||
- tabs:
|
||||
- history:
|
||||
- url: about:blank
|
||||
- url: http://localhost:*/data/numbers/1.txt
|
||||
- tabs:
|
||||
- history:
|
||||
- url: http://localhost:*/data/numbers/2.txt
|
||||
|
||||
Scenario: Give a tab from window with only one tab
|
||||
When I open data/hello.txt
|
||||
And I run :tab-give
|
||||
Then the error "Cannot detach from a window with only one tab" should be shown
|
||||
|
||||
# Other
|
||||
|
||||
Scenario: Using :tab-next after closing last tab (#1448)
|
||||
@ -1149,6 +1197,14 @@ Feature: Tab management
|
||||
And the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
|
||||
Scenario: :tab-pin open url
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
And I run :home
|
||||
Then the message "Tab is pinned!" should be shown
|
||||
And the following tabs should be open:
|
||||
- data/numbers/1.txt (active) (pinned)
|
||||
|
||||
Scenario: Cloning a pinned tab
|
||||
When I open data/numbers/1.txt
|
||||
And I run :tab-pin
|
||||
|
@ -17,10 +17,19 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
import pytest_bdd as bdd
|
||||
bdd.scenarios('marks.feature')
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def turn_on_scroll_logging(quteproc):
|
||||
"""Make sure all scrolling changes are logged."""
|
||||
quteproc.send_cmd(":debug-pyeval -q objreg.get('args')."
|
||||
"debug_flags.append('no-scroll-filtering')")
|
||||
|
||||
|
||||
@bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}"))
|
||||
def check_y(request, quteproc, x, y):
|
||||
data = quteproc.get_session()
|
||||
|
@ -17,5 +17,14 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
import pytest_bdd as bdd
|
||||
bdd.scenarios('scroll.feature')
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def turn_on_scroll_logging(quteproc):
|
||||
"""Make sure all scrolling changes are logged."""
|
||||
quteproc.send_cmd(":debug-pyeval -q objreg.get('args')."
|
||||
"debug_flags.append('no-scroll-filtering')")
|
||||
|
@ -105,6 +105,12 @@ def is_ignored_lowlevel_message(message):
|
||||
elif (message.startswith('QNetworkProxyFactory: factory 0x') and
|
||||
message.endswith(' has returned an empty result set')):
|
||||
return True
|
||||
elif message == ' Error: No such file or directory':
|
||||
# Qt 5.10 with debug Chromium
|
||||
# [1016/155149.941048:WARNING:stack_trace_posix.cc(625)] Failed to open
|
||||
# file: /home/florian/#14687139 (deleted)
|
||||
# Error: No such file or directory
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@ -170,6 +176,12 @@ def is_ignored_chromium_message(line):
|
||||
# WebFrame LEAKED 1 TIMES
|
||||
'WebFrame LEAKED 1 TIMES',
|
||||
|
||||
# Qt 5.10 with debug Chromium
|
||||
# [1016/155149.941048:WARNING:stack_trace_posix.cc(625)] Failed to open
|
||||
# file: /home/florian/#14687139 (deleted)
|
||||
# Error: No such file or directory
|
||||
'Failed to open file: * (deleted)',
|
||||
|
||||
# macOS on Travis
|
||||
# [5140:5379:0911/063441.239771:ERROR:mach_port_broker.mm(175)]
|
||||
# Unknown process 5176 is sending Mach IPC messages!
|
||||
|
@ -60,6 +60,9 @@ class WinRegistryHelper:
|
||||
|
||||
registry = attr.ib()
|
||||
|
||||
def windowTitle(self):
|
||||
return 'window title - qutebrowser'
|
||||
|
||||
def __init__(self):
|
||||
self._ids = []
|
||||
|
||||
|
@ -131,10 +131,11 @@ class FakeUrl:
|
||||
|
||||
"""QUrl stub which provides .path(), isValid() and host()."""
|
||||
|
||||
def __init__(self, path=None, valid=True, host=None):
|
||||
def __init__(self, path=None, valid=True, host=None, url=None):
|
||||
self.path = mock.Mock(return_value=path)
|
||||
self.isValid = mock.Mock(returl_value=valid)
|
||||
self.host = mock.Mock(returl_value=host)
|
||||
self.url = mock.Mock(return_value=url)
|
||||
|
||||
|
||||
class FakeNetworkReply:
|
||||
@ -377,7 +378,9 @@ class FakeTimer(QObject):
|
||||
def isSingleShot(self):
|
||||
return self._singleshot
|
||||
|
||||
def start(self):
|
||||
def start(self, interval=None):
|
||||
if interval:
|
||||
self._interval = interval
|
||||
self._started = True
|
||||
|
||||
def stop(self):
|
||||
@ -396,7 +399,7 @@ class InstaTimer(QObject):
|
||||
|
||||
timeout = pyqtSignal()
|
||||
|
||||
def start(self):
|
||||
def start(self, interval=None):
|
||||
self.timeout.emit()
|
||||
|
||||
def setSingleShot(self, yes):
|
||||
@ -520,6 +523,9 @@ class TabbedBrowserStub(QObject):
|
||||
def count(self):
|
||||
return len(self.tabs)
|
||||
|
||||
def widgets(self):
|
||||
return self.tabs
|
||||
|
||||
def widget(self, i):
|
||||
return self.tabs[i]
|
||||
|
||||
|
@ -146,3 +146,26 @@ class TestHistoryHandler:
|
||||
url = QUrl("qute://history/data?start_time={}".format(now))
|
||||
_mimetype, data = benchmark(qutescheme.qute_history, url)
|
||||
assert len(json.loads(data)) > 1
|
||||
|
||||
|
||||
class TestHelpHandler:
|
||||
|
||||
"""Tests for qute://help."""
|
||||
|
||||
@pytest.fixture
|
||||
def data_patcher(self, monkeypatch):
|
||||
def _patch(path, data):
|
||||
def _read_file(name, binary=False):
|
||||
assert path == name
|
||||
if binary:
|
||||
return data
|
||||
return data.decode('utf-8')
|
||||
|
||||
monkeypatch.setattr(qutescheme.utils, 'read_file', _read_file)
|
||||
return _patch
|
||||
|
||||
def test_unknown_file_type(self, data_patcher):
|
||||
data_patcher('html/doc/foo.bin', b'\xff')
|
||||
mimetype, data = qutescheme.qute_help(QUrl('qute://help/foo.bin'))
|
||||
assert mimetype == 'application/octet-stream'
|
||||
assert data == b'\xff'
|
||||
|
@ -31,6 +31,8 @@ QWebElement = pytest.importorskip('PyQt5.QtWebKit').QWebElement
|
||||
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.browser.webkit import webkitelem
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.utils import usertypes
|
||||
|
||||
|
||||
def get_webelem(geometry=None, frame=None, *, null=False, style=None,
|
||||
@ -715,8 +717,10 @@ class TestRectOnView:
|
||||
|
||||
@pytest.mark.parametrize('js_rect', [None, {}])
|
||||
@pytest.mark.parametrize('zoom_text_only', [True, False])
|
||||
def test_zoomed(self, stubs, config_stub, js_rect, zoom_text_only):
|
||||
def test_zoomed(self, stubs, config_stub, js_rect, monkeypatch,
|
||||
zoom_text_only):
|
||||
"""Make sure the coordinates are adjusted when zoomed."""
|
||||
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebKit)
|
||||
config_stub.val.zoom.text_only = zoom_text_only
|
||||
geometry = QRect(10, 10, 4, 4)
|
||||
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100), zoom=0.5)
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from qutebrowser.commands import runners, cmdexc
|
||||
from qutebrowser.commands import runners, cmdexc, cmdutils
|
||||
|
||||
|
||||
class TestCommandParser:
|
||||
@ -66,11 +66,47 @@ class TestCommandParser:
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse_all(command)
|
||||
|
||||
def test_partial_parsing(self):
|
||||
|
||||
class TestCompletions:
|
||||
|
||||
"""Tests for completions.use_best_match."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cmdutils_stub(self, monkeypatch, stubs):
|
||||
"""Patch the cmdutils module to provide fake commands."""
|
||||
monkeypatch.setattr(cmdutils, 'cmd_dict', {
|
||||
'one': stubs.FakeCommand(name='one'),
|
||||
'two': stubs.FakeCommand(name='two'),
|
||||
'two-foo': stubs.FakeCommand(name='two-foo'),
|
||||
})
|
||||
|
||||
def test_partial_parsing(self, config_stub):
|
||||
"""Test partial parsing with a runner where it's enabled.
|
||||
|
||||
The same with it being disabled is tested by test_parse_all.
|
||||
"""
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
result = parser.parse('message-i')
|
||||
assert result.cmd.name == 'message-info'
|
||||
result = parser.parse('on')
|
||||
assert result.cmd.name == 'one'
|
||||
|
||||
def test_dont_use_best_match(self, config_stub):
|
||||
"""Test multiple completion options with use_best_match set to false.
|
||||
|
||||
Should raise NoSuchCommandError
|
||||
"""
|
||||
config_stub.val.completion.use_best_match = False
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
|
||||
with pytest.raises(cmdexc.NoSuchCommandError):
|
||||
parser.parse('tw')
|
||||
|
||||
def test_use_best_match(self, config_stub):
|
||||
"""Test multiple completion options with use_best_match set to true.
|
||||
|
||||
The resulting command should be the best match
|
||||
"""
|
||||
config_stub.val.completion.use_best_match = True
|
||||
parser = runners.CommandParser(partial_match=True)
|
||||
|
||||
result = parser.parse('tw')
|
||||
assert result.cmd.name == 'two'
|
||||
|
@ -528,6 +528,30 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub,
|
||||
QUrl('https://duckduckgo.com')]
|
||||
|
||||
|
||||
def test_window_completion(qtmodeltester, fake_web_tab, tabbed_browser_stubs):
|
||||
tabbed_browser_stubs[0].tabs = [
|
||||
fake_web_tab(QUrl('https://github.com'), 'GitHub', 0),
|
||||
fake_web_tab(QUrl('https://wikipedia.org'), 'Wikipedia', 1),
|
||||
fake_web_tab(QUrl('https://duckduckgo.com'), 'DuckDuckGo', 2)
|
||||
]
|
||||
tabbed_browser_stubs[1].tabs = [
|
||||
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0)
|
||||
]
|
||||
|
||||
model = miscmodels.window()
|
||||
model.set_pattern('')
|
||||
qtmodeltester.data_display_may_return_none = True
|
||||
qtmodeltester.check(model)
|
||||
|
||||
_check_completions(model, {
|
||||
'Windows': [
|
||||
('0', 'window title - qutebrowser',
|
||||
'GitHub, Wikipedia, DuckDuckGo'),
|
||||
('1', 'window title - qutebrowser', 'ArchWiki')
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
def test_setting_option_completion(qtmodeltester, config_stub,
|
||||
configdata_stub, info):
|
||||
model = configmodel.option(info=info)
|
||||
|
@ -133,9 +133,9 @@ class TestSet:
|
||||
"QtWebEngine backend!"):
|
||||
commands.set(0, 'content.cookies.accept', 'all')
|
||||
|
||||
@pytest.mark.parametrize('option', ['?', '!', 'url.auto_search'])
|
||||
@pytest.mark.parametrize('option', ['?', 'url.auto_search'])
|
||||
def test_empty(self, commands, option):
|
||||
"""Run ':set ?' / ':set !' / ':set url.auto_search'.
|
||||
"""Run ':set ?' / ':set url.auto_search'.
|
||||
|
||||
Should show an error.
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/1109
|
||||
@ -145,6 +145,16 @@ class TestSet:
|
||||
"value"):
|
||||
commands.set(win_id=0, option=option)
|
||||
|
||||
def test_toggle(self, commands):
|
||||
"""Try toggling a value.
|
||||
|
||||
Should show an nicer error.
|
||||
"""
|
||||
with pytest.raises(cmdexc.CommandError,
|
||||
match="Toggling values was moved to the "
|
||||
":config-cycle command"):
|
||||
commands.set(win_id=0, option='javascript.enabled!')
|
||||
|
||||
def test_invalid(self, commands):
|
||||
"""Run ':set foo?'.
|
||||
|
||||
|
@ -580,6 +580,52 @@ class TestConfigPy:
|
||||
assert isinstance(error.exception, ZeroDivisionError)
|
||||
assert error.traceback is not None
|
||||
|
||||
@pytest.mark.parametrize('location', ['abs', 'rel'])
|
||||
def test_source(self, tmpdir, confpy, location):
|
||||
if location == 'abs':
|
||||
subfile = tmpdir / 'subfile.py'
|
||||
arg = str(subfile)
|
||||
else:
|
||||
subfile = tmpdir / 'config' / 'subfile.py'
|
||||
arg = 'subfile.py'
|
||||
|
||||
subfile.write_text("c.content.javascript.enabled = False",
|
||||
encoding='utf-8')
|
||||
confpy.write("config.source({!r})".format(arg))
|
||||
confpy.read()
|
||||
|
||||
assert not config.instance._values['content.javascript.enabled']
|
||||
|
||||
def test_source_errors(self, tmpdir, confpy):
|
||||
subfile = tmpdir / 'config' / 'subfile.py'
|
||||
subfile.write_text("c.foo = 42", encoding='utf-8')
|
||||
confpy.write("config.source('subfile.py')")
|
||||
error = confpy.read(error=True)
|
||||
|
||||
assert error.text == "While setting 'foo'"
|
||||
assert isinstance(error.exception, configexc.NoOptionError)
|
||||
|
||||
def test_source_multiple_errors(self, tmpdir, confpy):
|
||||
subfile = tmpdir / 'config' / 'subfile.py'
|
||||
subfile.write_text("c.foo = 42", encoding='utf-8')
|
||||
confpy.write("config.source('subfile.py')", "c.bar = 23")
|
||||
|
||||
with pytest.raises(configexc.ConfigFileErrors) as excinfo:
|
||||
configfiles.read_config_py(confpy.filename)
|
||||
|
||||
errors = excinfo.value.errors
|
||||
assert len(errors) == 2
|
||||
|
||||
for error in errors:
|
||||
assert isinstance(error.exception, configexc.NoOptionError)
|
||||
|
||||
def test_source_not_found(self, confpy):
|
||||
confpy.write("config.source('doesnotexist.py')")
|
||||
error = confpy.read(error=True)
|
||||
|
||||
assert error.text == "Error while reading doesnotexist.py"
|
||||
assert isinstance(error.exception, FileNotFoundError)
|
||||
|
||||
|
||||
class TestConfigPyWriter:
|
||||
|
||||
|
@ -258,6 +258,15 @@ class TestEarlyInit:
|
||||
# Font subclass, but doesn't end with "monospace"
|
||||
assert 'fonts.web.family.standard' not in changed_options
|
||||
|
||||
def test_setting_monospace_fonts_family(self, init_patch, args):
|
||||
"""Make sure setting fonts.monospace after a family works.
|
||||
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/3130
|
||||
"""
|
||||
configinit.early_init(args)
|
||||
config.instance.set_str('fonts.web.family.standard', '')
|
||||
config.instance.set_str('fonts.monospace', 'Terminus')
|
||||
|
||||
def test_force_software_rendering(self, monkeypatch, config_stub):
|
||||
"""Setting force_software_rendering should set the environment var."""
|
||||
envvar = 'QT_XCB_FORCE_SOFTWARE_OPENGL'
|
||||
|
@ -28,8 +28,8 @@ import pytest
|
||||
from qutebrowser.misc import checkpyver
|
||||
|
||||
|
||||
TEXT = (r"At least Python 3.5 is required to run qutebrowser, but "
|
||||
r"\d+\.\d+\.\d+ is installed!\n")
|
||||
TEXT = (r"At least Python 3.5 is required to run qutebrowser, but it's "
|
||||
r"running with \d+\.\d+\.\d+.\n")
|
||||
|
||||
|
||||
@pytest.mark.not_frozen
|
||||
|
@ -39,7 +39,7 @@ def test_sqlerror():
|
||||
class TestSqliteError:
|
||||
|
||||
@pytest.mark.parametrize('error_code, environmental', [
|
||||
('9', True), # SQLITE_LOCKED
|
||||
('5', True), # SQLITE_BUSY
|
||||
('19', False), # SQLITE_CONSTRAINT
|
||||
])
|
||||
def test_environmental(self, error_code, environmental):
|
||||
|
@ -103,6 +103,20 @@ def test_fake_windows(tmpdir, monkeypatch, what):
|
||||
assert func() == str(tmpdir / APPNAME / what)
|
||||
|
||||
|
||||
def test_fake_haiku(tmpdir, monkeypatch):
|
||||
"""Test getting data dir on HaikuOS."""
|
||||
locations = {
|
||||
QStandardPaths.DataLocation: '',
|
||||
QStandardPaths.ConfigLocation: str(tmpdir / 'config' / APPNAME),
|
||||
}
|
||||
monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation',
|
||||
locations.get)
|
||||
monkeypatch.setattr(standarddir.sys, 'platform', 'haiku1')
|
||||
|
||||
standarddir._init_data(args=None)
|
||||
assert standarddir.data() == str(tmpdir / 'config' / APPNAME / 'data')
|
||||
|
||||
|
||||
class TestWritableLocation:
|
||||
|
||||
"""Tests for _writable_location."""
|
||||
|
@ -356,7 +356,7 @@ class TestGitStrSubprocess:
|
||||
def test_real_git(self, git_repo):
|
||||
"""Test with a real git repository."""
|
||||
ret = version._git_str_subprocess(str(git_repo))
|
||||
assert ret == 'foobar (1970-01-01 01:00:00 +0100)'
|
||||
assert ret == '6e4b65a (1970-01-01 01:00:00 +0100)'
|
||||
|
||||
def test_missing_dir(self, tmpdir):
|
||||
"""Test with a directory which doesn't exist."""
|
||||
|
Loading…
Reference in New Issue
Block a user