Merge branch 'newcmd'
Conflicts: .flake8 pkg/PKGBUILD.qutebrowser-git qutebrowser/browser/commands.py qutebrowser/browser/hints.py qutebrowser/config/configdata.py qutebrowser/network/qutescheme.py qutebrowser/test/config/test_configtypes.py qutebrowser/utils/utils.py
This commit is contained in:
commit
812a0fdd41
3
.flake8
3
.flake8
@ -11,8 +11,9 @@
|
|||||||
# E222: Multiple spaces after operator
|
# E222: Multiple spaces after operator
|
||||||
# F811: Redifiniton
|
# F811: Redifiniton
|
||||||
# W292: No newline at end of file
|
# W292: No newline at end of file
|
||||||
|
# E701: multiple statements on one line
|
||||||
# E702: multiple statements on one line
|
# E702: multiple statements on one line
|
||||||
# E225: missing whitespace around operator
|
# E225: missing whitespace around operator
|
||||||
ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E702,E225
|
ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E701,E702,E225
|
||||||
max_complexity = 12
|
max_complexity = 12
|
||||||
exclude = ez_setup.py
|
exclude = ez_setup.py
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,7 +10,6 @@ __pycache__
|
|||||||
/setuptools-*.egg
|
/setuptools-*.egg
|
||||||
/setuptools-*.zip
|
/setuptools-*.zip
|
||||||
/qutebrowser/git-commit-id
|
/qutebrowser/git-commit-id
|
||||||
# We can probably remove these later
|
|
||||||
*.asciidoc
|
|
||||||
/doc/*.html
|
/doc/*.html
|
||||||
/README.html
|
/README.html
|
||||||
|
/qutebrowser/html/doc/
|
||||||
|
@ -46,6 +46,10 @@ After installing the <<requirements,requirements>>, you have these options:
|
|||||||
* Run `python3 setup.py install` to install qutebrowser, then call
|
* Run `python3 setup.py install` to install qutebrowser, then call
|
||||||
`qutebrowser`.
|
`qutebrowser`.
|
||||||
|
|
||||||
|
NOTE: If you're running qutebrowser from the git repository rather than a
|
||||||
|
released version, you should run `scripts/asciidoc2html.py` to generate the
|
||||||
|
documentation.
|
||||||
|
|
||||||
Contributions / Bugs
|
Contributions / Bugs
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
706
doc/help/commands.asciidoc
Normal file
706
doc/help/commands.asciidoc
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
= Commands
|
||||||
|
|
||||||
|
== Normal commands
|
||||||
|
.Quick reference
|
||||||
|
[options="header",width="75%",cols="25%,75%"]
|
||||||
|
|==============
|
||||||
|
|Command|Description
|
||||||
|
|<<back,back>>|Go back in the history of the current tab.
|
||||||
|
|<<bind,bind>>|Bind a key to a command.
|
||||||
|
|<<cancel-download,cancel-download>>|Cancel the first/[count]th download.
|
||||||
|
|<<download-page,download-page>>|Download the current page.
|
||||||
|
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||||
|
|<<help,help>>|Show help about a command or setting.
|
||||||
|
|<<hint,hint>>|Start hinting.
|
||||||
|
|<<home,home>>|Open main startpage in current tab.
|
||||||
|
|<<inspector,inspector>>|Toggle the web inspector.
|
||||||
|
|<<later,later>>|Execute a command after some time.
|
||||||
|
|<<next-page,next-page>>|Open a "next" link.
|
||||||
|
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||||
|
|<<paste,paste>>|Open a page from the clipboard.
|
||||||
|
|<<prev-page,prev-page>>|Open a "previous" link.
|
||||||
|
|<<print,print>>|Print the current/[count]th tab.
|
||||||
|
|<<quickmark-add,quickmark-add>>|Add a new quickmark.
|
||||||
|
|<<quickmark-load,quickmark-load>>|Load a quickmark.
|
||||||
|
|<<quickmark-save,quickmark-save>>|Save the current page as a quickmark.
|
||||||
|
|<<quit,quit>>|Quit qutebrowser.
|
||||||
|
|<<reload,reload>>|Reload the current/[count]th tab.
|
||||||
|
|<<report,report>>|Report a bug in qutebrowser.
|
||||||
|
|<<restart,restart>>|Restart qutebrowser while keeping existing tabs open.
|
||||||
|
|<<run-userscript,run-userscript>>|Run an userscript given as argument.
|
||||||
|
|<<save,save>>|Save the config file.
|
||||||
|
|<<set,set>>|Set an option.
|
||||||
|
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|
||||||
|
|<<spawn,spawn>>|Spawn a command in a shell.
|
||||||
|
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
||||||
|
|<<tab-close,tab-close>>|Close the current/[count]th tab.
|
||||||
|
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|
||||||
|
|<<tab-move,tab-move>>|Move the current tab.
|
||||||
|
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|
||||||
|
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|
||||||
|
|<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|
||||||
|
|<<unbind,unbind>>|Unbind a keychain.
|
||||||
|
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|
||||||
|
|<<yank,yank>>|Yank the current URL/title to the clipboard or primary selection.
|
||||||
|
|<<zoom,zoom>>|Set the zoom level for the current tab.
|
||||||
|
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
|
||||||
|
|<<zoom-out,zoom-out>>|Decrease the zoom level for the current tab.
|
||||||
|
|==============
|
||||||
|
[[back]]
|
||||||
|
=== back
|
||||||
|
Go back in the history of the current tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many pages to go back.
|
||||||
|
|
||||||
|
[[bind]]
|
||||||
|
=== bind
|
||||||
|
Syntax: +:bind [*--mode* 'MODE'] 'key' 'command' ['command' ...]+
|
||||||
|
|
||||||
|
Bind a key to a command.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'key'+: The keychain or special key (inside `<...>`) to bind.
|
||||||
|
* +'command'+: The command to execute, with optional args.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
|
||||||
|
|
||||||
|
|
||||||
|
[[cancel-download]]
|
||||||
|
=== cancel-download
|
||||||
|
Cancel the first/[count]th download.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The index of the download to cancel.
|
||||||
|
|
||||||
|
[[download-page]]
|
||||||
|
=== download-page
|
||||||
|
Download the current page.
|
||||||
|
|
||||||
|
[[forward]]
|
||||||
|
=== forward
|
||||||
|
Go forward in the history of the current tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many pages to go forward.
|
||||||
|
|
||||||
|
[[help]]
|
||||||
|
=== help
|
||||||
|
Syntax: +:help ['topic']+
|
||||||
|
|
||||||
|
Show help about a command or setting.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'topic'+: The topic to show help for.
|
||||||
|
|
||||||
|
- :__command__ for commands.
|
||||||
|
- __section__\->__option__ for settings.
|
||||||
|
|
||||||
|
|
||||||
|
[[hint]]
|
||||||
|
=== hint
|
||||||
|
Syntax: +:hint ['group'] ['target'] ['args' ['args' ...]]+
|
||||||
|
|
||||||
|
Start hinting.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'group'+: The hinting mode to use.
|
||||||
|
|
||||||
|
- `all`: All clickable elements.
|
||||||
|
- `links`: Only links.
|
||||||
|
- `images`: Only images.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* +'target'+: What to do with the selected element.
|
||||||
|
|
||||||
|
- `normal`: Open the link in the current tab.
|
||||||
|
- `tab`: Open the link in a new tab.
|
||||||
|
- `tab-bg`: Open the link in a new background tab.
|
||||||
|
- `yank`: Yank the link to the clipboard.
|
||||||
|
- `yank-primary`: Yank the link to the primary selection.
|
||||||
|
- `fill`: Fill the commandline with the command given as
|
||||||
|
argument.
|
||||||
|
- `rapid`: Open the link in a new tab and stay in hinting mode.
|
||||||
|
- `download`: Download the link.
|
||||||
|
- `userscript`: Call an userscript with `$QUTE_URL` set to the
|
||||||
|
link.
|
||||||
|
- `spawn`: Spawn a command.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* +'args'+: Arguments for spawn/userscript/fill.
|
||||||
|
|
||||||
|
- With `spawn`: The executable and arguments to spawn.
|
||||||
|
`{hint-url}` will get replaced by the selected
|
||||||
|
URL.
|
||||||
|
- With `userscript`: The userscript to execute.
|
||||||
|
- With `fill`: The command to fill the statusbar with.
|
||||||
|
`{hint-url}` will get replaced by the selected
|
||||||
|
URL.
|
||||||
|
|
||||||
|
|
||||||
|
[[home]]
|
||||||
|
=== home
|
||||||
|
Open main startpage in current tab.
|
||||||
|
|
||||||
|
[[inspector]]
|
||||||
|
=== inspector
|
||||||
|
Toggle the web inspector.
|
||||||
|
|
||||||
|
[[later]]
|
||||||
|
=== later
|
||||||
|
Syntax: +:later 'ms' 'command' ['command' ...]+
|
||||||
|
|
||||||
|
Execute a command after some time.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'ms'+: How many milliseconds to wait.
|
||||||
|
* +'command'+: The command to run, with optional args.
|
||||||
|
|
||||||
|
[[next-page]]
|
||||||
|
=== next-page
|
||||||
|
Syntax: +:next-page [*--tab*]+
|
||||||
|
|
||||||
|
Open a "next" link.
|
||||||
|
|
||||||
|
This tries to automatically click on typical _Next Page_ links using some heuristics.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||||
|
|
||||||
|
[[open]]
|
||||||
|
=== open
|
||||||
|
Syntax: +:open [*--bg*] [*--tab*] 'url'+
|
||||||
|
|
||||||
|
Open a URL in the current/[count]th tab.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'url'+: The URL to open.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-b*+, +*--bg*+: Open in a new background tab.
|
||||||
|
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to open the URL in.
|
||||||
|
|
||||||
|
[[paste]]
|
||||||
|
=== paste
|
||||||
|
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*]+
|
||||||
|
|
||||||
|
Open a page from the clipboard.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||||
|
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||||
|
* +*-b*+, +*--bg*+: Open in a background tab.
|
||||||
|
|
||||||
|
[[prev-page]]
|
||||||
|
=== prev-page
|
||||||
|
Syntax: +:prev-page [*--tab*]+
|
||||||
|
|
||||||
|
Open a "previous" link.
|
||||||
|
|
||||||
|
This tries to automatically click on typical _Previous Page_ links using some heuristics.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--tab*+: Open in a new tab.
|
||||||
|
|
||||||
|
[[print]]
|
||||||
|
=== print
|
||||||
|
Syntax: +:print [*--preview*]+
|
||||||
|
|
||||||
|
Print the current/[count]th tab.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-p*+, +*--preview*+: Show preview instead of printing.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to print.
|
||||||
|
|
||||||
|
[[quickmark-add]]
|
||||||
|
=== quickmark-add
|
||||||
|
Syntax: +:quickmark-add 'url' 'name'+
|
||||||
|
|
||||||
|
Add a new quickmark.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'url'+: The url to add as quickmark.
|
||||||
|
* +'name'+: The name for the new quickmark.
|
||||||
|
|
||||||
|
[[quickmark-load]]
|
||||||
|
=== quickmark-load
|
||||||
|
Syntax: +:quickmark-load [*--tab*] [*--bg*] 'name'+
|
||||||
|
|
||||||
|
Load a quickmark.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'name'+: The name of the quickmark to load.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--tab*+: Load the quickmark in a new tab.
|
||||||
|
* +*-b*+, +*--bg*+: Load the quickmark in a new background tab.
|
||||||
|
|
||||||
|
[[quickmark-save]]
|
||||||
|
=== quickmark-save
|
||||||
|
Save the current page as a quickmark.
|
||||||
|
|
||||||
|
[[quit]]
|
||||||
|
=== quit
|
||||||
|
Quit qutebrowser.
|
||||||
|
|
||||||
|
[[reload]]
|
||||||
|
=== reload
|
||||||
|
Reload the current/[count]th tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to reload.
|
||||||
|
|
||||||
|
[[report]]
|
||||||
|
=== report
|
||||||
|
Report a bug in qutebrowser.
|
||||||
|
|
||||||
|
[[restart]]
|
||||||
|
=== restart
|
||||||
|
Restart qutebrowser while keeping existing tabs open.
|
||||||
|
|
||||||
|
[[run-userscript]]
|
||||||
|
=== run-userscript
|
||||||
|
Syntax: +:run-userscript 'cmd' ['args' ['args' ...]]+
|
||||||
|
|
||||||
|
Run an userscript given as argument.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'cmd'+: The userscript to run.
|
||||||
|
* +'args'+: Arguments to pass to the userscript.
|
||||||
|
|
||||||
|
[[save]]
|
||||||
|
=== save
|
||||||
|
Save the config file.
|
||||||
|
|
||||||
|
[[set]]
|
||||||
|
=== set
|
||||||
|
Syntax: +:set [*--temp*] 'section' 'option' ['value']+
|
||||||
|
|
||||||
|
Set an option.
|
||||||
|
|
||||||
|
If the option name ends with '?', the value of the option is shown instead.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'section'+: The section where the option is in.
|
||||||
|
* +'option'+: The name of the option.
|
||||||
|
* +'value'+: The value to set.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--temp*+: Set value temporarily.
|
||||||
|
|
||||||
|
[[set-cmd-text]]
|
||||||
|
=== set-cmd-text
|
||||||
|
Syntax: +:set-cmd-text 'text'+
|
||||||
|
|
||||||
|
Preset the statusbar to some text.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'text'+: The commandline to set.
|
||||||
|
|
||||||
|
[[spawn]]
|
||||||
|
=== spawn
|
||||||
|
Syntax: +:spawn 'args' ['args' ...]+
|
||||||
|
|
||||||
|
Spawn a command in a shell.
|
||||||
|
|
||||||
|
Note the {url} variable which gets replaced by the current URL might be useful here.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'args'+: The commandline to execute.
|
||||||
|
|
||||||
|
[[stop]]
|
||||||
|
=== stop
|
||||||
|
Stop loading in the current/[count]th tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to stop.
|
||||||
|
|
||||||
|
[[tab-close]]
|
||||||
|
=== tab-close
|
||||||
|
Close the current/[count]th tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to close
|
||||||
|
|
||||||
|
[[tab-focus]]
|
||||||
|
=== tab-focus
|
||||||
|
Syntax: +:tab-focus ['index']+
|
||||||
|
|
||||||
|
Select the tab given as argument/[count].
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab.
|
||||||
|
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The tab index to focus, starting with 1.
|
||||||
|
|
||||||
|
[[tab-move]]
|
||||||
|
=== tab-move
|
||||||
|
Syntax: +:tab-move ['direction']+
|
||||||
|
|
||||||
|
Move the current tab.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'direction'+: `+` or `-` for relative moving, not given for absolute moving.
|
||||||
|
|
||||||
|
|
||||||
|
==== count
|
||||||
|
If moving absolutely: New position (default: 0) If moving relatively: Offset.
|
||||||
|
|
||||||
|
|
||||||
|
[[tab-next]]
|
||||||
|
=== tab-next
|
||||||
|
Switch to the next tab, or switch [count] tabs forward.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many tabs to switch forward.
|
||||||
|
|
||||||
|
[[tab-only]]
|
||||||
|
=== tab-only
|
||||||
|
Close all tabs except for the current one.
|
||||||
|
|
||||||
|
[[tab-prev]]
|
||||||
|
=== tab-prev
|
||||||
|
Switch to the previous tab, or switch [count] tabs back.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many tabs to switch back.
|
||||||
|
|
||||||
|
[[unbind]]
|
||||||
|
=== unbind
|
||||||
|
Syntax: +:unbind 'key' ['mode']+
|
||||||
|
|
||||||
|
Unbind a keychain.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'key'+: The keychain or special key (inside <...>) to unbind.
|
||||||
|
* +'mode'+: A comma-separated list of modes to unbind the key in (default: `normal`).
|
||||||
|
|
||||||
|
|
||||||
|
[[undo]]
|
||||||
|
=== undo
|
||||||
|
Re-open a closed tab (optionally skipping [count] closed tabs).
|
||||||
|
|
||||||
|
[[yank]]
|
||||||
|
=== yank
|
||||||
|
Syntax: +:yank [*--title*] [*--sel*]+
|
||||||
|
|
||||||
|
Yank the current URL/title to the clipboard or primary selection.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-t*+, +*--title*+: Yank the title instead of the URL.
|
||||||
|
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||||
|
|
||||||
|
[[zoom]]
|
||||||
|
=== zoom
|
||||||
|
Syntax: +:zoom ['zoom']+
|
||||||
|
|
||||||
|
Set the zoom level for the current tab.
|
||||||
|
|
||||||
|
The zoom can be given as argument or as [count]. If neither of both is given, the zoom is set to 100%.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'zoom'+: The zoom percentage to set.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
The zoom percentage to set.
|
||||||
|
|
||||||
|
[[zoom-in]]
|
||||||
|
=== zoom-in
|
||||||
|
Increase the zoom level for the current tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many steps to zoom in.
|
||||||
|
|
||||||
|
[[zoom-out]]
|
||||||
|
=== zoom-out
|
||||||
|
Decrease the zoom level for the current tab.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many steps to zoom out.
|
||||||
|
|
||||||
|
|
||||||
|
== Hidden commands
|
||||||
|
.Quick reference
|
||||||
|
[options="header",width="75%",cols="25%,75%"]
|
||||||
|
|==============
|
||||||
|
|Command|Description
|
||||||
|
|<<command-accept,command-accept>>|Execute the command currently in the commandline.
|
||||||
|
|<<command-history-next,command-history-next>>|Go forward in the commandline history.
|
||||||
|
|<<command-history-prev,command-history-prev>>|Go back in the commandline history.
|
||||||
|
|<<completion-item-next,completion-item-next>>|Select the next completion item.
|
||||||
|
|<<completion-item-prev,completion-item-prev>>|Select the previous completion item.
|
||||||
|
|<<enter-mode,enter-mode>>|Enter a key mode.
|
||||||
|
|<<follow-hint,follow-hint>>|Follow the currently selected hint.
|
||||||
|
|<<leave-mode,leave-mode>>|Leave the mode we're currently in.
|
||||||
|
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||||
|
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||||
|
|<<prompt-no,prompt-no>>|Answer no to a yes/no prompt.
|
||||||
|
|<<prompt-yes,prompt-yes>>|Answer yes to a yes/no prompt.
|
||||||
|
|<<rl-backward-char,rl-backward-char>>|Move back a character.
|
||||||
|
|<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor.
|
||||||
|
|<<rl-backward-word,rl-backward-word>>|Move back to the start of the current or previous word.
|
||||||
|
|<<rl-beginning-of-line,rl-beginning-of-line>>|Move to the start of the line.
|
||||||
|
|<<rl-delete-char,rl-delete-char>>|Delete the character after the cursor.
|
||||||
|
|<<rl-end-of-line,rl-end-of-line>>|Move to the end of the line.
|
||||||
|
|<<rl-forward-char,rl-forward-char>>|Move forward a character.
|
||||||
|
|<<rl-forward-word,rl-forward-word>>|Move forward to the end of the next word.
|
||||||
|
|<<rl-kill-line,rl-kill-line>>|Remove chars from the cursor to the end of the line.
|
||||||
|
|<<rl-kill-word,rl-kill-word>>|Remove chars from the cursor to the end of the current word.
|
||||||
|
|<<rl-unix-line-discard,rl-unix-line-discard>>|Remove chars backward from the cursor to the beginning of the line.
|
||||||
|
|<<rl-unix-word-rubout,rl-unix-word-rubout>>|Remove chars from the cursor to the beginning of the word.
|
||||||
|
|<<rl-yank,rl-yank>>|Paste the most recently deleted text.
|
||||||
|
|<<scroll,scroll>>|Scroll the current tab by 'count * dx/dy'.
|
||||||
|
|<<scroll-page,scroll-page>>|Scroll the frame page-wise.
|
||||||
|
|<<scroll-perc,scroll-perc>>|Scroll to a specific percentage of the page.
|
||||||
|
|<<search-next,search-next>>|Continue the search to the ([count]th) next term.
|
||||||
|
|<<search-prev,search-prev>>|Continue the search to the ([count]th) previous term.
|
||||||
|
|==============
|
||||||
|
[[command-accept]]
|
||||||
|
=== command-accept
|
||||||
|
Execute the command currently in the commandline.
|
||||||
|
|
||||||
|
[[command-history-next]]
|
||||||
|
=== command-history-next
|
||||||
|
Go forward in the commandline history.
|
||||||
|
|
||||||
|
[[command-history-prev]]
|
||||||
|
=== command-history-prev
|
||||||
|
Go back in the commandline history.
|
||||||
|
|
||||||
|
[[completion-item-next]]
|
||||||
|
=== completion-item-next
|
||||||
|
Select the next completion item.
|
||||||
|
|
||||||
|
[[completion-item-prev]]
|
||||||
|
=== completion-item-prev
|
||||||
|
Select the previous completion item.
|
||||||
|
|
||||||
|
[[enter-mode]]
|
||||||
|
=== enter-mode
|
||||||
|
Syntax: +:enter-mode 'mode'+
|
||||||
|
|
||||||
|
Enter a key mode.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'mode'+: The mode to enter.
|
||||||
|
|
||||||
|
[[follow-hint]]
|
||||||
|
=== follow-hint
|
||||||
|
Follow the currently selected hint.
|
||||||
|
|
||||||
|
[[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.
|
||||||
|
|
||||||
|
The editor which should be launched can be configured via the `general -> editor` config option.
|
||||||
|
|
||||||
|
[[prompt-accept]]
|
||||||
|
=== prompt-accept
|
||||||
|
Accept the current prompt.
|
||||||
|
|
||||||
|
[[prompt-no]]
|
||||||
|
=== prompt-no
|
||||||
|
Answer no to a yes/no prompt.
|
||||||
|
|
||||||
|
[[prompt-yes]]
|
||||||
|
=== prompt-yes
|
||||||
|
Answer yes to a yes/no prompt.
|
||||||
|
|
||||||
|
[[rl-backward-char]]
|
||||||
|
=== rl-backward-char
|
||||||
|
Move back a character.
|
||||||
|
|
||||||
|
This acts like readline's backward-char.
|
||||||
|
|
||||||
|
[[rl-backward-delete-char]]
|
||||||
|
=== rl-backward-delete-char
|
||||||
|
Delete the character before the cursor.
|
||||||
|
|
||||||
|
This acts like readline's backward-delete-char.
|
||||||
|
|
||||||
|
[[rl-backward-word]]
|
||||||
|
=== rl-backward-word
|
||||||
|
Move back to the start of the current or previous word.
|
||||||
|
|
||||||
|
This acts like readline's backward-word.
|
||||||
|
|
||||||
|
[[rl-beginning-of-line]]
|
||||||
|
=== rl-beginning-of-line
|
||||||
|
Move to the start of the line.
|
||||||
|
|
||||||
|
This acts like readline's beginning-of-line.
|
||||||
|
|
||||||
|
[[rl-delete-char]]
|
||||||
|
=== rl-delete-char
|
||||||
|
Delete the character after the cursor.
|
||||||
|
|
||||||
|
This acts like readline's delete-char.
|
||||||
|
|
||||||
|
[[rl-end-of-line]]
|
||||||
|
=== rl-end-of-line
|
||||||
|
Move to the end of the line.
|
||||||
|
|
||||||
|
This acts like readline's end-of-line.
|
||||||
|
|
||||||
|
[[rl-forward-char]]
|
||||||
|
=== rl-forward-char
|
||||||
|
Move forward a character.
|
||||||
|
|
||||||
|
This acts like readline's forward-char.
|
||||||
|
|
||||||
|
[[rl-forward-word]]
|
||||||
|
=== rl-forward-word
|
||||||
|
Move forward to the end of the next word.
|
||||||
|
|
||||||
|
This acts like readline's forward-word.
|
||||||
|
|
||||||
|
[[rl-kill-line]]
|
||||||
|
=== rl-kill-line
|
||||||
|
Remove chars from the cursor to the end of the line.
|
||||||
|
|
||||||
|
This acts like readline's kill-line.
|
||||||
|
|
||||||
|
[[rl-kill-word]]
|
||||||
|
=== rl-kill-word
|
||||||
|
Remove chars from the cursor to the end of the current word.
|
||||||
|
|
||||||
|
This acts like readline's kill-word.
|
||||||
|
|
||||||
|
[[rl-unix-line-discard]]
|
||||||
|
=== rl-unix-line-discard
|
||||||
|
Remove chars backward from the cursor to the beginning of the line.
|
||||||
|
|
||||||
|
This acts like readline's unix-line-discard.
|
||||||
|
|
||||||
|
[[rl-unix-word-rubout]]
|
||||||
|
=== rl-unix-word-rubout
|
||||||
|
Remove chars from the cursor to the beginning of the word.
|
||||||
|
|
||||||
|
This acts like readline's unix-word-rubout.
|
||||||
|
|
||||||
|
[[rl-yank]]
|
||||||
|
=== rl-yank
|
||||||
|
Paste the most recently deleted text.
|
||||||
|
|
||||||
|
This acts like readline's yank.
|
||||||
|
|
||||||
|
[[scroll]]
|
||||||
|
=== scroll
|
||||||
|
Syntax: +:scroll 'dx' 'dy'+
|
||||||
|
|
||||||
|
Scroll the current tab by 'count * dx/dy'.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'dx'+: How much to scroll in x-direction.
|
||||||
|
* +'dy'+: How much to scroll in x-direction.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
multiplier
|
||||||
|
|
||||||
|
[[scroll-page]]
|
||||||
|
=== scroll-page
|
||||||
|
Syntax: +:scroll-page 'x' 'y'+
|
||||||
|
|
||||||
|
Scroll the frame page-wise.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'x'+: How many pages to scroll to the right.
|
||||||
|
* +'y'+: How many pages to scroll down.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
multiplier
|
||||||
|
|
||||||
|
[[scroll-perc]]
|
||||||
|
=== scroll-perc
|
||||||
|
Syntax: +:scroll-perc [*--horizontal*] ['perc']+
|
||||||
|
|
||||||
|
Scroll to a specific percentage of the page.
|
||||||
|
|
||||||
|
The percentage can be given either as argument or as count. If no percentage is given, the page is scrolled to the end.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'perc'+: Percentage to scroll.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-x*+, +*--horizontal*+: Scroll horizontally instead of vertically.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
Percentage to scroll.
|
||||||
|
|
||||||
|
[[search-next]]
|
||||||
|
=== search-next
|
||||||
|
Continue the search to the ([count]th) next term.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many elements to ignore.
|
||||||
|
|
||||||
|
[[search-prev]]
|
||||||
|
=== search-prev
|
||||||
|
Continue the search to the ([count]th) previous term.
|
||||||
|
|
||||||
|
==== count
|
||||||
|
How many elements to ignore.
|
||||||
|
|
||||||
|
|
||||||
|
== Debugging commands
|
||||||
|
These commands are mainly intended for debugging. They are hidden if qutebrowser was started without the `--debug`-flag.
|
||||||
|
|
||||||
|
.Quick reference
|
||||||
|
[options="header",width="75%",cols="25%,75%"]
|
||||||
|
|==============
|
||||||
|
|Command|Description
|
||||||
|
|<<debug-all-objects,debug-all-objects>>|Print a list of all objects to the debug log.
|
||||||
|
|<<debug-all-widgets,debug-all-widgets>>|Print a list of all widgets to debug log.
|
||||||
|
|<<debug-cache-stats,debug-cache-stats>>|Print LRU cache stats.
|
||||||
|
|<<debug-console,debug-console>>|Show the debugging console.
|
||||||
|
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|
||||||
|
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a webpage.
|
||||||
|
|==============
|
||||||
|
[[debug-all-objects]]
|
||||||
|
=== debug-all-objects
|
||||||
|
Print a list of all objects to the debug log.
|
||||||
|
|
||||||
|
[[debug-all-widgets]]
|
||||||
|
=== debug-all-widgets
|
||||||
|
Print a list of all widgets to debug log.
|
||||||
|
|
||||||
|
[[debug-cache-stats]]
|
||||||
|
=== debug-cache-stats
|
||||||
|
Print LRU cache stats.
|
||||||
|
|
||||||
|
[[debug-console]]
|
||||||
|
=== debug-console
|
||||||
|
Show the debugging console.
|
||||||
|
|
||||||
|
[[debug-crash]]
|
||||||
|
=== debug-crash
|
||||||
|
Syntax: +:debug-crash ['typ']+
|
||||||
|
|
||||||
|
Crash for debugging purposes.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'typ'+: either 'exception' or 'segfault'.
|
||||||
|
|
||||||
|
[[debug-pyeval]]
|
||||||
|
=== debug-pyeval
|
||||||
|
Syntax: +:debug-pyeval 's'+
|
||||||
|
|
||||||
|
Evaluate a python string and display the results as a webpage.
|
||||||
|
|
||||||
|
==== positional arguments
|
||||||
|
* +'s'+: The string to evaluate.
|
||||||
|
|
50
doc/help/index.asciidoc
Normal file
50
doc/help/index.asciidoc
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
qutebrowser help
|
||||||
|
================
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
The following help pages are currently available:
|
||||||
|
|
||||||
|
* link:FAQ.html[Frequently asked questions]
|
||||||
|
* link:commands.html[Documentation of commands]
|
||||||
|
* link:settings.html[Documentation of settings]
|
||||||
|
|
||||||
|
Getting help
|
||||||
|
------------
|
||||||
|
|
||||||
|
You can get help in the IRC channel
|
||||||
|
irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
|
||||||
|
http://freenode.net/[Freenode]
|
||||||
|
(https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a
|
||||||
|
message to the
|
||||||
|
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||||
|
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||||
|
|
||||||
|
Bugs
|
||||||
|
----
|
||||||
|
|
||||||
|
If you found a bug or have a feature request, you can report it in several
|
||||||
|
ways:
|
||||||
|
|
||||||
|
* Use the built-in `:report` command or the automatic crash dialog.
|
||||||
|
* Open an issue in the Github issue tracker.
|
||||||
|
* Write a mail to the
|
||||||
|
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||||
|
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
This program 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.
|
||||||
|
|
||||||
|
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
1199
doc/help/settings.asciidoc
Normal file
1199
doc/help/settings.asciidoc
Normal file
File diff suppressed because it is too large
Load Diff
14
doc/notes
14
doc/notes
@ -61,3 +61,17 @@ Completion view (not QTreeView)
|
|||||||
|
|
||||||
Perhaps using a QHBoxLayout of QTableViews and creating/destroying them based
|
Perhaps using a QHBoxLayout of QTableViews and creating/destroying them based
|
||||||
on the completion would be a better idea?
|
on the completion would be a better idea?
|
||||||
|
|
||||||
|
HTML help pages
|
||||||
|
===============
|
||||||
|
|
||||||
|
- Only generate HTML when releasing (and ship it with the releases!)
|
||||||
|
(setuptools integration)
|
||||||
|
X Update asciidoc along with source updates
|
||||||
|
X Provide script to generate HTML from asciidoc
|
||||||
|
- Show error page with some instructions when HTMLs are missing.
|
||||||
|
- Show some kind of message when:
|
||||||
|
- .html files are found
|
||||||
|
- .asciidoc files are found (because qutebrowser is running locally from
|
||||||
|
gitrepo)
|
||||||
|
- .asciidoc files are newer than .html files
|
||||||
|
120
doc/qutebrowser.1.asciidoc
Normal file
120
doc/qutebrowser.1.asciidoc
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Note some sections in this file (everything between QUTE_*_START and
|
||||||
|
// QUTE_*_END) are autogenerated by scripts/generate_doc.sh. DO NOT edit them
|
||||||
|
// by hand.
|
||||||
|
|
||||||
|
= qutebrowser(1)
|
||||||
|
:doctype: manpage
|
||||||
|
:man source: qutebrowser
|
||||||
|
:man manual: qutebrowser manpage
|
||||||
|
:toc:
|
||||||
|
:homepage: http://www.qutebrowser.org/
|
||||||
|
|
||||||
|
== NAME
|
||||||
|
qutebrowser - A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.
|
||||||
|
|
||||||
|
== SYNOPSIS
|
||||||
|
*qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']]
|
||||||
|
|
||||||
|
== DESCRIPTION
|
||||||
|
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
|
||||||
|
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||||
|
|
||||||
|
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||||
|
|
||||||
|
== OPTIONS
|
||||||
|
// QUTE_OPTIONS_START
|
||||||
|
=== positional arguments
|
||||||
|
*':command'*::
|
||||||
|
Commands to execute on startup.
|
||||||
|
|
||||||
|
*'URL'*::
|
||||||
|
URLs to open on startup.
|
||||||
|
|
||||||
|
=== optional arguments
|
||||||
|
*-h*, *--help*::
|
||||||
|
show this help message and exit
|
||||||
|
|
||||||
|
*-c* 'CONFDIR', *--confdir* 'CONFDIR'::
|
||||||
|
Set config directory (empty for no config storage)
|
||||||
|
|
||||||
|
*-V*, *--version*::
|
||||||
|
Show version and quit.
|
||||||
|
|
||||||
|
=== debug arguments
|
||||||
|
*-l* 'LOGLEVEL', *--loglevel* 'LOGLEVEL'::
|
||||||
|
Set loglevel
|
||||||
|
|
||||||
|
*--logfilter* 'LOGFILTER'::
|
||||||
|
Comma-separated list of things to be logged to the debug log on stdout.
|
||||||
|
|
||||||
|
*--loglines* 'LOGLINES'::
|
||||||
|
How many lines of the debug log to keep in RAM (-1: unlimited).
|
||||||
|
|
||||||
|
*--debug*::
|
||||||
|
Turn on debugging options.
|
||||||
|
|
||||||
|
*--nocolor*::
|
||||||
|
Turn off colored logging.
|
||||||
|
|
||||||
|
*--harfbuzz* '{old,new,system,auto}'::
|
||||||
|
HarfBuzz engine version to use. Default: auto.
|
||||||
|
|
||||||
|
*--nowindow*::
|
||||||
|
Don't show the main window.
|
||||||
|
|
||||||
|
*--debug-exit*::
|
||||||
|
Turn on debugging of late exit.
|
||||||
|
|
||||||
|
*--qt-style* 'STYLE'::
|
||||||
|
Set the Qt GUI style to use.
|
||||||
|
|
||||||
|
*--qt-stylesheet* 'STYLESHEET'::
|
||||||
|
Override the Qt application stylesheet.
|
||||||
|
|
||||||
|
*--qt-widgetcount*::
|
||||||
|
Print debug message at the end about number of widgets left undestroyed and maximum number of widgets existed at the same time.
|
||||||
|
|
||||||
|
*--qt-reverse*::
|
||||||
|
Set the application's layout direction to right-to-left.
|
||||||
|
|
||||||
|
*--qt-qmljsdebugger* 'port:PORT[,block]'::
|
||||||
|
Activate the QML/JS debugger with a specified port. 'block' is optional and will make the application wait until a debugger connects to it.
|
||||||
|
// QUTE_OPTIONS_END
|
||||||
|
|
||||||
|
== BUGS
|
||||||
|
Bugs are tracked at two locations:
|
||||||
|
|
||||||
|
* The link:BUGS[doc/BUGS] and link:TODO[doc/TODO] files shipped with
|
||||||
|
qutebrowser.
|
||||||
|
* The Github issue tracker at
|
||||||
|
https://github.com/The-Compiler/qutebrowser/issues.
|
||||||
|
|
||||||
|
If you found a bug or have a suggestion, either open a ticket in the github
|
||||||
|
issue tracker, or write a mail to the
|
||||||
|
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||||
|
mailto:qutebrowser@lists.qutebrowser.org[].
|
||||||
|
|
||||||
|
== COPYRIGHT
|
||||||
|
This program 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.
|
||||||
|
|
||||||
|
This program 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
|
||||||
|
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
== RESOURCES
|
||||||
|
* Website: http://www.qutebrowser.org/
|
||||||
|
* Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] /
|
||||||
|
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser
|
||||||
|
* IRC: irc://irc.freenode.org/#qutebrowser[`#qutebrowser`]
|
||||||
|
http://freenode.net/[Freenode]
|
||||||
|
* Github: https://github.com/The-Compiler/qutebrowser
|
||||||
|
|
||||||
|
== AUTHOR
|
||||||
|
*qutebrowser* was written by Florian Bruhin. All contributors can be found in
|
||||||
|
the README file distributed with qutebrowser.
|
@ -11,7 +11,7 @@ license=('GPL')
|
|||||||
depends=('python>=3.4' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2'
|
depends=('python>=3.4' 'python-setuptools' 'python-pyqt5>=5.2' 'qt5-base>=5.2'
|
||||||
'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-pypeg2' 'python-jinja'
|
'qt5-webkit>=5.2' 'libxkbcommon-x11' 'python-pypeg2' 'python-jinja'
|
||||||
'python-pygments')
|
'python-pygments')
|
||||||
makedepends=('python' 'python-setuptools')
|
makedepends=('python' 'python-setuptools' 'asciidoc')
|
||||||
optdepends=('python-colorlog: colored logging output')
|
optdepends=('python-colorlog: colored logging output')
|
||||||
options=(!emptydirs)
|
options=(!emptydirs)
|
||||||
source=('qutebrowser::git://the-compiler.org/qutebrowser')
|
source=('qutebrowser::git://the-compiler.org/qutebrowser')
|
||||||
@ -24,5 +24,8 @@ pkgver() {
|
|||||||
|
|
||||||
package() {
|
package() {
|
||||||
cd "$srcdir/qutebrowser"
|
cd "$srcdir/qutebrowser"
|
||||||
|
python scripts/asciidoc2html.py
|
||||||
python setup.py install --root="$pkgdir/" --optimize=1
|
python setup.py install --root="$pkgdir/" --optimize=1
|
||||||
|
a2x -f manpage doc/qutebrowser.1.asciidoc
|
||||||
|
install -Dm644 doc/qutebrowser.1 "$pkgdir/usr/share/man/man1/qutebrowser.1"
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ from PyQt5.QtCore import (pyqtSlot, QTimer, QEventLoop, Qt, QStandardPaths,
|
|||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.commands import userscripts, runners, cmdutils
|
from qutebrowser.commands import userscripts, runners, cmdutils
|
||||||
from qutebrowser.config import (style, config, websettings, iniparsers,
|
from qutebrowser.config import (style, config, websettings, iniparsers,
|
||||||
lineparser, configtypes)
|
lineparser, configtypes, keyconfparser)
|
||||||
from qutebrowser.network import qutescheme, proxy
|
from qutebrowser.network import qutescheme, proxy
|
||||||
from qutebrowser.browser import quickmarks, cookies, downloads, cache
|
from qutebrowser.browser import quickmarks, cookies, downloads, cache
|
||||||
from qutebrowser.widgets import mainwindow, console, crash
|
from qutebrowser.widgets import mainwindow, console, crash
|
||||||
@ -104,6 +104,7 @@ class Application(QApplication):
|
|||||||
self.modeman = None
|
self.modeman = None
|
||||||
self.cmd_history = None
|
self.cmd_history = None
|
||||||
self.config = None
|
self.config = None
|
||||||
|
self.keyconfig = None
|
||||||
|
|
||||||
sys.excepthook = self._exception_hook
|
sys.excepthook = self._exception_hook
|
||||||
|
|
||||||
@ -176,6 +177,8 @@ class Application(QApplication):
|
|||||||
self)
|
self)
|
||||||
except (configtypes.ValidationError,
|
except (configtypes.ValidationError,
|
||||||
config.NoOptionError,
|
config.NoOptionError,
|
||||||
|
config.NoSectionError,
|
||||||
|
config.UnknownSectionError,
|
||||||
config.InterpolationSyntaxError,
|
config.InterpolationSyntaxError,
|
||||||
configparser.InterpolationError,
|
configparser.InterpolationError,
|
||||||
configparser.DuplicateSectionError,
|
configparser.DuplicateSectionError,
|
||||||
@ -191,6 +194,20 @@ class Application(QApplication):
|
|||||||
msgbox.exec_()
|
msgbox.exec_()
|
||||||
# We didn't really initialize much so far, so we just quit hard.
|
# We didn't really initialize much so far, so we just quit hard.
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
try:
|
||||||
|
self.keyconfig = keyconfparser.KeyConfigParser(
|
||||||
|
confdir, 'keys.conf')
|
||||||
|
except keyconfparser.KeyConfigError as e:
|
||||||
|
log.init.exception(e)
|
||||||
|
errstr = "Error while reading key config:\n"
|
||||||
|
if e.lineno is not None:
|
||||||
|
errstr += "In line {}: ".format(e.lineno)
|
||||||
|
errstr += str(e)
|
||||||
|
msgbox = QMessageBox(QMessageBox.Critical,
|
||||||
|
"Error while reading key config!", errstr)
|
||||||
|
msgbox.exec_()
|
||||||
|
# We didn't really initialize much so far, so we just quit hard.
|
||||||
|
sys.exit(1)
|
||||||
self.stateconfig = iniparsers.ReadWriteConfigParser(confdir, 'state')
|
self.stateconfig = iniparsers.ReadWriteConfigParser(confdir, 'state')
|
||||||
self.cmd_history = lineparser.LineConfigParser(
|
self.cmd_history = lineparser.LineConfigParser(
|
||||||
confdir, 'cmd_history', ('completion', 'history-length'))
|
confdir, 'cmd_history', ('completion', 'history-length'))
|
||||||
@ -203,14 +220,13 @@ class Application(QApplication):
|
|||||||
utypes.KeyMode.hint:
|
utypes.KeyMode.hint:
|
||||||
modeparsers.HintKeyParser(self),
|
modeparsers.HintKeyParser(self),
|
||||||
utypes.KeyMode.insert:
|
utypes.KeyMode.insert:
|
||||||
keyparser.PassthroughKeyParser('keybind.insert', self),
|
keyparser.PassthroughKeyParser('insert', self),
|
||||||
utypes.KeyMode.passthrough:
|
utypes.KeyMode.passthrough:
|
||||||
keyparser.PassthroughKeyParser('keybind.passthrough', self),
|
keyparser.PassthroughKeyParser('passthrough', self),
|
||||||
utypes.KeyMode.command:
|
utypes.KeyMode.command:
|
||||||
keyparser.PassthroughKeyParser('keybind.command', self),
|
keyparser.PassthroughKeyParser('command', self),
|
||||||
utypes.KeyMode.prompt:
|
utypes.KeyMode.prompt:
|
||||||
keyparser.PassthroughKeyParser('keybind.prompt', self,
|
keyparser.PassthroughKeyParser('prompt', self, warn=False),
|
||||||
warn=False),
|
|
||||||
utypes.KeyMode.yesno:
|
utypes.KeyMode.yesno:
|
||||||
modeparsers.PromptKeyParser(self),
|
modeparsers.PromptKeyParser(self),
|
||||||
}
|
}
|
||||||
@ -402,9 +418,10 @@ class Application(QApplication):
|
|||||||
# config
|
# config
|
||||||
self.config.style_changed.connect(style.get_stylesheet.cache_clear)
|
self.config.style_changed.connect(style.get_stylesheet.cache_clear)
|
||||||
for obj in (tabs, completion, self.mainwindow, self.cmd_history,
|
for obj in (tabs, completion, self.mainwindow, self.cmd_history,
|
||||||
websettings, kp[utypes.KeyMode.normal], self.modeman,
|
websettings, self.modeman, status, status.txt):
|
||||||
status, status.txt):
|
|
||||||
self.config.changed.connect(obj.on_config_changed)
|
self.config.changed.connect(obj.on_config_changed)
|
||||||
|
for obj in kp.values():
|
||||||
|
self.keyconfig.changed.connect(obj.on_keyconfig_changed)
|
||||||
|
|
||||||
# statusbar
|
# statusbar
|
||||||
# FIXME some of these probably only should be triggered on mainframe
|
# FIXME some of these probably only should be triggered on mainframe
|
||||||
@ -575,7 +592,7 @@ class Application(QApplication):
|
|||||||
self._destroy_crashlogfile()
|
self._destroy_crashlogfile()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@cmdutils.register(instance='', nargs=0)
|
@cmdutils.register(instance='', ignore_args=True)
|
||||||
def restart(self, shutdown=True, pages=None):
|
def restart(self, shutdown=True, pages=None):
|
||||||
"""Restart qutebrowser while keeping existing tabs open."""
|
"""Restart qutebrowser while keeping existing tabs open."""
|
||||||
# We don't use _recover_pages here as it's too forgiving when
|
# We don't use _recover_pages here as it's too forgiving when
|
||||||
@ -711,7 +728,7 @@ class Application(QApplication):
|
|||||||
# event loop, so we can shut down immediately.
|
# event loop, so we can shut down immediately.
|
||||||
self._shutdown(status)
|
self._shutdown(status)
|
||||||
|
|
||||||
def _shutdown(self, status):
|
def _shutdown(self, status): # noqa
|
||||||
"""Second stage of shutdown."""
|
"""Second stage of shutdown."""
|
||||||
log.destroy.debug("Stage 2 of shutting down...")
|
log.destroy.debug("Stage 2 of shutting down...")
|
||||||
# Remove eventfilter
|
# Remove eventfilter
|
||||||
@ -726,7 +743,10 @@ class Application(QApplication):
|
|||||||
if hasattr(self, 'config') and self.config is not None:
|
if hasattr(self, 'config') and self.config is not None:
|
||||||
to_save = []
|
to_save = []
|
||||||
if self.config.get('general', 'auto-save-config'):
|
if self.config.get('general', 'auto-save-config'):
|
||||||
to_save.append(("config", self.config.save))
|
if hasattr(self, 'config'):
|
||||||
|
to_save.append(("config", self.config.save))
|
||||||
|
if hasattr(self, 'keyconfig'):
|
||||||
|
to_save.append(("keyconfig", self.keyconfig.save))
|
||||||
to_save += [("window geometry", self._save_geometry),
|
to_save += [("window geometry", self._save_geometry),
|
||||||
("quickmarks", quickmarks.save)]
|
("quickmarks", quickmarks.save)]
|
||||||
if hasattr(self, 'cmd_history'):
|
if hasattr(self, 'cmd_history'):
|
||||||
|
@ -81,9 +81,7 @@ class CommandDispatcher:
|
|||||||
if perc is None and count is None:
|
if perc is None and count is None:
|
||||||
perc = 100
|
perc = 100
|
||||||
elif perc is None:
|
elif perc is None:
|
||||||
perc = int(count)
|
perc = count
|
||||||
else:
|
|
||||||
perc = float(perc)
|
|
||||||
perc = qtutils.check_overflow(perc, 'int', fatal=False)
|
perc = qtutils.check_overflow(perc, 'int', fatal=False)
|
||||||
frame = self._current_widget().page().currentFrame()
|
frame = self._current_widget().page().currentFrame()
|
||||||
m = frame.scrollBarMaximum(orientation)
|
m = frame.scrollBarMaximum(orientation)
|
||||||
@ -164,28 +162,35 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', name='open',
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='open',
|
||||||
split=False)
|
split=False)
|
||||||
def openurl(self, urlstr, count=None):
|
def openurl(self, url, bg=False, tab=False, count=None):
|
||||||
"""Open a URL in the current/[count]th tab.
|
"""Open a URL in the current/[count]th tab.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
urlstr: The URL to open, as string.
|
url: The URL to open.
|
||||||
|
bg: Open in a new background tab.
|
||||||
|
tab: Open in a new tab.
|
||||||
count: The tab index to open the URL in, or None.
|
count: The tab index to open the URL in, or None.
|
||||||
"""
|
"""
|
||||||
tab = self._tabs.cntwidget(count)
|
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(urlstr)
|
url = urlutils.fuzzy_url(url)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.FuzzyUrlError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
if tab is None:
|
if tab:
|
||||||
if count is None:
|
self._tabs.tabopen(url, background=False, explicit=True)
|
||||||
# We want to open a URL in the current tab, but none exists
|
elif bg:
|
||||||
# yet.
|
self._tabs.tabopen(url, background=True, explicit=True)
|
||||||
self._tabs.tabopen(url)
|
|
||||||
else:
|
|
||||||
# Explicit count with a tab that doesn't exist.
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
tab.openurl(url)
|
curtab = self._tabs.cntwidget(count)
|
||||||
|
if curtab is None:
|
||||||
|
if count is None:
|
||||||
|
# We want to open a URL in the current tab, but none exists
|
||||||
|
# yet.
|
||||||
|
self._tabs.tabopen(url)
|
||||||
|
else:
|
||||||
|
# Explicit count with a tab that doesn't exist.
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
curtab.openurl(url)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', name='reload')
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='reload')
|
||||||
def reloadpage(self, count=None):
|
def reloadpage(self, count=None):
|
||||||
@ -209,29 +214,12 @@ class CommandDispatcher:
|
|||||||
if tab is not None:
|
if tab is not None:
|
||||||
tab.stop()
|
tab.stop()
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def print_preview(self, count=None):
|
|
||||||
"""Preview printing of the current/[count]th tab.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
count: The tab index to print, or None.
|
|
||||||
"""
|
|
||||||
if not qtutils.check_print_compat():
|
|
||||||
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
|
|
||||||
raise cmdexc.CommandError(
|
|
||||||
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
|
|
||||||
tab = self._tabs.cntwidget(count)
|
|
||||||
if tab is not None:
|
|
||||||
preview = QPrintPreviewDialog()
|
|
||||||
preview.setAttribute(Qt.WA_DeleteOnClose)
|
|
||||||
preview.paintRequested.connect(tab.print)
|
|
||||||
preview.exec_()
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', name='print')
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='print')
|
||||||
def printpage(self, count=None):
|
def printpage(self, preview=False, count=None):
|
||||||
"""Print the current/[count]th tab.
|
"""Print the current/[count]th tab.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
preview: Show preview instead of printing.
|
||||||
count: The tab index to print, or None.
|
count: The tab index to print, or None.
|
||||||
"""
|
"""
|
||||||
if not qtutils.check_print_compat():
|
if not qtutils.check_print_compat():
|
||||||
@ -240,9 +228,15 @@ class CommandDispatcher:
|
|||||||
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
|
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
|
||||||
tab = self._tabs.cntwidget(count)
|
tab = self._tabs.cntwidget(count)
|
||||||
if tab is not None:
|
if tab is not None:
|
||||||
printdiag = QPrintDialog()
|
if preview:
|
||||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
diag = QPrintPreviewDialog()
|
||||||
printdiag.open(lambda: tab.print(printdiag.printer()))
|
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||||
|
diag.paintRequested.connect(tab.print)
|
||||||
|
diag.exec_()
|
||||||
|
else:
|
||||||
|
diag = QPrintDialog()
|
||||||
|
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||||
|
diag.open(lambda: tab.print(diag.printer()))
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def back(self, count=1):
|
def back(self, count=1):
|
||||||
@ -265,7 +259,8 @@ class CommandDispatcher:
|
|||||||
self._current_widget().go_forward()
|
self._current_widget().go_forward()
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def hint(self, group='all', target='normal', *args):
|
def hint(self, group=webelem.Group.all, target=hints.Target.normal,
|
||||||
|
*args: {'nargs': '*'}):
|
||||||
"""Start hinting.
|
"""Start hinting.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -284,10 +279,6 @@ class CommandDispatcher:
|
|||||||
- `yank-primary`: Yank the link to the primary selection.
|
- `yank-primary`: Yank the link to the primary selection.
|
||||||
- `fill`: Fill the commandline with the command given as
|
- `fill`: Fill the commandline with the command given as
|
||||||
argument.
|
argument.
|
||||||
- `cmd-tab`: Fill the commandline with `:open-tab` and the
|
|
||||||
link.
|
|
||||||
- `cmd-tag-bg`: Fill the commandline with `:open-tab-bg` and
|
|
||||||
the link.
|
|
||||||
- `rapid`: Open the link in a new tab and stay in hinting mode.
|
- `rapid`: Open the link in a new tab and stay in hinting mode.
|
||||||
- `download`: Download the link.
|
- `download`: Download the link.
|
||||||
- `userscript`: Call an userscript with `$QUTE_URL` set to the
|
- `userscript`: Call an userscript with `$QUTE_URL` set to the
|
||||||
@ -308,18 +299,8 @@ class CommandDispatcher:
|
|||||||
frame = widget.page().mainFrame()
|
frame = widget.page().mainFrame()
|
||||||
if frame is None:
|
if frame is None:
|
||||||
raise cmdexc.CommandError("No frame focused!")
|
raise cmdexc.CommandError("No frame focused!")
|
||||||
try:
|
widget.hintmanager.start(frame, self._tabs.current_url(), group,
|
||||||
group_enum = webelem.Group[group.replace('-', '_')]
|
target, *args)
|
||||||
except KeyError:
|
|
||||||
raise cmdexc.CommandError("Unknown hinting group {}!".format(
|
|
||||||
group))
|
|
||||||
try:
|
|
||||||
target_enum = hints.Target[target.replace('-', '_')]
|
|
||||||
except KeyError:
|
|
||||||
raise cmdexc.CommandError("Unknown hinting target {}!".format(
|
|
||||||
target))
|
|
||||||
widget.hintmanager.start(frame, self._tabs.current_url(), group_enum,
|
|
||||||
target_enum, *args)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
||||||
def follow_hint(self):
|
def follow_hint(self):
|
||||||
@ -327,43 +308,31 @@ class CommandDispatcher:
|
|||||||
self._current_widget().hintmanager.follow_hint()
|
self._current_widget().hintmanager.follow_hint()
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def prev_page(self):
|
def prev_page(self, tab=False):
|
||||||
"""Open a "previous" link.
|
"""Open a "previous" link.
|
||||||
|
|
||||||
This tries to automaticall click on typical "Previous Page" links using
|
This tries to automatically click on typical _Previous Page_ links
|
||||||
some heuristics.
|
using some heuristics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: Open in a new tab.
|
||||||
"""
|
"""
|
||||||
self._prevnext(prev=True, newtab=False)
|
self._prevnext(prev=True, newtab=tab)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def next_page(self):
|
def next_page(self, tab=False):
|
||||||
"""Open a "next" link.
|
"""Open a "next" link.
|
||||||
|
|
||||||
This tries to automatically click on typical "Next Page" links using
|
This tries to automatically click on typical _Next Page_ links using
|
||||||
some heuristics.
|
some heuristics.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: Open in a new tab.
|
||||||
"""
|
"""
|
||||||
self._prevnext(prev=False, newtab=False)
|
self._prevnext(prev=False, newtab=tab)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def prev_page_tab(self):
|
|
||||||
"""Open a "previous" link in a new tab.
|
|
||||||
|
|
||||||
This tries to automatically click on typical "Previous Page" links
|
|
||||||
using some heuristics.
|
|
||||||
"""
|
|
||||||
self._prevnext(prev=True, newtab=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def next_page_tab(self):
|
|
||||||
"""Open a "next" link in a new tab.
|
|
||||||
|
|
||||||
This tries to automatically click on typical "Previous Page" links
|
|
||||||
using some heuristics.
|
|
||||||
"""
|
|
||||||
self._prevnext(prev=False, newtab=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
||||||
def scroll(self, dx, dy, count=1):
|
def scroll(self, dx: float, dy: float, count=1):
|
||||||
"""Scroll the current tab by 'count * dx/dy'.
|
"""Scroll the current tab by 'count * dx/dy'.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -371,40 +340,30 @@ class CommandDispatcher:
|
|||||||
dy: How much to scroll in x-direction.
|
dy: How much to scroll in x-direction.
|
||||||
count: multiplier
|
count: multiplier
|
||||||
"""
|
"""
|
||||||
dx = int(int(count) * float(dx))
|
dx *= count
|
||||||
dy = int(int(count) * float(dy))
|
dy *= count
|
||||||
cmdutils.check_overflow(dx, 'int')
|
cmdutils.check_overflow(dx, 'int')
|
||||||
cmdutils.check_overflow(dy, 'int')
|
cmdutils.check_overflow(dy, 'int')
|
||||||
self._current_widget().page().currentFrame().scroll(dx, dy)
|
self._current_widget().page().currentFrame().scroll(dx, dy)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
||||||
def scroll_perc_x(self, perc=None, count=None):
|
def scroll_perc(self, perc: float=None,
|
||||||
"""Scroll horizontally to a specific percentage of the page.
|
horizontal: {'flag': 'x'}=False, count=None):
|
||||||
|
"""Scroll to a specific percentage of the page.
|
||||||
|
|
||||||
The percentage can be given either as argument or as count.
|
The percentage can be given either as argument or as count.
|
||||||
If no percentage is given, the page is scrolled to the end.
|
If no percentage is given, the page is scrolled to the end.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
perc: Percentage to scroll.
|
perc: Percentage to scroll.
|
||||||
|
horizontal: Scroll horizontally instead of vertically.
|
||||||
count: Percentage to scroll.
|
count: Percentage to scroll.
|
||||||
"""
|
"""
|
||||||
self._scroll_percent(perc, count, Qt.Horizontal)
|
self._scroll_percent(perc, count,
|
||||||
|
Qt.Horizontal if horizontal else Qt.Vertical)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
||||||
def scroll_perc_y(self, perc=None, count=None):
|
def scroll_page(self, x: float, y: float, count=1):
|
||||||
"""Scroll vertically to a specific percentage of the page.
|
|
||||||
|
|
||||||
The percentage can be given either as argument or as count.
|
|
||||||
If no percentage is given, the page is scrolled to the end.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
perc: Percentage to scroll.
|
|
||||||
count: Percentage to scroll.
|
|
||||||
"""
|
|
||||||
self._scroll_percent(perc, count, Qt.Vertical)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
|
|
||||||
def scroll_page(self, x, y, count=1):
|
|
||||||
"""Scroll the frame page-wise.
|
"""Scroll the frame page-wise.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -414,51 +373,36 @@ class CommandDispatcher:
|
|||||||
"""
|
"""
|
||||||
frame = self._current_widget().page().currentFrame()
|
frame = self._current_widget().page().currentFrame()
|
||||||
size = frame.geometry()
|
size = frame.geometry()
|
||||||
dx = int(count) * float(x) * size.width()
|
dx = count * x * size.width()
|
||||||
dy = int(count) * float(y) * size.height()
|
dy = count * y * size.height()
|
||||||
cmdutils.check_overflow(dx, 'int')
|
cmdutils.check_overflow(dx, 'int')
|
||||||
cmdutils.check_overflow(dy, 'int')
|
cmdutils.check_overflow(dy, 'int')
|
||||||
frame.scroll(dx, dy)
|
frame.scroll(dx, dy)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def yank(self, sel=False):
|
def yank(self, title=False, sel=False):
|
||||||
"""Yank the current URL to the clipboard or primary selection.
|
"""Yank the current URL/title to the clipboard or primary selection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sel: True to use primary selection, False to use clipboard
|
sel: Use the primary selection instead of the clipboard.
|
||||||
|
title: Yank the title instead of the URL.
|
||||||
"""
|
"""
|
||||||
clipboard = QApplication.clipboard()
|
clipboard = QApplication.clipboard()
|
||||||
urlstr = self._tabs.current_url().toString(
|
if title:
|
||||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
s = self._tabs.tabText(self._tabs.currentIndex())
|
||||||
|
else:
|
||||||
|
s = self._tabs.current_url().toString(
|
||||||
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||||
if sel and clipboard.supportsSelection():
|
if sel and clipboard.supportsSelection():
|
||||||
mode = QClipboard.Selection
|
mode = QClipboard.Selection
|
||||||
target = "primary selection"
|
target = "primary selection"
|
||||||
else:
|
else:
|
||||||
mode = QClipboard.Clipboard
|
mode = QClipboard.Clipboard
|
||||||
target = "clipboard"
|
target = "clipboard"
|
||||||
log.misc.debug("Yanking to {}: '{}'".format(target, urlstr))
|
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
||||||
clipboard.setText(urlstr, mode)
|
clipboard.setText(s, mode)
|
||||||
message.info("URL yanked to {}".format(target))
|
what = 'Title' if title else 'URL'
|
||||||
|
message.info("{} yanked to {}".format(what, target))
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def yank_title(self, sel=False):
|
|
||||||
"""Yank the current title to the clipboard or primary selection.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sel: True to use primary selection, False to use clipboard
|
|
||||||
"""
|
|
||||||
clipboard = QApplication.clipboard()
|
|
||||||
title = self._tabs.tabText(self._tabs.currentIndex())
|
|
||||||
mode = QClipboard.Selection if sel else QClipboard.Clipboard
|
|
||||||
if sel and clipboard.supportsSelection():
|
|
||||||
mode = QClipboard.Selection
|
|
||||||
target = "primary selection"
|
|
||||||
else:
|
|
||||||
mode = QClipboard.Clipboard
|
|
||||||
target = "clipboard"
|
|
||||||
log.misc.debug("Yanking to {}: '{}'".format(target, title))
|
|
||||||
clipboard.setText(title, mode)
|
|
||||||
message.info("Title yanked to {}".format(target))
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def zoom_in(self, count=1):
|
def zoom_in(self, count=1):
|
||||||
@ -506,24 +450,6 @@ class CommandDispatcher:
|
|||||||
continue
|
continue
|
||||||
self._tabs.close_tab(tab)
|
self._tabs.close_tab(tab)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
|
|
||||||
def open_tab(self, urlstr):
|
|
||||||
"""Open a new tab with a given url."""
|
|
||||||
try:
|
|
||||||
url = urlutils.fuzzy_url(urlstr)
|
|
||||||
except urlutils.FuzzyUrlError as e:
|
|
||||||
raise cmdexc.CommandError(e)
|
|
||||||
self._tabs.tabopen(url, background=False, explicit=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
|
|
||||||
def open_tab_bg(self, urlstr):
|
|
||||||
"""Open a new tab in background."""
|
|
||||||
try:
|
|
||||||
url = urlutils.fuzzy_url(urlstr)
|
|
||||||
except urlutils.FuzzyUrlError as e:
|
|
||||||
raise cmdexc.CommandError(e)
|
|
||||||
self._tabs.tabopen(url, background=True, explicit=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def undo(self):
|
def undo(self):
|
||||||
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
|
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
|
||||||
@ -562,13 +488,14 @@ class CommandDispatcher:
|
|||||||
else:
|
else:
|
||||||
raise cmdexc.CommandError("Last tab")
|
raise cmdexc.CommandError("Last tab")
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', nargs=(0, 1))
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def paste(self, sel=False, tab=False):
|
def paste(self, sel=False, tab=False, bg=False):
|
||||||
"""Open a page from the clipboard.
|
"""Open a page from the clipboard.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sel: True to use primary selection, False to use clipboard
|
sel: Use the primary selection instead of the clipboard.
|
||||||
tab: True to open in a new tab.
|
tab: Open in a new tab.
|
||||||
|
bg: Open in a background tab.
|
||||||
"""
|
"""
|
||||||
clipboard = QApplication.clipboard()
|
clipboard = QApplication.clipboard()
|
||||||
if sel and clipboard.supportsSelection():
|
if sel and clipboard.supportsSelection():
|
||||||
@ -586,22 +513,15 @@ class CommandDispatcher:
|
|||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.FuzzyUrlError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
if tab:
|
if tab:
|
||||||
self._tabs.tabopen(url, explicit=True)
|
self._tabs.tabopen(url, background=False, explicit=True)
|
||||||
|
elif bg:
|
||||||
|
self._tabs.tabopen(url, background=True, explicit=True)
|
||||||
else:
|
else:
|
||||||
widget = self._current_widget()
|
widget = self._current_widget()
|
||||||
widget.openurl(url)
|
widget.openurl(url)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def paste_tab(self, sel=False):
|
def tab_focus(self, index: (int, 'last')=None, count=None):
|
||||||
"""Open a page from the clipboard in a new tab.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sel: True to use primary selection, False to use clipboard
|
|
||||||
"""
|
|
||||||
self.paste(sel, True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def tab_focus(self, index=None, count=None):
|
|
||||||
"""Select the tab given as argument/[count].
|
"""Select the tab given as argument/[count].
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -625,11 +545,12 @@ class CommandDispatcher:
|
|||||||
idx))
|
idx))
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def tab_move(self, direction=None, count=None):
|
def tab_move(self, direction: ('+', '-')=None, count=None):
|
||||||
"""Move the current tab.
|
"""Move the current tab.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
direction: + or - for relative moving, none for absolute.
|
direction: `+` or `-` for relative moving, not given for absolute
|
||||||
|
moving.
|
||||||
count: If moving absolutely: New position (default: 0)
|
count: If moving absolutely: New position (default: 0)
|
||||||
If moving relatively: Offset.
|
If moving relatively: Offset.
|
||||||
"""
|
"""
|
||||||
@ -685,7 +606,7 @@ class CommandDispatcher:
|
|||||||
self.openurl(config.get('general', 'startpage')[0])
|
self.openurl(config.get('general', 'startpage')[0])
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def run_userscript(self, cmd, *args):
|
def run_userscript(self, cmd, *args: {'nargs': '*'}):
|
||||||
"""Run an userscript given as argument.
|
"""Run an userscript given as argument.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -701,26 +622,25 @@ class CommandDispatcher:
|
|||||||
quickmarks.prompt_save(self._tabs.current_url())
|
quickmarks.prompt_save(self._tabs.current_url())
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
||||||
def quickmark_load(self, name):
|
def quickmark_load(self, name, tab=False, bg=False):
|
||||||
"""Load a quickmark."""
|
"""Load a quickmark.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name of the quickmark to load.
|
||||||
|
tab: Load the quickmark in a new tab.
|
||||||
|
bg: Load the quickmark in a new background tab.
|
||||||
|
"""
|
||||||
urlstr = quickmarks.get(name)
|
urlstr = quickmarks.get(name)
|
||||||
url = QUrl(urlstr)
|
url = QUrl(urlstr)
|
||||||
if not url.isValid():
|
if not url.isValid():
|
||||||
raise cmdexc.CommandError("Invalid URL {} ({})".format(
|
raise cmdexc.CommandError("Invalid URL {} ({})".format(
|
||||||
urlstr, url.errorString()))
|
urlstr, url.errorString()))
|
||||||
self._current_widget().openurl(url)
|
if tab:
|
||||||
|
self._tabs.tabopen(url, background=False, explicit=True)
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
elif bg:
|
||||||
def quickmark_load_tab(self, name):
|
self._tabs.tabopen(url, background=True, explicit=True)
|
||||||
"""Load a quickmark in a new tab."""
|
else:
|
||||||
url = quickmarks.get(name)
|
self._current_widget().openurl(url)
|
||||||
self._tabs.tabopen(url, background=False, explicit=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd')
|
|
||||||
def quickmark_load_tab_bg(self, name):
|
|
||||||
"""Load a quickmark in a new background tab."""
|
|
||||||
url = quickmarks.get(name)
|
|
||||||
self._tabs.tabopen(url, background=True, explicit=True)
|
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
|
||||||
def toggle_inspector(self):
|
def toggle_inspector(self):
|
||||||
@ -769,6 +689,43 @@ class CommandDispatcher:
|
|||||||
tab.setHtml(highlighted, url)
|
tab.setHtml(highlighted, url)
|
||||||
tab.viewing_source = True
|
tab.viewing_source = True
|
||||||
|
|
||||||
|
@cmdutils.register(instance='mainwindow.tabs.cmd', name='help',
|
||||||
|
completion=[usertypes.Completion.helptopic])
|
||||||
|
def show_help(self, topic=None):
|
||||||
|
r"""Show help about a command or setting.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
topic: The topic to show help for.
|
||||||
|
|
||||||
|
- :__command__ for commands.
|
||||||
|
- __section__\->__option__ for settings.
|
||||||
|
"""
|
||||||
|
if topic is None:
|
||||||
|
path = 'index.html'
|
||||||
|
elif topic.startswith(':'):
|
||||||
|
command = topic[1:]
|
||||||
|
if command not in cmdutils.cmd_dict:
|
||||||
|
raise cmdexc.CommandError("Invalid command {}!".format(
|
||||||
|
command))
|
||||||
|
path = 'commands.html#{}'.format(command)
|
||||||
|
elif '->' in topic:
|
||||||
|
parts = topic.split('->')
|
||||||
|
if len(parts) != 2:
|
||||||
|
raise cmdexc.CommandError("Invalid help topic {}!".format(
|
||||||
|
topic))
|
||||||
|
try:
|
||||||
|
config.get(*parts)
|
||||||
|
except config.NoSectionError:
|
||||||
|
raise cmdexc.CommandError("Invalid section {}!".format(
|
||||||
|
parts[0]))
|
||||||
|
except config.NoOptionError:
|
||||||
|
raise cmdexc.CommandError("Invalid option {}!".format(
|
||||||
|
parts[1]))
|
||||||
|
path = 'settings.html#{}'.format(topic.replace('->', '-'))
|
||||||
|
else:
|
||||||
|
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
|
||||||
|
self.openurl('qute://help/{}'.format(path))
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.tabs.cmd',
|
@cmdutils.register(instance='mainwindow.tabs.cmd',
|
||||||
modes=[usertypes.KeyMode.insert],
|
modes=[usertypes.KeyMode.insert],
|
||||||
hide=True)
|
hide=True)
|
||||||
|
@ -365,7 +365,11 @@ class DownloadManager(QObject):
|
|||||||
|
|
||||||
@cmdutils.register(instance='downloadmanager')
|
@cmdutils.register(instance='downloadmanager')
|
||||||
def cancel_download(self, count=1):
|
def cancel_download(self, count=1):
|
||||||
"""Cancel the first/[count]th download."""
|
"""Cancel the first/[count]th download.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
count: The index of the download to cancel.
|
||||||
|
"""
|
||||||
if count == 0:
|
if count == 0:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -454,6 +454,47 @@ class HintManager(QObject):
|
|||||||
f.contentsSizeChanged.connect(self.on_contents_size_changed)
|
f.contentsSizeChanged.connect(self.on_contents_size_changed)
|
||||||
self._context.connected_frames.append(f)
|
self._context.connected_frames.append(f)
|
||||||
|
|
||||||
|
def _check_args(self, target, *args):
|
||||||
|
"""Check the arguments passed to start() and raise if they're wrong.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: A Target enum member.
|
||||||
|
args: Arguments for userscript/download
|
||||||
|
"""
|
||||||
|
if not isinstance(target, Target):
|
||||||
|
raise TypeError("Target {} is no Target member!".format(target))
|
||||||
|
if target in (Target.userscript, Target.spawn, Target.fill):
|
||||||
|
if not args:
|
||||||
|
raise cmdexc.CommandError(
|
||||||
|
"'args' is required with target userscript/spawn/fill.")
|
||||||
|
else:
|
||||||
|
if args:
|
||||||
|
raise cmdexc.CommandError(
|
||||||
|
"'args' is only allowed with target userscript/spawn.")
|
||||||
|
|
||||||
|
def _init_elements(self, mainframe, group):
|
||||||
|
"""Initialize the elements and labels based on the context set.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mainframe: The main QWebFrame.
|
||||||
|
group: A Group enum member (which elements to find).
|
||||||
|
"""
|
||||||
|
elems = []
|
||||||
|
for f in self._context.frames:
|
||||||
|
elems += f.findAllElements(webelem.SELECTORS[group])
|
||||||
|
# We wrap the elements late for performance reasons, as wrapping 1000s
|
||||||
|
# of elements (with ~50 methods each) just takes too much time...
|
||||||
|
elems = [webelem.WebElementWrapper(e) for e in elems]
|
||||||
|
filterfunc = webelem.FILTERS.get(group, lambda e: True)
|
||||||
|
elems = [e for e in elems if filterfunc(e)]
|
||||||
|
if not elems:
|
||||||
|
raise cmdexc.CommandError("No elements found.")
|
||||||
|
strings = self._hint_strings(elems)
|
||||||
|
for e, string in zip(elems, strings):
|
||||||
|
label = self._draw_label(e, string)
|
||||||
|
self._context.elems[string] = ElemTuple(e, label)
|
||||||
|
self.hint_strings_updated.emit(strings)
|
||||||
|
|
||||||
def follow_prevnext(self, frame, baseurl, prev=False, newtab=False):
|
def follow_prevnext(self, frame, baseurl, prev=False, newtab=False):
|
||||||
"""Click a "previous"/"next" element on the page.
|
"""Click a "previous"/"next" element on the page.
|
||||||
|
|
||||||
@ -487,46 +528,20 @@ class HintManager(QObject):
|
|||||||
Emit:
|
Emit:
|
||||||
hint_strings_updated: Emitted to update keypraser.
|
hint_strings_updated: Emitted to update keypraser.
|
||||||
"""
|
"""
|
||||||
if not isinstance(target, Target):
|
self._check_args(target, *args)
|
||||||
raise TypeError("Target {} is no Target member!".format(target))
|
|
||||||
if mainframe is None:
|
if mainframe is None:
|
||||||
# This should never happen since we check frame before calling
|
# This should never happen since we check frame before calling
|
||||||
# start. But since we had a bug where frame is None in
|
# start. But since we had a bug where frame is None in
|
||||||
# on_mode_left, we are extra careful here.
|
# on_mode_left, we are extra careful here.
|
||||||
raise ValueError("start() was called with frame=None")
|
raise ValueError("start() was called with frame=None")
|
||||||
if target in (Target.userscript, Target.spawn, Target.fill):
|
self._context = HintContext()
|
||||||
if not args:
|
self._context.target = target
|
||||||
raise cmdexc.CommandError(
|
self._context.baseurl = baseurl
|
||||||
"Additional arguments are required with target "
|
self._context.frames = webelem.get_child_frames(mainframe)
|
||||||
"userscript/spawn/fill.")
|
self._context.args = args
|
||||||
else:
|
self._init_elements(mainframe, group)
|
||||||
if args:
|
|
||||||
raise cmdexc.CommandError(
|
|
||||||
"Arguments are only allowed with target userscript/spawn.")
|
|
||||||
elems = []
|
|
||||||
ctx = HintContext()
|
|
||||||
ctx.frames = webelem.get_child_frames(mainframe)
|
|
||||||
for f in ctx.frames:
|
|
||||||
elems += f.findAllElements(webelem.SELECTORS[group])
|
|
||||||
elems = [e for e in elems if webelem.is_visible(e, mainframe)]
|
|
||||||
# We wrap the elements late for performance reasons, as wrapping 1000s
|
|
||||||
# of elements (with ~50 methods each) just takes too much time...
|
|
||||||
elems = [webelem.WebElementWrapper(e) for e in elems]
|
|
||||||
filterfunc = webelem.FILTERS.get(group, lambda e: True)
|
|
||||||
elems = [e for e in elems if filterfunc(e)]
|
|
||||||
if not elems:
|
|
||||||
raise cmdexc.CommandError("No elements found.")
|
|
||||||
ctx.target = target
|
|
||||||
ctx.baseurl = baseurl
|
|
||||||
ctx.args = args
|
|
||||||
message.instance().set_text(self.HINT_TEXTS[target])
|
message.instance().set_text(self.HINT_TEXTS[target])
|
||||||
strings = self._hint_strings(elems)
|
|
||||||
for e, string in zip(elems, strings):
|
|
||||||
label = self._draw_label(e, string)
|
|
||||||
ctx.elems[string] = ElemTuple(e, label)
|
|
||||||
self._context = ctx
|
|
||||||
self._connect_frame_signals()
|
self._connect_frame_signals()
|
||||||
self.hint_strings_updated.emit(strings)
|
|
||||||
try:
|
try:
|
||||||
modeman.enter(usertypes.KeyMode.hint, 'HintManager.start')
|
modeman.enter(usertypes.KeyMode.hint, 'HintManager.start')
|
||||||
except modeman.ModeLockedError:
|
except modeman.ModeLockedError:
|
||||||
|
@ -71,21 +71,21 @@ def prompt_save(url):
|
|||||||
|
|
||||||
|
|
||||||
@cmdutils.register()
|
@cmdutils.register()
|
||||||
def quickmark_add(urlstr, name):
|
def quickmark_add(url, name):
|
||||||
"""Add a new quickmark.
|
"""Add a new quickmark.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
urlstr: The url to add as quickmark, as string.
|
url: The url to add as quickmark.
|
||||||
name: The name for the new quickmark.
|
name: The name for the new quickmark.
|
||||||
"""
|
"""
|
||||||
if not name:
|
if not name:
|
||||||
raise cmdexc.CommandError("Can't set mark with empty name!")
|
raise cmdexc.CommandError("Can't set mark with empty name!")
|
||||||
if not urlstr:
|
if not url:
|
||||||
raise cmdexc.CommandError("Can't set mark with empty URL!")
|
raise cmdexc.CommandError("Can't set mark with empty URL!")
|
||||||
|
|
||||||
def set_mark():
|
def set_mark():
|
||||||
"""Really set the quickmark."""
|
"""Really set the quickmark."""
|
||||||
marks[name] = urlstr
|
marks[name] = url
|
||||||
|
|
||||||
if name in marks:
|
if name in marks:
|
||||||
message.confirm_async("Override existing quickmark?", set_mark,
|
message.confirm_async("Override existing quickmark?", set_mark,
|
||||||
|
116
qutebrowser/commands/argparser.py
Normal file
116
qutebrowser/commands/argparser.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""argparse.ArgumentParser subclass to parse qutebrowser commands."""
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QCoreApplication, QUrl
|
||||||
|
|
||||||
|
from qutebrowser.commands import cmdexc
|
||||||
|
from qutebrowser.utils import utils
|
||||||
|
|
||||||
|
|
||||||
|
SUPPRESS = argparse.SUPPRESS
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentParserError(Exception):
|
||||||
|
|
||||||
|
"""Exception raised when the ArgumentParser signals an error."""
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentParserExit(Exception):
|
||||||
|
|
||||||
|
"""Exception raised when the argument parser exitted."""
|
||||||
|
|
||||||
|
def __init__(self, status, msg):
|
||||||
|
self.status = status
|
||||||
|
super().__init__(msg)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpAction(argparse.Action):
|
||||||
|
|
||||||
|
"""Argparse action to open the help page in the browser.
|
||||||
|
|
||||||
|
This is horrible encapsulation, but I can't think of a good way to do this
|
||||||
|
better...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self, parser, _namespace, _values, _option_string=None):
|
||||||
|
QCoreApplication.instance().mainwindow.tabs.tabopen(
|
||||||
|
QUrl('qute://help/commands.html#{}'.format(parser.name)))
|
||||||
|
parser.exit()
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentParser(argparse.ArgumentParser):
|
||||||
|
|
||||||
|
"""Subclass ArgumentParser to be more suitable for runtime parsing."""
|
||||||
|
|
||||||
|
def __init__(self, name, *args, **kwargs):
|
||||||
|
self.name = name
|
||||||
|
super().__init__(*args, add_help=False, prog=name, **kwargs)
|
||||||
|
|
||||||
|
def exit(self, status=0, msg=None):
|
||||||
|
raise ArgumentParserExit(status, msg)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
raise ArgumentParserError(msg[0].upper() + msg[1:])
|
||||||
|
|
||||||
|
|
||||||
|
def enum_getter(enum):
|
||||||
|
"""Function factory to get an enum getter."""
|
||||||
|
|
||||||
|
def _get_enum_item(key):
|
||||||
|
"""Helper function to get an enum item.
|
||||||
|
|
||||||
|
Passes through existing items unmodified.
|
||||||
|
"""
|
||||||
|
if isinstance(key, enum):
|
||||||
|
return key
|
||||||
|
try:
|
||||||
|
return enum[key.replace('-', '_')]
|
||||||
|
except KeyError:
|
||||||
|
raise cmdexc.ArgumentTypeError("Invalid value {}.".format(key))
|
||||||
|
|
||||||
|
return _get_enum_item
|
||||||
|
|
||||||
|
|
||||||
|
def multitype_conv(tpl):
|
||||||
|
"""Function factory to get a type converter for a choice of types."""
|
||||||
|
|
||||||
|
def _convert(value):
|
||||||
|
"""Convert a value according to an iterable of possible arg types."""
|
||||||
|
for typ in set(tpl):
|
||||||
|
if isinstance(typ, str):
|
||||||
|
if value == typ:
|
||||||
|
return value
|
||||||
|
elif utils.is_enum(typ):
|
||||||
|
return enum_getter(typ)(value)
|
||||||
|
elif callable(typ):
|
||||||
|
# int, float, etc.
|
||||||
|
if isinstance(value, typ):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
return typ(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
raise cmdexc.ArgumentTypeError('Invalid value {}.'.format(value))
|
||||||
|
|
||||||
|
return _convert
|
@ -49,6 +49,13 @@ class ArgumentCountError(CommandMetaError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ArgumentTypeError(CommandMetaError):
|
||||||
|
|
||||||
|
"""Raised when an argument had an invalid type."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PrerequisitesError(CommandMetaError):
|
class PrerequisitesError(CommandMetaError):
|
||||||
|
|
||||||
"""Raised when a cmd can't be used because some prerequisites aren't met.
|
"""Raised when a cmd can't be used because some prerequisites aren't met.
|
||||||
|
@ -23,13 +23,11 @@ Module attributes:
|
|||||||
cmd_dict: A mapping from command-strings to command objects.
|
cmd_dict: A mapping from command-strings to command objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import inspect
|
from qutebrowser.utils import usertypes, qtutils, log
|
||||||
import collections
|
|
||||||
|
|
||||||
from qutebrowser.utils import usertypes, qtutils
|
|
||||||
from qutebrowser.commands import command, cmdexc
|
from qutebrowser.commands import command, cmdexc
|
||||||
|
|
||||||
cmd_dict = {}
|
cmd_dict = {}
|
||||||
|
aliases = []
|
||||||
|
|
||||||
|
|
||||||
def check_overflow(arg, ctype):
|
def check_overflow(arg, ctype):
|
||||||
@ -68,23 +66,19 @@ def arg_or_count(arg, count, default=None, countzero=None):
|
|||||||
The value to use.
|
The value to use.
|
||||||
|
|
||||||
Raise:
|
Raise:
|
||||||
ValueError: If nothing was set or the value couldn't be converted to
|
ValueError: If nothing was set.
|
||||||
an integer.
|
|
||||||
"""
|
"""
|
||||||
if count is not None and arg is not None:
|
if count is not None and arg is not None:
|
||||||
raise ValueError("Both count and argument given!")
|
raise ValueError("Both count and argument given!")
|
||||||
elif arg is not None:
|
elif arg is not None:
|
||||||
try:
|
return arg
|
||||||
return int(arg)
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError("Invalid number: {}".format(arg))
|
|
||||||
elif count is not None:
|
elif count is not None:
|
||||||
if countzero is not None and count == 0:
|
if countzero is not None and count == 0:
|
||||||
return countzero
|
return countzero
|
||||||
else:
|
else:
|
||||||
return int(count)
|
return count
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
return int(default)
|
return default
|
||||||
else:
|
else:
|
||||||
raise ValueError("Either count or argument have to be set!")
|
raise ValueError("Either count or argument have to be set!")
|
||||||
|
|
||||||
@ -99,7 +93,6 @@ class register: # pylint: disable=invalid-name
|
|||||||
Attributes:
|
Attributes:
|
||||||
instance: The instance to be used as "self", as a dotted string.
|
instance: The instance to be used as "self", as a dotted string.
|
||||||
name: The name (as string) or names (as list) of the command.
|
name: The name (as string) or names (as list) of the command.
|
||||||
nargs: A (minargs, maxargs) tuple of valid argument counts, or an int.
|
|
||||||
split: Whether to split the arguments.
|
split: Whether to split the arguments.
|
||||||
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
|
||||||
@ -107,11 +100,12 @@ class register: # pylint: disable=invalid-name
|
|||||||
modes/not_modes: List of modes to use/not use.
|
modes/not_modes: List of modes to use/not use.
|
||||||
needs_js: If javascript is needed for this command.
|
needs_js: If javascript is needed for this command.
|
||||||
debug: Whether this is a debugging command (only shown with --debug).
|
debug: Whether this is a debugging command (only shown with --debug).
|
||||||
|
ignore_args: Whether to ignore the arguments of the function.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, instance=None, name=None, nargs=None, split=True,
|
def __init__(self, instance=None, name=None, split=True, hide=False,
|
||||||
hide=False, completion=None, modes=None, not_modes=None,
|
completion=None, modes=None, not_modes=None, needs_js=False,
|
||||||
needs_js=False, debug=False):
|
debug=False, ignore_args=False):
|
||||||
"""Save decorator arguments.
|
"""Save decorator arguments.
|
||||||
|
|
||||||
Gets called on parse-time with the decorator arguments.
|
Gets called on parse-time with the decorator arguments.
|
||||||
@ -125,13 +119,13 @@ class register: # pylint: disable=invalid-name
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.split = split
|
self.split = split
|
||||||
self.hide = hide
|
self.hide = hide
|
||||||
self.nargs = nargs
|
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
self.modes = modes
|
self.modes = modes
|
||||||
self.not_modes = not_modes
|
self.not_modes = not_modes
|
||||||
self.needs_js = needs_js
|
self.needs_js = needs_js
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
self.ignore_args = ignore_args
|
||||||
if modes is not None:
|
if modes is not None:
|
||||||
for m in modes:
|
for m in modes:
|
||||||
if not isinstance(m, usertypes.KeyMode):
|
if not isinstance(m, usertypes.KeyMode):
|
||||||
@ -141,6 +135,28 @@ class register: # pylint: disable=invalid-name
|
|||||||
if not isinstance(m, usertypes.KeyMode):
|
if not isinstance(m, usertypes.KeyMode):
|
||||||
raise TypeError("Mode {} is no KeyMode member!".format(m))
|
raise TypeError("Mode {} is no KeyMode member!".format(m))
|
||||||
|
|
||||||
|
def _get_names(self, func):
|
||||||
|
"""Get the name(s) which should be used for the current command.
|
||||||
|
|
||||||
|
If the name hasn't been overridden explicitely, the function name is
|
||||||
|
transformed.
|
||||||
|
|
||||||
|
If it has been set, it can either be a string which is
|
||||||
|
used directly, or an iterable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: The function to get the name of.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A list of names, with the main name being the first item.
|
||||||
|
"""
|
||||||
|
if self.name is None:
|
||||||
|
return [func.__name__.lower().replace('_', '-')]
|
||||||
|
elif isinstance(self.name, str):
|
||||||
|
return [self.name]
|
||||||
|
else:
|
||||||
|
return self.name
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
"""Register the command before running the function.
|
"""Register the command before running the function.
|
||||||
|
|
||||||
@ -155,74 +171,18 @@ class register: # pylint: disable=invalid-name
|
|||||||
Return:
|
Return:
|
||||||
The original function (unmodified).
|
The original function (unmodified).
|
||||||
"""
|
"""
|
||||||
names = []
|
global aliases
|
||||||
if self.name is None:
|
names = self._get_names(func)
|
||||||
name = func.__name__.lower().replace('_', '-')
|
log.commands.vdebug("Registering command {}".format(names[0]))
|
||||||
else:
|
for name in names:
|
||||||
name = self.name
|
if name in cmd_dict:
|
||||||
if isinstance(name, str):
|
raise ValueError("{} is already registered!".format(name))
|
||||||
mainname = name
|
|
||||||
names.append(name)
|
|
||||||
else:
|
|
||||||
mainname = name[0]
|
|
||||||
names += name
|
|
||||||
if mainname in cmd_dict:
|
|
||||||
raise ValueError("{} is already registered!".format(name))
|
|
||||||
argspec = inspect.getfullargspec(func)
|
|
||||||
if 'self' in argspec.args and self.instance is None:
|
|
||||||
raise ValueError("{} is a class method, but instance was not "
|
|
||||||
"given!".format(mainname))
|
|
||||||
count, nargs = self._get_nargs_count(argspec)
|
|
||||||
if func.__doc__ is not None:
|
|
||||||
desc = func.__doc__.splitlines()[0].strip()
|
|
||||||
else:
|
|
||||||
desc = ""
|
|
||||||
cmd = command.Command(
|
cmd = command.Command(
|
||||||
name=mainname, split=self.split, hide=self.hide, nargs=nargs,
|
name=names[0], split=self.split, hide=self.hide,
|
||||||
count=count, desc=desc, instance=self.instance, handler=func,
|
instance=self.instance, completion=self.completion,
|
||||||
completion=self.completion, modes=self.modes,
|
modes=self.modes, not_modes=self.not_modes, needs_js=self.needs_js,
|
||||||
not_modes=self.not_modes, needs_js=self.needs_js, debug=self.debug)
|
is_debug=self.debug, ignore_args=self.ignore_args, handler=func)
|
||||||
for name in names:
|
for name in names:
|
||||||
cmd_dict[name] = cmd
|
cmd_dict[name] = cmd
|
||||||
|
aliases += names[1:]
|
||||||
return func
|
return func
|
||||||
|
|
||||||
def _get_nargs_count(self, spec):
|
|
||||||
"""Get the number of command-arguments and count-support for a func.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
spec: A FullArgSpec as returned by inspect.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
A (count, (minargs, maxargs)) tuple, with maxargs=None if there are
|
|
||||||
infinite args. count is True if the function supports count, else
|
|
||||||
False.
|
|
||||||
|
|
||||||
Mapping from old nargs format to (minargs, maxargs):
|
|
||||||
? (0, 1)
|
|
||||||
N (N, N)
|
|
||||||
+ (1, None)
|
|
||||||
* (0, None)
|
|
||||||
"""
|
|
||||||
count = 'count' in spec.args
|
|
||||||
# we assume count always has a default (and it should!)
|
|
||||||
if self.nargs is not None:
|
|
||||||
# If nargs is overriden, use that.
|
|
||||||
if isinstance(self.nargs, collections.Iterable):
|
|
||||||
# Iterable (min, max)
|
|
||||||
# pylint: disable=unpacking-non-sequence
|
|
||||||
minargs, maxargs = self.nargs
|
|
||||||
else:
|
|
||||||
# Single int
|
|
||||||
minargs, maxargs = self.nargs, self.nargs
|
|
||||||
else:
|
|
||||||
defaultcount = (len(spec.defaults) if spec.defaults is not None
|
|
||||||
else 0)
|
|
||||||
argcount = len(spec.args)
|
|
||||||
if 'self' in spec.args:
|
|
||||||
argcount -= 1
|
|
||||||
minargs = argcount - defaultcount
|
|
||||||
if spec.varargs is not None:
|
|
||||||
maxargs = None
|
|
||||||
else:
|
|
||||||
maxargs = argcount - int(count) # -1 if count is defined
|
|
||||||
return (count, (minargs, maxargs))
|
|
||||||
|
@ -19,11 +19,14 @@
|
|||||||
|
|
||||||
"""Contains the Command class, a skeleton for a command."""
|
"""Contains the Command class, a skeleton for a command."""
|
||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication, QUrl
|
import inspect
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QCoreApplication
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
|
|
||||||
from qutebrowser.commands import cmdexc
|
from qutebrowser.commands import cmdexc, argparser
|
||||||
from qutebrowser.utils import log, utils
|
from qutebrowser.utils import log, utils, message, debug, usertypes
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
@ -34,7 +37,6 @@ class Command:
|
|||||||
name: The main name of the command.
|
name: The main name of the command.
|
||||||
split: Whether to split the arguments.
|
split: Whether to split the arguments.
|
||||||
hide: Whether to hide the arguments or not.
|
hide: Whether to hide the arguments or not.
|
||||||
nargs: A (minargs, maxargs) tuple, maxargs = None if there's no limit.
|
|
||||||
count: Whether the command supports a count, or not.
|
count: Whether the command supports a count, or not.
|
||||||
desc: The description of the command.
|
desc: The description of the command.
|
||||||
instance: How to get to the "self" argument of the handler.
|
instance: How to get to the "self" argument of the handler.
|
||||||
@ -43,38 +45,57 @@ class Command:
|
|||||||
completion: Completions to use for arguments, as a list of strings.
|
completion: Completions to use for arguments, as a list of strings.
|
||||||
needs_js: Whether the command needs javascript enabled
|
needs_js: Whether the command needs javascript enabled
|
||||||
debug: Whether this is a debugging command (only shown with --debug).
|
debug: Whether this is a debugging command (only shown with --debug).
|
||||||
|
parser: The ArgumentParser to use to parse this command.
|
||||||
|
type_conv: A mapping of conversion functions for arguments.
|
||||||
|
name_conv: A mapping of argument names to parameter names.
|
||||||
|
|
||||||
|
Class attributes:
|
||||||
|
AnnotationInfo: Named tuple for info from an annotation.
|
||||||
|
ParamType: Enum for an argparse parameter type.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO:
|
AnnotationInfo = collections.namedtuple('AnnotationInfo',
|
||||||
# we should probably have some kind of typing / argument casting for args
|
'kwargs, typ, name, flag')
|
||||||
# this might be combined with help texts or so as well
|
ParamType = usertypes.enum('ParamType', 'flag', 'positional')
|
||||||
|
|
||||||
def __init__(self, name, split, hide, nargs, count, desc, instance,
|
def __init__(self, name, split, hide, instance, completion, modes,
|
||||||
handler, completion, modes, not_modes, needs_js, debug):
|
not_modes, needs_js, is_debug, ignore_args,
|
||||||
|
handler):
|
||||||
# I really don't know how to solve this in a better way, I tried.
|
# I really don't know how to solve this in a better way, I tried.
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments,too-many-locals
|
||||||
self.name = name
|
self.name = name
|
||||||
self.split = split
|
self.split = split
|
||||||
self.hide = hide
|
self.hide = hide
|
||||||
self.nargs = nargs
|
|
||||||
self.count = count
|
|
||||||
self.desc = desc
|
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
self.handler = handler
|
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
self.modes = modes
|
self.modes = modes
|
||||||
self.not_modes = not_modes
|
self.not_modes = not_modes
|
||||||
self.needs_js = needs_js
|
self.needs_js = needs_js
|
||||||
self.debug = debug
|
self.debug = is_debug
|
||||||
|
self.ignore_args = ignore_args
|
||||||
|
self.handler = handler
|
||||||
|
self.docparser = utils.DocstringParser(handler)
|
||||||
|
self.parser = argparser.ArgumentParser(
|
||||||
|
name, description=self.docparser.short_desc,
|
||||||
|
epilog=self.docparser.long_desc)
|
||||||
|
self.parser.add_argument('-h', '--help', action=argparser.HelpAction,
|
||||||
|
default=argparser.SUPPRESS, nargs=0,
|
||||||
|
help=argparser.SUPPRESS)
|
||||||
|
self._check_func()
|
||||||
|
self.opt_args = collections.OrderedDict()
|
||||||
|
self.namespace = None
|
||||||
|
self.count = None
|
||||||
|
self.pos_args = []
|
||||||
|
has_count, desc, type_conv, name_conv = self._inspect_func()
|
||||||
|
self.has_count = has_count
|
||||||
|
self.desc = desc
|
||||||
|
self.type_conv = type_conv
|
||||||
|
self.name_conv = name_conv
|
||||||
|
|
||||||
def check(self, args):
|
def _check_prerequisites(self):
|
||||||
"""Check if the argument count is valid and the command is permitted.
|
"""Check if the command is permitted to run currently.
|
||||||
|
|
||||||
Args:
|
|
||||||
args: The supplied arguments
|
|
||||||
|
|
||||||
Raise:
|
Raise:
|
||||||
ArgumentCountError if the argument count is wrong.
|
|
||||||
PrerequisitesError if the command can't be called currently.
|
PrerequisitesError if the command can't be called currently.
|
||||||
"""
|
"""
|
||||||
# We don't use modeman.instance() here to avoid a circular import
|
# We don't use modeman.instance() here to avoid a circular import
|
||||||
@ -94,20 +115,267 @@ class Command:
|
|||||||
QWebSettings.JavascriptEnabled):
|
QWebSettings.JavascriptEnabled):
|
||||||
raise cmdexc.PrerequisitesError(
|
raise cmdexc.PrerequisitesError(
|
||||||
"{}: This command needs javascript enabled.".format(self.name))
|
"{}: This command needs javascript enabled.".format(self.name))
|
||||||
if self.nargs[1] is None and self.nargs[0] <= len(args):
|
|
||||||
pass
|
def _check_func(self):
|
||||||
elif self.nargs[0] <= len(args) <= self.nargs[1]:
|
"""Make sure the function parameters don't violate any rules."""
|
||||||
pass
|
signature = inspect.signature(self.handler)
|
||||||
|
if 'self' in signature.parameters and self.instance is None:
|
||||||
|
raise TypeError("{} is a class method, but instance was not "
|
||||||
|
"given!".format(self.name[0]))
|
||||||
|
elif 'self' not in signature.parameters and self.instance is not None:
|
||||||
|
raise TypeError("{} is not a class method, but instance was "
|
||||||
|
"given!".format(self.name[0]))
|
||||||
|
elif inspect.getfullargspec(self.handler).varkw is not None:
|
||||||
|
raise TypeError("{}: functions with varkw arguments are not "
|
||||||
|
"supported!".format(self.name[0]))
|
||||||
|
|
||||||
|
def _get_typeconv(self, param, typ):
|
||||||
|
"""Get a dict with a type conversion for the parameter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: The inspect.Parameter to handle.
|
||||||
|
typ: The type of the parameter.
|
||||||
|
"""
|
||||||
|
type_conv = {}
|
||||||
|
if utils.is_enum(typ):
|
||||||
|
type_conv[param.name] = argparser.enum_getter(typ)
|
||||||
|
elif isinstance(typ, tuple):
|
||||||
|
if param.default is not inspect.Parameter.empty:
|
||||||
|
typ = typ + (type(param.default),)
|
||||||
|
type_conv[param.name] = argparser.multitype_conv(typ)
|
||||||
|
return type_conv
|
||||||
|
|
||||||
|
def _get_nameconv(self, param, annotation_info):
|
||||||
|
"""Get a dict with a name conversion for the paraeter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: The inspect.Parameter to handle.
|
||||||
|
annotation_info: The AnnotationInfo tuple for the parameter.
|
||||||
|
"""
|
||||||
|
d = {}
|
||||||
|
if annotation_info.name is not None:
|
||||||
|
d[param.name] = annotation_info.name
|
||||||
|
return d
|
||||||
|
|
||||||
|
def _inspect_func(self):
|
||||||
|
"""Inspect the function to get useful informations from it.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A (has_count, desc, parser, type_conv) tuple.
|
||||||
|
has_count: Whether the command supports a count.
|
||||||
|
desc: The description of the command.
|
||||||
|
type_conv: A mapping of args to type converter callables.
|
||||||
|
name_conv: A mapping of names to convert.
|
||||||
|
"""
|
||||||
|
type_conv = {}
|
||||||
|
name_conv = {}
|
||||||
|
signature = inspect.signature(self.handler)
|
||||||
|
has_count = 'count' in signature.parameters
|
||||||
|
doc = inspect.getdoc(self.handler)
|
||||||
|
if doc is not None:
|
||||||
|
desc = doc.splitlines()[0].strip()
|
||||||
else:
|
else:
|
||||||
if self.nargs[0] == self.nargs[1]:
|
desc = ""
|
||||||
argcnt = str(self.nargs[0])
|
if not self.ignore_args:
|
||||||
elif self.nargs[1] is None:
|
for param in signature.parameters.values():
|
||||||
argcnt = '{}-inf'.format(self.nargs[0])
|
if param.name in ('self', 'count'):
|
||||||
|
continue
|
||||||
|
annotation_info = self._parse_annotation(param)
|
||||||
|
typ = self._get_type(param, annotation_info)
|
||||||
|
args, kwargs = self._param_to_argparse_args(
|
||||||
|
param, annotation_info)
|
||||||
|
type_conv.update(self._get_typeconv(param, typ))
|
||||||
|
name_conv.update(self._get_nameconv(param, annotation_info))
|
||||||
|
callsig = debug.format_call(
|
||||||
|
self.parser.add_argument, args, kwargs,
|
||||||
|
full=False)
|
||||||
|
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||||
|
param.name, typ, callsig))
|
||||||
|
self.parser.add_argument(*args, **kwargs)
|
||||||
|
return has_count, desc, type_conv, name_conv
|
||||||
|
|
||||||
|
def _param_to_argparse_args(self, param, annotation_info):
|
||||||
|
"""Get argparse arguments for a parameter.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
An (args, kwargs) tuple.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: The inspect.Parameter object to get the args for.
|
||||||
|
annotation_info: An AnnotationInfo tuple for the parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
typ = self._get_type(param, annotation_info)
|
||||||
|
param_type = self.ParamType.positional
|
||||||
|
|
||||||
|
try:
|
||||||
|
kwargs['help'] = self.docparser.arg_descs[param.name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if isinstance(typ, tuple):
|
||||||
|
pass
|
||||||
|
elif utils.is_enum(typ):
|
||||||
|
kwargs['choices'] = [e.name.replace('_', '-') for e in typ]
|
||||||
|
kwargs['metavar'] = param.name
|
||||||
|
elif typ is bool:
|
||||||
|
param_type = self.ParamType.flag
|
||||||
|
kwargs['action'] = 'store_true'
|
||||||
|
elif typ is not None:
|
||||||
|
kwargs['type'] = typ
|
||||||
|
|
||||||
|
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||||
|
kwargs['nargs'] = '+'
|
||||||
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
||||||
|
param_type = self.ParamType.flag
|
||||||
|
kwargs['default'] = param.default
|
||||||
|
elif typ is not bool and param.default is not inspect.Parameter.empty:
|
||||||
|
kwargs['default'] = param.default
|
||||||
|
kwargs['nargs'] = '?'
|
||||||
|
|
||||||
|
args = []
|
||||||
|
name = annotation_info.name or param.name
|
||||||
|
shortname = annotation_info.flag or param.name[0]
|
||||||
|
if param_type == self.ParamType.flag:
|
||||||
|
long_flag = '--{}'.format(name)
|
||||||
|
short_flag = '-{}'.format(shortname)
|
||||||
|
args.append(long_flag)
|
||||||
|
args.append(short_flag)
|
||||||
|
self.opt_args[param.name] = long_flag, short_flag
|
||||||
|
elif param_type == self.ParamType.positional:
|
||||||
|
args.append(name)
|
||||||
|
self.pos_args.append((param.name, name))
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid ParamType {}!".format(param_type))
|
||||||
|
kwargs.update(annotation_info.kwargs)
|
||||||
|
return args, kwargs
|
||||||
|
|
||||||
|
def _parse_annotation(self, param):
|
||||||
|
"""Get argparse arguments and type from a parameter annotation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: A inspect.Parameter instance.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
An AnnotationInfo namedtuple.
|
||||||
|
kwargs: A dict of keyword args to add to the
|
||||||
|
argparse.ArgumentParser.add_argument call.
|
||||||
|
typ: The type to use for this argument.
|
||||||
|
flag: The short name/flag if overridden.
|
||||||
|
name: The long name if overridden.
|
||||||
|
"""
|
||||||
|
info = {'kwargs': {}, 'typ': None, 'flag': None, 'name': None}
|
||||||
|
if param.annotation is not inspect.Parameter.empty:
|
||||||
|
log.commands.vdebug("Parsing annotation {}".format(
|
||||||
|
param.annotation))
|
||||||
|
if isinstance(param.annotation, dict):
|
||||||
|
for field in ('type', 'flag', 'name'):
|
||||||
|
if field in param.annotation:
|
||||||
|
info[field] = param.annotation[field]
|
||||||
|
del param.annotation[field]
|
||||||
|
info['kwargs'] = param.annotation
|
||||||
else:
|
else:
|
||||||
argcnt = '{}-{}'.format(self.nargs[0], self.nargs[1])
|
info['typ'] = param.annotation
|
||||||
raise cmdexc.ArgumentCountError(
|
return self.AnnotationInfo(**info)
|
||||||
"{}: {} args expected, but got {}".format(self.name, argcnt,
|
|
||||||
len(args)))
|
def _get_type(self, param, annotation_info):
|
||||||
|
"""Get the type of an argument from its default value or annotation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param: The inspect.Parameter to look at.
|
||||||
|
annotation_info: An AnnotationInfo tuple which overrides the type.
|
||||||
|
"""
|
||||||
|
if annotation_info.typ is not None:
|
||||||
|
return annotation_info.typ
|
||||||
|
elif param.default is None or param.default is inspect.Parameter.empty:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return type(param.default)
|
||||||
|
|
||||||
|
def _get_self_arg(self, param, args):
|
||||||
|
"""Get the self argument for a function call.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
param: The count parameter.
|
||||||
|
args: The positional argument list. Gets modified directly.
|
||||||
|
"""
|
||||||
|
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
app = QCoreApplication.instance()
|
||||||
|
if self.instance == '':
|
||||||
|
obj = app
|
||||||
|
else:
|
||||||
|
obj = utils.dotted_getattr(app, self.instance)
|
||||||
|
args.append(obj)
|
||||||
|
|
||||||
|
def _get_count_arg(self, param, args, kwargs):
|
||||||
|
"""Add the count argument to a function call.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
param: The count parameter.
|
||||||
|
args: The positional argument list. Gets modified directly.
|
||||||
|
kwargs: The keyword argument dict. Gets modified directly.
|
||||||
|
"""
|
||||||
|
if not self.has_count:
|
||||||
|
raise TypeError("{}: count argument given with a command which "
|
||||||
|
"does not support count!".format(self.name))
|
||||||
|
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||||
|
if self.count is not None:
|
||||||
|
args.append(self.count)
|
||||||
|
else:
|
||||||
|
args.append(param.default)
|
||||||
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
||||||
|
if self.count is not None:
|
||||||
|
kwargs['count'] = self.count
|
||||||
|
else:
|
||||||
|
raise TypeError("{}: invalid parameter type {} for argument "
|
||||||
|
"'count'!".format(self.name, param.kind))
|
||||||
|
|
||||||
|
def _get_param_name_and_value(self, param):
|
||||||
|
"""Get the converted name and value for an inspect.Parameter."""
|
||||||
|
name = self.name_conv.get(param.name, param.name)
|
||||||
|
value = getattr(self.namespace, name)
|
||||||
|
if param.name in self.type_conv:
|
||||||
|
# We convert enum types after getting the values from
|
||||||
|
# argparse, because argparse's choices argument is
|
||||||
|
# processed after type conversation, which is not what we
|
||||||
|
# want.
|
||||||
|
value = self.type_conv[param.name](value)
|
||||||
|
return name, value
|
||||||
|
|
||||||
|
def _get_call_args(self):
|
||||||
|
"""Get arguments for a function call.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
An (args, kwargs) tuple.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = []
|
||||||
|
kwargs = {}
|
||||||
|
signature = inspect.signature(self.handler)
|
||||||
|
|
||||||
|
for i, param in enumerate(signature.parameters.values()):
|
||||||
|
if i == 0 and self.instance is not None:
|
||||||
|
# Special case for 'self'.
|
||||||
|
self._get_self_arg(param, args)
|
||||||
|
continue
|
||||||
|
elif param.name == 'count':
|
||||||
|
# Special case for 'count'.
|
||||||
|
self._get_count_arg(param, args, kwargs)
|
||||||
|
continue
|
||||||
|
name, value = self._get_param_name_and_value(param)
|
||||||
|
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||||
|
args.append(value)
|
||||||
|
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||||
|
if value is not None:
|
||||||
|
args += value
|
||||||
|
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
|
||||||
|
kwargs[name] = value
|
||||||
|
else:
|
||||||
|
raise TypeError("{}: Invalid parameter type {} for argument "
|
||||||
|
"'{}'!".format(
|
||||||
|
self.name, param.kind, param.name))
|
||||||
|
return args, kwargs
|
||||||
|
|
||||||
def run(self, args=None, count=None):
|
def run(self, args=None, count=None):
|
||||||
"""Run the command.
|
"""Run the command.
|
||||||
@ -120,33 +388,22 @@ class Command:
|
|||||||
"""
|
"""
|
||||||
dbgout = ["command called:", self.name]
|
dbgout = ["command called:", self.name]
|
||||||
if args:
|
if args:
|
||||||
dbgout += args
|
dbgout.append(str(args))
|
||||||
if count is not None:
|
if count is not None:
|
||||||
dbgout.append("(count={})".format(count))
|
dbgout.append("(count={})".format(count))
|
||||||
log.commands.debug(' '.join(dbgout))
|
log.commands.debug(' '.join(dbgout))
|
||||||
|
try:
|
||||||
kwargs = {}
|
self.namespace = self.parser.parse_args(args)
|
||||||
app = QCoreApplication.instance()
|
except argparser.ArgumentParserError as e:
|
||||||
|
message.error('{}: {}'.format(self.name, e))
|
||||||
# Replace variables (currently only {url})
|
return
|
||||||
new_args = []
|
except argparser.ArgumentParserExit as e:
|
||||||
for arg in args:
|
log.commands.debug("argparser exited with status {}: {}".format(
|
||||||
if arg == '{url}':
|
e.status, e))
|
||||||
urlstr = app.mainwindow.tabs.current_url().toString(
|
return
|
||||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
self.count = count
|
||||||
new_args.append(urlstr)
|
posargs, kwargs = self._get_call_args()
|
||||||
else:
|
self._check_prerequisites()
|
||||||
new_args.append(arg)
|
log.commands.debug('Calling {}'.format(
|
||||||
|
debug.format_call(self.handler, posargs, kwargs)))
|
||||||
if self.instance is not None:
|
self.handler(*posargs, **kwargs)
|
||||||
# Add the 'self' parameter.
|
|
||||||
if self.instance == '':
|
|
||||||
obj = app
|
|
||||||
else:
|
|
||||||
obj = utils.dotted_getattr(app, self.instance)
|
|
||||||
new_args.insert(0, obj)
|
|
||||||
|
|
||||||
if count is not None and self.count:
|
|
||||||
kwargs = {'count': count}
|
|
||||||
|
|
||||||
self.handler(*new_args, **kwargs)
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""Module containing command managers (SearchRunner and CommandRunner)."""
|
"""Module containing command managers (SearchRunner and CommandRunner)."""
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QCoreApplication, QUrl
|
||||||
from PyQt5.QtWebKitWidgets import QWebPage
|
from PyQt5.QtWebKitWidgets import QWebPage
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
@ -27,6 +27,20 @@ from qutebrowser.commands import cmdexc, cmdutils
|
|||||||
from qutebrowser.utils import message, log, utils
|
from qutebrowser.utils import message, log, utils
|
||||||
|
|
||||||
|
|
||||||
|
def replace_variables(arglist):
|
||||||
|
"""Utility function to replace variables like {url} in a list of args."""
|
||||||
|
args = []
|
||||||
|
for arg in arglist:
|
||||||
|
if arg == '{url}':
|
||||||
|
app = QCoreApplication.instance()
|
||||||
|
url = app.mainwindow.tabs.current_url().toString(
|
||||||
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||||
|
args.append(url)
|
||||||
|
else:
|
||||||
|
args.append(arg)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
class SearchRunner(QObject):
|
class SearchRunner(QObject):
|
||||||
|
|
||||||
"""Run searches on webpages.
|
"""Run searches on webpages.
|
||||||
@ -198,14 +212,18 @@ class CommandRunner:
|
|||||||
parts = text.strip().split(maxsplit=1)
|
parts = text.strip().split(maxsplit=1)
|
||||||
if not parts:
|
if not parts:
|
||||||
raise cmdexc.NoSuchCommandError("No command given")
|
raise cmdexc.NoSuchCommandError("No command given")
|
||||||
cmdstr = parts[0]
|
elif len(parts) > 1:
|
||||||
|
cmdstr, argstr = parts
|
||||||
|
else:
|
||||||
|
cmdstr = parts[0]
|
||||||
|
argstr = None
|
||||||
if aliases:
|
if aliases:
|
||||||
new_cmd = self._get_alias(text, alias_no_args)
|
new_cmd = self._get_alias(text, alias_no_args)
|
||||||
if new_cmd is not None:
|
if new_cmd is not None:
|
||||||
log.commands.debug("Re-parsing with '{}'.".format(new_cmd))
|
log.commands.debug("Re-parsing with '{}'.".format(new_cmd))
|
||||||
return self.parse(new_cmd, aliases=False)
|
return self.parse(new_cmd, aliases=False)
|
||||||
try:
|
try:
|
||||||
cmd = cmdutils.cmd_dict[cmdstr]
|
self._cmd = cmdutils.cmd_dict[cmdstr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if fallback:
|
if fallback:
|
||||||
parts = text.split(' ')
|
parts = text.split(' ')
|
||||||
@ -215,36 +233,42 @@ class CommandRunner:
|
|||||||
else:
|
else:
|
||||||
raise cmdexc.NoSuchCommandError(
|
raise cmdexc.NoSuchCommandError(
|
||||||
'{}: no such command'.format(cmdstr))
|
'{}: no such command'.format(cmdstr))
|
||||||
if len(parts) == 1:
|
self._split_args(argstr)
|
||||||
args = []
|
retargs = self._args[:]
|
||||||
elif cmd.split:
|
|
||||||
args = utils.safe_shlex_split(parts[1])
|
|
||||||
else:
|
|
||||||
args = parts[1].split(maxsplit=cmd.nargs[0] - 1)
|
|
||||||
self._cmd = cmd
|
|
||||||
self._args = args
|
|
||||||
retargs = args[:]
|
|
||||||
if text.endswith(' '):
|
if text.endswith(' '):
|
||||||
retargs.append('')
|
retargs.append('')
|
||||||
return [cmdstr] + retargs
|
return [cmdstr] + retargs
|
||||||
|
|
||||||
def _check(self):
|
def _split_args(self, argstr):
|
||||||
"""Check if the argument count for the command is correct."""
|
"""Split the arguments from an arg string."""
|
||||||
self._cmd.check(self._args)
|
if argstr is None:
|
||||||
|
self._args = []
|
||||||
def _run(self, count=None):
|
elif self._cmd.split:
|
||||||
"""Run a command with an optional count.
|
self._args = utils.safe_shlex_split(argstr)
|
||||||
|
|
||||||
Args:
|
|
||||||
count: Count to pass to the command.
|
|
||||||
"""
|
|
||||||
if count is not None:
|
|
||||||
self._cmd.run(self._args, count=count)
|
|
||||||
else:
|
else:
|
||||||
self._cmd.run(self._args)
|
# If split=False, we still want to split the flags, but not
|
||||||
|
# everything after that.
|
||||||
|
# We first split the arg string and check the index of the first
|
||||||
|
# non-flag args, then we re-split again properly.
|
||||||
|
# example:
|
||||||
|
#
|
||||||
|
# input: "--foo -v bar baz"
|
||||||
|
# first split: ['--foo', '-v', 'bar', 'baz']
|
||||||
|
# 0 1 2 3
|
||||||
|
# second split: ['--foo', '-v', 'bar baz']
|
||||||
|
# (maxsplit=2)
|
||||||
|
split_args = argstr.split()
|
||||||
|
for i, arg in enumerate(split_args):
|
||||||
|
if not arg.startswith('-'):
|
||||||
|
self._args = argstr.split(maxsplit=i)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# If there are only flags, we got it right on the first try
|
||||||
|
# already.
|
||||||
|
self._args = split_args
|
||||||
|
|
||||||
def run(self, text, count=None):
|
def run(self, text, count=None):
|
||||||
"""Parse a command from a line of text.
|
"""Parse a command from a line of text and run it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text: The text to parse.
|
text: The text to parse.
|
||||||
@ -255,8 +279,11 @@ class CommandRunner:
|
|||||||
self.run(sub, count)
|
self.run(sub, count)
|
||||||
return
|
return
|
||||||
self.parse(text)
|
self.parse(text)
|
||||||
self._check()
|
args = replace_variables(self._args)
|
||||||
self._run(count=count)
|
if count is not None:
|
||||||
|
self._cmd.run(args, count=count)
|
||||||
|
else:
|
||||||
|
self._cmd.run(args)
|
||||||
|
|
||||||
@pyqtSlot(str, int)
|
@pyqtSlot(str, int)
|
||||||
def run_safely(self, text, count=None):
|
def run_safely(self, text, count=None):
|
||||||
|
@ -26,7 +26,6 @@ we borrow some methods and classes from there where it makes sense.
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import textwrap
|
|
||||||
import functools
|
import functools
|
||||||
import configparser
|
import configparser
|
||||||
import collections.abc
|
import collections.abc
|
||||||
@ -34,7 +33,7 @@ import collections.abc
|
|||||||
from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication
|
from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log
|
||||||
from qutebrowser.config import configdata, iniparsers, configtypes
|
from qutebrowser.config import configdata, iniparsers, configtypes, textwrapper
|
||||||
from qutebrowser.commands import cmdexc, cmdutils
|
from qutebrowser.commands import cmdexc, cmdutils
|
||||||
from qutebrowser.utils import message
|
from qutebrowser.utils import message
|
||||||
from qutebrowser.utils.usertypes import Completion
|
from qutebrowser.utils.usertypes import Completion
|
||||||
@ -76,6 +75,13 @@ class InterpolationSyntaxError(ValueError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownSectionError(Exception):
|
||||||
|
|
||||||
|
"""Exception raised when there was an unknwon section in the config."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager(QObject):
|
class ConfigManager(QObject):
|
||||||
|
|
||||||
"""Configuration manager for qutebrowser.
|
"""Configuration manager for qutebrowser.
|
||||||
@ -89,7 +95,6 @@ class ConfigManager(QObject):
|
|||||||
sections: The configuration data as an OrderedDict.
|
sections: The configuration data as an OrderedDict.
|
||||||
_fname: The filename to be opened.
|
_fname: The filename to be opened.
|
||||||
_configparser: A ReadConfigParser instance to load the config.
|
_configparser: A ReadConfigParser instance to load the config.
|
||||||
_wrapper_args: A dict with the default kwargs for the config wrappers.
|
|
||||||
_configdir: The dictionary to read the config from and save it in.
|
_configdir: The dictionary to read the config from and save it in.
|
||||||
_configfile: The config file path.
|
_configfile: The config file path.
|
||||||
_interpolation: An configparser.Interpolation object
|
_interpolation: An configparser.Interpolation object
|
||||||
@ -115,12 +120,6 @@ class ConfigManager(QObject):
|
|||||||
self.sections = configdata.DATA
|
self.sections = configdata.DATA
|
||||||
self._configparser = iniparsers.ReadConfigParser(configdir, fname)
|
self._configparser = iniparsers.ReadConfigParser(configdir, fname)
|
||||||
self._configfile = os.path.join(configdir, fname)
|
self._configfile = os.path.join(configdir, fname)
|
||||||
self._wrapper_args = {
|
|
||||||
'width': 72,
|
|
||||||
'replace_whitespace': False,
|
|
||||||
'break_long_words': False,
|
|
||||||
'break_on_hyphens': False,
|
|
||||||
}
|
|
||||||
self._configdir = configdir
|
self._configdir = configdir
|
||||||
self._fname = fname
|
self._fname = fname
|
||||||
self._interpolation = configparser.ExtendedInterpolation()
|
self._interpolation = configparser.ExtendedInterpolation()
|
||||||
@ -149,9 +148,7 @@ class ConfigManager(QObject):
|
|||||||
|
|
||||||
def _str_section_desc(self, sectname):
|
def _str_section_desc(self, sectname):
|
||||||
"""Get the section description string for sectname."""
|
"""Get the section description string for sectname."""
|
||||||
wrapper = textwrap.TextWrapper(initial_indent='# ',
|
wrapper = textwrapper.TextWrapper()
|
||||||
subsequent_indent='# ',
|
|
||||||
**self._wrapper_args)
|
|
||||||
lines = []
|
lines = []
|
||||||
seclines = configdata.SECTION_DESC[sectname].splitlines()
|
seclines = configdata.SECTION_DESC[sectname].splitlines()
|
||||||
for secline in seclines:
|
for secline in seclines:
|
||||||
@ -163,9 +160,8 @@ class ConfigManager(QObject):
|
|||||||
|
|
||||||
def _str_option_desc(self, sectname, sect):
|
def _str_option_desc(self, sectname, sect):
|
||||||
"""Get the option description strings for sect/sectname."""
|
"""Get the option description strings for sect/sectname."""
|
||||||
wrapper = textwrap.TextWrapper(initial_indent='#' + ' ' * 5,
|
wrapper = textwrapper.TextWrapper(initial_indent='#' + ' ' * 5,
|
||||||
subsequent_indent='#' + ' ' * 5,
|
subsequent_indent='#' + ' ' * 5)
|
||||||
**self._wrapper_args)
|
|
||||||
lines = []
|
lines = []
|
||||||
if not getattr(sect, 'descriptions', None):
|
if not getattr(sect, 'descriptions', None):
|
||||||
return lines
|
return lines
|
||||||
@ -217,7 +213,11 @@ class ConfigManager(QObject):
|
|||||||
Args:
|
Args:
|
||||||
cp: The configparser instance to read the values from.
|
cp: The configparser instance to read the values from.
|
||||||
"""
|
"""
|
||||||
for sectname in self.sections.keys():
|
for sectname in cp:
|
||||||
|
if sectname is not 'DEFAULT' and sectname not in self.sections:
|
||||||
|
raise UnknownSectionError("Unknown section '{}'!".format(
|
||||||
|
sectname))
|
||||||
|
for sectname in self.sections:
|
||||||
if sectname not in cp:
|
if sectname not in cp:
|
||||||
continue
|
continue
|
||||||
for k, v in cp[sectname].items():
|
for k, v in cp[sectname].items():
|
||||||
@ -307,28 +307,6 @@ class ConfigManager(QObject):
|
|||||||
self.get.cache_clear()
|
self.get.cache_clear()
|
||||||
return existed
|
return existed
|
||||||
|
|
||||||
@cmdutils.register(name='get', instance='config',
|
|
||||||
completion=[Completion.section, Completion.option])
|
|
||||||
def get_wrapper(self, sectname, optname):
|
|
||||||
"""Get the value from a section/option.
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
Wrapper for the get-command to output the value in the status bar.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sectname: The section where the option is in.
|
|
||||||
optname: The name of the option.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
val = self.get(sectname, optname, transformed=False)
|
|
||||||
except (NoOptionError, NoSectionError) as e:
|
|
||||||
raise cmdexc.CommandError("get: {} - {}".format(
|
|
||||||
e.__class__.__name__, e))
|
|
||||||
else:
|
|
||||||
message.info("{} {} = {}".format(sectname, optname, val),
|
|
||||||
immediately=True)
|
|
||||||
|
|
||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def get(self, sectname, optname, raw=False, transformed=True):
|
def get(self, sectname, optname, raw=False, transformed=True):
|
||||||
"""Get the value from a section/option.
|
"""Get the value from a section/option.
|
||||||
@ -365,9 +343,13 @@ class ConfigManager(QObject):
|
|||||||
@cmdutils.register(name='set', instance='config',
|
@cmdutils.register(name='set', instance='config',
|
||||||
completion=[Completion.section, Completion.option,
|
completion=[Completion.section, Completion.option,
|
||||||
Completion.value])
|
Completion.value])
|
||||||
def set_wrapper(self, sectname, optname, value):
|
def set_command(self, sectname: {'name': 'section'},
|
||||||
|
optname: {'name': 'option'}, value=None, temp=False):
|
||||||
"""Set an option.
|
"""Set an option.
|
||||||
|
|
||||||
|
If the option name ends with '?', the value of the option is shown
|
||||||
|
instead.
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
Wrapper for self.set() to output exceptions in the status bar.
|
Wrapper for self.set() to output exceptions in the status bar.
|
||||||
@ -376,36 +358,24 @@ class ConfigManager(QObject):
|
|||||||
sectname: The section where the option is in.
|
sectname: The section where the option is in.
|
||||||
optname: The name of the option.
|
optname: The name of the option.
|
||||||
value: The value to set.
|
value: The value to set.
|
||||||
|
temp: Set value temporarily.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.set('conf', sectname, optname, value)
|
if optname.endswith('?'):
|
||||||
|
val = self.get(sectname, optname[:-1], transformed=False)
|
||||||
|
message.info("{} {} = {}".format(sectname, optname[:-1], val),
|
||||||
|
immediately=True)
|
||||||
|
else:
|
||||||
|
if value is None:
|
||||||
|
raise cmdexc.CommandError("set: The following arguments "
|
||||||
|
"are required: value")
|
||||||
|
layer = 'temp' if temp else 'conf'
|
||||||
|
self.set(layer, sectname, optname, value)
|
||||||
except (NoOptionError, NoSectionError, configtypes.ValidationError,
|
except (NoOptionError, NoSectionError, configtypes.ValidationError,
|
||||||
ValueError) as e:
|
ValueError) as e:
|
||||||
raise cmdexc.CommandError("set: {} - {}".format(
|
raise cmdexc.CommandError("set: {} - {}".format(
|
||||||
e.__class__.__name__, e))
|
e.__class__.__name__, e))
|
||||||
|
|
||||||
@cmdutils.register(name='set-temp', instance='config',
|
|
||||||
completion=[Completion.section, Completion.option,
|
|
||||||
Completion.value])
|
|
||||||
def set_temp_wrapper(self, sectname, optname, value):
|
|
||||||
"""Set a temporary option.
|
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
Wrapper for self.set() to output exceptions in the status bar.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
sectname: The section where the option is in.
|
|
||||||
optname: The name of the option.
|
|
||||||
value: The value to set.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.set('temp', sectname, optname, value)
|
|
||||||
except (NoOptionError, NoSectionError,
|
|
||||||
configtypes.ValidationError) as e:
|
|
||||||
raise cmdexc.CommandError("set: {} - {}".format(
|
|
||||||
e.__class__.__name__, e))
|
|
||||||
|
|
||||||
def set(self, layer, sectname, optname, value):
|
def set(self, layer, sectname, optname, value):
|
||||||
"""Set an option.
|
"""Set an option.
|
||||||
|
|
||||||
|
@ -80,61 +80,6 @@ SECTION_DESC = {
|
|||||||
"bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will "
|
"bang-syntax, e.g. `:open qutebrowser !google`. The string `{}` will "
|
||||||
"be replaced by the search term, use `{{` and `}}` for literal "
|
"be replaced by the search term, use `{{` and `}}` for literal "
|
||||||
"`{`/`}` signs."),
|
"`{`/`}` signs."),
|
||||||
'keybind': (
|
|
||||||
"Bindings from a key(chain) to a command.\n"
|
|
||||||
"For special keys (can't be part of a keychain), enclose them in "
|
|
||||||
"`<`...`>`. For modifiers, you can use either `-` or `+` as "
|
|
||||||
"delimiters, and these names:\n\n"
|
|
||||||
" * Control: `Control`, `Ctrl`\n"
|
|
||||||
" * Meta: `Meta`, `Windows`, `Mod4`\n"
|
|
||||||
" * Alt: `Alt`, `Mod1`\n"
|
|
||||||
" * Shift: `Shift`\n\n"
|
|
||||||
"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 `Shift-` to match a key pressed with shift. "
|
|
||||||
"You can bind multiple commands by separating them with `;;`."),
|
|
||||||
'keybind.insert': (
|
|
||||||
"Keybindings for insert mode.\n"
|
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
|
||||||
"supported in this mode.\n"
|
|
||||||
"Useful hidden commands to map in this section:\n\n"
|
|
||||||
" * `open-editor`: Open a texteditor with the focused field.\n"
|
|
||||||
" * `leave-mode`: Leave the command mode."),
|
|
||||||
'keybind.hint': (
|
|
||||||
"Keybindings for hint mode.\n"
|
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
|
||||||
"supported in this mode.\n"
|
|
||||||
"Useful hidden commands to map in this section:\n\n"
|
|
||||||
" * `follow-hint`: Follow the currently selected hint.\n"
|
|
||||||
" * `leave-mode`: Leave the command mode."),
|
|
||||||
'keybind.passthrough': (
|
|
||||||
"Keybindings for passthrough mode.\n"
|
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
|
||||||
"supported in this mode.\n"
|
|
||||||
"Useful hidden commands to map in this section:\n\n"
|
|
||||||
" * `leave-mode`: Leave the passthrough mode."),
|
|
||||||
'keybind.command': (
|
|
||||||
"Keybindings for command mode.\n"
|
|
||||||
"Since normal keypresses are passed through, only special keys are "
|
|
||||||
"supported in this mode.\n"
|
|
||||||
"Useful hidden commands to map in this section:\n\n"
|
|
||||||
" * `command-history-prev`: Switch to previous command in history.\n"
|
|
||||||
" * `command-history-next`: Switch to next command in history.\n"
|
|
||||||
" * `completion-item-prev`: Select previous item in completion.\n"
|
|
||||||
" * `completion-item-next`: Select next item in completion.\n"
|
|
||||||
" * `command-accept`: Execute the command currently in the "
|
|
||||||
"commandline.\n"
|
|
||||||
" * `leave-mode`: Leave the command mode."),
|
|
||||||
'keybind.prompt': (
|
|
||||||
"Keybindings for prompts in the status line.\n"
|
|
||||||
"You can bind normal keys in this mode, but they will be only active "
|
|
||||||
"when a yes/no-prompt is asked. For other prompt modes, you can only "
|
|
||||||
"bind special keys.\n"
|
|
||||||
"Useful hidden commands to map in this section:\n\n"
|
|
||||||
" * `prompt-accept`: Confirm the entered value.\n"
|
|
||||||
" * `prompt-yes`: Answer yes to a yes/no question.\n"
|
|
||||||
" * `prompt-no`: Answer no to a yes/no question.\n"
|
|
||||||
" * `leave-mode`: Leave the prompt mode."),
|
|
||||||
'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 "
|
||||||
@ -586,177 +531,6 @@ DATA = collections.OrderedDict([
|
|||||||
('wiki', '${wikipedia}'),
|
('wiki', '${wikipedia}'),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
('keybind', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('o', 'set-cmd-text ":open "'),
|
|
||||||
('go', 'set-cmd-text :open {url}'),
|
|
||||||
('O', 'set-cmd-text ":open-tab "'),
|
|
||||||
('gO', 'set-cmd-text :open-tab {url}'),
|
|
||||||
('xo', 'set-cmd-text ":open-tab-bg "'),
|
|
||||||
('xO', 'set-cmd-text :open-tab-bg {url}'),
|
|
||||||
('ga', 'open-tab about:blank'),
|
|
||||||
('d', 'tab-close'),
|
|
||||||
('co', 'tab-only'),
|
|
||||||
('T', 'tab-focus'),
|
|
||||||
('gm', 'tab-move'),
|
|
||||||
('gl', 'tab-move -'),
|
|
||||||
('gr', 'tab-move +'),
|
|
||||||
('J', 'tab-next'),
|
|
||||||
('K', 'tab-prev'),
|
|
||||||
('r', 'reload'),
|
|
||||||
('H', 'back'),
|
|
||||||
('L', 'forward'),
|
|
||||||
('f', 'hint'),
|
|
||||||
('F', 'hint all tab'),
|
|
||||||
(';b', 'hint all tab-bg'),
|
|
||||||
(';i', 'hint images'),
|
|
||||||
(';I', 'hint images tab'),
|
|
||||||
('.i', 'hint images tab-bg'),
|
|
||||||
(';o', 'hint links fill :open {hint-url}'),
|
|
||||||
(';O', 'hint links fill :open-tab {hint-url}'),
|
|
||||||
('.o', 'hint links fill :open-tab-bg {hint-url}'),
|
|
||||||
(';y', 'hint links yank'),
|
|
||||||
(';Y', 'hint links yank-primary'),
|
|
||||||
(';r', 'hint links rapid'),
|
|
||||||
(';d', 'hint links download'),
|
|
||||||
('h', 'scroll -50 0'),
|
|
||||||
('j', 'scroll 0 50'),
|
|
||||||
('k', 'scroll 0 -50'),
|
|
||||||
('l', 'scroll 50 0'),
|
|
||||||
('u', 'undo'),
|
|
||||||
('gg', 'scroll-perc-y 0'),
|
|
||||||
('G', 'scroll-perc-y'),
|
|
||||||
('n', 'search-next'),
|
|
||||||
('N', 'search-prev'),
|
|
||||||
('i', 'enter-mode insert'),
|
|
||||||
('yy', 'yank'),
|
|
||||||
('yY', 'yank sel'),
|
|
||||||
('yt', 'yank-title'),
|
|
||||||
('yT', 'yank-title sel'),
|
|
||||||
('pp', 'paste'),
|
|
||||||
('pP', 'paste sel'),
|
|
||||||
('Pp', 'paste-tab'),
|
|
||||||
('PP', 'paste-tab sel'),
|
|
||||||
('m', 'quickmark-save'),
|
|
||||||
('b', 'set-cmd-text ":quickmark-load "'),
|
|
||||||
('B', 'set-cmd-text ":quickmark-load-tab "'),
|
|
||||||
('sf', 'save'),
|
|
||||||
('ss', 'set-cmd-text ":set "'),
|
|
||||||
('sl', 'set-cmd-text ":set-temp "'),
|
|
||||||
('sk', 'set-cmd-text ":set keybind "'),
|
|
||||||
('-', 'zoom-out'),
|
|
||||||
('+', 'zoom-in'),
|
|
||||||
('=', 'zoom'),
|
|
||||||
('[[', 'prev-page'),
|
|
||||||
(']]', 'next-page'),
|
|
||||||
('{{', 'prev-page-tab'),
|
|
||||||
('}}', 'next-page-tab'),
|
|
||||||
('wi', 'inspector'),
|
|
||||||
('gd', 'download-page'),
|
|
||||||
('ad', 'cancel-download'),
|
|
||||||
('gf', 'view-source'),
|
|
||||||
('<Ctrl-Tab>', 'tab-focus last'),
|
|
||||||
('<Ctrl-V>', 'enter-mode passthrough'),
|
|
||||||
('<Ctrl-Q>', 'quit'),
|
|
||||||
('<Ctrl-Shift-T>', 'undo'),
|
|
||||||
('<Ctrl-W>', 'tab-close'),
|
|
||||||
('<Ctrl-T>', 'open-tab about:blank'),
|
|
||||||
('<Ctrl-F>', 'scroll-page 0 1'),
|
|
||||||
('<Ctrl-B>', 'scroll-page 0 -1'),
|
|
||||||
('<Ctrl-D>', 'scroll-page 0 0.5'),
|
|
||||||
('<Ctrl-U>', 'scroll-page 0 -0.5'),
|
|
||||||
('<Alt-1>', 'tab-focus 1'),
|
|
||||||
('<Alt-2>', 'tab-focus 2'),
|
|
||||||
('<Alt-3>', 'tab-focus 3'),
|
|
||||||
('<Alt-4>', 'tab-focus 4'),
|
|
||||||
('<Alt-5>', 'tab-focus 5'),
|
|
||||||
('<Alt-6>', 'tab-focus 6'),
|
|
||||||
('<Alt-7>', 'tab-focus 7'),
|
|
||||||
('<Alt-8>', 'tab-focus 8'),
|
|
||||||
('<Alt-9>', 'tab-focus 9'),
|
|
||||||
('<Backspace>', 'back'),
|
|
||||||
('<Ctrl-h>', 'home'),
|
|
||||||
('<Ctrl-s>', 'stop'),
|
|
||||||
('<Ctrl-Alt-p>', 'print'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
('keybind.insert', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('<Escape>', 'leave-mode'),
|
|
||||||
('<Ctrl-N>', 'leave-mode'),
|
|
||||||
('<Ctrl-E>', 'open-editor'),
|
|
||||||
('<Ctrl-[>', '${<Escape>}'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
('keybind.hint', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('<Return>', 'follow-hint'),
|
|
||||||
('<Escape>', 'leave-mode'),
|
|
||||||
('<Ctrl-N>', 'leave-mode'),
|
|
||||||
('<Ctrl-[>', '${<Escape>}'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
('keybind.passthrough', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('<Escape>', 'leave-mode'),
|
|
||||||
('<Ctrl-[>', '${<Escape>}'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
# FIXME we should probably have a common section for input modes with a
|
|
||||||
# text field.
|
|
||||||
|
|
||||||
('keybind.command', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('<Escape>', 'leave-mode'),
|
|
||||||
('<Ctrl-P>', 'command-history-prev'),
|
|
||||||
('<Ctrl-N>', 'command-history-next'),
|
|
||||||
('<Shift-Tab>', 'completion-item-prev'),
|
|
||||||
('<Up>', 'completion-item-prev'),
|
|
||||||
('<Tab>', 'completion-item-next'),
|
|
||||||
('<Down>', 'completion-item-next'),
|
|
||||||
('<Return>', 'command-accept'),
|
|
||||||
('<Shift-Return>', 'command-accept'),
|
|
||||||
('<Ctrl-B>', 'rl-backward-char'),
|
|
||||||
('<Ctrl-F>', 'rl-forward-char'),
|
|
||||||
('<Alt-B>', 'rl-backward-word'),
|
|
||||||
('<Alt-F>', 'rl-forward-word'),
|
|
||||||
('<Ctrl-A>', 'rl-beginning-of-line'),
|
|
||||||
('<Ctrl-E>', 'rl-end-of-line'),
|
|
||||||
('<Ctrl-U>', 'rl-unix-line-discard'),
|
|
||||||
('<Ctrl-K>', 'rl-kill-line'),
|
|
||||||
('<Alt-D>', 'rl-kill-word'),
|
|
||||||
('<Ctrl-W>', 'rl-unix-word-rubout'),
|
|
||||||
('<Ctrl-Y>', 'rl-yank'),
|
|
||||||
('<Ctrl-?>', 'rl-delete-char'),
|
|
||||||
('<Ctrl-H>', 'rl-backward-delete-char'),
|
|
||||||
('<Ctrl-J>', '${<Return>}'),
|
|
||||||
('<Ctrl-[>', '${<Escape>}'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
('keybind.prompt', sect.ValueList(
|
|
||||||
typ.KeyBindingName(), typ.KeyBinding(),
|
|
||||||
('<Escape>', 'leave-mode'),
|
|
||||||
('<Return>', 'prompt-accept'),
|
|
||||||
('<Shift-Return>', 'prompt-accept'),
|
|
||||||
('y', 'prompt-yes'),
|
|
||||||
('n', 'prompt-no'),
|
|
||||||
('<Ctrl-B>', 'rl-backward-char'),
|
|
||||||
('<Ctrl-F>', 'rl-forward-char'),
|
|
||||||
('<Alt-B>', 'rl-backward-word'),
|
|
||||||
('<Alt-F>', 'rl-forward-word'),
|
|
||||||
('<Ctrl-A>', 'rl-beginning-of-line'),
|
|
||||||
('<Ctrl-E>', 'rl-end-of-line'),
|
|
||||||
('<Ctrl-U>', 'rl-unix-line-discard'),
|
|
||||||
('<Ctrl-K>', 'rl-kill-line'),
|
|
||||||
('<Alt-D>', 'rl-kill-word'),
|
|
||||||
('<Ctrl-W>', 'rl-unix-word-rubout'),
|
|
||||||
('<Ctrl-Y>', 'rl-yank'),
|
|
||||||
('<Ctrl-?>', 'rl-delete-char'),
|
|
||||||
('<Ctrl-H>', 'rl-backward-delete-char'),
|
|
||||||
('<Ctrl-J>', '${<Return>}'),
|
|
||||||
('<Ctrl-[>', '${<Escape>}'),
|
|
||||||
)),
|
|
||||||
|
|
||||||
('aliases', sect.ValueList(
|
('aliases', sect.ValueList(
|
||||||
typ.String(forbidden=' '), typ.Command(),
|
typ.String(forbidden=' '), typ.Command(),
|
||||||
)),
|
)),
|
||||||
@ -1010,3 +784,216 @@ DATA = collections.OrderedDict([
|
|||||||
"The default font size for fixed-pitch text."),
|
"The default font size for fixed-pitch text."),
|
||||||
)),
|
)),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
KEY_FIRST_COMMENT = """
|
||||||
|
# vim: ft=conf
|
||||||
|
#
|
||||||
|
# In this config file, qutebrowser's keybindings are configured.
|
||||||
|
# The format looks like this:
|
||||||
|
#
|
||||||
|
# [keymode]
|
||||||
|
#
|
||||||
|
# command
|
||||||
|
# keychain
|
||||||
|
# keychain2
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# All blank lines and lines starting with '#' are ignored.
|
||||||
|
# Inline-comments are not permitted.
|
||||||
|
#
|
||||||
|
# keymode is a comma separated list of modes in which the keybinding should be
|
||||||
|
# active. If keymode starts with !, the keybinding is active in all modes
|
||||||
|
# except the listed modes.
|
||||||
|
#
|
||||||
|
# For special keys (can't be part of a keychain), enclose them in `<`...`>`.
|
||||||
|
# For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
||||||
|
#
|
||||||
|
# * Control: `Control`, `Ctrl`
|
||||||
|
# * Meta: `Meta`, `Windows`, `Mod4`
|
||||||
|
# * Alt: `Alt`, `Mod1`
|
||||||
|
# * Shift: `Shift`
|
||||||
|
#
|
||||||
|
# For simple keys (no `<>`-signs), a capital letter means the key is pressed
|
||||||
|
# with Shift. For special keys (with `<>`-signs), you need to explicitely add
|
||||||
|
# `Shift-` to match a key pressed with shift. You can bind multiple commands
|
||||||
|
# by separating them with `;;`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
KEY_SECTION_DESC = {
|
||||||
|
'all': "Keybindings active in all modes.",
|
||||||
|
'normal': "Keybindings for normal mode.",
|
||||||
|
'insert': (
|
||||||
|
"Keybindings for insert mode.\n"
|
||||||
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
|
"supported in this mode.\n"
|
||||||
|
"Useful hidden commands to map in this section:\n\n"
|
||||||
|
" * `open-editor`: Open a texteditor with the focused field."),
|
||||||
|
'hint': (
|
||||||
|
"Keybindings for hint mode.\n"
|
||||||
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
|
"supported in this mode.\n"
|
||||||
|
"Useful hidden commands to map in this section:\n\n"
|
||||||
|
" * `follow-hint`: Follow the currently selected hint."),
|
||||||
|
'passthrough': (
|
||||||
|
"Keybindings for passthrough mode.\n"
|
||||||
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
|
"supported in this mode."),
|
||||||
|
'command': (
|
||||||
|
"Keybindings for command mode.\n"
|
||||||
|
"Since normal keypresses are passed through, only special keys are "
|
||||||
|
"supported in this mode.\n"
|
||||||
|
"Useful hidden commands to map in this section:\n\n"
|
||||||
|
" * `command-history-prev`: Switch to previous command in history.\n"
|
||||||
|
" * `command-history-next`: Switch to next command in history.\n"
|
||||||
|
" * `completion-item-prev`: Select previous item in completion.\n"
|
||||||
|
" * `completion-item-next`: Select next item in completion.\n"
|
||||||
|
" * `command-accept`: Execute the command currently in the "
|
||||||
|
"commandline."),
|
||||||
|
'prompt': (
|
||||||
|
"Keybindings for prompts in the status line.\n"
|
||||||
|
"You can bind normal keys in this mode, but they will be only active "
|
||||||
|
"when a yes/no-prompt is asked. For other prompt modes, you can only "
|
||||||
|
"bind special keys.\n"
|
||||||
|
"Useful hidden commands to map in this section:\n\n"
|
||||||
|
" * `prompt-accept`: Confirm the entered value.\n"
|
||||||
|
" * `prompt-yes`: Answer yes to a yes/no question.\n"
|
||||||
|
" * `prompt-no`: Answer no to a yes/no question."),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
KEY_DATA = collections.OrderedDict([
|
||||||
|
('!normal', collections.OrderedDict([
|
||||||
|
('leave-mode', ['<Escape>', '<Ctrl-[>']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('normal', collections.OrderedDict([
|
||||||
|
('set-cmd-text ":open "', ['o']),
|
||||||
|
('set-cmd-text ":open {url}"', ['go']),
|
||||||
|
('set-cmd-text ":open -t "', ['O']),
|
||||||
|
('set-cmd-text ":open -t {url}"', ['gO']),
|
||||||
|
('set-cmd-text ":open -b "', ['xo']),
|
||||||
|
('set-cmd-text ":open -b {url}"', ['xO']),
|
||||||
|
('open -t about:blank', ['ga']),
|
||||||
|
('tab-close', ['d', '<Ctrl-W>']),
|
||||||
|
('tab-only', ['co']),
|
||||||
|
('tab-focus', ['T']),
|
||||||
|
('tab-move', ['gm']),
|
||||||
|
('tab-move -', ['gl']),
|
||||||
|
('tab-move +', ['gr']),
|
||||||
|
('tab-next', ['J']),
|
||||||
|
('tab-prev', ['K']),
|
||||||
|
('reload', ['r']),
|
||||||
|
('back', ['H', '<Backspace>']),
|
||||||
|
('forward', ['L']),
|
||||||
|
('hint', ['f']),
|
||||||
|
('hint all tab', ['F']),
|
||||||
|
('hint all tab-bg', [';b']),
|
||||||
|
('hint images', [';i']),
|
||||||
|
('hint images tab', [';I']),
|
||||||
|
('hint images tab-bg', ['.i']),
|
||||||
|
('hint links fill ":open {hint-url}"', [';o']),
|
||||||
|
('hint links fill ":open -t {hint-url}"', [';O']),
|
||||||
|
('hint links fill ":open -b {hint-url}"', ['.o']),
|
||||||
|
('hint links yank', [';y']),
|
||||||
|
('hint links yank-primary', [';Y']),
|
||||||
|
('hint links rapid', [';r']),
|
||||||
|
('hint links download', [';d']),
|
||||||
|
('scroll -50 0', ['h']),
|
||||||
|
('scroll 0 50', ['j']),
|
||||||
|
('scroll 0 -50', ['k']),
|
||||||
|
('scroll 50 0', ['l']),
|
||||||
|
('undo', ['u', '<Ctrl-Shift-T>']),
|
||||||
|
('scroll-perc 0', ['gg']),
|
||||||
|
('scroll-perc', ['G']),
|
||||||
|
('search-next', ['n']),
|
||||||
|
('search-prev', ['N']),
|
||||||
|
('enter-mode insert', ['i']),
|
||||||
|
('yank', ['yy']),
|
||||||
|
('yank -s', ['yY']),
|
||||||
|
('yank -t', ['yt']),
|
||||||
|
('yank -ts', ['yT']),
|
||||||
|
('paste', ['pp']),
|
||||||
|
('paste -s', ['pP']),
|
||||||
|
('paste -t', ['Pp']),
|
||||||
|
('paste -ts', ['PP']),
|
||||||
|
('quickmark-save', ['m']),
|
||||||
|
('set-cmd-text ":quickmark-load "', ['b']),
|
||||||
|
('set-cmd-text ":quickmark-load -t "', ['B']),
|
||||||
|
('save', ['sf']),
|
||||||
|
('set-cmd-text ":set "', ['ss']),
|
||||||
|
('set-cmd-text ":set -t "', ['sl']),
|
||||||
|
('set-cmd-text ":set keybind "', ['sk']),
|
||||||
|
('zoom-out', ['-']),
|
||||||
|
('zoom-in', ['+']),
|
||||||
|
('zoom', ['=']),
|
||||||
|
('prev-page', ['[[']),
|
||||||
|
('next-page', [']]']),
|
||||||
|
('prev-page -t', ['{{']),
|
||||||
|
('next-page -t', ['}}']),
|
||||||
|
('inspector', ['wi']),
|
||||||
|
('download-page', ['gd']),
|
||||||
|
('cancel-download', ['ad']),
|
||||||
|
('view-source', ['gf']),
|
||||||
|
('tab-focus last', ['<Ctrl-Tab>']),
|
||||||
|
('enter-mode passthrough', ['<Ctrl-V>']),
|
||||||
|
('quit', ['<Ctrl-Q>']),
|
||||||
|
('open -t about:blank', ['<Ctrl-T>']),
|
||||||
|
('scroll-page 0 1', ['<Ctrl-F>']),
|
||||||
|
('scroll-page 0 -1', ['<Ctrl-B>']),
|
||||||
|
('scroll-page 0 0.5', ['<Ctrl-D>']),
|
||||||
|
('scroll-page 0 -0.5', ['<Ctrl-U>']),
|
||||||
|
('tab-focus 1', ['<Alt-1>']),
|
||||||
|
('tab-focus 2', ['<Alt-2>']),
|
||||||
|
('tab-focus 3', ['<Alt-3>']),
|
||||||
|
('tab-focus 4', ['<Alt-4>']),
|
||||||
|
('tab-focus 5', ['<Alt-5>']),
|
||||||
|
('tab-focus 6', ['<Alt-6>']),
|
||||||
|
('tab-focus 7', ['<Alt-7>']),
|
||||||
|
('tab-focus 8', ['<Alt-8>']),
|
||||||
|
('tab-focus 9', ['<Alt-9>']),
|
||||||
|
('home', ['<Ctrl-h>']),
|
||||||
|
('stop', ['<Ctrl-s>']),
|
||||||
|
('print', ['<Ctrl-Alt-p>']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('insert', collections.OrderedDict([
|
||||||
|
('open-editor', ['<Ctrl-E>']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('hint', collections.OrderedDict([
|
||||||
|
('follow-hint', ['<Return>']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('passthrough', {}),
|
||||||
|
|
||||||
|
('command', collections.OrderedDict([
|
||||||
|
('command-history-prev', ['<Ctrl-P>']),
|
||||||
|
('command-history-next', ['<Ctrl-N>']),
|
||||||
|
('completion-item-prev', ['<Shift-Tab>', '<Up>']),
|
||||||
|
('completion-item-next', ['<Tab>', '<Down>']),
|
||||||
|
('command-accept', ['<Return>', '<Ctrl-J>', '<Shift-Return>']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('prompt', collections.OrderedDict([
|
||||||
|
('prompt-accept', ['<Return>', '<Ctrl-J>', '<Shift-Return>']),
|
||||||
|
('prompt-yes', ['y']),
|
||||||
|
('prompt-no', ['n']),
|
||||||
|
])),
|
||||||
|
|
||||||
|
('command,prompt', collections.OrderedDict([
|
||||||
|
('rl-backward-char', ['<Ctrl-B>']),
|
||||||
|
('rl-forward-char', ['<Ctrl-F>']),
|
||||||
|
('rl-backward-word', ['<Alt-B>']),
|
||||||
|
('rl-forward-word', ['<Alt-F>']),
|
||||||
|
('rl-beginning-of-line', ['<Ctrl-A>']),
|
||||||
|
('rl-end-of-line', ['<Ctrl-E>']),
|
||||||
|
('rl-unix-line-discard', ['<Ctrl-U>']),
|
||||||
|
('rl-kill-line', ['<Ctrl-K>']),
|
||||||
|
('rl-kill-word', ['<Alt-D>']),
|
||||||
|
('rl-unix-word-rubout', ['<Ctrl-W>']),
|
||||||
|
('rl-yank', ['<Ctrl-Y>']),
|
||||||
|
('rl-delete-char', ['<Ctrl-?>']),
|
||||||
|
('rl-backward-delete-char', ['<Ctrl-H>']),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
@ -1046,25 +1046,6 @@ class SearchEngineUrl(BaseType):
|
|||||||
url.errorString()))
|
url.errorString()))
|
||||||
|
|
||||||
|
|
||||||
class KeyBindingName(BaseType):
|
|
||||||
|
|
||||||
"""The name (keys) of a keybinding."""
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
if not value:
|
|
||||||
if self.none_ok:
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
raise ValidationError(value, "may not be empty!")
|
|
||||||
|
|
||||||
|
|
||||||
class KeyBinding(Command):
|
|
||||||
|
|
||||||
"""The command of a keybinding."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Encoding(BaseType):
|
class Encoding(BaseType):
|
||||||
|
|
||||||
"""Setting for a python encoding."""
|
"""Setting for a python encoding."""
|
||||||
|
270
qutebrowser/config/keyconfparser.py
Normal file
270
qutebrowser/config/keyconfparser.py
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""Parser for the key configuration."""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, QObject
|
||||||
|
|
||||||
|
from qutebrowser.config import configdata, textwrapper
|
||||||
|
from qutebrowser.commands import cmdutils, cmdexc
|
||||||
|
from qutebrowser.utils import log
|
||||||
|
|
||||||
|
|
||||||
|
class KeyConfigError(Exception):
|
||||||
|
|
||||||
|
"""Raised on errors with the key config.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
lineno: The config line in which the exception occured.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg=None):
|
||||||
|
super().__init__(msg)
|
||||||
|
self.lineno = None
|
||||||
|
|
||||||
|
|
||||||
|
class KeyConfigParser(QObject):
|
||||||
|
|
||||||
|
"""Parser for the keybind config.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
FIXME
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
changed: Emitted when the config has changed.
|
||||||
|
arg: Name of the mode which was changed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
changed = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, configdir, fname, parent=None):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configdir: The directory to save the configs in.
|
||||||
|
fname: The filename of the config.
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self._configdir = configdir
|
||||||
|
self._configfile = os.path.join(self._configdir, fname)
|
||||||
|
self._cur_section = None
|
||||||
|
self._cur_command = None
|
||||||
|
# Mapping of section name(s) to keybinding -> command dicts.
|
||||||
|
self.keybindings = collections.OrderedDict()
|
||||||
|
if not os.path.exists(self._configfile):
|
||||||
|
self._load_default()
|
||||||
|
else:
|
||||||
|
self._read()
|
||||||
|
log.init.debug("Loaded bindings: {}".format(self.keybindings))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Get the config as string."""
|
||||||
|
lines = configdata.KEY_FIRST_COMMENT.strip('\n').splitlines()
|
||||||
|
lines.append('')
|
||||||
|
for sectname, sect in self.keybindings.items():
|
||||||
|
lines.append('[{}]'.format(sectname))
|
||||||
|
lines += self._str_section_desc(sectname)
|
||||||
|
lines.append('')
|
||||||
|
data = collections.OrderedDict()
|
||||||
|
for key, cmd in sect.items():
|
||||||
|
if cmd in data:
|
||||||
|
data[cmd].append(key)
|
||||||
|
else:
|
||||||
|
data[cmd] = [key]
|
||||||
|
for cmd, keys in data.items():
|
||||||
|
lines.append(cmd)
|
||||||
|
for k in keys:
|
||||||
|
lines.append(' ' * 4 + k)
|
||||||
|
lines.append('')
|
||||||
|
return '\n'.join(lines) + '\n'
|
||||||
|
|
||||||
|
def _str_section_desc(self, sectname):
|
||||||
|
"""Get the section description string for sectname."""
|
||||||
|
wrapper = textwrapper.TextWrapper()
|
||||||
|
lines = []
|
||||||
|
try:
|
||||||
|
seclines = configdata.KEY_SECTION_DESC[sectname].splitlines()
|
||||||
|
except KeyError:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
for secline in seclines:
|
||||||
|
if 'http://' in secline or 'https://' in secline:
|
||||||
|
lines.append('# ' + secline)
|
||||||
|
else:
|
||||||
|
lines += wrapper.wrap(secline)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Save the key config file."""
|
||||||
|
log.destroy.debug("Saving key config to {}".format(self._configfile))
|
||||||
|
with open(self._configfile, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(str(self))
|
||||||
|
|
||||||
|
@cmdutils.register(instance='keyconfig')
|
||||||
|
def bind(self, key, *command, mode=None):
|
||||||
|
"""Bind a key to a command.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The keychain or special key (inside `<...>`) to bind.
|
||||||
|
*command: The command to execute, with optional args.
|
||||||
|
mode: A comma-separated list of modes to bind the key in
|
||||||
|
(default: `normal`).
|
||||||
|
"""
|
||||||
|
if mode is None:
|
||||||
|
mode = 'normal'
|
||||||
|
mode = self._normalize_sectname(mode)
|
||||||
|
for m in mode.split(','):
|
||||||
|
if m not in configdata.KEY_DATA:
|
||||||
|
raise cmdexc.CommandError("Invalid mode {}!".format(m))
|
||||||
|
if command[0] not in cmdutils.cmd_dict:
|
||||||
|
raise cmdexc.CommandError("Invalid command {}!".format(command[0]))
|
||||||
|
try:
|
||||||
|
self._add_binding(mode, key, *command)
|
||||||
|
except KeyConfigError as e:
|
||||||
|
raise cmdexc.CommandError(e)
|
||||||
|
for m in mode.split(','):
|
||||||
|
self.changed.emit(m)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='keyconfig')
|
||||||
|
def unbind(self, key, mode=None):
|
||||||
|
"""Unbind a keychain.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: The keychain or special key (inside <...>) to unbind.
|
||||||
|
mode: A comma-separated list of modes to unbind the key in
|
||||||
|
(default: `normal`).
|
||||||
|
"""
|
||||||
|
if mode is None:
|
||||||
|
mode = 'normal'
|
||||||
|
mode = self._normalize_sectname(mode)
|
||||||
|
for m in mode.split(','):
|
||||||
|
if m not in configdata.KEY_DATA:
|
||||||
|
raise cmdexc.CommandError("Invalid mode {}!".format(m))
|
||||||
|
try:
|
||||||
|
sect = self.keybindings[mode]
|
||||||
|
except KeyError:
|
||||||
|
raise cmdexc.CommandError("Can't find mode section '{}'!".format(
|
||||||
|
sect))
|
||||||
|
try:
|
||||||
|
del sect[key]
|
||||||
|
except KeyError:
|
||||||
|
raise cmdexc.CommandError("Can't find binding '{}' in section "
|
||||||
|
"'{}'!".format(key, mode))
|
||||||
|
else:
|
||||||
|
for m in mode.split(','):
|
||||||
|
self.changed.emit(m)
|
||||||
|
|
||||||
|
def _normalize_sectname(self, s):
|
||||||
|
"""Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'."""
|
||||||
|
if s.startswith('!'):
|
||||||
|
inverted = True
|
||||||
|
s = s[1:]
|
||||||
|
else:
|
||||||
|
inverted = False
|
||||||
|
sections = ','.join(sorted(s.split(',')))
|
||||||
|
if inverted:
|
||||||
|
sections = '!' + sections
|
||||||
|
return sections
|
||||||
|
|
||||||
|
def _load_default(self):
|
||||||
|
"""Load the built-in default keybindings."""
|
||||||
|
for sectname, sect in configdata.KEY_DATA.items():
|
||||||
|
sectname = self._normalize_sectname(sectname)
|
||||||
|
if not sect:
|
||||||
|
self.keybindings[sectname] = collections.OrderedDict()
|
||||||
|
else:
|
||||||
|
for command, keychains in sect.items():
|
||||||
|
for e in keychains:
|
||||||
|
self._add_binding(sectname, e, command)
|
||||||
|
self.changed.emit(sectname)
|
||||||
|
|
||||||
|
def _read(self):
|
||||||
|
"""Read the config file from disk and parse it."""
|
||||||
|
with open(self._configfile, 'r', encoding='utf-8') as f:
|
||||||
|
for i, line in enumerate(f):
|
||||||
|
line = line.rstrip()
|
||||||
|
try:
|
||||||
|
if not line.strip() or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
elif line.startswith('[') and line.endswith(']'):
|
||||||
|
sectname = line[1:-1]
|
||||||
|
self._cur_section = self._normalize_sectname(sectname)
|
||||||
|
elif line.startswith((' ', '\t')):
|
||||||
|
line = line.strip()
|
||||||
|
self._read_keybinding(line)
|
||||||
|
else:
|
||||||
|
line = line.strip()
|
||||||
|
self._read_command(line)
|
||||||
|
except KeyConfigError as e:
|
||||||
|
e.lineno = i
|
||||||
|
raise
|
||||||
|
for sectname in self.keybindings:
|
||||||
|
self.changed.emit(sectname)
|
||||||
|
|
||||||
|
def _read_command(self, line):
|
||||||
|
"""Read a command from a line."""
|
||||||
|
if self._cur_section is None:
|
||||||
|
raise KeyConfigError("Got command '{}' without getting a "
|
||||||
|
"section!".format(line))
|
||||||
|
else:
|
||||||
|
command = line.split(maxsplit=1)[0]
|
||||||
|
if command not in cmdutils.cmd_dict:
|
||||||
|
raise KeyConfigError("Invalid command '{}'!".format(command))
|
||||||
|
self._cur_command = line
|
||||||
|
|
||||||
|
def _read_keybinding(self, line):
|
||||||
|
"""Read a keybinding from a line."""
|
||||||
|
if self._cur_command is None:
|
||||||
|
raise KeyConfigError("Got keybinding '{}' without getting a "
|
||||||
|
"command!".format(line))
|
||||||
|
else:
|
||||||
|
assert self._cur_section is not None
|
||||||
|
self._add_binding(self._cur_section, line, self._cur_command)
|
||||||
|
|
||||||
|
def _add_binding(self, sectname, keychain, command):
|
||||||
|
"""Add a new binding from keychain to command in section sectname."""
|
||||||
|
log.keyboard.debug("Adding binding {} -> {} in mode {}.".format(
|
||||||
|
keychain, command, sectname))
|
||||||
|
if sectname not in self.keybindings:
|
||||||
|
self.keybindings[sectname] = collections.OrderedDict()
|
||||||
|
if keychain in self.get_bindings_for(sectname):
|
||||||
|
raise KeyConfigError("Duplicate keychain '{}'!".format(keychain))
|
||||||
|
self.keybindings[sectname][keychain] = command
|
||||||
|
|
||||||
|
def get_bindings_for(self, section):
|
||||||
|
"""Get a dict with all merged keybindings for a section."""
|
||||||
|
bindings = {}
|
||||||
|
for sectstring, d in self.keybindings.items():
|
||||||
|
if sectstring.startswith('!'):
|
||||||
|
inverted = True
|
||||||
|
sectstring = sectstring[1:]
|
||||||
|
else:
|
||||||
|
inverted = False
|
||||||
|
sects = [s.strip() for s in sectstring.split(',')]
|
||||||
|
matches = any(s == section for s in sects)
|
||||||
|
if (not inverted and matches) or (inverted and not matches):
|
||||||
|
bindings.update(d)
|
||||||
|
try:
|
||||||
|
bindings.update(self.keybindings['all'])
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return bindings
|
39
qutebrowser/config/textwrapper.py
Normal file
39
qutebrowser/config/textwrapper.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""Textwrapper used for config files."""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
|
||||||
|
class TextWrapper(textwrap.TextWrapper):
|
||||||
|
|
||||||
|
"""Text wrapper customized to be used in configs."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kw = {
|
||||||
|
'width': 72,
|
||||||
|
'replace_whitespace': False,
|
||||||
|
'break_long_words': False,
|
||||||
|
'break_on_hyphens': False,
|
||||||
|
'initial_indent': '# ',
|
||||||
|
'subsequent_indent': '# ',
|
||||||
|
}
|
||||||
|
kw.update(kwargs)
|
||||||
|
super().__init__(*args, **kw)
|
@ -23,7 +23,7 @@ import re
|
|||||||
import string
|
import string
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, QCoreApplication
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import usertypes, log, utils
|
from qutebrowser.utils import usertypes, log, utils
|
||||||
@ -57,7 +57,7 @@ class BaseKeyParser(QObject):
|
|||||||
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
|
||||||
_timer: Timer for delayed execution.
|
_timer: Timer for delayed execution.
|
||||||
_confsectname: The name of the configsection.
|
_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
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ class BaseKeyParser(QObject):
|
|||||||
supports_chains=False):
|
supports_chains=False):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._timer = None
|
self._timer = None
|
||||||
self._confsectname = None
|
self._modename = None
|
||||||
self._keystring = ''
|
self._keystring = ''
|
||||||
if supports_count is None:
|
if supports_count is None:
|
||||||
supports_count = supports_chains
|
supports_count = supports_chains
|
||||||
@ -303,28 +303,26 @@ class BaseKeyParser(QObject):
|
|||||||
self.keystring_updated.emit(self._keystring)
|
self.keystring_updated.emit(self._keystring)
|
||||||
return handled
|
return handled
|
||||||
|
|
||||||
def read_config(self, sectname=None):
|
def read_config(self, modename=None):
|
||||||
"""Read the configuration.
|
"""Read the configuration.
|
||||||
|
|
||||||
Config format: key = command, e.g.:
|
Config format: key = command, e.g.:
|
||||||
<Ctrl+Q> = quit
|
<Ctrl+Q> = quit
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sectname: Name of the section to read.
|
modename: Name of the mode to use.
|
||||||
"""
|
"""
|
||||||
if sectname is None:
|
if modename is None:
|
||||||
if self._confsectname is None:
|
if self._modename is None:
|
||||||
raise ValueError("read_config called with no section, but "
|
raise ValueError("read_config called with no mode given, but "
|
||||||
"None defined so far!")
|
"None defined so far!")
|
||||||
sectname = self._confsectname
|
modename = self._modename
|
||||||
else:
|
else:
|
||||||
self._confsectname = sectname
|
self._modename = modename
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
self.special_bindings = {}
|
self.special_bindings = {}
|
||||||
sect = config.section(sectname)
|
keyconfparser = QCoreApplication.instance().keyconfig
|
||||||
if not sect.items():
|
for (key, cmd) in keyconfparser.get_bindings_for(modename).items():
|
||||||
log.keyboard.warning("No keybindings defined!")
|
|
||||||
for (key, cmd) in sect.items():
|
|
||||||
if not cmd:
|
if not cmd:
|
||||||
continue
|
continue
|
||||||
elif key.startswith('<') and key.endswith('>'):
|
elif key.startswith('<') and key.endswith('>'):
|
||||||
@ -334,8 +332,8 @@ class BaseKeyParser(QObject):
|
|||||||
self.bindings[key] = cmd
|
self.bindings[key] = cmd
|
||||||
elif self.warn_on_keychains:
|
elif self.warn_on_keychains:
|
||||||
log.keyboard.warning(
|
log.keyboard.warning(
|
||||||
"Ignoring keychain '{}' in section '{}' because "
|
"Ignoring keychain '{}' in mode '{}' because "
|
||||||
"keychains are not supported there.".format(key, sectname))
|
"keychains are not supported there.".format(key, modename))
|
||||||
|
|
||||||
def execute(self, cmdstr, keytype, count=None):
|
def execute(self, cmdstr, keytype, count=None):
|
||||||
"""Handle a completed keychain.
|
"""Handle a completed keychain.
|
||||||
@ -347,11 +345,11 @@ class BaseKeyParser(QObject):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str)
|
||||||
def on_config_changed(self, section, _option):
|
def on_keyconfig_changed(self, mode):
|
||||||
"""Re-read the config if a keybinding was changed."""
|
"""Re-read the config if a keybinding was changed."""
|
||||||
if self._confsectname is None:
|
if self._modename is None:
|
||||||
raise AttributeError("on_config_changed called but no section "
|
raise AttributeError("on_config_changed called but no section "
|
||||||
"defined!")
|
"defined!")
|
||||||
if section == self._confsectname:
|
if mode == self._modename:
|
||||||
self.read_config()
|
self.read_config()
|
||||||
|
@ -51,25 +51,25 @@ class PassthroughKeyParser(CommandKeyParser):
|
|||||||
Used for insert/passthrough modes.
|
Used for insert/passthrough modes.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
_confsect: The config section to use.
|
_mode: The mode this keyparser is for.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
do_log = False
|
do_log = False
|
||||||
|
|
||||||
def __init__(self, confsect, parent=None, warn=True):
|
def __init__(self, mode, parent=None, warn=True):
|
||||||
"""Constructor.
|
"""Constructor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
confsect: The config section to use.
|
mode: The mode this keyparser is for.
|
||||||
parent: Qt parent.
|
parent: Qt parent.
|
||||||
warn: Whether to warn if an ignored key was bound.
|
warn: Whether to warn if an ignored key was bound.
|
||||||
"""
|
"""
|
||||||
super().__init__(parent, supports_chains=False)
|
super().__init__(parent, supports_chains=False)
|
||||||
self.log = False
|
self.log = False
|
||||||
self.warn_on_keychains = warn
|
self.warn_on_keychains = warn
|
||||||
self.read_config(confsect)
|
self.read_config(mode)
|
||||||
self._confsect = confsect
|
self._mode = mode
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{} confsect={}, warn={})'.format(
|
return '<{} mode={}, warn={})'.format(
|
||||||
self.__class__.__name__, self._confsect, self.warn_on_keychains)
|
self.__class__.__name__, self._mode, self.warn_on_keychains)
|
||||||
|
@ -41,7 +41,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent, supports_count=True, supports_chains=True)
|
super().__init__(parent, supports_count=True, supports_chains=True)
|
||||||
self.read_config('keybind')
|
self.read_config('normal')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{}>'.format(self.__class__.__name__)
|
return '<{}>'.format(self.__class__.__name__)
|
||||||
@ -69,8 +69,8 @@ class PromptKeyParser(keyparser.CommandKeyParser):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent, supports_count=False, supports_chains=True)
|
super().__init__(parent, supports_count=False, supports_chains=True)
|
||||||
# We don't want an extra section for this in the config, so we just
|
# We don't want an extra section for this in the config, so we just
|
||||||
# abuse the keybind.prompt section.
|
# abuse the prompt section.
|
||||||
self.read_config('keybind.prompt')
|
self.read_config('prompt')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<{}>'.format(self.__class__.__name__)
|
return '<{}>'.format(self.__class__.__name__)
|
||||||
@ -98,7 +98,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
|||||||
super().__init__(parent, supports_count=False, supports_chains=True)
|
super().__init__(parent, supports_count=False, supports_chains=True)
|
||||||
self._filtertext = ''
|
self._filtertext = ''
|
||||||
self._last_press = LastPress.none
|
self._last_press = LastPress.none
|
||||||
self.read_config('keybind.hint')
|
self.read_config('hint')
|
||||||
|
|
||||||
def _handle_special_key(self, e):
|
def _handle_special_key(self, e):
|
||||||
"""Override _handle_special_key to handle string filtering.
|
"""Override _handle_special_key to handle string filtering.
|
||||||
|
@ -58,7 +58,7 @@ class SettingOptionCompletionModel(basecompletion.BaseCompletionModel):
|
|||||||
sectdata = configdata.DATA[section]
|
sectdata = configdata.DATA[section]
|
||||||
self._misc_items = {}
|
self._misc_items = {}
|
||||||
self._section = section
|
self._section = section
|
||||||
for name, _ in sectdata.items():
|
for name in sectdata.keys():
|
||||||
try:
|
try:
|
||||||
desc = sectdata.descriptions[name]
|
desc = sectdata.descriptions[name]
|
||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
@ -165,3 +165,45 @@ class CommandCompletionModel(basecompletion.BaseCompletionModel):
|
|||||||
cat = self.new_category("Commands")
|
cat = self.new_category("Commands")
|
||||||
for (name, desc) in sorted(cmdlist):
|
for (name, desc) in sorted(cmdlist):
|
||||||
self.new_item(cat, name, desc)
|
self.new_item(cat, name, desc)
|
||||||
|
|
||||||
|
|
||||||
|
class HelpCompletionModel(basecompletion.BaseCompletionModel):
|
||||||
|
|
||||||
|
"""A CompletionModel filled with help topics."""
|
||||||
|
|
||||||
|
# pylint: disable=abstract-method
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._init_commands()
|
||||||
|
self._init_settings()
|
||||||
|
|
||||||
|
def _init_commands(self):
|
||||||
|
"""Fill completion with :command entries."""
|
||||||
|
assert cmdutils.cmd_dict
|
||||||
|
cmdlist = []
|
||||||
|
for obj in set(cmdutils.cmd_dict.values()):
|
||||||
|
if obj.hide or (obj.debug and not
|
||||||
|
QCoreApplication.instance().args.debug):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
cmdlist.append((':' + obj.name, obj.desc))
|
||||||
|
cat = self.new_category("Commands")
|
||||||
|
for (name, desc) in sorted(cmdlist):
|
||||||
|
self.new_item(cat, name, desc)
|
||||||
|
|
||||||
|
def _init_settings(self):
|
||||||
|
"""Fill completion with section->option entries."""
|
||||||
|
cat = self.new_category("Settings")
|
||||||
|
for sectname, sectdata in configdata.DATA.items():
|
||||||
|
for optname in sectdata.keys():
|
||||||
|
try:
|
||||||
|
desc = sectdata.descriptions[optname]
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
# Some stuff (especially ValueList items) don't have a
|
||||||
|
# description.
|
||||||
|
desc = ""
|
||||||
|
else:
|
||||||
|
desc = desc.splitlines()[0]
|
||||||
|
name = '{}->{}'.format(sectname, optname)
|
||||||
|
self.new_item(cat, name, desc)
|
||||||
|
@ -31,8 +31,7 @@ from PyQt5.QtNetwork import QNetworkReply
|
|||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.network import schemehandler
|
from qutebrowser.network import schemehandler
|
||||||
from qutebrowser.utils import version, utils, jinja
|
from qutebrowser.utils import version, utils, jinja, log, message
|
||||||
from qutebrowser.utils import log as logutils
|
|
||||||
|
|
||||||
|
|
||||||
pyeval_output = ":pyeval was never called"
|
pyeval_output = ":pyeval was never called"
|
||||||
@ -54,66 +53,104 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
|
|||||||
A QNetworkReply.
|
A QNetworkReply.
|
||||||
"""
|
"""
|
||||||
path = request.url().path()
|
path = request.url().path()
|
||||||
# An url like "qute:foo" is split as "scheme:path", not
|
host = request.url().host()
|
||||||
# "scheme:host".
|
# An url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||||
logutils.misc.debug("url: {}, path: {}".format(
|
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||||
request.url().toDisplayString(), path))
|
request.url().toDisplayString(), path, host))
|
||||||
try:
|
try:
|
||||||
handler = getattr(QuteHandlers, path)
|
handler = HANDLERS[path]
|
||||||
except AttributeError:
|
except KeyError:
|
||||||
errorstr = "No handler found for {}!".format(
|
try:
|
||||||
request.url().toDisplayString())
|
handler = HANDLERS[host]
|
||||||
|
except KeyError:
|
||||||
|
errorstr = "No handler found for {}!".format(
|
||||||
|
request.url().toDisplayString())
|
||||||
|
return schemehandler.ErrorNetworkReply(
|
||||||
|
request, errorstr, QNetworkReply.ContentNotFoundError,
|
||||||
|
self.parent())
|
||||||
|
try:
|
||||||
|
data = handler(request)
|
||||||
|
except IOError as e:
|
||||||
return schemehandler.ErrorNetworkReply(
|
return schemehandler.ErrorNetworkReply(
|
||||||
request, errorstr, QNetworkReply.ContentNotFoundError,
|
request, str(e), QNetworkReply.ContentNotFoundError,
|
||||||
self.parent())
|
self.parent())
|
||||||
else:
|
|
||||||
data = handler()
|
|
||||||
return schemehandler.SpecialNetworkReply(
|
return schemehandler.SpecialNetworkReply(
|
||||||
request, data, 'text/html', self.parent())
|
request, data, 'text/html', self.parent())
|
||||||
|
|
||||||
|
|
||||||
class QuteHandlers:
|
def qute_pyeval(_request):
|
||||||
|
"""Handler for qute:pyeval. Return HTML content as bytes."""
|
||||||
|
html = jinja.env.get_template('pre.html').render(
|
||||||
|
title='pyeval', content=pyeval_output)
|
||||||
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
"""Handlers for qute:... pages."""
|
|
||||||
|
|
||||||
@classmethod
|
def qute_version(_request):
|
||||||
def pyeval(cls):
|
"""Handler for qute:version. Return HTML content as bytes."""
|
||||||
"""Handler for qute:pyeval. Return HTML content as bytes."""
|
html = jinja.env.get_template('version.html').render(
|
||||||
html = jinja.env.get_template('pre.html').render(
|
title='Version info', version=version.version(),
|
||||||
title='pyeval', content=pyeval_output)
|
copyright=qutebrowser.__copyright__)
|
||||||
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
|
def qute_plainlog(_request):
|
||||||
|
"""Handler for qute:plainlog. Return HTML content as bytes."""
|
||||||
|
if log.ram_handler is None:
|
||||||
|
text = "Log output was disabled."
|
||||||
|
else:
|
||||||
|
text = log.ram_handler.dump_log()
|
||||||
|
html = jinja.env.get_template('pre.html').render(title='log', content=text)
|
||||||
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
|
def qute_log(_request):
|
||||||
|
"""Handler for qute:log. Return HTML content as bytes."""
|
||||||
|
if log.ram_handler is None:
|
||||||
|
html_log = None
|
||||||
|
else:
|
||||||
|
html_log = log.ram_handler.dump_log(html=True)
|
||||||
|
html = jinja.env.get_template('log.html').render(
|
||||||
|
title='log', content=html_log)
|
||||||
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
|
def qute_gpl(_request):
|
||||||
|
"""Handler for qute:gpl. Return HTML content as bytes."""
|
||||||
|
return utils.read_file('html/COPYING.html').encode('ASCII')
|
||||||
|
|
||||||
|
|
||||||
|
def qute_help(request):
|
||||||
|
"""Handler for qute:help. Return HTML content as bytes."""
|
||||||
|
try:
|
||||||
|
utils.read_file('html/doc/index.html')
|
||||||
|
except FileNotFoundError:
|
||||||
|
html = jinja.env.get_template('error.html').render(
|
||||||
|
title="Error while loading documentation",
|
||||||
|
url=request.url().toDisplayString(),
|
||||||
|
error="This most likely means the documentation was not generated "
|
||||||
|
"properly. If you are running qutebrowser from the git "
|
||||||
|
"repository, please run scripts/asciidoc2html.py."
|
||||||
|
"If you're running a released version this is a bug, please "
|
||||||
|
"use :report to report it.",
|
||||||
|
icon='')
|
||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
urlpath = request.url().path()
|
||||||
|
if not urlpath or urlpath == '/':
|
||||||
|
urlpath = 'index.html'
|
||||||
|
else:
|
||||||
|
urlpath = urlpath.lstrip('/')
|
||||||
|
if not utils.docs_up_to_date(urlpath):
|
||||||
|
message.error("Your documentation is outdated! Please re-run scripts/"
|
||||||
|
"asciidoc2html.py.")
|
||||||
|
path = 'html/doc/{}'.format(urlpath)
|
||||||
|
return utils.read_file(path).encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def version(cls):
|
|
||||||
"""Handler for qute:version. Return HTML content as bytes."""
|
|
||||||
html = jinja.env.get_template('version.html').render(
|
|
||||||
title='Version info', version=version.version(),
|
|
||||||
copyright=qutebrowser.__copyright__)
|
|
||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
|
||||||
|
|
||||||
@classmethod
|
HANDLERS = {
|
||||||
def plainlog(cls):
|
'pyeval': qute_pyeval,
|
||||||
"""Handler for qute:plainlog. Return HTML content as bytes."""
|
'version': qute_version,
|
||||||
if logutils.ram_handler is None:
|
'plainlog': qute_plainlog,
|
||||||
text = "Log output was disabled."
|
'log': qute_log,
|
||||||
else:
|
'gpl': qute_gpl,
|
||||||
text = logutils.ram_handler.dump_log()
|
'help': qute_help,
|
||||||
html = jinja.env.get_template('pre.html').render(
|
}
|
||||||
title='log', content=text)
|
|
||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def log(cls):
|
|
||||||
"""Handler for qute:log. Return HTML content as bytes."""
|
|
||||||
if logutils.ram_handler is None:
|
|
||||||
html_log = None
|
|
||||||
else:
|
|
||||||
html_log = logutils.ram_handler.dump_log(html=True)
|
|
||||||
html = jinja.env.get_template('log.html').render(
|
|
||||||
title='log', content=html_log)
|
|
||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def gpl(cls):
|
|
||||||
"""Handler for qute:gpl. Return HTML content as bytes."""
|
|
||||||
return utils.read_file('html/COPYING.html').encode('ASCII')
|
|
||||||
|
@ -1735,32 +1735,6 @@ class SearchEngineUrlTests(unittest.TestCase):
|
|||||||
self.assertEqual(self.t.transform("foobar"), "foobar")
|
self.assertEqual(self.t.transform("foobar"), "foobar")
|
||||||
|
|
||||||
|
|
||||||
class KeyBindingNameTests(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Test KeyBindingName."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.t = configtypes.KeyBindingName()
|
|
||||||
|
|
||||||
def test_validate_empty(self):
|
|
||||||
"""Test validate with empty string and none_ok = False."""
|
|
||||||
with self.assertRaises(configtypes.ValidationError):
|
|
||||||
self.t.validate('')
|
|
||||||
|
|
||||||
def test_validate_empty_none_ok(self):
|
|
||||||
"""Test validate with empty string and none_ok = True."""
|
|
||||||
t = configtypes.KeyBindingName(none_ok=True)
|
|
||||||
t.validate('')
|
|
||||||
|
|
||||||
def test_transform_empty(self):
|
|
||||||
"""Test transform with an empty value."""
|
|
||||||
self.assertIsNone(self.t.transform(''))
|
|
||||||
|
|
||||||
def test_transform(self):
|
|
||||||
"""Test transform with a value."""
|
|
||||||
self.assertEqual(self.t.transform("foobar"), "foobar")
|
|
||||||
|
|
||||||
|
|
||||||
class UserStyleSheetTests(unittest.TestCase):
|
class UserStyleSheetTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Test UserStyleSheet."""
|
"""Test UserStyleSheet."""
|
||||||
|
@ -31,13 +31,15 @@ from qutebrowser.keyinput import basekeyparser
|
|||||||
from qutebrowser.test import stubs, helpers
|
from qutebrowser.test import stubs, helpers
|
||||||
|
|
||||||
|
|
||||||
CONFIG = {'test': {'<Ctrl-a>': 'ctrla',
|
CONFIG = {'input': {'timeout': 100}}
|
||||||
'a': 'a',
|
|
||||||
'ba': 'ba',
|
|
||||||
'ax': 'ax',
|
BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
|
||||||
'ccc': 'ccc'},
|
'a': 'a',
|
||||||
'input': {'timeout': 100},
|
'ba': 'ba',
|
||||||
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
|
'ax': 'ax',
|
||||||
|
'ccc': 'ccc'},
|
||||||
|
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
|
||||||
|
|
||||||
|
|
||||||
def setUpModule():
|
def setUpModule():
|
||||||
@ -51,6 +53,14 @@ def tearDownModule():
|
|||||||
logging.disable(logging.NOTSET)
|
logging.disable(logging.NOTSET)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_fake_application():
|
||||||
|
"""Construct a fake QApplication with a keyconfig."""
|
||||||
|
app = stubs.FakeQApplication()
|
||||||
|
app.keyconfig = mock.Mock(spec=['get_bindings_for'])
|
||||||
|
app.keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
class SplitCountTests(unittest.TestCase):
|
class SplitCountTests(unittest.TestCase):
|
||||||
|
|
||||||
"""Test the _split_count method.
|
"""Test the _split_count method.
|
||||||
@ -99,7 +109,7 @@ class ReadConfigTests(unittest.TestCase):
|
|||||||
"""Test reading the config."""
|
"""Test reading the config."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
basekeyparser.config = stubs.ConfigStub(CONFIG)
|
basekeyparser.QCoreApplication = _get_fake_application()
|
||||||
basekeyparser.usertypes.Timer = mock.Mock()
|
basekeyparser.usertypes.Timer = mock.Mock()
|
||||||
|
|
||||||
def test_read_config_invalid(self):
|
def test_read_config_invalid(self):
|
||||||
@ -136,7 +146,7 @@ class SpecialKeysTests(unittest.TestCase):
|
|||||||
autospec=True)
|
autospec=True)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
basekeyparser.config = stubs.ConfigStub(CONFIG)
|
basekeyparser.QCoreApplication = _get_fake_application()
|
||||||
self.kp = basekeyparser.BaseKeyParser()
|
self.kp = basekeyparser.BaseKeyParser()
|
||||||
self.kp.execute = mock.Mock()
|
self.kp.execute = mock.Mock()
|
||||||
self.kp.read_config('test')
|
self.kp.read_config('test')
|
||||||
@ -171,7 +181,7 @@ class KeyChainTests(unittest.TestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Set up mocks and read the test config."""
|
"""Set up mocks and read the test config."""
|
||||||
basekeyparser.config = stubs.ConfigStub(CONFIG)
|
basekeyparser.QCoreApplication = _get_fake_application()
|
||||||
self.timermock = mock.Mock()
|
self.timermock = mock.Mock()
|
||||||
basekeyparser.usertypes.Timer = mock.Mock(return_value=self.timermock)
|
basekeyparser.usertypes.Timer = mock.Mock(return_value=self.timermock)
|
||||||
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
||||||
@ -205,6 +215,7 @@ class KeyChainTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_ambigious_keychain(self):
|
def test_ambigious_keychain(self):
|
||||||
"""Test ambigious keychain."""
|
"""Test ambigious keychain."""
|
||||||
|
basekeyparser.config = stubs.ConfigStub(CONFIG)
|
||||||
# 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
|
||||||
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
|
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a'))
|
||||||
@ -235,7 +246,7 @@ class CountTests(unittest.TestCase):
|
|||||||
"""Test execute() with counts."""
|
"""Test execute() with counts."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
basekeyparser.config = stubs.ConfigStub(CONFIG)
|
basekeyparser.QCoreApplication = _get_fake_application()
|
||||||
basekeyparser.usertypes.Timer = mock.Mock()
|
basekeyparser.usertypes.Timer = mock.Mock()
|
||||||
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
|
||||||
supports_count=True)
|
supports_count=True)
|
||||||
|
@ -110,8 +110,7 @@ class FakeQApplication:
|
|||||||
|
|
||||||
"""Stub to insert as QApplication module."""
|
"""Stub to insert as QApplication module."""
|
||||||
|
|
||||||
def __init__(self, focus):
|
def __init__(self):
|
||||||
self.focusWidget = mock.Mock(return_value=focus)
|
|
||||||
self.instance = mock.Mock(return_value=self)
|
self.instance = mock.Mock(return_value=self)
|
||||||
|
|
||||||
|
|
||||||
|
@ -137,13 +137,13 @@ class TestDebug(unittest.TestCase):
|
|||||||
def test_dbg_signal_eliding(self):
|
def test_dbg_signal_eliding(self):
|
||||||
"""Test eliding in dbg_signal()."""
|
"""Test eliding in dbg_signal()."""
|
||||||
self.assertEqual(debug.dbg_signal(self.signal,
|
self.assertEqual(debug.dbg_signal(self.signal,
|
||||||
[12345678901234567890123]),
|
['x' * 201]),
|
||||||
'fake(1234567890123456789\u2026)')
|
"fake('{}\u2026)".format('x' * 198))
|
||||||
|
|
||||||
def test_dbg_signal_newline(self):
|
def test_dbg_signal_newline(self):
|
||||||
"""Test dbg_signal() with a newline."""
|
"""Test dbg_signal() with a newline."""
|
||||||
self.assertEqual(debug.dbg_signal(self.signal, ['foo\nbar']),
|
self.assertEqual(debug.dbg_signal(self.signal, ['foo\nbar']),
|
||||||
'fake(foo bar)')
|
r"fake('foo\nbar')")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -34,7 +34,8 @@ class NoneWidgetTests(unittest.TestCase):
|
|||||||
"""Tests when the focused widget is None."""
|
"""Tests when the focused widget is None."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
readline.QApplication = stubs.FakeQApplication(None)
|
readline.QApplication = stubs.FakeQApplication()
|
||||||
|
readline.QApplication.focusWidget = mock.Mock(return_value=None)
|
||||||
self.bridge = readline.ReadlineBridge()
|
self.bridge = readline.ReadlineBridge()
|
||||||
|
|
||||||
def test_none(self):
|
def test_none(self):
|
||||||
@ -52,7 +53,7 @@ class ReadlineBridgeTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.qle = mock.Mock()
|
self.qle = mock.Mock()
|
||||||
self.qle.__class__ = QLineEdit
|
self.qle.__class__ = QLineEdit
|
||||||
readline.QApplication = stubs.FakeQApplication(self.qle)
|
readline.QApplication.focusWidget = mock.Mock(return_value=self.qle)
|
||||||
self.bridge = readline.ReadlineBridge()
|
self.bridge = readline.ReadlineBridge()
|
||||||
|
|
||||||
def _set_selected_text(self, text):
|
def _set_selected_text(self, text):
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import enum
|
||||||
import shutil
|
import shutil
|
||||||
import unittest
|
import unittest
|
||||||
import os.path
|
import os.path
|
||||||
@ -526,5 +527,25 @@ class NormalizeTests(unittest.TestCase):
|
|||||||
self.assertEqual(utils.normalize_keystr(orig), repl)
|
self.assertEqual(utils.normalize_keystr(orig), repl)
|
||||||
|
|
||||||
|
|
||||||
|
class IsEnumTests(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Test is_enum."""
|
||||||
|
|
||||||
|
def test_enum(self):
|
||||||
|
"""Test is_enum with an enum."""
|
||||||
|
e = enum.Enum('Foo', 'bar, baz')
|
||||||
|
self.assertTrue(utils.is_enum(e))
|
||||||
|
|
||||||
|
def test_class(self):
|
||||||
|
"""Test is_enum with a non-enum class."""
|
||||||
|
# pylint: disable=multiple-statements,missing-docstring
|
||||||
|
class Test: pass
|
||||||
|
self.assertFalse(utils.is_enum(Test))
|
||||||
|
|
||||||
|
def test_object(self):
|
||||||
|
"""Test is_enum with a non-enum object."""
|
||||||
|
self.assertFalse(utils.is_enum(23))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -57,13 +57,15 @@ class Completer(QObject):
|
|||||||
usertypes.Completion.option: {},
|
usertypes.Completion.option: {},
|
||||||
usertypes.Completion.value: {},
|
usertypes.Completion.value: {},
|
||||||
}
|
}
|
||||||
self._init_command_completion()
|
self._init_static_completions()
|
||||||
self._init_setting_completions()
|
self._init_setting_completions()
|
||||||
|
|
||||||
def _init_command_completion(self):
|
def _init_static_completions(self):
|
||||||
"""Initialize the command completion model."""
|
"""Initialize the static completion models."""
|
||||||
self._models[usertypes.Completion.command] = CFM(
|
self._models[usertypes.Completion.command] = CFM(
|
||||||
models.CommandCompletionModel(self), self)
|
models.CommandCompletionModel(self), self)
|
||||||
|
self._models[usertypes.Completion.helptopic] = CFM(
|
||||||
|
models.HelpCompletionModel(self), self)
|
||||||
|
|
||||||
def _init_setting_completions(self):
|
def _init_setting_completions(self):
|
||||||
"""Initialize setting completion models."""
|
"""Initialize setting completion models."""
|
||||||
|
@ -21,58 +21,11 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import types
|
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import QEvent, QCoreApplication
|
from PyQt5.QtCore import QEvent
|
||||||
|
|
||||||
from qutebrowser.utils import log, utils
|
from qutebrowser.utils import log, utils
|
||||||
from qutebrowser.commands import cmdutils
|
|
||||||
from qutebrowser.config import config, style
|
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(debug=True)
|
|
||||||
def debug_crash(typ='exception'):
|
|
||||||
"""Crash for debugging purposes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
typ: either 'exception' or 'segfault'.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
raises Exception when typ is not segfault.
|
|
||||||
segfaults when typ is (you don't say...)
|
|
||||||
"""
|
|
||||||
if typ == 'segfault':
|
|
||||||
# From python's Lib/test/crashers/bogus_code_obj.py
|
|
||||||
co = types.CodeType(0, 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (),
|
|
||||||
'', '', 1, b'')
|
|
||||||
exec(co) # pylint: disable=exec-used
|
|
||||||
raise Exception("Segfault failed (wat.)")
|
|
||||||
else:
|
|
||||||
raise Exception("Forced crash")
|
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(debug=True)
|
|
||||||
def debug_all_widgets():
|
|
||||||
"""Print a list of all widgets to debug log."""
|
|
||||||
s = QCoreApplication.instance().get_all_widgets()
|
|
||||||
log.misc.debug(s)
|
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(debug=True)
|
|
||||||
def debug_all_objects():
|
|
||||||
"""Print a list of all objects to the debug log."""
|
|
||||||
s = QCoreApplication.instance().get_all_objects()
|
|
||||||
log.misc.debug(s)
|
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(debug=True)
|
|
||||||
def debug_cache_stats():
|
|
||||||
"""Print LRU cache stats."""
|
|
||||||
config_info = config.instance().get.cache_info()
|
|
||||||
style_info = style.get_stylesheet.cache_info()
|
|
||||||
log.misc.debug('config: {}'.format(config_info))
|
|
||||||
log.misc.debug('style: {}'.format(style_info))
|
|
||||||
|
|
||||||
|
|
||||||
def log_events(klass):
|
def log_events(klass):
|
||||||
@ -208,6 +161,18 @@ def signal_name(sig):
|
|||||||
return m.group(1)
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_args(args=None, kwargs=None):
|
||||||
|
"""Format a list of arguments/kwargs to a function-call like string."""
|
||||||
|
if args is not None:
|
||||||
|
arglist = [utils.compact_text(repr(arg), 200) for arg in args]
|
||||||
|
else:
|
||||||
|
arglist = []
|
||||||
|
if kwargs is not None:
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
arglist.append('{}={}'.format(k, utils.compact_text(repr(v), 200)))
|
||||||
|
return ', '.join(arglist)
|
||||||
|
|
||||||
|
|
||||||
def dbg_signal(sig, args):
|
def dbg_signal(sig, args):
|
||||||
"""Get a string representation of a signal for debugging.
|
"""Get a string representation of a signal for debugging.
|
||||||
|
|
||||||
@ -218,6 +183,26 @@ def dbg_signal(sig, args):
|
|||||||
Return:
|
Return:
|
||||||
A human-readable string representation of signal/args.
|
A human-readable string representation of signal/args.
|
||||||
"""
|
"""
|
||||||
argstr = ', '.join([utils.elide(str(a).replace('\n', ' '), 20)
|
return '{}({})'.format(signal_name(sig), _format_args(args))
|
||||||
for a in args])
|
|
||||||
return '{}({})'.format(signal_name(sig), argstr)
|
|
||||||
|
def format_call(func, args=None, kwargs=None, full=True):
|
||||||
|
"""Get a string representation of a function calls with the given args.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: The callable to print.
|
||||||
|
args: A list of positional arguments.
|
||||||
|
kwargs: A dict of named arguments.
|
||||||
|
full: Whether to print the full name
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A string with the function call.
|
||||||
|
"""
|
||||||
|
if full:
|
||||||
|
if func.__module__ is not None:
|
||||||
|
name = '.'.join([func.__module__, func.__qualname__])
|
||||||
|
else:
|
||||||
|
name = func.__qualname__
|
||||||
|
else:
|
||||||
|
name = func.__name__
|
||||||
|
return '{}({})'.format(name, _format_args(args, kwargs))
|
||||||
|
@ -248,7 +248,8 @@ KeyMode = enum('KeyMode', 'normal', 'hint', 'command', 'yesno', 'prompt',
|
|||||||
|
|
||||||
|
|
||||||
# Available command completions
|
# Available command completions
|
||||||
Completion = enum('Completion', 'command', 'section', 'option', 'value')
|
Completion = enum('Completion', 'command', 'section', 'option', 'value',
|
||||||
|
'helptopic')
|
||||||
|
|
||||||
|
|
||||||
class Question(QObject):
|
class Question(QObject):
|
||||||
|
@ -19,10 +19,15 @@
|
|||||||
|
|
||||||
"""Misc. utility commands exposed to the user."""
|
"""Misc. utility commands exposed to the user."""
|
||||||
|
|
||||||
from functools import partial
|
import types
|
||||||
|
import functools
|
||||||
|
|
||||||
from qutebrowser.utils import usertypes
|
|
||||||
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
|
from qutebrowser.utils import usertypes, log
|
||||||
from qutebrowser.commands import runners, cmdexc, cmdutils
|
from qutebrowser.commands import runners, cmdexc, cmdutils
|
||||||
|
from qutebrowser.config import config, style
|
||||||
|
|
||||||
|
|
||||||
_timers = []
|
_timers = []
|
||||||
@ -35,15 +40,14 @@ def init():
|
|||||||
_commandrunner = runners.CommandRunner()
|
_commandrunner = runners.CommandRunner()
|
||||||
|
|
||||||
|
|
||||||
@cmdutils.register(nargs=(2, None))
|
@cmdutils.register()
|
||||||
def later(ms, *command):
|
def later(ms: int, *command):
|
||||||
"""Execute a command after some time.
|
"""Execute a command after some time.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ms: How many milliseconds to wait.
|
ms: How many milliseconds to wait.
|
||||||
command: The command/args to run.
|
*command: The command to run, with optional args.
|
||||||
"""
|
"""
|
||||||
ms = int(ms)
|
|
||||||
timer = usertypes.Timer(name='later')
|
timer = usertypes.Timer(name='later')
|
||||||
timer.setSingleShot(True)
|
timer.setSingleShot(True)
|
||||||
if ms < 0:
|
if ms < 0:
|
||||||
@ -55,6 +59,51 @@ def later(ms, *command):
|
|||||||
"int representation.")
|
"int representation.")
|
||||||
_timers.append(timer)
|
_timers.append(timer)
|
||||||
cmdline = ' '.join(command)
|
cmdline = ' '.join(command)
|
||||||
timer.timeout.connect(partial(_commandrunner.run_safely, cmdline))
|
timer.timeout.connect(functools.partial(
|
||||||
|
_commandrunner.run_safely, cmdline))
|
||||||
timer.timeout.connect(lambda: _timers.remove(timer))
|
timer.timeout.connect(lambda: _timers.remove(timer))
|
||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
@cmdutils.register(debug=True)
|
||||||
|
def debug_crash(typ: ('exception', 'segfault')='exception'):
|
||||||
|
"""Crash for debugging purposes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
typ: either 'exception' or 'segfault'.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
raises Exception when typ is not segfault.
|
||||||
|
segfaults when typ is (you don't say...)
|
||||||
|
"""
|
||||||
|
if typ == 'segfault':
|
||||||
|
# From python's Lib/test/crashers/bogus_code_obj.py
|
||||||
|
co = types.CodeType(0, 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (),
|
||||||
|
'', '', 1, b'')
|
||||||
|
exec(co) # pylint: disable=exec-used
|
||||||
|
raise Exception("Segfault failed (wat.)")
|
||||||
|
else:
|
||||||
|
raise Exception("Forced crash")
|
||||||
|
|
||||||
|
|
||||||
|
@cmdutils.register(debug=True)
|
||||||
|
def debug_all_widgets():
|
||||||
|
"""Print a list of all widgets to debug log."""
|
||||||
|
s = QCoreApplication.instance().get_all_widgets()
|
||||||
|
log.misc.debug(s)
|
||||||
|
|
||||||
|
|
||||||
|
@cmdutils.register(debug=True)
|
||||||
|
def debug_all_objects():
|
||||||
|
"""Print a list of all objects to the debug log."""
|
||||||
|
s = QCoreApplication.instance().get_all_objects()
|
||||||
|
log.misc.debug(s)
|
||||||
|
|
||||||
|
|
||||||
|
@cmdutils.register(debug=True)
|
||||||
|
def debug_cache_stats():
|
||||||
|
"""Print LRU cache stats."""
|
||||||
|
config_info = config.instance().get.cache_info()
|
||||||
|
style_info = style.get_stylesheet.cache_info()
|
||||||
|
log.misc.debug('config: {}'.format(config_info))
|
||||||
|
log.misc.debug('style: {}'.format(style_info))
|
||||||
|
@ -21,8 +21,11 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import io
|
import io
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
import enum
|
||||||
import shlex
|
import shlex
|
||||||
|
import inspect
|
||||||
import os.path
|
import os.path
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@ -35,7 +38,7 @@ from PyQt5.QtGui import QKeySequence, QColor
|
|||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import qtutils, log
|
from qutebrowser.utils import qtutils, log, usertypes
|
||||||
|
|
||||||
|
|
||||||
def elide(text, length):
|
def elide(text, length):
|
||||||
@ -569,3 +572,130 @@ class prevent_exceptions: # pylint: disable=invalid-name
|
|||||||
return retval
|
return retval
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def is_enum(obj):
|
||||||
|
"""Check if a given object is an enum."""
|
||||||
|
try:
|
||||||
|
return issubclass(obj, enum.Enum)
|
||||||
|
except TypeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_git_repo():
|
||||||
|
"""Check if we're running from a git repository."""
|
||||||
|
gitfolder = os.path.join(qutebrowser.basedir, os.path.pardir, '.git')
|
||||||
|
return os.path.isdir(gitfolder)
|
||||||
|
|
||||||
|
|
||||||
|
def docs_up_to_date(path):
|
||||||
|
"""Check if the generated html documentation is up to date.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: The path of the document to check.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if they are up to date or we couldn't check.
|
||||||
|
False if they are outdated.
|
||||||
|
"""
|
||||||
|
if hasattr(sys, 'frozen') or not is_git_repo():
|
||||||
|
return True
|
||||||
|
html_path = os.path.join(qutebrowser.basedir, 'html', 'doc', path)
|
||||||
|
filename = os.path.splitext(path)[0]
|
||||||
|
asciidoc_path = os.path.join(qutebrowser.basedir, os.path.pardir,
|
||||||
|
'doc', 'help', filename + '.asciidoc')
|
||||||
|
try:
|
||||||
|
html_time = os.path.getmtime(html_path)
|
||||||
|
asciidoc_time = os.path.getmtime(asciidoc_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return True
|
||||||
|
return asciidoc_time <= html_time
|
||||||
|
|
||||||
|
|
||||||
|
class DocstringParser:
|
||||||
|
|
||||||
|
"""Generate documentation based on a docstring of a command handler.
|
||||||
|
|
||||||
|
The docstring needs to follow the format described in HACKING.
|
||||||
|
"""
|
||||||
|
|
||||||
|
State = usertypes.enum('State', 'short', 'desc', 'desc_hidden',
|
||||||
|
'arg_start', 'arg_inside', 'misc')
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
"""Constructor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: The function to parse the docstring for.
|
||||||
|
"""
|
||||||
|
self.state = self.State.short
|
||||||
|
self.short_desc = []
|
||||||
|
self.long_desc = []
|
||||||
|
self.arg_descs = collections.OrderedDict()
|
||||||
|
self.cur_arg_name = None
|
||||||
|
self.handlers = {
|
||||||
|
self.State.short: self._parse_short,
|
||||||
|
self.State.desc: self._parse_desc,
|
||||||
|
self.State.desc_hidden: self._skip,
|
||||||
|
self.State.arg_start: self._parse_arg_start,
|
||||||
|
self.State.arg_inside: self._parse_arg_inside,
|
||||||
|
self.State.misc: self._skip,
|
||||||
|
}
|
||||||
|
doc = inspect.getdoc(func)
|
||||||
|
for line in doc.splitlines():
|
||||||
|
handler = self.handlers[self.state]
|
||||||
|
stop = handler(line)
|
||||||
|
if stop:
|
||||||
|
break
|
||||||
|
for k, v in self.arg_descs.items():
|
||||||
|
self.arg_descs[k] = ' '.join(v).replace(', or None', '')
|
||||||
|
self.long_desc = ' '.join(self.long_desc)
|
||||||
|
self.short_desc = ' '.join(self.short_desc)
|
||||||
|
|
||||||
|
def _process_arg(self, line):
|
||||||
|
"""Helper method to process a line like 'fooarg: Blah blub'."""
|
||||||
|
self.cur_arg_name, argdesc = line.split(':', maxsplit=1)
|
||||||
|
self.cur_arg_name = self.cur_arg_name.strip().lstrip('*')
|
||||||
|
self.arg_descs[self.cur_arg_name] = [argdesc.strip()]
|
||||||
|
|
||||||
|
def _skip(self, line):
|
||||||
|
"""Handler to ignore everything until we get 'Args:'."""
|
||||||
|
if line.startswith('Args:'):
|
||||||
|
self.state = self.State.arg_start
|
||||||
|
|
||||||
|
def _parse_short(self, line):
|
||||||
|
"""Parse the short description (first block) in the docstring."""
|
||||||
|
if not line:
|
||||||
|
self.state = self.State.desc
|
||||||
|
else:
|
||||||
|
self.short_desc.append(line.strip())
|
||||||
|
|
||||||
|
def _parse_desc(self, line):
|
||||||
|
"""Parse the long description in the docstring."""
|
||||||
|
if line.startswith('Args:'):
|
||||||
|
self.state = self.State.arg_start
|
||||||
|
elif line.startswith('Emit:') or line.startswith('Raise:'):
|
||||||
|
self.state = self.State.misc
|
||||||
|
elif line.strip() == '//':
|
||||||
|
self.state = self.State.desc_hidden
|
||||||
|
elif line.strip():
|
||||||
|
self.long_desc.append(line.strip())
|
||||||
|
|
||||||
|
def _parse_arg_start(self, line):
|
||||||
|
"""Parse first argument line."""
|
||||||
|
self._process_arg(line)
|
||||||
|
self.state = self.State.arg_inside
|
||||||
|
|
||||||
|
def _parse_arg_inside(self, line):
|
||||||
|
"""Parse subsequent argument lines."""
|
||||||
|
argname = self.cur_arg_name
|
||||||
|
if re.match(r'^[A-Z][a-z]+:$', line):
|
||||||
|
if not self.arg_descs[argname][-1].strip():
|
||||||
|
self.arg_descs[argname] = self.arg_descs[argname][:-1]
|
||||||
|
return True
|
||||||
|
elif not line.strip():
|
||||||
|
self.arg_descs[argname].append('\n\n')
|
||||||
|
elif line[4:].startswith(' '):
|
||||||
|
self.arg_descs[argname].append(line.strip() + '\n')
|
||||||
|
else:
|
||||||
|
self._process_arg(line)
|
||||||
|
@ -142,7 +142,7 @@ class MainWindow(QWidget):
|
|||||||
if rect.isValid():
|
if rect.isValid():
|
||||||
self.completion.setGeometry(rect)
|
self.completion.setGeometry(rect)
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow', name=['quit', 'q'], nargs=0)
|
@cmdutils.register(instance='mainwindow', name=['quit', 'q'])
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Quit qutebrowser.
|
"""Quit qutebrowser.
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""The commandline in the statusbar."""
|
"""The commandline in the statusbar."""
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication, QUrl
|
||||||
from PyQt5.QtWidgets import QSizePolicy, QApplication
|
from PyQt5.QtWidgets import QSizePolicy, QApplication
|
||||||
|
|
||||||
from qutebrowser.keyinput import modeman, modeparsers
|
from qutebrowser.keyinput import modeman, modeparsers
|
||||||
@ -161,7 +161,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
self.show_cmd.emit()
|
self.show_cmd.emit()
|
||||||
|
|
||||||
@cmdutils.register(instance='mainwindow.status.cmd', name='set-cmd-text')
|
@cmdutils.register(instance='mainwindow.status.cmd', name='set-cmd-text')
|
||||||
def set_cmd_text_command(self, *strings):
|
def set_cmd_text_command(self, text):
|
||||||
"""Preset the statusbar to some text.
|
"""Preset the statusbar to some text.
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -170,9 +170,16 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
strings which will get joined.
|
strings which will get joined.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
strings: A list of strings to set.
|
text: The commandline to set.
|
||||||
"""
|
"""
|
||||||
text = ' '.join(strings)
|
app = QCoreApplication.instance()
|
||||||
|
url = app.mainwindow.tabs.current_url().toString(
|
||||||
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||||
|
# FIXME we currently replace the URL in any place in the arguments,
|
||||||
|
# rather than just replacing it if it is a dedicated argument. We could
|
||||||
|
# split the args, but then trailing spaces would be lost, so I'm not
|
||||||
|
# sure what's the best thing to do here
|
||||||
|
text = text.replace('{url}', url)
|
||||||
if not text[0] in modeparsers.STARTCHARS:
|
if not text[0] in modeparsers.STARTCHARS:
|
||||||
raise cmdexc.CommandError(
|
raise cmdexc.CommandError(
|
||||||
"Invalid command text '{}'.".format(text))
|
"Invalid command text '{}'.".format(text))
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QSizePolicy
|
from PyQt5.QtWidgets import QSizePolicy
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize, QTimer
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSize, QTimer, QUrl
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWebKitWidgets import QWebPage
|
from PyQt5.QtWebKitWidgets import QWebPage
|
||||||
|
|
||||||
@ -205,7 +205,11 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
Raise:
|
Raise:
|
||||||
CommandError if the current URL is invalid.
|
CommandError if the current URL is invalid.
|
||||||
"""
|
"""
|
||||||
url = self.currentWidget().cur_url
|
widget = self.currentWidget()
|
||||||
|
if widget is None:
|
||||||
|
url = QUrl()
|
||||||
|
else:
|
||||||
|
url = widget.cur_url
|
||||||
try:
|
try:
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
except qtutils.QtValueError as e:
|
except qtutils.QtValueError as e:
|
||||||
|
75
scripts/asciidoc2html.py
Normal file
75
scripts/asciidoc2html.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""Generate the html documentation based on the asciidoc files."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import glob
|
||||||
|
|
||||||
|
sys.path.insert(0, os.getcwd())
|
||||||
|
|
||||||
|
from scripts import utils
|
||||||
|
|
||||||
|
|
||||||
|
def call_asciidoc(src, dst):
|
||||||
|
"""Call asciidoc for the given files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
src: The source .asciidoc file.
|
||||||
|
dst: The destination .html file, or None to auto-guess.
|
||||||
|
"""
|
||||||
|
utils.print_col("Calling asciidoc for {}...".format(
|
||||||
|
os.path.basename(src)), 'cyan')
|
||||||
|
if os.name == 'nt':
|
||||||
|
# FIXME this is highly specific to my machine
|
||||||
|
args = [r'C:\Python27\python', r'J:\bin\asciidoc-8.6.9\asciidoc.py']
|
||||||
|
else:
|
||||||
|
args = ['asciidoc']
|
||||||
|
if dst is not None:
|
||||||
|
args += ['--out-file', dst]
|
||||||
|
args.append(src)
|
||||||
|
try:
|
||||||
|
subprocess.check_call(args)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
utils.print_col(str(e), 'red')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main(colors=False):
|
||||||
|
utils.use_color = colors
|
||||||
|
asciidoc_files = [
|
||||||
|
('doc/FAQ.asciidoc', 'qutebrowser/html/doc/FAQ.html'),
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
os.mkdir('qutebrowser/html/doc')
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
||||||
|
for src in glob.glob('doc/help/*.asciidoc'):
|
||||||
|
name, _ext = os.path.splitext(os.path.basename(src))
|
||||||
|
dst = 'qutebrowser/html/doc/{}.html'.format(name)
|
||||||
|
asciidoc_files.append((src, dst))
|
||||||
|
for src, dst in asciidoc_files:
|
||||||
|
call_asciidoc(src, dst)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(colors=True)
|
@ -32,7 +32,7 @@ recursive_lint = ('__pycache__', '*.pyc')
|
|||||||
lint = ('build', 'dist', 'pkg/pkg', 'pkg/qutebrowser-*.pkg.tar.xz', 'pkg/src',
|
lint = ('build', 'dist', 'pkg/pkg', 'pkg/qutebrowser-*.pkg.tar.xz', 'pkg/src',
|
||||||
'pkg/qutebrowser', 'qutebrowser.egg-info', 'setuptools-*.egg',
|
'pkg/qutebrowser', 'qutebrowser.egg-info', 'setuptools-*.egg',
|
||||||
'setuptools-*.zip', 'doc/qutebrowser.asciidoc', 'doc/*.html',
|
'setuptools-*.zip', 'doc/qutebrowser.asciidoc', 'doc/*.html',
|
||||||
'doc/qutebrowser.1', 'README.html')
|
'doc/qutebrowser.1', 'README.html', 'qutebrowser/html/doc')
|
||||||
|
|
||||||
|
|
||||||
def remove(path):
|
def remove(path):
|
||||||
|
@ -1,478 +0,0 @@
|
|||||||
#!/usr/bin/python3
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
# Copyright 2014 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/>.
|
|
||||||
|
|
||||||
"""Generate asciidoc source for qutebrowser based on docstrings."""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import html
|
|
||||||
import shutil
|
|
||||||
import inspect
|
|
||||||
import subprocess
|
|
||||||
import collections
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
sys.path.insert(0, os.getcwd())
|
|
||||||
|
|
||||||
import qutebrowser
|
|
||||||
# We import qutebrowser.app so all @cmdutils-register decorators are run.
|
|
||||||
import qutebrowser.app
|
|
||||||
from qutebrowser import qutebrowser as qutequtebrowser
|
|
||||||
from qutebrowser.commands import cmdutils
|
|
||||||
from qutebrowser.config import configdata
|
|
||||||
from qutebrowser.utils import usertypes
|
|
||||||
|
|
||||||
|
|
||||||
def _open_file(name, mode='w'):
|
|
||||||
"""Open a file with a preset newline/encoding mode."""
|
|
||||||
return open(name, mode, newline='\n', encoding='utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_docstring(func): # noqa
|
|
||||||
"""Generate documentation based on a docstring of a command handler.
|
|
||||||
|
|
||||||
The docstring needs to follow the format described in HACKING.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
func: The function to generate the docstring for.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
A (short_desc, long_desc, arg_descs) tuple.
|
|
||||||
"""
|
|
||||||
# pylint: disable=too-many-branches
|
|
||||||
State = usertypes.enum('State', 'short', # pylint: disable=invalid-name
|
|
||||||
'desc', 'desc_hidden', 'arg_start', 'arg_inside',
|
|
||||||
'misc')
|
|
||||||
doc = inspect.getdoc(func)
|
|
||||||
lines = doc.splitlines()
|
|
||||||
|
|
||||||
cur_state = State.short
|
|
||||||
|
|
||||||
short_desc = []
|
|
||||||
long_desc = []
|
|
||||||
arg_descs = collections.OrderedDict()
|
|
||||||
cur_arg_name = None
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
if cur_state == State.short:
|
|
||||||
if not line:
|
|
||||||
cur_state = State.desc
|
|
||||||
else:
|
|
||||||
short_desc.append(line.strip())
|
|
||||||
elif cur_state == State.desc:
|
|
||||||
if line.startswith('Args:'):
|
|
||||||
cur_state = State.arg_start
|
|
||||||
elif line.startswith('Emit:') or line.startswith('Raise:'):
|
|
||||||
cur_state = State.misc
|
|
||||||
elif line.strip() == '//':
|
|
||||||
cur_state = State.desc_hidden
|
|
||||||
elif line.strip():
|
|
||||||
long_desc.append(line.strip())
|
|
||||||
elif cur_state == State.misc:
|
|
||||||
if line.startswith('Args:'):
|
|
||||||
cur_state = State.arg_start
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
elif cur_state == State.desc_hidden:
|
|
||||||
if line.startswith('Args:'):
|
|
||||||
cur_state = State.arg_start
|
|
||||||
elif cur_state == State.arg_start:
|
|
||||||
cur_arg_name, argdesc = line.split(':', maxsplit=1)
|
|
||||||
cur_arg_name = cur_arg_name.strip().lstrip('*')
|
|
||||||
arg_descs[cur_arg_name] = [argdesc.strip()]
|
|
||||||
cur_state = State.arg_inside
|
|
||||||
elif cur_state == State.arg_inside:
|
|
||||||
if re.match('^[A-Z][a-z]+:$', line):
|
|
||||||
if not arg_descs[cur_arg_name][-1].strip():
|
|
||||||
arg_descs[cur_arg_name] = arg_descs[cur_arg_name][:-1]
|
|
||||||
break
|
|
||||||
elif not line.strip():
|
|
||||||
arg_descs[cur_arg_name].append('\n\n')
|
|
||||||
elif line[4:].startswith(' '):
|
|
||||||
arg_descs[cur_arg_name].append(line.strip() + '\n')
|
|
||||||
else:
|
|
||||||
cur_arg_name, argdesc = line.split(':', maxsplit=1)
|
|
||||||
cur_arg_name = cur_arg_name.strip().lstrip('*')
|
|
||||||
arg_descs[cur_arg_name] = [argdesc.strip()]
|
|
||||||
return (short_desc, long_desc, arg_descs)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_cmd_syntax(name, cmd):
|
|
||||||
"""Get the command syntax for a command."""
|
|
||||||
words = []
|
|
||||||
argspec = inspect.getfullargspec(cmd.handler)
|
|
||||||
if argspec.defaults is not None:
|
|
||||||
defaults = dict(zip(reversed(argspec.args),
|
|
||||||
reversed(list(argspec.defaults))))
|
|
||||||
else:
|
|
||||||
defaults = {}
|
|
||||||
words.append(name)
|
|
||||||
minargs, maxargs = cmd.nargs
|
|
||||||
i = 1
|
|
||||||
for arg in argspec.args:
|
|
||||||
if arg in ['self', 'count']:
|
|
||||||
continue
|
|
||||||
if minargs is not None and i <= minargs:
|
|
||||||
words.append('<{}>'.format(arg))
|
|
||||||
elif maxargs is None or i <= maxargs:
|
|
||||||
words.append('[<{}>]'.format(arg))
|
|
||||||
i += 1
|
|
||||||
if argspec.varargs is not None:
|
|
||||||
words.append('[<{name}> [...]]'.format(name=argspec.varargs))
|
|
||||||
return (' '.join(words), defaults)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_command_quickref(cmds):
|
|
||||||
"""Generate the command quick reference."""
|
|
||||||
out = []
|
|
||||||
out.append('[options="header",width="75%",cols="25%,75%"]')
|
|
||||||
out.append('|==============')
|
|
||||||
out.append('|Command|Description')
|
|
||||||
for name, cmd in cmds:
|
|
||||||
desc = inspect.getdoc(cmd.handler).splitlines()[0]
|
|
||||||
out.append('|<<cmd-{},{}>>|{}'.format(name, name, desc))
|
|
||||||
out.append('|==============')
|
|
||||||
return '\n'.join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_setting_quickref():
|
|
||||||
"""Generate the settings quick reference."""
|
|
||||||
out = []
|
|
||||||
for sectname, sect in configdata.DATA.items():
|
|
||||||
if not getattr(sect, 'descriptions'):
|
|
||||||
continue
|
|
||||||
out.append(".Quick reference for section ``{}''".format(sectname))
|
|
||||||
out.append('[options="header",width="75%",cols="25%,75%"]')
|
|
||||||
out.append('|==============')
|
|
||||||
out.append('|Setting|Description')
|
|
||||||
for optname, _option in sect.items():
|
|
||||||
desc = sect.descriptions[optname].splitlines()[0]
|
|
||||||
out.append('|<<setting-{}-{},{}>>|{}'.format(
|
|
||||||
sectname, optname, optname, desc))
|
|
||||||
out.append('|==============')
|
|
||||||
return '\n'.join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_command_doc(name, cmd):
|
|
||||||
"""Generate the documentation for a command."""
|
|
||||||
output = ['[[cmd-{}]]'.format(name)]
|
|
||||||
output += ['==== {}'.format(name)]
|
|
||||||
syntax, defaults = _get_cmd_syntax(name, cmd)
|
|
||||||
if syntax != name:
|
|
||||||
output.append('Syntax: +:{}+'.format(syntax))
|
|
||||||
output.append("")
|
|
||||||
short_desc, long_desc, arg_descs = _parse_docstring(cmd.handler)
|
|
||||||
output.append(' '.join(short_desc))
|
|
||||||
output.append("")
|
|
||||||
output.append(' '.join(long_desc))
|
|
||||||
if arg_descs:
|
|
||||||
output.append("")
|
|
||||||
for arg, desc in arg_descs.items():
|
|
||||||
text = ' '.join(desc).splitlines()
|
|
||||||
firstline = text[0].replace(', or None', '')
|
|
||||||
item = "* +{}+: {}".format(arg, firstline)
|
|
||||||
if arg in defaults:
|
|
||||||
val = defaults[arg]
|
|
||||||
if val is None:
|
|
||||||
item += " (optional)\n"
|
|
||||||
else:
|
|
||||||
item += " (default: +{}+)\n".format(defaults[arg])
|
|
||||||
item += '\n'.join(text[1:])
|
|
||||||
output.append(item)
|
|
||||||
output.append("")
|
|
||||||
output.append("")
|
|
||||||
return '\n'.join(output)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_action_metavar(action):
|
|
||||||
"""Get the metavar to display for an argparse action."""
|
|
||||||
if action.metavar is not None:
|
|
||||||
return "'{}'".format(action.metavar)
|
|
||||||
elif action.choices is not None:
|
|
||||||
choices = ','.join(map(str, action.choices))
|
|
||||||
return "'{{{}}}'".format(choices)
|
|
||||||
else:
|
|
||||||
return "'{}'".format(action.dest.upper())
|
|
||||||
|
|
||||||
|
|
||||||
def _format_action_args(action):
|
|
||||||
"""Get an argument string based on an argparse action."""
|
|
||||||
if action.nargs is None:
|
|
||||||
return _get_action_metavar(action)
|
|
||||||
elif action.nargs == '?':
|
|
||||||
return '[{}]'.format(_get_action_metavar(action))
|
|
||||||
elif action.nargs == '*':
|
|
||||||
return '[{mv} [{mv} ...]]'.format(mv=_get_action_metavar(action))
|
|
||||||
elif action.nargs == '+':
|
|
||||||
return '{mv} [{mv} ...]'.format(mv=_get_action_metavar(action))
|
|
||||||
elif action.nargs == '...':
|
|
||||||
return '...'
|
|
||||||
else:
|
|
||||||
return ' '.join([_get_action_metavar(action)] * action.nargs)
|
|
||||||
|
|
||||||
|
|
||||||
def _format_action(action):
|
|
||||||
"""Get an invocation string/help from an argparse action."""
|
|
||||||
if not action.option_strings:
|
|
||||||
invocation = '*{}*::'.format(_get_action_metavar(action))
|
|
||||||
else:
|
|
||||||
parts = []
|
|
||||||
if action.nargs == 0:
|
|
||||||
# Doesn't take a value, so the syntax is -s, --long
|
|
||||||
parts += ['*{}*'.format(s) for s in action.option_strings]
|
|
||||||
else:
|
|
||||||
# Takes a value, so the syntax is -s ARGS or --long ARGS.
|
|
||||||
args_string = _format_action_args(action)
|
|
||||||
for opt in action.option_strings:
|
|
||||||
parts.append('*{}* {}'.format(opt, args_string))
|
|
||||||
invocation = ', '.join(parts) + '::'
|
|
||||||
return '{}\n {}\n\n'.format(invocation, action.help)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_header(f):
|
|
||||||
"""Generate an asciidoc header for the manpage."""
|
|
||||||
f.write("// DO NOT EDIT THIS FILE BY HAND!\n")
|
|
||||||
f.write("// It is generated by `scripts/generate_doc.py`.\n")
|
|
||||||
f.write("// Most likely you'll need to rerun that script, or edit that "
|
|
||||||
"instead of this file.\n")
|
|
||||||
f.write('= qutebrowser(1)\n')
|
|
||||||
f.write(':doctype: manpage\n')
|
|
||||||
f.write(':man source: qutebrowser\n')
|
|
||||||
f.write(':man manual: qutebrowser manpage\n')
|
|
||||||
f.write(':toc:\n')
|
|
||||||
f.write(':homepage: http://www.qutebrowser.org/\n')
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_name(f):
|
|
||||||
"""Generate the NAME-section of the manpage."""
|
|
||||||
f.write('== NAME\n')
|
|
||||||
f.write('qutebrowser - {}\n'.format(qutebrowser.__description__))
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_synopsis(f):
|
|
||||||
"""Generate the SYNOPSIS-section of the manpage."""
|
|
||||||
f.write('== SYNOPSIS\n')
|
|
||||||
f.write("*qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] "
|
|
||||||
"['URL' ['...']]\n")
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_description(f):
|
|
||||||
"""Generate the DESCRIPTION-section of the manpage."""
|
|
||||||
f.write('== DESCRIPTION\n')
|
|
||||||
f.write("qutebrowser is a keyboard-focused browser with with a minimal "
|
|
||||||
"GUI. It's based on Python, PyQt5 and QtWebKit and free software, "
|
|
||||||
"licensed under the GPL.\n\n")
|
|
||||||
f.write("It was inspired by other browsers/addons like dwb and "
|
|
||||||
"Vimperator/Pentadactyl.\n\n")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_options(f):
|
|
||||||
"""Generate the OPTIONS-section of the manpage from an argparse parser."""
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
parser = qutequtebrowser.get_argparser()
|
|
||||||
f.write('== OPTIONS\n')
|
|
||||||
|
|
||||||
# positionals, optionals and user-defined groups
|
|
||||||
for group in parser._action_groups:
|
|
||||||
f.write('=== {}\n'.format(group.title))
|
|
||||||
if group.description is not None:
|
|
||||||
f.write(group.description + '\n')
|
|
||||||
for action in group._group_actions:
|
|
||||||
f.write(_format_action(action))
|
|
||||||
f.write('\n')
|
|
||||||
# epilog
|
|
||||||
if parser.epilog is not None:
|
|
||||||
f.write(parser.epilog)
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_commands(f):
|
|
||||||
"""Generate the complete commands section."""
|
|
||||||
f.write('\n')
|
|
||||||
f.write("== COMMANDS\n")
|
|
||||||
normal_cmds = []
|
|
||||||
hidden_cmds = []
|
|
||||||
debug_cmds = []
|
|
||||||
for name, cmd in cmdutils.cmd_dict.items():
|
|
||||||
if cmd.hide:
|
|
||||||
hidden_cmds.append((name, cmd))
|
|
||||||
elif cmd.debug:
|
|
||||||
debug_cmds.append((name, cmd))
|
|
||||||
else:
|
|
||||||
normal_cmds.append((name, cmd))
|
|
||||||
normal_cmds.sort()
|
|
||||||
hidden_cmds.sort()
|
|
||||||
debug_cmds.sort()
|
|
||||||
f.write("\n")
|
|
||||||
f.write("=== Normal commands\n")
|
|
||||||
f.write(".Quick reference\n")
|
|
||||||
f.write(_get_command_quickref(normal_cmds) + "\n")
|
|
||||||
for name, cmd in normal_cmds:
|
|
||||||
f.write(_get_command_doc(name, cmd) + "\n")
|
|
||||||
f.write("\n")
|
|
||||||
f.write("=== Hidden commands\n")
|
|
||||||
f.write(".Quick reference\n")
|
|
||||||
f.write(_get_command_quickref(hidden_cmds) + "\n")
|
|
||||||
for name, cmd in hidden_cmds:
|
|
||||||
f.write(_get_command_doc(name, cmd) + "\n")
|
|
||||||
f.write("\n")
|
|
||||||
f.write("=== Debugging commands\n")
|
|
||||||
f.write("These commands are mainly intended for debugging. They are "
|
|
||||||
"hidden if qutebrowser was started without the `--debug`-flag.\n")
|
|
||||||
f.write("\n")
|
|
||||||
f.write(".Quick reference\n")
|
|
||||||
f.write(_get_command_quickref(debug_cmds) + "\n")
|
|
||||||
for name, cmd in debug_cmds:
|
|
||||||
f.write(_get_command_doc(name, cmd) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_settings(f):
|
|
||||||
"""Generate the complete settings section."""
|
|
||||||
f.write("\n")
|
|
||||||
f.write("== SETTINGS\n")
|
|
||||||
f.write(_get_setting_quickref() + "\n")
|
|
||||||
for sectname, sect in configdata.DATA.items():
|
|
||||||
f.write("\n")
|
|
||||||
f.write("=== {}".format(sectname) + "\n")
|
|
||||||
f.write(configdata.SECTION_DESC[sectname] + "\n")
|
|
||||||
if not getattr(sect, 'descriptions'):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for optname, option in sect.items():
|
|
||||||
f.write("\n")
|
|
||||||
f.write('[[setting-{}-{}]]'.format(sectname, optname) + "\n")
|
|
||||||
f.write("==== {}".format(optname) + "\n")
|
|
||||||
f.write(sect.descriptions[optname] + "\n")
|
|
||||||
f.write("\n")
|
|
||||||
valid_values = option.typ.valid_values
|
|
||||||
if valid_values is not None:
|
|
||||||
f.write("Valid values:\n")
|
|
||||||
f.write("\n")
|
|
||||||
for val in valid_values:
|
|
||||||
try:
|
|
||||||
desc = valid_values.descriptions[val]
|
|
||||||
f.write(" * +{}+: {}".format(val, desc) + "\n")
|
|
||||||
except KeyError:
|
|
||||||
f.write(" * +{}+".format(val) + "\n")
|
|
||||||
f.write("\n")
|
|
||||||
if option.default():
|
|
||||||
f.write("Default: +pass:[{}]+\n".format(html.escape(
|
|
||||||
option.default())))
|
|
||||||
else:
|
|
||||||
f.write("Default: empty\n")
|
|
||||||
|
|
||||||
|
|
||||||
def _get_authors():
|
|
||||||
"""Get a list of authors based on git commit logs."""
|
|
||||||
commits = subprocess.check_output(['git', 'log', '--format=%aN'])
|
|
||||||
cnt = collections.Counter(commits.decode('utf-8').splitlines())
|
|
||||||
return reversed(sorted(cnt, key=lambda k: cnt[k]))
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_author(f):
|
|
||||||
"""Generate the manpage AUTHOR section."""
|
|
||||||
f.write("== AUTHOR\n")
|
|
||||||
f.write("Contributors, sorted by the number of commits in descending "
|
|
||||||
"order:\n\n")
|
|
||||||
for author in _get_authors():
|
|
||||||
f.write('* {}\n'.format(author))
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_bugs(f):
|
|
||||||
"""Generate the manpage BUGS section."""
|
|
||||||
f.write('== BUGS\n')
|
|
||||||
f.write("Bugs are tracked at two locations:\n\n")
|
|
||||||
f.write("* The link:BUGS[doc/BUGS] and link:TODO[doc/TODO] files shipped "
|
|
||||||
"with qutebrowser.\n")
|
|
||||||
f.write("* The Github issue tracker at https://github.com/The-Compiler/"
|
|
||||||
"qutebrowser/issues .\n\n")
|
|
||||||
f.write("If you found a bug or have a suggestion, either open a ticket "
|
|
||||||
"in the github issue tracker, or write a mail to the "
|
|
||||||
"https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser["
|
|
||||||
"mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[].\n\n")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_copyright(f):
|
|
||||||
"""Generate the COPYRIGHT section of the manpage."""
|
|
||||||
f.write('== COPYRIGHT\n')
|
|
||||||
f.write("This program 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.\n\n")
|
|
||||||
f.write("This program 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.\n\n")
|
|
||||||
f.write("You should have received a copy of the GNU General Public "
|
|
||||||
"License along with this program. If not, see "
|
|
||||||
"<http://www.gnu.org/licenses/>.\n")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_manpage_resources(f):
|
|
||||||
"""Generate the RESOURCES section of the manpage."""
|
|
||||||
f.write('== RESOURCES\n\n')
|
|
||||||
f.write("* Website: http://www.qutebrowser.org/\n")
|
|
||||||
f.write("* Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] / "
|
|
||||||
"https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser\n")
|
|
||||||
f.write("* IRC: irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on "
|
|
||||||
"http://freenode.net/[Freenode]\n")
|
|
||||||
f.write("* Github: https://github.com/The-Compiler/qutebrowser\n\n")
|
|
||||||
|
|
||||||
|
|
||||||
def regenerate_authors(filename):
|
|
||||||
"""Re-generate the authors inside README based on the commits made."""
|
|
||||||
oshandle, tmpname = tempfile.mkstemp()
|
|
||||||
with _open_file(filename, mode='r') as infile, \
|
|
||||||
_open_file(oshandle, mode='w') as temp:
|
|
||||||
ignore = False
|
|
||||||
for line in infile:
|
|
||||||
if line.strip() == '// QUTE_AUTHORS_START':
|
|
||||||
ignore = True
|
|
||||||
temp.write(line)
|
|
||||||
for author in _get_authors():
|
|
||||||
temp.write('* {}\n'.format(author))
|
|
||||||
elif line.strip() == '// QUTE_AUTHORS_END':
|
|
||||||
temp.write(line)
|
|
||||||
ignore = False
|
|
||||||
elif not ignore:
|
|
||||||
temp.write(line)
|
|
||||||
os.remove(filename)
|
|
||||||
shutil.move(tmpname, filename)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
with _open_file('doc/qutebrowser.1.asciidoc') as fobj:
|
|
||||||
generate_manpage_header(fobj)
|
|
||||||
generate_manpage_name(fobj)
|
|
||||||
generate_manpage_synopsis(fobj)
|
|
||||||
generate_manpage_description(fobj)
|
|
||||||
generate_manpage_options(fobj)
|
|
||||||
generate_settings(fobj)
|
|
||||||
generate_commands(fobj)
|
|
||||||
generate_manpage_bugs(fobj)
|
|
||||||
generate_manpage_author(fobj)
|
|
||||||
generate_manpage_resources(fobj)
|
|
||||||
generate_manpage_copyright(fobj)
|
|
||||||
regenerate_authors('README.asciidoc')
|
|
@ -18,7 +18,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/>.
|
||||||
|
|
||||||
# pylint: disable=broad-except, no-member
|
# pylint: disable=broad-except
|
||||||
|
|
||||||
""" Run different codecheckers over a codebase.
|
""" Run different codecheckers over a codebase.
|
||||||
|
|
||||||
|
411
scripts/src2asciidoc.py
Executable file
411
scripts/src2asciidoc.py
Executable file
@ -0,0 +1,411 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""Generate asciidoc source for qutebrowser based on docstrings."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import html
|
||||||
|
import shutil
|
||||||
|
import os.path
|
||||||
|
import inspect
|
||||||
|
import subprocess
|
||||||
|
import collections
|
||||||
|
import tempfile
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import colorama as col
|
||||||
|
|
||||||
|
sys.path.insert(0, os.getcwd())
|
||||||
|
|
||||||
|
# We import qutebrowser.app so all @cmdutils-register decorators are run.
|
||||||
|
import qutebrowser.app
|
||||||
|
from scripts import asciidoc2html
|
||||||
|
from qutebrowser import qutebrowser
|
||||||
|
from qutebrowser.commands import cmdutils
|
||||||
|
from qutebrowser.config import configdata
|
||||||
|
from qutebrowser.utils import utils
|
||||||
|
|
||||||
|
|
||||||
|
class UsageFormatter(argparse.HelpFormatter):
|
||||||
|
|
||||||
|
"""Patched HelpFormatter to include some asciidoc markup in the usage.
|
||||||
|
|
||||||
|
This does some horrible things, but the alternative would be to reimplement
|
||||||
|
argparse.HelpFormatter while copying 99% of the code :-/
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _format_usage(self, usage, actions, groups, _prefix):
|
||||||
|
"""Override _format_usage to not add the 'usage:' prefix."""
|
||||||
|
return super()._format_usage(usage, actions, groups, '')
|
||||||
|
|
||||||
|
def _metavar_formatter(self, action, default_metavar):
|
||||||
|
"""Override _metavar_formatter to add asciidoc markup to metavars.
|
||||||
|
|
||||||
|
Most code here is copied from Python 3.4's argparse.py.
|
||||||
|
"""
|
||||||
|
if action.metavar is not None:
|
||||||
|
result = "'{}'".format(action.metavar)
|
||||||
|
elif action.choices is not None:
|
||||||
|
choice_strs = [str(choice) for choice in action.choices]
|
||||||
|
result = '{%s}' % ','.join('*{}*'.format(e) for e in choice_strs)
|
||||||
|
else:
|
||||||
|
result = "'{}'".format(default_metavar)
|
||||||
|
|
||||||
|
def fmt(tuple_size):
|
||||||
|
"""Format the result according to the tuple size."""
|
||||||
|
if isinstance(result, tuple):
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return (result, ) * tuple_size
|
||||||
|
return fmt
|
||||||
|
|
||||||
|
def _format_actions_usage(self, actions, groups):
|
||||||
|
"""Override _format_actions_usage to add asciidoc markup to flags.
|
||||||
|
|
||||||
|
Because argparse.py's _format_actions_usage is very complex, we first
|
||||||
|
monkey-patch the option strings to include the asciidoc markup, then
|
||||||
|
run the original method, then undo the patching.
|
||||||
|
"""
|
||||||
|
old_option_strings = {}
|
||||||
|
for action in actions:
|
||||||
|
old_option_strings[action] = action.option_strings[:]
|
||||||
|
action.option_strings = ['*{}*'.format(s)
|
||||||
|
for s in action.option_strings]
|
||||||
|
ret = super()._format_actions_usage(actions, groups)
|
||||||
|
for action in actions:
|
||||||
|
action.option_strings = old_option_strings[action]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def _open_file(name, mode='w'):
|
||||||
|
"""Open a file with a preset newline/encoding mode."""
|
||||||
|
return open(name, mode, newline='\n', encoding='utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
def _get_cmd_syntax(_name, cmd):
|
||||||
|
"""Get the command syntax for a command.
|
||||||
|
|
||||||
|
We monkey-patch the parser's formatter_class here to use our UsageFormatter
|
||||||
|
which adds some asciidoc markup.
|
||||||
|
"""
|
||||||
|
old_fmt_class = cmd.parser.formatter_class
|
||||||
|
cmd.parser.formatter_class = UsageFormatter
|
||||||
|
usage = cmd.parser.format_usage().rstrip()
|
||||||
|
cmd.parser.formatter_class = old_fmt_class
|
||||||
|
return usage
|
||||||
|
|
||||||
|
|
||||||
|
def _get_command_quickref(cmds):
|
||||||
|
"""Generate the command quick reference."""
|
||||||
|
out = []
|
||||||
|
out.append('[options="header",width="75%",cols="25%,75%"]')
|
||||||
|
out.append('|==============')
|
||||||
|
out.append('|Command|Description')
|
||||||
|
for name, cmd in cmds:
|
||||||
|
desc = inspect.getdoc(cmd.handler).splitlines()[0]
|
||||||
|
out.append('|<<{},{}>>|{}'.format(name, name, desc))
|
||||||
|
out.append('|==============')
|
||||||
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_setting_quickref():
|
||||||
|
"""Generate the settings quick reference."""
|
||||||
|
out = []
|
||||||
|
for sectname, sect in configdata.DATA.items():
|
||||||
|
if not getattr(sect, 'descriptions'):
|
||||||
|
continue
|
||||||
|
out.append("")
|
||||||
|
out.append(".Quick reference for section ``{}''".format(sectname))
|
||||||
|
out.append('[options="header",width="75%",cols="25%,75%"]')
|
||||||
|
out.append('|==============')
|
||||||
|
out.append('|Setting|Description')
|
||||||
|
for optname, _option in sect.items():
|
||||||
|
desc = sect.descriptions[optname].splitlines()[0]
|
||||||
|
out.append('|<<{}-{},{}>>|{}'.format(
|
||||||
|
sectname, optname, optname, desc))
|
||||||
|
out.append('|==============')
|
||||||
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_command_doc(name, cmd):
|
||||||
|
"""Generate the documentation for a command."""
|
||||||
|
output = ['[[{}]]'.format(name)]
|
||||||
|
output += ['=== {}'.format(name)]
|
||||||
|
syntax = _get_cmd_syntax(name, cmd)
|
||||||
|
if syntax != name:
|
||||||
|
output.append('Syntax: +:{}+'.format(syntax))
|
||||||
|
output.append("")
|
||||||
|
parser = utils.DocstringParser(cmd.handler)
|
||||||
|
output.append(parser.short_desc)
|
||||||
|
if parser.long_desc:
|
||||||
|
output.append("")
|
||||||
|
output.append(parser.long_desc)
|
||||||
|
|
||||||
|
if cmd.pos_args:
|
||||||
|
output.append("")
|
||||||
|
output.append("==== positional arguments")
|
||||||
|
for arg, name in cmd.pos_args:
|
||||||
|
try:
|
||||||
|
output.append("* +'{}'+: {}".format(name,
|
||||||
|
parser.arg_descs[arg]))
|
||||||
|
except KeyError as e:
|
||||||
|
raise KeyError("No description for arg {} of command "
|
||||||
|
"'{}'!".format(e, cmd.name))
|
||||||
|
|
||||||
|
if cmd.opt_args:
|
||||||
|
output.append("")
|
||||||
|
output.append("==== optional arguments")
|
||||||
|
for arg, (long_flag, short_flag) in cmd.opt_args.items():
|
||||||
|
try:
|
||||||
|
output.append('* +*{}*+, +*{}*+: {}'.format(
|
||||||
|
short_flag, long_flag, parser.arg_descs[arg]))
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError("No description for arg {} of command "
|
||||||
|
"'{}'!".format(e, cmd.name))
|
||||||
|
|
||||||
|
if cmd.has_count:
|
||||||
|
output.append("")
|
||||||
|
output.append("==== count")
|
||||||
|
output.append(parser.arg_descs['count'])
|
||||||
|
|
||||||
|
output.append("")
|
||||||
|
output.append("")
|
||||||
|
return '\n'.join(output)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_action_metavar(action):
|
||||||
|
"""Get the metavar to display for an argparse action."""
|
||||||
|
if action.metavar is not None:
|
||||||
|
return "'{}'".format(action.metavar)
|
||||||
|
elif action.choices is not None:
|
||||||
|
choices = ','.join(map(str, action.choices))
|
||||||
|
return "'{{{}}}'".format(choices)
|
||||||
|
else:
|
||||||
|
return "'{}'".format(action.dest.upper())
|
||||||
|
|
||||||
|
|
||||||
|
def _format_action_args(action):
|
||||||
|
"""Get an argument string based on an argparse action."""
|
||||||
|
if action.nargs is None:
|
||||||
|
return _get_action_metavar(action)
|
||||||
|
elif action.nargs == '?':
|
||||||
|
return '[{}]'.format(_get_action_metavar(action))
|
||||||
|
elif action.nargs == '*':
|
||||||
|
return '[{mv} [{mv} ...]]'.format(mv=_get_action_metavar(action))
|
||||||
|
elif action.nargs == '+':
|
||||||
|
return '{mv} [{mv} ...]'.format(mv=_get_action_metavar(action))
|
||||||
|
elif action.nargs == '...':
|
||||||
|
return '...'
|
||||||
|
else:
|
||||||
|
return ' '.join([_get_action_metavar(action)] * action.nargs)
|
||||||
|
|
||||||
|
|
||||||
|
def _format_action(action):
|
||||||
|
"""Get an invocation string/help from an argparse action."""
|
||||||
|
if not action.option_strings:
|
||||||
|
invocation = '*{}*::'.format(_get_action_metavar(action))
|
||||||
|
else:
|
||||||
|
parts = []
|
||||||
|
if action.nargs == 0:
|
||||||
|
# Doesn't take a value, so the syntax is -s, --long
|
||||||
|
parts += ['*{}*'.format(s) for s in action.option_strings]
|
||||||
|
else:
|
||||||
|
# Takes a value, so the syntax is -s ARGS or --long ARGS.
|
||||||
|
args_string = _format_action_args(action)
|
||||||
|
for opt in action.option_strings:
|
||||||
|
parts.append('*{}* {}'.format(opt, args_string))
|
||||||
|
invocation = ', '.join(parts) + '::'
|
||||||
|
return '{}\n {}\n'.format(invocation, action.help)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_commands(filename):
|
||||||
|
"""Generate the complete commands section."""
|
||||||
|
with _open_file(filename) as f:
|
||||||
|
f.write("= Commands\n")
|
||||||
|
normal_cmds = []
|
||||||
|
hidden_cmds = []
|
||||||
|
debug_cmds = []
|
||||||
|
for name, cmd in cmdutils.cmd_dict.items():
|
||||||
|
if name in cmdutils.aliases:
|
||||||
|
continue
|
||||||
|
if cmd.hide:
|
||||||
|
hidden_cmds.append((name, cmd))
|
||||||
|
elif cmd.debug:
|
||||||
|
debug_cmds.append((name, cmd))
|
||||||
|
else:
|
||||||
|
normal_cmds.append((name, cmd))
|
||||||
|
normal_cmds.sort()
|
||||||
|
hidden_cmds.sort()
|
||||||
|
debug_cmds.sort()
|
||||||
|
f.write("\n")
|
||||||
|
f.write("== Normal commands\n")
|
||||||
|
f.write(".Quick reference\n")
|
||||||
|
f.write(_get_command_quickref(normal_cmds) + '\n')
|
||||||
|
for name, cmd in normal_cmds:
|
||||||
|
f.write(_get_command_doc(name, cmd))
|
||||||
|
f.write("\n")
|
||||||
|
f.write("== Hidden commands\n")
|
||||||
|
f.write(".Quick reference\n")
|
||||||
|
f.write(_get_command_quickref(hidden_cmds) + '\n')
|
||||||
|
for name, cmd in hidden_cmds:
|
||||||
|
f.write(_get_command_doc(name, cmd))
|
||||||
|
f.write("\n")
|
||||||
|
f.write("== Debugging commands\n")
|
||||||
|
f.write("These commands are mainly intended for debugging. They are "
|
||||||
|
"hidden if qutebrowser was started without the "
|
||||||
|
"`--debug`-flag.\n")
|
||||||
|
f.write("\n")
|
||||||
|
f.write(".Quick reference\n")
|
||||||
|
f.write(_get_command_quickref(debug_cmds) + '\n')
|
||||||
|
for name, cmd in debug_cmds:
|
||||||
|
f.write(_get_command_doc(name, cmd))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_settings(filename):
|
||||||
|
"""Generate the complete settings section."""
|
||||||
|
with _open_file(filename) as f:
|
||||||
|
f.write("= Settings\n")
|
||||||
|
f.write(_get_setting_quickref() + "\n")
|
||||||
|
for sectname, sect in configdata.DATA.items():
|
||||||
|
f.write("\n")
|
||||||
|
f.write("== {}".format(sectname) + "\n")
|
||||||
|
f.write(configdata.SECTION_DESC[sectname] + "\n")
|
||||||
|
if not getattr(sect, 'descriptions'):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for optname, option in sect.items():
|
||||||
|
f.write("\n")
|
||||||
|
f.write('[[{}-{}]]'.format(sectname, optname) + "\n")
|
||||||
|
f.write("=== {}".format(optname) + "\n")
|
||||||
|
f.write(sect.descriptions[optname] + "\n")
|
||||||
|
f.write("\n")
|
||||||
|
valid_values = option.typ.valid_values
|
||||||
|
if valid_values is not None:
|
||||||
|
f.write("Valid values:\n")
|
||||||
|
f.write("\n")
|
||||||
|
for val in valid_values:
|
||||||
|
try:
|
||||||
|
desc = valid_values.descriptions[val]
|
||||||
|
f.write(" * +{}+: {}".format(val, desc) + "\n")
|
||||||
|
except KeyError:
|
||||||
|
f.write(" * +{}+".format(val) + "\n")
|
||||||
|
f.write("\n")
|
||||||
|
if option.default():
|
||||||
|
f.write("Default: +pass:[{}]+\n".format(html.escape(
|
||||||
|
option.default())))
|
||||||
|
else:
|
||||||
|
f.write("Default: empty\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _get_authors():
|
||||||
|
"""Get a list of authors based on git commit logs."""
|
||||||
|
commits = subprocess.check_output(['git', 'log', '--format=%aN'])
|
||||||
|
cnt = collections.Counter(commits.decode('utf-8').splitlines())
|
||||||
|
return reversed(sorted(cnt, key=lambda k: cnt[k]))
|
||||||
|
|
||||||
|
|
||||||
|
def _format_block(filename, what, data):
|
||||||
|
"""Format a block in a file.
|
||||||
|
|
||||||
|
The block is delimited by markers like these:
|
||||||
|
// QUTE_*_START
|
||||||
|
...
|
||||||
|
// QUTE_*_END
|
||||||
|
|
||||||
|
The * part is the part which should be given as 'what'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: The file to change.
|
||||||
|
what: What to change (authors, options, etc.)
|
||||||
|
data; A list of strings which is the new data.
|
||||||
|
"""
|
||||||
|
what = what.upper()
|
||||||
|
oshandle, tmpname = tempfile.mkstemp()
|
||||||
|
try:
|
||||||
|
with _open_file(filename, mode='r') as infile, \
|
||||||
|
_open_file(oshandle, mode='w') as temp:
|
||||||
|
found_start = False
|
||||||
|
found_end = False
|
||||||
|
for line in infile:
|
||||||
|
if line.strip() == '// QUTE_{}_START'.format(what):
|
||||||
|
temp.write(line)
|
||||||
|
temp.write(''.join(data))
|
||||||
|
found_start = True
|
||||||
|
elif line.strip() == '// QUTE_{}_END'.format(what.upper()):
|
||||||
|
temp.write(line)
|
||||||
|
found_end = True
|
||||||
|
elif (not found_start) or found_end:
|
||||||
|
temp.write(line)
|
||||||
|
if not found_start:
|
||||||
|
raise Exception("Marker '// QUTE_{}_START' not found in "
|
||||||
|
"'{}'!".format(what, filename))
|
||||||
|
elif not found_end:
|
||||||
|
raise Exception("Marker '// QUTE_{}_END' not found in "
|
||||||
|
"'{}'!".format(what, filename))
|
||||||
|
except: # pylint: disable=bare-except
|
||||||
|
os.remove(tmpname)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
os.remove(filename)
|
||||||
|
shutil.move(tmpname, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def regenerate_authors(filename):
|
||||||
|
"""Re-generate the authors inside README based on the commits made."""
|
||||||
|
data = ['* {}\n'.format(author) for author in _get_authors()]
|
||||||
|
_format_block(filename, 'authors', data)
|
||||||
|
|
||||||
|
|
||||||
|
def regenerate_manpage(filename):
|
||||||
|
"""Update manpage OPTIONS using an argparse parser."""
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
parser = qutebrowser.get_argparser()
|
||||||
|
groups = []
|
||||||
|
# positionals, optionals and user-defined groups
|
||||||
|
for group in parser._action_groups:
|
||||||
|
groupdata = []
|
||||||
|
groupdata.append('=== {}'.format(group.title))
|
||||||
|
if group.description is not None:
|
||||||
|
groupdata.append(group.description)
|
||||||
|
for action in group._group_actions:
|
||||||
|
groupdata.append(_format_action(action))
|
||||||
|
groups.append('\n'.join(groupdata))
|
||||||
|
options = '\n'.join(groups)
|
||||||
|
# epilog
|
||||||
|
if parser.epilog is not None:
|
||||||
|
options.append(parser.epilog)
|
||||||
|
_format_block(filename, 'options', options)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Regenerate all documentation."""
|
||||||
|
print("{}Generating asciidoc files...{}".format(
|
||||||
|
col.Fore.CYAN, col.Fore.RESET))
|
||||||
|
regenerate_manpage('doc/qutebrowser.1.asciidoc')
|
||||||
|
generate_settings('doc/help/settings.asciidoc')
|
||||||
|
generate_commands('doc/help/commands.asciidoc')
|
||||||
|
regenerate_authors('README.asciidoc')
|
||||||
|
if '--html' in sys.argv:
|
||||||
|
asciidoc2html.main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
71
scripts/utils.py
Normal file
71
scripts/utils.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2014 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/>.
|
||||||
|
|
||||||
|
"""Utility functions for scripts."""
|
||||||
|
|
||||||
|
|
||||||
|
use_color = True
|
||||||
|
|
||||||
|
|
||||||
|
fg_colors = {
|
||||||
|
'black': 30,
|
||||||
|
'red': 31,
|
||||||
|
'green': 32,
|
||||||
|
'yellow': 33,
|
||||||
|
'blue': 34,
|
||||||
|
'magenta': 35,
|
||||||
|
'cyan': 36,
|
||||||
|
'white': 37,
|
||||||
|
'reset': 39,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bg_colors = {name: col + 10 for name, col in fg_colors.items()}
|
||||||
|
|
||||||
|
|
||||||
|
term_attributes = {
|
||||||
|
'bright': 1,
|
||||||
|
'dim': 2,
|
||||||
|
'normal': 22,
|
||||||
|
'reset': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _esc(code):
|
||||||
|
return '\033[{}m'.format(code)
|
||||||
|
|
||||||
|
|
||||||
|
def print_col(text, color):
|
||||||
|
"""Print a colorized text."""
|
||||||
|
if use_color:
|
||||||
|
fg = _esc(fg_colors[color.lower()])
|
||||||
|
reset = _esc(fg_colors['reset'])
|
||||||
|
print(''.join([fg, text, reset]))
|
||||||
|
else:
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
|
||||||
|
def print_bold(text):
|
||||||
|
"""Print a bold text."""
|
||||||
|
if use_color:
|
||||||
|
bold = _esc(term_attributes['bright'])
|
||||||
|
reset = _esc(term_attributes['reset'])
|
||||||
|
print(''.join([bold, text, reset]))
|
||||||
|
else:
|
||||||
|
print(text)
|
1
setup.py
1
setup.py
@ -42,7 +42,6 @@ try:
|
|||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
packages=setuptools.find_packages(exclude=['qutebrowser.test']),
|
packages=setuptools.find_packages(exclude=['qutebrowser.test']),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
package_data={'qutebrowser': ['html/*', 'git-commit-id']},
|
|
||||||
entry_points={'gui_scripts':
|
entry_points={'gui_scripts':
|
||||||
['qutebrowser = qutebrowser.qutebrowser:main']},
|
['qutebrowser = qutebrowser.qutebrowser:main']},
|
||||||
test_suite='qutebrowser.test',
|
test_suite='qutebrowser.test',
|
||||||
|
Loading…
Reference in New Issue
Block a user