Compare commits

...

895 Commits

Author SHA1 Message Date
Florian Bruhin
acf54c5cbe Release v0.7.0 2016-06-10 15:18:56 +02:00
Florian Bruhin
eb32056c26 Merge branch 'V155-master' 2016-06-10 15:17:51 +02:00
Florian Bruhin
4255c495a7 Merge branch 'master' of https://github.com/V155/qutebrowser into V155-master 2016-06-10 15:15:30 +02:00
Florian Bruhin
a95b8eed9b tests: Add config for v0.7.0 2016-06-10 15:14:22 +02:00
Florian Bruhin
823b7e2900 Update changelog 2016-06-10 14:47:41 +02:00
Florian Bruhin
0b4f4a7f23 Improve manual history test 2016-06-10 14:41:08 +02:00
Florian Bruhin
ff7d6250c6 Improve original URL handling
When setting self._orig_url by hand, sometimes on redirects the wrong
URL was picked up.
2016-06-10 14:41:08 +02:00
Florian Bruhin
66938ed44b Full redirect support for history
When a redirect occurs, the item is saved in history with a -r suffix
now. When opening qutebrowser that's picked up and the item is hidden
from completion.
2016-06-10 14:40:42 +02:00
Florian Bruhin
66aae2e5ce Revert "travis: Reactivate OS X tests"
This reverts commit af798c7450.

Currently installing Qt is broken because 5.6.1 was released
2016-06-10 13:48:17 +02:00
Florian Bruhin
e0acda403f Add an end-to-end test for :history-clear 2016-06-10 13:20:58 +02:00
Florian Bruhin
a0fd096038 Update CONTRIBUTING 2016-06-10 13:11:53 +02:00
Florian Bruhin
0a25bb508a Fix lint 2016-06-10 12:21:48 +02:00
Florian Bruhin
9298fa697b Allow to pass atime to WebHistory.add_url
This makes a lot of tests easier and/or more exact.
2016-06-10 12:12:29 +02:00
Florian Bruhin
7511bc1fe5 tests: 100% coverage for browser.history 2016-06-10 12:01:14 +02:00
Florian Bruhin
14a04f1535 Fix lint 2016-06-09 22:03:35 +02:00
Florian Bruhin
185b4bc18b Add more tests for browser.history 2016-06-09 21:14:04 +02:00
Florian Bruhin
f691439149 Make WebHistory parent optional 2016-06-09 21:14:04 +02:00
Florian Bruhin
1d1c71f919 Strip of trailing spaces for history entries 2016-06-09 21:14:04 +02:00
Florian Bruhin
b8fba0a138 Add some documentation to WebHistory 2016-06-09 21:14:04 +02:00
Florian Bruhin
e9e6da6510 Remove WebHistory.__getitem__ 2016-06-09 21:14:04 +02:00
Florian Bruhin
d3dd6d2f77 Add hypothesis test for history.Entry.from_str 2016-06-09 21:14:04 +02:00
Florian Bruhin
33f01d8375 Fix lint 2016-06-09 21:14:03 +02:00
Florian Bruhin
d5abfa3d0d Check if original URL is valid 2016-06-09 21:14:03 +02:00
Florian Bruhin
9510af9912 Use QUrl with QWebHistory
Now that we have our own history implementation, we get the URLs as QUrl
and not as string - so no point in converting them to string and back.
2016-06-09 21:14:03 +02:00
Florian Bruhin
5f25ce69ec Add some tests for browser.history 2016-06-09 21:14:03 +02:00
Florian Bruhin
6304565a9a Refactor browser.history
- Rename HistoryEntry to Entry
- Move history line parsing from WebHistory.async_read to Entry.from_str
- Improve errors for invalid history lines
- Pass history directory/filename from the outside to WebHistory
- Clear temp history after reading it when async_read is done
2016-06-09 21:14:00 +02:00
Florian Bruhin
446abefba5 Make sure there is no temp history without datadir 2016-06-09 17:44:04 +02:00
Florian Bruhin
789b9c9308 tests: Yield fake_save_manager from fixture 2016-06-09 17:44:04 +02:00
Florian Bruhin
75669dd21b tests: Move fake_save_manager to helper.fixtures 2016-06-09 17:44:04 +02:00
Florian Bruhin
ef2b3cd6d9 tests.helpers: Clean up imports 2016-06-09 17:44:04 +02:00
Florian Bruhin
346e59880e Add a manual test for visited history 2016-06-09 17:44:04 +02:00
Florian Bruhin
34ba44b0a3 Separate WebHistoryInterface from WebHistory 2016-06-09 17:44:04 +02:00
Florian Bruhin
089131c79d Improve error message when clicking invalid link 2016-06-09 17:43:33 +02:00
Florian Bruhin
86632804a9 behaviour -> behavior for consistency 2016-06-09 17:30:14 +02:00
Florian Bruhin
726ae50251 Clean up find-implementation in test_hints_html 2016-06-09 17:16:00 +02:00
Florian Bruhin
dbbc4b72ed Use quteproc.set_setting in test_hints_html 2016-06-09 17:15:34 +02:00
Florian Bruhin
ab8a2f7147 Fix lint 2016-06-09 17:12:23 +02:00
Florian Bruhin
035526848e Add a hints -> find-implementation setting
This makes it possible to switch to an alternative implementation if
there are weird issues like #1568. Some users might also prefer the
slightly better performance over more accurate hints.
2016-06-09 17:00:08 +02:00
Florian Bruhin
2d54c927e3 Fix urlutils tests on Qt 5.6.1
For some reason the behaviour of QHostAddress("31c3").isValid() changed
with Qt 5.6.1: https://bugreports.qt.io/browse/QTBUG-53983

This causes the test to fail because Qt thinks this is a valid IP, so we
think it's a valid URL.
2016-06-09 15:16:49 +02:00
Florian Bruhin
c390c797b2 tests: Ignore QtWebKit image format warning
In "Cancelling a MHTML download (issue 1535)" (downloads.feature) we
load an invalid page as a fake "image". With Qt 5.6.1, we get this
warning then.
2016-06-09 14:48:38 +02:00
Florian Bruhin
9e1d20017c tests: Mark "History with an error" as flaky
For some reason, sometimes on Travis the history file we read is empty.
I have no idea why though, as we successfully wait until ":save saved
history" is logged, and that is working fine.

Let's just mark the test as flaky for now so we can move on.
2016-06-09 11:17:18 +02:00
Florian Bruhin
288744c8d0 Use BDD test for link with spaces issue
Clicking actually works fine without the strip() as _resolve_url is
never called in that case, so we need to do something which actually
needs the URL as well.
2016-06-09 10:50:08 +02:00
Florian Bruhin
10918d2a3a Update docs 2016-06-09 10:50:08 +02:00
Florian Bruhin
281d2a427b Merge branch 'stripurl' of https://github.com/Konubinix/qutebrowser into Konubinix-stripurl 2016-06-09 10:38:35 +02:00
Florian Bruhin
aae205030c Add missing import 2016-06-09 10:29:16 +02:00
Florian Bruhin
d6926f0622 Fix expected data in AppendLineParser test
There were two different issues here:

- `\n` rather than `os.linesep` was used, which caused the "generated"
  file to have less data in it than expected
- A final `os.linesep` (or `\n`) was missing, but that was cancelled out
  by a off-by-one error when slicing, so wasn't an issue until we tried
  with \r\n endings.
2016-06-09 10:26:09 +02:00
Samuel Loury
a69610077e Add a test about a href with spaces around 2016-06-09 08:36:18 +02:00
Samuel Loury
8369140b72 Strip the url before processing it
This won't hurt and will help with some poorly formatted sites
including blank spaces around the url (e.g. the Previous link in a
dashboard make with CDash 2.0.2).
2016-06-09 08:23:53 +02:00
Florian Bruhin
c5c022226f Merge branch 'EliteTK-lowercase-toggle' 2016-06-08 22:40:05 +02:00
Florian Bruhin
5563552c13 Update docs 2016-06-08 22:39:54 +02:00
Florian Bruhin
4fe6935003 Merge branch 'lowercase-toggle' of https://github.com/EliteTK/qutebrowser into EliteTK-lowercase-toggle 2016-06-08 22:39:19 +02:00
Florian Bruhin
01a622bf02 history tests: Sync with :save better 2016-06-08 22:32:39 +02:00
Florian Bruhin
d40e8e1c2f tests: Set auto-save-interval to 0
Otherwise history tests could fail because waiting for
"Saved to *history" waited for a previous line, not the newest one.

It also doesn't make any sense to save stuff anyways.
2016-06-08 21:54:44 +02:00
Tomasz Kramkowski
a98cd9a528 Change relevant tests to work with new toggle behaviour 2016-06-08 19:37:13 +01:00
Tomasz Kramkowski
573c25073d Fix toggle config using capitalised boolean value. 2016-06-08 19:06:00 +01:00
Florian Bruhin
e08c6cb059 Don't save the original URL for redirected pages
See #1345
2016-06-08 17:15:08 +02:00
Florian Bruhin
263b5680c7 Improve :history-clear docstring 2016-06-08 16:49:07 +02:00
Florian Bruhin
483a5f8103 Fix sharing of cookie jars with private browsing
Fixes #1219
2016-06-08 16:35:43 +02:00
Florian Bruhin
530721522a Fix lint 2016-06-08 15:38:52 +02:00
Florian Bruhin
34f7b196b7 Merge branch 'toofar-history-title' 2016-06-08 15:28:10 +02:00
Florian Bruhin
1b2c6d30c5 Fix typo 2016-06-08 15:22:21 +02:00
Florian Bruhin
049510ea8f Update changelog 2016-06-08 15:17:27 +02:00
Florian Bruhin
b6e534f9b8 Add BDD tests for new history code 2016-06-08 15:15:54 +02:00
Florian Bruhin
f17d4388fd Use fully encoded URL for history
Otherwise e.g. spaces are literal spaces instead of %20 and the history
breaks.
2016-06-08 15:15:54 +02:00
Florian Bruhin
8c9bd95123 test webserver: Whitelist some more HTTP statuses
- HTTP 301 for redirect-to
- HTTP 404 for status/404
2016-06-08 15:15:54 +02:00
Florian Bruhin
e1fd9fc408 Also add title to history for redirected URLs 2016-06-08 13:30:40 +02:00
Florian Bruhin
9beb68bb5c Ignore symlinks when recompiling for :restart
Otherwise broken symlinks (*cough* emacs *cough*) cause this to fail
with FileNotFoundError.
2016-06-08 12:56:37 +02:00
Florian Bruhin
79e0aa2418 Merge branch 'history-title' of https://github.com/toofar/qutebrowser into toofar-history-title 2016-06-08 12:53:07 +02:00
Florian Bruhin
a5a4c17b1f Merge branch 'EliteTK-history-clear' 2016-06-08 11:40:00 +02:00
Florian Bruhin
af653e7cce Update docs 2016-06-08 11:33:37 +02:00
Florian Bruhin
a870a2888f Improve LineParser clear tests 2016-06-08 11:33:37 +02:00
Florian Bruhin
44f626134b Use real files for lineparser tests 2016-06-08 11:33:37 +02:00
Florian Bruhin
1d51d63f0c LineParser tests: Clear self._data in _open 2016-06-08 10:36:46 +02:00
Florian Bruhin
c686008325 LineParser: Don't write anything without data 2016-06-08 10:36:30 +02:00
Florian Bruhin
b09fe8dca0 Document LineParserMixin attributes 2016-06-08 10:22:55 +02:00
Florian Bruhin
33bcced4a2 Add a test for AppendLineParser.clear 2016-06-08 10:11:59 +02:00
Florian Bruhin
cca26f0f13 Fix lint 2016-06-08 09:45:05 +02:00
Florian Bruhin
4b78b22b9a Remove unnecessary method
This is already inherited from LineParser
2016-06-08 09:30:18 +02:00
Florian Bruhin
21d53b0265 Merge branch 'history-clear' of https://github.com/EliteTK/qutebrowser into EliteTK-history-clear 2016-06-08 09:28:36 +02:00
Florian Bruhin
bd111c2ab1 Fix lint 2016-06-08 07:16:27 +02:00
Florian Bruhin
016c102e57 Fix lint 2016-06-08 00:05:57 +02:00
Florian Bruhin
278093afae Add requirements-qutebrowser.txt-raw 2016-06-08 00:03:07 +02:00
Florian Bruhin
4bae659fbe Re-add requests to pylint envs
This is needed because there's a script using requests
2016-06-08 00:02:43 +02:00
Florian Bruhin
cfb4ebb312 Add missing replace to requirements-pylint.txt-raw 2016-06-07 23:47:10 +02:00
Florian Bruhin
f6473620c5 Fix recompile_requirements.py without arguments 2016-06-07 23:44:50 +02:00
Florian Bruhin
55da4ffc03 Also generate root requirements 2016-06-07 23:44:22 +02:00
Florian Bruhin
164d7bea4b recompile_requirements: Add replace command 2016-06-07 23:40:00 +02:00
Florian Bruhin
19037e8b75 Remove old .in requirements file 2016-06-07 23:32:35 +02:00
Florian Bruhin
81ccaff9c6 requirements: Update filter for Flask 2016-06-07 23:26:04 +02:00
Florian Bruhin
0819ed36f0 Update requirements README.md 2016-06-07 23:21:50 +02:00
Florian Bruhin
1c6b4da706 Use #@ as chars 2016-06-07 23:21:50 +02:00
Florian Bruhin
6249436bde Rename raw-requirements files
This hopefully means they're not picked up on requires.io.
2016-06-07 23:21:50 +02:00
Florian Bruhin
7571040316 Also ignore MarkupSafe dependency 2016-06-07 23:10:33 +02:00
Florian Bruhin
34a6451806 Add special #! commands to recompile_requirements 2016-06-07 23:08:14 +02:00
Florian Bruhin
136cb19814 reqs: Comment out ignored dependencies 2016-06-07 22:53:17 +02:00
Florian Bruhin
1a26c8c61f Add a script to compile requirement files 2016-06-07 22:45:59 +02:00
Florian Bruhin
1104a731a5 Fix filtering of all hints in number mode
Fixes #1559
2016-06-07 18:34:46 +02:00
Florian Bruhin
9880f5bd5f Handle FileNotFoundError in ipc.listen
Fixes #1530
2016-06-07 16:48:43 +02:00
Florian Bruhin
b972acf20c Merge branch 'hints' 2016-06-07 15:43:25 +02:00
Florian Bruhin
7014219a86 Update docs 2016-06-07 15:43:17 +02:00
Florian Bruhin
299fea830e Simplify some bool conditions 2016-06-07 15:42:59 +02:00
Florian Bruhin
5ddc57566d Refactor HintManager.filter_hints 2016-06-07 15:14:57 +02:00
Florian Bruhin
db0a67766d Ignore hints -> scatter for number hints
See https://github.com/The-Compiler/qutebrowser/issues/308#issuecomment-167091450
2016-06-07 15:13:30 +02:00
Florian Bruhin
20636a2343 Get normal parser via objreg 2016-06-07 14:30:46 +02:00
Florian Bruhin
e906178c7d Add changelog entry for multi-word matching 2016-06-07 14:28:36 +02:00
Jakub Klinkovský
de66b68b75 Fix problems reported by pylint 2016-06-07 14:27:37 +02:00
Jakub Klinkovský
1b32444256 Rename variables for consistency with other code and docstrings
As described in [1], the naming of some variables has become
inconsistent with the original code and even docstrings. This commit
corrects some of these problems, with the following terminology:

 - hint text: informative message (see HINT_TEXTS)
 - hint string: the text displayed on the hint (as instance of str)
 - hint label: the element representing the hint, added to the DOM
 - hint: too abstract, sensibly used only in docstrings to refer to the
   "visual result"

This commit amends b89e0f8803 and
8873aba09f.

[1] https://github.com/The-Compiler/qutebrowser/pull/1178#issuecomment-178795190
2016-06-07 14:27:06 +02:00
Florian Bruhin
ca88b7a1cf Add test for multi-word matching 2016-06-07 14:24:53 +02:00
Jakub Klinkovský
e4d5b550a3 Filter hints using a multi-word matching
(addresses https://github.com/The-Compiler/qutebrowser/issues/674#issuecomment-165101219 )
2016-06-07 14:24:17 +02:00
Florian Bruhin
d3eec49b6e Add automated test for #1186 2016-06-07 14:15:32 +02:00
Florian Bruhin
8ff53fdb7f Add an automated test for #576
This is now testable easily since hints are renumbered
2016-06-07 14:08:25 +02:00
Florian Bruhin
222ab6f75e Add changelog entry for #308 2016-06-07 13:58:22 +02:00
Florian Bruhin
efddd64d56 Add test for #308 2016-06-07 13:52:17 +02:00
Florian Bruhin
7f3991a8ca Set _filterstr in HintManager
This is also fixed in
fd12c7567e

But we need the fix in to make tests run.
2016-06-07 13:23:51 +02:00
Jakub Klinkovský
82da79d05a Renumber hints when filtering in numeric mode
(addresses #308)
2016-06-07 13:07:56 +02:00
Florian Bruhin
457913b2ec Add changelog for #576 2016-06-07 11:59:33 +02:00
Jakub Klinkovský
9d20280573 Save the filter string in HintManager
This allows restoring the filter during rapid hinting in numeric mode (addresses #576)
2016-06-07 11:57:31 +02:00
Florian Bruhin
335c6783ce Add a manual test page for #576 2016-06-07 11:57:01 +02:00
Florian Bruhin
cbc866d86d Add changelog entry for #1186 fix 2016-06-07 11:55:17 +02:00
Florian Bruhin
01c4c1717f Add manual test file for #1186 2016-06-07 11:55:17 +02:00
Jakub Klinkovský
3690ead5e0 Fix backspace handling in numeric hint mode
addresses #1186
2016-06-07 11:37:47 +02:00
Florian Bruhin
802258d70a Add hints -> auto-follow-timeout to CHANGELOG 2016-06-07 11:07:29 +02:00
Florian Bruhin
82d7c33b29 Add tests for hints -> auto-follow-timeout 2016-06-07 11:06:39 +02:00
Jakub Klinkovský
87faafd910 Fixed type of auto-follow-timeout option 2016-06-07 10:30:10 +02:00
Jakub Klinkovský
6e494605dd Add timeout after auto-followed hint 2016-06-07 10:30:08 +02:00
Florian Bruhin
b759f481c4 test requirements: Update decorator to 4.0.10
- Improved the documentation thanks to Tony Goodchild (zearin) who also
  provided a much better CSS than the one I was using.
2016-06-07 08:24:53 +02:00
Florian Bruhin
5004a571c4 test requirements: Update CherryPy to 6.0.1
* Correct typos in ``@cherrypy.expose`` decorators.
2016-06-07 08:24:12 +02:00
Florian Bruhin
bdef8c4e3a Fix lint 2016-06-07 08:23:35 +02:00
Florian Bruhin
e8123bb68a Fix pdfjs version parsing for never versions 2016-06-07 07:42:34 +02:00
Florian Bruhin
64fc1a3277 Revert "test requirements: Add pdbpp"
This reverts commit c43bfffd5c.
It uses setuptools_scm which causes... problems.
2016-06-07 06:21:54 +02:00
Florian Bruhin
9787e54238 Revert "test requirements: Also add pdbpp dependencies"
This reverts commit d77b9bff49.
2016-06-07 06:21:50 +02:00
Florian Bruhin
fd8286d4eb Fix BDD runtest_makereport hook on non-BDD tests 2016-06-06 23:37:26 +02:00
Florian Bruhin
a107c644c6 Merge branch 'lahwaacz-iframe_tests' 2016-06-06 23:32:56 +02:00
Florian Bruhin
6b24d69f5f Regenerate authors 2016-06-06 23:32:48 +02:00
Florian Bruhin
b635d10836 Merge branch 'iframe_tests' of https://github.com/lahwaacz/qutebrowser into lahwaacz-iframe_tests 2016-06-06 23:32:20 +02:00
Florian Bruhin
d77b9bff49 test requirements: Also add pdbpp dependencies 2016-06-06 23:22:28 +02:00
Florian Bruhin
c43bfffd5c test requirements: Add pdbpp
pdb sometimes segfaults for some reason - pdbpp works fine and gives us
nicer debugging.
2016-06-06 23:20:44 +02:00
Florian Bruhin
a9f27e3247 bdd: Turn off colors with --color=no 2016-06-06 23:19:14 +02:00
Florian Bruhin
b0df87842e Show BDD scenario on failed tests
See #1552
2016-06-06 23:09:19 +02:00
Florian Bruhin
ef82076819 Ignore pdbpp ResourceWarning
E       Failed: Logged unexpected errors:
E
E       22:30:18 WARNING  py.warnings pdb:<module>:4 /home/florian/proj/qutebrowser/git/.tox/py35/lib/python3.5/site-packages/_pdbpp_path_hack/pdb.py:4: ResourceWarning: unclosed file <_io.TextIOWrapper name='/home/florian/proj/qutebrowser/git/.tox/py35/lib/python3.5/site-packages/pdb.py' mode='r' encoding='UTF-8'>
E         os.path.dirname(os.path.dirname(__file__)), 'pdb.py')).read(), os.path.join(
2016-06-06 22:37:29 +02:00
Florian Bruhin
d2b1682915 Add #1514 to tests/manual/hints/other.html 2016-06-06 21:24:02 +02:00
Jakub Klinkovský
b1997c1e00 tests: add missing wait in "Scenario: Opening a link with specific target frame in a new tab" 2016-06-06 18:26:04 +02:00
Jakub Klinkovský
199f0aeb76 tests: un-xfail some iframe tests (#1525)
The given reason (clicking an iframe to get a focus) applies to only one
test, the others are either stable or flaky for a different reason.
2016-06-06 17:34:22 +02:00
Florian Bruhin
a5f12430ad pylint reqs: Update pylint/astroid to 1.4.6/1.5.6
pylint:

* config files with BOM markers can now be read.
* epylint.py_run does not crash on big files, using .communicate()
  instead of .wait()

astroid:

* Fix a crash which occurred when the class of a namedtuple could not be
  inferred.
* Functional form of enums support accessing values through __call__.
* Brain tips for the ssl library.
2016-06-06 17:14:59 +02:00
Florian Bruhin
d91f4e13d0 Fix long line 2016-06-06 17:13:56 +02:00
Florian Bruhin
009fbe8213 Clean up CommandRunner.parse 2016-06-06 17:10:29 +02:00
Florian Bruhin
3e22f64a20 Get tab indicator fix from #697 2016-06-06 16:49:29 +02:00
Florian Bruhin
7651b3a9f5 Fix userscript CommandRunner 2016-06-06 16:39:10 +02:00
Florian Bruhin
35c7e2b768 Remove unreachable NeighborList code 2016-06-06 16:21:05 +02:00
Florian Bruhin
57a1847e3a Merge branch 'adamwethington7-modifcation-for-issue-1386' 2016-06-06 16:19:49 +02:00
Florian Bruhin
a38ec6e5a3 Update docs 2016-06-06 16:19:42 +02:00
Florian Bruhin
520572321a Add unittests for partial command parsing 2016-06-06 16:18:49 +02:00
Florian Bruhin
c9d85d3a12 bdd: Add tests for partial commandline matching 2016-06-06 16:10:10 +02:00
Florian Bruhin
2f60073cdf bdd: Allow to run invalid commands via quteproc 2016-06-06 16:10:01 +02:00
Florian Bruhin
4a7a2e61d3 Only do partial matching with main CommandParser 2016-06-06 15:56:56 +02:00
Florian Bruhin
ec869686c2 Get rid of TabbedBrowser.got_cmd
Seems like it's not used anywhere anymore
2016-06-06 15:53:33 +02:00
Florian Bruhin
5b396dab26 Merge branch 'modifcation-for-issue-1386' of https://github.com/adamwethington7/qutebrowser into adamwethington7-modifcation-for-issue-1386 2016-06-06 15:44:56 +02:00
Florian Bruhin
4e86a888c9 Add webelem tests for JS/non-JS rect_on_view 2016-06-06 15:42:30 +02:00
Florian Bruhin
88debe4f5e Fix zoom adjusting in rect_on_view
We now make sure we move the rect without adjusting its size, and then
also adjust the size based on the zoom.
2016-06-06 15:18:53 +02:00
Florian Bruhin
b0e9d2aca5 webelem.rect_on_view: Actually use passed geometry 2016-06-06 13:26:46 +02:00
Florian Bruhin
abfd789f9e Fix zooming with a too big count
Fixes #1118
Supersedes #1140
2016-06-06 13:25:01 +02:00
Florian Bruhin
8d5fdf2833 Remove wrap mode for NeighborList 2016-06-06 13:10:14 +02:00
Florian Bruhin
d50af52d1a Fix webelem.is_visible with zoom 2016-06-06 12:54:24 +02:00
Florian Bruhin
b1914d6414 Merge branch 'lahwaacz-hints_positioning' 2016-06-06 12:12:43 +02:00
Florian Bruhin
45da93ab55 Update manual hint tests 2016-06-06 12:12:23 +02:00
Florian Bruhin
10630e30ab hints: Integrate _get_first_rectangle into webelem 2016-06-06 11:56:15 +02:00
Florian Bruhin
4d04d0a511 Update docs 2016-06-06 10:42:49 +02:00
Florian Bruhin
b262580b22 Fix hint position when zoom is used 2016-06-06 10:41:59 +02:00
Florian Bruhin
0e4dbd646c Merge branch 'hints_positioning' of https://github.com/lahwaacz/qutebrowser into lahwaacz-hints_positioning 2016-06-06 10:22:06 +02:00
Florian Bruhin
7db9b85d31 Add entry for exherbo.org to manual/hints/other 2016-06-06 09:17:25 +02:00
Florian Bruhin
164be08627 Rename shadowed function 2016-06-06 09:06:49 +02:00
Florian Bruhin
23bcc35ebd Add some manual test pages for hints 2016-06-06 09:04:35 +02:00
Florian Bruhin
dce969997a Reorganize tests/html 2016-06-06 08:38:35 +02:00
Florian Bruhin
3cfb430cdf bdd: Add test for spawning an external editor 2016-06-06 08:36:30 +02:00
Florian Bruhin
753036067d test requirements: Update CherryPy to 6.0.0
* Setuptools is now required to build CherryPy. Pure distutils installs
  are no longer supported. This change allows CherryPy to depend on
  other packages and re-use code from them. It's still possible to
  install pre-built CherryPy packages (wheels) using pip without
  Setuptools.
* Back out changes attempting to fix redirects with Unicode URLs, as it
  also had the unintended consequence of causing the 'Location' to be
  ``bytes`` on Python 3.
* ``cherrypy.expose`` now works on classes.
* ``cherrypy.config`` decorator is now used throughout the code
  internally.
2016-06-06 08:25:41 +02:00
Florian Bruhin
2df85e75cb test requirements: Update CherryPy to 5.6.0
* ``@cherrypy.expose`` now will also set the exposed
  attribute on a class.
* Rewrote all tutorials and internal usage to prefer
  the decorator usage of ``expose`` rather than setting
  the attribute explicitly.
* Removed test-specific code from tutorials.
2016-06-05 23:15:29 +02:00
Florian Bruhin
546c172e5e test requirements: Update CherryPy to 5.5.0
* Fix for filenames with semicolons and quote characters in filenames
  found in headers.
* Added decorator for registering tools.
* Use simpler encoding rules for SCRIPT_NAME and PATH_INFO environment
  variables in CherryPy Tree allowing non-latin characters to pass even
  when ``wsgi.version`` is not ``u.0``.
* Ensure that multipart fields are decoded even when cached in a file.
2016-06-05 20:44:04 +02:00
Florian Bruhin
3acd63c900 www: Add links to blog 2016-06-05 13:51:38 +02:00
Florian Bruhin
a5ce25e833 Merge branch 'oniondreams-patch-1' 2016-06-05 13:20:47 +02:00
Florian Bruhin
c9539e7dcc Regenerate authors 2016-06-05 13:20:40 +02:00
oniondreams
2be5e322fd Update INSTALL.asciidoc
Add instruction for getting sound and video to work on Gentoo
2016-06-05 12:21:34 +02:00
Florian Bruhin
4eaa9443db Merge branch 'rcorre-bind_case_fix' 2016-06-04 23:31:44 +02:00
Florian Bruhin
6d86ad7bef Update changelog 2016-06-04 23:31:32 +02:00
Florian Bruhin
9f3c2dfada Merge branch 'bind_case_fix' of https://github.com/rcorre/qutebrowser into rcorre-bind_case_fix 2016-06-04 23:30:50 +02:00
Florian Bruhin
1601b85d16 Merge branch 'rcorre-rapid_normal_hints' 2016-06-04 23:14:37 +02:00
Florian Bruhin
2ac521627e Update changelog 2016-06-04 23:13:13 +02:00
Florian Bruhin
7b852a7bbb Merge branch 'rapid_normal_hints' of https://github.com/rcorre/qutebrowser into rcorre-rapid_normal_hints 2016-06-04 23:11:54 +02:00
Florian Bruhin
5c329c409c Improve error messages in test_hints
See #1542
2016-06-04 23:09:08 +02:00
Florian Bruhin
831c3c0272 Add a README for tests/end2end/data/hints/html
See #1542
2016-06-04 23:08:59 +02:00
Florian Bruhin
dc6113dcfa Make scroll test page a bit wider
See #1542
2016-06-04 22:53:37 +02:00
Ryan Roden-Corrent
68faf2b873 Allow hint --rapid in normal mode.
This was disallowed by an assertion, but has a legitimate use case for
clicking multiple buttons or ticking multiple checkboxes.

Resolves #1541.
2016-06-04 15:32:58 -04:00
Florian Bruhin
41fcf0ed33 Make pytest_rerunfailures work with frozen tests 2016-06-04 17:07:51 +02:00
Ryan Roden-Corrent
9f660a98f2 Default mode='normal' for bind/unbind.
Both set mode=None, then later checked if mode == 'None' and set it to
'normal'.
This reduces the function complexity just enough that pylint will stop
complaining.
2016-06-04 07:39:09 -04:00
Ryan Roden-Corrent
1dc20f4d02 Handle special keystrings case-insensitively.
Load all special keystrings (e.g. <ctrl-a>) into memory as lowercase,
and automatically lowercase any special keystring given to bind/unbind.
This prevents <ctrl-a> and <Ctrl-A> from being treated differently.

Resolves #816.
Also resolves #1544 (dupe).
2016-06-04 07:39:09 -04:00
Florian Bruhin
4054992583 Merge branch 'Kingdread-issue-1535' 2016-06-04 13:15:44 +02:00
Florian Bruhin
53b8ac1a60 Update changelog 2016-06-04 13:15:30 +02:00
Florian Bruhin
163082b3ea Wait until download is started 2016-06-04 13:15:22 +02:00
Daniel Schadt
fd27caf311 tests: remove wait in mhtml cancel test 2016-06-03 16:09:31 +02:00
Daniel Schadt
44b1344467 typo used -> user 2016-06-03 15:52:29 +02:00
Daniel Schadt
c3e7ab52b5 tests: add test for cancelling a mhtml download 2016-06-02 23:07:03 +02:00
Daniel Schadt
1cabae0583 mhtml: don't crash when user cancels a download
Fixes 1535

The browser crashed because both callbacks were called (finished and
error), trying to remove the item twice from the list of downloads.
2016-06-02 21:23:11 +02:00
Florian Bruhin
24db93f3eb test req: Update pytest-mock to 1.1
- From this version onward, pytest-mock is licensed under the MIT
  license.
- Now the plugin also adds introspection information on differing call
  arguments when calling helper methods such as assert_called_once_with.
  The extra introspection information is similar to pytest's and can be
  disabled with the mock_traceback_monkeypatch option.
- mocker.stub() now allows passing in the name for the constructed Mock
  object instead of having to set it using the internal _mock_name
  attribute directly. This is useful for debugging as the name is used
  in the mock's repr string as well as related assertion failure
  messages.
- Monkey patching mock module for friendlier tracebacks is automatically
  disabled with the --tb=native option. The underlying mechanism used to
  suppress traceback entries from mock module does not work with that
  option anyway plus it generates confusing messages on Python 3.5 due
  to exception chaining.
- mock.call is now aliased as mocker.call for convenience.
2016-06-02 11:19:22 +02:00
Florian Bruhin
1b9a0367e7 flake8 req: Update sortedcontainers to 1.5.3 2016-06-02 11:18:49 +02:00
Florian Bruhin
a19bb40b50 requirements: Update codecov to 2.0.5
- fix detecting merge commits on all CI, not just Travis
- Use %20 for encoding spaces [appveyor]
2016-06-02 11:16:52 +02:00
Florian Bruhin
2562d57437 tox: Install requirements in pylint-master env 2016-06-02 09:54:51 +02:00
Florian Bruhin
ce0b50bafd Fix pytest_rerunfailures package name 2016-06-01 13:13:41 +02:00
Florian Bruhin
fd55f9236f Merge branch 'Kingdread-json-logging' 2016-06-01 13:09:53 +02:00
Florian Bruhin
14feffa09e Update docs 2016-06-01 13:08:18 +02:00
Florian Bruhin
fcba6beecf Merge branch 'json-logging' of https://github.com/Kingdread/qutebrowser into Kingdread-json-logging 2016-06-01 13:07:52 +02:00
Florian Bruhin
e6f3a200c8 Include pytest-rerunfailures for frozen tests 2016-06-01 12:39:29 +02:00
Florian Bruhin
97ad808fac Remove unneeded marker definitions from pytest.ini
Those are now defined in pytest core or pytest-rerunfailures
2016-05-31 23:45:12 +02:00
Florian Bruhin
b3c91d4b81 tox: Update pytest to 2.9.2
* skip tests where one parameterize dimension was empty
* Fix Xfail does not work with condition keyword argument.
* Fix win32 path issue when puttinging custom config file with absolute
  path in ``pytest.main("-c your_absolute_path")``.
* Fix maximum recursion depth detection when raised error class is not
  aware of unicode/encoded bytes.
* Fix ``pytest.mark.skip`` mark when used in strict mode.
* Minor improvements and fixes to the documentation.
* Fix ``--fixtures`` to show all fixture definitions as opposed to just
  one per fixture name.
2016-05-31 23:41:24 +02:00
Florian Bruhin
5c02f3655a requirements: Also update mccabe for pylint-master 2016-05-31 07:57:53 +02:00
Florian Bruhin
17f54d0ef7 tox: Use -j0 when invoking pylint
This makes things a bit faster
2016-05-31 00:00:40 +02:00
Florian Bruhin
3a8ab8ea02 flake8: Set min-version for flake8-future-imports 2016-05-30 23:53:46 +02:00
Florian Bruhin
100d284cdd requirements: Update mccabe to 0.5.0
- PyCon 2016 PDX release
- Add support for Flake8 3.0
2016-05-30 23:50:11 +02:00
Florian Bruhin
0c611d4d85 requirements: Update flake8-future-import to 0.4.1
- Do not ignore imports which are present and have been added after the
  minimum version
- Ignore imports which became mandatory with the minimum version
2016-05-30 23:47:35 +02:00
Florian Bruhin
2a49dbe817 Fix handling of cmd.no_cmd_split for docs 2016-05-30 16:42:53 +02:00
Florian Bruhin
3e5994cff6 bdd: Get rid of "I execute the userscript" step
Since we now have a (testdata) substitution this is much easier.
2016-05-30 16:21:32 +02:00
Florian Bruhin
9695828608 Merge branch 'rcorre-hint_spawn_fix' 2016-05-30 16:18:17 +02:00
Florian Bruhin
c17a1be8fd Add a test for flags passed to a command 2016-05-30 16:18:03 +02:00
Florian Bruhin
65817ebb81 Update docs 2016-05-30 16:15:20 +02:00
Florian Bruhin
0aa7ed2eb3 Move userscript to correct place 2016-05-30 16:10:52 +02:00
Florian Bruhin
a39c662633 Merge branch 'hint_spawn_fix' of https://github.com/rcorre/qutebrowser into rcorre-hint_spawn_fix 2016-05-30 16:09:28 +02:00
Daniel Schadt
a6b47a7c09 fix lint 2016-05-30 16:00:10 +02:00
Daniel Schadt
fa2636c2f6 tests: fix timezone issues for test_quteprocess
Depending on the timezone, you may either get 00:00 as time or 01:00,
which is bad for testing on different machines.
2016-05-30 16:00:10 +02:00
Daniel Schadt
5f2d5feb58 message: don't indent the traceback
This shouldn't be needed anymore with json-logging.
2016-05-30 16:00:10 +02:00
Daniel Schadt
65e5a3fe09 quteproc: match message with re.DOTALL
Since they may now contain newlines, we need to get the whole message,
which are otherwise not included in .
2016-05-30 16:00:10 +02:00
Daniel Schadt
f676a599a2 tests: remove leftover lines from writing the code
Those lines are not needed.
2016-05-30 16:00:10 +02:00
Daniel Schadt
484320ac19 quteproc: see lines starting with ' ' as error
Now that json logging is used, multiple lines should be correctly
escaped (and parsed), even in tracebacks, so this check should now be
obsolete.
2016-05-30 16:00:10 +02:00
Daniel Schadt
db240e294e quteproc: delegate LogLine to log.ColoredFormatter 2016-05-30 16:00:10 +02:00
Daniel Schadt
cc7c477e3e tests: add update tests for LogLine with json logs 2016-05-30 16:00:10 +02:00
Daniel Schadt
6a6f396f85 quteproc: set lineno to None if function is None 2016-05-30 16:00:10 +02:00
Daniel Schadt
cea32ea333 log: embed traceback in json object 2016-05-30 16:00:10 +02:00
Daniel Schadt
96b299a1fc quteproc: Nicer output for unexpected errors
Instead of
    LogLine('{"levelname": "ERROR", ...}')
we get
    21:22:34 ERROR    downloads  mhtml ...
2016-05-30 16:00:10 +02:00
Daniel Schadt
b4022b9795 quteproc: fix log_color in log message
This actually uses the escape code, not just the color name.
2016-05-30 16:00:10 +02:00
Daniel Schadt
ae16240d41 quteproc: fix docstring in formatted_str
Autocomplete was faster and inserted .strip() after line
2016-05-30 16:00:10 +02:00
Daniel Schadt
d4a295b22e tests: update test_log with json_logging namespace 2016-05-30 16:00:10 +02:00
Daniel Schadt
cf3930f925 quteproc: except ValueError
There is no json.decoder.JSONDecodeError, it was a lie all along...
2016-05-30 16:00:10 +02:00
Daniel Schadt
49419bc429 tests: update test_invocations for --json-logging 2016-05-30 16:00:10 +02:00
Daniel Schadt
4a59a1f112 log: implement JSON as logging output
Fixes #1501

Enabled via the --json-logging parameter.
2016-05-30 16:00:10 +02:00
Florian Bruhin
9a0fa9068c Stabilize tests using :follow-hint 2016-05-30 14:54:06 +02:00
Florian Bruhin
afb88a9560 requirements: Update flake8-future-import to 0.4.0
- Add two older future imports
- Issue an error when a future import does not exist
- Define which is the oldest Python version to be supported so that
  already mandatory features can be ignored and not yet supported
  features default to forbidden (ignoring the lower error code).
- Use return code of 1 if errors occurred
2016-05-30 12:57:57 +02:00
Florian Bruhin
6741a16957 Stabilize :buffer tests
Fixes #1493
2016-05-30 01:16:44 +02:00
Florian Bruhin
424efec8a9 travis: Use Python 3.5 on Ubuntu Xenial 2016-05-30 00:03:40 +02:00
Florian Bruhin
402e110cab bdd: Delete "Navigating up with root directory"
This test causes various trouble for reasons I don't understand, so
let's get rid of it.
2016-05-29 23:59:28 +02:00
Florian Bruhin
d385206ea0 tests: Set some settings for quteproc
We set ui -> message-timeout to 0 to get better error messages in the
log output and network -> ssl-strict to not hang on unexpected SSL
errors.
2016-05-29 23:40:09 +02:00
Florian Bruhin
6ec6657641 travis: Add a Ubuntu Xenial build environment 2016-05-29 23:26:12 +02:00
Florian Bruhin
3a0a30597e Dockerfile: Install dbus 2016-05-29 23:25:46 +02:00
Florian Bruhin
e3556f8fe0 Add a Ubuntu Xenial Dockerfile 2016-05-29 23:14:54 +02:00
Florian Bruhin
2e4c0c6599 Run dbus-uuidgen in Ubuntu Wily Dockerfile
Otherwise the tests will fail with:

  INVALID: process 975: D-Bus library appears to be incorrectly set up; failed to read machine uuid: UUID file '/etc/machine-id' should contain a hex string of length 32, not length 0, with no other text
  INVALID: See the manual page for dbus-uuidgen to correct this issue.
2016-05-29 23:14:10 +02:00
Florian Bruhin
e8528c2c75 travis: Switch back to autogenerated Docker images
Archlinux updated to PyQt 5.6 in [extra] now, so the autobuilt image
should work fine with Qt 5.6 now.
2016-05-29 23:00:32 +02:00
Florian Bruhin
9efcddf727 Add missing import 2016-05-29 22:47:44 +02:00
Florian Bruhin
08f5cfd366 Fix test_enter_folder on Windows 2016-05-29 22:46:32 +02:00
Florian Bruhin
86be7ad82c Move file_url from utils.jinja to utils.urlutils 2016-05-29 22:44:40 +02:00
Florian Bruhin
070e30658f Fix lint 2016-05-29 22:22:34 +02:00
Florian Bruhin
8e2d315807 Fix pytest_status workaround if .cache is missing 2016-05-29 22:21:35 +02:00
Florian Bruhin
2d9cf5ed3a Handle invalid URLs in wait_for_load_finished_url 2016-05-29 22:16:55 +02:00
Florian Bruhin
af798c7450 travis: Reactivate OS X tests 2016-05-29 22:07:24 +02:00
Florian Bruhin
001670969b tests: Ignore segfaults on pytest exit
In various situations (especially on OS X), pytest segfaults on exit probably
due to Qt/PyQt bugs.

We now have a wrapper script which ignores those segfaults if pytest did run
successfully.
2016-05-29 22:06:24 +02:00
Florian Bruhin
1332258767 Revert "travis_run: Show tox exit status"
This reverts commit 144a37ec06.
We accidentally always exit with status 0 with this...
2016-05-29 20:46:48 +02:00
Florian Bruhin
144a37ec06 travis_run: Show tox exit status 2016-05-29 18:47:58 +02:00
Florian Bruhin
119d76c502 Fix lint 2016-05-29 18:45:09 +02:00
Florian Bruhin
5b84fc93df Do a final userscript command read on cleanup
On OS X, sometimes the userscript exited before the QSocketNotifier was
triggered. By doing a final read when cleaning up we make sure we don't
miss anything.

Fixes #1533.
2016-05-29 18:42:56 +02:00
Florian Bruhin
ca87b9e285 tox: Remove weird minus 2016-05-29 18:28:20 +02:00
Florian Bruhin
e10e9c7df2 bdd: Don't run :spawn -u test on OS X for now
See #1533
2016-05-29 18:25:23 +02:00
Florian Bruhin
a905d46757 bdd: Merge userscripts.feature into spawn.feature 2016-05-29 18:24:34 +02:00
Florian Bruhin
800d069764 requirements: Blacklist Flask 0.11.0
See https://github.com/pallets/flask/issues/1825
2016-05-29 18:20:48 +02:00
Florian Bruhin
64d4c9f83e Clean up end2end test file structure
This renames tests/integration to tests/end2end and moves some files to
tests/end2end/fixtures.
2016-05-29 18:20:00 +02:00
Florian Bruhin
f10754fa77 Remove MarkupSafe pin in requirements-tests.txt
We already have that pinned in requirements.txt
2016-05-29 17:50:13 +02:00
Florian Bruhin
a3a160cb22 tests: Fix race condition in test_enter_folder 2016-05-29 17:48:02 +02:00
Florian Bruhin
867b4a8640 Make quteproc.wait_for_load_finished_url public 2016-05-29 17:47:39 +02:00
Florian Bruhin
e7a6907bc2 tests: Don't run geolocation tests on OS X
We already only run them on CI because they can be kind of
unpredictable, and it seems OS X on Travis hangs when running them too.
2016-05-29 17:35:43 +02:00
Florian Bruhin
7180a6df0b requirements: Update pytest dependency pins
This adds MarkupSafe and removes termcolor and wheel.
2016-05-29 17:34:19 +02:00
Florian Bruhin
d008b2a86c requirements: Pin all pylint dependencies 2016-05-29 17:34:16 +02:00
Florian Bruhin
bceb7cf89e tests: Avoid race conditions for :messages tests
We need to wait until the page is actually loaded before we check the
content.
2016-05-29 17:07:37 +02:00
Florian Bruhin
6f6303e0a6 Use requirements files for tox dependencies
While this makes things a little more complicated and means we'll need to use
`-r` to recreate tox environments, it has several advantages:

- Full support from requires.io (including PRs)
- Workaround for https://bitbucket.org/hpk42/tox/issues/332/ so we can update
  virtualenv/pip
2016-05-29 16:53:54 +02:00
Florian Bruhin
cd7b8c65df Remove OS X specific options from freeze.py 2016-05-29 16:26:21 +02:00
Florian Bruhin
9529d55649 Remove misc/qt_menu.nib
This was needed for cx_Freeze on OS X which we don't use anymore.
2016-05-29 16:24:16 +02:00
Florian Bruhin
d3fe2babd3 tests: Wait until page is loaded by default
When doing quteproc.open_path, by default the test didn't wait until the page
was loaded. This caused unintentional race conditions which e.g. caused
dirbrowser tests to fail on OS X:

https://travis-ci.org/The-Compiler/qutebrowser/jobs/133730001

Now instead we wait by default, unless wait=False is passed to open_path() or
open_url().
2016-05-29 16:14:33 +02:00
Florian Bruhin
9bb425d598 Install Qt5/PyQt5 from custom bottles on OS X
We have a custom build which comes with QtWebKit now.
2016-05-29 15:46:43 +02:00
Florian Bruhin
345768bfc4 Fix requirements installing on Appveyor 2016-05-29 15:28:49 +02:00
Florian Bruhin
a987b714de appveyor: Install pip/tox from requirements files 2016-05-29 15:14:42 +02:00
Florian Bruhin
f4ce199968 ci: Blacklist virtualenv > 15.0.1 2016-05-29 15:09:14 +02:00
Florian Bruhin
f72fc04bb1 Revert "ci: Update virtualenv to 15.0.2"
This reverts commit 3c978aa203.
We need to revert this due to the tox/pip bug with inline comments.
2016-05-29 15:08:43 +02:00
Florian Bruhin
51122a8010 tox: Blacklist Flask 0.11.0 2016-05-29 14:35:48 +02:00
Florian Bruhin
a921ee2a8f Revert "tox: Update Flask to 0.11.0"
This reverts commit 986288c408.
2016-05-29 14:35:37 +02:00
Florian Bruhin
986288c408 tox: Update Flask to 0.11.0
Released on May 29th 2016, codename Absinthe.

- Added support to serializing top-level arrays to
  :func:`flask.jsonify`. This introduces a security risk in ancient
  browsers. See :ref:`json-security` for details.
- Added before_render_template signal.
- Added `**kwargs` to :meth:`flask.Test.test_client` to support passing
  additional keyword arguments to the constructor of
  :attr:`flask.Flask.test_client_class`.
- Added ``SESSION_REFRESH_EACH_REQUEST`` config key that controls the
  set-cookie behavior.  If set to ``True`` a permanent session will be
  refreshed each request and get their lifetime extended, if set to
  ``False`` it will only be modified if the session actually modifies.
  Non permanent sessions are not affected by this and will always
  expire if the browser window closes.
- Made Flask support custom JSON mimetypes for incoming data.
- Added support for returning tuples in the form ``(response, headers)``
  from a view function.
- Added :meth:`flask.Config.from_json`.
- Added :attr:`flask.Flask.config_class`.
- Added :meth:`flask.config.Config.get_namespace`.
- Templates are no longer automatically reloaded outside of debug mode.
  This can be configured with the new ``TEMPLATES_AUTO_RELOAD`` config
  key.
- Added a workaround for a limitation in Python 3.3's namespace loader.
- Added support for explicit root paths when using Python 3.3's
  namespace packages.
- Added :command:`flask` and the ``flask.cli`` module to start the local
  debug server through the click CLI system. This is recommended over
  the old ``flask.run()`` method as it works faster and more reliable
  due to a different design and also replaces ``Flask-Script``.
- Error handlers that match specific classes are now checked first,
  thereby allowing catching exceptions that are subclasses of HTTP
  exceptions (in ``werkzeug.exceptions``).  This makes it possible
  for an extension author to create exceptions that will by default
  result in the HTTP error of their choosing, but may be caught with
  a custom error handler if desired.
- Added :meth:`flask.Config.from_mapping`.
- Flask will now log by default even if debug is disabled. The log
  format is now hardcoded but the default log handling can be disabled
  through the ``LOGGER_HANDLER_POLICY`` configuration key.
- Removed deprecated module functionality.
- Added the ``EXPLAIN_TEMPLATE_LOADING`` config flag which when enabled
  will instruct Flask to explain how it locates templates. This should
  help users debug when the wrong templates are loaded.
- Enforce blueprint handling in the order they were registered for
  template loading.
- Ported test suite to py.test.
- Deprecated ``request.json`` in favour of ``request.get_json()``.
- Add "pretty" and "compressed" separators definitions in jsonify()
  method.
  Reduces JSON response size when JSONIFY_PRETTYPRINT_REGULAR=False by
  removing unnecessary white space included by default after separators.
- JSON responses are now terminated with a newline character, because it
  is a convention that UNIX text files end with a newline and some
  clients don't deal well when this newline is missing. See
  https://github.com/pallets/flask/pull/1262 -- this came up originally
  as a part of https://github.com/kennethreitz/httpbin/issues/168
- The automatically provided ``OPTIONS`` method is now correctly
  disabled if the user registered an overriding rule with the
  lowercase-version ``options``.
- ``flask.json.jsonify`` now supports the ``datetime.date`` type.
- Don't leak exception info of already catched exceptions to context
  teardown handlers.
- Allow custom Jinja environment subclasses.
- ``flask.g`` now has ``pop()`` and ``setdefault`` methods.
- Turn on autoescape for ``flask.templating.render_template_string`` by
  default.
- ``flask.ext`` is now deprecated.
- ``send_from_directory`` now raises BadRequest if the filename is
  invalid on the server OS.
- Added the ``JSONIFY_MIMETYPE`` configuration variable.
- Exceptions during teardown handling will no longer leave bad
  application contexts lingering around.
2016-05-29 14:22:11 +02:00
Florian Bruhin
344569fb5b tox: Update flake8-string-format to 0.2.2
- Do not check simple expressions, except for docstrings, because they
  cannot be accessed anyway.
- Properly assert starred arguments in Python 3.5. Only the last element
  must be a vararg if varargs are present and not the complete list.
- Output correct column offset on Python 3.4.2, as that used the wrong
  offset inside calls.
2016-05-29 14:19:38 +02:00
Florian Bruhin
c9d49177cc tox: Update sortedcontainers to 1.5.2 2016-05-29 14:17:48 +02:00
Florian Bruhin
b97736b117 Revert "Work around flake8-string-format bug"
This reverts commit 6c4beef783.
2016-05-29 14:17:32 +02:00
Florian Bruhin
1e55db0630 Follow the American way(tm) of spelling things 2016-05-29 10:57:41 +02:00
Florian Bruhin
4b97ef88ac Merge branch 'hcraT-empty_uri' 2016-05-29 10:38:45 +02:00
Florian Bruhin
86d320b6a1 Update docs 2016-05-29 10:38:24 +02:00
Florian Bruhin
6caa89a622 Merge branch 'empty_uri' of https://github.com/hcraT/qutebrowser into hcraT-empty_uri 2016-05-29 00:37:12 +02:00
Florian Bruhin
3c978aa203 ci: Update virtualenv to 15.0.2 2016-05-28 23:27:42 +02:00
Florian Bruhin
b065d8efcd pyinstaller: Set NSHighResolutionCapable = True
This means qutebrowser will show up correctly on HiDPI/Retina screens
without being pixelated.
2016-05-28 23:20:00 +02:00
Florian Bruhin
90fa2a50ce pyinstaller: Use qutebrowser/__main__.py
When we use qutebrowser.py, PyInstaller 3.2 gets confused
somehow (because of the name conflict with qutebrowser/ maybe?) and
doesn't do anything when running the bundled app.

With qutebrowser/__main__.py the generated filename is still correct and
it actually works.
2016-05-28 23:18:29 +02:00
Tarcisio Fedrizzi
ec2935fab0 Fixes flake8 error 2016-05-28 17:38:31 +02:00
Florian Bruhin
fc3e1b4ede tox: Update hypothesis to 3.4.0
models() strategies from hypothesis.extra.django will now respect much
more of Django's validations out of the box. Wherever possible
full_clean() should succeed.

In particular:

- The max_length, blank and choices kwargs are now respected.
- Add support for DecimalField.
- If a field includes validators, the list of validators are used to
  filter the field strategy.
2016-05-27 15:40:50 +02:00
Florian Bruhin
1d87eee4d7 Fix starting when sys.stderr is None 2016-05-27 14:48:46 +02:00
Florian Bruhin
afcb018ee2 Fix some spelling mistakes
Found via http://jwilk.net/software/mwic
2016-05-27 12:07:00 +02:00
Florian Bruhin
29aedef420 Fix :debug-console with PyQt 5.6 2016-05-27 00:18:23 +02:00
Florian Bruhin
2fb5de8bd3 test_insert_mode: Always set fake clipboard
Otherwise if we only run a keypress test, the fake clipboard won't be
activated and thus the subsequent :yank-selected would write to the real
clipboard.
2016-05-27 00:12:53 +02:00
Florian Bruhin
6c4beef783 Work around flake8-string-format bug
When using flake8-string-format on Python 3.5 with str.format(*group) it
failed:

  Traceback (most recent call last):
    File "./.venv-flakes/bin/flake8", line 11, in <module>
      sys.exit(main())
    File ".../site-packages/flake8/main.py", line 33, in main
      report = flake8_style.check_files()
    File ".../site-packages/flake8/engine.py", line 181, in check_files
      return self._retry_serial(self._styleguide.check_files, paths=paths)
    File ".../site-packages/flake8/engine.py", line 172, in _retry_serial
      return func(*args, **kwargs)
    File ".../site-packages/pep8.py", line 1842, in check_files
      runner(path)
    File ".../site-packages/flake8/engine.py", line 126, in input_file
      return fchecker.check_all(expected=expected, line_offset=line_offset)
    File ".../site-packages/pep8.py", line 1574, in check_all
      self.check_ast()
    File ".../site-packages/pep8.py", line 1521, in check_ast
      for lineno, offset, text, check in checker.run():
    File ".../site-packages/flake8_string_format.py", line 288, in run
      assert isinstance(call.args, ast.Starred) is bool(has_starargs)
  AssertionError

This works around that issue.
See https://github.com/xZise/flake8-string-format/issues/11
2016-05-26 23:50:58 +02:00
Florian Bruhin
ee848ce09e tox: Update sortedcontainers to 1.5.1 2016-05-26 21:42:34 +02:00
Tarcisio Fedrizzi
a3e6761db6 Fixes pylint error 2016-05-26 18:01:01 +02:00
Tarcisio Fedrizzi
a9e96df5df Adds unit test when force_search is True 2016-05-26 18:01:01 +02:00
Tarcisio Fedrizzi
7da6faa7af Changes fuzzy_url to force search even when paths are passed 2016-05-26 18:01:01 +02:00
Tarcisio Fedrizzi
462f9d7e4c Refators discussed in the review
- refactors what discussed in the review
- adds unit tests for schemas without host and path
2016-05-26 18:01:01 +02:00
Ryan Roden-Corrent
db09cbb960 Implement a feature test for :hint all userscript.
This adds a (testdata) substitution for 'When I run {command}' to allow
providing an absolute path for the userscript.
2016-05-26 07:35:28 -04:00
Florian Bruhin
7c89f0c024 Merge branch 'nginth-singleoptioncrash' 2016-05-26 07:48:49 +02:00
Florian Bruhin
998f025553 Update docs 2016-05-26 07:45:17 +02:00
Florian Bruhin
a589c09f2e Merge branch 'singleoptioncrash' of https://github.com/nginth/qutebrowser into nginth-singleoptioncrash 2016-05-26 07:41:09 +02:00
Florian Bruhin
9d018b8fbd pylint: Disable too-many-return-statements 2016-05-26 07:39:47 +02:00
Nick Ginther
1ea99c5958 add completing single option argument test 2016-05-25 17:52:36 -05:00
Nick Ginther
2065f292ab fix crash on empty parts[] (#1523) 2016-05-25 16:45:06 -05:00
Florian Bruhin
20cfadbda7 Merge branch 'nginth-tabindexfix' 2016-05-25 22:10:59 +02:00
Florian Bruhin
4c0f85564d Update docs 2016-05-25 22:10:45 +02:00
Florian Bruhin
824e662154 Merge branch 'tabindexfix' of https://github.com/nginth/qutebrowser into nginth-tabindexfix 2016-05-25 22:09:01 +02:00
Florian Bruhin
a6abc86456 Merge branch 'rcorre-better_keyhints' 2016-05-25 22:07:23 +02:00
Florian Bruhin
ed6432136f Update docs 2016-05-25 22:06:59 +02:00
Florian Bruhin
c96722f169 Merge branch 'better_keyhints' of https://github.com/rcorre/qutebrowser into rcorre-better_keyhints 2016-05-25 22:04:51 +02:00
Nick Ginther
c70accda22 restructure code 2016-05-25 13:46:31 -05:00
Florian Bruhin
5a82bf3357 Add some more logging for SSL errors 2016-05-25 20:45:54 +02:00
Tarcisio Fedrizzi
73c200fb14 Tweaks the multi-line heuristic to handle scheme-like text
Adds some options to implement a way to treat multiline text that starts
with a scheme-like line as text instead as an URL.
2016-05-25 08:05:57 +02:00
Ryan Roden-Corrent
f58e2d91dc Add a keyhint blacklist.
Replace the setting ui.show-keyhints with ui.keyhint-blacklist, which
is a list of globs for keychains that shouldn't be hinted. This allows
users to prevent showing keyhints for keychains they already know.

keyhint-blacklist='*' is equivalent to show-keyhints=False.

Resolves #1515.
2016-05-24 20:46:39 -04:00
Florian Bruhin
f100eb6e03 Mark iframe hint tests as xfail_norun as a stopgap
See #1525.
2016-05-24 22:23:42 +02:00
Nick Ginther
8c487e9a5f fix tabindex statusbar not updating (#1247) 2016-05-24 15:18:22 -05:00
Florian Bruhin
3bbc950616 Fix lint 2016-05-24 21:53:50 +02:00
Florian Bruhin
f692d34d94 Merge branch 'Yatekii-master' 2016-05-24 21:37:02 +02:00
Florian Bruhin
9ef12a0af8 Regenerate authors 2016-05-24 21:36:47 +02:00
Florian Bruhin
846fe8b943 Add a test for #1484 2016-05-24 21:36:09 +02:00
Florian Bruhin
ea1d8902d5 Add logging for #1484 2016-05-24 21:35:48 +02:00
Florian Bruhin
0fb6e508e6 Merge branch 'master' of https://github.com/Yatekii/qutebrowser into Yatekii-master 2016-05-24 21:32:24 +02:00
Florian Bruhin
ce9324029e tox: Update werkzeug to 0.11.10
- Fixed a bug that occurs when running on Python 2.6 and using a broken
  locale.
- Fixed a crash when running the debugger on Google App Engine.
- Fixed an issue with multipart parsing that could cause memory
  exhaustion.
2016-05-24 15:45:19 +02:00
Florian Bruhin
f5c67785c7 tox: Remove pytest-html
We don't use pytest-html anywhere anymore since switching from the
buildbot to Travis CI.
2016-05-24 15:44:39 +02:00
Florian Bruhin
153e955561 tox: Update pbr to 1.10.0 2016-05-24 15:43:09 +02:00
Florian Bruhin
40601adef0 tox: Update hypothesis to 3.2.0
All tests using @given now fix the global random seed. This removes the
health check for that. If a non-zero seed is required for the final
falsifying example, it will be reported. Otherwise Hypothesis will
assume randomization was not a significant factor for the test and be
silent on the subject. If you use the random_module() strategy this will
continue to work and will always display the seed.
2016-05-24 15:41:55 +02:00
Florian Bruhin
c6cb8e5099 Remove unneeded "# pragma: no branch" comments
Those are not needed anymore with coverage 4.1.
2016-05-24 15:40:50 +02:00
Florian Bruhin
11363b9533 Fix some invalid "# pragma: no cover" comments
We accidentally used "# pragma: no coverage" instead.
2016-05-24 15:40:18 +02:00
Ryan Roden-Corrent
b1aaf0f10f Only show keyhints after a short delay.
If a user knows the keychain and can type it quickly, we shouldn't
annoy them with a popup. Only show the keyhint if the user doesn't
complete their keychain in 500ms.

The isVisible() check in the tests is somewhat invalid now because it
is never immediately visible and I don't want to add a delay to unit
tests. I added a check that text() is not set for one test that was
only checking isVisible().

Addresses part of #1515.
2016-05-23 21:21:03 -04:00
Noah Huesser
299c66b82c Possibly fixed issue: #1484 2016-05-23 11:32:27 +02:00
Jakub Klinkovský
ee8247525e simplify testcase for following a hint inside an iframe 2016-05-22 15:19:00 +02:00
Florian Bruhin
002e5801f1 Improve logging in add_js_bridge 2016-05-22 14:55:53 +02:00
Florian Bruhin
4066696956 tox/requirements: Update coverage to 4.1
Version 4.1 --- 2016-05-21

- The internal attribute Reporter.file_reporters was removed in 4.1b3.
  It should have come has no surprise that there were third-party tools
  out there using that attribute. It has been restored, but with a
  deprecation warning.

Version 4.1b3 --- 2016-05-10

- When running your program, execution can jump from an except X: line
  to some other line when an exception other than X happens. This jump
  is no longer considered a branch when measuring branch coverage.
- When measuring branch coverage, yield statements that were never
  resumed were incorrectly marked as missing. This is now fixed.
- During branch coverage of single-line callables like lambdas and
  generator expressions, coverage.py can now distinguish between them
  never being called, or being called but not completed.
- The HTML report now has a map of the file along the rightmost edge of
  the page, giving an overview of where the missed lines are. Thanks,
  Dmitry Shishov.
- The HTML report now uses different monospaced fonts, favoring Consolas
  over Courier. Along the way not properly handling one-space indents
  was fixed. The index page also has slightly different styling, to try
  to make the clickable detail pages more apparent.
- Missing branches reported with coverage report -m will now say ->exit
  for missed branches to the exit of a function, rather than a negative
  number.
- coverage --help and coverage --version now mention which tracer is
  installed, to help diagnose problems. The docs mention which features
  need the C extension.
- Officially support PyPy 5.1, which required no changes, just updates
  to the docs.
- The Coverage.report function had two parameters with non-None
  defaults, which have been changed. show_missing used to default to
  True, but now defaults to None. If you had been calling
  Coverage.report without specifying show_missing, you'll need to
  explicitly set it to True to keep the same behavior. skip_covered used
  to default to False. It is now None, which doesn't change the
  behavior.
- It's never been possible to pass a namespace module to one of the
  analysis functions, but now at least we raise a more specific error
  message, rather than getting confused.
- The coverage.process_startup function now returns the Coverage
  instance it creates.
- Make a small tweak to how we compare threads, to avoid buggy custom
  comparison code in thread classes.

Version 4.1b2 --- 2016-01-23

- Problems with the new branch measurement in 4.1 beta 1 were fixed:
  - Class docstrings were considered executable. Now they no longer are.
  - yield from and await were considered returns from functions, since
    they could tranfer control to the caller. This produced unhelpful
    "missing branch" reports in a number of circumstances. Now they no
    longer are considered returns.
  - In unusual situations, a missing branch to a negative number was
    reported.
- The XML report now produces correct package names for modules found in
  directories specified with source=.
- coverage report won't produce trailing whitespace.

Version 4.1b1 --- 2016-01-10

- Branch analysis has been rewritten: it used to be based on bytecode,
  but now uses AST analysis. This has changed a number of things:
  - More code paths are now considered runnable, especially in
    try/except structures. This may mean that coverage.py will identify
    more code paths as uncovered. This could either raise or lower your
    overall coverage number.
  - Python 3.5's async and await keywords are properly supported
  - Some long-standing branch coverage bugs were fixed:
    - functions with only a docstring for a body would incorrectly
      report a missing branch on the def line.
    - code in an except block could be incorrectly marked as a missing
      branch.
    - context managers (with statements) in a loop or try block could
      confuse the branch measurement, reporting incorrect partial
      branches.
    - In Python 3.5, an actual partial branch could be marked as
      complete.
- Pragmas to disable coverage measurement can now be used on decorator
  lines, and they will apply to the entire function or class being
  decorated.
- Multiprocessing support is now available on Windows.
- Files with two encoding declarations are properly supported.
- Non-ascii characters in regexes in the configuration file worked in
  3.7, but stopped working in 4.0. Now they work again.
- Form-feed characters would prevent accurate determination of the
  beginning of statements in the rest of the file. This is now fixed.
2016-05-22 14:35:26 +02:00
Ryan Roden-Corrent
648f89ef31 Implement more tests for the hinting.
Validate the hint spawn fix as well as add feature tests for previously
untested hinting behaviors (fill, run, --rapid).

There is still no test for the 'userscript' target as running a
userscript from hint mode during a test does not get the same
redirection as the 'I execute the userscript ...' statement, That is,
it looks in /usr/local/share/qutebrowser/userscripts rather than
tests/integration/data/userscripts.
2016-05-21 07:48:52 -04:00
Ryan Roden-Corrent
87cb5bf6c2 Fix test html link for hints 2016-05-20 22:13:49 -04:00
Ryan Roden-Corrent
f025394e04 Set maxsplit=2 for :hint.
This supports things like :hint all spawn -v echo as '-v echo' will be
passed as a single unit to spawn rather than -v being interpreted as a
flag for :hint.

Resolves #797.

Note that, while `:hint --rapid all spawn -v` echo works,
`:hint all --rapid spawn -v echo` does not (this did not work before
either).
2016-05-20 22:11:58 -04:00
Ryan Roden-Corrent
0300f03ebc Allow passing args to spawn from :hint.
Instead of creating a new guiprocess manually, just pass the args along
to the spawn command so it can accept args like -v.

Addresses part of #797 by allowing `hint -- all spawn -v echo`.
`hint all spawn -v echo` is still not supported.
2016-05-20 22:11:58 -04:00
Jakub Klinkovský
c919fcba35 hints: position according to getClientRects() 2016-05-20 21:26:29 +02:00
Jakub Klinkovský
4c06e34074 hints: change click position to the center of the largest square fitting to the top/left corner of the rectangle 2016-05-20 21:26:29 +02:00
Florian Bruhin
0f8b298fad Merge branch 'lahwaacz-hints_clicking' 2016-05-20 18:11:54 +02:00
Florian Bruhin
4b5788a878 Update docs 2016-05-20 18:08:46 +02:00
Florian Bruhin
a5c08e23b6 Fix path to link.html in new tests 2016-05-20 17:21:05 +02:00
Florian Bruhin
ea1f46d542 Merge branch 'hints_clicking' of https://github.com/lahwaacz/qutebrowser into lahwaacz-hints_clicking 2016-05-20 17:09:16 +02:00
Florian Bruhin
2c42219b23 Don't autofollow hint when unfiltering w/ rapid
When we are in rapid mode with only one link, after following the hint, fire()
called filter_hints(None) to display all hints again. Then filter_hints tried
to follow that link, fire() tried to show all again, etc., leading to a
RecursionError.

Fixes #1513.

A test will be added via #1510.
2016-05-20 16:41:51 +02:00
Florian Bruhin
107934e4e1 bdd: Wait a bit in "No crash should happen" step
If we don't do this, the qutebrowser process will be terminated and exit
before it actually has a chance to crash.

See #1510
2016-05-20 16:03:52 +02:00
Florian Bruhin
7898507775 tox: Update pytest-mock to 1.0.0
Fix AttributeError with mocker.spy when spying on inherited methods
2016-05-20 13:10:48 +02:00
Florian Bruhin
d67bfc8c45 Merge branch 'rcorre-keyhint_fix' 2016-05-20 13:07:47 +02:00
Florian Bruhin
b934f8bc4e Fix lint 2016-05-20 13:07:36 +02:00
Ryan Roden-Corrent
b1440a1804 Implement utils.is_special_key.
The check `key.startswith('<') and key.endswith('>') is repeated many
times in code to check for a special key. Replace all these with a call
to the same function.
2016-05-19 20:48:48 -04:00
Ryan Roden-Corrent
5992b81850 Don't show keyhint if there are no hints.
Currently, the keyhint window is shown even if the keystring matches no
possible bindings. This causes an empty keyhint window to hang around
after entering hinting mode.

Instead, the window is now hidden if no bindings match the current
keystring.

Resolves #1507.
2016-05-19 07:02:23 -04:00
Florian Bruhin
93989c035f Merge branch 'rumpelsepp-master' 2016-05-18 23:55:49 +02:00
Florian Bruhin
01e2fb37a0 Regenerate authors 2016-05-18 23:55:43 +02:00
Florian Bruhin
2b0f780500 Merge branch 'master' of https://github.com/rumpelsepp/qutebrowser into rumpelsepp-master 2016-05-18 23:55:34 +02:00
Stefan Tatschner
2c7dc6ffcc Add invalid url to 'test_set_hover_url_encoded'
This covers the recently introduced code path, in 1c23815 (Fix crash
when hovering over an invalid URL, 2016-05-18) when hovering an
invalid URL.
2016-05-18 22:23:26 +02:00
Florian Bruhin
f02bcf256b Update changelog 2016-05-18 18:49:37 +02:00
Florian Bruhin
9111d1b10f Merge branch 'rcorre-command_binding_completion' 2016-05-18 18:48:56 +02:00
Florian Bruhin
b5f8e7306f Merge branch 'command_binding_completion' of https://github.com/rcorre/qutebrowser into rcorre-command_binding_completion 2016-05-18 18:47:13 +02:00
Florian Bruhin
aef952637d Try to fix broken yelo link in README
See #1497
2016-05-18 18:43:16 +02:00
Stefan Tatschner
1c238152f7 Fix crash when hovering over an invalid URL 2016-05-18 16:20:03 +02:00
Ryan Roden-Corrent
4dd1478d2c Use ', '.join instead of str.join(', ', ...). 2016-05-18 08:55:17 -04:00
Ryan Roden-Corrent
0a0f0feaec Update command completions when keyconf changes.
When a user changes keybindings, update the command completion model so
the new keybindings are shown.
2016-05-18 08:00:21 -04:00
Ryan Roden-Corrent
c050b4973b Show special keys last in command completion.
When showing the currently bound key in the misc column for command
completion, if the command has multiple bindings, show special bindings
(e.g. <ctrl-a>) after non-special bindings.
2016-05-18 07:54:06 -04:00
Ryan Roden-Corrent
06caee41b8 Clean up command_binding_completion.
- Add a space after the comman for multiple binding suggestions.
- Use defaultdict(list) instead of defaultdict(lambda: [])
- Move the pylint comment back to the top of the class
2016-05-18 07:46:27 -04:00
Florian Bruhin
dc975cfac0 Update docs 2016-05-18 08:24:20 +02:00
Florian Bruhin
db0e29ae1d Merge branch 'rcorre-keystring' 2016-05-18 07:38:08 +02:00
Florian Bruhin
213677d30a Update changelog 2016-05-18 07:37:20 +02:00
Florian Bruhin
5b65ec17fd Set Qt.RichText textFormat for KeyHintView 2016-05-18 07:35:56 +02:00
Florian Bruhin
5c8d1656ff Fix path to test_keyhints.py in check_coverage.py
utils -> misc
2016-05-18 07:34:40 +02:00
Florian Bruhin
d0af80fbd5 Merge branch 'keystring' of https://github.com/rcorre/qutebrowser into rcorre-keystring 2016-05-18 07:33:17 +02:00
Florian Bruhin
32c7518124 Merge branch 'Liambeguin-hint_inputs' 2016-05-18 07:31:00 +02:00
Florian Bruhin
9a0083ea65 Regenerate authors 2016-05-18 07:30:54 +02:00
Florian Bruhin
324fcfadb0 Merge branch 'hint_inputs' of https://github.com/Liambeguin/qutebrowser into Liambeguin-hint_inputs 2016-05-18 07:30:46 +02:00
Florian Bruhin
d837e3eb91 Merge branch 'forkbong-pretty-hover-url' 2016-05-18 07:28:39 +02:00
Florian Bruhin
6aed55f09d Update changelog 2016-05-18 07:28:28 +02:00
Florian Bruhin
3a29abc779 Regenerate authors 2016-05-18 07:27:38 +02:00
Florian Bruhin
02cbc2f986 Merge branch 'pretty-hover-url' of https://github.com/forkbong/qutebrowser into forkbong-pretty-hover-url 2016-05-18 07:27:30 +02:00
Florian Bruhin
43518a4624 Merge branch 'timeout_docs' of https://github.com/rcorre/qutebrowser into rcorre-timeout_docs 2016-05-18 07:26:12 +02:00
Florian Bruhin
e4d84b0bfc Simplify argparser.type_check
If the value isn't param.default, it will always be a string or a flag, as
type_conv never gets called when the function is called from Python.
2016-05-18 07:16:17 +02:00
Florian Bruhin
284d4e4b97 Only pass is_bool to Command._param_to_argparse_* 2016-05-18 06:55:27 +02:00
Florian Bruhin
a83bf9c3ee Simplify argparser.type_conv
Since we're not using those functions as argparse callbacks anymore, we
can write a normal function instead of factories, which simplifies
things a lot.
2016-05-18 06:55:27 +02:00
Florian Bruhin
8ba6a0f5a7 Update CONTRIBUTING with cmdutils changes 2016-05-18 06:55:27 +02:00
Florian Bruhin
c0d044447d Add tests for qutebrowser.utils.typing 2016-05-18 06:55:17 +02:00
Florian Bruhin
6ed9b6b13f Make sure typing.Union[str, int] gets handled
str always needs to be the last element checked as otherwise it'd always
win.
2016-05-18 06:55:17 +02:00
Florian Bruhin
a0d0b6464f Use typing.py-like annotations for command args
This means:

- An annotation like (int, str) is now typing.Union[int, str].
- utils.typing got expanded so it acts like the real typing.py, with
  issubclass() working properly with typing.Union and __union_params__
  being set.
- A literal string doesn't exist anymore as annotation, instead
  @cmdutils.argument now has a 'choices' argument which can be used like
  @cmdutils.argument('arg', choices=['val1', 'val2']).
- Argument validating/converting is now entirely handled by
  argparser.type_conv instead of relying on python's argparse, i.e.
  type/choices is now not passed to argparse anymore.
2016-05-18 06:55:17 +02:00
Florian Bruhin
cdcd276a1a Also use @cmdutils.argument for :bind
This was added in master and not updated when this branch was rebased on
master.
2016-05-18 06:55:17 +02:00
Florian Bruhin
3a33bc42a6 Add initial support for the typing module 2016-05-18 06:55:17 +02:00
Florian Bruhin
9694374673 Add test for cmd arg types 2016-05-18 06:55:17 +02:00
Florian Bruhin
bb8d0a8ee4 Fix long lines 2016-05-18 06:55:17 +02:00
Florian Bruhin
c33f0c3512 Use @cmdutils.argument for completions 2016-05-18 06:55:17 +02:00
Florian Bruhin
3ab40bbc23 Clear globals correctly for all cmdutils tests
TestArgument didn't clear the globals as the fixture was inside
TestRegister.

This means test_run_vulture failed in funny ways because run_vulture.py
generated a whitelist containing "<locals>" for commands:

    tests/unit/scripts/test_run_vulture.py:55: in run
        return run_vulture.run([str(e.basename) for e in files])
    scripts/dev/run_vulture.py:146: in run
        vult.scavenge(files + [whitelist_file.name])
    .tox/py35/lib/python3.5/site-packages/vulture.py:107: in scavenge
        self.scan(module_string)
    .tox/py35/lib/python3.5/site-packages/vulture.py:75: in scan
        node = ast.parse(node_string, filename=self.file)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    source = 'qutebrowser.browser.commands.CommandDispatcher.buffer\nqutebrowser.misc.savemanager.SaveManager.save_command\nqutebro...iidoc.UsageFormatter._get_default_metavar_for_positional\nscripts.dev.src2asciidoc.UsageFormatter._metavar_formatter\n'
    filename = '/tmp/tmp_ein2umn', mode = 'exec'

        def parse(source, filename='<unknown>', mode='exec'):
            """
            Parse the source into an AST node.
            Equivalent to compile(source, filename, mode, PyCF_ONLY_AST).
            """
    >       return compile(source, filename, mode, PyCF_ONLY_AST)
    E         File "/tmp/tmp_ein2umn", line 16
    E           test_cmdutils.TestArgument.test_wrong_order.<locals>.fun
    E                                                       ^
    E       SyntaxError: invalid syntax
2016-05-18 06:55:17 +02:00
Florian Bruhin
1611562271 Get rid of dict annotations 2016-05-18 06:55:17 +02:00
Florian Bruhin
d990032a2e Remove leftovers of 'name' annotation key 2016-05-18 06:55:17 +02:00
Florian Bruhin
77151d394a Use @cmdutils.argument for metavar 2016-05-18 06:55:17 +02:00
Florian Bruhin
c14db202d6 Use @cmdutils.argument to hide arguments 2016-05-18 06:55:17 +02:00
Florian Bruhin
2fc4f408cd Fix src2asciidoc 2016-05-18 06:55:17 +02:00
Florian Bruhin
1d5fc4a140 Update @cmdutils.argument docs 2016-05-18 06:55:17 +02:00
Florian Bruhin
2370793f38 Make win_id/count mutually exclusive for ArgInfo 2016-05-18 06:55:17 +02:00
Florian Bruhin
35135c4b0d Use @cmdutils.argument for win_id/count 2016-05-18 06:55:17 +02:00
Florian Bruhin
04367851c3 Refactor how cmdutils.ArgInfo works
It's now a real class, and some other aspects about how it's handled
were cleaned up as well.
2016-05-18 06:55:17 +02:00
Florian Bruhin
9eeaeb95c3 Make sure @cmdutils.argument after @register fails 2016-05-18 06:55:17 +02:00
Florian Bruhin
3c586f34ff Add a @cmdutils.argument decorator
For now the only available keyword argument is 'flag' which customizes
the flag an argument will get.

See #637.
2016-05-18 06:55:17 +02:00
Florian Bruhin
965a012db7 Name strip_whitespace.sh correctly 2016-05-18 06:54:14 +02:00
Florian Bruhin
ee579b4bc3 Add a script to strip trailing whitespace
Obsoletes #1498
2016-05-18 06:37:26 +02:00
Liam BEGUIN
f689cca101 Added missing All group 2016-05-17 23:57:36 -04:00
Liam BEGUIN
c3dfd62172 broke line to fit properly 2016-05-17 23:39:24 -04:00
Ryan Roden-Corrent
52641e25f5 Show currently bound keys in command completion.
The command completion menu now uses the misc colum to show a
comma-separated list of normal-mode keybindings for each command.
2016-05-17 20:25:09 -04:00
Ryan Roden-Corrent
acdeb0f57c Bump default for partial-timeout to 5000ms.
Give more time to read the keyhint widget.
2016-05-17 20:13:25 -04:00
Ryan Roden-Corrent
e810317057 Restore bottom padding of keyhint widget.
Previously, negative bottom padding was used (probably to compensate
for a trailing <br>). With the table format, this is no longer
necessary and causes the last line to be drawn too low.
2016-05-17 20:10:52 -04:00
Liam BEGUIN
ce0d23bd10 Added tests for the inputs group 2016-05-17 19:58:48 -04:00
Liam BEGUIN
b66d5e1ce9 Fixed indentations 2016-05-17 19:58:27 -04:00
Ryan Roden-Corrent
822d148713 Update key hint tests for new format.
Change the unit tests to expect the new tabular format.
Also generally clean up the tests -- refactor from a class to
module-level functions as there was no need for a class here.
2016-05-17 07:04:53 -04:00
Panagiotis Ktistakis
959f777f92 Assert that hovered link generates a valid QUrl 2016-05-17 10:28:33 +03:00
Jimmy
cd21f6326e Save redirect links that are clicked on to history.
This allows webkit to color links that are clicked on but never rendered as
visited too. It also means if you get redirected from eg http://site.com to
http://site.com/ you have essentially duplicates in your history. This makes
the history completion a bit noisier. I suppose normalising paths before
checking for duplicates might help. Also note that otter has an isTypedIn flag
which might be used for dealing with this.
2016-05-17 17:15:22 +12:00
Jimmy
b48b36a88d fixup! Switch browsing history away from QWebHistoryInterface. 2016-05-17 17:15:22 +12:00
Jimmy
33fbe97863 Switch browsing history away from QWebHistoryInterface.
Now adds a url to browser history once we have connected and got enough data
to start rendering the page. The previous approach saved urls as soon as
navigation was initiated, so upon encountering a redirect the final url wasn't
saved.

Using layout started rather than load finished means that pages whose contents
manage to load minus one troublesome asset will still be saved.
2016-05-17 17:15:22 +12:00
Jimmy
1524f29f71 Add titles to history entry.
Adds a title to the HistoryEntry class and includes it in the serialization
stuff. Not currently set from anywhere.

Not sure if anything more needs to be done to support non-ascii characters.
Everything works fine for me with unicode chars in url and title but
everything in my stack is utf-8.
2016-05-17 17:15:22 +12:00
Liam BEGUIN
531a5071f6 updated inputs-group 2016-05-17 00:38:23 -04:00
Ryan Roden-Corrent
86f95c302d Explain timeout vs partial-timeout better in docs. 2016-05-16 21:40:30 -04:00
Felix Van der Jeugt
acb60a1bf4 align using a table 2016-05-16 07:14:34 -04:00
Liam BEGUIN
3b0354518b Added basic inputs-group to :hint 2016-05-15 22:46:12 -04:00
Ryan Roden-Corrent
3cd252ef82 Clean up html for keyhint text.
The \t was behaving the same as a space and the <b> was doing nothing.
2016-05-15 22:33:53 -04:00
Ryan Roden-Corrent
8eee5def5d Add unit tests for the keyhint widget.
- validate keyhint text for a partial keychain
- ensure special keybindings are not suggested
- ensure it is not visible when disabled
- ensure changes to the suffix color are picked up
2016-05-15 22:20:52 -04:00
Ryan Roden-Corrent
581a521b4d Allow setting mock keybindings for unit tests.
Implement mock_key_config.set_bindings_for to set bindings that will be
retrieved by mock_key_config.get_bindings_for.
This is useful for testing the new keyhint ui.
2016-05-15 22:18:16 -04:00
Ryan Roden-Corrent
d506d7793f Further clean up keyhints.
Don't show special keys in the keyhint window as these currently cannot
be part of keychains.
Use a rounded border on the top-right corner and square on the rest.
2016-05-15 12:32:36 -04:00
Panagiotis Ktistakis
224c877fec Hide passwords in hovering URLs 2016-05-15 19:07:10 +03:00
Florian Bruhin
0c1e82a103 Fix lint 2016-05-15 11:53:58 +02:00
Florian Bruhin
6fc2217b07 Merge branch 'rcorre-messages' 2016-05-15 11:51:20 +02:00
Florian Bruhin
76a755634b Update changelog 2016-05-15 11:51:09 +02:00
Florian Bruhin
9b28d90543 Improve output if :messages is used without errors 2016-05-15 11:50:29 +02:00
Florian Bruhin
b704c911ae Fix running qute:log and qute:plainlog
This failed because dict.get('level') returned None with no level
parameter, and the subsequent [0] raised:

    Traceback (most recent call last):
      File ".../qutebrowser/utils/utils.py", line 624, in wrapper
        return func(*args, **kwargs)
      File ".../qutebrowser/browser/network/networkmanager.py", line 445, in createRequest
        op, req, outgoing_data)
      File ".../qutebrowser/browser/network/qutescheme.py", line 107, in createRequest
        data = handler(self._win_id, request)
      File ".../qutebrowser/browser/network/qutescheme.py", line 189, in qute_log
        level = urllib.parse.parse_qs(request.url().query()).get('level')[0]
    TypeError: 'NoneType' object is not subscriptable
2016-05-15 11:33:30 +02:00
Florian Bruhin
bb31787931 Regenerate docs 2016-05-15 11:21:02 +02:00
Florian Bruhin
e21039094d Merge branch 'messages' of https://github.com/rcorre/qutebrowser into rcorre-messages 2016-05-15 11:20:06 +02:00
Florian Bruhin
8193644a04 Merge branch 'forkbong-set-cmd-text-variables' 2016-05-15 11:09:37 +02:00
Florian Bruhin
a9ab6abafd Document {url} and {url:pretty} for :set-cmd-text 2016-05-15 11:09:16 +02:00
Florian Bruhin
3831dc38f7 Regenerate authors 2016-05-15 11:09:08 +02:00
Florian Bruhin
0888f676f3 Change go/gO/xO/wO bindings to use {url:pretty}
This is now possible with #1494.
See #1483.
2016-05-15 11:06:54 +02:00
Florian Bruhin
b75d1629a8 Merge branch 'set-cmd-text-variables' of https://github.com/forkbong/qutebrowser into forkbong-set-cmd-text-variables 2016-05-15 11:01:58 +02:00
Florian Bruhin
6b4efc1822 Fix log output in crash report
With the changes to get rid of colorlog, we broke this, as e.g. {green}
was an undefined key for the vanilla logging.Formatter used for the
in-RAM log. Now we instead use a ColoredFormatter with colors turned
off.
2016-05-15 10:46:46 +02:00
Florian Bruhin
33236b5314 Make tox -e mkenv work
This is a common typo, so let's just make things work. It does the exact
same thing as tox -e mkvenv.
2016-05-15 09:59:44 +02:00
Ryan Roden-Corrent
231950aa88 Increase default value for input.partial-timeout.
Increase from 500 to 2500.
This allows the user more time to read hints shown in the new keyhint
popup.
2016-05-14 19:30:39 -04:00
Ryan Roden-Corrent
d592a3e764 Clean up keyhint implementation.
From code review:

- escape all strings used in the keyhint html
- read the prefix color each time the hint is shown
- use show/hide instead of setVisible
- clean up pylint/flake8 errors
- use CssColor instead of QssColor for keyhint.fg.suffix
- add some padding to the keyhint popup
2016-05-14 19:30:28 -04:00
Florian Bruhin
a1bac9509b Merge branch 'ismail-s-patch-1' 2016-05-14 23:52:43 +02:00
Florian Bruhin
e476a21876 Regenerate authors 2016-05-14 23:52:36 +02:00
Florian Bruhin
827ad3fb13 Merge branch 'patch-1' of https://github.com/ismail-s/qutebrowser into ismail-s-patch-1 2016-05-14 23:52:22 +02:00
Panagiotis Ktistakis
c88883fcb3 Decode URL in statusbar when hovering over it 2016-05-14 19:52:24 +03:00
Ryan Roden-Corrent
e7ff717d52 Show key hints for all modes, not just normal. 2016-05-14 12:50:26 -04:00
Ryan Roden-Corrent
8406746889 Show possible keychains from current input.
When the current keystring is a partial match for one or more bindings,
show the possible bindings in a small overlay.

The overlay is partially transparent by default, but the background
color is configurable as ui->keystring.bg.
2016-05-14 12:50:26 -04:00
Ryan Roden-Corrent
fcd233a675 Clean up :messages implementation.
- Add log.LOG_LEVELS to map names to levels (instead of using
  logging._levelToName)
- Test that log pages do not contain messages below the requested level
- Use pythons urllib.parse.parse_qs instead of Qt's UrlQuery
- Document tab, bg, window args for :messages
- Clean up style
2016-05-14 12:45:22 -04:00
Panagiotis Ktistakis
9527712daa Add tests for {url:pretty} in :set-cmd-text 2016-05-14 18:44:05 +03:00
Ryan Roden-Corrent
c36f760114 Add bdd test for page not containing plaintext.
You can now use 'the page should not contain the plaintext ...' in a
feature test.
2016-05-14 07:10:58 -04:00
Ryan Roden-Corrent
800c1c3cf8 Add :messages command to show past messages.
This adds a 'level' query parameter to qute://log and qute://plainlog.
For example, qute://log?level=warning will show an html page containing
log entries with severity warning or greater.
If the query is omitted, the original behavior of qute://log is
preserved.

:messages [level] is a command that opens qute://log?level=<level>.
By default, level defaults to 'error' as an easy way to see missed
error messages.
2016-05-14 07:10:58 -04:00
Ismail
d6fede7f1d Change 'commands' to 'keybindings' 2016-05-13 23:32:45 +01:00
Ismail
40c2bc7151 Add more commands to docs 2016-05-13 23:29:04 +01:00
Florian Bruhin
42460c3c04 tox: Update pyparsing to 2.1.4
- Split out the '==' behavior in ParserElement, now implemented
  as the ParserElement.matches() method. Using '==' for string test
  purposes will be removed in a future release.

- Expanded capabilities of runTests(). Will now accept embedded
  comments (default is Python style, leading '#' character, but
  customizable). Comments will be emitted along with the tests and
  test output. Useful during test development, to create a test string
  consisting only of test case description comments separated by
  blank lines, and then fill in the test cases. Will also highlight
  ParseFatalExceptions with "(FATAL)".

- Added a 'pyparsing_common' class containing common/helpful little
  expressions such as integer, float, identifier, etc. I used this
  class as a sort of embedded namespace, to contain these helpers
  without further adding to pyparsing's namespace bloat.

- Minor enhancement to traceParseAction decorator, to retain the
  parse action's name for the trace output.

- Added optional 'fatal' keyword arg to addCondition, to indicate that
  a condition failure should halt parsing immediately.
2016-05-13 21:21:14 +02:00
Florian Bruhin
c64e5c9bd5 Get rid of the colorlog dependency
colorlog was problematic for various reasons:

- Not commonly packaged for Linux distributions
- Calling colorama.init() automatically on import
- Not supporting {foo} log formatting
- Not supporting an easy way to turn colors off

Instead we now do the log coloring by hand, which is simpler and means
everyone will have colored logs.
2016-05-13 21:19:33 +02:00
Panagiotis Ktistakis
bb45392c1b Use the right split function 2016-05-13 19:32:35 +03:00
Ismail
9264bd077a Add basic commands to docs 2016-05-13 17:13:45 +01:00
Panagiotis Ktistakis
ecfa6cfc63 Split arguments using qutebrowser.utils.split 2016-05-13 18:59:24 +03:00
Panagiotis Ktistakis
7488f2a5cb Remove unused imports 2016-05-13 18:58:42 +03:00
Panagiotis Ktistakis
38edb1b16d Properly replace variables in set-cmd-text command
This fixes #123 and allows variables like {url:pretty} to be used with
set-cmd-text
2016-05-13 18:12:53 +03:00
Florian Bruhin
a9a853baf0 Improve message stack output
The output failed the tests as it was parsed as invalid. Indenting helps
with that.

This also simplifies things a bit by having a _log_stack function and
shows the type of stack we're printing.
2016-05-13 08:27:11 +02:00
Florian Bruhin
4a551a6758 Show exception stack when handling cmdexc errors. 2016-05-13 07:43:44 +02:00
Florian Bruhin
b6abada50a Fix lint 2016-05-13 06:42:09 +02:00
Florian Bruhin
a7ece80d34 Merge branch 'pyinstaller' 2016-05-13 06:21:28 +02:00
Florian Bruhin
d507727bb3 tox: Update PyInstaller to 3.2
- Even the “main” script is now byte-compiled
- The manual is on readthedocs.io now
- On installation try to compile the bootloader if there is none for the
  current plattform
- (Unix) Use objcopy to create a valid ELF file
- (Linux): Compile with _FORTIFY_SOURCE
- New, updated and fixed hooks: CherryPy, Cryptography, enchant,
  gi.repository.GdkPixbuf, gst, Lib2to3, PyQt4, PyQt5, PySide, SciPy,
  sphinx, sqlalchemy, traitlets, wx.lib.pubsub
- For windowed mode add isatty() for our dummy NullWriter
- Suppress “Failed to execute script” in case of SystemExit
- Do not apply Upx compressor for bootloader files
- Fix absolute path for lib used via ctypes
- (OSX) Fix binary cache on NFS
- (Windows) Fix message in grab_version
- (Windows) Fix wrong icon paramter in Windows example
- (Windows) Fix win32 unicode handling
- (Windows) Fix unnecessary rebuilds caused by rebuilding winmanifest
- (Cygwin) Fix finding the Python library for Cygwin 64-bit
- (OSX) Fix compilation issue
- (Windows) No longer bundle pefile, use package from for windows
- (Windows) Provide a more robust means of executing a Python script
- AIX fixes.
- Update waf to version 1.8.20
- Fix excludedimports, more predictable order how hooks are applied
- Internal impovements and code clean-up
- Clean-ups fixes and improvements for the test suite
2016-05-13 05:23:19 +02:00
Florian Bruhin
b755d56c02 Rename setup.cfg to .flake8
We only use it to store flake8 config, and it turns out flake8 didn't
actually deprecate .flake8.
2016-05-12 22:58:12 +02:00
Florian Bruhin
aea30e810a Fix lint 2016-05-12 21:11:45 +02:00
Florian Bruhin
af1880fc1d tox: Update pyflakes to 1.2.3 2016-05-12 21:11:14 +02:00
Florian Bruhin
7901d2929a ci: Update pip to 8.1.2
Since virtualenv uses the built-in pip version with VIRTUALENV_DOWNLOAD=no we
need to make sure we keep virtualenv capped, but we can use a newer pip.
2016-05-12 20:32:03 +02:00
Florian Bruhin
b634b051c8 travis: Set VIRTUALENV_DOWNLOAD=no
Things broke because of the virtualenv upgrade in requirements-tox.txt.
virtualenv bundles a "good" pip version (good: doesn't suffer
from #1486). However the virtualenv upgrade caused us to get a new
version which downloads a new pip. Setting VIRTUALENV_DOWNLOAD=no
prevents that from happening.
2016-05-12 20:32:03 +02:00
Florian Bruhin
e70d6d49d9 travis: Separate requirement files
This way we can update pip independently before installing the rest, and
avoid installing codecov (and thus coverage which attempts to build C
extension) where it's not needed.
2016-05-12 20:32:03 +02:00
Florian Bruhin
d4e2b4ebb4 travis: Don't update Python for flake8 env
This was needed because of a bug for which the backfix seems to be
ported to Ubuntu now.
2016-05-12 20:32:03 +02:00
Florian Bruhin
56a2b6778c travis: Install libpython3.4-dev
This is now needed because we use "language: generic" which doesn't come
with that package.
2016-05-12 20:32:03 +02:00
Florian Bruhin
0086fc7851 travis: Use "language: generic"
This means we'll get the most minimal image and can install exactly what
we need.
2016-05-12 20:32:03 +02:00
Florian Bruhin
0632f6886e flake8: Adjust appveyor_install.py path
We have some ignores local to that file, and after renaming the file
they showed up again.

We also remove the C901 ignore (mccabe complexity check) as the script
got simpler.
2016-05-12 20:32:03 +02:00
Florian Bruhin
e13320b398 travis: Use python2 for pip
This means we won't need to install pip via apt-get, and using a
Python 2 tox/pip is fine.
2016-05-12 20:32:03 +02:00
Florian Bruhin
3033f77f99 travis: Use (( in travis_retry
If we use [, the script exits when a condition is false (as we use
set -e).
2016-05-12 20:32:03 +02:00
Florian Bruhin
cdc79339fb travis: Use requirement files for pip
This way we can pin tox/pip versions (which are used in the native
Travis environment) *and* get notified about updates by requires.io.
2016-05-12 20:32:03 +02:00
Florian Bruhin
6558e196b4 Simplify package installation on CI
Having a Python script was a good idea back when we could do almost the
same steps on every CI configuration.

This turned out to grow into a complicated script, so it's easier to
split off things for Linux/OS X into a small shell script (and keep
Python for Windows, as I really don't want to use .bat/.ps).
2016-05-12 20:32:03 +02:00
Florian Bruhin
768ff8b193 tox: update pyparsing to 2.1.3
- _trim_arity fix in 2.1.2 was very version-dependent on Py 3.5.0.
  Now works for Python 2.x, 3.3, 3.4, 3.5.0, and 3.5.1 (and hopefully
  beyond).

This fixes pyparsing/flake8-putyt/flake8 being broken with python 3.4:

    >>> import pyparsing
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File ".../pyparsing.py", line 3478, in <module>
        _escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
      File ".../pyparsing.py", line 948, in setParseAction
        self.parseAction = list(map(_trim_arity, list(fns)))
      File ".../pyparsing.py", line 808, in _trim_arity
        this_line = extract_stack()[-1]
      File ".../pyparsing.py", line 793, in extract_stack
        return [(frame_summary.filename, frame_summary.lineno)]
    AttributeError: 'tuple' object has no attribute 'filename'

See https://sourceforge.net/p/pyparsing/bugs/95/
2016-05-12 20:32:03 +02:00
Florian Bruhin
37a1d0cb6f Log stacktrace of error messages to debug log 2016-05-12 20:30:07 +02:00
Florian Bruhin
e095f9ded2 tox: Downgrade pyparsing to 2.1.1
It seems pyparsing 2.1.2 is broken with Python 3.4:

    >>> import pyparsing
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File ".../pyparsing.py", line 3478, in <module>
        _escapedPunc = Word( _bslash, r"\[]-*.$+^?()~ ", exact=2 ).setParseAction(lambda s,l,t:t[0][1])
      File ".../pyparsing.py", line 948, in setParseAction
        self.parseAction = list(map(_trim_arity, list(fns)))
      File ".../pyparsing.py", line 808, in _trim_arity
        this_line = extract_stack()[-1]
      File ".../pyparsing.py", line 793, in extract_stack
        return [(frame_summary.filename, frame_summary.lineno)]
    AttributeError: 'tuple' object has no attribute 'filename'

That breaks flake8-putty and thus flake8.
See https://sourceforge.net/p/pyparsing/bugs/95/
2016-05-11 08:15:12 +02:00
Florian Bruhin
3098d1fd8c tox: Hardcode more indirect deps for flake8 env
We added more flake8 checkers but never regenerated those, causing some
updates to happen silently and (probably?) breaking stuff.
2016-05-11 06:34:19 +02:00
Florian Bruhin
7a51387fb3 Move comment in tox.ini
On Travis CI, the flake8 env suddenly started to fail (pip upgrade?):

    Invalid requirement: 'pep257==0.7.0  # still needed by flake8-docstrings but ignored'
    Traceback (most recent call last):
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/packaging/requirements.py", line 92, in __init__
        req = REQUIREMENT.parseString(requirement_string)
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 1172, in parseString
        raise exc
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 1162, in parseString
        loc, tokens = self._parse( instring, 0 )
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 1028, in _parseNoCache
        loc,tokens = self.parseImpl( instring, preloc, doActions )
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 2462, in parseImpl
        loc, exprtokens = e._parse( instring, loc, doActions )
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 1032, in _parseNoCache
        loc,tokens = self.parseImpl( instring, preloc, doActions )
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/pyparsing.py", line 2265, in parseImpl
        raise ParseException(instring, loc, self.errmsg, self)
    pip._vendor.pyparsing.ParseException: Expected stringEnd (at char 15), (line:1, col:16)
    During handling of the above exception, another exception occurred:
    Traceback (most recent call last):
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/req/req_install.py", line 78, in __init__
        req = Requirement(req)
      File "/home/travis/build/The-Compiler/qutebrowser/.tox/flake8/lib/python3.4/site-packages/pip/_vendor/packaging/requirements.py", line 96, in __init__
        requirement_string[e.loc:e.loc + 8]))
    pip._vendor.packaging.requirements.InvalidRequirement: Invalid requirement, parse error at "'# still '"

I can't reproduce this locally, but I hope this'll help.
2016-05-11 06:08:48 +02:00
Florian Bruhin
ac41c0ba18 tox: Update CherryPy to 5.4.0
* ``cherrypy.test.webtest.WebCase`` now honors a
  'WEBTEST_INTERACTIVE' environment variable to disable
  interactive tests (still enabled by default). Set to '0'
  or 'false' or 'False' to disable interactive tests.
* Fix AttributeError when listiterator was accessed
  using the ``next`` attribute.
* Removed ``cherrypy.lib.sessions.PostgresqlSession``.
* Fix errors with redirects to Unicode URLs.
2016-05-11 05:16:51 +02:00
Florian Bruhin
181a785ce6 Update changelog
[ci skip]
2016-05-10 23:49:10 +02:00
Florian Bruhin
62d35db16a Merge branch 'rcorre-show_binding2' 2016-05-10 23:48:30 +02:00
Florian Bruhin
cb27dbbfb5 Fix error message check for :bind test 2016-05-10 23:47:16 +02:00
Florian Bruhin
ea243ae022 Renumber keybindings in keyinput.feature
An unique keybinding for each test means we have some level of
isolation and can understand error messages more easily.

As we're >10 now, let's use a leading zero to avoid shadowed
keybindings.
2016-05-10 23:46:02 +02:00
Florian Bruhin
90c42ff2e2 Regenerate docs 2016-05-10 23:39:48 +02:00
Florian Bruhin
24d16dd0a7 Merge branch 'show_binding2' of https://github.com/rcorre/qutebrowser into rcorre-show_binding2 2016-05-10 23:39:32 +02:00
Florian Bruhin
36da07c73b Fix lint 2016-05-10 07:56:54 +02:00
Florian Bruhin
5eff35ba30 cmdutils.register: annotation -> arg for flags
Instead of using a 'flag' key in the annotation dict, we now use a flags
argument to @cmdutils.register which is a {argument: flag} dict.

See #637.
2016-05-10 07:35:41 +02:00
Florian Bruhin
49fb981e7f Add test for completion count in cmdutils.register 2016-05-10 07:12:50 +02:00
Florian Bruhin
9ca5acd546 Add tests for flags with @cmdutils.register 2016-05-10 07:00:10 +02:00
Florian Bruhin
b17ecd1376 Add tests for cmdutils.register/star_args_optional 2016-05-10 06:41:42 +02:00
Florian Bruhin
73fbfb9731 Replace 'nargs' annotation by star_args_optional
Before we used a {'nargs': '*'} annotation for the respective argument
to tell qutebrowser it's optional for the commandline. Now we instead
use a star_args_optional argument for @cmdutils.register as a first step
towards freeing up argument annotations for PEP 484.

See #637.
2016-05-09 22:49:24 +02:00
Florian Bruhin
e4e98c6c23 Delete QTextDocument properly in completion.
The CompletionItemDelegate gets reused by Qt for various items in the
completion. Every time _get_textdoc() was called we created a new
QTextDocument, but since it has a long-living parent set (the delegate)
the old one was never actually garbage collected.

We now explicitly delete the old QTextDocument as it's not needed
anymore by either Qt or Python.

See #1476.
2016-05-09 09:11:14 +02:00
Florian Bruhin
56f1d885f9 Use parens instead of \ to continue line 2016-05-09 07:18:12 +02:00
Florian Bruhin
2c6826f9e0 Merge branch 'phansch-bdd_test_insert_mode' 2016-05-09 07:15:48 +02:00
Florian Bruhin
8f3dda6709 Regenerate authors 2016-05-09 07:15:19 +02:00
Florian Bruhin
a8845be9e6 Merge branch 'bdd_test_insert_mode' of https://github.com/phansch/qutebrowser into phansch-bdd_test_insert_mode 2016-05-09 07:15:10 +02:00
Florian Bruhin
e132c1cc1e Merge branch 'Kingdread-confirm-quit-downloads' 2016-05-09 07:07:59 +02:00
Florian Bruhin
7f99c36ec5 Update changelog 2016-05-09 07:07:46 +02:00
Florian Bruhin
f7dc9b54bd Add a test for #846 2016-05-09 07:06:58 +02:00
Daniel Schadt
1fa50021c1 downloads: use right index for beginInsertRows
len(self.downloads) is already the index of the item in the download
list, this should be used for beginInsertRows(). The +1 is only for the
human readable part.
2016-05-08 23:36:31 +02:00
Daniel Schadt
99182e3e79 downloads: change len() to sum() 2016-05-08 23:36:31 +02:00
Daniel Schadt
643d2cc6dd fix confirm-quit=downloads with finished downloads
Issue #846

.rowCount() returns all downloads, even the finished ones that have not
yet been removed from the list. For confirming the quit event, we should
only consider downloads that are still running.
2016-05-08 23:36:31 +02:00
Florian Bruhin
a23aa1cc47 Fix broken supports_selection() test 2016-05-08 22:39:39 +02:00
Florian Bruhin
89c7b0e7f8 Simplify if-statement 2016-05-08 22:18:14 +02:00
Florian Bruhin
165504c1f2 bdd: Fix clipboard_contains_multiline 2016-05-08 22:09:19 +02:00
Florian Bruhin
59ec5fa947 Show debug instead of warning for #670
The user can't do much about this anyways, and I have no idea what
triggers it, so let's not annoy them about it.
2016-05-08 22:08:08 +02:00
Florian Bruhin
b9b6f357da Add utils.supports_selection() 2016-05-08 22:06:59 +02:00
Florian Bruhin
3e6ac28c66 Fix ;Y on systems not supporting primary selection
Instead we paste clipboard like we already do with some other commands
when primary selection is not supported.

Fixes #1336
2016-05-08 21:59:25 +02:00
Florian Bruhin
4d9a98a11d Paste clipboard with :paste-primary on Windows 2016-05-08 21:18:57 +02:00
Florian Bruhin
ddc1f803c0 Clean up assertion 2016-05-08 20:01:35 +02:00
Florian Bruhin
f49cc4e901 Only keep contain tests 2016-05-08 19:57:59 +02:00
Florian Bruhin
65ed878dcf Update copyright 2016-05-08 19:57:18 +02:00
Florian Bruhin
fdb28e4c71 Add test_sortfilter from #950 2016-05-08 19:56:41 +02:00
Florian Bruhin
55f6ce1c95 Merge branch 'fix-link-pyqt' of https://github.com/Kingdread/qutebrowser into Kingdread-fix-link-pyqt 2016-05-08 19:12:26 +02:00
Florian Bruhin
f306ca9b53 Fix lint 2016-05-08 11:14:42 +02:00
Florian Bruhin
b4272975f2 Revert "Use lists instead of tuples"
This reverts commit eb5bfc1659.
2016-05-08 11:09:47 +02:00
Daniel Schadt
dcb2c7f868 link_pyqt: fix crash with spaces in pypath
This fixes a bug where link_pyqt crashes when the path to the python
interpreter contains a space.
2016-05-08 01:08:36 +02:00
Florian Bruhin
95fa78fce3 Merge branch 'jcorentin-test_adblock' 2016-05-07 23:45:53 +02:00
Florian Bruhin
4403f02ac5 Fix HostBlocker.on_config_changed with no datadir 2016-05-07 23:35:30 +02:00
Florian Bruhin
6d1764e732 Clear blocked hosts on start correctly 2016-05-07 23:31:35 +02:00
Florian Bruhin
b6add69705 Improve exception handling in HostBlocker
In on_config_changed, we now ignore FileNotFoundError as that's a common
occurence and not something worth logging.

In case of other OSError's we now also log the exact error message.
2016-05-07 23:30:32 +02:00
Florian Bruhin
f6544786c1 dict 2016-05-07 22:41:22 +02:00
Florian Bruhin
2d8bde62a5 docstring 2016-05-07 22:39:53 +02:00
Florian Bruhin
9e64e5eab4 Check CommandError exception value 2016-05-07 22:39:09 +02:00
Florian Bruhin
f4f329714d Reformat dicts 2016-05-07 22:38:37 +02:00
Florian Bruhin
1a03388fb5 Fix docstrings 2016-05-07 22:28:06 +02:00
Florian Bruhin
eb5bfc1659 Use lists instead of tuples 2016-05-07 22:12:28 +02:00
Florian Bruhin
ccd0d1621c Regenerate authors 2016-05-07 22:10:19 +02:00
Florian Bruhin
bc8d19f003 Fix typo 2016-05-07 22:09:43 +02:00
Florian Bruhin
2ae8ecff71 Use qapp fixture in all adblock tests 2016-05-07 22:08:55 +02:00
Florian Bruhin
7db6f52fa1 Merge branch 'test_adblock' of https://github.com/jcorentin/qutebrowser into jcorentin-test_adblock 2016-05-07 22:06:43 +02:00
Florian Bruhin
6042aa48ca Merge branch 'haitaka-feature-#1349' 2016-05-07 21:59:38 +02:00
Florian Bruhin
a5cea14a0f Update docs 2016-05-07 21:39:19 +02:00
Florian Bruhin
f90776f75c Add documentation for default_window_icon 2016-05-07 21:37:01 +02:00
Florian Bruhin
2ea76c282e Merge branch 'feature-#1349' of https://github.com/haitaka/qutebrowser into haitaka-feature-#1349 2016-05-07 21:32:05 +02:00
Florian Bruhin
d3d765fb97 tox: Update pyflakes to 1.2.2
- Avoid traceback when exception is del-ed in except
2016-05-06 19:02:59 +02:00
Florian Bruhin
612ef09f97 Merge branch 'Kingdread-download-ssl-crash' 2016-05-06 18:23:24 +02:00
Florian Bruhin
e81c13cf35 Update changelog 2016-05-06 18:23:16 +02:00
Florian Bruhin
be7dd6e045 Merge branch 'download-ssl-crash' of https://github.com/Kingdread/qutebrowser into Kingdread-download-ssl-crash 2016-05-06 18:18:06 +02:00
Daniel Schadt
2918c5cd57 downloads: close fileobject in DownloadItem._die
Otherwise we will get a unclosed resource warning.
2016-05-06 18:01:45 +02:00
Florian Bruhin
16f071c778 tox: Update pyflakes to 1.2.1
1.2.1 (2015-05-05):
  - Fix false RedefinedWhileUnused for submodule imports

1.2.0 (2016-05-03):
  - Warn against reusing exception names after the except: block on Python 3
  - Improve the error messages for imports
2016-05-06 16:24:58 +02:00
Daniel Schadt
dcac832f5e netmanager: fix crash when asking with no tab_id
Issue 1413

This happens when the networkmanager is used by something that has no
tab_id, like the generic DownloadManager. In this case, we should just
skip the webview connection (as it makes no sense) instead of crashing
(which is the last thing we want to do).
2016-05-05 00:34:16 +02:00
Florian Bruhin
d2be8a28ca Also ignore segfaults in paintEvent 2016-05-04 23:06:26 +02:00
Florian Bruhin
2d0604ec08 Remove unused imports 2016-05-04 22:15:47 +02:00
Florian Bruhin
034f8585a6 Disable segfault reports for Qt mainloop crashes 2016-05-04 21:50:44 +02:00
Fritz Reichwald
fb88a7b1ef Change the link description to release page 2016-05-04 21:03:19 +02:00
Florian Bruhin
3f66ea1a10 Stabilize buffer test, attempt 2
"Current tab changed" actually waited for an unrelated earlier event.
2016-05-04 20:56:54 +02:00
Florian Bruhin
7a82c13b27 bdd: Also log already found log messages 2016-05-04 20:54:34 +02:00
Florian Bruhin
0ff5c56d05 Merge branch 'error800-patch-1' 2016-05-04 07:09:43 +02:00
Florian Bruhin
e880497d17 Regenerate authors 2016-05-04 07:09:37 +02:00
Florian Bruhin
0d68ae259d Merge branch 'patch-1' of https://github.com/error800/qutebrowser into error800-patch-1 2016-05-04 07:09:25 +02:00
Florian Bruhin
8912012c7d Merge branch 'rcorre-color_regex' 2016-05-04 07:07:47 +02:00
Florian Bruhin
0efd87af12 Clean up formatting of num 2016-05-04 07:07:24 +02:00
Florian Bruhin
46189977ae Merge branch 'color_regex' of https://github.com/rcorre/qutebrowser into rcorre-color_regex 2016-05-04 07:04:40 +02:00
Florian Bruhin
4a1a87a4db I don't know how to magit 2016-05-04 07:03:46 +02:00
Florian Bruhin
9c09c266a6 tox: Exclude pyflakes 1.2.0 for requirements.io 2016-05-04 07:02:33 +02:00
Ryan Roden-Corrent
b8a593cac5 Show command completions for :bind.
All commands will be offered as completions for the <command> argument
of :bind.

Due to the way completers parse the command line, the following

bind --mode caret j

will throw off completions as 'caret' is treated as a positional arg in
terms of the argument count for completions.
In the above example, completion will be triggered for 'j' and no
completions will be given for the actual command.

bind --mode=caret j will complete correctly, though completions are not
filtered by the given mode.
I attempted an approach to filter the commands based on the mode but it
ended up being messy and flaky.
2016-05-03 23:29:34 -04:00
Ryan Roden-Corrent
2536766cac Run :bind <key> to print the current binding.
The <command> arg is now optional. If omitted, :bind prints the current
binding as a message. If --mode is given, the binding for that mode is
printed.
2016-05-03 23:29:34 -04:00
Ryan Roden-Corrent
98508bdd26 Allow flexible whitespace in color strings.
Allow a variable amount of whitespace for rgb, rgba, hsv, and hsva
strings in the config.
Previously only 'rgb(0, 0, 0)' was allowed. Now things like
'rgb(0,0,0)' are permitted.
The repeated 3-digit segments of the regexes were separated out to
reduce repetition and line length.
2016-05-03 23:15:39 -04:00
Fritz Reichwald
8c0d2f4dcc Add xpm version of the 32x32 png logo 2016-05-03 22:22:58 +02:00
Error 800
68a24b2aa4 Update 9. How do I play Youtube videos with mpv?
Add description of umpv script.
2016-05-03 11:12:20 +02:00
Florian Bruhin
d03659b3da Regenerate docs 2016-05-03 06:51:24 +02:00
Florian Bruhin
a6cd91c386 Turn off WebGL by default
This was mainly turned on because that's Qt's upstream default, but
there's no reason for it to be turned on.
2016-05-03 06:33:08 +02:00
Fritz Reichwald
e56c928703 use asciidoc instead of markdown 2016-05-02 21:38:35 +02:00
Fritz Reichwald
d994694739 Add manual .deb installation instructions 2016-05-02 20:50:03 +02:00
Florian Bruhin
e6ab681a08 Add codecov.yml to MANIFEST.in 2016-05-02 07:14:08 +02:00
Florian Bruhin
35bfcdba8a Add a codecov.yml
This hopefully turns off commit status and comments
2016-05-02 06:52:16 +02:00
Florian Bruhin
e5630e9518 Fix lint 2016-05-01 23:03:27 +02:00
Florian Bruhin
8fb1d568ee tests: Actually log the colored log 2016-05-01 23:01:22 +02:00
Florian Bruhin
3eeacd7e09 Fix userscripts on Windows
If the process emitted error() and then finished(), we already set
self._filepath to None and did other cleanup.

Instead we do the file reading inside _cleanup and call that from
on_process_error and on_process_finished.
2016-05-01 22:47:03 +02:00
Florian Bruhin
43908dba20 Use colored logging for end-to-end tests 2016-05-01 22:45:21 +02:00
Florian Bruhin
0b24916fc7 Add missing attribute 2016-05-01 22:34:04 +02:00
Florian Bruhin
c76746ca32 Refine Logjam cipher blacklisting
- The OS check was wrong
- EDH ciphers are (hopefully) not affected and break e.g. gnupg.org
2016-05-01 22:19:11 +02:00
Florian Bruhin
4e333d61cd Clean up failed userscripts correctly 2016-05-01 22:13:52 +02:00
Florian Bruhin
e3f1949f57 bdd: Fix parsing of logged python warnings 2016-05-01 22:13:31 +02:00
Florian Bruhin
dc1b0920ab tox: Update hypothesis to 3.1.3
- Another charmap problem. In 3.1.2 text/characters would break on
  systems which had /tmp/ mounted on a different partition than the
  Hypothesis storage directory (usually in home). This fixes that.
2016-05-01 17:49:44 +02:00
Florian Bruhin
2875596a1e tox: Update flake8-docstrings to 0.2.6
- Respect pep257’s default ignore list
- Handle AllError and other exceptions from pep257
2016-05-01 17:48:58 +02:00
Florian Bruhin
73d5d24cb4 Fix lint 2016-05-01 17:48:35 +02:00
Florian Bruhin
6bd2591681 Disable all Diffie-Hellman ciphers on Windows 2016-05-01 17:42:20 +02:00
Florian Bruhin
0124354b91 Disable more bad SSL ciphers with old Qt/OpenSSL
See #594
2016-05-01 17:31:00 +02:00
Florian Bruhin
7b575460d5 Fix URL for :quickmark-save test
We never noticed this because CherryPy handled this incorrectly in
versions < 5.3.0
2016-04-30 19:08:36 +02:00
Florian Bruhin
2b890901ae Unskip :stop test on OS X
CherryPy now ignores EPROTOTYPE on OS X with 5.3.0, so we can safely
reenable the test.
2016-04-30 18:43:31 +02:00
Florian Bruhin
c718254e0c tox: Update CherryPy to 5.3.0
* Add support for specifying a certificate authority when serving SSL
  using the built-in SSL support.
* Use ssl.create_default_context when available.
* Catch platform-specific socket errors on OS X.
* Fix parsing of URIs containing ``://`` in the path part.
2016-04-30 18:42:50 +02:00
Florian Bruhin
7a3c0b959d Fix release checklist 2016-04-30 18:40:27 +02:00
Florian Bruhin
b7225924e3 Fix lint 2016-04-30 18:39:26 +02:00
Florian Bruhin
48c7eee6f6 Release v0.6.2 2016-04-30 18:16:43 +02:00
Florian Bruhin
379c6561e5 tox: Update requests everywhere 2016-04-30 17:51:04 +02:00
Florian Bruhin
fe9a27a607 tox: Update hypothesis to 3.1.2
Anything which used a text() or characters() strategy was broken on
Windows and I hadn't updated appveyor to use the new repository location
so I didn't notice. This is now fixed and windows support should work
correctly.
2016-04-30 17:49:38 +02:00
Florian Bruhin
ccdd832cbc tox: Update CherryPy to 5.2.0 2016-04-30 17:49:28 +02:00
Florian Bruhin
43d898aa63 Fix log tests 2016-04-30 17:45:45 +02:00
Florian Bruhin
528e303d6e Add a --force-color argument for logging 2016-04-30 17:01:45 +02:00
Florian Bruhin
48d7185c94 log: Don't use colorama on POSIX 2016-04-30 16:59:59 +02:00
Florian Bruhin
365a6ec3ad Update changelog for v0.6.2 2016-04-30 14:47:05 +02:00
Florian Bruhin
f613835873 tox: Update requests to 2.10.0
- SOCKS proxy support
- Updated bundled urllib3 to 1.15.1.
2016-04-30 14:32:43 +02:00
Florian Bruhin
89caf3f497 Add a test for #1464 2016-04-30 14:29:55 +02:00
Florian Bruhin
1ea516f0b5 Update changelog 2016-04-30 14:11:08 +02:00
Florian Bruhin
e9da3ef8bb Merge branch 'NoctuaNivalis-issue-1393' 2016-04-30 14:10:37 +02:00
Florian Bruhin
580f3ed7dc Remove added blank line 2016-04-30 14:09:14 +02:00
Florian Bruhin
e04bf2b74b Regenerate authors 2016-04-30 14:08:51 +02:00
Florian Bruhin
b5aad7d1b9 Merge branch 'issue-1393' of https://github.com/NoctuaNivalis/qutebrowser into NoctuaNivalis-issue-1393 2016-04-30 14:06:54 +02:00
Florian Bruhin
e06a32ff42 Revert "travis: Switch to autobuilt Docker images"
This reverts commit b2e52408f7.
Turns out only [testing] is on PyQt 5.6, and even with that the testsuite
doesn't seem to load properly...
2016-04-29 23:21:41 +02:00
Florian Bruhin
b2e52408f7 travis: Switch to autobuilt Docker images
Since Archlinux switched to PyQt 5.6, we can now use the autobuilt image
without a handbuilt PyQt. This means we have an up-to-date image with
each build again.
2016-04-29 22:54:00 +02:00
Florian Bruhin
294e0799c2 tox: Update hypothesis to 3.1.1
- Fix concurrency issue when running tests that use text() from multiple
  processes at once
- Improve performance of code using lists with max_size
- Fix install on Python 2 with ancient versions of pip so that it
  installs the enum34 backport
- Remove duplicated __all__ exports from hypothesis.strategies
- Update headers to point to new repository location.
- Allow use of strategies that can’t be used in find() (e.g. choices) in
  stateful testing.
2016-04-29 22:25:50 +02:00
Florian Bruhin
4807746e8f Add @pyqtSlot for qApp.focusChanged slot 2016-04-29 22:25:27 +02:00
Felix Van der Jeugt
86d08f741c shorten page and wait for load finished 2016-04-29 21:56:24 +02:00
Florian Bruhin
2d5ffbfd02 Revert "Handle counts for special keys."
This reverts commit c881730fad.

This is just a temporary solution until we can properly fix this.

See #1464
2016-04-29 14:20:11 +02:00
Florian Bruhin
60f8681b41 Improve name of unused variable 2016-04-28 22:28:12 +02:00
Florian Bruhin
868f7c6ea5 Merge branch 'rcorre-special_key_count' 2016-04-28 22:25:46 +02:00
Florian Bruhin
e374599988 Update changelog 2016-04-28 22:25:39 +02:00
Florian Bruhin
8b227f4ba4 Merge branch 'special_key_count' of https://github.com/rcorre/qutebrowser into rcorre-special_key_count 2016-04-28 22:22:23 +02:00
adam
5eea9d0605 Cleanup for flake8/pylint 2016-04-28 09:20:16 -04:00
Florian Bruhin
c39800675e Merge branch 'toofar-buffer_completion_delete_item-1443' 2016-04-28 07:09:38 +02:00
Florian Bruhin
2f10ab4e9a Clean up coding style 2016-04-28 07:07:49 +02:00
Florian Bruhin
c228bb263b Update docs 2016-04-28 07:05:55 +02:00
Florian Bruhin
15a145cf92 Merge branch 'buffer_completion_delete_item-1443' of https://github.com/toofar/qutebrowser into toofar-buffer_completion_delete_item-1443
Merge conflict in qutebrowser/completion/models/miscmodels.py due to a simple
style fix in the old code.
2016-04-28 07:03:27 +02:00
adam
8fd371d836 Proposed addition for issue #1386 2016-04-27 16:47:36 -04:00
Florian Bruhin
0a76a7584d Update changelog 2016-04-27 21:21:30 +02:00
Florian Bruhin
4384e6f81c Merge branch 'Kingdread-issue-1412' 2016-04-27 21:19:24 +02:00
Florian Bruhin
076b486368 Merge branch 'issue-1412' of https://github.com/Kingdread/qutebrowser into Kingdread-issue-1412 2016-04-27 21:19:04 +02:00
Florian Bruhin
ebfcce172b Stabilize :buffer tests 2016-04-27 21:08:16 +02:00
Florian Bruhin
24cde47881 Merge branch 'marks_invalid_url' of https://github.com/rcorre/qutebrowser into rcorre-marks_invalid_url 2016-04-27 21:00:21 +02:00
Florian Bruhin
2a343cb3a1 Various code style improvements 2016-04-27 20:25:27 +02:00
Ryan Roden-Corrent
88f66874a1 Don't crash when setting mark on invalid url. 2016-04-27 08:10:53 -04:00
Daniel Schadt
e8aa242d10 tests: fix invocation test for --cachedir
--cachedir="" doesn't work because the quotes are not processed (as they
would be by the shell) and the cachedir is set to ./"" (that is a
directory with two double quotes as name). The correct start parameter
is thus --cachedir=, which correctly fails when the fix is reverted.
2016-04-27 13:02:18 +02:00
Daniel Schadt
951cab237f tests: factor out common code in test_invocations 2016-04-27 00:04:03 +02:00
Daniel Schadt
758fb94414 tests: add test for DiskCache with cache_dir=None 2016-04-27 00:04:02 +02:00
Daniel Schadt
c0b372591a tests: add tests for --cachedir="" 2016-04-26 23:26:35 +02:00
Daniel Schadt
dcad81a78f cache: fix crash when cache_dir is None
Issue #1412

When passing --cachedir="" on the command line, standarddir.cache()
returns None, which stands for "deactivate cache" and has to be
properly handled in DiskCache.__init__() (i.e. don't pass it to
os.path.join)
2016-04-26 23:14:55 +02:00
Florian Bruhin
fd20b46b33 Split IPCServer.on_ready_read into two methods 2016-04-26 23:14:03 +02:00
Florian Bruhin
7c3361d8da Stabilize :download-delete test 2016-04-26 22:56:06 +02:00
Felix Van der Jeugt
b7ba3cd53e fix flake and pep remarks 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
2d71c541c6 allow swapping dict at runtime 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
fdb630555d make robust against short dicts 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
ae72841856 read the wait_for code 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
9633e79d87 why don't I even know my own options 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
7d9d4937aa initial testing - local tox does not work yet 2016-04-26 22:52:29 +02:00
Felix Van der Jeugt
fe4800b68f prevent words from the dictionary prefixing smart hints 2016-04-26 22:52:29 +02:00
Florian Bruhin
3c8598f691 Work around PyQt 5.6 segfault when using IPC
PyQt 5.6 seems to segfault when emitting None with a signal which is
declared as emitting a string:

https://www.riverbankcomputing.com/pipermail/pyqt/2016-April/037375.html

We now avoid this by using an empty string explicitly instead of None.
2016-04-26 22:30:27 +02:00
Florian Bruhin
48b069e89c Remove @pyqtSlot for on_new_window
This worked fine with Python 3.5 but causes a circular import which is
hard to break with Python 3.4.

The original solution was to do @pyqtSlot(object), but that doesn't work
with PyQt 5.6 anymore...
2016-04-26 22:16:04 +02:00
Florian Bruhin
59c4cdd1c2 Revert "Break up circular import on Python 3.4"
This reverts commit e5be48fcc0.
There are other circular imports as well.
2016-04-26 22:14:59 +02:00
Florian Bruhin
e5be48fcc0 Break up circular import on Python 3.4 2016-04-26 21:44:30 +02:00
Florian Bruhin
37b5f49c85 Fix types in @pyqtSlot decorations
PyQt 5.5 enforces correct type signatures, and there were a lot of
places where we were simply wrong, causing qutebrowser to not start at
all...
2016-04-26 20:34:38 +02:00
Florian Bruhin
da24e43fa5 Remove crowdfunding notes 2016-04-25 22:32:29 +02:00
Florian Bruhin
2604f0841a Update changelog 2016-04-25 19:20:37 +02:00
Florian Bruhin
3b23bfbeea Merge branch 'rcorre-undo_crash' 2016-04-25 19:19:48 +02:00
Florian Bruhin
0e27f1a597 Regenerate authors 2016-04-25 19:19:40 +02:00
Florian Bruhin
32d64b1a9a Merge branch 'undo_crash' of https://github.com/rcorre/qutebrowser into rcorre-undo_crash 2016-04-25 19:19:08 +02:00
Florian Bruhin
dd2a7110e7 Merge branch 'kanikaa1234-develop' 2016-04-25 19:06:04 +02:00
Florian Bruhin
3fa5c8885e Regenerate authors 2016-04-25 19:05:55 +02:00
Florian Bruhin
341dae0b3f Simplify deleting fragment 2016-04-25 19:05:20 +02:00
Florian Bruhin
d6c72c5821 Merge branch 'develop' of https://github.com/kanikaa1234/qutebrowser into kanikaa1234-develop 2016-04-25 19:03:20 +02:00
Florian Bruhin
6349c00c72 Fix crash with :tab-{prev,next,focus} with 0 tabs
When using :tab-prev/:tab-next (or :tab-focus which uses :tab-next
internally) immediately after the last tab, those functions could be
called with 0 tabs open, which caused a ZeroDivisionError when trying to
do % 0.

Fixes #1448.
2016-04-25 18:55:15 +02:00
Florian Bruhin
a5679dff04 tox: Update werkzeug to 0.11.9
- Corrected an issue that caused the debugger not to use the
  machine GUID on POSIX systems.
- Corrected an Unicode error on Python 3 for the debugger's
  PIN usage.
- Corrected the timestamp verification in the pin debug code.
  Without this fix the pin was remebered until too long.
2016-04-25 06:12:12 +02:00
kanikaa1234
22ad416f35 Addressing test 2016-04-24 19:33:31 +05:30
Jimmy
32edd35c7a buffer completion: handle deleting items see issue #1443
Allows to use ctrl+d to close tabs from the buffer completion widget
when they are selected. Respects current tab settings like whether you
can close the last tab in a window.

Had to change the `rebuild()` method to use `setData()` when possible
because the selection was being lost if the whole model was being rebuilt.

Current problems are:

1) When opening a new window while you already the tab completion open on
   one window a category is added for the new window but new rows in
   that category aren't picked up. Interesting if you open a third
   window then close the second window the completion display is now
   correct... I can see that the model is being updated correctly but I
   am not sure why that isn't propagating to the view. Not sure whether
   it is worth looking into (further) either.

2) Bit of duplication of code, it iterates over the window registry
   twice. Could put everything in one loop but then that would be
   dependant on the current behaviour of the `tab_closed` signal being
   called with the relevant `tabbed_browser` still existing but with the
   `shutting_down` flag set.

3) I'm still using just the one `rebuild()` method and removing items from
   the end then calling `setData` on everything rather than having special
   `on_tab/window_closed` methods (or partial functions) that delete the
   actual corresponding item. Because if I did that I would also have to
   special case tab moves etc.
2016-04-23 02:55:53 +12:00
Florian Bruhin
77a9bbb4b4 Fix accidental double-import 2016-04-21 23:12:54 +02:00
Florian Bruhin
39e8ac5159 Merge branch 'rcorre-marks' 2016-04-21 22:55:44 +02:00
Florian Bruhin
0955ed49a7 Update docs 2016-04-21 22:55:36 +02:00
Florian Bruhin
5b34f1b429 Fix ModeManager.leave docstring 2016-04-21 20:12:02 +02:00
Florian Bruhin
e0aa35b05a Fix type in @pyqtSlot for ModeManager.leave
For some reason it worked with str as well, but the signal emits a
KeyMode member.
2016-04-21 20:10:58 +02:00
Florian Bruhin
ef91fa3821 Merge branch 'marks' of https://github.com/rcorre/qutebrowser into rcorre-marks 2016-04-21 20:05:41 +02:00
Florian Bruhin
46a525d0d7 bdd: Improve test IDs for :buffer tests 2016-04-21 19:47:26 +02:00
Florian Bruhin
10fe9cf32d Stabilize test_buffer_two_window_index_present
Fixes #1440
I'm not sure if this was the case, but probably it was.
2016-04-21 19:34:39 +02:00
Ryan Roden-Corrent
399cddf7ac Actually test double-undo. 2016-04-21 08:04:20 -04:00
Ryan Roden-Corrent
ccd04ca548 Don't crash when undoing twice on default page.
Avoid a crash when undoing twice on the default page with last-close set to
default-page.
This was caused by logic to reuse the current tab if it is on the default page
and has no history. The fix is using openurl rather than removeTab/tabopen.
2016-04-21 08:04:20 -04:00
Ryan Roden-Corrent
600b5082a9 Touch up tests and add pyqtSlot for marks.
Small code review changes.
2016-04-20 21:53:12 -04:00
Florian Bruhin
a55952375b bdd: Improve output when comparing sessions
See #1440.
2016-04-21 00:15:37 +02:00
Florian Bruhin
e53c136342 tests: Refactor partial_compare
Functions now return a PartialCompareOutcome to attach an error message instead
of a bool, and the main function got rewritten based on a handler dict.
2016-04-21 00:14:13 +02:00
Florian Bruhin
cd128bec8e bdd: __tracbackhide__ in QuteProcess._wait_for_*
This should improve the output we got here:
https://ci.appveyor.com/project/The-Compiler/qutebrowser/build/master-2404/job/gr7m54km6h6nul04
2016-04-20 22:39:52 +02:00
Florian Bruhin
eeb830e040 travis: Ignore doc changes when building a PR
Fixes #1441
2016-04-20 22:35:02 +02:00
Florian Bruhin
391900cc99 Merge branch 'forkbong-pretty_url' 2016-04-20 22:17:35 +02:00
Florian Bruhin
9ae826295b Update docs 2016-04-20 22:17:27 +02:00
Florian Bruhin
e18b056877 Merge branch 'pretty_url' of https://github.com/forkbong/qutebrowser into forkbong-pretty_url 2016-04-20 22:15:54 +02:00
Florian Bruhin
0277c47ffa Merge branch 'forkbong-pretty_url_2' 2016-04-20 20:59:35 +02:00
Florian Bruhin
c27d4f4407 Update docs 2016-04-20 20:59:27 +02:00
Florian Bruhin
6a6285495a Merge branch 'pretty_url_2' of https://github.com/forkbong/qutebrowser into forkbong-pretty_url_2 2016-04-20 20:56:24 +02:00
Florian Bruhin
3237e36db5 tox: Update hacking to 0.11.0 2016-04-20 20:16:50 +02:00
Panagiotis Ktistakis
1ef2f042cf Mention {url:pretty} in the docs 2016-04-20 19:39:18 +03:00
Florian Bruhin
664a542ff6 requirements: Update colorlog to 2.6.3 2016-04-20 17:51:16 +02:00
Florian Bruhin
a6f496dc2a Merge branch 'blyxxyz-master' 2016-04-20 17:49:31 +02:00
Florian Bruhin
41c7f37618 Regenerate authors 2016-04-20 17:48:51 +02:00
Florian Bruhin
6c6b8e10a6 Merge branch 'master' of https://github.com/blyxxyz/qutebrowser into blyxxyz-master 2016-04-20 17:48:41 +02:00
Panagiotis Ktistakis
8550fb3401 Add tests for {url:pretty} variable 2016-04-20 17:58:14 +03:00
Panagiotis Ktistakis
6c3b0219e7 Style fix 2016-04-20 17:25:26 +03:00
Philipp Hansch
dfc1e5703a Cleanup unused files 2016-04-20 16:23:18 +02:00
Philipp Hansch
376c5c458e Convert insert mode tests to non-bdd tests 2016-04-20 16:19:47 +02:00
Philipp Hansch
ff13921aad Add tests for auto-[leave-]insert-mode 2016-04-20 12:50:33 +02:00
Philipp Hansch
601687ba61 Add insert mode feature tests
Issue-Link: #999
2016-04-20 11:04:53 +02:00
Florian Bruhin
22582773b1 Fix lint 2016-04-20 09:00:01 +02:00
Florian Bruhin
bc815505f1 bdd: Output fewer = chars for summary headers
Otherwise we have line breaks on Travis CI
2016-04-20 07:55:59 +02:00
Florian Bruhin
4520261884 bdd: Make TestProcess.log_summary public
We want to use it from the BDD conftest.py
2016-04-20 07:55:23 +02:00
Florian Bruhin
6cf4cebfd4 bdd: Show messages we're waiting for in the log 2016-04-20 07:49:08 +02:00
Florian Bruhin
a99cb5f6b2 bdd: Improve wait_for_load_finished output 2016-04-20 07:38:32 +02:00
Florian Bruhin
5e91ad6c42 bdd: Shorten InvalidLine exception output 2016-04-20 07:38:32 +02:00
Florian Bruhin
266bc6e8d8 bdd: Don't shorten log if an exception happened 2016-04-20 07:38:32 +02:00
Florian Bruhin
1579f27564 bdd: Add sections to log
See #1418
2016-04-20 07:38:12 +02:00
Ryan Roden-Corrent
c881730fad Handle counts for special keys.
Now 3<ctrl-o> will execute whatever <ctrl-o> is mapped to with count=3.
2016-04-19 23:39:02 -04:00
Ryan Roden-Corrent
0755e163bb Only set the ' mark on normal/current hinting.
Don't set ' for downloading, hovering, or opening a link in a tab.
2016-04-19 21:18:20 -04:00
Ryan Roden-Corrent
268d490e25 Return False when MarkKeyParser gets special key. 2016-04-19 17:30:57 -04:00
Jan Verbeek
3788eeb10b Add a few common generic browser keybindings 2016-04-19 22:24:24 +02:00
kanikaa1234
fae005ab7f :navigate: command and test feature 2016-04-19 20:30:20 +05:30
Panagiotis Ktistakis
c7534bd4a3 Allow {url:pretty} variable in commands 2016-04-19 16:37:05 +03:00
Ryan Roden-Corrent
6466ff919a Don't limit marks to alpha characters. 2016-04-19 08:28:11 -04:00
Ryan Roden-Corrent
ae267c466f Small fixes for marks.
Mark two callbacks with pyqtSlot as appropriate.
Return False instead of raising NotImplementedException to avoid pylint
identifying MarkKeyParser as abstract.
2016-04-19 08:28:02 -04:00
Florian Bruhin
5c97ec1659 Fix HeaderDict lint 2016-04-19 09:50:53 +02:00
Florian Bruhin
6aaea2aa31 Fix HeaderDict with an empty value 2016-04-19 09:43:54 +02:00
Florian Bruhin
4ea6bd40e4 Update docs 2016-04-19 09:40:39 +02:00
Florian Bruhin
e860d8cfea Add a custom-headers setting
Supersedes #1132
Closes #1020.
2016-04-19 06:35:32 +02:00
Florian Bruhin
c5999443a1 Implement a HeaderDict config type
Supersedes #1132.
Fixes #1022.
2016-04-19 06:35:20 +02:00
Ryan Roden-Corrent
c7ee7a9702 Update docs for jump-mark and set-mark 2016-04-18 18:15:11 -04:00
Panagiotis Ktistakis
045b54b94b Add commands to yank pretty decoded URLs
yp and yP, yank to the clipboard and primary selection respectively
2016-04-18 23:11:27 +03:00
Panagiotis Ktistakis
bd6783c7e6 Add --pretty flag to :yank
With --pretty, the URL is yanked in a "pretty form", with most
percent-encoded characters decoded. Partially fixes #1372.
2016-04-18 23:10:53 +03:00
Ryan Roden-Corrent
2b68aaa311 Make set_mark searching tests less flaky.
Replace all the html numbers with a few widely spaced divs to guarantee the
search will move the viewport on almost and reasonable screen size.
2016-04-18 12:30:16 -04:00
Ryan Roden-Corrent
a924144d9a Set ' mark after searching.
Allow jumping back to the previous position after a search jumps you around.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
4b00a17d71 Fix MarkKeyParser crash when changing keyconfig.
Ensure MarkKeyParser implements on_keyconfig_changed, so it doesn't fail when
rebinding a key. It doesn't have keybindings, so the implementation is just
`pass`.

This also fixes a few flake8 style errors.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
be6308534f Store both x and y position for marks.
Previously only stored/used y.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
f4b9573744 Remove cyclic dependency from MarkKeyParser.
Rather than calling modeman.leave directly, modeman hooks into a request_leave
signal that is fired when MarkKeyParser wants to exit mark mode.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
49b2f6e967 Move jump_mark logic to TabbedBrowser.
This simplifies the MarkKeyParser by removing its dependency on the
commandrunner. It also removes the need for a new exception type.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
e684cfa03f Trivial fixes for marks based on code review.
- Fix a docstring copy-paste
- Add own name/copyright date to new file
- Simplify a bdd expression (no need for regex)
- Scroll to a pixel position in a single operation
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
540f4af225 Set the ' mark after following a link.
This moves mark storage from CommandDispatcher to TabbedBrowser, so it can also
be accessed by the HintManager.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
dc246772e7 Add integration tests for set-mark and jump-mark. 2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
c7b830d69d Fix up mistakes caught by pylint. 2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
84eb30bc9a Marks are local to urls, not tabs.
Rather than binding each set of local marks to a tab, bind them to a
url. Strip the fragment from the url, as two pages that differ only in
fragment are likely the same page.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
4037719a78 Add default bindings for set-mark and jump-mark.
Bind set-mark to ` and jump-mark to '.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
7bfea773db Add set_mark and jump_mark modes.
These modes use a custom handler to pass whatever the next keypress is
to either set_mark or jump_mark.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
9062f5925e Set the ' mark on a jump.
Automatically set the special "'" mark when jumping.
jump-mark "'" will jump to the last position before the previous jump.
A jump could be navigating via a link, jumping to another mark, or
scrolling by percentage (e.g. gg or G).
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
ddcae14ba4 Implement set-mark and jump-mark.
set-mark <key> saves your current scroll position as mark <key>.
jump-mark <key> jumps to the position previously set for mark <key>.

If <key> is lowercase, it is local to the current tab. Each tab has its
own set of lowercase marks.

If <key> is uppercase, it is global across tabs, and stores a url and a
scroll position. Jumping to an uppercase mark navigates to that url,
then scrolls to the saved position.

Resolves #310.
2016-04-17 21:04:08 -04:00
Ryan Roden-Corrent
f5bb75a186 Fix docstring in scroll_px. 2016-04-17 21:04:08 -04:00
Jakub Klinkovský
0eab422540 tests: replace paths to data/hints/link.html
an adjustment due to 35ed70cfe0
2016-04-15 23:19:08 +02:00
Jakub Klinkovský
3265601eab Merge remote-tracking branch 'upstream/master' into hints_clicking
* upstream/master: (327 commits)
  Remove unused import
  tox: Update Werkzeug to 0.11.8
  Regenerate authors
  Use __file__ instead of sys.argv[0]
  Regenerate authors
  Make update_3rdparty.py install correctly when run from any directory
  Open command line urls explicitly.
  tox: Update Werkzeug to 0.11.6
  Move qutebrowser.rcc to misc/
  Regenerate resources
  Fix CHANGELOG/link in README
  New qutebrowser logo!
  www: Add releases link
  Release v0.6.1
  release checklist: Clarify how to build on Windows
  Make sure the cheatsheet PNG is included in sdist
  Fix cheatsheet link URL in quickstart
  Mark segfault on exit in test_smoke as xfail
  Add a xfail test for #797
  Add missing file
  ...

Conflicts:
	tests/integration/features/hints.feature
2016-04-15 22:37:47 +02:00
Florian Bruhin
d33fae455d Remove unused import 2016-04-15 19:31:54 +02:00
Florian Bruhin
210374a73d tox: Update Werkzeug to 0.11.8
- fixed a regression on Python 3 for the debugger.
- fixed a problem with the machine GUID detection code on OS X
  on Python 3.
2016-04-15 19:27:28 +02:00
Florian Bruhin
72e0b80ed0 Merge branch 'blyxxyz-master' 2016-04-15 19:12:23 +02:00
Florian Bruhin
98bc1c9f85 Regenerate authors 2016-04-15 19:12:16 +02:00
Florian Bruhin
1f66d74f01 Use __file__ instead of sys.argv[0] 2016-04-15 19:12:10 +02:00
Florian Bruhin
38110146ab Merge branch 'master' of https://github.com/blyxxyz/qutebrowser into blyxxyz-master 2016-04-15 19:10:29 +02:00
Florian Bruhin
800cbb644b Merge branch 'Xitian9-cmdline_explicit' 2016-04-15 19:08:13 +02:00
Florian Bruhin
13a0b0eb75 Regenerate authors 2016-04-15 19:08:06 +02:00
Jan Verbeek
7f9a7a282f Make update_3rdparty.py install correctly when run from any directory 2016-04-15 11:23:52 +02:00
Xitian9
c76d4479ee Open command line urls explicitly. 2016-04-15 18:50:42 +10:00
Alexey Glushko
2645334425 Indent fixing 2016-04-14 17:20:34 +00:00
Florian Bruhin
4368daf0d8 tox: Update Werkzeug to 0.11.6
- werkzeug.serving: Still show the client address on bad requests.
- improved the PIN based protection for the debugger to make it harder to
  brute force via trying cookies.  Please keep in mind that the debugger
  *is not intended for running on production environments*
- increased the pin timeout to a week to make it less annoying for people
  which should decrease the change that users disable the pin check
  entirely.
- werkzeug.serving: Fix broken HTTP_HOST when path starts with double slash.
2016-04-14 18:00:06 +02:00
Florian Bruhin
cd5591040e Move qutebrowser.rcc to misc/ 2016-04-14 17:59:45 +02:00
Florian Bruhin
8dec54f7d9 Regenerate resources 2016-04-14 17:59:28 +02:00
Florian Bruhin
4f84911474 Fix CHANGELOG/link in README 2016-04-14 17:50:03 +02:00
Florian Bruhin
d3e7be1eb0 New qutebrowser logo!
Thanks to http://www.yelostudio.com
2016-04-14 17:44:38 +02:00
haitaka
d61ee2681f set back to the qutebrowser logo in on_load_started 2016-04-14 20:26:00 +06:00
Florian Bruhin
cd575e0587 www: Add releases link 2016-04-13 22:30:03 +02:00
haitaka
de93e6c7ee remove trailing whitespace 2016-04-13 19:15:01 +06:00
haitaka
b10c1b063d Site favicon as window icon when 'tabs-are-windows' is enabled 2016-04-13 19:08:50 +06:00
Florian Bruhin
c690e652dc Release v0.6.1 2016-04-10 21:25:32 +02:00
Florian Bruhin
35c413c9c5 release checklist: Clarify how to build on Windows 2016-04-10 21:21:40 +02:00
Florian Bruhin
230186f229 Make sure the cheatsheet PNG is included in sdist
Fixes #1403
2016-04-10 20:59:29 +02:00
Florian Bruhin
14119b99e3 Fix cheatsheet link URL in quickstart 2016-04-10 20:59:07 +02:00
Florian Bruhin
0776aaf32c Mark segfault on exit in test_smoke as xfail
See #1387.
I know this is... less than optimal, but I can't do anything :-/
2016-04-10 20:52:07 +02:00
Florian Bruhin
6e7aefca00 Add a xfail test for #797 2016-04-10 20:30:26 +02:00
Florian Bruhin
3409559958 Add missing file 2016-04-10 20:28:10 +02:00
Florian Bruhin
6989d4d210 Fix downloading of non-ascii files with LC_ALL=C
Fixes #908.
2016-04-10 20:16:40 +02:00
Florian Bruhin
98f0938d35 Rename test_cmdline_args to test_invocations 2016-04-10 19:56:44 +02:00
Florian Bruhin
1954ebd63c Fix test_last_window_session_none 2016-04-10 18:22:58 +02:00
Florian Bruhin
b3dfa5757e Docker: Install libjs-pdf on Debian/Ubuntu 2016-04-10 18:09:17 +02:00
Florian Bruhin
a33aa524de Don't crash if data is None while saving session
Under some circumstances I can't reproduce (switching/turning off
monitors?) it seems it's possible that SessionManager.save gets called
with last_window=True, without on_last_window_closed being called.

This might be to one of the Qt screen management bugs fixed in Qt 5.6,
which would explain why I can't reproduce it.

Instead of crashing, let's log the error and not save the session.
2016-04-10 17:47:14 +02:00
Florian Bruhin
20f80610be Add some more logging for standarddir 2016-04-10 17:47:04 +02:00
Florian Bruhin
e6334e196c Fix #1414 with a weird workaround 2016-04-10 15:26:02 +02:00
Florian Bruhin
9e8302ed5c Merge branch 'axiom-tab-focus-negative-index' 2016-04-09 21:20:26 +02:00
Florian Bruhin
d01616109e Update docs 2016-04-09 21:20:14 +02:00
Johannes Martinsson
570d8b4abe Make tab-focus count from end with negative index
This makes it possible to jump to the very last tab, as opposed to the
last focused tab, by using -1 as the index. Generally negative indexes
are counted from the end.

Solves issue #1166
2016-04-09 03:40:36 +02:00
kanikaa1234
a2bcf80aae Fixed the tests 2016-04-08 20:51:07 +05:30
kanikaa1234
5c976d724b Fixes #1318 2016-04-08 19:40:45 +05:30
Florian Bruhin
917aaefa5a tox: Update flake8-future-import 2016-04-08 07:49:11 +02:00
Florian Bruhin
579b3e81e4 flake8: Remove unnecessary putty-ignore option 2016-04-08 07:41:40 +02:00
Florian Bruhin
79bb8871ee flake8: Use putty-auto-ignore 2016-04-08 07:39:11 +02:00
Florian Bruhin
f097b2e57f tox: Update flake8-putty to 0.3.2
Fix regex selector matching multiple codes
2016-04-08 07:38:30 +02:00
Florian Bruhin
642dc46ba9 flake8: Add hacking 2016-04-08 07:35:53 +02:00
Florian Bruhin
735db072c6 flake8: Add flake8-future-import 2016-04-08 07:10:15 +02:00
Florian Bruhin
41d52386e8 tox: Add flake8-tuple/flake8-tidy-imports
flake8-tuple: Check code for 1-element tuples
flake8-tidy-imports: A flake8 plugin that helps you write tidier
                     imports.
2016-04-08 07:00:29 +02:00
Florian Bruhin
00873fd000 Fix Proxy configtype test 2016-04-07 21:42:26 +02:00
Florian Bruhin
f5b9e0ab27 Add Tor/HTTP completions for network -> proxy 2016-04-07 21:25:23 +02:00
Florian Bruhin
0e6bada68f build_release: Build docs/update 3rdparty on OS X 2016-04-07 08:53:34 +02:00
Florian Bruhin
3f0429574c Makefile-dmg: Add /Applications link to dmg 2016-04-07 08:53:34 +02:00
Florian Bruhin
3984437484 build_release: Build dmg via Makefile-dmg 2016-04-07 08:53:34 +02:00
Florian Bruhin
03be10b4fe Makefile-dmg: Copy files from subdirs
We don't want a /dist/qutebrowser.app in the dmg, only a /qutebrowser.app.
2016-04-07 08:53:34 +02:00
Florian Bruhin
97cb429b57 Makefile-dmg: Set variables 2016-04-07 08:53:34 +02:00
Florian Bruhin
533b9d22e7 Add fancy-dmg Makefile 2016-04-07 08:53:34 +02:00
Florian Bruhin
362837e98c update_3rdparty: Add fancy-dmg Makefile 2016-04-07 08:53:25 +02:00
Florian Bruhin
ae41a074e8 build_release: Add basic OS X build 2016-04-07 08:53:17 +02:00
Florian Bruhin
bfb4f20510 pyinstaller: Build a single-executable app 2016-04-07 08:53:12 +02:00
Florian Bruhin
460f613fec pyinstaller: Set icon 2016-04-07 08:53:12 +02:00
Florian Bruhin
2f8ce31e1a pyinstaller: Add git-commit-id file 2016-04-07 08:53:12 +02:00
Florian Bruhin
f24a721e55 pyinstaller: Include data files
This makes the following things work:

- Error pages
- Caret mode
- pdfjs
- :help
2016-04-07 08:53:12 +02:00
Florian Bruhin
b505c65873 pyinstaller: Add basic qutebrowser.spec 2016-04-07 08:53:07 +02:00
Florian Bruhin
1bf15a053d tox: Add a pyinstaller env 2016-04-07 08:50:36 +02:00
Florian Bruhin
485af2dde7 tox: Update flake8-putty to 0.3.1
- Allow regex selector to use codes extracted from each line
- Add predefined ignore pattern # flake8: disable=<codes>
- Add environment marker selector (PEP 0496)
- Fix bug when filename selector contained trailing whitespace
- Switched to using tox-travis, and added PyPy 3 testing
2016-04-07 07:12:54 +02:00
Florian Bruhin
dad1e48018 Update Homebrew/OS X install instructions 2016-04-07 07:10:05 +02:00
Florian Bruhin
455e32cc0c tox: Update pytest-rerunfailures to 2.0.0
Drop support for Python 3.2, since supporting it became too much of a
hassle. (Reason: Virtualenv 14+ / PIP 8+ do not support Python 3.2
anymore.)
2016-04-06 18:55:54 +02:00
Florian Bruhin
9698af72e8 Merge branch 'jgkamat-editorconfig' 2016-04-06 18:54:51 +02:00
Florian Bruhin
e49db65e33 Regenerate authors 2016-04-06 18:54:45 +02:00
Florian Bruhin
dac1429fb0 Merge branch 'editorconfig' of https://github.com/jgkamat/qutebrowser into jgkamat-editorconfig 2016-04-06 18:54:33 +02:00
Jay Kamat
fa84aae133 Add basic editorconfig for enforcing style 2016-04-06 02:55:34 -04:00
Florian Bruhin
9ea7716b06 Stop supporting running tests without Xvfb/DISPLAY
This always caused problems and unneeded complexity in the past, and it
also increases Travis CI runtime.

We now require either a DISPLAY during tests or Xvfb being installed.
2016-04-06 08:27:43 +02:00
Florian Bruhin
2aef8913ec travis: Don't install recommended packages via apt
This passes --no-install-recommends both for Dockerfiles and Travis
itself.
2016-04-06 08:22:41 +02:00
Florian Bruhin
2ce2b2ab9f travis: Only modify $PATH as needed
See #1396.
2016-04-06 08:22:41 +02:00
Florian Bruhin
d02d99e53e bdd: Make quteproc.set_setting work with quotes
This hopefully fixes editor BDD tests on Windows as the editor path is
now correctly quoted.
2016-04-06 08:13:43 +02:00
Florian Bruhin
14eb63d9e9 ci/install: Fix updating of pip on Appveyor
Windows can't update pip when using the wrapper-executable.
2016-04-06 07:43:55 +02:00
Florian Bruhin
7141941ece Fix lint 2016-04-06 07:17:13 +02:00
Florian Bruhin
57cb812219 ci/install: Wait 30s before re-calling apt-get
See #1396.
2016-04-06 07:14:21 +02:00
Florian Bruhin
b70975223f travis: Use sudo -H to call pip
pip apparently needs this to make caching work properly?
See #1396.
2016-04-06 07:13:03 +02:00
Florian Bruhin
812c255379 travis: Use pip install -U to install packages 2016-04-06 07:12:21 +02:00
Florian Bruhin
2ad4fa5c8a ci/install: Upgrade pip on Travis/AppVeyor
See #1396
2016-04-06 07:11:57 +02:00
Florian Bruhin
f0b66130d6 Merge branch 'rcorre-edit-url' 2016-04-06 06:52:43 +02:00
Florian Bruhin
86dec02fe8 bdd: Add tests for :edit-url 2016-04-06 06:47:19 +02:00
Florian Bruhin
c4878bb7ed Don't raise cmdexc.CommandError in :open
:open can be called via :edit-url async, so we need to use message.error
by hand there.
2016-04-06 06:46:42 +02:00
Florian Bruhin
776c4c4400 Ensure -t/-b/-w are exclusive in :edit-url
Otherwise those would be passed as-is to :open and an unhandled
cmdexc.CommandError would be raised there.
2016-04-06 06:44:50 +02:00
Florian Bruhin
2f520f3b17 Rename test_editor.py to test_editor_unit.py 2016-04-05 19:49:01 +02:00
Florian Bruhin
9db697452d Remove self._editor in CommandDispatcher
This was needed before there was editor.ExternalEditor as there were
various commands which needed to access the editor object.

Since this is encapsulated in ExternalEditor now, no need to keep a
reference to the object around.
2016-04-05 18:48:58 +02:00
Florian Bruhin
107126c42e Regenerate docs 2016-04-05 18:38:56 +02:00
Florian Bruhin
6b3ee53064 Fix lint 2016-04-05 18:37:50 +02:00
Florian Bruhin
fc92137706 Handle count correctly for :edit-url 2016-04-05 18:37:03 +02:00
Florian Bruhin
d56cdd64db Update changelog 2016-04-05 18:35:47 +02:00
Florian Bruhin
332e045f54 Merge branch 'edit-url' of https://github.com/rcorre/qutebrowser into rcorre-edit-url 2016-04-05 18:28:50 +02:00
Florian Bruhin
537d017ec2 @Kingdread is faster than AppVeyor 2016-04-05 14:11:51 +02:00
Florian Bruhin
0abd16f6e5 tests: Use repr to wait for download question
The tests failed on Windows because a double-slash was printed in the
logs, but the tests expected a single one.
2016-04-05 14:09:25 +02:00
Ryan Roden-Corrent
e0d1e527d0 Fix up edit-url implementation.
Remove spaces around '=' for kwargs, don't set the _editor member.
2016-04-05 07:49:01 -04:00
Florian Bruhin
fb741582ef scripts/dev/ci/install: Retry apt-get if it fails 2016-04-05 12:43:51 +02:00
Florian Bruhin
860853f66f Fix lint 2016-04-05 12:40:46 +02:00
Florian Bruhin
b5c177526c tests: Add some "# pragma: no cover" for downloads 2016-04-05 11:43:35 +02:00
Florian Bruhin
ebee06a9c4 bdd: Add tests for download-path-suggestion 2016-04-05 11:43:35 +02:00
Florian Bruhin
6c1ee07c85 travis: Self-update npm in eslint env 2016-04-05 07:34:57 +02:00
Florian Bruhin
e64987d08c MANIFEST: prune scripts/textbrowser_cpp 2016-04-05 07:11:05 +02:00
Florian Bruhin
b9b2297d64 Add minimal C++ testbrowser 2016-04-05 06:56:57 +02:00
Florian Bruhin
1e740aa78b Update release checklist in CONTRIBUTING 2016-04-05 06:54:24 +02:00
Ryan Roden-Corrent
c8848a2641 Implement edit-url to craft a url with an editor.
The edit-url command opens a url (by default, the current url) in the
user's external editor and navigates to the result when the editor is
closed. This makes it easy to tweak the current url to navigate within
a site.

`edit-url` accepts the same flags as `open` (e.g. -t will open in a new
tab.

One may provide a url as an argument to create a shortcut to
pre-populate part of a url and allow filling in the rest.

There is no default keybinding.

Resolves #1261.
2016-04-04 20:47:42 -04:00
Florian Bruhin
f91935c8e8 freeze: Add pkg_resources._vendor.six
Another package added by pkg_resources and not picked up by cx_Freeze...
2016-04-04 21:31:27 +02:00
Florian Bruhin
1d89ea397a tox: Update setuptools in cxfreeze-windows env
In 43a4a6a3e7 we added
pkg_resources._vendor.pyparsing to the frozen modules - however, older
setuptools (and thus pkg_resources) versions don't have that module yet,
so freezing would fail.
2016-04-04 19:03:13 +02:00
Florian Bruhin
910adaf097 build_release: Don't call update_3rdparty.main
main tries to parse sys.argv which will fail when called from
build_release when e.g. --asciidoc is given.
2016-04-04 18:32:49 +02:00
Jakub Klinkovský
e121bd764f hints: add another iframe test 2016-03-16 21:47:58 +01:00
Jakub Klinkovský
164df521c1 hints: add verifications for iframe tests 2016-03-16 20:59:46 +01:00
Jakub Klinkovský
d4ea62a834 hints: add xfailed tests for hinting inside iframes 2016-02-24 23:15:13 +01:00
Jakub Klinkovský
b1207650b0 hints: translate client rectangles into parent frames 2016-02-24 22:56:06 +01:00
Jakub Klinkovský
d630f966e7 hints: ignore too small rectangles returned by getClientRects()
Apparently we can have a '1px x 1px' rectangle at some zoom levels
and '0px x 0px' at others. We can't reliably click these, so let's
ignore them.
2016-02-23 20:23:11 +01:00
Jakub Klinkovský
891a31a0ca hints: move zoom tests to test_hints_html.py 2016-02-23 18:51:32 +01:00
Jakub Klinkovský
35ed70cfe0 hints: split tests into individual HTML files 2016-02-23 18:19:52 +01:00
Jakub Klinkovský
58d2d92d67 Merge remote-tracking branch 'upstream/master' into hints_clicking
* upstream/master: (22 commits)
  Regenerate authors.
  Only run geolocation tests on CI
  Switch to flake8-docstrings with pydocstyle
  Fix lint
  Move pylint plugins to an installed package.
  Include pytest-xvfb properly in frozen tests
  tests: Handle trailing / in wait_for_load_finished.
  Fix lint.
  tox: Upgrade hypothesis to 3.0.2.
  tests: Add first end-to-end test for hints.
  hints: Log the used hint chars
  bdd: use quteproc.wait_for_load_finished.
  Remove xvfbwrapper from freeze_tests.py.
  Regenerate authors.
  Combine launch/crash time into one section.
  Split long lines.
  Switch to pytest-xvfb. Fixes #1309.
  no ellipsis is inserted in big windows
  Regenerate authors.
  fixes #1308
  ...
2016-02-23 17:40:02 +01:00
Jakub Klinkovský
52a9405f74 hints: fix descriptions of zoom tests 2016-02-21 12:53:03 +01:00
Jakub Klinkovský
1cff16b3b4 hints: fix bug in the correction of coordinates based on zoom level 2016-02-19 21:15:56 +01:00
Jakub Klinkovský
97d2038528 hints: make sure that all hints stay visible in tests 2016-02-16 00:05:58 +01:00
Jakub Klinkovský
9799c30c2b hints: add precision tests for different zoom levels 2016-02-15 23:31:49 +01:00
Jakub Klinkovský
dd594b0eca hints: split getClientRects() into separate method
this will be useful for positioning the hint label
2016-02-15 23:28:52 +01:00
Jakub Klinkovský
239b7497e9 hints: simplify tests and make them more reliable 2016-02-15 20:41:54 +01:00
Jakub Klinkovský
748ec043fb hints: add tests for links known to cause troubles 2016-02-15 19:02:29 +01:00
Jakub Klinkovský
a546933516 hints: use getClientRects() JS method to get the correct click position 2016-02-15 18:17:53 +01:00
Tomasz Kramkowski
399aaa2b70 history: Add clear() method and history-clear command
WebHistory now has a clear() method which is also a command
(history-clear) which clears the qutebrowser history using the new
lineparser clear() method and emits a cleared signal.

The completion model urlmodel connects to the WebHistory.cleared signal
and clears its history category completion list.

I am adding this as a temporary fix before #58 or #1051 get implemented.
2016-01-23 22:36:24 +00:00
Tomasz Kramkowski
6894033f8d lineparser: Add clear() method.
The lineparser clear method, implemented for all lineparser subclasses,
clears the underlying file and also empties any lineparser data
structures.
2016-01-23 22:35:19 +00:00
Corentin Julé
6fd8dc4e57 Correct test_config_change 2016-01-23 16:34:05 +01:00
Corentin Julé
47261dbd30 Code cleanup / Good practices 2016-01-23 16:15:19 +01:00
Corentin Julé
ff8d5370a3 Add ideas for improvement 2015-12-08 10:37:04 +01:00
Corentin Julé
99ede3f551 Improve coverage and code cleanup 2015-12-08 00:52:51 +01:00
Corentin Julé
f8fcd48998 remove now useless tests 2015-12-07 00:11:19 +01:00
Corentin Julé
6be0ff67f7 Replace some tests with a generic host blocking list 2015-12-07 00:09:51 +01:00
Corentin Julé
a24a7790cd Improve code readability 2015-12-06 19:33:35 +01:00
Corentin Julé
8bff518ba4 Refactor and add tests 2015-12-06 15:35:10 +01:00
Corentin Julé
ac3d0b9a4c Comply with pylint 2015-12-06 00:51:44 +01:00
Corentin Julé
1ba634969a Correct QUrl creation 2015-12-06 00:42:33 +01:00
Corentin Julé
472585edd5 Add tests for HostBlocker class 2015-12-06 00:10:56 +01:00
Corentin Julé
9da15ae2f9 Correct is_whitelisted_host test 2015-12-05 12:41:52 +01:00
Corentin Jule
d55e6d7d7e Comply with PEP257 2015-12-05 01:35:20 +01:00
Corentin Jule
d5fc7e0389 Merge upstream/master 2015-12-05 01:29:20 +01:00
Corentin Jule
5e5531f924 Revert "Remove old pull request commits"
This reverts commit 5369fc30bc.

messed with git - revert
2015-12-05 01:21:32 +01:00
Corentin Jule
cc946ba6e6 implementation of config_stub fixture 2015-12-05 01:09:11 +01:00
Corentin Jule
8222a86201 Remove useless test 2015-12-05 00:16:38 +01:00
Corentin Jule
3426dd06f6 Add some docstrings 2015-12-04 21:57:34 +01:00
Corentin Jule
e78f1a7412 Merge remote-tracking branch 'upstream/master' into test_adblock 2015-12-04 13:19:00 +01:00
Corentin Jule
95b200ead9 Pylint code cleanup 2015-12-04 01:52:00 +01:00
Corentin Jule
5369fc30bc Remove old pull request commits 2015-12-03 22:13:50 +01:00
Corentin Jule
8dd0249af9 Code cleanup - Pep8 2015-12-03 22:00:39 +01:00
Corentin Jule
2d850f7106 Add tests for first functions of adblock.py 2015-12-03 21:12:51 +01:00
380 changed files with 16305 additions and 8216 deletions

View File

@@ -11,7 +11,7 @@ environment:
- TESTENV: pylint
install:
- C:\Python27\python -u scripts\dev\ci\install.py
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py
test_script:
- C:\Python34\Scripts\tox -e %TESTENV%

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 79
indent_style = space
indent_size = 4
[*.yml]
indent_size = 2
[*.feature]
max_line_length = 9999

View File

@@ -30,27 +30,29 @@ exclude = .venv,.hypothesis,.git,__pycache__,resources.py
# D211: No blank lines allowed before class docstring
# (PEP257 got changed, but let's stick to the old standard)
# D402: First line should not be function's signature (false-positives)
# H101: Use TODO(NAME)
# H201: bare except
# H238: Use new-stule classes
# H301: one import per line
# H306: imports not in alphabetical order
ignore =
E128,E226,E265,E501,E402,E266,
F401,
N802,
L101,L102,L103,L201,L202,L203,L204,L207,L302,
P101,P102,P103,
D102,D103,D104,D105,D209,D211,D402
D102,D103,D104,D105,D209,D211,D402,
H101,H201,H238,H301,H306
min-version = 3.4.0
max-complexity = 12
putty-auto-ignore = True
putty-ignore =
/# pylint: disable=invalid-name/ : +N801,N806
/# pylint: disable=wildcard-import/ : +F403
/# pragma: no mccabe/ : +C901
/# flake8: disable=E131/ : +E131
/# flake8: disable=N803/ : +N803
/# flake8: disable=T002/ : +T002
/# flake8: disable=F841/ : +F841
/# flake8: disable=S001/ : +S001
tests/*/*/test_*.py : +D100,D101,D401
tests/*/test_*.py : +D100,D101,D401
tests/unit/browser/http/test_content_disposition.py : +D400
scripts/dev/ci/install.py : +C901
scripts/dev/ci/appveyor_install.py : +FI53
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

6
.gitignore vendored
View File

@@ -1,4 +1,5 @@
__pycache__
*.py~
*.pyc
*.swp
/build
@@ -30,4 +31,9 @@ __pycache__
/.testmondata
/.hypothesis
/prof
/venv
TODO
/scripts/testbrowser_cpp/Makefile
/scripts/testbrowser_cpp/main.o
/scripts/testbrowser_cpp/testbrowser
/scripts/dev/pylint_checkers/qute_pylint.egg-info

View File

@@ -31,7 +31,8 @@ disable=no-self-use,
wrong-import-order,
ungrouped-imports,
redefined-variable-type,
suppressed-message
suppressed-message,
too-many-return-statements
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$
@@ -57,6 +58,9 @@ dummy-variables-rgx=_.*
[DESIGN]
max-args=10
[CLASSES]
valid-metaclass-classmethod-first-arg=cls
[TYPECHECK]
# MsgType added as WORKAROUND for
# https://bitbucket.org/logilab/pylint/issues/690/

View File

@@ -1,17 +1,11 @@
sudo: required
dist: trusty
env:
global:
- PATH=/home/travis/bin:/home/travis/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Not really, but this is here so we can do stuff by hand.
language: c
language: generic
matrix:
include:
- os: linux
env: TESTENV=py34-cov
- os: linux
env: TESTENV=unittests-nodisp
- os: linux
env: DOCKER=debian-jessie
services: docker
@@ -21,6 +15,9 @@ matrix:
- os: linux
env: DOCKER=ubuntu-wily
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
- os: osx
env: TESTENV=py35
- os: linux
@@ -48,8 +45,12 @@ cache:
- $HOME/.cache/pip
- $HOME/build/The-Compiler/qutebrowser/.cache
before_install:
# We need to do this so we pick up the system-wide python properly
- 'export PATH="/usr/bin:$PATH"'
install:
- python scripts/dev/ci/install.py
- bash scripts/dev/ci/travis_install.sh
script:
- bash scripts/dev/ci/travis_run.sh

View File

@@ -14,6 +14,117 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.7.0
------
Added
~~~~~
- New `:edit-url` command to edit the URL in an external editor.
- New `network -> custom-headers` setting to send custom headers with every request.
- New `{url:pretty}` commandline replacement which gets replaced by the decoded URL.
- New marks to remember a scroll position:
- New `:jump-mark` command to jump to a mark, bound to `'`
- New `:set-mark` command to set a mark, bound to ```(backtick)
- The `'` mark gets set when moving away (hinting link with anchor, searching, etc.) so you can move back with `''`
- New `--force-color` argument to force colored logging even if stdout is not a
terminal
- New `:messages` command to show error messages
- New pop-up showing possible keybinding when the first key of a keychain is
pressed. This can be turned off using `:set ui keyhint-blacklist *`.
- New `hints -> auto-follow-timeout` setting to ignore keypresses after
following a hint when filtering in number mode.
- New `:history-clear` command to clear the entire history
- New `hints -> find-implementation` to select which implementation (JS/Python)
should be used to find hints on a page. The `javascript` implementation is
better, but slower.
Changed
~~~~~~~
- qutebrowser got a new (slightly updated) logo
- `:tab-focus` can now take a negative index to focus the nth tab counted from
the right.
- `:yank` can now yank the pretty/decoded URL by adding `--pretty`
- `:navigate` now clears the URL fragment
- `:completion-item-del` (`Ctrl-D`) can now be used in `:buffer` completion to
close a tab
- Counts can now be used with special keybindings (e.g. with modifiers)
- Various SSL ciphers are now disabled by default. With recent Qt/OpenSSL
versions those already all are disabled, but with older versions they might
not be.
- Show favicons as window icon with `tabs-are-windows` set.
- `:bind <key>` without a command now shows the existing binding
- The optional `colorlog` dependency got removed, as qutebrowser now displays
colored logs without it.
- URLs are now shown decoded when hovering.
- Keybindings are now shown in the command completion
- Improved behavior when pasting multiple lines
- Rapid hints can now also be used for the `normal` hint target, which can be
useful with javascript click handlers or checkboxes which don't actually open
a new page.
- `:zoom-in` or `:zoom-out` (`+`/`-`) with a too large count now zooms to the
smallest/largest zoom instead of doing nothing.
- The commandline now accepts partially typed commands if they're unique.
- Number hints are now kept filtered after following a hint in rapid mode.
- Number hints are now renumbered after filtering
- Number hints can now be filtered with multiple space-separated search terms
- `hints -> scatter` is now ignored for number hints
- Better history implementation which also stores titles.
As a consequence, URLs which redirect to another URL are now added to the
history too, marked with a `-r` suffix to the timestamp field.
Fixed
-----
- Fixed using `:hint links spawn` with flags - you can now use things like the
`-v` argument for `:spawn` or pass flags to the spawned commands.
- Various fixes for hinting corner-cases where following a link didn't work or
the hint was drawn at the wrong position.
- Fixed crash when downloading from an URL with SSL errors
- Close file handles correctly when a download failed
- Fixed crash when using `;Y` (`:hint links yank-primary`) on a system without
primary selection
- Don't display quit confirmation with finished downloads
- Fixed updating the tab index in the statusbar when opening a background tab
- Fixed a crash when entering `:-- ` in the commandline
- Fixed `:debug-console` with PyQt 5.6
- Fixed qutebrowser not starting when `sys.stderr` is `None`
- Fixed crash when cancelling a download which belongs to a MHTML download
- Fixed rebinding of keybindings being case-sensitive
- Fix for tab indicators getting lost when moving tabs
- Fixed handling of backspace in number hinting mode
- Fixed `FileNotFoundError` when starting in some cases on old Qt versions
- Fixed sharing of cookies between tabs when `private-browsing` is enabled
- Toggling values with `:set` now uses lower-case values
- Hints now work with (non-standard) links with spaces around the URL
- Strip off trailing spaces for history entries with no title
v0.6.2
------
Fixed
~~~~~
- Fixed crash when using `:tab-{prev,next,focus}` right after closing the last
tab with `last-close` set to `close`.
- Fixed crash when doing `:undo` in a new instance with `tabs -> last-close` set
to `default-page`.
- Fixed crash when starting with --cachedir=""
- Fixed crash in some circumstances when using dictionary hints
- Fixed various crashes related to PyQt 5.6
v0.6.1
-----
Fixed
~~~~~~
- Fixed broken cheatsheet image which was missing from package
- Fixed occasional crash when switching/disconnecting monitors
- Fixed crash when downloading non-ascii files with a broken locale (`LC_ALL=C`)
- Added workaround for a Qt/PyQt bug which is too weird to describe here
v0.6.0
------

View File

@@ -158,11 +158,11 @@ Examples:
# run only pytest tests which failed in last run:
tox -e py35 -- --lf
# run only the integration feature tests:
tox -e py35 -- tests/integration/features
# run only the end2end feature tests:
tox -e py35 -- tests/end2end/features
# run everything with undo in the generated name, based on the scenario text
tox -e py35 -- tests/integration/features/test_tabs.py -k undo
tox -e py35 -- tests/end2end/features/test_tabs_bdd.py -k undo
# run coverage test for specific file (updates htmlcov/index.html)
tox -e py35-cov -- tests/unit/browser/test_webelem.py
@@ -174,10 +174,14 @@ Profiling
In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
and shows a graphical representation of what takes how much time.
It needs https://pypi.python.org/pypi/pyprof2calltree/[pyprof2calltree] and
http://kcachegrind.sourceforge.net/html/Home.html[KCacheGrind]. It uses the
built-in Python https://docs.python.org/3.4/library/profile.html[cProfile]
module.
It uses the built-in Python
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
the output in four different ways:
* Raw profile file (`--profile-tool=none`)
* https://pypi.python.org/pypi/pyprof2calltree/[pyprof2calltree] and http://kcachegrind.sourceforge.net/html/Home.html[KCacheGrind] (`--profile-tool=kcachegrind`)
* https://jiffyclub.github.io/snakeviz/[SnakeViz] (`--profile-tool=snakeviz`)
* https://github.com/jrfonseca/gprof2dot[gprof2dot] (needs `dot` from http://graphviz.org/[Graphviz] and http://feh.finalrewind.org/[feh])
Debugging
~~~~~~~~~
@@ -212,7 +216,6 @@ Documentation of used Python libraries:
* http://pythonhosted.org/setuptools/[setuptools]
* http://cx-freeze.readthedocs.org/en/latest/overview.html[cx_Freeze]
* https://pypi.python.org/pypi/colorama[colorama]
* https://pypi.python.org/pypi/colorlog[colorlog]
Related RFCs and standards:
@@ -371,7 +374,7 @@ The following logging levels are available for every logger:
[width="75%",cols="25%,75%"]
|=======================================================================
|criticial |Critical issue, qutebrowser can't continue to run.
|critical |Critical issue, qutebrowser can't continue to run.
|error |There was an issue and some kind of operation was abandoned.
|warning |There was an issue but the operation can continue running.
|info |General informational messages.
@@ -417,35 +420,59 @@ The types of the function arguments are inferred based on their default values,
e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in
qutebrowser's commandline.
This behavior can be overridden using Python's
http://legacy.python.org/dev/peps/pep-3107/[function annotations]. The
annotation should always be a `dict`, like this:
The type can be overridden using Python's
http://legacy.python.org/dev/peps/pep-3107/[function annotations]:
[source,python]
----
@cmdutils.register(...)
def foo(bar: {'type': int}, baz=True):
def foo(bar: int, baz=True):
...
----
The following keys are supported in the dict:
* `type`: The type this value should have. The value entered by the user is
then automatically checked. Possible values:
Possible values:
- A callable (`int`, `float`, etc.): Gets called to validate/convert the
value.
- A string: The value must match exactly (mainly useful with tuples to get
a choice of values, see below).
- A python enum type: All members of the enum are possible values.
- A tuple of multiple types above: Any of these types are valid values,
e.g. `('foo', 'bar')` or `(int, 'foo')`.
* `flag`: The flag to be used, as 1-char string (default: First char of the
long name).
* `nargs`: Gets passed to argparse, see
https://docs.python.org/dev/library/argparse.html#nargs[its documentation].
- A `typing.Union` of multiple types above: Any of these types are valid
values, e.g. `typing.Union[str, int]`
You can customize how an argument is handled using the `@cmdutils.argument`
decorator *after* `@cmdutils.register`. This can e.g. be used to customize the
flag an argument should get:
[source,python]
----
@cmdutils.register(...)
@cmdutils.argument('bar', flag='c')
def foo(bar):
...
----
For a `str` argument, you can restrict the allowed strings using `choices`:
[source,python]
----
@cmdutils.register(...)
@cmdutils.argument('bar', choices=['val1', 'val2'])
def foo(bar: str):
...
----
For `typing.Union` types, the given `choices` are only checked if other types
(like `int`) don't match.
The following arguments are supported for `@cmdutils.argument`:
- `flag`: Customize the short flag (`-x`) the argument will get.
- `win_id=True`: Mark the argument as special window ID argument
- `count=True`: Mark the argument as special count argument
- `hide=True`: Hide the argument from the documentation
- `completion`: A `usertypes.Completion` member to use as completion.
- `choices`: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any trailing
underscores stripped.
underscores stripped and underscores replaced by dashes.
[[handling-urls]]
Handling URLs
@@ -568,10 +595,7 @@ Return:
* The layout of a class should be like this:
- docstring
- `__magic__` methods
- properties
- _private methods
- public methods
- `on_*` methods
- other methods
- overrides of Qt methods
Checklists
@@ -593,6 +617,14 @@ workaround.
* Check relevant
https://github.com/The-Compiler/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser
bugs] and check if they're fixed.
* As soon as Homebrew updated, update the custom OS X bottle:
- Update https://github.com/The-Compiler/homebrew-qt5-webkit/blob/master/Formula/qt5.rb[qt5.rb]
- `brew install --build-from-source --verbose qt5.rb`
- `brew bottle qt5`
- `brew install --build-from-source --verbose pyqt5`
- `brew bottle pyqt5`
- Upload bottles to github
- Adjust `scripts/dev/ci/travis_install.sh`
New PyQt release
~~~~~~~~~~~~~~~~
@@ -606,6 +638,11 @@ qutebrowser release
* Make sure there are no unstaged changes and the tests are green.
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
- `python -m qutebrowser --basedir conf :quit`
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf`
- `rm -r conf`
- commit
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Remove *(unreleased)* from changelog.
* Run tests again
@@ -613,7 +650,6 @@ qutebrowser release
* Commit
* Create annotated git tag (`git tag -s "v0.X.Y" -m "Release v0.X.Y"`)
* If it's a new minor, create git branch `v0.X.x`
* If committing on minor branch, cherry-pick release commit to master.
* `git push origin`; `git push origin v0.X.Y`
* Create release on github
@@ -622,12 +658,16 @@ as closed.
* Run `scripts/dev/build_release.py` on Linux to build an sdist
* Upload to PyPI: `twine upload dist/foo{,.asc}`
* Create Windows packages via `scripts/dev/build_release.py` and upload.
* Create Windows packages via `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py`
* Upload to github
* Upload to qutebrowser.org with checksum/GPG
- On server: `sudo mkdir -p /srv/http/qutebrowser/releases/v0.X.Y/windows`
- `rsync -avPh dist/ tonks:`
- On server: `sudo mv qutebrowser-0.X.Y.tar.gz* /srv/http/qutebrowser/releases/v0.X.Y`
* Update AUR package
- Upload windows release:
- `scp bb-win8:proj/qutebrowser/qutebrowser-0.X.Y-windows.zip .`
- `aunpack qutebrowser-0.X.Y-windows.zip`
- `sudo mv qutebrowser-0.X.Y-windows/* /srv/http/qutebrowser/releases/v0.X.Y/windows`
* Update `qutebrowser-git` PKGBUILD
* Announce to qutebrowser mailinglist

View File

@@ -80,13 +80,27 @@ How do I play Youtube videos with mpv?::
player - optionally even with hinting for links:
+
----
:bind x spawn mpv {url}
:bind ;x hint links spawn mpv {hint-url}
:bind m spawn mpv {url}
:bind M hint links spawn mpv {hint-url}
----
+
Note that you might need an additional package (e.g.
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
Archlinux) to play web videos with mpv.
+
There is a very useful script for mpv, which emulates "unique application"
functionality. This way you can add links to the mpv playlist instead of
playing them all at once.
+
You can find the script here: https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv
+
It also works nicely with rapid hints:
+
----
:bind m spawn umpv {url}
:bind M hint links spawn umpv {hint-url}
:bind ;M hint --rapid links spawn umpv {hint-url}
----
How do I use qutebrowser with mutt?::
Due to a Qt limitation, local files without `.html` extensions are

View File

@@ -10,9 +10,31 @@ qutebrowser should run on these systems:
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint 17+)
Unfortunately there is no Debian package yet, but installing qutebrowser is
still relatively easy! If you want to help packaging it for Debian, please
https://github.com/The-Compiler/qutebrowser/issues/582[get in touch]!
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
still relatively easy!
You can use packages that are built for every release or build it yourself from git.
Using the packages
~~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
----
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-sip python3-jinja2 python3-pygments python3-yaml
----
Get the packages from the https://github.com/The-Compiler/qutebrowser/releases[release page]
Install the packages:
----
# dpkg -i python3-pypeg2_*_all.deb
# dpkg -i qutebrowser_*_all.deb
----
Build it from git
~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
@@ -121,6 +143,13 @@ Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
----
On Void Linux
-------------
@@ -200,21 +229,25 @@ Then <<tox,install qutebrowser via tox>>.
On OS X
-------
*Using qutebrowser with Homebrew on OS X is currently broken, as Homebrew
dropped QtWebKit support with Qt 5.6. I'm working on building a standalone
`.app` for OS X instead, but it'll still take a few days until it's ready.*
To install qutebrowser on OS X, you'll want a package manager, e.g.
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]. Also make
sure, you have https://itunes.apple.com/en/app/xcode/id497799835[XCode]
installed to compile PyQt5 in a later step.
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts].
For Homebrew, a few extra steps are necessary since Homebrew dropped QtWebKit
from Qt 5.6.
This installs a Qt 5.5 and symlinks it so PyQt5 will work with it instead of Qt
5.6. This requires that `qt5` is not installed via Homebrew:
----
$ brew install python3 pyqt5
$ brew install python3 d-bus mysql sip xz
$ brew install homebrew/versions/qt55
$ brew install --ignore-dependencies pyqt5
$ ln -s /usr/local/opt/qt55 /usr/local/opt/qt5
$ pip3.5 install qutebrowser
----
if you are using Homebrew. For MacPorts, run:
For MacPorts, run:
----
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5

View File

@@ -1,8 +1,8 @@
recursive-include qutebrowser *.py
recursive-include qutebrowser/html *.html
recursive-include qutebrowser/img *.svg *.png
recursive-include qutebrowser/test *.py
recursive-include qutebrowser/javascript *.js
graft qutebrowser/html
graft qutebrowser/3rdparty
graft icons
graft doc/img
@@ -18,12 +18,14 @@ include qutebrowser.py
prune www
prune scripts/dev
prune scripts/testbrowser_cpp
exclude scripts/asciidoc2html.py
exclude doc/notes
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
prune tests
prune qutebrowser/3rdparty
exclude .editorconfig
exclude pytest.ini
exclude qutebrowser.rcc
exclude .coveragerc
@@ -33,7 +35,9 @@ exclude .eslintignore
exclude doc/help
exclude .appveyor.yml
exclude .travis.yml
exclude codecov.yml
exclude .pydocstylerc
exclude misc/appveyor_install.py
exclude .flake8
global-exclude __pycache__ *.pyc *.pyo

View File

@@ -15,6 +15,8 @@ image:https://requires.io/github/The-Compiler/qutebrowser/requirements.svg?branc
image:https://travis-ci.org/The-Compiler/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/The-Compiler/qutebrowser"]
image:https://ci.appveyor.com/api/projects/status/9gmnuip6i1oq7046?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/The-Compiler/qutebrowser"]
image:https://codecov.io/github/The-Compiler/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/The-Compiler/qutebrowser?branch=master"]
link:http://www.qutebrowser.org[website] | link:http://blog.qutebrowser.org[blog] | link:https://github.com/The-Compiler/qutebrowser/releases[releases]
// QUTE_WEB_HIDE_END
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
@@ -22,12 +24,6 @@ on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
// QUTE_WEB_HIDE
**qutebrowser is currently running a crowdfunding campaign to add support for
the QtWebEngine backend, which fixes many issues. Please
link:http://igg.me/at/qutebrowser[check it out]!**
// QUTE_WEB_HIDE_END
Screenshots
-----------
@@ -115,11 +111,8 @@ The following libraries are optional and provide a better user experience:
To generate the documentation for the `:help` command, when using the git
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
The following libraries are optional and provide colored logging in the
console:
* https://pypi.python.org/pypi/colorlog/[colorlog]
* On Windows: https://pypi.python.org/pypi/colorama/[colorama]
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
display colored log output.
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.
@@ -149,76 +142,89 @@ Contributors, sorted by the number of commits in descending order:
* Florian Bruhin
* Daniel Schadt
* Antoni Boucher
* Ryan Roden-Corrent
* Lamar Pavel
* Bruno Oliveira
* Alexander Cogneau
* Martin Tournoij
* Felix Van der Jeugt
* Martin Tournoij
* Jakub Klinkovský
* Raphael Pierzina
* Joel Torstensson
* Tarcisio Fedrizzi
* Patric Schmitz
* Claude
* Corentin Julé
* meles5
* Tarcisio Fedrizzi
* Philipp Hansch
* Panagiotis Ktistakis
* Artur Shaik
* Nathan Isom
* Thorsten Wißmann
* Philipp Hansch
* Kevin Velghe
* Austin Anderson
* Jimmy
* Alexey "Averrin" Nabrodov
* avk
* ZDarian
* Milan Svoboda
* John ShaggyTwoDope Jenkins
* Jimmy
* Peter Vilim
* Clayton Craft
* Oliver Caldwell
* Jonas Schürmann
* Panagiotis Ktistakis
* Jakub Klinkovský
* skinnay
* error800
* Liam BEGUIN
* skinnay
* Zach-Button
* Tomasz Kramkowski
* Halfwit
* rikn00
* kanikaa1234
* haitaka
* Nick Ginther
* Michael Ilsaas
* Martin Zimmermann
* Fritz Reichwald
* Brian Jackson
* sbinix
* neeasade
* jnphilipp
* Tobias Patzl
* Stefan Tatschner
* Samuel Loury
* Peter Michely
* Link
* Larry Hynes
* Johannes Altmanninger
* Ismail
* adam
* Samir Benmendil
* Ryan Roden-Corrent
* Regina Hug
* Mathias Fussenegger
* Marcelo Santos
* Jan Verbeek
* Fritz V155 Reichwald
* Franz Fellner
* Corentin Jule
* zwarag
* xd1le
* oniondreams
* issue
* haxwithaxe
* evan
* dylan araps
* Tomasz Kramkowski
* Xitian9
* Tomas Orsava
* Tobias Werth
* Tim Harder
* Thiago Barroso Perrotta
* Stefan Tatschner
* Sorokin Alexei
* Samuel Loury
* Noah Huesser
* Matthias Lisin
* Marcel Schilling
* Johannes Martinsson
* Jean-Christophe Petkovich
* Jay Kamat
* Helen Sherwood-Taylor
* HalosGhost
* Gregor Pohl
@@ -231,7 +237,8 @@ Contributors, sorted by the number of commits in descending order:
The following people have contributed graphics:
* WOFall (icon)
* Jad/link:http://yelostudio.com[yelo] (new icon)
* WOFall (original icon)
* regines (key binding cheatsheet)
Thanks / Similar projects

9
codecov.yml Normal file
View File

@@ -0,0 +1,9 @@
status:
project:
enabled: no
patch:
enabled: no
changes:
enabled: no
comment: off

View File

@@ -20,15 +20,19 @@
|<<download-open,download-open>>|Open the last/[count]th download.
|<<download-remove,download-remove>>|Remove the last/[count]th download from the list.
|<<download-retry,download-retry>>|Retry the first failed/[count]th download.
|<<edit-url,edit-url>>|Navigate to a url formed in an external editor.
|<<fake-key,fake-key>>|Send a fake keypress or key string to the website or qutebrowser.
|<<forward,forward>>|Go forward in the history of the current tab.
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|<<help,help>>|Show help about a command or setting.
|<<hint,hint>>|Start hinting.
|<<history-clear,history-clear>>|Clear all browsing history.
|<<home,home>>|Open main startpage in current tab.
|<<inspector,inspector>>|Toggle the web inspector.
|<<jseval,jseval>>|Evaluate a JavaScript string.
|<<jump-mark,jump-mark>>|Jump to the mark named by `key`.
|<<later,later>>|Execute a command after some time.
|<<messages,messages>>|Show a log of past messages.
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|<<open,open>>|Open a URL in the current/[count]th tab.
|<<paste,paste>>|Open a page from the clipboard.
@@ -49,6 +53,7 @@
|<<session-save,session-save>>|Save a session.
|<<set,set>>|Set an option.
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|<<set-mark,set-mark>>|Set a mark at the current scroll position in the current tab.
|<<spawn,spawn>>|Spawn a command in a shell.
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|<<tab-clone,tab-clone>>|Duplicate the current tab.
@@ -91,13 +96,14 @@ How many pages to go back.
[[bind]]
=== bind
Syntax: +:bind [*--mode* 'MODE'] [*--force*] 'key' 'command'+
Syntax: +:bind [*--mode* 'mode'] [*--force*] 'key' ['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.
* +'command'+: The command to execute, with optional args, or not given to print the current binding.
==== optional arguments
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
@@ -123,7 +129,6 @@ Delete a bookmark.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[bookmark-load]]
=== bookmark-load
@@ -141,7 +146,6 @@ Load a bookmark.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[buffer]]
=== buffer
@@ -161,7 +165,7 @@ Close the current window.
[[download]]
=== download
Syntax: +:download [*--mhtml*] [*--dest* 'DEST'] ['url'] ['dest-old']+
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+
Download a given URL, or current page if no URL given.
@@ -223,6 +227,25 @@ Retry the first failed/[count]th download.
==== count
The index of the download to retry.
[[edit-url]]
=== edit-url
Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the `general -> editor` config option.
==== positional arguments
* +'url'+: URL to edit; defaults to the current page url.
==== optional arguments
* +*-b*+, +*--bg*+: Open in a new background tab.
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-w*+, +*--window*+: Open in a new window.
==== count
The tab index to open the URL in.
[[fake-key]]
=== fake-key
Syntax: +:fake-key [*--global*] 'keystring'+
@@ -331,6 +354,15 @@ Start hinting.
`window`, `run`, `hover`, `userscript` and `spawn`.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
[[history-clear]]
=== history-clear
Clear all browsing history.
Note this only clears the global history (e.g. `~/.local/share/qutebrowser/history` on Linux) but not cookies, the back/forward history of a tab, cache or other persistent data.
[[home]]
=== home
Open main startpage in current tab.
@@ -357,6 +389,15 @@ Evaluate a JavaScript string.
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[jump-mark]]
=== jump-mark
Syntax: +:jump-mark 'key'+
Jump to the mark named by `key`.
==== positional arguments
* +'key'+: mark identifier; capital indicates a global mark
[[later]]
=== later
Syntax: +:later 'ms' 'command'+
@@ -371,6 +412,22 @@ Execute a command after some time.
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[messages]]
=== messages
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+
Show a log of past messages.
==== positional arguments
* +'level'+: Include messages with `level` or higher severity. Valid values: vdebug, debug, info, warning, error, critical.
==== optional arguments
* +*-p*+, +*--plain*+: Whether to show plaintext (as opposed to html).
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-b*+, +*--bg*+: Open in a background tab.
* +*-w*+, +*--window*+: Open in a new window.
[[navigate]]
=== navigate
Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+
@@ -415,7 +472,6 @@ The tab index to open the URL in.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[paste]]
=== paste
@@ -464,7 +520,6 @@ Delete a quickmark.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[quickmark-load]]
=== quickmark-load
@@ -482,7 +537,6 @@ Load a quickmark.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[quickmark-save]]
=== quickmark-save
@@ -550,7 +604,6 @@ Search for a text on the current page. With no text, clear results.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[session-delete]]
=== session-delete
@@ -618,6 +671,8 @@ Syntax: +:set-cmd-text [*--space*] [*--append*] 'text'+
Preset the statusbar to some text.
You can use the `{url}` and `{url:pretty}` variables here which will get replaced by the encoded/decoded URL.
==== positional arguments
* +'text'+: The commandline to set.
@@ -627,7 +682,15 @@ Preset the statusbar to some text.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[set-mark]]
=== set-mark
Syntax: +:set-mark 'key'+
Set a mark at the current scroll position in the current tab.
==== positional arguments
* +'key'+: mark identifier; capital indicates a global mark
[[spawn]]
=== spawn
@@ -635,7 +698,7 @@ Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
Spawn a command in a shell.
Note the {url} variable which gets replaced by the current URL might be useful here.
Note the `{url}` and `{url:pretty}` variables might be useful here. `{url}` gets replaced by the URL in fully encoded format and `{url:pretty}` uses a "pretty form" with most percent-encoded characters decoded.
==== positional arguments
* +'cmdline'+: The commandline to execute.
@@ -652,7 +715,6 @@ Note the {url} variable which gets replaced by the current URL might be useful h
==== note
* This command does not split arguments after the last argument and handles quotes literally.
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
[[stop]]
=== stop
@@ -699,7 +761,8 @@ Select the tab given as argument/[count].
If neither count nor index are given, it behaves like tab-next.
==== positional arguments
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab.
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab. Negative indexes
counts from the end, such that -1 is the last tab.
==== count
@@ -773,7 +836,7 @@ Save open pages and quit.
[[yank]]
=== yank
Syntax: +:yank [*--title*] [*--sel*] [*--domain*]+
Syntax: +:yank [*--title*] [*--sel*] [*--domain*] [*--pretty*]+
Yank the current URL/title to the clipboard or primary selection.
@@ -781,6 +844,7 @@ Yank the current URL/title to the clipboard or primary selection.
* +*-t*+, +*--title*+: Yank the title instead of the URL.
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number.
* +*-p*+, +*--pretty*+: Yank the URL in pretty decoded form.
[[yank-selected]]
=== yank-selected
@@ -1222,7 +1286,7 @@ Scroll the current tab by 'count * dx/dy' pixels.
==== positional arguments
* +'dx'+: How much to scroll in x-direction.
* +'dy'+: How much to scroll in x-direction.
* +'dy'+: How much to scroll in y-direction.
==== count
multiplier

View File

@@ -49,6 +49,7 @@
|<<ui-hide-mouse-cursor,hide-mouse-cursor>>|Whether to hide the mouse cursor.
|<<ui-modal-js-dialog,modal-js-dialog>>|Use standard JavaScript modal dialog for alert() and confirm()
|<<ui-hide-wayland-decoration,hide-wayland-decoration>>|Hide the window decoration when using wayland (requires restart)
|<<ui-keyhint-blacklist,keyhint-blacklist>>|Keychains that shouldn't be shown in the keyhint dialog
|==============
.Quick reference for section ``network''
@@ -63,6 +64,7 @@
|<<network-proxy-dns-requests,proxy-dns-requests>>|Whether to send DNS requests over the configured proxy.
|<<network-ssl-strict,ssl-strict>>|Whether to validate SSL handshakes.
|<<network-dns-prefetch,dns-prefetch>>|Whether to try to pre-fetch DNS entries to speed up browsing.
|<<network-custom-headers,custom-headers>>|Set custom headers for qutebrowser HTTP requests.
|==============
.Quick reference for section ``completion''
@@ -177,12 +179,14 @@
|<<hints-mode,mode>>|Mode to use for hints.
|<<hints-chars,chars>>|Chars used for hint strings.
|<<hints-min-chars,min-chars>>|Minimum number of chars used for hint strings.
|<<hints-scatter,scatter>>|Whether to scatter hint key chains (like Vimium) or not (like dwb).
|<<hints-scatter,scatter>>|Whether to scatter hint key chains (like Vimium) or not (like dwb). Ignored for number hints.
|<<hints-uppercase,uppercase>>|Make chars in hint strings uppercase.
|<<hints-dictionary,dictionary>>|The dictionary file to be used by the word hints.
|<<hints-auto-follow,auto-follow>>|Follow a hint immediately when the hint text is completely matched.
|<<hints-auto-follow-timeout,auto-follow-timeout>>|A timeout to inhibit normal-mode key bindings after a successfulauto-follow.
|<<hints-next-regexes,next-regexes>>|A comma-separated list of regexes to use for 'next' links.
|<<hints-prev-regexes,prev-regexes>>|A comma-separated list of regexes to use for 'prev' links.
|<<hints-find-implementation,find-implementation>>|Which implementation to use to find elements to hint.
|==============
.Quick reference for section ``colors''
@@ -252,6 +256,9 @@
|<<colors-downloads.fg.error,downloads.fg.error>>|Foreground color for downloads with errors.
|<<colors-downloads.bg.error,downloads.bg.error>>|Background color for downloads with errors.
|<<colors-webpage.bg,webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color)
|<<colors-keyhint.fg,keyhint.fg>>|Text color for the keyhint widget.
|<<colors-keyhint.fg.suffix,keyhint.fg.suffix>>|Highlight color for keys to complete the current keychain
|<<colors-keyhint.bg,keyhint.bg>>|Background color of the keyhint widget.
|==============
.Quick reference for section ``fonts''
@@ -275,6 +282,7 @@
|<<fonts-web-size-minimum-logical,web-size-minimum-logical>>|The minimum logical font size that is applied when zooming out.
|<<fonts-web-size-default,web-size-default>>|The default font size for regular text.
|<<fonts-web-size-default-fixed,web-size-default-fixed>>|The default font size for fixed-pitch text.
|<<fonts-keyhint,keyhint>>|Font used in the keyhint widget.
|==============
== general
@@ -665,6 +673,14 @@ Valid values:
Default: +pass:[false]+
[[ui-keyhint-blacklist]]
=== keyhint-blacklist
Keychains that shouldn't be shown in the keyhint dialog
Globs are supported, so ';*' will blacklist all keychainsstarting with ';'. Use '*' to disable keyhints
Default: empty
== network
Settings related to the network.
@@ -750,6 +766,12 @@ Valid values:
Default: +pass:[true]+
[[network-custom-headers]]
=== custom-headers
Set custom headers for qutebrowser HTTP requests.
Default: empty
== completion
Options related to completion and command history.
@@ -856,13 +878,17 @@ Options related to input modes.
=== timeout
Timeout for ambiguous key bindings.
If the current input forms both a complete match and a partial match, the complete match will be executed after this time.
Default: +pass:[500]+
[[input-partial-timeout]]
=== partial-timeout
Timeout for partially typed key bindings.
Default: +pass:[1000]+
If the current input forms only partial matches, the keystring will be cleared after this time.
Default: +pass:[5000]+
[[input-insert-mode-on-plugins]]
=== insert-mode-on-plugins
@@ -1321,7 +1347,7 @@ Valid values:
* +true+
* +false+
Default: +pass:[true]+
Default: +pass:[false]+
[[content-css-regions]]
=== css-regions
@@ -1557,7 +1583,7 @@ Default: +pass:[1]+
[[hints-scatter]]
=== scatter
Whether to scatter hint key chains (like Vimium) or not (like dwb).
Whether to scatter hint key chains (like Vimium) or not (like dwb). Ignored for number hints.
Valid values:
@@ -1594,6 +1620,12 @@ Valid values:
Default: +pass:[true]+
[[hints-auto-follow-timeout]]
=== auto-follow-timeout
A timeout to inhibit normal-mode key bindings after a successfulauto-follow.
Default: +pass:[0]+
[[hints-next-regexes]]
=== next-regexes
A comma-separated list of regexes to use for 'next' links.
@@ -1606,6 +1638,17 @@ A comma-separated list of regexes to use for 'prev' links.
Default: +pass:[\bprev(ious)?\b,\bback\b,\bolder\b,\b[&lt;←≪]\b,\b(&lt;&lt;|«)\b]+
[[hints-find-implementation]]
=== find-implementation
Which implementation to use to find elements to hint.
Valid values:
* +javascript+: Better but slower
* +python+: Slightly worse but faster
Default: +pass:[javascript]+
== searchengines
Definitions of search engines which can be used via the address bar.
The searchengine named `DEFAULT` is used when `general -> auto-search` is true and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
@@ -2030,6 +2073,24 @@ Background color for webpages if unset (or empty to use the theme's color)
Default: +pass:[white]+
[[colors-keyhint.fg]]
=== keyhint.fg
Text color for the keyhint widget.
Default: +pass:[#FFFFFF]+
[[colors-keyhint.fg.suffix]]
=== keyhint.fg.suffix
Highlight color for keys to complete the current keychain
Default: +pass:[#FFFF00]+
[[colors-keyhint.bg]]
=== keyhint.bg
Background color of the keyhint widget.
Default: +pass:[rgba(0, 0, 0, 80%)]+
== fonts
Fonts used for the UI, with optional style/weight/size.
@@ -2138,3 +2199,9 @@ Default: empty
The default font size for fixed-pitch text.
Default: empty
[[fonts-keyhint]]
=== keyhint
Font used in the keyhint widget.
Default: +pass:[8pt ${_monospace}]+

View File

@@ -5,10 +5,23 @@ The Compiler <mail@qutebrowser.org>
NOTE: This page will only appear on the first start. To view it at a later
time, use the `:help` command.
Basic keybindings to get you started
------------------------------------
* Use the arrow keys or `hjkl` to move around a webpage (vim-like syntax is used in quite a few places)
* To go to a new webpage, press `o`, then type a url, then press Enter (Use `O` to open the url in a new tab). If what you've typed isn't a url, then a search engine will be used instead (DuckDuckGo, by default)
* To switch between tabs, use `J` (next tab) and `K` (previous tab), or press `<Alt-num>`, where `num` is the position of the tab to switch to
* To close the current tab, press `d` (and press `u` to undo closing a tab)
* Use `H` and `L` to go back and forth in the history
* To click on something without using the mouse, press `f` to show the hints, then type the keys next to what you want to click on (if that sounds weird, then just try pressing `f` and see what happens)
* Press `:` to show the commandline
* To search in a page, press `/`, type the phrase to search for, then press Enter. Use `n` and `N` to go back and forth through the matches, and press Esc to stop doing the search.
* To close qutebrowser, press `Alt-F4`, or `:q`, or `:wq` to save the currently open tabs and quit (note that in the settings you can make qutebrowser always save the currently open tabs)
What to do now
--------------
* View the http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
to make yourself familiar with the key bindings: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* Run `:adblock-update` to download adblock lists and activate adblocking.

View File

@@ -78,9 +78,15 @@ show it.
*--debug*::
Turn on debugging options.
*--json-logging*::
Output log lines in JSON format (one object per line).
*--nocolor*::
Turn off colored logging.
*--force-color*::
Force colored logging
*--harfbuzz* '{old,new,system,auto}'::
HarfBuzz engine version to use. Default: auto.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 B

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

2183
icons/qutebrowser-all.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 128 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

207
icons/qutebrowser.xpm Normal file
View File

@@ -0,0 +1,207 @@
/* XPM */
static char *qutebrowser[] = {
/* columns rows colors chars-per-pixel */
"32 32 169 2 ",
" c #0A396E",
". c #0B3C72",
"X c #0B4077",
"o c #0C437B",
"O c #134175",
"+ c #15467C",
"@ c #18477B",
"# c #1A497D",
"$ c #0D4B86",
"% c #0F4E8D",
"& c #124A80",
"* c #1F4F83",
"= c #0E518C",
"- c #1F5084",
"; c #11508C",
": c #0F5193",
"> c #115799",
", c #115B9C",
"< c #204F83",
"1 c #245287",
"2 c #2A598C",
"3 c #325E8F",
"4 c #11609F",
"5 c #346496",
"6 c #3B6898",
"7 c #115CA1",
"8 c #115EAC",
"9 c #1263A3",
"0 c #1260AD",
"q c #136BAC",
"w c #136BB2",
"e c #1366BA",
"r c #196BB2",
"t c #157ABB",
"y c #1577BB",
"u c #2E6DB0",
"i c #387FB1",
"p c #456E9A",
"a c #4873A1",
"s c #4375AA",
"d c #507AA6",
"f c #597EA4",
"g c #4D7EB3",
"h c #156FCB",
"j c #167AC5",
"k c #1675CA",
"l c #177BCE",
"z c #1777D8",
"x c #1476E4",
"c c #167BE6",
"v c #167DE8",
"b c #197EEF",
"n c #1A7FF0",
"m c #1A80BE",
"M c #5F87AF",
"N c #5D8BBA",
"B c #5A84B1",
"V c #6C8FB3",
"C c #6F96BE",
"Z c #1886CC",
"A c #1883D7",
"S c #198DD5",
"D c #1987D9",
"F c #198ADC",
"G c #1A96DC",
"H c #3090D9",
"J c #1682E9",
"K c #1983ED",
"L c #1689E9",
"P c #1A8DEE",
"I c #1B95ED",
"U c #1C9EEA",
"Y c #1B97E4",
"T c #1A84F2",
"R c #1A8BF2",
"E c #1C94F4",
"W c #1D9CF5",
"Q c #3388E6",
"! c #3D90E9",
"~ c #228EF3",
"^ c #229FF6",
"/ c #3294F4",
"( c #3D9FF6",
") c #339CF4",
"_ c #1CA2E5",
"` c #1DABEE",
"' c #1DA4F6",
"] c #1EA9F7",
"[ c #1EADF8",
"{ c #1FB4F9",
"} c #1FB9FA",
"| c #20ACF8",
" . c #27A4F6",
".. c #3DA9F6",
"X. c #20B9FA",
"o. c #2EB6F9",
"O. c #458DC9",
"+. c #5C8DC1",
"@. c #5795C6",
"#. c #709DCB",
"$. c #74A8DD",
"%. c #4A97EA",
"&. c #4896EA",
"*. c #559EEA",
"=. c #439AF5",
"-. c #46A3F6",
";. c #5FA9F6",
":. c #5EA6F3",
">. c #47BCF9",
",. c #51B5F8",
"<. c #58BDF8",
"1. c #68ABEF",
"2. c #7DB9E7",
"3. c #63AEF7",
"4. c #6FB1F7",
"5. c #66B9F8",
"6. c #61B2F6",
"7. c #71B4F7",
"8. c #78B7F4",
"9. c #72BFF9",
"0. c #3BC0FA",
"q. c #6FCEFB",
"w. c #6CC5FA",
"e. c #7BCAF9",
"r. c #89A7C3",
"t. c #83A2C1",
"y. c #98B6D3",
"u. c #9DB9D3",
"i. c #89B6E4",
"p. c #83B6E9",
"a. c #81BDF7",
"s. c #83BFF8",
"d. c #9EC4E9",
"f. c #8CC2F9",
"g. c #85CDFB",
"h. c #87C4F9",
"j. c #92C6F9",
"k. c #95CAFA",
"l. c #9CCBFA",
"z. c #89D7FC",
"x. c #91D9FC",
"c. c #9CDEFD",
"v. c #9ED2FB",
"b. c #A7CAEC",
"n. c #B5CEE3",
"m. c #A1CEFA",
"M. c #AED0F0",
"N. c #ACD6FA",
"B. c #A0DFFC",
"V. c #AFD8FC",
"C. c #B5D9FB",
"Z. c #BCDDFC",
"A. c #BFDCF5",
"S. c #ACE3FD",
"D. c #B5E5FE",
"F. c #BBE2FC",
"G. c #CFE5F5",
"H. c #C3E1FC",
"J. c #CAE6FD",
"K. c #CCEBFD",
"L. c #C4EBFE",
"P. c #D6EDFE",
"I. c #DAEEFD",
"U. c #DEF1FE",
"Y. c #D6F2FE",
"T. c #E4F4FE",
"R. c #E9F6FE",
"E. c #EBF8FF",
"W. c None",
/* pixels */
"W.W.W.W.W.W.W.W.W.W.W.c.S.L.Y.E.E.S.X.} W.W.W.W.W.W.W.W.W.W.W.W.",
"W.W.W.W.W.W.W.W.W.D.T.E.E.T.L.D.c.z.} } X.} } W.W.W.W.W.W.W.W.W.",
"W.W.W.W.W.W.W.B.T.T.R.T.R.U.0.X.z.S.} } } } { { X.W.W.W.W.W.W.W.",
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
"I E l u W E W E W E E E E A . - k.6.",
"P E E 7 m.o E E E E E E E E l . = E P ",
"L E E E > . O s.o E E E E E E E E 7 , E L ",
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
"W.W.W.W.W.c T T =.f.j.j.s.j.j.j.j.$.g s u e h b T T v W.W.W.W.W.",
"W.W.W.W.W.W.c b n 4.f.f.s.m.s.s.s.j.s.j./ T n T b c W.W.W.W.W.W.",
"W.W.W.W.W.W.W.c x 1.s.s.s.s.s.s.s.s.4.=.n T n c c W.W.W.W.W.W.W.",
"W.W.W.W.W.W.W.W.W.&.*.1.a.s.s.s.s.3.n n v x x W.W.W.W.W.W.W.W.W.",
"W.W.W.W.W.W.W.W.W.W.W.%.%.%.%.*.*.Q x x x W.W.W.W.W.W.W.W.W.W.W."
};

View File

@@ -5,9 +5,18 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install python3-pyqt5 python3-pyqt5.qtwebkit python-tox \
python3-sip xvfb git python3-setuptools wget \
herbstluftwm locales
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
locales \
libjs-pdf
RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen
RUN useradd user && mkdir /home/user && chown -R user:users /home/user

View File

@@ -5,9 +5,21 @@ ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install python3-pyqt5 python3-pyqt5.qtwebkit python-tox \
python3-sip xvfb git python3-setuptools wget \
herbstluftwm language-pack-en
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
language-pack-en \
libjs-pdf \
dbus
RUN dbus-uuidgen --ensure
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user

View File

@@ -0,0 +1,37 @@
FROM ubuntu:xenial
MAINTAINER Florian Bruhin <me@the-compiler.org>
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -y update && \
apt-get -y dist-upgrade && \
apt-get -y install --no-install-recommends \
python3-pyqt5 \
python3-pyqt5.qtwebkit \
python-tox \
python3-sip \
xvfb \
git \
python3-setuptools \
wget \
herbstluftwm \
language-pack-en \
libjs-pdf \
dbus
RUN dbus-uuidgen --ensure
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
USER user
WORKDIR /home/user
ENV DISPLAY=:0
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
CMD Xvfb -screen 0 800x600x24 :0 & \
sleep 2 && \
herbstluftwm & \
git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
tox -e py35

View File

@@ -1,7 +0,0 @@
These files are copied from Qt's source tree in
src/plugins/platforms/cocoa/qt_menu.nib at revision
b8246f08e49eb672974fd3d3d972a5ff13c1524d.
http://code.qt.io/cgit/qt/qtbase.git/tree/src/plugins/platforms/cocoa/qt_menu.nib
They are needed for cx_Freeze and don't seem to be bundled with Qt anymore.

View File

@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>ACTIONS</key>
<dict>
<key>hide</key>
<string>id</string>
<key>hideOtherApplications</key>
<string>id</string>
<key>orderFrontStandardAboutPanel</key>
<string>id</string>
<key>qtDispatcherToQPAMenuItem</key>
<string>id</string>
<key>terminate</key>
<string>id</string>
<key>unhideAllApplications</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>QCocoaMenuLoader</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>aboutItem</key>
<string>NSMenuItem</string>
<key>aboutQtItem</key>
<string>NSMenuItem</string>
<key>appMenu</key>
<string>NSMenu</string>
<key>hideItem</key>
<string>NSMenuItem</string>
<key>preferencesItem</key>
<string>NSMenuItem</string>
<key>quitItem</key>
<string>NSMenuItem</string>
<key>theMenu</key>
<string>NSMenu</string>
</dict>
<key>SUPERCLASS</key>
<string>NSResponder</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>672</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>57</integer>
</array>
<key>IBSystem Version</key>
<string>9L31a</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

Binary file not shown.

74
misc/qutebrowser.spec Normal file
View File

@@ -0,0 +1,74 @@
# -*- mode: python -*-
import sys
import os
sys.path.insert(0, os.getcwd())
from scripts import setupcommon
block_cipher = None
def get_data_files():
data_files = [
('../qutebrowser/html', 'html'),
('../qutebrowser/img', 'img'),
('../qutebrowser/javascript', 'javascript'),
('../qutebrowser/html/doc', 'html/doc'),
('../qutebrowser/git-commit-id', '')
]
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
else:
print("Warning: excluding pdfjs as it's not present!")
return data_files
setupcommon.write_git_file()
if os.name == 'nt':
icon = 'icons/qutebrowser.ico'
elif sys.platform == 'darwin':
icon = 'icons/qutebrowser.icns'
else:
icon = None
a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'],
binaries=None,
datas=get_data_files(),
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='qutebrowser',
icon=icon,
debug=False,
strip=False,
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='qutebrowser')
app = BUNDLE(coll,
name='qutebrowser.app',
icon=icon,
info_plist={'NSHighResolutionCapable': 'True'},
bundle_identifier=None)

View File

@@ -0,0 +1,20 @@
This directory contains various `requirements` files which are used by `tox` to
have reproducable tests with pinned versions.
The files are generated based on unpinned requirements in `*.txt-raw` files.
Those files can also contain some special commands:
- Add an additional comment to a line: `#@ comment: <package> <comment here>`
- Filter a line for requirements.io: `#@ filter: <package> <filter>`
- Don't include a package in the output: `#@ ignore: <package>` (or multiple packages)
- Replace a part of a frozen package specification with another: `#@ replace <regex> <replacement>`
Some examples:
```
#@ comment: mypkg blah blub
#@ filter: mypkg != 1.0.0
#@ ignore: mypkg, otherpkg
#@ replace: foo bar
```

View File

@@ -0,0 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
check-manifest==0.31

View File

@@ -0,0 +1 @@
check-manifest

View File

@@ -0,0 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
codecov==2.0.5
coverage==4.1
requests==2.10.0

View File

@@ -0,0 +1 @@
codecov

View File

@@ -0,0 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cx-Freeze==4.3.4

View File

@@ -0,0 +1 @@
cx_Freeze

View File

@@ -0,0 +1,29 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
ebb-lint==0.4.4
flake8==2.5.4
flake8-copyright==0.1
flake8-debugger==1.4.0
flake8-deprecated==1.0
flake8-docstrings==0.2.6
flake8-future-import==0.4.1
flake8-mock==0.2
flake8-pep3101==0.3
flake8-putty==0.3.2
flake8-string-format==0.2.2
flake8-tidy-imports==1.0.0
flake8-tuple==0.2.9
hacking==0.11.0
intervaltree==2.1.0
mccabe==0.5.0
packaging==16.7
pbr==1.10.0
pep257==0.7.0 # still needed by flake8-docstrings but ignored
pep8==1.7.0
pep8-naming==0.3.3
pydocstyle==1.0.0
pyflakes==1.2.3
pyparsing==2.1.4
six==1.10.0
sortedcontainers==1.5.3
venusian==1.0

View File

@@ -0,0 +1,21 @@
ebb-lint
flake8
flake8-copyright
flake8-debugger
flake8-deprecated
flake8-docstrings
flake8-future-import
flake8-mock
flake8-pep3101
flake8-putty
flake8-string-format
flake8-tidy-imports
flake8-tuple
hacking
pep8-naming
pydocstyle
pyflakes
mccabe==0.5.0
#@ comment: pep257 still needed by flake8-docstrings but ignored

View File

@@ -0,0 +1 @@
pip==8.1.2

View File

@@ -0,0 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyInstaller==3.2

View File

@@ -0,0 +1 @@
PyInstaller

View File

@@ -0,0 +1,11 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
isort==4.2.5
lazy-object-proxy==1.2.2
mccabe==0.5.0
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers
requests==2.10.0
six==1.10.0
wrapt==1.10.8

View File

@@ -0,0 +1,13 @@
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers
requests
# https://github.com/PyCQA/pylint/issues/932
mccabe==0.5.0
# remove @commit-id for scm installs
#@ replace: @.*# #
# fix qute-pylint location
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers

View File

@@ -0,0 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.4.6
colorama==0.3.7
lazy-object-proxy==1.2.2
pylint==1.5.6
./scripts/dev/pylint_checkers
requests==2.10.0
six==1.10.0
wrapt==1.10.8

View File

@@ -0,0 +1,6 @@
pylint
./scripts/dev/pylint_checkers
requests
# fix qute-pylint location
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers

View File

@@ -0,0 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
docutils==0.12
pyroma==2.0.2

View File

@@ -0,0 +1 @@
pyroma

View File

@@ -0,0 +1,6 @@
Jinja2
Pygments
pyPEG2
PyYAML
colorama
cssutils

View File

@@ -0,0 +1,32 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
beautifulsoup4==4.4.1
CherryPy==6.0.1
coverage==4.1
decorator==4.0.10
Flask==0.10.1 # rq.filter: < 0.11.0
glob2==0.4.1
httpbin==0.4.1
hypothesis==3.4.0
itsdangerous==0.24
# Jinja2==2.8
Mako==1.0.4
# MarkupSafe==0.23
parse==1.6.6
parse-type==0.3.4
py==1.4.31
pytest==2.9.2
pytest-bdd==2.16.1
pytest-catchlog==1.2.2
pytest-cov==2.2.1
pytest-faulthandler==1.3.0
pytest-instafail==0.3.0
pytest-mock==1.1
pytest-qt==1.11.0
pytest-repeat==0.2
pytest-rerunfailures==2.0.0
pytest-travis-fold==1.2.0
pytest-xvfb==0.2.0
six==1.10.0
vulture==0.8.1
Werkzeug==0.11.10

View File

@@ -0,0 +1,22 @@
beautifulsoup4
CherryPy
coverage
Flask==0.10.1
httpbin
hypothesis
pytest
pytest-bdd
pytest-catchlog
pytest-cov
pytest-faulthandler
pytest-instafail
pytest-mock
pytest-qt
pytest-repeat
pytest-rerunfailures
pytest-travis-fold
pytest-xvfb
vulture
#@ filter: Flask < 0.11.0
#@ ignore: Jinja2, MarkupSafe

View File

@@ -0,0 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pluggy==0.3.1
py==1.4.31
tox==2.3.1
virtualenv==15.0.2

View File

@@ -0,0 +1 @@
tox

View File

@@ -0,0 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
vulture==0.8.1

View File

@@ -0,0 +1 @@
vulture

View File

@@ -22,7 +22,7 @@ Pass backend: (see also passwordstore.org)
must be contained in a later line beginning with "user:", "login:", or
"username:" (configurable by the user_pattern variable).
Behaviour:
Behavior:
It will try to find a username/password entry in the configured backend
(currently only pass) for the current website and will load that pair of
username and password to any form on the current page that has some password
@@ -61,7 +61,7 @@ die() {
}
javascript_escape() {
# print the first argument in a escaped way, such that it can savely
# print the first argument in a escaped way, such that it can safely
# be used within javascripts double quotes
sed "s,[\\\'\"],\\\&,g" <<< "$1"
}
@@ -80,7 +80,7 @@ javascript_escape() {
# between different paths on the same domain.
simplify_url() {
simple_url="${1##*://}" # remove protocoll specification
simple_url="${1##*://}" # remove protocol specification
simple_url="${simple_url%%\?*}" # remove GET parameters
simple_url="${simple_url%%/*}" # remove directory path
simple_url="${simple_url%:*}" # remove port
@@ -89,7 +89,7 @@ simplify_url() {
# no_entries_found() is called if the first query_entries() call did not find
# any matching entries. Multiple implementations are possible:
# The easiest behaviour is to quit:
# The easiest behavior is to quit:
#no_entries_found() {
# if [ 0 -eq "${#files[@]}" ] ; then
# die "No entry found for »$simple_url«"
@@ -106,7 +106,7 @@ simplify_url() {
# fi
# }
# Another beahviour is to drop another level of subdomains until search hits
# Another behavior is to drop another level of subdomains until search hits
# are found:
no_entries_found() {
while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
@@ -170,7 +170,7 @@ choose_entry_random() {
# dmenu or zenity or even qutebrowser completion in future?) which entry to
# pick
MENU_COMMAND=( head -n 1 )
# whether to show the menu if there is only one entrie in it
# whether to show the menu if there is only one entry in it
menu_if_one_entry=0
choose_entry_menu() {
local nr=${#files[@]}
@@ -245,7 +245,7 @@ pass_backend() {
done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
fi
if ((match_filename)) ; then
# add entries wth matching filepath
# add entries with matching filepath
while read -r passfile ; do
passfile="${passfile#$PREFIX}"
passfile="${passfile#/}"

View File

@@ -1,6 +1,6 @@
#!/bin/bash -e
#
# Behaviour:
# Behavior:
# Userscript for qutebrowser which views the current web page in mpv using
# sensible mpv-flags. While viewing the page in MPV, all <video>, <embed>,
# and <object> tags in the original page are temporarily removed. Clicking on

View File

@@ -11,12 +11,12 @@ markers =
not_frozen: Tests which can't be run if sys.frozen is True.
no_xvfb: Tests which can't be run with Xvfb.
frozen: Tests which can only be run if sys.frozen is True.
integration: Tests which test a bigger portion of code, run without coverage.
skip: Always skipped test.
integration: Tests which test a bigger portion of code
end2end: End to end tests which run qutebrowser as subprocess
pyqt531_or_newer: Needs PyQt 5.3.1 or newer.
xfail_norun: xfail the test with out running it
ci: Tests which should only run on CI.
flaky: Tests which are flaky and should be rerun
flaky_once: Try to rerun this test once if it fails
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
@@ -37,5 +37,6 @@ qt_log_ignore =
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
^QXcbClipboard: Cannot transfer data, no data available
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
qt_wait_signal_raising = true
xfail_strict = true

View File

@@ -28,7 +28,7 @@ __copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (0, 6, 0)
__version_info__ = (0, 7, 0)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."

View File

@@ -31,7 +31,7 @@ import atexit
import datetime
import tokenize
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
@@ -244,12 +244,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
cwd: The cwd to use for fuzzy_url.
target_arg: Command line argument received by a running instance via
ipc. If the --target argument was not specified, target_arg
will be an empty string instead of None. This behavior is
caused by the PyQt signal
``got_args = pyqtSignal(list, str, str)``
used in the misc.ipc.IPCServer class. PyQt converts the
None value into a null QString and then back to an empty
python string
will be an empty string.
"""
if via_ipc and not args:
win_id = mainwindow.get_window(via_ipc, force_window=True)
@@ -275,6 +270,8 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
log.init.debug("Startup URL {}".format(cmd))
if not cwd: # could also be an empty string due to the PyQt signal
cwd = None
try:
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
except urlutils.InvalidUrlError as e:
@@ -282,7 +279,8 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
"{}".format(cmd, e))
else:
background = open_target in ('tab-bg', 'tab-bg-silent')
tabbed_browser.tabopen(url, background=background)
tabbed_browser.tabopen(url, background=background,
explicit=True)
def _open_startpage(win_id=None):
@@ -342,20 +340,21 @@ def _save_version():
state_config['general']['version'] = qutebrowser.__version__
@pyqtSlot('QWidget*', 'QWidget*')
def on_focus_changed(_old, new):
"""Register currently focused main window in the object registry."""
if new is None:
window = None
else:
window = new.window()
if window is None or not isinstance(window, mainwindow.MainWindow):
if not isinstance(new, QWidget):
log.misc.debug("on_focus_changed called with non-QWidget {!r}".format(
new))
if new is None or not isinstance(new, mainwindow.MainWindow):
try:
objreg.delete('last-focused-main-window')
except KeyError:
pass
qApp.restoreOverrideCursor()
else:
objreg.register('last-focused-main-window', window, update=True)
objreg.register('last-focused-main-window', new.window(), update=True)
_maybe_hide_mouse_cursor()
@@ -427,7 +426,9 @@ def _init_modules(args, crash_handler):
proxy.init()
log.init.debug("Initializing cookies...")
cookie_jar = cookies.CookieJar(qApp)
ram_cookie_jar = cookies.RAMCookieJar(qApp)
objreg.register('cookie-jar', cookie_jar)
objreg.register('ram-cookie-jar', ram_cookie_jar)
log.init.debug("Initializing cache...")
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
objreg.register('cache', diskcache)
@@ -498,7 +499,7 @@ class Quitter:
for dirpath, _dirnames, filenames in os.walk(path):
for fn in filenames:
if os.path.splitext(fn)[1] == '.py':
if os.path.splitext(fn)[1] == '.py' and os.path.isfile(fn):
with tokenize.open(os.path.join(dirpath, fn)) as f:
compile(f.read(), fn, 'exec')
@@ -723,8 +724,8 @@ class Quitter:
# segfaults.
QTimer.singleShot(0, functools.partial(qApp.exit, status))
@cmdutils.register(instance='quitter', name='wq',
completion=[usertypes.Completion.sessions])
@cmdutils.register(instance='quitter', name='wq')
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
def save_and_quit(self, name=sessions.default):
"""Save open pages and quit.

View File

@@ -116,6 +116,7 @@ class HostBlocker:
self._local_hosts_file = None
else:
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
self.on_config_changed()
config_dir = standarddir.config()
if config_dir is None:
@@ -176,7 +177,8 @@ class HostBlocker:
message.info('current',
"Run :adblock-update to get adblock lists.")
@cmdutils.register(instance='host-blocker', win_id='win_id')
@cmdutils.register(instance='host-blocker')
@cmdutils.argument('win_id', win_id=True)
def adblock_update(self, win_id):
"""Update the adblock block lists.
@@ -273,11 +275,13 @@ class HostBlocker:
def on_config_changed(self):
"""Update files when the config changed."""
urls = config.get('content', 'host-block-lists')
if urls is None:
if urls is None and self._local_hosts_file is not None:
try:
os.remove(self._local_hosts_file)
except OSError:
log.misc.exception("Failed to delete hosts file.")
except FileNotFoundError:
pass
except OSError as e:
log.misc.exception("Failed to delete hosts file: {}".format(e))
def on_download_finished(self, download):
"""Check if all downloads are finished and if so, trigger reading.

View File

@@ -32,16 +32,23 @@ class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size.
If the cache is deactivated via the command line argument --cachedir="",
both attributes _cache_dir and _http_cache_dir are set to None.
Attributes:
_activated: Whether the cache should be used.
_cache_dir: The base directory for cache files (standarddir.cache())
_http_cache_dir: the HTTP subfolder in _cache_dir.
_cache_dir: The base directory for cache files (standarddir.cache()) or
None.
_http_cache_dir: the HTTP subfolder in _cache_dir or None.
"""
def __init__(self, cache_dir, parent=None):
super().__init__(parent)
self._cache_dir = cache_dir
self._http_cache_dir = os.path.join(cache_dir, 'http')
if cache_dir is None:
self._http_cache_dir = None
else:
self._http_cache_dir = os.path.join(cache_dir, 'http')
self._maybe_activate()
objreg.get('config').changed.connect(self.on_config_changed)
@@ -65,7 +72,7 @@ class DiskCache(QNetworkDiskCache):
"""Update cache size/activated if the config was changed."""
if (section, option) == ('storage', 'cache-size'):
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
elif (section, option) == ('general', # pragma: no branch
elif (section, option) == ('general', # pragma: no branch
'private-browsing'):
self._maybe_activate()

View File

@@ -42,7 +42,7 @@ from qutebrowser.config import config, configexc
from qutebrowser.browser import webelem, inspector, urlmarks, downloads, mhtml
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils)
objreg, utils, typing)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import instances, sortfilter
@@ -65,7 +65,6 @@ class CommandDispatcher:
"""
def __init__(self, win_id, tabbed_browser):
self._editor = None
self._win_id = win_id
self._tabbed_browser = tabbed_browser
@@ -200,8 +199,8 @@ class CommandDispatcher:
"{!r}!".format(conf_selection))
return None
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_close(self, left=False, right=False, opposite=False, count=None):
"""Close the current/[count]th tab.
@@ -227,8 +226,9 @@ class CommandDispatcher:
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
@cmdutils.register(instance='command-dispatcher', name='open',
maxsplit=0, scope='window', count='count',
completion=[usertypes.Completion.url])
maxsplit=0, scope='window')
@cmdutils.argument('url', completion=usertypes.Completion.url)
@cmdutils.argument('count', count=True)
def openurl(self, url=None, bg=False, tab=False, window=False, count=None):
"""Open a URL in the current/[count]th tab.
@@ -249,7 +249,10 @@ class CommandDispatcher:
try:
url = urlutils.fuzzy_url(url)
except urlutils.InvalidUrlError as e:
raise cmdexc.CommandError(e)
# We don't use cmdexc.CommandError here as this can be called
# async from edit_url
message.error(self._win_id, str(e))
return
if tab or bg or window:
self._open(url, tab, bg, window)
else:
@@ -266,7 +269,8 @@ class CommandDispatcher:
curtab.openurl(url)
@cmdutils.register(instance='command-dispatcher', name='reload',
scope='window', count='count')
scope='window')
@cmdutils.argument('count', count=True)
def reloadpage(self, force=False, count=None):
"""Reload the current/[count]th tab.
@@ -281,8 +285,8 @@ class CommandDispatcher:
else:
tab.reload()
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def stop(self, count=None):
"""Stop loading in the current/[count]th tab.
@@ -294,7 +298,8 @@ class CommandDispatcher:
tab.stop()
@cmdutils.register(instance='command-dispatcher', name='print',
scope='window', count='count')
scope='window')
@cmdutils.argument('count', count=True)
def printpage(self, preview=False, count=None):
"""Print the current/[count]th tab.
@@ -348,6 +353,8 @@ class CommandDispatcher:
new_tabbed_browser.set_page_title(idx, cur_title)
if config.get('tabs', 'show-favicons'):
new_tabbed_browser.setTabIcon(idx, curtab.icon())
if config.get('tabs', 'tabs-are-windows'):
new_tabbed_browser.window().setWindowIcon(curtab.icon())
newtab.keep_icon = True
newtab.setZoomFactor(curtab.zoomFactor())
history = qtutils.serialize(curtab.history())
@@ -387,8 +394,8 @@ class CommandDispatcher:
raise cmdexc.CommandError("At beginning of history.")
widget.back()
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def back(self, tab=False, bg=False, window=False, count=1):
"""Go back in the history of the current tab.
@@ -400,8 +407,8 @@ class CommandDispatcher:
"""
self._back_forward(tab, bg, window, count, forward=False)
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def forward(self, tab=False, bg=False, window=False, count=1):
"""Go forward in the history of the current tab.
@@ -428,6 +435,7 @@ class CommandDispatcher:
new_url = urlutils.incdec_number(url, incdec, segments=segments)
except urlutils.IncDecError as error:
raise cmdexc.CommandError(error.msg)
self._open(new_url, tab, background, window)
def _navigate_up(self, url, tab, background, window):
@@ -447,9 +455,9 @@ class CommandDispatcher:
self._open(url, tab, background, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
def navigate(self, where: {'type': ('prev', 'next', 'up', 'increment',
'decrement')},
tab=False, bg=False, window=False):
@cmdutils.argument('where', choices=['prev', 'next', 'up', 'increment',
'decrement'])
def navigate(self, where: str, tab=False, bg=False, window=False):
"""Open typical prev/next links or navigate using the URL path.
This tries to automatically click on typical _Previous Page_ or
@@ -470,10 +478,13 @@ class CommandDispatcher:
bg: Open in a background tab.
window: Open in a new window.
"""
# save the pre-jump position in the special ' mark
self.set_mark("'")
cmdutils.check_exclusive((tab, bg, window), 'tbw')
widget = self._current_widget()
frame = widget.page().currentFrame()
url = self._current_url()
url = self._current_url().adjusted(QUrl.RemoveFragment)
if frame is None:
raise cmdexc.CommandError("No frame focused!")
hintmanager = objreg.get('hintmanager', scope='tab', tab='current')
@@ -492,13 +503,14 @@ class CommandDispatcher:
"`where'.".format(where))
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
def scroll_px(self, dx: {'type': int}, dy: {'type': int}, count=1):
scope='window')
@cmdutils.argument('count', count=True)
def scroll_px(self, dx: int, dy: int, count=1):
"""Scroll the current tab by 'count * dx/dy' pixels.
Args:
dx: How much to scroll in x-direction.
dy: How much to scroll in x-direction.
dy: How much to scroll in y-direction.
count: multiplier
"""
dx *= count
@@ -508,8 +520,9 @@ class CommandDispatcher:
self._current_widget().page().currentFrame().scroll(dx, dy)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
def scroll(self, direction: {'type': (str, int)}, count=1):
scope='window')
@cmdutils.argument('count', count=True)
def scroll(self, direction: typing.Union[str, int], count=1):
"""Scroll the current tab in the given direction.
Args:
@@ -567,9 +580,10 @@ class CommandDispatcher:
widget.keyReleaseEvent(release_evt)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
def scroll_perc(self, perc: {'type': float}=None,
horizontal: {'flag': 'x'}=False, count=None):
scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('horizontal', flag='x')
def scroll_perc(self, perc: float=None, horizontal=False, count=None):
"""Scroll to a specific percentage of the page.
The percentage can be given either as argument or as count.
@@ -580,6 +594,9 @@ class CommandDispatcher:
horizontal: Scroll horizontally instead of vertically.
count: Percentage to scroll.
"""
# save the pre-jump position in the special ' mark
self.set_mark("'")
if perc is None and count is None:
perc = 100
elif perc is None:
@@ -600,12 +617,14 @@ class CommandDispatcher:
frame.setScrollBarValue(orientation, int(m * perc / 100))
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
def scroll_page(self, x: {'type': float}, y: {'type': float}, *,
top_navigate: {'type': ('prev', 'decrement'),
'metavar': 'ACTION'}=None,
bottom_navigate: {'type': ('next', 'increment'),
'metavar': 'ACTION'}=None,
scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('top_navigate', metavar='ACTION',
choices=('prev', 'decrement'))
@cmdutils.argument('bottom_navigate', metavar='ACTION',
choices=('next', 'increment'))
def scroll_page(self, x: float, y: float, *,
top_navigate: str=None, bottom_navigate: str=None,
count=1):
"""Scroll the frame page-wise.
@@ -652,13 +671,14 @@ class CommandDispatcher:
frame.scroll(dx, dy)
@cmdutils.register(instance='command-dispatcher', scope='window')
def yank(self, title=False, sel=False, domain=False):
def yank(self, title=False, sel=False, domain=False, pretty=False):
"""Yank the current URL/title to the clipboard or primary selection.
Args:
sel: Use the primary selection instead of the clipboard.
title: Yank the title instead of the URL.
domain: Yank only the scheme, domain, and port number.
pretty: Yank the URL in pretty decoded form.
"""
if title:
s = self._tabbed_browser.page_title(self._current_index())
@@ -670,11 +690,13 @@ class CommandDispatcher:
':' + str(port) if port > -1 else '')
what = 'domain'
else:
s = self._current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
flags = QUrl.RemovePassword
if not pretty:
flags |= QUrl.FullyEncoded
s = self._current_url().toString(flags)
what = 'URL'
if sel and QApplication.clipboard().supportsSelection():
if sel and utils.supports_selection():
target = "primary selection"
else:
sel = False
@@ -684,8 +706,8 @@ class CommandDispatcher:
message.info(self._win_id, "Yanked {} to {}: {}".format(
what, target, s))
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_in(self, count=1):
"""Increase the zoom level for the current tab.
@@ -699,8 +721,8 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom_out(self, count=1):
"""Decrease the zoom level for the current tab.
@@ -714,9 +736,9 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
message.info(self._win_id, "Zoom level: {}%".format(perc))
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
def zoom(self, zoom: {'type': int}=None, count=None):
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom(self, zoom: int=None, count=None):
"""Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither of both is
@@ -766,14 +788,18 @@ class CommandDispatcher:
except IndexError:
raise cmdexc.CommandError("Nothing to undo!")
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_prev(self, count=1):
"""Switch to the previous tab, or switch [count] tabs back.
Args:
count: How many tabs to switch back.
"""
if self._count() == 0:
# Running :tab-prev after last tab was closed
# See https://github.com/The-Compiler/qutebrowser/issues/1448
return
newidx = self._current_index() - count
if newidx >= 0:
self._set_current_index(newidx)
@@ -782,14 +808,18 @@ class CommandDispatcher:
else:
raise cmdexc.CommandError("First tab")
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_next(self, count=1):
"""Switch to the next tab, or switch [count] tabs forward.
Args:
count: How many tabs to switch forward.
"""
if self._count() == 0:
# Running :tab-next after last tab was closed
# See https://github.com/The-Compiler/qutebrowser/issues/1448
return
newidx = self._current_index() + count
if newidx < self._count():
self._set_current_index(newidx)
@@ -811,7 +841,8 @@ class CommandDispatcher:
bg: Open in a background tab.
window: Open in new window.
"""
if sel and QApplication.clipboard().supportsSelection():
force_search = False
if sel and utils.supports_selection():
target = "Primary selection"
else:
sel = False
@@ -824,19 +855,20 @@ class CommandDispatcher:
if (len(text_urls) > 1 and not urlutils.is_url(text_urls[0]) and
urlutils.get_path_if_valid(
text_urls[0], check_exists=True) is None):
force_search = True
text_urls = [text]
for i, text_url in enumerate(text_urls):
if not window and i > 0:
tab = False
bg = True
try:
url = urlutils.fuzzy_url(text_url)
url = urlutils.fuzzy_url(text_url, force_search=force_search)
except urlutils.InvalidUrlError as e:
raise cmdexc.CommandError(e)
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window',
completion=[usertypes.Completion.tab])
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', completion=usertypes.Completion.tab)
def buffer(self, index):
"""Select tab by index or url/title best match.
@@ -889,16 +921,18 @@ class CommandDispatcher:
window.raise_()
tabbed_browser.setCurrentIndex(idx-1)
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
def tab_focus(self, index: {'type': (int, 'last')}=None, count=None):
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last'])
@cmdutils.argument('count', count=True)
def tab_focus(self, index: typing.Union[str, int]=None, count=None):
"""Select the tab given as argument/[count].
If neither count nor index are given, it behaves like tab-next.
Args:
index: The tab index to focus, starting with 1. The special value
`last` focuses the last focused tab.
`last` focuses the last focused tab. Negative indexes
counts from the end, such that -1 is the last tab.
count: The tab index to focus, starting with 1.
"""
if index == 'last':
@@ -907,6 +941,8 @@ class CommandDispatcher:
if index is None and count is None:
self.tab_next()
return
if index is not None and index < 0:
index = self._count() + index + 1
try:
idx = cmdutils.arg_or_count(index, count, default=1,
countzero=self._count())
@@ -919,9 +955,10 @@ class CommandDispatcher:
raise cmdexc.CommandError("There's no tab with index {}!".format(
idx))
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count')
def tab_move(self, direction: {'type': ('+', '-')}=None, count=None):
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('direction', choices=['+', '-'])
@cmdutils.argument('count', count=True)
def tab_move(self, direction: str=None, count=None):
"""Move the current tab.
Args:
@@ -958,9 +995,12 @@ class CommandDispatcher:
cmdutils.check_overflow(new_idx, 'int')
self._tabbed_browser.setUpdatesEnabled(False)
try:
color = self._tabbed_browser.tabBar().tab_data(
cur_idx, 'indicator-color')
self._tabbed_browser.removeTab(cur_idx)
self._tabbed_browser.insertTab(new_idx, tab, icon, label)
self._set_current_index(new_idx)
self._tabbed_browser.set_tab_indicator_color(new_idx, color)
finally:
self._tabbed_browser.setUpdatesEnabled(True)
@@ -969,8 +1009,10 @@ class CommandDispatcher:
def spawn(self, cmdline, userscript=False, verbose=False, detach=False):
"""Spawn a command in a shell.
Note the {url} variable which gets replaced by the current URL might be
useful here.
Note the `{url}` and `{url:pretty}` variables might be useful here.
`{url}` gets replaced by the URL in fully encoded format and
`{url:pretty}` uses a "pretty form" with most percent-encoded
characters decoded.
Args:
userscript: Run the command as a userscript. You can use an
@@ -1054,8 +1096,9 @@ class CommandDispatcher:
quickmark_manager.prompt_save(self._win_id, self._current_url())
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0,
completion=[usertypes.Completion.quickmark_by_name])
maxsplit=0)
@cmdutils.argument('name',
completion=usertypes.Completion.quickmark_by_name)
def quickmark_load(self, name, tab=False, bg=False, window=False):
"""Load a quickmark.
@@ -1085,8 +1128,8 @@ class CommandDispatcher:
"Bookmarked {}!".format(url.toDisplayString()))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0,
completion=[usertypes.Completion.bookmark_by_url])
maxsplit=0)
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
def bookmark_load(self, url, tab=False, bg=False, window=False):
"""Load a bookmark.
@@ -1163,8 +1206,8 @@ class CommandDispatcher:
cur.inspector.show()
@cmdutils.register(instance='command-dispatcher', scope='window')
def download(self, url=None, dest_old: {'hide': True}=None, *,
mhtml_=False, dest=None):
@cmdutils.argument('dest_old', hide=True)
def download(self, url=None, dest_old=None, *, mhtml_=False, dest=None):
"""Download a given URL, or current page if no URL given.
The form `:download [url] [dest]` is deprecated, use `:download --dest
@@ -1177,9 +1220,9 @@ class CommandDispatcher:
mhtml_: Download the current page and all assets as mhtml file.
"""
if dest_old is not None:
message.warning(
self._win_id, ":download [url] [dest] is deprecated - use"
" download --dest [dest] [url]")
message.warning(self._win_id,
":download [url] [dest] is deprecated - use"
" download --dest [dest] [url]")
if dest is not None:
raise cmdexc.CommandError("Can't give two destinations for the"
" download.")
@@ -1235,8 +1278,8 @@ class CommandDispatcher:
frame = widget.page().currentFrame()
html = frame.toHtml()
lexer = pygments.lexers.HtmlLexer()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
formatter = pygments.formatters.HtmlFormatter(full=True,
linenos='table')
highlighted = pygments.highlight(html, lexer, formatter)
current_url = self._current_url()
tab = self._tabbed_browser.tabopen(explicit=True)
@@ -1270,8 +1313,8 @@ class CommandDispatcher:
message.info(self._win_id, "Dumped page to {}.".format(dest))
@cmdutils.register(instance='command-dispatcher', name='help',
completion=[usertypes.Completion.helptopic],
scope='window')
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
def show_help(self, tab=False, bg=False, window=False, topic=None):
r"""Show help about a command or setting.
@@ -1311,6 +1354,27 @@ class CommandDispatcher:
url = QUrl('qute://help/{}'.format(path))
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
def messages(self, level='error', plain=False, tab=False, bg=False,
window=False):
"""Show a log of past messages.
Args:
level: Include messages with `level` or higher severity.
Valid values: vdebug, debug, info, warning, error, critical.
plain: Whether to show plaintext (as opposed to html).
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
"""
if level.upper() not in log.LOG_LEVELS:
raise cmdexc.CommandError("Invalid log level {}!".format(level))
if plain:
url = QUrl('qute://plainlog?level={}'.format(level))
else:
url = QUrl('qute://log?level={}'.format(level))
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher',
modes=[KeyMode.insert], hide=True, scope='window')
def open_editor(self):
@@ -1330,11 +1394,10 @@ class CommandDispatcher:
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
self._editor = editor.ExternalEditor(
self._win_id, self._tabbed_browser)
self._editor.editing_finished.connect(
functools.partial(self.on_editing_finished, elem))
self._editor.edit(text)
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
ed.editing_finished.connect(functools.partial(
self.on_editing_finished, elem))
ed.edit(text)
def on_editing_finished(self, elem, text):
"""Write the editor text into the form field and clean up tempfile.
@@ -1374,7 +1437,7 @@ class CommandDispatcher:
try:
sel = utils.get_clipboard(selection=True)
except utils.SelectionUnsupportedError:
return
sel = utils.get_clipboard()
log.misc.debug("Pasting primary selection into element {}".format(
elem.debug_text()))
@@ -1405,6 +1468,7 @@ class CommandDispatcher:
text: The text to search for.
reverse: Reverse search direction.
"""
self.set_mark("'")
view = self._current_widget()
self._clear_search(view, text)
flags = 0
@@ -1428,13 +1492,15 @@ class CommandDispatcher:
self._tabbed_browser.search_flags = flags
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
scope='window')
@cmdutils.argument('count', count=True)
def search_next(self, count=1):
"""Continue the search to the ([count]th) next term.
Args:
count: How many elements to ignore.
"""
self.set_mark("'")
view = self._current_widget()
self._clear_search(view, self._tabbed_browser.search_text)
@@ -1448,13 +1514,15 @@ class CommandDispatcher:
view.search(view.search_text, view.search_flags)
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window', count='count')
scope='window')
@cmdutils.argument('count', count=True)
def search_prev(self, count=1):
"""Continue the search to the ([count]th) previous term.
Args:
count: How many elements to ignore.
"""
self.set_mark("'")
view = self._current_widget()
self._clear_search(view, self._tabbed_browser.search_text)
@@ -1475,7 +1543,8 @@ class CommandDispatcher:
view.search(view.search_text, flags)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_line(self, count=1):
"""Move the cursor or selection to the next line.
@@ -1491,7 +1560,8 @@ class CommandDispatcher:
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_line(self, count=1):
"""Move the cursor or selection to the prev line.
@@ -1507,7 +1577,8 @@ class CommandDispatcher:
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_char(self, count=1):
"""Move the cursor or selection to the next char.
@@ -1523,7 +1594,8 @@ class CommandDispatcher:
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_char(self, count=1):
"""Move the cursor or selection to the previous char.
@@ -1539,7 +1611,8 @@ class CommandDispatcher:
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_word(self, count=1):
"""Move the cursor or selection to the end of the word.
@@ -1560,7 +1633,8 @@ class CommandDispatcher:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_next_word(self, count=1):
"""Move the cursor or selection to the next word.
@@ -1581,7 +1655,8 @@ class CommandDispatcher:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_prev_word(self, count=1):
"""Move the cursor or selection to the previous word.
@@ -1619,7 +1694,8 @@ class CommandDispatcher:
webview.triggerPageAction(act)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_start_of_next_block(self, count=1):
"""Move the cursor or selection to the start of next block.
@@ -1638,7 +1714,8 @@ class CommandDispatcher:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_start_of_prev_block(self, count=1):
"""Move the cursor or selection to the start of previous block.
@@ -1657,7 +1734,8 @@ class CommandDispatcher:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_next_block(self, count=1):
"""Move the cursor or selection to the end of next block.
@@ -1676,7 +1754,8 @@ class CommandDispatcher:
webview.triggerPageAction(a)
@cmdutils.register(instance='command-dispatcher', hide=True,
modes=[KeyMode.caret], scope='window', count='count')
modes=[KeyMode.caret], scope='window')
@cmdutils.argument('count', count=True)
def move_to_end_of_prev_block(self, count=1):
"""Move the cursor or selection to the end of previous block.
@@ -1727,7 +1806,7 @@ class CommandDispatcher:
message.info(self._win_id, "Nothing to yank")
return
if sel and QApplication.clipboard().supportsSelection():
if sel and utils.supports_selection():
target = "primary selection"
else:
sel = False
@@ -1755,7 +1834,8 @@ class CommandDispatcher:
self._current_widget().triggerPageAction(QWebPage.MoveToNextChar)
@cmdutils.register(instance='command-dispatcher', scope='window',
count='count', debug=True)
debug=True)
@cmdutils.argument('count', count=True)
def debug_webaction(self, action, count=1):
"""Execute a webaction.
@@ -1847,3 +1927,47 @@ class CommandDispatcher:
"""Clear remembered SSL error answers."""
nam = self._current_widget().page().networkAccessManager()
nam.clear_all_ssl_errors()
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def edit_url(self, url=None, bg=False, tab=False, window=False,
count=None):
"""Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the
`general -> editor` config option.
Args:
url: URL to edit; defaults to the current page url.
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
count: The tab index to open the URL in, or None.
"""
cmdutils.check_exclusive((tab, bg, window), 'tbw')
ed = editor.ExternalEditor(self._win_id, self._tabbed_browser)
# Passthrough for openurl args (e.g. -t, -b, -w)
ed.editing_finished.connect(functools.partial(
self.openurl, bg=bg, tab=tab, window=window, count=count))
ed.edit(url or self._current_url().toString())
@cmdutils.register(instance='command-dispatcher', scope='window')
def set_mark(self, key):
"""Set a mark at the current scroll position in the current tab.
Args:
key: mark identifier; capital indicates a global mark
"""
self._tabbed_browser.set_mark(key)
@cmdutils.register(instance='command-dispatcher', scope='window')
def jump_mark(self, key):
"""Jump to the mark named by `key`.
Args:
key: mark identifier; capital indicates a global mark
"""
self._tabbed_browser.jump_mark(key)

View File

@@ -49,10 +49,7 @@ ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
DownloadPath = collections.namedtuple('DownloadPath', ['filename',
'question'])
DownloadPath = collections.namedtuple('DownloadPath', ['filename', 'question'])
# Remember the last used directory
last_used_directory = None
@@ -90,7 +87,7 @@ def path_suggestion(filename):
return filename
elif suggestion == 'both':
return os.path.join(download_dir(), filename)
else:
else: # pragma: no cover
raise ValueError("Invalid suggestion value {}!".format(suggestion))
@@ -104,6 +101,11 @@ def create_full_filename(basename, filename):
Return:
The full absolute path, or None if filename creation was not possible.
"""
# Remove chars which can't be encoded in the filename encoding.
# See https://github.com/The-Compiler/qutebrowser/issues/427
encoding = sys.getfilesystemencoding()
filename = utils.force_encoding(filename, encoding)
basename = utils.force_encoding(basename, encoding)
if os.path.isabs(filename) and os.path.isdir(filename):
# We got an absolute directory from the user, so we save it under
# the default filename in that directory.
@@ -143,8 +145,7 @@ def ask_for_filename(suggested_filename, win_id, *, parent=None,
return DownloadPath(filename=download_dir(), question=None)
encoding = sys.getfilesystemencoding()
suggested_filename = utils.force_encoding(suggested_filename,
encoding)
suggested_filename = utils.force_encoding(suggested_filename, encoding)
q = usertypes.Question(parent)
q.text = "Save file to:"
@@ -226,7 +227,7 @@ class DownloadItemStats(QObject):
else:
return remaining_bytes / avg
@pyqtSlot(int, int)
@pyqtSlot('qint64', 'qint64')
def on_download_progress(self, bytes_done, bytes_total):
"""Update local variables when the download progress changed.
@@ -411,6 +412,8 @@ class DownloadItem(QObject):
self.reply = None
self.done = True
self.data_changed.emit()
if self.fileobj is not None:
self.fileobj.close()
def init_reply(self, reply):
"""Set a new reply and connect its signals.
@@ -456,8 +459,8 @@ class DownloadItem(QObject):
elif self.stats.percentage() is None:
return start
else:
return utils.interpolate_color(
start, stop, self.stats.percentage(), system)
return utils.interpolate_color(start, stop,
self.stats.percentage(), system)
@pyqtSlot()
def cancel(self, remove_data=True):
@@ -518,15 +521,11 @@ class DownloadItem(QObject):
None: special value to stop the download.
"""
global last_used_directory
if self.fileobj is not None:
if self.fileobj is not None: # pragma: no cover
raise ValueError("fileobj was already set! filename: {}, "
"existing: {}, fileobj {}".format(
filename, self._filename, self.fileobj))
filename = os.path.expanduser(filename)
# Remove chars which can't be encoded in the filename encoding.
# See https://github.com/The-Compiler/qutebrowser/issues/427
encoding = sys.getfilesystemencoding()
filename = utils.force_encoding(filename, encoding)
self._filename = create_full_filename(self.basename, filename)
if self._filename is None:
# We only got a filename (without directory) or a relative path
@@ -535,7 +534,7 @@ class DownloadItem(QObject):
self._filename = create_full_filename(
self.basename, os.path.join(download_dir(), filename))
# At this point, we have a misconfigured XDG_DOWNLOAd_DIR, as
# At this point, we have a misconfigured XDG_DOWNLOAD_DIR, as
# download_dir() + filename is still no absolute path.
# The config value is checked for "absoluteness", but
# ~/.config/user-dirs.dirs may be misconfigured and a non-absolute path
@@ -561,8 +560,8 @@ class DownloadItem(QObject):
txt = self._filename + " already exists. Overwrite?"
self._ask_confirm_question(txt)
# FIFO, device node, etc. Make sure we want to do this
elif (os.path.exists(self._filename) and not
os.path.isdir(self._filename)):
elif (os.path.exists(self._filename) and
not os.path.isdir(self._filename)):
txt = (self._filename + " already exists and is a special file. "
"Write to this?")
self._ask_confirm_question(txt)
@@ -575,7 +574,7 @@ class DownloadItem(QObject):
Args:
fileobj: A file-like object.
"""
if self.fileobj is not None:
if self.fileobj is not None: # pragma: no cover
raise ValueError("fileobj was already set! Old: {}, new: "
"{}".format(self.fileobj, fileobj))
self.fileobj = fileobj
@@ -649,7 +648,7 @@ class DownloadItem(QObject):
except OSError as e:
self._die(e.strerror)
@pyqtSlot(int)
@pyqtSlot('QNetworkReply::NetworkError')
def on_reply_error(self, code):
"""Handle QNetworkReply errors."""
if code == QNetworkReply.OperationCanceledError:
@@ -785,7 +784,7 @@ class DownloadManager(QAbstractListModel):
If not, None.
"""
if fileobj is not None and filename is not None:
if fileobj is not None and filename is not None: # pragma: no cover
raise TypeError("Only one of fileobj/filename may be given!")
# WORKAROUND for Qt corrupting data loaded from cache:
# https://bugreports.qt.io/browse/QTBUG-42757
@@ -869,7 +868,7 @@ class DownloadManager(QAbstractListModel):
Return:
The created DownloadItem.
"""
if fileobj is not None and filename is not None:
if fileobj is not None and filename is not None: # pragma: no cover
raise TypeError("Only one of fileobj/filename may be given!")
if not suggested_filename:
if filename is not None:
@@ -898,8 +897,8 @@ class DownloadManager(QAbstractListModel):
download.redirected.connect(
functools.partial(self.on_redirect, download))
download.basename = suggested_filename
idx = len(self.downloads) + 1
download.index = idx
idx = len(self.downloads)
download.index = idx + 1 # "Human readable" index
self.beginInsertRows(QModelIndex(), idx, idx)
self.downloads.append(download)
self.endInsertRows()
@@ -947,8 +946,8 @@ class DownloadManager(QAbstractListModel):
raise cmdexc.CommandError("There's no download!")
raise cmdexc.CommandError("There's no download {}!".format(count))
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@cmdutils.register(instance='download-manager', scope='window')
@cmdutils.argument('count', count=True)
def download_cancel(self, all_=False, count=0):
"""Cancel the last/[count]th download.
@@ -974,8 +973,8 @@ class DownloadManager(QAbstractListModel):
.format(count))
download.cancel()
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@cmdutils.register(instance='download-manager', scope='window')
@cmdutils.argument('count', count=True)
def download_delete(self, count=0):
"""Delete the last/[count]th download from disk.
@@ -992,9 +991,10 @@ class DownloadManager(QAbstractListModel):
raise cmdexc.CommandError("Download {} is not done!".format(count))
download.delete()
self.remove_item(download)
log.downloads.debug("deleted download {}".format(download))
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@cmdutils.register(instance='download-manager', scope='window')
@cmdutils.argument('count', count=True)
def download_open(self, count=0):
"""Open the last/[count]th download.
@@ -1011,8 +1011,8 @@ class DownloadManager(QAbstractListModel):
raise cmdexc.CommandError("Download {} is not done!".format(count))
download.open_file()
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@cmdutils.register(instance='download-manager', scope='window')
@cmdutils.argument('count', count=True)
def download_retry(self, count=0):
"""Retry the first failed/[count]th download.
@@ -1097,8 +1097,8 @@ class DownloadManager(QAbstractListModel):
finished_items = [d for d in self.downloads if d.done]
self.remove_items(finished_items)
@cmdutils.register(instance='download-manager', scope='window',
count='count')
@cmdutils.register(instance='download-manager', scope='window')
@cmdutils.argument('count', count=True)
def download_remove(self, all_=False, count=0):
"""Remove the last/[count]th download from the list.
@@ -1228,7 +1228,8 @@ class DownloadManager(QAbstractListModel):
def flags(self, _index):
"""Override flags so items aren't selectable.
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable."""
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable.
"""
return Qt.ItemIsEnabled | Qt.ItemNeverHasChildren
def rowCount(self, parent=QModelIndex()):
@@ -1237,3 +1238,11 @@ class DownloadManager(QAbstractListModel):
# We don't have children
return 0
return len(self.downloads)
def running_downloads(self):
"""Return the amount of still running downloads.
Return:
The number of unfinished downloads.
"""
return sum(1 for download in self.downloads if not download.done)

View File

@@ -23,7 +23,7 @@ import collections
import functools
import math
import re
import string
from string import ascii_lowercase
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl,
QTimer)
@@ -36,7 +36,6 @@ from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
from qutebrowser.misc import guiprocess
ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label'])
@@ -68,7 +67,9 @@ class HintContext:
frames: The QWebFrames to use.
destroyed_frames: id()'s of QWebFrames which have been destroyed.
(Workaround for https://github.com/The-Compiler/qutebrowser/issues/152)
all_elems: A list of all (elem, label) namedtuples ever created.
elems: A mapping from key strings to (elem, label) namedtuples.
May contain less elements than `all_elems` due to filtering.
baseurl: The URL of the current page.
target: What to do with the opened links.
normal/current/tab/tab_fg/tab_bg/window: Get passed to
@@ -87,6 +88,7 @@ class HintContext:
"""
def __init__(self):
self.all_elems = []
self.elems = {}
self.target = None
self.baseurl = None
@@ -118,6 +120,7 @@ class HintManager(QObject):
_context: The HintContext for the current invocation.
_win_id: The window ID this HintManager is associated with.
_tab_id: The tab ID this HintManager is associated with.
_filterstr: Used to save the filter string for restoring in rapid mode.
Signals:
mouse_event: Mouse event to be posted in the web view.
@@ -154,6 +157,7 @@ class HintManager(QObject):
self._win_id = win_id
self._tab_id = tab_id
self._context = None
self._filterstr = None
self._word_hinter = WordHinter()
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
@@ -169,7 +173,7 @@ class HintManager(QObject):
def _cleanup(self):
"""Clean up after hinting."""
for elem in self._context.elems.values():
for elem in self._context.all_elems:
try:
elem.label.removeFromDocument()
except webelem.IsNullError:
@@ -219,7 +223,7 @@ class HintManager(QObject):
else:
chars = config.get('hints', 'chars')
min_chars = config.get('hints', 'min-chars')
if config.get('hints', 'scatter'):
if config.get('hints', 'scatter') and hint_mode != 'number':
return self._hint_scattered(min_chars, chars, elems)
else:
return self._hint_linear(min_chars, chars, elems)
@@ -351,7 +355,7 @@ class HintManager(QObject):
('display', 'inline !important'),
('z-index', '{} !important'.format(int(2 ** 32 / 2 - 1))),
('pointer-events', 'none !important'),
('position', 'absolute !important'),
('position', 'fixed !important'),
('color', config.get('colors', 'hints.fg') + ' !important'),
('background', config.get('colors', 'hints.bg') + ' !important'),
('font', config.get('fonts', 'hints') + ' !important'),
@@ -377,19 +381,16 @@ class HintManager(QObject):
elem: The QWebElement to set the style attributes for.
label: The label QWebElement.
"""
rect = elem.geometry()
no_js = config.get('hints', 'find-implementation') != 'javascript'
rect = elem.rect_on_view(adjust_zoom=False, no_js=no_js)
left = rect.x()
top = rect.y()
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only'):
left /= zoom
top /= zoom
log.hints.vdebug("Drawing label '{!r}' at {}/{} for element '{!r}', "
"zoom level {}".format(label, left, top, elem, zoom))
log.hints.vdebug("Drawing label '{!r}' at {}/{} for element '{!r}' "
"(no_js: {})".format(label, left, top, elem, no_js))
label.setStyleProperty('left', '{}px !important'.format(left))
label.setStyleProperty('top', '{}px !important'.format(top))
def _draw_label(self, elem, text):
def _draw_label(self, elem, string):
"""Draw a hint label over an element.
Args:
@@ -414,7 +415,7 @@ class HintManager(QObject):
label = webelem.WebElementWrapper(parent.lastChild())
label['class'] = 'qutehint'
self._set_style_properties(elem, label)
label.setPlainText(text)
label.setPlainText(string)
return label
def _show_url_error(self):
@@ -441,14 +442,22 @@ class HintManager(QObject):
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
else:
target_mapping[Target.tab] = usertypes.ClickTarget.tab
# FIXME Instead of clicking the center, we could have nicer heuristics.
# e.g. parse (-webkit-)border-radius correctly and click text fields at
# the bottom right, and everything else on the top left or so.
# https://github.com/The-Compiler/qutebrowser/issues/70
pos = elem.rect_on_view().center()
# Click the center of the largest square fitting into the top/left
# corner of the rectangle, this will help if part of the <a> element
# is hidden behind other elements
# https://github.com/The-Compiler/qutebrowser/issues/1005
rect = elem.rect_on_view()
if rect.width() > rect.height():
rect.setWidth(rect.height())
else:
rect.setHeight(rect.width())
pos = rect.center()
action = "Hovering" if context.target == Target.hover else "Clicking"
log.hints.debug("{} on '{}' at {}/{}".format(
action, elem, pos.x(), pos.y()))
log.hints.debug("{} on '{}' at position {}".format(
action, elem.debug_text(), pos))
self.start_hinting.emit(target_mapping[context.target])
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
Target.window]:
@@ -466,6 +475,13 @@ class HintManager(QObject):
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, modifiers),
]
if context.target in [Target.normal, Target.current]:
# Set the pre-jump mark ', so we can jump back here after following
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
tabbed_browser.set_mark("'")
if context.target == Target.current:
elem.remove_blank_target()
for evt in events:
@@ -483,7 +499,9 @@ class HintManager(QObject):
url: The URL to open as a QUrl.
context: The HintContext to use.
"""
sel = context.target == Target.yank_primary
sel = (context.target == Target.yank_primary and
utils.supports_selection())
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
utils.set_clipboard(urlstr, selection=sel)
@@ -572,9 +590,8 @@ class HintManager(QObject):
"""
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
args = context.get_args(urlstr)
cmd, *args = args
proc = guiprocess.GUIProcess(self._win_id, what='command', parent=self)
proc.start(cmd, args)
commandrunner = runners.CommandRunner(self._win_id)
commandrunner.run_safely('spawn ' + ' '.join(args))
def _resolve_url(self, elem, baseurl):
"""Resolve a URL and check if we want to keep it.
@@ -588,7 +605,7 @@ class HintManager(QObject):
"""
for attr in ('href', 'src'):
if attr in elem:
text = elem[attr]
text = elem[attr].strip()
break
else:
return None
@@ -604,8 +621,7 @@ class HintManager(QObject):
def _find_prevnext(self, frame, prev=False):
"""Find a prev/next element in frame."""
# First check for <link rel="prev(ious)|next">
elems = frame.findAllElements(
webelem.SELECTORS[webelem.Group.links])
elems = frame.findAllElements(webelem.SELECTORS[webelem.Group.links])
rel_values = ('prev', 'previous') if prev else ('next')
for e in elems:
e = webelem.WebElementWrapper(e)
@@ -681,15 +697,27 @@ class HintManager(QObject):
elems = [e for e in elems if filterfunc(e)]
if not elems:
raise cmdexc.CommandError("No elements found.")
hints = self._hint_strings(elems)
log.hints.debug("hints: {}".format(', '.join(hints)))
for e, hint in zip(elems, hints):
label = self._draw_label(e, hint)
self._context.elems[hint] = ElemTuple(e, label)
strings = self._hint_strings(elems)
log.hints.debug("hints: {}".format(', '.join(strings)))
for e, string in zip(elems, strings):
label = self._draw_label(e, string)
elem = ElemTuple(e, label)
self._context.all_elems.append(elem)
self._context.elems[string] = elem
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
keyparser = keyparsers[usertypes.KeyMode.hint]
keyparser.update_bindings(hints)
keyparser.update_bindings(strings)
def _filter_matches(self, filterstr, elemstr):
"""Return True if `filterstr` matches `elemstr`."""
# Empty string and None always match
if not filterstr:
return True
filterstr = filterstr.casefold()
elemstr = elemstr.casefold()
# Do multi-word matching
return all(word in elemstr for word in filterstr.split())
def follow_prevnext(self, frame, baseurl, prev=False, tab=False,
background=False, window=False):
@@ -729,9 +757,10 @@ class HintManager(QObject):
webview.openurl(url)
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
win_id='win_id')
star_args_optional=True, maxsplit=2)
@cmdutils.argument('win_id', win_id=True)
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
*args: {'nargs': '*'}, win_id):
*args, win_id):
"""Start hinting.
Args:
@@ -795,7 +824,7 @@ class HintManager(QObject):
if rapid:
if target in [Target.tab_bg, Target.window, Target.run,
Target.hover, Target.userscript, Target.spawn,
Target.download]:
Target.download, Target.normal, Target.current]:
pass
elif (target == Target.tab and
config.get('tabs', 'background-tabs')):
@@ -833,53 +862,115 @@ class HintManager(QObject):
def handle_partial_key(self, keystr):
"""Handle a new partial keypress."""
log.hints.debug("Handling new keystring: '{}'".format(keystr))
for (text, elems) in self._context.elems.items():
for string, elem in self._context.elems.items():
try:
if text.startswith(keystr):
matched = text[:len(keystr)]
rest = text[len(keystr):]
if string.startswith(keystr):
matched = string[:len(keystr)]
rest = string[len(keystr):]
match_color = config.get('colors', 'hints.fg.match')
elems.label.setInnerXml(
elem.label.setInnerXml(
'<font color="{}">{}</font>{}'.format(
match_color, matched, rest))
if self._is_hidden(elems.label):
if self._is_hidden(elem.label):
# hidden element which matches again -> show it
self._show_elem(elems.label)
self._show_elem(elem.label)
else:
# element doesn't match anymore -> hide it
self._hide_elem(elems.label)
self._hide_elem(elem.label)
except webelem.IsNullError:
pass
def _filter_number_hints(self):
"""Apply filters for numbered hints and renumber them.
Return:
Elements which are still visible
"""
# renumber filtered hints
elems = []
for e in self._context.all_elems:
try:
if not self._is_hidden(e.label):
elems.append(e)
except webelem.IsNullError:
pass
if not elems:
# Whoops, filtered all hints
modeman.leave(self._win_id, usertypes.KeyMode.hint,
'all filtered')
return {}
strings = self._hint_strings(elems)
self._context.elems = {}
for elem, string in zip(elems, strings):
elem.label.setInnerXml(string)
self._context.elems[string] = elem
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
keyparser = keyparsers[usertypes.KeyMode.hint]
keyparser.update_bindings(strings, preserve_filter=True)
return self._context.elems
def _filter_non_number_hints(self):
"""Apply filters for letter/word hints.
Return:
Elements which are still visible
"""
visible = {}
for string, elem in self._context.elems.items():
try:
if not self._is_hidden(elem.label):
visible[string] = elem
except webelem.IsNullError:
pass
if not visible:
# Whoops, filtered all hints
modeman.leave(self._win_id, usertypes.KeyMode.hint,
'all filtered')
return visible
def filter_hints(self, filterstr):
"""Filter displayed hints according to a text.
Args:
filterstr: The string to filter with, or None to show all.
filterstr: The string to filter with, or None to use the filter
from previous call (saved in `self._filterstr`). If
`filterstr` is an empty string or if both `filterstr`
and `self._filterstr` are None, all hints are shown.
"""
for elems in self._context.elems.values():
if filterstr is None:
filterstr = self._filterstr
else:
self._filterstr = filterstr
for elem in self._context.all_elems:
try:
if (filterstr is None or
filterstr.casefold() in str(elems.elem).casefold()):
if self._is_hidden(elems.label):
if self._filter_matches(filterstr, str(elem.elem)):
if self._is_hidden(elem.label):
# hidden element which matches again -> show it
self._show_elem(elems.label)
self._show_elem(elem.label)
else:
# element doesn't match anymore -> hide it
self._hide_elem(elems.label)
self._hide_elem(elem.label)
except webelem.IsNullError:
pass
visible = {}
for k, e in self._context.elems.items():
try:
if not self._is_hidden(e.label):
visible[k] = e
except webelem.IsNullError:
pass
if not visible:
# Whoops, filtered all hints
modeman.leave(self._win_id, usertypes.KeyMode.hint, 'all filtered')
elif len(visible) == 1 and config.get('hints', 'auto-follow'):
if config.get('hints', 'mode') == 'number':
visible = self._filter_number_hints()
else:
visible = self._filter_non_number_hints()
if (len(visible) == 1 and
config.get('hints', 'auto-follow') and
filterstr is not None):
# apply auto-follow-timeout
timeout = config.get('hints', 'auto-follow-timeout')
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
normal_parser = keyparsers[usertypes.KeyMode.normal]
normal_parser.set_inhibited_timeout(timeout)
# unpacking gets us the first (and only) key in the dict.
self.fire(*visible)
@@ -917,30 +1008,31 @@ class HintManager(QObject):
}
elem = self._context.elems[keystr].elem
if elem.webFrame() is None:
message.error(self._win_id, "This element has no webframe.",
message.error(self._win_id,
"This element has no webframe.",
immediately=True)
return
if self._context.target in elem_handlers:
handler = functools.partial(
elem_handlers[self._context.target], elem, self._context)
handler = functools.partial(elem_handlers[self._context.target],
elem, self._context)
elif self._context.target in url_handlers:
url = self._resolve_url(elem, self._context.baseurl)
if url is None:
self._show_url_error()
return
handler = functools.partial(
url_handlers[self._context.target], url, self._context)
handler = functools.partial(url_handlers[self._context.target],
url, self._context)
else:
raise ValueError("No suitable handler found!")
if not self._context.rapid:
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
'followed')
else:
# Show all hints again
# Reset filtering
self.filter_hints(None)
# Undo keystring highlighting
for (text, elems) in self._context.elems.items():
elems.label.setInnerXml(text)
for string, elem in self._context.elems.items():
elem.label.setInnerXml(string)
handler()
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
@@ -964,13 +1056,13 @@ class HintManager(QObject):
def on_contents_size_changed(self, _size):
"""Reposition hints if contents size changed."""
log.hints.debug("Contents size changed...!")
for elems in self._context.elems.values():
for e in self._context.all_elems:
try:
if elems.elem.webFrame() is None:
if e.elem.webFrame() is None:
# This sometimes happens for some reason...
elems.label.removeFromDocument()
e.label.removeFromDocument()
continue
self._set_style_position(elems.elem, elems.label)
self._set_style_position(e.elem, e.label)
except webelem.IsNullError:
pass
@@ -997,14 +1089,17 @@ class WordHinter:
def __init__(self):
# will be initialized on first use.
self.words = set()
self.dictionary = None
def ensure_initialized(self):
"""Generate the used words if yet uninialized."""
if not self.words:
dictionary = config.get("hints", "dictionary")
"""Generate the used words if yet uninitialized."""
dictionary = config.get("hints", "dictionary")
if not self.words or self.dictionary != dictionary:
self.words.clear()
self.dictionary = dictionary
try:
with open(dictionary, encoding="UTF-8") as wordfile:
alphabet = set(string.ascii_lowercase)
alphabet = set(ascii_lowercase)
hints = set()
lines = (line.rstrip().lower() for line in wordfile)
for word in lines:
@@ -1034,13 +1129,11 @@ class WordHinter:
"text": str,
}
extractable_attrs = collections.defaultdict(
list, {
"IMG": ["alt", "title", "src"],
"A": ["title", "href", "text"],
"INPUT": ["name"]
}
)
extractable_attrs = collections.defaultdict(list, {
"IMG": ["alt", "title", "src"],
"A": ["title", "href", "text"],
"INPUT": ["name"]
})
return (attr_extractors[attr](elem)
for attr in extractable_attrs[elem.tagName()]
@@ -1058,15 +1151,19 @@ class WordHinter:
yield candidate[match.start():match.end()].lower()
def any_prefix(self, hint, existing):
return any(hint.startswith(e) or e.startswith(hint)
for e in existing)
return any(hint.startswith(e) or e.startswith(hint) for e in existing)
def new_hint_for(self, elem, existing):
def filter_prefixes(self, hints, existing):
return (h for h in hints if not self.any_prefix(h, existing))
def new_hint_for(self, elem, existing, fallback):
"""Return a hint for elem, not conflicting with the existing."""
new = self.tag_words_to_hints(self.extract_tag_words(elem))
no_prefixes = (h for h in new if not self.any_prefix(h, existing))
new_no_prefixes = self.filter_prefixes(new, existing)
fallback_no_prefixes = self.filter_prefixes(fallback, existing)
# either the first good, or None
return next(no_prefixes, None)
return (next(new_no_prefixes, None) or
next(fallback_no_prefixes, None))
def hint(self, elems):
"""Produce hint labels based on the html tags.
@@ -1086,7 +1183,9 @@ class WordHinter:
used_hints = set()
words = iter(self.words)
for elem in elems:
hint = self.new_hint_for(elem, used_hints) or next(words)
hint = self.new_hint_for(elem, used_hints, words)
if not hint:
raise WordHintingError("Not enough words in the dictionary.")
used_hints.add(hint)
hints.append(hint)
return hints

View File

@@ -22,45 +22,144 @@
import time
import collections
from PyQt5.QtCore import pyqtSignal, QUrl
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from PyQt5.QtWebKit import QWebHistoryInterface
from qutebrowser.utils import utils, objreg, standarddir, log
from qutebrowser.commands import cmdutils
from qutebrowser.utils import utils, objreg, standarddir, log, qtutils
from qutebrowser.config import config
from qutebrowser.misc import lineparser
class HistoryEntry:
class Entry:
"""A single entry in the web history.
Attributes:
atime: The time the page was accessed.
url: The URL which was accessed as QUrl.
url_string: The URL which was accessed as string.
redirect: If True, don't save this entry to disk
"""
def __init__(self, atime, url):
def __init__(self, atime, url, title, redirect=False):
self.atime = float(atime)
self.url = QUrl(url)
self.url_string = url
self.url = url
self.title = title
self.redirect = redirect
qtutils.ensure_valid(url)
def __repr__(self):
return utils.get_repr(self, constructor=True, atime=self.atime,
url=self.url.toDisplayString())
url=self.url_str(), title=self.title,
redirect=self.redirect)
def __str__(self):
return '{} {}'.format(int(self.atime), self.url_string)
atime = str(int(self.atime))
if self.redirect:
atime += '-r' # redirect flag
elems = [atime, self.url_str()]
if self.title:
elems.append(self.title)
return ' '.join(elems)
def __eq__(self, other):
return (self.atime == other.atime and
self.title == other.title and
self.url == other.url and
self.redirect == other.redirect)
def url_str(self):
"""Get the URL as a lossless string."""
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
@classmethod
def from_str(cls, line):
"""Parse a history line like '12345 http://example.com title'."""
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
title = ""
elif len(data) == 3:
atime, url, title = data
else:
raise ValueError("2 or 3 fields expected")
url = QUrl(url)
if not url.isValid():
raise ValueError("Invalid URL: {}".format(url.errorString()))
if atime.startswith('\0'):
log.init.debug(
"Removing NUL bytes from entry {!r} - see "
"https://github.com/The-Compiler/qutebrowser/issues/"
"670".format(data))
atime = atime.lstrip('\0')
if '-' in atime:
atime, flags = atime.split('-')
else:
flags = ''
if not set(flags).issubset('r'):
raise ValueError("Invalid flags {!r}".format(flags))
redirect = 'r' in flags
return cls(atime, url, title, redirect=redirect)
class WebHistory(QWebHistoryInterface):
class WebHistoryInterface(QWebHistoryInterface):
"""A QWebHistoryInterface which supports being written to disk.
"""Glue code between WebHistory and Qt's QWebHistoryInterface.
Attributes:
_history: The WebHistory object.
"""
def __init__(self, webhistory, parent=None):
super().__init__(parent)
self._history = webhistory
def addHistoryEntry(self, url_string):
"""Required for a QWebHistoryInterface impl, obsoleted by add_url."""
pass
def historyContains(self, url_string):
"""Called by WebKit to determine if an URL is contained in the history.
Args:
url_string: The URL (as string) to check for.
Return:
True if the url is in the history, False otherwise.
"""
return url_string in self._history.history_dict
class WebHistory(QObject):
"""The global history of visited pages.
This is a little more complex as you'd expect so the history can be read
from disk async while new history is already arriving.
self.history_dict is the main place where the history is stored, in an
OrderedDict (sorted by time) of URL strings mapped to Entry objects.
While reading from disk is still ongoing, the history is saved in
self._temp_history instead, and then appended to self.history_dict once
that's fully populated.
All history which is new in this session (rather than read from disk from a
previous browsing session) is also stored in self._new_history.
self._saved_count tracks how many of those entries were already written to
disk, so we can always append to the existing data.
Attributes:
history_dict: An OrderedDict of URLs read from the on-disk history.
_hist_dir: The directory to store the history in
_lineparser: The AppendLineParser used to save the history.
_history_dict: An OrderedDict of URLs read from the on-disk history.
_new_history: A list of HistoryEntry items of the current session.
_new_history: A list of Entry items of the current session.
_saved_count: How many HistoryEntries have been written to disk.
_initial_read_started: Whether async_read was called.
_initial_read_done: Whether async_read has completed.
@@ -68,23 +167,28 @@ class WebHistory(QWebHistoryInterface):
async_read was called.
Signals:
add_completion_item: Emitted before a new HistoryEntry is added.
arg: The new HistoryEntry.
item_added: Emitted after a new HistoryEntry is added.
arg: The new HistoryEntry.
add_completion_item: Emitted before a new Entry is added.
Used to sync with the completion.
arg: The new Entry.
item_added: Emitted after a new Entry is added.
Used to tell the savemanager that the history is dirty.
arg: The new Entry.
cleared: Emitted after the history is cleared.
"""
add_completion_item = pyqtSignal(HistoryEntry)
item_added = pyqtSignal(HistoryEntry)
add_completion_item = pyqtSignal(Entry)
item_added = pyqtSignal(Entry)
cleared = pyqtSignal()
async_read_done = pyqtSignal()
def __init__(self, parent=None):
def __init__(self, hist_dir, hist_name, parent=None):
super().__init__(parent)
self._initial_read_started = False
self._initial_read_done = False
self._lineparser = lineparser.AppendLineParser(
standarddir.data(), 'history', parent=self)
self._history_dict = collections.OrderedDict()
self._hist_dir = hist_dir
self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name,
parent=self)
self.history_dict = collections.OrderedDict()
self._temp_history = collections.OrderedDict()
self._new_history = []
self._saved_count = 0
@@ -94,14 +198,11 @@ class WebHistory(QWebHistoryInterface):
def __repr__(self):
return utils.get_repr(self, length=len(self))
def __getitem__(self, key):
return self._new_history[key]
def __iter__(self):
return iter(self._history_dict.values())
return iter(self.history_dict.values())
def __len__(self):
return len(self._history_dict)
return len(self.history_dict)
def async_read(self):
"""Read the initial history."""
@@ -110,52 +211,51 @@ class WebHistory(QWebHistoryInterface):
return
self._initial_read_started = True
if standarddir.data() is None:
if self._hist_dir is None:
self._initial_read_done = True
self.async_read_done.emit()
assert not self._temp_history
return
with self._lineparser.open():
for line in self._lineparser:
yield
data = line.rstrip().split(maxsplit=1)
if not data:
# empty line
line = line.rstrip()
if not line:
continue
elif len(data) != 2:
# other malformed line
log.init.warning("Invalid history entry {!r}!".format(
line))
try:
entry = Entry.from_str(line)
except ValueError as e:
log.init.warning("Invalid history entry {!r}: {}!".format(
line, e))
continue
atime, url = data
if atime.startswith('\0'):
log.init.warning(
"Removing NUL bytes from entry {!r} - see "
"https://github.com/The-Compiler/qutebrowser/issues/"
"670".format(data))
atime = atime.lstrip('\0')
# This de-duplicates history entries; only the latest
# entry for each URL is kept. If you want to keep
# information about previous hits change the items in
# old_urls to be lists or change HistoryEntry to have a
# old_urls to be lists or change Entry to have a
# list of atimes.
entry = HistoryEntry(atime, url)
self._add_entry(entry)
self._initial_read_done = True
self.async_read_done.emit()
for url, entry in self._temp_history.items():
self._new_history.append(entry)
for entry in self._temp_history.values():
self._add_entry(entry)
self.add_completion_item.emit(entry)
self._new_history.append(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
self._temp_history.clear()
def _add_entry(self, entry, target=None):
"""Add an entry to self._history_dict or another given OrderedDict."""
"""Add an entry to self.history_dict or another given OrderedDict."""
if target is None:
target = self._history_dict
target[entry.url_string] = entry
target.move_to_end(entry.url_string)
target = self.history_dict
url_str = entry.url_str()
target[url_str] = entry
target.move_to_end(url_str)
def get_recent(self):
"""Get the most recent history entries."""
@@ -169,36 +269,44 @@ class WebHistory(QWebHistoryInterface):
self._lineparser.save()
self._saved_count = len(self._new_history)
def addHistoryEntry(self, url_string):
@cmdutils.register(name='history-clear', instance='web-history')
def clear(self):
"""Clear all browsing history.
Note this only clears the global history
(e.g. `~/.local/share/qutebrowser/history` on Linux) but not cookies,
the back/forward history of a tab, cache or other persistent data.
"""
self._lineparser.clear()
self.history_dict.clear()
self._temp_history.clear()
self._new_history.clear()
self._saved_count = 0
self.cleared.emit()
def add_url(self, url, title="", *, redirect=False, atime=None):
"""Called by WebKit when an URL should be added to the history.
Args:
url_string: An url as string to add to the history.
url: An url (as QUrl) to add to the history.
redirect: Whether the entry was redirected to another URL
(hidden in completion)
atime: Override the atime used to add the entry
"""
if not url_string:
return
if config.get('general', 'private-browsing'):
return
entry = HistoryEntry(time.time(), url_string)
if atime is None:
atime = time.time()
entry = Entry(atime, url, title, redirect=redirect)
if self._initial_read_done:
self.add_completion_item.emit(entry)
self._new_history.append(entry)
self._add_entry(entry)
self._new_history.append(entry)
self.item_added.emit(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
else:
self._add_entry(entry, target=self._temp_history)
def historyContains(self, url_string):
"""Called by WebKit to determine if an URL is contained in the history.
Args:
url_string: The URL (as string) to check for.
Return:
True if the url is in the history, False otherwise.
"""
return url_string in self._history_dict
def init(parent=None):
"""Initialize the web history.
@@ -206,6 +314,9 @@ def init(parent=None):
Args:
parent: The parent to use for WebHistory.
"""
history = WebHistory(parent)
history = WebHistory(hist_dir=standarddir.data(), hist_name='history',
parent=parent)
objreg.register('web-history', history)
QWebHistoryInterface.setDefaultInterface(history)
interface = WebHistoryInterface(history, parent=history)
QWebHistoryInterface.setDefaultInterface(interface)

View File

@@ -343,12 +343,9 @@ class _Downloader:
item = download_manager.get(url, fileobj=_NoCloseBytesIO(),
auto_remove=True)
self.pending_downloads.add((url, item))
item.finished.connect(
functools.partial(self._finished, url, item))
item.error.connect(
functools.partial(self._error, url, item))
item.cancelled.connect(
functools.partial(self._error, url, item))
item.finished.connect(functools.partial(self._finished, url, item))
item.error.connect(functools.partial(self._error, url, item))
item.cancelled.connect(functools.partial(self._cancelled, url, item))
def _finished(self, url, item):
"""Callback when a single asset is downloaded.
@@ -404,7 +401,7 @@ class _Downloader:
"""Callback when a download error occurred.
Args:
url: The orignal url of the asset as QUrl.
url: The original url of the asset as QUrl.
item: The DownloadItem given by the DownloadManager.
"""
try:
@@ -421,6 +418,20 @@ class _Downloader:
return
self._finish_file()
def _cancelled(self, url, item):
"""Callback when a download is cancelled by the user.
Args:
url: The original url of the asset as QUrl.
item: The DownloadItem given by the DownloadManager.
"""
# This callback is called before _finished, so there's no need to
# remove the item or close the fileobject.
log.downloads.debug("MHTML download cancelled by user: {}".format(url))
# Write an empty file instead
item.fileobj.seek(0)
item.fileobj.truncate()
def _finish_file(self):
"""Save the file to the filename given in __init__."""
if self._finished_file:

View File

@@ -31,7 +31,6 @@ from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError,
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
urlutils, debug)
from qutebrowser.browser import cookies
from qutebrowser.browser.network import qutescheme, networkreply
from qutebrowser.browser.network import filescheme
@@ -41,14 +40,73 @@ ProxyId = collections.namedtuple('ProxyId', 'type, hostname, port')
_proxy_auth_cache = {}
def _is_secure_cipher(cipher):
"""Check if a given SSL cipher (hopefully) isn't broken yet."""
tokens = [e.upper() for e in cipher.name().split('-')]
if cipher.usedBits() < 128:
# https://codereview.qt-project.org/#/c/75943/
return False
# OpenSSL should already protect against this in a better way
elif cipher.keyExchangeMethod() == 'DH' and os.name == 'nt':
# https://weakdh.org/
return False
elif cipher.encryptionMethod().upper().startswith('RC4'):
# http://en.wikipedia.org/wiki/RC4#Security
# https://codereview.qt-project.org/#/c/148906/
return False
elif cipher.encryptionMethod().upper().startswith('DES'):
# http://en.wikipedia.org/wiki/Data_Encryption_Standard#Security_and_cryptanalysis
return False
elif 'MD5' in tokens:
# http://www.win.tue.nl/hashclash/rogue-ca/
return False
# OpenSSL should already protect against this in a better way
# elif (('CBC3' in tokens or 'CBC' in tokens) and (cipher.protocol() not in
# [QSsl.TlsV1_0, QSsl.TlsV1_1, QSsl.TlsV1_2])):
# # http://en.wikipedia.org/wiki/POODLE
# return False
### These things should never happen as those are already filtered out by
### either the SSL libraries or Qt - but let's be sure.
elif cipher.authenticationMethod() in ['aNULL', 'NULL']:
# Ciphers without authentication.
return False
elif cipher.encryptionMethod() in ['eNULL', 'NULL']:
# Ciphers without encryption.
return False
elif 'EXP' in tokens or 'EXPORT' in tokens:
# Weak export-grade ciphers
return False
elif 'ADH' in tokens:
# No MITM protection
return False
### This *should* happen ;)
else:
return True
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
if not qtutils.version_check('5.3.0'):
# Disable weak SSL ciphers.
# See https://codereview.qt-project.org/#/c/75943/
good_ciphers = [c for c in QSslSocket.supportedCiphers()
if c.usedBits() >= 128]
QSslSocket.setDefaultCiphers(good_ciphers)
if qtutils.version_check('5.3.0'):
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
else:
# https://codereview.qt-project.org/#/c/75943/
default_ciphers = QSslSocket.supportedCiphers()
log.init.debug("Supported Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
bad_ciphers = []
for cipher in default_ciphers:
if _is_secure_cipher(cipher):
good_ciphers.append(cipher)
else:
bad_ciphers.append(cipher)
log.init.debug("Disabling bad ciphers: {}".format(
', '.join(c.name() for c in bad_ciphers)))
QSslSocket.setDefaultCiphers(good_ciphers)
class SslError(QSslError):
@@ -125,15 +183,15 @@ class NetworkManager(QNetworkAccessManager):
private: Whether we're currently in private browsing mode.
"""
if private:
cookie_jar = cookies.RAMCookieJar(self)
self.setCookieJar(cookie_jar)
cookie_jar = objreg.get('ram-cookie-jar')
else:
# We have a shared cookie jar - we restore its parent so we don't
# take ownership of it.
app = QCoreApplication.instance()
cookie_jar = objreg.get('cookie-jar')
self.setCookieJar(cookie_jar)
cookie_jar.setParent(app)
# We have a shared cookie jar - we restore its parent so we don't
# take ownership of it.
self.setCookieJar(cookie_jar)
app = QCoreApplication.instance()
cookie_jar.setParent(app)
def _set_cache(self):
"""Set the cache of the NetworkManager correctly.
@@ -166,9 +224,13 @@ class NetworkManager(QNetworkAccessManager):
self.shutting_down.connect(q.abort)
if owner is not None:
owner.destroyed.connect(q.abort)
webview = objreg.get('webview', scope='tab', window=self._win_id,
tab=self._tab_id)
webview.loadStarted.connect(q.abort)
# This might be a generic network manager, e.g. one belonging to a
# DownloadManager. In this case, just skip the webview thing.
if self._tab_id is not None:
webview = objreg.get('webview', scope='tab', window=self._win_id,
tab=self._tab_id)
webview.loadStarted.connect(q.abort)
bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
bridge.ask(q, blocking=True)
@@ -210,6 +272,9 @@ class NetworkManager(QNetworkAccessManager):
is_rejected = set(errors).issubset(
self._rejected_ssl_errors[host_tpl])
log.webview.debug("Already accepted: {} / "
"rejected {}".format(is_accepted, is_rejected))
if (ssl_strict and ssl_strict != 'ask') or is_rejected:
return
elif is_accepted:
@@ -220,6 +285,7 @@ class NetworkManager(QNetworkAccessManager):
err_string = '\n'.join('- ' + err.errorString() for err in errors)
answer = self._ask('SSL errors - continue?\n{}'.format(err_string),
mode=usertypes.PromptMode.yesno, owner=reply)
log.webview.debug("Asked for SSL errors, answer {}".format(answer))
if answer:
reply.ignoreSslErrors()
err_dict = self._accepted_ssl_errors
@@ -228,6 +294,7 @@ class NetworkManager(QNetworkAccessManager):
if host_tpl is not None:
err_dict[host_tpl] += errors
else:
log.webview.debug("ssl-strict is False, only warning about errors")
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/The-Compiler/qutebrowser/issues/114
@@ -253,7 +320,7 @@ class NetworkManager(QNetworkAccessManager):
except KeyError:
pass
@pyqtSlot('QNetworkReply', 'QAuthenticator')
@pyqtSlot('QNetworkReply*', 'QAuthenticator*')
def on_authentication_required(self, reply, authenticator):
"""Called when a website needs authentication."""
user, password = None, None
@@ -276,17 +343,16 @@ class NetworkManager(QNetworkAccessManager):
if user is None:
# netrc check failed
answer = self._ask(
"Username ({}):".format(authenticator.realm()),
mode=usertypes.PromptMode.user_pwd,
owner=reply)
answer = self._ask("Username ({}):".format(authenticator.realm()),
mode=usertypes.PromptMode.user_pwd,
owner=reply)
if answer is not None:
user, password = answer.user, answer.password
if user is not None:
authenticator.setUser(user)
authenticator.setPassword(password)
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
@pyqtSlot('QNetworkProxy', 'QAuthenticator*')
def on_proxy_authentication_required(self, proxy, authenticator):
"""Called when a proxy needs authentication."""
proxy_id = ProxyId(proxy.type(), proxy.hostName(), proxy.port())
@@ -295,8 +361,9 @@ class NetworkManager(QNetworkAccessManager):
authenticator.setUser(user)
authenticator.setPassword(password)
else:
answer = self._ask("Proxy username ({}):".format(
authenticator.realm()), mode=usertypes.PromptMode.user_pwd)
answer = self._ask(
"Proxy username ({}):".format(authenticator.realm()),
mode=usertypes.PromptMode.user_pwd)
if answer is not None:
authenticator.setUser(answer.user)
authenticator.setPassword(answer.password)
@@ -345,7 +412,7 @@ class NetworkManager(QNetworkAccessManager):
# instead of no header at all
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
elif (referer_header_conf == 'same-domain' and
not urlutils.same_domain(req.url(), current_url)):
not urlutils.same_domain(req.url(), current_url)):
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
# If refer_header_conf is set to 'always', we leave the header
# alone as QtWebKit did set it.
@@ -398,6 +465,13 @@ class NetworkManager(QNetworkAccessManager):
req.setRawHeader('DNT'.encode('ascii'), dnt)
req.setRawHeader('X-Do-Not-Track'.encode('ascii'), dnt)
# Load custom headers
custom_headers = config.get('network', 'custom-headers')
if custom_headers is not None:
for header, value in custom_headers.items():
req.setRawHeader(header.encode('ascii'), value.encode('ascii'))
# There are some scenarios where we can't figure out current_url:
# - There's a generic NetworkManager, e.g. for downloads
# - The download was in a tab which is now closed.

View File

@@ -26,6 +26,7 @@ Module attributes:
import functools
import configparser
import mimetypes
import urllib.parse
from PyQt5.QtCore import pyqtSlot, QObject
from PyQt5.QtNetwork import QNetworkReply
@@ -109,13 +110,13 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
request, str(e), QNetworkReply.ContentNotFoundError,
self.parent())
except QuteSchemeError as e:
return networkreply.ErrorNetworkReply(
request, e.errorstring, e.error, self.parent())
return networkreply.ErrorNetworkReply(request, e.errorstring,
e.error, self.parent())
mimetype, _encoding = mimetypes.guess_type(request.url().fileName())
if mimetype is None:
mimetype = 'text/html'
return networkreply.FixedDataNetworkReply(
request, data, mimetype, self.parent())
return networkreply.FixedDataNetworkReply(request, data, mimetype,
self.parent())
class JSBridge(QObject):
@@ -158,23 +159,42 @@ def qute_version(_win_id, _request):
@add_handler('plainlog')
def qute_plainlog(_win_id, _request):
"""Handler for qute:plainlog. Return HTML content as bytes."""
def qute_plainlog(_win_id, request):
"""Handler for qute:plainlog. Return HTML content as bytes.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
"""
if log.ram_handler is None:
text = "Log output was disabled."
else:
text = log.ram_handler.dump_log()
try:
level = urllib.parse.parse_qs(request.url().query())['level'][0]
except KeyError:
level = 'vdebug'
text = log.ram_handler.dump_log(html=False, level=level)
html = jinja.render('pre.html', title='log', content=text)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@add_handler('log')
def qute_log(_win_id, _request):
"""Handler for qute:log. Return HTML content as bytes."""
def qute_log(_win_id, request):
"""Handler for qute:log. Return HTML content as bytes.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
"""
if log.ram_handler is None:
html_log = None
else:
html_log = log.ram_handler.dump_log(html=True)
try:
level = urllib.parse.parse_qs(request.url().query())['level'][0]
except KeyError:
level = 'vdebug'
html_log = log.ram_handler.dump_log(html=True, level=level)
html = jinja.render('log.html', title='log', content=html_log)
return html.encode('UTF-8', errors='xmlcharrefreplace')
@@ -240,4 +260,4 @@ def qute_pdfjs(_win_id, request):
log.misc.warning(
"pdfjs resource requested but not found: {}".format(e.path))
raise QuteSchemeError("Can't find pdfjs resource '{}'".format(e.path),
QNetworkReply.ContentNotFoundError)
QNetworkReply.ContentNotFoundError)

View File

@@ -51,9 +51,8 @@ def generate_pdfjs_page(url):
"""
viewer = get_pdfjs_res('web/viewer.html').decode('utf-8')
script = _generate_pdfjs_script(url)
html_page = viewer.replace(
'</body>', '</body><script>{}</script>'.format(script)
)
html_page = viewer.replace('</body>',
'</body><script>{}</script>'.format(script))
return html_page

View File

@@ -154,8 +154,7 @@ class QuickmarkManager(UrlMarkManager):
try:
key, url = line.rsplit(maxsplit=1)
except ValueError:
message.error('current', "Invalid quickmark '{}'".format(
line))
message.error('current', "Invalid quickmark '{}'".format(line))
else:
self.marks[key] = url
@@ -174,7 +173,8 @@ class QuickmarkManager(UrlMarkManager):
win_id, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, win_id, urlstr))
@cmdutils.register(instance='quickmark-manager', win_id='win_id')
@cmdutils.register(instance='quickmark-manager')
@cmdutils.argument('win_id', win_id=True)
def quickmark_add(self, win_id, url, name):
"""Add a new quickmark.
@@ -204,8 +204,9 @@ class QuickmarkManager(UrlMarkManager):
else:
set_mark()
@cmdutils.register(instance='quickmark-manager', maxsplit=0,
completion=[usertypes.Completion.quickmark_by_name])
@cmdutils.register(instance='quickmark-manager', maxsplit=0)
@cmdutils.argument('name',
completion=usertypes.Completion.quickmark_by_name)
def quickmark_del(self, name):
"""Delete a quickmark.
@@ -284,8 +285,8 @@ class BookmarkManager(UrlMarkManager):
self.changed.emit()
self.added.emit(title, urlstr)
@cmdutils.register(instance='bookmark-manager', maxsplit=0,
completion=[usertypes.Completion.bookmark_by_url])
@cmdutils.register(instance='bookmark-manager', maxsplit=0)
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
def bookmark_del(self, url):
"""Delete a bookmark.

View File

@@ -38,7 +38,7 @@ from qutebrowser.utils import log, usertypes, utils
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext',
'focus'])
'focus', 'inputs'])
SELECTORS = {
@@ -50,6 +50,9 @@ SELECTORS = {
Group.url: '[src], [href]',
Group.prevnext: 'a, area, button, link, [role=button]',
Group.focus: '*:focus',
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], textarea'),
}
@@ -157,8 +160,7 @@ class WebElementWrapper(collections.abc.MutableMapping):
def _check_vanished(self):
"""Raise an exception if the element vanished (is null)."""
if self._elem.isNull():
raise IsNullError('Element {} vanished!'.format(
self._elem))
raise IsNullError('Element {} vanished!'.format(self._elem))
def is_visible(self, mainframe):
"""Check whether the element is currently visible on the screen.
@@ -171,9 +173,9 @@ class WebElementWrapper(collections.abc.MutableMapping):
"""
return is_visible(self._elem, mainframe)
def rect_on_view(self):
def rect_on_view(self, **kwargs):
"""Get the geometry of the element relative to the webview."""
return rect_on_view(self._elem)
return rect_on_view(self._elem, **kwargs)
def is_writable(self):
"""Check whether an element is writable."""
@@ -254,7 +256,6 @@ class WebElementWrapper(collections.abc.MutableMapping):
Return:
True if we should switch to insert mode, False otherwise.
"""
# pylint: disable=too-many-return-statements
self._check_vanished()
roles = ('combobox', 'textbox')
log.misc.debug("Checking if element is editable: {}".format(
@@ -362,29 +363,80 @@ def focus_elem(frame):
return WebElementWrapper(elem)
def rect_on_view(elem, elem_geometry=None):
def rect_on_view(elem, *, elem_geometry=None, adjust_zoom=True, no_js=False):
"""Get the geometry of the element relative to the webview.
We need this as a standalone function (as opposed to a WebElementWrapper
method) because we want to run is_visible before wrapping when hinting for
performance reasons.
Uses the getClientRects() JavaScript method to obtain the collection of
rectangles containing the element and returns the first rectangle which is
large enough (larger than 1px times 1px). If all rectangles returned by
getClientRects() are too small, falls back to elem.rect_on_view().
Skipping of small rectangles is due to <a> elements containing other
elements with "display:block" style, see
https://github.com/The-Compiler/qutebrowser/issues/1298
Args:
elem: The QWebElement to get the rect for.
elem_geometry: The geometry of the element, or None.
Calling QWebElement::geometry is rather expensive so we
want to avoid doing it twice.
adjust_zoom: Whether to adjust the element position based on the
current zoom level.
no_js: Fall back to the Python implementation
"""
if elem.isNull():
raise IsNullError("Got called on a null element!")
# First try getting the element rect via JS, as that's usually more
# accurate
if elem_geometry is None and not no_js:
rects = elem.evaluateJavaScript("this.getClientRects()")
text = utils.compact_text(elem.toOuterXml(), 500)
log.hints.vdebug("Client rectangles of element '{}': {}".format(text,
rects))
for i in range(int(rects.get("length", 0))):
rect = rects[str(i)]
width = rect.get("width", 0)
height = rect.get("height", 0)
if width > 1 and height > 1:
# fix coordinates according to zoom level
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only') and adjust_zoom:
rect["left"] *= zoom
rect["top"] *= zoom
width *= zoom
height *= zoom
rect = QRect(rect["left"], rect["top"], width, height)
frame = elem.webFrame()
while frame is not None:
# Translate to parent frames' position
# (scroll position is taken care of inside getClientRects)
rect.translate(frame.geometry().topLeft())
frame = frame.parentFrame()
return rect
# No suitable rects found via JS, try via the QWebElement API
if elem_geometry is None:
elem_geometry = elem.geometry()
geometry = elem.geometry()
else:
geometry = elem_geometry
frame = elem.webFrame()
rect = QRect(elem_geometry)
rect = QRect(geometry)
while frame is not None:
rect.translate(frame.geometry().topLeft())
rect.translate(frame.scrollPosition() * -1)
frame = frame.parentFrame()
# We deliberately always adjust the zoom here, even with adjust_zoom=False
if elem_geometry is None:
zoom = elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only'):
rect.moveTo(rect.left() / zoom, rect.top() / zoom)
rect.setWidth(rect.width() / zoom)
rect.setHeight(rect.height() / zoom)
return rect
@@ -421,8 +473,7 @@ def is_visible(elem, mainframe):
else:
# We got an invalid rectangle (width/height 0/0 probably), but this
# can still be a valid link.
visible_on_screen = mainframe_geometry.contains(
elem_rect.topLeft())
visible_on_screen = mainframe_geometry.contains(elem_rect.topLeft())
# Then check if it's visible in its frame if it's not in the main
# frame.
elem_frame = elem.webFrame()

View File

@@ -33,7 +33,7 @@ from qutebrowser.config import config
from qutebrowser.browser import http, tabhistory, pdfjs
from qutebrowser.browser.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg, debug)
objreg, debug, urlutils)
class BrowserPage(QWebPage):
@@ -288,7 +288,7 @@ class BrowserPage(QWebPage):
window=self._win_id)
download_manager.get_request(req, page=self)
@pyqtSlot('QNetworkReply')
@pyqtSlot('QNetworkReply*')
def on_unsupported_content(self, reply):
"""Handle an unsupportedContent signal.
@@ -334,7 +334,7 @@ class BrowserPage(QWebPage):
else:
self.error_occurred = False
@pyqtSlot('QWebFrame', 'QWebPage::Feature')
@pyqtSlot('QWebFrame*', 'QWebPage::Feature')
def on_feature_permission_requested(self, frame, feature):
"""Ask the user for approval for geolocation/notifications."""
options = {
@@ -439,7 +439,7 @@ class BrowserPage(QWebPage):
if 'scroll-pos' in data and frame.scrollPosition() == QPoint(0, 0):
frame.setScrollPosition(data['scroll-pos'])
@pyqtSlot(str)
@pyqtSlot(usertypes.ClickTarget)
def on_start_hinting(self, hint_target):
"""Emitted before a hinting-click takes place.
@@ -570,9 +570,8 @@ class BrowserPage(QWebPage):
if typ != QWebPage.NavigationTypeLinkClicked:
return True
if not url.isValid():
message.error(self._win_id, "Invalid link {} clicked!".format(
urlstr))
log.webview.debug(url.errorString())
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(self._win_id, msg)
self.open_target = usertypes.ClickTarget.normal
return False
tabbed_browser = objreg.get('tabbed-browser', scope='window',

View File

@@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QStyleFactory
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
@@ -145,6 +145,19 @@ class WebView(QWebView):
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
objreg.get('config').changed.connect(self.on_config_changed)
@pyqtSlot()
def on_initial_layout_completed(self):
"""Add url to history now that we have displayed something."""
history = objreg.get('web-history')
no_formatting = QUrl.UrlFormattingOption(0)
orig_url = self.page().mainFrame().requestedUrl()
if (orig_url.isValid() and
not orig_url.matches(self.cur_url, no_formatting)):
# If the url of the page is different than the url of the link
# originally clicked, save them both.
history.add_url(orig_url, self.title(), redirect=True)
history.add_url(self.cur_url, self.title())
def _init_page(self):
"""Initialize the QWebPage used by this view."""
page = webpage.BrowserPage(self.win_id, self.tab_id, self)
@@ -152,6 +165,8 @@ class WebView(QWebView):
page.linkHovered.connect(self.linkHovered)
page.mainFrame().loadStarted.connect(self.on_load_started)
page.mainFrame().loadFinished.connect(self.on_load_finished)
page.mainFrame().initialLayoutCompleted.connect(
self.on_initial_layout_completed)
page.statusBarMessage.connect(
lambda msg: setattr(self, 'statusbar_message', msg))
page.networkAccessManager().sslErrors.connect(
@@ -213,7 +228,7 @@ class WebView(QWebView):
"""Initialize the _zoom neighborlist."""
levels = config.get('ui', 'zoom-levels')
self._zoom = usertypes.NeighborList(
levels, mode=usertypes.NeighborList.Modes.block)
levels, mode=usertypes.NeighborList.Modes.edge)
self._zoom.fuzzyval = config.get('ui', 'default-zoom')
def _mousepress_backforward(self, e):
@@ -352,9 +367,15 @@ class WebView(QWebView):
frame = self.page().mainFrame()
frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)
@pyqtSlot()
def add_js_bridge(self):
"""Add the javascript bridge for qute:... pages."""
frame = self.sender()
if not isinstance(frame, QWebFrame):
log.webview.error("Got non-QWebFrame {!r} in "
"add_js_bridge!".format(frame))
return
if frame.url().scheme() == 'qute':
bridge = objreg.get('js-bridge')
frame.addToJavaScriptWindowObject('qute', bridge)

View File

@@ -19,7 +19,6 @@
"""argparse.ArgumentParser subclass to parse qutebrowser commands."""
import argparse
from PyQt5.QtCore import QUrl
@@ -84,43 +83,81 @@ class ArgumentParser(argparse.ArgumentParser):
raise ArgumentParserError(msg.capitalize())
def enum_getter(enum):
"""Function factory to get an enum getter."""
def _get_enum_item(key):
"""Helper function to get an enum item.
def arg_name(name):
"""Get the name an argument should have based on its Python name."""
return name.rstrip('_').replace('_', '-')
Passes through existing items unmodified.
"""
if isinstance(key, enum):
return key
def _check_choices(param, value, choices):
if value not in choices:
expected_values = ', '.join(arg_name(val) for val in choices)
raise cmdexc.ArgumentTypeError("{}: Invalid value {} - expected "
"one of: {}".format(
param.name, value, expected_values))
def type_conv(param, typ, value, *, str_choices=None):
"""Convert a value based on a type.
Args:
param: The argparse.Parameter we're checking
types: The allowed type
value: The value to convert
str_choices: The allowed choices if the type ends up being a string
Return:
The converted value
"""
if isinstance(typ, str):
raise TypeError("{}: Legacy string type!".format(param.name))
if value is param.default:
return value
assert isinstance(value, str), repr(value)
if utils.is_enum(typ):
_check_choices(param, value, [arg_name(e.name) for e in typ])
return typ[value.replace('-', '_')]
elif typ is str:
if str_choices is not None:
_check_choices(param, value, str_choices)
return value
elif callable(typ):
# int, float, etc.
try:
return enum[key.replace('-', '_')]
except KeyError:
raise cmdexc.ArgumentTypeError("Invalid value {}.".format(key))
return _get_enum_item
return typ(value)
except (TypeError, ValueError):
msg = '{}: Invalid {} value {}'.format(
param.name, typ.__name__, value)
raise cmdexc.ArgumentTypeError(msg)
else:
raise ValueError("{}: Unknown type {!r}!".format(param.name, typ))
def multitype_conv(types):
"""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(types):
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
else:
raise ValueError("Unknown type {!r}!".format(typ))
raise cmdexc.ArgumentTypeError('Invalid value {}.'.format(value))
def multitype_conv(param, types, value, *, str_choices=None):
"""Convert a value based on a choice of types.
return _convert
Args:
param: The inspect.Parameter we're checking
types: The allowed types ("overloads")
value: The value to convert
str_choices: The allowed choices if the type ends up being a string
Return:
The converted value
"""
types = list(set(types))
if str in types:
# Make sure str is always the last type in the list, so e.g. '23' gets
# returned as 23 if we have typing.Union[str, int]
types.remove(str)
types.append(str)
for typ in types:
try:
return type_conv(param, typ, value, str_choices=str_choices)
except cmdexc.ArgumentTypeError:
pass
raise cmdexc.ArgumentTypeError('{}: Invalid value {}'.format(
param.name, value))

View File

@@ -24,6 +24,8 @@ Module attributes:
aliases: A list of all aliases, needed for doc generation.
"""
import inspect
from qutebrowser.utils import qtutils, log
from qutebrowser.commands import command, cmdexc
@@ -164,3 +166,35 @@ class register: # pylint: disable=invalid-name
cmd_dict[name] = cmd
aliases += names[1:]
return func
class argument: # pylint: disable=invalid-name
"""Decorator to customize an argument for @cmdutils.register.
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
Attributes:
_argname: The name of the argument to handle.
_kwargs: Keyword arguments, valid ArgInfo members
"""
def __init__(self, argname, **kwargs):
self._argname = argname
self._kwargs = kwargs
def __call__(self, func):
funcname = func.__name__
if self._argname not in inspect.signature(func).parameters:
raise ValueError("{} has no argument {}!".format(funcname,
self._argname))
if not hasattr(func, 'qute_args'):
func.qute_args = {}
elif func.qute_args is None:
raise ValueError("@cmdutils.argument got called above (after) "
"@cmdutils.register for {}!".format(funcname))
func.qute_args[self._argname] = command.ArgInfo(**self._kwargs)
return func

View File

@@ -21,17 +21,46 @@
import inspect
import collections
import traceback
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import log, utils, message, docutils, objreg, usertypes
from qutebrowser.utils import (log, utils, message, docutils, objreg,
usertypes, typing)
from qutebrowser.utils import debug as debug_utils
def arg_name(name):
"""Get the name an argument should have based on its Python name."""
return name.rstrip('_').replace('_', '-')
class ArgInfo:
"""Information about an argument."""
def __init__(self, win_id=False, count=False, flag=None, hide=False,
metavar=None, completion=None, choices=None):
if win_id and count:
raise TypeError("Argument marked as both count/win_id!")
self.win_id = win_id
self.count = count
self.flag = flag
self.hide = hide
self.metavar = metavar
self.completion = completion
self.choices = choices
def __eq__(self, other):
return (self.win_id == other.win_id and
self.count == other.count and
self.flag == other.flag and
self.hide == other.hide and
self.metavar == other.metavar and
self.completion == other.completion and
self.choices == other.choices)
def __repr__(self):
return utils.get_repr(self, win_id=self.win_id, count=self.count,
flag=self.flag, hide=self.hide,
metavar=self.metavar, completion=self.completion,
choices=self.choices, constructor=True)
class Command:
@@ -49,31 +78,21 @@ class Command:
completion: Completions to use for arguments, as a list of strings.
debug: Whether this is a debugging command (only shown with --debug).
parser: The ArgumentParser to use to parse this command.
count_arg: The name of the count parameter, or None.
win_id_arg: The name of the win_id parameter, or None.
flags_with_args: A list of flags which take an argument.
no_cmd_split: If true, ';;' to split sub-commands is ignored.
_type_conv: A mapping of conversion functions for arguments.
_qute_args: The saved data from @cmdutils.argument
_needs_js: Whether the command needs javascript enabled
_modes: The modes the command can be executed in.
_not_modes: The modes the command can not be executed in.
_count: The count set for the command.
_instance: The object to bind 'self' to.
_scope: The scope to get _instance for in the object registry.
Class attributes:
AnnotationInfo: Named tuple for info from an annotation.
"""
AnnotationInfo = collections.namedtuple('AnnotationInfo',
['kwargs', 'type', 'flag', 'hide',
'metavar'])
def __init__(self, *, handler, name, instance=None, maxsplit=None,
hide=False, completion=None, modes=None, not_modes=None,
needs_js=False, debug=False, ignore_args=False,
deprecated=False, no_cmd_split=False, scope='global',
count=None, win_id=None):
hide=False, modes=None, not_modes=None, needs_js=False,
debug=False, ignore_args=False, deprecated=False,
no_cmd_split=False, star_args_optional=False, scope='global'):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-locals
if modes is not None and not_modes is not None:
@@ -89,22 +108,21 @@ class Command:
if scope != 'global' and instance is None:
raise ValueError("Setting scope without setting instance makes "
"no sense!")
self.name = name
self.maxsplit = maxsplit
self.hide = hide
self.deprecated = deprecated
self._instance = instance
self.completion = completion
self._modes = modes
self._not_modes = not_modes
self._scope = scope
self._needs_js = needs_js
self._star_args_optional = star_args_optional
self.debug = debug
self.ignore_args = ignore_args
self.handler = handler
self.no_cmd_split = no_cmd_split
self.count_arg = count
self.win_id_arg = win_id
self.docparser = docutils.DocstringParser(handler)
self.parser = argparser.ArgumentParser(
name, description=self.docparser.short_desc,
@@ -119,11 +137,19 @@ class Command:
self.pos_args = []
self.desc = None
self.flags_with_args = []
self._type_conv = {}
count = self._inspect_func()
if self.completion is not None and len(self.completion) > count:
raise ValueError("Got {} completions, but only {} "
"arguments!".format(len(self.completion), count))
# This is checked by future @cmdutils.argument calls so they fail
# (as they'd be silently ignored otherwise)
self._qute_args = getattr(self.handler, 'qute_args', {})
self.handler.qute_args = None
args = self._inspect_func()
self.completion = []
for arg in args:
arg_completion = self.get_arg_info(arg).completion
if arg_completion is not None:
self.completion.append(arg_completion)
def _check_prerequisites(self, win_id):
"""Check if the command is permitted to run currently.
@@ -166,21 +192,9 @@ class Command:
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_arg_info(self, param):
"""Get an ArgInfo tuple for the given inspect.Parameter."""
return self._qute_args.get(param.name, ArgInfo())
def _inspect_special_param(self, param):
"""Check if the given parameter is a special one.
@@ -191,12 +205,13 @@ class Command:
Return:
True if the parameter is special, False otherwise.
"""
if param.name == self.count_arg:
arg_info = self.get_arg_info(param)
if arg_info.count:
if param.default is inspect.Parameter.empty:
raise TypeError("{}: handler has count parameter "
"without default!".format(self.name))
return True
elif param.name == self.win_id_arg:
elif arg_info.win_id:
return True
def _inspect_func(self):
@@ -210,53 +225,40 @@ class Command:
"""
signature = inspect.signature(self.handler)
doc = inspect.getdoc(self.handler)
arg_count = 0
if doc is not None:
self.desc = doc.splitlines()[0].strip()
else:
self.desc = ""
if (self.count_arg is not None and
self.count_arg not in signature.parameters):
raise ValueError("count parameter {} does not exist!".format(
self.count_arg))
if (self.win_id_arg is not None and
self.win_id_arg not in signature.parameters):
raise ValueError("win_id parameter {} does not exist!".format(
self.win_id_arg))
if not self.ignore_args:
for param in signature.parameters.values():
annotation_info = self._parse_annotation(param)
if param.name == 'self':
continue
if self._inspect_special_param(param):
continue
arg_count += 1
typ = self._get_type(param, annotation_info)
kwargs = self._param_to_argparse_kwargs(param, annotation_info)
args = self._param_to_argparse_args(param, annotation_info)
self._type_conv.update(self._get_typeconv(param, typ))
typ = self._get_type(param)
is_bool = typ is bool
kwargs = self._param_to_argparse_kwargs(param, is_bool)
args = self._param_to_argparse_args(param, is_bool)
callsig = debug_utils.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 arg_count
return signature.parameters.values()
def _param_to_argparse_kwargs(self, param, annotation_info):
def _param_to_argparse_kwargs(self, param, is_bool):
"""Get argparse keyword arguments for a parameter.
Args:
param: The inspect.Parameter object to get the args for.
annotation_info: An AnnotationInfo tuple for the parameter.
is_bool: Whether the parameter is a boolean.
Return:
A kwargs dict.
"""
kwargs = {}
typ = self._get_type(param, annotation_info)
try:
kwargs['help'] = self.docparser.arg_descs[param.name]
@@ -265,92 +267,69 @@ class Command:
kwargs['dest'] = param.name
if isinstance(typ, tuple):
kwargs['metavar'] = annotation_info.metavar or param.name
elif utils.is_enum(typ):
kwargs['choices'] = [arg_name(e.name) for e in typ]
kwargs['metavar'] = annotation_info.metavar or param.name
elif typ is bool:
arg_info = self.get_arg_info(param)
if is_bool:
kwargs['action'] = 'store_true'
elif typ is not None:
kwargs['type'] = typ
else:
if arg_info.metavar is not None:
kwargs['metavar'] = arg_info.metavar
else:
kwargs['metavar'] = argparser.arg_name(param.name)
if param.kind == inspect.Parameter.VAR_POSITIONAL:
kwargs['nargs'] = '+'
kwargs['nargs'] = '*' if self._star_args_optional else '+'
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
kwargs['default'] = param.default
elif typ is not bool and param.default is not inspect.Parameter.empty:
elif not is_bool and param.default is not inspect.Parameter.empty:
kwargs['default'] = param.default
kwargs['nargs'] = '?'
kwargs.update(annotation_info.kwargs)
return kwargs
def _param_to_argparse_args(self, param, annotation_info):
def _param_to_argparse_args(self, param, is_bool):
"""Get argparse positional arguments for a parameter.
Args:
param: The inspect.Parameter object to get the args for.
annotation_info: An AnnotationInfo tuple for the parameter.
is_bool: Whether the parameter is a boolean.
Return:
A list of args.
"""
args = []
name = arg_name(param.name)
shortname = annotation_info.flag or name[0]
name = argparser.arg_name(param.name)
arg_info = self.get_arg_info(param)
if arg_info.flag is not None:
shortname = arg_info.flag
else:
shortname = name[0]
if len(shortname) != 1:
raise ValueError("Flag '{}' of parameter {} (command {}) must be "
"exactly 1 char!".format(shortname, name,
self.name))
typ = self._get_type(param, annotation_info)
if typ is bool or param.kind == inspect.Parameter.KEYWORD_ONLY:
if is_bool or param.kind == inspect.Parameter.KEYWORD_ONLY:
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
if typ is not bool:
if not is_bool:
self.flags_with_args += [short_flag, long_flag]
else:
if not annotation_info.hide:
if not arg_info.hide:
self.pos_args.append((param.name, name))
return args
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': {}, 'type': None, 'flag': None, 'hide': False,
'metavar': None}
if param.annotation is not inspect.Parameter.empty:
log.commands.vdebug("Parsing annotation {}".format(
param.annotation))
for field in ('type', 'flag', 'name', 'hide', 'metavar'):
if field in param.annotation:
info[field] = param.annotation[field]
if 'nargs' in param.annotation:
info['kwargs'] = {'nargs': param.annotation['nargs']}
return self.AnnotationInfo(**info)
def _get_type(self, param, annotation_info):
def _get_type(self, param):
"""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.type is not None:
return annotation_info.type
if param.annotation is not inspect.Parameter.empty:
return param.annotation
elif param.default is None or param.default is inspect.Parameter.empty:
return None
else:
@@ -418,12 +397,29 @@ class Command:
def _get_param_value(self, param):
"""Get the converted value for an inspect.Parameter."""
value = getattr(self.namespace, param.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)
typ = self._get_type(param)
if isinstance(typ, tuple):
raise TypeError("{}: Legacy tuple type annotation!".format(
self.name))
elif issubclass(typ, typing.Union):
# this is... slightly evil, I know
types = list(typ.__union_params__)
if param.default is not inspect.Parameter.empty:
types.append(type(param.default))
choices = self.get_arg_info(param).choices
value = argparser.multitype_conv(param, types, value,
str_choices=choices)
elif typ is str:
choices = self.get_arg_info(param).choices
value = argparser.type_conv(param, typ, value, str_choices=choices)
elif typ is None:
pass
elif typ is bool: # no type conversion for flags
assert isinstance(value, bool)
else:
value = argparser.type_conv(param, typ, value)
return value
def _get_call_args(self, win_id):
@@ -446,15 +442,17 @@ class Command:
return args, kwargs
for i, param in enumerate(signature.parameters.values()):
arg_info = self.get_arg_info(param)
if i == 0 and self._instance is not None:
# Special case for 'self'.
self._get_self_arg(win_id, param, args)
continue
elif param.name == self.count_arg:
elif arg_info.count:
# Special case for count parameter.
self._get_count_arg(param, args, kwargs)
continue
elif param.name == self.win_id_arg:
# elif arg_info.win_id:
elif arg_info.win_id:
# Special case for win_id parameter.
self._get_win_id_arg(win_id, param, args, kwargs)
continue
@@ -491,7 +489,8 @@ class Command:
try:
self.namespace = self.parser.parse_args(args)
except argparser.ArgumentParserError as e:
message.error(win_id, '{}: {}'.format(self.name, e))
message.error(win_id, '{}: {}'.format(self.name, e),
stack=traceback.format_exc())
return
except argparser.ArgumentParserExit as e:
log.commands.debug("argparser exited with status {}: {}".format(

View File

@@ -20,6 +20,7 @@
"""Module containing command managers (SearchRunner and CommandRunner)."""
import collections
import traceback
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
@@ -33,24 +34,33 @@ ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline',
'count'])
def _current_url(tabbed_browser):
"""Convenience method to get the current url."""
try:
return tabbed_browser.current_url()
except qtutils.QtValueError as e:
msg = "Current URL is invalid"
if e.reason:
msg += " ({})".format(e.reason)
msg += "!"
raise cmdexc.CommandError(msg)
def replace_variables(win_id, arglist):
"""Utility function to replace variables like {url} in a list of args."""
args = []
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if '{url}' in arglist:
try:
url = tabbed_browser.current_url().toString(QUrl.FullyEncoded |
QUrl.RemovePassword)
except qtutils.QtValueError as e:
msg = "Current URL is invalid"
if e.reason:
msg += " ({})".format(e.reason)
msg += "!"
raise cmdexc.CommandError(msg)
url = _current_url(tabbed_browser).toString(QUrl.FullyEncoded |
QUrl.RemovePassword)
if '{url:pretty}' in arglist:
pretty_url = _current_url(tabbed_browser).toString(QUrl.RemovePassword)
for arg in arglist:
if arg == '{url}':
args.append(url)
elif arg == '{url:pretty}':
args.append(pretty_url)
else:
args.append(arg)
return args
@@ -62,10 +72,12 @@ class CommandRunner(QObject):
Attributes:
_win_id: The window this CommandRunner is associated with.
_partial_match: Whether to allow partial command matches.
"""
def __init__(self, win_id, parent=None):
def __init__(self, win_id, partial_match=False, parent=None):
super().__init__(parent)
self._partial_match = partial_match
self._win_id = win_id
def _get_alias(self, text):
@@ -138,6 +150,15 @@ class CommandRunner(QObject):
count = None
return (count, cmdstr)
def _parse_fallback(self, text, count, keep):
"""Parse the given commandline without a valid command."""
if keep:
cmdstr, sep, argstr = text.partition(' ')
cmdline = [cmdstr, sep] + argstr.split()
else:
cmdline = text.split()
return ParseResult(cmd=None, args=None, cmdline=cmdline, count=count)
def parse(self, text, *, aliases=True, fallback=False, keep=False):
"""Split the commandline text into command and arguments.
@@ -156,36 +177,52 @@ class CommandRunner(QObject):
if not cmdstr and not fallback:
raise cmdexc.NoSuchCommandError("No command given")
if aliases:
new_cmd = self._get_alias(text)
if new_cmd is not None:
log.commands.debug("Re-parsing with '{}'.".format(new_cmd))
return self.parse(new_cmd, aliases=False, fallback=fallback,
keep=keep)
if self._partial_match:
cmdstr = self._completion_match(cmdstr)
try:
cmd = cmdutils.cmd_dict[cmdstr]
except KeyError:
if fallback:
cmd = None
args = None
if keep:
cmdstr, sep, argstr = text.partition(' ')
cmdline = [cmdstr, sep] + argstr.split()
else:
cmdline = text.split()
else:
raise cmdexc.NoSuchCommandError('{}: no such command'.format(
cmdstr))
if not fallback:
raise cmdexc.NoSuchCommandError(
'{}: no such command'.format(cmdstr))
return self._parse_fallback(text, count, keep)
args = self._split_args(cmd, argstr, keep)
if keep and args:
cmdline = [cmdstr, sep + args[0]] + args[1:]
elif keep:
cmdline = [cmdstr, sep]
else:
args = self._split_args(cmd, argstr, keep)
if keep and args:
cmdline = [cmdstr, sep + args[0]] + args[1:]
elif keep:
cmdline = [cmdstr, sep]
else:
cmdline = [cmdstr] + args[:]
cmdline = [cmdstr] + args[:]
return ParseResult(cmd=cmd, args=args, cmdline=cmdline, count=count)
def _completion_match(self, cmdstr):
"""Replace cmdstr with a matching completion if there's only one match.
Args:
cmdstr: The string representing the entered command so far
Return:
cmdstr modified to the matching completion or unmodified
"""
matches = []
for valid_command in cmdutils.cmd_dict.keys():
if valid_command.find(cmdstr) == 0:
matches.append(valid_command)
if len(matches) == 1:
cmdstr = matches[0]
return cmdstr
def _split_args(self, cmd, argstr, keep):
"""Split the arguments from an arg string.
@@ -249,20 +286,23 @@ class CommandRunner(QObject):
result.cmd.run(self._win_id, args)
@pyqtSlot(str, int)
@pyqtSlot(str)
def run_safely(self, text, count=None):
"""Run a command and display exceptions in the statusbar."""
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(self._win_id, e, immediately=True)
message.error(self._win_id, e, immediately=True,
stack=traceback.format_exc())
@pyqtSlot(str, int)
def run_safely_init(self, text, count=None):
"""Run a command and display exceptions in the statusbar.
Contrary to run_safely, error messages are queued so this is more
suitable to use while initializing."""
suitable to use while initializing.
"""
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(self._win_id, e)
message.error(self._win_id, e, stack=traceback.format_exc())

View File

@@ -71,6 +71,8 @@ class _QtFIFOReader(QObject):
def cleanup(self):
"""Clean up so the FIFO can be closed."""
self._notifier.setEnabled(False)
for line in self._fifo:
self.got_line.emit(line.rstrip('\r\n'))
self._fifo.close()
@@ -82,6 +84,7 @@ class _BaseUserscriptRunner(QObject):
_filepath: The path of the file/FIFO which is being read.
_proc: The GUIProcess which is being executed.
_win_id: The window ID this runner is associated with.
_cleaned_up: Whether temporary files were cleaned up.
Signals:
got_cmd: Emitted when a new command arrived and should be executed.
@@ -93,6 +96,7 @@ class _BaseUserscriptRunner(QObject):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._cleaned_up = False
self._win_id = win_id
self._filepath = None
self._proc = None
@@ -114,10 +118,14 @@ class _BaseUserscriptRunner(QObject):
additional_env=self._env,
verbose=verbose, parent=self)
self._proc.finished.connect(self.on_proc_finished)
self._proc.error.connect(self.on_proc_error)
self._proc.start(cmd, args)
def _cleanup(self):
"""Clean up temporary files."""
if self._cleaned_up:
return
self._cleaned_up = True
tempfiles = [self._filepath]
if 'QUTE_HTML' in self._env:
tempfiles.append(self._env['QUTE_HTML'])
@@ -150,6 +158,7 @@ class _BaseUserscriptRunner(QObject):
"""
raise NotImplementedError
@pyqtSlot()
def on_proc_finished(self):
"""Called when the process has finished.
@@ -157,6 +166,14 @@ class _BaseUserscriptRunner(QObject):
"""
raise NotImplementedError
@pyqtSlot()
def on_proc_error(self):
"""Called when the process encountered an error.
Needs to be overridden by subclasses.
"""
raise NotImplementedError
class _POSIXUserscriptRunner(_BaseUserscriptRunner):
@@ -184,8 +201,8 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
# pylint: disable=no-member,useless-suppression
os.mkfifo(self._filepath)
except OSError as e:
message.error(self._win_id, "Error while creating FIFO: {}".format(
e))
message.error(self._win_id,
"Error while creating FIFO: {}".format(e))
return
self._reader = _QtFIFOReader(self._filepath)
@@ -193,12 +210,18 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
self._run_process(cmd, *args, env=env, verbose=verbose)
@pyqtSlot()
def on_proc_finished(self):
"""Interrupt the reader when the process finished."""
self.finish()
self._cleanup()
def finish(self):
"""Quit the thread and clean up when the reader finished."""
@pyqtSlot()
def on_proc_error(self):
self._cleanup()
def _cleanup(self):
"""Clean up reader and temporary files."""
if self._cleaned_up:
return
log.procs.debug("Cleaning up")
self._reader.cleanup()
self._reader.deleteLater()
@@ -229,24 +252,33 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
def _cleanup(self):
"""Clean up temporary files after the userscript finished."""
try:
os.close(self._oshandle)
except OSError:
log.procs.exception("Failed to close file handle!")
super()._cleanup()
self._oshandle = None
if self._cleaned_up:
return
def on_proc_finished(self):
"""Read back the commands when the process finished."""
try:
with open(self._filepath, 'r', encoding='utf-8') as f:
for line in f:
self.got_cmd.emit(line.rstrip())
except OSError:
log.procs.exception("Failed to read command file!")
self._cleanup()
try:
os.close(self._oshandle)
except OSError:
log.procs.exception("Failed to close file handle!")
super()._cleanup()
self._oshandle = None
self.finished.emit()
@pyqtSlot()
def on_proc_error(self):
self._cleanup()
@pyqtSlot()
def on_proc_finished(self):
"""Read back the commands when the process finished."""
self._cleanup()
def run(self, cmd, *args, env=None, verbose=False):
try:
self._oshandle, self._filepath = tempfile.mkstemp(text=True)
@@ -335,11 +367,11 @@ def run(cmd, *args, win_id, env, verbose=False):
"""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
commandrunner = runners.CommandRunner(win_id, tabbed_browser)
commandrunner = runners.CommandRunner(win_id, parent=tabbed_browser)
runner = UserscriptRunner(win_id, tabbed_browser)
runner.got_cmd.connect(
lambda cmd: log.commands.debug("Got userscript command: {}".format(
cmd)))
lambda cmd:
log.commands.debug("Got userscript command: {}".format(cmd)))
runner.got_cmd.connect(commandrunner.run_safely)
user_agent = config.get('network', 'user-agent')
if user_agent is not None:

View File

@@ -204,6 +204,8 @@ class Completer(QObject):
parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part)
log.completion.debug("After filtering flags: parts {}, cursor_part "
"{}".format(parts, cursor_part))
if not parts:
return None
if cursor_part == 0:
# '|' or 'set|'
model = instances.get(usertypes.Completion.command)

View File

@@ -183,6 +183,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
text_option.setAlignment(QStyle.visualAlignment(
self._opt.direction, self._opt.displayAlignment))
if self._doc is not None:
self._doc.deleteLater()
self._doc = QTextDocument(self)
self._doc.setDefaultFont(self._opt.font)
self._doc.setDefaultTextOption(text_option)

View File

@@ -201,8 +201,7 @@ class CompletionView(QTreeView):
idx = self._next_idx(prev)
qtutils.ensure_valid(idx)
self.selectionModel().setCurrentIndex(
idx, QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Rows)
idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
def set_model(self, model):
"""Switch completion to a new model.

View File

@@ -29,7 +29,8 @@ import functools
from PyQt5.QtCore import pyqtSlot
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.completion.models import (miscmodels, urlmodel, configmodel,
base)
from qutebrowser.utils import objreg, usertypes, log, debug
from qutebrowser.config import configdata
@@ -119,6 +120,13 @@ def init_session_completion():
_instances[usertypes.Completion.sessions] = model
def _init_empty_completion():
"""Initialize empty completion model."""
log.completion.debug("Initializing empty completion.")
if usertypes.Completion.empty not in _instances:
_instances[usertypes.Completion.empty] = base.BaseCompletionModel()
INITIALIZERS = {
usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion,
@@ -130,6 +138,7 @@ INITIALIZERS = {
usertypes.Completion.quickmark_by_name: init_quickmark_completions,
usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion,
usertypes.Completion.empty: _init_empty_completion,
}
@@ -177,3 +186,7 @@ def init():
history = objreg.get('web-history')
history.async_read_done.connect(
functools.partial(update, [usertypes.Completion.url]))
keyconf = objreg.get('key-config')
keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.command]))

View File

@@ -19,11 +19,12 @@
"""Misc. CompletionModels."""
from collections import defaultdict
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from qutebrowser.browser import webview
from qutebrowser.config import config, configdata
from qutebrowser.utils import objreg, log
from qutebrowser.utils import objreg, log, qtutils, utils
from qutebrowser.commands import cmdutils
from qutebrowser.completion.models import base
@@ -35,6 +36,8 @@ class CommandCompletionModel(base.BaseCompletionModel):
# https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 60, 20)
def __init__(self, parent=None):
super().__init__(parent)
assert cmdutils.cmd_dict
@@ -48,8 +51,18 @@ class CommandCompletionModel(base.BaseCompletionModel):
for name, cmd in config.section('aliases').items():
cmdlist.append((name, "Alias for '{}'".format(cmd)))
cat = self.new_category("Commands")
# map each command to its bound keys and show these in the misc column
keyconf = objreg.get('key-config')
cmd_to_keys = defaultdict(list)
for key, cmd in keyconf.get_bindings_for('normal').items():
# put special bindings last
if utils.is_special_key(key):
cmd_to_keys[cmd].append(key)
else:
cmd_to_keys[cmd].insert(0, key)
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
self.new_item(cat, name, desc, ', '.join(cmd_to_keys[name]))
class HelpCompletionModel(base.BaseCompletionModel):
@@ -147,12 +160,13 @@ class TabCompletionModel(base.BaseCompletionModel):
"""A model to complete on open tabs across all windows.
Used for switching the buffer command."""
Used for switching the buffer command.
"""
# https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method
#IDX_COLUMN = 0
IDX_COLUMN = 0
URL_COLUMN = 1
TEXT_COLUMN = 2
@@ -166,7 +180,7 @@ class TabCompletionModel(base.BaseCompletionModel):
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
window=win_id)
for i in range(tabbed_browser.count()):
tab = tabbed_browser.widget(i)
tab.url_text_changed.connect(self.rebuild)
@@ -175,9 +189,6 @@ class TabCompletionModel(base.BaseCompletionModel):
objreg.get("app").new_window.connect(self.on_new_window)
self.rebuild()
# slot argument should be mainwindow.MainWindow but can't import
# that at module level because of import loops.
@pyqtSlot(object)
def on_new_window(self, window):
"""Add hooks to new windows."""
window.tabbed_browser.new_tab.connect(self.on_new_tab)
@@ -203,15 +214,56 @@ class TabCompletionModel(base.BaseCompletionModel):
make sure we handled background loads too ... but iterating over a
few/few dozen/few hundred tabs doesn't take very long at all.
"""
self.removeRows(0, self.rowCount())
window_count = 0
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if not tabbed_browser.shutting_down:
window_count += 1
if window_count < self.rowCount():
self.removeRows(window_count, self.rowCount() - window_count)
for i, win_id in enumerate(objreg.window_registry):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
continue
c = self.new_category("{}".format(win_id))
for i in range(tabbed_browser.count()):
tab = tabbed_browser.widget(i)
self.new_item(c, "{}/{}".format(win_id, i+1),
tab.url().toDisplayString(),
tabbed_browser.page_title(i))
if i >= self.rowCount():
c = self.new_category("{}".format(win_id))
else:
c = self.item(i, 0)
c.setData("{}".format(win_id), Qt.DisplayRole)
if tabbed_browser.count() < c.rowCount():
c.removeRows(tabbed_browser.count(),
c.rowCount() - tabbed_browser.count())
for idx in range(tabbed_browser.count()):
tab = tabbed_browser.widget(idx)
if idx >= c.rowCount():
self.new_item(c, "{}/{}".format(win_id, idx + 1),
tab.url().toDisplayString(),
tabbed_browser.page_title(idx))
else:
c.child(idx, 0).setData("{}/{}".format(win_id, idx + 1),
Qt.DisplayRole)
c.child(idx, 1).setData(tab.url().toDisplayString(),
Qt.DisplayRole)
c.child(idx, 2).setData(tabbed_browser.page_title(idx),
Qt.DisplayRole)
def delete_cur_item(self, completion):
"""Delete the selected item.
Args:
completion: The Completion object to use.
"""
index = completion.currentIndex()
qtutils.ensure_valid(index)
category = index.parent()
qtutils.ensure_valid(category)
index = category.child(index.row(), self.IDX_COLUMN)
win_id, tab_index = index.data().split('/')
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=int(win_id))
tabbed_browser.on_tab_close_requested(int(tab_index) - 1)

View File

@@ -32,7 +32,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
"""A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command."""
Used for the `open` command.
"""
# https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method
@@ -73,9 +74,10 @@ class UrlCompletionModel(base.BaseCompletionModel):
self._max_history = config.get('completion', 'web-history-max-items')
history = utils.newest_slice(self._history, self._max_history)
for entry in history:
self._add_history_entry(entry)
self._history.add_completion_item.connect(
self.on_history_item_added)
if not entry.redirect:
self._add_history_entry(entry)
self._history.add_completion_item.connect(self.on_history_item_added)
self._history.cleared.connect(self.on_history_cleared)
objreg.get('config').changed.connect(self.reformat_timestamps)
@@ -99,7 +101,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
def _add_history_entry(self, entry):
"""Add a new history entry to the completion."""
self.new_item(self._history_cat, entry.url.toDisplayString(), "",
self.new_item(self._history_cat, entry.url.toDisplayString(),
entry.title,
self._fmt_atime(entry.atime), sort=int(entry.atime),
userdata=entry.url)
@@ -122,14 +125,20 @@ class UrlCompletionModel(base.BaseCompletionModel):
for i in range(self._history_cat.rowCount()):
url_item = self._history_cat.child(i, self.URL_COLUMN)
atime_item = self._history_cat.child(i, self.TIME_COLUMN)
title_item = self._history_cat.child(i, self.TEXT_COLUMN)
url = url_item.data(base.Role.userdata)
if url == entry.url:
atime_item.setText(self._fmt_atime(entry.atime))
title_item.setText(entry.title)
url_item.setData(int(entry.atime), base.Role.sort)
break
else:
self._add_history_entry(entry)
@pyqtSlot()
def on_history_cleared(self):
self._history_cat.removeRows(0, self._history_cat.rowCount())
def _remove_item(self, data, category, column):
"""Helper function for on_quickmark_removed and on_bookmark_removed.

View File

@@ -95,6 +95,7 @@ class change_filter: # pylint: disable=invalid-name
"""
if self._function:
@pyqtSlot(str, str)
@pyqtSlot()
@functools.wraps(func)
def wrapper(sectname=None, optname=None):
if sectname is None and optname is None:
@@ -108,6 +109,7 @@ class change_filter: # pylint: disable=invalid-name
return func()
else:
@pyqtSlot(str, str)
@pyqtSlot()
@functools.wraps(func)
def wrapper(wrapper_self, sectname=None, optname=None):
if sectname is None and optname is None:
@@ -339,6 +341,7 @@ class ConfigManager(QObject):
('colors', 'tab.indicator.system'): 'tabs.indicator.system',
('completion', 'history-length'): 'cmd-history-max-items',
('colors', 'downloads.fg'): 'downloads.fg.start',
('ui', 'show-keyhints'): 'keyhint-blacklist',
}
DELETED_OPTIONS = [
('colors', 'tab.separator'),
@@ -358,6 +361,8 @@ class ConfigManager(QObject):
_get_value_transformer({'false': '-1', 'true': '1000'}),
('general', 'log-javascript-console'):
_get_value_transformer({'false': 'none', 'true': 'debug'}),
('ui', 'keyhint-blacklist'):
_get_value_transformer({'false': '*', 'true': ''}),
}
changed = pyqtSignal(str, str)
@@ -684,9 +689,11 @@ class ConfigManager(QObject):
raise cmdexc.CommandError("set: {} - {}".format(
e.__class__.__name__, e))
@cmdutils.register(name='set', instance='config', win_id='win_id',
completion=[Completion.section, Completion.option,
Completion.value])
@cmdutils.register(name='set', instance='config')
@cmdutils.argument('section_', completion=Completion.section)
@cmdutils.argument('option', completion=Completion.option)
@cmdutils.argument('value', completion=Completion.value)
@cmdutils.argument('win_id', win_id=True)
def set_command(self, win_id, section_=None, option=None, value=None,
temp=False, print_=False):
"""Set an option.
@@ -727,7 +734,7 @@ class ConfigManager(QObject):
val = self.get(section_, option)
layer = 'temp' if temp else 'conf'
if isinstance(val, bool):
self.set(layer, section_, option, str(not val))
self.set(layer, section_, option, str(not val).lower())
else:
raise cmdexc.CommandError(
"set: Attempted inversion of non-boolean value.")

View File

@@ -355,6 +355,12 @@ def data(readonly=False):
"Hide the window decoration when using wayland "
"(requires restart)"),
('keyhint-blacklist',
SettingValue(typ.List(none_ok=True), ''),
"Keychains that shouldn't be shown in the keyhint dialog\n\n"
"Globs are supported, so ';*' will blacklist all keychains"
"starting with ';'. Use '*' to disable keyhints"),
readonly=readonly
)),
@@ -401,6 +407,10 @@ def data(readonly=False):
SettingValue(typ.Bool(), 'true'),
"Whether to try to pre-fetch DNS entries to speed up browsing."),
('custom-headers',
SettingValue(typ.HeaderDict(none_ok=True), ''),
"Set custom headers for qutebrowser HTTP requests."),
readonly=readonly
)),
@@ -466,11 +476,15 @@ def data(readonly=False):
('input', sect.KeyValue(
('timeout',
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '500'),
"Timeout for ambiguous key bindings."),
"Timeout for ambiguous key bindings.\n\n"
"If the current input forms both a complete match and a partial "
"match, the complete match will be executed after this time."),
('partial-timeout',
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '1000'),
"Timeout for partially typed key bindings."),
SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '5000'),
"Timeout for partially typed key bindings.\n\n"
"If the current input forms only partial matches, the keystring "
"will be cleared after this time."),
('insert-mode-on-plugins',
SettingValue(typ.Bool(), 'false'),
@@ -741,7 +755,7 @@ def data(readonly=False):
"are not affected by this setting."),
('webgl',
SettingValue(typ.Bool(), 'true'),
SettingValue(typ.Bool(), 'false'),
"Enables or disables WebGL."),
('css-regions',
@@ -884,7 +898,7 @@ def data(readonly=False):
('scatter',
SettingValue(typ.Bool(), 'true'),
"Whether to scatter hint key chains (like Vimium) or not (like "
"dwb)."),
"dwb). Ignored for number hints."),
('uppercase',
SettingValue(typ.Bool(), 'false'),
@@ -899,6 +913,11 @@ def data(readonly=False):
"Follow a hint immediately when the hint text is completely "
"matched."),
('auto-follow-timeout',
SettingValue(typ.Int(), '0'),
"A timeout to inhibit normal-mode key bindings after a successful"
"auto-follow."),
('next-regexes',
SettingValue(typ.RegexList(flags=re.IGNORECASE),
r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,'
@@ -911,6 +930,14 @@ def data(readonly=False):
r'\b(<<|«)\b'),
"A comma-separated list of regexes to use for 'prev' links."),
('find-implementation',
SettingValue(typ.String(
valid_values=typ.ValidValues(
('javascript', "Better but slower"),
('python', "Slightly worse but faster"),
)), 'javascript'),
"Which implementation to use to find elements to hint."),
readonly=readonly
)),
@@ -1192,6 +1219,18 @@ def data(readonly=False):
"Background color for webpages if unset (or empty to use the "
"theme's color)"),
('keyhint.fg',
SettingValue(typ.QssColor(), '#FFFFFF'),
"Text color for the keyhint widget."),
('keyhint.fg.suffix',
SettingValue(typ.CssColor(), '#FFFF00'),
"Highlight color for keys to complete the current keychain"),
('keyhint.bg',
SettingValue(typ.QssColor(), 'rgba(0, 0, 0, 80%)'),
"Background color of the keyhint widget."),
readonly=readonly
)),
@@ -1273,6 +1312,10 @@ def data(readonly=False):
typ.Int(none_ok=True, minval=1, maxval=MAXVALS['int']), ''),
"The default font size for fixed-pitch text."),
('keyhint',
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used in the keyhint widget."),
readonly=readonly
)),
])
@@ -1382,14 +1425,15 @@ KEY_DATA = collections.OrderedDict([
('normal', collections.OrderedDict([
('clear-keychain ;; search', ['<Escape>']),
('set-cmd-text -s :open', ['o']),
('set-cmd-text :open {url}', ['go']),
('set-cmd-text :open {url:pretty}', ['go']),
('set-cmd-text -s :open -t', ['O']),
('set-cmd-text :open -t {url}', ['gO']),
('set-cmd-text :open -t {url:pretty}', ['gO']),
('set-cmd-text -s :open -b', ['xo']),
('set-cmd-text :open -b {url}', ['xO']),
('set-cmd-text :open -b {url:pretty}', ['xO']),
('set-cmd-text -s :open -w', ['wo']),
('set-cmd-text :open -w {url}', ['wO']),
('set-cmd-text :open -w {url:pretty}', ['wO']),
('open -t', ['ga', '<Ctrl-T>']),
('open -w', ['<Ctrl-N>']),
('tab-close', ['d', '<Ctrl-W>']),
('tab-close -o', ['D']),
('tab-only', ['co']),
@@ -1397,11 +1441,11 @@ KEY_DATA = collections.OrderedDict([
('tab-move', ['gm']),
('tab-move -', ['gl']),
('tab-move +', ['gr']),
('tab-focus', ['J']),
('tab-prev', ['K']),
('tab-focus', ['J', '<Ctrl-PgDown>']),
('tab-prev', ['K', '<Ctrl-PgUp>']),
('tab-clone', ['gC']),
('reload', ['r']),
('reload -f', ['R']),
('reload', ['r', '<F5>']),
('reload -f', ['R', '<Ctrl-F5>']),
('back', ['H']),
('back -t', ['th']),
('back -w', ['wh']),
@@ -1437,12 +1481,16 @@ KEY_DATA = collections.OrderedDict([
('search-prev', ['N']),
('enter-mode insert', ['i']),
('enter-mode caret', ['v']),
('enter-mode set_mark', ['`']),
('enter-mode jump_mark', ["'"]),
('yank', ['yy']),
('yank -s', ['yY']),
('yank -t', ['yt']),
('yank -ts', ['yT']),
('yank -d', ['yd']),
('yank -ds', ['yD']),
('yank -p', ['yp']),
('yank -ps', ['yP']),
('paste', ['pp']),
('paste -s', ['pP']),
('paste -t', ['Pp']),

View File

@@ -20,6 +20,7 @@
"""Setting options used for qutebrowser."""
import re
import json
import shlex
import base64
import codecs
@@ -172,8 +173,8 @@ class BaseType:
if self.valid_values is not None:
if value not in self.valid_values:
raise configexc.ValidationError(
value, "valid values: {}".format(', '.join(
self.valid_values)))
value,
"valid values: {}".format(', '.join(self.valid_values)))
else:
raise NotImplementedError("{} does not implement validate.".format(
self.__class__.__name__))
@@ -262,8 +263,8 @@ class String(BaseType):
if self.valid_values is not None:
if value not in self.valid_values:
raise configexc.ValidationError(
value, "valid values: {}".format(', '.join(
self.valid_values)))
value,
"valid values: {}".format(', '.join(self.valid_values)))
if self.forbidden is not None and any(c in value
for c in self.forbidden):
@@ -741,11 +742,13 @@ class QssColor(CssColor):
color_func_regexes: Valid function regexes.
"""
num = r'[0-9]{1,3}%?'
color_func_regexes = [
r'rgb\([0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?\)',
r'rgba\([0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?\)',
r'hsv\([0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?\)',
r'hsva\([0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?, [0-9]{1,3}%?\)',
r'rgb\({num},\s*{num},\s*{num}\)'.format(num=num),
r'rgba\({num},\s*{num},\s*{num},\s*{num}\)'.format(num=num),
r'hsv\({num},\s*{num},\s*{num}\)'.format(num=num),
r'hsva\({num},\s*{num},\s*{num},\s*{num}\)'.format(num=num),
r'qlineargradient\(.*\)',
r'qradialgradient\(.*\)',
r'qconicalgradient\(.*\)',
@@ -1146,8 +1149,8 @@ class Proxy(BaseType):
return
url = QUrl(value)
if not url.isValid():
raise configexc.ValidationError(value, "invalid url, {}".format(
url.errorString()))
raise configexc.ValidationError(
value, "invalid url, {}".format(url.errorString()))
elif url.scheme() not in self.PROXY_TYPES:
raise configexc.ValidationError(value, "must be a proxy URL "
"(http://... or socks://...) or "
@@ -1159,6 +1162,8 @@ class Proxy(BaseType):
out.append((val, self.valid_values.descriptions[val]))
out.append(('http://', 'HTTP proxy URL'))
out.append(('socks://', 'SOCKS proxy URL'))
out.append(('socks://localhost:9050/', 'Tor via SOCKS'))
out.append(('http://localhost:8080/', 'Local HTTP proxy'))
return out
def transform(self, value):
@@ -1208,8 +1213,8 @@ class SearchEngineUrl(BaseType):
url = QUrl(value.replace('{}', 'foobar'))
if not url.isValid():
raise configexc.ValidationError(value, "invalid url, {}".format(
url.errorString()))
raise configexc.ValidationError(
value, "invalid url, {}".format(url.errorString()))
class FuzzyUrl(BaseType):
@@ -1421,6 +1426,61 @@ class UrlList(List):
"{}".format(val.errorString()))
class HeaderDict(BaseType):
"""A JSON-like dictionary for custom HTTP headers."""
def _validate_str(self, value, what):
"""Check if the given thing is an ascii-only string.
Raises ValidationError if not.
Args:
value: The value to check.
what: Either 'key' or 'value'.
"""
if not isinstance(value, str):
msg = "Expected string for {} {!r} but got {}".format(
what, value, type(value))
raise configexc.ValidationError(value, msg)
try:
value.encode('ascii')
except UnicodeEncodeError as e:
msg = "{} {!r} contains non-ascii characters: {}".format(
what.capitalize(), value, e)
raise configexc.ValidationError(value, msg)
def validate(self, value):
self._basic_validation(value)
if not value:
return
try:
json_val = json.loads(value)
except ValueError as e:
raise configexc.ValidationError(value, str(e))
if not isinstance(json_val, dict):
raise configexc.ValidationError(value, "Expected json dict, but "
"got {}".format(type(json_val)))
if not json_val:
if self.none_ok:
return
else:
raise configexc.ValidationError(value, "may not be empty!")
for key, val in json_val.items():
self._validate_str(key, 'key')
self._validate_str(val, 'value')
def transform(self, value):
if not value:
return None
val = json.loads(value)
return val or None
class SessionName(BaseType):
"""The name of a session."""

View File

@@ -27,7 +27,7 @@ from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.config import configdata, textwrapper
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import log, utils, qtutils
from qutebrowser.utils import log, utils, qtutils, message, usertypes
class KeyConfigError(Exception):
@@ -151,18 +151,34 @@ class KeyConfigParser(QObject):
f.write(data)
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True)
def bind(self, key, command, *, mode=None, force=False):
@cmdutils.argument('win_id', win_id=True)
@cmdutils.argument('key', completion=usertypes.Completion.empty)
@cmdutils.argument('command', completion=usertypes.Completion.command)
def bind(self, key, win_id, command=None, *, mode='normal', force=False):
"""Bind a key to a command.
Args:
key: The keychain or special key (inside `<...>`) to bind.
command: The command to execute, with optional args.
command: The command to execute, with optional args, or None to
print the current binding.
mode: A comma-separated list of modes to bind the key in
(default: `normal`).
force: Rebind the key if it is already bound.
"""
if mode is None:
mode = 'normal'
if utils.is_special_key(key):
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
key = key.lower()
if command is None:
cmd = self.get_bindings_for(mode).get(key, None)
if cmd is None:
message.info(win_id, "{} is unbound in {} mode".format(
key, mode))
else:
message.info(win_id, "{} is bound to '{}' in {} mode".format(
key, cmd, mode))
return
mode = self._normalize_sectname(mode)
for m in mode.split(','):
if m not in configdata.KEY_DATA:
@@ -183,7 +199,7 @@ class KeyConfigParser(QObject):
self._mark_config_dirty()
@cmdutils.register(instance='key-config')
def unbind(self, key, mode=None):
def unbind(self, key, mode='normal'):
"""Unbind a keychain.
Args:
@@ -191,8 +207,10 @@ class KeyConfigParser(QObject):
mode: A comma-separated list of modes to unbind the key in
(default: `normal`).
"""
if mode is None:
mode = 'normal'
if utils.is_special_key(key):
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
key = key.lower()
mode = self._normalize_sectname(mode)
for m in mode.split(','):
if m not in configdata.KEY_DATA:
@@ -362,6 +380,9 @@ class KeyConfigParser(QObject):
def _add_binding(self, sectname, keychain, command, *, force=False):
"""Add a new binding from keychain to command in section sectname."""
if utils.is_special_key(keychain):
# <Ctrl-t>, <ctrl-T>, and <ctrl-t> should be considered equivalent
keychain = keychain.lower()
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
keychain, command, sectname))
if sectname not in self.keybindings:

Some files were not shown because too many files have changed in this diff Show More