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