Merge remote-tracking branch 'origin/master' into layout
Conflicts: qutebrowser/test/keyinput/test_basekeyparser.py qutebrowser/test/utils/test_standarddir.py test/browser/http/test_content_disposition.py test/config/test_configtypes.py test/misc/test_editor.py test/utils/test_debug.py test/utils/test_utils.py tox.ini
This commit is contained in:
commit
4fa2294805
@ -278,7 +278,7 @@ There are currently these object registries, also called 'scopes':
|
||||
`cookie-jar`, etc.)
|
||||
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
|
||||
etc.). Passing this scope to `objreg.get()` selects the object in the currently
|
||||
focused tab by default. A tab can be explicitely selected by passing
|
||||
focused tab by default. A tab can be explicitly selected by passing
|
||||
+tab=_tab-id_, window=_win-id_+ to it.
|
||||
|
||||
A new object can be registered by using
|
||||
@ -373,7 +373,7 @@ The types of the function arguments are inferred based on their default values,
|
||||
e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in
|
||||
qutebrowser's commandline.
|
||||
|
||||
This behaviour can be overridden using Python's
|
||||
This behavior can be overridden using Python's
|
||||
http://legacy.python.org/dev/peps/pep-3107/[function annotations]. The
|
||||
annotation should always be a `dict`, like this:
|
||||
|
||||
@ -447,7 +447,7 @@ This option controls Valgrind's detection of self-modifying code. If no
|
||||
checking is done, if a program executes some code, then overwrites it with new
|
||||
code, and executes the new code, Valgrind will continue to execute the
|
||||
translations it made for the old code. This will likely lead to incorrect
|
||||
behaviour and/or crashes.
|
||||
behavior and/or crashes.
|
||||
|
||||
...
|
||||
|
||||
|
@ -43,8 +43,8 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* A http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* A http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* link:doc/FAQ.asciidoc[Frequently asked questions]
|
||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
||||
@ -157,6 +157,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* Helen Sherwood-Taylor
|
||||
* HalosGhost
|
||||
* Gregor Pohl
|
||||
* Franz Fellner
|
||||
* Eivind Uggedal
|
||||
* Andreas Fischer
|
||||
// QUTE_AUTHORS_END
|
||||
@ -164,7 +165,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
The following people have contributed graphics:
|
||||
|
||||
* WOFall (icon)
|
||||
* regines (keybinding cheatsheet)
|
||||
* regines (key binding cheatsheet)
|
||||
|
||||
Thanks / Similiar projects
|
||||
--------------------------
|
||||
|
@ -9,7 +9,7 @@ What is qutebrowser based on?::
|
||||
+
|
||||
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
|
||||
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
|
||||
keybindings are similar to dwb.
|
||||
key bindings are similar to dwb.
|
||||
|
||||
Why another browser?::
|
||||
It might be hard to believe, but I didn't find any browser which I was
|
||||
@ -76,7 +76,7 @@ Is there an adblocker?::
|
||||
for v0.1 if at all.
|
||||
|
||||
How do I play Youtube videos with mpv?::
|
||||
You can easily add a keybinding to play youtube videos inside a real video
|
||||
You can easily add a key binding to play youtube videos inside a real video
|
||||
player - optionally even with hinting for links:
|
||||
+
|
||||
----
|
||||
|
@ -35,7 +35,7 @@
|
||||
|<<report,report>>|Report a bug in qutebrowser.
|
||||
|<<restart,restart>>|Restart qutebrowser while keeping existing tabs open.
|
||||
|<<save,save>>|Save configs and state.
|
||||
|<<search,search>>|Search for a text on the current page.
|
||||
|<<search,search>>|Search for a text on the current page. With no text, clear results.
|
||||
|<<session-delete,session-delete>>|Delete a session.
|
||||
|<<session-load,session-load>>|Load a session.
|
||||
|<<session-save,session-save>>|Save a session.
|
||||
@ -394,9 +394,9 @@ Save configs and state.
|
||||
|
||||
[[search]]
|
||||
=== search
|
||||
Syntax: +:search [*--reverse*] 'text'+
|
||||
Syntax: +:search [*--reverse*] ['text']+
|
||||
|
||||
Search for a text on the current page.
|
||||
Search for a text on the current page. With no text, clear results.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The text to search for.
|
||||
@ -406,16 +406,20 @@ Search for a text on the current page.
|
||||
|
||||
[[session-delete]]
|
||||
=== session-delete
|
||||
Syntax: +:session-delete 'name'+
|
||||
Syntax: +:session-delete [*--force*] 'name'+
|
||||
|
||||
Delete a session.
|
||||
|
||||
==== positional arguments
|
||||
* +'name'+: The name of the session.
|
||||
|
||||
==== optional arguments
|
||||
* +*-f*+, +*--force*+: Force deleting internal sessions (starting with an underline).
|
||||
|
||||
|
||||
[[session-load]]
|
||||
=== session-load
|
||||
Syntax: +:session-load [*--clear*] 'name'+
|
||||
Syntax: +:session-load [*--clear*] [*--force*] 'name'+
|
||||
|
||||
Load a session.
|
||||
|
||||
@ -424,10 +428,12 @@ Load a session.
|
||||
|
||||
==== optional arguments
|
||||
* +*-c*+, +*--clear*+: Close all existing windows.
|
||||
* +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline).
|
||||
|
||||
|
||||
[[session-save]]
|
||||
=== session-save
|
||||
Syntax: +:session-save [*--quiet*] ['name']+
|
||||
Syntax: +:session-save [*--quiet*] [*--force*] ['name']+
|
||||
|
||||
Save a session.
|
||||
|
||||
@ -436,6 +442,7 @@ Save a session.
|
||||
|
||||
==== optional arguments
|
||||
* +*-q*+, +*--quiet*+: Don't show confirmation message.
|
||||
* +*-f*+, +*--force*+: Force saving internal sessions (starting with an underline).
|
||||
|
||||
[[set]]
|
||||
=== set
|
||||
@ -456,13 +463,16 @@ If the option name ends with '?', the value of the option is shown instead. If t
|
||||
|
||||
[[set-cmd-text]]
|
||||
=== set-cmd-text
|
||||
Syntax: +:set-cmd-text 'text'+
|
||||
Syntax: +:set-cmd-text [*--space*] 'text'+
|
||||
|
||||
Preset the statusbar to some text.
|
||||
|
||||
==== positional arguments
|
||||
* +'text'+: The commandline to set.
|
||||
|
||||
==== optional arguments
|
||||
* +*-s*+, +*--space*+: If given, a space is added to the end.
|
||||
|
||||
[[spawn]]
|
||||
=== spawn
|
||||
Syntax: +:spawn [*--userscript*] 'args' ['args' ...]+
|
||||
@ -503,7 +513,7 @@ Close the current/[count]th tab.
|
||||
==== optional arguments
|
||||
* +*-l*+, +*--left*+: Force selecting the tab to the left of the current tab.
|
||||
* +*-r*+, +*--right*+: Force selecting the tab to the right of the current tab.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the oppsite direction of what's configured in 'tabs->select-on-remove'.
|
||||
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
|
||||
|
||||
|
||||
==== count
|
||||
@ -643,7 +653,6 @@ How many steps to zoom out.
|
||||
|<<completion-item-prev,completion-item-prev>>|Select the previous completion item.
|
||||
|<<enter-mode,enter-mode>>|Enter a key mode.
|
||||
|<<follow-hint,follow-hint>>|Follow the currently selected hint.
|
||||
|<<fooled,fooled>>|Turn off april's fools.
|
||||
|<<leave-mode,leave-mode>>|Leave the mode we're currently in.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||
@ -701,10 +710,6 @@ Enter a key mode.
|
||||
=== follow-hint
|
||||
Follow the currently selected hint.
|
||||
|
||||
[[fooled]]
|
||||
=== fooled
|
||||
Turn off april's fools.
|
||||
|
||||
[[leave-mode]]
|
||||
=== leave-mode
|
||||
Leave the mode we're currently in.
|
||||
@ -874,7 +879,8 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|
||||
|<<debug-cache-stats,debug-cache-stats>>|Print LRU cache stats.
|
||||
|<<debug-console,debug-console>>|Show the debugging console.
|
||||
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|
||||
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a webpage.
|
||||
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|
||||
|<<debug-trace,debug-trace>>|Trace executed code via hunter.
|
||||
|==============
|
||||
[[debug-all-objects]]
|
||||
=== debug-all-objects
|
||||
@ -901,8 +907,17 @@ Crash for debugging purposes.
|
||||
=== debug-pyeval
|
||||
Syntax: +:debug-pyeval 's'+
|
||||
|
||||
Evaluate a python string and display the results as a webpage.
|
||||
Evaluate a python string and display the results as a web page.
|
||||
|
||||
==== positional arguments
|
||||
* +'s'+: The string to evaluate.
|
||||
|
||||
[[debug-trace]]
|
||||
=== debug-trace
|
||||
Syntax: +:debug-trace ['expr']+
|
||||
|
||||
Trace executed code via hunter.
|
||||
|
||||
==== positional arguments
|
||||
* +'expr'+: What to trace, passed to hunter.
|
||||
|
||||
|
@ -74,8 +74,8 @@
|
||||
[options="header",width="75%",cols="25%,75%"]
|
||||
|==============
|
||||
|Setting|Description
|
||||
|<<input-timeout,timeout>>|Timeout for ambiguous keybindings.
|
||||
|<<input-partial-timeout,partial-timeout>>|Timeout for partially typed keybindings.
|
||||
|<<input-timeout,timeout>>|Timeout for ambiguous key bindings.
|
||||
|<<input-partial-timeout,partial-timeout>>|Timeout for partially typed key bindings.
|
||||
|<<input-insert-mode-on-plugins,insert-mode-on-plugins>>|Whether to switch to insert mode when clicking flash and other plugins.
|
||||
|<<input-auto-leave-insert-mode,auto-leave-insert-mode>>|Whether to leave insert mode if a non-editable element is clicked.
|
||||
|<<input-auto-insert-mode,auto-insert-mode>>|Whether to automatically enter insert mode if an editable element is focused after page load.
|
||||
@ -93,10 +93,10 @@
|
||||
|<<tabs-background-tabs,background-tabs>>|Whether to open new tabs (middleclick/ctrl+click) in background.
|
||||
|<<tabs-select-on-remove,select-on-remove>>|Which tab to select when the focused tab is removed.
|
||||
|<<tabs-new-tab-position,new-tab-position>>|How new tabs are positioned.
|
||||
|<<tabs-new-tab-position-explicit,new-tab-position-explicit>>|How new tabs opened explicitely are positioned.
|
||||
|<<tabs-new-tab-position-explicit,new-tab-position-explicit>>|How new tabs opened explicitly are positioned.
|
||||
|<<tabs-last-close,last-close>>|Behaviour when the last tab is closed.
|
||||
|<<tabs-hide-auto,hide-auto>>|Hide the tabbar if only one tab is open.
|
||||
|<<tabs-hide-always,hide-always>>|Always hide the tabbar.
|
||||
|<<tabs-hide-auto,hide-auto>>|Hide the tab bar if only one tab is open.
|
||||
|<<tabs-hide-always,hide-always>>|Always hide the tab bar.
|
||||
|<<tabs-wrap,wrap>>|Whether to wrap when changing tabs.
|
||||
|<<tabs-movable,movable>>|Whether tabs should be movable.
|
||||
|<<tabs-close-mouse-button,close-mouse-button>>|On which mouse button to close tabs.
|
||||
@ -196,7 +196,7 @@
|
||||
|<<colors-tabs.bg.odd,tabs.bg.odd>>|Background color of unselected odd tabs.
|
||||
|<<colors-tabs.bg.even,tabs.bg.even>>|Background color of unselected even tabs.
|
||||
|<<colors-tabs.bg.selected,tabs.bg.selected>>|Background color of selected tabs.
|
||||
|<<colors-tabs.bg.bar,tabs.bg.bar>>|Background color of the tabbar.
|
||||
|<<colors-tabs.bg.bar,tabs.bg.bar>>|Background color of the tab bar.
|
||||
|<<colors-tabs.indicator.start,tabs.indicator.start>>|Color gradient start for the tab indicator.
|
||||
|<<colors-tabs.indicator.stop,tabs.indicator.stop>>|Color gradient end for the tab indicator.
|
||||
|<<colors-tabs.indicator.error,tabs.indicator.error>>|Color for the tab indicator on errors..
|
||||
@ -218,7 +218,7 @@
|
||||
|Setting|Description
|
||||
|<<fonts-_monospace,_monospace>>|Default monospace fonts.
|
||||
|<<fonts-completion,completion>>|Font used in the completion widget.
|
||||
|<<fonts-tabbar,tabbar>>|Font used in the tabbar.
|
||||
|<<fonts-tabbar,tabbar>>|Font used in the tab bar.
|
||||
|<<fonts-statusbar,statusbar>>|Font used in the statusbar.
|
||||
|<<fonts-downloads,downloads>>|Font used for the downloadbar.
|
||||
|<<fonts-hints,hints>>|Font used for the hints.
|
||||
@ -537,7 +537,7 @@ The format to use for the window title. The following placeholders are defined:
|
||||
|
||||
* `{perc}`: The percentage as a string like `[10%]`.
|
||||
* `{perc_raw}`: The raw percentage, e.g. `10`
|
||||
* `{title}`: The title of the current webpage
|
||||
* `{title}`: The title of the current web page
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{id}`: The internal window ID of this window.
|
||||
|
||||
@ -697,13 +697,13 @@ Options related to input modes.
|
||||
|
||||
[[input-timeout]]
|
||||
=== timeout
|
||||
Timeout for ambiguous keybindings.
|
||||
Timeout for ambiguous key bindings.
|
||||
|
||||
Default: +pass:[500]+
|
||||
|
||||
[[input-partial-timeout]]
|
||||
=== partial-timeout
|
||||
Timeout for partially typed keybindings.
|
||||
Timeout for partially typed key bindings.
|
||||
|
||||
Default: +pass:[1000]+
|
||||
|
||||
@ -834,7 +834,7 @@ Default: +pass:[right]+
|
||||
|
||||
[[tabs-new-tab-position-explicit]]
|
||||
=== new-tab-position-explicit
|
||||
How new tabs opened explicitely are positioned.
|
||||
How new tabs opened explicitly are positioned.
|
||||
|
||||
Valid values:
|
||||
|
||||
@ -859,7 +859,7 @@ Default: +pass:[ignore]+
|
||||
|
||||
[[tabs-hide-auto]]
|
||||
=== hide-auto
|
||||
Hide the tabbar if only one tab is open.
|
||||
Hide the tab bar if only one tab is open.
|
||||
|
||||
Valid values:
|
||||
|
||||
@ -870,7 +870,7 @@ Default: +pass:[false]+
|
||||
|
||||
[[tabs-hide-always]]
|
||||
=== hide-always
|
||||
Always hide the tabbar.
|
||||
Always hide the tab bar.
|
||||
|
||||
Valid values:
|
||||
|
||||
@ -972,7 +972,7 @@ The format to use for the tab title. The following placeholders are defined:
|
||||
|
||||
* `{perc}`: The percentage as a string like `[10%]`.
|
||||
* `{perc_raw}`: The raw percentage, e.g. `10`
|
||||
* `{title}`: The title of the current webpage
|
||||
* `{title}`: The title of the current web page
|
||||
* `{title_sep}`: The string ` - ` if a title is set, empty otherwise.
|
||||
* `{index}`: The index of this tab.
|
||||
* `{id}`: The internal tab ID of this tab.
|
||||
@ -1208,7 +1208,7 @@ Whether to accept cookies.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +default+: Default QtWebKit behaviour.
|
||||
* +default+: Default QtWebKit behavior.
|
||||
* +never+: Don't accept cookies at all.
|
||||
|
||||
Default: +pass:[default]+
|
||||
@ -1332,7 +1332,7 @@ Default: +pass:[\bprev(ious)?\b,\bback\b,\bolder\b,\b[<←≪]\b,\b(<<|
|
||||
|
||||
== searchengines
|
||||
Definitions of search engines which can be used via the address bar.
|
||||
The searchengine named `DEFAULT` is used when `general -> auto-search` is true and something else than a URL was entered to be opened. Other search engines can be used via the bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
|
||||
The searchengine named `DEFAULT` is used when `general -> auto-search` is true and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
|
||||
|
||||
== aliases
|
||||
Aliases for commands.
|
||||
@ -1535,7 +1535,7 @@ Default: +pass:[black]+
|
||||
|
||||
[[colors-tabs.bg.bar]]
|
||||
=== tabs.bg.bar
|
||||
Background color of the tabbar.
|
||||
Background color of the tab bar.
|
||||
|
||||
Default: +pass:[#555555]+
|
||||
|
||||
@ -1650,7 +1650,7 @@ Default: +pass:[8pt ${_monospace}]+
|
||||
|
||||
[[fonts-tabbar]]
|
||||
=== tabbar
|
||||
Font used in the tabbar.
|
||||
Font used in the tab bar.
|
||||
|
||||
Default: +pass:[8pt ${_monospace}]+
|
||||
|
||||
|
@ -8,9 +8,9 @@ time, use the `:help` command.
|
||||
What to do now
|
||||
--------------
|
||||
|
||||
* View the http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]
|
||||
to make yourself familiar with the keybindings: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* View the http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
to make yourself familiar with the key bindings: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* If you just cloned the repository, you'll need to run
|
||||
`scripts/asciidoc2html.py` to generate the documentation.
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
|
||||
|
@ -106,7 +106,7 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
|
||||
- '~/.config/qutebrowser/qutebrowser.conf': Main config file.
|
||||
- '~/.config/qutebrowser/quickmarks': Saved quickmarks.
|
||||
- '~/.config/qutebrowser/keys.conf': Defined keybindings.
|
||||
- '~/.config/qutebrowser/keys.conf': Defined key bindings.
|
||||
- '~/.local/share/qutebrowser/': Various state information.
|
||||
- '~/.cache/qutebrowser/': Temporary data.
|
||||
|
||||
|
@ -3,14 +3,14 @@ Writing qutebrowser userscripts
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
qutebrowser is extensible by writing userscripts which can be called via the
|
||||
`:spawn --userscript` command, or via a keybinding.
|
||||
`:spawn --userscript` command, or via a key binding.
|
||||
|
||||
These userscripts are similiar to the (non-javascript) dwb userscripts. They
|
||||
can be written in any language which can read environment variables and write
|
||||
to a FIFO.
|
||||
|
||||
Note for simple things such as opening the current page with another browser or
|
||||
mpv, a simple keybinding to something like `:spawn mpv {url}` should suffice.
|
||||
mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
|
||||
|
||||
Also note userscripts need to have the executable bit set (`chmod +x`) for
|
||||
qutebrowser to run them.
|
||||
@ -21,7 +21,7 @@ Getting information
|
||||
The following environment variables will be set when an userscript is launched:
|
||||
|
||||
- `QUTE_MODE`: Either `hints` (started via hints) or `command` (started via
|
||||
command or keybinding).
|
||||
command or key binding).
|
||||
- `QUTE_USER_AGENT`: The currently set user agent.
|
||||
- `QUTE_FIFO`: The FIFO or file to write commands to.
|
||||
|
||||
|
@ -30,12 +30,16 @@ import base64
|
||||
import functools
|
||||
import traceback
|
||||
import faulthandler
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QObject, Qt, QSocketNotifier)
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources # pylint: disable=unused-import
|
||||
@ -50,7 +54,7 @@ from qutebrowser.misc import (crashdialog, readline, ipc, earlyinit,
|
||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
|
||||
debug, objreg, usertypes, standarddir)
|
||||
objreg, usertypes, standarddir)
|
||||
# We import utilcmds to run the cmdutils.register decorators.
|
||||
|
||||
|
||||
@ -157,15 +161,6 @@ class Application(QApplication):
|
||||
if self._crashdlg is not None:
|
||||
self._crashdlg.raise_()
|
||||
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
fooled = state_config['general']['fooled']
|
||||
except KeyError:
|
||||
fooled = False
|
||||
if datetime.date.today() == datetime.date(2015, 4, 1) and not fooled:
|
||||
message.info('current', "Happy April's fools! Use :fooled to turn "
|
||||
"this off.")
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@ -312,6 +307,9 @@ class Application(QApplication):
|
||||
del state_config['general']['session']
|
||||
except KeyError:
|
||||
pass
|
||||
# If this was a _restart session, delete it.
|
||||
if name == '_restart':
|
||||
session_manager.delete('_restart')
|
||||
|
||||
def _get_window(self, via_ipc, force_window=False, force_tab=False):
|
||||
"""Helper function for process_pos_args to get a window id.
|
||||
@ -433,10 +431,6 @@ class Application(QApplication):
|
||||
window='last-focused')
|
||||
tabbed_browser.tabopen(
|
||||
QUrl('http://www.qutebrowser.org/quickstart.html'))
|
||||
try:
|
||||
state_config.add_section('general')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['general']['quickstart-done'] = '1'
|
||||
|
||||
def _setup_signals(self):
|
||||
@ -557,19 +551,11 @@ class Application(QApplication):
|
||||
if self.geometry is not None:
|
||||
state_config = objreg.get('state-config')
|
||||
geom = base64.b64encode(self.geometry).decode('ASCII')
|
||||
try:
|
||||
state_config.add_section('geometry')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['geometry']['mainwindow'] = geom
|
||||
|
||||
def _save_version(self):
|
||||
"""Save the current version to the state config."""
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
state_config.add_section('general')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['general']['version'] = qutebrowser.__version__
|
||||
|
||||
def _destroy_crashlogfile(self):
|
||||
@ -656,7 +642,8 @@ class Application(QApplication):
|
||||
self._args.debug, pages, cmd_history, exc, objects)
|
||||
ret = self._crashdlg.exec_()
|
||||
if ret == QDialog.Accepted: # restore
|
||||
self.restart(shutdown=False, pages=pages)
|
||||
self._do_restart(pages)
|
||||
|
||||
# We might risk a segfault here, but that's better than continuing to
|
||||
# run in some undefined state, so we only do the most needed shutdown
|
||||
# here.
|
||||
@ -664,11 +651,12 @@ class Application(QApplication):
|
||||
self._destroy_crashlogfile()
|
||||
sys.exit(1)
|
||||
|
||||
def _get_restart_args(self, pages):
|
||||
def _get_restart_args(self, pages=(), session=None):
|
||||
"""Get the current working directory and args to relaunch qutebrowser.
|
||||
|
||||
Args:
|
||||
pages: The pages to re-open.
|
||||
session: The session to load, or None.
|
||||
|
||||
Return:
|
||||
An (args, cwd) tuple.
|
||||
@ -691,46 +679,86 @@ class Application(QApplication):
|
||||
# cwd=None and see if that works out.
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/323
|
||||
cwd = None
|
||||
for arg in sys.argv[1:]:
|
||||
if arg.startswith('-'):
|
||||
# We only want to preserve options on a restart.
|
||||
args.append(arg)
|
||||
|
||||
# Add all open pages so they get reopened.
|
||||
page_args = []
|
||||
for win in pages:
|
||||
page_args.extend(win)
|
||||
page_args.append('')
|
||||
if page_args:
|
||||
args.extend(page_args[:-1])
|
||||
|
||||
# Serialize the argparse namespace into json and pass that to the new
|
||||
# process via --json-args.
|
||||
# We do this as there's no way to "unparse" the namespace while
|
||||
# ignoring some arguments.
|
||||
argdict = vars(self._args)
|
||||
argdict['session'] = None
|
||||
argdict['url'] = []
|
||||
argdict['command'] = page_args[:-1]
|
||||
argdict['json_args'] = None
|
||||
# Ensure the given session (or none at all) gets opened.
|
||||
if session is None:
|
||||
argdict['session'] = None
|
||||
argdict['override_restore'] = True
|
||||
else:
|
||||
argdict['session'] = session
|
||||
argdict['override_restore'] = False
|
||||
# Dump the data
|
||||
data = json.dumps(argdict)
|
||||
args += ['--json-args', data]
|
||||
|
||||
log.destroy.debug("args: {}".format(args))
|
||||
log.destroy.debug("cwd: {}".format(cwd))
|
||||
|
||||
return args, cwd
|
||||
|
||||
@cmdutils.register(instance='app', ignore_args=True)
|
||||
def restart(self, shutdown=True, pages=None):
|
||||
@cmdutils.register(instance='app')
|
||||
def restart(self):
|
||||
"""Restart qutebrowser while keeping existing tabs open."""
|
||||
if pages is None:
|
||||
pages = self._recover_pages()
|
||||
ok = self._do_restart(session='_restart')
|
||||
if ok:
|
||||
self.shutdown()
|
||||
|
||||
def _do_restart(self, pages=(), session=None):
|
||||
"""Inner logic to restart qutebrowser.
|
||||
|
||||
The "better" way to restart is to pass a session (_restart usually) as
|
||||
that'll save the complete state.
|
||||
|
||||
However we don't do that (and pass a list of pages instead) when we
|
||||
restart because of an exception, as that's a lot simpler and we don't
|
||||
want to risk anything going wrong.
|
||||
|
||||
Args:
|
||||
pages: A list of URLs to open.
|
||||
session: The session to load, or None.
|
||||
|
||||
Return:
|
||||
True if the restart succeeded, False otherwise.
|
||||
"""
|
||||
log.destroy.debug("sys.executable: {}".format(sys.executable))
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
||||
# Save the session if one is given.
|
||||
if session is not None:
|
||||
session_manager = objreg.get('session-manager')
|
||||
session_manager.save(session)
|
||||
# Open a new process and immediately shutdown the existing one
|
||||
try:
|
||||
args, cwd = self._get_restart_args(pages)
|
||||
args, cwd = self._get_restart_args(pages, session)
|
||||
if cwd is None:
|
||||
subprocess.Popen(args)
|
||||
else:
|
||||
subprocess.Popen(args, cwd=cwd)
|
||||
except OSError:
|
||||
log.destroy.exception("Failed to restart")
|
||||
return False
|
||||
else:
|
||||
if shutdown:
|
||||
self.shutdown()
|
||||
return True
|
||||
|
||||
@cmdutils.register(instance='app', maxsplit=0, debug=True)
|
||||
def debug_pyeval(self, s):
|
||||
"""Evaluate a python string and display the results as a webpage.
|
||||
"""Evaluate a python string and display the results as a web page.
|
||||
|
||||
//
|
||||
|
||||
@ -843,8 +871,8 @@ class Application(QApplication):
|
||||
deferrer = True
|
||||
if deferrer:
|
||||
# If shutdown was called while we were asking a question, we're in
|
||||
# a still sub-eventloop (which gets quitted now) and not in the
|
||||
# main one.
|
||||
# a still sub-eventloop (which gets quit now) and not in the main
|
||||
# one.
|
||||
# This means we need to defer the real shutdown to when we're back
|
||||
# in the real main event loop, or we'll get a segfault.
|
||||
log.destroy.debug("Deferring real shutdown because question was "
|
||||
@ -894,7 +922,7 @@ class Application(QApplication):
|
||||
qInstallMessageHandler(None)
|
||||
# Now we can hopefully quit without segfaults
|
||||
log.destroy.debug("Deferring QApplication::exit...")
|
||||
# We use a singleshot timer to exit here to minimize the likelyhood of
|
||||
# We use a singleshot timer to exit here to minimize the likelihood of
|
||||
# segfaults.
|
||||
QTimer.singleShot(0, functools.partial(self.exit, status))
|
||||
|
||||
@ -924,6 +952,10 @@ class Application(QApplication):
|
||||
"""Extend QApplication::exit to log the event."""
|
||||
log.destroy.debug("Now calling QApplication::exit.")
|
||||
if self._args.debug_exit:
|
||||
print("Now logging late shutdown.", file=sys.stderr)
|
||||
debug.trace_lines(True)
|
||||
if hunter is None:
|
||||
print("Not logging late shutdown because hunter could not be "
|
||||
"imported!", file=sys.stderr)
|
||||
else:
|
||||
print("Now logging late shutdown.", file=sys.stderr)
|
||||
hunter.trace()
|
||||
super().exit(status)
|
||||
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Functions related to adblocking."""
|
||||
"""Functions related to ad blocking."""
|
||||
|
||||
import io
|
||||
import os.path
|
||||
|
@ -227,7 +227,7 @@ class CommandDispatcher:
|
||||
Args:
|
||||
left: Force selecting the tab to the left of the current tab.
|
||||
right: Force selecting the tab to the right of the current tab.
|
||||
opposite: Force selecting the tab in the oppsite direction of
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
|
||||
Return:
|
||||
@ -259,7 +259,7 @@ class CommandDispatcher:
|
||||
Args:
|
||||
left: Force selecting the tab to the left of the current tab.
|
||||
right: Force selecting the tab to the right of the current tab.
|
||||
opposite: Force selecting the tab in the oppsite direction of
|
||||
opposite: Force selecting the tab in the opposite direction of
|
||||
what's configured in 'tabs->select-on-remove'.
|
||||
count: The tab index to close, or None
|
||||
"""
|
||||
|
@ -185,7 +185,7 @@ class DownloadItem(QObject):
|
||||
done: Whether the download is finished.
|
||||
stats: A DownloadItemStats object.
|
||||
index: The index of the download in the view.
|
||||
successful: Whether the download has completed sucessfully.
|
||||
successful: Whether the download has completed successfully.
|
||||
error_msg: The current error message, or None
|
||||
autoclose: Whether to close the associated file if the download is
|
||||
done.
|
||||
@ -204,7 +204,7 @@ class DownloadItem(QObject):
|
||||
data_changed: The downloads metadata changed.
|
||||
finished: The download was finished.
|
||||
cancelled: The download was cancelled.
|
||||
error: An error with the download occured.
|
||||
error: An error with the download occurred.
|
||||
arg: The error message as string.
|
||||
redirected: Signal emitted when a download was redirected.
|
||||
arg 0: The new QNetworkRequest.
|
||||
|
@ -123,7 +123,7 @@ class DownloadView(QListView):
|
||||
Return:
|
||||
A list of either:
|
||||
- (QAction, callable) tuples.
|
||||
- (None, None) for a seperator
|
||||
- (None, None) for a separator
|
||||
"""
|
||||
actions = []
|
||||
if item is None:
|
||||
|
@ -62,7 +62,7 @@ class HintContext:
|
||||
frames: The QWebFrames to use.
|
||||
destroyed_frames: id()'s of QWebFrames which have been destroyed.
|
||||
(Workaround for https://github.com/The-Compiler/qutebrowser/issues/152)
|
||||
elems: A mapping from keystrings to (elem, label) namedtuples.
|
||||
elems: A mapping from key strings to (elem, label) namedtuples.
|
||||
baseurl: The URL of the current page.
|
||||
target: What to do with the opened links.
|
||||
normal/tab/tab_bg/window: Get passed to BrowserTab.
|
||||
@ -77,7 +77,7 @@ class HintContext:
|
||||
args: Custom arguments for userscript/spawn
|
||||
rapid: Whether to do rapid hinting.
|
||||
mainframe: The main QWebFrame where we started hinting in.
|
||||
group: The group of webelements to hint.
|
||||
group: The group of web elements to hint.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@ -455,7 +455,7 @@ class HintManager(QObject):
|
||||
"""Yank an element to the clipboard or primary selection.
|
||||
|
||||
Args:
|
||||
url: The URL to open as a QURL.
|
||||
url: The URL to open as a QUrl.
|
||||
context: The HintContext to use.
|
||||
"""
|
||||
sel = context.target == Target.yank_primary
|
||||
@ -816,7 +816,7 @@ class HintManager(QObject):
|
||||
'<font color="{}">{}</font>{}'.format(
|
||||
match_color, matched, rest))
|
||||
if self._is_hidden(elems.label):
|
||||
# hidden element which matches again -> unhide it
|
||||
# hidden element which matches again -> show it
|
||||
self._show_elem(elems.label)
|
||||
else:
|
||||
# element doesn't match anymore -> hide it
|
||||
@ -835,7 +835,7 @@ class HintManager(QObject):
|
||||
if (filterstr is None or
|
||||
str(elems.elem).lower().startswith(filterstr)):
|
||||
if self._is_hidden(elems.label):
|
||||
# hidden element which matches again -> unhide it
|
||||
# hidden element which matches again -> show it
|
||||
self._show_elem(elems.label)
|
||||
else:
|
||||
# element doesn't match anymore -> hide it
|
||||
|
@ -132,6 +132,8 @@ class WebHistory(QWebHistoryInterface):
|
||||
Args:
|
||||
url_string: An url as string to add to the history.
|
||||
"""
|
||||
if not url_string:
|
||||
return
|
||||
if not config.get('general', 'private-browsing'):
|
||||
entry = HistoryEntry(time.time(), url_string)
|
||||
self.item_about_to_be_added.emit(entry)
|
||||
|
@ -176,7 +176,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
if answer is not None:
|
||||
# Since the answer could be something else than (user, password)
|
||||
# pylint seems to think we're unpacking a non-sequence. However we
|
||||
# *did* explicitely ask for a tuple, so it *will* always be one.
|
||||
# *did* explicitly ask for a tuple, so it *will* always be one.
|
||||
user, password = answer
|
||||
authenticator.setUser(user)
|
||||
authenticator.setPassword(password)
|
||||
|
@ -43,9 +43,19 @@ class QuickmarkManager(QObject):
|
||||
marks: An OrderedDict of all quickmarks.
|
||||
_lineparser: The LineParser used for the quickmarks, or None
|
||||
(when qutebrowser is started with -c '').
|
||||
|
||||
Signals:
|
||||
changed: Emitted when anything changed.
|
||||
added: Emitted when a new quickmark was added.
|
||||
arg 0: The name of the quickmark.
|
||||
arg 1: The URL of the quickmark, as string.
|
||||
removed: Emitted when an existing quickmark was removed.
|
||||
arg 0: The name of the quickmark.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
added = pyqtSignal(str, str)
|
||||
removed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize and read quickmarks."""
|
||||
@ -117,6 +127,7 @@ class QuickmarkManager(QObject):
|
||||
"""Really set the quickmark."""
|
||||
self.marks[name] = url
|
||||
self.changed.emit()
|
||||
self.added.emit(name, url)
|
||||
|
||||
if name in self.marks:
|
||||
message.confirm_async(
|
||||
@ -138,6 +149,7 @@ class QuickmarkManager(QObject):
|
||||
raise cmdexc.CommandError("Quickmark '{}' not found!".format(name))
|
||||
else:
|
||||
self.changed.emit()
|
||||
self.removed.emit(name)
|
||||
|
||||
def get(self, name):
|
||||
"""Get the URL of the quickmark named name as a QUrl."""
|
||||
|
@ -133,7 +133,7 @@ def serialize(items):
|
||||
|
||||
Return:
|
||||
A (stream, data, user_data) tuple.
|
||||
stream: The resetted QDataStream.
|
||||
stream: The reseted QDataStream.
|
||||
data: The QByteArray with the raw data.
|
||||
user_data: A list with each item's user data.
|
||||
|
||||
|
@ -332,7 +332,7 @@ def get_child_frames(startframe):
|
||||
|
||||
|
||||
def focus_elem(frame):
|
||||
"""Get the focused element in a webframe.
|
||||
"""Get the focused element in a web frame.
|
||||
|
||||
FIXME: Add tests.
|
||||
|
||||
|
@ -41,7 +41,7 @@ class BrowserPage(QWebPage):
|
||||
"""Our own QWebPage with advanced features.
|
||||
|
||||
Attributes:
|
||||
error_occured: Whether an error occured while loading.
|
||||
error_occurred: Whether an error occurred while loading.
|
||||
open_target: Where to open the next navigation request.
|
||||
("normal", "tab", "tab_bg")
|
||||
_hint_target: Override for open_target while hinting, or None.
|
||||
@ -69,7 +69,7 @@ class BrowserPage(QWebPage):
|
||||
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occured = False
|
||||
self.error_occurred = False
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self._hint_target = None
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
@ -147,7 +147,7 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
error_str = info.errorString
|
||||
if error_str == networkmanager.HOSTBLOCK_ERROR_STRING:
|
||||
# We don't set error_occured in this case.
|
||||
# We don't set error_occurred in this case.
|
||||
error_str = "Request blocked by host blocker."
|
||||
main_frame = info.frame.page().mainFrame()
|
||||
if info.frame != main_frame:
|
||||
@ -160,7 +160,7 @@ class BrowserPage(QWebPage):
|
||||
return False
|
||||
else:
|
||||
self._ignore_load_started = True
|
||||
self.error_occured = True
|
||||
self.error_occurred = True
|
||||
log.webview.error("Error while loading {}: {}".format(
|
||||
urlstr, error_str))
|
||||
log.webview.debug("Error domain: {}, error code: {}".format(
|
||||
@ -248,7 +248,7 @@ class BrowserPage(QWebPage):
|
||||
frame.setScrollPosition, cur_data['scroll-pos']))
|
||||
|
||||
def display_content(self, reply, mimetype):
|
||||
"""Display a QNetworkReply with an explicitely set mimetype."""
|
||||
"""Display a QNetworkReply with an explicitly set mimetype."""
|
||||
self.mainFrame().setContent(reply.readAll(), mimetype, reply.url())
|
||||
reply.deleteLater()
|
||||
|
||||
@ -312,11 +312,11 @@ class BrowserPage(QWebPage):
|
||||
|
||||
@pyqtSlot()
|
||||
def on_load_started(self):
|
||||
"""Reset error_occured when loading of a new page started."""
|
||||
"""Reset error_occurred when loading of a new page started."""
|
||||
if self._ignore_load_started:
|
||||
self._ignore_load_started = False
|
||||
else:
|
||||
self.error_occured = False
|
||||
self.error_occurred = False
|
||||
|
||||
@pyqtSlot('QWebFrame', 'QWebPage::Feature')
|
||||
def on_feature_permission_requested(self, frame, feature):
|
||||
@ -393,8 +393,8 @@ class BrowserPage(QWebPage):
|
||||
# With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
|
||||
# RuntimeError: wrapped C/C++ object of type BrowserPage has
|
||||
# been deleted
|
||||
# Since the information here isn't that important for closing
|
||||
# webviews anyways, we ignore this error.
|
||||
# Since the information here isn't that important for closing web
|
||||
# views anyways, we ignore this error.
|
||||
return
|
||||
data = {
|
||||
'zoom': frame.zoomFactor(),
|
||||
|
@ -52,7 +52,7 @@ class WebView(QWebView):
|
||||
hintmanager: The HintManager instance for this view.
|
||||
progress: loading progress of this page.
|
||||
scroll_pos: The current scroll position as (x%, y%) tuple.
|
||||
statusbar_message: The current javscript statusbar message.
|
||||
statusbar_message: The current javascript statusbar message.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
load_status: loading status of this page (index into LoadStatus)
|
||||
viewing_source: Whether the webview is currently displaying source
|
||||
@ -63,7 +63,7 @@ class WebView(QWebView):
|
||||
tab_id: The tab ID of the view.
|
||||
win_id: The window ID of the view.
|
||||
_cur_url: The current URL (accessed via cur_url property).
|
||||
_has_ssl_errors: Whether SSL errors occured during loading.
|
||||
_has_ssl_errors: Whether SSL errors occurred during loading.
|
||||
_zoom: A NeighborList with the zoom levels.
|
||||
_old_scroll_pos: The old scroll position.
|
||||
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
||||
@ -234,7 +234,7 @@ class WebView(QWebView):
|
||||
# me, but it works this way.
|
||||
hitresult = frame.hitTestContent(pos)
|
||||
if hitresult.isNull():
|
||||
# For some reason, the whole hitresult can be null sometimes (e.g.
|
||||
# For some reason, the whole hit result can be null sometimes (e.g.
|
||||
# on doodle menu links). If this is the case, we schedule a check
|
||||
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
||||
log.mouse.debug("Hitresult is null!")
|
||||
@ -243,7 +243,7 @@ class WebView(QWebView):
|
||||
try:
|
||||
elem = webelem.WebElementWrapper(hitresult.element())
|
||||
except webelem.IsNullError:
|
||||
# For some reason, the hitresult element can be a null element
|
||||
# For some reason, the hit result element can be a null element
|
||||
# sometimes (e.g. when clicking the timetable fields on
|
||||
# http://www.sbb.ch/ ). If this is the case, we schedule a check
|
||||
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
|
||||
@ -372,7 +372,7 @@ class WebView(QWebView):
|
||||
|
||||
@pyqtSlot('QMouseEvent')
|
||||
def on_mouse_event(self, evt):
|
||||
"""Post a new mouseevent from a hintmanager."""
|
||||
"""Post a new mouse event from a hintmanager."""
|
||||
log.modes.debug("Hint triggered, focusing {!r}".format(self))
|
||||
self.setFocus()
|
||||
QApplication.postEvent(self, evt)
|
||||
@ -393,7 +393,7 @@ class WebView(QWebView):
|
||||
true when the QWebPage has an ErrorPageExtension implemented.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/84
|
||||
"""
|
||||
ok = not self.page().error_occured
|
||||
ok = not self.page().error_occurred
|
||||
if ok and not self._has_ssl_errors:
|
||||
self._set_load_status(LoadStatus.success)
|
||||
elif ok:
|
||||
|
@ -32,7 +32,7 @@ class CommandError(Exception):
|
||||
|
||||
class CommandMetaError(Exception):
|
||||
|
||||
"""Common base class for exceptions occuring before a command is run."""
|
||||
"""Common base class for exceptions occurring before a command is run."""
|
||||
|
||||
|
||||
class NoSuchCommandError(CommandMetaError):
|
||||
|
@ -101,7 +101,7 @@ class register: # pylint: disable=invalid-name
|
||||
_instance: The object from the object registry to be used as "self".
|
||||
_scope: The scope to get _instance for.
|
||||
_name: The name (as string) or names (as list) of the command.
|
||||
_maxsplit: The maxium amounts of splits to do for the commandline, or
|
||||
_maxsplit: The maximum amounts of splits to do for the commandline, or
|
||||
None.
|
||||
_hide: Whether to hide the command or not.
|
||||
_completion: Which completion to use for arguments, as a list of
|
||||
@ -151,7 +151,7 @@ class register: # pylint: disable=invalid-name
|
||||
def _get_names(self, func):
|
||||
"""Get the name(s) which should be used for the current command.
|
||||
|
||||
If the name hasn't been overridden explicitely, the function name is
|
||||
If the name hasn't been overridden explicitly, the function name is
|
||||
transformed.
|
||||
|
||||
If it has been set, it can either be a string which is
|
||||
|
@ -160,7 +160,7 @@ class Command:
|
||||
return type_conv
|
||||
|
||||
def _get_nameconv(self, param, annotation_info):
|
||||
"""Get a dict with a name conversion for the paraeter.
|
||||
"""Get a dict with a name conversion for the parameter.
|
||||
|
||||
Args:
|
||||
param: The inspect.Parameter to handle.
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
"""Module containing command managers (SearchRunner and CommandRunner)."""
|
||||
|
||||
import re
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
|
||||
@ -55,7 +53,7 @@ def replace_variables(win_id, arglist):
|
||||
|
||||
class SearchRunner(QObject):
|
||||
|
||||
"""Run searches on webpages.
|
||||
"""Run searches on web pages.
|
||||
|
||||
Attributes:
|
||||
_text: The text from the last search.
|
||||
@ -79,8 +77,8 @@ class SearchRunner(QObject):
|
||||
|
||||
@pyqtSlot(str)
|
||||
@cmdutils.register(instance='search-runner', scope='window', maxsplit=0)
|
||||
def search(self, text, reverse=False):
|
||||
"""Search for a text on the current page.
|
||||
def search(self, text="", reverse=False):
|
||||
"""Search for a text on the current page. With no text, clear results.
|
||||
|
||||
Args:
|
||||
text: The text to search for.
|
||||
@ -266,16 +264,8 @@ class CommandRunner(QObject):
|
||||
else:
|
||||
self._args = []
|
||||
maxsplit = i + self._cmd.maxsplit + flag_arg_count
|
||||
args = split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
for s in args:
|
||||
# remove quotes and replace \" by "
|
||||
if s == '""' or s == "''":
|
||||
s = ''
|
||||
else:
|
||||
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
|
||||
s = re.sub(r"""\\(["'])""", r'\1', s)
|
||||
self._args.append(s)
|
||||
self._args = split.simple_split(argstr, keep=keep,
|
||||
maxsplit=maxsplit)
|
||||
break
|
||||
else:
|
||||
# If there are only flags, we got it right on the first try
|
||||
|
@ -52,7 +52,7 @@ class _QtFIFOReader(QObject):
|
||||
|
||||
@pyqtSlot()
|
||||
def read_line(self):
|
||||
"""(Try to) read a line from the fifo."""
|
||||
"""(Try to) read a line from the FIFO."""
|
||||
log.procs.debug("QSocketNotifier triggered!")
|
||||
self._notifier.setEnabled(False)
|
||||
for line in self.fifo:
|
||||
@ -60,7 +60,7 @@ class _QtFIFOReader(QObject):
|
||||
self._notifier.setEnabled(True)
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up so the fifo can be closed."""
|
||||
"""Clean up so the FIFO can be closed."""
|
||||
self._notifier.setEnabled(False)
|
||||
|
||||
|
||||
|
@ -196,7 +196,13 @@ class Completer(QObject):
|
||||
data = model.data(indexes[0])
|
||||
if data is None:
|
||||
return
|
||||
data = self._quote(data)
|
||||
parts = self.split()
|
||||
try:
|
||||
needs_quoting = cmdutils.cmd_dict[parts[0]].maxsplit is None
|
||||
except KeyError:
|
||||
needs_quoting = True
|
||||
if needs_quoting:
|
||||
data = self._quote(data)
|
||||
if model.count() == 1 and config.get('completion', 'quick-complete'):
|
||||
# If we only have one item, we want to apply it immediately
|
||||
# and go on to the next part.
|
||||
@ -245,7 +251,7 @@ class Completer(QObject):
|
||||
if self._cmd.prefix() != ':':
|
||||
# This is a search or gibberish, so we don't need to complete
|
||||
# anything (yet)
|
||||
# FIXME complete searchs
|
||||
# FIXME complete searches
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/32
|
||||
completion.hide()
|
||||
return
|
||||
|
@ -151,7 +151,10 @@ class CompletionView(QTreeView):
|
||||
idx = self.selectionModel().currentIndex()
|
||||
if not idx.isValid():
|
||||
# No item selected yet
|
||||
return self.model().first_item()
|
||||
if upwards:
|
||||
return self.model().last_item()
|
||||
else:
|
||||
return self.model().first_item()
|
||||
while True:
|
||||
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
||||
# wrap around if we arrived at beginning/end
|
||||
|
@ -121,4 +121,5 @@ class SessionCompletionModel(base.BaseCompletionModel):
|
||||
super().__init__(parent)
|
||||
cat = self.new_category("Sessions")
|
||||
for name in objreg.get('session-manager').list_sessions():
|
||||
self.new_item(cat, name)
|
||||
if not name.startswith('_'):
|
||||
self.new_item(cat, name)
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
|
||||
from qutebrowser.utils import objreg, utils
|
||||
from qutebrowser.completion.models import base
|
||||
@ -42,20 +42,21 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
self._quickmark_cat = self.new_category("Quickmarks")
|
||||
self._history_cat = self.new_category("History")
|
||||
|
||||
quickmarks = objreg.get('quickmark-manager').marks.items()
|
||||
self._history = objreg.get('web-history')
|
||||
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
quickmarks = quickmark_manager.marks.items()
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(self._quickmark_cat, qm_url, qm_name)
|
||||
self._add_quickmark_entry(qm_name, qm_url)
|
||||
quickmark_manager.added.connect(self.on_quickmark_added)
|
||||
quickmark_manager.removed.connect(self.on_quickmark_removed)
|
||||
|
||||
self._history = objreg.get('web-history')
|
||||
max_history = config.get('completion', 'web-history-max-items')
|
||||
history = utils.newest_slice(self._history, max_history)
|
||||
|
||||
for entry in history:
|
||||
self._add_history_entry(entry)
|
||||
|
||||
self._history.item_about_to_be_added.connect(
|
||||
self.on_history_item_added)
|
||||
|
||||
objreg.get('config').changed.connect(self.reformat_timestamps)
|
||||
|
||||
def _fmt_atime(self, atime):
|
||||
@ -71,6 +72,15 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
self._fmt_atime(entry.atime), sort=int(entry.atime),
|
||||
userdata=entry.url)
|
||||
|
||||
def _add_quickmark_entry(self, name, url):
|
||||
"""Add a new quickmark entry to the completion.
|
||||
|
||||
Args:
|
||||
name: The name of the new quickmark.
|
||||
url: The URL of the new quickmark.
|
||||
"""
|
||||
self.new_item(self._quickmark_cat, url, name)
|
||||
|
||||
@config.change_filter('completion', 'timestamp-format')
|
||||
def reformat_timestamps(self):
|
||||
"""Reformat the timestamps if the config option was changed."""
|
||||
@ -93,3 +103,26 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
||||
break
|
||||
else:
|
||||
self._add_history_entry(entry)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def on_quickmark_added(self, name, url):
|
||||
"""Called when a quickmark has been added by the user.
|
||||
|
||||
Args:
|
||||
name: The name of the new quickmark.
|
||||
url: The url of the new quickmark, as string.
|
||||
"""
|
||||
self._add_quickmark_entry(name, url)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_quickmark_removed(self, name):
|
||||
"""Called when a quickmark has been removed by the user.
|
||||
|
||||
Args:
|
||||
name: The name of the quickmark which has been removed.
|
||||
"""
|
||||
for i in range(self._quickmark_cat.rowCount()):
|
||||
name_item = self._quickmark_cat.child(i, 1)
|
||||
if name_item.data(Qt.DisplayRole) == name:
|
||||
self._quickmark_cat.removeRow(i)
|
||||
break
|
||||
|
@ -20,8 +20,8 @@
|
||||
"""Configuration storage and config-related utilities.
|
||||
|
||||
This borrows a lot of ideas from configparser, but also has some things that
|
||||
are fundamentally different. This is why nothing inherts from configparser, but
|
||||
we borrow some methods and classes from there where it makes sense.
|
||||
are fundamentally different. This is why nothing inherits from configparser,
|
||||
but we borrow some methods and classes from there where it makes sense.
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -144,7 +144,7 @@ def _init_main_config():
|
||||
for sect in config_obj.sections.values():
|
||||
for opt in sect.values.values():
|
||||
if opt.values['conf'] is None:
|
||||
# Option added to builtin defaults but not in user's
|
||||
# Option added to built-in defaults but not in user's
|
||||
# config yet
|
||||
save_manager.save('config', explicit=True, force=True)
|
||||
return
|
||||
@ -171,14 +171,22 @@ def _init_key_config():
|
||||
save_manager = objreg.get('save-manager')
|
||||
filename = os.path.join(standarddir.config(), 'keys.conf')
|
||||
save_manager.add_saveable(
|
||||
'key-config', key_config.save, key_config.changed,
|
||||
config_opt=('general', 'auto-save-config'), filename=filename)
|
||||
'key-config', key_config.save, key_config.config_dirty,
|
||||
config_opt=('general', 'auto-save-config'), filename=filename,
|
||||
dirty=key_config.is_dirty)
|
||||
|
||||
|
||||
def _init_misc():
|
||||
"""Initialize misc. config-related files."""
|
||||
save_manager = objreg.get('save-manager')
|
||||
state_config = ini.ReadWriteConfigParser(standarddir.data(), 'state')
|
||||
for sect in ('general', 'geometry'):
|
||||
try:
|
||||
state_config.add_section(sect)
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
|
||||
state_config['general'].pop('fooled', None)
|
||||
objreg.register('state-config', state_config)
|
||||
save_manager.add_saveable('state-config', state_config.save)
|
||||
|
||||
@ -262,8 +270,8 @@ class ConfigManager(QObject):
|
||||
('completion', 'history-length'): 'cmd-history-max-items',
|
||||
}
|
||||
DELETED_OPTIONS = [
|
||||
('colors', 'tab.seperator'),
|
||||
('colors', 'tabs.seperator'),
|
||||
('colors', 'tab.separator'),
|
||||
('colors', 'tabs.separator'),
|
||||
('colors', 'completion.item.bg'),
|
||||
]
|
||||
|
||||
@ -476,7 +484,7 @@ class ConfigManager(QObject):
|
||||
def items(self, sectname, raw=True):
|
||||
"""Get a list of (optname, value) tuples for a section.
|
||||
|
||||
Implemented for configparser interpolation compatbility.
|
||||
Implemented for configparser interpolation compatibility
|
||||
|
||||
Args:
|
||||
sectname: The name of the section to get.
|
||||
@ -539,7 +547,7 @@ class ConfigManager(QObject):
|
||||
The value of the option.
|
||||
"""
|
||||
if not self._initialized:
|
||||
raise Exception("get got called before initialisation was "
|
||||
raise Exception("get got called before initialization was "
|
||||
"complete!")
|
||||
try:
|
||||
sect = self.sections[sectname]
|
||||
|
@ -56,7 +56,7 @@ FIRST_COMMENT = r"""
|
||||
# described below.
|
||||
#
|
||||
# This is the default config, so if you want to remove anything from
|
||||
# here (as opposed to change/add), for example a keybinding, set it to
|
||||
# here (as opposed to change/add), for example a key binding, set it to
|
||||
# an empty value.
|
||||
#
|
||||
# You will need to escape the following values:
|
||||
@ -80,10 +80,10 @@ SECTION_DESC = {
|
||||
"bar.\n"
|
||||
"The searchengine named `DEFAULT` is used when "
|
||||
"`general -> auto-search` is true and something else than a URL was "
|
||||
"entered to be opened. Other search engines can be used via the "
|
||||
"bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will "
|
||||
"be replaced by the search term, use `{{` and `}}` for literal "
|
||||
"`{`/`}` signs."),
|
||||
"entered to be opened. Other search engines can be used by prepending "
|
||||
"the search engine name to the search term, e.g. "
|
||||
"`:open google qutebrowser`. The string `{}` will be replaced by the "
|
||||
"search term, use `{{` and `}}` for literal `{`/`}` signs."),
|
||||
'aliases': (
|
||||
"Aliases for commands.\n"
|
||||
"By default, no aliases are defined. Example which adds a new command "
|
||||
@ -270,7 +270,7 @@ DATA = collections.OrderedDict([
|
||||
"are defined:\n\n"
|
||||
"* `{perc}`: The percentage as a string like `[10%]`.\n"
|
||||
"* `{perc_raw}`: The raw percentage, e.g. `10`\n"
|
||||
"* `{title}`: The title of the current webpage\n"
|
||||
"* `{title}`: The title of the current web page\n"
|
||||
"* `{title_sep}`: The string ` - ` if a title is set, empty "
|
||||
"otherwise.\n"
|
||||
"* `{id}`: The internal window ID of this window."),
|
||||
@ -350,11 +350,11 @@ DATA = collections.OrderedDict([
|
||||
('input', sect.KeyValue(
|
||||
('timeout',
|
||||
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '500'),
|
||||
"Timeout for ambiguous keybindings."),
|
||||
"Timeout for ambiguous key bindings."),
|
||||
|
||||
('partial-timeout',
|
||||
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '1000'),
|
||||
"Timeout for partially typed keybindings."),
|
||||
"Timeout for partially typed key bindings."),
|
||||
|
||||
('insert-mode-on-plugins',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
@ -414,7 +414,7 @@ DATA = collections.OrderedDict([
|
||||
|
||||
('new-tab-position-explicit',
|
||||
SettingValue(typ.NewTabPosition(), 'last'),
|
||||
"How new tabs opened explicitely are positioned."),
|
||||
"How new tabs opened explicitly are positioned."),
|
||||
|
||||
('last-close',
|
||||
SettingValue(typ.LastClose(), 'ignore'),
|
||||
@ -422,11 +422,11 @@ DATA = collections.OrderedDict([
|
||||
|
||||
('hide-auto',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Hide the tabbar if only one tab is open."),
|
||||
"Hide the tab bar if only one tab is open."),
|
||||
|
||||
('hide-always',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Always hide the tabbar."),
|
||||
"Always hide the tab bar."),
|
||||
|
||||
('wrap',
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
@ -473,7 +473,7 @@ DATA = collections.OrderedDict([
|
||||
"are defined:\n\n"
|
||||
"* `{perc}`: The percentage as a string like `[10%]`.\n"
|
||||
"* `{perc_raw}`: The raw percentage, e.g. `10`\n"
|
||||
"* `{title}`: The title of the current webpage\n"
|
||||
"* `{title}`: The title of the current web page\n"
|
||||
"* `{title_sep}`: The string ` - ` if a title is set, empty "
|
||||
"otherwise.\n"
|
||||
"* `{index}`: The index of this tab.\n"
|
||||
@ -810,7 +810,7 @@ DATA = collections.OrderedDict([
|
||||
|
||||
('tabs.bg.bar',
|
||||
SettingValue(typ.QtColor(), '#555555'),
|
||||
"Background color of the tabbar."),
|
||||
"Background color of the tab bar."),
|
||||
|
||||
('tabs.indicator.start',
|
||||
SettingValue(typ.QtColor(), '#0000aa'),
|
||||
@ -881,7 +881,7 @@ DATA = collections.OrderedDict([
|
||||
|
||||
('tabbar',
|
||||
SettingValue(typ.QtFont(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
|
||||
"Font used in the tabbar."),
|
||||
"Font used in the tab bar."),
|
||||
|
||||
('statusbar',
|
||||
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
|
||||
@ -949,7 +949,7 @@ DATA = collections.OrderedDict([
|
||||
KEY_FIRST_COMMENT = """
|
||||
# vim: ft=conf
|
||||
#
|
||||
# In this config file, qutebrowser's keybindings are configured.
|
||||
# In this config file, qutebrowser's key bindings are configured.
|
||||
# The format looks like this:
|
||||
#
|
||||
# [keymode]
|
||||
@ -962,8 +962,8 @@ KEY_FIRST_COMMENT = """
|
||||
# All blank lines and lines starting with '#' are ignored.
|
||||
# Inline-comments are not permitted.
|
||||
#
|
||||
# keymode is a comma separated list of modes in which the keybinding should be
|
||||
# active. If keymode starts with !, the keybinding is active in all modes
|
||||
# keymode is a comma separated list of modes in which the key binding should be
|
||||
# active. If keymode starts with !, the key binding is active in all modes
|
||||
# except the listed modes.
|
||||
#
|
||||
# For special keys (can't be part of a keychain), enclose them in `<`...`>`.
|
||||
@ -975,7 +975,7 @@ KEY_FIRST_COMMENT = """
|
||||
# * Shift: `Shift`
|
||||
#
|
||||
# For simple keys (no `<>`-signs), a capital letter means the key is pressed
|
||||
# with Shift. For special keys (with `<>`-signs), you need to explicitely add
|
||||
# with Shift. For special keys (with `<>`-signs), you need to explicitly add
|
||||
# `Shift-` to match a key pressed with shift. You can bind multiple commands
|
||||
# by separating them with `;;`.
|
||||
"""
|
||||
@ -1029,14 +1029,14 @@ KEY_DATA = collections.OrderedDict([
|
||||
|
||||
('normal', collections.OrderedDict([
|
||||
('search ""', ['<Escape>']),
|
||||
('set-cmd-text ":open "', ['o']),
|
||||
('set-cmd-text ":open {url}"', ['go']),
|
||||
('set-cmd-text ":open -t "', ['O']),
|
||||
('set-cmd-text ":open -t {url}"', ['gO']),
|
||||
('set-cmd-text ":open -b "', ['xo']),
|
||||
('set-cmd-text ":open -b {url}"', ['xO']),
|
||||
('set-cmd-text ":open -w "', ['wo']),
|
||||
('set-cmd-text ":open -w {url}"', ['wO']),
|
||||
('set-cmd-text -s :open', ['o']),
|
||||
('set-cmd-text :open {url}', ['go']),
|
||||
('set-cmd-text -s :open -t', ['O']),
|
||||
('set-cmd-text :open -t {url}', ['gO']),
|
||||
('set-cmd-text -s :open -b', ['xo']),
|
||||
('set-cmd-text :open -b {url}', ['xO']),
|
||||
('set-cmd-text -s :open -w', ['wo']),
|
||||
('set-cmd-text :open -w {url}', ['wO']),
|
||||
('open -t', ['ga']),
|
||||
('tab-close', ['d', '<Ctrl-W>']),
|
||||
('tab-close -o', ['D']),
|
||||
@ -1179,3 +1179,16 @@ KEY_DATA = collections.OrderedDict([
|
||||
('rl-backward-delete-char', ['<Ctrl-H>']),
|
||||
])),
|
||||
])
|
||||
|
||||
|
||||
# A list of (regex, replacement) tuples of changed key commands.
|
||||
|
||||
CHANGED_KEY_COMMANDS = [
|
||||
(re.compile(r'^open -([twb]) about:blank$'), r'open -\1'),
|
||||
(re.compile(r'^download-page$'), r'download'),
|
||||
(re.compile(r'^cancel-download$'), r'download-cancel'),
|
||||
(re.compile(r'^search ""$'), r'search'),
|
||||
(re.compile(r"^search ''$"), r'search'),
|
||||
(re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'),
|
||||
(re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'),
|
||||
]
|
||||
|
@ -32,9 +32,9 @@ class ValidationError(Error):
|
||||
"""Raised when a value for a config type was invalid.
|
||||
|
||||
Attributes:
|
||||
section: Section in which the error occured (added when catching and
|
||||
section: Section in which the error occurred (added when catching and
|
||||
re-raising the exception).
|
||||
option: Option in which the error occured.
|
||||
option: Option in which the error occurred.
|
||||
"""
|
||||
|
||||
def __init__(self, value, msg):
|
||||
|
@ -1328,7 +1328,7 @@ class AcceptCookies(BaseType):
|
||||
|
||||
"""Whether to accept a cookie."""
|
||||
|
||||
valid_values = ValidValues(('default', "Default QtWebKit behaviour."),
|
||||
valid_values = ValidValues(('default', "Default QtWebKit behavior."),
|
||||
('never', "Don't accept cookies at all."))
|
||||
|
||||
|
||||
|
@ -60,7 +60,7 @@ class ReadConfigParser(configparser.ConfigParser):
|
||||
|
||||
class ReadWriteConfigParser(ReadConfigParser):
|
||||
|
||||
"""ConfigParser subclass used for auxillary config files."""
|
||||
"""ConfigParser subclass used for auxiliary config files."""
|
||||
|
||||
def save(self):
|
||||
"""Save the config file."""
|
||||
|
@ -34,7 +34,7 @@ class KeyConfigError(Exception):
|
||||
"""Raised on errors with the key config.
|
||||
|
||||
Attributes:
|
||||
lineno: The config line in which the exception occured.
|
||||
lineno: The config line in which the exception occurred.
|
||||
"""
|
||||
|
||||
def __init__(self, msg=None):
|
||||
@ -55,13 +55,16 @@ class KeyConfigParser(QObject):
|
||||
_configfile: The filename of the config or None.
|
||||
_cur_section: The section currently being processed by _read().
|
||||
_cur_command: The command currently being processed by _read().
|
||||
is_dirty: Whether the config is currently dirty.
|
||||
|
||||
Signals:
|
||||
changed: Emitted when the config has changed.
|
||||
changed: Emitted when the internal data has changed.
|
||||
arg: Name of the mode which was changed.
|
||||
config_dirty: Emitted when the config should be re-saved.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal(str)
|
||||
config_dirty = pyqtSignal()
|
||||
|
||||
def __init__(self, configdir, fname, parent=None):
|
||||
"""Constructor.
|
||||
@ -71,9 +74,10 @@ class KeyConfigParser(QObject):
|
||||
fname: The filename of the config.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.is_dirty = False
|
||||
self._cur_section = None
|
||||
self._cur_command = None
|
||||
# Mapping of section name(s) to keybinding -> command dicts.
|
||||
# Mapping of section name(s) to key binding -> command dicts.
|
||||
self.keybindings = collections.OrderedDict()
|
||||
if configdir is None:
|
||||
self._configfile = None
|
||||
@ -165,6 +169,7 @@ class KeyConfigParser(QObject):
|
||||
raise cmdexc.CommandError(e)
|
||||
for m in mode.split(','):
|
||||
self.changed.emit(m)
|
||||
self._mark_config_dirty()
|
||||
|
||||
@cmdutils.register(instance='key-config')
|
||||
def unbind(self, key, mode=None):
|
||||
@ -194,6 +199,7 @@ class KeyConfigParser(QObject):
|
||||
else:
|
||||
for m in mode.split(','):
|
||||
self.changed.emit(m)
|
||||
self._mark_config_dirty()
|
||||
|
||||
def _normalize_sectname(self, s):
|
||||
"""Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'."""
|
||||
@ -208,7 +214,7 @@ class KeyConfigParser(QObject):
|
||||
return sections
|
||||
|
||||
def _load_default(self):
|
||||
"""Load the built-in default keybindings."""
|
||||
"""Load the built-in default key bindings."""
|
||||
for sectname, sect in configdata.KEY_DATA.items():
|
||||
sectname = self._normalize_sectname(sectname)
|
||||
if not sect:
|
||||
@ -242,10 +248,15 @@ class KeyConfigParser(QObject):
|
||||
e.lineno = i
|
||||
raise
|
||||
except OSError:
|
||||
log.keyboard.exception("Failed to read keybindings!")
|
||||
log.keyboard.exception("Failed to read key bindings!")
|
||||
for sectname in self.keybindings:
|
||||
self.changed.emit(sectname)
|
||||
|
||||
def _mark_config_dirty(self):
|
||||
"""Mark the config as dirty."""
|
||||
self.is_dirty = True
|
||||
self.config_dirty.emit()
|
||||
|
||||
def _read_command(self, line):
|
||||
"""Read a command from a line."""
|
||||
if self._cur_section is None:
|
||||
@ -255,12 +266,17 @@ class KeyConfigParser(QObject):
|
||||
command = line.split(maxsplit=1)[0]
|
||||
if command not in cmdutils.cmd_dict:
|
||||
raise KeyConfigError("Invalid command '{}'!".format(command))
|
||||
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
|
||||
if rgx.match(line):
|
||||
line = rgx.sub(repl, line)
|
||||
self._mark_config_dirty()
|
||||
break
|
||||
self._cur_command = line
|
||||
|
||||
def _read_keybinding(self, line):
|
||||
"""Read a keybinding from a line."""
|
||||
"""Read a key binding from a line."""
|
||||
if self._cur_command is None:
|
||||
raise KeyConfigError("Got keybinding '{}' without getting a "
|
||||
raise KeyConfigError("Got key binding '{}' without getting a "
|
||||
"command!".format(line))
|
||||
else:
|
||||
assert self._cur_section is not None
|
||||
@ -280,7 +296,7 @@ class KeyConfigParser(QObject):
|
||||
self.keybindings[sectname][keychain] = command
|
||||
|
||||
def get_bindings_for(self, section):
|
||||
"""Get a dict with all merged keybindings for a section."""
|
||||
"""Get a dict with all merged key bindings for a section."""
|
||||
bindings = {}
|
||||
for sectstring, d in self.keybindings.items():
|
||||
if sectstring.startswith('!'):
|
||||
|
@ -133,7 +133,7 @@ class ValueList(Section):
|
||||
"""This class represents a section with a list key-value settings.
|
||||
|
||||
These are settings inside sections which don't have fixed keys, but instead
|
||||
have a dynamic list of "key = value" pairs, like keybindings or
|
||||
have a dynamic list of "key = value" pairs, like key bindings or
|
||||
searchengines.
|
||||
|
||||
They basically consist of two different SettingValues.
|
||||
|
@ -94,7 +94,7 @@ class ColorDict(dict):
|
||||
log.style.exception("No color defined for {}!")
|
||||
return ''
|
||||
if isinstance(val, QColor):
|
||||
# This could happen when accidentaly declarding something as
|
||||
# This could happen when accidentally declaring something as
|
||||
# QtColor instead of Color in the config, and it'd go unnoticed as
|
||||
# the CSS is invalid then.
|
||||
raise TypeError("QColor passed to ColorDict!")
|
||||
|
@ -26,7 +26,7 @@ class SettingValue:
|
||||
|
||||
"""Base class for setting values.
|
||||
|
||||
Intended to be subclassed by config value "types".
|
||||
Intended to be sub-classed by config value "types".
|
||||
|
||||
Attributes:
|
||||
typ: A BaseType subclass instance.
|
||||
|
@ -24,12 +24,9 @@ Module attributes:
|
||||
constants.
|
||||
"""
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import standarddir, objreg, log, utils, debug
|
||||
@ -194,22 +191,6 @@ class Setter(Base):
|
||||
self._setter(*args)
|
||||
|
||||
|
||||
class AprilSetter(Setter):
|
||||
|
||||
"""Set something... unless it's the 1st of April."""
|
||||
|
||||
def _set(self, value, qws=None):
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
fooled = state_config['general']['fooled']
|
||||
except KeyError:
|
||||
fooled = False
|
||||
if datetime.date.today() == datetime.date(2015, 4, 1) and not fooled:
|
||||
pass
|
||||
else:
|
||||
super()._set(value, qws)
|
||||
|
||||
|
||||
class NullStringSetter(Setter):
|
||||
|
||||
"""A setter for settings requiring a null QString as default.
|
||||
@ -336,8 +317,8 @@ MAPPINGS = {
|
||||
'frame-flattening':
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
'user-stylesheet':
|
||||
AprilSetter(getter=QWebSettings.userStyleSheetUrl,
|
||||
setter=QWebSettings.setUserStyleSheetUrl),
|
||||
Setter(getter=QWebSettings.userStyleSheetUrl,
|
||||
setter=QWebSettings.setUserStyleSheetUrl),
|
||||
'css-media-type':
|
||||
NullStringSetter(getter=QWebSettings.cssMediaType,
|
||||
setter=QWebSettings.setCSSMediaType),
|
||||
@ -399,21 +380,6 @@ def init():
|
||||
QWebSettings.setOfflineStoragePath(
|
||||
os.path.join(standarddir.data(), 'offline-storage'))
|
||||
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
fooled = state_config['general']['fooled']
|
||||
except KeyError:
|
||||
fooled = False
|
||||
if datetime.date.today() == datetime.date(2015, 4, 1) and not fooled:
|
||||
value = """
|
||||
html {
|
||||
-webkit-transform:rotate(3deg) scale(0.99);
|
||||
}
|
||||
"""
|
||||
data = base64.b64encode(value.encode('utf-8')).decode('ascii')
|
||||
url = QUrl("data:text/css;charset=utf-8;base64,{}".format(data))
|
||||
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
|
||||
|
||||
for sectname, section in MAPPINGS.items():
|
||||
for optname, mapping in section.items():
|
||||
default = mapping.save_default()
|
||||
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Base class for vim-like keysequence parser."""
|
||||
"""Base class for vim-like key sequence parser."""
|
||||
|
||||
import re
|
||||
import functools
|
||||
@ -44,20 +44,20 @@ class BaseKeyParser(QObject):
|
||||
ambiguous: There are both a partial and a definitive match.
|
||||
none: No more matches possible.
|
||||
|
||||
Types: type of a keybinding.
|
||||
chain: execute() was called via a chain-like keybinding
|
||||
special: execute() was called via a special keybinding
|
||||
Types: type of a key binding.
|
||||
chain: execute() was called via a chain-like key binding
|
||||
special: execute() was called via a special key binding
|
||||
|
||||
do_log: Whether to log keypresses or not.
|
||||
|
||||
Attributes:
|
||||
bindings: Bound keybindings
|
||||
bindings: Bound key bindings
|
||||
special_bindings: Bound special bindings (<Foo>).
|
||||
_win_id: The window ID this keyparser is associated with.
|
||||
_warn_on_keychains: Whether a warning should be logged when binding
|
||||
keychains in a section which does not support them.
|
||||
_keystring: The currently entered key sequence
|
||||
_ambigious_timer: Timer for delayed execution with ambigious bindings.
|
||||
_ambiguous_timer: Timer for delayed execution with ambiguous bindings.
|
||||
_modename: The name of the input mode associated with this keyparser.
|
||||
_supports_count: Whether count is supported
|
||||
_supports_chains: Whether keychains are supported
|
||||
@ -78,8 +78,8 @@ class BaseKeyParser(QObject):
|
||||
supports_chains=False):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._ambigious_timer = usertypes.Timer(self, 'ambigious-match')
|
||||
self._ambigious_timer.setSingleShot(True)
|
||||
self._ambiguous_timer = usertypes.Timer(self, 'ambiguous-match')
|
||||
self._ambiguous_timer.setSingleShot(True)
|
||||
self._modename = None
|
||||
self._keystring = ''
|
||||
if supports_count is None:
|
||||
@ -248,11 +248,11 @@ class BaseKeyParser(QObject):
|
||||
|
||||
def _stop_timers(self):
|
||||
"""Stop a delayed execution if any is running."""
|
||||
if self._ambigious_timer.isActive() and self.do_log:
|
||||
if self._ambiguous_timer.isActive() and self.do_log:
|
||||
log.keyboard.debug("Stopping delayed execution.")
|
||||
self._ambigious_timer.stop()
|
||||
self._ambiguous_timer.stop()
|
||||
try:
|
||||
self._ambigious_timer.timeout.disconnect()
|
||||
self._ambiguous_timer.timeout.disconnect()
|
||||
except TypeError:
|
||||
# no connections
|
||||
pass
|
||||
@ -274,10 +274,10 @@ class BaseKeyParser(QObject):
|
||||
# execute in `time' ms
|
||||
self._debug_log("Scheduling execution of {} in {}ms".format(
|
||||
binding, time))
|
||||
self._ambigious_timer.setInterval(time)
|
||||
self._ambigious_timer.timeout.connect(
|
||||
self._ambiguous_timer.setInterval(time)
|
||||
self._ambiguous_timer.timeout.connect(
|
||||
functools.partial(self.delayed_exec, binding, count))
|
||||
self._ambigious_timer.start()
|
||||
self._ambiguous_timer.start()
|
||||
|
||||
def delayed_exec(self, command, count):
|
||||
"""Execute a delayed command.
|
||||
@ -350,7 +350,7 @@ class BaseKeyParser(QObject):
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_keyconfig_changed(self, mode):
|
||||
"""Re-read the config if a keybinding was changed."""
|
||||
"""Re-read the config if a key binding was changed."""
|
||||
if self._modename is None:
|
||||
raise AttributeError("on_keyconfig_changed called but no section "
|
||||
"defined!")
|
||||
|
@ -32,6 +32,33 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
|
||||
|
||||
class KeyEvent:
|
||||
|
||||
"""A small wrapper over a QKeyEvent storing its data.
|
||||
|
||||
This is needed because Qt apparently mutates existing events with new data.
|
||||
It doesn't store the modifiers because they can be different for a key
|
||||
press/release.
|
||||
|
||||
Attributes:
|
||||
key: A Qt.Key member (QKeyEvent::key).
|
||||
text: A string (QKeyEvent::text).
|
||||
"""
|
||||
|
||||
def __init__(self, keyevent):
|
||||
self.key = keyevent.key()
|
||||
self.text = keyevent.text()
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, key=self.key, text=self.text)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.key == other.key and self.text == other.text
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.key, self.text))
|
||||
|
||||
|
||||
class NotInModeError(Exception):
|
||||
|
||||
"""Exception raised when we want to leave a mode we're not in."""
|
||||
@ -95,7 +122,7 @@ def maybe_leave(win_id, mode, reason=None):
|
||||
|
||||
class EventFilter(QObject):
|
||||
|
||||
"""Event filter which passes the event to the corrent ModeManager."""
|
||||
"""Event filter which passes the event to the current ModeManager."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -143,7 +170,7 @@ class ModeManager(QObject):
|
||||
_win_id: The window ID of this ModeManager
|
||||
_handlers: A dictionary of modes and their handlers.
|
||||
_forward_unbound_keys: If we should forward unbound keys.
|
||||
_releaseevents_to_pass: A list of keys where the keyPressEvent was
|
||||
_releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was
|
||||
passed through, so the release event should as
|
||||
well.
|
||||
|
||||
@ -166,7 +193,7 @@ class ModeManager(QObject):
|
||||
self._handlers = {}
|
||||
self.passthrough = []
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = []
|
||||
self._releaseevents_to_pass = set()
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
|
||||
@ -207,7 +234,7 @@ class ModeManager(QObject):
|
||||
filter_this = True
|
||||
|
||||
if not filter_this:
|
||||
self._releaseevents_to_pass.append(event)
|
||||
self._releaseevents_to_pass.add(KeyEvent(event))
|
||||
|
||||
if curmode != usertypes.KeyMode.insert:
|
||||
log.modes.debug("handled: {}, forward-unbound-keys: {}, "
|
||||
@ -228,10 +255,9 @@ class ModeManager(QObject):
|
||||
True if event should be filtered, False otherwise.
|
||||
"""
|
||||
# handle like matching KeyPress
|
||||
if event in self._releaseevents_to_pass:
|
||||
# remove all occurences
|
||||
self._releaseevents_to_pass = [
|
||||
e for e in self._releaseevents_to_pass if e != event]
|
||||
keyevent = KeyEvent(event)
|
||||
if keyevent in self._releaseevents_to_pass:
|
||||
self._releaseevents_to_pass.remove(keyevent)
|
||||
filter_this = False
|
||||
else:
|
||||
filter_this = True
|
||||
@ -245,7 +271,7 @@ class ModeManager(QObject):
|
||||
Args:
|
||||
mode: The name of the mode.
|
||||
handler: Handler for keyPressEvents.
|
||||
passthrough: Whether to pass keybindings in this mode through to
|
||||
passthrough: Whether to pass key bindings in this mode through to
|
||||
the widgets.
|
||||
"""
|
||||
if not isinstance(mode, usertypes.KeyMode):
|
||||
|
@ -37,7 +37,7 @@ LastPress = usertypes.enum('LastPress', ['none', 'filtertext', 'keystring'])
|
||||
|
||||
class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
|
||||
"""KeyParser for normalmode with added STARTCHARS detection and more.
|
||||
"""KeyParser for normal mode with added STARTCHARS detection and more.
|
||||
|
||||
Attributes:
|
||||
_partial_timer: Timer to clear partial keypresses.
|
||||
|
@ -43,7 +43,7 @@ class MainWindow(QWidget):
|
||||
|
||||
"""The main window of qutebrowser.
|
||||
|
||||
Adds all needed components to a vbox, initializes subwidgets and connects
|
||||
Adds all needed components to a vbox, initializes sub-widgets and connects
|
||||
signals.
|
||||
|
||||
Attributes:
|
||||
@ -84,7 +84,7 @@ class MainWindow(QWidget):
|
||||
self._load_state_geometry()
|
||||
else:
|
||||
self._set_default_geometry()
|
||||
log.init.debug("Initial mainwindow geometry: {}".format(
|
||||
log.init.debug("Initial main window geometry: {}".format(
|
||||
self.geometry()))
|
||||
self._vbox = QVBoxLayout(self)
|
||||
self._vbox.setContentsMargins(0, 0, 0, 0)
|
||||
@ -242,6 +242,8 @@ class MainWindow(QWidget):
|
||||
tabs.current_tab_changed.connect(status.percentage.on_tab_changed)
|
||||
tabs.cur_scroll_perc_changed.connect(status.percentage.set_perc)
|
||||
|
||||
tabs.tab_index_changed.connect(status.tabindex.on_tab_index_changed)
|
||||
|
||||
tabs.current_tab_changed.connect(status.txt.on_tab_changed)
|
||||
tabs.cur_statusbar_message.connect(status.txt.on_statusbar_message)
|
||||
tabs.cur_load_started.connect(status.txt.on_load_started)
|
||||
|
@ -28,7 +28,8 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
from qutebrowser.mainwindow.statusbar import (command, progress, keystring,
|
||||
percentage, url, prompt)
|
||||
percentage, url, prompt,
|
||||
tabindex)
|
||||
from qutebrowser.mainwindow.statusbar import text as textwidget
|
||||
|
||||
|
||||
@ -174,6 +175,9 @@ class StatusBar(QWidget):
|
||||
self.percentage = percentage.Percentage()
|
||||
self._hbox.addWidget(self.percentage)
|
||||
|
||||
self.tabindex = tabindex.TabIndex()
|
||||
self._hbox.addWidget(self.tabindex)
|
||||
|
||||
# We add a parent to Progress here because it calls self.show() based
|
||||
# on some signals, and if that happens before it's added to the layout,
|
||||
# it will quickly blink up as independent window.
|
||||
|
@ -98,7 +98,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
@cmdutils.register(instance='status-command', name='set-cmd-text',
|
||||
scope='window', maxsplit=0)
|
||||
def set_cmd_text_command(self, text):
|
||||
def set_cmd_text_command(self, text, space=False):
|
||||
"""Preset the statusbar to some text.
|
||||
|
||||
//
|
||||
@ -108,6 +108,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
Args:
|
||||
text: The commandline to set.
|
||||
space: If given, a space is added to the end.
|
||||
"""
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
@ -127,7 +128,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
# I'm not sure what's the best thing to do here
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/123
|
||||
text = text.replace('{url}', url)
|
||||
if not text[0] in modeparsers.STARTCHARS:
|
||||
if space:
|
||||
text += ' '
|
||||
if not text or text[0] not in modeparsers.STARTCHARS:
|
||||
raise cmdexc.CommandError(
|
||||
"Invalid command text '{}'.".format(text))
|
||||
self.set_cmd_text(text)
|
||||
@ -179,7 +182,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
def on_mode_left(self, mode):
|
||||
"""Clear up when command mode was left.
|
||||
|
||||
- Clear the statusbar text if it's explicitely unfocused.
|
||||
- Clear the statusbar text if it's explicitly unfocused.
|
||||
- Clear completion selection
|
||||
- Hide completion
|
||||
|
||||
|
@ -187,7 +187,7 @@ class Prompter(QObject):
|
||||
def shutdown(self):
|
||||
"""Cancel all blocking questions.
|
||||
|
||||
Quits and removes all running eventloops.
|
||||
Quits and removes all running event loops.
|
||||
|
||||
Return:
|
||||
True if loops needed to be aborted,
|
||||
|
34
qutebrowser/mainwindow/statusbar/tabindex.py
Normal file
34
qutebrowser/mainwindow/statusbar/tabindex.py
Normal file
@ -0,0 +1,34 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 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/>.
|
||||
|
||||
"""TabIndex displayed in the statusbar."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
|
||||
from qutebrowser.mainwindow.statusbar import textbase
|
||||
|
||||
|
||||
class TabIndex(textbase.TextBase):
|
||||
|
||||
"""Shows current tab index and number of tabs in the statusbar."""
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def on_tab_index_changed(self, current, count):
|
||||
"""Update tab index when tab changed."""
|
||||
self.setText('[{}/{}]'.format(current + 1, count))
|
@ -32,7 +32,7 @@ class TextBase(QLabel):
|
||||
|
||||
Unlike QLabel, the text will get elided.
|
||||
|
||||
Eliding is loosly based on
|
||||
Eliding is loosely based on
|
||||
http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html
|
||||
|
||||
Attributes:
|
||||
@ -64,7 +64,7 @@ class TextBase(QLabel):
|
||||
|
||||
This update the elided text after setting the text, and also works
|
||||
around a weird QLabel redrawing bug where it doesn't redraw correctly
|
||||
when the text is empty -- we explicitely need to call repaint() to
|
||||
when the text is empty -- we explicitly need to call repaint() to
|
||||
resolve this.
|
||||
|
||||
More info:
|
||||
|
@ -40,7 +40,7 @@ class UrlText(textbase.TextBase):
|
||||
_normal_url: The normal URL to be displayed as a UrlType instance.
|
||||
_normal_url_type: The type of the normal URL as a UrlType instance.
|
||||
_hover_url: The URL we're currently hovering over.
|
||||
_ssl_errors: Whether SSL errors occured while loading.
|
||||
_ssl_errors: Whether SSL errors occurred while loading.
|
||||
|
||||
Class attributes:
|
||||
_urltype: The URL type to show currently (normal/ok/error/warn/hover).
|
||||
|
@ -48,12 +48,12 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
"""A TabWidget with QWebViews inside.
|
||||
|
||||
Provides methods to manage tabs, convenience methods to interact with the
|
||||
current tab (cur_*) and filters signals to re-emit them when they occured
|
||||
current tab (cur_*) and filters signals to re-emit them when they occurred
|
||||
in the currently visible tab.
|
||||
|
||||
For all tab-specific signals (cur_*) emitted by a tab, this happens:
|
||||
- the signal gets filtered with _filter_signals and self.cur_* gets
|
||||
emitted if the signal occured in the current tab.
|
||||
emitted if the signal occurred in the current tab.
|
||||
|
||||
Attributes:
|
||||
_win_id: The window ID this tabbedbrowser is associated with.
|
||||
@ -331,7 +331,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
url: The URL to open as QUrl or None for an empty tab.
|
||||
background: Whether to open the tab in the background.
|
||||
if None, the background-tabs setting decides.
|
||||
explicit: Whether the tab was opened explicitely.
|
||||
explicit: Whether the tab was opened explicitly.
|
||||
If this is set, the new position might be different. With
|
||||
the default settings we handle it like Chromium does:
|
||||
- Tabs from clicked links etc. are to the right of
|
||||
@ -368,7 +368,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
"""Get the index of a tab to insert.
|
||||
|
||||
Args:
|
||||
explicit: Whether the tab was opened explicitely.
|
||||
explicit: Whether the tab was opened explicitly.
|
||||
|
||||
Return:
|
||||
The index of the new tab.
|
||||
@ -590,7 +590,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if tab.page().error_occured:
|
||||
if tab.page().error_occurred:
|
||||
color = config.get('colors', 'tabs.indicator.error')
|
||||
else:
|
||||
start = config.get('colors', 'tabs.indicator.start')
|
||||
|
@ -26,7 +26,7 @@ Module attributes:
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QSize, QRect, QPoint, QTimer
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer
|
||||
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
|
||||
QStyle, QStylePainter, QStyleOptionTab)
|
||||
from PyQt5.QtGui import QIcon, QPalette, QColor
|
||||
@ -41,7 +41,15 @@ PM_TabBarPadding = QStyle.PM_CustomBase
|
||||
|
||||
class TabWidget(QTabWidget):
|
||||
|
||||
"""The tabwidget used for TabbedBrowser."""
|
||||
"""The tab widget used for TabbedBrowser.
|
||||
|
||||
Signals:
|
||||
tab_index_changed: Emitted when the current tab was changed.
|
||||
arg 0: The index of the tab which is now focused.
|
||||
arg 1: The total count of tabs.
|
||||
"""
|
||||
|
||||
tab_index_changed = pyqtSignal(int, int)
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
@ -50,6 +58,7 @@ class TabWidget(QTabWidget):
|
||||
bar.tabCloseRequested.connect(self.tabCloseRequested)
|
||||
bar.tabMoved.connect(functools.partial(
|
||||
QTimer.singleShot, 0, self.update_tab_titles))
|
||||
bar.currentChanged.connect(self.emit_tab_index_changed)
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||
self.setDocumentMode(True)
|
||||
self.setElideMode(Qt.ElideRight)
|
||||
@ -65,10 +74,10 @@ class TabWidget(QTabWidget):
|
||||
self.setMovable(config.get('tabs', 'movable'))
|
||||
self.setTabsClosable(False)
|
||||
position = config.get('tabs', 'position')
|
||||
selection_behaviour = config.get('tabs', 'select-on-remove')
|
||||
selection_behavior = config.get('tabs', 'select-on-remove')
|
||||
self.setTabPosition(position)
|
||||
tabbar.vertical = position in (QTabWidget.West, QTabWidget.East)
|
||||
tabbar.setSelectionBehaviorOnRemove(selection_behaviour)
|
||||
tabbar.setSelectionBehaviorOnRemove(selection_behavior)
|
||||
tabbar.refresh()
|
||||
|
||||
def set_tab_indicator_color(self, idx, color):
|
||||
@ -84,7 +93,7 @@ class TabWidget(QTabWidget):
|
||||
|
||||
def set_page_title(self, idx, title):
|
||||
"""Set the tab title user data."""
|
||||
self.tabBar().set_tab_data(idx, 'page-title', title.replace('&', '&&'))
|
||||
self.tabBar().set_tab_data(idx, 'page-title', title)
|
||||
self.update_tab_title(idx)
|
||||
|
||||
def page_title(self, idx):
|
||||
@ -94,7 +103,7 @@ class TabWidget(QTabWidget):
|
||||
def update_tab_title(self, idx):
|
||||
"""Update the tab text for the given tab."""
|
||||
widget = self.widget(idx)
|
||||
page_title = self.page_title(idx)
|
||||
page_title = self.page_title(idx).replace('&', '&&')
|
||||
|
||||
fields = {}
|
||||
if widget.load_status == webview.LoadStatus.loading:
|
||||
@ -184,10 +193,15 @@ class TabWidget(QTabWidget):
|
||||
self.set_page_title(new_idx, text)
|
||||
return new_idx
|
||||
|
||||
@pyqtSlot(int)
|
||||
def emit_tab_index_changed(self, index):
|
||||
"""Emit the tab_index_changed signal if the current tab changed."""
|
||||
self.tab_index_changed.emit(index, self.count())
|
||||
|
||||
|
||||
class TabBar(QTabBar):
|
||||
|
||||
"""Custom tabbar with our own style.
|
||||
"""Custom tab bar with our own style.
|
||||
|
||||
FIXME: Dragging tabs doesn't look as nice as it does in QTabBar. However,
|
||||
fixing this would be a lot of effort, so we'll postpone it until we're
|
||||
@ -221,16 +235,16 @@ class TabBar(QTabBar):
|
||||
|
||||
@config.change_filter('tabs', 'hide-auto')
|
||||
def autohide(self):
|
||||
"""Hide tabbar if needed when tabs->hide-auto got changed."""
|
||||
"""Hide tab bar if needed when tabs->hide-auto got changed."""
|
||||
self._tabhide()
|
||||
|
||||
@config.change_filter('tabs', 'hide-always')
|
||||
def alwayshide(self):
|
||||
"""Hide tabbar if needed when tabs->hide-always got changed."""
|
||||
"""Hide tab bar if needed when tabs->hide-always got changed."""
|
||||
self._tabhide()
|
||||
|
||||
def _tabhide(self):
|
||||
"""Hide the tabbar if needed."""
|
||||
"""Hide the tab bar if needed."""
|
||||
hide_auto = config.get('tabs', 'hide-auto')
|
||||
hide_always = config.get('tabs', 'hide-always')
|
||||
if hide_always or (hide_auto and self.count() == 1):
|
||||
@ -264,7 +278,7 @@ class TabBar(QTabBar):
|
||||
|
||||
Args:
|
||||
idx: The tab index to get the title for.
|
||||
handle_unset: Whether to return an emtpy string on KeyError.
|
||||
handle_unset: Whether to return an empty string on KeyError.
|
||||
"""
|
||||
try:
|
||||
return self.tab_data(idx, 'page-title')
|
||||
@ -279,12 +293,12 @@ class TabBar(QTabBar):
|
||||
|
||||
@config.change_filter('fonts', 'tabbar')
|
||||
def set_font(self):
|
||||
"""Set the tabbar font."""
|
||||
"""Set the tab bar font."""
|
||||
self.setFont(config.get('fonts', 'tabbar'))
|
||||
|
||||
@config.change_filter('colors', 'tabs.bg.bar')
|
||||
def set_colors(self):
|
||||
"""Set the tabbar colors."""
|
||||
"""Set the tab bar colors."""
|
||||
p = self.palette()
|
||||
p.setColor(QPalette.Window, config.get('colors', 'tabs.bg.bar'))
|
||||
self.setPalette(p)
|
||||
@ -311,7 +325,7 @@ class TabBar(QTabBar):
|
||||
"""Set the minimum tab size to indicator/icon/... text.
|
||||
|
||||
Args:
|
||||
index: The index of the tab to get a sizehint for.
|
||||
index: The index of the tab to get a size hint for.
|
||||
|
||||
Return:
|
||||
A QSize.
|
||||
|
@ -149,7 +149,7 @@ class ConsoleWidget(QWidget):
|
||||
_output: The output widget in the console.
|
||||
_vbox: The layout which contains everything.
|
||||
_more: A flag which is set when more input is expected.
|
||||
_buffer: The buffer for multiline commands.
|
||||
_buffer: The buffer for multi-line commands.
|
||||
_interpreter: The InteractiveInterpreter to execute code with.
|
||||
"""
|
||||
|
||||
@ -195,13 +195,13 @@ class ConsoleWidget(QWidget):
|
||||
self._buffer.append(line)
|
||||
source = '\n'.join(self._buffer)
|
||||
self.write(line + '\n')
|
||||
# We do two special things with the contextmanagers here:
|
||||
# We do two special things with the context managers here:
|
||||
# - We replace stdout/stderr to capture output. Even if we could
|
||||
# override InteractiveInterpreter's write method, most things are
|
||||
# printed elsewhere (e.g. by exec). Other Python GUI shells do the
|
||||
# same.
|
||||
# - We disable our exception hook, so exceptions from the console get
|
||||
# printed and don't ooen a crashdialog.
|
||||
# printed and don't open a crashdialog.
|
||||
with utils.fake_io(self.write), utils.disabled_excepthook():
|
||||
self._more = self._interpreter.runsource(source, '<console>')
|
||||
self.write(self._curprompt())
|
||||
|
@ -82,7 +82,7 @@ def get_fatal_crash_dialog(debug, data):
|
||||
else:
|
||||
title = "qutebrowser was restarted after a fatal crash!"
|
||||
text = ("<b>qutebrowser was restarted after a fatal crash!</b><br/>"
|
||||
"Unfortunately, this crash occured in Qt (the library "
|
||||
"Unfortunately, this crash occurred in Qt (the library "
|
||||
"qutebrowser uses), and your version ({}) is outdated - "
|
||||
"Qt 5.4 or later is recommended. Unfortuntately Debian and "
|
||||
"Ubuntu don't ship a newer version (yet?)...".format(
|
||||
@ -403,12 +403,12 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
|
||||
class FatalCrashDialog(_CrashDialog):
|
||||
|
||||
"""Dialog which gets shown when a fatal error occured.
|
||||
"""Dialog which gets shown when a fatal error occurred.
|
||||
|
||||
Attributes:
|
||||
_log: The log text to display.
|
||||
_type: The type of error which occured.
|
||||
_func: The function (top of the stack) in which the error occured.
|
||||
_type: The type of error which occurred.
|
||||
_func: The function (top of the stack) in which the error occurred.
|
||||
_chk_history: A checkbox for the user to decide if page history should
|
||||
be sent.
|
||||
"""
|
||||
|
@ -20,6 +20,13 @@
|
||||
At this point we can be sure we have all python 3.4 features available.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Importing hunter to register its atexit handler early so it gets called
|
||||
# late.
|
||||
import hunter # pylint: disable=import-error,unused-import
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
import os
|
||||
import sys
|
||||
import faulthandler
|
||||
@ -32,7 +39,7 @@ try:
|
||||
except ImportError:
|
||||
tkinter = None
|
||||
# NOTE: No qutebrowser or PyQt import should be done here, as some early
|
||||
# initialisation needs to take place before that!
|
||||
# initialization needs to take place before that!
|
||||
|
||||
|
||||
def _missing_str(name, *, windows=None, pip=None):
|
||||
@ -90,7 +97,7 @@ def _die(message, exception=None):
|
||||
def init_faulthandler(fileobj=sys.__stderr__):
|
||||
"""Enable faulthandler module if available.
|
||||
|
||||
This print a nice traceback on segfauls.
|
||||
This print a nice traceback on segfaults.
|
||||
|
||||
We use sys.__stderr__ instead of sys.stderr here so this will still work
|
||||
when sys.stderr got replaced, e.g. by "Python Tools for Visual Studio".
|
||||
@ -156,7 +163,7 @@ def fix_harfbuzz(args):
|
||||
elif args.harfbuzz in ('old', 'new'):
|
||||
# forced harfbuzz variant
|
||||
# FIXME looking at the Qt code, 'new' isn't a valid value, but leaving
|
||||
# it empty and using new yields different behaviour...
|
||||
# it empty and using new yields different behavior...
|
||||
# (probably irrelevant when workaround gets removed)
|
||||
log.init.debug("Using {} harfbuzz engine (forced)".format(
|
||||
args.harfbuzz))
|
||||
@ -256,7 +263,7 @@ def init_log(args):
|
||||
|
||||
|
||||
def earlyinit(args):
|
||||
"""Do all needed early initialisation.
|
||||
"""Do all needed early initialization.
|
||||
|
||||
Note that it's vital the other earlyinit functions get called in the right
|
||||
order!
|
||||
@ -265,7 +272,7 @@ def earlyinit(args):
|
||||
args: The argparse namespace.
|
||||
"""
|
||||
# First we initialize the faulthandler as early as possible, so we
|
||||
# theoretically could catch segfaults occuring later during earlyinit.
|
||||
# theoretically could catch segfaults occurring later during earlyinit.
|
||||
init_faulthandler()
|
||||
# Here we check if QtCore is available, and if not, print a message to the
|
||||
# console or via Tk.
|
||||
|
@ -127,7 +127,7 @@ class _CommandValidator(QValidator):
|
||||
|
||||
Args:
|
||||
string: The string to validate.
|
||||
pos: The current curser position.
|
||||
pos: The current cursor position.
|
||||
|
||||
Return:
|
||||
A tuple (status, string, pos) as a QValidator should.
|
||||
|
@ -22,7 +22,7 @@
|
||||
import os.path
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdutils
|
||||
@ -39,7 +39,8 @@ class Saveable:
|
||||
_save_handler: The function to call to save this Saveable.
|
||||
_save_on_exit: Whether to always save this saveable on exit.
|
||||
_config_opt: A (section, option) tuple of a config option which decides
|
||||
whether to autosave or not. None if no such option exists.
|
||||
whether to auto-save or not. None if no such option
|
||||
exists.
|
||||
_filename: The filename of the underlying file.
|
||||
"""
|
||||
|
||||
@ -77,7 +78,7 @@ class Saveable:
|
||||
|
||||
Args:
|
||||
is_exit: Whether we're currently exiting qutebrowser.
|
||||
explicit: Whether the user explicitely requested this save.
|
||||
explicit: Whether the user explicitly requested this save.
|
||||
silent: Don't write informations to log.
|
||||
force: Force saving, no matter what.
|
||||
"""
|
||||
@ -119,7 +120,7 @@ class SaveManager(QObject):
|
||||
return utils.get_repr(self, saveables=self.saveables)
|
||||
|
||||
def init_autosave(self):
|
||||
"""Initialize autosaving.
|
||||
"""Initialize auto-saving.
|
||||
|
||||
We don't do this in __init__ because the config needs to be initialized
|
||||
first, but the config needs the save manager.
|
||||
@ -129,7 +130,7 @@ class SaveManager(QObject):
|
||||
|
||||
@config.change_filter('general', 'auto-save-interval')
|
||||
def set_autosave_interval(self):
|
||||
"""Set the autosave interval."""
|
||||
"""Set the auto-save interval."""
|
||||
interval = config.get('general', 'auto-save-interval')
|
||||
if interval == 0:
|
||||
self._save_timer.stop()
|
||||
@ -138,22 +139,26 @@ class SaveManager(QObject):
|
||||
self._save_timer.start()
|
||||
|
||||
def add_saveable(self, name, save, changed=None, config_opt=None,
|
||||
filename=None):
|
||||
filename=None, dirty=False):
|
||||
"""Add a new saveable.
|
||||
|
||||
Args:
|
||||
name: The name to use.
|
||||
save: The function to call to save this saveable.
|
||||
changed: The signal emitted when this saveable changed.
|
||||
config_opt: A (section, option) tuple deciding whether to autosave
|
||||
config_opt: A (section, option) tuple deciding whether to auto-save
|
||||
or not.
|
||||
filename: The filename of the underlying file, so we can force
|
||||
saving if it doesn't exist.
|
||||
dirty: Whether the saveable is already dirty.
|
||||
"""
|
||||
if name in self.saveables:
|
||||
raise ValueError("Saveable {} already registered!".format(name))
|
||||
self.saveables[name] = Saveable(name, save, changed, config_opt,
|
||||
filename)
|
||||
saveable = Saveable(name, save, changed, config_opt, filename)
|
||||
self.saveables[name] = saveable
|
||||
if dirty:
|
||||
saveable.mark_dirty()
|
||||
QTimer.singleShot(0, saveable.save)
|
||||
|
||||
def save(self, name, is_exit=False, explicit=False, silent=False,
|
||||
force=False):
|
||||
@ -161,7 +166,7 @@ class SaveManager(QObject):
|
||||
|
||||
Args:
|
||||
is_exit: Whether we're currently exiting qutebrowser.
|
||||
explicit: Whether this save operation was triggered explicitely.
|
||||
explicit: Whether this save operation was triggered explicitly.
|
||||
silent: Don't write informations to log. Used to reduce log spam
|
||||
when autosaving.
|
||||
force: Force saving, no matter what.
|
||||
|
@ -21,7 +21,6 @@
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import configparser
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, QObject, QPoint, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@ -185,10 +184,6 @@ class SessionManager(QObject):
|
||||
self.update_completion.emit()
|
||||
if load_next_time:
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
state_config.add_section('general')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['general']['session'] = name
|
||||
|
||||
def save_last_window_session(self):
|
||||
@ -266,13 +261,18 @@ class SessionManager(QObject):
|
||||
|
||||
@cmdutils.register(completion=[usertypes.Completion.sessions],
|
||||
instance='session-manager')
|
||||
def session_load(self, name, clear=False):
|
||||
def session_load(self, name, clear=False, force=False):
|
||||
"""Load a session.
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
clear: Close all existing windows.
|
||||
force: Force loading internal sessions (starting with an
|
||||
underline).
|
||||
"""
|
||||
if name.startswith('_') and not force:
|
||||
raise cmdexc.CommandError("{!r} is an internal session, use "
|
||||
"--force to load anyways.".format(name))
|
||||
old_windows = list(objreg.window_registry.values())
|
||||
try:
|
||||
self.load(name)
|
||||
@ -290,14 +290,18 @@ class SessionManager(QObject):
|
||||
completion=[usertypes.Completion.sessions],
|
||||
instance='session-manager')
|
||||
def session_save(self, win_id: {'special': 'win_id'}, name='default',
|
||||
quiet=False):
|
||||
quiet=False, force=False):
|
||||
"""Save a session.
|
||||
|
||||
Args:
|
||||
win_id: The current window ID.
|
||||
name: The name of the session.
|
||||
quiet: Don't show confirmation message.
|
||||
force: Force saving internal sessions (starting with an underline).
|
||||
"""
|
||||
if name.startswith('_') and not force:
|
||||
raise cmdexc.CommandError("{!r} is an internal session, use "
|
||||
"--force to save anyways.".format(name))
|
||||
try:
|
||||
self.save(name)
|
||||
except SessionError as e:
|
||||
@ -310,12 +314,18 @@ class SessionManager(QObject):
|
||||
|
||||
@cmdutils.register(completion=[usertypes.Completion.sessions],
|
||||
instance='session-manager')
|
||||
def session_delete(self, name):
|
||||
def session_delete(self, name, force=False):
|
||||
"""Delete a session.
|
||||
|
||||
Args:
|
||||
name: The name of the session.
|
||||
force: Force deleting internal sessions (starting with an
|
||||
underline).
|
||||
"""
|
||||
if name.startswith('_') and not force:
|
||||
raise cmdexc.CommandError("{!r} is an internal session, use "
|
||||
"--force to delete anyways.".format(
|
||||
name))
|
||||
try:
|
||||
self.delete(name)
|
||||
except OSError as e:
|
||||
|
@ -49,7 +49,7 @@ class ShellLexer:
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Reset the statemachine state to the defaults."""
|
||||
"""Reset the state machine state to the defaults."""
|
||||
self.quoted = False
|
||||
self.escapedstate = ' '
|
||||
self.token = ''
|
||||
@ -190,7 +190,7 @@ def simple_split(s, keep=False, maxsplit=None):
|
||||
whitespace = '\n\t '
|
||||
if maxsplit == 0:
|
||||
# re.split with maxsplit=0 splits everything, while str.split splits
|
||||
# nothing (which is the behaviour we want).
|
||||
# nothing (which is the behavior we want).
|
||||
if keep:
|
||||
return [s]
|
||||
else:
|
||||
|
@ -19,11 +19,14 @@
|
||||
|
||||
"""Misc. utility commands exposed to the user."""
|
||||
|
||||
import configparser
|
||||
import functools
|
||||
import types
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
from qutebrowser.utils import log, objreg, usertypes
|
||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
@ -119,14 +122,17 @@ def debug_console():
|
||||
con_widget.show()
|
||||
|
||||
|
||||
@cmdutils.register(hide=True)
|
||||
def fooled():
|
||||
"""Turn off april's fools."""
|
||||
from qutebrowser.config import websettings
|
||||
state_config = objreg.get('state-config')
|
||||
@cmdutils.register(debug=True, maxsplit=0)
|
||||
def debug_trace(expr=""):
|
||||
"""Trace executed code via hunter.
|
||||
|
||||
Args:
|
||||
expr: What to trace, passed to hunter.
|
||||
"""
|
||||
if hunter is None:
|
||||
raise cmdexc.CommandError("You need to install 'hunter' to use this "
|
||||
"command!")
|
||||
try:
|
||||
state_config.add_section('general')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['general']['fooled'] = '1'
|
||||
websettings.update_settings('ui', 'user-stylesheet')
|
||||
eval('hunter.trace({})'.format(expr))
|
||||
except Exception as e:
|
||||
raise cmdexc.CommandError("{}: {}".format(e.__class__.__name__, e))
|
||||
|
@ -20,6 +20,7 @@
|
||||
"""Early initialization and main entry point."""
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
import qutebrowser
|
||||
try:
|
||||
@ -58,6 +59,7 @@ def get_argparser():
|
||||
parser.add_argument('-R', '--override-restore', help="Don't restore a "
|
||||
"session even if one would be restored.",
|
||||
action='store_true')
|
||||
parser.add_argument('--json-args', help=argparse.SUPPRESS)
|
||||
|
||||
debug = parser.add_argument_group('debug arguments')
|
||||
debug.add_argument('-l', '--loglevel', dest='loglevel',
|
||||
@ -118,6 +120,13 @@ def main():
|
||||
"""Main entry point for qutebrowser."""
|
||||
parser = get_argparser()
|
||||
args = parser.parse_args()
|
||||
if args.json_args is not None:
|
||||
# Restoring after a restart.
|
||||
# When restarting, we serialize the argparse namespace into json, and
|
||||
# construct a "fake" argparse.Namespace here based on the data loaded
|
||||
# from json.
|
||||
data = json.loads(args.json_args)
|
||||
args = argparse.Namespace(**data)
|
||||
earlyinit.earlyinit(args)
|
||||
# We do this imports late as earlyinit needs to be run first (because of
|
||||
# the harfbuzz fix and version checking).
|
||||
@ -133,7 +142,7 @@ def main():
|
||||
"""
|
||||
return app.exec_()
|
||||
|
||||
# We set qApp explicitely here to reduce the risk of segfaults while
|
||||
# We set qApp explicitly here to reduce the risk of segfaults while
|
||||
# quitting.
|
||||
# See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/561303/comments/7
|
||||
# While this is a workaround for PyQt4 which should be fixed in PyQt, it
|
||||
|
@ -20,7 +20,6 @@
|
||||
"""Utilities used for debugging."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
import functools
|
||||
import datetime
|
||||
@ -87,36 +86,6 @@ def log_signals(obj):
|
||||
connect_log_slot(obj)
|
||||
|
||||
|
||||
def trace_lines(do_trace):
|
||||
"""Turn on/off printing each executed line.
|
||||
|
||||
Args:
|
||||
do_trace: Whether to start tracing (True) or stop it (False).
|
||||
"""
|
||||
def trace(frame, event, arg):
|
||||
"""Trace function passed to sys.settrace.
|
||||
|
||||
Return:
|
||||
Itself, so tracing continues.
|
||||
"""
|
||||
if sys is not None:
|
||||
loc = '{}:{}'.format(frame.f_code.co_filename, frame.f_lineno)
|
||||
if arg is not None:
|
||||
arg = utils.compact_text(str(arg), 200)
|
||||
else:
|
||||
arg = ''
|
||||
print("{:11} {:80} {}".format(event, loc, arg), file=sys.stderr)
|
||||
return trace
|
||||
else:
|
||||
# When tracing while shutting down, it seems sys can be None
|
||||
# sometimes... if that's the case, we stop tracing.
|
||||
return None
|
||||
if do_trace:
|
||||
sys.settrace(trace)
|
||||
else:
|
||||
sys.settrace(None)
|
||||
|
||||
|
||||
def qenum_key(base, value, add_base=False, klass=None):
|
||||
"""Convert a Qt Enum value to its key as a string.
|
||||
|
||||
|
@ -49,7 +49,7 @@ class Loader(jinja2.BaseLoader):
|
||||
|
||||
|
||||
def _guess_autoescape(template_name):
|
||||
"""Turn autoescape on/off based on the filetype.
|
||||
"""Turn auto-escape on/off based on the file type.
|
||||
|
||||
Based on http://jinja.pocoo.org/docs/dev/api/#autoescaping
|
||||
"""
|
||||
|
@ -97,7 +97,12 @@ def on_focus_changed():
|
||||
delta = datetime.datetime.now() - msg.time
|
||||
log.misc.debug("Handling queued {} for window {}, delta {}".format(
|
||||
msg.method_name, msg.win_id, delta))
|
||||
bridge = _get_bridge(msg.win_id)
|
||||
try:
|
||||
bridge = _get_bridge(msg.win_id)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# Non-mainwindow window focused.
|
||||
_QUEUED.append(msg)
|
||||
return
|
||||
if delta.total_seconds() < 1:
|
||||
text = msg.text
|
||||
else:
|
||||
|
@ -77,7 +77,7 @@ def _from_args(typ, args):
|
||||
Return:
|
||||
A (override, path) tuple.
|
||||
override: boolean, if the user did override the path
|
||||
path: The overriden path, or None to turn off storage.
|
||||
path: The overridden path, or None to turn off storage.
|
||||
"""
|
||||
typ_to_argparse_arg = {
|
||||
QStandardPaths.ConfigLocation: 'confdir'
|
||||
|
@ -148,7 +148,7 @@ def fuzzy_url(urlstr, cwd=None, relative=False, do_search=True):
|
||||
do_search: Whether to perform a search on non-URLs.
|
||||
|
||||
Return:
|
||||
A target QUrl to a searchpage or the original URL.
|
||||
A target QUrl to a search page or the original URL.
|
||||
"""
|
||||
expanded = os.path.expanduser(urlstr)
|
||||
if relative and cwd:
|
||||
|
@ -17,7 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Custom useful datatypes.
|
||||
"""Custom useful data types.
|
||||
|
||||
Module attributes:
|
||||
_UNSET: Used as default argument in the constructor so default can be None.
|
||||
@ -40,7 +40,7 @@ def enum(name, items, start=1, is_int=False):
|
||||
|
||||
Args:
|
||||
name: Name of the enum
|
||||
items: Iterable of ttems to be sequentally enumerated.
|
||||
items: Iterable of items to be sequentially enumerated.
|
||||
start: The number to use for the first value.
|
||||
We use 1 as default so enum members are always True.
|
||||
is_init: True if the enum should be a Python IntEnum
|
||||
@ -309,7 +309,7 @@ class Question(QObject):
|
||||
|
||||
@pyqtSlot()
|
||||
def done(self):
|
||||
"""Must be called when the queston was answered completely."""
|
||||
"""Must be called when the question was answered completely."""
|
||||
self.answered.emit(self.answer)
|
||||
if self.mode == PromptMode.yesno:
|
||||
if self.answer:
|
||||
|
@ -95,7 +95,7 @@ def read_file(filename, binary=False):
|
||||
def actute_warning():
|
||||
"""Display a warning about the dead_actute issue if needed."""
|
||||
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
|
||||
# Non linux OS' aren't affected
|
||||
# Non Linux OS' aren't affected
|
||||
if not sys.platform.startswith('linux'):
|
||||
return
|
||||
# If no compose file exists for some reason, we're not affected
|
||||
@ -151,7 +151,7 @@ def interpolate_color(start, end, percent, colorspace=QColor.Rgb):
|
||||
start: The start color.
|
||||
end: The end color.
|
||||
percent: Which value to get (0 - 100)
|
||||
colorspace: The desired interpolation colorsystem,
|
||||
colorspace: The desired interpolation color system,
|
||||
QColor::{Rgb,Hsv,Hsl} (from QColor::Spec enum)
|
||||
|
||||
Return:
|
||||
@ -435,7 +435,7 @@ class prevent_exceptions: # pylint: disable=invalid-name
|
||||
silently ignores them.
|
||||
|
||||
We used to re-raise the exception with a single-shot QTimer in a similar
|
||||
case, but that lead to a strange proble with a KeyError with some random
|
||||
case, but that lead to a strange problem with a KeyError with some random
|
||||
jinja template stuff as content. For now, we only log it, so it doesn't
|
||||
pass 100% silently.
|
||||
|
||||
|
@ -21,20 +21,29 @@
|
||||
"""Various small code checkers."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import os.path
|
||||
import argparse
|
||||
import subprocess
|
||||
import tokenize
|
||||
import traceback
|
||||
import collections
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
|
||||
from scripts import utils
|
||||
|
||||
|
||||
def _py_files(target):
|
||||
"""Iterate over all python files and yield filenames."""
|
||||
for (dirpath, _dirnames, filenames) in os.walk(target):
|
||||
for name in (e for e in filenames if e.endswith('.py')):
|
||||
yield os.path.join(dirpath, name)
|
||||
|
||||
|
||||
def check_git():
|
||||
"""Check for uncommited git files.."""
|
||||
"""Check for uncommitted git files.."""
|
||||
if not os.path.isdir(".git"):
|
||||
print("No .git dir, ignoring")
|
||||
print()
|
||||
@ -55,18 +64,49 @@ def check_git():
|
||||
return status
|
||||
|
||||
|
||||
def check_spelling(target):
|
||||
"""Check commonly misspelled words."""
|
||||
# Words which I often misspell
|
||||
words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully',
|
||||
'occur[^r .]', 'seperator', 'explicitely', 'resetted',
|
||||
'auxillary', 'accidentaly', 'ambigious', 'loosly',
|
||||
'initialis', 'convienence', 'similiar', 'uncommited',
|
||||
'reproducable'}
|
||||
|
||||
# Words which look better when splitted, but might need some fine tuning.
|
||||
words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence',
|
||||
'normalmode', 'eventloops', 'sizehint', 'statemachine',
|
||||
'metaobject', 'logrecord', 'monkeypatch', 'filetype'}
|
||||
|
||||
seen = collections.defaultdict(list)
|
||||
try:
|
||||
ok = True
|
||||
for fn in _py_files(target):
|
||||
with tokenize.open(fn) as f:
|
||||
if fn == os.path.join('scripts', 'misc_checks.py'):
|
||||
continue
|
||||
for line in f:
|
||||
for w in words:
|
||||
if re.search(w, line) and fn not in seen[w]:
|
||||
print("Found '{}' in {}!".format(w, fn))
|
||||
seen[w].append(fn)
|
||||
print()
|
||||
return ok
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def check_vcs_conflict(target):
|
||||
"""Check VCS conflict markers."""
|
||||
try:
|
||||
ok = True
|
||||
for (dirpath, _dirnames, filenames) in os.walk(target):
|
||||
for name in (e for e in filenames if e.endswith('.py')):
|
||||
fn = os.path.join(dirpath, name)
|
||||
with tokenize.open(fn) as f:
|
||||
for line in f:
|
||||
if any(line.startswith(c * 7) for c in '<>=|'):
|
||||
print("Found conflict marker in {}".format(fn))
|
||||
ok = False
|
||||
for fn in _py_files(target):
|
||||
with tokenize.open(fn) as f:
|
||||
for line in f:
|
||||
if any(line.startswith(c * 7) for c in '<>=|'):
|
||||
print("Found conflict marker in {}".format(fn))
|
||||
ok = False
|
||||
print()
|
||||
return ok
|
||||
except Exception:
|
||||
@ -77,7 +117,7 @@ def check_vcs_conflict(target):
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('checker', choices=('git', 'vcs'),
|
||||
parser.add_argument('checker', choices=('git', 'vcs', 'spelling'),
|
||||
help="Which checker to run.")
|
||||
parser.add_argument('target', help="What to check", nargs='*')
|
||||
args = parser.parse_args()
|
||||
@ -91,6 +131,13 @@ def main():
|
||||
if not ok:
|
||||
is_ok = False
|
||||
return 0 if is_ok else 1
|
||||
elif args.checker == 'spelling':
|
||||
is_ok = True
|
||||
for target in args.target:
|
||||
ok = check_spelling(target)
|
||||
if not ok:
|
||||
is_ok = False
|
||||
return 0 if is_ok else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -74,7 +74,7 @@ def main():
|
||||
('http://www.binpress.com/', False),
|
||||
('http://david.li/flow/', False),
|
||||
('https://imzdl.com/', False),
|
||||
# not reproducable
|
||||
# not reproducible
|
||||
# https://bugreports.qt-project.org/browse/QTBUG-39847
|
||||
('http://www.20min.ch/', True),
|
||||
# HarfBuzz, https://bugreports.qt-project.org/browse/QTBUG-39278
|
||||
|
@ -227,6 +227,8 @@ def _format_action_args(action):
|
||||
|
||||
def _format_action(action):
|
||||
"""Get an invocation string/help from an argparse action."""
|
||||
if action.help == argparse.SUPPRESS:
|
||||
return None
|
||||
if not action.option_strings:
|
||||
invocation = '*{}*::'.format(_get_action_metavar(action))
|
||||
else:
|
||||
@ -396,7 +398,9 @@ def regenerate_manpage(filename):
|
||||
if group.description is not None:
|
||||
groupdata.append(group.description)
|
||||
for action in group._group_actions:
|
||||
groupdata.append(_format_action(action))
|
||||
action_data = _format_action(action)
|
||||
if action_data is not None:
|
||||
groupdata.append(action_data)
|
||||
groups.append('\n'.join(groupdata))
|
||||
options = '\n'.join(groups)
|
||||
# epilog
|
||||
|
@ -61,7 +61,7 @@ term_attributes = {
|
||||
|
||||
|
||||
def _esc(code):
|
||||
"""Get an ANSII color code based on a color number."""
|
||||
"""Get an ANSI color code based on a color number."""
|
||||
return '\033[{}m'.format(code)
|
||||
|
||||
|
||||
|
@ -124,7 +124,7 @@ class TestInline:
|
||||
variation of the test checks whether whatever handles PDF display
|
||||
receives the filename information, and acts upon it (this was tested
|
||||
with the latest Acrobat Reader plugin, or, in the case of Chrome, using
|
||||
the builtin PDF handler).
|
||||
the built-in PDF handler).
|
||||
"""
|
||||
header_checker.check_filename('inline; filename="foo.pdf"', "foo.pdf",
|
||||
expected_inline=True)
|
||||
|
@ -39,8 +39,8 @@ def get_webelem(geometry=None, frame=None, null=False, visibility='',
|
||||
geometry: The geometry of the QWebElement as QRect.
|
||||
frame: The QWebFrame the element is in.
|
||||
null: Whether the element is null or not.
|
||||
visibility: The CSS visibility style property calue.
|
||||
display: The CSS display style property calue.
|
||||
visibility: The CSS visibility style property value.
|
||||
display: The CSS display style property value.
|
||||
attributes: Boolean HTML attributes to be added.
|
||||
tagname: The tag name.
|
||||
classes: HTML classes to be added.
|
||||
@ -536,7 +536,7 @@ class TestIsEditable:
|
||||
assert not elem.is_editable()
|
||||
|
||||
def test_div_noneditable(self):
|
||||
"""Test div-element with non-editableclass."""
|
||||
"""Test div-element with non-editable class."""
|
||||
elem = get_webelem(tagname='div', classes='foo-kix-bar')
|
||||
assert not elem.is_editable()
|
||||
|
||||
|
@ -1810,7 +1810,7 @@ class TestSearchEngineUrl:
|
||||
self.t.validate('http://example.com/?q={}')
|
||||
|
||||
def test_validate_invalid_url(self):
|
||||
"""Test validate with an invalud URL."""
|
||||
"""Test validate with an invalid URL."""
|
||||
with pytest.raises(configexc.ValidationError):
|
||||
self.t.validate(':{}')
|
||||
|
||||
|
@ -210,11 +210,11 @@ class TestKeyChain:
|
||||
self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None)
|
||||
assert self.kp._keystring == ''
|
||||
|
||||
def test_ambigious_keychain(self, fake_keyevent_factory, mocker, stubs):
|
||||
def test_ambiguous_keychain(self, fake_keyevent_factory, mocker, stubs):
|
||||
"""Test ambigious keychain."""
|
||||
mocker.patch('qutebrowser.keyinput.basekeyparser.config',
|
||||
new=stubs.ConfigStub(CONFIG))
|
||||
timer = self.kp._ambigious_timer
|
||||
timer = self.kp._ambiguous_timer
|
||||
assert not timer.isActive()
|
||||
# We start with 'a' where the keychain gives us an ambigious result.
|
||||
# Then we check if the timer has been set up correctly
|
||||
|
@ -52,7 +52,7 @@ def qt_message_handler(msg_type, context, msg):
|
||||
QtFatalMsg: logging.CRITICAL,
|
||||
}
|
||||
level = qt_to_logging[msg_type]
|
||||
# There's very similiar code in utils.log, but we want it duplicated here
|
||||
# There's very similar code in utils.log, but we want it duplicated here
|
||||
# for the tests.
|
||||
if context.function is None:
|
||||
func = 'none'
|
||||
|
@ -102,7 +102,7 @@ class TestFileHandling(object):
|
||||
self.editor = editor.ExternalEditor(0)
|
||||
|
||||
def test_file_handling_closed_ok(self):
|
||||
"""Test file handling when closing with an exitstatus == 0."""
|
||||
"""Test file handling when closing with an exit status == 0."""
|
||||
self.editor.edit("")
|
||||
filename = self.editor._filename
|
||||
assert os.path.exists(filename)
|
||||
@ -110,7 +110,7 @@ class TestFileHandling(object):
|
||||
assert not os.path.exists(filename)
|
||||
|
||||
def test_file_handling_closed_error(self, caplog):
|
||||
"""Test file handling when closing with an exitstatus != 0."""
|
||||
"""Test file handling when closing with an exit status != 0."""
|
||||
self.editor.edit("")
|
||||
filename = self.editor._filename
|
||||
assert os.path.exists(filename)
|
||||
|
@ -146,20 +146,20 @@ class SimpleSplitTests(unittest.TestCase):
|
||||
}
|
||||
|
||||
def test_str_split(self):
|
||||
"""Test if the behaviour matches str.split."""
|
||||
"""Test if the behavior matches str.split."""
|
||||
for test in self.TESTS:
|
||||
with self.subTest(string=test):
|
||||
self.assertEqual(split.simple_split(test),
|
||||
test.rstrip().split())
|
||||
|
||||
def test_str_split_maxsplit_1(self):
|
||||
"""Test if the behaviour matches str.split with maxsplit=1."""
|
||||
"""Test if the behavior matches str.split with maxsplit=1."""
|
||||
string = "foo bar baz"
|
||||
self.assertEqual(split.simple_split(string, maxsplit=1),
|
||||
string.rstrip().split(maxsplit=1))
|
||||
|
||||
def test_str_split_maxsplit_0(self):
|
||||
"""Test if the behaviour matches str.split with maxsplit=0."""
|
||||
"""Test if the behavior matches str.split with maxsplit=0."""
|
||||
string = " foo bar baz "
|
||||
self.assertEqual(split.simple_split(string, maxsplit=0),
|
||||
string.rstrip().split(maxsplit=0))
|
||||
|
45
test/utils/debug/test_log_time.py
Normal file
45
test/utils/debug/test_log_time.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for qutebrowser.utils.debug.log_time."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
def test_log_time(caplog):
|
||||
"""Test if log_time logs properly."""
|
||||
logger_name = 'qt-tests'
|
||||
|
||||
with caplog.atLevel(logging.DEBUG, logger=logger_name):
|
||||
with debug.log_time(logging.getLogger(logger_name), action='foobar'):
|
||||
time.sleep(0.1)
|
||||
|
||||
records = caplog.records()
|
||||
assert len(records) == 1
|
||||
|
||||
pattern = re.compile(r'^Foobar took ([\d.]*) seconds\.$')
|
||||
match = pattern.match(records[0].msg)
|
||||
assert match
|
||||
|
||||
duration = float(match.group(1))
|
||||
assert 0.08 <= duration <= 0.12
|
65
test/utils/debug/test_qenum_key.py
Normal file
65
test/utils/debug/test_qenum_key.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for qutebrowser.utils.debug.qenum_key."""
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QStyle, QFrame
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
def test_no_metaobj():
|
||||
"""Test with an enum with no meta-object."""
|
||||
assert not hasattr(QStyle.PrimitiveElement, 'staticMetaObject')
|
||||
key = debug.qenum_key(QStyle, QStyle.PE_PanelButtonCommand)
|
||||
assert key == 'PE_PanelButtonCommand'
|
||||
|
||||
|
||||
def test_metaobj():
|
||||
"""Test with an enum with meta-object."""
|
||||
assert hasattr(QFrame, 'staticMetaObject')
|
||||
key = debug.qenum_key(QFrame, QFrame.Sunken)
|
||||
assert key == 'Sunken'
|
||||
|
||||
|
||||
def test_add_base():
|
||||
"""Test with add_base=True."""
|
||||
key = debug.qenum_key(QFrame, QFrame.Sunken, add_base=True)
|
||||
assert key == 'QFrame.Sunken'
|
||||
|
||||
|
||||
def test_int_noklass():
|
||||
"""Test passing an int without explicit klass given."""
|
||||
with pytest.raises(TypeError):
|
||||
debug.qenum_key(QFrame, 42)
|
||||
|
||||
|
||||
def test_int():
|
||||
"""Test passing an int with explicit klass given."""
|
||||
key = debug.qenum_key(QFrame, 0x0030, klass=QFrame.Shadow)
|
||||
assert key == 'Sunken'
|
||||
|
||||
|
||||
def test_unknown():
|
||||
"""Test passing an unknown value."""
|
||||
key = debug.qenum_key(QFrame, 0x1337, klass=QFrame.Shadow)
|
||||
assert key == '0x1337'
|
77
test/utils/debug/test_qflags_key.py
Normal file
77
test/utils/debug/test_qflags_key.py
Normal file
@ -0,0 +1,77 @@
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for qutebrowser.utils.debug.qflags_key.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/issues/42
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
fixme = pytest.mark.xfail(reason="See issue #42", raises=AssertionError)
|
||||
|
||||
|
||||
@fixme
|
||||
def test_single():
|
||||
"""Test with single value."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignTop)
|
||||
assert flags == 'AlignTop'
|
||||
|
||||
|
||||
@fixme
|
||||
def test_multiple():
|
||||
"""Test with multiple values."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignLeft | Qt.AlignTop)
|
||||
assert flags == 'AlignLeft|AlignTop'
|
||||
|
||||
|
||||
def test_combined():
|
||||
"""Test with a combined value."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignCenter)
|
||||
assert flags == 'AlignHCenter|AlignVCenter'
|
||||
|
||||
|
||||
@fixme
|
||||
def test_add_base():
|
||||
"""Test with add_base=True."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignTop, add_base=True)
|
||||
assert flags == 'Qt.AlignTop'
|
||||
|
||||
|
||||
def test_int_noklass():
|
||||
"""Test passing an int without explicit klass given."""
|
||||
with pytest.raises(TypeError):
|
||||
debug.qflags_key(Qt, 42)
|
||||
|
||||
|
||||
@fixme
|
||||
def test_int():
|
||||
"""Test passing an int with explicit klass given."""
|
||||
flags = debug.qflags_key(Qt, 0x0021, klass=Qt.Alignment)
|
||||
assert flags == 'AlignLeft|AlignTop'
|
||||
|
||||
|
||||
def test_unknown():
|
||||
"""Test passing an unknown value."""
|
||||
flags = debug.qflags_key(Qt, 0x1100, klass=Qt.Alignment)
|
||||
assert flags == '0x0100|0x1000'
|
52
test/utils/debug/test_signal.py
Normal file
52
test/utils/debug/test_signal.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Test signal debug output functions."""
|
||||
|
||||
import pytest
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def signal(stubs):
|
||||
"""Fixture to provide a faked pyqtSignal."""
|
||||
return stubs.FakeSignal()
|
||||
|
||||
|
||||
def test_signal_name(signal):
|
||||
"""Test signal_name()."""
|
||||
assert debug.signal_name(signal) == 'fake'
|
||||
|
||||
|
||||
def test_dbg_signal(signal):
|
||||
"""Test dbg_signal()."""
|
||||
assert debug.dbg_signal(signal, [23, 42]) == 'fake(23, 42)'
|
||||
|
||||
|
||||
def test_dbg_signal_eliding(signal):
|
||||
"""Test eliding in dbg_signal()."""
|
||||
dbg_signal = debug.dbg_signal(signal, ['x' * 201])
|
||||
assert dbg_signal == "fake('{}\u2026)".format('x' * 198)
|
||||
|
||||
|
||||
def test_dbg_signal_newline(signal):
|
||||
"""Test dbg_signal() with a newline."""
|
||||
dbg_signal = debug.dbg_signal(signal, ['foo\nbar'])
|
||||
assert dbg_signal == r"fake('foo\nbar')"
|
@ -1,168 +0,0 @@
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Tests for qutebrowser.utils.debug."""
|
||||
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QStyle, QFrame
|
||||
import pytest
|
||||
|
||||
from qutebrowser.utils import debug
|
||||
|
||||
|
||||
class TestQEnumKey:
|
||||
|
||||
"""Tests for qenum_key."""
|
||||
|
||||
def test_no_metaobj(self):
|
||||
"""Test with an enum with no metaobject."""
|
||||
with pytest.raises(AttributeError):
|
||||
# Make sure it doesn't have a meta object
|
||||
# pylint: disable=pointless-statement,no-member
|
||||
QStyle.PrimitiveElement.staticMetaObject
|
||||
key = debug.qenum_key(QStyle, QStyle.PE_PanelButtonCommand)
|
||||
assert key == 'PE_PanelButtonCommand'
|
||||
|
||||
def test_metaobj(self):
|
||||
"""Test with an enum with metaobject."""
|
||||
# pylint: disable=pointless-statement
|
||||
QFrame.staticMetaObject # make sure it has a metaobject
|
||||
key = debug.qenum_key(QFrame, QFrame.Sunken)
|
||||
assert key == 'Sunken'
|
||||
|
||||
def test_add_base(self):
|
||||
"""Test with add_base=True."""
|
||||
key = debug.qenum_key(QFrame, QFrame.Sunken, add_base=True)
|
||||
assert key == 'QFrame.Sunken'
|
||||
|
||||
def test_int_noklass(self):
|
||||
"""Test passing an int without explicit klass given."""
|
||||
with pytest.raises(TypeError):
|
||||
debug.qenum_key(QFrame, 42)
|
||||
|
||||
def test_int(self):
|
||||
"""Test passing an int with explicit klass given."""
|
||||
key = debug.qenum_key(QFrame, 0x0030, klass=QFrame.Shadow)
|
||||
assert key == 'Sunken'
|
||||
|
||||
def test_unknown(self):
|
||||
"""Test passing an unknown value."""
|
||||
key = debug.qenum_key(QFrame, 0x1337, klass=QFrame.Shadow)
|
||||
assert key == '0x1337'
|
||||
|
||||
def test_reconverted(self):
|
||||
"""Test passing a flag value which was re-converted to an enum."""
|
||||
# FIXME maybe this should return the right thing anyways?
|
||||
debug.qenum_key(Qt, Qt.Alignment(int(Qt.AlignLeft)))
|
||||
|
||||
|
||||
class TestQFlagsKey:
|
||||
|
||||
"""Tests for qflags_key()."""
|
||||
|
||||
fail_issue42 = pytest.mark.xfail(
|
||||
reason='https://github.com/The-Compiler/qutebrowser/issues/42')
|
||||
|
||||
@fail_issue42
|
||||
def test_single(self):
|
||||
"""Test with single value."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignTop)
|
||||
assert flags == 'AlignTop'
|
||||
|
||||
@fail_issue42
|
||||
def test_multiple(self):
|
||||
"""Test with multiple values."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignLeft | Qt.AlignTop)
|
||||
assert flags == 'AlignLeft|AlignTop'
|
||||
|
||||
def test_combined(self):
|
||||
"""Test with a combined value."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignCenter)
|
||||
assert flags == 'AlignHCenter|AlignVCenter'
|
||||
|
||||
@fail_issue42
|
||||
def test_add_base(self):
|
||||
"""Test with add_base=True."""
|
||||
flags = debug.qflags_key(Qt, Qt.AlignTop, add_base=True)
|
||||
assert flags == 'Qt.AlignTop'
|
||||
|
||||
def test_int_noklass(self):
|
||||
"""Test passing an int without explicit klass given."""
|
||||
with pytest.raises(TypeError):
|
||||
debug.qflags_key(Qt, 42)
|
||||
|
||||
@fail_issue42
|
||||
def test_int(self):
|
||||
"""Test passing an int with explicit klass given."""
|
||||
flags = debug.qflags_key(Qt, 0x0021, klass=Qt.Alignment)
|
||||
assert flags == 'AlignLeft|AlignTop'
|
||||
|
||||
def test_unknown(self):
|
||||
"""Test passing an unknown value."""
|
||||
flags = debug.qflags_key(Qt, 0x1100, klass=Qt.Alignment)
|
||||
assert flags == '0x0100|0x1000'
|
||||
|
||||
|
||||
class TestDebug:
|
||||
|
||||
"""Test signal debug output functions."""
|
||||
|
||||
@pytest.fixture
|
||||
def signal(self, stubs):
|
||||
return stubs.FakeSignal()
|
||||
|
||||
def test_signal_name(self, signal):
|
||||
"""Test signal_name()."""
|
||||
assert debug.signal_name(signal) == 'fake'
|
||||
|
||||
def test_dbg_signal(self, signal):
|
||||
"""Test dbg_signal()."""
|
||||
assert debug.dbg_signal(signal, [23, 42]) == 'fake(23, 42)'
|
||||
|
||||
|
||||
def test_dbg_signal_eliding(self, signal):
|
||||
"""Test eliding in dbg_signal()."""
|
||||
assert debug.dbg_signal(signal, ['x' * 201]) == \
|
||||
"fake('{}\u2026)".format('x' * 198)
|
||||
|
||||
def test_dbg_signal_newline(self, signal):
|
||||
"""Test dbg_signal() with a newline."""
|
||||
assert debug.dbg_signal(signal, ['foo\nbar']) == r"fake('foo\nbar')"
|
||||
|
||||
|
||||
class TestLogTime:
|
||||
|
||||
"""Test log_time."""
|
||||
|
||||
def test_log_time(self, caplog):
|
||||
"""Test if log_time logs properly."""
|
||||
logger = logging.getLogger('qt-tests')
|
||||
with caplog.atLevel(logging.DEBUG, logger.name):
|
||||
with debug.log_time(logger, action='foobar'):
|
||||
time.sleep(0.1)
|
||||
assert len(caplog.records()) == 1
|
||||
pattern = re.compile(r'^Foobar took ([\d.]*) seconds\.$')
|
||||
match = pattern.match(caplog.records()[0].msg)
|
||||
assert match
|
||||
duration = float(match.group(1))
|
||||
assert 0.09 <= duration <= 0.11
|
@ -114,7 +114,7 @@ class LogFilterTests(unittest.TestCase):
|
||||
self.assertTrue(logfilter.filter(record))
|
||||
|
||||
def test_matching(self):
|
||||
"""Test if a filter lets an exactly matching logrecord through."""
|
||||
"""Test if a filter lets an exactly matching log record through."""
|
||||
logfilter = log.LogFilter(["eggs", "bacon"])
|
||||
record = self._make_record("eggs")
|
||||
self.assertTrue(logfilter.filter(record))
|
||||
|
@ -83,7 +83,7 @@ class CheckOverflowTests(unittest.TestCase):
|
||||
|
||||
|
||||
def argparser_exit(status=0, message=None): # pylint: disable=unused-argument
|
||||
"""Function to monkeypatch .exit() of the argparser so it doesn't exit."""
|
||||
"""Function to monkey-patch .exit() of the argparser so it doesn't exit."""
|
||||
raise Exception
|
||||
|
||||
|
||||
|
@ -70,7 +70,7 @@ class TestReadFile:
|
||||
"""Test read_file."""
|
||||
|
||||
def test_readfile(self):
|
||||
"""Read a testfile."""
|
||||
"""Read a test file."""
|
||||
directory = os.path.dirname(__file__)
|
||||
content = utils.read_file(os.path.join(directory, 'testfile'))
|
||||
assert content.splitlines()[0] == "Hello World!"
|
||||
|
@ -245,14 +245,14 @@ class BlockTests(unittest.TestCase):
|
||||
mode=usertypes.NeighborList.Modes.block)
|
||||
|
||||
def test_first(self):
|
||||
"""Test ouf of bounds previtem()."""
|
||||
"""Test out of bounds previtem()."""
|
||||
self.nl.firstitem()
|
||||
self.assertEqual(self.nl._idx, 0)
|
||||
self.assertEqual(self.nl.previtem(), 1)
|
||||
self.assertEqual(self.nl._idx, 0)
|
||||
|
||||
def test_last(self):
|
||||
"""Test ouf of bounds nextitem()."""
|
||||
"""Test out of bounds nextitem()."""
|
||||
self.nl.lastitem()
|
||||
self.assertEqual(self.nl._idx, 4)
|
||||
self.assertEqual(self.nl.nextitem(), 5)
|
||||
@ -272,14 +272,14 @@ class WrapTests(unittest.TestCase):
|
||||
[1, 2, 3, 4, 5], default=3, mode=usertypes.NeighborList.Modes.wrap)
|
||||
|
||||
def test_first(self):
|
||||
"""Test ouf of bounds previtem()."""
|
||||
"""Test out of bounds previtem()."""
|
||||
self.nl.firstitem()
|
||||
self.assertEqual(self.nl._idx, 0)
|
||||
self.assertEqual(self.nl.previtem(), 5)
|
||||
self.assertEqual(self.nl._idx, 4)
|
||||
|
||||
def test_last(self):
|
||||
"""Test ouf of bounds nextitem()."""
|
||||
"""Test out of bounds nextitem()."""
|
||||
self.nl.lastitem()
|
||||
self.assertEqual(self.nl._idx, 4)
|
||||
self.assertEqual(self.nl.nextitem(), 1)
|
||||
@ -300,7 +300,7 @@ class RaiseTests(unittest.TestCase):
|
||||
mode=usertypes.NeighborList.Modes.exception)
|
||||
|
||||
def test_first(self):
|
||||
"""Test ouf of bounds previtem()."""
|
||||
"""Test out of bounds previtem()."""
|
||||
self.nl.firstitem()
|
||||
self.assertEqual(self.nl._idx, 0)
|
||||
with self.assertRaises(IndexError):
|
||||
@ -308,7 +308,7 @@ class RaiseTests(unittest.TestCase):
|
||||
self.assertEqual(self.nl._idx, 0)
|
||||
|
||||
def test_last(self):
|
||||
"""Test ouf of bounds nextitem()."""
|
||||
"""Test out of bounds nextitem()."""
|
||||
self.nl.lastitem()
|
||||
self.assertEqual(self.nl._idx, 4)
|
||||
with self.assertRaises(IndexError):
|
||||
|
3
tox.ini
3
tox.ini
@ -19,6 +19,7 @@ setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envsitepackagesdir}/PyQt5/plugins/platform
|
||||
deps =
|
||||
py==1.4.26
|
||||
pytest==2.7.0
|
||||
pytest-capturelog==0.7
|
||||
pytest-qt==1.3.0
|
||||
pytest-mock==0.4.2
|
||||
# We don't use {[testenv:mkvenv]commands} here because that seems to be broken
|
||||
@ -32,6 +33,7 @@ deps =
|
||||
{[testenv:unittests]deps}
|
||||
coverage==3.7.1
|
||||
pytest-cov==1.8.1
|
||||
pytest-capturelog==0.7
|
||||
pytest-qt==1.3.0
|
||||
pytest-mock==0.4.2
|
||||
cov-core==1.15.0
|
||||
@ -43,6 +45,7 @@ commands =
|
||||
commands =
|
||||
{envpython} scripts/misc_checks.py git
|
||||
{envpython} scripts/misc_checks.py vcs qutebrowser scripts
|
||||
{envpython} scripts/misc_checks.py spelling qutebrowser scripts
|
||||
|
||||
[testenv:pylint]
|
||||
skip_install = true
|
||||
|
Loading…
Reference in New Issue
Block a user