Compare commits

...

2074 Commits

Author SHA1 Message Date
Florian Bruhin
e11fcda240 Release v0.4.0. 2015-09-11 18:47:50 +02:00
Florian Bruhin
2f36204e08 Update changelog. 2015-09-11 18:47:50 +02:00
Florian Bruhin
5fe420efb5 standarddir: Fix TOCTOU issue when creating paths.
Fixes #942.
2015-09-11 18:21:20 +02:00
Florian Bruhin
5d90e0ecd3 Add a test for creating standarddir dirs. 2015-09-11 17:50:17 +02:00
Florian Bruhin
b06578a816 Fix lint. 2015-09-11 08:39:42 +02:00
Florian Bruhin
9ca001e71c Fix lint. 2015-09-11 08:32:45 +02:00
Florian Bruhin
c4c06467da Add missing pyqtSlot decorator. 2015-09-11 08:32:37 +02:00
Florian Bruhin
c77956c9c5 Fix GUIProcess tests.
The logging checks were of little use and some tests were basically duplicated.
2015-09-11 08:32:16 +02:00
Florian Bruhin
28c8e4acbf Unskip GUIProcess tests on Windows. 2015-09-11 07:37:54 +02:00
Florian Bruhin
b126030f62 Revert "ipc: Skip problematic test on Ubuntu Trusty."
This reverts commit 729be7e7cc.
2015-09-11 07:37:13 +02:00
Florian Bruhin
e2c07d3cef tox: Update pytest-qt to 1.6.0.
- Reduced verbosity when exceptions are captured in virtual methods.

- `pytestqt.plugin` has been split in several files and tests have been
  moved out of the `pytestqt` package. This should not affect users, but it
  is worth mentioning nonetheless.

- `QApplication.processEvents()` is now called before and after other fixtures
  and teardown hooks, to better try to avoid non-processed events from leaking
  from one test to the next.

- Show Qt/PyQt/PySide versions in pytest header.

- Disconnect SignalBlocker functions after its loop exits to ensure second
  emissions that call the internal functions on the now-garbage-collected
  SignalBlocker instance.
2015-09-11 07:35:29 +02:00
Florian Bruhin
c4828cf67c tox: Update hypothesis-pytest to 0.18.1.
- Remove deprecated entry point
2015-09-11 07:34:40 +02:00
Florian Bruhin
d15cc07ed3 Log executed command for GUIProcess.
See #797.
2015-09-11 06:24:05 +02:00
Florian Bruhin
729be7e7cc ipc: Skip problematic test on Ubuntu Trusty. 2015-09-10 08:21:37 +02:00
Florian Bruhin
087e9a4266 Fix ipc on Windows, take 2. 2015-09-10 08:04:02 +02:00
Florian Bruhin
87ccc31cce ipc: Add some more tests for _atime_timer. 2015-09-10 08:02:19 +02:00
Florian Bruhin
1dba5b0bbd ipc: Fix _atime_timer shutdown on Windows. 2015-09-10 07:52:26 +02:00
Florian Bruhin
d2bce5dca9 ipc tests: Try to disconnect _atime_timer.
For some reason this fails during teardown on OS X!?

    File "/Users/buildbot/buildbot/slave/osx/build/qutebrowser/misc/ipc.py", line 357, in update_atime
      path = self._server.fullServerName()
    File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 895, in __call__
      _mock_self._mock_check_sig(*args, **kwargs)
    File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/mock.py", line 107, in checksig
      sig.bind(*args, **kwargs)
    File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/inspect.py", line 2652, in bind
      return args[0]._bind(args[1:], kwargs)
    File "/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/inspect.py", line 2537, in _bind
      param = next(parameters)
    File "/Users/buildbot/buildbot/slave/osx/build/.tox/py34/bin/../lib/python3.4/_collections_abc.py", line 512, in __iter__
      for key in self._mapping:
    File "/Users/buildbot/buildbot/slave/osx/build/.tox/py34/lib/python3.4/collections/__init__.py", line 91, in __iter__
      curr = root.next
2015-09-10 07:49:45 +02:00
Florian Bruhin
e6cf76e40c Fix coverage and lint. 2015-09-10 07:44:45 +02:00
Florian Bruhin
5be408b79f Update changelog. 2015-09-10 07:43:56 +02:00
Florian Bruhin
58073fd768 ipc: Update the atime of the socket all 6h.
See #888.
2015-09-10 07:38:11 +02:00
Florian Bruhin
1311e99e7c Make pep8 shut up. 2015-09-09 23:00:52 +02:00
Florian Bruhin
bcee12853d Cleanup for ignored log messages. 2015-09-09 22:53:37 +02:00
Florian Bruhin
d217fe09c6 Hide some more lgo messages on OS X.
Fixes #931.
2015-09-09 22:50:43 +02:00
Florian Bruhin
c8f69d29a8 tests: Ignore SSLRead warning on OS X, take 3. 2015-09-09 22:05:48 +02:00
Florian Bruhin
2ca8070e7a Add workaround for OS X issue. 2015-09-09 20:48:23 +02:00
Florian Bruhin
f7d3070c34 ipc: Fix start when a stale legacy server exists. 2015-09-09 19:39:01 +02:00
Florian Bruhin
37a2523bff Add a py_proc fixture to conftest.py. 2015-09-09 19:39:01 +02:00
Florian Bruhin
1d7c2b6b82 Don't set TMPDIR for test_legacy.
This has no real benefit and seems to break tests with some platforms.
2015-09-09 19:31:52 +02:00
Florian Bruhin
011b571336 Use a shorter tempdir. 2015-09-09 19:31:52 +02:00
Florian Bruhin
b135569d5c ipc: Add workaround for NameError w/ SocketOptions. 2015-09-09 19:31:52 +02:00
Florian Bruhin
69e735c42e ipc: Add username to hash instead. 2015-09-09 19:31:52 +02:00
Florian Bruhin
34bd000442 Use shorter names.
The typical test path for a legacy FIFO was something like:

    /tmp/pytest-92/test_correct_socket_name0/qutebrowser_test/qutebrowser-ipc-dfc627b5be8602ea0e9cd258b73c0bc3

This is probably too long for a Unix local domain socket (104 chars max).
2015-09-09 19:31:52 +02:00
Florian Bruhin
4daa7e6979 Don't add username to FIFO name on Linux.
The runtime directory already has a qutebrowser subdir.
2015-09-09 19:31:52 +02:00
Florian Bruhin
3573b57130 Set socket permissions correctly. 2015-09-09 19:31:52 +02:00
Florian Bruhin
78cb0eaf85 More work on #888 (new IPC path).
First trying the legacy path and then using the new one works fine now, but the
permissions are still wrong.
2015-09-09 19:31:52 +02:00
Florian Bruhin
74d7997a67 Set application info earlier. 2015-09-09 19:31:52 +02:00
Florian Bruhin
a4bc4ad478 Only log a single message in utils.error.
This helps with seeing the full message with logfail.
2015-09-09 19:31:52 +02:00
Florian Bruhin
9d9372c6a8 Add standarddir.temp(). 2015-09-09 19:31:52 +02:00
Florian Bruhin
44b1ca5c2f Fix comment. 2015-09-09 19:31:52 +02:00
Florian Bruhin
7dedf73ea4 tests: Fix ignored SSLRead warning on OS X. 2015-09-09 17:50:09 +02:00
Florian Bruhin
b0c2021eca Revert "logfail: Update tests to use testdir."
This reverts commit f7e40f73ab.

Conflicts:
	tests/helpers/test_logfail.py
2015-09-09 08:49:51 +02:00
Florian Bruhin
2f8b39df1c Revert "logfail: Switch tests to subprocess pytest runs."
This reverts commit 09c265ddb0.
2015-09-09 08:49:17 +02:00
Florian Bruhin
f7c405d2f4 Revert "logfail: Continue running test and fail afterwards."
This reverts commit 66ed4e9c4e.
2015-09-09 08:49:06 +02:00
Florian Bruhin
d18c33987d Quick pep8 fix.
This is already fixed in the new-ipc-path branch, but let's at least get this
straight in master.
2015-09-09 08:06:56 +02:00
Florian Bruhin
0ec9efcdc0 tox: Update pytest-html to 1.6.
Changelog:

Preserve environment details when using pytest-xdist.
2015-09-09 08:00:18 +02:00
Florian Bruhin
909cafb516 Fix lint. 2015-09-09 07:58:14 +02:00
Florian Bruhin
66ed4e9c4e logfail: Continue running test and fail afterwards. 2015-09-09 07:58:14 +02:00
Florian Bruhin
09c265ddb0 logfail: Switch tests to subprocess pytest runs.
It seems they're much more reliable this way.
2015-09-09 07:58:14 +02:00
Florian Bruhin
f7e40f73ab logfail: Update tests to use testdir.
This makes more sophisticated tests easier.
2015-09-09 07:58:14 +02:00
Florian Bruhin
f5d9e967ef logfail: Rename capturelog module.
When trying to use this as conftest for the updated tests, pytest_* would be
interpreted as hook otherwise.
2015-09-09 07:58:14 +02:00
Florian Bruhin
376edd739b Remove double changelog entry. 2015-09-07 10:12:48 +02:00
Florian Bruhin
e81432fd6e Update changelog. 2015-09-07 10:05:28 +02:00
Florian Bruhin
6a0994038e Start working on #888 (new IPC path). 2015-09-06 19:50:22 +02:00
Florian Bruhin
bfd8faafef Add a protocol version to IPC.
Fixes #909.
2015-09-06 18:43:24 +02:00
Florian Bruhin
b95fd2c814 Test raw json data for IPC. 2015-09-06 18:06:23 +02:00
Florian Bruhin
e9608a6aea Add qutebrowser version to IPC data.
See #909.
2015-09-06 18:06:03 +02:00
Florian Bruhin
1832d44da1 Use Travis/AppVeyor badges in README. 2015-09-06 17:21:00 +02:00
Florian Bruhin
cc80cfdfd6 Add missing return. 2015-09-06 17:15:13 +02:00
Florian Bruhin
ef9e1bef1b Improve performance when adding new history item.
Fixes #919.

There were two issues here:

- CompletionWidget didn't delete the old model when setting a new one. This
  means filterAcceptsRow was called for models which aren't even used anymore.

- setChild was used instead of appendRow for the BaseCompletionModel, which
  caused Qt to call filterAcceptsRow once for every item of the completion
  model instead of only once.
2015-09-06 16:59:43 +02:00
Florian Bruhin
02a539f2d7 Rename TestError to Error. 2015-09-06 16:43:23 +02:00
Florian Bruhin
bf9a897ce8 Skip some more IPC tests on Windows. 2015-09-06 16:42:44 +02:00
Florian Bruhin
9521da3c73 Fix error output with --no-err-windows. 2015-09-04 08:04:18 +02:00
Florian Bruhin
9b1d0af20d ipc: Simplify exception handling. 2015-09-04 07:16:16 +02:00
Florian Bruhin
ea0cbea1dd ipc: Use a custom class for exceptions. 2015-09-04 07:12:23 +02:00
Florian Bruhin
2a4cd02704 100% coverage for misc.ipc. 2015-09-04 06:58:46 +02:00
Florian Bruhin
f77bdb7aec Adjust test_ipcserver_socket_none for prev commit. 2015-09-03 23:38:33 +02:00
Florian Bruhin
0d0e704b6b ipc: Reduce logging on Windows.
Otherwise the tests fail there...
2015-09-03 23:33:41 +02:00
Florian Bruhin
fab6e2eafc ipc: Fix exception handling on AddressError. 2015-09-03 23:13:55 +02:00
Florian Bruhin
a6b9d28b96 ipc: Fix sleep time on AddressError. 2015-09-03 23:13:29 +02:00
Florian Bruhin
b40fb4dd43 ipc: Handle None-socket in slots correctly. 2015-09-03 20:48:51 +02:00
Florian Bruhin
27ee09a7a1 ipc: Skip test_normal on Windows. 2015-09-03 14:35:27 +02:00
Florian Bruhin
2f92ea92d8 test_ipc: Remove disconnect assert. 2015-09-03 11:55:21 +02:00
Florian Bruhin
f682477960 Fix some more IPC tests on Windows. 2015-09-03 11:55:17 +02:00
Florian Bruhin
ab9c046d54 Adjust default keybinding to clear-keychain on Esc.
Fixes #917.
2015-09-03 11:14:05 +02:00
Florian Bruhin
3a5cd8f3ff Revert "Remove test_ipc workaround again."
This reverts commit f4d2e0746e.
2015-09-03 06:51:15 +02:00
Florian Bruhin
46b28387db Rewrite test_double_connection.
This caused various problems in the old form.
2015-09-03 06:38:35 +02:00
Florian Bruhin
f4d2e0746e Remove test_ipc workaround again. 2015-09-03 06:36:33 +02:00
Florian Bruhin
efe40fa7e0 Try different workaround for test_ipc issues. 2015-09-03 06:12:36 +02:00
Florian Bruhin
154c380fd0 Fix pylint in test_ipc. 2015-09-02 23:32:06 +02:00
Florian Bruhin
d966720900 ipc tests: More tests. 2015-09-02 23:32:06 +02:00
Florian Bruhin
b4c90c5db4 ipc tests: Clean up qlocalsocket correctly.
If we don't do this, on_error will be invoked with old sockets, when
self._socket is None...
2015-09-02 23:32:06 +02:00
Florian Bruhin
6468e60a36 ipc tests: Fix waiting for FakeSocket. 2015-09-02 23:32:06 +02:00
Florian Bruhin
e0c6a322c6 ipc: Pass socket to send_to_running_instance. 2015-09-02 23:32:06 +02:00
Florian Bruhin
82c647a87d ipc: Mark on_ready_read cornercase as no-cover. 2015-09-02 23:32:06 +02:00
Florian Bruhin
a63f0d5409 More IPC tests. 2015-09-02 23:32:06 +02:00
Florian Bruhin
18af0b4b35 tests: Make FakeSignal callable. 2015-09-02 23:32:06 +02:00
Florian Bruhin
38ebd806cc Add some tests for misc.ipc. 2015-09-02 23:32:06 +02:00
Florian Bruhin
1953bb8458 ipc: Disconnect on invalid data. 2015-09-02 23:32:06 +02:00
Florian Bruhin
6b4b65e585 ipc: Clarify comment. 2015-09-02 23:32:06 +02:00
Florian Bruhin
cbcd6261b1 ipc: Add got_invalid_data signal. 2015-09-02 23:32:06 +02:00
Florian Bruhin
52210442c4 Merge branch 'jnphilipp-master' 2015-09-02 14:06:17 +02:00
Florian Bruhin
0fcab6a632 Regenerate authors. 2015-09-02 14:06:11 +02:00
Florian Bruhin
fe06db2571 Merge branch 'master' of https://github.com/jnphilipp/qutebrowser into jnphilipp-master 2015-09-02 14:05:57 +02:00
jnphilipp
6ffcb387eb Incorporated The-Compilers suggested chanes. 2015-09-02 14:02:24 +02:00
jnphilipp
1cbc555933 Rewrote userscript to use BeautifulSoup. 2015-09-01 22:08:37 +02:00
Florian Bruhin
b23ca85b37 Set basepython for all envs in tox.ini. 2015-09-01 08:54:53 +02:00
Florian Bruhin
1fe1200b71 Install libpython3.4-dev on Travis. 2015-09-01 08:54:53 +02:00
Florian Bruhin
0b911d2e20 Split integration tests, run smoke via pytest. 2015-09-01 08:54:53 +02:00
Florian Bruhin
44f594662a Merge branch 'acogneau-autoupdate_tests' 2015-09-01 08:49:53 +02:00
Florian Bruhin
216ecd16cc Regenerate authors. 2015-09-01 08:49:43 +02:00
Florian Bruhin
0c3ee46fe0 Merge branch 'autoupdate_tests' of https://github.com/acogneau/qutebrowser into acogneau-autoupdate_tests 2015-09-01 08:48:52 +02:00
Alexander Cogneau
2d12c26e8c Add extra url assertion for autoupdate 2015-09-01 01:06:11 +02:00
jnphilipp
8a1a090dea Added userscript to open feed links. 2015-08-31 22:45:17 +02:00
Florian Bruhin
16e3bad7af Hide scroll buttons in vertical tab bar. 2015-08-31 22:00:26 +02:00
Florian Bruhin
fecc4be057 tox: Update hypothesis to 0.18.0.
Changelog:

- text() with a non-string alphabet would have used the repr() of the the
  alphabet instead of its contexts. This is obviously silly. It now works with
  any sequence of things convertible to unicode strings.

- @given will now work on methods whose definitions contains no explicit
  positional arguments, only varargs. This may have some knock on effects
  because it means that @given no longer changes the argspec of functions other
  than by adding defaults.

- Introduction of new @composite feature for more natural definition of
  strategies you'd previously have used flatmap for.
2015-08-31 21:03:13 +02:00
Florian Bruhin
3046d2f068 Add note about youtube-dl to FAQ.
See #350.
2015-08-31 20:57:59 +02:00
Alexander Cogneau
8ab85d6246 small edits to autoupdate test 2015-08-31 09:30:16 +02:00
Florian Bruhin
8ea91b5bbc Fix cookiejar_and_cache move. 2015-08-31 08:00:21 +02:00
Florian Bruhin
03f4d738ab Rename klass fixture to klass_fixt.
See https://github.com/pytest-dev/pytest/issues/979.
2015-08-31 07:42:53 +02:00
Florian Bruhin
9e6bcb97ca Output coverage to coverage.xml.
This avoids https://bitbucket.org/ned/coveragepy/issues/400/
2015-08-31 07:41:06 +02:00
Alexander Cogneau
ee77951e66 PEP8 fixes 2015-08-31 01:25:42 +02:00
Alexander Cogneau
e499d8932f 100% misc.autoupdate coverage 2015-08-31 01:10:04 +02:00
Florian Bruhin
79c1867e6c Don't save cookies when starting in private mode.
Whoops... :(

Fixes #903.
2015-08-30 23:17:48 +02:00
Florian Bruhin
6df00f8266 Move cookiejar_and_cache fixture to conftest.py. 2015-08-30 23:11:23 +02:00
Florian Bruhin
8913c1883e ipc: Fix missing import. 2015-08-30 22:57:45 +02:00
Florian Bruhin
6dbd669efe ipc: Disconnect properly from server. 2015-08-30 20:35:50 +02:00
Florian Bruhin
a1cb47936a IPC: Fix shutdown when self._socket is None. 2015-08-30 20:35:34 +02:00
Florian Bruhin
39e40a7887 ipc: Pass args to send_to_running_instance. 2015-08-29 23:09:44 +02:00
Florian Bruhin
fe493f8565 ipc: Pass socket name to IPCServer. 2015-08-29 21:26:22 +02:00
Florian Bruhin
d5668dd687 ipc: Don't listen in IPCServer.__init__ already. 2015-08-27 23:09:25 +02:00
Florian Bruhin
a7bf0744e0 Move some IPC code from app.py to ipc.py. 2015-08-27 23:04:27 +02:00
Florian Bruhin
ff6e96347b ipc: Refactor _get_socketname to take better args. 2015-08-27 22:32:29 +02:00
Florian Bruhin
0cd265296e Add tests/html/jsalert_multiline.html. 2015-08-27 22:13:40 +02:00
Florian Bruhin
4cd0ad77a6 Regenerate docs. 2015-08-27 22:13:30 +02:00
Florian Bruhin
8d7249ebc6 Implement statusbar padding. 2015-08-27 22:12:39 +02:00
Florian Bruhin
4891fe9457 Always expand ~ when starting scripts. 2015-08-27 20:30:41 +02:00
Florian Bruhin
84a41cf9cf Merge branch 'meles5-contributions' 2015-08-27 13:53:45 +02:00
Florian Bruhin
37aebc7580 Regenerate authors. 2015-08-27 13:53:36 +02:00
meles5
afddf75bae Fixed license issue 2015-08-27 13:37:31 +02:00
Florian Bruhin
3059d82c5d Merge branch 'acogneau-cmdhistory_tests' 2015-08-27 06:41:10 +02:00
Florian Bruhin
ed9a29fb9a test_cmdhistory: Remove patching for prev/nextitem. 2015-08-27 06:40:14 +02:00
Florian Bruhin
3fac246744 test_cmdhistory: Add test for __getitem__. 2015-08-27 06:38:41 +02:00
Florian Bruhin
4a48ef2573 Add my copyright. 2015-08-27 06:30:54 +02:00
Florian Bruhin
38c5abfc98 Remove unneeded self-parameter. 2015-08-27 06:30:12 +02:00
Florian Bruhin
66700e9a94 Remove exception string checks. 2015-08-27 06:28:26 +02:00
Florian Bruhin
3e8136e353 Revert changes to cmdhistory.py. 2015-08-27 06:25:42 +02:00
Florian Bruhin
4544c78395 Remove unneeded comments/monkeypatching. 2015-08-27 06:25:10 +02:00
Florian Bruhin
9eb0240739 Update authors. 2015-08-27 06:18:42 +02:00
Florian Bruhin
4fb3114af8 Merge branch 'cmdhistory_tests' of git://github.com/acogneau/qutebrowser into acogneau-cmdhistory_tests 2015-08-27 06:18:30 +02:00
Florian Bruhin
182dd26fb7 Refactor test_basekeyparser. 2015-08-26 23:02:23 +02:00
Florian Bruhin
29e5726101 tox: Update hypothesis to 1.10.6.
Changelog:

Fix support for fixtures on Django 1.7.
2015-08-26 20:41:01 +02:00
Florian Bruhin
d3e9ffec65 Revert "Revert "Add basekeyparser.py to PERFECT_FILES""
This reverts commit 35cbee41d6.
2015-08-26 20:38:56 +02:00
Florian Bruhin
1f26b3090c tests: Add _debug_log test for BaseKeyParser. 2015-08-26 20:38:29 +02:00
Florian Bruhin
f78fb0c027 basekeyparser: Remove dead code.
We added this in 836a5e04a5 for the old INI-based
config, but this is not needed anymore as the keys.conf syntax does not allow
this.
2015-08-26 20:31:17 +02:00
Florian Bruhin
3e255eae64 Skip some tests when frozen. 2015-08-26 20:29:56 +02:00
Florian Bruhin
cb0e92b4e8 Only run test_check_coverage.py on Linux. 2015-08-26 20:20:08 +02:00
Florian Bruhin
2750c6ab5a Make check_coverage.py more testable and add tests. 2015-08-26 20:08:40 +02:00
Florian Bruhin
ae2ee68b85 Fix branch checking in check_coverage.py. 2015-08-26 17:51:51 +02:00
Florian Bruhin
fa8e207101 Merge branch 'acogneau-basekeyparser_tests' 2015-08-26 17:48:26 +02:00
Florian Bruhin
35cbee41d6 Revert "Add basekeyparser.py to PERFECT_FILES"
This reverts commit b4f4c97cf9.
2015-08-26 17:48:10 +02:00
Florian Bruhin
1d25194bfb Update authors. 2015-08-26 17:34:25 +02:00
Florian Bruhin
4f1c27d230 Merge branch 'basekeyparser_tests' of git://github.com/acogneau/qutebrowser into acogneau-basekeyparser_tests 2015-08-26 17:34:10 +02:00
Florian Bruhin
57918b651f tox: Remove cov-core. 2015-08-26 17:31:07 +02:00
Alexander Cogneau
cd34fc4b57 Small changes to basekeyparser tests 2015-08-26 12:13:47 +02:00
Alexander Cogneau
fd6e0559a6 remove unnecessary function 2015-08-26 12:10:25 +02:00
Alexander Cogneau
3a4069667a Remove test functions out of class 2015-08-26 12:07:47 +02:00
Florian Bruhin
8ffe591f98 Skip TestSave.test_long_output on Windows.
This seems to segfault unpredictably when exiting pytest and I can't find out
why.

Fixes #895.
2015-08-26 10:06:49 +02:00
Alexander Cogneau
b4f4c97cf9 Add basekeyparser.py to PERFECT_FILES 2015-08-26 01:22:09 +02:00
Alexander Cogneau
933d683ff4 Add cmdhistory.py to PERFECT_FILES 2015-08-26 01:20:57 +02:00
Alexander Cogneau
9ef9224c32 Remove unecessary class 2015-08-26 00:20:14 +02:00
Alexander Cogneau
a1dff7d535 Final cmdhistory tests 2015-08-26 00:16:18 +02:00
Alexander Cogneau
d3488172ec Lift duration for test_debug 2015-08-25 22:41:54 +02:00
Alexander Cogneau
18d42d1f0a More tests for cmdhistory 2015-08-25 22:17:42 +02:00
Florian Bruhin
9229957b93 tests: Ignore QSslSocketBackendPrivate warning.
This apparently happens during test_split_hypothesis:test_simple_split[1-True]
for @acogneau.
2015-08-25 22:11:29 +02:00
Florian Bruhin
f3b4d0ce38 Ignore htmlcov dir for spellchecks. 2015-08-25 22:09:15 +02:00
Florian Bruhin
11f4fbc772 Silence pylint. 2015-08-25 21:24:58 +02:00
Florian Bruhin
38a07cc152 Fix lint. 2015-08-25 21:21:49 +02:00
Florian Bruhin
e94f2f11d8 Merge branch 't-wissmann-progressbar' 2015-08-25 21:18:52 +02:00
Florian Bruhin
124099ac4c Fix docstrings. 2015-08-25 21:18:45 +02:00
Florian Bruhin
0ef323c26a Update authors. 2015-08-25 21:18:10 +02:00
Florian Bruhin
8171a53478 Merge branch 'progressbar' of git://github.com/t-wissmann/qutebrowser into t-wissmann-progressbar 2015-08-25 21:18:02 +02:00
Florian Bruhin
abba3215f2 Add missing docstring. 2015-08-25 21:17:04 +02:00
Florian Bruhin
8c76db3892 Add some tests for progressbar sizing in statusbar. 2015-08-25 21:07:35 +02:00
Alexander Cogneau
26058f4e80 A few cmdhistory tests 2015-08-25 18:46:49 +02:00
Florian Bruhin
a981688509 tox: Always generate HTML coverage report. 2015-08-25 17:59:36 +02:00
Florian Bruhin
5eed9e55ad check_coverage.py: Always delete .coverage.xml. 2015-08-25 17:59:10 +02:00
Florian Bruhin
987b39885a sessions: Add more tests. 2015-08-25 17:49:45 +02:00
Alexander Cogneau
f54295f95c Test _warn_on_keychains. 2015-08-25 16:28:02 +02:00
Alexander Cogneau
09161faca5 Refactor read_config for easier testing 2015-08-25 12:04:22 +02:00
Florian Bruhin
d17744ffed Regenerate authors. 2015-08-25 11:38:07 +02:00
Thorsten Wißmann
183a5910de Fix progressbar height to statusbar height.
Formerly, the statusbar height changed when the progressbar was visible.
This was caused by the default font-size of the progressbar text (though
invisible). Overriding the minimumSizeHint() method ignores the
font-size of the hidden text and the statusbar height does not change
anymore.

This fixes the first issue of #886.
2015-08-25 11:14:52 +02:00
Thorsten Wißmann
830136540d Hide the border of the completionwidget
Having a light Qt theme but a dark qutebrowser theme, one can see an
ugly white border around the completion widget which is some relict from
the underlying Qt widget QTreeView. As qutebrowser has its own theming
settings for the mainwindow, it should hide the Qt theme as far as
possible.
2015-08-25 11:01:25 +02:00
Alexander Cogneau
05eb9bd08c Remove unnecessary lines 2015-08-25 10:28:46 +02:00
Alexander Cogneau
1a227ae3a7 pytestqt is not required 2015-08-24 19:29:50 +02:00
Florian Bruhin
1a062035eb Log IPC server name to debug log. 2015-08-24 19:28:09 +02:00
Alexander Cogneau
af9647221a Add tests for BaseKeyParser 2015-08-24 18:12:12 +02:00
Florian Bruhin
a8d549cb24 sessions: Add some more tests. 2015-08-24 17:12:20 +02:00
Florian Bruhin
201f2f67d3 sessions: Logging/error message improvements. 2015-08-24 17:12:20 +02:00
Florian Bruhin
f79e2c92a4 Read back session test data as UTF-8. 2015-08-24 08:44:41 +02:00
Florian Bruhin
9d601e8eb9 Fix session tests. 2015-08-24 08:31:11 +02:00
Florian Bruhin
8b40603562 Revert "Fix argparser test after win_registry change."
This reverts commit 70243d6e2f.
2015-08-24 08:05:34 +02:00
Florian Bruhin
e7a52a0833 Add some tests for misc.sessions. 2015-08-24 08:00:32 +02:00
Florian Bruhin
4f250ba5d7 tests: Allow tests to add windows to win_registry. 2015-08-24 08:00:16 +02:00
Florian Bruhin
4577659342 Fix crash when there's no completion. 2015-08-24 00:08:55 +02:00
Florian Bruhin
70243d6e2f Fix argparser test after win_registry change.
The last focused window was now window 1 - so we better make this explicit.
2015-08-23 22:37:27 +02:00
Florian Bruhin
7bc5996d52 tests: Provide activeWindow() in FakeQApplication. 2015-08-23 21:55:25 +02:00
Florian Bruhin
9ff97d31da tests: Provide two windows in win_registry fixture. 2015-08-23 21:55:12 +02:00
Florian Bruhin
b783069f48 SessionManager: Factor out name handling in save. 2015-08-23 21:54:55 +02:00
Florian Bruhin
2556e2e27b Remove unused import. 2015-08-23 18:29:02 +02:00
Florian Bruhin
0907d8bf76 tests: Add a webview fixture. 2015-08-23 18:17:22 +02:00
Florian Bruhin
17215822bc sessions: Use try/except to create base-path. 2015-08-23 18:17:10 +02:00
Florian Bruhin
6d8854bc07 Simplify usage of tabhistory.TabHistoryItem. 2015-08-23 18:16:19 +02:00
Florian Bruhin
ece2786d40 tox: Update pytest-cov to 2.1.0.
Changelog:

- Added support for coverage 4.0b2.
- Added the --cov-append command line option.
2015-08-23 17:57:37 +02:00
Florian Bruhin
ab04ca4f36 tox: Update hypothesis to 1.10.5. 2015-08-22 23:45:29 +02:00
Alexander Cogneau
193a8d5242 Add unit tests for KeyInput.BaseKeyParser 2015-08-22 23:26:13 +02:00
Florian Bruhin
c18b68ac99 Merge branch 'acogneau-separate_completion_filters' 2015-08-22 23:05:50 +02:00
Florian Bruhin
fc6c49f57c Cleanup 2015-08-22 22:59:41 +02:00
Florian Bruhin
17f971344d Update authors. 2015-08-22 22:53:52 +02:00
Florian Bruhin
2a5d352c7b Merge branch 'separate_completion_filters' of git://github.com/acogneau/qutebrowser into acogneau-separate_completion_filters 2015-08-22 22:53:35 +02:00
Florian Bruhin
3d5599facb Move session dir handling out of SessionManager. 2015-08-22 22:53:03 +02:00
Alexander Cogneau
7ed4977d64 Change parent of filter models 2015-08-21 16:56:36 +02:00
Alexander Cogneau
729c10e0a9 Fix PEP issue 2015-08-21 16:23:28 +02:00
Alexander Cogneau
387c84beff Completion model filters are now per-window (via completer.py) 2015-08-21 16:05:33 +02:00
Florian Bruhin
355074f248 Add a . for spatial-navigation docs. 2015-08-20 11:11:31 +02:00
Florian Bruhin
ba636ebbb0 Don't run pyflakes on AppVeyor.
It's broken and I don't want to fix it.
2015-08-20 07:52:02 +02:00
Florian Bruhin
543053c8f5 test_signalfilter: Remove unused fixture. 2015-08-20 07:14:32 +02:00
Florian Bruhin
7ee4d2f2c9 100% test coverage for commands.argparser. 2015-08-20 07:14:25 +02:00
Florian Bruhin
ed70d636d0 argparser: Check for unknown types. 2015-08-20 07:09:09 +02:00
Florian Bruhin
506917882e argparser: Style fixes. 2015-08-20 07:08:59 +02:00
Florian Bruhin
774ef58432 Use qapp fixture in enable_caret_browsing.
This fixes a segfault when only running test_position_caret.
2015-08-19 21:40:13 +02:00
Florian Bruhin
92facb6f50 tox: Update hypothesis to 1.10.3.
Upstream changelog:

* lists(elements, unique_by=some_function, min_size=n) would have raised a
  ValidationError if n > Settings.default.average_list_length because it would
  have wanted to use an average list length shorter than the minimum size of
  the list, which is impossible. Now it instead defaults to twice the minimum
  size in these circumstances.
* basic() strategy would have only ever produced at most ten distinct values
  per run of the test (which is bad if you e.g. have it inside a list). This
  was obviously silly. It will now produce a much better distribution of data,
  both duplicated and non duplicated.
* star imports from hypothesis should now work correctly.
* example quality for examples using flatmap will be better, as the way it had
  previously been implemented was causing problems where Hypothesis was
  erroneously labelling some examples as being duplicates.
2015-08-19 21:24:22 +02:00
Florian Bruhin
1a1bc4b8a8 Fix lint. 2015-08-19 21:13:35 +02:00
Florian Bruhin
a79c139aa4 Revert "style: Check for QColor when setting in ColorDict."
This reverts commit 9b82fae6fb.
2015-08-19 20:48:19 +02:00
Florian Bruhin
1d5cae3146 style: Use a collection.UserDict. 2015-08-19 20:46:02 +02:00
Florian Bruhin
5a975d1b90 100% test coverage for config.style. 2015-08-19 20:43:06 +02:00
Florian Bruhin
b11e075047 Add __getitem__ to ConfigStub. 2015-08-19 20:39:48 +02:00
Florian Bruhin
b3395a1a9c style: Fix logging in ColorDict. 2015-08-19 20:39:31 +02:00
Florian Bruhin
9b82fae6fb style: Check for QColor when setting in ColorDict. 2015-08-19 20:39:16 +02:00
Florian Bruhin
dc0e8b4626 Don't pass config to stylesheets. 2015-08-19 20:38:19 +02:00
Florian Bruhin
1ddd65304a tests: Use a real signal for ConfigStub. 2015-08-19 19:37:19 +02:00
Florian Bruhin
3d4fd2652b test_editor: Skip un{read,writ}able on Windows.
Windows doesn't really have working file permissions...
2015-08-19 10:18:55 +02:00
Florian Bruhin
086c6c81a1 Simplify message_mock usage and assert more things. 2015-08-19 09:44:31 +02:00
Florian Bruhin
685bbaae6d 100% test coverage for misc.editor. 2015-08-19 09:34:44 +02:00
Florian Bruhin
aa367fa004 Simplify test_editor. 2015-08-19 09:09:09 +02:00
Florian Bruhin
acfdf8b956 Fix lint. 2015-08-19 07:57:47 +02:00
Florian Bruhin
aca082ce83 100% test coverage for misc.guiprocess. 2015-08-19 07:57:02 +02:00
Florian Bruhin
8fe3a1e9ce Regenerate docs. 2015-08-19 07:13:33 +02:00
Florian Bruhin
4efa022528 Fix test_checkpyver on Pythons without Tkinter. 2015-08-19 07:12:04 +02:00
Florian Bruhin
45e7be4940 Increase default hint size a bit.
See #871.
2015-08-19 06:40:43 +02:00
Florian Bruhin
07c6c40548 Set messagebox = None without Tk in checkpyver. 2015-08-19 06:39:22 +02:00
Florian Bruhin
1a61e53daa 100% test coverage for misc.checkpyver. 2015-08-19 05:58:52 +02:00
Florian Bruhin
e4a0f1972f tests: Improve MessageMock and use it. 2015-08-18 21:38:18 +02:00
Florian Bruhin
6d1b0ba260 Clean up conftest.py. 2015-08-18 20:43:42 +02:00
Florian Bruhin
2c5269acd6 Reorganize tests directory. 2015-08-18 20:19:02 +02:00
Florian Bruhin
d3d999e041 Show a confirmation when adding bookmarks. 2015-08-18 19:03:46 +02:00
Florian Bruhin
3b747d91d2 tox: Update pytest-html to 1.5.1.
Changelog:

* Make environment fixture session scoped to avoid repeating content
* Improve string formatting
* Replace custom hook with fixture for setting environment section
2015-08-18 17:41:35 +02:00
Florian Bruhin
d8734a668c Add workaround for pytest-capturelog bug.
This should fix the tests on Windows.
See https://bitbucket.org/memedough/pytest-capturelog/issues/7/
2015-08-18 08:06:41 +02:00
Florian Bruhin
1e08a6a202 Fix message box tests on OS X.
From the QMessageBox::setWindowTitle docs:

     On Mac OS X, the window title is ignored (as required by the Mac OS X
     Guidelines).
2015-08-17 23:37:55 +02:00
Florian Bruhin
5d013a67a7 100% coverage for misc.msgbox. 2015-08-17 23:18:52 +02:00
Florian Bruhin
9892c10f49 100% test coverage for utils.error. 2015-08-17 23:18:39 +02:00
Florian Bruhin
127514f719 Document cmdutils.aliases attribute. 2015-08-17 21:13:23 +02:00
Florian Bruhin
9cd2f6ba24 100% test coverage for commands.cmdutils. 2015-08-17 21:13:13 +02:00
Florian Bruhin
00d81a74c2 100% coverage for browser.network.networkreply. 2015-08-17 07:16:33 +02:00
Florian Bruhin
7ce78bb560 networkreply: Add is{Running,Finished} methods. 2015-08-17 07:15:55 +02:00
Florian Bruhin
e909b1f36d Fix lint. 2015-08-16 23:20:30 +02:00
Florian Bruhin
dff4c37f54 100% test coverage for browser/signalfilter.py. 2015-08-16 23:16:13 +02:00
Florian Bruhin
75fd97f74f Skip command test. 2015-08-16 22:51:00 +02:00
Florian Bruhin
6656e6aa9b Merge branch 'command-tests' 2015-08-16 22:48:07 +02:00
Florian Bruhin
8655ebc9c8 Ignore "load glyph failed" message in JS test. 2015-08-16 22:00:21 +02:00
Florian Bruhin
641c09c011 Don't skip freezing qutebrowser/html for tests. 2015-08-16 21:48:41 +02:00
Florian Bruhin
044a63d4a4 Skip all GUIProcess tests on Windows. 2015-08-16 21:46:36 +02:00
Florian Bruhin
a463811940 Include BeautifulSoup4 in freeze_tests.py. 2015-08-16 21:41:30 +02:00
Florian Bruhin
ffe6411a5a Fix asciidoc2html path in build_release.py. 2015-08-16 21:30:12 +02:00
Florian Bruhin
83efd6c33f Merge branch 'dirbrowser' 2015-08-16 21:24:10 +02:00
Florian Bruhin
38ada881a3 Update changelog. 2015-08-16 21:21:15 +02:00
Florian Bruhin
023fa54cda Update authors. 2015-08-16 21:01:31 +02:00
Florian Bruhin
6b19a7b1fa Also include img directory when freezing. 2015-08-16 20:59:20 +02:00
Florian Bruhin
d94c1736db Use a proper file:// URL for dirbrowser icons. 2015-08-16 19:04:54 +02:00
Florian Bruhin
97a9255400 Don't import function in filescheme. 2015-08-16 18:52:54 +02:00
Florian Bruhin
a6c104f0ef Fix TestFileSchemeHandler.test_dir on Windows. 2015-08-16 18:28:46 +02:00
Florian Bruhin
2a0dd341de Move test_filescheme.py. 2015-08-16 18:27:26 +02:00
Florian Bruhin
fb1cffd158 Merge branch 'feature/directory-browser' of git://github.com/antoyo/qutebrowser into dirbrowser 2015-08-16 18:27:05 +02:00
Florian Bruhin
3bfd049a0a Don't use inspect.getfullargspec().
It seems to be deprecated in Python 3.5.
2015-08-16 15:43:28 +02:00
Florian Bruhin
402f9be7e9 Fix lint. 2015-08-16 15:40:26 +02:00
Florian Bruhin
9eca7ae556 appveyor: Fix Python path in registry.
This makes PyQt pick up C:\Python34 correctly so we can use the newer AppVeyor
image again.

See https://github.com/appveyor/ci/issues/363.
2015-08-16 11:14:40 +02:00
Florian Bruhin
952893d984 appveyor: Update to PyQt 5.5 and self-host it. 2015-08-16 11:13:05 +02:00
Florian Bruhin
f08d871c24 appveyor: Force using previous image.
See https://github.com/appveyor/ci/issues/363
2015-08-16 00:10:09 +02:00
Florian Bruhin
f2c8ff8aa5 Remove .exe. 2015-08-15 19:19:03 +02:00
Florian Bruhin
36a9b816a7 *Really* fix path in ci_install.py 2015-08-15 19:08:22 +02:00
Antoni Boucher
149ca68853 Renamed setReferer to set_referer. 2015-08-14 08:24:13 -04:00
Florian Bruhin
5d0ffcd14d Whoops. 2015-08-14 07:56:51 +02:00
Florian Bruhin
d8a49f95a8 Really fix .eslintrc for eslint v1.0.0. 2015-08-14 07:55:52 +02:00
Florian Bruhin
2405bf1984 ci_install: Fix path. 2015-08-14 07:42:51 +02:00
Florian Bruhin
fb48059ae9 ci_install: Don't use os.system. 2015-08-14 07:17:53 +02:00
Florian Bruhin
515c9611c4 ci_install: Hopefully fix checking setup. 2015-08-14 07:14:13 +02:00
Florian Bruhin
d20f7b76b3 Fix .eslintrc for newest version. 2015-08-14 07:01:02 +02:00
Florian Bruhin
3bfcfaba4c Fix lint. 2015-08-14 06:59:36 +02:00
Florian Bruhin
4dee427f0e ci_install: Check setup after installing. 2015-08-14 06:55:31 +02:00
Florian Bruhin
433bdbfedb Don't cache .tox on CIs.
I think this makes things slower rather than faster.
2015-08-14 06:44:08 +02:00
Florian Bruhin
11502b7942 signalfilter: Remove annoying type check. 2015-08-14 06:43:23 +02:00
Antoni Boucher
abeb7e3390 Fixed issues. 2015-08-13 19:54:23 -04:00
Florian Bruhin
bbb581eaf8 100% coverage for browser.network.filescheme. 2015-08-13 21:56:22 +02:00
Florian Bruhin
b3df642b21 Write more tests for dirbrowser. 2015-08-13 21:56:22 +02:00
Florian Bruhin
7b3de27b44 Add class="parent" in dirbrowser.html. 2015-08-13 21:56:22 +02:00
Florian Bruhin
8450093de0 Install BeautifulSoup4 for tests. 2015-08-13 21:56:22 +02:00
Antoni Boucher
814841200a Fixes issues. 2015-08-12 17:24:01 -04:00
Antoni Boucher
77190554cc Merge branch 'master' into feature/directory-browser 2015-08-12 16:57:45 -04:00
Florian Bruhin
591a5b8c56 Revert "Add a workaround for pytest-html surrogate issue."
This reverts commit 9c3c46f677.
2015-08-12 18:27:41 +02:00
Florian Bruhin
72e5df0d57 tox: Update pytest-html to 1.4.
Changelog:

* Drop support for pytest < 2.6
* Mention support for PyPy3 in README
* Fix unencodable strings for Python 3.
2015-08-12 18:24:13 +02:00
Florian Bruhin
3e2aafa7a4 Merge branch 'acogneau-dynamic-column-widths' 2015-08-12 18:22:14 +02:00
Florian Bruhin
313f37bbc9 Update authors. 2015-08-12 18:22:03 +02:00
Florian Bruhin
6f4141956b Merge branch 'dynamic-column-widths' of git://github.com/acogneau/qutebrowser into acogneau-dynamic-column-widths 2015-08-12 18:20:01 +02:00
Alexander Cogneau
5a0b160736 Remove whitespace 2015-08-12 11:02:41 +02:00
Alexander Cogneau
61a6b196e9 Parametrization for completion tests 2015-08-12 09:08:04 +02:00
Florian Bruhin
25b43d528c Clear textbase text properly. 2015-08-12 07:41:06 +02:00
Florian Bruhin
5a1663c584 100% coverage for mainwindow.statusbar.textbase. 2015-08-12 07:40:45 +02:00
Florian Bruhin
8609663f40 statusbar.textbase: Fix broken text check. 2015-08-12 07:35:24 +02:00
Florian Bruhin
c1484553c1 Remove unused import. 2015-08-12 07:07:28 +02:00
Florian Bruhin
c55cb5b16b config.textwrapper: Remove *args.
textwrap.TextWrapper only takes kwargs.
2015-08-12 07:01:21 +02:00
Florian Bruhin
927cf84e14 100% test coverage for config.textwrapper. 2015-08-12 07:01:21 +02:00
Florian Bruhin
e47e131d41 Increase timeouts in test_timer.
Seems like Windows is too slow...
2015-08-12 06:53:34 +02:00
Florian Bruhin
a6d09b8cc9 100% coverage for browser.network.schemehandler. 2015-08-12 06:53:32 +02:00
Florian Bruhin
da9cb88c81 pylint: Disable protected-access for tests. 2015-08-12 06:44:33 +02:00
Florian Bruhin
83a4451f93 Skip coverage checks when tests are filtered. 2015-08-12 06:42:05 +02:00
Florian Bruhin
cf45d97046 100% test coverage for utils.jinja. 2015-08-12 06:25:05 +02:00
Florian Bruhin
4d7949cc3e Merge branch 't-wissmann-gt' 2015-08-11 22:01:35 +02:00
Florian Bruhin
637325fc76 Regenerate docs. 2015-08-11 22:01:26 +02:00
Florian Bruhin
44f8cf4b1a Move :tab-focus documentation. 2015-08-11 22:01:18 +02:00
Florian Bruhin
705544cb05 Merge branch 'gt' of git://github.com/t-wissmann/qutebrowser into t-wissmann-gt 2015-08-11 21:58:53 +02:00
Florian Bruhin
15c5cf75cd Reset standarddir args in TestConfigInit.
This caused test_transform_userstylesheet_base64 to fail.
2015-08-11 21:22:36 +02:00
Florian Bruhin
9c3c46f677 Add a workaround for pytest-html surrogate issue.
See https://github.com/davehunt/pytest-html/issues/12
2015-08-11 21:11:28 +02:00
Florian Bruhin
6b6bceeb3a tox: Run all tests in one step.
This partially reverts 14545a3 - this didn't play nice with HTML and other
things.
2015-08-11 21:05:20 +02:00
Florian Bruhin
8d8b46604d Fix htmlcov path in .gitignore. 2015-08-11 20:18:24 +02:00
Florian Bruhin
8351c6d951 Make TestFileAndUserStyleSheet use qapp fixture. 2015-08-11 20:16:19 +02:00
Florian Bruhin
88d92db6e4 Add tests for rel. inexistent File/UserStyleSheet. 2015-08-11 20:15:55 +02:00
Florian Bruhin
2980bc808e Remove dead configtypes code. 2015-08-11 20:15:42 +02:00
Florian Bruhin
14545a3a22 Mark some tests as integration and no coverage. 2015-08-11 19:36:27 +02:00
Florian Bruhin
dc2b3ff433 tox: Update hypothesis-pytest to 0.17.0.
Changelog:

- Mark tests with 'hypothesis' if they use hypothesis.
2015-08-11 19:35:59 +02:00
Florian Bruhin
0f47ef17a5 Make test_get_all_objects_qapp work on Windows. 2015-08-11 19:29:30 +02:00
Florian Bruhin
68f80c602b Remove unused import. 2015-08-11 19:23:29 +02:00
Florian Bruhin
55bf555bfb Mark test_get_all_objects_qapp to use qapp. 2015-08-11 18:14:17 +02:00
Florian Bruhin
52ec9ed28f Move get_all_objects() tests into a class. 2015-08-11 18:13:02 +02:00
Florian Bruhin
bf156cf554 Mock out QApplication.allWidgets.
This could return widgets which are still alive from previous tests, so it's
not reliable.
2015-08-11 18:08:11 +02:00
Florian Bruhin
5bd55cb38b Fix lint. 2015-08-11 17:16:58 +02:00
Florian Bruhin
2d8aaecd65 100% coverage for utils.debug. 2015-08-11 17:11:00 +02:00
Florian Bruhin
64a1cad604 Clean up output for get_all_objects(). 2015-08-11 17:10:41 +02:00
Florian Bruhin
a3a7f8936b Allow to pass a root object to _get_pyqt_objects. 2015-08-11 17:10:02 +02:00
Florian Bruhin
307bbde6e0 Fix utils.debug.get_all_children(). 2015-08-11 17:09:20 +02:00
Florian Bruhin
0ca96740c9 Make utils.debug.format_args public. 2015-08-11 17:08:49 +02:00
Florian Bruhin
caedb57e56 Fix staticMetaObject handling in qenum_key.
Since we checked on klass instead of base, there never was a staticMetaObject
and the slower path was always taken - this corrects that.
2015-08-11 17:07:47 +02:00
Florian Bruhin
10298e9692 Don't connect destroyed signal in log_signals.
This causes weird segfaults and is probably not that important to log.

Fixes #867.
2015-08-11 14:29:06 +02:00
Thorsten Wißmann
da6d12a657 Make tab-focus (bound to gt) behave as in VIM
If no count or index is given, tab-focus switches to the next tab (using
tab-next internally). So the keychain gt behaves as gt in vim:

  - gt focuses the next tab
  - 1gt focuses the first tab
  - <n>gt (e.g. 5gt) focuses the n'th (e.g. fith) tab
2015-08-11 10:50:27 +02:00
Alexander Cogneau
8be433f5f6 Add tests:
- sum of column widths equals 100
- column widths tuple has 3 elements
2015-08-11 09:17:46 +02:00
Florian Bruhin
7412e4f723 Reorganize utils.debug tests and add some more. 2015-08-11 07:41:25 +02:00
Florian Bruhin
dcaae51b4f Remove unused imports. 2015-08-10 19:47:19 +02:00
Florian Bruhin
c8679d6544 100% coverage for browser.webelem. 2015-08-10 19:37:16 +02:00
Florian Bruhin
9cabd4828c Skip test_guiprocess:test_error on Windows.
Waiting for a new pytest-qt release which will probably fix this.
2015-08-10 06:54:11 +02:00
Florian Bruhin
69f6822c82 Fix lint. 2015-08-09 23:09:48 +02:00
Florian Bruhin
2fe1bcfc2b Skip GUI tests when no DISPLAY is available.
Fixes #851.
2015-08-09 22:45:24 +02:00
Florian Bruhin
fbf53168c2 Skip test_double_start_finished on Windows.
It seems the process sometimes crashes...
2015-08-09 20:00:45 +02:00
Florian Bruhin
c4ebfcd4b3 Make tests fail on unexpected logging messages. 2015-08-09 20:00:36 +02:00
Florian Bruhin
aed915b1ec Handle invalid URLs when checking for same domain.
The old code only checked whether current_url is invalid, but the request URL
can be invalid as well, e.g. on http://www.playstation.com/

/cc @Carpetsmoker
2015-08-09 18:52:11 +02:00
Florian Bruhin
fe3eb30892 Reorganize exceptions in urlutils.
- Instead of ValueError, a new InvalidUrlError is raised with invalid URLs.
- FuzzyUrlError got removed as it's basically the same as InvalidUrlError.
2015-08-09 18:48:32 +02:00
Antoni Boucher
052d18147e Added permission check. 2015-08-09 11:29:18 -04:00
Florian Bruhin
0bf651d1fa tox: Don't run smoke environment. 2015-08-09 11:35:34 +02:00
Antoni Boucher
4d2a55190f Removed useless method. 2015-08-08 19:52:13 -04:00
Antoni Boucher
0896d7a8b3 Fixed file scheme handler. 2015-08-08 19:45:00 -04:00
Antoni Boucher
e5779d0775 Fixed tests. 2015-08-08 19:32:47 -04:00
Alexander Cogneau
753f87aa15 Default size of third column is now 0 2015-08-08 23:49:54 +02:00
Florian Bruhin
a28b0c3386 Remove .coverage.ini in check_coverage.py. 2015-08-08 22:55:37 +02:00
Florian Bruhin
409b32122e Don't generate HTML coverage by default. 2015-08-08 22:55:13 +02:00
Florian Bruhin
3179455e69 Reorganize tox.ini.
- The environment to run unittests is now called py34 as that's the common
  thing used, and will also allow us to run the tests with Python 3.5.
- The default tests now also run coverage.py and do a coverage check (on
  Linux).
- The smoke tests are now part of the default environment.
2015-08-08 22:33:29 +02:00
Antoni Boucher
a6010e3ead Renamed test function. 2015-08-08 15:40:18 -04:00
Antoni Boucher
14ae308279 Added a file:// scheme. 2015-08-08 15:16:48 -04:00
Antoni Boucher
7e20d77bdf Switch to SVG file and tango theme. 2015-08-08 14:43:55 -04:00
Antoni Boucher
2969599390 Use toLocalFile function instead of slicing. 2015-08-08 14:13:09 -04:00
Antoni Boucher
e6521b047d Added get_file_list function and tests. 2015-08-08 14:10:27 -04:00
Antoni Boucher
b8809f879d Added resource_filename function and tests. 2015-08-08 13:47:47 -04:00
Antoni Boucher
ec5049f801 Renamed url to urlstring. 2015-08-08 13:32:04 -04:00
Alexander Cogneau
e29c642bc2 Fix wrong propertyname 2015-08-08 18:12:51 +02:00
Alexander Cogneau
f2c3cc6a3e Module import of completion.models instead of class. 2015-08-08 17:58:12 +02:00
Alexander Cogneau
0e9f268817 CompletionView:
- column_widths -> _column_widths
- removed if-statement to verify if source model has 'column_widths'-property
2015-08-08 17:47:18 +02:00
Alexander Cogneau
36372418ca Added the default column_widths as a class attribute instead of a config option. 2015-08-08 17:27:21 +02:00
Alexander Cogneau
5c2d3ec96a Add a column_widths property to the base class for completion models. 2015-08-08 16:46:57 +02:00
Florian Bruhin
06a82c5566 Merge branch 'Kingdread-master' 2015-08-08 15:16:24 +02:00
Florian Bruhin
cef0ac46b8 Update authors. 2015-08-08 15:16:18 +02:00
Florian Bruhin
f76b741fb6 Merge branch 'master' of git://github.com/Kingdread/qutebrowser into Kingdread-master 2015-08-08 15:14:08 +02:00
Florian Bruhin
f6c1d355e3 Pass $HOME to misc testenv.
Fixes #860.
2015-08-08 14:46:23 +02:00
Daniel
bb6d6e51f6 Remove trailing whitespace in test_urlutils.py 2015-08-08 13:59:43 +02:00
Daniel
72c65a812f Move incdec_number tests to own class
and add tests for numbers in anchors
2015-08-08 13:47:17 +02:00
Antoni Boucher
2be0743378 Added images. 2015-08-07 23:28:24 -04:00
Antoni Boucher
fedf9d9c72 Replaced tuple by dict. 2015-08-07 22:45:54 -04:00
Antoni Boucher
4c1ed35390 Removed .. when in root folder. 2015-08-07 22:45:42 -04:00
Antoni Boucher
c8d3fc57c2 First version of directory browser. 2015-08-07 21:57:19 -04:00
Daniel
c3f6246274 Update CHANGELOG.asciidoc 2015-08-08 00:57:17 +02:00
Daniel
9e98ab181a Add URL validity check + tests to incdec_number 2015-08-08 00:57:16 +02:00
Daniel
c4c3a83ac0 rename url_incdec_number to incdec_number 2015-08-08 00:57:16 +02:00
Florian Bruhin
0acbd77ada Merge branch 'acogneau-launch_time' 2015-08-07 22:32:30 +02:00
Florian Bruhin
6e52b789ed Update authors. 2015-08-07 22:32:20 +02:00
Florian Bruhin
3a85afe1f4 Use datetime.ctime(). 2015-08-07 22:32:02 +02:00
Alexander Cogneau
43e0ac1844 Fix PEP8: line too long warning 2015-08-07 21:15:40 +02:00
Alexander Cogneau
73c28c12f3 Show launch time in crash logs. 2015-08-07 20:36:38 +02:00
Daniel
276b163e0d Move logic from _navigate_incdec to urlutils
Also add unittests for url_incdec_number
2015-08-07 18:48:07 +02:00
Daniel
0f3aa0bd8c Ctrl-A only increments number in path segment
This prevents a host like "myfoo42.bar" changing to "myfoo43.bar" when
pressing Ctrl-A. It further prevents increasing the port number, e.g.
going from "foo.bar:8080" to "foo.bar:8081".
2015-08-07 17:21:18 +02:00
Florian Bruhin
5f122759db Fix config migration for tab position values. 2015-08-07 11:59:31 +02:00
Florian Bruhin
cdd53a4515 tox: Set PYTEST_QT_API. 2015-08-07 06:38:12 +02:00
Florian Bruhin
01f71e980d pylint: Ignore import-error. 2015-08-07 00:16:37 +02:00
Florian Bruhin
8aab87e2a2 Don't readd capturelog handler after log tests.
I don't really know why, but doing that ends up with something calling
sys.stdout.close()...

Fixes #856.
2015-08-07 00:09:02 +02:00
Florian Bruhin
aa1ea9b063 Use parametrization for TestKeyToString:test_all.
This generates a lot of tests, but is more stable than our current approach.
2015-08-06 23:43:53 +02:00
Florian Bruhin
5ccdec4162 Add comment about Qt warning. 2015-08-06 23:19:05 +02:00
Florian Bruhin
2ab7ad59ee Fix lint. 2015-08-06 21:19:36 +02:00
Florian Bruhin
87af685f26 Merge branch 'Carpetsmoker-favicon-size-2' 2015-08-06 21:14:34 +02:00
Florian Bruhin
3f445ba6ca Draw favicon at correct position/size. 2015-08-06 21:14:05 +02:00
Florian Bruhin
58a9677af8 Use QFontMetrics instead of QFontInfo. 2015-08-06 21:11:06 +02:00
Florian Bruhin
e1c2250690 Merge branch 'favicon-size-2' of git://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-favicon-size-2 2015-08-06 21:10:11 +02:00
Florian Bruhin
61df5fcd7a Skip confirm-quit checks when crashing.
Fixes #853.
2015-08-06 21:08:20 +02:00
Florian Bruhin
9c6782be1d downloads: Fix size calculation with unknown size.
Fixes #854.
2015-08-06 21:00:36 +02:00
Florian Bruhin
36adaf0cf3 Simplify argument handling for DownloadManager. 2015-08-06 20:41:57 +02:00
Florian Bruhin
5fca27ad02 Fix :adblock-update. 2015-08-06 20:38:51 +02:00
Florian Bruhin
46f3be2df7 Add rapid hinting for downloads. 2015-08-06 19:09:21 +02:00
Florian Bruhin
c715b24bd3 Merge branch 'acogneau-master' 2015-08-06 19:00:02 +02:00
Florian Bruhin
55d282dadb Regenerate docs. 2015-08-06 18:58:09 +02:00
Florian Bruhin
a34fa93f62 Wrap long lines. 2015-08-06 18:50:32 +02:00
Alexander Cogneau
8ffe73cc5f Change 'prompt-download-location' setting to 'prompt-download-directory' 2015-08-06 16:44:58 +02:00
Alexander Cogneau
9a85b66452 Merge remote-tracking branch 'upstream/master' 2015-08-06 16:40:48 +02:00
Florian Bruhin
c6c14e967d Change Position conftypes to top/bottom/left/right. 2015-08-05 23:37:51 +02:00
Martin Tournoij
58aa1a738d Favicon sizing is messed up with tabs -> orientation = west #847
Here's a patch which seems to work well in my initial testing. We now use the
font size rather than the tabbar size, since the tabbar size is the window
size when it's vertical.

This also works nicer with the new tabs.padding setting (which didn't exist when
I wrote the first patch).
2015-08-05 22:33:24 +02:00
Florian Bruhin
4314b96512 Update changelog. 2015-08-05 18:30:31 +02:00
Florian Bruhin
5a25f0b98b Don't crash on :completion-item-del with no item.
If :completion-item-del was invoked with no item selected (e.g. directly after
pressing 'o'), there was a crash because the currentIndex was invalid.

/cc @antoyo (but I believe one of my changes on top of yours caused this)
2015-08-05 06:55:39 +02:00
Florian Bruhin
15e854237e Fix exception on ":set -p foo bar!". 2015-08-04 23:50:43 +02:00
Florian Bruhin
d59fa24fd5 Update README for Qt 5.5. 2015-08-04 23:16:30 +02:00
Florian Bruhin
0a16f29bd1 Hexlify strings in TestJavascriptEscape. 2015-08-04 23:12:40 +02:00
Alexander Cogneau
3fa99abca8 Merge remote-tracking branch 'upstream/master' 2015-08-04 16:32:31 +02:00
Alexander Cogneau
e43a1e6444 Added config option for prompting the user for a download location. 2015-08-04 16:30:55 +02:00
Florian Bruhin
0ce9ae070c Add some more test cases for TestJavascriptEscape. 2015-08-04 12:42:49 +02:00
Florian Bruhin
93d27cbb5f Escape 0x00 in javascript_escape().
This is needed in older PyQt-versions.
2015-08-04 12:42:49 +02:00
Florian Bruhin
8ac48699f2 tox: Update hypothesis/hypothesis-pytest. 2015-08-04 12:00:23 +02:00
Florian Bruhin
7e7fe9b4ce Simplify utils.qualname, take two. 2015-08-04 10:39:34 +02:00
Florian Bruhin
c67f7b6b21 Simplify utils.qualname. 2015-08-04 10:36:35 +02:00
Florian Bruhin
c4183bc34d tox: Update hypothesis/hypothesis-pytest to 1.10.0
This is just a bugfix and performance release, but it changes some semi-public
APIs, hence the minor version bump.

- Significant performance improvements for strategies which are one_of() many
  branches. In particular this included recursive() strategies. This should
  take the case where you use one recursive() strategy as the base strategy of
  another from unusably slow (tens of seconds per generated example) to
  reasonably fast.
- Better handling of just() and sampled_from() for values which have an
  incorrect __repr__ implementation that returns non-ASCII unicode on Python 2.
- Better performance for flatmap from changing the internal morpher API to be
  significantly less general purpose.
- Introduce a new semi-public BuildContext/cleanup API. This allows strategies
  to register cleanup activities that should run once the example is complete.
  Note that this will interact somewhat weirdly with find.
- Better simplification behaviour for streaming strategies.
- Don't error on lambdas which use destructuring arguments in Python 2.
- Add some better reprs for a few strategies that were missing good ones.
- The Random instances provided by randoms() are now copyable.
- Slightly more debugging information about simplify when using a debug
  verbosity level.
- Support using given for functions with varargs, but not passing arguments to
  it as positional.
2015-08-03 23:40:39 +02:00
Florian Bruhin
43266ac08a webelem: Add more tests. 2015-08-03 23:40:39 +02:00
Florian Bruhin
ed4fc4d1ba webelem: Escape \r in javascript_escape(). 2015-08-03 23:22:23 +02:00
Florian Bruhin
8011cefea6 webelem: Check if elem is writable with roles. 2015-08-03 23:21:45 +02:00
Florian Bruhin
0330adf284 Filter links for :navigate. 2015-08-03 23:21:20 +02:00
Florian Bruhin
21a60f06c0 webelem: Add <link> element to all/prevnext. 2015-08-03 23:17:54 +02:00
Florian Bruhin
030678602a tox: Update astroid to 1.3.8.
Changelog:

Filter out YES nodes when creating a temporary class for the with_metaclass
hack.

Having an YES node in a class bases will lead to a crash with a TypeError
when trying to obtain the ancestors of the given class, because .ancestors()
will try to iterate each inferred node from the bases, thus will try to
iterate over an YES node.
2015-08-03 10:38:42 +02:00
Florian Bruhin
c0941ab19b Add more webelem tests. 2015-08-02 23:49:22 +02:00
Florian Bruhin
c6a5731560 webelem: Check for vanished elem in is_text_input. 2015-08-02 23:49:22 +02:00
Florian Bruhin
3d9e4817f2 webelem: Fix return value for is_editable(). 2015-08-02 23:49:12 +02:00
Florian Bruhin
5c58641c81 Fix WebElementWrapper.__delitem__. 2015-08-02 23:47:35 +02:00
Florian Bruhin
b850df090b Add branch coverage workaround for coverage.py bug. 2015-08-02 23:47:22 +02:00
Florian Bruhin
b93b32c92f tox: Ignore test modules for pep257. 2015-08-02 22:16:03 +02:00
Florian Bruhin
982a6eccfb Rewrite tests/browser/test_webelem.py for pytest. 2015-08-02 22:16:03 +02:00
Florian Bruhin
d23096f898 Also check opposite condition in check_coverage.py. 2015-08-02 22:16:03 +02:00
Florian Bruhin
7d284fa575 pylint: Disable missing-docstring for tests. 2015-08-02 20:38:13 +02:00
Florian Bruhin
e5056e1c43 100% coverage for mainwindow.statusbar.tabindex. 2015-08-02 20:34:37 +02:00
Florian Bruhin
2190316b27 Fix lint. 2015-08-02 20:16:28 +02:00
Florian Bruhin
f8f03ea99d Small simplification in test_http_hypothesis. 2015-08-02 20:14:39 +02:00
Florian Bruhin
07641830ae Add hypothesis tests for http/rfc6266.
See #830.
2015-08-02 20:12:10 +02:00
Florian Bruhin
6c0f523c89 100% coverage for browser.http and browser.rfc6266. 2015-08-02 19:52:43 +02:00
Florian Bruhin
62fde783be pylint: Increase maximum function name length. 2015-08-02 19:51:48 +02:00
Florian Bruhin
2c7dd5c60c rfc6266: Simplify ContentDisposition. 2015-08-02 19:48:31 +02:00
Florian Bruhin
25d1064aee Add some more rfc6266 tests. 2015-08-02 19:39:05 +02:00
Florian Bruhin
aaf90d0fe3 Simplify RFC6266 error handling. 2015-08-02 18:09:01 +02:00
Florian Bruhin
cd7c3ec3a4 Fix docstring for http module. 2015-08-02 17:20:21 +02:00
Florian Bruhin
33915b65cf Test getting path without Content-Disposition. 2015-08-02 17:19:20 +02:00
Florian Bruhin
b4a695d5b8 Strip trailing slash when getting path from URL. 2015-08-02 17:18:59 +02:00
Florian Bruhin
359b886ba0 Small style fixes for test_content_disposition. 2015-08-02 14:07:28 +02:00
Florian Bruhin
1d4bb8d8da 100% coverage for misc.split. 2015-08-02 13:42:01 +02:00
Florian Bruhin
b2cb9d6d46 Add hypothesis tests for misc.split.
See #830.
2015-08-02 13:40:24 +02:00
Florian Bruhin
59460035c5 Add simple_split() test with maxsplit=0 and keep. 2015-08-02 13:20:30 +02:00
Florian Bruhin
c13f7e5f78 Remove dead ShellLexer code. 2015-08-02 13:05:19 +02:00
Florian Bruhin
9318173dc8 Test ShellLexer with empty input. 2015-08-02 13:02:32 +02:00
Florian Bruhin
dc4d4e70e4 Make sure state is valid in ShellLexer. 2015-08-02 12:44:54 +02:00
Florian Bruhin
dacf2cace2 Fix line length. 2015-08-02 01:57:16 +02:00
Florian Bruhin
49f017c0dd Fix mainwindow.statusbar.percentage tests. 2015-08-02 01:53:30 +02:00
Florian Bruhin
d5888fea89 100% coverage for mainwindow.statusbar.progress. 2015-08-02 01:49:33 +02:00
Florian Bruhin
fc09d63eb1 Fix lint. 2015-08-02 01:46:32 +02:00
Florian Bruhin
f21cffd9b8 100% coverage for mainwindow.statusbar.percentage. 2015-08-02 01:45:19 +02:00
Florian Bruhin
43db9d4526 Fix wrong pyqtSlot type. 2015-08-02 01:42:16 +02:00
Florian Bruhin
e59f533f9d Add statusbar.keystring to 100% coverage modules. 2015-08-02 01:34:40 +02:00
Florian Bruhin
9db0e03f05 Add some tests for config.configexc. 2015-08-02 01:31:43 +02:00
Florian Bruhin
7f3070f793 Remove : for configexc.NoOptionError. 2015-08-02 01:29:24 +02:00
Florian Bruhin
e94e90baec Add some tests for config.configdata. 2015-08-02 00:59:13 +02:00
Florian Bruhin
26f6bb7d0d Rewrite browser.test_tabhistory.py for pytest. 2015-08-02 00:34:04 +02:00
Florian Bruhin
0e25f5c730 Use a fake process for test_cmd_args.
This should fix the teardown exception (because the message module is not
patched anymore) with the new pytest-qt version.
2015-08-01 23:50:29 +02:00
Florian Bruhin
0734d9f0de Merge branch 'artur-shaik-auto_show_tabbar' 2015-08-01 22:43:26 +02:00
Florian Bruhin
f89adc2873 Update docs. 2015-08-01 22:43:20 +02:00
Florian Bruhin
d8017a04a8 Mark old tabbar hide settings as removed. 2015-08-01 22:36:59 +02:00
Florian Bruhin
6b98158d64 Fix lint. 2015-08-01 22:21:08 +02:00
Artur Shaik
e58735f1d7 'Tabs show' recommendations applied. 2015-08-01 22:21:08 +02:00
Artur Shaik
b4d5f9e7a6 Tabs->show option.
Issue #771
Implemted common option for tab bar show strategy.
Options: always, never, multiple, switching.
2015-08-01 22:21:08 +02:00
Florian Bruhin
edf431f541 tox: Update pytest-qt to 1.5.1.
Upstream changelog:

Exceptions are now captured also during test tear down, as delayed events will
get processed then and might raise exceptions in virtual methods; this is
specially problematic in PyQt5.5, which changed the behavior to call abort by
default, which will crash the interpreter.
2015-08-01 21:42:04 +02:00
Florian Bruhin
2bd36e21f1 Merge branch 'download-cpu-2' 2015-08-01 14:57:47 +02:00
Florian Bruhin
8314f7f93c Fix referer handling with generic download NAMs.
This broke e.g. :adblock-update as tab_id for those is set to None.
2015-08-01 14:54:46 +02:00
Florian Bruhin
d6585202fd Remember the last used download directory.
Thanks to @Carpetsmoker for the original PR.

Closes #745.
Closes #37.
2015-08-01 14:19:06 +02:00
Florian Bruhin
68512ce2cd Ignore DBus warning message for tests. 2015-08-01 13:50:39 +02:00
Florian Bruhin
52d7a5693f Fix lint. 2015-08-01 13:47:42 +02:00
Florian Bruhin
fa131e3290 Test urlutils.same_domain with invalid URLs. 2015-08-01 13:45:51 +02:00
Florian Bruhin
9041d6bdfc Merge branch 'Carpetsmoker-referer-header' 2015-08-01 13:36:02 +02:00
Florian Bruhin
2130f01ec7 Regerate docs. 2015-08-01 13:35:54 +02:00
Florian Bruhin
81c3c2d15f Small config rewording. 2015-08-01 13:35:30 +02:00
Florian Bruhin
44d109ca92 Save referer-header setting. 2015-08-01 13:32:11 +02:00
Florian Bruhin
976f758da1 Fix getting of the current URL. 2015-08-01 13:29:25 +02:00
Florian Bruhin
336b7de6d4 Clean up same_domain tests. 2015-08-01 13:23:03 +02:00
Florian Bruhin
27f65be860 Merge branch 'referer-header' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-referer-header
Conflicts:
      tests/utils/test_urlutils.py
2015-08-01 12:44:57 +02:00
Florian Bruhin
cc66feac09 Handle invalid history timestamps. 2015-08-01 00:57:34 +02:00
Florian Bruhin
9167d3ef17 Regenerate docs. 2015-08-01 00:35:59 +02:00
Florian Bruhin
d04848ac19 Remove unused import. 2015-08-01 00:34:09 +02:00
Florian Bruhin
451477593f Always require 4 values for padding. 2015-08-01 00:34:09 +02:00
Florian Bruhin
22ae0c5bca Don't call setIconSize if height doesn't change. 2015-08-01 00:26:37 +02:00
Florian Bruhin
2c716dbf6c Merge branch 'favicon-size' of https://github.com/Carpetsmoker/qutebrowser into tabbar-height 2015-08-01 00:22:17 +02:00
Florian Bruhin
132d02e8ca Update changelog. 2015-08-01 00:20:03 +02:00
Florian Bruhin
78d2514087 Update docs. 2015-08-01 00:19:04 +02:00
Florian Bruhin
be88ba6f05 Remove indicator-space setting. 2015-07-31 18:55:17 +02:00
Florian Bruhin
fdcb69f5d4 Fix indicator layout and make it configurable. 2015-07-31 18:52:31 +02:00
Florian Bruhin
3083eaa27b Clean up TabBarStyle._tab_layout. 2015-07-31 18:06:01 +02:00
Florian Bruhin
41eb36148d Clean up TabBarStyle.drawControl. 2015-07-31 17:59:38 +02:00
Florian Bruhin
f5725ec11e Use a namedtuple for _tab_layout. 2015-07-31 17:49:18 +02:00
Florian Bruhin
754c31850b Add configurable tab padding. 2015-07-31 17:24:35 +02:00
Florian Bruhin
041315b65f Honour right-padding in tabs correctly. 2015-07-31 17:02:42 +02:00
Florian Bruhin
8dcf9fd963 Separate padding/icon padding for tab bar. 2015-07-31 16:55:48 +02:00
Florian Bruhin
776ace2d3f Clean up comparison. 2015-07-31 16:41:02 +02:00
Florian Bruhin
87d8bfd7a7 Use an enum for custom PixelMetrics. 2015-07-31 16:40:06 +02:00
Florian Bruhin
452c4115d3 Add a Padding config type. 2015-07-31 15:38:41 +02:00
Florian Bruhin
accd2399ed Merge branch 'antoyo-master' 2015-07-31 13:16:10 +02:00
Florian Bruhin
907a4b0e5e Add path assertion to check_coverage.py.
https://bitbucket.org/ned/coveragepy/issues/235 confuses me - maybe the XML
format will change in the future to not contain the full filename? To make
sure I'll add this assertion, then I'll notice.
2015-07-29 17:31:26 +02:00
Florian Bruhin
daa6f8276f tox: Update pytest-html to 1.3.2.
Changelog:

* Always include log but only display additional row if log has content or
  there's additional HTML
2015-07-29 17:03:30 +02:00
Florian Bruhin
7830e40cce tox: Update pytest-cov to 2.0.0.
Changelog:

* Added --cov-fail-under, akin to the new fail_under option in coverage-4.0
  (automatically activated if there's a [report] fail_under = ... in
  .coveragerc).
* Changed --cov-report=term to automatically upgrade to
  --cov-report=term-missing if there's [run] show_missing = True in
  .coveragerc.
* Changed --cov so it can be used with no path argument (in wich case the
  source settings from .coveragerc will be used instead).
* Fixed .pth installation to work in all cases (install, easy_install, wheels,
  develop etc).
* Fixed .pth uninstallation to work for wheel installs.
* Support for coverage 4.0.
* Data file suffixing changed to use coverage's data_suffix=True option
  (instead of the custom suffixing).
* Avoid warning about missing coverage data (just like
  coverage.control.process_startup).
* Fixed a race condition when running with xdist (all the workers tried to
  combine the files). It's possible that this issue is not present in
  pytest-cov 1.8.X.
2015-07-29 17:02:15 +02:00
Florian Bruhin
8be69e2f41 tox: Update hypothesis/-pytest to 1.9.0/0.14.0.
Changelog:

Codename: The great bundling.

This release contains two fairly major changes.

The first is the deprecation of the hypothesis-extra mechanism. From now on all
the packages that were previously bundled under it other than hypothesis-pytest
(which is a different beast and will remain separate). The functionality
remains unchanged and you can still import them from exactly the same location,
they just are no longer separate packages.

The second is that this introduces a new way of building strategies which lets
you build up strategies recursively from other strategies.

It also contains the minor change that calling .example() on a strategy object
will give you examples that are more representative of the actual data you'll
get. There used to be some logic in there to make the examples artificially
simple but this proved to be a bad idea.
2015-07-29 16:59:51 +02:00
Florian Bruhin
418c7e4a96 tox: Update astroid to 1.3.7.
Changelog:

Improved handling of the six package.
2015-07-29 16:55:53 +02:00
Florian Bruhin
3991cf9dc5 Update jinja2 to 2.8.0.
Changelog:

* Added target parameter to urlize function.
* Added support for followsymlinks to the file system loader.
* The truncate filter now counts the length.
* Added equalto filter that helps with select filters.
* Changed cache keys to use absolute file names if available instead of load
  names.
* Fixed loop length calculation for some iterators.
* Changed how Jinja2 enforces strings to be native strings in Python 2 to work
  when people break their default encoding.
* Added make_logging_undefined() which returns an undefined object that logs
  failures into a logger.
* If unmarshalling of cached data fails the template will be reloaded now.
* Implemented a block set tag.
* Default cache size was incrased to 400 from a low 50.
* Fixed is number test to accept long integers in all Python versions.
* Changed is number to accept Decimal as a number.
* Added a check for default arguments followed by non-default arguments. This
  change makes {% macro m(x, y=1, z) %}...{% endmacro %} a syntax error. The
  previous behavior for this code was broken anyway (resulting in the default
  value being applied to y).
* Add ability to use custom subclasses of jinja2.compiler.CodeGenerator and
  jinja2.runtime.Context by adding two new attributes to the environment
  (code_generator_class and context_class) (pull request #404).
* added support for context/environment/evalctx decorator functions on the
  finalize callback of the environment.
* escape query strings for urlencode properly. Previously slashes were not
  escaped in that place.
* Add ‘base’ parameter to ‘int’ filter.
2015-07-29 16:49:14 +02:00
Florian Bruhin
f323a54d8d Update changelog. 2015-07-29 15:19:37 +02:00
Florian Bruhin
57e79db136 Fix undefined name error. 2015-07-29 15:04:07 +02:00
Florian Bruhin
8f2a4fc0c4 Update changelog. 2015-07-29 12:49:30 +02:00
Florian Bruhin
6c5d1c96d2 Regenerate docs. 2015-07-29 12:46:33 +02:00
Florian Bruhin
d9d68db5df Simplify delete_cur_item for UrlCompletionModel. 2015-07-29 12:44:38 +02:00
Florian Bruhin
08fe1d59e6 Get rid of bookmark_by_title completion. 2015-07-29 12:36:45 +02:00
Florian Bruhin
69ade32cb9 Get rid of quickmark_by_name completion. 2015-07-29 12:35:43 +02:00
Florian Bruhin
c016e8a4cf Improve error handling for quick-/bookmarks. 2015-07-29 12:34:53 +02:00
Florian Bruhin
093b3cba25 Add a bookmark-del command. 2015-07-26 21:05:32 +02:00
Florian Bruhin
0acd1b8dc8 Use urlutils.fuzzy_url for loading bookmarks. 2015-07-26 18:52:15 +02:00
Florian Bruhin
b962fff7f1 Don't show message when deleting items. 2015-07-26 18:48:00 +02:00
Florian Bruhin
ecf3e166ff Fix wrong column attribute. 2015-07-26 18:47:02 +02:00
Florian Bruhin
660b5531e5 Share code between on_{quick,book}mark_removed. 2015-07-26 18:43:01 +02:00
Florian Bruhin
b5a9467b5c Get rid of _add_*mark_entry in urlmodel. 2015-07-26 18:35:49 +02:00
Florian Bruhin
2d2779d6f3 Clean up column handling in urlmodel. 2015-07-26 18:23:12 +02:00
Florian Bruhin
aaa523ce7c Filter by cols_to_filter in CompletionFilterModel. 2015-07-26 17:41:32 +02:00
Florian Bruhin
3b0125e8cd Rename cols_to_highlight to _filter and simplify. 2015-07-26 17:22:45 +02:00
Florian Bruhin
c7f88c93b2 Style fix. 2015-07-26 17:11:34 +02:00
Florian Bruhin
16ac877227 Add default keybindings for loading bookmarks.
See #13, #681.
2015-07-26 16:42:47 +02:00
Florian Bruhin
2f11b41ae6 Merge bookmarks and quickmarks into urlmarks. 2015-07-26 16:37:10 +02:00
Florian Bruhin
b52a41ac6f Merge branch 'master' of https://github.com/antoyo/qutebrowser into antoyo-master
Conflicts:
      .gitignore
2015-07-26 15:08:58 +02:00
Florian Bruhin
c750ff3f50 configtypes: Handle invalid format syntax. 2015-07-26 13:56:46 +02:00
Florian Bruhin
4bdf00b148 configtypes: Handle {1} correctly. 2015-07-26 13:56:40 +02:00
Florian Bruhin
94b0f92b75 Fix tabs -> last-close = ignore.
See #822 / 71fee12b5b.
Fixes #834.
2015-07-26 12:08:19 +02:00
Florian Bruhin
d0f1523363 tox: Update hypothesis to 1.8.5. 2015-07-24 19:15:07 +02:00
Florian Bruhin
b03fea26c2 Add hypothesis to frozen tests. 2015-07-24 18:43:08 +02:00
Florian Bruhin
cbed62cafc Remove dead code.
This will already be checked by WebKitBytes._basic_validation.
2015-07-24 18:23:17 +02:00
Florian Bruhin
694fbe053d Fix lint. 2015-07-24 18:11:52 +02:00
Florian Bruhin
44bf4ae883 configtypes: Fix ' ' value with Command. 2015-07-24 17:56:12 +02:00
Florian Bruhin
ebdfa0be73 Add hypothesis tests for configtypes.
See #830.
2015-07-24 17:49:09 +02:00
Florian Bruhin
b19852b6e7 configtypes: Add _basic_validation() to BaseType.
This has a few implications:

- Checking for empty/none_ok is now easier as _basic_validation() does this.
- To make things easier, WebKitBytes and WebKitBytesList now need to have
  none_ok passed as well instead of assuming True.
- _basic_validation() checks for unprintable chars and raises ValidationError
  if they occur, so this gets checked for all types.
2015-07-24 17:39:02 +02:00
Florian Bruhin
fd5a89dccd scripts: Use runcall in run_profile.py. 2015-07-24 15:01:18 +02:00
Florian Bruhin
f19ba277d6 tests: Ignore QProcess warning. 2015-07-24 14:56:00 +02:00
Florian Bruhin
d805e2d71e Don't use lxml for check_coverage.py.
See #792.
2015-07-24 14:42:18 +02:00
Florian Bruhin
1fd386e57e configtypes: Get rid of typestr.
Closes #819.
2015-07-24 14:18:41 +02:00
Florian Bruhin
7f2abd1a46 tests: Enforce 100% coverage for perfect modules.
Fixes #792.
2015-07-24 14:04:40 +02:00
Florian Bruhin
dfba381b57 Fix broken utils.version test. 2015-07-24 13:53:26 +02:00
Florian Bruhin
5643b14987 configtypes: Add test for UserAgent.complete(). 2015-07-24 00:29:12 +02:00
Florian Bruhin
cc6602926f configtypes: Add tests for ConfirmQuit. 2015-07-24 00:24:17 +02:00
Florian Bruhin
730a8afc6b configtypes: Handle empty values in ConfirmQuit 2015-07-24 00:24:04 +02:00
Florian Bruhin
b3755f4ca1 configtypes: Add tests for SessionName. 2015-07-24 00:11:52 +02:00
Florian Bruhin
e145d73852 configtypes: Add a MappingType base class. 2015-07-24 00:11:52 +02:00
Florian Bruhin
0b1704d829 configtypes: Add a __repr__ to ValidValues. 2015-07-24 00:11:52 +02:00
Florian Bruhin
8f48247b8f configtypes: Add invalid URL for TestUrlList. 2015-07-24 00:11:52 +02:00
Florian Bruhin
71188bb67b configtypes: Simplify UrlList tests. 2015-07-23 23:14:20 +02:00
Florian Bruhin
a558f666bc configtypes: Be case-insensitive for Position. 2015-07-23 23:13:58 +02:00
Florian Bruhin
e4c7e70aba configtypes: Fix ColorTests parametrization. 2015-07-23 21:33:16 +02:00
Florian Bruhin
488676e0e9 docs: Fix 'with with' typo. 2015-07-23 20:56:57 +02:00
Florian Bruhin
fb6bf5c34f configtypes: Change tests to not subclass. 2015-07-23 20:55:27 +02:00
Florian Bruhin
b35a1f3d15 pylint: Change maximum method name length to 50.
40 wasn't enough for tests.
2015-07-23 20:55:21 +02:00
Florian Bruhin
41333cd6e1 configtypes: Add some additional tests. 2015-07-23 16:58:21 +02:00
Florian Bruhin
eb28365d82 configtypes: Add os.path.join emulation to os_mock. 2015-07-23 16:57:42 +02:00
Florian Bruhin
85e748df4f configtypes: Add else-branch to QtFont. 2015-07-23 16:57:28 +02:00
Florian Bruhin
b0c3f5381b configtypes: Add none_ok param to UserStyleSheet. 2015-07-23 16:57:14 +02:00
Florian Bruhin
cf5296ebb5 configtypes: Fix broken Command test. 2015-07-23 14:41:29 +02:00
Florian Bruhin
75991e1f87 configtypes: Add tests for BoolAsk. 2015-07-23 14:41:14 +02:00
Florian Bruhin
8b9c8eb0bf Add .testmondata to .gitignore. 2015-07-23 14:12:26 +02:00
Florian Bruhin
c46abd8f89 Fix none_ok for RegexList and PercList. 2015-07-23 14:08:34 +02:00
Florian Bruhin
23583b7d48 Refactor test_configtypes for pytest. 2015-07-23 12:37:14 +02:00
Florian Bruhin
4007027617 pylint: Change minimal length for docstrings to 3. 2015-07-23 11:32:19 +02:00
Florian Bruhin
5e58d814d7 tox: Add a unittests-watch environment. 2015-07-23 11:31:51 +02:00
Florian Bruhin
88416db6a3 configtypes: Make none_ok work for IntList. 2015-07-21 15:20:23 +02:00
Florian Bruhin
073504abb4 configtypes: Make none_ok public. 2015-07-21 15:17:28 +02:00
Florian Bruhin
1b643ff55f Handle empty values for ConfirmQuit conftype. 2015-07-21 13:00:01 +02:00
Florian Bruhin
e81ac925d7 tox: Adjust pep257 comment. 2015-07-21 10:26:57 +02:00
Florian Bruhin
0b8c054dc1 tox: Update pep257 to 0.6.0
Upstream changelog:

New Features

* Added support for more flexible error selections using --ignore, --select,
  --convention, --add-ignore and --add-select (#96, #123).

Bug Fixes

* Property setter and deleter methods are now treated as private and do not
  require docstrings separate from the main property method (#69, #107).

* Fixed an issue where pep257 did not accept docstrings that are both
  unicode and raw in Python 2.x (#116, #119).

* Fixed an issue where Python 3.x files with Unicode encodings were
  not read correctly (#118).
2015-07-21 10:21:48 +02:00
Florian Bruhin
d942325c51 Add pytest.ini to MANIFEST.in. 2015-07-20 11:33:30 +02:00
Florian Bruhin
b670c45381 Fix typo. 2015-07-20 11:23:43 +02:00
Florian Bruhin
a02055414d Make command tests work. 2015-07-20 11:23:34 +02:00
Florian Bruhin
e5843ffcf6 Command tests WIP 2015-07-19 21:56:23 +02:00
Florian Bruhin
474255cbe7 Move pytest options to pytest.ini. 2015-07-19 21:56:09 +02:00
Florian Bruhin
43500b2e24 tox: Fix posargs handling for tests. 2015-07-19 21:54:51 +02:00
Florian Bruhin
95fb908b9b INSTALL: Add some text about native packages. 2015-07-17 06:57:48 +02:00
Florian Bruhin
fd57a20022 INSTALL: Use a common section for tox. 2015-07-17 06:55:03 +02:00
Florian Bruhin
5c5562af2d Merge branch 'zwarag-master' 2015-07-17 06:51:47 +02:00
Florian Bruhin
4c2393b61a Regenerate authors 2015-07-17 06:51:41 +02:00
Florian Bruhin
8f0266f06a Simplify Fedora version list. 2015-07-17 06:51:29 +02:00
Florian Bruhin
ce7d8a3041 Merge branch 'master' of https://github.com/zwarag/qutebrowser into zwarag-master 2015-07-17 06:50:42 +02:00
Florian Bruhin
bbea66c91f Merge branch 'Carpetsmoker-last-tab' 2015-07-17 06:40:14 +02:00
Florian Bruhin
5f10a12be9 Use keyword argument for newtab. 2015-07-17 06:39:53 +02:00
Florian Bruhin
ed1ba03c19 Merge branch 'last-tab' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-last-tab 2015-07-17 06:27:19 +02:00
Florian Bruhin
0972087e34 Add a note about #730 to :inspect docs.
Fixes #730.
2015-07-16 20:54:10 +02:00
Martin Tournoij
71fee12b5b Always remove the last tab, instead of opening a new page in it.
IMHO this makes much more sense; for example, if you close the last tab but then
press u to "undo" it, you'll actually load the second-last tab. To undo you need
H for "back". Other things like gC, session save, etc. also behave in a way that
is, IMHO, unexpected...

I can also make a new option out of this, if you prefer. But I don't think that
many people would expect the current behaviour...
2015-07-16 15:26:15 +02:00
Florian Bruhin
83572948ca Add (unreleased) for 0.4.0 to changelog. 2015-07-16 13:58:12 +02:00
Florian Bruhin
704c5ff919 Make :yank-selected work in all modes. 2015-07-16 13:57:53 +02:00
zwarag
5177e656b6 adding fedora installation guide 2015-07-15 12:30:23 +02:00
Florian Bruhin
63ff39ad65 Don't use 0 as window ID for early messages.
The issue here was that only message calls with 'current' as window get cached
if the window is unavailable. We used 0 instead.

Fixes #815.
See #812.
2015-07-14 18:36:12 +02:00
Florian Bruhin
60b6652006 Better output on errors. 2015-07-14 16:18:34 +02:00
Florian Bruhin
03383c48eb Use imported file paths in link_pyqt.py.
See #804.
2015-07-14 16:18:34 +02:00
Florian Bruhin
1a8afb95d3 Cache some things on CIs. 2015-07-14 07:17:36 +02:00
Florian Bruhin
c310156dde Fix lint. 2015-07-13 18:11:35 +02:00
Florian Bruhin
a3776e361b minimal_webkit_testbrowser: Add WebEngine support. 2015-07-13 17:39:09 +02:00
Antoni Boucher
91561e2c5b Fixed style. 2015-07-12 20:46:40 -04:00
Antoni Boucher
5bca951c21 Removed casefold() function call when using a custom filter. 2015-07-12 20:29:40 -04:00
Antoni Boucher
1b24cfd618 Removed useless bookmark by title model. 2015-07-12 20:21:49 -04:00
Antoni Boucher
d4c91f7b0c Fixed completion highlighting. 2015-07-12 20:18:32 -04:00
Antoni Boucher
523cb458a6 Merge remote-tracking branch 'upstream/master' 2015-07-12 10:52:32 -04:00
Florian Bruhin
ca5a78dfc7 Attach numerical code to Qt OSErrors and fix tests.
This fixes tests with tox < 2.0.0 on systems with a non-english locale, as it's
no longer the errorString which gets compared.

Fixes #806.
2015-07-12 11:20:28 +02:00
Antoni Boucher
5dbaea7a83 Fixed empty title. 2015-07-11 20:28:31 -04:00
Antoni Boucher
1e354a797e Removed useless checks. 2015-07-11 19:56:51 -04:00
Antoni Boucher
5e8129788a Removed try/except. 2015-07-11 19:48:46 -04:00
Antoni Boucher
96a2178a25 Renamed bookmark_del to delete in bookmark manager. 2015-07-11 19:23:21 -04:00
Antoni Boucher
4bc2f63608 Renamed bookmark_add to add in bookmark manager. 2015-07-11 19:18:45 -04:00
Antoni Boucher
8159c5f567 Fixed last merge. 2015-07-11 18:56:19 -04:00
Antoni Boucher
fe829699be Merge remote-tracking branch 'upstream/master' 2015-07-11 17:40:43 -04:00
Florian Bruhin
0037b0db7e Actually run OS X tests on OS X. 2015-07-11 20:14:26 +02:00
Florian Bruhin
3a95cd470a minimal_webkit_testbrowser: Add switch for plugins. 2015-07-10 21:36:53 +02:00
Florian Bruhin
55cf470436 minimal_webkit_testbrowser: Add some output. 2015-07-10 21:34:33 +02:00
Florian Bruhin
0be2192eab minimal_webkit_testbrowser: Use QUrl.fromUserInput. 2015-07-10 21:32:36 +02:00
Florian Bruhin
0bdcf2910a minimal_webkit_testbrowser: Use argparse. 2015-07-10 21:32:00 +02:00
Florian Bruhin
4905cfc7a5 tox: Update logilab-common to 1.0.2.
Upstream changelog:

* declare setuptools requirement in __pkginfo__/setup.py
* randomize order of test modules in pytest -t
2015-07-10 12:00:26 +02:00
Florian Bruhin
dbef785a94 tox: Update pytest-mock to 0.7.0. 2015-07-08 07:14:22 +02:00
Florian Bruhin
de91142880 tox: Add path to tests. 2015-07-06 18:50:34 +02:00
Florian Bruhin
e010d3dabc Fix os.path patching in tests.
Fixes #802.
2015-07-06 18:50:34 +02:00
Florian Bruhin
20ff7e702a Increase all test_guiprocess timeouts.
Windows is fscking slow...
2015-07-06 18:04:54 +02:00
Florian Bruhin
c5b75b0b16 Merge remote-tracking branch 'origin/usertypes-tests' 2015-07-06 17:58:13 +02:00
Florian Bruhin
382b52a0b8 Merge remote-tracking branch 'origin/urlutils-tests' 2015-07-06 17:58:06 +02:00
Florian Bruhin
cef350ad66 tests: Use multiple pytest.mark.parametrize calls. 2015-07-06 17:56:13 +02:00
Florian Bruhin
a0a7f9feda Skip test_file_absolute_expanded on Windows. 2015-07-06 17:22:52 +02:00
Florian Bruhin
3f13c2bd3e Add/improve tests for qutebrowser.utils.urlutils. 2015-07-06 17:10:57 +02:00
Florian Bruhin
e3fcc0e091 Move isabs check to top in fuzzy_url. 2015-07-06 17:10:24 +02:00
Florian Bruhin
47d5262cd9 Abort scrolling wen reaching min/max position.
Fixes #801.
2015-07-06 15:13:18 +02:00
Florian Bruhin
aa1bf00274 Add tests for usertypes.Question. 2015-07-06 14:34:47 +02:00
Florian Bruhin
11e88fbd12 Don't bind backspace by default.
Fixes #789.
2015-07-06 13:18:05 +02:00
Florian Bruhin
d232437105 Update to beautifulsoup 4.4.0.
Upstream changelog:

Especially important changes:

* Added a warning when you instantiate a BeautifulSoup object without
  explicitly naming a parser. [bug=1398866]

* __repr__ now returns an ASCII bytestring in Python 2, and a Unicode
  string in Python 3, instead of a UTF8-encoded bytestring in both
  versions. In Python 3, __str__ now returns a Unicode string instead
  of a bytestring. [bug=1420131]

* The `text` argument to the find_* methods is now called `string`,
  which is more accurate. `text` still works, but `string` is the
  argument described in the documentation. `text` may eventually
  change its meaning, but not for a very long time. [bug=1366856]

* Changed the way soup objects work under copy.copy(). Copying a
  NavigableString or a Tag will give you a new NavigableString that's
  equal to the old one but not connected to the parse tree. Patch by
  Martijn Peters. [bug=1307490]

* Started using a standard MIT license. [bug=1294662]

* Added a Chinese translation of the documentation by Delong .w.

New features:

* Introduced the select_one() method, which uses a CSS selector but
  only returns the first match, instead of a list of
  matches. [bug=1349367]

* You can now create a Tag object without specifying a
  TreeBuilder. Patch by Martijn Pieters. [bug=1307471]

* You can now create a NavigableString or a subclass just by invoking
  the constructor. [bug=1294315]

* Added an `exclude_encodings` argument to UnicodeDammit and to the
  Beautiful Soup constructor, which lets you prohibit the detection of
  an encoding that you know is wrong. [bug=1469408]

* The select() method now supports selector grouping. Patch by
  Francisco Canas [bug=1191917]

Bug fixes:

* Fixed yet another problem that caused the html5lib tree builder to
  create a disconnected parse tree. [bug=1237763]

* Force object_was_parsed() to keep the tree intact even when an element
  from later in the document is moved into place. [bug=1430633]

* Fixed yet another bug that caused a disconnected tree when html5lib
  copied an element from one part of the tree to another. [bug=1270611]

* Fixed a bug where Element.extract() could create an infinite loop in
  the remaining tree.

* The select() method can now find tags whose names contain
  dashes. Patch by Francisco Canas. [bug=1276211]

* The select() method can now find tags with attributes whose names
  contain dashes. Patch by Marek Kapolka. [bug=1304007]

* Improved the lxml tree builder's handling of processing
  instructions. [bug=1294645]

* Restored the helpful syntax error that happens when you try to
  import the Python 2 edition of Beautiful Soup under Python
  3. [bug=1213387]

* In Python 3.4 and above, set the new convert_charrefs argument to
  the html.parser constructor to avoid a warning and future
  failures. Patch by Stefano Revera. [bug=1375721]

* The warning when you pass in a filename or URL as markup will now be
  displayed correctly even if the filename or URL is a Unicode
  string. [bug=1268888]

* If the initial <html> tag contains a CDATA list attribute such as
  'class', the html5lib tree builder will now turn its value into a
  list, as it would with any other tag. [bug=1296481]

* Fixed an import error in Python 3.5 caused by the removal of the
  HTMLParseError class. [bug=1420063]

* Improved docstring for encode_contents() and
  decode_contents(). [bug=1441543]

* Fixed a crash in Unicode, Dammit's encoding detector when the name
  of the encoding itself contained invalid bytes. [bug=1360913]

* Improved the exception raised when you call .unwrap() or
  .replace_with() on an element that's not attached to a tree.

* Raise a NotImplementedError whenever an unsupported CSS pseudoclass
  is used in select(). Previously some cases did not result in a
  NotImplementedError.

* It's now possible to pickle a BeautifulSoup object no matter which
  tree builder was used to create it. However, the only tree builder
  that survives the pickling process is the HTMLParserTreeBuilder
  ('html.parser'). If you unpickle a BeautifulSoup object created with
  some other tree builder, soup.builder will be None. [bug=1231545]
2015-07-06 10:49:59 +02:00
Florian Bruhin
b127c7b069 Update changelog. 2015-07-02 22:11:12 +02:00
Florian Bruhin
4cef4012e5 Catch OSError when loading qute:help.
Fixes #763.
2015-07-02 22:08:41 +02:00
Florian Bruhin
1533108ca6 Add tests for usertypes.Timer. 2015-07-02 21:10:56 +02:00
Florian Bruhin
b02867fe37 Improve enum tests. 2015-07-02 21:10:56 +02:00
Florian Bruhin
4e0d00098c Improve NeighborList tests. 2015-07-02 21:10:56 +02:00
Martin Tournoij
aa909c0506 Fix warnings from tox 2015-07-02 21:07:38 +02:00
Florian Bruhin
2643975c3c tox: Update logilab-common to 1.0.1. 2015-07-02 21:05:48 +02:00
Florian Bruhin
4bddcd4c1a Remove (y/N) suffix from download questions. 2015-07-02 20:57:23 +02:00
Florian Bruhin
c32d80c7bc Merge branch 'Carpetsmoker-save-fifo' 2015-07-02 20:56:17 +02:00
Florian Bruhin
dc29ad430e Change how lines are split. 2015-07-02 20:51:51 +02:00
Florian Bruhin
d44f14063a Merge branch 'save-fifo' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-save-fifo 2015-07-02 19:16:04 +02:00
Florian Bruhin
ba7f7163e5 Merge branch 'radhermit-gentoo' 2015-06-30 23:28:48 +02:00
Florian Bruhin
9961ab383b Update authors 2015-06-30 23:28:38 +02:00
Tim Harder
0d8bc405e0 doc: update Gentoo install instructions 2015-06-30 16:57:32 -04:00
Florian Bruhin
ec1476da2e tox: Update pylint and logilab-common.
pylint changelog:

- Support for logilab-common 1.0.0

logilab-common changelog:

- remove unused/deprecated modules: cli, contexts, corbautils, dbf, pyro_ext,
  xmlrpcutils. __pkginfo__ is no longer installed.
- major layout change
- use setuptools exclusively
- 'logilab' is now a proper namespace package
- modutils: basic support for namespace packages
- registry: ambiguous selects now raise a specific exception
- testlib: better support for non-pytest launchers
- testlib: Tags() now work with py3k
2015-06-30 20:50:12 +02:00
Florian Bruhin
e6655cf66d tox: Add requirements.io filter for logilab-common.
pylint is broken with logilab-common=1.0.0 currently:

https://bitbucket.org/logilab/pylint/issue/575/support-for-logilabcommon-100
2015-06-30 17:58:53 +02:00
Martin Tournoij
2c5898b9f7 Rename variable to fix pylint warning:
https://travis-ci.org/The-Compiler/qutebrowser/jobs/68949925

	************* Module qutebrowser.browser.downloads
	W:299,36: Redefining name 'message' from outer scope (line 39) (redefined-outer-name)
2015-06-30 13:22:20 +02:00
Martin Tournoij
c7cd51a7d5 "Fix" silly pep8 warnings 2015-06-30 12:01:05 +02:00
Florian Bruhin
616c842f2f tox: Update pytest-qt to 1.5.0.
Upstream changelog:

* Fixed log line number in messages, and provide better contextual information
  in Qt5 (#55, thanks @The-Compiler);
* Fixed issue where exceptions inside a `waitSignals` or `waitSignal`
  with-statement block would be swallowed and a `SignalTimeoutError` would be
  raised instead. (#59, thanks @The-Compiler for bringing up the issue and
  providing a test case);
* Fixed issue where the first usage of `qapp` fixture would return `None`.
  Thanks to @gqmelo for noticing and providing a PR;
* New `qtlog` now sports a context manager method, `disabled` (#58). Thanks
  @The-Compiler for the idea and testing;
2015-06-29 23:16:42 +02:00
Florian Bruhin
b4e2b00437 Add sip to Debian install instructions. 2015-06-29 22:25:15 +02:00
Florian Bruhin
2dbc2b2c07 Merge branch 'jagajaga-update/install' 2015-06-29 20:17:17 +02:00
Florian Bruhin
5358ac60c8 Update authors 2015-06-29 20:17:10 +02:00
Florian Bruhin
1b6ce1b7f4 Remove trailing whitespace. 2015-06-29 20:16:27 +02:00
Arseniy Seroka
a4d8f3a012 doc: add nixos instructions 2015-06-29 19:03:45 +03:00
Florian Bruhin
7b8490b6c0 Fix 'an user' spelling. 2015-06-29 17:49:19 +02:00
Florian Bruhin
db06eeded5 Fix spelling. 2015-06-29 17:45:56 +02:00
Florian Bruhin
d845ecd7fc Also check for spelling errors in .asciidoc/.js files. 2015-06-29 17:44:36 +02:00
Florian Bruhin
1672399252 Remove unused imports. 2015-06-28 23:20:56 +02:00
Florian Bruhin
b673c39b62 tox: Fix script path for docs env. 2015-06-28 23:04:18 +02:00
Florian Bruhin
9b264c7514 tests: Share common markers. 2015-06-28 22:58:48 +02:00
Florian Bruhin
be3f61af62 Move developer scripts to dev/ subfolder.
Closes #783.
2015-06-28 22:31:30 +02:00
Florian Bruhin
e19efcf8a8 I accidentally the changelog. 2015-06-28 18:25:34 +02:00
Florian Bruhin
b3cd31a808 Release v0.3.0. 2015-06-28 12:45:58 +02:00
Martin Tournoij
63dae3a885 Set favicon size to tabbar size.
Fixes #119. Also see #754
2015-06-28 00:03:53 +02:00
Martin Tournoij
d114e64b05 Ask question instead of aborting 2015-06-27 22:28:06 +02:00
Martin Tournoij
26664ba644 Merge branch 'master' into save-fifo 2015-06-27 22:23:47 +02:00
Martin Tournoij
a346644c71 Changed:
- same_domain: If the tld is unknown, only return True if the hostnames are the same
- Fix when starting without an open page
2015-06-27 21:15:54 +02:00
Martin Tournoij
f806eefba6 Merge branch 'master' into referer-header 2015-06-27 20:43:54 +02:00
Florian Bruhin
f31f254d9b Update changelog for v0.3. 2015-06-27 20:27:23 +02:00
Florian Bruhin
63dee327c9 Update docs. 2015-06-27 19:59:06 +02:00
Florian Bruhin
8d3a8f9eda Merge branch 'completion-auto-open' 2015-06-27 19:56:42 +02:00
Florian Bruhin
bf4e968c67 Add new completion -> auto-open option.
Closes #557.
2015-06-27 19:55:04 +02:00
Florian Bruhin
b3869fe42b sessions: Store zoom/scroll-pos per history entry.
Also fixes #728.
2015-06-27 18:06:18 +02:00
Florian Bruhin
58b738ca5b Regenerate docs. 2015-06-26 22:44:52 +02:00
Florian Bruhin
9eaa0d0968 Update changelog. 2015-06-26 22:44:01 +02:00
Florian Bruhin
93f5e30a00 tox: Whoops, update all py-lines. 2015-06-26 22:41:30 +02:00
Florian Bruhin
ddf7f202d8 Set default for new-instance-open-target to tab. 2015-06-26 22:40:16 +02:00
Florian Bruhin
086010d81e tox: Update py (pylib) to 1.4.30.
Upstream changelog:

fix issue68 an assert with a multiline list comprehension was not reported
correctly. Thanks Henrik Heibuerger.
2015-06-26 22:38:58 +02:00
Florian Bruhin
6dbac1c047 Rewrite version.version() tests and test short arg. 2015-06-26 22:37:00 +02:00
Florian Bruhin
e9b5c355d2 Add a short=True argument to version.version().
Fixes #787.
2015-06-26 22:33:29 +02:00
Florian Bruhin
956baed76b Use exclude= instead of include= for find_packages.
It seems the old setuptool version in Ubuntu Trusty doesn't have include=...
2015-06-26 19:45:56 +02:00
Florian Bruhin
f10b9f1172 Adjust MANIFEST.in to include all needed scripts. 2015-06-26 19:36:54 +02:00
Florian Bruhin
97cc90b49f Revert "Revert "Don't install scripts package.""
This reverts commit 345d048f43.
2015-06-26 19:25:58 +02:00
Florian Bruhin
345d048f43 Revert "Don't install scripts package."
This reverts commit f61aaa9053.
2015-06-26 10:00:36 +02:00
Florian Bruhin
f61aaa9053 Don't install scripts package.
See #783.
2015-06-26 09:47:34 +02:00
Florian Bruhin
4652843b38 Move command-related zoom logic out of WebView.
After f8d66f3fe1 loading a session showed the
zoom percentage of all tabs.

This logic doesn't really belong into webview.py anyways, so it gets moved to
browser/commands.py here.
2015-06-24 23:06:55 +02:00
Florian Bruhin
5d490a4e22 Update authors 2015-06-24 21:31:10 +02:00
Franz Fellner
534a85cf8f Create a new QStyleOptionTab object for each tab.
It seems under some circumstances (on Gentoo?), the existing QStyleOptionTab
object was reused, causing subsequent tabs to have the same title as the first
one.

Fixes #778.
2015-06-24 21:27:37 +02:00
Florian Bruhin
75b894a186 Include DESKTOP_SESSION in qute:version. 2015-06-24 20:54:37 +02:00
Florian Bruhin
220ac021f0 Print style name in version info. 2015-06-24 20:37:48 +02:00
Florian Bruhin
24424a0486 Fix expected qWarning messages for Qt 5.5. 2015-06-24 20:30:26 +02:00
Florian Bruhin
db267ae195 tests: Increase timeout for starting processes.
Windows can be slow...
2015-06-24 18:32:56 +02:00
Florian Bruhin
d5d85bd9c7 Regenerate authors 2015-06-24 18:23:37 +02:00
Martin Tournoij
f8d66f3fe1 Use zoom_perc instead of setZoomFactor to set zoom.
When using setZoomFactor, the NeighborList's fuzzyval doesn't get updated,
which means the next -/+ press would do something weird.
2015-06-24 18:16:59 +02:00
Florian Bruhin
0f1ba4739c Mark OS X on Travis as expected failure.
Travis currently has a bug where OS X builds are routed to Ubuntu Trusty when
"dist: trusty" and "os: osx" is given.
2015-06-24 08:10:22 +02:00
Florian Bruhin
4c7c38efcb Update changelog. 2015-06-24 08:10:16 +02:00
Florian Bruhin
b7c3e7b959 Disallow {foo} in search engine URLs.
This causes an KeyError otherwise when trying to use str.format to insert the
search term.
2015-06-24 07:46:15 +02:00
Florian Bruhin
b21b4377a8 Add a smoke test to build_release.py. 2015-06-24 00:05:35 +02:00
Florian Bruhin
10b00da1ae Reorder commands in build_release.py.
This should be slightly faster as the venv is only recreated once.
2015-06-23 23:35:20 +02:00
Florian Bruhin
b337cfe4c6 Enforce a 32bit Python in build_release.py. 2015-06-23 23:34:30 +02:00
Florian Bruhin
3dbf3f9e0a Use tox/virtualenv to build Windows packages.
This makes sure we have all needed dependencies installed in the version which
is in requirements.txt.
Fixes #776.
2015-06-23 23:31:28 +02:00
Florian Bruhin
d02b63a847 tox: Use --ignore=tests for pep8/pyflakes/mccabe. 2015-06-23 18:17:05 +02:00
Florian Bruhin
f2d7391974 tox: Update pytest to 2.7.2 and pylib to 1.4.29.
pytest upstream changelog
=========================

- fix issue767: pytest.raises value attribute does not contain the exception
  instance on Python 2.6. Thanks Eric Siegerman for providing the test
  case and Bruno Oliveira for PR.

- Automatically create directory for junitxml and results log.
  Thanks Aron Curzon.

- fix issue713: JUnit XML reports for doctest failures.
  Thanks Punyashloka Biswal.

- fix issue735: assertion failures on debug versions of Python 3.4+
  Thanks Benjamin Peterson.

- fix issue114: skipif marker reports to internal skipping plugin;
  Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR.

- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin.
  Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.

- fix issue718: failed to create representation of sets containing unsortable
  elements in python 2. Thanks Edison Gustavo Muenz

- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
  which has a refined algorithm for traceback generation.

py upstream changelog
=====================

- fix issue55: revert a change to the statement finding algorithm
  which is used by pytest for generating tracebacks.
  Thanks Daniel Hahler for initial analysis.

- fix pytest issue254 for when traceback rendering can't
  find valid source code.  Thanks Ionel Cristian Maries.
2015-06-23 17:19:48 +02:00
Florian Bruhin
4dd23c530a Merge branch 'artur-shaik-caret_on_selection' 2015-06-20 13:09:32 +02:00
Florian Bruhin
e459ac52cc Use existing selection when entering caret mode. 2015-06-20 13:09:00 +02:00
Florian Bruhin
5cf1dce89e Simplify condition and add comment. 2015-06-20 13:07:51 +02:00
Artur Shaik
94d394001e Don't position caret if there is selection on page 2015-06-20 12:47:46 +02:00
Florian Bruhin
e2c375b874 Add missing docstring for get_build_exe_options(). 2015-06-19 09:40:48 +02:00
Florian Bruhin
fd82587213 Skip documentation when freezing for smoke-frozen. 2015-06-19 09:40:26 +02:00
Florian Bruhin
a5610fd6da Fix TestReadFile when frozen. 2015-06-19 09:40:26 +02:00
Florian Bruhin
85f6b3c6df Fix TestGitStr when frozen. 2015-06-19 09:40:26 +02:00
Florian Bruhin
08c8a5f7dd Skip tests which need sys.executable when frozen.
See #770
2015-06-19 09:40:26 +02:00
Florian Bruhin
b0012fd410 Freeze utils/testfile when freezing tests. 2015-06-19 09:40:16 +02:00
Florian Bruhin
894ec7d66a Use a function for build_exe_options in freeze.py. 2015-06-19 09:40:15 +02:00
Florian Bruhin
42b5ee831e Add a unittests-frozen testenv.
See #770.
2015-06-19 09:39:55 +02:00
Florian Bruhin
3ba63128da Add a smoke-frozen testenv.
See #770.
2015-06-19 07:35:01 +02:00
Florian Bruhin
425a6d33cf Add __name__ == '__main__' block in freeze.py.
freeze.py now gets imported from freeze_tests.py, and shouldn't run its own
setup in that case.
2015-06-19 07:35:01 +02:00
Florian Bruhin
2f59abaf13 Add empty includes=[] to freeze.py.
This makes freeze_tests.py easier.
2015-06-18 22:39:04 +02:00
Florian Bruhin
3de1299650 tests: Use utils.read_file to get javascript files.
This will make those tests pass when frozen.

See #770.
2015-06-18 21:09:10 +02:00
Florian Bruhin
0d59a1cba8 Include javascript folder when freezing.
See #770.
2015-06-18 21:09:10 +02:00
Florian Bruhin
9ca06ecfa2 Use pkg_resources instead of distutils for version
Fixes #767. See #770.
2015-06-18 20:54:05 +02:00
Florian Bruhin
ef78f69822 Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-06-18 15:04:20 +02:00
Lamar Pavel
c72da37916 Fix link to contribution guidelines in README 2015-06-18 15:03:58 +02:00
Florian Bruhin
1cc6a6669b Bind <Alt-Backspace> to rl-unix-word-rubout. 2015-06-18 15:02:30 +02:00
Florian Bruhin
14237c2c62 Merge pull request #768 from lamarpavel/master
Fix link to contribution guidelines in README
2015-06-18 13:22:25 +02:00
Lamar Pavel
a5c078516b Fix link to contribution guidelines in README 2015-06-18 13:15:00 +02:00
Florian Bruhin
c3c52220f6 Update changelog. 2015-06-18 11:57:55 +02:00
Florian Bruhin
0350d19bd3 Load geometry after completion is initialized.
Fixes #766.
2015-06-18 10:32:07 +02:00
Florian Bruhin
c64d9520ff Fix lint.
Thanks to @Carpetsmoker for spotting this in #705.
2015-06-18 08:10:14 +02:00
Florian Bruhin
59cdbd780c Fix {url} substitution with :spawn.
See #759.

This is a regression introduced in 6dbdea0ee3.
2015-06-18 07:01:30 +02:00
Florian Bruhin
703b0043db tox: Update pyflakes to 0.9.2.
Upstream changelog:

- Fix a traceback when a global is defined in one scope, and used in another.
2015-06-17 17:48:25 +02:00
Florian Bruhin
6dbdea0ee3 Set maxsplit=0 for :spawn and split manually.
Fixes #759.
2015-06-17 07:57:38 +02:00
Florian Bruhin
b1334bcc22 Use repr() for unknown objects in utils.qualname. 2015-06-17 06:46:03 +02:00
Florian Bruhin
dfe98d1053 completion: Fix initial _cursor_part value.
Fixes #749.
2015-06-16 13:22:55 +02:00
Florian Bruhin
a024c14dd6 tox: Add smoke test to the default envlist. 2015-06-16 07:37:08 +02:00
Florian Bruhin
e7b84d4089 Update changelog. 2015-06-16 07:16:32 +02:00
Florian Bruhin
0119cf510f Fix loading of _temp_history. 2015-06-16 07:16:02 +02:00
Florian Bruhin
a545b919f7 Do history loading after qutebrowser has started. 2015-06-16 07:06:56 +02:00
Florian Bruhin
b43d8b13d8 tox: Update mccabe to 0.3.1.
Upstream changelog:

- Include test_mccabe.py in releases.
- Always coerce the max_complexity value from Flake8's entry-point to an integer.
2015-06-15 06:21:14 +02:00
Florian Bruhin
70699988ed Fix context managers behavior on exceptions. 2015-06-15 06:19:14 +02:00
Florian Bruhin
8dc9f0562a tox: Update pyroma to 1.8.2.
Upstream changelog:

Do not complain that the version number should be a string, when it is a
basestring. [maurits]
2015-06-14 13:45:09 +02:00
Florian Bruhin
f1ba14b496 Fix exception when using :set.
This is a regression introduced in 167faafff2.
2015-06-13 13:26:29 +02:00
Florian Bruhin
9bf749643a Fix ci_install.py for Travis on OS X. 2015-06-12 17:06:49 +02:00
Florian Bruhin
219c2f8ae8 Ignore "Unable to set geometry" warnings in tests.
This reverts commits 9b066ec50a and
83f7cf84a9.

This was still broken even after setting the geometry:

https://ci.appveyor.com/project/The-Compiler/qutebrowser
2015-06-12 17:04:12 +02:00
Florian Bruhin
f17131f6c2 Change Qt links to point to qt.io. 2015-06-12 16:59:33 +02:00
Florian Bruhin
84a269f36a Add missing keys to key_to_string. 2015-06-12 16:37:17 +02:00
Florian Bruhin
8033931bae Test key_to_string with all Qt.Key members. 2015-06-12 16:37:07 +02:00
Florian Bruhin
9b066ec50a Set geometry in test_textbase.py.
See 83f7cf84a9 - it seems with the Qt 5.4.2
upgrade there are some more warnings on Windows.
2015-06-12 13:44:10 +02:00
Florian Bruhin
b1e9ff059a Update for PyQt 5.4.2.
Upstream changelog:

2015-06-11  Phil Thompson  <phil@riverbankcomputing.com>

    * .hgtags:
    Added tag 5.4.2 for changeset 5a34feb6b31d
    [6f80aa2771d3] [tip] <5.4-maint>

    * NEWS:
    Released as v5.4.2.
    [5a34feb6b31d] [5.4.2] <5.4-maint>

    * installers/PyQt5-Qt5-gpl.nsi:
    Fixed a missing image plugin in the Windows installer.
    [29760ab3d5f9] <5.4-maint>

    * Makefile:
    Clean up any extra Mac crap.
    [dcbc92d15a8b] <5.4-maint>

2015-06-07  Phil Thompson  <phil@riverbankcomputing.com>

    * pyuic/uic/Compiler/compiler.py,
    pyuic/uic/Compiler/qobjectcreator.py:
    Make sure all generedt imports are sorted and therefore repeatable.
    [9ad1a251d97b] <5.4-maint>

2015-06-05  Phil Thompson  <phil@riverbankcomputing.com>

    * NEWS, PyQt5.msp:
    Completed the support for Qt v5.4.2.
    [02c99f5affde] <5.4-maint>

    * PyQt5.msp:
    Scanned Qt v5.4.2.
    [7fbd795f8c5e] <5.4-maint>

    * installers/PyQt5-Qt5-gpl.nsi:
    Updated the Windows installer for Qt v5.4.2.
    [74c080b5bdb2] <5.4-maint>

    * PyQt5.msp:
    Added missing /Factory/ annotations from the create() and
    beginCreate() methods of QQmlComponent.
    [56be1a87fd2c] <5.4-maint>

2015-06-02  Phil Thompson  <phil@riverbankcomputing.com>

    * PyQt5.msp:
    Fixed the handling of the value returned by Python re-
    implementations of QSGMaterialShader.attributeNames().
    [cb620297cbc8] <5.4-maint>

2015-05-23  Phil Thompson  <phil@riverbankcomputing.com>

    * lib/configure.py, sphinx/installation.rst:
    Added the --no-python-dbus option to configure.py.
    [df17d3eace7a] <5.4-maint>

2015-05-18  Phil Thompson  <phil@riverbankcomputing.com>

    * pyuic/uic/uiparser.py:
    Fixed another deprecation warning in pyuic.
    [6333c15a9a6b] <5.4-maint>

    * pyuic/uic/driver.py, pyuic/uic/objcreator.py,
    pyuic/uic/port_v2/load_plugin.py, pyuic/uic/port_v3/load_plugin.py:
    Fixed all the deprecation warnings from pyuic.
    [e8f96fbc8cf0] <5.4-maint>

2015-05-08  Phil Thompson  <phil@riverbankcomputing.com>

    * Makefile:
    Fixed the path to SIP on OS/X.
    [39ecf0bc71e1] <5.4-maint>

2015-05-06  Phil Thompson  <phil@riverbankcomputing.com>

    * pyuic/uic/Compiler/qobjectcreator.py,
    pyuic/uic/Loader/qobjectcreator.py, pyuic/uic/icon_cache.py,
    pyuic/uic/objcreator.py:
    Fixed the handling of themed icons by uic.loadUi().
    [506c268c8f43] <5.4-maint>

2015-04-24  Phil Thompson  <phil@riverbankcomputing.com>

    * qpy/QtCore/qpycore_chimera.cpp:
    Handle properties that are objects that are defined in QML.
    [aebd6aab85d4] <5.4-maint>

2015-04-04  Phil Thompson  <phil@riverbankcomputing.com>

    * pyuic/uic/properties.py, pyuic/uic/uiparser.py:
    Fixed pyuic's handling of default margins.
    [6a7e3e6175c8] <5.4-maint>

    * pyuic/uic/properties.py, pyuic/uic/uiparser.py:
    Fixed pyuic's handling of the default spacing.
    [12193d5afbe1] <5.4-maint>

2015-04-03  Phil Thompson  <phil@riverbankcomputing.com>

    * pylupdate/main.cpp:
    pylupdate now saves locations as relative to the .ts file.
    [1757d2e318f6] <5.4-maint>

2015-04-01  Phil Thompson  <phil@riverbankcomputing.com>

    * PyQt5.msp:
    Added QWIDGETSIZE_MAX to QtWidgets.
    [b136fd7c485e] <5.4-maint>

2015-03-25  Phil Thompson  <phil@riverbankcomputing.com>

    * sphinx/static/classic.css, sphinx/static/default.css:
    Fixed the stylesheet.
    [d35996e57f02] <5.4-maint>

2015-03-16  Phil Thompson  <phil@riverbankcomputing.com>

    * PyQt5.msp:
    The GIL is now released for all QImage ctors and methods that might
    block.
    [3fd70eec66b9] <5.4-maint>

    * PyQt5.msp:
    Removed the internal QGraphicsSceneEvent.setWidget().
    [622e5b5ebcfc] <5.4-maint>

2015-03-11  Phil Thompson  <phil@riverbankcomputing.com>

    * installers/PyQt5-Qt5-gpl.nsi:
    Added the OpenGL v2.1 backend to the Windows installer.
    [ca1e4c121c78] <5.4-maint>

    * sphinx/conf.py:
    Updated for sphinx v1.3.
    [1c1cd1eac7ce] <5.4-maint>

    * qpy/QtCore/qsysinfo.sip:
    Added Yosemite and iOS v8.0 to QSysInfo.
    [01d4d1af5961] <5.4-maint>

    * pyuic/uic/uiparser.py:
    pyuic now handles empty zorder elements.
    [a0dcd07b7e72] <5.4-maint>

    * lib/configure.py:
    Added nostrup to the generated .pro file.
    [d6445df281a6] <5.4-maint>

2015-03-01  Phil Thompson  <phil@riverbankcomputing.com>

    * pyuic/uic/uiparser.py:
    pyuic will now ignore spacer items when setting the z-order.
    [28704a096a3a] <5.4-maint>

2015-02-26  Phil Thompson  <phil@riverbankcomputing.com>

    * installers/PyQt5-Qt5-gpl.nsi:
    Installer fix for Qt v5.4.1.
    [0b21a7fa6750] <5.4-maint>:
2015-06-12 13:21:37 +02:00
Florian Bruhin
e8830a631e Increase test_guiprocess timeouts.
Apparently 1 second is not enough for Windows to start a process...
2015-06-12 11:54:20 +02:00
Florian Bruhin
425fcdf8e4 Merge branch 'util-tests-1' 2015-06-12 11:50:57 +02:00
Florian Bruhin
d2e103ecc1 Merge branch 'Carpetsmoker-jseval' 2015-06-12 11:46:23 +02:00
Florian Bruhin
167faafff2 Fix command parsing for arguments containing _. 2015-06-12 11:42:16 +02:00
Florian Bruhin
8369c74f74 Update changelog. 2015-06-12 11:24:57 +02:00
Florian Bruhin
6f690c442e Regenerate authors. 2015-06-12 11:24:21 +02:00
Florian Bruhin
efcea65596 Add --quiet argument to :jseval. 2015-06-12 11:24:04 +02:00
Florian Bruhin
8ecc3a3bb0 Fix lint. 2015-06-12 11:22:37 +02:00
Florian Bruhin
ea1921defd Merge branch 'jseval' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-jseval 2015-06-12 11:21:10 +02:00
Florian Bruhin
36a2f4a15a Fix newline stripping. 2015-06-11 23:14:56 +02:00
Florian Bruhin
fc32858e5c Add GUIProcess tests. 2015-06-11 23:05:57 +02:00
Florian Bruhin
1956158096 Make keyword arguments work for MessageModule stub. 2015-06-11 23:03:15 +02:00
Florian Bruhin
33ad0ab1fc Fix startDetached return value for GUIProcess. 2015-06-11 23:02:47 +02:00
Florian Bruhin
fc5349e1dc Change FakeQProcess stub to a function with spec. 2015-06-11 23:02:18 +02:00
Florian Bruhin
d3b727d0c7 Fix lint. 2015-06-11 20:32:27 +02:00
Florian Bruhin
6736f6a3f2 Regenerate docs. 2015-06-11 20:30:37 +02:00
Florian Bruhin
5828bbafe9 Add -v (not -q) to :spawn and make it work with -u. 2015-06-11 20:30:37 +02:00
Florian Bruhin
84dacc9bc8 Remove double output for :spawn. 2015-06-11 20:30:37 +02:00
Florian Bruhin
8a87b5d357 Rename GUIProcess.started to _started.
It's unneeded for this to be public, and it conflicts with the pyqtSignal.
2015-06-11 20:30:37 +02:00
Florian Bruhin
ad401e035f Proxy QProcess signals. 2015-06-11 20:30:37 +02:00
Florian Bruhin
1f67353a40 Adjust editor tests for GUIProcess. 2015-06-11 20:30:28 +02:00
Florian Bruhin
62d2018695 Add cmd/args/started to GUIProcess. 2015-06-11 20:30:28 +02:00
Florian Bruhin
163bc2e12e Add GUIProcess.
This aims to unify the code which spawns a process and then shows statusbar
notifications when it exited, etc.
2015-06-11 20:30:03 +02:00
Florian Bruhin
1a9bc64776 Display an error on non-zero :spawn exit. 2015-06-11 20:28:05 +02:00
Florian Bruhin
231f1d90ce Add a -d/--detach argument to :spawn. 2015-06-11 20:28:05 +02:00
Florian Bruhin
17bb9fc21c Use QProcess instead of subprocess.
Closes #646.
Fixes #688.
2015-06-11 20:28:04 +02:00
Florian Bruhin
90bbe4d1ef Make ci_install.py python2 compatible. 2015-06-11 17:09:17 +02:00
Florian Bruhin
364e13f4c2 Add OS X support for Travis. 2015-06-11 16:36:58 +02:00
Florian Bruhin
a79b07bd94 Rename appveyor_install to ci_install. 2015-06-11 16:15:33 +02:00
Florian Bruhin
c4eabcd663 Merge pull request #751 from ProtractorNinja/fix-context-menu
More specific statusbar styling: resolves #750.
2015-06-11 14:42:29 +02:00
Austin Anderson
599f582c20 More specific statusbar styling: resolves #750. 2015-06-11 08:07:59 -04:00
Florian Bruhin
3e8a394217 Disable no-member for pylint for os.SEEK_*.
This should fix pylint on Windows.
2015-06-11 10:49:06 +02:00
Florian Bruhin
480c4e878e Ignore pylint warning on Ubuntu/Travis. 2015-06-11 10:26:18 +02:00
Florian Bruhin
fdd302e4f7 Update changelog. 2015-06-10 21:17:23 +02:00
Florian Bruhin
e16d89a548 Merge branch 'ProtractorNinja-more-color-settings' 2015-06-10 21:11:09 +02:00
Florian Bruhin
9b7b97d626 Improve docs. 2015-06-10 21:10:59 +02:00
Florian Bruhin
ab27612139 Merge branch 'more-color-settings' of https://github.com/ProtractorNinja/qutebrowser into ProtractorNinja-more-color-settings 2015-06-10 20:05:23 +02:00
Florian Bruhin
863e194073 Update MANIFEST.in 2015-06-10 18:35:33 +02:00
Florian Bruhin
5a8b7910e0 tox: Use python -m to start pylint.
This makes it also work on Windows, where bin/ is called Scripts/.
2015-06-10 18:35:33 +02:00
Florian Bruhin
67473c6db1 tox: Add PYTHON to passenv. 2015-06-10 18:35:33 +02:00
Florian Bruhin
68d8900c6c link_pyqt: Support PYTHON environment variable. 2015-06-10 18:35:33 +02:00
Florian Bruhin
ddd343c89c link_pyqt: Be less verbose. 2015-06-10 18:35:33 +02:00
Florian Bruhin
67e895b6c7 Hide SetProcessDpiAwareness Qt warning.
This shows up on AppVeyor CI for some reason.
See https://bugreports.qt.io/browse/QTBUG-38993
2015-06-10 18:35:33 +02:00
Florian Bruhin
b57027f800 Fix pylint warnings on Windows. 2015-06-10 18:35:33 +02:00
Florian Bruhin
fc15e85811 Add AppVeyor support. 2015-06-10 18:35:33 +02:00
Austin Anderson
3be9a9b051 Catalogued a configuration option change for updates. 2015-06-10 08:16:15 -04:00
Florian Bruhin
645a1512dd tox: Update pyflakes to 0.9.1. 2015-06-10 06:10:12 +02:00
Florian Bruhin
4532176e7b Don't use substitutions in tox.ini.
These seem to break things on Ubuntu Trusty...
2015-06-09 19:12:19 +02:00
Florian Bruhin
80a59720de Add .travis.yml. 2015-06-09 19:12:19 +02:00
Martin Tournoij
9df5a89037 Don't crash when trying to save to a FIFO or other special file
When giving the path to a FIFO or other special file qutebrowser would
completely hang, and has to be killed.

Tested:

- Asks for overwrite: file, symlink to file
- Saves in dir: dir, symlink to dir
- Aborts: block dev, char dev, fifo, socket, and a symlink to all of these
2015-06-08 22:42:06 +02:00
Florian Bruhin
839d2b1cbe Merge branch 'Carpetsmoker-3rd-party-cookies' 2015-06-08 20:50:37 +02:00
Florian Bruhin
0120061456 Add changelog entry. 2015-06-08 20:50:28 +02:00
Florian Bruhin
108e722c85 Add config migration. 2015-06-08 20:48:35 +02:00
Florian Bruhin
3b4fe97dbc Merge branch '3rd-party-cookies' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-3rd-party-cookies 2015-06-08 20:38:19 +02:00
Florian Bruhin
b6349437f7 Fix broken check for changing js on qute:settings.
See #727.
2015-06-08 20:29:55 +02:00
Florian Bruhin
b5cd082e43 config: Make it possible to migrate values.
Needed for #729.
2015-06-08 19:34:11 +02:00
Florian Bruhin
154af84714 Update changelog. 2015-06-08 19:01:37 +02:00
Florian Bruhin
10e8b78695 Merge branch 'lamarpavel-relapaths' 2015-06-08 18:56:49 +02:00
Florian Bruhin
150ca90517 Regenerate docs 2015-06-08 18:54:07 +02:00
Florian Bruhin
5a2d909607 Update user-stylesheet docs. 2015-06-08 18:50:09 +02:00
Florian Bruhin
171a0f201b Merge branch 'relapaths' of https://github.com/lamarpavel/qutebrowser into lamarpavel-relapaths 2015-06-08 18:48:11 +02:00
Lamar Pavel
7f27c183be Include expandvars in File.validate
I thought I put this in here before, but apparently I did not. So here it is,
together with a new test to verify it. Other tests needed to be updated with a
mock for os.path.expandvars.
2015-06-08 13:18:16 +02:00
Lamar Pavel
0e50760b70 Differentiate exceptions; remove obsolete test
In function File.validate the try-except block has been re-written to
differentiate raised errors.

In function File.transform there was a check for validity of the file path that
is alraedy performed by File.validate under the same conditions. This check has
been removed.
2015-06-08 12:53:59 +02:00
Florian Bruhin
c08078841f Fix test_qprocess. 2015-06-08 07:49:22 +02:00
Florian Bruhin
1fcce12870 Fix TestPyQIODevice.failing_open on Windows. 2015-06-08 07:45:19 +02:00
Florian Bruhin
00747be9d3 Fix TestSavefileOpen.test_existing_dir on older Qt. 2015-06-08 07:43:40 +02:00
Florian Bruhin
261c44bea9 Fix TestPyQIODevice.test_qprocess on Windows. 2015-06-08 07:42:17 +02:00
Antoni Boucher
c2eabb13b0 Fixed style. 2015-06-07 20:25:04 -04:00
Antoni Boucher
31eed6c9a6 Fixed to avoid having duplicate bookmarks. 2015-06-07 20:16:45 -04:00
Austin Anderson
34d4c08374 Significantly reduced the size of the bar stylesheet. 2015-06-07 20:13:52 -04:00
Austin Anderson
ebc013ac2a Removed redundant setter. 2015-06-07 20:13:52 -04:00
Antoni Boucher
d93732a6b3 Fixed to use 'bookmarks/urls' file instead of bookmarks. 2015-06-07 20:04:42 -04:00
Antoni Boucher
c4fc5c0c43 Fixed to use the title "(null)" when the page does not have any title. 2015-06-07 19:51:46 -04:00
Antoni Boucher
57a72a7120 Refactored bookmark removal to use a command. 2015-06-07 19:36:19 -04:00
Florian Bruhin
1e982a9a84 Add/improve tests for qutebrowser.utils.qtutils. 2015-06-07 23:20:34 +02:00
Florian Bruhin
e60f698615 Add/improve tests for qutebrowser.utils.standarddir. 2015-06-07 23:20:34 +02:00
Florian Bruhin
df53ccf426 Write tests for qutebrowser.utils.version. 2015-06-07 23:20:34 +02:00
Florian Bruhin
4204579c06 Add/improve tests for qutebrowser.utils.utils. 2015-06-07 22:53:30 +02:00
Florian Bruhin
1e5e6a63a5 Batch download redrawings/updates. 2015-06-07 22:22:23 +02:00
Florian Bruhin
4a4856c176 Merge branch 'Carpetsmoker-downloads-cpu' 2015-06-07 21:51:39 +02:00
Florian Bruhin
90b3927906 Merge branch 'downloads-cpu' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-downloads-cpu 2015-06-07 21:51:18 +02:00
Florian Bruhin
2ff6dbd482 Remove unused import. 2015-06-07 21:44:45 +02:00
Florian Bruhin
f85ca19cef Use <noscript> tag for qute:settings without JS.
There was already a noscript tag, this just removes the special check and makes
it a bit more obvious.

See #727.
2015-06-07 21:38:44 +02:00
Florian Bruhin
da2ff6f3cb Update recommended Qt version in README. 2015-06-07 21:27:50 +02:00
Antoni Boucher
cf4b89efe3 Merge remote-tracking branch 'upstream/master' 2015-06-07 14:38:17 -04:00
Florian Bruhin
525d3ee4c9 Regenerate docs 2015-06-07 17:38:27 +02:00
Florian Bruhin
6b94dc5279 Add continue to default next-regexes. 2015-06-07 17:20:52 +02:00
Martin Tournoij
2fa6c952c2 Use less CPU when downloading files
When downloading a bunch (7 or 8) of files I noticed qutebrowser was using a lot
of CPU (>60%).

I did some looking, and in the `downloadProgress` callback qutebrower emits the
updated signal which causes everything to be updated. We don't really need this,
since _update_speed() calls it every 500ms anyway.

I tested by downloading 3 copies of the 1GB file [on this
page]( http://www.thinkbroadband.com/download.html ) qutebrowser consistently
pulls about 25% CPU on my system.

When removing this call, the system pulls about 17% CPU. Not a great amount, but
still significant enough to warrant a pull request ;-)

Some other notes:

- wget uses about 1.5%-2% for each process when downloading.
- When not doing any UI updates & speed calculations qutebrowser uses about 15%.
- Doing some quick profiling and strategic commenting seems to indicate there
  isn't any other low hanging fruit to be improved on here.
2015-06-07 17:15:04 +02:00
Florian Bruhin
83f7cf84a9 tests: Set progress widget geometry.
This hopefully fixes this warning on Windows:

    QWindowsWindow::setGeometryDp: Unable to set geometry 113x16+192+124 on
    QWidgetWindow/'ProgressClassWindow'. Resulting geometry:  124x16+192+124
    (frame: 8, 31, 8, 8, custom margin: 0, 0, 0, 0, minimum size: 0x0, maximum
    size: 16777215x16777215).
2015-06-07 11:14:14 +02:00
Florian Bruhin
37750b9e30 Regenerate docs. 2015-06-07 10:47:53 +02:00
Florian Bruhin
a82b0d007d Enforce a Qt with SSL support. 2015-06-07 10:47:28 +02:00
Florian Bruhin
e98a05e53d Fix scroll_anchor in javascript tests.
It seems scrollRequested doesn't actually get emitted.
2015-06-07 10:38:58 +02:00
Florian Bruhin
d887623377 Make tests fail on unexpected Qt messages. 2015-06-07 02:34:19 +02:00
Florian Bruhin
e86a79740a Use raising=True for QtBot.waitSignal. 2015-06-07 02:30:36 +02:00
Florian Bruhin
aa4cb2927d Fix TestHideQtWarning tests for pytest 1.4.0.
pytest captures the Qt logging messages, so we can't use qWarning to test.
2015-06-07 02:29:20 +02:00
Florian Bruhin
2117b2afc6 Revert "Skip test which might be responsible for segfaults."
This reverts commit 592ace18d4.
2015-06-07 01:25:10 +02:00
Florian Bruhin
5310c60d58 Remove unused import. 2015-06-07 01:24:24 +02:00
Florian Bruhin
a0e5a3e8ee tox: Update pytest-qt to 1.4.0.
Upstream changelog:

- Messages sent by qDebug, qWarning, qCritical are captured and displayed when
  tests fail, similar to pytest-catchlog. Also, tests can be configured to
  automatically fail if an unexpected message is generated. (See docs).
- New method waitSignals: will block untill all signals given are triggered, see
  docs (thanks @The-Compiler for idea and complete PR).
- New parameter raising to waitSignals and waitSignals: when True (defaults to
  False) will raise a qtbot.SignalTimeoutError exception when timeout is reached,
  see docs (thanks again to @The-Compiler for idea and complete PR).
- pytest-qt now requires pytest version >= 2.7.

Internal changes to improve memory management

- QApplication.exit() is no longer called at the end of the test session and
  the QApplication instance is not garbage collected anymore;
- QtBot no longer receives a QApplication as a parameter in the constructor,
  always referencing QApplication.instance() now; this avoids keeping an extra
  reference in the qtbot instances.
- deleteLater is called on widgets added in QtBot.addWidget at the end of each
  test;
- QApplication.processEvents() is called at the end of each test to make sure
  widgets are cleaned up;
2015-06-07 01:24:02 +02:00
Florian Bruhin
5a73ad0c19 Improve spell-checker case-sensitivity.
This only checks case-insensitively for the first char, so things like
"QMouseEvent" don't trigger the check.
2015-06-07 01:24:02 +02:00
Florian Bruhin
def41e70bf Fix some spelling mistakes. 2015-06-07 01:24:02 +02:00
Florian Bruhin
fd75f77108 Fix spell checker to check all files. 2015-06-07 01:24:02 +02:00
Lamar Pavel
5bacbc9d38 Remove obsolete try-except block 2015-06-06 14:07:57 +02:00
Lamar Pavel
de0686c50a Error messages and explicit test for None
Error messages for validate() are more specific.

Return of standarddir.conf() is explicitly tested for None to avoid ambiguity
with other falsey values.
2015-06-06 14:04:45 +02:00
Martin Tournoij
b0880df695 Execute in the current tab, and not the first one 2015-06-05 23:29:38 +02:00
Martin Tournoij
94178c558a Well, getting the error doesn't work... 2015-06-05 20:09:19 +02:00
Martin Tournoij
463e85ff5d Add referer-header setting, #712 2015-06-05 18:00:21 +02:00
Florian Bruhin
2459f14f6f Update changelog. 2015-06-05 17:53:16 +02:00
Florian Bruhin
015de0e6db misc_checks: Check spelling case-insensitively. 2015-06-05 17:51:33 +02:00
Florian Bruhin
ace7877010 Merge branch 'Carpetsmoker-issue-716' 2015-06-05 17:50:29 +02:00
Florian Bruhin
d3e85ad982 Update docs. 2015-06-05 17:50:00 +02:00
Florian Bruhin
5fb23f1373 Also migrate older search calls. 2015-06-05 17:45:38 +02:00
Florian Bruhin
8001099661 Adjust tests. 2015-06-05 17:45:32 +02:00
Florian Bruhin
708d0d9c27 Merge branch 'issue-716' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-issue-716 2015-06-05 17:38:52 +02:00
Florian Bruhin
d3f7d9319a tox: Update py to 1.4.28.
Upstream changelog:

- fix issue64 -- dirpath regression when "abs=True" is passed. Thanks Gilles
  Dartiguelongue.
2015-06-05 17:29:00 +02:00
Florian Bruhin
6ec8bbaca5 tox: Update pytest-mock to 0.6.0.
Upstream changelog:

- Two new auxiliary methods, spy and stub.
2015-06-05 17:27:59 +02:00
Florian Bruhin
e38169433e tox: Update pytest-flakes to 1.0.0.
Upstream changelog:

- Fix issue #6 - support PEP263 for source file encoding.
- Clarified license to be MIT like pytest-pep8 from which this is derived.
2015-06-05 17:26:49 +02:00
Martin Tournoij
dfada850e0 Update code after refactor, and add migration 2015-06-05 16:52:33 +02:00
Martin Tournoij
a7b10a090f Merge branch 'master' into issue-716 2015-06-05 16:45:52 +02:00
Martin Tournoij
fc4c7bd2e4 Merge the cookies-accept and third-party-cookie-policy settings 2015-06-05 16:20:50 +02:00
Lamar Pavel
402aa66756 Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-06-05 16:10:55 +02:00
Florian Bruhin
b55e22b5c3 Refactor key mode/parser handling in modeman. 2015-06-05 15:29:09 +02:00
Martin Tournoij
fa65f345ac Perhaps fix it more properly after all :-) 2015-06-05 15:19:40 +02:00
Martin Tournoij
57ddd8e95e Always handle the <Esc> key, even if it's bound.
This fixes #716, which sufficiently annoyed me to make this quick fix. It's not
a great fix, but it's not worse than what we had already, and the current
behaviour is very surprising IMHO.
2015-06-05 14:26:17 +02:00
Florian Bruhin
728f06e797 Close context menu if another mode was entered.
Fixes #735.
2015-06-05 11:15:22 +02:00
Florian Bruhin
7102459c81 Rename _get_modeman() to instance(). 2015-06-05 11:15:18 +02:00
Florian Bruhin
622938e3d3 Fix completion performance with shrink=True.
Before, the completion was shrinked every time any item was removed/added to
the completion (rowsRemoved/rowsInserted signals), which was >3000 times when
completing history.

Also, the signals got connected multiple times if setting the same model, which
made the situation worse.

Fixes #734.
2015-06-05 07:16:33 +02:00
Florian Bruhin
c776958388 Merge branch 'Carpetsmoker-yank-domain' 2015-06-05 06:39:53 +02:00
Florian Bruhin
05fe68ccab Regenerate authors 2015-06-05 06:39:37 +02:00
Florian Bruhin
c907572557 Merge branch 'yank-domain' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-yank-domain 2015-06-05 06:38:22 +02:00
Florian Bruhin
4a909aa028 Use pylint's built-in checker to check for CRLF. 2015-06-04 15:25:36 +02:00
Florian Bruhin
f41acc8fb5 Remove changelog entry as it's a recent regression 2015-06-04 15:14:56 +02:00
Florian Bruhin
9ec6e6da80 Fix exit status codes to be 0-based. 2015-06-04 15:13:20 +02:00
Martin Tournoij
d60d4d756c Also yank port number 2015-06-04 13:20:39 +02:00
Martin Tournoij
0132bea42b Add --domain to yank to yank only the domain
... As I want to copy only the domain fairly frequently.

I also changed the message in the statusline to show the actual text being
copied, which I find helpful. But if you disagree, then just undo it (it's not
that important or anything).
2015-06-04 12:20:43 +02:00
Antoni Boucher
8b14145a4d Fixed style. 2015-06-03 19:31:31 -04:00
Martin Tournoij
78e159cb27 Add referer-header settng, #712 2015-06-04 01:26:00 +02:00
Martin Tournoij
472071c047 Add setting: 'content.third-party-cookie-policy', fixes #607
This sets the third-party cookie policy.

- I created a new ThirdPartyCookiePolicy() class, since this setting seems to be
  unique in the way it is set...

- I set the default to 'never', which is the most secure/private setting, but
  *may* break *some* features of a (very) limited number of sites; these are
  usually "non-critical" features.
  For example, on Stack Exchange sites you're logged in all 200+ sites if you
  sign in on one of them, this features required 3rd party cookies. You can
  still sign in with out, but you have to do so 200+ times (this is actually the
  only example I've ever noticed).

  AFAIK all "major" browsers accept 3rd-party cookies by default, except for
  Safari. Firefox also made this change, but reversed it (see:
  https://brendaneich.com/2013/05/c-is-for-cookie/), but they don't offer any
  good arguments to *not* have it IMHO, at least not that I could find.

  In any case, in my humble opinion "secure and private by default" is the best
  way to ship. But you're of course free to change it if you disagree ;-)
2015-06-04 00:26:39 +02:00
Martin Tournoij
85eea17b18 Try to get the error ... not sure about this ...
source is undefined when you type stuff in the console, I *think* this is the
only scenario? But maybe not?

<script>
setInterval(function() {
	if (window.__qute_jseval__) {
		throw new Error('jseval hack failed. Sorry :-( ' + window.__qute_jseval__);
	}
}, 1);
</script>
2015-06-03 22:31:15 +02:00
Florian Bruhin
e780efb3d9 Handle javascript in qute:settings more gracefully.
Fixes #727.
2015-06-03 15:03:04 +02:00
Austin Anderson
4d141f489f Added pylint workaround directive to quash rebellion. 2015-06-03 08:42:13 -04:00
Florian Bruhin
f0c58b58dd Mention pytest in CONTRIBUTING. 2015-06-02 20:51:48 +02:00
Florian Bruhin
36803cba06 Switch from flake8 to pytest-{mccabe,flakes,pep8}. 2015-06-02 20:51:06 +02:00
Antoni Boucher
f1874ff44f Added possibility to remove bookmarks and quickmarks. 2015-06-01 20:00:21 -04:00
Antoni Boucher
c8bbef0ab0 Fixed bookmark command name in config. 2015-06-01 19:49:32 -04:00
Antoni Boucher
5085844550 Added highlighting for completion in name column. 2015-06-01 17:55:09 -04:00
Antoni Boucher
9582162927 Fixed bookmarks command names. 2015-06-01 17:52:23 -04:00
Florian Bruhin
d8e58b5886 Fix some typos. 2015-06-01 22:45:40 +02:00
Florian Bruhin
592ace18d4 Skip test which might be responsible for segfaults. 2015-06-01 22:32:11 +02:00
Florian Bruhin
e767f7c0b8 Regenerate docs. 2015-06-01 22:29:49 +02:00
Florian Bruhin
1bf036d1ba Add setting for the webpage bg color to use.
Fixes #719.
2015-06-01 22:27:15 +02:00
Florian Bruhin
131f345007 Update changelog. 2015-06-01 19:04:21 +02:00
Florian Bruhin
dc59ed4d73 Regenerate authors 2015-06-01 19:04:21 +02:00
Martin Tournoij
e22ef776f9 Fix crash when executing "qutebrowser :set".
Fixes #720.
See #721.
2015-06-01 19:04:21 +02:00
Florian Bruhin
b5a70dbdec Spelling fix. 2015-06-01 13:43:40 +02:00
Florian Bruhin
6c2fe3417e Accept numpad-enter as return in default bindings.
See https://bbs.archlinux.org/viewtopic.php?pid=1523326#p1523326
2015-06-01 13:39:13 +02:00
Florian Bruhin
f1c0781a4c Use sip.SIP_VERSION_STR to get sip version. 2015-06-01 09:09:10 +02:00
Florian Bruhin
7daf1cb239 Merge branch 'rltests' 2015-06-01 09:03:06 +02:00
Antoni Boucher
1698c60124 Merge remote-tracking branch 'upstream/master' 2015-05-31 15:59:46 -04:00
Florian Bruhin
0ddf1316f7 Merge branch 'Carpetsmoker-modal-js-dialogs' 2015-05-31 21:43:16 +02:00
Florian Bruhin
a14685be3d Update changelog. 2015-05-31 21:42:25 +02:00
Florian Bruhin
f52f3db1f2 Regenerate docs. 2015-05-31 21:41:32 +02:00
Florian Bruhin
e7619477cd Rename _frame argument to frame.
_foo is used to denote unused arguments, so renaming this as it's now used.
2015-05-31 21:40:19 +02:00
Florian Bruhin
018d7a87be Merge branch 'modal-js-dialogs' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-modal-js-dialogs 2015-05-31 21:39:47 +02:00
Martin Tournoij
4204a8de9a Add ui.modal-js-dialog to restore the default JS dialogs 2015-05-31 21:32:16 +02:00
Florian Bruhin
122f0a7edc Update changelog 2015-05-31 21:25:16 +02:00
Florian Bruhin
9cece08b2b Merge branch 'antoyo-issue-401' 2015-05-31 21:17:11 +02:00
Florian Bruhin
172d0c3ca2 Regenerate docs. 2015-05-31 21:16:53 +02:00
Florian Bruhin
4c8b1be19c Merge branch 'issue-401' of https://github.com/antoyo/qutebrowser into antoyo-issue-401 2015-05-31 19:46:17 +02:00
Antoni Boucher
3d0721afea Fixed error messages. 2015-05-31 12:56:08 -04:00
Antoni Boucher
27cbe618f0 Added hasSelection check before trying to click on a selected link. 2015-05-31 12:53:14 -04:00
Antoni Boucher
c0b6aef774 Fixed command name. 2015-05-31 12:50:28 -04:00
Antoni Boucher
d0eda3336c Added a page variable. 2015-05-31 12:18:27 -04:00
Antoni Boucher
1cd64481de Fixed for relative url. 2015-05-31 12:13:37 -04:00
Antoni Boucher
87e9888167 Added exception handling for href attribute. 2015-05-31 12:07:08 -04:00
Antoni Boucher
c5c145320c Fixed exception handling in select_follow command. 2015-05-31 12:02:15 -04:00
Antoni Boucher
4ff9d585ea Fixed to use qualified import. 2015-05-31 11:56:27 -04:00
Florian Bruhin
1e5c67f152 Merge branch 'Carpetsmoker-scroll_page_navigate' 2015-05-31 15:18:46 +02:00
Florian Bruhin
54c1cd7c05 Add link to issue. 2015-05-31 15:11:37 +02:00
Florian Bruhin
1814b672d7 Regenerate docs. 2015-05-31 15:11:04 +02:00
Florian Bruhin
6b550defae scroll-page: Add custom metavar for navigate-*. 2015-05-31 15:10:35 +02:00
Florian Bruhin
cdde1d7dfc command: Add support for custom metavar for docs. 2015-05-31 15:10:12 +02:00
Florian Bruhin
11b258568d Improve docstring. 2015-05-31 15:02:09 +02:00
Florian Bruhin
5b3ffa2419 Merge branch 'scroll_page_navigate' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-scroll_page_navigate 2015-05-31 14:59:22 +02:00
Lamar Pavel
b0bd8170e0 Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-05-31 10:34:30 +02:00
Florian Bruhin
81345eb17e Hide some QXcbWindow warnings. 2015-05-31 00:11:33 +02:00
Antoni Boucher
b1f8a70c02 Added try/except for parse error. 2015-05-30 18:03:39 -04:00
Florian Bruhin
0be0884a5b link_pyqt: Only link/copy files if they changed.
This reduces the output noise a bit and hopefully makes things a bit faster on
Windows.
2015-05-30 23:49:36 +02:00
Florian Bruhin
3879b8301f Remove unneeded int().
See #706.
2015-05-30 22:51:00 +02:00
Florian Bruhin
3c8e616eb9 Merge branch 'Carpetsmoker-issue-401' 2015-05-30 22:49:48 +02:00
Florian Bruhin
b501677c0e Regenerate authors 2015-05-30 22:48:24 +02:00
Florian Bruhin
5b891ecaca Merge branch 'issue-401' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-issue-401 2015-05-30 22:48:11 +02:00
Martin Tournoij
4dc54f881c Give a better error when wrapping on search
Previously, it just said "Text not found" when you hit the bottom.
2015-05-30 22:07:32 +02:00
Austin Anderson
5c599879f8 Fixed a line-length error. 2015-05-30 16:03:36 -04:00
Austin Anderson
b59dc8e89b Merge branch 'master' into more-color-settings 2015-05-30 15:56:11 -04:00
Austin Anderson
fed2cdad4e Cleaned up download configuration options. 2015-05-30 15:22:00 -04:00
Austin Anderson
7b5d2ace24 Added assertion for parameterized download color picker. 2015-05-30 15:21:34 -04:00
Antoni Boucher
989e3b7291 Added a fallback for when JavaScript is disabled. 2015-05-30 13:56:36 -04:00
Florian Bruhin
b1dd649278 Replace _ by - in command flag names.
See #698.
2015-05-30 19:30:08 +02:00
Florian Bruhin
e48e063c0f src2asciidoc.py: Improve exception handling. 2015-05-30 19:29:37 +02:00
Antoni Boucher
a56a14fb70 Added the possibility to open a selected link in a new tab. 2015-05-30 13:15:53 -04:00
Antoni Boucher
e92c493b07 Fixed bug making the application crash. 2015-05-30 12:37:21 -04:00
Antoni Boucher
6ca541d359 Fixed issue #401. 2015-05-30 10:37:25 -04:00
Martin Tournoij
70956aaeca oops 2015-05-29 23:57:57 +02:00
Martin Tournoij
9c99c22f1b Fix issue #701 2015-05-29 23:49:48 +02:00
Florian Bruhin
6d592c7c75 Merge branch 'Carpetsmoker-editor_temp_name' 2015-05-29 23:47:40 +02:00
Florian Bruhin
0d19d1bcf7 Regenerate authors 2015-05-29 23:47:01 +02:00
Florian Bruhin
1b89d880f5 Merge branch 'editor_temp_name' of https://github.com/Carpetsmoker/qutebrowser into Carpetsmoker-editor_temp_name 2015-05-29 23:46:46 +02:00
Martin Tournoij
8c80f99a32 Improve navigate option to scroll_page() 2015-05-29 21:18:44 +02:00
Martin Tournoij
c1dadeff6f Fix silly mistake... 2015-05-29 20:48:43 +02:00
Martin Tournoij
27fdf4903a Implement :jseval (Issue #334)
TODO:
- Tests
- Doesn't show errors
2015-05-29 18:36:39 +02:00
Martin Tournoij
c7dcaff025 Add navigate option to scroll_page()
So you can scroll down & navigate when you're at the bottom.

To bind this to space:

	scroll-page 0 1 next
		<Space>

Not sure if it's a good idea to bind this by default? May surprise some
people...

See #696
2015-05-29 18:35:15 +02:00
Martin Tournoij
f7b517f3aa Revert some accidental changes >_< 2015-05-29 17:08:01 +02:00
Florian Bruhin
48735315f8 docs: Fix typo. 2015-05-29 16:53:01 +02:00
Martin Tournoij
d20872d576 Fix feedback from #690 2015-05-29 14:50:15 +02:00
Martin Tournoij
c76221c14e Use a specific 'qutebrowser_editor_' prefix for <C-e> instead of 'tmp'.
Why does this matter? In my vimrc I have this:

	" When using dwb <C-e>; assume markdown, and don't store in viminfo since these are
	" temporary files
	autocmd BufRead,BufNewFile /home/martin/.cache/dwb/edit* setlocal ft=markdown viminfo=

I would like to do the same with qutebrowser, but this is not possible with a
file name like '/tmp/tmpSJsgSG4'
2015-05-29 02:07:20 +02:00
Antoni Boucher
cbc4ec6531 Added filter bookmarks by name as well as url. 2015-05-28 19:55:29 -04:00
Lamar Pavel
63c9e6a444 Another indentation-related fix 2015-05-28 13:20:00 +02:00
Lamar Pavel
f5d299d8c7 Fix intents 2015-05-28 13:05:12 +02:00
Lamar Pavel
b5eea81e2e Fix File.validate and corresponding tests
There were no tests regarding the return value of standarddir.config() and thus
it wasn't caught that it returned None in some cases. This is now fixed by
checking the return of standdarddir.config before calling it and modifying the
corresponding test_validate_exists_rel as well as adding a new
test_validate_rel_config_none.
2015-05-28 12:14:12 +02:00
Lamar Pavel
4851a3d442 Replace isabs with exists in transform
In UserStyleSheet.transform os.path.isabs was replaced with os.path.exists, a
more fitting condition. Accordingly two test cases needed to include mocks for
os.path.exists and QUrl.fromLocalFile.
2015-05-27 15:39:58 +02:00
Lamar Pavel
e12dce9d55 Include expandvars in File.transform, adjust test 2015-05-27 14:40:07 +02:00
Lamar Pavel
cfae36c5c8 Adjust name and doc of modified test 2015-05-27 14:05:29 +02:00
Lamar Pavel
4e61a6123e Probably shouldn't include changes to the gitignore in a PR 2015-05-27 12:06:51 +02:00
Lamar Pavel
f326fa28a6 Merge branch 'master' into relapaths
Sync with upstream/master before creating a pull request
2015-05-27 11:57:13 +02:00
Florian Bruhin
534dbfc4c2 tox: Update check-manifest to 0.25.
Upstream changelog:

    Stop dynamic computation of install_requires in setup.py: this doesn't work
    well in the presence of the pip 7 wheel cache. Use PEP-426 environment
    markers instead (this means we now require setuptools version 0.7 or
    newer).
2015-05-27 08:51:24 +02:00
Florian Bruhin
091353a773 Mention :adblock-update in quickstart. 2015-05-27 08:30:26 +02:00
Florian Bruhin
2a269e9cd9 tox: Make sipconfig.py optional in link_pyqt.py.
For some reason sipconfig.py doesn't exist at all on Windows...
2015-05-27 08:10:02 +02:00
Florian Bruhin
1b48dc8749 tox: Also provide sipconfig in link_pyqt.py. 2015-05-27 07:54:25 +02:00
Florian Bruhin
ddf86600d1 tests: Rename Testable* classes.
This hides some pytest warnings as it tried to collect those classes.
2015-05-27 07:51:53 +02:00
Florian Bruhin
6f3fa9dca6 tox: Show more information when testing. 2015-05-27 07:51:53 +02:00
Florian Bruhin
a969fe021d tox: Install requirements.txt for tests. 2015-05-27 07:45:21 +02:00
Florian Bruhin
6452c8f883 PyQIODevice: Add context manager support. 2015-05-26 20:57:11 +02:00
Florian Bruhin
b8dd71a343 PyQIODevice: Add .open()/.close(). 2015-05-26 20:57:11 +02:00
Florian Bruhin
460308f388 PyQIODevice: Don't use errorString for failed seek. 2015-05-26 20:57:11 +02:00
Florian Bruhin
6a26bc23ab PyQIODevice: Remove unneeded check. 2015-05-26 20:57:11 +02:00
Florian Bruhin
48de8b145b PyQIODevice: Properly fix read/readLine. 2015-05-26 20:57:01 +02:00
Florian Bruhin
0788054dd3 PyQIODevice: Expose underlying device. 2015-05-26 20:57:01 +02:00
Florian Bruhin
b2d763f993 PyQIODevice: Check if device is readable/writable. 2015-05-26 20:57:01 +02:00
Florian Bruhin
35f0b26f4a PyQIODevice: Remove readinto().
Our implementation was broken, and the BufferedIOBase mixin does a better job
at doing this.
2015-05-26 20:57:01 +02:00
Florian Bruhin
ba9c782824 PyQIODevice: First attempt at fixing read().
This was completely broken because one read overload doesn't exist in PyQt and
apparently it was never tested...
2015-05-26 20:56:51 +02:00
Florian Bruhin
fa69786b0f PyQIODevice: Raise ValueError when closed. 2015-05-26 20:49:58 +02:00
Florian Bruhin
e10da78a1a urlutils: Remove some more dead code. 2015-05-26 20:49:43 +02:00
Florian Bruhin
92abf4bdf8 tox: Update pytest-html to 1.3.1.
Upstream changelog:

1.3.1:

Fix encoding issue in Python 3

1.3:

Bump version number to 1.3
Simplify example in README
Show extra content in report regardless of test result
Support extra content in JSON format
2015-05-26 19:25:45 +02:00
Florian Bruhin
27e82ce6c8 Improve exception handling in qsavefile_open.
Sometimes exceptions were shadowed with new exceptions because of the file
flushing.
2015-05-26 19:25:05 +02:00
Lamar Pavel
f1129460d8 Class File now validates relative paths
The code from function validate in class UserStyleSheet has been migrated to
class File. One test had to be modified due to different expected behaviour.
2015-05-26 13:54:27 +02:00
Lamar Pavel
c54c637ccc Class File not transforms relative paths
The code from function transform in class UserStyleSheet is now migrated to
class File.
2015-05-26 12:38:04 +02:00
Florian Bruhin
e300b2e30d Update changelog. 2015-05-26 12:10:36 +02:00
Florian Bruhin
11c03d79cd Merge branch 'tharugrim-master' 2015-05-26 10:30:28 +02:00
Florian Bruhin
6b98c48985 Regenerate authors. 2015-05-26 10:30:21 +02:00
Tobias Patzl
b858b6ac75 call e.ignore() when the event is not handled 2015-05-26 10:24:32 +02:00
Austin Anderson
a8d2dbfdfb Added downloads bar fg customization, and refactored the download's color-picking. 2015-05-25 20:47:16 -04:00
Austin Anderson
0553094494 Added explanation of *.system values to settings page. 2015-05-25 19:20:33 -04:00
Tobias Patzl
61519e6383 move part of the logic to TabbedBrowser 2015-05-25 20:21:37 +02:00
Tobias Patzl
45dea54e3c Add setting to disable mousewheel tab switching.
See #374.
2015-05-25 15:23:14 +02:00
Florian Bruhin
a345b02729 Fix exception when downloading links without name.
We also set a default name to prevent "is a directory" errors.

This is a regression introduced in 8f33fcfc52.
Fixes #682.
2015-05-25 11:28:50 +02:00
Florian Bruhin
6d879bbca3 Exclude resources.py from coverage. 2015-05-25 01:38:17 +02:00
Florian Bruhin
0f13d9325b Don't use parametrization for deprecated keys.
This showed up as 2400 tests for what basically is one.
2015-05-25 01:26:52 +02:00
Antoni Boucher
ad763685e5 Added bookmark command default key binding. 2015-05-24 19:26:23 -04:00
Florian Bruhin
120d2e12b0 Improve QtValueError wording for ensure_not_null. 2015-05-25 01:21:57 +02:00
Antoni Boucher
ece32e930c Added bookmarks command. 2015-05-24 19:18:40 -04:00
Florian Bruhin
8d15bbdded utils.version: Add SIP line on ImportError. 2015-05-24 21:00:46 +02:00
Antoni Boucher
aaf35536a7 Removed unused commands and renamed bookmark-save command. 2015-05-23 16:02:02 -04:00
Antoni Boucher
0ee7e40e69 Fixed broken quickmarks completion. 2015-05-23 15:57:52 -04:00
Lamar Pavel
ad7920dda1 Fix bug; all tox tests succeed
My logic in the validate function of class UserStyleSheet was faulty and
caused the check for encoding to be skipped. This is now fixed and all
tests run successfully.
2015-05-23 16:49:40 +02:00
Lamar Pavel
93b92f4aab Fix tox failure regarding exceptions in transform
Function transform is not supposed to raise exceptions, so I wrapped the
call to os.path.join in an if-clause to test if standarddir.config
returns a valid value.
2015-05-23 16:09:44 +02:00
Lamar Pavel
61f32b3e9b Revert some changes, trying to get rid of the tox failures 2015-05-22 18:40:56 +02:00
Lamar Pavel
14ba20670b Fix potential bug with missing path-expansion
The last commit removed two lines in function validate of class
UserStyleSheet that were expanding the path. As it turns out those two
lines are needed by validate as well as transform, so I outsourced them
to the function they both call at that point.
2015-05-22 17:31:37 +02:00
Lamar Pavel
29b25206f6 Fix UserStyleSheet, roll back File
The former version of UserStyleSheet never actually loaded the css file,
this is now fixed. The changes to class File were rolled back as its
functions are overloaded by UserStyleSheet; a general solution in
classes File and Directory can be implemented when the changes in
UserStyleSheet meet the expectation.
2015-05-22 17:21:00 +02:00
Lamar Pavel
58f031630c user-stylesheet can be read from relative paths
This ist just a first draft to approach issue622
(https://github.com/The-Compiler/qutebrowser/issues/622) and my very
first babysteps with python.

With this change it is possible to set a user-stylesheet with a relative
path, eg.:

    :set ui user-stylesheet mystyle.css

where mystyle.css is in the ~/.config/qutebrowser/.
2015-05-22 14:44:04 +02:00
Antoni Boucher
2c0c2e220e Fixed style issue. 2015-05-21 19:38:30 -04:00
Antoni Boucher
28caf45707 First version of bookmarks. 2015-05-21 18:17:22 -04:00
Florian Bruhin
ee0eabc202 scripts: Add --profile-dot to run_profile. 2015-05-21 07:45:20 +02:00
Florian Bruhin
43898ebb71 Update changelog. 2015-05-20 13:38:56 +02:00
Florian Bruhin
0252f5fdbf tox: Update pytest-html to 1.2. 2015-05-20 13:37:44 +02:00
Florian Bruhin
aaab05793e urlutils: Handle localhost correctly in all cases. 2015-05-20 13:36:40 +02:00
Florian Bruhin
ddb6743b26 urlutils: Clean up qurl_from_user_input in is_url. 2015-05-20 13:36:40 +02:00
Florian Bruhin
269676318b urlutils: Raise exception on errors in host_tuple. 2015-05-20 13:36:40 +02:00
Florian Bruhin
6f904759b5 urlutils: Fix str() of FuzzyUrlError. 2015-05-20 13:36:40 +02:00
Florian Bruhin
f8db4b8147 urlutils: Improve debug logging. 2015-05-20 13:36:40 +02:00
Florian Bruhin
14df72a7a1 urlutils: Add get_errstring(). 2015-05-20 13:36:40 +02:00
Florian Bruhin
e590bf26ad urlutils: Check bogus IPs in _is_url_dns. 2015-05-20 13:36:40 +02:00
Florian Bruhin
40cc354030 urlutils: Pass URL string to _is_url_dns. 2015-05-20 13:36:40 +02:00
Florian Bruhin
c0b41d8c62 urlutils: Use utils.raises in _is_url_naive. 2015-05-20 13:36:40 +02:00
Florian Bruhin
1f048a38f8 urlutils: Remove dead code in _get_search_url.
term should always contain something.
2015-05-20 13:36:40 +02:00
Florian Bruhin
e187cda292 Sort attrs for utils.get_repr(). 2015-05-19 16:23:50 +02:00
Florian Bruhin
daaf7a62c8 tox: Update pytest to 2.7.1. 2015-05-19 12:38:13 +02:00
Florian Bruhin
341708f543 Refactor readline tests.
They now use a real QLineEdit and verify a lot more.

See #660, #678.
2015-05-19 12:36:07 +02:00
Florian Bruhin
ad181ec7eb Spelling fix on cheatsheet. 2015-05-19 08:58:27 +02:00
Florian Bruhin
069d7b26a2 pytest: Use common fixture for fake_keyconfig. 2015-05-19 07:46:56 +02:00
Florian Bruhin
cc88451003 Update cheatsheet. 2015-05-19 06:43:42 +02:00
Florian Bruhin
7ca9a007f8 Update changelog. 2015-05-19 06:40:42 +02:00
Florian Bruhin
b78d5f57aa Make new-instance-open-target docs more clear. 2015-05-19 06:13:29 +02:00
Florian Bruhin
98d1fca220 Use monkeypatch instead of mocker in some tests.
See #660.
2015-05-18 23:32:01 +02:00
Florian Bruhin
beb970d7d5 Strip whitespace for position_caret tests.
It seems on Windows, QWebPage.SelectNextWord includes the trailing space. This
should fix those tests on Windows.
2015-05-18 23:04:11 +02:00
Florian Bruhin
37b431f72f Fix lint. 2015-05-18 22:37:48 +02:00
Florian Bruhin
9a1cf2b03a Fix deprecated config. 2015-05-18 22:36:10 +02:00
Florian Bruhin
e0dee14df4 Regenerate docs. 2015-05-18 22:34:21 +02:00
Florian Bruhin
f2e2748c59 Fix quotes. 2015-05-18 22:32:17 +02:00
Florian Bruhin
03e59051dc Remove count for move-to-end-of-line. 2015-05-18 22:32:17 +02:00
Florian Bruhin
91ad91cc7b Spelling fixes. 2015-05-18 22:27:44 +02:00
Florian Bruhin
b650ec75f3 Merge branch 'visual' 2015-05-18 22:25:03 +02:00
Florian Bruhin
c00dccfbb2 src2asciidoc: Improve error output on missing count arg. 2015-05-18 22:23:39 +02:00
Florian Bruhin
8941b5dc96 Merge branch 'visual' 2015-05-18 21:43:25 +02:00
Florian Bruhin
8e417970c3 Merge branch 'pytest-rewrites' 2015-05-18 21:42:15 +02:00
Florian Bruhin
1a957b6c10 Merge pull request #668 from The-Compiler/visual-tests
Add javascript tests for position_caret.js.
2015-05-18 21:40:26 +02:00
Florian Bruhin
8eb483d66b Set Qt.ItemNeverHasChildren for leaf model items.
This allows Qt to do some optimizations.
2015-05-18 21:35:14 +02:00
Florian Bruhin
dd292b0781 Don't depend on objreg for CommandDispatcher.
See #640.
2015-05-18 21:34:00 +02:00
Florian Bruhin
54eae77328 Fix tests on OS X, take three. 2015-05-17 19:04:07 +02:00
Florian Bruhin
81ba49e79b Fix tests on OS X, take two. 2015-05-17 18:59:40 +02:00
Florian Bruhin
a9f5d45c34 Fix tests on OS X. 2015-05-17 18:52:55 +02:00
Florian Bruhin
e24d2e1b8c Update changelog. 2015-05-17 18:50:08 +02:00
Florian Bruhin
10985c3505 Fix handling of Meta/Control on OS X.
Fixes #110.
See #420.

See http://doc.qt.io/qt-5.4/osx-issues.html#special-keys :

    To provide the expected behavior for Qt applications on OS X, the Qt::Meta,
    Qt::MetaModifier, and Qt::META enum values correspond to the Control keys
    on the standard Apple keyboard, and the Qt::Control, Qt::ControlModifier,
    and Qt::CTRL enum values correspond to the Command keys.
2015-05-17 18:44:36 +02:00
Florian Bruhin
5ef40829aa tox: Pass $USERNAME and $USER for smoke env.
This fixes getpass.getuser() on Windows for the smoke tests.
2015-05-17 18:14:21 +02:00
Florian Bruhin
b60f673468 Fix @pyqtSlot signature for on_config_changed. 2015-05-17 14:14:23 +02:00
Florian Bruhin
8ab2772dd9 Use atexit to remove temp basedir.
This hopefully fixes a PermissionError on Windows.
2015-05-17 01:29:47 +02:00
Florian Bruhin
f17238d3d4 tox: Set QT_QPA_PLATFORM_PLUGIN_PATH for smoke.
This fixes smoke tests on Windows (I think).
2015-05-17 01:19:23 +02:00
Florian Bruhin
b5dc4ea040 tox: Use absolute path for -rrequirements.txt.
This fixes a FileNotFoundError on Ubuntu Trusty.
2015-05-17 01:18:19 +02:00
Florian Bruhin
7fc99f3d80 adblock: Don't show message with --basedir given. 2015-05-17 01:09:33 +02:00
Florian Bruhin
f54c416ddd tox: Fixes for smoke environment. 2015-05-17 01:07:36 +02:00
Florian Bruhin
f6ad556f34 Get rid of --no-crash-dialog. 2015-05-17 01:03:34 +02:00
Florian Bruhin
b94fcf2c3c Clean up sys.exit call. 2015-05-17 00:52:37 +02:00
Florian Bruhin
315725a3ac Print info with --no-err-windows on earlyinit errs. 2015-05-17 00:48:15 +02:00
Florian Bruhin
002346a125 Clean up exception_hook. 2015-05-17 00:44:04 +02:00
Florian Bruhin
b619d835e6 Make usertypes.Exit an IntEnum. 2015-05-17 00:29:28 +02:00
Florian Bruhin
3f98bf372e Merge branch 'smoke'
Conflicts:
      doc/qutebrowser.1.asciidoc
      qutebrowser/app.py
      qutebrowser/config/config.py
      qutebrowser/qutebrowser.py
      tox.ini
2015-05-17 00:28:56 +02:00
Florian Bruhin
9be5992a9a Smoke test WIP 2015-05-16 23:43:34 +02:00
Florian Bruhin
62426380e5 Update changelog. 2015-05-16 23:29:23 +02:00
Florian Bruhin
a1f7eed5a7 Add --temp-basedir option. 2015-05-16 23:26:15 +02:00
Florian Bruhin
d7999577dd Fix shutdown from pdb. 2015-05-16 23:13:36 +02:00
Florian Bruhin
54131e9d3e Add --basedir arg with multiple instance support.
Closes #510.
2015-05-16 23:10:20 +02:00
Florian Bruhin
aab5411317 Fix test function name. 2015-05-16 23:06:33 +02:00
Florian Bruhin
183049ef2e Make sure self._opened is reset on exceptions. 2015-05-16 22:48:13 +02:00
Florian Bruhin
42c27ddbc0 Use temp dir for standarddir arg tests. 2015-05-16 22:30:00 +02:00
Florian Bruhin
c762340a0c Add --datadir/--cachedir arguments. Closes #136. 2015-05-16 22:12:27 +02:00
Florian Bruhin
9b372de4a9 Use fake-key scrolling for :scroll-perc 0/100. 2015-05-16 15:51:41 +02:00
Florian Bruhin
4dbc4ba93f tox: Fix QT_QPA_PLATFORM_PLUGIN_PATH.
See 677cfc9410.
2015-05-16 14:22:56 +02:00
Florian Bruhin
dd83a40df4 tox: Set passenv for tox 2.0. 2015-05-16 14:13:24 +02:00
Florian Bruhin
677cfc9410 tox: envsitespackagedir workaround for tox 2.0.1. 2015-05-16 14:12:33 +02:00
Florian Bruhin
c91344cdf5 scripts: Add docstring for keytester. 2015-05-16 14:12:16 +02:00
Florian Bruhin
137badc77f Add some more informations to keytester script.
See #658, #420.
2015-05-16 12:57:29 +02:00
Florian Bruhin
ad338e7a17 Add setting to enable/disable hyperlink auditing.
See #612.
2015-05-16 00:46:39 +02:00
Florian Bruhin
0cabedfeef Add settings to enable/disable CSS regions.
See #612.
2015-05-16 00:46:27 +02:00
Florian Bruhin
cd53318c7f Add setting to enable/disable WebGL.
See #612.
2015-05-16 00:31:13 +02:00
Florian Bruhin
f855d5f349 Add support for smooth scrolling.
See #612.
2015-05-15 23:53:08 +02:00
Florian Bruhin
e3bfe73442 Fix :scroll-page. 2015-05-15 21:32:42 +02:00
Florian Bruhin
7e2c67a7e4 Fix tests/lint. 2015-05-15 20:25:29 +02:00
Florian Bruhin
12940eb542 Handle QtInfoMsg (Qt 5.5) in qt_message_handler. 2015-05-15 20:15:09 +02:00
Florian Bruhin
1a1a8ba26f Update changelog. 2015-05-15 19:28:41 +02:00
Florian Bruhin
1a67794293 Regenerate docs. 2015-05-15 19:19:49 +02:00
Florian Bruhin
aaf09dc573 Add possibility to hide command args from docs. 2015-05-15 19:19:30 +02:00
Florian Bruhin
f49dba6e38 Use fake key events for scrolling.
Closes #669.
Fixes #218.

See #246, #534.
2015-05-15 19:02:33 +02:00
Florian Bruhin
c236046a73 Avoid double-opening LineParser.
Hopefully helps with diagnosing #670.
2015-05-14 15:11:16 +02:00
Florian Bruhin
17fc6622bb Strip NUL bytes when loading history.
This is a workaround so people can start qutebrowser again, but the real bug
should be found and fixed...

See #670.
2015-05-13 23:46:22 +02:00
Florian Bruhin
d992caf8fc Clean up statusbar caret handling. 2015-05-13 22:44:37 +02:00
Florian Bruhin
947dcd556b Clean up CaretKeyParser. 2015-05-13 22:29:21 +02:00
Florian Bruhin
bc54eb8671 Make get_modeman private again. 2015-05-13 22:27:54 +02:00
Florian Bruhin
222627b08d Clean up caret initialisation. 2015-05-13 22:25:21 +02:00
Florian Bruhin
a728704cce toggle-selection cleanup 2015-05-13 21:52:42 +02:00
Florian Bruhin
f8f8699ab8 Fix key config migration for rapid hinting. 2015-05-13 10:45:20 +02:00
Florian Bruhin
5d13d0073c Add some tests for key config migrations. 2015-05-13 10:41:23 +02:00
Florian Bruhin
f6ef657952 Fix default search binding. 2015-05-13 08:26:56 +02:00
Florian Bruhin
25005ded8a Add a test for deprecated default bindings. 2015-05-13 08:26:19 +02:00
Florian Bruhin
a93bf184aa Fix lint. 2015-05-13 08:05:33 +02:00
Florian Bruhin
f59a147589 Leave mode when yanking by default.
See #653.
2015-05-13 07:58:33 +02:00
Florian Bruhin
866b299fef Fix adding of new default section to keyconf.
When trying to add a new binding with multiple values, the bindings were added
immediately and the next _is_new() check returned False because the command was
already bound.

With this change, the new bindings first get added to a temporary dict so
_is_new() returns the correct result.

See #653.
2015-05-13 07:55:49 +02:00
Florian Bruhin
a74a9c8a21 Fix adding of new default section to keyconf.
When trying to add a new binding with multiple values, the bindings were added
immediately and the next _is_new() check returned False because the command was
already bound.

With this change, the new bindings first get added to a temporary dict so
_is_new() returns the correct result.

See #653.
2015-05-13 07:54:06 +02:00
Florian Bruhin
88fc186402 Add tmux-like Enter binding.
See #653.
2015-05-13 07:29:59 +02:00
Florian Bruhin
ce1b82616d Fix spelling. 2015-05-13 07:29:59 +02:00
Florian Bruhin
dd0e230a32 Re-add v keybinding for toggle-selection.
See #653.
2015-05-13 07:29:59 +02:00
Florian Bruhin
e35d284282 Remove blank line. 2015-05-13 06:32:09 +02:00
Florian Bruhin
9fde38d96a Reset CaretBrowsingEnabled to original value. 2015-05-13 06:31:48 +02:00
Florian Bruhin
e62ba57291 Always save last window session.
len(objreg.window_registry) can actually lag behind because single-shot QTimers
are used to remove the windows from the registry - but actually it doesn't even
matter if this is the last window or not. We just always save to
SessionManager._last_window_session, and it gets used in SessionManager.save.

Fixes #650.
2015-05-12 21:04:18 +02:00
Florian Bruhin
2775f2b2ee Add some more tests. 2015-05-12 19:15:27 +02:00
Florian Bruhin
7edfdaa271 Add test for invisible elements. 2015-05-12 19:08:54 +02:00
Florian Bruhin
54ae6a63ee Fix lint. 2015-05-12 17:58:53 +02:00
Florian Bruhin
2b440bc8db Handle QWebPage javascript methods. 2015-05-12 17:44:06 +02:00
Florian Bruhin
27a34d5499 Close anchor. 2015-05-12 17:32:33 +02:00
Florian Bruhin
aa2e5a35d6 Add javascript tests for position_caret.js. 2015-05-12 17:05:01 +02:00
Florian Bruhin
ae512f451e Fix lint. 2015-05-12 09:10:02 +02:00
Florian Bruhin
c88393ccfd Add minimal key tester script.
See #658.
2015-05-12 09:03:25 +02:00
Florian Bruhin
d9655f5eb9 Merge branch 'Zach-Button-master' 2015-05-12 07:50:09 +02:00
Florian Bruhin
3cb756699f Regenerate authors. 2015-05-12 07:50:03 +02:00
Florian Bruhin
785f948bc7 Correct typo. 2015-05-12 07:49:53 +02:00
Florian Bruhin
38ac2c6598 Merge branch 'master' of https://github.com/Zach-Button/qutebrowser into Zach-Button-master 2015-05-12 07:49:29 +02:00
Florian Bruhin
a960658617 js: Fix more lint. 2015-05-12 07:16:16 +02:00
Florian Bruhin
28ec7b4698 js: Fix radix parameters. 2015-05-12 07:16:13 +02:00
Florian Bruhin
d1e88c5e8d js: Add 'var'. 2015-05-12 07:16:10 +02:00
Florian Bruhin
3f21ac6b6a js: Use an IIFE. 2015-05-12 07:16:10 +02:00
Florian Bruhin
7a67af24f0 js: Fix some lint. 2015-05-12 07:16:08 +02:00
Florian Bruhin
f36a7444d7 js: Add .eslintrc. 2015-05-12 07:16:05 +02:00
Austin Anderson
229733f1b0 Properly distinguish between statusbar modes when styling line input. 2015-05-11 22:46:26 -04:00
Austin Anderson
0d66647918 Set extra foreground colors to match the default by default. 2015-05-11 22:35:44 -04:00
Austin Anderson
14c1332017 Reordered statusbar stylesheet to match configuration ordering. 2015-05-11 22:28:12 -04:00
Austin Anderson
1a2a57d59e Added command mode color configuration options.
Including necessary tracker variable _command_active.
2015-05-11 22:27:21 -04:00
Florian Bruhin
418934644b Improve docstrings. 2015-05-11 22:29:44 +02:00
Florian Bruhin
8b435ec88f doc: Improve Arch install instructions. 2015-05-11 22:23:03 +02:00
Florian Bruhin
756aa3e16f Fix tests because of new '0' key handling. 2015-05-11 21:10:18 +02:00
Florian Bruhin
1f94e0fee6 js: Remove obsolete argument to createTreeWalker.
"createNodeIterator() and createTreeWalker() now have optional arguments and
lack a fourth argument which is no longer relevant given entity references
never made it into the DOM."
2015-05-11 20:33:42 +02:00
Florian Bruhin
37050c49fc Include .js files in MANIFEST. 2015-05-11 20:33:16 +02:00
Florian Bruhin
a36c0fcd4c Fix lint. 2015-05-11 20:32:27 +02:00
Florian Bruhin
d3c6ebcf15 Rename caret_selection to caret-selection. 2015-05-11 20:21:01 +02:00
Florian Bruhin
012e124eaf Merge pull request #653 from artur-shaik/visual
Visual
2015-05-11 20:19:01 +02:00
Florian Bruhin
9fadc78e4d Update changelog. 2015-05-11 19:51:49 +02:00
Florian Bruhin
6f620a6a9e Handle title correctly for pages without title.
Fixes #667.
2015-05-11 19:11:49 +02:00
Florian Bruhin
21dcf73e38 Add testresults.html to .gitignore. 2015-05-10 22:10:30 +02:00
Florian Bruhin
18eace37f8 tox: Add pytest-html. 2015-05-10 21:47:05 +02:00
Austin Anderson
244d2753df Reordered fg/bg statusbar color options
Options are now all fg, bg for each variant.
2015-05-10 15:33:58 -04:00
Florian Bruhin
452e03f9af Rewrite test_lineparser.py to use pytest.
See #660.
2015-05-10 16:19:30 +02:00
Florian Bruhin
db0a54b03f Rewrite test_crashdialog.py to use pytest.
See #660.
2015-05-10 16:19:30 +02:00
Florian Bruhin
392fb3e1d7 Rewrite test_neighborlist.py to use pytest.
See #660.
2015-05-10 16:19:30 +02:00
Florian Bruhin
021c94eece Rewrite test_enum.py to use pytest.
See #660.
2015-05-10 16:19:30 +02:00
Florian Bruhin
8398fe3bdd Rewrite test_log.py to use pytest.
See #660.
2015-05-10 16:19:30 +02:00
Florian Bruhin
99a4765e75 Fix confusing websetting log output. 2015-05-10 14:50:56 +02:00
Austin Anderson
69f729dbe5 Added foreground color settings for statusbar messages. 2015-05-09 18:07:40 -04:00
Florian Bruhin
41ecc0ad3d Merge remote-tracking branch 'github/master' 2015-05-07 22:57:19 +02:00
Florian Bruhin
f9876823b8 Add a new config_stub fixture.
This replaces various other constructs:

- The default_config fixture - this means the config values used by
  test_progress.py are set explicitly and the (rather complex) default config
  is mocked out.

- stubs.ConfigStub which was created by the tests manually before.
2015-05-07 22:56:31 +02:00
Florian Bruhin
7975bd8796 Remove unused import. 2015-05-07 22:55:21 +02:00
Florian Bruhin
8837abc208 Merge pull request #659 from The-Compiler/config-stub
Add a new config_stub fixture.
2015-05-07 22:17:56 +02:00
Florian Bruhin
ad822b72c7 tox: Update py to 1.27.
Upstream changelog:

    - fix issue59: point to new repo site

    - allow a new ensuresyspath="append" mode for py.path.local.pyimport()
      so that a neccessary import path is appended instead of prepended to
      sys.path

    - strike undocumented, untested argument to py.path.local.pypkgpath

    - speed up py.path.local.dirpath by a factor of 10
2015-05-07 14:50:32 +02:00
Florian Bruhin
ec43aab999 Add a new config_stub fixture.
This replaces various other constructs:

- The default_config fixture - this means the config values used by
  test_progress.py are set explicitly and the (rather complex) default config
  is mocked out.

- stubs.ConfigStub which was created by the tests manually before.
2015-05-07 09:50:25 +02:00
Florian Bruhin
3b5b49daac Move quitter/signal/crash_handler out of qApp. 2015-05-07 09:23:34 +02:00
Artur Shaik
57cad14714 Move JS snippet in external js file. 2015-05-07 12:41:02 +06:00
Artur Shaik
778ad5df3a Comment clean. 2015-05-07 12:23:33 +06:00
Artur Shaik
d936be450b Add jumps through text blocks in caret mode. 2015-05-07 12:19:35 +06:00
Artur Shaik
178d0dfa58 Add count for actions. Zero key treat as command. 2015-05-07 11:51:10 +06:00
Florian Bruhin
564a589bc6 Fix indent. 2015-05-06 23:36:01 +02:00
Florian Bruhin
9ceb43ec44 Make F (:hint tab) honour background-tabs.
Fixes #621.
2015-05-06 23:25:42 +02:00
Florian Bruhin
98596d439f Emit ClickTarget from HintManager.start_hinting.
This is much clearer than transmitting a string which must match the
ClickTarget enum.
2015-05-06 23:17:23 +02:00
Florian Bruhin
f99a070735 Update docs. 2015-05-06 22:46:41 +02:00
Florian Bruhin
21dfcf1e1b Add some bindings to switch hint modes.
Fixes #613.
2015-05-06 22:38:41 +02:00
Florian Bruhin
2f0b976bca Leave and re-enter hint mode when double-hinting.
See #613.
2015-05-06 22:38:08 +02:00
Florian Bruhin
9a5839650c Allow 'yes' value for geolocation/notifications.
Fixes #655.
2015-05-06 22:21:11 +02:00
Florian Bruhin
deb3c31f2f Merge branch 'refactor' 2015-05-06 21:51:04 +02:00
Florian Bruhin
2d91ff3f5d Fix line lengths. 2015-05-06 16:47:52 +02:00
Florian Bruhin
4fb026708b Merge branch 'V155-master' 2015-05-06 16:38:27 +02:00
Florian Bruhin
5b2013c037 Regenerate authors. 2015-05-06 16:38:22 +02:00
Fritz V155 Reichwald
b98bafaefe Add C-M and C-J for every command that got Return as key 2015-05-06 16:33:12 +02:00
Fritz V155 Reichwald
8806aac362 Add Ctrl-M as keybind for command-accept 2015-05-06 16:11:30 +02:00
Florian Bruhin
903d437943 Fix flaky log_time test. 2015-05-06 11:21:49 +02:00
Florian Bruhin
024549e3b0 Use a namedtuple for authentication prompts. 2015-05-06 11:05:17 +02:00
Florian Bruhin
842c69dfdd Cache proxy authentication credentials. 2015-05-06 10:46:42 +02:00
Florian Bruhin
2777e4113e Fix shutdown 2015-05-06 07:35:11 +02:00
Florian Bruhin
8aec5244de Fix crash restart. 2015-05-06 07:11:14 +02:00
Artur Shaik
15c8a937f4 Merge branch 'visual' of github.com:artur-shaik/qutebrowser into visual 2015-05-05 12:24:57 +06:00
Artur Shaik
489c913e58 Implement caret selection and positioning
Added option to webview for selection enabled caret mode.
In status bar checking value of this option to identificate about it.

Added bindings: <Space> for toggle selection mode, <Ctrl+Space> drop
selection and keep selection mode enabled.

In webview added javascript snippet to position caret at top of the
viewport after caret enabling. This code mostly was taken from cVim sources.
2015-05-05 12:21:48 +06:00
Artur Shaik
d594798db8 Implement caret selection and positioning
Added option to webview for selection enabled caret mode.
In status bar checking value of this option to identificate about it.

Added bindings: <Space> for toggle selection mode, <Ctrl+Space> drop
selection and keep selection mode enabled.

In webview added javascript snippet to position caret at top of the
viewport after caret enabling. This code mostly was taken from cVim sources.
2015-05-05 10:18:24 +06:00
Artur Shaik
aeaa20c3b7 Disable support count for CaretKeyParser
Allow using '0' for move caret to beginnig of the line.
2015-05-04 18:00:40 +06:00
Florian Bruhin
46dbfa2fce Mention Debian's experimental repo. 2015-05-04 13:37:07 +02:00
Florian Bruhin
530fe5e933 tox.ini: Update pytest-mock to 0.5.
Changelog:
    Mock and Magic mock are now accessible from the mocker fixture.
2015-05-04 07:47:58 +02:00
Florian Bruhin
f499fd85d0 Fix IPC. 2015-05-01 14:46:17 +02:00
Florian Bruhin
d3a7b2e4ca Big refactoring of app.py. 2015-04-30 07:37:25 +02:00
Zach-Button
d496ea2d59 Update dmenu_qutebrowser 2015-04-28 11:02:45 -06:00
Florian Bruhin
32562c6878 Fix lint. 2015-04-28 16:50:42 +02:00
Florian Bruhin
9e8c781871 Use clearFocus/setFocus as workaround. 2015-04-28 16:12:23 +02:00
Florian Bruhin
640f758605 Merge branch 'master' into visual
Conflicts:
	qutebrowser/browser/commands.py
2015-04-28 15:54:26 +02:00
Florian Bruhin
1903792239 tox: Update pyroma to 1.8.1.
Changelog:
    - More robust rating. [Jeff Quast]
    - Closed #24. ("pyroma some_pypi_package" fails)
2015-04-27 13:07:57 +02:00
Zach-Button
329030e913 Update qutebrowser_viewsource 2015-04-24 14:05:27 -06:00
Zach-Button
205f37fe09 Update dmenu_qutebrowser 2015-04-24 14:04:27 -06:00
Florian Bruhin
8edfa4281e Revert "tox.ini: Use pytest-qt from git."
This reverts commit 71608af486.
2015-04-24 17:34:10 +02:00
Florian Bruhin
f5227ef982 Update changelog. 2015-04-24 17:33:59 +02:00
Florian Bruhin
844473e47a Fix /-foo searches. 2015-04-24 17:25:53 +02:00
Florian Bruhin
71608af486 tox.ini: Use pytest-qt from git.
See https://github.com/pytest-dev/pytest-qt/pull/38.
2015-04-22 18:12:03 +02:00
Florian Bruhin
8e0ef128c9 Regenerate authors. 2015-04-22 15:46:26 +02:00
Florian Bruhin
07552dddfe Merge pull request #648 from hackebrot/sync-2
Sync pytest changes
2015-04-22 06:40:04 -07:00
Florian Bruhin
e1f2259e98 Fix typo. 2015-04-22 07:46:01 +02:00
Florian Bruhin
4925091ede Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-04-22 07:43:01 +02:00
Florian Bruhin
09c77cfa83 Merge pull request #25 from hackebrot/markers
Markers
2015-04-21 22:41:57 -07:00
Florian Bruhin
c21ae0b651 Add a :debug-webaction command. 2015-04-22 07:13:56 +02:00
Zach-Button
049955dfd5 Change path to use mktemp
Path now uses mktemp instead of timestamp
2015-04-21 16:12:05 -06:00
Zach-Button
5359463d79 Add misc/userscripts
- Added misc/userscripts/dmenu_qutebrowser
- Added misc/userscripts/qutebrowser_viewsource
2015-04-21 14:52:43 -06:00
Florian Bruhin
6ca39dd851 Handle --relaxed-config for keys.conf as well. 2015-04-21 22:48:45 +02:00
Florian Bruhin
6c8e073dc8 Merge branch 'caret_visual_mode' of https://github.com/artur-shaik/qutebrowser into visual
Conflicts:
      qutebrowser/browser/commands.py
      qutebrowser/browser/webview.py
      qutebrowser/config/configdata.py
2015-04-21 21:29:00 +02:00
Florian Bruhin
3164ee06eb Handle new sections in KeyConfgParser._is_new(). 2015-04-21 18:32:32 +02:00
Florian Bruhin
9f443d026a Make pylint shut up. 2015-04-20 23:12:15 +02:00
Florian Bruhin
e783310eb4 Merge pull request #24 from hackebrot/misc-widgets-tests
Add tests for CommandLineEdit
2015-04-20 14:00:34 -07:00
Florian Bruhin
a7dfdd48e0 Fix lint. 2015-04-20 22:59:35 +02:00
Florian Bruhin
9ee74253e4 Remove name annotation for cmdutils.register.
See #637.
2015-04-20 22:25:27 +02:00
Florian Bruhin
b805f903c9 Fix lint. 2015-04-20 20:50:51 +02:00
Florian Bruhin
f7cf33b596 Remember web inspector geometry in state file. 2015-04-20 20:40:03 +02:00
Florian Bruhin
2a0a2d926e Adjust CONTRIBUTING. 2015-04-20 20:09:25 +02:00
Florian Bruhin
7439586334 Move special params to cmdutils.register decorator
See #637.
2015-04-20 19:33:05 +02:00
Florian Bruhin
0195cb31bb Don't set scope in cmdutils.register w/o instance. 2015-04-20 18:55:22 +02:00
Florian Bruhin
8f1b074595 Show commandline being executed with :spawn.
Closes #616.
2015-04-20 18:44:58 +02:00
Florian Bruhin
94d49b4801 Add :message-{info,error,warning} commands. 2015-04-20 18:32:15 +02:00
Florian Bruhin
1b13b0c385 Add --strict to pytest invocation. 2015-04-20 18:02:59 +02:00
Florian Bruhin
c098d0de37 Register the gui marker in tox.ini. 2015-04-20 18:02:04 +02:00
Bruno Oliveira
69061c5629 Remove LimitLineParser from test
As suggested by @The-Compiler, this is not really necessary
2015-04-20 12:51:36 -03:00
Florian Bruhin
f93eef848c Store QUTE_TEXT/QUTE_HTML in files for userscripts.
Fixes #644.
2015-04-20 07:50:47 +02:00
Bruno Oliveira
f55242ad93 Use pytest-mock to install QApplication.clipboard mock 2015-04-19 17:13:47 -03:00
Bruno Oliveira
2d19708a41 Play nice with other plugins in conftest.py
Some plugins might create their own Item subclasses without
a `fixturenames` attribute. Discovered while taking pytest-flakes
for a spin.
2015-04-19 17:11:29 -03:00
Raphael Pierzina
6c97a4a6e0 Remove blank line at end of file to fix flake8 2015-04-19 21:10:27 +02:00
Florian Bruhin
9442fd4b75 Release v0.2.1 2015-04-19 20:04:14 +02:00
Florian Bruhin
78bbbb968f Update CHANGELOG for v0.2.1. 2015-04-19 20:01:05 +02:00
Florian Bruhin
66640df541 Fix MANIFET.in to Include qutebrowser.1.asciidoc. 2015-04-19 19:46:52 +02:00
Florian Bruhin
5b48b0a7fe Release v0.2.0 2015-04-19 18:29:27 +02:00
Florian Bruhin
42577b454b Fix download view popping up as window on start.
This is a regression introduced in cc2c7c09ea as
show() was called before the downloadview was added to the mainwindow vbox.

See #575.
2015-04-19 17:30:58 +02:00
Florian Bruhin
900fe3aa08 Update changelog for v0.2.0. 2015-04-19 17:04:27 +02:00
Florian Bruhin
43df32949d Refactor IPC exceptions handling.
Also fixes an IPC error when qutebrowser was started twice without delay
between the invocations.
2015-04-17 19:24:27 +02:00
Florian Bruhin
38d34e1dea Fix deprecation message for :cancel-download. 2015-04-17 14:33:45 +02:00
Florian Bruhin
4436941d97 Clean up unused imports. 2015-04-17 08:00:56 +02:00
Florian Bruhin
006b7716c8 Move WebPage init out from QWebView's __init__. 2015-04-17 08:00:15 +02:00
Florian Bruhin
ffd1e673b3 Get rid of SearchRunner.
A SearchRunner was per-mainwindow, which caused bugs when searching in a tab
and in another before clearing the search.

Instead we now split it between WebView/CommandDispatcher.

Fixes #638.
2015-04-17 07:51:21 +02:00
Florian Bruhin
71ffe8f656 Use config.get() for qute:settings.
Fixes #628.
2015-04-16 22:20:38 +02:00
Florian Bruhin
cc738fa846 Make test_log_time less flaky. 2015-04-16 08:58:48 +02:00
Florian Bruhin
508993ac68 Add a special <unbound> command for the keyconf.
When a default keybinding is unbound it'd get readded, so we add a new special
<unbound> "command" to the config, and keys listed there won't get rebound
automatically.

Also, :unbind now maps the key to <unbound> if it exists in the default config.

See #525.
2015-04-16 07:49:27 +02:00
Florian Bruhin
980b3506a3 Don't unnecessarily mark the key config as dirty. 2015-04-16 07:48:57 +02:00
Florian Bruhin
3cf6d1c185 Add missing new keybindings to config.
See #525.
2015-04-16 06:41:00 +02:00
Bruno Oliveira
f5e6091ff6 Add tests for CommandLineEdit 2015-04-15 20:22:03 -03:00
Florian Bruhin
f313bcaf13 Fix for cache never being saved.
This is a regression introduced in 76c5c8bf8e.
See #43.
2015-04-15 06:54:55 +02:00
Florian Bruhin
d8d29449ca Improve error message on duplicate keychains. 2015-04-14 07:13:52 +02:00
Florian Bruhin
987bab9960 Merge pull request #19 from hackebrot/parametrize-sub-tests
Parametrize sub tests
2015-04-14 07:01:53 +02:00
Florian Bruhin
ba678e29fb Fix lint. 2015-04-14 07:00:56 +02:00
Florian Bruhin
10214a8b83 Merge pull request #23 from hackebrot/single-qnam
Use a single QNetworkAccessManager per session.
2015-04-14 07:00:25 +02:00
Florian Bruhin
f6b88770d1 doc: userscripts are not related to Greasemonkey.
See #450.
2015-04-14 06:39:06 +02:00
Bruno Oliveira
0233c96d48 Merge pull request #21 from hackebrot/command-tests
Add tests for CommandRunner/KeyConfigParser.
2015-04-13 19:45:24 -03:00
Bruno Oliveira
6ae94d6f49 Create module overflow_test_cases
As suggested by @The-Compiler
2015-04-13 18:20:40 -03:00
Florian Bruhin
e8ddd9397d Use a single QNetworkAccessManager per session. 2015-04-13 22:34:30 +02:00
Florian Bruhin
a6e3199616 Fix binding of special keys with lower-case mods. 2015-04-13 22:08:57 +02:00
Florian Bruhin
982733e1f4 Allow commands with ;; with :bind. 2015-04-13 21:21:26 +02:00
Florian Bruhin
4e18e54803 Fix handling of flags with args with maxsplit #2.
Fixes #614.

We only added the long variant of the flag, but not the short one.
2015-04-13 21:12:14 +02:00
Florian Bruhin
501138d5a0 Fix splitting of flags with args with maxsplit.
See #614.

flags_with_args was set on keyword-only arguments, but an argument (like
--force for :bind) can also be keyword-only *and* bool.
2015-04-13 21:07:58 +02:00
Florian Bruhin
b609f993c3 Add a timeout to HTTPClient.
Fixes #629.
2015-04-13 20:59:05 +02:00
Florian Bruhin
9381aac501 Merge {Pastebin,PyPIVersion}Client into HTTPClient. 2015-04-13 20:42:28 +02:00
Artur Shaik
e603d9a2d0 Slight modify of autofocus caret
Make mouseclick event point slightly down.
Add commented tries of more reliable methods of caret focusing.
2015-04-13 19:55:45 +06:00
Artur Shaik
a6443231e5 Add statusbar coloring for caret and visual modes 2015-04-13 19:50:27 +06:00
Florian Bruhin
e5d33a6706 freeze.py: Set bundle_name for OS X.
See #384.
2015-04-13 15:39:31 +02:00
Florian Bruhin
d413aacc19 Add an .icns file for OS X.
See #384.
2015-04-13 15:01:30 +02:00
Artur Shaik
941eac848e Remove "c" key from normal -> caret mode key bindings 2015-04-13 18:37:33 +06:00
Florian Bruhin
3e1d62171f Ignore -psn_0_* argument on OS X.
See #384.
2015-04-13 14:33:46 +02:00
Florian Bruhin
bd9168fdfe Add qt_menu.nib for freeze.py on OS X. 2015-04-13 14:15:40 +02:00
Florian Bruhin
4547fd2c5d Add an applications shortcut to the .dmg.
See #384.
2015-04-13 12:33:18 +02:00
Florian Bruhin
566ffdbe23 scripts: Fix exe name for non-Windows in freeze.py.
See #384.
2015-04-13 12:25:57 +02:00
Florian Bruhin
39f7850942 Fix lint. 2015-04-13 08:52:58 +02:00
Florian Bruhin
c071bcbec8 Clean up loggers. 2015-04-13 08:49:04 +02:00
Florian Bruhin
f85ba8645f Handle a missing session folder at some places. 2015-04-13 08:38:12 +02:00
Florian Bruhin
3433a1ec7a Add tests for CommandRunner/KeyConfigParser. 2015-04-13 07:54:24 +02:00
Florian Bruhin
fa2340b61e Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-04-13 07:53:59 +02:00
Florian Bruhin
d700d18780 Fix handling of no_cmd_split cmds with args.
When we have something like ":bind x foo;;bar" it wasn't recognized "bind" is a
no_cmd_split command because we tried to look up "bind x foo" in cmd_dict.

See #615.
2015-04-13 07:39:18 +02:00
Florian Bruhin
e24b06cdf9 Refactor and fix split commands in CommandRunner.
- split() now returns a ParseResult namedtuple with (cmd, args, cmdline)
  arguments instead of only returning cmdline and setting self._cmd/self._args.

- Handling of split commands (;;) is now done in a separate parse_all()
  function instead of run() to make testing easier.

See #615.
2015-04-13 07:38:25 +02:00
Florian Bruhin
6b0c16f109 Fix default 'ga' binding. 2015-04-11 13:20:56 +02:00
Bruno Oliveira
f4c46ec1c5 Improve test legibility in TestCheckOverflow
Created OverflowTestCases which is responsible to provide data for the tests
2015-04-10 18:22:02 -03:00
Florian Bruhin
31bcc70efb Treat commands using ;; in key config as valid. 2015-04-10 19:45:59 +02:00
Florian Bruhin
3bc55e0405 Merge pull request #20 from hackebrot/validate-key-config
Add a test to validate the default key config.
2015-04-10 08:44:57 +02:00
Raphael Pierzina
0b2e39e4a4 Merge remote-tracking branch 'upstream/master' 2015-04-10 08:40:17 +02:00
Florian Bruhin
f865b87a74 Show a message and update notifier on reports.
Fixes #340.
Fixes #447.
See #429.
2015-04-10 08:09:18 +02:00
Florian Bruhin
e294e325f0 Ignore invalid history entries on start. 2015-04-10 06:40:48 +02:00
Bruno Oliveira
29c51c288b Fix small typo in docstring 2015-04-09 18:47:25 -03:00
Bruno Oliveira
6f1e830aba Parametrize test_str_split_maxsplit
As suggested by @hackebrot
2015-04-09 18:44:40 -03:00
Bruno Oliveira
253f3b2cd7 Use namedtuple and parametrized fixture for TestSplit
As discussed in the PR, this greatly improves legibility
2015-04-09 18:40:56 -03:00
Bruno Oliveira
55e3645131 Add comment to test samples in test_basekeyparser 2015-04-09 18:13:13 -03:00
Florian Bruhin
91b72ef292 Add a test to validate the default key config. 2015-04-09 21:20:17 +02:00
Florian Bruhin
8d98868ccd Fix deprecated default keybindings.
Those were auto-corrected with the next run, but still are bad...
2015-04-09 20:36:11 +02:00
Florian Bruhin
83dbe48469 Refactor EventFilter. 2015-04-09 20:22:00 +02:00
Florian Bruhin
f77ba5744b Add a ui -> hide-mouse-cursor option. 2015-04-09 19:59:16 +02:00
Artur Shaik
695712e50c Basic caret and visual modes implementation
Allow user switch in caret mode for browsing with caret, and visual mode
for select and yank text with keyboard.

Default keybindings is c or v for caret mode, and again v for visual mode. All
basic movements provided by WebAction enum implemened with vim-like
bindings. Yanking with y and Y for selection and clipboard respectively.

There is bug/feature in WebKit that after caret enabled, caret doesn't
show until mouse click (or sometimes Tab helps). So I add some workaround
for that with mouse event. I think should be better aproach.

Signed-off-by: Artur Shaik <ashaihullin@gmail.com>
2015-04-09 22:55:42 +06:00
Florian Bruhin
7160a89cb9 Fix NameError in hints.py. 2015-04-09 17:47:09 +02:00
Florian Bruhin
2d8df76609 Add $QUTE_HTML and $QUTE_TEXT for userscripts. 2015-04-09 17:45:16 +02:00
Florian Bruhin
ecb0a4e2f8 manpage: Mention XDG_*_HOME in the FILES section.
Closes #619.
2015-04-09 13:24:52 +02:00
Florian Bruhin
9e0d65c219 manpage: Mention ":help".
Closes #618.
2015-04-09 13:22:16 +02:00
Florian Bruhin
9111ae7b3c tox: Update pytest-mock to 0.4.3.
Upstream changelog:

- mocker and the backward compatible mock fixture now return the same object.
2015-04-09 13:19:39 +02:00
Bruno Oliveira
96ddfd5b65 Parametrize TestSplitCount in test_basekeyparser
As pointed out by @The-Compiler
2015-04-09 07:57:32 -03:00
Florian Bruhin
e33517f592 Merge branch 'oed-downloadview-placement' 2015-04-09 12:55:06 +02:00
Florian Bruhin
2a796d9aa4 Regenerate docs. 2015-04-09 12:54:59 +02:00
Joel Torstensson
12c83b721f Fixed some style errors. 2015-04-09 12:49:32 +02:00
Joel Torstensson
cc2c7c09ea Changing position without restart now possible. 2015-04-09 11:47:35 +02:00
Joel Torstensson
2fa66ba250 Added option for downloadview placement. 2015-04-09 11:44:59 +02:00
Florian Bruhin
425cffc2f7 pylint: Ignore 'undefined-variable' for tests.
It's less than optimal, but disabling it selectively because of
https://bitbucket.org/logilab/pylint/issue/511/ is too annoying.
2015-04-09 07:43:47 +02:00
Florian Bruhin
74f4642a2c Fix lint. 2015-04-09 07:35:33 +02:00
Florian Bruhin
a2772db9da Merge pull request #18 from hackebrot/convert-test-jinja
Convert test_jinja.py to pytest
2015-04-09 06:54:21 +02:00
Florian Bruhin
44a6617184 Add docstring for patch_read_file. 2015-04-09 06:53:21 +02:00
Florian Bruhin
1770570921 Merge pull request #17 from hackebrot/gui-marker
Custom "gui" marker for GUI tests.
2015-04-09 06:45:49 +02:00
Florian Bruhin
343a091aee Small docstring cleanup. 2015-04-09 06:42:34 +02:00
Florian Bruhin
6c566198f1 Merge branch 'hackebrot-change-test-layout-and-add-gui-tests' 2015-04-09 06:35:49 +02:00
Florian Bruhin
33dbed5624 Update authors. 2015-04-09 06:35:41 +02:00
Bruno Oliveira
853280feeb Convert test_qtutils to pytest 2015-04-08 20:25:01 -03:00
Bruno Oliveira
6037fd74cd Convert test_split to pytest 2015-04-08 20:07:14 -03:00
Raphael Pierzina
b18c1254a4 Use an autofixture that monkeypatches read_file for both tests 2015-04-09 00:46:48 +02:00
Raphael Pierzina
c3e615dfa3 Remove the test class from test_jinja.py 2015-04-09 00:38:57 +02:00
Raphael Pierzina
d91400c3be Use pytest monkeypatch instead of unittest.mock.patch 2015-04-09 00:32:24 +02:00
Bruno Oliveira
d375ddebea Add new-line at the end of conftest.py 2015-04-08 19:16:45 -03:00
Bruno Oliveira
894a2a4e7b Add custom "gui" marker to tests which use qtbot fixture
Fixes #15
2015-04-08 19:14:06 -03:00
Raphael Pierzina
63ce7d6e02 Remove unittest methods in favor of pytest assert statements 2015-04-08 23:57:08 +02:00
Raphael Pierzina
ebfcc0a83c Merge remote-tracking branch 'upstream/master' 2015-04-08 23:56:17 +02:00
Bruno Oliveira
e584aa319f Using parametrization in test_textbase
Also changed the wording a bit as suggested by @The-Compiler

Conflicts:
	tests/mainwindow/statusbar/test_textbase.py
2015-04-08 14:05:52 +02:00
Florian Bruhin
76651822bd Merge pull request #16 from hackebrot/layout
Adopt test layout to pytest.
2015-04-08 14:03:02 +02:00
Florian Bruhin
7d4e6dfd67 Another workaround for a pylint bug. 2015-04-08 06:39:12 +02:00
Florian Bruhin
679ffa452a Add some more invalid testcases to TestFont. 2015-04-08 06:22:05 +02:00
Florian Bruhin
fe696aeba5 Fix string concatenation and indenting for INVALID. 2015-04-08 06:20:43 +02:00
Bruno Oliveira
fd88311d9b Use inline list comprehension for parametrize expression 2015-04-08 01:07:56 -03:00
Bruno Oliveira
6e3c3d7a70 Use single-quoted string for consistency 2015-04-08 01:07:56 -03:00
Bruno Oliveira
a29b78e8ca Use mocker fixture instead of unittest.mock
As pointed out by @hackebrot
2015-04-08 01:07:55 -03:00
Bruno Oliveira
abc2c2b087 Remove spurious dependencies from tox.ini as pointed out by @The-Compiler 2015-04-08 01:06:31 -03:00
Bruno Oliveira
26dc275db3 Compare full lists instead of looping over items as suggested by @hackebrot 2015-04-08 01:06:31 -03:00
Bruno Oliveira
8702ac8a98 Fix small docstring issues 2015-04-08 01:06:30 -03:00
Bruno Oliveira
75386e4051 Remove "object" subclassing from Test classes missed initially 2015-04-08 01:06:30 -03:00
Florian Bruhin
10619325f6 Update MANIFEST.in/.gitignore. 2015-04-07 22:46:32 +02:00
Florian Bruhin
b591dedf7f Move FAQ/INSTALL to repo root.
That's where most people probably expect them...
2015-04-07 22:38:51 +02:00
Florian Bruhin
1f39c7782a Add a changelog for released versions.
See #608.
2015-04-07 22:35:35 +02:00
Florian Bruhin
1345a13a71 Add startpage/default-page to tabs -> last-close. 2015-04-07 17:36:19 +02:00
Florian Bruhin
4e2ef45cd8 Ignore Qt warning about WOFF/zlib. 2015-04-07 13:00:26 +02:00
Florian Bruhin
a480b297ca src2asciidoc: Split _get_command_doc. 2015-04-06 19:48:36 +02:00
Florian Bruhin
2d258ec53f Add note about maxsplit/no_split_cmd args to docs. 2015-04-06 17:32:33 +02:00
Florian Bruhin
b1c475c61d Use **kwargs to simplify cmdutils.register. 2015-04-06 17:25:42 +02:00
Florian Bruhin
20f0ef7ccc Ignore ;; for splitting with some commands.
Fixes #405.
2015-04-06 17:00:52 +02:00
Florian Bruhin
46d1760798 Keep progress bar height fix on multiline text.
See #364 and #63.
2015-04-06 14:27:41 +02:00
Florian Bruhin
2876ba5cfa Pass a parent to global QObjects. 2015-04-06 00:10:37 +02:00
Florian Bruhin
d83da987ae Move SessionManager init to sessions.py. 2015-04-05 23:44:25 +02:00
Florian Bruhin
067ac13018 Regenerate docs. 2015-04-05 23:35:00 +02:00
Florian Bruhin
9b8f5e3ff0 Make the default session name configurable.
See #523.
2015-04-05 23:30:43 +02:00
Florian Bruhin
2c99520f79 Update PyQt version in README 2015-04-05 21:49:02 +02:00
Florian Bruhin
1eb2a9a725 Regenerate docs. 2015-04-05 21:39:24 +02:00
Florian Bruhin
476ccd8fe1 Add tab-bg{,-silent} to new-instance-open-target. 2015-04-05 21:34:41 +02:00
Florian Bruhin
9d44f777c0 Fix lint. 2015-04-05 20:30:31 +02:00
Florian Bruhin
143228d593 misc_checks.py: Fix exit status for spellcheck. 2015-04-05 19:37:56 +02:00
Florian Bruhin
d3a92d505c Make lints run with adjusted test folder location.
For pylint we need a custom script; see
https://bitbucket.org/logilab/pylint/issue/512/
2015-04-05 18:45:48 +02:00
Florian Bruhin
eb76cd71de Fix testfile path in MANIFEST.in. 2015-04-05 18:45:48 +02:00
Bruno Oliveira
967c706bf0 Removed xfail from test since issue has been fixed on master 2015-04-05 12:29:18 -03:00
Bruno Oliveira
3864eff0be Merge remote-tracking branch 'origin/master' into layout 2015-04-05 12:27:55 -03:00
Bruno Oliveira
bfc99f09f9 Renamed test to tests as suggested by @The-Compiler 2015-04-05 12:23:04 -03:00
Bruno Oliveira
5fb7ad383d Fixed assertions and other issues as reported by @The-Compiler 2015-04-05 12:22:12 -03:00
Florian Bruhin
8bbff689b4 Add a -t/--temp flag to :session-load.
See #523.
2015-04-05 16:06:41 +02:00
Florian Bruhin
17ebbc37c5 Add a -c/--current parameter to :session-save.
See #523.
2015-04-05 16:04:03 +02:00
Florian Bruhin
ce0b9eab58 tox: Make it possible to pass args to pytest. 2015-04-05 15:39:05 +02:00
Bruno Oliveira
3de584f02c Moving testfile used by test_utils into qutebrowser.utils 2015-04-04 19:15:23 -03:00
Florian Bruhin
6a03089639 Fix deleting of envvars in TestGetStandardDirLinux 2015-04-05 00:03:09 +02:00
Florian Bruhin
1e2a902a9f Fix paths for TestGetStandardDirLinux. 2015-04-05 00:03:09 +02:00
Florian Bruhin
87d5f57c08 Merge branch 'hackebrot-master'
https://github.com/The-Compiler/qutebrowser/pull/604

This PR splits test_debug.py into several modules located in a new sub
directory.

Most importantly it changes the according tests to py.test syntax and
introduces a plugin to examine log messages, namely pytest-capturelog.
2015-04-04 23:32:47 +02:00
Florian Bruhin
1e1a433dff Regenerate authors. 2015-04-04 23:32:32 +02:00
Florian Bruhin
84d2556863 Merge branch 'master' of github.com:The-Compiler/qutebrowser 2015-04-04 22:05:31 +02:00
Florian Bruhin
1fdd7051c3 config: Filter docs for change_filter. 2015-04-04 22:03:28 +02:00
Florian Bruhin
57158e7191 Don't make default config a mutable global.
Before, configdata.DATA only existed once - that means when something
manipulated it, instantiating a new ConfigManager actually gave us the
*modified* rather than the default data.

There's still a (now readonly) configdata.DATA for performance reasons -
before, the settings completion model called data() many times, which caused
initializing of it taking a few (instead of nearly 0) seconds.

See https://github.com/hackebrot/qutebrowser/pull/16#discussion-diff-27770433
2015-04-04 22:01:02 +02:00
Bruno Oliveira
4fa2294805 Merge remote-tracking branch 'origin/master' into layout
Conflicts:
	qutebrowser/test/keyinput/test_basekeyparser.py
	qutebrowser/test/utils/test_standarddir.py
	test/browser/http/test_content_disposition.py
	test/config/test_configtypes.py
	test/misc/test_editor.py
	test/utils/test_debug.py
	test/utils/test_utils.py
	tox.ini
2015-04-04 14:29:49 -03:00
Bruno Oliveira
8222097942 Removed unnecessary module 'helpers' 2015-04-04 14:12:22 -03:00
Bruno Oliveira
2666388a6d Converted test_utils to pytest 2015-04-04 14:05:05 -03:00
Bruno Oliveira
4ab03b1536 Converted test_urlutils to pytest 2015-04-04 13:49:26 -03:00
Bruno Oliveira
5cf8ff1f84 Converted test_debug to pytest 2015-04-04 13:39:04 -03:00
Bruno Oliveira
bd9c807fb1 Converted test_editor to pytest 2015-04-04 13:29:11 -03:00
Bruno Oliveira
12903a34f4 Converted test_modeparsers to pytest 2015-04-04 13:02:31 -03:00
Bruno Oliveira
065c3fcd9d Renamed singleShot to isSingleShot in QTimer stub
As in compliance to: http://doc.qt.io/qt-5/qtimer.html
2015-04-04 12:52:49 -03:00
Bruno Oliveira
6388ec4794 Converted test_readline to pytest 2015-04-04 12:49:23 -03:00
Bruno Oliveira
7e7a1b7b28 Converted test_basekeyparser to pytest 2015-04-04 12:37:14 -03:00
Bruno Oliveira
9128ac41cb Removing (object) subclassing from test classes
Old habit from python 2
2015-04-04 12:05:44 -03:00
Bruno Oliveira
8000ea33d2 Converted test_config to pytest 2015-04-04 12:02:32 -03:00
Bruno Oliveira
ea16fb3684 Converted test_tabhistory to pytest 2015-04-04 11:43:14 -03:00
Florian Bruhin
cef88d6e19 test_log_time: Prettify duration assertion.
As suggested by @nicoddemus in #13.
2015-04-04 16:33:10 +02:00
Bruno Oliveira
6429d29a23 Converted test_conftest to pytest 2015-04-04 11:24:48 -03:00
Florian Bruhin
b2df5a5b47 docs: Make it clear cache settings are global.
Closes #602.
2015-04-04 15:31:12 +02:00
Florian Bruhin
18dea8c7cb Add note about about:blank to default-page docs. 2015-04-04 15:10:22 +02:00
Florian Bruhin
217e788f4b Add 'cd' shortcut to clear downloads. 2015-04-04 15:02:08 +02:00
Florian Bruhin
f1ebbda7a0 test_signal: Add docstring for signal(). 2015-04-04 12:08:22 +02:00
Florian Bruhin
8e93747040 test_log_time: Fix/simplify duration assert.
See #13.
2015-04-04 12:07:15 +02:00
Raphael Pierzina
dd4096b5a4 Merge remote-tracking branch 'upstream/master' 2015-04-04 11:12:39 +02:00
Florian Bruhin
e23c9401f2 Merge branch 'convert-debug-signal-tests' 2015-04-04 01:18:38 +02:00
Bruno Oliveira
99abd1edeb Adding pytest-mock to tox 2015-04-03 20:17:52 -03:00
Florian Bruhin
cd7319de1e Merge branch 'convert-logtime-tests' 2015-04-04 01:12:18 +02:00
Florian Bruhin
80b0692971 Remove blank line to make pep257 happy. 2015-04-04 01:11:57 +02:00
Bruno Oliveira
22df30cdcc Converted test_http to pytest 2015-04-03 20:09:53 -03:00
Bruno Oliveira
3129def33e Converted test_content_disposition to pytest 2015-04-03 20:07:20 -03:00
Florian Bruhin
1c9f116370 Merge branch 'convert-qflags-key-tests' 2015-04-03 23:52:53 +02:00
Florian Bruhin
2ac0c7b8f0 Remove now unused import from test_debug. 2015-04-03 23:52:13 +02:00
Florian Bruhin
05087b976a test_qflags_key: Improve xfail message. 2015-04-03 23:43:06 +02:00
Bruno Oliveira
7442e30f29 Converted test_webelem to pytest 2015-04-03 18:36:35 -03:00
Bruno Oliveira
f57223f7eb Removed environ_set_temp as we will use monkeypatch 2015-04-03 18:35:40 -03:00
Florian Bruhin
70f52fa9cc Merge pull request #11 from hackebrot/convert-qenum-key-tests
Convert qenum key tests
2015-04-03 23:29:10 +02:00
Florian Bruhin
544dc650e7 Remove now unused imports from test_debug. 2015-04-03 23:23:31 +02:00
Florian Bruhin
0e76f9b1f1 Whitespace adjustments 2015-04-03 23:19:49 +02:00
Florian Bruhin
e94a8a80f1 test_qenum_key: Use hasattr() for attribute checks. 2015-04-03 23:18:56 +02:00
Bruno Oliveira
3421e5e34f Created stubs fixture and converted test_stubs to pytest 2015-04-03 18:12:49 -03:00
Florian Bruhin
fba0ae69ce Remove test_reconverted from qenum_key tests.
The test didn't really seem to test anything useful, and also uses a QFlags
instead of a QEnum.
2015-04-03 23:05:08 +02:00
Raphael Pierzina
231feda2c8 Use logger with name of local var logger_name 2015-04-03 22:45:26 +02:00
Raphael Pierzina
efbc8e0cbf Remove former unittest module test_debug.py 2015-04-03 21:55:19 +02:00
Raphael Pierzina
7540a5bbf4 Convert test_dbg_signal_newline 2015-04-03 21:54:24 +02:00
Raphael Pierzina
22522406e1 Convert test_dbg_signal_eliding 2015-04-03 21:50:32 +02:00
Bruno Oliveira
9c533e1941 Moved tests to outside of qutebrowser package 2015-04-03 16:49:01 -03:00
Raphael Pierzina
9d39fbd4e5 Convert test_dbg_signal 2015-04-03 21:45:10 +02:00
Raphael Pierzina
45e95d497d Convert test_signal_name using a signal fixture 2015-04-03 21:41:52 +02:00
Florian Bruhin
068947ba7e Fix search engine syntax documentation.
We changed the syntax in 68398035ef but didn't
adjust the documentation.

See #14.
2015-04-03 20:54:27 +02:00
Raphael Pierzina
91a8b23aeb Use actual unittest implementation of assertAlmostEqual 2015-04-03 20:40:37 +02:00
Raphael Pierzina
6fb83aacae Add a local variable for the logger name 2015-04-03 20:32:29 +02:00
Raphael Pierzina
58a8a7e992 Introduce pytest plugin capturelog and convert test_log_time 2015-04-03 20:10:41 +02:00
Florian Bruhin
25fca03dca Don't double ampersands in window title.
Fixes #599.
2015-04-03 20:04:44 +02:00
Florian Bruhin
6917c3b32d set-cmd-text: Add -s/--space argument.
We need this because quotes are ignored now, so there'd be no way to set the
text to ":open -t " for example.
2015-04-03 19:07:29 +02:00
Florian Bruhin
3b3b55234b Add a signal to KeyConfigParser to save config.
Before, we used the 'changed' signal for the SaveManager - however, that also
was emitted when only the internal structure changed. Now we add a new signal
for that.
2015-04-03 19:07:29 +02:00
Florian Bruhin
ac63fc073f save: Add possibility to mark things dirty on add.
KeyConfig needs this feature, because it can fix some deprecated commands
during __init__ and emit its dirty-signal, but that happens before the saveable
is added.
2015-04-03 19:07:29 +02:00
Florian Bruhin
630a827afc Change CHANGED_KEY_COMMANDS to be regexes.
Break after first regex
2015-04-03 19:03:30 +02:00
Florian Bruhin
a504bd1436 Don't quote completions for maxsplit-commands.
Fixes #564.
Obsoletes #313 and #453.
2015-04-03 19:03:30 +02:00
Florian Bruhin
0b26e295bc Revert fixes for quotes/spaces in maxsplit cmds.
Revert "Fix maxsplit-splitting with empty args (""/'')."
This reverts commit 46396cce1e.

Revert "Remove quotes with split=False commands."
This reverts commit 81bc5dae94.

See #564 and #453.
2015-04-03 19:00:16 +02:00
Raphael Pierzina
5b372aeee0 Remove blank lines in test_qflags_key 2015-04-03 18:55:24 +02:00
Raphael Pierzina
0b063ab4b4 Convert test_unknown 2015-04-03 18:51:40 +02:00
Raphael Pierzina
ff75d18e62 Convert test_int 2015-04-03 18:40:41 +02:00
Raphael Pierzina
086f12600c Convert test_int_noklass 2015-04-03 18:37:12 +02:00
Raphael Pierzina
75e927f79e Convert test_add_base 2015-04-03 18:33:22 +02:00
Raphael Pierzina
6482025399 Convert test_combined 2015-04-03 18:29:33 +02:00
Raphael Pierzina
f68cfc13e0 Convert test_multiple and use custom xfail marker 2015-04-03 18:25:28 +02:00
Raphael Pierzina
9a47848794 Create a new module and convert test_single 2015-04-03 18:22:13 +02:00
Raphael Pierzina
96a600e9dc Change containing directory name to debug 2015-04-03 16:37:31 +02:00
Raphael Pierzina
b938318d5f Remove former unittest class and skip test_reconverted 2015-04-03 16:34:42 +02:00
Raphael Pierzina
6b7ae70e6d Convert test_unknown 2015-04-03 16:34:42 +02:00
Raphael Pierzina
1b476d9af7 Convert test_int 2015-04-03 16:34:42 +02:00
Raphael Pierzina
9e59108788 Convert test_int_noklass 2015-04-03 16:34:42 +02:00
Raphael Pierzina
df3096fbb5 Convert test_add_base 2015-04-03 16:34:42 +02:00
Raphael Pierzina
11ded52f06 Convert test_metaobj 2015-04-03 16:34:42 +02:00
Raphael Pierzina
d4d14598dd Convert test_no_metaobj 2015-04-03 16:34:42 +02:00
Raphael Pierzina
64b1b48be6 Extract test_no_metaobj to separate module 2015-04-03 16:34:42 +02:00
Florian Bruhin
7e51addeb0 Fix :set-cmd-text with empty argument. 2015-04-03 14:42:19 +02:00
Florian Bruhin
4e0712622b Clear search when :search without args is given.
Needed for #564 because :search "" won't work anymore.
2015-04-03 14:40:26 +02:00
Florian Bruhin
1dcc5a32d6 Correct keybindings to deprecated commands.
Needed for #564 (because of :search "").
Also see #525.
2015-04-03 14:14:20 +02:00
Bruno Oliveira
298892a4a8 Converted test_standarddir to pytest
related to #10
2015-04-02 19:46:52 -03:00
Bruno Oliveira
751b62e344 Moving logging and QApplication to conftest
As discussed in #8
2015-04-02 19:09:06 -03:00
Florian Bruhin
18b5512fe9 Remove 'fooled' from state file. 2015-04-02 14:58:34 +02:00
Florian Bruhin
953119ef75 Revert "Minor QWebSettings fix."
Happy April's fools!

This reverts commit a98060e020.

Conflicts:
	qutebrowser/app.py
	qutebrowser/misc/utilcmds.py
2015-04-02 14:56:42 +02:00
Bruno Oliveira
47b9ea1f88 Fixing docstring typo in test_progress 2015-04-02 08:05:23 -03:00
Florian Bruhin
e1cdbd5f16 Merge branch 'ff2000-tabindex_in_statusbar' 2015-04-02 11:59:29 +02:00
Florian Bruhin
009e595780 Regenerate authors. 2015-04-02 11:58:09 +02:00
Florian Bruhin
84b9d34a7f Fix lint. 2015-04-02 11:57:56 +02:00
Franz Fellner
3d3324ccfa Add TabIndex label to the statusbar.
It shows the current tab index and the number of tabs of the windows it
sits in.
2015-04-02 11:55:42 +02:00
Florian Bruhin
9f9996bc66 Refuse to add empty URLs to history. 2015-04-02 09:09:17 +02:00
Florian Bruhin
214347497a Fix handling of first :completion-item-prev call.
Before, the first item was unconditionally selected when none was selected
before. With :completion-item-prev (e.g. Shift-Tab), it makes more sense to
select the *last* one.
2015-04-02 07:41:56 +02:00
Florian Bruhin
37ab5296a7 Adjust URL completion when quickmarks are changed.
Fixes #590.
2015-04-02 07:40:00 +02:00
Bruno Oliveira
79be5b0f4a Implemented test for Progress widget
Also created a conftest file with a "default_config" fixture.
2015-04-01 22:39:25 -03:00
Bruno Oliveira
1f08d8e319 Implemented test for Percentage widget 2015-04-01 21:24:25 -03:00
Bruno Oliveira
3096f3856a Implemented test for TextBase widget 2015-04-01 20:50:20 -03:00
Florian Bruhin
068e1c14b6 Don't display internal sessions in completion. 2015-04-01 22:32:41 +02:00
Florian Bruhin
1fb848249e Handle sessions starting with _ as internal.
:session-{load,save,delete} now refuses to handle sessions starting with _,
unless a new -f/--force parameter is given.
2015-04-01 22:31:19 +02:00
Florian Bruhin
840652f396 Use sessions for :restart.
This saves a lot more state compared to just passing a list of pages.
2015-04-01 22:18:28 +02:00
Florian Bruhin
2ba28a59fe Prevent session from being loaded with :restart. 2015-04-01 21:42:02 +02:00
Florian Bruhin
371ec564e1 Split restart() into :restart and _do_restart(). 2015-04-01 21:38:18 +02:00
Florian Bruhin
11bd4a13f6 Serialize arguments via json on restart.
We want to ignore some positional arguments without ignoring flags/values - and
since there's no easy way to "unparse" an argparse namespace, we instead pass
it as json.

Also note we can't pass it as a file easily, as args have to be available very
early. Passing it as an argument shouldn't be an issue though.
2015-04-01 21:37:06 +02:00
Florian Bruhin
8748420b1b src2asciidoc: Skip suppressed arguments. 2015-04-01 21:37:06 +02:00
Florian Bruhin
6e435ad215 Add state config sections when initializing. 2015-04-01 18:26:45 +02:00
Florian Bruhin
a98060e020 Minor QWebSettings fix.
If you're reading the diff, congrats. Please be quiet and don't spoil the fun
for others though! :)
2015-04-01 00:06:34 +02:00
Florian Bruhin
eeb875d098 Handle unavailable registry in on_focus_changed. 2015-04-01 00:02:29 +02:00
Florian Bruhin
431257d380 Fix handling of key release events.
Fixes #593.

It seems Qt "re-uses" existing keyevents, so we have to save and compare the
data instead.
2015-03-31 23:09:40 +02:00
Florian Bruhin
16ffafb769 Regenerate docs. 2015-03-31 22:14:35 +02:00
Florian Bruhin
38c63ca2ea Add a checker for words which I often misspell. 2015-03-31 22:12:38 +02:00
Florian Bruhin
8ebac8d38c Various spelling fixes. 2015-03-31 21:11:47 +02:00
Florian Bruhin
eb3b0b960f Use hunter for line tracing. 2015-03-31 20:38:46 +02:00
Florian Bruhin
96090b86fd tox: Use pytest-cov for coverage. 2015-03-30 23:36:27 +02:00
Florian Bruhin
36421934f9 tox: Use pytest instead of unittest. 2015-03-30 23:09:35 +02:00
Florian Bruhin
2f629befc3 Add an option to disable host blocking. 2015-03-30 18:33:10 +02:00
Florian Bruhin
70ccdd86b2 Add zooming with Ctrl-mousewheel.
Closes #51.
2015-03-30 15:26:07 +02:00
Florian Bruhin
fab6bc285c Add readlink in stacktrace.asciidoc. 2015-03-30 15:06:38 +02:00
Florian Bruhin
a38c3ae1e1 Add a :tab-detach command.
See #62.
2015-03-30 12:14:15 +02:00
Florian Bruhin
33dff70357 :session-load: Add a -c/--clear argument.
See #523.
2015-03-30 07:13:13 +02:00
Florian Bruhin
dff8f73a11 :session-save: Show which session was saved.
This also adds a -q/--quiet argument to not print this.

See #523.
2015-03-30 07:13:13 +02:00
Florian Bruhin
5233e7fac8 Fix UnboundLocalError on invalid quickmarks.
This also improves FuzzyUrlError messages.
2015-03-29 21:20:42 +02:00
Florian Bruhin
b2427701fa Handle element webFrame being None when hinting. 2015-03-29 19:52:30 +02:00
Florian Bruhin
8af2e712ae Add a --pdb-postmortem argument. 2015-03-29 19:45:00 +02:00
Florian Bruhin
34a0976a6f Fix requirements.io filter. 2015-03-27 19:27:57 +01:00
Florian Bruhin
d062ff5138 Fix starting with -c '' again.
Before c5a2039da4 (standarddir refactoring), we
only checked the commandline arguments for the config file, but not when
getting the quickmarks location (as the 'args' argument was None). This means
quickmarks were saved to the default config dir even with -c ''.

With that commit, this was "fixed" accidentally, but quickmarks couldn't handle
the filename being None.
2015-03-27 12:29:12 +01:00
Florian Bruhin
1e18ce94cf doc: Add requirements.io badge to README. 2015-03-26 21:36:54 +01:00
Florian Bruhin
51141adb24 tox: Better requirements.io filter for pep8. 2015-03-26 21:34:13 +01:00
Florian Bruhin
c562fac9cb tox: Use git directly to check for changes (docs).
We also remove checking for uncommited changes in misc_checks.py, as this
usually just is noise.
2015-03-26 20:37:53 +01:00
Florian Bruhin
16ab2ad167 tox: Add skip_install to more environments. 2015-03-26 20:19:55 +01:00
Florian Bruhin
acb13bb61e tox: Update check-manifest to 0.24.
Changelog:

* Make sure setup.py not being added to the VCS doesn't cause
  hard-to-understand errors (issue #46).
2015-03-26 19:56:29 +01:00
Florian Bruhin
b6dc43396b Update CONTRIBUTING. 2015-03-26 19:47:34 +01:00
Florian Bruhin
6a02ee1cbb Update INSTALL. 2015-03-26 19:12:52 +01:00
Florian Bruhin
67b9036574 tox: tox.ini fixes for Ubuntu Trusty/tox 1.6
- Don't use old PYTHONPATH in tox.ini.
  PYTHONPATH should be unset anyways, so it's okay to ignore the old value.

- Don't use config interpolation for unittests.
  This shows a "command not found" error for some reason.
2015-03-26 13:23:07 +01:00
Florian Bruhin
cb3fcd3d8a Don't use {envsitepackagesdir} for link_pyqt.py.
It seems this is broken (and passes the global path) on Ubuntu Trusty.
2015-03-26 13:23:07 +01:00
Florian Bruhin
738f6a4510 tox: Set QT_QPA_PLATFORM_PLUGIN_PATH for tests.
This is needed on Windows so the qwindows plugin is found.
It makes more sense to set this here instead of setting it in the buildbot
settings.
2015-03-26 13:23:07 +01:00
Florian Bruhin
b409517777 tox: Use python -m pep257 for pep257. 2015-03-26 13:23:03 +01:00
Florian Bruhin
a1df3194ff tox: Add coverage/docs environments. 2015-03-26 13:21:10 +01:00
Florian Bruhin
fc14b5b6b2 Fix link_pyqt.py on Debian/Windows. 2015-03-26 13:16:48 +01:00
Florian Bruhin
8285245641 Update INSTALL.asciidoc for tox. 2015-03-26 08:17:06 +01:00
Florian Bruhin
320fd87cbc Update MANIFEST.in.
This makes check-manifest run clean again.
2015-03-26 08:17:06 +01:00
Florian Bruhin
9099d8c466 Update .gitignore. 2015-03-26 08:17:06 +01:00
Florian Bruhin
1d29e3462f Use tox to manage virtualenvs.
Obsoletes #463.
Fixes #558.
Fixes part of #474.
Closes #479.
Closes #452.
2015-03-26 08:15:38 +01:00
Florian Bruhin
07da31e2a0 Remove run_checks/init_venv.
This will be replaced by tox.
2015-03-26 07:59:01 +01:00
Florian Bruhin
513fbb1539 Make setup.py work with python2.
This is needed for distributions (Debian/Ubuntu) which only have a python2 tox.
Tests will still be run with python3, but the setup will be called with
python2.
2015-03-26 07:57:39 +01:00
Florian Bruhin
f518b5b7f2 lint: Move options from parameters to config files.
This is needed for tox support as we get rid of run_checks.py.
2015-03-26 07:56:55 +01:00
Florian Bruhin
82322beb03 Fix pep257 issues. 2015-03-26 07:08:54 +01:00
Florian Bruhin
5f454f3440 Add a build_release.py script. 2015-03-24 23:14:09 +01:00
Florian Bruhin
09526ad715 asciidoc2html: Try to find asciidoc on the system.
Fixes #106.
2015-03-24 23:13:51 +01:00
Florian Bruhin
ec487dd6b1 Add some logging for javascript messages. 2015-03-24 11:35:58 +01:00
Florian Bruhin
5043f58f3c Add an --override-restore argument.
See #523.
2015-03-24 07:50:23 +01:00
Florian Bruhin
db98b03f34 Never open URL arguments in background. 2015-03-24 07:44:49 +01:00
Florian Bruhin
1d1ac1ef6f Save session to load in state file.
Before, we always loaded the default session (if it existed) and then deleted
it. This was surprising as the default session was deleted even when another
session was loaded.

Now we don't delete it at all, and save the session to load in the state file.

See #523.
2015-03-24 07:12:35 +01:00
Florian Bruhin
1425d306bc Fix lint. 2015-03-23 08:19:31 +01:00
Florian Bruhin
0e8b42a9d8 Add a --relaxed-config options. 2015-03-23 07:58:28 +01:00
Florian Bruhin
e7f5433da3 Enable python warnings earlier and unconditionally. 2015-03-23 07:40:22 +01:00
Florian Bruhin
21d2bb2291 Simplify some unneeded lambdas. 2015-03-23 07:04:50 +01:00
Florian Bruhin
8811947f50 Re-enable some pylint checks. 2015-03-23 07:04:41 +01:00
Florian Bruhin
d2f829ebd3 Regenerate docs 2015-03-22 23:52:08 +01:00
Florian Bruhin
97b678d8c7 Don't display time deltas < 1s in messages. 2015-03-22 23:50:12 +01:00
Florian Bruhin
c13e09b706 Add option to queue all msgs in unfocused windows. 2015-03-22 23:47:16 +01:00
Florian Bruhin
157c25bb13 Queue messages for 'current' window if unfocused.
Fixes #512.
2015-03-22 22:39:56 +01:00
Florian Bruhin
19d369377e Update references to HACKING/CONTRIBUTING. 2015-03-20 12:18:15 +01:00
Florian Bruhin
8c6ad697ce Update CONTRIBUTING. 2015-03-20 12:16:00 +01:00
Florian Bruhin
c67fcc4fd6 Rename/move doc/HACKING.asciidoc to CONTRIBUTING.
This is so GitHub picks it up and shows a banner:
https://github.com/blog/1184-contributing-guidelines
2015-03-20 11:55:25 +01:00
Florian Bruhin
565303ebcd Fix hinting when page has no URL set yet. 2015-03-20 08:35:33 +01:00
Florian Bruhin
858c38964b Release v0.1.4 2015-03-20 08:30:02 +01:00
Florian Bruhin
f77c0f9afa Simplify package output in earlyinit.py.
It doesn't really help much to have package names for distributions in there,
and it's way too much effort. Closes #475.
2015-03-20 07:16:47 +01:00
Florian Bruhin
adb11360db Disallow tab=None with objreg.get(scope='tab').
This would've made it a lot easier to detect #563.
2015-03-19 22:28:24 +01:00
Florian Bruhin
7a4a4a4a4e Pass tab_id correctly with scope='tab' commands.
Fixes #563.
2015-03-19 22:19:25 +01:00
Florian Bruhin
218822d6e8 Clear rejected SSL questions when reloading page.
Fixes #565.
2015-03-19 21:21:40 +01:00
Florian Bruhin
d6732c64a3 Revert "Handle NavigationTypeOther acceptNavigationRequest"
This reverts commit 4b4bb3af88.

Fixes #567. #488 is still okay because of
f3b55d68db.
2015-03-19 19:06:58 +01:00
Florian Bruhin
fb5fbd09da Handle unencodable file paths in config types.
If an user e.g. has a download-directory of ~/föö, but has LC_ALL=C set, we'll
get an UnicodeEncodeError when trying to validate it. This is now handled
properly by raising a ValidationError.

Fixes #562.
2015-03-19 12:42:35 +01:00
Florian Bruhin
330e03d382 Merge branch 'sbinix-master' 2015-03-18 20:44:21 +01:00
Florian Bruhin
e3f9a08611 Regenerate docs 2015-03-18 20:24:54 +01:00
Florian Bruhin
1a534454e2 Make it possible to correct author names in src2asciidoc. 2015-03-18 20:24:47 +01:00
Florian Bruhin
c83775cf29 Update icon db path when private-browsing changed. 2015-03-18 20:20:04 +01:00
Florian Bruhin
706cc1a87f Merge branch 'master' of https://github.com/sbinix/qutebrowser into sbinix-master
Conflicts:
	qutebrowser/config/websettings.py
2015-03-18 20:16:36 +01:00
Florian Bruhin
ca22ed02e6 Fix via_ipc typo. 2015-03-17 19:13:44 +01:00
Florian Bruhin
2b10adfad7 Remove colors -> completion.item.bg config option.
This wasn't used anywhere...
2015-03-17 07:45:11 +01:00
Florian Bruhin
2dcf323077 Add missing docstrings. 2015-03-17 06:39:02 +01:00
Florian Bruhin
94bc10405a Merge branch 'histcomplete' 2015-03-17 06:16:26 +01:00
Florian Bruhin
9a405df560 Whoops. 2015-03-16 23:32:49 +01:00
Florian Bruhin
210ce8ca7c Don't poll for signals on Unix.
A better solution is to use QSocketNotifier and os.wakeup_fd to get notified
about new signals.

Thanks to Yuya Nishihara / TortoiseHG for the hint!

Fixes #555.
2015-03-16 23:25:36 +01:00
Florian Bruhin
6dc65287a9 Discard uninteresting events early in eventFilter.
Before, we ran quite a lot of code (e.g. objreg) on every event, even if it
turns out to not be a keypress/release event at all.
2015-03-16 23:25:29 +01:00
Florian Bruhin
f1b9a3c8b5 Ensure there's no size for font-family settings.
See #549.
2015-03-16 18:32:17 +01:00
Florian Bruhin
4157cfe86f Merge branch 'issue549-fix' 2015-03-16 18:03:00 +01:00
Florian Bruhin
b1f99392e8 Add some more logging for #549. 2015-03-16 18:01:02 +01:00
Florian Bruhin
b226426f15 Adjust package names in stacktrace.asciidoc. 2015-03-16 17:45:53 +01:00
Florian Bruhin
1aaa538b45 Ignore empty lines in history. 2015-03-16 10:45:50 +01:00
Florian Bruhin
59bbca9b40 Fix updating of existing items in hist-completion.
Before we limited the history items we could simply call WebHistory's
historyContains before iterating through all items in the history completion.

Now however it's possible an item is in the real WebHistory, but not actually
in the completion - so we always have to check the whole completion.
2015-03-16 09:20:09 +01:00
Florian Bruhin
777e3f58e1 Make network inaccessible in test QWebPages.
Maybe fixes #553.
2015-03-16 08:32:17 +01:00
Florian Bruhin
806742abd3 Move new entries to the end when loading history.
Before, if an URL was present early in the history and then again later, we
didn't move it to the end of the OrderedDict. This means it won't be loaded in
the completion.
2015-03-16 07:54:39 +01:00
Florian Bruhin
3df5e13c65 Fix exception in filtermodel if model has no items. 2015-03-16 07:42:21 +01:00
Florian Bruhin
503060881a Compare history items based on QUrl. 2015-03-16 07:42:10 +01:00
Florian Bruhin
693ea0c312 Cleanup 2015-03-16 07:10:06 +01:00
Florian Bruhin
553d8cf986 Also save the QUrl in a HistoryEntry.
We also use QUrl::toDisplayString for the completion so things like spaces or
umlauts are decoded properly.
2015-03-16 07:03:30 +01:00
Florian Bruhin
46c31911a6 Add a test for utils.debug.log_time. 2015-03-16 06:43:56 +01:00
Florian Bruhin
57b7b43802 Regenerate docs. 2015-03-15 23:25:57 +01:00
Florian Bruhin
001bf982e5 Alternate row colors in completion. 2015-03-15 23:16:57 +01:00
Florian Bruhin
d266665955 Fix adding of URLs to history completion.
Before, the item_added signal was emitted *after* an item was added, which
means the on_history_item_added slot always assumed the item already is in the
history.
2015-03-15 21:16:45 +01:00
Florian Bruhin
9512a52d21 completion: Don't unnecessarily expand all items.
Instead of calling expandAll() and iterating through all items, we can just
force the top-level items to be expanded.
2015-03-14 22:51:53 +01:00
Florian Bruhin
cdbb118238 Also measure the time it takes to set the pattern. 2015-03-14 13:35:32 +01:00
Florian Bruhin
70cd8e74eb Measure time it takes to init URL completion. 2015-03-14 13:32:47 +01:00
Florian Bruhin
a857b9a638 Regenerate docs. 2015-03-13 19:50:20 +01:00
Florian Bruhin
833830d5e9 Limit the count of history items in the completion. 2015-03-13 19:50:08 +01:00
Florian Bruhin
55eabafc0d Rename completion -> history-length.
This is now renamed to cmd-history-max-items to avoid confusion with the web
history.
2015-03-13 19:46:21 +01:00
Florian Bruhin
901db0911e Add __len__ to WebHistory. 2015-03-13 19:45:43 +01:00
Florian Bruhin
994546f04d Use an OrderedDict for WebHistory.
We need the URLs in the correct order to get the newest items anyways.
2015-03-13 19:44:15 +01:00
Florian Bruhin
94f694bd77 Add an utils.newest_slice.
This takes an iterable and uses itertools.islice to get the n newest elements
from it.
2015-03-13 19:25:48 +01:00
Florian Bruhin
96da7d9fe6 Fix lint. 2015-03-13 16:26:27 +01:00
Florian Bruhin
74892ac8e4 Initialize completions lazily and only once.
Before, we initialized the completions once for every window spawned, which was
a waste of CPU-time and RAM.

Now we only initialize them once, when the user uses the completion for the
first time.
2015-03-13 16:25:13 +01:00
Florian Bruhin
cef49864d9 Refactor websettings and save/restore defaults.
This makes qutebrowser.config.websettings much easier to understand, and saves
all defaults so it can restore them properly when a setting is set to an empty
string.

Before, when we set the fonts to empty strings instead of the true default, in
some cases anti-aliasing was broken.

Fixes #549.
2015-03-13 10:03:17 +01:00
Florian Bruhin
ccce2eddad Add logging for websettings init.
See #549.
2015-03-12 22:41:12 +01:00
Florian Bruhin
389feab1df Make sure args are not int in new_item().
Otherwise we would construct a QStandardItem with the
QStandardItem(int rows, int columns = 1) constructor, which will most likely
not do what we want.
2015-03-12 15:35:53 +01:00
Florian Bruhin
dbd121a079 Set data of existing item. 2015-03-12 15:34:32 +01:00
Florian Bruhin
97dd86735a Improve types of history model values.
- HistoryItem.atime now always should be an int/float.
- The data for the sort role should also be an int, not a string.
  A float would also work, but maybe be slower for no real benefit.
2015-03-12 15:34:32 +01:00
Florian Bruhin
8023b1456d Make it possible to configure the timestamp format. 2015-03-12 15:25:39 +01:00
Florian Bruhin
7a28b6c821 Fix lint. 2015-03-12 14:55:54 +01:00
Florian Bruhin
299dbfa56a history: Remove unneeded _old_hit and _old_miss.
The need for those were removed in #548.
2015-03-12 14:46:49 +01:00
Jimmy
61e732f217 history: simplify 2015-03-12 21:35:56 +13:00
Jimmy
1efe18ecc6 Prevent duplicates in history completion.
Two things here. One is to use `WebHistory._new_history` only as a to-save
queue, so we now add entries to `_old_urls` when they are first created and
can now no longer iterate of `_new_history` in `__iter__()`.

Second is to stop blindly tacking new history entries on the end of the
history completion model. It does involve iterating over the model to find the
existing entry but we only do that if we know the duplicate is there, which is
fast to check.

This also ads another point of mutation to the history completion model which
may prove problematic if it leads to more segfaults.
2015-03-12 20:15:03 +13:00
Jimmy
734268187c Clean up incomprehensible comment. 2015-03-12 20:12:59 +13:00
Florian Bruhin
9ee19be70d Convert the atime to float in HistoryEntry. 2015-03-12 08:07:40 +01:00
Florian Bruhin
0b975db4dd Refactor how completions are organized. 2015-03-11 23:07:58 +01:00
Florian Bruhin
34b24aafa8 Fix lint 2015-03-11 22:22:49 +01:00
Florian Bruhin
fe4f32606d Use CompletionFilterModel's sort implementation. 2015-03-11 21:50:16 +01:00
Jimmy
834832e3ba Web history changed signal now emits the new entry.
Each new HistoryEntry is emitted after being added to the global history
store. Current members of the HistoryEntry are `url` and `atime`. `title`
should be coming soon.
2015-03-11 21:50:16 +01:00
Jimmy
59948a038c Add new UrlCompletion model which includes web history and quickmarks.
I went to some effort to avoid duplipcating code which which leads to some
arguably ugly class method calling.
2015-03-11 21:50:16 +01:00
Jimmy
f6a7ef3985 Add url history completion for open.
Adds a basic completion model implementation around the global browser
history and registers that for the open command.

Modifies WebHistory to add an __iter__ method and to use a dict instead of a
set to store an entire HistoryEntry for each archived item instead of just the
URL. Brief tests showed that the lookup time for set and dict are very
similar. They are at least on the same order of magnitude. Testing membership
of a list on the other hand, as was the case before a set was used, was four
orders of magnitude slower on my machine.
2015-03-11 21:50:16 +01:00
Florian Bruhin
0778e33142 Merge branch 'error800-master' 2015-03-11 21:49:49 +01:00
Florian Bruhin
4439ef8ddd Regenerate docs 2015-03-11 21:49:41 +01:00
Florian Bruhin
fb85a279f4 Merge branch 'master' of https://github.com/error800/qutebrowser into error800-master 2015-03-11 21:49:27 +01:00
Florian Bruhin
e8e6d8409b Adjust pylint exceptions. 2015-03-11 20:14:39 +01:00
Florian Bruhin
cd14ae2f1f Merge branch 'flvi0-master' 2015-03-11 17:49:14 +01:00
Florian Bruhin
1a4f7170a5 Regenerate docs 2015-03-11 17:49:07 +01:00
Florian Bruhin
12a82eb371 Add an unittest for foo::bar URLs.
See #544, #546.
2015-03-11 17:48:24 +01:00
Patric Schmitz
4fa64350ca Handle URLs with double-colon at the beginning as search strings
Closes #544. We might also merge #542 now.
2015-03-11 17:35:32 +01:00
error800
94666fe979 Removed default search engines. Closes #533. 2015-03-11 11:25:50 +01:00
Florian Bruhin
0f5391c4fa Scroll completion to top when showing it.
See #531.
2015-03-11 07:50:51 +01:00
Florian Bruhin
1b879faf84 completion: Highlight text case-insensitively.
See #531.
2015-03-11 07:50:45 +01:00
Florian Bruhin
5b4f6d39c2 Add a dumb sorting option to CompletionFilterModel.
This makes it possible to use Qt's QSortFilterProxyModel::lessThan option for
completions where it doesn't make sense to priorize matches starting with the
entered string, e.g. for URLs. In return, we get a *much* better performance
(several seconds when opening the completion).

See #531.
2015-03-11 07:50:33 +01:00
Florian Bruhin
5ab052c40f Regenerate docs. 2015-03-10 23:25:37 +01:00
Florian Bruhin
1ead66a4d5 quickmarks: Ignore empty and whitespace-only lines. 2015-03-10 23:25:02 +01:00
Florian Bruhin
2b06d4e684 Add documentation about how to write userscripts.
Closes #450.
2015-03-10 23:22:53 +01:00
Florian Bruhin
acc33b4f91 Add a -f/--force argument to :bind. 2015-03-10 22:32:11 +01:00
Florian Bruhin
aca44da26e Force saving with :save even w/o auto-save-config. 2015-03-10 22:24:34 +01:00
Florian Bruhin
596bff0772 Don't try to add tab repr in TabDeletedError.
This will always fail with another RuntimeError...
2015-03-10 22:16:41 +01:00
Florian Bruhin
d87a1bb2b4 Add a hints -> scatter option.
This is enabled by default to keep the same default behaviour which is like
Vimium - mixing e.g. single-char letters and double-char letters, and
scattering/shuffling the labels to have an uniform hint key distribution.

If disabled, the behaviour is more similiar to dwb, which has a fixed hint
string length and simply fills the string starting with the first possible hint
char.
2015-03-10 21:19:47 +01:00
Florian Bruhin
2f0522ebb0 Add a --rapid option to :hint, remove rapid target
This makes it possible to use rapid hinting for the run/hover/userscript/spawn
targets as well, and makes more sense anyways.
2015-03-10 19:40:30 +01:00
Florian Bruhin
4b6d49e926 Make QSslError hashable for Qt <= 5.4.
PyQt uses qHash() for __hash__, and qHash for QSslError was added with Qt 5.4.

This means 2da45e98ca raised TypeError there as
QSslError is unhashable.

For those older Qt versions, we implement __hash__ ourselves which does about
the same thing as Qt does, combining the DER (binary) representation of the
certificate and the error() (which is just a QEnum, hashable as int).
2015-03-10 08:29:56 +01:00
Florian Bruhin
2da45e98ca Auto-handle equal SSL errors for the same host.
For every (scheme, host, port) tuple, we save all SSL errors we asked the user
about, and if everything matches (scheme, host, port, error, certificate), we
don't ask the user again.

Fixes #422.
2015-03-10 07:58:40 +01:00
Florian Bruhin
8307b546b7 Adjust prompt size hint based on content.
See #26.
Fixes #506.

Related to 06cc982ab5.
2015-03-09 19:35:50 +01:00
Florian Bruhin
9ffb30a16f Ignore RuntimeError in mouserelease_insertmode.
It seems when clicking certain elements, the webview can get deleted before the
singleShot QTimer will activate.
2015-03-09 11:49:01 +01:00
Florian Bruhin
e78fa431c5 Hide Qt warning when aborting download reply. 2015-03-09 07:49:02 +01:00
Florian Bruhin
6a16875f50 Fix retrying of downloads from closed tabs.
Fixes #502.
2015-03-09 07:39:40 +01:00
Florian Bruhin
bfc114ae35 Fix lint 2015-03-08 23:15:35 +01:00
Florian Bruhin
bd3d091318 pylint/openencoding: Fix checking of nonconst mode 2015-03-08 22:31:29 +01:00
Florian Bruhin
181bcc4f8d Add tests for lineparser. 2015-03-08 22:13:29 +01:00
Florian Bruhin
60b6519b04 lineparser: Call _prepare_save() properly. 2015-03-08 21:53:42 +01:00
Florian Bruhin
181426b50a AppendLineParser: Strip newlines in __iter__. 2015-03-08 21:53:42 +01:00
Florian Bruhin
2010e8115b lineparser: Rename _open_for_reading to _open. 2015-03-08 21:53:42 +01:00
Florian Bruhin
27f4ada799 Add AppendLineParser and use it in WebHistory.
The former approach (always reading the whole history from disk) was rather
inefficient, and we had performance problems e.g. when marking text in Qt
documentation.
2015-03-08 21:53:42 +01:00
Florian Bruhin
5b4b793538 Split LineParser into multiple classes.
There is now:
    - BaseLineParser
    - LineParser
    - LimitLineParser
2015-03-08 21:53:42 +01:00
Florian Bruhin
99de995813 Rename/move config.parsers.line.LineConfigParser.
It's now misc.lineparser.LineParser since it handles other stuff than just
config.
2015-03-08 21:53:42 +01:00
Florian Bruhin
a3a2c15114 Update MANIFEST.in 2015-03-08 15:31:45 +01:00
Florian Bruhin
86e77e19b6 run_checks: Add --verbose 2015-03-08 15:26:49 +01:00
Florian Bruhin
ee8beb174d Fix :tab-clone -w (i.e. back -w). Fixes #536. 2015-03-08 15:02:18 +01:00
Florian Bruhin
cd34562d34 Fix :tab-clone with tabs -> tabs-are-windows=true.
See #536.
2015-03-08 14:54:42 +01:00
Florian Bruhin
1d9738c1ab run_checks: Fix running of pyroma/check-manifest. 2015-03-06 17:51:21 +01:00
Florian Bruhin
e48f419f78 run_checks: Stop messing with logging config.
It seems pyroma got less insane, and this breaks logging in the unittests.
2015-03-06 17:50:49 +01:00
Florian Bruhin
a919ce2ffe init_venv: Also install pyroma/check-manifest. 2015-03-06 17:50:15 +01:00
Florian Bruhin
f7b036cf15 Merge branch 'oed-download_filename_handling' 2015-03-06 17:06:01 +01:00
Florian Bruhin
edf762e210 Avoid pylint duplicate-code warning. 2015-03-06 17:04:56 +01:00
Florian Bruhin
8ee8d28f03 Regenerate docs. 2015-03-06 17:01:59 +01:00
Florian Bruhin
858131c9bc Merge branch 'download_filename_handling' of https://github.com/oed/qutebrowser into oed-download_filename_handling 2015-03-06 16:39:44 +01:00
Florian Bruhin
0827ddec86 utils.log: Simplify stack printing on Qt warnings. 2015-03-06 16:33:22 +01:00
Florian Bruhin
3e5b9a4a4a log.utils: Add Qt warning filter context manager. 2015-03-06 16:32:26 +01:00
Florian Bruhin
2c9b5f24fc Use _shutting_down instead of disconnecting signal
This will most likely cause less pain than disconnecting the signal, which
seems to be broken on OS X.
2015-03-06 16:29:04 +01:00
Florian Bruhin
034f1136d3 Add missing qutebrowser.test.log module. 2015-03-06 16:28:30 +01:00
Joel Torstensson
0fb74da4ff Can now handle relative paths. 2015-03-05 23:44:12 +01:00
Florian Bruhin
7ed8f3d4ac unittests: Set up logging properly. 2015-03-05 23:41:46 +01:00
Florian Bruhin
1c48440797 Merge branch 'master' of ssh://tonks/qutebrowser 2015-03-05 21:49:41 +01:00
Florian Bruhin
7c125642b9 Hide "Error while shutting down tabs" message.
This makes no sense at all, yet seems to happen when closing qutebrowser on OS
X via Cmd+Q.
2015-03-05 21:46:56 +01:00
Joel Torstensson
d449a60078 Fixed bug using download dirs with trailing slash. 2015-03-05 20:33:02 +01:00
Joel Torstensson
9cfa34c009 Readded suggested_filename fallback. 2015-03-05 20:17:48 +01:00
Florian Bruhin
73de32d62d Add a section about valgrind/QtWebKit to HACKING. 2015-03-05 10:47:36 +01:00
Florian Bruhin
a46d36b3b0 Add some files to omit in .coveragerc. 2015-03-05 07:40:59 +01:00
Florian Bruhin
61a52f3b91 command: Check the completion/argument counts.
See #531.
2015-03-05 06:20:55 +01:00
Florian Bruhin
048823650c Add .coveragerc. 2015-03-04 23:35:59 +01:00
Florian Bruhin
b61f8941de run_checks: Show coverage percentage. 2015-03-04 23:16:24 +01:00
Joel Torstensson
7d48845afa Made static functions private. 2015-03-04 23:05:23 +01:00
Florian Bruhin
4fa5872733 Add coverage.py support to run_checks.py 2015-03-04 21:39:14 +01:00
Florian Bruhin
ebae77e8c5 Fix lint. 2015-03-04 20:52:33 +01:00
Florian Bruhin
e5ebea80b3 Add qApp.quit atexit handler in tests.
This FINALLY fixes the test segfaults on Windows 8.
2015-03-04 20:48:58 +01:00
Florian Bruhin
be6ea2f0e8 Call QApplication.processEvents on exit. 2015-03-04 20:26:19 +01:00
Florian Bruhin
e431f09fab test_editor: Fix handling of statusbar messages. 2015-03-04 20:25:57 +01:00
Joel Torstensson
4e7e97232e Downloads using get_request specifying only path now works. 2015-03-04 12:24:26 +01:00
Joel Torstensson
49c666a4a8 get_request is now correct. 2015-03-04 12:06:08 +01:00
Joel Torstensson
68774a2c75 Style fixes. 2015-03-04 11:31:54 +01:00
Joel Torstensson
8e0c1cff7b Made download suggestion configurable.
Fix #505
2015-03-04 11:31:54 +01:00
Joel Torstensson
82deaeed2e Downloads now shows path in question. 2015-03-04 11:29:36 +01:00
Florian Bruhin
40af99bacc Clear open target in acceptNavigationRequest.
This is a regression introduced in a76868c0f4.
Fixes #530.
2015-03-03 23:28:45 +01:00
Florian Bruhin
801f6b2667 Fix handling of signals with deleted tabs. 2015-03-03 23:22:29 +01:00
Gregor Pohl
c8c095d499 Add Opera-like mouse rocker gestures. 2015-03-03 12:58:32 +01:00
Florian Bruhin
f19eba3b40 Don't log cur_link_hovered signals. 2015-03-03 09:07:30 +01:00
Florian Bruhin
21ab5f8685 Add logging for acceptNavigationRequest. 2015-03-03 09:06:43 +01:00
Florian Bruhin
caf0c76a4e Update docs. 2015-03-02 14:01:36 +01:00
Florian Bruhin
65f21fc8ee Use unittest.mock.patch for tests. Closes #76. 2015-03-01 22:10:16 +01:00
Florian Bruhin
c0eb8daff7 Add tests for keyinput->partial-timeout. 2015-03-01 21:35:14 +01:00
Florian Bruhin
003f7fd957 tests: Improve FakeTimer. 2015-03-01 21:30:22 +01:00
Florian Bruhin
bb2caaa11d Suppress Qt warning in unittests. 2015-03-01 21:30:22 +01:00
Florian Bruhin
cdc298fbc5 Regenerate docs 2015-02-27 12:46:13 +01:00
Florian Bruhin
84643b4a39 Various unittest fixes and improvements. 2015-02-27 12:43:54 +01:00
Florian Bruhin
072210c47b Log rfc6266 UnicodeDecodeError to correct logger. 2015-02-27 12:35:46 +01:00
Florian Bruhin
e696898c4a Add input -> partial-timeout option to clear partial keystrings. 2015-02-27 10:59:03 +01:00
Florian Bruhin
42e2438efb Return Match instead bool in _handle_single_key. 2015-02-27 10:59:03 +01:00
Florian Bruhin
d66997610b Use a single Timer for ambigious keybindings. 2015-02-27 10:59:03 +01:00
Florian Bruhin
bfd0a3fbc2 Fix AttributeError when doing extended hinting. 2015-02-27 08:44:44 +01:00
Florian Bruhin
561ebd07f9 Make it possible to use :open -[twb] without url. 2015-02-27 08:10:00 +01:00
Florian Bruhin
5f46870594 Add a FuzzyUrl config type. 2015-02-27 08:08:46 +01:00
Florian Bruhin
6d51fcfb2e Add a do_search argument to fuzzy_url.
This turns off searching no matter what autosearch is set to, and also makes it
possible to use fuzzy_url before the config is up.

For now, we use this for quickmarks and the startpage.
2015-02-27 08:07:40 +01:00
Florian Bruhin
a76868c0f4 Refactor how click/hint open targets are handled. 2015-02-26 20:41:04 +01:00
Florian Bruhin
fa0bfaa49e Merge branch 'master' of ssh://tonks/qutebrowser 2015-02-26 20:21:00 +01:00
Florian Bruhin
eb8bad3d18 Reset open_target in acceptNavigationRequest.
After ddb39275eb, when something was opened via
hints in a new tab, the open_target still was set afterwards and the next
regular open did open in a new tab.
2015-02-26 20:17:01 +01:00
Florian Bruhin
a12dee8898 hints: Include button in buttons().
From the QMouseEvent::buttons documentation:

    For mouse move events, this is all buttons that are pressed down. For mouse
    press and double click events this includes the button that caused the
    event. For mouse release events this excludes the button that caused the
    event.
2015-02-26 20:12:48 +01:00
Florian Bruhin
ddb39275eb Simulate Ctrl-click when hinting in new tab/win.
This works around the fact some pages (e.g. github) load their content via AJAX
on a normal left click, so we'll never get acceptNavigationRequest and thus
can't open them in a new tab.

Fixes #488.
2015-02-26 20:11:13 +01:00
Florian Bruhin
4b4bb3af88 Handle NavigationTypeOther acceptNavigationRequest
This fixes hinting in some cases where javascript is used to load content, e.g.
on duckduckgo. However it still doesn't seem to help with github files etc.

See #488.
2015-02-26 20:04:00 +01:00
Florian Bruhin
0ebef4069e Remove debug console completing completely.
Turns out pylint doesn't like it if stuff is unused because we commented code
out ;)
2015-02-26 17:56:45 +01:00
Florian Bruhin
813ce9a513 Disable completion for debug console.
As long as it's broken it's more annoying rather than useful.
See #117.
2015-02-26 17:48:47 +01:00
Florian Bruhin
8e0dddf86a Restore sys.std* in utils.fake_io on exceptions. 2015-02-26 17:47:07 +01:00
Florian Bruhin
fcbd69e209 Clean up standarddir handling #2.
We already attempted this in c5a2039da4, but
having the directories as module attributes means they'll be created on start
(rather than when they're actually used), and it'd also be impossible to change
them after init for some reason in the future.

To still have a nice short API, we simply change the attributes to functions.
2015-02-26 07:01:22 +01:00
Florian Bruhin
8078068552 Merge branch 'Ram-Z-min-char-hints' 2015-02-26 06:14:08 +01:00
Florian Bruhin
f91aaf778a Regenerate docs. 2015-02-26 06:13:58 +01:00
Florian Bruhin
0d9bf5e2c9 Fix lint. 2015-02-26 06:13:27 +01:00
Samir Benmendil
81af41d77f Add option to set minimum number of chars in hints 2015-02-26 00:49:50 +00:00
Florian Bruhin
25b09b60d9 Fix buildbot badge in README 2015-02-25 23:23:43 +01:00
Florian Bruhin
1dc9862c0b Allow font names with integers in them. 2015-02-25 23:20:09 +01:00
Florian Bruhin
1d27dcca81 Ignore RuntimeError because of deleted Question. 2015-02-25 23:12:23 +01:00
Florian Bruhin
31d9018fc4 Disable insecure SSL ciphers (< 128bit) for Qt 5.2.
This is only an issue for the users which are stuck on Ubuntu Trusty.
2015-02-25 21:07:44 +01:00
Florian Bruhin
0fcd016427 Move cursor to end of textboxes when hinting. 2015-02-25 19:56:03 +01:00
Florian Bruhin
9d716d74b4 Fix lint. 2015-02-25 18:01:30 +01:00
Florian Bruhin
891c07f7e3 Merge branch 'master' of ssh://tonks/qutebrowser 2015-02-25 17:27:01 +01:00
Florian Bruhin
caad56c978 Print stacktrace on Qt warnings. 2015-02-24 07:12:25 +01:00
Florian Bruhin
0f9a1fe178 Display ImportError messages in earlyinit errors. 2015-02-23 15:16:30 +01:00
Florian Bruhin
94434ea739 Decorate DownloadItem slots with @pyqtSlot.
Before, using the right-click menu to cancel the download didn't actually
cancel it, as the QAction.toggled signal was emitted with checked=False which
got interpreted as remove_data=False.
2015-02-23 10:01:37 +01:00
Florian Bruhin
c5a2039da4 Refactor QStandardPaths handling. 2015-02-22 19:13:51 +01:00
Florian Bruhin
cabe5bf2a3 Use the real argparser instance for qtutils tests. 2015-02-22 12:10:42 +01:00
Florian Bruhin
617cd8977b Add --qt-name argument. See #514. 2015-02-21 18:53:01 +01:00
Florian Bruhin
359482b511 Fix getting Qt arguments. 2015-02-21 18:52:14 +01:00
Florian Bruhin
0ccb104f48 Merge branch 'master' of ssh://tonks/qutebrowser 2015-02-20 17:35:31 +01:00
Florian Bruhin
05d8a2429b tests: Fix double tearDown. 2015-02-20 09:27:41 +01:00
Florian Bruhin
42c8acc7aa Fix lint 2015-02-20 09:23:06 +01:00
Florian Bruhin
f33bc7bf31 tests: Get rid of second QCoreApplication. 2015-02-20 09:21:59 +01:00
Florian Bruhin
684f0d3df5 Fix starting with -c '' again. 2015-02-20 09:12:56 +01:00
Florian Bruhin
5fe85d0dde Add test for starting with -c ''. 2015-02-20 09:09:35 +01:00
Florian Bruhin
60d4305cc4 tests: Adjust environ_set_temp to take a dict. 2015-02-20 08:34:24 +01:00
Florian Bruhin
634028e277 Fix QIODevice warnings when closing tabs.
This is a regression introduced in 43c9d69295.
Fixes #517.
2015-02-20 07:43:40 +01:00
Florian Bruhin
14f2420500 Fix wrong parsing of faulthandler logs. 2015-02-19 22:45:37 +01:00
Florian Bruhin
a41331a402 Remove test QApplication "fix" again.
This causes more segfaults than it prevents...
2015-02-19 22:41:36 +01:00
Florian Bruhin
87951ee3a8 Fix crash when closing tabs on Qt 5.2.1 (Trusty)
This issue was introduced in 8f1d81a644.
2015-02-19 22:20:39 +01:00
Florian Bruhin
b5d3b264e8 Merge branch 'master' of ssh://tonks/qutebrowser 2015-02-19 21:38:11 +01:00
Florian Bruhin
2d4b03fbc9 Improve parsing of faulthandler logs. 2015-02-19 21:38:02 +01:00
Florian Bruhin
81fb57bbf0 Revert "Try another workaround for broken Windows tests."
This reverts commit 852fe2f84c.
2015-02-19 16:59:33 +01:00
Florian Bruhin
852fe2f84c Try another workaround for broken Windows tests. 2015-02-19 10:41:04 +01:00
Florian Bruhin
7dd908bd51 Try calling sip.delete on the test QApplication.
Maybe this helps with the unit tests on Windows?
2015-02-19 10:03:19 +01:00
Florian Bruhin
543c6cb90b Quit test QApplication properly.
Maybe this fixes the hangs and crashes on the Windows buildbots?
2015-02-19 07:46:08 +01:00
Florian Bruhin
3d5012ccca Rename 'Allowed' header for value completions. 2015-02-19 07:10:40 +01:00
Florian Bruhin
dc9e2a9772 Add a list of common user agents to completion. 2015-02-19 07:09:34 +01:00
Florian Bruhin
8c32fb86e2 Make it possible to set options with ! in it.
This is needed for a quit! alias for example. The option was wrongly treated as
an inversion even though a value was given.
2015-02-18 23:06:35 +01:00
Florian Bruhin
ea2dba6b38 Merge branch 'master' of ssh://tonks/qutebrowser 2015-02-18 22:20:41 +01:00
Florian Bruhin
3d72235023 Set the QSettings path to a config-subdirectory.
QWebInspector uses QSettings to save its GUI-settings. However, the default
path for QSettings is ~/.config/qutebrowser/qutebrowser.conf which overwrites
our own config file.

This fixes one part of #515.
2015-02-18 22:18:55 +01:00
Florian Bruhin
9534deb2e7 Add a context manager to unset organizationName. 2015-02-18 22:18:55 +01:00
Florian Bruhin
1268cbecf3 Merge branch 'Ram-Z-expandvars' 2015-02-18 16:17:01 +01:00
Florian Bruhin
78e2d03f04 Update docs. 2015-02-18 16:16:54 +01:00
Samir Benmendil
0ec05e071f expand environment vars in settings accepting paths 2015-02-18 15:04:49 +00:00
Florian Bruhin
e04af40140 flake8: Ignore W503. 2015-02-18 14:45:31 +01:00
Florian Bruhin
7a90d7fca8 Fix standarddir tests when XDG_*_HOME is set. 2015-02-18 13:47:15 +01:00
Florian Bruhin
83b636a0a7 Add some more tabhistory test URLs. 2015-02-18 07:10:46 +01:00
Florian Bruhin
62fb4b0d0b Fix initial creating of config. 2015-02-17 22:43:32 +01:00
Florian Bruhin
b31a432a1a Add options to ignore javascript prompts/alerts.
New options: content -> ignore-javascript-{prompt,alert}.
2015-02-17 19:03:18 +01:00
Florian Bruhin
238761bd5b Don't write zoom to QWebHistory.
If we set the zoom, it seems WebCore sets its own zoom independent of the
QtWebKit zoom, which leads to funny effects.
2015-02-17 07:48:19 +01:00
Florian Bruhin
5b33f6c5fe Re-add save-session setting. 2015-02-17 07:45:06 +01:00
Florian Bruhin
56b0ae2b6e Get rid of mainwindow.MainWindow.spawn. 2015-02-16 22:56:12 +01:00
Florian Bruhin
d8fe62bc61 Add workaround for adblock-message without window. 2015-02-16 22:21:36 +01:00
Florian Bruhin
46ca0e447e Get rid of the save-session setting for now.
With the way quitting is handled currently, it's hard to save the session on
quit as the windows will already be closed.
2015-02-16 21:33:24 +01:00
Florian Bruhin
8f1d81a644 Add session support.
Closes #12.
See #499.
See #11.

This adds PyYAML as a new dependency.

It adds the following new commands:

    :session-delete <name>
    Delete a session.

    :session-load <name>
    Load a session.

    :session-save [<name>]
    Save a session.

    :wq [<name>]
    Save open pages and quit.

And the following new settings:

    general -> save-session:
    Whether to always save the open pages.
2015-02-16 20:26:09 +01:00
Florian Bruhin
53b024f246 docutils: Fix handling of ", or None ...". 2015-02-14 19:41:10 +01:00
Florian Bruhin
b78d3934d7 Merge branch 'oed-download_cmd_interface' 2015-02-14 19:37:01 +01:00
Florian Bruhin
8e2e996369 Merge branch 'download_cmd_interface' of https://github.com/oed/qutebrowser into oed-download_cmd_interface 2015-02-14 19:06:47 +01:00
Joel Torstensson
4d2aa6a4d4 Fixed errors in update_indexes, etc. 2015-02-14 14:11:38 +01:00
Florian Bruhin
da9a8d368f Add FAQ about using mpv for youtube. Closes #435. 2015-02-14 00:35:55 +01:00
Florian Bruhin
f27d1364df Regenerate docs 2015-02-14 00:29:12 +01:00
Florian Bruhin
5817f3c18d Split config init into three functions. 2015-02-14 00:25:26 +01:00
Florian Bruhin
66d3ec1c08 Make it possible to configure tab titles. 2015-02-13 23:57:31 +01:00
Florian Bruhin
1cf34e7984 Force saving the config if new options were added. 2015-02-13 23:53:56 +01:00
Florian Bruhin
a38a77b16b Fix searching for terms starting with a slash.
Fixes #507.
2015-02-13 22:27:21 +01:00
Florian Bruhin
94b51128d1 Whitespace fixes. 2015-02-13 22:19:45 +01:00
Florian Bruhin
44b21374cb Fix :tab-clone.
We checked for None when getting the QColor, but now with the Python dict
instead of a QVariant that's a KeyError.

This is a regression introduced in 3cf9768f21.
2015-02-13 19:33:53 +01:00
Florian Bruhin
3cf9768f21 Use a dict for tab data instead of a single value. 2015-02-13 18:59:59 +01:00
Florian Bruhin
e459e1a472 Fix loading of history with URLs containing spaces
Fixes #508.
2015-02-13 18:58:44 +01:00
Florian Bruhin
1c5f036d4e Merge branch 'master' of ssh://git/qutebrowser 2015-02-13 13:42:57 +01:00
Florian Bruhin
3eb4aec0ca Add some Qt bug links to the FAQ. 2015-02-13 13:42:49 +01:00
Joel Torstensson
049a360abc Fixed error messages. 2015-02-13 12:40:37 +01:00
Joel Torstensson
cd5d4f4fee Regenerated docs. 2015-02-12 23:30:31 +01:00
Joel Torstensson
dfb801a0b7 dataChanged is now emited in update_indexes. 2015-02-12 23:29:05 +01:00
Joel Torstensson
9f0658f191 Minor fixes. 2015-02-12 22:21:30 +01:00
Joel Torstensson
8cd5f9e6d1 Implemented :download-delete. 2015-02-12 22:20:22 +01:00
Joel Torstensson
dd995c434c :download-cancel now only cancels downloads that are done. 2015-02-12 21:17:23 +01:00
Joel Torstensson
1f39200b28 :download-open and :download-remove now complains if you try to execute on a download that is not done. 2015-02-12 21:05:53 +01:00
Joel Torstensson
91f7056649 Added indexes to download view. 2015-02-12 20:43:13 +01:00
Florian Bruhin
22fab87311 Ignore tab key presses if they'd switch focus.
If the mainwindow is focused but not the web view (e.g. in prompt mode), an
unbound tab key should be filtered so it doesn't change keyboard focus.

Fixes #504.
2015-02-12 18:57:56 +01:00
Florian Bruhin
0b55f4df77 Fix starting with -c "".
This is a regression introduced in 9b1729c77e.
2015-02-12 13:24:17 +01:00
Florian Bruhin
5a73f5d2c1 Release v0.1.3 2015-02-12 07:47:17 +01:00
Florian Bruhin
c2f9cae770 Don't show line edit text in its repr().
Previously we showed self.text in the __repr__ of MinimalLineEdit. This however
is a bad idea, because it exposes passwords to the debug log when the currently
focused widget is logged.
2015-02-11 23:33:31 +01:00
Joel Torstensson
658ab70e98 :download-open now also checks whether download was successful. 2015-02-11 22:13:29 +01:00
Florian Bruhin
4e5bac709b Regenerate docs 2015-02-11 07:08:37 +01:00
Florian Bruhin
d4ef66714f Deny HTML5 permissions when question is cancelled.
Before we just ignored the request but deleted the Question object, which lead
to a RuntimeError when the question was cancelled and the page was reloaded,
because on_permission_canceled tried to abort the question.
2015-02-10 21:15:17 +01:00
Florian Bruhin
e44c5aee5b Add config options for geolocation/notifications. 2015-02-10 21:09:08 +01:00
Joel Torstensson
6e3d5867f9 Fixed docs. 2015-02-09 17:38:50 +01:00
Joel Torstensson
55193803a1 Changed :downloads-clear to :download-remove. 2015-02-09 17:17:34 +01:00
Joel Torstensson
e9da7b5391 :download-open now only opens finished downloads. 2015-02-09 17:03:02 +01:00
Florian Bruhin
43c9d69295 Handle shutdown of page with prompt correctly.
Before we didn't cancel javascript prompts when a page was closed, which lead
to exceptions or segfaults.
2015-02-09 15:41:27 +01:00
Florian Bruhin
bc43fb5e4c Implement requests for geolocation/notifications.
Closes #503.
2015-02-09 15:32:26 +01:00
Florian Bruhin
2ad1c4737c Revert "Add QApplication check to init_venv.py."
This reverts commit 3729ccb8cf.

This just lead to unnecessary problems where the toolchain actually works just
fine.
2015-02-09 15:25:52 +01:00
Joel Torstensson
767ca42e46 Some style fixes. 2015-02-09 12:06:49 +01:00
Florian Bruhin
494825fed0 Don't log statusbar messages in signalfilter. 2015-02-09 07:50:32 +01:00
Florian Bruhin
4704e81b41 Don't log javascript console messages by default. 2015-02-09 07:46:31 +01:00
Florian Bruhin
0f48ea62c1 Keep more debug log in memory. 2015-02-09 07:34:12 +01:00
Florian Bruhin
3729ccb8cf Add QApplication check to init_venv.py.
If no XServer is available, importing QtWidgets will work, but Qt will abort
when trying to construct a QApplication.
2015-02-09 00:19:02 +01:00
Joel Torstensson
6bbb655a54 Fixed bug when specifying filename on download. 2015-02-08 22:21:34 +01:00
Joel Torstensson
9428338389 Merged :download and :download-page.
Fix #449
2015-02-08 22:08:16 +01:00
Joel Torstensson
6f89ab628b More command actions on downloads. 2015-02-08 22:03:29 +01:00
Florian Bruhin
be48f3c875 flake8: Ignore E402. 2015-02-08 21:56:52 +01:00
Florian Bruhin
1c055a25b6 Remove ez_setup.py exclude from .flake8.
We don't ship that file anymore, and it seems to trigger a bug in pep8.
2015-02-08 21:46:38 +01:00
Florian Bruhin
31e71ed6d9 Fix copyright years.
Files created in 2015 don't need to have 2014 as copyright year.
2015-02-06 00:21:57 +01:00
Florian Bruhin
97d7e727b7 Regenerate docs. 2015-02-05 08:29:33 +01:00
Florian Bruhin
514ae1e798 Add periods for argparse help texts. 2015-02-05 08:29:13 +01:00
Florian Bruhin
a55076dfdf src2asciidoc: Fix metavar with nargs != 1. 2015-02-05 08:28:05 +01:00
Florian Bruhin
f78b21874f Add -p/--print argument to :set to print value. 2015-02-05 07:58:51 +01:00
Florian Bruhin
7615e20091 Add -s argument to set temporary options. 2015-02-05 07:54:19 +01:00
Florian Bruhin
5ed592a447 Replace some QTimers by named Timers. 2015-02-05 07:17:58 +01:00
Florian Bruhin
370c182f48 Fix header encoding in FakeNetworkReply test stub.
This was broken since ef9ddb2d5f but we never
noticed it, as an __init__.py was missing in the test folder since
06ec1a3885.
2015-02-05 07:12:36 +01:00
Florian Bruhin
0957d5df8e Make tests for browser.http run again. 2015-02-05 06:59:00 +01:00
Florian Bruhin
7d01abacaa run_checks: Add checker for missing __init__.py. 2015-02-05 06:55:48 +01:00
Florian Bruhin
af53a670ee Reorder stacktrace.asciidoc 2015-02-03 20:45:44 +01:00
Florian Bruhin
a18b3fe2a8 Update Arch section in stacktrace.asciidoc. 2015-02-03 20:30:51 +01:00
Florian Bruhin
fba2b2b5ae fuzzy_url: handle invalid URLs with autosearch off
Fixes #497.
2015-02-02 22:24:01 +01:00
Florian Bruhin
a95dda8e92 Handle explicit searches with auto-search=false.
See #497.
2015-02-02 22:19:43 +01:00
Florian Bruhin
cbde36948a Abort download override question on error/cancel.
If a download error occured or the user cancelled the download during the file
override question, an exception occured as the download was no longer valid
when the question was answered.

See #416.
2015-02-02 06:53:55 +01:00
Florian Bruhin
18b58b2001 Set a higher z-index for hint labels.
This fixes hinting on the youtube top bar.

Fixes #496.
2015-02-02 06:35:45 +01:00
Florian Bruhin
59a11c178f Don't open relative files in fuzzy_url with :open
With most actions which use fuzzy_url (:open/quickmarks/etc.) it's rather
confusing when relative files are opened - the only place where they should be
opened is when we're processing a commandline argument.
2015-02-01 23:55:37 +01:00
Florian Bruhin
9b1729c77e Always write config files to disk on first start. 2015-02-01 23:47:40 +01:00
Florian Bruhin
1c919967bb Send history (optionally) in fatal crash dialog. 2015-02-01 23:12:46 +01:00
Florian Bruhin
2f01c7c3ae Minor style adjustments in crashdialog.py. 2015-02-01 23:12:32 +01:00
Florian Bruhin
dc6aaecc78 Add __getitem__ to WebHistory. 2015-02-01 23:12:02 +01:00
Florian Bruhin
d9ae2183e8 Initialize web history earlier.
This is needed when displaying the history in the segfault report dialog.
2015-02-01 23:11:30 +01:00
Florian Bruhin
503fc9f56b Re-enable some flake8 checks. 2015-02-01 22:38:40 +01:00
Florian Bruhin
33a2181e31 Record global page history to disk.
We currently don't do anything with it yet, but people could use it in scripts
already and we have the history later when completion or other stuff will be
added based on it.

See #33.
2015-02-01 22:27:58 +01:00
Florian Bruhin
933151abd7 Regenerate docs. 2015-02-01 01:53:29 +01:00
Florian Bruhin
1266f147c8 Auto-save state/config/... periodically.
See #15.
2015-02-01 01:38:16 +01:00
Florian Bruhin
822bf90b26 Fix another speeling mistake. 2015-02-01 01:22:46 +01:00
Florian Bruhin
3b667325ca Move initializing of version/geometry saveables. 2015-02-01 01:22:24 +01:00
Florian Bruhin
43c5dc3bf6 Refactor saving logic, only save stuff if modified.
Fixes #113.
See #11.
2015-02-01 00:43:07 +01:00
Florian Bruhin
d6e87a2672 Hide adblocked iframes.
We now hide iframes which have been blocked completely instead of displaying an
error page in there. Displaying the error page also did break back/forward,
e.g. on reddit.

Fixes #493.
2015-01-31 22:38:31 +01:00
Florian Bruhin
9736224fa6 Close contextmenu when closing tab to avoid crash.
Fixes #494.
2015-01-31 21:01:03 +01:00
Florian Bruhin
55649882a0 Revert "Paste primary selection into forms on Shift-Insert."
This reverts commit 68a0428a09.

Even if this works fine for me, various people reported segfault issues when
using Shift-Insert, so I'm reverting this for now until I find a proper
solution.

See #491.
2015-01-30 11:50:05 +01:00
Florian Bruhin
c40e70ed11 Revert "Also copy QByteArray of clipboard data."
This reverts commit 4138debd1e.

It seems even with this, there are still weird segfault issues.

See #491.
2015-01-30 11:48:40 +01:00
Florian Bruhin
6bf87dd1d7 Fix statusbar quickly popping up as window. 2015-01-30 11:36:07 +01:00
Florian Bruhin
4138debd1e Also copy QByteArray of clipboard data.
This could possibly fix segfaults some people have seen when pasting after
68a0428a09.

See #491.
2015-01-30 06:42:55 +01:00
Florian Bruhin
b721a0e992 Clean up NetworkManager after downloads finished.
Fixes #490.
2015-01-28 22:52:24 +01:00
Florian Bruhin
66ec4f0599 Resize completion when it's shown, and only then.
Before this, we always resized the completion when the mainwindow was resized.
If the statusbar is hidden during the resize (ui -> hide-statusbar is true), we
got an invalid calculated QRect for the completion, causing the update to be
not applied at all - so the completion showed up incorrectly.

With this change, another resize is done when the completion is shown - at this
point it's certain the statusbar is visible. Also we only update it while it's
shown - it doesn't make sense to always adjust its size when it's hidden
anyways.
2015-01-28 22:16:22 +01:00
Florian Bruhin
81b91888f4 Add logging to completion resizing. 2015-01-28 22:15:57 +01:00
Florian Bruhin
052d4f513c Add .venv to .gitignore. 2015-01-28 20:35:05 +01:00
Florian Bruhin
4486573b2a Add jsconfirm.html test file. 2015-01-28 20:34:46 +01:00
Florian Bruhin
cf5fd9456b Add gen_resources.py script. 2015-01-28 20:33:54 +01:00
Florian Bruhin
1526cf1532 Merge branch 'master' of ssh://tonks/qutebrowser 2015-01-28 08:43:48 +01:00
Florian Bruhin
b9f16804f7 Make the window title configurable.
Closes #489.
2015-01-28 08:40:16 +01:00
Florian Bruhin
68a0428a09 Paste primary selection into forms on Shift-Insert.
Closes #491.
2015-01-28 06:56:38 +01:00
Florian Bruhin
d90814aabe Fix restoring of cmd widget after an error.
We accidentally restored the prompt widget instead of the command widget when
an immediate error message interrupted command mode.

Fixes #487.
2015-01-27 21:18:24 +01:00
Florian Bruhin
4c87287f4e Don't set up on_ssl_errors slot without SSL. 2015-01-26 13:23:41 +01:00
Florian Bruhin
7169d02609 Support running qutebrowser without SSL. 2015-01-26 13:15:08 +01:00
Florian Bruhin
bc380fca61 Clone zoom factor when cloning tab. 2015-01-26 07:20:03 +01:00
Florian Bruhin
ab2d2d79ca Clone text/icon as well when cloning tabs.
Closes #316.
2015-01-26 07:18:07 +01:00
Florian Bruhin
6576796718 Fix retrying of downloads after the tab is closed. 2015-01-25 23:53:23 +01:00
Florian Bruhin
cd39be62ee Show loading percentage in window title. 2015-01-25 23:25:08 +01:00
Florian Bruhin
594438e4d8 Regenerate docs. 2015-01-25 22:43:13 +01:00
Florian Bruhin
fe90b153ed Add new option ui->hide-statusbar to hide the bar.
Closes #486.
2015-01-25 22:41:19 +01:00
Florian Bruhin
8ffc1a3966 Change tabwidget calls from autohide to _tabhide. 2015-01-25 22:29:20 +01:00
Florian Bruhin
41fd89a206 crashdialog: Shorten paste titles.
Unfortunately the maximum char count for paste titles is 32...
2015-01-25 22:14:55 +01:00
Florian Bruhin
323db55a9c Remove restore checkbox from fatal report dialog. 2015-01-25 21:46:48 +01:00
Florian Bruhin
bd0a3a86d9 Display error on qt_mainloop crashes with old Qt.
See #447.
2015-01-25 21:37:28 +01:00
Florian Bruhin
09ea733231 Use qVersion() instead of QT_VERSION_STR. 2015-01-25 21:12:50 +01:00
Florian Bruhin
9702433d4e Crash dialog redesign.
We now have "Report/Don't report" buttons and a restart checkbox (checked by
default), so users don't accidentally send reports when they don't want to.
2015-01-24 20:28:44 +01:00
Florian Bruhin
7a11be1fb1 Merge branch 'smalltock-toggletab' 2015-01-24 18:10:52 +01:00
Florian Bruhin
ad53950e28 Regenerate docs. 2015-01-24 18:10:45 +01:00
Florian Bruhin
0d93d1eaff Minor style fixes. 2015-01-24 18:10:24 +01:00
Florian Bruhin
6ab65eb9d3 Merge branch 'toggletab' of https://github.com/smalltock/qutebrowser into smalltock-toggletab 2015-01-24 18:04:24 +01:00
Florian Bruhin
1e52f3856c venv: Fix handling of --cache 2015-01-24 18:01:25 +01:00
Florian Bruhin
29b9526a8e venv: Ignore more unneeded files when copying PyQt 2015-01-24 17:56:55 +01:00
Florian Bruhin
1d167fa428 Fix duplicate method name in crashdialog tests. 2015-01-24 17:45:39 +01:00
Florian Bruhin
b808aa07ba venv: Fix removing of venv if it's linked. 2015-01-24 17:43:56 +01:00
Florian Bruhin
3edffefff4 Fix qutebrowser.misc tests.
After the utils -> misc rename, we forgot an __init__.py, which means these
tests did never run.
2015-01-24 14:48:16 +01:00
Florian Bruhin
5b3b324331 Produce better titles for crash logs.
See #483 and #447.
2015-01-24 14:48:16 +01:00
ZDarian
62adc5ffe3 remove extra newline 2015-01-23 20:46:52 -07:00
Florian Bruhin
b86aa9061a Revert "Flush stdout before running setup.py."
This reverts commit bd633609ff.

We now set PYTHONUNBUFFERED in the buildbot environment.
2015-01-23 21:11:46 +01:00
Florian Bruhin
d16ac8f3ce Fix deleting of directories in link_pyqt. 2015-01-23 20:08:20 +01:00
Florian Bruhin
100e21d50c venv: Add argument to add a suffix to the cache.
This is mainly needed for the buildbot where multiple builds run in parallel on
the same host.
2015-01-23 19:55:43 +01:00
Florian Bruhin
bd633609ff Flush stdout before running setup.py. 2015-01-23 19:39:12 +01:00
Florian Bruhin
4dbbde9eaa venv: Better cache output. 2015-01-23 19:29:03 +01:00
Florian Bruhin
768e6ac5bf venv: Fix filtering of files to copy. 2015-01-23 19:14:46 +01:00
Florian Bruhin
feb964cff9 venv: Fix output when copying files. 2015-01-23 19:14:32 +01:00
Florian Bruhin
be568e1681 Fix lint 2015-01-23 18:54:17 +01:00
Florian Bruhin
2b7a843136 venv: Output all files copied into the venv. 2015-01-23 18:49:17 +01:00
Florian Bruhin
f3d570dd5b venv: Only copy needed files on Windows. 2015-01-23 18:32:33 +01:00
Florian Bruhin
6ceb0a41ff venv: Save cache to standard cache location.
Saving the cache inside the repository means the buildbot will clear it
automatically.
2015-01-23 17:47:56 +01:00
Florian Bruhin
409c04b6d4 Correct speeling error for .venv-cache. 2015-01-23 15:13:26 +01:00
Florian Bruhin
ca590c5df7 Fix lint 2015-01-23 15:10:44 +01:00
Florian Bruhin
e3ca06bc53 Add .venv_cache to .gitignore. 2015-01-23 15:09:48 +01:00
Florian Bruhin
0587cc8b1d Really fix pylint and unicodedata.category. 2015-01-23 15:06:51 +01:00
Florian Bruhin
e38f9747e7 init_venv: Add option to cache finished virtualenv.
This will hopefully speed up the tests (especially on Windows) since
dependencies don't get downloaded and built on every test.
2015-01-23 15:02:03 +01:00
Florian Bruhin
d7c7e91f2b pylint: Ignore no-member for unicodedata.category.
This seems to be broken for pylint since #463 on Windows, so we just ignore the
warning there.
2015-01-23 14:40:40 +01:00
Florian Bruhin
981a3ef96b Always use 'python' binary in virtualenv.
This should fix venv on OS X. See #463.
2015-01-23 14:37:56 +01:00
ZDarian
a08b814e5f Accidentally left in print() used for testing 2015-01-23 06:35:06 -07:00
ZDarian
00f67135ae Fixed tabbar visibility update 2015-01-23 06:34:01 -07:00
Florian Bruhin
eb428f2aeb Regenerate authors 2015-01-23 14:32:58 +01:00
Florian Bruhin
7580473a43 Install pip by hand in virtualenv on Debian.
It seems Debian/Ubuntu don't have the ensurepip module, so Python's venv will
fail unless started with --without-pip and us installing pip by hand via
get-pip.py :(

Related bugs:

    https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=772730
    https://bugs.launchpad.net/ubuntu/+source/python3.4/+bug/1290847

See #463.
2015-01-23 14:30:14 +01:00
Florian Bruhin
2201ca600b Fix long lines in init_venv.py.
See #463.
2015-01-23 14:14:30 +01:00
Florian Bruhin
d1df0b843e Merge branch 'venv'
Closes #451.
2015-01-23 13:55:10 +01:00
Florian Bruhin
d1d43b29dc Remove virtualenv dependency in INSTALL.asciidoc. 2015-01-23 13:54:18 +01:00
Florian Bruhin
6f1facac60 Also get rid of system_site_packages on Windows.
We can't symlink, so we copy the files instead.
2015-01-23 13:47:27 +01:00
Florian Bruhin
b2646cb5c0 Check if venv already exists.
According to the documentation, Python should do that already:

    If the target directory already exists an error will be raised, unless the
    --clear or --upgrade option was provided.

However that doesn't seem to be the case: http://bugs.python.org/issue23202

We do this by hand to make sure the user doesn't accidentally overwrite
something.

See #463.
2015-01-23 13:23:33 +01:00
Florian Bruhin
b8c54b5f02 Don't use system_site_packages on non-Windows.
See #463.
2015-01-23 13:17:39 +01:00
Florian Bruhin
907440d12c Simplify option handling in create_venv().
See #463.
2015-01-23 13:17:18 +01:00
Florian Bruhin
2264b55e92 Simplify create_venv().
See #463.
2015-01-23 13:16:53 +01:00
Florian Bruhin
816fcf3a6c Fix whitespace at EOL.
See #463.
2015-01-23 13:15:50 +01:00
Florian Bruhin
f76ce3c152 Regenerate authors
See #463.
2015-01-23 13:15:32 +01:00
Patric Schmitz
2404c75012 Use venv API for building the venv instead of calling pyvenv 2015-01-23 13:15:27 +01:00
Patric Schmitz
7813d9a93d Add hidden --force option for backwards compatibility with existing scripts 2015-01-23 13:15:23 +01:00
Patric Schmitz
4eefc53ed0 Change init_venv to use python 3.x venv
--force was replaced in favor of --clear and --upgrade which
correspond to the respective pyvenv options. The pyvenv help is
not explicit on the behavior if --clear is not given but the path
exists. https://docs.python.org/3/library/venv.html states pyvenv
would fail in that case, but it does not with Python 3.4.2, which
I don't consider a problem however.
Added a newline here and there for better readability.
2015-01-23 13:15:18 +01:00
Florian Bruhin
f6d0907736 init_venv: Pass --upgrade to pip install.
On Windows, --system-site-packages is enabled, so pip didn't upgrade pylint
from the system-wide version as it was already installed.
2015-01-22 13:37:58 +01:00
Florian Bruhin
2a72d290a7 Revert "init_venv: Pass --no-clean to pip on Windows."
This reverts commit 28fe84944c.

It didn't really help and it's deprecated (and un-deprecated?!) in the recent
pip.
2015-01-22 10:44:03 +01:00
Florian Bruhin
b387b4c7a9 Merge branch 'master' of ssh://git/qutebrowser 2015-01-22 10:41:58 +01:00
ZDarian
d7b5f2bf52 Remove always-hide from changed cfg 2015-01-21 23:21:43 -07:00
Florian Bruhin
5c92144f6b Fix spawn --userscript with multiple args.
This is a regression introduced in 4485e4ee1b.

We didn't unpack the argument list properly before passing it to
run_userscripts.

Fixes #476.
See #448.
2015-01-22 07:10:32 +01:00
Florian Bruhin
011e398f77 Merge branch 'thorsten-fix-check-libraries' 2015-01-22 07:01:54 +01:00
Florian Bruhin
011cd08fc8 Update AUTHORS 2015-01-22 07:01:46 +01:00
Thorsten Wißmann
5d5e26eb7b Fix some check_libraries() for arch
By now, the python-jinja and python-pygments packages are available in
community.
2015-01-22 07:01:35 +01:00
Florian Bruhin
5a0a0302df Merge branch 'new-pylint' 2015-01-22 07:00:01 +01:00
Florian Bruhin
3b6a504d7b crashdialog: Move contact info to top.
See #447.
2015-01-22 06:58:37 +01:00
Florian Bruhin
aa3017dd58 crashdialog: Reword contact info text.
See #447.
2015-01-22 06:56:45 +01:00
Florian Bruhin
50557a9b3e crashdialog: Remove Github from contact types.
See #447.
2015-01-22 06:56:23 +01:00
ZDarian
dffa7ccf46 Merge branch 'master' of https://github.com/The-Compiler/qutebrowser into toggletab 2015-01-21 20:51:43 -07:00
ZDarian
ef5412f596 tab *-hide -> tab hide-* 2015-01-21 20:50:30 -07:00
ZDarian
335f72a93f perm-hide -> always-hide 2015-01-21 07:27:08 -07:00
ZDarian
5100f6fc8f better type check; -'aborting' 2015-01-21 07:24:05 -07:00
Florian Bruhin
fb2e84be2a Merge branch 'uggedal-voidlinux' 2015-01-21 07:16:53 +01:00
Florian Bruhin
8da4e2b6f4 Update AUTHORS 2015-01-21 07:16:29 +01:00
Florian Bruhin
407edef2bc Merge branch 'voidlinux' of https://github.com/uggedal/qutebrowser into uggedal-voidlinux 2015-01-21 07:16:16 +01:00
Florian Bruhin
6c2471bf9c Merge branch 'peterlvilim-show-default-value' 2015-01-21 07:14:55 +01:00
Florian Bruhin
4f20d6123c Update AUTHORS 2015-01-21 07:14:48 +01:00
Florian Bruhin
d540a1ee22 Merge branch 'show-default-value' of https://github.com/peterlvilim/qutebrowser into peterlvilim-show-default-value 2015-01-21 07:07:25 +01:00
Florian Bruhin
6d3f871119 Add workaround for unknown pylint no-member bug. 2015-01-21 00:01:05 +01:00
Florian Bruhin
958e67ab9e Add workaround for pylint performance bug.
See https://bitbucket.org/logilab/pylint/issue/395/horrible-performance-related-to-inspect
2015-01-21 00:00:52 +01:00
Florian Bruhin
2e45c2c063 Stop pinning pylint/astroid to 1.3.1/1.2.1. 2015-01-21 00:00:51 +01:00
Florian Bruhin
532ec30d00 Fix executing of virtualenv pylint on Windows.
6a7e454789 broke executing pylint on Windows,
because there was a pylint Python script in vev\Scripts, and subprocess tried
to execute that instead of the .exe.
2015-01-20 23:06:03 +01:00
Florian Bruhin
f4479a8140 Fix adding epilogue in src2asciidoc.py. 2015-01-20 00:02:16 +01:00
Florian Bruhin
6a7e454789 Add proper virtualenv support to run_checks.py 2015-01-19 23:50:01 +01:00
Peter Vilim
c30978be2f Add quotes for empty default value 2015-01-19 13:17:17 -06:00
Florian Bruhin
18443a6880 run_checks: Support print_version for check_pep257. 2015-01-19 00:53:05 +01:00
Florian Bruhin
aa6750ac1b run_checks: Add a comment for check_pep257. 2015-01-19 00:51:33 +01:00
Florian Bruhin
dc9263a77c Revert "run_checks: Run pep257 via subprocess."
This reverts commit 380537d49c.

Conflicts:
	scripts/run_checks.py

This is needed because it seems pep257 doesn't install a binary on Windows.
2015-01-19 00:50:18 +01:00
Florian Bruhin
1e8729eac7 run_checks: Add a --print-version argument. 2015-01-19 00:45:01 +01:00
Florian Bruhin
380537d49c run_checks: Run pep257 via subprocess. 2015-01-19 00:42:39 +01:00
Eivind Uggedal
d0f416386a INSTALL: instructions for Void Linux 2015-01-18 22:32:50 +00:00
ZDarian
52afa1a479 Added permanent (count-independent) tab hide according to perm-hide variable. 2015-01-18 07:39:26 -07:00
ZDarian
3c21d5986e Added toggle ability to :set
Append '!' to option name of boolean value to toggle its state.
2015-01-18 07:34:33 -07:00
Florian Bruhin
ddc4e7b309 Unset __PYVENV_LAUNCHER__ to fix init_venv on OS X.
For some weird reason, pip installed logilab.common into /usr/local when
launching it via subprocess, because __PYVENV_LAUNCHER__ was set...
2015-01-18 00:05:08 +01:00
Florian Bruhin
b3b576f5d2 Handle all IPCErrors properly. 2015-01-16 11:34:15 +01:00
Florian Bruhin
c98bfa9a9d Move version info more to the top in logs. 2015-01-16 09:22:22 +01:00
Florian Bruhin
aae33a0308 Handle another webelem.IsNullError with hints. 2015-01-16 09:09:12 +01:00
Florian Bruhin
dbd0d1fff9 Save report dialog contact infomation. 2015-01-16 07:36:38 +01:00
Florian Bruhin
4f1e0d32b0 Handle UnicodeDecodeError when reading configs.
(WTF are you guys doing?!)
2015-01-16 07:10:12 +01:00
Florian Bruhin
e9786458fa Really ignore pylint checks. 2015-01-15 23:09:18 +01:00
Florian Bruhin
eadaef3ce9 Ignore pylint warnings for the older pep257 code 2015-01-15 22:46:03 +01:00
Florian Bruhin
04598b2315 Fix pep257 check for newer versions 2015-01-15 22:41:01 +01:00
Florian Bruhin
4485e4ee1b Merge :run-userscripts into :spawn.
:run-userscripts is now marked as deprecated, and :spawn has a new
-u/--userscript option instead.

Closes #448.
2015-01-15 22:29:06 +01:00
Florian Bruhin
a32f1e6180 Make it possible to deprecate commands.
See #448.
2015-01-15 22:29:00 +01:00
Florian Bruhin
223f8f243e Make it possible to display warnings in the bar.
Closes #114.
2015-01-15 22:29:00 +01:00
sbinix
969c3550cd revert qutebrowser.1.asciidoc 2015-01-09 23:25:53 +00:00
Florian Bruhin
48c83505df Bump master to v0.1.2 2015-01-10 00:08:38 +01:00
Florian Bruhin
8c227324fe Use qurl_from_user_input() in urlutils.is_url().
It seems 354018efcd broke IPv6 IPs on older Qt
versions:

======================================================================
FAIL: test_urls (qutebrowser.test.utils.test_urlutils.IsUrlTests) (url='2001:41d0:2:6c11::1')
Test things which are URLs.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/lib/buildbot/slaves/slave/ubuntu-utopic/build/qutebrowser/test/utils/test_urlutils.py", line 168, in test_urls
    self.assertTrue(urlutils.is_url(url), url)
AssertionError: False is not true : 2001:41d0:2:6c11::1
2015-01-09 22:13:23 +01:00
Florian Bruhin
a412814dee Enter KeyMode.normal directly in ModeManager.
We used to enter KeyMode.none and then with a zero-time singleShot QTimer enter
the normal mode. This doesn't really make sense, and caused an exception if a
keypress was processed before the timer fired.

Fixes #433.
2015-01-09 21:49:39 +01:00
Florian Bruhin
d147ba90d4 Remove old comment. 2015-01-09 21:49:39 +01:00
Florian Bruhin
354018efcd Make sure QUrl::fromUserInput is valid in is_url.
Fixes #460.

Without this fix, it's possible for URLs to be valid according to is_url, but
not according to QUrl::fromUserInput, e.g. "http:foo:0". This caused an
exception later because fuzzy_url runs qtutils.ensure_valid.
2015-01-09 21:49:26 +01:00
Florian Bruhin
3eac528716 Switch Qt style to Fusion on OS X on Qt 5.4.
Fixes #462.
See #459.

Upstream bugs:

https://bugreports.qt.io/browse/QTBUG-42948
https://bugreports.qt.io/browse/QTBUG-43070
2015-01-09 15:43:03 +01:00
Florian Bruhin
3ba202d467 Fix config breakage because of deleted option. 2015-01-09 14:38:20 +01:00
Florian Bruhin
b9ed0b37f0 Improve README (badges, alt-texts). 2015-01-09 14:30:18 +01:00
Florian Bruhin
8fb1a887db Remove old colors->tabs.separator option. 2015-01-09 14:19:04 +01:00
Florian Bruhin
5947994479 Fix error handling for local files in :adblock-update 2015-01-09 06:53:00 +01:00
Florian Bruhin
4c1113cdf4 Add cache location to manpage. See #461. 2015-01-09 06:13:09 +01:00
binix
6d0fff1c24 add cache folder to files section of man page 2015-01-08 23:39:40 +00:00
Florian Bruhin
30e93ca4b8 Merge branch 'balanceofcowards-master' 2015-01-08 23:11:53 +01:00
Florian Bruhin
e767862bee Regenerate authors 2015-01-08 23:10:38 +01:00
Florian Bruhin
5fc900a1cb Merge branch 'master' of https://github.com/balanceofcowards/qutebrowser into balanceofcowards-master 2015-01-08 23:09:58 +01:00
Florian Bruhin
395047d778 Merge branch 'peterlvilim-show-default-value' 2015-01-08 23:04:53 +01:00
Florian Bruhin
c4b4027104 Merge branch 'show-default-value' of https://github.com/peterlvilim/qutebrowser into peterlvilim-show-default-value 2015-01-08 23:04:35 +01:00
Peter Vilim
14afb3ef14 show-default-value: update heading 2015-01-08 12:52:21 -06:00
binix
8c7a7aaf20 Stop the icon database from being created when private-browsing is set to true 2015-01-08 17:09:55 +00:00
Andreas Fischer
34c9a73e32 Fix bug #399 (crashes on systems with pyqt < 5.3)
Crash occurs in usertypes.py / Question class due to Python slots
being called on deleted Qt objects. This causes either a TypeError or
an AttributeError (probably depending on the state of the deleted Qt
object?).

Fixed by declaring slots in the Question object explicitly via
decorator "@pyqtSlot()".

Possible further TODOs:
- Find out whether this is a problem for slots in other objects as
  well.
- Create unittest for this bug (might me somewhat tricky, though).
2015-01-08 09:52:53 +01:00
Florian Bruhin
1086c33ec2 Add suckless surf ML post to notes. 2015-01-08 06:41:44 +01:00
Peter Vilim
eba4b58a7c Show default config value in completion 2015-01-07 18:16:59 -06:00
Florian Bruhin
b12b83f98c Hide 2 more Qt warnings. 2015-01-07 06:59:48 +01:00
Florian Bruhin
75ac9cce49 Use Esc in normal mode to clear search highlights. 2015-01-06 18:46:44 +01:00
Florian Bruhin
77df4c7241 Add !important to all hint properties. 2015-01-06 17:10:54 +01:00
Florian Bruhin
9ebf36f26b Merge branch 'error800-master' 2015-01-06 16:55:37 +01:00
Florian Bruhin
55d9f62c9f Regenerate authors. 2015-01-06 16:55:28 +01:00
Error 800
82ee78b3db Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-06 16:23:45 +01:00
Error 800
c4619874e6 Merge remote-tracking branch 'upstream/master' 2015-01-06 16:20:35 +01:00
Error 800
c008ee8dd7 Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-06 16:13:28 +01:00
Florian Bruhin
492f066bd8 Merge branch 'peterlvilim-download-quit-confirm' 2015-01-06 11:29:19 +01:00
Florian Bruhin
c4bb9344a9 Regenerate docs. 2015-01-06 11:29:13 +01:00
Florian Bruhin
dda54a2cc9 Merge branch 'download-quit-confirm' of https://github.com/peterlvilim/qutebrowser into peterlvilim-download-quit-confirm 2015-01-06 11:28:16 +01:00
Florian Bruhin
3ff28027de Make init_venv.py work with multiple sip .so files.
On my Debian jessie there's a sip.cpython-34m-x86_64-linux-gnu.so and a
sip.cpython-34dm-x86_64-linux-gnu.so.
2015-01-06 11:26:40 +01:00
Peter Vilim
5c37d4a19d fix line lengths 2015-01-06 04:14:41 -06:00
Peter Vilim
f828e554f7 misc fixes 2015-01-06 04:03:21 -06:00
Florian Bruhin
46396cce1e Fix maxsplit-splitting with empty args (""/'').
Fixes #453.
2015-01-06 10:00:28 +01:00
Florian Bruhin
e07146be7c Add "$@" to example wrapper script in INSTALL.
Thanks to mkonig for the suggestion!
2015-01-06 09:51:53 +01:00
Peter Vilim
dfa276a20c backward compatibility, space, combinations 2015-01-05 22:41:42 -06:00
Florian Bruhin
e339b0cef9 Fix missing whitespace. 2015-01-05 18:56:09 +01:00
Florian Bruhin
9cc5bedb4d Merge branch 'thiagowfx-master' 2015-01-05 18:47:31 +01:00
Florian Bruhin
2d77381660 Regenerate authors 2015-01-05 18:47:25 +01:00
Florian Bruhin
85c89305a7 Merge branch 'master' of https://github.com/thiagowfx/qutebrowser into thiagowfx-master 2015-01-05 18:47:16 +01:00
Florian Bruhin
7bf0013e60 Merge branch 'error800-master' 2015-01-05 18:39:35 +01:00
Florian Bruhin
6722780f86 Regenerate authors 2015-01-05 18:39:27 +01:00
Florian Bruhin
60874aad28 Merge branch 'master' of https://github.com/error800/qutebrowser into error800-master 2015-01-05 18:38:54 +01:00
Error 800
ce5629eab3 Fixed uppercase hints option
Corrected CSS property from 'texttransform' to 'text-transform'
2015-01-05 16:20:45 +01:00
Peter Vilim
0305dedbfb Use multiple lines for quit messages 2015-01-05 01:01:22 -06:00
Thiago Barroso Perrotta
94ea35c9e8 add g[tT] for cycling through tabs (LuaKit/vim like) 2015-01-05 01:57:10 -02:00
Florian Bruhin
ff0c845c50 Uncheck sending of debug log with private browsing.
Fixes #436.
2015-01-04 20:41:35 +01:00
Florian Bruhin
efe96462c9 Pass more useful information to userscripts.
Closes #379.
2015-01-04 20:16:15 +01:00
Florian Bruhin
013f906c3b hints: Small cleanup (add _show_url_error method). 2015-01-04 20:15:45 +01:00
Florian Bruhin
b8a04f5309 Don't raise CommandError in TabbedBrowser.current_url.
TabbedBrowser.current_url used to process the qtutils.QtValueError exception
and raise a cmdexc.CommandError based on it. While this was useful for some
callers, it made handling it in others weird, and it doesn't really belong
there - so now the caller handles this.
2015-01-04 20:13:25 +01:00
Florian Bruhin
04c8a17b2e Merge branch 'userscript-runner' 2015-01-04 15:21:31 +01:00
Florian Bruhin
f64269c57a Add qutebrowser- to FIFO name. 2015-01-04 14:53:00 +01:00
Florian Bruhin
b1b1cecdb7 Add some more logging 2015-01-04 14:51:50 +01:00
Florian Bruhin
ecc7f09f86 Use QSocketNotifier for userscripts. 2015-01-04 14:36:59 +01:00
Florian Bruhin
37e31d92c7 Merge branch 'ml--master' 2015-01-04 13:37:35 +01:00
Florian Bruhin
abe2dd7589 Regenerate docs 2015-01-04 13:37:09 +01:00
Florian Bruhin
6d8bffe405 Merge branch 'master' of https://github.com/ml-/qutebrowser into ml--master 2015-01-04 13:35:50 +01:00
Florian Bruhin
e0483363aa Add a test making sure the default config is valid.
See #438, #439, #440, #441.
2015-01-04 13:34:05 +01:00
Peter Vilim
8a3aca63b0 Confirm quit if downloads running 2015-01-04 04:35:27 -06:00
Matthias Lisin
2814456586 Commas are awesome
Fixes #438
Fixes #439
2015-01-04 02:02:26 +01:00
Florian Bruhin
2203db298d Abort blocking questions when new page is loaded.
Fixes #430.
Fixes #431.
Hopefully fixes #354.
Hopefully fixes #434.
2015-01-03 22:21:47 +01:00
Florian Bruhin
f811f511fa Use self._win_id in QNetworkManager._ask. 2015-01-03 22:04:56 +01:00
Florian Bruhin
9521d253a1 Fix validation of ShellCommand config type.
Fixes #432.
2015-01-03 21:19:43 +01:00
Florian Bruhin
e54d3c21ee Add SSL info to version info. 2015-01-03 18:19:16 +01:00
Florian Bruhin
7dbbfedd3b Replace unencodable chars in download filenames.
Fixes #427.
2015-01-03 17:50:59 +01:00
Florian Bruhin
450d1ab70d Update copyright years 2015-01-03 15:51:31 +01:00
Florian Bruhin
7e642fb3e4 Merge branch 'regines-master' 2015-01-02 21:41:31 +01:00
Florian Bruhin
7165ce0b1f Regenerate authors 2015-01-02 21:41:27 +01:00
Regina Hug
0712037a52 Add 'R' to cheatsheet. 2015-01-02 21:22:03 +01:00
Florian Bruhin
6d1ac5d2a2 Merge branch 'krobelus-search' 2015-01-01 19:15:21 +01:00
Florian Bruhin
b0afe72e42 Regenerate authors 2015-01-01 19:15:16 +01:00
Florian Bruhin
e30f79981d Merge branch 'search' of https://github.com/krobelus/qutebrowser into krobelus-search 2015-01-01 19:15:02 +01:00
Florian Bruhin
a5ce9571ff Remove hosts-file.net from blocker default lists. 2015-01-01 19:14:45 +01:00
Florian Bruhin
eac186bd64 Merge branch 'posativ-patch-1' 2014-12-31 22:00:26 +01:00
Florian Bruhin
df45cdfa86 Regenerate authors 2014-12-31 22:00:18 +01:00
Martin Zimmermann
f2649ccc4b qutebrowser overlay is now available in layman 2014-12-31 21:56:21 +01:00
Florian Bruhin
6ec5b70067 Correct virtualenv links in INSTALL.asciidoc. 2014-12-31 21:42:47 +01:00
Florian Bruhin
56d844aff8 Fix user-stylesheet setting with an empty value. 2014-12-30 00:56:53 +01:00
Johannes Altmanninger
8b69f9b62c Registered 'search' as command.
Fixes #421.
2014-12-29 22:45:26 +01:00
Florian Bruhin
4471f81c11 Expand ~ to home dir with :run-userscript. 2014-12-29 22:17:58 +01:00
Florian Bruhin
8b9f323f41 Merge branch 'halosghost-force-reload' 2014-12-29 22:12:43 +01:00
Florian Bruhin
0cd0f97587 Call triggerAction on page, not view. 2014-12-29 22:12:09 +01:00
Florian Bruhin
8e550ebe88 Regenerate docs 2014-12-29 22:04:22 +01:00
Florian Bruhin
0ea25c6ef0 Add missing imports 2014-12-29 22:04:09 +01:00
HalosGhost
0a1fa87ac9 Add -f option to reload 2014-12-29 22:03:54 +01:00
Florian Bruhin
5ca58843fc Merge branch 'oed-master' 2014-12-29 22:01:34 +01:00
Florian Bruhin
b5848a70ee Regenerate docs 2014-12-29 22:01:27 +01:00
Florian Bruhin
ad6065605a Merge branch 'master' of https://github.com/oed/qutebrowser into oed-master 2014-12-29 21:58:51 +01:00
Joel Torstensson
8746715bf0 Moved fullscreen method. 2014-12-22 22:56:52 +01:00
Joel Torstensson
19f75984e3 Implemented fullscreen mode. Fix #385 2014-12-22 21:57:46 +01:00
290 changed files with 33424 additions and 12311 deletions

18
.appveyor.yml Normal file
View File

@@ -0,0 +1,18 @@
shallow_clone: true
version: '{branch}-{build}'
cache:
- C:\projects\qutebrowser\.cache
build: off
environment:
PYTHON: 'C:\Python34'
PYTHONUNBUFFERED: 1
install:
- C:\Python27\python -u scripts\dev\ci_install.py
test_script:
- C:\Python34\Scripts\tox -e py34
- C:\Python34\Scripts\tox -e py34-integration
- C:\Python34\Scripts\tox -e unittests-frozen
- C:\Python34\Scripts\tox -e smoke-frozen
- C:\Python34\Scripts\tox -e pylint

17
.coveragerc Normal file
View File

@@ -0,0 +1,17 @@
[run]
branch = true
omit =
qutebrowser/__main__.py
*/__init__.py
qutebrowser/resources.py
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == ["']__main__["']:
[xml]
output=coverage.xml

49
.eslintrc Normal file
View File

@@ -0,0 +1,49 @@
# vim: ft=yaml
env:
browser: true
rules:
block-scoped-var: 2
dot-location: 2
default-case: 2
guard-for-in: 2
no-div-regex: 2
no-param-reassign: 2
no-eq-null: 2
no-floating-decimal: 2
no-self-compare: 2
no-throw-literal: 2
no-void: 2
radix: 2
wrap-iife: [2, "inside"]
brace-style: [2, "1tbs", {"allowSingleLine": true}]
comma-style: [2, "last"]
consistent-this: [2, "self"]
func-style: [2, "declaration"]
indent: [2, 4, {"SwitchCase": 1}]
linebreak-style: [2, "unix"]
max-nested-callbacks: [2, 3]
no-lonely-if: 2
no-multiple-empty-lines: [2, {"max": 2}]
no-nested-ternary: 2
no-unneeded-ternary: 2
operator-assignment: [2, "always"]
operator-linebreak: [2, "after"]
space-after-keywords: [2, "always"]
space-before-blocks: [2, "always"]
space-before-function-paren: [2, {"anonymous": "never", "named": "never"}]
object-curly-spacing: [2, "never"]
array-bracket-spacing: [2, "never"]
computed-property-spacing: [2, "never"]
space-in-parens: [2, "never"]
space-unary-ops: [2, {"words": true, "nonwords": false}]
spaced-comment: [2, "always"]
max-depth: [2, 5]
max-len: [2, 79, 4]
max-params: [2, 5]
max-statements: [2, 30]
no-bitwise: 2
quote-props: [2, "always"]
global-strict: 0
quotes: 0

19
.flake8
View File

@@ -1,19 +0,0 @@
# vim: ft=dosini fileencoding=utf-8:
[flake8]
# E241: Multiple spaces after ,
# E265: Block comment should start with '#'
# checked by pylint:
# F401: Unused import
# E501: Line too long
# F821: undefined name
# F841: unused variable
# E222: Multiple spaces after operator
# F811: Redifiniton
# W292: No newline at end of file
# E701: multiple statements on one line
# E702: multiple statements on one line
# E225: missing whitespace around operator
ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E701,E702,E225
max_complexity = 12
exclude = ez_setup.py

15
.gitignore vendored
View File

@@ -1,5 +1,6 @@
__pycache__
*.pyc
*.swp
/build
/dist
/qutebrowser.egg-info
@@ -12,4 +13,18 @@ __pycache__
/qutebrowser/git-commit-id
/doc/*.html
/README.html
/CHANGELOG.html
/CONTRIBUTING.html
/FAQ.html
/INSTALL.html
/qutebrowser/html/doc/
/.venv
/.coverage
/htmlcov
/.coverage.xml
/.tox
/testresults.html
/.cache
/.testmondata
/.hypothesis
TODO

View File

@@ -1,40 +1,46 @@
# vim: ft=dosini fileencoding=utf-8:
[MASTER]
ignore=ez_setup.py
ignore=resources.py
extension-pkg-whitelist=PyQt5,sip
load-plugins=pylint_checkers.config,
pylint_checkers.modeline,
pylint_checkers.openencoding,
pylint_checkers.settrace
[MESSAGES CONTROL]
disable=no-self-use,
super-on-old-class,
old-style-class,
abstract-class-little-used,
bad-builtin,
star-args,
fixme,
global-statement,
no-init,
locally-disabled,
too-many-ancestors,
too-few-public-methods,
too-many-public-methods,
cyclic-import,
bad-option-value,
bad-continuation,
too-many-instance-attributes,
unnecessary-lambda,
blacklisted-name,
too-many-lines
too-many-lines,
logging-format-interpolation,
interface-not-implemented,
broad-except,
bare-except,
eval-used,
exec-used,
file-ignored
[BASIC]
module-rgx=(__)?[a-z][a-z0-9_]*(__)?$
function-rgx=([a-z_][a-z0-9_]{2,30}|setUpModule|tearDownModule)$
function-rgx=([a-z_][a-z0-9_]{2,50}|setUpModule|tearDownModule)$
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
method-rgx=[a-z_][A-Za-z0-9_]{2,40}$
method-rgx=[a-z_][A-Za-z0-9_]{2,50}$
attr-rgx=[a-z_][a-z0-9_]{0,30}$
argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$
class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$
inlinevar-rgx=[a-z_][a-z0-9_]*$
docstring-min-length=3
[FORMAT]
max-line-length=79

View File

@@ -1,20 +0,0 @@
# vim: ft=dosini
[DEFAULT]
targets=qutebrowser,scripts
[pep257]
# D102: Docstring missing, will be handled by others
# D209: Blank line before closing """ (removed from PEP257)
# D402: First line should not be function's signature (false-positives)
disable=D102,D209,D402
exclude=test_.*
[pylint]
args=--output-format=colorized,--reports=no,--rcfile=.pylintrc
plugins=config,crlf,modeline,settrace,openencoding
exclude=resources.py
[flake8]
args=--config=.flake8
exclude=resources.py

33
.travis.yml Normal file
View File

@@ -0,0 +1,33 @@
dist: trusty
os:
- linux
- osx
# Not really, but this is here so we can do stuff by hand.
language: c
cache:
directories:
- $HOME/.cache/pip
- $HOME/build/The-Compiler/qutebrowser/.cache
install:
- python scripts/dev/ci_install.py
script:
- xvfb-run -s "-screen 0 640x480x16" tox -e py34,py34-integration
- tox -e misc
- tox -e pep257
- tox -e pyflakes
- tox -e pep8
- tox -e mccabe
- tox -e pylint
- tox -e pyroma
- tox -e check-manifest
# Travis bug - OS X builds get routed to Ubuntu Trusty if "dist: trusty" is
# given.
matrix:
allow_failures:
- os: osx

502
CHANGELOG.asciidoc Normal file
View File

@@ -0,0 +1,502 @@
Change Log
===========
// http://keepachangelog.com/
All notable changes to this project will be documented in this file.
This project adheres to http://semver.org/[Semantic Versioning].
// tags:
// `Added` for new features.
// `Changed` for changes in existing functionality.
// `Deprecated` for once-stable features removed in upcoming releases.
// `Removed` for deprecated features removed in this release.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.4.0
------
Added
~~~~~
- New bookmark functionality (similar to quickmarks without a name).
* New command `:bookmark-add` to bookmark the current page (bound to `M`).
* New command `:bookmark-load` to load a bookmark (bound to `gb`/`gB`/`wB`).
- New (hidden) command `:completion-item-del` (bound to `<Ctrl-D>`) to delete
the current item in the completion (for quickmarks/bookmarks).
- New settings `tabs -> padding` and `tabs -> indicator-tabbing` to control the
size/padding of the tabbar.
- New setting `ui -> statusbar-padding` to control the size/padding of the
status bar.
- New setting `network -> referer-header` to configure when the referer should
be sent (by default it's only sent while on the same domain).
- New setting `tabs -> show` which supersedes the old `tabs -> hide-*` options
and has an additional `switching` option which shows tab while switching
them. There's also a new `show-switching` option to configure the timeout.
- New setting `storage -> remember-download-directory` to remember the last
used download directory.
- New setting `storage -> prompt-download-directory` to download all downloads
without asking.
- Rapid hinting is now also possible for downloads.
- Directory browsing via `file://` is now supported.
Changed
~~~~~~~
- Some developer scripts got moved to `scripts/dev/`
- When downloading to a FIFO or special file, a confirmation is displayed as
this might cause qutebrowser to hang.
- The `:yank-selected` command now works in all modes instead of just caret
mode and is not hidden anymore.
- `minimal_webkit_testbrowser.py` now has a `--webengine` switch to test
QtWebEngine if it's installed.
- The column width percentages for the completion view now depend on the
completion model.
- The values for `tabs -> position` and `ui -> downloads-position` got changed
from `north`/`south`/`west/`east` to `top`/`bottom`/`left`/`right`. Existing
configs should be adjusted automatically.
- `:tab-focus`/`gt` now behaves like `:tab-next` if no count/index is given.
- The completion widget doesn't show a border anymore.
- The tabbar doesn't display ugly arrows anymore if there isn't enough space
for all tabs.
- Some insignificant Qt warnings which were printed on OS X are now hidden.
- Better support for Qt 5.5 and Python 3.5.
Fixed
~~~~~
- Fixed a bug where cookies were saved despite qutebrowser being started in
private browsing mode.
- The local socket used for inter-process communication (opening new instances)
is now ensured to only be accessible by the user on all operating systems.
- Various corner cases for inter-process communication issues got fixed.
- `link_pyqt.py` now should work better on untested distributions.
- Fixed various corner-cases with crashes when reading invalid config values
and the history file.
- Fixed various corner-cases when setting text via an external editor.
- Fixed potential crash when hinting a text field.
- Fixed entering of insert mode when certain disabled text fields were clicked.
- Fixed a crash when using `:set` with `-p` and `!` (invert value)
- Downloads with unknown size are now handled correctly.
- `:navigate increment/decrement` (`<Ctrl-A>`/`<Ctrl-X>`) now handles some
corner-cases better.
- Fixed a bug where the completion got affected by another window's completion
if it was open in both windows.
- Fixed a performance issue with large histories when opening previously
unvisited websites.
- The progress bar now doesn't cause the statusbar to change it's height
anymore.
- `~` is now always expanded when spawning a script.
- Fixed various corner cases when opening links in an existing instance.
- Fixed a race-condition causing an exception when starting qutebrowser.
Removed
~~~~~~~
- The `tabs -> indicator-space` setting got removed as the new padding settings
should be used instead.
- The `tabs -> hide-always` and `tabs -> hide-auto` settings got merged into
the new `tabs -> show` setting.
v0.3.0
------
Added
~~~~~
- New commands `:message-info`, `:message-error` and `:message-warning` to show messages in the statusbar, e.g. from a userscript.
- New command `:scroll-px` which replaces `:scroll` for pixel-exact scrolling.
- New command `:jseval` to run a javascript snippet on the current page.
- New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`).
- New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `<Escape>` by default, in addition to clearing search).
- New setting `ui -> smooth-scrolling`.
- New setting `content -> webgl` to enable/disable https://www.khronos.org/webgl/[WebGL].
- New setting `content -> css-regions` to enable/disable support for http://dev.w3.org/csswg/css-regions/[CSS Regions].
- New setting `content -> hyperlink-auditing` to enable/disable support for https://html.spec.whatwg.org/multipage/semantics.html#hyperlink-auditing[hyperlink auditing].
- New setting `tabs -> mousewheel-tab-switching` to control mousewheel behavior on the tab bar.
- New arguments `--datadir` and `--cachedir` to set the data/cache location.
- New arguments `--basedir` and `--temp-basedir` (intended for debugging) to set a different base directory for all data, which allows multiple invocations.
- New argument `--no-err-windows` to suppress all error windows.
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully.
- Many new color settings (foreground setting for every background setting).
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
- New setting `completion -> auto-open` to only open the completion when tab is pressed (if set to false).
- New visual/caret mode (bound to `v`) to select text by keyboard.
- There are now some example userscripts in `misc/userscripts`.
- Support for Qt 5.5 and tox 2.0
Changed
~~~~~~~
- *Breaking change for userscripts:* `QUTE_HTML` and `QUTE_TEXT` for userscripts now don't store the contents directly, and instead contain a filename.
- The `content -> geolocation` and `notifications` settings now support a `true` value to always allow those. However, this is *not recommended*.
- New bindings `<Ctrl-R>` (rapid), `<Ctrl-F>` (foreground) and `<Ctrl-B>` (background) to switch hint modes while hinting.
- `<Ctrl-M>` and numpad-enter are now bound by default for bindings where `<Return>` was bound.
- `:hint tab` and `F` now respect the `background-tabs` setting. To enforce a foreground tab (what `F` did before), use `:hint tab-fg` or `;f`.
- `:scroll` now takes a direction argument (`up`/`down`/`left`/`right`/`top`/`bottom`/`page-up`/`page-down`) instead of two pixel arguments (`dx`/`dy`). The old form still works but is deprecated.
- The `ui -> user-stylesheet` setting now also takes file paths relative to the config directory.
- The `content -> cookies-accept` setting now has new `no-3rdparty` (default) and `no-unknown-3rdparty` values to block third-party cookies. The `default` value got renamed to `all`.
- Improved startup time by reading the webpage history while qutebrowser is open.
- The way `:spawn` splits its commandline has been changed slightly to allow commands with flags.
- The default for the `new-instance-open-target` setting has been changed to `tab`.
- Sessions now store zoom/scroll-position separately for each entry.
Deprecated
~~~~~~~~~~
- `:scroll` with two pixel-arguments is now deprecated - `:scroll-px` should be used instead.
Removed
~~~~~~~
- The `--no-crash-dialog` argument which was intended for debugging only was removed as it's replaced by `--no-err-windows` which suppresses all error windows.
- Support for Qt installations without SSL support was dropped.
Fixed
~~~~~
- Scrolling should now work more reliably on some pages where arrow keys worked but `hjkl` didn't.
- Small improvements when checking if an input is an URL or not.
- Fixed wrong cursor position when completing the first item in the completion.
- Fixed exception when using search engines with {foo} in their name.
- Fixed a bug where the same title was shown for all tabs on some systems.
- Don't install the scripts package when installing qutebrowser.
- Fixed searching for terms starting with a hyphen (e.g. `/-foo`)
- Proxy authentication credentials are now remembered between different tabs.
- Fixed updating of the tab title on pages without title.
- Fixed AssertionError when closing many windows quickly.
- Various fixes for deprecated key bindings and auto-migrations.
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug).
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
- Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...".
- Fixed exception when starting qutebrowser with `:set` as argument.
- Fixed horrible completion performance when the `shrink` option was set.
- Sessions now store zoom/scroll-position correctly.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1]
-----------------------------------------------------------------------
Fixed
~~~~~
- Added missing manpage (doc/qutebrowser.1.asciidoc) to archive.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.0[v0.2.0]
-----------------------------------------------------------------------
Added
~~~~~
- Session support
* new command `:session-load` to load a session.
* new command `:session-save` to save a session.
* new command `:session-delete` to delete a session.
* new setting `general -> save-session` to always save the session on quit.
* new setting `general -> session-default-name` to configure the session name to use if none is given.
* new argument `-r`/`--restore` to specify a session to load.
* new argument `-R`/`--override-restore` to not load a session even if one was saved.
- New commands to manage downloads:
* `:download` to download a URL or the current page.
* `:download-cancel` to cancel a download.
* `:download-delete` to delete a download from disk.
* `:download-open` to open a finished download.
* `:download-remove` to remove a download from the list. `:download-remove --all` or the new 'cd' keybinding can be used to clear all finished downloads.
- History completion
* New option `completion -> timestamp-format` to set the format used to display the history timestamps.
* New option `completion -> web-history-max-items` to configure how many history items to show in the completion.
* The option `completion -> history-length` for the command history got renamed to `cmd-history-max-items`.
- Better save logic for the config/state:
* Only save files if modified (e.g. don't overwrite the config if it was edited outside of qutebrowser and nothing was changed in qutebrowser).
* Save things (cookies, config, quickmarks, ...) periodically all 15 seconds (time can be changed with the `general -> auto-save-interval` option).
- Opera-like mouse rocker gestures
* New option `input -> rocker-gestures`. When turned on, the history can be navigated back/forward by holding a mouse button and pressing the other one.
- New `-f` option for `:reload` to reload and bypass the cache.
- Pass more information (`QUTE_MODE`, `QUTE_SELECTED_TEXT`, `QUTE_SELECTED_HTML`, `QUTE_USER_AGENT`, `QUTE_HTML`, `QUTE_TEXT`) to userscripts.
- New `--userscript` option to `:spawn` (which deprecates `:run-userscript`).
- Ability to toggle a value to `:set` by appending a `!` to the value.
- New options to hide the tab-/statusbar:
* `tabs -> hide-always` for the tabbar
* `ui -> hide-statusbar` for the statusbar
- New options to configure how the tab/window titles should look:
* `tabs -> title-format` for the tabbar
* `ui -> window-title-format` for the window title
- HTML5 Geolocation/Notification support:
* New option `content -> geolocation` to permanently turn the geolocation off.
* New option `content -> notifications` to permanently turn notifications off.
- New options to disable javascript prompts/alerts:
* `content -> ignore-javascript-prompt` to turn off prompts.
* `content -> ignore-javascript-alerts` to turn off alerts.
- Two new options to customize the behavior of hints:
* `hints -> min-chars` to set minimum number of chars in hints.
* `hints -> scatter` which when turned off distributes the hints sequentially (like dwb) instead of scattering their positions (like Vimium).
- Make it possible to use `:open -[twb]` without url.
* New option `general -> default-page` to set the page to be opened when doing that.
- New `input -> partial-timeout` option to clear partial keystrings.
- New option `completion -> download-path-suggestion` to configure what to show in the completion for downloads.
- Queue messages shown in unfocused windows and show them when the window is focused.
* New option `ui -> message-unfocused` to disable this behavior.
- New `--relaxed-config` argument which ignores unknown options.
- New `:tab-detach` command to open the current tab in a new window.
- Zooming via Ctrl-Mousewheel.
* New option `input -> mouse-zoom-divider` to control how much the page is zoomed when rotating the wheel.
- New option (`content -> host-blocking-enabled`) to enable/disable host blocking.
- New values `tab-bg`/`tab-bg-silent` for `new-instance-open-target` to open a background tab.
- New `ui -> downloads-position` setting to move the downloads to the bottom.
- New `ui -> hide-mouse-cursor` option to hide the mouse cursor inside qutebrowser.
- New argument `-s` for qutebrowser to set a temporary config option.
- New argument `-p` for the `:set` command to print the new value.
- New `--rapid` option to `:hint`. The `rapid`/`rapid-win` targets are now deprecated, and `--rapid` can be used as well with the targets run/hover/userscript/spawn as well.
- New `-f` argument to `:bind` to overwrite the old binding.
- New `--qt-name` argument to qutebrowser which is passed to Qt to set `WM_CLASS`.
- Alternating row colors in completion. This adds a new `colors -> completion.alternate-bg` option.
Changed
~~~~~~~
- Ignore quotes with maxsplit-commands (`:open`, `:quickmark-load`, etc.) and don't quote arguments for those commands in the completions. This also means some commands needed adjustments:
* Clear search when `:search` without arguments is given. (`:search ""` will now search for the literal text `""`)
* Add `-s`/`--space` argument to `:set-cmd-text` (as `:set-cmd-text "foo "` will now set the literal text `"foo "`)
- Ignore `;;` for splitting with some commands like `:bind`.
- Add unbound (new) default keybindings to config. This also adds a new `<unbound>` special command.
* To unbind a command keybinding without binding it to a new key, you now have to bind it to `<unbound>` or it'll be readded automatically.
- If an SSL error is raised multiple times with the same error/certificate/host/scheme/port, the user is only asked once.
- Jump to last instead of first item when pressing Shift-Tab the first time in the completion.
- Add a fullscreen keybinding.
- Add a `:search` command in addition to `/foo` so it's more visible and can be used from scripts.
- Various improvements to documentation, logging, and the crash reporter.
- Expand `~` to the users home directory with `:run-userscript`.
- Improve the userscript runner on Linux/OS X by using `QSocketNotifier`.
- Add luakit-like `gt`/`gT` keybindings to cycle through tabs.
- Show default value for config values in the completion.
- Clone tab icon, tab text and zoom level when cloning tabs.
- Don't open relative file paths with `:open`, only with commandline arguments.
- Expand environment variables in config settings which take a file path.
- Add a list of common user agents to the user agent setting completion.
- Move cursor to end of textboxes when hinting.
- Don't start searches on invalid URLs for quickmarks/startpage.
- Various performance improvements for the completion.
- Always open URLs given as argument in the foreground.
- Improve various error messages.
- Add `startpage`/`default-page` values to `tabs -> last-close`.
- Various improvements to `:restart` - it should be more robust now and uses sessions so all state (focused tab, scroll position, etc.) gets remembered.
- Add tab index display to the statusbar.
- Keep progress bar height fixed when the statusbar is multiline.
- Many improvements to tests and related infrastructure:
* `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead.
* The tests now use http://pytest.org/[pytest]
* Many new tests added
* Mac Mini buildbot to run the tests on OS X.
* Coverage recording via http://nedbatchelder.com/code/coverage/[coverage.py].
* New `--pdb-postmortem argument` to drop into the pdb debugger on exceptions.
* Use https://github.com/ionelmc/python-hunter[hunter] for line tracing instead of a selfmade solution.
Deprecated
~~~~~~~~~~
- The `:run-userscript` command - use `:spawn --userscript` instead.
- The `rapid` and `rapid-win` targets for `:hint` - use the `--rapid` argument to `:hint` instead.
- The `:cancel-download` command - use `:download-cancel` instead.
- The `:download-page` command - use `:download` instead.
Removed
~~~~~~~
- `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead..
Fixed
~~~~~
- Fix for cache never being used.
- Fixed handling of key release events (e.g. for javascript) when holding a key and pressing a second one.
- Fix handling of commands using `;;` at various places (key config, command parser, `:bind`)
- Fix splitting of flags with arguments (`:bind -m`/`--mode`).
- Fix bindings of special keys with lower-case modifiers (e.g. `<ctrl-x>`)
- Fix for weird search highlights when changing tabs while search is active.
- Fix starting with `-c ""`.
- Fix removing of partial downloads when a download is cancelled via context menu.
- Fix retrying of downloads which were started in a now closed tab.
- Highlight text case-insensitively in completion.
- Scroll completion to top when showing it.
- Handle unencodable file paths in config types correctly.
- Fix for crash when executing a delayed command (because of a shadowed keybinding) and then unfocusing the window.
- Fix for crash when hinting on a page which doesn't have an URL yet.
- Fix exception when using `:set-cmd-text` with an empty argument.
- Add a timeout to pastebin HTTP replies.
- Various other fixes for small/rare bugs.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.4[v0.1.4]
-----------------------------------------------------------------------
Changed
~~~~~~~
* The Windows builds come with Qt 5.4.1 which has some https://lists.schokokeks.org/pipermail/qutebrowser/2015-March/000054.html[related bugfixes].
* Improvements to CPU usage when idle.
* Ensure there's no size for `font-family` settings.
* Handle URLs with double-colon as search strings.
* Adjust prompt size hint based on content.
* Refactor websettings and save/restore defaults.
* Various small improvements to logging.
* Various improvements for hinting.
* Improve parsing of `faulthandler` logs.
Removed
~~~~~~~
* Remove default search engines.
* Remove debug console completing completely.
Fixed
~~~~~
* Ignore RuntimeError in `mouserelease_insertmode`.
* Hide Qt warning when aborting download reply.
* Hide "Error while shutting down tabs" message.
* Clear open target in `acceptNavigationRequest`.
* Fix handling of signals with deleted tabs.
* Restore `sys.std*` in `utils.fake_io` on exceptions.
* Allow font names with integers in them.
* Fix `QIODevice` warnings when closing tabs.
* Set the `QSettings` path to a config-subdirectory.
* Add workaround for adblock-message without window.
* Fix searching for terms starting with a slash.
* Ignore tab key presses if they'd switch focus.
Security
~~~~~~~~
* Stop the icon database from being created when private-browsing is set to true.
* Disable insecure SSL ciphers.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.3[v0.1.3]
-----------------------------------------------------------------------
Changed
~~~~~~~
* Various small logging improvements.
* Don't open relative files in `fuzzy_url` with `:open`
* Various crashdialog improvements.
* Hide adblocked iframes.
Fixed
~~~~~
* Handle shutdown of page with prompt correctly.
* fuzzy_url: handle invalid URLs with autosearch off
* Handle explicit searches with `auto-search=false`.
* Abort download override question on error/cancel.
* Set a higher z-index for hint labels.
* Close contextmenu when closing tab to avoid crash.
* Fix statusbar quickly popping up as window.
* Clean up `NetworkManager` after downloads finished.
* Fix restoring of cmd widget after an error.
* Fix retrying of downloads after the tab is closed.
* Fix `check_libraries()` output for Arch Linux.
* Handle all `IPCErrors` properly.
* Handle another `webelem.IsNullError` with hints.
* Handle `UnicodeDecodeError` when reading configs.
Security
~~~~~~~~
* Fix for HTTP passwords accidentally being written to debug log.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.2[v0.1.2]
-----------------------------------------------------------------------
Changed
~~~~~~~
* Uncheck sending of debug log by default when private browsing is on.
* Add SSL info to version info.
Removed
~~~~~~~
* Remove hosts-file.net from blocker default lists.
Fixed
~~~~~
* Fix rare exception when a key is pressed shorly after opening a window
* Fix exception with certain invalid URLs like `http:foo:0`
* Work around Qt bug which renders checkboxes on OS X unusable
* Fix exception when a local files can't be read in `:adblock-update`
* Hide 2 more Qt warnings.
* Add `!important` to hint CSS so websites don't override the hint look
* Make `init_venv.py` work with multiple sip `.so` files.
* Fix splitting with certain commands with an empty argument
* Fix uppercase hints.
* Fix segfaults if another page is loaded while a prompt is open
* Fix exception with invalid `ShellCommand` config values.
* Replace unencodable chars
* Fix user-stylesheet setting with an empty value.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.1[v0.1.1]
-----------------------------------------------------------------------
Added
~~~~~
* Set window icon and add a qutebrowser.ico file for Windows.
* Ask the user when downloading to an already existing file.
* Add a `network -> proxy-dns-requests` option.
* Add "Remove finished" to the download context menu
* Open and remove clicked downloads.
Changes
~~~~~~~
* Windows releases are now built with Qt 5.4 which brings many improvements and bugfixes.
* Add a troubleshooting section to the FAQ.
* Display IPC errors to the user.
* Rewrite keymode handling to use only one mode which also fixes various bugs.
* Save version to state config.
* Set zoom to default instead of 100% with `:zoom`/`=`.
* Adjust page zoom if default zoom changed.
* Force tabs to be focused on `:undo`.
* Replace manual installation instructions on OS X with homebrew/macports.
* Allow min-/maximizing of print preview on Windows.
* Various documentation improvements.
* Various other small improvements and cleanups.
Removed
~~~~~~~
* Clean up and temporarily disable alias completion.
Fixed
~~~~~
* Fix setting of `QWebSettings` (e.g. web fonts) with empty strings.
* Re-focus web view when leaving prompt/yesno mode.
* Handle `:restart` correctly with Python eggs.
* Handle an invalid cwd properly.
* Fix popping of a dead question in prompter.
* Fix `AttributeError` on config changes on Ubuntu.
* Don't treat things like "31c3" as IP address.
* Handle category being `None` in Qt message handler.
* Force-include pygments in `freeze.py`.
* Fix scroll percentage not updating on some pages like twitter.
* Encode `Content-Disposition` header name properly.
* Fix item sorting in `NeighborList`.
* Handle data being `None` in download read timer.
* Stop download read timer when reply has finished.
* Fix handling of small/big `fuzzyval`'s in `NeighborList`.
* Fix crashes when entering invalid values in `qute:settings`.
* Abort questions in `NetworkManager` when destroyed.
* Fix height calculation of download view.
* Always auto-remove adblock downloads when done.
* Ensure the docs get included in `freeze.py`.
* Fix crash with `:zoom`.
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1[v0.1]
-------------------------------------------------------------------
Initial release.

View File

@@ -1,5 +1,5 @@
qutebrowser HACKING
===================
Contributing to qutebrowser
===========================
The Compiler <mail@qutebrowser.org>
:icons:
:data-uri:
@@ -37,8 +37,6 @@ If you want to find something useful to do, check the
https://github.com/The-Compiler/qutebrowser/issues[issue tracker]. Some
pointers:
* https://github.com/The-Compiler/qutebrowser/milestones/v0.1[Open issues for
the v0.1 release]
* https://github.com/The-Compiler/qutebrowser/labels/easy[Issues which should
be easy to solve]
* https://github.com/The-Compiler/qutebrowser/labels/not%20code[Issues which
@@ -57,7 +55,7 @@ qutebrowser uses http://git-scm.com/[git] for its development. You can clone
the repo like this:
----
git clone git://the-compiler.org/qutebrowser
git clone https://github.com/The-Compiler/qutebrowser.git
----
If you don't know git, a http://git-scm.com/[git cheatsheet] might come in
@@ -68,8 +66,13 @@ contributing, feel free to send normal patches instead, e.g. generated via
Getting patches
~~~~~~~~~~~~~~~
After you finished your work and did `git commit`, you can get patches of your
changes like this:
The preferred way of submitting changes is to
https://help.github.com/articles/fork-a-repo/[fork the repository] and to
https://help.github.com/articles/creating-a-pull-request/[submit a pull
request].
If you prefer to send a patch to the mailinglist, you can generate a patch
based on your changes like this:
----
git format-patch origin/master <1>
@@ -83,32 +86,26 @@ Useful utilities
Checkers
~~~~~~~~
In the _scripts/_ subfolder, there is a `run_checks.py` script.
qutebrowser uses http://tox.readthedocs.org/en/latest/[tox] to run its
unittests and several linters/checkers.
It runs a bunch of static checks on all source files, using the following
checkers:
Currently, the following tools will be invoked when you run `tox`:
* Unit tests using the Python
https://docs.python.org/3.4/library/unittest.html[unittest] framework
* https://pypi.python.org/pypi/flake8/1.3.1[flake8]
* Unit tests using https://www.pytest.org[pytest].
* https://pypi.python.org/pypi/pyflakes[pyflakes] via https://pypi.python.org/pypi/pytest-flakes[pytest-flakes]
* https://pypi.python.org/pypi/pep8[pep8] via https://pypi.python.org/pypi/pytest-pep8[pytest-pep8]
* https://pypi.python.org/pypi/mccabe[mccabe] via https://pypi.python.org/pypi/pytest-mccabe[pytest-mccabe]
* https://github.com/GreenSteam/pep257/[pep257]
* http://pylint.org/[pylint]
* A custom checker for the following things:
* https://pypi.python.org/pypi/pyroma/[pyroma]
* https://github.com/mgedmin/check-manifest[check-manifest]
* `scripts/misc_checks.py` which checks for the following things:
- untracked git files
- VCS conflict markers
If you changed `setup.py` or `MANIFEST.in`, add the `--setup` argument to run
the following additional checkers:
* https://pypi.python.org/pypi/pyroma/0.9.3[pyroma]
* https://github.com/mgedmin/check-manifest[check-manifest]
It needs all the checkers to be installed and also needs
https://pypi.python.org/pypi/colorama/[colorama].
Please make sure this script runs without any warnings on your new
contributions. There's of course the possibility of false-positives, and the
following techniques are useful to handle these:
Please make sure the checks run without any warnings on your new contributions.
There's of course the possibility of false-positives, and the following
techniques are useful to handle these:
* Use `_foo` for unused parameters, with `foo` being a descriptive name. Using
`_` is discouraged.
@@ -156,7 +153,7 @@ Useful websites
Some resources which might be handy:
* http://qt-project.org/doc/qt-5/classes.html[The Qt5 reference]
* http://doc.qt.io/qt-5/classes.html[The Qt5 reference]
* https://docs.python.org/3/library/index.html[The Python reference]
* http://httpbin.org/[httpbin, a test service for HTTP requests/responses]
* http://requestb.in/[RequestBin, a service to inspect HTTP requests]
@@ -214,8 +211,7 @@ Other
Languages] (http://www.rfc-editor.org/errata_search.php?rfc=5646[Errata])
* http://www.w3.org/TR/CSS2/[Cascading Style Sheets Level 2 Revision 1 (CSS
2.1) Specification]
* http://qt-project.org/doc/qt-4.8/stylesheet-reference.html[Qt Style Sheets
Reference]
* http://doc.qt.io/qt-5/stylesheet-reference.html[Qt Style Sheets Reference]
* http://mimesniff.spec.whatwg.org/[MIME Sniffing Standard]
* http://spec.whatwg.org/[WHATWG specifications]
* http://www.w3.org/html/wg/drafts/html/master/Overview.html[HTML 5.1 Nightly]
@@ -241,9 +237,7 @@ There are some exceptions to that:
* `QThread` is used instead of Python threads because it provides signals and
slots.
* `QProcess` is used instead of Python's `subprocess` if certain actions (e.g.
cleanup) when the process finished are desired, as it provides signals for
that.
* `QProcess` is used instead of Python's `subprocess`
* `QUrl` is used instead of storing URLs as string, see the
<<handling-urls,handling URLs>> section for details.
@@ -282,7 +276,7 @@ There are currently these object registries, also called 'scopes':
`cookie-jar`, etc.)
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
etc.). Passing this scope to `objreg.get()` selects the object in the currently
focused tab by default. A tab can be explicitely selected by passing
focused tab by default. A tab can be explicitly selected by passing
+tab=_tab-id_, window=_win-id_+ to it.
A new object can be registered by using
@@ -298,8 +292,8 @@ All objects can be printed by starting with the `--debug` flag and using the
The registry is mainly used for <<commands,command handlers>> but also can be
useful in places where using Qt's
http://qt-project.org/doc/qt-5/signalsandslots.html[signals and slots]
mechanism would be difficult.
http://doc.qt.io/qt-5/signalsandslots.html[signals and slots] mechanism would
be difficult.
Logging
~~~~~~~
@@ -377,7 +371,7 @@ The types of the function arguments are inferred based on their default values,
e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in
qutebrowser's commandline.
This behaviour can be overridden using Python's
This behavior can be overridden using Python's
http://legacy.python.org/dev/peps/pep-3107/[function annotations]. The
annotation should always be a `dict`, like this:
@@ -401,13 +395,12 @@ then automatically checked. Possible 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).
* `name`: The long name to be used, as string (default: Name of the parameter).
* `special`: The string `count` or `win_id` if the parameter should be
auto-filled (with the count given by the user and the window ID the command was
executed in, respectively).
* `nargs`: Gets passed to argparse, see
https://docs.python.org/dev/library/argparse.html#nargs[its documentation].
The name of an argument will always be the parameter name, with any trailing
underscores stripped.
[[handling-urls]]
Handling URLs
~~~~~~~~~~~~~
@@ -436,6 +429,30 @@ displaying it to the user.
`QUrl` and take appropriate action if not. Note the URL of the current page
always could be an invalid QUrl (if nothing is loaded yet).
Running valgrind on QtWebKit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to run qutebrowser (and thus QtWebKit) with
http://valgrind.org/[valgrind], you'll need to pass `--smc-check=all` to it or
recompile QtWebKit with the Javascript JIT disabled.
This is needed so valgrind handles self-modifying code correctly:
[quote]
____
This option controls Valgrind's detection of self-modifying code. If no
checking is done, if a program executes some code, then overwrites it with new
code, and executes the new code, Valgrind will continue to execute the
translations it made for the old code. This will likely lead to incorrect
behavior and/or crashes.
...
Note that the default option will catch the vast majority of cases. The main
case it will not catch is programs such as JIT compilers that dynamically
generate code and subsequently overwrite part or all of it. Running with all
will slow Valgrind down noticeably.
____
Style conventions
-----------------
@@ -521,18 +538,15 @@ New Qt release
* Run all tests and check nothing is broken.
* Check the
https://bugreports.qt-project.org/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
https://bugreports.qt.io/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
and make sure all bugs marked as resolved are actually fixed.
* Update own PKGBUILDs based on upstream Archlinux updates.
* Build developer packages.
* Build non-developer symbol packages.
* Upload symbols patch to http://www.qutebrowser.org/qt-symbols.patch
* Upload symbols packages to http://www.qutebrowser.org/qt-symbols-pkg/
* Update own PKGBUILDs based on upstream Archlinux updates and rebuild.
* Update recommended Qt version in `README`
* Update OS X instructions in `README`
* Make sure Gentoo instructions are up to date.
* Grep for `WORKAROUND` in the code and test if fixed stuff works without the
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.
qutebrowser release
~~~~~~~~~~~~~~~~~~~
@@ -546,17 +560,15 @@ qutebrowser release
* Test an upgrade from the previous version (no manual intervention).
* Test an upgrade from the first version (no manual intervention).
* Create annotated git tag (`git tag -s "v0.1" -m "Release v0.1"`)
* Create git branch `v0.1.x`
* Push including `--tags`
* 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`
* `git push`; `git push "v0.X.Y"`
* Create release on github
* Mark the milestone at https://github.com/The-Compiler/qutebrowser/milestones
as closed.
* Create standalone Windows package (32/64bit) in Windows VM
* Upload to PyPI: `python setup.py register sdist upload --sign`
* Maybe upload to http://qt-apps.org/
* Upload to webpage with checksum/GPG (when/if it exists)
* Upload to qutebrowser.org with checksum/GPG
* Announce to qutebrowser mailinglist
* Maybe annouce at other places?

View File

@@ -4,18 +4,18 @@ The Compiler <mail@qutebrowser.org>
[qanda]
What is qutebrowser based on?::
qutebrowser uses http://www.python.org/[Python], http://qt-project.org/[Qt]
and http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
keybindings are similar to dwb.
key bindings are similar to dwb.
Why another browser?::
It might be hard to believe, but I didn't find any browser which I was
happy with, so I started to write my own. Also, I needed a project to get
into writing GUI applications with Python and
link:http://qt-project.org/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
Read the next few questions to find out why I was unhappy with existing
software.
@@ -32,12 +32,11 @@ API] seems to lack basic features like proxy support, and almost no projects
seem to have started porting to WebKit2 (I only know of
http://www.uzbl.org/[uzbl]).
+
qutebrowser uses http://qt-project.org/[Qt] and
http://qt-project.org/wiki/QtWebKit[QtWebKit] instead, which suffers from far
less such crashes. It might switch to
http://qt-project.org/wiki/QtWebEngine[QtWebEngine] in the future, which is
based on Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink]
rendering engine.
qutebrowser uses http://qt.io/[Qt] and http://wiki.qt.io/QtWebKit[QtWebKit]
instead, which suffers from far less such crashes. It might switch to
http://wiki.qt.io/QtWebEngine[QtWebEngine] in the future, which is based on
Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink] rendering
engine.
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
Firefox likes to break compatibility with addons on each upgrade, gets
@@ -54,10 +53,10 @@ What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.gith
Why Python?::
I enjoy writing Python since 2011, which made it one of the possible
choices. I wanted to use http://qt-project.org/[Qt] because of
http://qt-project.org/wiki/QtWebKit[QtWebKit] so I didn't have
http://qt-project.org/wiki/Category:LanguageBindings[many other choices]. I
don't like C++ and can't write it very well, so that wasn't an alternative.
choices. I wanted to use http://qt.io/[Qt] because of
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
like C++ and can't write it very well, so that wasn't an alternative.
But isn't Python too slow for a browser?::
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
@@ -75,6 +74,19 @@ Is there an adblocker?::
usage], so implementing it properly might take some time and won't be done
for v0.1 if at all.
How do I play Youtube videos with mpv?::
You can easily add a key binding to play youtube videos inside a real video
player - optionally even with hinting for links:
+
----
:bind x spawn mpv {url}
:bind ;x 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.
== Troubleshooting
Configuration not saved after modifying config.::
@@ -103,10 +115,17 @@ Experiencing segfaults (crashes) on Debian systems.::
Segfaults on Facebook, Medium, Amazon, ...::
If you are on a Debian or Ubuntu based system, you might experience some crashes
visting these sites. This is caused by a known bug in Qt which has been
visting these sites. This is caused by various bugs in Qt which have been
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
some packages. There is currently no easy way to manually upgrade to Qt
5.4 on those systems.
some packages. On Debian Jessie, it's recommended to use the experimental
repos as described in https://github.com/The-Compiler/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
+
Since Ubuntu Trusty (using Qt 5.2.1),
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
it's still
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
20 important bugs].
My issue is not listed.::
If you experience any segfaults or crashes, you can report the issue in

240
INSTALL.asciidoc Normal file
View File

@@ -0,0 +1,240 @@
Installing qutebrowser
======================
On Debian / Ubuntu
------------------
qutebrowser should run on these systems:
* Debian jessie or newer
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint)
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]!
Install the dependencies via apt-get:
[NOTE]
==========================
On Debian, it's recommended to install the Qt packages from the
https://wiki.debian.org/DebianExperimental[experimental] repository as those
are a much newer version of Qt which is more stable.
Add the following line to your `/etc/apt/sources.list`:
----
deb http://ftp.debian.org/debian experimental main
----
Then install the packages like this:
----
# apt-get update
# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-sip
# apt-get install python-tox
----
It's also recommended to pin those packages to receive updates by creating a
file `/etc/apt/preferences.d/qutebrowser` with the following contents:
----
Package: python3-pyqt5* libqt5*
Pin: release a=experimental
Pin-Priority: 800
----
==========================
For distributions other than Debian or if you prefer to not use the
experimental repo:
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-tox python3-sip
----
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# apt-get install asciidoc
$ python3 scripts/asciidoc2html.py
----
Then <<tox,install qutebrowser via tox>>.
On Fedora
---------
qutebrowser should run on Fedora 22.
Unfortunately there is no Fedora package yet, but installing qutebrowser is
still relatively easy! If you want to help packaging it for Fedora, please
mailto:mail@qutebrowser.org[get in touch]!
Install the dependencies via dnf:
----
# dnf update
# dnf install python3-qt5 python-tox python3-sip
----
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# dnf install asciidoc
$ python3 scripts/asciidoc2html.py
----
Then <<tox,install qutebrowser via tox>>.
On Archlinux
------------
There are two Archlinux packages available in the AUR:
https://aur.archlinux.org/packages/qutebrowser/[qutebrowser] and
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
You can install them (and the needed pypeg2 dependency) like this:
----
$ wget https://aur.archlinux.org/packages/py/python-pypeg2/python-pypeg2.tar.gz
$ tar xzf python-pypeg2.tar.gz
$ cd python-pypeg2
$ makepkg -si
$ cd ..
$ rm -r python-pypeg2 python-pypeg2.tar.gz
$ wget https://aur.archlinux.org/packages/qu/qutebrowser/qutebrowser.tar.gz
$ tar xzf qutebrowser.tar.gz
$ cd qutebrowser
$ makepkg -si
$ cd ..
$ rm -r qutebrowser qutebrowser.tar.gz
----
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
On Gentoo
---------
qutebrowser is available in the main repository and can be installed with:
----
# emerge -av qutebrowser
----
Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
On Void Linux
-------------
qutebrowser is available in the official repositories and can be installed
with:
----
# xbps-install qutebrowser
----
On NixOS
--------
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
it with:
----
$ nix-env -i qutebrowser
----
On Windows
----------
You can either use one of the
https://github.com/The-Compiler/qutebrowser/releases[prebuilt standalone
packages or MSI installers], or install manually:
* Use the installer from http://www.python.org/downloads[python.org] to get
Python 3 (be sure to install pip).
* Use the installer from
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
to get Qt and PyQt5.
* Install https://testrun.org/tox/latest/index.html[tox] via
https://pip.pypa.io/en/latest/[pip]:
----
$ pip install tox
----
Then <<tox,install qutebrowser via tox>>.
On OS X
-------
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.
----
$ brew install python3 pyqt5
$ pip3.4 install qutebrowser
----
if you are using Homebrew. For MacPorts, run:
----
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5
$ sudo pip3.4 install qutebrowser
----
The preferences for qutebrowser are stored in
`~/Library/Preferences/qutebrowser`, the application data is stored in
`~/Library/Application Support/qutebrowser`.
Packagers
---------
There are example .desktop and icon files provided. They would go in the
standard location for your distro (`/usr/share/applications` and
`/usr/share/pixmaps` for example).
The normal `setup.py install` doesn't install these files, so you'll have to do
it as part of the packaging process.
[[tox]]
Installing qutebrowser with tox
-------------------------------
Run tox like this to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
----
$ tox -e mkvenv
----
This installs all needed Python dependencies in a `.venv` subfolder. The
system-wide Qt5/PyQt5 installations are symlinked into the virtual environment.
You can then create a simple wrapper script to start qutebrowser somewhere in
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
----
Updating
~~~~~~~~
When you updated your local copy of the code (e.g. by pulling the git repo, or
extracting a new version), the virtualenv should automatically use the updated
code. However, if dependencies got added, this won't be reflected in the
virtualenv. Thus it's recommended to run the following command to recreate the
virtualenv:
----
$ tox -r -e mkvenv
----

View File

@@ -1,17 +1,34 @@
recursive-include qutebrowser/html *.html
recursive-include qutebrowser/test *.py
recursive-include icons *
include qutebrowser/test/testfile
include qutebrowser/git-commit-id
include COPYING doc/* README.asciidoc
include qutebrowser.desktop
global-exclude __pycache__ *.pyc *.pyo
exclude scripts/run_checks.py
exclude scripts/cleanup.py
exclude scripts/minimal_webkit_testbrowser.py
exclude scripts/run_profile.py
exclude scripts/generate_authors.sh
exclude .flake8
exclude .pylintrc
recursive-include qutebrowser *.py
recursive-include qutebrowser/html *.html
recursive-include qutebrowser/img *.svg
recursive-include qutebrowser/test *.py
recursive-include qutebrowser/javascript *.js
graft icons
graft doc/img
graft misc
graft scripts
include qutebrowser/utils/testfile
include qutebrowser/git-commit-id
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
include qutebrowser.desktop
include requirements.txt
include tox.ini
include qutebrowser.py
prune scripts/dev
exclude scripts/asciidoc2html.py
exclude doc/notes
prune pkg
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
prune tests
exclude pytest.ini
exclude qutebrowser.rcc
exclude .coveragerc
exclude .pylintrc
exclude .eslintrc
exclude doc/help
exclude .appveyor.yml
exclude .travis.yml
exclude misc/appveyor_install.py

View File

@@ -6,10 +6,16 @@
qutebrowser
===========
image:icons/qutebrowser-64x64.png[] _A keyboard-driven, vim-like browser based
on PyQt5 and QtWebKit._
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.*
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/The-Compiler/qutebrowser/blob/master/COPYING"]
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
image:https://img.shields.io/github/issues/The-Compiler/qutebrowser.svg?style=flat["issues badge",link="https://github.com/The-Compiler/qutebrowser/issues"]
image:https://requires.io/github/The-Compiler/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/The-Compiler/qutebrowser/requirements/?branch=master"]
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"]
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
@@ -17,10 +23,10 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
Screenshots
-----------
image:doc/img/main.png[width=300,link="doc/img/main.png"]
image:doc/img/downloads.png[width=300,link="doc/img/downloads.png"]
image:doc/img/completion.png[width=300,link="doc/img/completion.png"]
image:doc/img/hints.png[width=300,link="doc/img/hints.png"]
image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"]
image:doc/img/downloads.png["screenshot 2",width=300j,link="doc/img/downloads.png"]
image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"]
image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"]
Downloads
---------
@@ -29,7 +35,7 @@ See the https://github.com/The-Compiler/qutebrowser/releases[github releases
page] for available downloads (currently a source archive, and standalone
packages as well as MSI installers for Windows).
See link:doc/INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
qutebrowser running for various platforms.
Documentation
@@ -38,13 +44,15 @@ Documentation
In addition to the topics mentioned in this README, the following documents are
available:
* A http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* A http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* link:doc/quickstart.asciidoc[Quick start guide]
* link:doc/FAQ.asciidoc[Frequently asked questions]
* link:doc/HACKING.asciidoc[HACKING]
* link:doc/INSTALL.asciidoc[INSTALL]
* link:FAQ.asciidoc[Frequently asked questions]
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
* link:INSTALL.asciidoc[INSTALL]
* link:CHANGELOG.asciidoc[Change Log]
* link:doc/stacktrace.asciidoc[Reporting segfaults]
* link:doc/userscripts.asciidoc[How to write userscripts]
Getting help
------------
@@ -61,7 +69,8 @@ Contributions / Bugs
--------------------
You want to contribute to qutebrowser? Awesome! Please read
link:doc/HACKING.asciidoc[HACKING] for details and useful hints.
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
useful hints.
If you found a bug or have a feature request, you can report it in several
ways:
@@ -81,14 +90,15 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4
* http://qt-project.org/[Qt] 5.2.0 or newer (5.4 recommended)
* http://qt.io/[Qt] 5.2.0 or newer (5.5.0 recommended)
* QtWebKit
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.3.2 recommended) for Python 3
(5.5.0 recommended) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments]
* http://pyyaml.org/wiki/PyYAML[PyYAML]
To generate the documentation for the `:help` command, when using the git
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
@@ -99,8 +109,8 @@ console:
* https://pypi.python.org/pypi/colorlog/[colorlog]
* On Windows: https://pypi.python.org/pypi/colorama/[colorama]
See link:doc/INSTALL.asciidoc[INSTALL] for directions on how to install
qutebrowser and its dependencies.
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.
Donating
--------
@@ -125,27 +135,59 @@ Contributors, sorted by the number of commits in descending order:
// QUTE_AUTHORS_START
* Florian Bruhin
* Antoni Boucher
* Bruno Oliveira
* Martin Tournoij
* Alexander Cogneau
* Raphael Pierzina
* Joel Torstensson
* Claude
* Lamar Pavel
* Austin Anderson
* Artur Shaik
* ZDarian
* Peter Vilim
* John ShaggyTwoDope Jenkins
* Daniel
* Jimmy
* Zach-Button
* rikn00
* Brian Jackson
* Thorsten Wißmann
* Patric Schmitz
* Martin Zimmermann
* Error 800
* Brian Jackson
* sbinix
* jnphilipp
* Tobias Patzl
* Johannes Altmanninger
* Samir Benmendil
* Regina Hug
* Mathias Fussenegger
* Larry Hynes
* Johannes Altmanninger
* Joel Torstensson
* Regina Hug
* Peter Vilim
* Fritz V155 Reichwald
* Franz Fellner
* zwarag
* meles5
* error800
* Tim Harder
* Thiago Barroso Perrotta
* Matthias Lisin
* Helen Sherwood-Taylor
* HalosGhost
* Gregor Pohl
* Eivind Uggedal
* Arseniy Seroka
* Andreas Fischer
// QUTE_AUTHORS_END
The following people have contributed graphics:
* WOFall (icon)
* regines (keybinding cheatsheet)
* regines (key binding cheatsheet)
Thanks / Similiar projects
--------------------------
Thanks / Similar projects
-------------------------
Many projects with a similar goal as qutebrowser exist:
@@ -188,7 +230,7 @@ Also, thanks to:
* Everyone who had the patience to test qutebrowser before v0.1.
* Everyone triaging/fixing my bugs in the
https://bugreports.qt-project.org/secure/Dashboard.jspa[Qt bugtracker]
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker]
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow]
and in IRC.
* All the projects which were a great help while developing qutebrowser.

View File

@@ -1,142 +0,0 @@
Installing qutebrowser
======================
On Debian / Ubuntu
------------------
qutebrowser should run on these systems:
* Debian jessie or newer
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint)
Install the dependencies via apt-get:
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-virtualenv
----
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# apt-get install asciidoc
# python3 scripts/asciidoc2html.py
----
Then run the supplied script to run qutebrowser inside a
https://virtualenv.pypa.io/en/latest/virtualenv.html[virtualenv]:
----
# python3 scripts/init_venv.py
----
This installs all needed Python dependencies in a `.venv` subfolder. The
system-wide Qt5/PyQt5 installations are symlinked into the virtualenv.
You can then create a simple wrapper script to start qutebrowser somewhere in
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser
----
On Archlinux
------------
There are two Archlinux packages available in the AUR:
https://aur.archlinux.org/packages/qutebrowser/[qutebrowser] and
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
You can install them like this:
----
$ mkdir qutebrowser
$ cd qutebrowser
$ wget https://aur.archlinux.org/packages/qu/qutebrowser-git/PKGBUILD
$ makepkg -si
----
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
On Gentoo
---------
A dedicated overlay is available on
https://github.com/posativ/qutebrowser-overlay[GitHub]. To install it, add the
overlay with http://wiki.gentoo.org/wiki/Layman[layman]:
----
# wget https://raw.githubusercontent.com/posativ/qutebrowser-overlay/master/overlays.xml -O /etc/layman/overlays/qutebrowser.xml
# layman -a qutebrowser
----
Note, that Qt5 is available in the portage tree, but masked. You may need to do
a lot of keywording to install qutebrowser. Also make sure you have `python3_4`
in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system
(`emerge -uDNav @world`). Afterwards, you can install qutebrowser:
----
# emerge -av qutebrowser
----
On Windows
----------
You can either use one of the
https://github.com/The-Compiler/qutebrowser/releases[prebuilt standalone
packages or MSI installers], or install manually:
* Use the installer from http://www.python.org/downloads[python.org] to get
Python 3 (be sure to install pip).
* Use the installer from
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
to get Qt and PyQt5.
* Run `pip install virtualenv` or
http://www.lfd.uci.edu/~gohlke/pythonlibs/#virtualenv[the installer from here]
to install virtualenv.
Then run the supplied script to run qutebrowser inside a
https://virtualenv.pypa.io/en/latest/virtualenv.html[virtualenv]:
----
# python3 scripts/init_venv.py
----
This installs all needed Python dependencies in a `.venv` subfolder. The
system-wide Qt5/PyQt5 installations are used in the virtualenv.
On OS X
-------
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.
----
$ brew install python3 pyqt5
$ pip3.4 install qutebrowser
----
if you are using Homebrew. For MacPorts, run:
----
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5
$ sudo pip3.4 install qutebrowser
----
The preferences for qutebrowser are stored in
`~/Library/Preferences/qutebrowser`, the application data is stored in
`~/Library/Application Support/qutebrowser`.
Packagers
---------
There are example .desktop and icon files provided. They would go in the
standard location for your distro (`/usr/share/applications` and
`/usr/share/pixmaps` for example).
The normal `setup.py install` doesn't install these files, so you'll have to do
it as part of the packaging process.

View File

@@ -8,15 +8,22 @@
|<<adblock-update,adblock-update>>|Update the adblock block lists.
|<<back,back>>|Go back in the history of the current tab.
|<<bind,bind>>|Bind a key to a command.
|<<cancel-download,cancel-download>>|Cancel the first/[count]th download.
|<<bookmark-add,bookmark-add>>|Save the current page as a bookmark.
|<<bookmark-del,bookmark-del>>|Delete a bookmark.
|<<bookmark-load,bookmark-load>>|Load a bookmark.
|<<close,close>>|Close the current window.
|<<download,download>>|Download a given URL, given as string.
|<<download-page,download-page>>|Download the current page.
|<<download,download>>|Download a given URL, or current page if no URL given.
|<<download-cancel,download-cancel>>|Cancel the last/[count]th download.
|<<download-delete,download-delete>>|Delete the last/[count]th download from disk.
|<<download-open,download-open>>|Open the last/[count]th download.
|<<download-remove,download-remove>>|Remove the last/[count]th download from the list.
|<<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.
|<<home,home>>|Open main startpage in current tab.
|<<inspector,inspector>>|Toggle the web inspector.
|<<jseval,jseval>>|Evaluate a JavaScript string.
|<<later,later>>|Execute a command after some time.
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|<<open,open>>|Open a URL in the current/[count]th tab.
@@ -31,14 +38,18 @@
|<<repeat,repeat>>|Repeat a given command.
|<<report,report>>|Report a bug in qutebrowser.
|<<restart,restart>>|Restart qutebrowser while keeping existing tabs open.
|<<run-userscript,run-userscript>>|Run an userscript given as argument.
|<<save,save>>|Save the config file.
|<<save,save>>|Save configs and state.
|<<search,search>>|Search for a text on the current page. With no text, clear results.
|<<session-delete,session-delete>>|Delete a session.
|<<session-load,session-load>>|Load a session.
|<<session-save,session-save>>|Save a session.
|<<set,set>>|Set an option.
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|<<spawn,spawn>>|Spawn a command in a shell.
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|<<tab-close,tab-close>>|Close the current/[count]th tab.
|<<tab-detach,tab-detach>>|Detach the current tab to its own window.
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|<<tab-move,tab-move>>|Move the current tab.
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
@@ -47,7 +58,9 @@
|<<unbind,unbind>>|Unbind a keychain.
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|<<view-source,view-source>>|Show the source of the current page.
|<<wq,wq>>|Save open pages and quit.
|<<yank,yank>>|Yank the current URL/title to the clipboard or primary selection.
|<<yank-selected,yank-selected>>|Yank the selected text to the clipboard or primary selection.
|<<zoom,zoom>>|Set the zoom level for the current tab.
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
|<<zoom-out,zoom-out>>|Decrease the zoom level for the current tab.
@@ -72,7 +85,7 @@ How many pages to go back.
[[bind]]
=== bind
Syntax: +:bind [*--mode* 'MODE'] 'key' 'command'+
Syntax: +:bind [*--mode* 'MODE'] [*--force*] 'key' 'command'+
Bind a key to a command.
@@ -83,13 +96,46 @@ Bind a key to a command.
==== optional arguments
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
* +*-f*+, +*--force*+: Rebind the key if it is already bound.
[[cancel-download]]
=== cancel-download
Cancel the first/[count]th download.
==== 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.
==== count
The index of the download to cancel.
[[bookmark-add]]
=== bookmark-add
Save the current page as a bookmark.
[[bookmark-del]]
=== bookmark-del
Syntax: +:bookmark-del 'url'+
Delete a bookmark.
==== positional arguments
* +'url'+: The URL of the bookmark to delete.
==== 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
Syntax: +:bookmark-load [*--tab*] [*--bg*] [*--window*] 'url'+
Load a bookmark.
==== positional arguments
* +'url'+: The url of the bookmark to load.
==== optional arguments
* +*-t*+, +*--tab*+: Load the bookmark in a new tab.
* +*-b*+, +*--bg*+: Load the bookmark in a new background tab.
* +*-w*+, +*--window*+: Load the bookmark in a new window.
==== 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.
[[close]]
=== close
@@ -97,17 +143,46 @@ Close the current window.
[[download]]
=== download
Syntax: +:download 'url' ['dest']+
Syntax: +:download ['url'] ['dest']+
Download a given URL, given as string.
Download a given URL, or current page if no URL given.
==== positional arguments
* +'url'+: The URL to download
* +'dest'+: The file path to write the download to to ask.
* +'url'+: The URL to download. If not given, download the current page.
* +'dest'+: The file path to write the download to, or not given to ask.
[[download-page]]
=== download-page
Download the current page.
[[download-cancel]]
=== download-cancel
Cancel the last/[count]th download.
==== count
The index of the download to cancel.
[[download-delete]]
=== download-delete
Delete the last/[count]th download from disk.
==== count
The index of the download to cancel.
[[download-open]]
=== download-open
Open the last/[count]th download.
==== count
The index of the download to cancel.
[[download-remove]]
=== download-remove
Syntax: +:download-remove [*--all*]+
Remove the last/[count]th download from the list.
==== optional arguments
* +*-a*+, +*--all*+: If given removes all finished downloads.
==== count
The index of the download to cancel.
[[forward]]
=== forward
@@ -123,6 +198,10 @@ Go forward in the history of the current tab.
==== count
How many pages to go forward.
[[fullscreen]]
=== fullscreen
Toggle fullscreen mode.
[[help]]
=== help
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
@@ -143,7 +222,7 @@ Show help about a command or setting.
[[hint]]
=== hint
Syntax: +:hint ['group'] ['target'] ['args' ['args' ...]]+
Syntax: +:hint [*--rapid*] ['group'] ['target'] ['args' ['args' ...]]+
Start hinting.
@@ -159,7 +238,9 @@ Start hinting.
* +'target'+: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `tab`: Open the link in a new tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
- `tab-fg`: Open the link in a new foreground tab.
- `tab-bg`: Open the link in a new background tab.
- `window`: Open the link in a new window.
- `hover` : Hover over the link.
@@ -168,11 +249,8 @@ Start hinting.
- `run`: Run the argument as command.
- `fill`: Fill the commandline with the command given as
argument.
- `rapid`: Open the link in a new tab and stay in hinting mode.
- `rapid-win`: Open the link in a new window and stay in
hinting mode.
- `download`: Download the link.
- `userscript`: Call an userscript with `$QUTE_URL` set to the
- `userscript`: Call a userscript with `$QUTE_URL` set to the
link.
- `spawn`: Spawn a command.
@@ -190,6 +268,11 @@ Start hinting.
- With `run`: Same as `fill`.
==== optional arguments
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
[[home]]
=== home
Open main startpage in current tab.
@@ -198,6 +281,24 @@ Open main startpage in current tab.
=== inspector
Toggle the web inspector.
Note: Due a bug in Qt, the inspector will show incorrect request headers in the network tab.
[[jseval]]
=== jseval
Syntax: +:jseval [*--quiet*] 'js-code'+
Evaluate a JavaScript string.
==== positional arguments
* +'js-code'+: The string to evaluate.
==== optional arguments
* +*-q*+, +*--quiet*+: Don't show resulting JS object.
==== 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.
[[later]]
=== later
Syntax: +:later 'ms' 'command'+
@@ -208,6 +309,10 @@ Execute a command after some time.
* +'ms'+: How many milliseconds to wait.
* +'command'+: The command to run, with optional args.
==== 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.
[[navigate]]
=== navigate
Syntax: +:navigate [*--tab*] [*--bg*] [*--window*] 'where'+
@@ -235,7 +340,7 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
[[open]]
=== open
Syntax: +:open [*--bg*] [*--tab*] [*--window*] 'url'+
Syntax: +:open [*--bg*] [*--tab*] [*--window*] ['url']+
Open a URL in the current/[count]th tab.
@@ -250,6 +355,10 @@ Open a URL in the current/[count]th tab.
==== count
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
Syntax: +:paste [*--sel*] [*--tab*] [*--bg*] [*--window*]+
@@ -293,6 +402,10 @@ Delete a quickmark.
==== positional arguments
* +'name'+: The name of the quickmark to delete.
==== 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
Syntax: +:quickmark-load [*--tab*] [*--bg*] [*--window*] 'name'+
@@ -307,6 +420,10 @@ Load a quickmark.
* +*-b*+, +*--bg*+: Load the quickmark in a new background tab.
* +*-w*+, +*--window*+: Load the quickmark in a new window.
==== 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
Save the current page as a quickmark.
@@ -317,8 +434,13 @@ Quit qutebrowser.
[[reload]]
=== reload
Syntax: +:reload [*--force*]+
Reload the current/[count]th tab.
==== optional arguments
* +*-f*+, +*--force*+: Bypass the page cache.
==== count
The tab index to reload.
@@ -332,6 +454,10 @@ Repeat a given command.
* +'times'+: How many times to repeat.
* +'command'+: The command to run, with optional args.
==== 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.
[[report]]
=== report
Report a bug in qutebrowser.
@@ -340,27 +466,82 @@ Report a bug in qutebrowser.
=== restart
Restart qutebrowser while keeping existing tabs open.
[[run-userscript]]
=== run-userscript
Syntax: +:run-userscript 'cmd' ['args' ['args' ...]]+
Run an userscript given as argument.
==== positional arguments
* +'cmd'+: The userscript to run.
* +'args'+: Arguments to pass to the userscript.
[[save]]
=== save
Save the config file.
Syntax: +:save ['what' ['what' ...]]+
Save configs and state.
==== positional arguments
* +'what'+: What to save (`config`/`key-config`/`cookies`/...). If not given, everything is saved.
[[search]]
=== search
Syntax: +:search [*--reverse*] ['text']+
Search for a text on the current page. With no text, clear results.
==== positional arguments
* +'text'+: The text to search for.
==== optional arguments
* +*-r*+, +*--reverse*+: Reverse search direction.
==== 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
Syntax: +:session-delete [*--force*] 'name'+
Delete a session.
==== positional arguments
* +'name'+: The name of the session.
==== optional arguments
* +*-f*+, +*--force*+: Force deleting internal sessions (starting with an underline).
[[session-load]]
=== session-load
Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] 'name'+
Load a session.
==== positional arguments
* +'name'+: The name of the session.
==== optional arguments
* +*-c*+, +*--clear*+: Close all existing windows.
* +*-t*+, +*--temp*+: Don't set the current session for :session-save.
* +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline).
[[session-save]]
=== session-save
Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] ['name']+
Save a session.
==== positional arguments
* +'name'+: The name of the session. If not given, the session configured in general -> session-default-name is saved.
==== optional arguments
* +*-c*+, +*--current*+: Save the current session instead of the default.
* +*-q*+, +*--quiet*+: Don't show confirmation message.
* +*-f*+, +*--force*+: Force saving internal sessions (starting with an underline).
[[set]]
=== set
Syntax: +:set [*--temp*] ['section'] ['option'] ['value']+
Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['value']+
Set an option.
If the option name ends with '?', the value of the option is shown instead.
If the option name ends with '?', the value of the option is shown instead. If the option name ends with '!' and it is a boolean value, toggle it.
==== positional arguments
* +'section'+: The section where the option is in.
@@ -369,26 +550,43 @@ If the option name ends with '?', the value of the option is shown instead.
==== optional arguments
* +*-t*+, +*--temp*+: Set value temporarily.
* +*-p*+, +*--print*+: Print the value after setting.
[[set-cmd-text]]
=== set-cmd-text
Syntax: +:set-cmd-text 'text'+
Syntax: +:set-cmd-text [*--space*] 'text'+
Preset the statusbar to some text.
==== positional arguments
* +'text'+: The commandline to set.
==== optional arguments
* +*-s*+, +*--space*+: If given, a space is added to the end.
==== 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.
[[spawn]]
=== spawn
Syntax: +:spawn 'args' ['args' ...]+
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.
==== positional arguments
* +'args'+: The commandline to execute.
* +'cmdline'+: The commandline to execute.
==== optional arguments
* +*-u*+, +*--userscript*+: Run the command as a userscript.
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
==== 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
@@ -416,18 +614,24 @@ Close the current/[count]th tab.
==== optional arguments
* +*-l*+, +*--left*+: Force selecting the tab to the left of the current tab.
* +*-r*+, +*--right*+: Force selecting the tab to the right of the current tab.
* +*-o*+, +*--opposite*+: Force selecting the tab in the oppsite direction of what's configured in 'tabs->select-on-remove'.
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
==== count
The tab index to close
[[tab-detach]]
=== tab-detach
Detach the current tab to its own window.
[[tab-focus]]
=== tab-focus
Syntax: +:tab-focus ['index']+
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.
@@ -492,15 +696,35 @@ Re-open a closed tab (optionally skipping [count] closed tabs).
=== view-source
Show the source of the current page.
[[wq]]
=== wq
Syntax: +:wq ['name']+
Save open pages and quit.
==== positional arguments
* +'name'+: The name of the session.
[[yank]]
=== yank
Syntax: +:yank [*--title*] [*--sel*]+
Syntax: +:yank [*--title*] [*--sel*] [*--domain*]+
Yank the current URL/title to the clipboard or primary selection.
==== optional arguments
* +*-t*+, +*--title*+: Yank the title instead of the URL.
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number.
[[yank-selected]]
=== yank-selected
Syntax: +:yank-selected [*--sel*] [*--keep*]+
Yank the selected text to the clipboard or primary selection.
==== optional arguments
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
* +*-k*+, +*--keep*+: If given, stay in visual mode after yanking.
[[zoom]]
=== zoom
@@ -536,14 +760,36 @@ How many steps to zoom out.
[options="header",width="75%",cols="25%,75%"]
|==============
|Command|Description
|<<clear-keychain,clear-keychain>>|Clear the currently entered key chain.
|<<command-accept,command-accept>>|Execute the command currently in the commandline.
|<<command-history-next,command-history-next>>|Go forward in the commandline history.
|<<command-history-prev,command-history-prev>>|Go back in the commandline history.
|<<completion-item-del,completion-item-del>>|Delete the current completion item.
|<<completion-item-next,completion-item-next>>|Select the next completion item.
|<<completion-item-prev,completion-item-prev>>|Select the previous completion item.
|<<drop-selection,drop-selection>>|Drop selection and keep selection mode enabled.
|<<enter-mode,enter-mode>>|Enter a key mode.
|<<follow-hint,follow-hint>>|Follow the currently selected hint.
|<<follow-selected,follow-selected>>|Follow the selected text.
|<<leave-mode,leave-mode>>|Leave the mode we're currently in.
|<<message-error,message-error>>|Show an error message in the statusbar.
|<<message-info,message-info>>|Show an info message in the statusbar.
|<<message-warning,message-warning>>|Show a warning message in the statusbar.
|<<move-to-end-of-document,move-to-end-of-document>>|Move the cursor or selection to the end of the document.
|<<move-to-end-of-line,move-to-end-of-line>>|Move the cursor or selection to the end of line.
|<<move-to-end-of-next-block,move-to-end-of-next-block>>|Move the cursor or selection to the end of next block.
|<<move-to-end-of-prev-block,move-to-end-of-prev-block>>|Move the cursor or selection to the end of previous block.
|<<move-to-end-of-word,move-to-end-of-word>>|Move the cursor or selection to the end of the word.
|<<move-to-next-char,move-to-next-char>>|Move the cursor or selection to the next char.
|<<move-to-next-line,move-to-next-line>>|Move the cursor or selection to the next line.
|<<move-to-next-word,move-to-next-word>>|Move the cursor or selection to the next word.
|<<move-to-prev-char,move-to-prev-char>>|Move the cursor or selection to the previous char.
|<<move-to-prev-line,move-to-prev-line>>|Move the cursor or selection to the prev line.
|<<move-to-prev-word,move-to-prev-word>>|Move the cursor or selection to the previous word.
|<<move-to-start-of-document,move-to-start-of-document>>|Move the cursor or selection to the start of the document.
|<<move-to-start-of-line,move-to-start-of-line>>|Move the cursor or selection to the start of the line.
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-no,prompt-no>>|Answer no to a yes/no prompt.
@@ -561,12 +807,18 @@ How many steps to zoom out.
|<<rl-unix-line-discard,rl-unix-line-discard>>|Remove chars backward from the cursor to the beginning of the line.
|<<rl-unix-word-rubout,rl-unix-word-rubout>>|Remove chars from the cursor to the beginning of the word.
|<<rl-yank,rl-yank>>|Paste the most recently deleted text.
|<<scroll,scroll>>|Scroll the current tab by 'count * dx/dy'.
|<<scroll,scroll>>|Scroll the current tab in the given direction.
|<<scroll-page,scroll-page>>|Scroll the frame page-wise.
|<<scroll-perc,scroll-perc>>|Scroll to a specific percentage of the page.
|<<scroll-px,scroll-px>>|Scroll the current tab by 'count * dx/dy' pixels.
|<<search-next,search-next>>|Continue the search to the ([count]th) next term.
|<<search-prev,search-prev>>|Continue the search to the ([count]th) previous term.
|<<toggle-selection,toggle-selection>>|Toggle caret selection mode.
|==============
[[clear-keychain]]
=== clear-keychain
Clear the currently entered key chain.
[[command-accept]]
=== command-accept
Execute the command currently in the commandline.
@@ -579,6 +831,10 @@ Go forward in the commandline history.
=== command-history-prev
Go back in the commandline history.
[[completion-item-del]]
=== completion-item-del
Delete the current completion item.
[[completion-item-next]]
=== completion-item-next
Select the next completion item.
@@ -587,6 +843,10 @@ Select the next completion item.
=== completion-item-prev
Select the previous completion item.
[[drop-selection]]
=== drop-selection
Drop selection and keep selection mode enabled.
[[enter-mode]]
=== enter-mode
Syntax: +:enter-mode 'mode'+
@@ -600,10 +860,139 @@ Enter a key mode.
=== follow-hint
Follow the currently selected hint.
[[follow-selected]]
=== follow-selected
Syntax: +:follow-selected [*--tab*]+
Follow the selected text.
==== optional arguments
* +*-t*+, +*--tab*+: Load the selected link in a new tab.
[[leave-mode]]
=== leave-mode
Leave the mode we're currently in.
[[message-error]]
=== message-error
Syntax: +:message-error 'text'+
Show an error message in the statusbar.
==== positional arguments
* +'text'+: The text to show.
[[message-info]]
=== message-info
Syntax: +:message-info 'text'+
Show an info message in the statusbar.
==== positional arguments
* +'text'+: The text to show.
[[message-warning]]
=== message-warning
Syntax: +:message-warning 'text'+
Show a warning message in the statusbar.
==== positional arguments
* +'text'+: The text to show.
[[move-to-end-of-document]]
=== move-to-end-of-document
Move the cursor or selection to the end of the document.
[[move-to-end-of-line]]
=== move-to-end-of-line
Move the cursor or selection to the end of line.
[[move-to-end-of-next-block]]
=== move-to-end-of-next-block
Move the cursor or selection to the end of next block.
==== count
How many blocks to move.
[[move-to-end-of-prev-block]]
=== move-to-end-of-prev-block
Move the cursor or selection to the end of previous block.
==== count
How many blocks to move.
[[move-to-end-of-word]]
=== move-to-end-of-word
Move the cursor or selection to the end of the word.
==== count
How many words to move.
[[move-to-next-char]]
=== move-to-next-char
Move the cursor or selection to the next char.
==== count
How many lines to move.
[[move-to-next-line]]
=== move-to-next-line
Move the cursor or selection to the next line.
==== count
How many lines to move.
[[move-to-next-word]]
=== move-to-next-word
Move the cursor or selection to the next word.
==== count
How many words to move.
[[move-to-prev-char]]
=== move-to-prev-char
Move the cursor or selection to the previous char.
==== count
How many chars to move.
[[move-to-prev-line]]
=== move-to-prev-line
Move the cursor or selection to the prev line.
==== count
How many lines to move.
[[move-to-prev-word]]
=== move-to-prev-word
Move the cursor or selection to the previous word.
==== count
How many words to move.
[[move-to-start-of-document]]
=== move-to-start-of-document
Move the cursor or selection to the start of the document.
[[move-to-start-of-line]]
=== move-to-start-of-line
Move the cursor or selection to the start of the line.
[[move-to-start-of-next-block]]
=== move-to-start-of-next-block
Move the cursor or selection to the start of next block.
==== count
How many blocks to move.
[[move-to-start-of-prev-block]]
=== move-to-start-of-prev-block
Move the cursor or selection to the start of previous block.
==== count
How many blocks to move.
[[open-editor]]
=== open-editor
Open an external editor with the currently selected form field.
@@ -702,20 +1091,20 @@ This acts like readline's yank.
[[scroll]]
=== scroll
Syntax: +:scroll 'dx' 'dy'+
Syntax: +:scroll 'direction' ['dy']+
Scroll the current tab by 'count * dx/dy'.
Scroll the current tab in the given direction.
==== positional arguments
* +'dx'+: How much to scroll in x-direction.
* +'dy'+: How much to scroll in x-direction.
* +'direction'+: In which direction to scroll (up/down/left/right/top/bottom).
==== count
multiplier
[[scroll-page]]
=== scroll-page
Syntax: +:scroll-page 'x' 'y'+
Syntax: +:scroll-page [*--top-navigate* 'ACTION'] [*--bottom-navigate* 'ACTION'] 'x' 'y'+
Scroll the frame page-wise.
@@ -723,6 +1112,12 @@ Scroll the frame page-wise.
* +'x'+: How many pages to scroll to the right.
* +'y'+: How many pages to scroll down.
==== optional arguments
* +*-t*+, +*--top-navigate*+: :navigate action (prev, decrement) to run when scrolling up at the top of the page.
* +*-b*+, +*--bottom-navigate*+: :navigate action (next, increment) to run when scrolling down at the bottom of the page.
==== count
multiplier
@@ -743,6 +1138,19 @@ The percentage can be given either as argument or as count. If no percentage is
==== count
Percentage to scroll.
[[scroll-px]]
=== scroll-px
Syntax: +:scroll-px 'dx' 'dy'+
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.
==== count
multiplier
[[search-next]]
=== search-next
Continue the search to the ([count]th) next term.
@@ -757,6 +1165,10 @@ Continue the search to the ([count]th) previous term.
==== count
How many elements to ignore.
[[toggle-selection]]
=== toggle-selection
Toggle caret selection mode.
== Debugging commands
These commands are mainly intended for debugging. They are hidden if qutebrowser was started without the `--debug`-flag.
@@ -769,7 +1181,9 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|<<debug-cache-stats,debug-cache-stats>>|Print LRU cache stats.
|<<debug-console,debug-console>>|Show the debugging console.
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a webpage.
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|<<debug-trace,debug-trace>>|Trace executed code via hunter.
|<<debug-webaction,debug-webaction>>|Execute a webaction.
|==============
[[debug-all-objects]]
=== debug-all-objects
@@ -796,8 +1210,39 @@ Crash for debugging purposes.
=== debug-pyeval
Syntax: +:debug-pyeval 's'+
Evaluate a python string and display the results as a webpage.
Evaluate a python string and display the results as a web page.
==== positional arguments
* +'s'+: The string to evaluate.
==== 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.
[[debug-trace]]
=== debug-trace
Syntax: +:debug-trace ['expr']+
Trace executed code via hunter.
==== positional arguments
* +'expr'+: What to trace, passed to hunter.
==== 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.
[[debug-webaction]]
=== debug-webaction
Syntax: +:debug-webaction 'action'+
Execute a webaction.
See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the available actions.
==== positional arguments
* +'action'+: The action to execute, e.g. MoveToNextChar.
==== count
How many times to repeat the action.

View File

@@ -8,8 +8,10 @@ The following help pages are currently available:
* link:quickstart.html[Quick start guide]
* link:FAQ.html[Frequently asked questions]
* link:CHANGELOG.html[Change Log]
* link:commands.html[Documentation of commands]
* link:settings.html[Documentation of settings]
* link:userscripts.html[How to write userscripts]
Getting help
------------

File diff suppressed because it is too large Load Diff

114
doc/notes
View File

@@ -80,3 +80,117 @@ some cases passing the url to some cli program).
I've also noticed the lack of completion. For example, on "o" pentadactyl will
show sites (e.g. from history) that can be completed. I think I've been spoiled
by pentadactyl having completion for just about everything.
suckless surf ML post
=====================
From: Ben Woolley <tautolog_AT_gmail.com>
Date: Wed, 7 Jan 2015 18:29:25 -0800
Hi all,
This patch is a bit of a beast for surf. It is intended to be applied after
the disk cache patch. It breaks some internal interfaces, so it could
conflict with other patches.
I have been wanting a browser to implement a complete same-origin policy,
and have been investigating how to do this in various browsers for many
months. When I saw how surf opened new windows in a separate process, and
was so simple, I knew I could do it quickly. Over the last two weeks, I
have been developing this implementation on surf.
The basic idea is to prevent browser-based tracking as you browse from site
to site, or origin to origin. By "origin" domain, I mean the "first-party"
domain, the domain normally in the location bar (of the typical browser
interface). Each origin domain effectively gets its own browser profile,
and a browser process only ever deals with one origin domain at a time.
This isolates origins vertically, preventing cookies, disk cache, memory
cache, and window.name vulnerabilities. Basically, all known
vulnerabilities that google and Mozilla cite as counter-examples when they
explain why they haven't disabled third-party cookies yet.
When you are on msnbc.com, the tracking pixels will be stored in a cookie
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
stored in a cookie file for cnn.com. You will not be tracked between them.
However, third-party cookies, and the caching of third party resources will
still work, but they will be isolated between origin domains. Instead of
blocking cookies and cache entries, they are "double-keyed", or *also*
keyed by origin.
There is a unidirectional communication channel, however, from one origin
to the next, through navigation from one origin to the next. That is, the
query string is passed from one origin to the next, and may embed
identifiers. One example is an affiliate link that identifies where the
lead came from. I have implemented what I call "horizontal isolation", in
the form of an "Origin Crossing Gate".
Whenever you follow a link to a new domain, or even are just redirected to
a new domain, a new window/tab is opened, and passed the referring origin
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
page that is loaded in the new origin's context. That page tells you the
origin you were on, the new origin, and the full link, and you can decide
to go just to the new origin, or go to the full URL, after reviewing it for
tracking data.
Also, you may click links that store your trust of that relationship with
various expiration times, the same way you would trust geolocation requests
for a particular origin for a period of time. The database used is actually
the new origin's cookie file. Since the origin prompt is loaded in the new
origin's context, I can set a cookie on behalf of the new origin. The
expiration time of the trust is the expiration time of the cookie. The
cookie implementation in webkit automatically expires the trust as part of
how cookies work. Each time you cross an origin, the origin crossing page
checks the cookie to see if trust is still established. If so, it will use
window.location.replace() to continue on automatically. The initial page
renders blank until the trust is invalidated, in which case the content of
the gate is made visible.
However, the new origin is technically able to mess with those cookies, so
a website could set trust for an origin crossing. I have addressed that by
hashing the key with a salt, and setting the real expiration time as the
value, along with an HMAC to verify the contents of the value. If the
cookie is messed with in any way, the trust will be disabled, and the
prompt will appear again. So it has a fail-safe function.
I know it seems a bit convoluted, but it just started out as a nice little
rabbit hole, and I just wanted to get something workable. At first I
thought using the cookie expiration time was convenient, but then when I
realized that I needed to protect the cookie, things got a bit hairy. But
it works.
Each profile is, by default, stored in ~/.surf/origins/$origin/
The interesting side effect is that if there is a problem where a website
relies on the cross-site cookie vulnerability to make a connection, you can
simply make a symbolic link from one origin folder to another, and they
will share the same profile. And if you want to delete cookies and/or cache
for a particular origin, you just rm -rf the origin's profile folder, and
don't have to interfere with your other sites that are working just fine.
One thing I don't handle are cross-origins POSTs. They just end up as GET
requests right now. I intend to do something about that, but I haven't
figured that out yet.
I have only been using this functionality for a few days myself, so I have
absolutely no feedback yet. I wanted to provide the first implementation of
the management of identity as a system resource the same way that things
like geolocation, camera, and microphone resources are managed in browsers
and mobile apps.
Currently, Mozilla and Tor have are working on third-party tracking issues
in Firefox.
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
Up to this point, Tor has provided a patch that double-keys cookies with
the origin domain, but no other progress is visible. I have seen no
discussion of how horizontal isolation is supposed to happen, and I wanted
to show people that it can be done, and this is one way it can be done, and
to compel the other browser makers to catch up, and hopefully the community
can work toward a standard *without* the tracking loopholes, by showing
people what a *complete* solution looks like.
Thank you,
Ben Woolley
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch

View File

@@ -8,9 +8,10 @@ time, use the `:help` command.
What to do now
--------------
* View the http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]
to make yourself familiar with the keybindings: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* View the http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
to make yourself familiar with the key bindings: +
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* Run `:adblock-update` to download adblock lists and activate adblocking.
* If you just cloned the repository, you'll need to run
`scripts/asciidoc2html.py` to generate the documentation.
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.

View File

@@ -16,11 +16,15 @@ qutebrowser - a keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.
*qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']]
== DESCRIPTION
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
Note the commands and settings of qutebrowser are not described in this
manpage, but in the help integrated in qutebrowser - use the ":help" command to
show it.
== OPTIONS
// QUTE_OPTIONS_START
=== positional arguments
@@ -35,11 +39,29 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
show this help message and exit
*-c* 'CONFDIR', *--confdir* 'CONFDIR'::
Set config directory (empty for no config storage)
Set config directory (empty for no config storage).
*--datadir* 'DATADIR'::
Set data directory (empty for no data storage).
*--cachedir* 'CACHEDIR'::
Set cache directory (empty for no cache storage).
*--basedir* 'BASEDIR'::
Base directory for all storage. Other --*dir arguments are ignored if this is given.
*-V*, *--version*::
Show version and quit.
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
Set a temporary setting for this session.
*-r* 'SESSION', *--restore* 'SESSION'::
Restore a named session.
*-R*, *--override-restore*::
Don't restore a session even if one would be restored.
=== debug arguments
*-l* 'LOGLEVEL', *--loglevel* 'LOGLEVEL'::
Set loglevel
@@ -59,14 +81,26 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
*--harfbuzz* '{old,new,system,auto}'::
HarfBuzz engine version to use. Default: auto.
*--relaxed-config*::
Silently remove unknown config options.
*--nowindow*::
Don't show the main window.
*--debug-exit*::
Turn on debugging of late exit.
*--no-crash-dialog*::
Don't show a crash dialog.
*--pdb-postmortem*::
Drop into pdb on exceptions.
*--temp-basedir*::
Use a temporary basedir.
*--no-err-windows*::
Don't show any error windows (used for tests/smoke.py).
*--qt-name* 'NAME'::
Set the window name.
*--qt-style* 'STYLE'::
Set the Qt GUI style to use.
@@ -88,8 +122,14 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
- '~/.config/qutebrowser/qutebrowser.conf': Main config file.
- '~/.config/qutebrowser/quickmarks': Saved quickmarks.
- '~/.config/qutebrowser/keys.conf': Defined keybindings.
- '~/.local/share/qutebrowser/': Various state information
- '~/.config/qutebrowser/keys.conf': Defined key bindings.
- '~/.local/share/qutebrowser/': Various state information.
- '~/.cache/qutebrowser/': Temporary data.
Note qutebrowser conforms to the XDG basedir specification - if
'XDG_CONFIG_HOME', 'XDG_DATA_HOME' or 'XDG_CACHE_HOME' are set in the
environment, the directories configured there are used instead of the above
defaults.
== BUGS
Bugs are tracked in the Github issue tracker at

View File

@@ -1,5 +1,6 @@
Getting stacktraces on crashes
==============================
:toc:
The Compiler <mail@qutebrowser.org>
When there is a fatal crash in qutebrowser - most of the times a
@@ -14,10 +15,17 @@ https://en.wikipedia.org/wiki/Debug_symbol[debugging symbols] is required.
The rest of this guide is quite Linux specific, though there is a
<<windows,section for Windows>> at the end.
Getting debugging symbols
-------------------------
Crashes which can be reproduced
-------------------------------
.Debian/Ubuntu/...
If a crash can be reproduced, packages with debugging symbols should be
installed, and the crash should be reproduced under gdb.
Getting debugging symbols
~~~~~~~~~~~~~~~~~~~~~~~~~
Debian/Ubuntu/...
^^^^^^^^^^^^^^^^^
For Debian based systems (Debian, Ubuntu, Linux Mint, ...), debug information
is available in the repositories:
@@ -26,76 +34,66 @@ is available in the repositories:
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
----
.Archlinux
Archlinux
^^^^^^^^^
For Archlinux, no debug informations are provided. You can either compile Qt
yourself (which will take a few hours even on a modern machine) or use
debugging symbols compiled by me (x86_64 only).
debugging symbols compiled/packaged by me (x86_64 only).
To compile by yourself:
.To compile by yourself
----
$ git clone https://github.com/The-Compiler/qt-debug-pkgbuild.git
$ cd qt-debug-pkgbuild
$ git checkout symbols
$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
$ cd qt5
$ makepkg -si
$ makepkg -si --pkg qt5-base-debug,qt5-webkit-debug
$ cd ../pyqt5
$ makepkg -si
$ makepkg -si --pkg pyqt5-common-debug,python-pyqt5-debug
----
To install my pre-built packages:
.To install my pre-built packages
First download and sign the key:
----
$ mkdir qt-debug
$ cd qt-debug
$ wget -r -l1 -A '*.tar.xz' -L -np -nd http://www.qutebrowser.org/qt-symbols-pkg/
# pacman -U *.pkg.tar.xz
# pacman-key -r 0xD6A1C70FE80A0C82
$ pacman-key -f 0xD6A1C70FE80A0C82
Key fingerprint = 14AF EC28 70C6 4863 C5C7 ACCB D6A1 C70F E80A 0C82
# pacman-key --lsign-key 0xD6A1C70FE80A0C82
----
After you are done debugging, make sure to install the system packages again so
you get updates. This can be done with this command:
Then edit your `/etc/pacman.conf` to add the repository to the bottom:
----
# pacman -S qt5
[qt-debug]
Server = http://qutebrowser.org/qt-debug/$arch
----
Getting a core dump
-------------------
The next step is finding the core dump so we can get a stacktrace from it.
First of all, try to reproduce your problem. If you can, run qutebrowser
directly inside gdb like this:
Then install the packages:
----
$ gdb $(which python3) -ex 'run -m qutebrowser --debug'
# pacman -Sy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug
----
If you cannot reproduce the problem, you need to check if a coredump got
written somewhere.
The `-debug` packages conflict with the non-debug variants - it's safe to
remove them.
Check the file `/proc/sys/kernel/core_pattern` on your system. If it does not
start with a `|` character (pipe), check if there is a file named `core` or
`core.NNNN` in the directory from that file, or in the current directory.
Getting the stack trace
~~~~~~~~~~~~~~~~~~~~~~~
If so, execute gdb like this:
First install `gdb` on your system if it's not installed already.
Then run qutebrowser directly inside gdb like this:
----
$ gdb $(which python3) /path/to/core
$ gdb $(readlink -f $(which python3)) -ex 'run -m qutebrowser --debug'
----
If your `/proc/sys/kernel/core_pattern` contains something like
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` as root to run gdb:
----
# coredumpctl gdb $(which python3)
----
Getting a stack trace
---------------------
Regardless of the way you used to open gdb, you should now see something like:
After you reproduce the crash, you should now see something like:
----
Program received signal SIGSEGV, Segmentation fault.
@@ -107,16 +105,58 @@ Now enter these commands at the gdb prompt:
----
(gdb) set logging on
(gdb) set logging redirect on
(gdb) bt
(gdb) bt full
# you might have to press enter a few times until you get the prompt back
(gdb) set logging redirect off
(gdb) quit
----
Now copy the last few lines of the debug log (before you got the gdb prompt)
and the full content of `gdb.txt` into the bug report. Please also add some
words about what you were doing (or what pages you visited) before the crash
This will create a `gdb.txt` in your current directory.
Copy the last few lines of the debug log (before you got the gdb prompt) and
the full content of `gdb.txt` into the bug report. Please also add some words
about what you were doing (or what pages you visited) before the crash
happened.
Crashes which can NOT be reproduced
-----------------------------------
If you cannot reproduce the problem, you need to check if a coredump got
written somewhere. You should not install debug symbols as they won't match the
generated coredump.
First install `gdb` on your system if it's not installed already.
Then check the file `/proc/sys/kernel/core_pattern` on your system. If it does
not start with a `|` character (pipe), check if there is a file named `core` or
`core.NNNN` in the directory from that file, or in the current directory.
If so, execute gdb like this:
----
$ gdb $(readlink -f $(which python3)) /path/to/core
----
If your `/proc/sys/kernel/core_pattern` contains something like
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` to run gdb:
----
$ coredumpctl gdb $(readlink -f $(which python3))
----
Getting the stack trace
~~~~~~~~~~~~~~~~~~~~~~~
Now enter these commands at the gdb prompt:
----
(gdb) set logging on
(gdb) bt
# you might have to press enter a few times until you get the prompt back
(gdb) quit
----
Copy the content of `gdb.txt` into the bug report. Please also add some words
about what you were doing (or what pages you visited) before the crash
happened.
[[windows]]
@@ -130,9 +170,9 @@ file displayed there.
Now install
http://www.microsoft.com/en-us/download/details.aspx?id=42933[DebugDiag] from
Microsoft, then run the "DebugDiag 2 Analysis" tool. There, check
"CrashHangAnalysis" and add your crash dump via "Add Data files". Then click
"Start analysis".
Microsoft, then run the *DebugDiag 2 Analysis* tool. There, check
*CrashHangAnalysis* and add your crash dump via *Add Data files*. Then click
*Start analysis*.
Close the Internet Explorer which opens when it's done and use the
folder-button at the top left to get to the reports. There find the report file

66
doc/userscripts.asciidoc Normal file
View File

@@ -0,0 +1,66 @@
Writing qutebrowser userscripts
===============================
The Compiler <mail@qutebrowser.org>
qutebrowser is extensible by writing userscripts which can be called via the
`:spawn --userscript` command, or via a key binding.
These userscripts are similar to the (non-javascript) dwb userscripts. They can
be written in any language which can read environment variables and write to a
FIFO. Note they are *not* related to Greasemonkey userscripts.
Note for simple things such as opening the current page with another browser or
mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
Also note userscripts need to have the executable bit set (`chmod +x`) for
qutebrowser to run them.
Getting information
-------------------
The following environment variables will be set when a userscript is launched:
- `QUTE_MODE`: Either `hints` (started via hints) or `command` (started via
command or key binding).
- `QUTE_USER_AGENT`: The currently set user agent.
- `QUTE_FIFO`: The FIFO or file to write commands to.
- `QUTE_HTML`: Path of a file containing the HTML source of the current page.
- `QUTE_TEXT`: Path of a file containing the plaintext of the current page.
In `command` mode:
- `QUTE_URL`: The current URL.
- `QUTE_TITLE`: The title of the current page.
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
- `QUTE_SELECTED_HTML` The HTML currently selected on the page.
In `hints` mode:
- `QUTE_URL`: The URL selected via hints.
- `QUTE_SELECTED_TEXT`: The plain text of the element selected via hints.
- `QUTE_SELECTED_HTML` The HTML of the element selected via hints.
Sending commands
----------------
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
executed.
On Unix/OS X, this is a named pipe and commands written to it will get executed
immediately.
On Windows, this is a regular file, and the commands in it will be executed as
soon as your userscript terminates. This means when writing multiple commands,
you should append to the file (`>>` in bash) rather than overwrite it (`>`).
Examples
--------
Opening the currently selected word on http://www.dict.cc/[dict.cc]:
[source,bash]
----
#!/bin/bash
echo "open -t http://www.dict.cc/?s=$QUTE_SELECTED_TEXT" >> "$QUTE_FIFO"
----

BIN
icons/qutebrowser.icns Normal file

Binary file not shown.

View File

@@ -32,21 +32,22 @@
objecttolerance="10"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.2432572"
inkscape:cx="510.06077"
inkscape:cy="315.85317"
inkscape:zoom="0.8791156"
inkscape:cx="768.67127"
inkscape:cy="133.80749"
inkscape:document-units="px"
inkscape:current-layer="layer1"
width="1024px"
height="640px"
showgrid="false"
inkscape:window-width="1024"
inkscape:window-height="723"
inkscape:window-x="0"
inkscape:window-width="636"
inkscape:window-height="536"
inkscape:window-x="2560"
inkscape:window-y="0"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-maximized="1">
inkscape:window-maximized="0"
inkscape:snap-text-baseline="true">
<inkscape:grid
id="GridFromPre046Settings"
type="xygrid"
@@ -1454,23 +1455,27 @@
x="714.29938"
y="108.87096">)</tspan></text>
<rect
ry="4.3646927"
y="363.55695"
ry="3.3457608"
y="363.19348"
x="238.30771"
height="58.443066"
height="44.799603"
width="361.69229"
id="rect5017"
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none" />
<text
xml:space="preserve"
style="font-size:13px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
x="245.32532"
y="395.78867"
id="text5021"><tspan
sodipodi:role="line"
id="tspan5023"
<g
id="g4061"
transform="translate(0,-6.7232151)">
<text
id="text5021"
y="395.78867"
x="245.32532"
y="395.78867">Space</tspan></text>
style="font-style:normal;font-weight:normal;font-size:13px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
xml:space="preserve"><tspan
y="395.78867"
x="245.32532"
id="tspan5023"
sodipodi:role="line">Space</tspan></text>
</g>
<text
id="text6971"
y="317.98907"
@@ -1865,16 +1870,16 @@
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
x="320.22501"
x="317.63174"
y="195.40761"
id="text7245"
sodipodi:linespacing="89.999998%"><tspan
sodipodi:role="line"
x="320.22501"
x="317.63174"
y="195.40761"
id="tspan7366" /><tspan
sodipodi:role="line"
x="320.22501"
x="317.63174"
y="202.78995"
id="tspan7249"
style="font-size:8px">reload</tspan></text>
@@ -1934,7 +1939,7 @@
x="542.06946"
sodipodi:role="line"
id="tspan4938"
style="font-size:8px">scoll</tspan><tspan
style="font-size:8px">scroll</tspan><tspan
y="276.1955"
x="542.06946"
sodipodi:role="line"
@@ -2089,7 +2094,7 @@
id="tspan4998"
style="font-size:8px">new tab<tspan
style="fill:#ff0000"
id="tspan3699"></tspan></tspan><tspan
id="tspan3699" /></tspan><tspan
y="177.83009"
x="670.26074"
sodipodi:role="line"
@@ -2624,8 +2629,8 @@
<flowRoot
xml:space="preserve"
id="flowRoot5691"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
transform="translate(0,-14.539167)"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
transform="translate(0,-38.539167)"><flowRegion
id="flowRegion5693"><rect
id="rect5695"
width="322.5"
@@ -2634,8 +2639,8 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705">(1)</flowSpan> copying/yanking:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5701">yy - copy/yank URL</flowPara><flowPara
@@ -2647,10 +2652,10 @@
id="flowPara5709">yT - copy title to selection</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5711" /></flowRoot> <flowRoot
transform="translate(0.713591,62.823906)"
transform="translate(0.713591,38.823906)"
xml:space="preserve"
id="flowRoot5691-0"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-7"><rect
id="rect5695-0"
width="322.5"
@@ -2659,8 +2664,8 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-9"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-5">(2)</flowSpan> pasting:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5701-9">pp - open URL from clipboard</flowPara><flowPara
@@ -2668,26 +2673,26 @@
id="flowPara5703-8">pP - open URL from selection</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5707-0">Pp - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6101">pp</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5709-3">PP - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6103">pP</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5763">wp - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6105">pp</flowSpan>, in new window</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5765">wP - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6107">pP</flowSpan>, in new window</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5711-1" /></flowRoot> <flowRoot
transform="translate(171.2479,-14.539167)"
transform="translate(171.2479,-38.539167)"
xml:space="preserve"
id="flowRoot5691-0-9"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-7-0"><rect
id="rect5695-0-5"
width="322.5"
@@ -2695,9 +2700,9 @@
x="17.5"
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
id="flowPara5701-9-6"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-5-8">(3)</flowSpan> navigation:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5829">[[ - click &quot;previous&quot;-link on page</flowPara><flowPara
@@ -2705,11 +2710,11 @@
id="flowPara5703-8-2">]] - click &quot;next&quot;-link on page</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5707-0-7">{{ - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6111">[[</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5709-3-1">}} - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6109">]]</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5835">&lt;Ctrl-A&gt; - increment no. in URL</flowPara><flowPara
@@ -2769,10 +2774,10 @@
id="tspan4936-1-1-9-2"
style="font-size:8px;fill:#ff0000">(3)</tspan></text>
<flowRoot
transform="translate(169.83695,87.823906)"
transform="translate(169.83695,63.823906)"
xml:space="preserve"
id="flowRoot5691-4"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9"><rect
id="rect5695-9"
width="322.5"
@@ -2781,8 +2786,8 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-0">(4)</flowSpan> scrolling:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5701-8">&lt;Ctrl-F&gt; - page down</flowPara><flowPara
@@ -2792,59 +2797,59 @@
id="flowPara5962">&lt;Ctrl-D&gt; - half page down</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5711-7">&lt;Ctrl-U&gt; - half page up</flowPara></flowRoot> <flowRoot
transform="translate(360.81663,-14.539167)"
transform="translate(360.81663,-38.539167)"
xml:space="preserve"
id="flowRoot5691-4-9"
style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"><flowRegion
style="font-style:normal;font-weight:bold;font-size:40px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1"><rect
id="rect5695-9-8"
width="322.5"
height="162.5"
x="17.5"
y="448.75"
style="font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold" /></flowRegion><flowPara
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#000000" /></flowRegion><flowPara
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
id="flowPara4171">in prompt mode:</flowPara><flowPara
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara4175">Enter - accept prompt</flowPara><flowPara
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara4177">y - answer yes to prompt</flowPara><flowPara
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara4179">n - answer no to prompt</flowPara><flowPara
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara6016" /></flowRoot> <flowRoot
transform="translate(360.8264,40.645949)"
transform="translate(360.8264,16.645949)"
xml:space="preserve"
id="flowRoot5691-0-9-9"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-7-0-2"><rect
id="rect5695-0-5-6"
width="322.5"
height="162.5"
x="17.5"
y="448.75"
style="font-style:normal;fill:#000000;-inkscape-font-specification:Sans" /></flowRegion><flowPara
style="font-size:10px;font-style:normal;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
style="font-style:normal;-inkscape-font-specification:Sans;fill:#000000" /></flowRegion><flowPara
style="font-style:normal;font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
id="flowPara5701-9-6-8"><flowSpan
style="font-style:normal;font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-style:normal;font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-5-8-3">(6)</flowSpan> opening:</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5829-1">go - open based on cur. URL</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5703-8-2-8">gO - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6132">go</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara3581">xO - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan6134">go</flowSpan>, in bg. tab</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5709-3-1-6">xo - open in background tab</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5841-1">wo - open in new window</flowPara><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5839-8" /><flowPara
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
id="flowPara5711-1-8-7" /></flowRoot> <text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
@@ -2899,10 +2904,10 @@
id="tspan6219"
style="font-size:8px">mode</tspan></text>
<flowRoot
transform="translate(361.29883,121.78408)"
transform="translate(361.29883,97.78408)"
xml:space="preserve"
id="flowRoot5691-4-9-3"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7"><rect
id="rect5695-9-8-7"
width="322.5"
@@ -2911,8 +2916,8 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3-7-6"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-0-4-7">(7)</flowSpan> back/forward:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5701-8-5-8"><flowSpan
@@ -2959,10 +2964,10 @@
style="font-size:8px;fill:#ff0000"
id="tspan3662">(9)</tspan></tspan></text>
<flowRoot
transform="translate(526.15723,-14.548933)"
transform="translate(526.15723,-38.548933)"
xml:space="preserve"
id="flowRoot5691-4-9-3-6"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7-3"><rect
id="rect5695-9-8-7-7"
width="322.5"
@@ -2971,15 +2976,15 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3-7-6-8"
style="font-size:10px;font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold">(8)</flowPara><flowPara
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#ff0000">(8)</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3626-7">prefix with w - in new window</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3725" /></flowRoot> <flowRoot
transform="translate(525.65723,34.440325)"
transform="translate(525.65723,10.440325)"
xml:space="preserve"
id="flowRoot5691-4-9-3-1"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7-1"><rect
id="rect5695-9-8-7-5"
width="322.5"
@@ -2988,12 +2993,14 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3-7-6-1"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan5705-0-4-7-6">(9)</flowSpan> extended hint mode:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3626-73">;b - open hint in background tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4051">;f - open hint in foreground tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3788">;h - hover over hint (mouse-over)</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3790">;i - hint images</flowPara><flowPara
@@ -3003,7 +3010,7 @@
id="flowPara3794">;o - put hinted URL in cmd. line</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3796">;O - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan3798">;o</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3800">;y - yank hinted URL to clipboard</flowPara><flowPara
@@ -3013,24 +3020,24 @@
id="flowPara3804">;r - rapid hinting</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3806">;R - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan3810">;r</flowSpan>, in new window</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3808">;d - download hinted URL</flowPara></flowRoot> <flowRoot
transform="translate(706.84131,-14.539167)"
transform="translate(706.84131,-38.539167)"
xml:space="preserve"
id="flowRoot5691-4-9-3-6-1"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7-3-5"><rect
id="rect5695-9-8-7-7-0"
width="148.08141"
height="203.19766"
width="154.90645"
height="240.73535"
x="17.5"
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3-7-6-8-2"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan3852">(10)</flowSpan> misc. commands:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3725-0"><flowSpan
@@ -3052,7 +3059,7 @@
id="flowPara3915">gu - navigate up in URL</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3917">gU - like <flowSpan
style="font-style:italic;-inkscape-font-specification:Sans Italic"
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
id="flowSpan3923">gu</flowSpan>, in new tab</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3921">sf - save config</flowPara><flowPara
@@ -3072,10 +3079,16 @@
id="flowPara4169"><flowSpan
style="fill:#0000ff"
id="flowSpan5438">ad</flowSpan> - cancel download</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4077">co - close other tabs</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4081">cd - clear downloads</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3933" /><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3935" /></flowRoot> <text
id="flowPara3935" /><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4079" /></flowRoot> <text
sodipodi:linespacing="89.999998%"
id="text9514-8-9-0-8"
y="204.26315"
@@ -3112,10 +3125,10 @@
id="tspan4936-1-1-9-59-5"
style="font-size:8px;fill:#ff0000">(10)</tspan></text>
<flowRoot
transform="translate(841.04351,-14.539167)"
transform="translate(841.04351,-38.539167)"
xml:space="preserve"
id="flowRoot5691-4-9-3-6-1-2"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7-3-5-2"><rect
id="rect5695-9-8-7-7-0-9"
width="328.31396"
@@ -3124,8 +3137,8 @@
y="448.75"
style="fill:#000000" /></flowRegion><flowPara
id="flowPara5697-3-7-6-8-2-0"
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
id="flowSpan3852-6">(11)</flowSpan> modifier commands:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara3933-6">&lt;Alt-num&gt; - select tab</flowPara><flowPara
@@ -3141,11 +3154,11 @@
id="flowPara4138">&lt;Ctrl-S&gt; - stop loading</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4140">&lt;Ctrl-Alt-P&gt; - print</flowPara><flowPara
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
id="flowPara4142">in insert mode:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4144">&lt;Ctrl-E&gt; - open editor</flowPara><flowPara
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
id="flowPara4146">in command mode:</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4148">&lt;Ctrl-P&gt; - prev. history item</flowPara><flowPara
@@ -3154,126 +3167,142 @@
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
id="rect3764-9"
width="60"
height="60"
height="45.993073"
x="168.32558"
y="362"
ry="4.480969" />
ry="3.4348924" />
<rect
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
id="rect3764-9-3"
width="60"
height="60"
height="45.993073"
x="47.906979"
y="362"
ry="4.480969" />
ry="3.4348924" />
<rect
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
id="rect3764-9-1"
width="60"
height="60"
height="45.993073"
x="613.81396"
y="362"
ry="4.480969" />
ry="3.4348924" />
<rect
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
id="rect3764-9-7"
width="60"
height="60"
height="45.993073"
x="730.46509"
y="362"
ry="4.480969" />
<text
id="text7358-8"
y="395.78867"
x="62.269463"
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
xml:space="preserve"><tspan
y="395.78867"
ry="3.4348924" />
<g
id="g4049"
transform="translate(1.3728676,-1.9658966)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
x="62.269463"
id="tspan7360-1"
sodipodi:role="line"
style="font-size:12px;font-family:DejaVu Sans Mono">Ctrl</tspan></text>
<text
id="text7358-8-3"
y="395.78867"
x="745.17719"
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
xml:space="preserve"><tspan
y="395.78867"
x="745.17719"
id="tspan7360-1-7"
sodipodi:role="line"
style="font-size:12px;font-family:DejaVu Sans Mono">Ctrl</tspan></text>
<text
id="text7358-8-3-8"
y="395.78867"
x="627.75677"
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
xml:space="preserve"><tspan
y="395.78867"
x="627.75677"
id="tspan7360-1-7-0"
sodipodi:role="line"
style="font-size:12px;font-family:DejaVu Sans Mono">Alt</tspan></text>
<text
id="text7358-8-3-8-1"
y="395.78867"
x="186.34709"
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
xml:space="preserve"><tspan
y="395.78867"
x="186.34709"
id="tspan7360-1-7-0-2"
sodipodi:role="line"
style="font-size:12px;font-family:DejaVu Sans Mono">Alt</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text9514-8-9-0-8-4-0"
y="410.26315"
x="67.315361"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
xml:space="preserve"><tspan
y="410.26315"
y="385.78867"
id="text7358-8"><tspan
style="font-size:12px;font-family:'DejaVu Sans Mono'"
sodipodi:role="line"
id="tspan7360-1"
x="62.269463"
y="385.78867">Ctrl</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="67.315361"
sodipodi:role="line"
id="tspan4936-1-1-9-59-8-3"
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text9514-8-9-0-8-4-0-8"
y="410.26315"
x="187.47893"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
xml:space="preserve"><tspan
y="410.26315"
y="400.26315"
id="text9514-8-9-0-8-4-0"
sodipodi:linespacing="89.999998%"><tspan
style="font-size:8px;fill:#ff0000"
id="tspan4936-1-1-9-59-8-3"
sodipodi:role="line"
x="67.315361"
y="400.26315">(11)</tspan></text>
</g>
<g
id="g4055"
transform="translate(1.6278992,-11.965897)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
x="186.34709"
y="395.78867"
id="text7358-8-3-8-1"><tspan
style="font-size:12px;font-family:'DejaVu Sans Mono'"
sodipodi:role="line"
id="tspan7360-1-7-0-2"
x="186.34709"
y="395.78867">Alt</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="187.47893"
sodipodi:role="line"
id="tspan4936-1-1-9-59-8-3-8"
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text9514-8-9-0-8-4-0-7"
y="410.26315"
x="628.88861"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
xml:space="preserve"><tspan
y="410.26315"
id="text9514-8-9-0-8-4-0-8"
sodipodi:linespacing="89.999998%"><tspan
style="font-size:8px;fill:#ff0000"
id="tspan4936-1-1-9-59-8-3-8"
sodipodi:role="line"
x="187.47893"
y="410.26315">(11)</tspan></text>
</g>
<g
id="g4065"
transform="translate(5.706604,-11.965897)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
x="627.75677"
y="395.78867"
id="text7358-8-3-8"><tspan
style="font-size:12px;font-family:'DejaVu Sans Mono'"
sodipodi:role="line"
id="tspan7360-1-7-0"
x="627.75677"
y="395.78867">Alt</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="628.88861"
sodipodi:role="line"
id="tspan4936-1-1-9-59-8-3-82"
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text9514-8-9-0-8-4-0-3"
y="410.26315"
x="750.22308"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
xml:space="preserve"><tspan
y="410.26315"
id="text9514-8-9-0-8-4-0-7"
sodipodi:linespacing="89.999998%"><tspan
style="font-size:8px;fill:#ff0000"
id="tspan4936-1-1-9-59-8-3-82"
sodipodi:role="line"
x="628.88861"
y="410.26315">(11)</tspan></text>
</g>
<g
id="g4071"
transform="translate(1.0232544,-11.965897)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
x="745.17719"
y="395.78867"
id="text7358-8-3"><tspan
style="font-size:12px;font-family:'DejaVu Sans Mono'"
sodipodi:role="line"
id="tspan7360-1-7"
x="745.17719"
y="395.78867">Ctrl</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
x="750.22308"
sodipodi:role="line"
id="tspan4936-1-1-9-59-8-3-4"
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
y="410.26315"
id="text9514-8-9-0-8-4-0-3"
sodipodi:linespacing="89.999998%"><tspan
style="font-size:8px;fill:#ff0000"
id="tspan4936-1-1-9-59-8-3-4"
sodipodi:role="line"
x="750.22308"
y="410.26315">(11)</tspan></text>
</g>
<text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
@@ -3297,27 +3326,15 @@
style="font-size:8px">tab</tspan></text>
<text
xml:space="preserve"
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
x="267.67316"
y="326.20523"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ff0000;fill-opacity:1;stroke:none"
x="274.21381"
y="343.17578"
id="text10547-23-6-7"
sodipodi:linespacing="89.999998%"><tspan
sodipodi:role="line"
x="267.67316"
y="326.20523"
id="tspan10560-1-3-1" /><tspan
sodipodi:role="line"
x="267.67316"
y="333.40524"
id="tspan5325">co: close</tspan><tspan
sodipodi:role="line"
x="267.67316"
y="340.60522"
id="tspan5327">other</tspan><tspan
sodipodi:role="line"
x="267.67316"
y="347.80524"
id="tspan10562-12-5-98">tabs</tspan></text>
x="274.21381"
y="343.17578"
id="tspan4052">(10)</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text10564-6-7-8-0"
@@ -3398,10 +3415,10 @@
id="tspan4936-1-1-9-59-5-6"
style="font-size:8px;fill:#ff0000">(10)</tspan></text>
<flowRoot
transform="translate(838.55559,158.52236)"
transform="translate(838.55559,134.52236)"
xml:space="preserve"
id="flowRoot5691-4-9-3-6-6"
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion5693-9-1-7-3-8"><rect
id="rect5695-9-8-7-7-6"
width="322.5"
@@ -3412,9 +3429,50 @@
style="font-size:10px;fill:#000000"
id="flowPara3626-7-0"><flowSpan
id="flowSpan5520"
style="font-size:10px;font-weight:bold;fill:#0000ff;-inkscape-font-specification:Sans Bold">blue keys </flowSpan><flowSpan
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#0000ff">blue keys </flowSpan><flowSpan
style="fill:#0000ff"
id="flowSpan5528">can be</flowSpan></flowPara><flowPara
style="font-size:10px;fill:#0000ff"
id="flowPara3725-9">prefixed by a count</flowPara></flowRoot> </g>
id="flowPara3725-9">prefixed by a count</flowPara></flowRoot> <text
xml:space="preserve"
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
x="317.95987"
y="155.85321"
id="text7245-1-7"
sodipodi:linespacing="89.999998%"><tspan
sodipodi:role="line"
x="317.95987"
y="155.85321"
id="tspan7366-3-3" /><tspan
sodipodi:role="line"
x="317.95987"
y="163.23555"
id="tspan5293-5"
style="font-size:8px">reload </tspan><tspan
sodipodi:role="line"
x="317.95987"
y="170.43555"
style="font-size:8px"
id="tspan3716">(bypass </tspan><tspan
sodipodi:role="line"
x="317.95987"
y="177.63554"
style="font-size:8px"
id="tspan3719">cache)</tspan></text>
<text
sodipodi:linespacing="89.999998%"
id="text9514-60-7-7-0-8"
y="338.04874"
x="342.42523"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
xml:space="preserve"><tspan
y="338.04874"
x="342.42523"
sodipodi:role="line"
id="tspan5689-6">visual</tspan><tspan
y="345.24875"
x="342.42523"
sodipodi:role="line"
id="tspan4112">mode</tspan></text>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 137 KiB

7
misc/qt_menu.nib/README Normal file
View File

@@ -0,0 +1,7 @@
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.

59
misc/qt_menu.nib/classes.nib generated Normal file
View File

@@ -0,0 +1,59 @@
<?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>

18
misc/qt_menu.nib/info.nib generated Normal file
View File

@@ -0,0 +1,18 @@
<?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>

BIN
misc/qt_menu.nib/keyedobjects.nib generated Normal file

Binary file not shown.

View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# Pipes history, quickmarks, and URL into dmenu.
#
# If run from qutebrowser as a userscript, it runs :open on the URL
# If not, it opens a new qutebrowser window at the URL
#
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# :bind o spawn --userscript dmenu_qutebrowser
#
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
# You can simulate "go" by pressing "o<tab>", as the current URL is always first in the list
#
# I personally use "<Mod4>o" to launch this script. For me, my workflow is:
# Default keys Keys with this script
# O <Mod4>o
# o o
# go o<Tab>
# gO gC, then o<Tab>
# (This is unnecessarily long. I use this rarely, feel free to make this script accept parameters.)
#
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
url=$(echo "$QUTE_URL" | cat - ~/.config/qutebrowser/quickmarks ~/.local/share/qutebrowser/history | dmenu -l 15 -p qutebrowser)
url=$(echo $url | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo $url)
[ -z "${url// }" ] && exit
echo "open $url" >> "$QUTE_FIFO" || qutebrowser "$url"

38
misc/userscripts/openfeeds Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2015 jnphilipp <me@jnphilipp.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# Opens all links to feeds defined in the head of a site
#
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# :bind gF spawn --userscript openfeeds
#
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open
#
import os
import re
from bs4 import BeautifulSoup
with open(os.environ['QUTE_HTML'], 'r') as f:
soup = BeautifulSoup(f)
with open(os.environ['QUTE_FIFO'], 'w') as f:
for link in soup.find_all('link', rel='alternate', type=re.compile(r'application/((rss|rdf|atom)\+)?xml|text/xml')):
f.write('open -t %s\n' % link.get('href'))

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
#!/bin/bash
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
#
# This file is part of qutebrowser.
#
@@ -17,4 +17,16 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Tests for the qutebrowser.utils package."""
#
# This script fetches the unprocessed HTML source for a page and opens it in vim.
# :bind gf spawn --userscript qutebrowser_viewsource
#
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
#
path=/tmp/qutebrowser_$(mktemp XXXXXXXX).html
curl "$QUTE_URL" > $path
urxvt -e vim "$path"
rm "$path"

32
pytest.ini Normal file
View File

@@ -0,0 +1,32 @@
[pytest]
norecursedirs = .tox .venv
markers =
gui: Tests using the GUI (e.g. spawning widgets)
posix: Tests which only can run on a POSIX OS.
windows: Tests which only can run on Windows.
linux: Tests which only can run on Linux.
osx: Tests which only can run on OS X.
not_frozen: Tests which can't be run if sys.frozen is True.
frozen: Tests which can only be run if sys.frozen is True.
integration: Tests which test a bigger portion of code, run without coverage.
flakes-ignore =
UnusedImport
UnusedVariable
resources.py ALL
pep8ignore =
E265 # Block comment should start with '#'
E501 # Line too long
E402 # module level import not at top of file
E266 # too many leading '#' for block comment
W503 # line break before binary operator
resources.py ALL
.hypothesis/* ALL
mccabe-complexity = 12
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
^SetProcessDpiAwareness failed: .*
^QWindowsWindow::setGeometryDp: Unable to set geometry .*
^QProcess: Destroyed while process .* is still running\.
^"Method "GetAll" with signature "s" on interface "org\.freedesktop\.DBus\.Properties" doesn't exist
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805

View File

@@ -2,7 +2,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -24,11 +24,11 @@
import os.path
__author__ = "Florian Bruhin"
__copyright__ = "Copyright 2014 Florian Bruhin (The Compiler)"
__copyright__ = "Copyright 2014-2015 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (0, 1, 1)
__version_info__ = (0, 4, 0)
__version__ = '.'.join(map(str, __version_info__))
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Functions related to adblocking."""
"""Functions related to ad blocking."""
import io
import os.path
@@ -25,11 +25,9 @@ import functools
import posixpath
import zipfile
from PyQt5.QtCore import QStandardPaths
from qutebrowser.config import config
from qutebrowser.utils import objreg, standarddir, log, message
from qutebrowser.commands import cmdutils
from qutebrowser.commands import cmdutils, cmdexc
def guess_zip_filename(zf):
@@ -92,13 +90,18 @@ class HostBlocker:
self.blocked_hosts = set()
self._in_progress = []
self._done_count = 0
data_dir = standarddir.get(QStandardPaths.DataLocation)
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
data_dir = standarddir.data()
if data_dir is None:
self._hosts_file = None
else:
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
objreg.get('config').changed.connect(self.on_config_changed)
def read_hosts(self):
"""Read hosts from the existing blocked-hosts file."""
self.blocked_hosts = set()
if self._hosts_file is None:
return
if os.path.exists(self._hosts_file):
try:
with open(self._hosts_file, 'r', encoding='utf-8') as f:
@@ -107,13 +110,17 @@ class HostBlocker:
except OSError:
log.misc.exception("Failed to read host blocklist!")
else:
if config.get('content', 'host-block-lists') is not None:
message.info('last-focused',
args = objreg.get('args')
if (config.get('content', 'host-block-lists') is not None and
args.basedir is None):
message.info('current',
"Run :adblock-update to get adblock lists.")
@cmdutils.register(instance='host-blocker')
def adblock_update(self):
@cmdutils.register(instance='host-blocker', win_id='win_id')
def adblock_update(self, win_id):
"""Update the adblock block lists."""
if self._hosts_file is None:
raise cmdexc.CommandError("No data storage is configured!")
self.blocked_hosts = set()
self._done_count = 0
urls = config.get('content', 'host-block-lists')
@@ -125,8 +132,10 @@ class HostBlocker:
if url.scheme() == 'file':
try:
fileobj = open(url.path(), 'rb')
except OSError:
log.misc.exception("Failed to open block list!")
except OSError as e:
message.error(win_id, "adblock: Error while reading {}: "
"{}".format(url.path(), e.strerror))
continue
download = FakeDownload(fileobj)
self._in_progress.append(download)
self.on_download_finished(download)
@@ -154,9 +163,8 @@ class HostBlocker:
f = get_fileobj(byte_io)
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
zipfile.LargeZipFile) as e:
message.error('last-focused', "adblock: Error while reading {}: "
"{} - {}".format(
byte_io.name, e.__class__.__name__, e))
message.error('current', "adblock: Error while reading {}: {} - "
"{}".format(byte_io.name, e.__class__.__name__, e))
return
for line in f:
line_count += 1
@@ -184,17 +192,16 @@ class HostBlocker:
self.blocked_hosts.add(host)
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
if error_count > 0:
message.error('last-focused', "adblock: {} read errors for "
"{}".format(error_count, byte_io.name))
message.error('current', "adblock: {} read errors for {}".format(
error_count, byte_io.name))
def on_lists_downloaded(self):
"""Install block lists after files have been downloaded."""
with open(self._hosts_file, 'w', encoding='utf-8') as f:
for host in sorted(self.blocked_hosts):
f.write(host + '\n')
message.info('last-focused', "adblock: Read {} hosts from {} "
"sources.".format(len(self.blocked_hosts),
self._done_count))
message.info('current', "adblock: Read {} hosts from {} sources."
.format(len(self.blocked_hosts), self._done_count))
@config.change_filter('content', 'host-block-lists')
def on_config_changed(self):

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,7 +21,7 @@
import os.path
from PyQt5.QtCore import QStandardPaths
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
from qutebrowser.config import config
@@ -30,24 +30,41 @@ from qutebrowser.utils import utils, standarddir, objreg
class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size."""
"""Disk cache which sets correct cache dir and size.
Attributes:
_activated: Whether the cache should be used.
"""
def __init__(self, parent=None):
super().__init__(parent)
cache_dir = standarddir.get(QStandardPaths.CacheLocation)
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
cache_dir = standarddir.cache()
if config.get('general', 'private-browsing') or cache_dir is None:
self._activated = False
else:
self._activated = True
self.setCacheDirectory(os.path.join(standarddir.cache(), 'http'))
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
objreg.get('config').changed.connect(self.cache_size_changed)
objreg.get('config').changed.connect(self.on_config_changed)
def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(),
path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
def cache_size_changed(self):
"""Update cache size if the config was changed."""
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""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', 'private-browsing'):
if (config.get('general', 'private-browsing') or
standarddir.cache() is None):
self._activated = False
else:
self._activated = True
self.setCacheDirectory(
os.path.join(standarddir.cache(), 'http'))
def cacheSize(self):
"""Return the current size taken up by the cache.
@@ -55,13 +72,13 @@ class DiskCache(QNetworkDiskCache):
Return:
An int.
"""
if objreg.get('general', 'private-browsing'):
return 0
else:
if self._activated:
return super().cacheSize()
else:
return 0
def fileMetaData(self, filename):
"""Returns the QNetworkCacheMetaData for the cache file filename.
"""Return the QNetworkCacheMetaData for the cache file filename.
Args:
filename: The file name as a string.
@@ -69,10 +86,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QNetworkCacheMetaData object.
"""
if objreg.get('general', 'private-browsing'):
return QNetworkCacheMetaData()
else:
if self._activated:
return super().fileMetaData(filename)
else:
return QNetworkCacheMetaData()
def data(self, url):
"""Return the data associated with url.
@@ -83,10 +100,10 @@ class DiskCache(QNetworkDiskCache):
return:
A QIODevice or None.
"""
if objreg.get('general', 'private-browsing'):
return None
else:
if self._activated:
return super().data(url)
else:
return None
def insert(self, device):
"""Insert the data in device and the prepared meta data into the cache.
@@ -94,10 +111,10 @@ class DiskCache(QNetworkDiskCache):
Args:
device: A QIODevice.
"""
if objreg.get('general', 'private-browsing'):
return
else:
if self._activated:
super().insert(device)
else:
return None
def metaData(self, url):
"""Return the meta data for the url url.
@@ -108,10 +125,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QNetworkCacheMetaData object.
"""
if objreg.get('general', 'private-browsing'):
return QNetworkCacheMetaData()
else:
if self._activated:
return super().metaData(url)
else:
return QNetworkCacheMetaData()
def prepare(self, meta_data):
"""Return the device that should be populated with the data.
@@ -122,10 +139,10 @@ class DiskCache(QNetworkDiskCache):
Return:
A QIODevice or None.
"""
if objreg.get('general', 'private-browsing'):
return None
else:
if self._activated:
return super().prepare(meta_data)
else:
return None
def remove(self, url):
"""Remove the cache entry for url.
@@ -133,25 +150,25 @@ class DiskCache(QNetworkDiskCache):
Return:
True on success, False otherwise.
"""
if objreg.get('general', 'private-browsing'):
return False
else:
if self._activated:
return super().remove(url)
else:
return False
def updateMetaData(self, meta_data):
"""Updates the cache meta date for the meta_data's url to meta_data.
"""Update the cache meta date for the meta_data's url to meta_data.
Args:
meta_data: A QNetworkCacheMetaData object.
"""
if objreg.get('general', 'private-browsing'):
return
else:
if self._activated:
super().updateMetaData(meta_data)
else:
return
def clear(self):
"""Removes all items from the cache."""
if objreg.get('general', 'private-browsing'):
return
else:
"""Remove all items from the cache."""
if self._activated:
super().clear()
else:
return

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -20,16 +20,25 @@
"""Handling of HTTP cookies."""
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
from PyQt5.QtCore import QStandardPaths, QDateTime
from PyQt5.QtCore import pyqtSignal, QDateTime
from qutebrowser.config import config
from qutebrowser.config.parsers import line as lineparser
from qutebrowser.utils import utils, standarddir, objreg
from qutebrowser.misc import lineparser
class RAMCookieJar(QNetworkCookieJar):
"""An in-RAM cookie jar."""
"""An in-RAM cookie jar.
Signals:
changed: Emitted when the cookie store was changed.
"""
changed = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
def __repr__(self):
return utils.get_repr(self, count=len(self.allCookies()))
@@ -47,6 +56,7 @@ class RAMCookieJar(QNetworkCookieJar):
if config.get('content', 'cookies-accept') == 'never':
return False
else:
self.changed.emit()
return super().setCookiesFromUrl(cookies, url)
@@ -55,24 +65,26 @@ class CookieJar(RAMCookieJar):
"""A cookie jar saving cookies to disk.
Attributes:
_linecp: The LineConfigParser managing the cookies file.
_lineparser: The LineParser managing the cookies file.
"""
def __init__(self, parent=None):
super().__init__(parent)
datadir = standarddir.get(QStandardPaths.DataLocation)
self._linecp = lineparser.LineConfigParser(datadir, 'cookies',
binary=True)
self._lineparser = lineparser.LineParser(
standarddir.data(), 'cookies', binary=True, parent=self)
cookies = []
for line in self._linecp:
for line in self._lineparser:
cookies += QNetworkCookie.parseCookies(line)
self.setAllCookies(cookies)
objreg.get('config').changed.connect(self.cookies_store_changed)
objreg.get('save-manager').add_saveable(
'cookies', self.save, self.changed,
config_opt=('content', 'cookies-store'))
def purge_old_cookies(self):
"""Purge expired cookies from the cookie jar."""
# Based on:
# http://qt-project.org/doc/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
# http://doc.qt.io/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
now = QDateTime.currentDateTime()
cookies = [c for c in self.allCookies()
if c.isSessionCookie() or c.expirationDate() >= now]
@@ -80,19 +92,18 @@ class CookieJar(RAMCookieJar):
def save(self):
"""Save cookies to disk."""
if not config.get('content', 'cookies-store'):
return
self.purge_old_cookies()
lines = []
for cookie in self.allCookies():
if not cookie.isSessionCookie():
lines.append(cookie.toRawForm())
self._linecp.data = lines
self._linecp.save()
self._lineparser.data = lines
self._lineparser.save()
@config.change_filter('content', 'cookies-store')
def cookies_store_changed(self):
"""Delete stored cookies if cookies-store changed."""
if not config.get('content', 'cookies-store'):
self._linecp.data = []
self._linecp.save()
self._lineparser.data = []
self._lineparser.save()
self.changed.emit()

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,14 +21,14 @@
import io
import os
import sys
import os.path
import shutil
import functools
import collections
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QTimer,
QStandardPaths, Qt, QVariant, QAbstractListModel,
QModelIndex, QUrl)
Qt, QVariant, QAbstractListModel, QModelIndex, QUrl)
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
# We need this import so PyQt can use it inside pyqtSlot
@@ -48,13 +48,51 @@ ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
# Remember the last used directory
_last_used_directory = None
# All REFRESH_INTERVAL milliseconds, speeds will be recalculated and downloads
# redrawn.
REFRESH_INTERVAL = 500
def _download_dir():
"""Get the download directory to use."""
directory = config.get('storage', 'download-directory')
remember_dir = config.get('storage', 'remember-download-directory')
if remember_dir and _last_used_directory is not None:
return _last_used_directory
elif directory is None:
return standarddir.download()
else:
return directory
def _path_suggestion(filename):
"""Get the suggested file path.
Args:
filename: The filename to use if included in the suggestion.
"""
suggestion = config.get('completion', 'download-path-suggestion')
if suggestion == 'path':
# add trailing '/' if not present
return os.path.join(_download_dir(), '')
elif suggestion == 'filename':
return filename
elif suggestion == 'both':
return os.path.join(_download_dir(), filename)
else:
raise ValueError("Invalid suggestion value {}!".format(suggestion))
class DownloadItemStats(QObject):
"""Statistics (bytes done, total bytes, time, etc.) about a download.
Class attributes:
SPEED_REFRESH_INTERVAL: How often to refresh the speed, in msec.
SPEED_AVG_WINDOW: How many seconds of speed data to average to
estimate the remaining time.
@@ -67,42 +105,40 @@ class DownloadItemStats(QObject):
the speed the last time.
"""
SPEED_REFRESH_INTERVAL = 500
SPEED_AVG_WINDOW = 30
updated = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self.total = None
self.done = 0
self.speed = 0
self._last_done = 0
samples = int(self.SPEED_AVG_WINDOW *
(1000 / self.SPEED_REFRESH_INTERVAL))
samples = int(self.SPEED_AVG_WINDOW * (1000 / REFRESH_INTERVAL))
self._speed_avg = collections.deque(maxlen=samples)
self.timer = usertypes.Timer(self, 'speed_refresh')
self.timer.timeout.connect(self._update_speed)
self.timer.setInterval(self.SPEED_REFRESH_INTERVAL)
self.timer.start()
@pyqtSlot()
def _update_speed(self):
"""Recalculate the current download speed."""
def update_speed(self):
"""Recalculate the current download speed.
The caller needs to guarantee this is called all REFRESH_INTERVAL ms.
"""
if self.done is None:
# this can happen for very fast downloads, e.g. when actually
# opening a file
return
delta = self.done - self._last_done
self.speed = delta * 1000 / self.SPEED_REFRESH_INTERVAL
self.speed = delta * 1000 / REFRESH_INTERVAL
self._speed_avg.append(self.speed)
self._last_done = self.done
self.updated.emit()
def finish(self):
"""Set the download stats as finished."""
self.timer.stop()
self.done = self.total
def percentage(self):
"""The current download percentage, or None if unknown."""
if self.total == 0 or self.total is None:
if self.done == self.total:
return 100
elif self.total == 0 or self.total is None:
return None
else:
return 100 * self.done / self.total
@@ -122,7 +158,7 @@ class DownloadItemStats(QObject):
@pyqtSlot(int, int)
def on_download_progress(self, bytes_done, bytes_total):
"""Upload local variables when the download progress changed.
"""Update local variables when the download progress changed.
Args:
bytes_done: How many bytes are downloaded.
@@ -132,7 +168,6 @@ class DownloadItemStats(QObject):
bytes_total = None
self.done = bytes_done
self.total = bytes_total
self.updated.emit()
class DownloadItem(QObject):
@@ -158,32 +193,33 @@ class DownloadItem(QObject):
Attributes:
done: Whether the download is finished.
stats: A DownloadItemStats object.
successful: Whether the download has completed sucessfully.
index: The index of the download in the view.
successful: Whether the download has completed successfully.
error_msg: The current error message, or None
autoclose: Whether to close the associated file if the download is
done.
fileobj: The file object to download the file to.
reply: The QNetworkReply associated with this download.
retry_info: A RetryInfo instance.
_filename: The filename of the download.
_redirects: How many time we were redirected already.
_buffer: A BytesIO object to buffer incoming data until we know the
target file.
_read_timer: A QTimer which reads the QNetworkReply into self._buffer
_read_timer: A Timer which reads the QNetworkReply into self._buffer
periodically.
_retry_info: A RetryInfo instance.
_win_id: The window ID the DownloadItem runs in.
Signals:
data_changed: The downloads metadata changed.
finished: The download was finished.
cancelled: The download was cancelled.
error: An error with the download occured.
error: An error with the download occurred.
arg: The error message as string.
redirected: Signal emitted when a download was redirected.
arg 0: The new QNetworkRequest.
arg 1: The old QNetworkReply.
do_retry: Emitted when a request should be re-tried.
arg: The QNetworkRequest to download.
do_retry: Emitted when a download is retried.
arg 0: The new DownloadItem
"""
MAX_REDIRECTS = 10
@@ -192,7 +228,7 @@ class DownloadItem(QObject):
error = pyqtSignal(str)
cancelled = pyqtSignal()
redirected = pyqtSignal(QNetworkRequest, QNetworkReply)
do_retry = pyqtSignal('QNetworkReply')
do_retry = pyqtSignal(object) # DownloadItem
def __init__(self, reply, win_id, parent=None):
"""Constructor.
@@ -201,14 +237,14 @@ class DownloadItem(QObject):
reply: The QNetworkReply to download.
"""
super().__init__(parent)
self._retry_info = None
self.retry_info = None
self.done = False
self.stats = DownloadItemStats(self)
self.stats.updated.connect(self.data_changed)
self.index = 0
self.autoclose = True
self.reply = None
self._buffer = io.BytesIO()
self._read_timer = QTimer()
self._read_timer = usertypes.Timer(self, name='download-read-timer')
self._read_timer.setInterval(500)
self._read_timer.timeout.connect(self.on_read_timer_timeout)
self._redirects = 0
@@ -237,8 +273,9 @@ class DownloadItem(QObject):
else:
errmsg = " - {}".format(self.error_msg)
if all(e is None for e in (perc, remaining, self.stats.total)):
return ('{name} [{speed:>10}|{down}]{errmsg}'.format(
name=self.basename, speed=speed, down=down, errmsg=errmsg))
return ('{index}: {name} [{speed:>10}|{down}]{errmsg}'.format(
index=self.index, name=self.basename, speed=speed,
down=down, errmsg=errmsg))
if perc is None:
perc = '??'
else:
@@ -249,17 +286,18 @@ class DownloadItem(QObject):
remaining = utils.format_seconds(remaining)
total = utils.format_size(self.stats.total, suffix='B')
if self.done:
return ('{name} [{perc:>2}%|{total}]{errmsg}'.format(
name=self.basename, perc=perc, total=total,
errmsg=errmsg))
return ('{index}: {name} [{perc:>2}%|{total}]{errmsg}'.format(
index=self.index, name=self.basename, perc=perc,
total=total, errmsg=errmsg))
else:
return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
return ('{index}: {name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
'{down}/{total}]{errmsg}'.format(
name=self.basename, speed=speed, remaining=remaining,
perc=perc, down=down, total=total, errmsg=errmsg))
index=self.index, name=self.basename, speed=speed,
remaining=remaining, perc=perc, down=down,
total=total, errmsg=errmsg))
def _create_fileobj(self):
"""Creates a file object using the internal filename."""
"""Create a file object using the internal filename."""
try:
fileobj = open(self._filename, 'wb')
except OSError as e:
@@ -267,14 +305,16 @@ class DownloadItem(QObject):
else:
self.set_fileobj(fileobj)
def _ask_overwrite_question(self):
def _ask_confirm_question(self, msg):
"""Create a Question object to be asked."""
q = usertypes.Question(self)
q.text = self._filename + " already exists. Overwrite? (y/n)"
q.text = msg
q.mode = usertypes.PromptMode.yesno
q.answered_yes.connect(self._create_fileobj)
q.answered_no.connect(functools.partial(self.cancel, False))
q.cancelled.connect(functools.partial(self.cancel, False))
self.cancelled.connect(q.abort)
self.error.connect(q.abort)
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
message_bridge.ask(q, blocking=False)
@@ -290,7 +330,11 @@ class DownloadItem(QObject):
self.error_msg = msg
self.stats.finish()
self.error.emit(msg)
self.reply.abort()
with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
'problem, this method must only be called '
'once.'):
# See https://codereview.qt-project.org/#/c/107863/
self.reply.abort()
self.reply.deleteLater()
self.reply = None
self.done = True
@@ -310,8 +354,8 @@ class DownloadItem(QObject):
reply.finished.connect(self.on_reply_finished)
reply.error.connect(self.on_reply_error)
reply.readyRead.connect(self.on_ready_read)
self._retry_info = RetryInfo(request=reply.request(),
manager=reply.manager())
self.retry_info = RetryInfo(request=reply.request(),
manager=reply.manager())
if not self.fileobj:
self._read_timer.start()
# We could have got signals before we connected slots to them.
@@ -320,12 +364,19 @@ class DownloadItem(QObject):
if reply.error() != QNetworkReply.NoError:
QTimer.singleShot(0, lambda: self.error.emit(reply.errorString()))
def bg_color(self):
"""Background color to be shown."""
start = config.get('colors', 'downloads.bg.start')
stop = config.get('colors', 'downloads.bg.stop')
system = config.get('colors', 'downloads.bg.system')
error = config.get('colors', 'downloads.bg.error')
def get_status_color(self, position):
"""Choose an appropriate color for presenting the download's status.
Args:
position: The color type requested, can be 'fg' or 'bg'.
"""
# pylint: disable=bad-config-call
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
assert position in ("fg", "bg")
start = config.get('colors', 'downloads.{}.start'.format(position))
stop = config.get('colors', 'downloads.{}.stop'.format(position))
system = config.get('colors', 'downloads.{}.system'.format(position))
error = config.get('colors', 'downloads.{}.error'.format(position))
if self.error_msg is not None:
assert not self.successful
return error
@@ -335,6 +386,7 @@ class DownloadItem(QObject):
return utils.interpolate_color(
start, stop, self.stats.percentage(), system)
@pyqtSlot()
def cancel(self, remove_data=True):
"""Cancel the download.
@@ -351,22 +403,32 @@ class DownloadItem(QObject):
self.reply = None
if self.fileobj is not None:
self.fileobj.close()
try:
if (self._filename is not None and os.path.exists(self._filename)
and remove_data):
os.remove(self._filename)
except OSError:
log.downloads.exception("Failed to remove partial file")
if remove_data:
self.delete()
self.done = True
self.finished.emit()
self.data_changed.emit()
def delete(self):
"""Delete the downloaded file."""
try:
if self._filename is not None and os.path.exists(self._filename):
os.remove(self._filename)
except OSError:
log.downloads.exception("Failed to remove partial file")
@pyqtSlot()
def retry(self):
"""Retry a failed download."""
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
new_reply = self.retry_info.manager.get(self.retry_info.request)
new_download = download_manager.fetch(
new_reply, suggested_filename=self.basename)
self.do_retry.emit(new_download)
self.cancel()
new_reply = self._retry_info.manager.get(self._retry_info.request)
self.do_retry.emit(new_reply)
@pyqtSlot()
def open_file(self):
"""Open the downloaded file."""
assert self.successful
@@ -380,36 +442,57 @@ class DownloadItem(QObject):
filename: The full filename to save the download to.
None: special value to stop the download.
"""
global _last_used_directory
if self.fileobj is not None:
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)
if not self._create_full_filename(filename):
# We only got a filename (without directory) or a relative path
# from the user, so we append that to the default directory and
# try again.
self._create_full_filename(os.path.join(_download_dir(), filename))
_last_used_directory = os.path.dirname(self._filename)
log.downloads.debug("Setting filename to {}".format(filename))
if os.path.isfile(self._filename):
# The file already exists, so ask the user if it should be
# overwritten.
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)):
txt = (self._filename + " already exists and is a special file. "
"Write to this?")
self._ask_confirm_question(txt)
else:
self._create_fileobj()
def _create_full_filename(self, filename):
"""Try to create the full filename.
Return:
True if the full filename was created, False otherwise.
"""
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.
self._filename = os.path.join(filename, self.basename)
return True
elif os.path.isabs(filename):
# We got an absolute filename from the user, so we save it under
# that filename.
self._filename = filename
self.basename = os.path.basename(self._filename)
else:
# We only got a filename (without directory) from the user, so we
# save it under that filename in the default directory.
download_dir = config.get('storage', 'download-directory')
if download_dir is None:
download_dir = standarddir.get(
QStandardPaths.DownloadLocation)
self._filename = os.path.join(download_dir, filename)
self.basename = filename
log.downloads.debug("Setting filename to {}".format(filename))
if os.path.isfile(self._filename):
# The file already exists, so ask the user if it should be
# overwritten.
self._ask_overwrite_question()
else:
self._create_fileobj()
return True
return False
def set_fileobj(self, fileobj):
""""Set the file object to write the download to.
@@ -557,7 +640,11 @@ class DownloadManager(QAbstractListModel):
self._win_id = win_id
self.downloads = []
self.questions = []
self._networkmanager = networkmanager.NetworkManager(win_id, self)
self._networkmanager = networkmanager.NetworkManager(
win_id, None, self)
self._update_timer = usertypes.Timer(self, 'download-update')
self._update_timer.timeout.connect(self.update_gui)
self._update_timer.setInterval(REFRESH_INTERVAL)
def __repr__(self):
return utils.get_repr(self, downloads=len(self.downloads))
@@ -572,30 +659,21 @@ class DownloadManager(QAbstractListModel):
self.questions.append(q)
return q
@cmdutils.register(instance='download-manager', scope='window')
def download(self, url, dest=None):
"""Download a given URL, given as string.
Args:
url: The URL to download
dest: The file path to write the download to, or None to ask.
"""
url = urlutils.qurl_from_user_input(url)
urlutils.raise_cmdexc_if_invalid(url)
self.get(url, filename=dest)
@pyqtSlot()
def update_gui(self):
"""Periodical GUI update of all items."""
assert self.downloads
for dl in self.downloads:
dl.stats.update_speed()
self.dataChanged.emit(self.index(0), self.last_index())
@pyqtSlot('QUrl', 'QWebPage')
def get(self, url, page=None, fileobj=None, filename=None,
auto_remove=False):
def get(self, url, **kwargs):
"""Start a download with a link URL.
Args:
url: The URL to get, as QUrl
page: The QWebPage to get the download from.
fileobj: The file object to write the answer to.
filename: A path to write the data to.
auto_remove: Whether to remove the download even if
ui -> remove-finished-downloads is set to false.
**kwargs: passed to get_request().
Return:
If the download could start immediately, (fileobj/filename given),
@@ -603,25 +681,24 @@ class DownloadManager(QAbstractListModel):
If not, None.
"""
if fileobj is not None and filename is not None:
raise TypeError("Only one of fileobj/filename may be given!")
if not url.isValid():
urlutils.invalid_url_error(self._win_id, url, "start download")
return
req = QNetworkRequest(url)
return self.get_request(req, page, fileobj, filename, auto_remove)
return self.get_request(req, **kwargs)
def get_request(self, request, page=None, fileobj=None, filename=None,
auto_remove=False):
def get_request(self, request, *, fileobj=None, filename=None,
prompt_download_directory=None, **kwargs):
"""Start a download with a QNetworkRequest.
Args:
request: The QNetworkRequest to download.
page: The QWebPage to use.
fileobj: The file object to write the answer to.
filename: A path to write the data to.
auto_remove: Whether to remove the download even if
ui -> remove-finished-downloads is set to false.
prompt_download_directory: Whether to prompt for the download dir
or automatically download. If None, the
config is used.
**kwargs: Passed to fetch_request.
Return:
If the download could start immediately, (fileobj/filename given),
@@ -632,33 +709,47 @@ class DownloadManager(QAbstractListModel):
if fileobj is not None and filename is not None:
raise TypeError("Only one of fileobj/filename may be given!")
# WORKAROUND for Qt corrupting data loaded from cache:
# https://bugreports.qt-project.org/browse/QTBUG-42757
# https://bugreports.qt.io/browse/QTBUG-42757
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
QNetworkRequest.AlwaysNetwork)
suggested_fn = urlutils.filename_from_url(request.url())
if prompt_download_directory is None:
prompt_download_directory = config.get(
'storage', 'prompt-download-directory')
if not prompt_download_directory and not fileobj:
filename = config.get('storage', 'download-directory')
if fileobj is not None or filename is not None:
return self.fetch_request(request, filename, fileobj, page,
auto_remove)
return self.fetch_request(request,
fileobj=fileobj,
filename=filename,
suggested_filename=suggested_fn,
**kwargs)
if suggested_fn is None:
suggested_fn = 'qutebrowser-download'
else:
encoding = sys.getfilesystemencoding()
suggested_fn = utils.force_encoding(suggested_fn, encoding)
q = self._prepare_question()
q.default = urlutils.filename_from_url(request.url())
q.default = _path_suggestion(suggested_fn)
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
q.answered.connect(
lambda fn: self.fetch_request(request, filename=fn, page=page,
auto_remove=auto_remove))
lambda fn: self.fetch_request(request,
filename=fn,
suggested_filename=suggested_fn,
**kwargs))
message_bridge.ask(q, blocking=False)
return None
def fetch_request(self, request, page=None, fileobj=None, filename=None,
auto_remove=False):
def fetch_request(self, request, *, page=None, **kwargs):
"""Download a QNetworkRequest to disk.
Args:
request: The QNetworkRequest to download.
page: The QWebPage to use.
fileobj: The file object to write the answer to.
filename: A path to write the data to.
auto_remove: Whether to remove the download even if
ui -> remove-finished-downloads is set to false.
**kwargs: passed to fetch().
Return:
The created DownloadItem.
@@ -668,10 +759,11 @@ class DownloadManager(QAbstractListModel):
else:
nam = page.networkAccessManager()
reply = nam.get(request)
return self.fetch(reply, fileobj, filename, auto_remove)
return self.fetch(reply, **kwargs)
@pyqtSlot('QNetworkReply')
def fetch(self, reply, fileobj=None, filename=None, auto_remove=False):
def fetch(self, reply, *, fileobj=None, filename=None, auto_remove=False,
suggested_filename=None, prompt_download_directory=None):
"""Download a QNetworkReply to disk.
Args:
@@ -686,12 +778,13 @@ class DownloadManager(QAbstractListModel):
"""
if fileobj is not None and filename is not None:
raise TypeError("Only one of fileobj/filename may be given!")
if filename is not None:
suggested_filename = os.path.basename(filename)
elif fileobj is not None and getattr(fileobj, 'name', None):
suggested_filename = fileobj.name
else:
_inline, suggested_filename = http.parse_content_disposition(reply)
if not suggested_filename:
if filename is not None:
suggested_filename = os.path.basename(filename)
elif fileobj is not None and getattr(fileobj, 'name', None):
suggested_filename = fileobj.name
else:
_, suggested_filename = http.parse_content_disposition(reply)
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
suggested_filename))
download = DownloadItem(reply, self._win_id, self)
@@ -705,13 +798,21 @@ class DownloadManager(QAbstractListModel):
download.error.connect(self.on_error)
download.redirected.connect(
functools.partial(self.on_redirect, download))
download.do_retry.connect(self.fetch)
download.basename = suggested_filename
idx = len(self.downloads) + 1
download.index = idx
self.beginInsertRows(QModelIndex(), idx, idx)
self.downloads.append(download)
self.endInsertRows()
if not self._update_timer.isActive():
self._update_timer.start()
prompt_download_directory = config.get('storage',
'prompt-download-directory')
if not prompt_download_directory and not fileobj:
filename = config.get('storage', 'download-directory')
if filename is not None:
download.set_filename(filename)
elif fileobj is not None:
@@ -719,7 +820,7 @@ class DownloadManager(QAbstractListModel):
download.autoclose = False
else:
q = self._prepare_question()
q.default = suggested_filename
q.default = _path_suggestion(suggested_filename)
q.answered.connect(download.set_filename)
q.cancelled.connect(download.cancel)
download.cancelled.connect(q.abort)
@@ -730,20 +831,82 @@ class DownloadManager(QAbstractListModel):
return download
@cmdutils.register(instance='download-manager', scope='window')
def cancel_download(self, count: {'special': 'count'}=1):
def raise_no_download(self, count):
"""Raise an exception that the download doesn't exist.
Args:
count: The index of the download
"""
if not count:
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')
def download_cancel(self, count=0):
"""Cancel the last/[count]th download.
Args:
count: The index of the download to cancel.
"""
try:
download = self.downloads[count - 1]
except IndexError:
self.raise_no_download(count)
if download.done:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is already done!"
.format(count))
download.cancel()
@cmdutils.register(instance='download-manager', scope='window',
count='count')
def download_delete(self, count=0):
"""Delete the last/[count]th download from disk.
Args:
count: The index of the download to cancel.
"""
try:
download = self.downloads[count - 1]
except IndexError:
self.raise_no_download(count)
if not download.successful:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is not done!".format(count))
download.delete()
self.remove_item(download)
@cmdutils.register(instance='download-manager', scope='window',
deprecated="Use :download-cancel instead.",
count='count')
def cancel_download(self, count=1):
"""Cancel the first/[count]th download.
Args:
count: The index of the download to cancel.
"""
if count == 0:
return
self.download_cancel(count)
@cmdutils.register(instance='download-manager', scope='window',
count='count')
def download_open(self, count=0):
"""Open the last/[count]th download.
Args:
count: The index of the download to cancel.
"""
try:
download = self.downloads[count - 1]
except IndexError:
raise cmdexc.CommandError("There's no download {}!".format(count))
download.cancel()
self.raise_no_download(count)
if not download.successful:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is not done!".format(count))
download.open_file()
@pyqtSlot(QNetworkRequest, QNetworkReply)
def on_redirect(self, download, request, reply):
@@ -785,21 +948,44 @@ class DownloadManager(QAbstractListModel):
Return:
A boolean.
"""
assert nam.adopted_downloads == 0
for download in self.downloads:
if download.reply is not None and download.reply.manager() is nam:
return True
return False
running_download = (download.reply is not None and
download.reply.manager() is nam)
# user could request retry after tab is closed.
failed_download = (download.done and (not download.successful) and
download.retry_info.manager is nam)
if running_download or failed_download:
nam.adopt_download(download)
return nam.adopted_downloads
def can_clear(self):
"""Check if there are finished downloads to clear."""
if self.downloads:
return any(download.done for download in self.downloads)
else:
return False
return any(download.done for download in self.downloads)
def clear(self):
"""Remove all finished downloads."""
self.remove_items(d for d in self.downloads if d.done)
@cmdutils.register(instance='download-manager', scope='window',
count='count')
def download_remove(self, all_=False, count=0):
"""Remove the last/[count]th download from the list.
Args:
all_: If given removes all finished downloads.
count: The index of the download to cancel.
"""
if all_:
finished_items = [d for d in self.downloads if d.done]
self.remove_items(finished_items)
else:
try:
download = self.downloads[count - 1]
except IndexError:
self.raise_no_download(count)
if not download.done:
if not count:
count = len(self.downloads)
raise cmdexc.CommandError("Download {} is not done!"
.format(count))
self.remove_item(download)
def last_index(self):
"""Get the last index in the model.
@@ -821,6 +1007,9 @@ class DownloadManager(QAbstractListModel):
del self.downloads[idx]
self.endRemoveRows()
download.deleteLater()
self.update_indexes()
if not self.downloads:
self._update_timer.stop()
def remove_items(self, downloads):
"""Remove an iterable of downloads."""
@@ -849,6 +1038,20 @@ class DownloadManager(QAbstractListModel):
else:
download.deleteLater()
self.endRemoveRows()
if not self.downloads:
self._update_timer.stop()
def update_indexes(self):
"""Update indexes of all DownloadItems."""
first_idx = None
for i, d in enumerate(self.downloads, 1):
if first_idx is None and d.index != i:
first_idx = i - 1
d.index = i
if first_idx is not None:
model_idx = self.index(first_idx, 0)
qtutils.ensure_valid(model_idx)
self.dataChanged.emit(model_idx, self.last_index())
def headerData(self, section, orientation, role):
"""Simple constant header."""
@@ -868,9 +1071,9 @@ class DownloadManager(QAbstractListModel):
if role == Qt.DisplayRole:
data = str(item)
elif role == Qt.ForegroundRole:
data = config.get('colors', 'downloads.fg')
data = item.get_status_color('fg')
elif role == Qt.BackgroundRole:
data = item.bg_color()
data = item.get_status_color('bg')
elif role == ModelRole.item:
data = item
elif role == Qt.ToolTipRole:
@@ -886,7 +1089,7 @@ class DownloadManager(QAbstractListModel):
"""Override flags so items aren't selectable.
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable."""
return Qt.ItemIsEnabled
return Qt.ItemIsEnabled | Qt.ItemNeverHasChildren
def rowCount(self, parent=QModelIndex()):
"""Get count of active downloads."""

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -31,9 +31,7 @@ from qutebrowser.utils import qtutils, utils, objreg
def update_geometry(obj):
"""WORKAROUND
This is a horrible workaround for some weird PyQt bug (probably).
"""Weird WORKAROUND for some weird PyQt bug (probably).
This actually should be a method of DownloadView, but for some reason the
rowsInserted/rowsRemoved signals don't get disconnected from this method
@@ -44,7 +42,6 @@ def update_geometry(obj):
Original bug: https://github.com/The-Compiler/qutebrowser/issues/167
Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171
"""
def _update_geometry():
"""Actually update the geometry if the object still exists."""
if sip.isdeleted(obj):
@@ -126,7 +123,7 @@ class DownloadView(QListView):
Return:
A list of either:
- (QAction, callable) tuples.
- (None, None) for a seperator
- (None, None) for a separator
"""
actions = []
if item is None:
@@ -142,7 +139,8 @@ class DownloadView(QListView):
actions.append(("Cancel", item.cancel))
if self.model().can_clear():
actions.append((None, None))
actions.append(("Remove all finished", self.model().clear))
actions.append(("Remove all finished", functools.partial(
self.model().download_remove, True)))
return actions
@pyqtSlot('QPoint')

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,28 +21,30 @@
import math
import functools
import subprocess
import collections
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl,
QTimer)
from PyQt5.QtGui import QMouseEvent, QClipboard
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKit import QWebElement
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
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
from qutebrowser.misc import guiprocess
ElemTuple = collections.namedtuple('ElemTuple', ['elem', 'label'])
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_bg', 'window', 'yank',
'yank_primary', 'run', 'fill', 'hover',
'rapid', 'rapid_win', 'download',
'userscript', 'spawn'])
Target = usertypes.enum('Target', ['normal', 'tab', 'tab_fg', 'tab_bg',
'window', 'yank', 'yank_primary', 'run',
'fill', 'hover', 'rapid', 'rapid_win',
'download', 'userscript', 'spawn'])
@pyqtSlot(usertypes.KeyMode)
@@ -60,10 +62,10 @@ class HintContext:
frames: The QWebFrames to use.
destroyed_frames: id()'s of QWebFrames which have been destroyed.
(Workaround for https://github.com/The-Compiler/qutebrowser/issues/152)
elems: A mapping from keystrings to (elem, label) namedtuples.
elems: A mapping from key strings to (elem, label) namedtuples.
baseurl: The URL of the current page.
target: What to do with the opened links.
normal/tab/tab_bg/window: Get passed to BrowserTab.
normal/tab/tab_fg/tab_bg/window: Get passed to BrowserTab.
yank/yank_primary: Yank to clipboard/primary selection.
run: Run a command.
fill: Fill commandline with link.
@@ -73,6 +75,9 @@ class HintContext:
spawn: Spawn a simple command.
to_follow: The link to follow when enter is pressed.
args: Custom arguments for userscript/spawn
rapid: Whether to do rapid hinting.
mainframe: The main QWebFrame where we started hinting in.
group: The group of web elements to hint.
"""
def __init__(self):
@@ -80,9 +85,12 @@ class HintContext:
self.target = None
self.baseurl = None
self.to_follow = None
self.rapid = False
self.frames = []
self.destroyed_frames = []
self.args = []
self.mainframe = None
self.group = None
def get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL."""
@@ -108,28 +116,30 @@ class HintManager(QObject):
Signals:
mouse_event: Mouse event to be posted in the web view.
arg: A QMouseEvent
set_open_target: Set a new target to open the links in.
start_hinting: Emitted when hinting starts, before a link is clicked.
arg: The ClickTarget to use.
stop_hinting: Emitted after a link was clicked.
"""
HINT_TEXTS = {
Target.normal: "Follow hint...",
Target.tab: "Follow hint in new tab...",
Target.tab_bg: "Follow hint in background tab...",
Target.window: "Follow hint in new window...",
Target.yank: "Yank hint to clipboard...",
Target.yank_primary: "Yank hint to primary selection...",
Target.run: "Run a command on a hint...",
Target.fill: "Set hint in commandline...",
Target.hover: "Hover over a hint...",
Target.rapid: "Follow hint (rapid mode)...",
Target.rapid_win: "Follow hint in new window (rapid mode)...",
Target.download: "Download hint...",
Target.userscript: "Call userscript via hint...",
Target.spawn: "Spawn command via hint...",
Target.normal: "Follow hint",
Target.tab: "Follow hint in new tab",
Target.tab_fg: "Follow hint in foreground tab",
Target.tab_bg: "Follow hint in background tab",
Target.window: "Follow hint in new window",
Target.yank: "Yank hint to clipboard",
Target.yank_primary: "Yank hint to primary selection",
Target.run: "Run a command on a hint",
Target.fill: "Set hint in commandline",
Target.hover: "Hover over a hint",
Target.download: "Download hint",
Target.userscript: "Call userscript via hint",
Target.spawn: "Spawn command via hint",
}
mouse_event = pyqtSignal('QMouseEvent')
set_open_target = pyqtSignal(str)
start_hinting = pyqtSignal(usertypes.ClickTarget)
stop_hinting = pyqtSignal()
def __init__(self, win_id, tab_id, parent=None):
"""Constructor."""
@@ -141,6 +151,14 @@ class HintManager(QObject):
window=win_id)
mode_manager.left.connect(self.on_mode_left)
def _get_text(self):
"""Get a hint text based on the current context."""
text = self.HINT_TEXTS[self._context.target]
if self._context.rapid:
text += ' (rapid mode)'
text += '...'
return text
def _cleanup(self):
"""Clean up after hinting."""
for elem in self._context.elems.values():
@@ -164,7 +182,7 @@ class HintManager(QObject):
# See # https://github.com/The-Compiler/qutebrowser/issues/263
pass
log.hints.debug("Disconnected.")
text = self.HINT_TEXTS[self._context.target]
text = self._get_text()
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
message_bridge.maybe_reset_text(text)
@@ -185,14 +203,32 @@ class HintManager(QObject):
chars = '0123456789'
else:
chars = config.get('hints', 'chars')
min_chars = config.get('hints', 'min-chars')
if config.get('hints', 'scatter'):
return self._hint_scattered(min_chars, chars, elems)
else:
return self._hint_linear(min_chars, chars, elems)
def _hint_scattered(self, min_chars, chars, elems):
"""Produce scattered hint labels with variable length (like Vimium).
Args:
min_chars: The minimum length of labels.
chars: The alphabet to use for labels.
elems: The elements to generate labels for.
"""
# Determine how many digits the link hints will require in the worst
# case. Usually we do not need all of these digits for every link
# single hint, so we can show shorter hints for a few of the links.
needed = math.ceil(math.log(len(elems), len(chars)))
needed = max(min_chars, math.ceil(math.log(len(elems), len(chars))))
# Short hints are the number of hints we can possibly show which are
# (needed - 1) digits in length.
short_count = math.floor((len(chars) ** needed - len(elems)) /
len(chars))
if needed > min_chars:
short_count = math.floor((len(chars) ** needed - len(elems)) /
len(chars))
else:
short_count = 0
long_count = len(elems) - short_count
strings = []
@@ -207,6 +243,20 @@ class HintManager(QObject):
return self._shuffle_hints(strings, len(chars))
def _hint_linear(self, min_chars, chars, elems):
"""Produce linear hint labels with constant length (like dwb).
Args:
min_chars: The minimum length of labels.
chars: The alphabet to use for labels.
elems: The elements to generate labels for.
"""
strings = []
needed = max(min_chars, math.ceil(math.log(len(elems), len(chars))))
for i in range(len(elems)):
strings.append(self._number_to_hint_str(i, chars, needed))
return strings
def _shuffle_hints(self, hints, length):
"""Shuffle the given set of hints so that they're scattered.
@@ -267,6 +317,14 @@ class HintManager(QObject):
display = elem.styleProperty('display', QWebElement.InlineStyle)
return display == 'none'
def _show_elem(self, elem):
"""Show a given element."""
elem.setStyleProperty('display', 'inline !important')
def _hide_elem(self, elem):
"""Hide a given element."""
elem.setStyleProperty('display', 'none !important')
def _set_style_properties(self, elem, label):
"""Set the hint CSS on the element given.
@@ -275,23 +333,23 @@ class HintManager(QObject):
label: The label QWebElement.
"""
attrs = [
('display', 'inline'),
('z-index', '100000'),
('pointer-events', 'none'),
('position', 'absolute'),
('color', config.get('colors', 'hints.fg')),
('background', config.get('colors', 'hints.bg')),
('font', config.get('fonts', 'hints')),
('border', config.get('hints', 'border')),
('opacity', str(config.get('hints', 'opacity'))),
('display', 'inline !important'),
('z-index', '{} !important'.format(int(2 ** 32 / 2 - 1))),
('pointer-events', 'none !important'),
('position', 'absolute !important'),
('color', config.get('colors', 'hints.fg') + ' !important'),
('background', config.get('colors', 'hints.bg') + ' !important'),
('font', config.get('fonts', 'hints') + ' !important'),
('border', config.get('hints', 'border') + ' !important'),
('opacity', str(config.get('hints', 'opacity')) + ' !important'),
]
# Make text uppercase if set in config
if (config.get('hints', 'uppercase') and
config.get('hints', 'mode') == 'letter'):
attrs.append(('texttransform', 'uppercase'))
attrs.append(('text-transform', 'uppercase !important'))
else:
attrs.append(('texttransform', 'none'))
attrs.append(('text-transform', 'none !important'))
for k, v in attrs:
label.setStyleProperty(k, v)
@@ -313,8 +371,8 @@ class HintManager(QObject):
top /= zoom
log.hints.vdebug("Drawing label '{!r}' at {}/{} for element '{!r}', "
"zoom level {}".format(label, left, top, elem, zoom))
label.setStyleProperty('left', '{}px'.format(left))
label.setStyleProperty('top', '{}px'.format(top))
label.setStyleProperty('left', '{}px !important'.format(left))
label.setStyleProperty('top', '{}px !important'.format(top))
def _draw_label(self, elem, string):
"""Draw a hint label over an element.
@@ -344,6 +402,11 @@ class HintManager(QObject):
label.setPlainText(string)
return label
def _show_url_error(self):
"""Show an error because no link was found."""
message.error(self._win_id, "No suitable link found for this element.",
immediately=True)
def _click(self, elem, context):
"""Click an element.
@@ -351,40 +414,57 @@ class HintManager(QObject):
elem: The QWebElement to click.
context: The HintContext to use.
"""
if context.target == Target.rapid:
target = Target.tab_bg
elif context.target == Target.rapid_win:
target = Target.window
target_mapping = {
Target.rapid: usertypes.ClickTarget.tab_bg,
Target.rapid_win: usertypes.ClickTarget.window,
Target.normal: usertypes.ClickTarget.normal,
Target.tab_fg: usertypes.ClickTarget.tab,
Target.tab_bg: usertypes.ClickTarget.tab_bg,
Target.window: usertypes.ClickTarget.window,
Target.hover: usertypes.ClickTarget.normal,
}
if config.get('tabs', 'background-tabs'):
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
else:
target = context.target
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()
action = "Hovering" if target == Target.hover else "Clicking"
action = "Hovering" if context.target == Target.hover else "Clicking"
log.hints.debug("{} on '{}' at {}/{}".format(
action, elem, pos.x(), pos.y()))
self.start_hinting.emit(target_mapping[context.target])
if context.target in [Target.tab, Target.tab_fg, Target.tab_bg,
Target.window, Target.rapid, Target.rapid_win]:
modifiers = Qt.ControlModifier
else:
modifiers = Qt.NoModifier
events = [
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
Qt.NoModifier),
]
if target != Target.hover:
self.set_open_target.emit(target.name)
if context.target != Target.hover:
events += [
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
Qt.NoButton, Qt.NoModifier),
Qt.LeftButton, modifiers),
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
Qt.NoButton, Qt.NoModifier),
Qt.NoButton, modifiers),
]
for evt in events:
self.mouse_event.emit(evt)
if elem.is_text_input() and elem.is_editable():
QTimer.singleShot(0, functools.partial(
elem.webFrame().page().triggerAction,
QWebPage.MoveToEndOfDocument))
QTimer.singleShot(0, self.stop_hinting.emit)
def _yank(self, url, context):
"""Yank an element to the clipboard or primary selection.
Args:
url: The URL to open as a QURL.
url: The URL to open as a QUrl.
context: The HintContext to use.
"""
sel = context.target == Target.yank_primary
@@ -432,24 +512,38 @@ class HintManager(QObject):
"""
url = self._resolve_url(elem, context.baseurl)
if url is None:
message.error(self._win_id,
"No suitable link found for this element.",
immediately=True)
self._show_url_error()
return
if context.rapid:
prompt = False
else:
prompt = None
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
download_manager.get(url, elem.webFrame().page())
download_manager.get(url, page=elem.webFrame().page(),
prompt_download_directory=prompt)
def _call_userscript(self, url, context):
"""Call an userscript from a hint.
def _call_userscript(self, elem, context):
"""Call a userscript from a hint.
Args:
url: The URL to open as a QUrl.
elem: The QWebElement to use in the userscript.
context: The HintContext to use.
"""
cmd = context.args[0]
args = context.args[1:]
userscripts.run(cmd, *args, url=url, win_id=self._win_id)
frame = context.mainframe
env = {
'QUTE_MODE': 'hints',
'QUTE_SELECTED_TEXT': str(elem),
'QUTE_SELECTED_HTML': elem.toOuterXml(),
}
url = self._resolve_url(elem, context.baseurl)
if url is not None:
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
env.update(userscripts.store_source(frame))
userscripts.run(cmd, *args, win_id=self._win_id, env=env)
def _spawn(self, url, context):
"""Spawn a simple command from a hint.
@@ -460,11 +554,9 @@ class HintManager(QObject):
"""
urlstr = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
args = context.get_args(urlstr)
try:
subprocess.Popen(args)
except OSError as e:
msg = "Error while spawning command: {}".format(e)
message.error(self._win_id, msg, immediately=True)
cmd, *args = args
proc = guiprocess.GUIProcess(self._win_id, what='command', parent=self)
proc.start(cmd, args)
def _resolve_url(self, elem, baseurl):
"""Resolve a URL and check if we want to keep it.
@@ -510,13 +602,16 @@ class HintManager(QObject):
# Then check for regular links/buttons.
elems = frame.findAllElements(
webelem.SELECTORS[webelem.Group.prevnext])
elems = [webelem.WebElementWrapper(e) for e in elems]
filterfunc = webelem.FILTERS[webelem.Group.prevnext]
elems = [e for e in elems if filterfunc(e)]
option = 'prev-regexes' if prev else 'next-regexes'
if not elems:
return None
for regex in config.get('hints', option):
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
for e in elems:
e = webelem.WebElementWrapper(e)
text = str(e)
if not text:
continue
@@ -554,21 +649,17 @@ class HintManager(QObject):
raise cmdexc.CommandError(
"'args' is only allowed with target userscript/spawn.")
def _init_elements(self, mainframe, group):
"""Initialize the elements and labels based on the context set.
Args:
mainframe: The main QWebFrame.
group: A Group enum member (which elements to find).
"""
def _init_elements(self):
"""Initialize the elements and labels based on the context set."""
elems = []
for f in self._context.frames:
elems += f.findAllElements(webelem.SELECTORS[group])
elems = [e for e in elems if webelem.is_visible(e, mainframe)]
elems += f.findAllElements(webelem.SELECTORS[self._context.group])
elems = [e for e in elems
if webelem.is_visible(e, self._context.mainframe)]
# We wrap the elements late for performance reasons, as wrapping 1000s
# of elements (with ~50 methods each) just takes too much time...
elems = [webelem.WebElementWrapper(e) for e in elems]
filterfunc = webelem.FILTERS.get(group, lambda e: True)
filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
if not elems:
raise cmdexc.CommandError("No elements found.")
@@ -593,6 +684,7 @@ class HintManager(QObject):
background: True to open in a background tab.
window: True to open in a new window, False for the current one.
"""
from qutebrowser.mainwindow import mainwindow
elem = self._find_prevnext(frame, prev)
if elem is None:
raise cmdexc.CommandError("No {} links found!".format(
@@ -603,11 +695,10 @@ class HintManager(QObject):
"prev" if prev else "forward"))
qtutils.ensure_valid(url)
if window:
main_window = objreg.get('main-window', scope='window',
window=self._win_id)
win_id = main_window.spawn()
new_window = mainwindow.MainWindow()
new_window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
window=new_window.win_id)
tabbed_browser.tabopen(url, background=False)
elif tab:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
@@ -618,12 +709,16 @@ class HintManager(QObject):
tab=self._tab_id)
webview.openurl(url)
@cmdutils.register(instance='hintmanager', scope='tab', name='hint')
def start(self, group=webelem.Group.all, target=Target.normal,
*args: {'nargs': '*'}):
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
win_id='win_id')
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
*args: {'nargs': '*'}, win_id):
"""Start hinting.
Args:
rapid: Whether to do rapid hinting. This is only possible with
targets `tab` (with background-tabs=true), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
group: The hinting mode to use.
- `all`: All clickable elements.
@@ -633,7 +728,9 @@ class HintManager(QObject):
target: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `tab`: Open the link in a new tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
- `tab-fg`: Open the link in a new foreground tab.
- `tab-bg`: Open the link in a new background tab.
- `window`: Open the link in a new window.
- `hover` : Hover over the link.
@@ -642,11 +739,8 @@ class HintManager(QObject):
- `run`: Run the argument as command.
- `fill`: Fill the commandline with the command given as
argument.
- `rapid`: Open the link in a new tab and stay in hinting mode.
- `rapid-win`: Open the link in a new window and stay in
hinting mode.
- `download`: Download the link.
- `userscript`: Call an userscript with `$QUTE_URL` set to the
- `userscript`: Call a userscript with `$QUTE_URL` set to the
link.
- `spawn`: Spawn a command.
@@ -672,11 +766,29 @@ class HintManager(QObject):
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if mode_manager.mode == usertypes.KeyMode.hint:
raise cmdexc.CommandError("Already hinting!")
modeman.leave(win_id, usertypes.KeyMode.hint, 're-hinting')
if rapid:
if target in [Target.tab_bg, Target.window, Target.run,
Target.hover, Target.userscript, Target.spawn,
Target.download]:
pass
elif (target == Target.tab and
config.get('tabs', 'background-tabs')):
pass
else:
name = target.name.replace('_', '-')
raise cmdexc.CommandError("Rapid hinting makes no sense with "
"target {}!".format(name))
self._check_args(target, *args)
self._context = HintContext()
self._context.target = target
self._context.baseurl = tabbed_browser.current_url()
self._context.rapid = rapid
try:
self._context.baseurl = tabbed_browser.current_url()
except qtutils.QtValueError:
raise cmdexc.CommandError("No URL set for this page yet!")
self._context.frames = webelem.get_child_frames(mainframe)
for frame in self._context.frames:
# WORKAROUND for
@@ -684,14 +796,40 @@ class HintManager(QObject):
frame.destroyed.connect(functools.partial(
self._context.destroyed_frames.append, id(frame)))
self._context.args = args
self._init_elements(mainframe, group)
self._context.mainframe = mainframe
self._context.group = group
self._handle_old_rapid_targets(win_id)
self._init_elements()
message_bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
message_bridge.set_text(self.HINT_TEXTS[target])
message_bridge.set_text(self._get_text())
self._connect_frame_signals()
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
def _handle_old_rapid_targets(self, win_id):
"""Switch to the new way for rapid hinting with a rapid target.
Args:
win_id: The window ID to display the warning in.
DEPRECATED.
"""
old_rapid_targets = {
Target.rapid: Target.tab_bg,
Target.rapid_win: Target.window,
}
target = self._context.target
if target in old_rapid_targets:
self._context.target = old_rapid_targets[target]
self._context.rapid = True
name = target.name.replace('_', '-')
group_name = self._context.group.name.replace('_', '-')
new_name = self._context.target.name.replace('_', '-')
message.warning(
win_id, ':hint with target {} is deprecated, use :hint '
'--rapid {} {} instead!'.format(name, group_name, new_name))
def handle_partial_key(self, keystr):
"""Handle a new partial keypress."""
log.hints.debug("Handling new keystring: '{}'".format(keystr))
@@ -705,11 +843,11 @@ class HintManager(QObject):
'<font color="{}">{}</font>{}'.format(
match_color, matched, rest))
if self._is_hidden(elems.label):
# hidden element which matches again -> unhide it
elems.label.setStyleProperty('display', 'inline')
# hidden element which matches again -> show it
self._show_elem(elems.label)
else:
# element doesn't match anymore -> hide it
elems.label.setStyleProperty('display', 'none')
self._hide_elem(elems.label)
except webelem.IsNullError:
pass
@@ -724,17 +862,20 @@ class HintManager(QObject):
if (filterstr is None or
str(elems.elem).lower().startswith(filterstr)):
if self._is_hidden(elems.label):
# hidden element which matches again -> unhide it
elems.label.setStyleProperty('display', 'inline')
# hidden element which matches again -> show it
self._show_elem(elems.label)
else:
# element doesn't match anymore -> hide it
elems.label.setStyleProperty('display', 'none')
self._hide_elem(elems.label)
except webelem.IsNullError:
pass
visible = {}
for k, e in self._context.elems.items():
if not self._is_hidden(e.label):
visible[k] = e
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')
@@ -757,13 +898,13 @@ class HintManager(QObject):
elem_handlers = {
Target.normal: self._click,
Target.tab: self._click,
Target.tab_fg: self._click,
Target.tab_bg: self._click,
Target.window: self._click,
Target.rapid: self._click,
Target.rapid_win: self._click,
Target.hover: self._click,
# _download needs a QWebElement to get the frame.
Target.download: self._download,
Target.userscript: self._call_userscript,
}
# Handlers which take a QUrl
url_handlers = {
@@ -771,25 +912,26 @@ class HintManager(QObject):
Target.yank_primary: self._yank,
Target.run: self._run_cmd,
Target.fill: self._preset_cmd_text,
Target.userscript: self._call_userscript,
Target.spawn: self._spawn,
}
elem = self._context.elems[keystr].elem
if elem.webFrame() is None:
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)
elif self._context.target in url_handlers:
url = self._resolve_url(elem, self._context.baseurl)
if url is None:
message.error(self._win_id,
"No suitable link found for this element.",
immediately=True)
self._show_url_error()
return
handler = functools.partial(
url_handlers[self._context.target], url, self._context)
else:
raise ValueError("No suitable handler found!")
if self._context.target not in (Target.rapid, Target.rapid_win):
if not self._context.rapid:
modeman.maybe_leave(self._win_id, usertypes.KeyMode.hint,
'followed')
else:

View File

@@ -0,0 +1,216 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Simple history which gets written to disk."""
import time
import collections
from PyQt5.QtCore import pyqtSignal, QUrl
from PyQt5.QtWebKit import QWebHistoryInterface
from qutebrowser.utils import utils, objreg, standarddir, log
from qutebrowser.config import config
from qutebrowser.misc import lineparser
class HistoryEntry:
"""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.
"""
def __init__(self, atime, url):
self.atime = float(atime)
self.url = QUrl(url)
self.url_string = url
def __repr__(self):
return utils.get_repr(self, constructor=True, atime=self.atime,
url=self.url.toDisplayString())
def __str__(self):
return '{} {}'.format(int(self.atime), self.url_string)
@classmethod
def from_str(cls, s):
"""Get a history based on a 'TIME URL' string."""
return cls(*s.split(' ', maxsplit=1))
class WebHistory(QWebHistoryInterface):
"""A QWebHistoryInterface which supports being written to disk.
Attributes:
_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.
_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.
_temp_history: OrderedDict of temporary history entries before
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 = pyqtSignal(HistoryEntry)
item_added = pyqtSignal(HistoryEntry)
async_read_done = pyqtSignal()
def __init__(self, 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._temp_history = collections.OrderedDict()
self._new_history = []
self._saved_count = 0
objreg.get('save-manager').add_saveable(
'history', self.save, self.item_added)
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())
def __len__(self):
return len(self._history_dict)
def async_read(self):
"""Read the initial history."""
if self._initial_read_started:
log.init.debug("Ignoring async_read() because reading is started.")
return
self._initial_read_started = True
if standarddir.data() is None:
self._initial_read_done = True
self.async_read_done.emit()
return
with self._lineparser.open():
for line in self._lineparser:
yield
data = line.rstrip().split(maxsplit=1)
if not data:
# empty line
continue
elif len(data) != 2:
# other malformed line
log.init.warning("Invalid history entry {!r}!".format(
line))
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
# 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)
self._add_entry(entry)
self.add_completion_item.emit(entry)
def _add_entry(self, entry, target=None):
"""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)
def get_recent(self):
"""Get the most recent history entries."""
old = self._lineparser.get_recent()
return old + [str(e) for e in self._new_history]
def save(self):
"""Save the history to disk."""
new = (str(e) for e in self._new_history[self._saved_count:])
self._lineparser.new_data = new
self._lineparser.save()
self._saved_count = len(self._new_history)
def addHistoryEntry(self, url_string):
"""Called by WebKit when an URL should be added to the history.
Args:
url_string: An url as string to add to the history.
"""
if not url_string:
return
if config.get('general', 'private-browsing'):
return
entry = HistoryEntry(time.time(), url_string)
if self._initial_read_done:
self.add_completion_item.emit(entry)
self._new_history.append(entry)
self._add_entry(entry)
self.item_added.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.
Args:
parent: The parent to use for WebHistory.
"""
history = WebHistory(parent)
objreg.register('web-history', history)
QWebHistoryInterface.setDefaultInterface(history)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Other utilities which don't fit anywhere else. """
"""Parsing functions for various HTTP headers."""
import os.path
@@ -46,16 +46,19 @@ def parse_content_disposition(reply):
# We use the unsafe variant of the filename as we sanitize it via
# os.path.basename later.
try:
content_disposition = rfc6266.parse_headers(
bytes(reply.rawHeader(content_disposition_header)))
value = bytes(reply.rawHeader(content_disposition_header))
log.rfc6266.debug("Parsing Content-Disposition: {}".format(value))
content_disposition = rfc6266.parse_headers(value)
filename = content_disposition.filename()
except UnicodeDecodeError:
log.misc.exception("Error while decoding filename")
except (SyntaxError, UnicodeDecodeError, rfc6266.Error):
log.rfc6266.exception("Error while parsing filename")
else:
is_inline = content_disposition.is_inline()
# Then try to get filename from url
if not filename:
filename = reply.url().path()
path = reply.url().path()
if path is not None:
filename = path.rstrip('/')
# If that fails as well, use a fallback
if not filename:
filename = 'qutebrowser-download'

View File

@@ -0,0 +1,61 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Customized QWebInspector."""
import base64
import binascii
from PyQt5.QtWebKitWidgets import QWebInspector
from qutebrowser.utils import log, objreg
class WebInspector(QWebInspector):
"""A customized WebInspector which stores its geometry."""
def __init__(self, parent=None):
super().__init__(parent)
self._load_state_geometry()
def closeEvent(self, e):
"""Save the geometry when closed."""
state_config = objreg.get('state-config')
data = bytes(self.saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
state_config['geometry']['inspector'] = geom
super().closeEvent(e)
def _load_state_geometry(self):
"""Load the geometry from the state file."""
state_config = objreg.get('state-config')
try:
data = state_config['geometry']['inspector']
geom = base64.b64decode(data, validate=True)
except KeyError:
# First start
pass
except binascii.Error:
log.misc.exception("Error while reading geometry")
else:
log.init.debug("Loading geometry from {}".format(geom))
ok = self.restoreGeometry(geom)
if not ok:
log.init.warning("Error while loading geometry.")

View File

@@ -0,0 +1,128 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Antoni Boucher (antoyo) <bouanto@zoho.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
#
# pylint complains when using .render() on jinja templates, so we make it shut
# up for this whole module.
"""Handler functions for file:... pages."""
import os
from PyQt5.QtCore import QUrl
from qutebrowser.browser.network import schemehandler, networkreply
from qutebrowser.utils import utils, jinja
def get_file_list(basedir, all_files, filterfunc):
"""Get a list of files filtered by a filter function and sorted by name.
Args:
basedir: The parent directory of all files.
all_files: The list of files to filter and sort.
filterfunc: The filter function.
Return:
A list of dicts. Each dict contains the name and absname keys.
"""
items = []
for filename in all_files:
absname = os.path.join(basedir, filename)
if filterfunc(absname):
items.append({'name': filename, 'absname': absname})
return sorted(items, key=lambda v: v['name'].lower())
def is_root(directory):
"""Check if the directory is the root directory.
Args:
directory: The directory to check.
Return:
Whether the directory is a root directory or not.
"""
return os.path.dirname(directory) == directory
def dirbrowser_html(path):
"""Get the directory browser web page.
Args:
path: The directory path.
Return:
The HTML of the web page.
"""
title = "Browse directory: {}".format(path)
template = jinja.env.get_template('dirbrowser.html')
# pylint: disable=no-member
# https://bitbucket.org/logilab/pylint/issue/490/
folder_icon = utils.resource_filename('img/folder.svg')
file_icon = utils.resource_filename('img/file.svg')
folder_url = QUrl.fromLocalFile(folder_icon).toString(QUrl.FullyEncoded)
file_url = QUrl.fromLocalFile(file_icon).toString(QUrl.FullyEncoded)
if is_root(path):
parent = None
else:
parent = os.path.dirname(path)
try:
all_files = os.listdir(path)
except OSError as e:
html = jinja.env.get_template('error.html').render(
title="Error while reading directory",
url='file://%s' % path,
error=str(e),
icon='')
return html.encode('UTF-8', errors='xmlcharrefreplace')
files = get_file_list(path, all_files, os.path.isfile)
directories = get_file_list(path, all_files, os.path.isdir)
html = template.render(title=title, url=path, icon='',
parent=parent, files=files,
directories=directories, folder_url=folder_url,
file_url=file_url)
return html.encode('UTF-8', errors='xmlcharrefreplace')
class FileSchemeHandler(schemehandler.SchemeHandler):
"""Scheme handler for file: URLs."""
def createRequest(self, _op, request, _outgoing_data):
"""Create a new request.
Args:
request: const QNetworkRequest & req
_op: Operation op
_outgoing_data: QIODevice * outgoingData
Return:
A QNetworkReply for directories, None for files.
"""
path = request.url().toLocalFile()
if os.path.isdir(path):
data = dirbrowser_html(path)
return networkreply.FixedDataNetworkReply(
request, data, 'text/html', self.parent())

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,23 +19,46 @@
"""Our own QNetworkAccessManager."""
from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply
import collections
try:
from PyQt5.QtNetwork import QSslSocket
except ImportError:
SSL_AVAILABLE = False
else:
SSL_AVAILABLE = QSslSocket.supportsSsl()
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
QUrl, QByteArray)
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError,
QSslSocket)
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, utils, objreg
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
urlutils)
from qutebrowser.browser import cookies
from qutebrowser.browser.network import qutescheme, networkreply
from qutebrowser.browser.network import filescheme
HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
ProxyId = collections.namedtuple('ProxyId', 'type, hostname, port')
_proxy_auth_cache = {}
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)
class SslError(QSslError):
"""A QSslError subclass which provides __hash__ on Qt < 5.4."""
def __hash__(self):
try:
# Qt >= 5.4
return super().__hash__()
except TypeError:
return hash((self.certificate().toDer(), self.error()))
class NetworkManager(QNetworkAccessManager):
@@ -43,10 +66,18 @@ class NetworkManager(QNetworkAccessManager):
"""Our own QNetworkAccessManager.
Attributes:
adopted_downloads: If downloads are running with this QNAM but the
associated tab gets closed already, the NAM gets
reparented to the DownloadManager. This counts the
still running downloads, so the QNAM can clean
itself up when this reaches zero again.
_requests: Pending requests.
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
schemes.
_win_id: The window ID this NetworkManager is associated with.
_tab_id: The tab ID this NetworkManager is associated with.
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
Signals:
shutting_down: Emitted when the QNAM is shutting down.
@@ -54,22 +85,26 @@ class NetworkManager(QNetworkAccessManager):
shutting_down = pyqtSignal()
def __init__(self, win_id, parent=None):
def __init__(self, win_id, tab_id, parent=None):
log.init.debug("Initializing NetworkManager")
with log.disable_qt_msghandler():
# WORKAROUND for a hang when a message is printed - See:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
super().__init__(parent)
log.init.debug("NetworkManager init done")
self.adopted_downloads = 0
self._win_id = win_id
self._tab_id = tab_id
self._requests = []
self._scheme_handlers = {
'qute': qutescheme.QuteSchemeHandler(win_id),
'file': filescheme.FileSchemeHandler(win_id),
}
self._set_cookiejar()
self._set_cookiejar(private=config.get('general', 'private-browsing'))
self._set_cache()
if SSL_AVAILABLE:
self.sslErrors.connect(self.on_ssl_errors)
self.sslErrors.connect(self.on_ssl_errors)
self._rejected_ssl_errors = collections.defaultdict(list)
self._accepted_ssl_errors = collections.defaultdict(list)
self.authenticationRequired.connect(self.on_authentication_required)
self.proxyAuthenticationRequired.connect(
self.on_proxy_authentication_required)
@@ -105,11 +140,10 @@ class NetworkManager(QNetworkAccessManager):
self.setCache(cache)
cache.setParent(app)
def _ask(self, win_id, text, mode, owner=None):
def _ask(self, text, mode, owner=None):
"""Ask a blocking question in the statusbar.
Args:
win_id: The ID of the window which is calling this function.
text: The text to display to the user.
mode: A PromptMode.
owner: An object which will abort the question if destroyed, or
@@ -124,21 +158,15 @@ class NetworkManager(QNetworkAccessManager):
self.shutting_down.connect(q.abort)
if owner is not None:
owner.destroyed.connect(q.abort)
bridge = objreg.get('message-bridge', scope='window', window=win_id)
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)
q.deleteLater()
return q.answer
def _fill_authenticator(self, authenticator, answer):
"""Fill a given QAuthenticator object with an answer."""
if answer is not None:
# Since the answer could be something else than (user, password)
# pylint seems to think we're unpacking a non-sequence. However we
# *did* explicitely ask for a tuple, so it *will* always be one.
user, password = answer
authenticator.setUser(user)
authenticator.setPassword(password)
def shutdown(self):
"""Abort all running requests."""
self.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
@@ -148,7 +176,7 @@ class NetworkManager(QNetworkAccessManager):
self.shutting_down.emit()
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
def on_ssl_errors(self, reply, errors):
def on_ssl_errors(self, reply, errors): # pragma: no mccabe
"""Decide if SSL errors should be ignored or not.
This slot is called on SSL/TLS errors by the self.sslErrors signal.
@@ -157,15 +185,37 @@ class NetworkManager(QNetworkAccessManager):
reply: The QNetworkReply that is encountering the errors.
errors: A list of errors.
"""
errors = [SslError(e) for e in errors]
ssl_strict = config.get('network', 'ssl-strict')
if ssl_strict == 'ask':
err_string = '\n'.join('- ' + err.errorString() for err in errors)
answer = self._ask(self._win_id,
'SSL errors - continue?\n{}'.format(err_string),
mode=usertypes.PromptMode.yesno,
owner=reply)
if answer:
try:
host_tpl = urlutils.host_tuple(reply.url())
except ValueError:
host_tpl = None
is_accepted = False
is_rejected = False
else:
is_accepted = set(errors).issubset(
self._accepted_ssl_errors[host_tpl])
is_rejected = set(errors).issubset(
self._rejected_ssl_errors[host_tpl])
if is_accepted:
reply.ignoreSslErrors()
elif is_rejected:
pass
else:
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)
if answer:
reply.ignoreSslErrors()
d = self._accepted_ssl_errors
else:
d = self._rejected_ssl_errors
if host_tpl is not None:
d[host_tpl] += errors
elif ssl_strict:
pass
else:
@@ -176,21 +226,43 @@ class NetworkManager(QNetworkAccessManager):
'SSL error: {}'.format(err.errorString()))
reply.ignoreSslErrors()
@pyqtSlot(QUrl)
def clear_rejected_ssl_errors(self, url):
"""Clear the rejected SSL errors on a reload.
Args:
url: The URL to remove.
"""
try:
del self._rejected_ssl_errors[url]
except KeyError:
pass
@pyqtSlot('QNetworkReply', 'QAuthenticator')
def on_authentication_required(self, reply, authenticator):
"""Called when a website needs authentication."""
answer = self._ask(self._win_id,
"Username ({}):".format(authenticator.realm()),
answer = self._ask("Username ({}):".format(authenticator.realm()),
mode=usertypes.PromptMode.user_pwd,
owner=reply)
self._fill_authenticator(authenticator, answer)
if answer is not None:
authenticator.setUser(answer.user)
authenticator.setPassword(answer.password)
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
def on_proxy_authentication_required(self, _proxy, authenticator):
def on_proxy_authentication_required(self, proxy, authenticator):
"""Called when a proxy needs authentication."""
answer = self._ask(self._win_id, "Proxy username ({}):".format(
authenticator.realm()), mode=usertypes.PromptMode.user_pwd)
self._fill_authenticator(authenticator, answer)
proxy_id = ProxyId(proxy.type(), proxy.hostName(), proxy.port())
if proxy_id in _proxy_auth_cache:
user, password = _proxy_auth_cache[proxy_id]
authenticator.setUser(user)
authenticator.setPassword(password)
else:
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)
_proxy_auth_cache[proxy_id] = answer
@config.change_filter('general', 'private-browsing')
def on_config_changed(self):
@@ -203,6 +275,47 @@ class NetworkManager(QNetworkAccessManager):
# switched from private mode to normal mode
self._set_cookiejar()
@pyqtSlot()
def on_adopted_download_destroyed(self):
"""Check if we can clean up if an adopted download was destroyed.
See the description for adopted_downloads for details.
"""
self.adopted_downloads -= 1
log.downloads.debug("Adopted download destroyed, {} left.".format(
self.adopted_downloads))
assert self.adopted_downloads >= 0
if self.adopted_downloads == 0:
self.deleteLater()
@pyqtSlot(object) # DownloadItem
def adopt_download(self, download):
"""Adopt a new DownloadItem."""
self.adopted_downloads += 1
log.downloads.debug("Adopted download, {} adopted.".format(
self.adopted_downloads))
download.destroyed.connect(self.on_adopted_download_destroyed)
download.do_retry.connect(self.adopt_download)
def set_referer(self, req, current_url):
"""Set the referer header."""
referer_header_conf = config.get('network', 'referer-header')
try:
if referer_header_conf == 'never':
# Note: using ''.encode('ascii') sends a header with no value,
# 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)):
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
# If refer_header_conf is set to 'always', we leave the header
# alone as QtWebKit did set it.
except urlutils.InvalidUrlError:
# req.url() or current_url can be invalid - this happens on
# https://www.playstation.com/ for example.
pass
# WORKAROUND for:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-September/034806.html
#
@@ -225,26 +338,38 @@ class NetworkManager(QNetworkAccessManager):
A QNetworkReply.
"""
scheme = req.url().scheme()
if scheme == 'https' and not SSL_AVAILABLE:
return networkreply.ErrorNetworkReply(
req, "SSL is not supported by the installed Qt library!",
QNetworkReply.ProtocolUnknownError, self)
elif scheme in self._scheme_handlers:
return self._scheme_handlers[scheme].createRequest(
if scheme in self._scheme_handlers:
result = self._scheme_handlers[scheme].createRequest(
op, req, outgoing_data)
if result is not None:
return result
host_blocker = objreg.get('host-blocker')
if (op == QNetworkAccessManager.GetOperation and
req.url().host() in objreg.get('host-blocker').blocked_hosts):
req.url().host() in host_blocker.blocked_hosts and
config.get('content', 'host-blocking-enabled')):
log.webview.info("Request to {} blocked by host blocker.".format(
req.url().host()))
return networkreply.ErrorNetworkReply(
req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied,
self)
if config.get('network', 'do-not-track'):
dnt = '1'.encode('ascii')
else:
dnt = '0'.encode('ascii')
req.setRawHeader('DNT'.encode('ascii'), dnt)
req.setRawHeader('X-Do-Not-Track'.encode('ascii'), dnt)
if self._tab_id is None:
current_url = QUrl() # generic NetworkManager, e.g. for downloads
else:
webview = objreg.get('webview', scope='tab', window=self._win_id,
tab=self._tab_id)
current_url = webview.url()
self.set_referer(req, current_url)
accept_language = config.get('network', 'accept-language')
if accept_language is not None:
req.setRawHeader('Accept-Language'.encode('ascii'),
@@ -262,5 +387,5 @@ class NetworkManager(QNetworkAccessManager):
else:
reply = super().createRequest(op, req, outgoing_data)
self._requests.append(reply)
reply.destroyed.connect(lambda obj: self._requests.remove(obj))
reply.destroyed.connect(self._requests.remove)
return reply

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
@@ -54,6 +54,7 @@ class FixedDataNetworkReply(QNetworkReply):
self.setAttribute(QNetworkRequest.HttpReasonPhraseAttribute, 'OK')
# For some reason, a segfault will be triggered if these lambdas aren't
# there.
# pylint: disable=unnecessary-lambda
QTimer.singleShot(0, lambda: self.metaDataChanged.emit())
QTimer.singleShot(0, lambda: self.readyRead.emit())
QTimer.singleShot(0, lambda: self.finished.emit())
@@ -86,9 +87,11 @@ class FixedDataNetworkReply(QNetworkReply):
return buf
def isFinished(self):
"""Check if the reply is finished."""
return True
def isRunning(self):
return False
class ErrorNetworkReply(QNetworkReply):
@@ -112,6 +115,7 @@ class ErrorNetworkReply(QNetworkReply):
self.setError(error, errorstring)
# For some reason, a segfault will be triggered if these lambdas aren't
# there.
# pylint: disable=unnecessary-lambda
QTimer.singleShot(0, lambda: self.error.emit(error))
QTimer.singleShot(0, lambda: self.finished.emit())
@@ -126,3 +130,9 @@ class ErrorNetworkReply(QNetworkReply):
def readData(self):
"""No data available."""
return bytes()
def isFinished(self):
return True
def isRunning(self):
return False

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,21 +19,19 @@
"""Client for the pastebin."""
import functools
import urllib.request
import urllib.parse
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkRequest,
QNetworkReply)
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from qutebrowser.misc import httpclient
class PastebinClient(QObject):
"""A client for http://p.cmpl.cc/ using QNetworkAccessManager.
"""A client for http://p.cmpl.cc/ using HTTPClient.
Attributes:
_nam: The QNetworkAccessManager used.
_client: The HTTPClient used.
Class attributes:
API_URL: The base API URL.
@@ -51,7 +49,9 @@ class PastebinClient(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._nam = QNetworkAccessManager(self)
self._client = httpclient.HTTPClient(self)
self._client.error.connect(self.error)
self._client.success.connect(self.on_client_success)
def paste(self, name, title, text, parent=None):
"""Paste the text into a pastebin and return the URL.
@@ -69,33 +69,17 @@ class PastebinClient(QObject):
}
if parent is not None:
data['reply'] = parent
encoded_data = urllib.parse.urlencode(data).encode('utf-8')
create_url = urllib.parse.urljoin(self.API_URL, 'create')
request = QNetworkRequest(QUrl(create_url))
request.setHeader(QNetworkRequest.ContentTypeHeader,
'application/x-www-form-urlencoded;charset=utf-8')
reply = self._nam.post(request, encoded_data)
if reply.isFinished():
self.on_reply_finished(reply)
else:
reply.finished.connect(functools.partial(
self.on_reply_finished, reply))
url = QUrl(urllib.parse.urljoin(self.API_URL, 'create'))
self._client.post(url, data)
def on_reply_finished(self, reply):
"""Read the data and finish when the reply finished.
@pyqtSlot(str)
def on_client_success(self, data):
"""Process the data and finish when the client finished.
Args:
reply: The QNetworkReply which finished.
data: A string with the received data.
"""
if reply.error() != QNetworkReply.NoError:
self.error.emit(reply.errorString())
return
try:
url = bytes(reply.readAll()).decode('utf-8')
except UnicodeDecodeError:
self.error.emit("Invalid UTF-8 data received in reply!")
return
if url.startswith('http://'):
self.success.emit(url)
if data.startswith('http://'):
self.success.emit(data)
else:
self.error.emit("Invalid data received in reply!")

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,7 +19,9 @@
#
# pylint complains when using .render() on jinja templates, so we make it shut
# up for this whole module.
# pylint: disable=maybe-no-member
# pylint: disable=no-member
# https://bitbucket.org/logilab/pylint/issue/490/
"""Handler functions for different qute:... pages.
@@ -27,6 +29,7 @@ Module attributes:
pyeval_output: The output of the last :pyeval command.
"""
import functools
import configparser
from PyQt5.QtCore import pyqtSlot, QObject
@@ -93,6 +96,12 @@ class JSBridge(QObject):
@pyqtSlot(int, str, str, str)
def set(self, win_id, sectname, optname, value):
"""Slot to set a setting from qute:settings."""
# https://github.com/The-Compiler/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'):
message.error(win_id, "Refusing to disable javascript via "
"qute:settings as it needs javascript support.")
return
try:
objreg.get('config').set('conf', sectname, optname, value)
except (configexc.Error, configparser.Error) as e:
@@ -144,13 +153,13 @@ def qute_help(win_id, request):
"""Handler for qute:help. Return HTML content as bytes."""
try:
utils.read_file('html/doc/index.html')
except FileNotFoundError:
except OSError:
html = jinja.env.get_template('error.html').render(
title="Error while loading documentation",
url=request.url().toDisplayString(),
error="This most likely means the documentation was not generated "
"properly. If you are running qutebrowser from the git "
"repository, please run scripts/asciidoc2html.py."
"repository, please run scripts/asciidoc2html.py. "
"If you're running a released version this is a bug, please "
"use :report to report it.",
icon='')
@@ -168,9 +177,11 @@ def qute_help(win_id, request):
def qute_settings(win_id, _request):
"""Handler for qute:settings. View/change qute configuration"""
"""Handler for qute:settings. View/change qute configuration."""
config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.env.get_template('settings.html').render(
win_id=win_id, title='settings', config=configdata)
win_id=win_id, title='settings', config=configdata,
confget=config_getter)
return html.encode('UTF-8', errors='xmlcharrefreplace')

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>

View File

@@ -1,140 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Manager for quickmarks.
Note we violate our general QUrl rule by storing url strings in the marks
OrderedDict. This is because we read them from a file at start and write them
to a file on shutdown, so it makes sense to keep them as strings here.
"""
import functools
import collections
from PyQt5.QtCore import pyqtSignal, QStandardPaths, QUrl, QObject
from qutebrowser.utils import message, usertypes, urlutils, standarddir
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config.parsers import line as lineparser
class QuickmarkManager(QObject):
"""Manager for quickmarks.
Attributes:
marks: An OrderedDict of all quickmarks.
_linecp: The LineConfigParser used for the quickmarks.
"""
changed = pyqtSignal()
def __init__(self, parent=None):
"""Initialize and read quickmarks."""
super().__init__(parent)
self.marks = collections.OrderedDict()
confdir = standarddir.get(QStandardPaths.ConfigLocation)
self._linecp = lineparser.LineConfigParser(confdir, 'quickmarks')
for line in self._linecp:
try:
key, url = line.rsplit(maxsplit=1)
except ValueError:
message.error(0, "Invalid quickmark '{}'".format(line))
else:
self.marks[key] = url
def save(self):
"""Save the quickmarks to disk."""
self._linecp.data = [' '.join(tpl) for tpl in self.marks.items()]
self._linecp.save()
def prompt_save(self, win_id, url):
"""Prompt for a new quickmark name to be added and add it.
Args:
win_id: The current window ID.
url: The quickmark url as a QUrl.
"""
if not url.isValid():
urlutils.invalid_url_error(win_id, url, "save quickmark")
return
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask_async(
win_id, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, win_id, urlstr))
@cmdutils.register(instance='quickmark-manager')
def quickmark_add(self, win_id: {'special': 'win_id'}, url, name):
"""Add a new quickmark.
Args:
win_id: The window ID to display the errors in.
url: The url to add as quickmark.
name: The name for the new quickmark.
"""
# We don't raise cmdexc.CommandError here as this can be called async
# via prompt_save.
if not name:
message.error(win_id, "Can't set mark with empty name!")
return
if not url:
message.error(win_id, "Can't set mark with empty URL!")
return
def set_mark():
"""Really set the quickmark."""
self.marks[name] = url
self.changed.emit()
if name in self.marks:
message.confirm_async(
win_id, "Override existing quickmark?", set_mark, default=True)
else:
set_mark()
@cmdutils.register(instance='quickmark-manager', maxsplit=0,
completion=[usertypes.Completion.quickmark_by_name])
def quickmark_del(self, name):
"""Delete a quickmark.
Args:
name: The name of the quickmark to delete.
"""
try:
del self.marks[name]
except KeyError:
raise cmdexc.CommandError("Quickmark '{}' not found!".format(name))
else:
self.changed.emit()
def get(self, name):
"""Get the URL of the quickmark named name as a QUrl."""
if name not in self.marks:
raise cmdexc.CommandError(
"Quickmark '{}' does not exist!".format(name))
urlstr = self.marks[name]
try:
url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError:
raise cmdexc.CommandError(
"Invalid URL for quickmark {}: {} ({})".format(
name, urlstr, url.errorString()))
return url

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,17 +17,16 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""pyPEG parsing for the RFC 6266 (Content-Disposition) header. """
"""pyPEG parsing for the RFC 6266 (Content-Disposition) header."""
import collections
import urllib.parse
import string
import re
import pypeg2 as peg # pylint: disable=import-error
# (fails on win7 in venv...)
import pypeg2 as peg
from qutebrowser.utils import log, utils
from qutebrowser.utils import utils
class UniqueNamespace(peg.Namespace):
@@ -122,6 +121,7 @@ class Language(str):
FIXME: This grammar is not 100% correct yet.
https://github.com/The-Compiler/qutebrowser/issues/105
"""
grammar = re.compile('[A-Za-z0-9-]+')
@@ -215,17 +215,22 @@ class ContentDispositionValue:
LangTagged = collections.namedtuple('LangTagged', ['string', 'langtag'])
class DuplicateParamError(Exception):
class Error(Exception):
"""Base class for RFC6266 errors."""
class DuplicateParamError(Error):
"""Exception raised when a parameter has been given twice."""
class InvalidISO8859Error(Exception):
class InvalidISO8859Error(Error):
"""Exception raised when a byte is invalid in ISO-8859-1."""
class ContentDisposition:
class _ContentDisposition:
"""Records various indications and hints about content disposition.
@@ -234,24 +239,15 @@ class ContentDisposition:
in the download case.
"""
def __init__(self, disposition='inline', assocs=None):
"""This constructor is used internally after parsing the header.
Instances should generally be created from a factory
function, such as parse_headers and its variants.
"""
if len(disposition) != 1:
self.disposition = 'inline'
else:
self.disposition = disposition[0]
if assocs is None:
self.assocs = {}
else:
self.assocs = dict(assocs) # So we can change values
if 'filename*' in self.assocs:
param = self.assocs['filename*']
assert isinstance(param, ExtDispositionParm)
self.assocs['filename*'] = parse_ext_value(param.value).string
def __init__(self, disposition, assocs):
"""Used internally after parsing the header."""
assert len(disposition) == 1
self.disposition = disposition[0]
self.assocs = dict(assocs) # So we can change values
if 'filename*' in self.assocs:
param = self.assocs['filename*']
assert isinstance(param, ExtDispositionParm)
self.assocs['filename*'] = parse_ext_value(param.value).string
def filename(self):
"""The filename from the Content-Disposition header or None.
@@ -265,7 +261,6 @@ class ContentDisposition:
well, due to a certain browser using the part after the dot for
mime-sniffing. Saving it to a database is fine by itself though.
"""
if 'filename*' in self.assocs:
return self.assocs['filename*']
elif 'filename' in self.assocs:
@@ -292,8 +287,10 @@ def normalize_ws(text):
def parse_headers(content_disposition):
"""Build a ContentDisposition from header values."""
# pylint: disable=maybe-no-member
"""Build a _ContentDisposition from header values."""
# https://bitbucket.org/logilab/pylint/issue/492/
# pylint: disable=no-member
# We allow non-ascii here (it will only be parsed inside of qdtext, and
# rejected by the grammar if it appears in other places), although parsing
# it can be ambiguous. Parsing it ensures that a non-ambiguous filename*
@@ -301,8 +298,6 @@ def parse_headers(content_disposition):
# filename parameter. But it does mean we occasionally give
# less-than-certain values for some legacy senders.
content_disposition = content_disposition.decode('iso-8859-1')
log.rfc6266.debug("Parsing Content-Disposition: {}".format(
content_disposition))
# Our parsing is relaxed in these regards:
# - The grammar allows a final ';' in the header;
# - We do LWS-folding, and possibly normalise other broken
@@ -310,14 +305,8 @@ def parse_headers(content_disposition):
# XXX Would prefer to accept only the quoted whitespace
# case, rather than normalising everything.
content_disposition = normalize_ws(content_disposition)
try:
parsed = peg.parse(content_disposition, ContentDispositionValue)
except (SyntaxError, DuplicateParamError, InvalidISO8859Error):
log.rfc6266.exception("Error while parsing Content-Disposition")
return ContentDisposition()
else:
return ContentDisposition(disposition=parsed.dtype,
assocs=parsed.params)
parsed = peg.parse(content_disposition, ContentDispositionValue)
return _ContentDisposition(disposition=parsed.dtype, assocs=parsed.params)
def parse_ext_value(val):

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -24,7 +24,6 @@ import functools
from PyQt5.QtCore import QObject
from qutebrowser.utils import debug, log, objreg
from qutebrowser.browser import webview
class SignalFilter(QObject):
@@ -41,7 +40,8 @@ class SignalFilter(QObject):
BLACKLIST: List of signal names which should not be logged.
"""
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress']
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress',
'cur_statusbar_message', 'cur_link_hovered']
def __init__(self, win_id, parent=None):
super().__init__(parent)
@@ -57,9 +57,6 @@ class SignalFilter(QObject):
Return:
A partial functon calling _filter_signals with a signal.
"""
if not isinstance(tab, webview.WebView):
raise ValueError("Tried to create filter for {} which is no "
"WebView!".format(tab))
return functools.partial(self._filter_signals, signal, tab)
def _filter_signals(self, signal, tab, *args):

View File

@@ -0,0 +1,180 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Utilities related to QWebHistory."""
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from qutebrowser.utils import utils, qtutils
HISTORY_STREAM_VERSION = 2
BACK_FORWARD_TREE_VERSION = 2
class TabHistoryItem:
"""A single item in the tab history.
Attributes:
url: The QUrl of this item.
original_url: The QUrl of this item which was originally requested.
title: The title as string of this item.
active: Whether this item is the item currently navigated to.
user_data: The user data for this item.
"""
def __init__(self, url, title, *, original_url=None, active=False,
user_data=None):
self.url = url
if original_url is None:
self.original_url = url
else:
self.original_url = original_url
self.title = title
self.active = active
self.user_data = user_data
def __repr__(self):
return utils.get_repr(self, constructor=True, url=self.url,
original_url=self.original_url, title=self.title,
active=self.active, user_data=self.user_data)
def _encode_url(url):
"""Encode an QUrl suitable to pass to QWebHistory."""
data = bytes(QUrl.toPercentEncoding(url.toString(), b':/#?&+=@%*'))
return data.decode('ascii')
def _serialize_item(i, item, stream):
"""Serialize a single WebHistoryItem into a QDataStream.
Args:
i: The index of the current item.
item: The WebHistoryItem to write.
stream: The QDataStream to write to.
"""
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## urlString
stream.writeQString(_encode_url(item.url))
## title
stream.writeQString(item.title)
## originalURLString
stream.writeQString(_encode_url(item.original_url))
### Source/WebCore/history/HistoryItem.cpp decodeBackForwardTree
## backForwardTreeEncodingVersion
stream.writeUInt32(BACK_FORWARD_TREE_VERSION)
## size (recursion stack)
stream.writeUInt64(0)
## node->m_documentSequenceNumber
# If two HistoryItems have the same document sequence number, then they
# refer to the same instance of a document. Traversing history from one
# such HistoryItem to another preserves the document.
stream.writeInt64(i + 1)
## size (node->m_documentState)
stream.writeUInt64(0)
## node->m_formContentType
# info used to repost form data
stream.writeQString(None)
## hasFormData
stream.writeBool(False)
## node->m_itemSequenceNumber
# If two HistoryItems have the same item sequence number, then they are
# clones of one another. Traversing history from one such HistoryItem to
# another is a no-op. HistoryItem clones are created for parent and
# sibling frames when only a subframe navigates.
stream.writeInt64(i + 1)
## node->m_referrer
stream.writeQString(None)
## node->m_scrollPoint (x)
try:
stream.writeInt32(item.user_data['scroll-pos'].x())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_scrollPoint (y)
try:
stream.writeInt32(item.user_data['scroll-pos'].y())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_pageScaleFactor
stream.writeFloat(1)
## hasStateObject
# Support for HTML5 History
stream.writeBool(False)
## node->m_target
stream.writeQString(None)
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## validUserData
# We could restore the user data here, but we prefer to use the
# QWebHistoryItem API for that.
stream.writeBool(False)
def serialize(items):
"""Serialize a list of QWebHistoryItems to a data stream.
Args:
items: An iterable of WebHistoryItems.
Return:
A (stream, data, user_data) tuple.
stream: The reseted QDataStream.
data: The QByteArray with the raw data.
user_data: A list with each item's user data.
Warning:
If 'data' goes out of scope, reading from 'stream' will result in a
segfault!
"""
data = QByteArray()
stream = QDataStream(data, QIODevice.ReadWrite)
user_data = []
current_idx = None
for i, item in enumerate(items):
if item.active:
if current_idx is not None:
raise ValueError("Multiple active items ({} and {}) "
"found!".format(current_idx, i))
else:
current_idx = i
if items:
if current_idx is None:
raise ValueError("No active item found!")
else:
current_idx = 0
### Source/WebKit/qt/Api/qwebhistory.cpp operator<<
stream.writeInt(HISTORY_STREAM_VERSION)
stream.writeInt(len(items))
stream.writeInt(current_idx)
for i, item in enumerate(items):
_serialize_item(i, item, stream)
user_data.append(item.user_data)
stream.device().reset()
qtutils.check_qdatastream(stream)
return stream, data, user_data

View File

@@ -0,0 +1,296 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015 Antoni Boucher <bouanto@zoho.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Managers for bookmarks and quickmarks.
Note we violate our general QUrl rule by storing url strings in the marks
OrderedDict. This is because we read them from a file at start and write them
to a file on shutdown, so it makes sense to keep them as strings here.
"""
import os
import os.path
import functools
import collections
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
from qutebrowser.utils import message, usertypes, urlutils, standarddir, objreg
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.misc import lineparser
class Error(Exception):
"""Base class for all errors in this module."""
pass
class InvalidUrlError(Error):
"""Exception emitted when a URL is invalid."""
pass
class DoesNotExistError(Error):
"""Exception emitted when a given URL does not exist."""
pass
class AlreadyExistsError(Error):
"""Exception emitted when a given URL does already exist."""
pass
class UrlMarkManager(QObject):
"""Base class for BookmarkManager and QuickmarkManager.
Attributes:
marks: An OrderedDict of all quickmarks/bookmarks.
_lineparser: The LineParser used for the marks, or None
(when qutebrowser is started with -c '').
Signals:
changed: Emitted when anything changed.
added: Emitted when a new quickmark/bookmark was added.
removed: Emitted when an existing quickmark/bookmark was removed.
"""
changed = pyqtSignal()
added = pyqtSignal(str, str)
removed = pyqtSignal(str)
def __init__(self, parent=None):
"""Initialize and read quickmarks."""
super().__init__(parent)
self.marks = collections.OrderedDict()
self._lineparser = None
if standarddir.config() is None:
return
self._init_lineparser()
for line in self._lineparser:
if not line.strip():
# Ignore empty or whitespace-only lines.
continue
self._parse_line(line)
self._init_savemanager(objreg.get('save-manager'))
def _init_lineparser(self):
raise NotImplementedError
def _parse_line(self, line):
raise NotImplementedError
def _init_savemanager(self, _save_manager):
raise NotImplementedError
def save(self):
"""Save the marks to disk."""
if self._lineparser is not None:
self._lineparser.data = [' '.join(tpl)
for tpl in self.marks.items()]
self._lineparser.save()
def delete(self, key):
"""Delete a quickmark/bookmark.
Args:
key: The key to delete (name for quickmarks, URL for bookmarks.)
"""
del self.marks[key]
self.changed.emit()
self.removed.emit(key)
class QuickmarkManager(UrlMarkManager):
"""Manager for quickmarks.
The primary key for quickmarks is their *name*, this means:
- self.marks maps names to URLs.
- changed gets emitted with the name as first argument and the URL as
second argument.
- removed gets emitted with the name as argument.
"""
def _init_lineparser(self):
self._lineparser = lineparser.LineParser(
standarddir.config(), 'quickmarks', parent=self)
def _init_savemanager(self, save_manager):
filename = os.path.join(standarddir.config(), 'quickmarks')
save_manager.add_saveable('quickmark-manager', self.save, self.changed,
filename=filename)
def _parse_line(self, line):
try:
key, url = line.rsplit(maxsplit=1)
except ValueError:
message.error('current', "Invalid quickmark '{}'".format(
line))
else:
self.marks[key] = url
def prompt_save(self, win_id, url):
"""Prompt for a new quickmark name to be added and add it.
Args:
win_id: The current window ID.
url: The quickmark url as a QUrl.
"""
if not url.isValid():
urlutils.invalid_url_error(win_id, url, "save quickmark")
return
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask_async(
win_id, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, win_id, urlstr))
@cmdutils.register(instance='quickmark-manager', win_id='win_id')
def quickmark_add(self, win_id, url, name):
"""Add a new quickmark.
Args:
win_id: The window ID to display the errors in.
url: The url to add as quickmark.
name: The name for the new quickmark.
"""
# We don't raise cmdexc.CommandError here as this can be called async
# via prompt_save.
if not name:
message.error(win_id, "Can't set mark with empty name!")
return
if not url:
message.error(win_id, "Can't set mark with empty URL!")
return
def set_mark():
"""Really set the quickmark."""
self.marks[name] = url
self.changed.emit()
self.added.emit(name, url)
if name in self.marks:
message.confirm_async(
win_id, "Override existing quickmark?", set_mark, default=True)
else:
set_mark()
@cmdutils.register(instance='quickmark-manager', maxsplit=0,
completion=[usertypes.Completion.quickmark_by_name])
def quickmark_del(self, name):
"""Delete a quickmark.
Args:
name: The name of the quickmark to delete.
"""
try:
self.delete(name)
except KeyError:
raise cmdexc.CommandError("Quickmark '{}' not found!".format(name))
def get(self, name):
"""Get the URL of the quickmark named name as a QUrl."""
if name not in self.marks:
raise DoesNotExistError(
"Quickmark '{}' does not exist!".format(name))
urlstr = self.marks[name]
try:
url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e:
raise InvalidUrlError(
"Invalid URL for quickmark {}: {}".format(name, str(e)))
return url
class BookmarkManager(UrlMarkManager):
"""Manager for bookmarks.
The primary key for bookmarks is their *url*, this means:
- self.marks maps URLs to titles.
- changed gets emitted with the URL as first argument and the title as
second argument.
- removed gets emitted with the URL as argument.
"""
def _init_lineparser(self):
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
if not os.path.isdir(bookmarks_directory):
os.makedirs(bookmarks_directory)
self._lineparser = lineparser.LineParser(
standarddir.config(), 'bookmarks/urls', parent=self)
def _init_savemanager(self, save_manager):
filename = os.path.join(standarddir.config(), 'bookmarks/urls')
save_manager.add_saveable('bookmark-manager', self.save, self.changed,
filename=filename)
def _parse_line(self, line):
parts = line.split(maxsplit=1)
if len(parts) == 2:
self.marks[parts[0]] = parts[1]
elif len(parts) == 1:
self.marks[parts[0]] = ''
def add(self, url, title):
"""Add a new bookmark.
Args:
url: The url to add as bookmark.
title: The title for the new bookmark.
"""
if not url.isValid():
errstr = urlutils.get_errstring(url)
raise InvalidUrlError(errstr)
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
if urlstr in self.marks:
raise AlreadyExistsError("Bookmark already exists!")
else:
self.marks[urlstr] = title
self.changed.emit()
self.added.emit(title, urlstr)
@cmdutils.register(instance='bookmark-manager', maxsplit=0,
completion=[usertypes.Completion.bookmark_by_url])
def bookmark_del(self, url):
"""Delete a bookmark.
Args:
url: The URL of the bookmark to delete.
"""
try:
self.delete(url)
except KeyError:
raise cmdexc.CommandError("Bookmark '{}' not found!".format(url))

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -43,18 +43,23 @@ Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext',
SELECTORS = {
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, [onclick], [onmousedown], [role=link], '
'frame, iframe, link, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'),
Group.links: 'a, area, link, [role=link]',
Group.images: 'img',
Group.url: '[src], [href]',
Group.prevnext: 'a, area, button, [role=button]',
Group.prevnext: 'a, area, button, link, [role=button]',
Group.focus: '*:focus',
}
def filter_links(elem):
return 'href' in elem and QUrl(elem['href']).scheme() != 'javascript'
FILTERS = {
Group.links: (lambda e: 'href' in e and
QUrl(e['href']).scheme() != 'javascript'),
Group.links: filter_links,
Group.prevnext: filter_links,
}
@@ -136,7 +141,7 @@ class WebElementWrapper(collections.abc.MutableMapping):
self._check_vanished()
if key not in self:
raise KeyError(key)
self.removeAttribute(key)
self._elem.removeAttribute(key)
def __contains__(self, key):
self._check_vanished()
@@ -179,8 +184,6 @@ class WebElementWrapper(collections.abc.MutableMapping):
def is_content_editable(self):
"""Check if an element has a contenteditable attribute.
FIXME: Add tests.
Args:
elem: The QWebElement to check.
@@ -240,12 +243,11 @@ class WebElementWrapper(collections.abc.MutableMapping):
for klass in self._elem.classes():
if any([klass.startswith(e) for e in div_classes]):
return True
return False
def is_editable(self, strict=False):
"""Check whether we should switch to insert mode for this element.
FIXME: add tests
Args:
strict: Whether to do stricter checking so only fields where we can
get the value match, for use with the :editor command.
@@ -261,7 +263,7 @@ class WebElementWrapper(collections.abc.MutableMapping):
tag = self._elem.tagName().lower()
if self.is_content_editable() and self.is_writable():
return True
elif self.get('role', None) in roles:
elif self.get('role', None) in roles and self.is_writable():
return True
elif tag == 'input':
return self._is_editable_input()
@@ -277,6 +279,13 @@ class WebElementWrapper(collections.abc.MutableMapping):
else:
return False
def is_text_input(self):
"""Check if this element is some kind of text box."""
self._check_vanished()
roles = ('combobox', 'textbox')
tag = self._elem.tagName().lower()
return self.get('role', None) in roles or tag in ('input', 'textarea')
def debug_text(self):
"""Get a text based on an element suitable for debug output."""
self._check_vanished()
@@ -297,6 +306,8 @@ def javascript_escape(text):
("'", r"\'"), # Then escape ' and " as \' and \".
('"', r'\"'), # (note it won't hurt when we escape the wrong one).
('\n', r'\n'), # We also need to escape newlines for some reason.
('\r', r'\r'),
('\x00', r'\x00'),
)
for orig, repl in replacements:
text = text.replace(orig, repl)
@@ -306,7 +317,7 @@ def javascript_escape(text):
def get_child_frames(startframe):
"""Get all children recursively of a given QWebFrame.
Loosly based on http://blog.nextgenetics.net/?e=64
Loosely based on http://blog.nextgenetics.net/?e=64
Args:
startframe: The QWebFrame to start with.
@@ -326,9 +337,7 @@ def get_child_frames(startframe):
def focus_elem(frame):
"""Get the focused element in a webframe.
FIXME: Add tests.
"""Get the focused element in a web frame.
Args:
frame: The QWebFrame to search in.

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,7 +21,8 @@
import functools
from PyQt5.QtCore import pyqtSlot, PYQT_VERSION, Qt, QUrl
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QUrl, QPoint,
QTimer)
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QFileDialog
@@ -29,10 +30,10 @@ from PyQt5.QtPrintSupport import QPrintDialog
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
from qutebrowser.browser import http
from qutebrowser.browser import http, tabhistory
from qutebrowser.browser.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg)
objreg, debug)
class BrowserPage(QWebPage):
@@ -40,29 +41,52 @@ class BrowserPage(QWebPage):
"""Our own QWebPage with advanced features.
Attributes:
error_occured: Whether an error occured while loading.
error_occurred: Whether an error occurred while loading.
open_target: Where to open the next navigation request.
("normal", "tab", "tab_bg")
_hint_target: Override for open_target while hinting, or None.
_extension_handlers: Mapping of QWebPage extensions to their handlers.
_networkmnager: The NetworkManager used.
_networkmanager: The NetworkManager used.
_win_id: The window ID this BrowserPage is associated with.
_ignore_load_started: Whether to ignore the next loadStarted signal.
_is_shutting_down: Whether the page is currently shutting down.
Signals:
shutting_down: Emitted when the page is currently shutting down.
reloading: Emitted before a web page reloads.
arg: The URL which gets reloaded.
"""
def __init__(self, win_id, parent=None):
shutting_down = pyqtSignal()
reloading = pyqtSignal(QUrl)
def __init__(self, win_id, tab_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._is_shutting_down = False
self._extension_handlers = {
QWebPage.ErrorPageExtension: self._handle_errorpage,
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
}
self._ignore_load_started = False
self.error_occured = False
self._networkmanager = networkmanager.NetworkManager(win_id, self)
self.error_occurred = False
self.open_target = usertypes.ClickTarget.normal
self._hint_target = None
self._networkmanager = networkmanager.NetworkManager(
win_id, tab_id, self)
self.setNetworkAccessManager(self._networkmanager)
self.setForwardUnsupportedContent(True)
self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors)
self.printRequested.connect(self.on_print_requested)
self.downloadRequested.connect(self.on_download_requested)
self.unsupportedContent.connect(self.on_unsupported_content)
self.loadStarted.connect(self.on_load_started)
self.featurePermissionRequested.connect(
self.on_feature_permission_requested)
self.saveFrameStateRequested.connect(
self.on_save_frame_state_requested)
self.restoreFrameStateRequested.connect(
self.on_restore_frame_state_requested)
if PYQT_VERSION > 0x050300:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
@@ -72,8 +96,11 @@ class BrowserPage(QWebPage):
def javaScriptPrompt(self, _frame, msg, default):
"""Override javaScriptPrompt to use the statusbar."""
answer = message.ask(self._win_id, "js: {}".format(msg),
usertypes.PromptMode.text, default)
if (self._is_shutting_down or
config.get('content', 'ignore-javascript-prompt')):
return (False, "")
answer = self._ask("js: {}".format(msg), usertypes.PromptMode.text,
default)
if answer is None:
return (False, "")
else:
@@ -82,7 +109,7 @@ class BrowserPage(QWebPage):
def _handle_errorpage(self, info, errpage):
"""Display an error page if needed.
Loosly based on Helpviewer/HelpBrowserWV.py from eric5
Loosely based on Helpviewer/HelpBrowserWV.py from eric5
(line 260 @ 5d937eb378dd)
Args:
@@ -120,18 +147,29 @@ class BrowserPage(QWebPage):
else:
error_str = info.errorString
if error_str == networkmanager.HOSTBLOCK_ERROR_STRING:
# We don't set error_occurred in this case.
error_str = "Request blocked by host blocker."
# we don't set error_occured in this case.
main_frame = info.frame.page().mainFrame()
if info.frame != main_frame:
# Content in an iframe -> Hide the frame so it doesn't use
# any space. We can't hide the frame's documentElement
# directly though.
for elem in main_frame.documentElement().findAll('iframe'):
if QUrl(elem.attribute('src')) == info.url:
elem.setAttribute('style', 'display: none')
return False
else:
self._ignore_load_started = True
self.error_occured = True
self.error_occurred = True
log.webview.error("Error while loading {}: {}".format(
urlstr, error_str))
log.webview.debug("Error domain: {}, error code: {}".format(
info.domain, info.error))
title = "Error loading page: {}".format(urlstr)
template = jinja.env.get_template('error.html')
html = template.render( # pylint: disable=maybe-no-member
# pylint: disable=no-member
# https://bitbucket.org/logilab/pylint/issue/490/
html = template.render(
title=title, url=urlstr, error=error_str, icon='')
errpage.content = html.encode('utf-8')
errpage.encoding = 'utf-8'
@@ -140,7 +178,7 @@ class BrowserPage(QWebPage):
def _handle_multiple_files(self, info, files):
"""Handle uploading of multiple files.
Loosly based on Helpviewer/HelpBrowserWV.py from eric5.
Loosely based on Helpviewer/HelpBrowserWV.py from eric5.
Args:
info: The ChooseMultipleFilesExtensionOption instance.
@@ -157,8 +195,60 @@ class BrowserPage(QWebPage):
suggested_file)
return True
def _ask(self, text, mode, default=None):
"""Ask a blocking question in the statusbar.
Args:
text: The text to display to the user.
mode: A PromptMode.
default: The default value to display.
Return:
The answer the user gave or None if the prompt was cancelled.
"""
q = usertypes.Question()
q.text = text
q.mode = mode
q.default = default
self.loadStarted.connect(q.abort)
self.shutting_down.connect(q.abort)
bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
bridge.ask(q, blocking=True)
q.deleteLater()
return q.answer
def shutdown(self):
"""Prepare the web page for being deleted."""
self._is_shutting_down = True
self.shutting_down.emit()
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
nam = self.networkAccessManager()
if download_manager.has_downloads_with_nam(nam):
nam.setParent(download_manager)
else:
nam.shutdown()
def load_history(self, entries):
"""Load the history from a list of TabHistoryItem objects."""
stream, _data, user_data = tabhistory.serialize(entries)
history = self.history()
qtutils.deserialize_stream(stream, history)
for i, data in enumerate(user_data):
history.itemAt(i).setUserData(data)
cur_data = history.currentItem().userData()
if cur_data is not None:
frame = self.mainFrame()
if 'zoom' in cur_data:
frame.page().view().zoom_perc(cur_data['zoom'] * 100)
if ('scroll-pos' in cur_data and
frame.scrollPosition() == QPoint(0, 0)):
QTimer.singleShot(0, functools.partial(
frame.setScrollPosition, cur_data['scroll-pos']))
def display_content(self, reply, mimetype):
"""Display a QNetworkReply with an explicitely set mimetype."""
"""Display a QNetworkReply with an explicitly set mimetype."""
self.mainFrame().setContent(reply.readAll(), mimetype, reply.url())
reply.deleteLater()
@@ -196,12 +286,13 @@ class BrowserPage(QWebPage):
At some point we might want to implement the MIME Sniffing standard
here: http://mimesniff.spec.whatwg.org/
"""
inline, _suggested_filename = http.parse_content_disposition(reply)
inline, suggested_filename = http.parse_content_disposition(reply)
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
if not inline:
# Content-Disposition: attachment -> force download
download_manager.fetch(reply)
download_manager.fetch(reply,
suggested_filename=suggested_filename)
return
mimetype, _rest = http.parse_content_type(reply)
if mimetype == 'image/jpg':
@@ -216,15 +307,136 @@ class BrowserPage(QWebPage):
self.display_content, reply, 'image/jpeg'))
else:
# Unknown mimetype, so download anyways.
download_manager.fetch(reply)
download_manager.fetch(reply,
suggested_filename=suggested_filename)
@pyqtSlot()
def on_load_started(self):
"""Reset error_occured when loading of a new page started."""
"""Reset error_occurred when loading of a new page started."""
if self._ignore_load_started:
self._ignore_load_started = False
else:
self.error_occured = False
self.error_occurred = False
@pyqtSlot('QWebFrame', 'QWebPage::Feature')
def on_feature_permission_requested(self, frame, feature):
"""Ask the user for approval for geolocation/notifications."""
options = {
QWebPage.Notifications: ('content', 'notifications'),
QWebPage.Geolocation: ('content', 'geolocation'),
}
config_val = config.get(*options[feature])
if config_val == 'ask':
bridge = objreg.get('message-bridge', scope='window',
window=self._win_id)
q = usertypes.Question(bridge)
q.mode = usertypes.PromptMode.yesno
msgs = {
QWebPage.Notifications: 'show notifications',
QWebPage.Geolocation: 'access your location',
}
host = frame.url().host()
if host:
q.text = "Allow the website at {} to {}?".format(
frame.url().host(), msgs[feature])
else:
q.text = "Allow the website to {}?".format(msgs[feature])
yes_action = functools.partial(
self.setFeaturePermission, frame, feature,
QWebPage.PermissionGrantedByUser)
q.answered_yes.connect(yes_action)
no_action = functools.partial(
self.setFeaturePermission, frame, feature,
QWebPage.PermissionDeniedByUser)
q.answered_no.connect(no_action)
q.cancelled.connect(no_action)
q.completed.connect(q.deleteLater)
self.featurePermissionRequestCanceled.connect(functools.partial(
self.on_feature_permission_cancelled, q, frame, feature))
self.loadStarted.connect(q.abort)
bridge.ask(q, blocking=False)
elif config_val:
self.setFeaturePermission(frame, feature,
QWebPage.PermissionGrantedByUser)
else:
self.setFeaturePermission(frame, feature,
QWebPage.PermissionDeniedByUser)
def on_feature_permission_cancelled(self, question, frame, feature,
cancelled_frame, cancelled_feature):
"""Slot invoked when a feature permission request was cancelled.
To be used with functools.partial.
"""
if frame is cancelled_frame and feature == cancelled_feature:
try:
question.abort()
except RuntimeError:
# The question could already be deleted, e.g. because it was
# aborted after a loadStarted signal.
pass
def on_save_frame_state_requested(self, frame, item):
"""Save scroll position and zoom in history.
Args:
frame: The QWebFrame which gets saved.
item: The QWebHistoryItem to be saved.
"""
try:
if frame != self.mainFrame():
return
except RuntimeError:
# With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
# RuntimeError: wrapped C/C++ object of type BrowserPage has
# been deleted
# Since the information here isn't that important for closing web
# views anyways, we ignore this error.
return
data = {
'zoom': frame.zoomFactor(),
'scroll-pos': frame.scrollPosition(),
}
item.setUserData(data)
def on_restore_frame_state_requested(self, frame):
"""Restore scroll position and zoom from history.
Args:
frame: The QWebFrame which gets restored.
"""
if frame != self.mainFrame():
return
data = self.history().currentItem().userData()
if data is None:
return
if 'zoom' in data:
frame.page().view().zoom_perc(data['zoom'] * 100)
if 'scroll-pos' in data and frame.scrollPosition() == QPoint(0, 0):
frame.setScrollPosition(data['scroll-pos'])
@pyqtSlot(str)
def on_start_hinting(self, hint_target):
"""Emitted before a hinting-click takes place.
Args:
hint_target: A ClickTarget member to set self._hint_target to.
"""
log.webview.debug("Setting force target to {}".format(hint_target))
self._hint_target = hint_target
@pyqtSlot()
def on_stop_hinting(self):
"""Emitted when hinting is finished."""
log.webview.debug("Finishing hinting.")
self._hint_target = None
def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
@@ -266,20 +478,33 @@ class BrowserPage(QWebPage):
return super().extension(ext, opt, out)
return handler(opt, out)
def javaScriptAlert(self, _frame, msg):
def javaScriptAlert(self, frame, msg):
"""Override javaScriptAlert to use the statusbar."""
message.ask(self._win_id, "[js alert] {}".format(msg),
usertypes.PromptMode.alert)
log.js.debug("alert: {}".format(msg))
if config.get('ui', 'modal-js-dialog'):
return super().javaScriptAlert(frame, msg)
def javaScriptConfirm(self, _frame, msg):
if (self._is_shutting_down or
config.get('content', 'ignore-javascript-alert')):
return
self._ask("[js alert] {}".format(msg), usertypes.PromptMode.alert)
def javaScriptConfirm(self, frame, msg):
"""Override javaScriptConfirm to use the statusbar."""
ans = message.ask(self._win_id, "[js confirm] {}".format(msg),
usertypes.PromptMode.yesno)
log.js.debug("confirm: {}".format(msg))
if config.get('ui', 'modal-js-dialog'):
return super().javaScriptConfirm(frame, msg)
if self._is_shutting_down:
return False
ans = self._ask("[js confirm] {}".format(msg),
usertypes.PromptMode.yesno)
return bool(ans)
def javaScriptConsoleMessage(self, msg, line, source):
"""Override javaScriptConsoleMessage to use debug log."""
log.js.debug("[{}:{}] {}".format(source, line, msg))
if config.get('general', 'log-javascript-console'):
log.js.debug("[{}:{}] {}".format(source, line, msg))
def chooseFile(self, _frame, suggested_file):
"""Override QWebPage's chooseFile to be able to chose a file to upload.
@@ -293,8 +518,8 @@ class BrowserPage(QWebPage):
def shouldInterruptJavaScript(self):
"""Override shouldInterruptJavaScript to use the statusbar."""
answer = message.ask(self._win_id, "Interrupt long-running "
"javascript?", usertypes.PromptMode.yesno)
answer = self._ask("Interrupt long-running javascript?",
usertypes.PromptMode.yesno)
if answer is None:
answer = True
return answer
@@ -314,30 +539,41 @@ class BrowserPage(QWebPage):
request: QNetworkRequest
typ: QWebPage::NavigationType
"""
if typ != QWebPage.NavigationTypeLinkClicked:
return True
url = request.url()
urlstr = url.toDisplayString()
if typ == QWebPage.NavigationTypeReload:
self.reloading.emit(url)
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())
self.open_target = usertypes.ClickTarget.normal
return False
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
open_target = self.view().open_target
if open_target == usertypes.ClickTarget.tab:
log.webview.debug("acceptNavigationRequest, url {}, type {}, hint "
"target {}, open_target {}".format(
urlstr, debug.qenum_key(QWebPage, typ),
self._hint_target, self.open_target))
if self._hint_target is not None:
target = self._hint_target
else:
target = self.open_target
self.open_target = usertypes.ClickTarget.normal
if target == usertypes.ClickTarget.tab:
tabbed_browser.tabopen(url, False)
return False
elif open_target == usertypes.ClickTarget.tab_bg:
elif target == usertypes.ClickTarget.tab_bg:
tabbed_browser.tabopen(url, True)
return False
elif open_target == usertypes.ClickTarget.window:
main_window = objreg.get('main-window', scope='window',
window=self._win_id)
win_id = main_window.spawn()
elif target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow()
window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
window=window.win_id)
tabbed_browser.tabopen(url, False)
return False
else:

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,11 +19,13 @@
"""The main browser widgets."""
import sys
import itertools
import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QApplication, QStyleFactory
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
@@ -31,7 +33,6 @@ from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg
from qutebrowser.browser import webpage, hints, webelem
from qutebrowser.commands import cmdexc
LoadStatus = usertypes.enum('LoadStatus', ['none', 'success', 'error', 'warn',
@@ -51,23 +52,25 @@ class WebView(QWebView):
hintmanager: The HintManager instance for this view.
progress: loading progress of this page.
scroll_pos: The current scroll position as (x%, y%) tuple.
statusbar_message: The current javscript statusbar message.
statusbar_message: The current javascript statusbar message.
inspector: The QWebInspector used for this webview.
load_status: loading status of this page (index into LoadStatus)
open_target: Where to open the next tab ("normal", "tab", "tab_bg")
viewing_source: Whether the webview is currently displaying source
code.
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
load.
registry: The ObjectRegistry associated with this tab.
tab_id: The tab ID of the view.
win_id: The window ID of the view.
search_text: The text of the last search.
search_flags: The search flags of the last search.
_cur_url: The current URL (accessed via cur_url property).
_has_ssl_errors: Whether SSL errors occured during loading.
_has_ssl_errors: Whether SSL errors occurred during loading.
_zoom: A NeighborList with the zoom levels.
_old_scroll_pos: The old scroll position.
_force_open_target: Override for open_target.
_check_insertmode: If True, in mouseReleaseEvent we should check if we
need to enter/leave insert mode.
_default_zoom_changed: Whether the zoom was changed from the default.
_win_id: The window ID of the view.
Signals:
scroll_pos_changed: Scroll percentage of current tab changed.
@@ -76,27 +79,36 @@ class WebView(QWebView):
linkHovered: QWebPages linkHovered signal exposed.
load_status_changed: The loading status changed
url_text_changed: Current URL string changed.
shutting_down: Emitted when the view is shutting down.
"""
scroll_pos_changed = pyqtSignal(int, int)
linkHovered = pyqtSignal(str, str, str)
load_status_changed = pyqtSignal(str)
url_text_changed = pyqtSignal(str)
shutting_down = pyqtSignal()
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
if sys.platform == 'darwin' and qtutils.version_check('5.4'):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
# See https://github.com/The-Compiler/qutebrowser/issues/462
self.setStyle(QStyleFactory.create('Fusion'))
self.win_id = win_id
self.load_status = LoadStatus.none
self._check_insertmode = False
self.inspector = None
self.scroll_pos = (-1, -1)
self.statusbar_message = ''
self._old_scroll_pos = (-1, -1)
self.open_target = usertypes.ClickTarget.normal
self._force_open_target = None
self._zoom = None
self._has_ssl_errors = False
self.keep_icon = False
self.search_text = None
self.search_flags = 0
self.selection_enabled = False
self.init_neighborlist()
self._set_bg_color()
cfg = objreg.get('config')
cfg.changed.connect(self.init_neighborlist)
# For some reason, this signal doesn't get disconnected automatically
@@ -113,36 +125,44 @@ class WebView(QWebView):
window=win_id)
tab_registry[self.tab_id] = self
objreg.register('webview', self, registry=self.registry)
page = webpage.BrowserPage(win_id, self)
self.setPage(page)
page = self._init_page()
hintmanager = hints.HintManager(win_id, self.tab_id, self)
hintmanager.mouse_event.connect(self.on_mouse_event)
hintmanager.set_open_target.connect(self.set_force_open_target)
hintmanager.start_hinting.connect(page.on_start_hinting)
hintmanager.stop_hinting.connect(page.on_stop_hinting)
objreg.register('hintmanager', hintmanager, registry=self.registry)
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
mode_manager.entered.connect(self.on_mode_entered)
mode_manager.left.connect(self.on_mode_left)
page.linkHovered.connect(self.linkHovered)
page.mainFrame().loadStarted.connect(self.on_load_started)
self.urlChanged.connect(self.on_url_changed)
page.mainFrame().loadFinished.connect(self.on_load_finished)
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
self.page().statusBarMessage.connect(
lambda msg: setattr(self, 'statusbar_message', msg))
self.page().networkAccessManager().sslErrors.connect(
lambda *args: setattr(self, '_has_ssl_errors', True))
self.viewing_source = False
self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
self._default_zoom_changed = False
if config.get('input', 'rocker-gestures'):
self.setContextMenuPolicy(Qt.PreventContextMenu)
self.urlChanged.connect(self.on_url_changed)
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
objreg.get('config').changed.connect(self.on_config_changed)
def _init_page(self):
"""Initialize the QWebPage used by this view."""
page = webpage.BrowserPage(self.win_id, self.tab_id, self)
self.setPage(page)
page.linkHovered.connect(self.linkHovered)
page.mainFrame().loadStarted.connect(self.on_load_started)
page.mainFrame().loadFinished.connect(self.on_load_finished)
page.statusBarMessage.connect(
lambda msg: setattr(self, 'statusbar_message', msg))
page.networkAccessManager().sslErrors.connect(
lambda *args: setattr(self, '_has_ssl_errors', True))
return page
def __repr__(self):
url = utils.elide(self.url().toDisplayString(), 50)
return utils.get_repr(self, tab_id=self.tab_id, url=url)
def __del__(self):
# Explicitely releasing the page here seems to prevent some segfaults
# Explicitly releasing the page here seems to prevent some segfaults
# when quitting.
# Copied from:
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
@@ -162,6 +182,15 @@ class WebView(QWebView):
self.load_status = val
self.load_status_changed.emit(val.name)
def _set_bg_color(self):
"""Set the webpage background color as configured."""
col = config.get('colors', 'webpage.bg')
palette = self.palette()
if col is None:
col = self.style().standardPalette().color(QPalette.Base)
palette.setColor(QPalette.Base, col)
self.setPalette(palette)
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""Reinitialize the zoom neighborlist if related config changed."""
@@ -171,6 +200,13 @@ class WebView(QWebView):
100)
self._default_zoom_changed = False
self.init_neighborlist()
elif section == 'input' and option == 'rocker-gestures':
if config.get('input', 'rocker-gestures'):
self.setContextMenuPolicy(Qt.PreventContextMenu)
else:
self.setContextMenuPolicy(Qt.DefaultContextMenu)
elif section == 'colors' and option == 'webpage.bg':
self._set_bg_color()
def init_neighborlist(self):
"""Initialize the _zoom neighborlist."""
@@ -185,19 +221,19 @@ class WebView(QWebView):
Args:
e: The QMouseEvent.
"""
if e.button() == Qt.XButton1:
# Back button on mice which have it.
if e.button() in (Qt.XButton1, Qt.LeftButton):
# Back button on mice which have it, or rocker gesture
if self.page().history().canGoBack():
self.back()
else:
message.error(self._win_id, "At beginning of history.",
message.error(self.win_id, "At beginning of history.",
immediately=True)
elif e.button() == Qt.XButton2:
# Forward button on mice which have it.
elif e.button() in (Qt.XButton2, Qt.RightButton):
# Forward button on mice which have it, or rocker gesture
if self.page().history().canGoForward():
self.forward()
else:
message.error(self._win_id, "At end of history.",
message.error(self.win_id, "At end of history.",
immediately=True)
def _mousepress_insertmode(self, e):
@@ -220,7 +256,7 @@ class WebView(QWebView):
# me, but it works this way.
hitresult = frame.hitTestContent(pos)
if hitresult.isNull():
# For some reason, the whole hitresult can be null sometimes (e.g.
# For some reason, the whole hit result can be null sometimes (e.g.
# on doodle menu links). If this is the case, we schedule a check
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
log.mouse.debug("Hitresult is null!")
@@ -229,7 +265,7 @@ class WebView(QWebView):
try:
elem = webelem.WebElementWrapper(hitresult.element())
except webelem.IsNullError:
# For some reason, the hitresult element can be a null element
# For some reason, the hit result element can be a null element
# sometimes (e.g. when clicking the timetable fields on
# http://www.sbb.ch/ ). If this is the case, we schedule a check
# later (in mouseReleaseEvent) which uses webelem.focus_elem.
@@ -239,12 +275,12 @@ class WebView(QWebView):
if ((hitresult.isContentEditable() and elem.is_writable()) or
elem.is_editable()):
log.mouse.debug("Clicked editable element!")
modeman.enter(self._win_id, usertypes.KeyMode.insert, 'click',
modeman.enter(self.win_id, usertypes.KeyMode.insert, 'click',
only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element!")
if config.get('input', 'auto-leave-insert-mode'):
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
'click')
def mouserelease_insertmode(self):
@@ -254,17 +290,17 @@ class WebView(QWebView):
self._check_insertmode = False
try:
elem = webelem.focus_elem(self.page().currentFrame())
except webelem.IsNullError:
log.mouse.warning("Element vanished!")
except (webelem.IsNullError, RuntimeError):
log.mouse.warning("Element/page vanished!")
return
if elem.is_editable():
log.mouse.debug("Clicked editable element (delayed)!")
modeman.enter(self._win_id, usertypes.KeyMode.insert,
modeman.enter(self.win_id, usertypes.KeyMode.insert,
'click-delayed', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element (delayed)!")
if config.get('input', 'auto-leave-insert-mode'):
modeman.maybe_leave(self._win_id, usertypes.KeyMode.insert,
modeman.maybe_leave(self.win_id, usertypes.KeyMode.insert,
'click-delayed')
def _mousepress_opentarget(self, e):
@@ -273,41 +309,30 @@ class WebView(QWebView):
Args:
e: The QMouseEvent.
"""
if self._force_open_target is not None:
self.open_target = self._force_open_target
self._force_open_target = None
log.mouse.debug("Setting force target: {}".format(
self.open_target))
elif (e.button() == Qt.MidButton or
e.modifiers() & Qt.ControlModifier):
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
background_tabs = config.get('tabs', 'background-tabs')
if e.modifiers() & Qt.ShiftModifier:
background_tabs = not background_tabs
if background_tabs:
self.open_target = usertypes.ClickTarget.tab_bg
target = usertypes.ClickTarget.tab_bg
else:
self.open_target = usertypes.ClickTarget.tab
log.mouse.debug("Middle click, setting target: {}".format(
self.open_target))
target = usertypes.ClickTarget.tab
self.page().open_target = target
log.mouse.debug("Middle click, setting target: {}".format(target))
else:
self.open_target = usertypes.ClickTarget.normal
self.page().open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target")
def shutdown(self):
"""Shut down the webview."""
self.shutting_down.emit()
# We disable javascript because that prevents some segfaults when
# quitting it seems.
log.destroy.debug("Shutting down {!r}.".format(self))
settings = self.settings()
settings.setAttribute(QWebSettings.JavascriptEnabled, False)
self.stop()
download_manager = objreg.get('download-manager', scope='window',
window=self._win_id)
nam = self.page().networkAccessManager()
if download_manager.has_downloads_with_nam(nam):
nam.setParent(download_manager)
else:
nam.shutdown()
self.page().shutdown()
def openurl(self, url):
"""Open a URL in the browser.
@@ -343,9 +368,8 @@ class WebView(QWebView):
if fuzzyval:
self._zoom.fuzzyval = int(perc)
if perc < 0:
raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
raise ValueError("Can't zoom {}%!".format(perc))
self.setZoomFactor(float(perc) / 100)
message.info(self._win_id, "Zoom level: {}%".format(perc))
self._default_zoom_changed = True
def zoom(self, offset):
@@ -353,9 +377,13 @@ class WebView(QWebView):
Args:
offset: The offset in the zoom level list.
Return:
The new zoom percentage.
"""
level = self._zoom.getitem(offset)
self.zoom_perc(level, fuzzyval=False)
return level
@pyqtSlot('QUrl')
def on_url_changed(self, url):
@@ -366,10 +394,12 @@ class WebView(QWebView):
if url.isValid():
self.cur_url = url
self.url_text_changed.emit(url.toDisplayString())
if not self.title():
self.titleChanged.emit(self.url().toDisplayString())
@pyqtSlot('QMouseEvent')
def on_mouse_event(self, evt):
"""Post a new mouseevent from a hintmanager."""
"""Post a new mouse event from a hintmanager."""
log.modes.debug("Hint triggered, focusing {!r}".format(self))
self.setFocus()
QApplication.postEvent(self, evt)
@@ -384,23 +414,29 @@ class WebView(QWebView):
@pyqtSlot()
def on_load_finished(self):
"""Handle auto-insert-mode after loading finished.
"""Handle a finished page load.
We don't take loadFinished's ok argument here as it always seems to be
true when the QWebPage has an ErrorPageExtension implemented.
See https://github.com/The-Compiler/qutebrowser/issues/84
"""
ok = not self.page().error_occured
ok = not self.page().error_occurred
if ok and not self._has_ssl_errors:
self._set_load_status(LoadStatus.success)
elif ok:
self._set_load_status(LoadStatus.warn)
else:
self._set_load_status(LoadStatus.error)
if not self.title():
self.titleChanged.emit(self.url().toDisplayString())
self._handle_auto_insert_mode(ok)
def _handle_auto_insert_mode(self, ok):
"""Handle auto-insert-mode after loading finished."""
if not config.get('input', 'auto-insert-mode'):
return
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
window=self.win_id)
cur_mode = mode_manager.mode
if cur_mode == usertypes.KeyMode.insert or not ok:
return
@@ -412,7 +448,7 @@ class WebView(QWebView):
return
log.modes.debug("focus element: {}".format(repr(elem)))
if elem.is_editable():
modeman.enter(self._win_id, usertypes.KeyMode.insert,
modeman.enter(self.win_id, usertypes.KeyMode.insert,
'load finished', only_if_normal=True)
@pyqtSlot(usertypes.KeyMode)
@@ -423,6 +459,25 @@ class WebView(QWebView):
log.webview.debug("Ignoring focus because mode {} was "
"entered.".format(mode))
self.setFocusPolicy(Qt.NoFocus)
elif mode == usertypes.KeyMode.caret:
settings = self.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
self.selection_enabled = bool(self.page().selectedText())
if self.isVisible():
# Sometimes the caret isn't immediately visible, but unfocusing
# and refocusing it fixes that.
self.clearFocus()
self.setFocus(Qt.OtherFocusReason)
# Move the caret to the first element in the viewport if there
# isn't any text which is already selected.
#
# Note: We can't use hasSelection() here, as that's always
# true in caret mode.
if not self.page().selectedText():
self.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js'))
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
@@ -431,18 +486,58 @@ class WebView(QWebView):
usertypes.KeyMode.yesno):
log.webview.debug("Restoring focus policy because mode {} was "
"left.".format(mode))
elif mode == usertypes.KeyMode.caret:
settings = self.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
if self.selection_enabled and self.hasSelection():
# Remove selection if it exists
self.triggerPageAction(QWebPage.MoveToNextChar)
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
self.selection_enabled = False
self.setFocusPolicy(Qt.WheelFocus)
@pyqtSlot(str)
def set_force_open_target(self, target):
"""Change the forced link target. Setter for _force_open_target.
def search(self, text, flags):
"""Search for text in the current page.
Args:
target: A string to set self._force_open_target to.
text: The text to search for.
flags: The QWebPage::FindFlags.
"""
t = getattr(usertypes.ClickTarget, target)
log.webview.debug("Setting force target to {}/{}".format(target, t))
self._force_open_target = t
log.webview.debug("Searching with text '{}' and flags "
"0x{:04x}.".format(text, int(flags)))
old_scroll_pos = self.scroll_pos
flags = QWebPage.FindFlags(flags)
found = self.findText(text, flags)
backward = flags & QWebPage.FindBackward
if not found and not flags & QWebPage.HighlightAllOccurrences and text:
# User disabled wrapping; but findText() just returns False. If we
# have a selection, we know there's a match *somewhere* on the page
if (not flags & QWebPage.FindWrapsAroundDocument and
self.hasSelection()):
if not backward:
message.warning(self.win_id, "Search hit BOTTOM without "
"match for: {}".format(text),
immediately=True)
else:
message.warning(self.win_id, "Search hit TOP without "
"match for: {}".format(text),
immediately=True)
else:
message.error(self.win_id, "Text '{}' not found on "
"page!".format(text), immediately=True)
else:
def check_scroll_pos():
"""Check if the scroll position got smaller and show info."""
if not backward and self.scroll_pos < old_scroll_pos:
message.info(self.win_id, "Search hit BOTTOM, continuing "
"at TOP", immediately=True)
elif backward and self.scroll_pos > old_scroll_pos:
message.info(self.win_id, "Search hit TOP, continuing at "
"BOTTOM", immediately=True)
# We first want QWebPage to refresh.
QTimer.singleShot(0, check_scroll_pos)
def createWindow(self, wintype):
"""Called by Qt when a page wants to create a new window.
@@ -467,7 +562,7 @@ class WebView(QWebView):
log.webview.warning("WebModalDialog requested, but we don't "
"support that!")
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
window=self.win_id)
return tabbed_browser.tabopen(background=False)
def paintEvent(self, e):
@@ -502,7 +597,7 @@ class WebView(QWebView):
This does the following things:
- Check if a link was clicked with the middle button or Ctrl and
set the open_target attribute accordingly.
set the page's open_target attribute accordingly.
- Emit the editable_elem_selected signal if an editable element was
clicked.
@@ -512,7 +607,10 @@ class WebView(QWebView):
Return:
The superclass return value.
"""
if e.button() in (Qt.XButton1, Qt.XButton2):
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
e.buttons() == Qt.LeftButton | Qt.RightButton)
if e.button() in (Qt.XButton1, Qt.XButton2) or is_rocker_gesture:
self._mousepress_backforward(e)
super().mousePressEvent(e)
return
@@ -526,3 +624,30 @@ class WebView(QWebView):
# We want to make sure we check the focus element after the WebView is
# updated completely.
QTimer.singleShot(0, self.mouserelease_insertmode)
def contextMenuEvent(self, e):
"""Save a reference to the context menu so we can close it."""
menu = self.page().createStandardContextMenu()
self.shutting_down.connect(menu.close)
modeman.instance(self.win_id).entered.connect(menu.close)
menu.exec_(e.globalPos())
def wheelEvent(self, e):
"""Zoom on Ctrl-Mousewheel.
Args:
e: The QWheelEvent.
"""
if e.modifiers() & Qt.ControlModifier:
e.accept()
divider = config.get('input', 'mouse-zoom-divider')
factor = self.zoomFactor() + e.angleDelta().y() / divider
if factor < 0:
return
perc = int(100 * factor)
message.info(self.win_id, "Zoom level: {}%".format(perc))
self._zoom.fuzzyval = perc
self.setZoomFactor(factor)
self._default_zoom_changed = True
else:
super().wheelEvent(e)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -81,12 +81,11 @@ class ArgumentParser(argparse.ArgumentParser):
raise ArgumentParserExit(status, msg)
def error(self, msg):
raise ArgumentParserError(msg[0].upper() + msg[1:])
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.
@@ -102,12 +101,11 @@ def enum_getter(enum):
return _get_enum_item
def multitype_conv(tpl):
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(tpl):
for typ in set(types):
if isinstance(typ, str):
if value == typ:
return value
@@ -121,6 +119,8 @@ def multitype_conv(tpl):
return typ(value)
except (TypeError, ValueError):
pass
else:
raise ValueError("Unknown type {!r}!".format(typ))
raise cmdexc.ArgumentTypeError('Invalid value {}.'.format(value))
return _convert

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -32,7 +32,7 @@ class CommandError(Exception):
class CommandMetaError(Exception):
"""Common base class for exceptions occuring before a command is run."""
"""Common base class for exceptions occurring before a command is run."""
class NoSuchCommandError(CommandMetaError):

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,9 +21,10 @@
Module attributes:
cmd_dict: A mapping from command-strings to command objects.
aliases: A list of all aliases, needed for doc generation.
"""
from qutebrowser.utils import usertypes, qtutils, log
from qutebrowser.utils import qtutils, log
from qutebrowser.commands import command, cmdexc
cmd_dict = {}
@@ -99,22 +100,11 @@ class register: # pylint: disable=invalid-name
Attributes:
_instance: The object from the object registry to be used as "self".
_scope: The scope to get _instance for.
_name: The name (as string) or names (as list) of the command.
_maxsplit: The maxium amounts of splits to do for the commandline, or
None.
_hide: Whether to hide the command or not.
_completion: Which completion to use for arguments, as a list of
strings.
_modes/_not_modes: List of modes to use/not use.
_needs_js: If javascript is needed for this command.
_debug: Whether this is a debugging command (only shown with --debug).
_ignore_args: Whether to ignore the arguments of the function.
_kwargs: The arguments to pass to Command.
"""
def __init__(self, instance=None, name=None, maxsplit=None, hide=False,
completion=None, modes=None, not_modes=None, needs_js=False,
debug=False, ignore_args=False, scope='global'):
def __init__(self, *, instance=None, name=None, **kwargs):
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
@@ -122,33 +112,14 @@ class register: # pylint: disable=invalid-name
Args:
See class attributes.
"""
# pylint: disable=too-many-arguments
if modes is not None and not_modes is not None:
raise ValueError("Only modes or not_modes can be given!")
self._name = name
self._maxsplit = maxsplit
self._hide = hide
self._instance = instance
self._scope = scope
self._completion = completion
self._modes = modes
self._not_modes = not_modes
self._needs_js = needs_js
self._debug = debug
self._ignore_args = ignore_args
if modes is not None:
for m in modes:
if not isinstance(m, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(m))
if not_modes is not None:
for m in not_modes:
if not isinstance(m, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(m))
self._name = name
self._kwargs = kwargs
def _get_names(self, func):
"""Get the name(s) which should be used for the current command.
If the name hasn't been overridden explicitely, the function name is
If the name hasn't been overridden explicitly, the function name is
transformed.
If it has been set, it can either be a string which is
@@ -187,12 +158,8 @@ class register: # pylint: disable=invalid-name
for name in names:
if name in cmd_dict:
raise ValueError("{} is already registered!".format(name))
cmd = command.Command(
name=names[0], maxsplit=self._maxsplit, hide=self._hide,
instance=self._instance, scope=self._scope,
completion=self._completion, modes=self._modes,
not_modes=self._not_modes, needs_js=self._needs_js,
is_debug=self._debug, ignore_args=self._ignore_args, handler=func)
cmd = command.Command(name=names[0], instance=self._instance,
handler=func, **self._kwargs)
for name in names:
cmd_dict[name] = cmd
aliases += names[1:]

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -25,7 +25,13 @@ import collections
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import log, utils, message, debug, docutils, objreg
from qutebrowser.utils import log, utils, message, docutils, objreg, usertypes
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 Command:
@@ -37,16 +43,17 @@ class Command:
maxsplit: The maximum amount of splits to do for the commandline, or
None.
hide: Whether to hide the arguments or not.
deprecated: False, or a string to describe why a command is deprecated.
desc: The description of the command.
handler: The handler function to call.
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.
special_params: A dict with the names of the special parameters as
values.
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.
_name_conv: A mapping of argument names to parameter names.
_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.
@@ -59,26 +66,45 @@ class Command:
"""
AnnotationInfo = collections.namedtuple('AnnotationInfo',
['kwargs', 'type', 'name', 'flag',
'special'])
['kwargs', 'type', 'flag', 'hide',
'metavar'])
def __init__(self, name, maxsplit, hide, instance, completion, modes,
not_modes, needs_js, is_debug, ignore_args,
handler, scope):
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):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-arguments,too-many-locals
if modes is not None and not_modes is not None:
raise ValueError("Only modes or not_modes can be given!")
if modes is not None:
for m in modes:
if not isinstance(m, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(m))
if not_modes is not None:
for m in not_modes:
if not isinstance(m, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(m))
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.debug = is_debug
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,
@@ -91,12 +117,13 @@ class Command:
self.namespace = None
self._count = None
self.pos_args = []
self.special_params = {'count': None, 'win_id': None}
self.desc = None
self.flags_with_args = []
self._type_conv = {}
self._name_conv = {}
self._inspect_func()
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))
def _check_prerequisites(self, win_id):
"""Check if the command is permitted to run currently.
@@ -121,6 +148,9 @@ class Command:
QWebSettings.JavascriptEnabled):
raise cmdexc.PrerequisitesError(
"{}: This command needs javascript enabled.".format(self.name))
if self.deprecated:
message.warning(win_id, '{} is deprecated - {}'.format(
self.name, self.deprecated))
def _check_func(self):
"""Make sure the function parameters don't violate any rules."""
@@ -131,7 +161,8 @@ class Command:
elif 'self' not in signature.parameters and self._instance is not None:
raise TypeError("{} is not a class method, but instance was "
"given!".format(self.name[0]))
elif inspect.getfullargspec(self.handler).varkw is not None:
elif any(param.kind == inspect.Parameter.VAR_KEYWORD
for param in signature.parameters.values()):
raise TypeError("{}: functions with varkw arguments are not "
"supported!".format(self.name[0]))
@@ -151,84 +182,68 @@ class Command:
type_conv[param.name] = argparser.multitype_conv(typ)
return type_conv
def _get_nameconv(self, param, annotation_info):
"""Get a dict with a name conversion for the paraeter.
Args:
param: The inspect.Parameter to handle.
annotation_info: The AnnotationInfo tuple for the parameter.
"""
d = {}
if annotation_info.name is not None:
d[param.name] = annotation_info.name
return d
def _inspect_special_param(self, param, annotation_info):
def _inspect_special_param(self, param):
"""Check if the given parameter is a special one.
Args:
param: The inspect.Parameter to handle.
annotation_info: The AnnotationInfo tuple for the parameter.
Return:
True if the parameter is special, False otherwise.
"""
special = annotation_info.special
if special == 'count':
if self.special_params['count'] is not None:
raise ValueError("Registered multiple parameters ({}/{}) as "
"count!".format(self.special_params['count'],
param.name))
if param.name == self.count_arg:
if param.default is inspect.Parameter.empty:
raise TypeError("{}: handler has count parameter "
"without default!".format(self.name))
self.special_params['count'] = param.name
return True
elif special == 'win_id':
if self.special_params['win_id'] is not None:
raise ValueError("Registered multiple parameters ({}/{}) as "
"win_id!".format(
self.special_params['win_id'],
param.name))
self.special_params['win_id'] = param.name
elif param.name == self.win_id_arg:
return True
elif special is None:
return False
else:
raise ValueError("{}: Invalid value '{}' for 'special' "
"annotation!".format(self.name, special))
def _inspect_func(self):
"""Inspect the function to get useful informations from it.
Sets instance attributes (desc, type_conv, name_conv) based on the
informations.
Return:
How many user-visible arguments the command has.
"""
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, annotation_info):
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))
self._name_conv.update(
self._get_nameconv(param, annotation_info))
callsig = debug.format_call(
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
def _param_to_argparse_kwargs(self, param, annotation_info):
"""Get argparse keyword arguments for a parameter.
@@ -248,11 +263,13 @@ class Command:
except KeyError:
pass
kwargs['dest'] = param.name
if isinstance(typ, tuple):
pass
kwargs['metavar'] = annotation_info.metavar or param.name
elif utils.is_enum(typ):
kwargs['choices'] = [e.name.replace('_', '-') for e in typ]
kwargs['metavar'] = param.name
kwargs['choices'] = [arg_name(e.name) for e in typ]
kwargs['metavar'] = annotation_info.metavar or param.name
elif typ is bool:
kwargs['action'] = 'store_true'
elif typ is not None:
@@ -279,8 +296,8 @@ class Command:
A list of args.
"""
args = []
name = annotation_info.name or param.name
shortname = annotation_info.flag or param.name[0]
name = arg_name(param.name)
shortname = annotation_info.flag or name[0]
if len(shortname) != 1:
raise ValueError("Flag '{}' of parameter {} (command {}) must be "
"exactly 1 char!".format(shortname, name,
@@ -292,11 +309,11 @@ class Command:
args.append(long_flag)
args.append(short_flag)
self.opt_args[param.name] = long_flag, short_flag
if param.kind == inspect.Parameter.KEYWORD_ONLY:
self.flags_with_args.append(param.name)
if typ is not bool:
self.flags_with_args += [short_flag, long_flag]
else:
args.append(name)
self.pos_args.append((param.name, name))
if not annotation_info.hide:
self.pos_args.append((param.name, name))
return args
def _parse_annotation(self, param):
@@ -313,12 +330,12 @@ class Command:
flag: The short name/flag if overridden.
name: The long name if overridden.
"""
info = {'kwargs': {}, 'type': None, 'flag': None, 'name': None,
'special': None}
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', 'special'):
for field in ('type', 'flag', 'name', 'hide', 'metavar'):
if field in param.annotation:
info[field] = param.annotation[field]
if 'nargs' in param.annotation:
@@ -348,10 +365,17 @@ class Command:
args: The positional argument list. Gets modified directly.
"""
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
if self._scope is not 'window':
if self._scope == 'global':
tab_id = None
win_id = None
obj = objreg.get(self._instance, scope=self._scope,
window=win_id)
elif self._scope == 'tab':
tab_id = 'current'
elif self._scope == 'window':
tab_id = None
else:
raise ValueError("Invalid scope {}!".format(self._scope))
obj = objreg.get(self._instance, scope=self._scope, window=win_id,
tab=tab_id)
args.append(obj)
def _get_count_arg(self, param, args, kwargs):
@@ -391,19 +415,18 @@ class Command:
raise TypeError("{}: invalid parameter type {} for argument "
"{!r}!".format(self.name, param.kind, param.name))
def _get_param_name_and_value(self, param):
"""Get the converted name and value for an inspect.Parameter."""
name = self._name_conv.get(param.name, param.name)
value = getattr(self.namespace, name)
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)
return name, value
return value
def _get_call_args(self, win_id): # noqa
def _get_call_args(self, win_id):
"""Get arguments for a function call.
Args:
@@ -412,7 +435,6 @@ class Command:
Return:
An (args, kwargs) tuple.
"""
args = []
kwargs = {}
signature = inspect.signature(self.handler)
@@ -428,22 +450,22 @@ class Command:
# Special case for 'self'.
self._get_self_arg(win_id, param, args)
continue
elif param.name == self.special_params['count']:
elif param.name == self.count_arg:
# Special case for count parameter.
self._get_count_arg(param, args, kwargs)
continue
elif param.name == self.special_params['win_id']:
elif param.name == self.win_id_arg:
# Special case for win_id parameter.
self._get_win_id_arg(win_id, param, args, kwargs)
continue
name, value = self._get_param_name_and_value(param)
value = self._get_param_value(param)
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
args.append(value)
elif param.kind == inspect.Parameter.VAR_POSITIONAL:
if value is not None:
args += value
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
kwargs[name] = value
kwargs[param.name] = value
else:
raise TypeError("{}: Invalid parameter type {} for argument "
"'{}'!".format(
@@ -479,5 +501,5 @@ class Command:
posargs, kwargs = self._get_call_args(win_id)
self._check_prerequisites(win_id)
log.commands.debug('Calling {}'.format(
debug.format_call(self.handler, posargs, kwargs)))
debug_utils.format_call(self.handler, posargs, kwargs)))
self.handler(*posargs, **kwargs)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,151 +19,52 @@
"""Module containing command managers (SearchRunner and CommandRunner)."""
import re
import collections
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
from PyQt5.QtWebKitWidgets import QWebPage
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
from qutebrowser.config import config, configexc
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, log, utils, objreg
from qutebrowser.utils import message, log, objreg, qtutils
from qutebrowser.misc import split
ParseResult = collections.namedtuple('ParseResult', 'cmd, args, cmdline')
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)
for arg in arglist:
if arg == '{url}':
# Note we have to do this in here as the user gets an error message
# by current_url if no URL is open yet.
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)
for arg in arglist:
if arg == '{url}':
args.append(url)
else:
args.append(arg)
return args
class SearchRunner(QObject):
"""Run searches on webpages.
Attributes:
_text: The text from the last search.
_flags: The flags from the last search.
Signals:
do_search: Emitted when a search should be started.
arg 1: Search string.
arg 2: Flags to use.
"""
do_search = pyqtSignal(str, 'QWebPage::FindFlags')
def __init__(self, parent=None):
super().__init__(parent)
self._text = None
self._flags = 0
def __repr__(self):
return utils.get_repr(self, text=self._text, flags=self._flags)
def _search(self, text, rev=False):
"""Search for a text on the current page.
Args:
text: The text to search for.
rev: Search direction, True if reverse, else False.
"""
if self._text is not None and self._text != text:
# We first clear the marked text, then the highlights
self.do_search.emit('', 0)
self.do_search.emit('', QWebPage.HighlightAllOccurrences)
self._text = text
self._flags = 0
ignore_case = config.get('general', 'ignore-case')
if ignore_case == 'smart':
if not text.islower():
self._flags |= QWebPage.FindCaseSensitively
elif not ignore_case:
self._flags |= QWebPage.FindCaseSensitively
if config.get('general', 'wrap-search'):
self._flags |= QWebPage.FindWrapsAroundDocument
if rev:
self._flags |= QWebPage.FindBackward
# We actually search *twice* - once to highlight everything, then again
# to get a mark so we can navigate.
self.do_search.emit(self._text, self._flags)
self.do_search.emit(self._text, self._flags |
QWebPage.HighlightAllOccurrences)
@pyqtSlot(str)
def search(self, text):
"""Search for a text on a website.
Args:
text: The text to search for.
"""
self._search(text)
@pyqtSlot(str)
def search_rev(self, text):
"""Search for a text on a website in reverse direction.
Args:
text: The text to search for.
"""
self._search(text, rev=True)
@cmdutils.register(instance='search-runner', hide=True, scope='window')
def search_next(self, count: {'special': 'count'}=1):
"""Continue the search to the ([count]th) next term.
Args:
count: How many elements to ignore.
"""
if self._text is not None:
for _ in range(count):
self.do_search.emit(self._text, self._flags)
@cmdutils.register(instance='search-runner', hide=True, scope='window')
def search_prev(self, count: {'special': 'count'}=1):
"""Continue the search to the ([count]th) previous term.
Args:
count: How many elements to ignore.
"""
if self._text is None:
return
# The int() here serves as a QFlags constructor to create a copy of the
# QFlags instance rather as a reference. I don't know why it works this
# way, but it does.
flags = int(self._flags)
if flags & QWebPage.FindBackward:
flags &= ~QWebPage.FindBackward
else:
flags |= QWebPage.FindBackward
for _ in range(count):
self.do_search.emit(self._text, flags)
class CommandRunner(QObject):
"""Parse and run qutebrowser commandline commands.
Attributes:
_cmd: The command which was parsed.
_args: The arguments which were parsed.
_win_id: The window this CommandRunner is associated with.
"""
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._cmd = None
self._args = []
self._win_id = win_id
def _get_alias(self, text):
@@ -189,7 +90,34 @@ class CommandRunner(QObject):
new_cmd += ' '
return new_cmd
def parse(self, text, aliases=True, fallback=False, keep=False):
def parse_all(self, text, *args, **kwargs):
"""Split a command on ;; and parse all parts.
If the first command in the commandline is a non-split one, it only
returns that.
Args:
text: Text to parse.
*args/**kwargs: Passed to parse().
Yields:
ParseResult tuples.
"""
if ';;' in text:
# Get the first command and check if it doesn't want to have ;;
# split.
first = text.split(';;')[0]
result = self.parse(first, *args, **kwargs)
if result.cmd.no_cmd_split:
sub_texts = [text]
else:
sub_texts = [e.strip() for e in text.split(';;')]
else:
sub_texts = [text]
for sub in sub_texts:
yield self.parse(sub, *args, **kwargs)
def parse(self, text, *, aliases=True, fallback=False, keep=False):
"""Split the commandline text into command and arguments.
Args:
@@ -200,7 +128,7 @@ class CommandRunner(QObject):
keep: Whether to keep special chars and whitespace
Return:
A split string commandline, e.g ['open', 'www.google.com']
A (cmd, args, cmdline) ParseResult tuple.
"""
cmdstr, sep, argstr = text.partition(' ')
if not cmdstr and not fallback:
@@ -212,29 +140,34 @@ class CommandRunner(QObject):
return self.parse(new_cmd, aliases=False, fallback=fallback,
keep=keep)
try:
self._cmd = cmdutils.cmd_dict[cmdstr]
cmd = cmdutils.cmd_dict[cmdstr]
except KeyError:
if fallback and keep:
cmdstr, sep, argstr = text.partition(' ')
return [cmdstr, sep] + argstr.split()
elif fallback:
return text.split()
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))
self._split_args(argstr, keep)
retargs = self._args[:]
if keep and retargs:
return [cmdstr, sep + retargs[0]] + retargs[1:]
elif keep:
return [cmdstr, sep]
raise cmdexc.NoSuchCommandError('{}: no such command'.format(
cmdstr))
else:
return [cmdstr] + retargs
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[:]
return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
def _split_args(self, argstr, keep):
def _split_args(self, cmd, argstr, keep):
"""Split the arguments from an arg string.
Args:
cmd: The command we're currently handling.
argstr: An argument string.
keep: Whether to keep special chars and whitespace
@@ -242,9 +175,9 @@ class CommandRunner(QObject):
A list containing the splitted strings.
"""
if not argstr:
self._args = []
elif self._cmd.maxsplit is None:
self._args = split.split(argstr, keep=keep)
return []
elif cmd.maxsplit is None:
return split.split(argstr, keep=keep)
else:
# If split=False, we still want to split the flags, but not
# everything after that.
@@ -262,23 +195,16 @@ class CommandRunner(QObject):
for i, arg in enumerate(split_args):
arg = arg.strip()
if arg.startswith('-'):
if arg.lstrip('-') in self._cmd.flags_with_args:
if arg in cmd.flags_with_args:
flag_arg_count += 1
else:
self._args = []
maxsplit = i + self._cmd.maxsplit + flag_arg_count
args = split.simple_split(argstr, keep=keep,
maxsplit = i + cmd.maxsplit + flag_arg_count
return split.simple_split(argstr, keep=keep,
maxsplit=maxsplit)
for s in args:
# remove quotes and replace \" by "
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
s = re.sub(r"""\\(["'])""", r'\1', s)
self._args.append(s)
break
else:
else: # pylint: disable=useless-else-on-loop
# If there are only flags, we got it right on the first try
# already.
self._args = split_args
return split_args
def run(self, text, count=None):
"""Parse a command from a line of text and run it.
@@ -287,16 +213,12 @@ class CommandRunner(QObject):
text: The text to parse.
count: The count to pass to the command.
"""
if ';;' in text:
for sub in text.split(';;'):
self.run(sub, count)
return
self.parse(text)
args = replace_variables(self._win_id, self._args)
if count is not None:
self._cmd.run(self._win_id, args, count=count)
else:
self._cmd.run(self._win_id, args)
for result in self.parse_all(text):
args = replace_variables(self._win_id, result.args)
if count is not None:
result.cmd.run(self._win_id, args, count=count)
else:
result.cmd.run(self._win_id, args)
@pyqtSlot(str, int)
def run_safely(self, text, count=None):

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,72 +17,51 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Functions to execute an userscript."""
"""Functions to execute a userscript."""
import os
import os.path
import tempfile
import select
from PyQt5.QtCore import (pyqtSignal, QObject, QThread, QStandardPaths,
QProcessEnvironment, QProcess, QUrl)
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QSocketNotifier
from qutebrowser.utils import message, log, objreg, standarddir
from qutebrowser.commands import runners, cmdexc
from qutebrowser.config import config
from qutebrowser.misc import guiprocess
class _BlockingFIFOReader(QObject):
class _QtFIFOReader(QObject):
"""A worker which reads commands from a FIFO endlessly.
This is intended to be run in a separate QThread. It reads from the given
FIFO even across EOF so an userscript can write to it multiple times.
It uses select() so it can timeout once per second, checking if termination
was requested.
Attributes:
_filepath: The filename of the FIFO to read.
fifo: The file object which is being read.
Signals:
got_line: Emitted when a new line arrived.
finished: Emitted when the read loop realized it should terminate and
is about to do so.
"""
"""A FIFO reader based on a QSocketNotifier."""
got_line = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, filepath, parent=None):
super().__init__(parent)
self._filepath = filepath
self.fifo = None
# We open as R/W so we never get EOF and have to reopen the pipe.
# See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/
# We also use os.open and os.fdopen rather than built-in open so we
# can add O_NONBLOCK.
fd = os.open(filepath, os.O_RDWR |
os.O_NONBLOCK) # pylint: disable=no-member
self.fifo = os.fdopen(fd, 'r')
self._notifier = QSocketNotifier(fd, QSocketNotifier.Read, self)
self._notifier.activated.connect(self.read_line)
def read(self):
"""Blocking read loop which emits got_line when a new line arrived."""
try:
# We open as R/W so we never get EOF and have to reopen the pipe.
# See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/
# We also use os.open and os.fdopen rather than built-in open so we
# can add O_NONBLOCK.
fd = os.open(self._filepath, os.O_RDWR |
os.O_NONBLOCK) # pylint: disable=no-member
self.fifo = os.fdopen(fd, 'r')
except OSError:
log.procs.exception("Failed to read FIFO")
self.finished.emit()
return
while True:
log.procs.debug("thread loop")
ready_r, _ready_w, _ready_e = select.select([self.fifo], [], [], 1)
if ready_r:
log.procs.debug("reading data")
for line in self.fifo:
self.got_line.emit(line.rstrip())
if QThread.currentThread().isInterruptionRequested():
self.finished.emit()
return
@pyqtSlot()
def read_line(self):
"""(Try to) read a line from the FIFO."""
log.procs.debug("QSocketNotifier triggered!")
self._notifier.setEnabled(False)
for line in self.fifo:
self.got_line.emit(line.rstrip('\r\n'))
self._notifier.setEnabled(True)
def cleanup(self):
"""Clean up so the FIFO can be closed."""
self._notifier.setEnabled(False)
class _BaseUserscriptRunner(QObject):
@@ -91,13 +70,9 @@ class _BaseUserscriptRunner(QObject):
Attributes:
_filepath: The path of the file/FIFO which is being read.
_proc: The QProcess which is being executed.
_proc: The GUIProcess which is being executed.
_win_id: The window ID this runner is associated with.
Class attributes:
PROCESS_MESSAGES: A mapping of QProcess::ProcessError members to
human-readable error strings.
Signals:
got_cmd: Emitted when a new command arrived and should be executed.
finished: Emitted when the userscript finished running.
@@ -106,149 +81,129 @@ class _BaseUserscriptRunner(QObject):
got_cmd = pyqtSignal(str)
finished = pyqtSignal()
PROCESS_MESSAGES = {
QProcess.FailedToStart: "The process failed to start.",
QProcess.Crashed: "The process crashed.",
QProcess.Timedout: "The last waitFor...() function timed out.",
QProcess.WriteError: ("An error occurred when attempting to write to "
"the process."),
QProcess.ReadError: ("An error occurred when attempting to read from "
"the process."),
QProcess.UnknownError: "An unknown error occurred.",
}
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._filepath = None
self._proc = None
self._env = None
def _run_process(self, cmd, *args, env):
"""Start the given command via QProcess.
def _run_process(self, cmd, *args, env, verbose):
"""Start the given command.
Args:
cmd: The command to be started.
*args: The arguments to hand to the command
env: A dictionary of environment variables to add.
verbose: Show notifications when the command started/exited.
"""
self._proc = QProcess(self)
procenv = QProcessEnvironment.systemEnvironment()
procenv.insert('QUTE_FIFO', self._filepath)
if env is not None:
for k, v in env.items():
procenv.insert(k, v)
self._proc.setProcessEnvironment(procenv)
self._env = {'QUTE_FIFO': self._filepath}
self._env.update(env)
self._proc = guiprocess.GUIProcess(self._win_id, 'userscript',
additional_env=self._env,
verbose=verbose, parent=self)
self._proc.error.connect(self.on_proc_error)
self._proc.finished.connect(self.on_proc_finished)
self._proc.start(cmd, args)
def _cleanup(self):
"""Clean up the temporary file."""
try:
os.remove(self._filepath)
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(self._win_id,
"Failed to delete tempfile... ({})".format(e))
"""Clean up temporary files."""
tempfiles = [self._filepath]
if 'QUTE_HTML' in self._env:
tempfiles.append(self._env['QUTE_HTML'])
if 'QUTE_TEXT' in self._env:
tempfiles.append(self._env['QUTE_TEXT'])
for fn in tempfiles:
log.procs.debug("Deleting temporary file {}.".format(fn))
try:
os.remove(fn)
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(
self._win_id, "Failed to delete tempfile {} ({})!".format(
fn, e))
self._filepath = None
self._proc = None
self._env = None
def run(self, cmd, *args, env=None):
def run(self, cmd, *args, env=None, verbose=False):
"""Run the userscript given.
Needs to be overridden by superclasses.
Needs to be overridden by subclasses.
Args:
cmd: The command to be started.
*args: The arguments to hand to the command
env: A dictionary of environment variables to add.
verbose: Show notifications when the command started/exited.
"""
raise NotImplementedError
def on_proc_finished(self):
"""Called when the process has finished.
Needs to be overridden by superclasses.
Needs to be overridden by subclasses.
"""
raise NotImplementedError
def on_proc_error(self, error):
"""Called when the process encountered an error."""
msg = self.PROCESS_MESSAGES[error]
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error(self._win_id,
"Error while calling userscript: {}".format(msg))
raise NotImplementedError
class _POSIXUserscriptRunner(_BaseUserscriptRunner):
"""Userscript runner to be used on POSIX. Uses _BlockingFIFOReader.
"""Userscript runner to be used on POSIX. Uses _QtFIFOReader.
The OS must have support for named pipes and select(). Commands are
executed immediately when they arrive in the FIFO.
Commands are executed immediately when they arrive in the FIFO.
Attributes:
_reader: The _BlockingFIFOReader instance.
_thread: The QThread where reader runs.
_reader: The _QtFIFOReader instance.
"""
def __init__(self, win_id, parent=None):
super().__init__(win_id, parent)
self._reader = None
self._thread = None
def run(self, cmd, *args, env=None):
rundir = standarddir.get(QStandardPaths.RuntimeLocation)
def run(self, cmd, *args, env=None, verbose=False):
try:
# tempfile.mktemp is deprecated and discouraged, but we use it here
# to create a FIFO since the only other alternative would be to
# create a directory and place the FIFO there, which sucks. Since
# os.kfifo will raise an exception anyways when the path doesn't
# os.mkfifo will raise an exception anyways when the path doesn't
# exist, it shouldn't be a big issue.
self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
self._filepath = tempfile.mktemp(prefix='qutebrowser-userscript-',
dir=standarddir.runtime())
os.mkfifo(self._filepath) # pylint: disable=no-member
except OSError as e:
message.error(self._win_id, "Error while creating FIFO: {}".format(
e))
return
self._reader = _BlockingFIFOReader(self._filepath)
self._thread = QThread(self)
self._reader.moveToThread(self._thread)
self._reader = _QtFIFOReader(self._filepath)
self._reader.got_line.connect(self.got_cmd)
self._thread.started.connect(self._reader.read)
self._reader.finished.connect(self.on_reader_finished)
self._thread.finished.connect(self.on_thread_finished)
self._run_process(cmd, *args, env=env)
self._thread.start()
self._run_process(cmd, *args, env=env, verbose=verbose)
def on_proc_finished(self):
"""Interrupt the reader when the process finished."""
log.procs.debug("proc finished")
self._thread.requestInterruption()
self.finish()
def on_proc_error(self, error):
"""Interrupt the reader when the process had an error."""
super().on_proc_error(error)
self._thread.requestInterruption()
self.finish()
def on_reader_finished(self):
def finish(self):
"""Quit the thread and clean up when the reader finished."""
log.procs.debug("reader finished")
self._thread.quit()
log.procs.debug("Cleaning up")
self._reader.cleanup()
self._reader.fifo.close()
self._reader.deleteLater()
self._reader = None
super()._cleanup()
self.finished.emit()
def on_thread_finished(self):
"""Clean up the QThread object when the thread finished."""
log.procs.debug("thread finished")
self._thread.deleteLater()
class _WindowsUserscriptRunner(_BaseUserscriptRunner):
@@ -281,7 +236,6 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
def on_proc_finished(self):
"""Read back the commands when the process finished."""
log.procs.debug("proc finished")
try:
with open(self._filepath, 'r', encoding='utf-8') as f:
for line in f:
@@ -293,18 +247,17 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
def on_proc_error(self, error):
"""Clean up when the process had an error."""
super().on_proc_error(error)
self._cleanup()
self.finished.emit()
def run(self, cmd, *args, env=None):
def run(self, cmd, *args, env=None, verbose=False):
try:
self._oshandle, self._filepath = tempfile.mkstemp(text=True)
except OSError as e:
message.error(self._win_id, "Error while creating tempfile: "
"{}".format(e))
return
self._run_process(cmd, *args, env=env)
self._run_process(cmd, *args, env=env, verbose=verbose)
class _DummyUserscriptRunner:
@@ -320,8 +273,9 @@ class _DummyUserscriptRunner:
finished = pyqtSignal()
def run(self, _cmd, *_args, _env=None):
def run(self, cmd, *args, env=None, verbose=False):
"""Print an error as userscripts are not supported."""
# pylint: disable=unused-argument,unused-variable
self.finished.emit()
raise cmdexc.CommandError(
"Userscripts are not supported on this platform!")
@@ -337,16 +291,59 @@ else:
UserscriptRunner = _DummyUserscriptRunner
def run(cmd, *args, url, win_id):
"""Convenience method to run an userscript."""
def store_source(frame):
"""Store HTML/plaintext in files.
This writes files containing the HTML/plaintext source of the page, and
returns a dict with the paths as QUTE_HTML/QUTE_TEXT.
Args:
frame: The QWebFrame to get the info from, or None to do nothing.
Return:
A dictionary with the needed environment variables.
Warning:
The caller is responsible to delete the files after using them!
"""
if frame is None:
return {}
env = {}
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
suffix='.html',
delete=False) as html_file:
html_file.write(frame.toHtml())
env['QUTE_HTML'] = html_file.name
with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8',
suffix='.txt',
delete=False) as txt_file:
txt_file.write(frame.toPlainText())
env['QUTE_TEXT'] = txt_file.name
return env
def run(cmd, *args, win_id, env, verbose=False):
"""Convenience method to run a userscript.
Args:
cmd: The userscript binary to run.
*args: The arguments to pass to the userscript.
win_id: The window id the userscript is executed in.
env: A dictionary of variables to add to the process environment.
verbose: Show notifications when the command started/exited.
"""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
# We don't remove the password in the URL here, as it's probably safe to
# pass via env variable..
urlstr = url.toString(QUrl.FullyEncoded)
commandrunner = runners.CommandRunner(win_id, tabbed_browser)
runner = UserscriptRunner(win_id, tabbed_browser)
runner.got_cmd.connect(
lambda cmd: log.commands.debug("Got userscript command: {}".format(
cmd)))
runner.got_cmd.connect(commandrunner.run_safely)
runner.run(cmd, *args, env={'QUTE_URL': urlstr})
user_agent = config.get('network', 'user-agent')
if user_agent is not None:
env['QUTE_USER_AGENT'] = user_agent
cmd = os.path.expanduser(cmd)
runner.run(cmd, *args, env=env, verbose=verbose)
runner.finished.connect(commandrunner.deleteLater)
runner.finished.connect(runner.deleteLater)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,14 +19,12 @@
"""Completer attached to a CompletionView."""
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
from qutebrowser.config import config, configdata
from qutebrowser.commands import cmdutils, runners
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils, runners
from qutebrowser.utils import usertypes, log, objreg, utils
from qutebrowser.completion.models import completion as models
from qutebrowser.completion.models.sortfilter import (
CompletionFilterModel as CFM)
from qutebrowser.completion.models import instances, sortfilter
class Completer(QObject):
@@ -34,7 +32,6 @@ class Completer(QObject):
"""Completer which manages completions in a CompletionView.
Attributes:
models: dict of available completion models.
_cmd: The statusbar Command object this completer belongs to.
_ignore_change: Whether to ignore the next completion update.
_win_id: The window ID this completer is in.
@@ -43,24 +40,24 @@ class Completer(QObject):
_last_cursor_pos: The old cursor position so we avoid double completion
updates.
_last_text: The old command text so we avoid double completion updates.
_signals_connected: Whether the signals are connected to update the
completion when the command widget requests that.
Signals:
next_prev_item: Emitted to select the next/previous item in the
completion.
arg0: True for the previous item, False for the next.
"""
next_prev_item = pyqtSignal(bool)
def __init__(self, cmd, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self._cmd = cmd
self._cmd.update_completion.connect(self.schedule_completion_update)
self._cmd.textEdited.connect(self.on_text_edited)
self._signals_connected = False
self._ignore_change = False
self._empty_item_idx = None
self._models = {
usertypes.Completion.option: {},
usertypes.Completion.value: {},
}
self._init_static_completions()
self._init_setting_completions()
self.init_quickmark_completions()
self._timer = QTimer()
self._timer.setSingleShot(True)
self._timer.setInterval(0)
@@ -69,76 +66,98 @@ class Completer(QObject):
self._last_cursor_pos = None
self._last_text = None
objreg.get('config').changed.connect(self.on_auto_open_changed)
self.handle_signal_connections()
self._cmd.clear_completion_selection.connect(
self.handle_signal_connections)
def __repr__(self):
return utils.get_repr(self)
@config.change_filter('completion', 'auto-open')
def on_auto_open_changed(self):
self.handle_signal_connections()
@pyqtSlot()
def handle_signal_connections(self):
self._connect_signals(config.get('completion', 'auto-open'))
def _connect_signals(self, connect=True):
"""Connect or disconnect the completion signals.
Args:
connect: Whether to connect (True) or disconnect (False) the
signals.
Return:
True if the signals were connected (connect=True and aren't
connected yet) - otherwise False.
"""
connections = [
(self._cmd.update_completion, self.schedule_completion_update),
(self._cmd.textChanged, self.on_text_edited),
]
if connect and not self._signals_connected:
for sender, receiver in connections:
sender.connect(receiver)
self._signals_connected = True
return True
elif not connect:
for sender, receiver in connections:
try:
sender.disconnect(receiver)
except TypeError:
# Don't fail if not connected
pass
self._signals_connected = False
return False
def _open_completion_if_needed(self):
"""If auto-open is false, temporarily connect signals.
Also opens the completion.
"""
if not config.get('completion', 'auto-open'):
connected = self._connect_signals(True)
if connected:
self.update_completion()
def _model(self):
"""Convienience method to get the current completion model."""
completion = objreg.get('completion', scope='window',
window=self._win_id)
return completion.model()
def _init_static_completions(self):
"""Initialize the static completion models."""
self._models[usertypes.Completion.command] = CFM(
models.CommandCompletionModel(self), self)
self._models[usertypes.Completion.helptopic] = CFM(
models.HelpCompletionModel(self), self)
def _init_setting_completions(self):
"""Initialize setting completion models."""
self._models[usertypes.Completion.section] = CFM(
models.SettingSectionCompletionModel(self), self)
self._models[usertypes.Completion.option] = {}
self._models[usertypes.Completion.value] = {}
for sectname in configdata.DATA:
model = models.SettingOptionCompletionModel(sectname, self)
self._models[usertypes.Completion.option][sectname] = CFM(
model, self)
self._models[usertypes.Completion.value][sectname] = {}
for opt in configdata.DATA[sectname].keys():
model = models.SettingValueCompletionModel(sectname, opt, self)
self._models[usertypes.Completion.value][sectname][opt] = CFM(
model, self)
@pyqtSlot()
def init_quickmark_completions(self):
"""Initialize quickmark completion models."""
try:
self._models[usertypes.Completion.quickmark_by_url].deleteLater()
self._models[usertypes.Completion.quickmark_by_name].deleteLater()
except KeyError:
pass
self._models[usertypes.Completion.quickmark_by_url] = CFM(
models.QuickmarkCompletionModel('url', self), self)
self._models[usertypes.Completion.quickmark_by_name] = CFM(
models.QuickmarkCompletionModel('name', self), self)
def _get_completion_model(self, completion, parts, cursor_part):
"""Get a completion model based on an enum member.
Args:
completion: An usertypes.Completion member.
completion: A usertypes.Completion member.
parts: The parts currently in the commandline.
cursor_part: The part the cursor is in.
Return:
A completion model.
A completion model or None.
"""
if completion == usertypes.Completion.option:
section = parts[cursor_part - 1]
model = self._models[completion].get(section)
model = instances.get(completion).get(section)
elif completion == usertypes.Completion.value:
section = parts[cursor_part - 2]
option = parts[cursor_part - 1]
try:
model = self._models[completion][section][option]
model = instances.get(completion)[section][option]
except KeyError:
# No completion model for this section/option.
model = None
else:
model = self._models.get(completion)
return model
model = instances.get(completion)
if model is None:
return None
else:
return sortfilter.CompletionFilterModel(source=model, parent=self)
def _filter_cmdline_parts(self, parts, cursor_part):
"""Filter a list of commandline parts to exclude flags.
@@ -187,7 +206,8 @@ class Completer(QObject):
"{}".format(parts, cursor_part))
if cursor_part == 0:
# '|' or 'set|'
return self._models[usertypes.Completion.command]
model = instances.get(usertypes.Completion.command)
return sortfilter.CompletionFilterModel(source=model, parent=self)
# delegate completion to command
try:
completions = cmdutils.cmd_dict[parts[0]].completion
@@ -243,7 +263,13 @@ class Completer(QObject):
data = model.data(indexes[0])
if data is None:
return
data = self._quote(data)
parts = self.split()
try:
needs_quoting = cmdutils.cmd_dict[parts[0]].maxsplit is None
except KeyError:
needs_quoting = True
if needs_quoting:
data = self._quote(data)
if model.count() == 1 and config.get('completion', 'quick-complete'):
# If we only have one item, we want to apply it immediately
# and go on to the next part.
@@ -292,7 +318,7 @@ class Completer(QObject):
if self._cmd.prefix() != ':':
# This is a search or gibberish, so we don't need to complete
# anything (yet)
# FIXME complete searchs
# FIXME complete searches
# https://github.com/The-Compiler/qutebrowser/issues/32
completion.hide()
return
@@ -313,7 +339,7 @@ class Completer(QObject):
pattern = parts[self._cursor_part].strip()
except IndexError:
pattern = ''
self._model().set_pattern(pattern)
completion.set_pattern(pattern)
log.completion.debug(
"New completion for {}: {}, with pattern '{}'".format(
@@ -343,7 +369,8 @@ class Completer(QObject):
# the whitespace.
return [text]
runner = runners.CommandRunner(self._win_id)
parts = runner.parse(text, fallback=True, aliases=aliases, keep=keep)
result = runner.parse(text, fallback=True, aliases=aliases, keep=keep)
parts = result.cmdline
if self._empty_item_idx is not None:
log.completion.debug("Empty element queued at {}, "
"inserting.".format(self._empty_item_idx))
@@ -368,7 +395,7 @@ class Completer(QObject):
cursor_pos))
skip = 0
for i, part in enumerate(parts):
log.completion.vdebug("Checking part {}: {}".format(i, parts[i]))
log.completion.vdebug("Checking part {}: {!r}".format(i, parts[i]))
if not part:
skip += 1
continue
@@ -390,7 +417,11 @@ class Completer(QObject):
"Removing len({!r}) -> {} from cursor_pos -> {}".format(
part, len(part), cursor_pos))
else:
self._cursor_part = i - skip
if i == 0:
# Initial `:` press without any text.
self._cursor_part = 0
else:
self._cursor_part = i - skip
if spaces:
self._empty_item_idx = i - skip
else:
@@ -441,3 +472,30 @@ class Completer(QObject):
# We also want to update the cursor part and emit update_completion
# here, but that's already done for us by cursorPositionChanged
# anyways, so we don't need to do it twice.
@cmdutils.register(instance='completer', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_prev(self):
"""Select the previous completion item."""
self._open_completion_if_needed()
self.next_prev_item.emit(True)
@cmdutils.register(instance='completer', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_next(self):
"""Select the next completion item."""
self._open_completion_if_needed()
self.next_prev_item.emit(False)
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_del(self):
"""Delete the current completion item."""
completion = objreg.get('completion', scope='window',
window=self._win_id)
if not completion.currentIndex().isValid():
raise cmdexc.CommandError("No item selected!")
try:
self.model().srcmodel.delete_cur_item(completion)
except NotImplementedError:
raise cmdexc.CommandError("Cannot delete this item.")

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -22,7 +22,9 @@
We use this to be able to highlight parts of the text.
"""
import re
import html
from PyQt5.QtWidgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
from PyQt5.QtCore import QRectF, QSize, Qt
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
@@ -143,7 +145,6 @@ class CompletionItemDelegate(QStyledItemDelegate):
rect: The QRect to clip the drawing to.
"""
# We can't use drawContents because then the color would be ignored.
# See: https://qt-project.org/forums/viewthread/21492
clip = QRectF(0, 0, rect.width(), rect.height())
self._painter.save()
if self._opt.state & QStyle.State_Selected:
@@ -194,10 +195,11 @@ class CompletionItemDelegate(QStyledItemDelegate):
if index.parent().isValid():
pattern = index.model().pattern
if index.column() == 0 and pattern:
text = self._opt.text.replace(
pattern,
'<span class="highlight">{}</span>'.format(pattern))
columns_to_filter = index.model().srcmodel.columns_to_filter
if index.column() in columns_to_filter and pattern:
repl = r'<span class="highlight">\g<0></span>'
text = re.sub(re.escape(pattern), repl, self._opt.text,
flags=re.IGNORECASE)
self._doc.setHtml(text)
else:
self._doc.setPlainText(self._opt.text)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -26,10 +26,10 @@ subclasses to provide completions.
from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel
from qutebrowser.commands import cmdutils
from qutebrowser.config import config, style
from qutebrowser.completion import completiondelegate, completer
from qutebrowser.utils import usertypes, qtutils, objreg, utils
from qutebrowser.completion.models import base
from qutebrowser.utils import qtutils, objreg, utils
class CompletionView(QTreeView):
@@ -39,15 +39,13 @@ class CompletionView(QTreeView):
Based on QTreeView but heavily customized so root elements show as category
headers, and children show as flat list.
Class attributes:
COLUMN_WIDTHS: A list of column widths, in percent.
Attributes:
enabled: Whether showing the CompletionView is enabled.
_win_id: The ID of the window this CompletionView is associated with.
_height: The height to use for the CompletionView.
_height_perc: Either None or a percentage if height should be relative.
_delegate: The item delegate used.
_column_widths: A list of column widths, in percent.
Signals:
resize_completion: Emitted when the completion should be resized.
@@ -59,7 +57,9 @@ class CompletionView(QTreeView):
QTreeView {
{{ font['completion'] }}
{{ color['completion.bg'] }}
alternate-background-color: {{ color['completion.alternate-bg'] }};
outline: 0;
border: 0px;
}
QTreeView::item:disabled {
@@ -82,7 +82,6 @@ class CompletionView(QTreeView):
border: 0px;
}
"""
COLUMN_WIDTHS = (20, 70, 10)
# FIXME style scrollbar
# https://github.com/The-Compiler/qutebrowser/issues/117
@@ -95,18 +94,22 @@ class CompletionView(QTreeView):
objreg.register('completion', self, scope='window', window=win_id)
cmd = objreg.get('status-command', scope='window', window=win_id)
completer_obj = completer.Completer(cmd, win_id, self)
completer_obj.next_prev_item.connect(self.on_next_prev_item)
objreg.register('completer', completer_obj, scope='window',
window=win_id)
self.enabled = config.get('completion', 'show')
objreg.get('config').changed.connect(self.set_enabled)
# FIXME handle new aliases.
#objreg.get('config').changed.connect(self.init_command_completion)
# objreg.get('config').changed.connect(self.init_command_completion)
self._column_widths = base.BaseCompletionModel.COLUMN_WIDTHS
self._delegate = completiondelegate.CompletionItemDelegate(self)
self.setItemDelegate(self._delegate)
style.set_register_stylesheet(self)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)
self.setHeaderHidden(True)
self.setAlternatingRowColors(True)
self.setIndentation(0)
self.setItemsExpandable(False)
self.setExpandsOnDoubleClick(False)
@@ -126,9 +129,9 @@ class CompletionView(QTreeView):
return utils.get_repr(self)
def _resize_columns(self):
"""Resize the completion columns based on COLUMN_WIDTHS."""
"""Resize the completion columns based on column_widths."""
width = self.size().width()
pixel_widths = [(width * perc // 100) for perc in self.COLUMN_WIDTHS]
pixel_widths = [(width * perc // 100) for perc in self._column_widths]
if self.verticalScrollBar().isVisible():
pixel_widths[-1] -= self.style().pixelMetric(
QStyle.PM_ScrollBarExtent) + 5
@@ -149,7 +152,10 @@ class CompletionView(QTreeView):
idx = self.selectionModel().currentIndex()
if not idx.isValid():
# No item selected yet
return self.model().first_item()
if upwards:
return self.model().last_item()
else:
return self.model().first_item()
while True:
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
# wrap around if we arrived at beginning/end
@@ -163,12 +169,15 @@ class CompletionView(QTreeView):
# Item is a real item, not a category header -> success
return idx
def _next_prev_item(self, prev):
@pyqtSlot(bool)
def on_next_prev_item(self, prev):
"""Handle a tab press for the CompletionView.
Select the previous/next item and write the new text to the
statusbar.
Called from the Completer's next_prev_item signal.
Args:
prev: True for prev item, False for next one.
"""
@@ -189,14 +198,32 @@ class CompletionView(QTreeView):
Args:
model: The model to use.
"""
old_model = self.model()
sel_model = self.selectionModel()
self.setModel(model)
if sel_model is not None:
sel_model.deleteLater()
self.expandAll()
if old_model is not None:
old_model.deleteLater()
for i in range(model.rowCount()):
self.expand(model.index(i, 0))
self._column_widths = model.srcmodel.COLUMN_WIDTHS
self._resize_columns()
model.rowsRemoved.connect(self.maybe_resize_completion)
model.rowsInserted.connect(self.maybe_resize_completion)
self.maybe_resize_completion()
def set_pattern(self, pattern):
"""Set the completion pattern for the current model.
Called from on_update_completion().
Args:
pattern: The filter pattern to set (what the user entered).
"""
self.model().set_pattern(pattern)
self.maybe_resize_completion()
@pyqtSlot()
@@ -218,18 +245,6 @@ class CompletionView(QTreeView):
selmod.clearSelection()
selmod.clearCurrentIndex()
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_prev(self):
"""Select the previous completion item."""
self._next_prev_item(prev=True)
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command], scope='window')
def completion_item_next(self):
"""Select the next completion item."""
self._next_prev_item(prev=False)
def selectionChanged(self, selected, deselected):
"""Extend selectionChanged to call completers selection_changed."""
super().selectionChanged(selected, deselected)
@@ -241,3 +256,11 @@ class CompletionView(QTreeView):
"""Extend resizeEvent to adjust column size."""
super().resizeEvent(e)
self._resize_columns()
def showEvent(self, e):
"""Adjust the completion size and scroll when it's freshly shown."""
self.resize_completion.emit()
scrollbar = self.verticalScrollBar()
if scrollbar is not None:
scrollbar.setValue(scrollbar.minimum())
super().showEvent(e)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -29,7 +29,8 @@ from PyQt5.QtGui import QStandardItemModel, QStandardItem
from qutebrowser.utils import usertypes, qtutils
Role = usertypes.enum('Role', ['sort'], start=Qt.UserRole, is_int=True)
Role = usertypes.enum('Role', ['sort', 'userdata'], start=Qt.UserRole,
is_int=True)
class BaseCompletionModel(QStandardItemModel):
@@ -38,11 +39,20 @@ class BaseCompletionModel(QStandardItemModel):
Used for showing completions later in the CompletionView. Supports setting
marks and adding new categories/items easily.
Class Attributes:
COLUMN_WIDTHS: The width percentages of the columns used in the
completion view.
DUMB_SORT: the dumb sorting used by the model
"""
COLUMN_WIDTHS = (30, 70, 0)
DUMB_SORT = None
def __init__(self, parent=None):
super().__init__(parent)
self.setColumnCount(3)
self.columns_to_filter = [0]
def new_category(self, name, sort=None):
"""Add a new category to the model.
@@ -60,7 +70,8 @@ class BaseCompletionModel(QStandardItemModel):
self.appendRow(cat)
return cat
def new_item(self, cat, name, desc='', misc=None):
def new_item(self, cat, name, desc='', misc=None, sort=None,
userdata=None):
"""Add a new item to a category.
Args:
@@ -68,22 +79,34 @@ class BaseCompletionModel(QStandardItemModel):
name: The name of the item.
desc: The description of the item.
misc: Misc text to display.
sort: Data for the sort role (int).
userdata: User data to be added for the first column.
Return:
A (nameitem, descitem, miscitem) tuple.
"""
assert not isinstance(name, int)
assert not isinstance(desc, int)
assert not isinstance(misc, int)
nameitem = QStandardItem(name)
descitem = QStandardItem(desc)
if misc is None:
miscitem = QStandardItem()
else:
miscitem = QStandardItem(misc)
idx = cat.rowCount()
cat.setChild(idx, 0, nameitem)
cat.setChild(idx, 1, descitem)
cat.setChild(idx, 2, miscitem)
cat.appendRow([nameitem, descitem, miscitem])
if sort is not None:
nameitem.setData(sort, Role.sort)
if userdata is not None:
nameitem.setData(userdata, Role.userdata)
return nameitem, descitem, miscitem
def delete_cur_item(self, win_id):
"""Delete the selected item."""
raise NotImplementedError
def flags(self, index):
"""Return the item flags for index.
@@ -98,7 +121,8 @@ class BaseCompletionModel(QStandardItemModel):
qtutils.ensure_valid(index)
if index.parent().isValid():
# item
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
return (Qt.ItemIsEnabled | Qt.ItemIsSelectable |
Qt.ItemNeverHasChildren)
else:
# category
return Qt.NoItemFlags
@@ -109,3 +133,13 @@ class BaseCompletionModel(QStandardItemModel):
Override QAbstractItemModel::sort.
"""
raise NotImplementedError
def custom_filter(self, pattern, row, parent):
"""Custom filter.
Args:
pattern: The current filter pattern.
row: The row to accept or reject in the filter.
parent: The parent item QModelIndex.
"""
raise NotImplementedError

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,13 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""CompletionModels for different usages."""
"""CompletionModels for the config."""
from PyQt5.QtCore import pyqtSlot, Qt
from qutebrowser.config import config, configdata
from qutebrowser.utils import log, qtutils, objreg
from qutebrowser.commands import cmdutils
from qutebrowser.completion.models import base
@@ -33,6 +32,8 @@ class SettingSectionCompletionModel(base.BaseCompletionModel):
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 70, 10)
def __init__(self, parent=None):
super().__init__(parent)
cat = self.new_category("Sections")
@@ -52,6 +53,8 @@ class SettingOptionCompletionModel(base.BaseCompletionModel):
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 70, 10)
def __init__(self, section, parent=None):
super().__init__(parent)
cat = self.new_category(section)
@@ -105,17 +108,23 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 70, 10)
def __init__(self, section, option, parent=None):
super().__init__(parent)
self._section = section
self._option = option
objreg.get('config').changed.connect(self.update_current_value)
cur_cat = self.new_category("Current", sort=0)
cur_cat = self.new_category("Current/Default", sort=0)
value = config.get(section, option, raw=True)
if not value:
value = '""'
self.cur_item, _descitem, _miscitem = self.new_item(cur_cat, value,
"Current value")
default_value = configdata.DATA[section][option].default()
if not default_value:
default_value = '""'
self.new_item(cur_cat, default_value, "Default value")
if hasattr(configdata.DATA[section], 'valtype'):
# Same type for all values (ValueList)
vals = configdata.DATA[section].valtype.complete()
@@ -126,7 +135,7 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
# Different type for each value (KeyValue)
vals = configdata.DATA[section][option].typ.complete()
if vals is not None:
cat = self.new_category("Allowed", sort=1)
cat = self.new_category("Completions", sort=1)
for (val, desc) in vals:
self.new_item(cat, val, desc)
@@ -144,89 +153,3 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
if not ok:
raise ValueError("Setting data failed! (section: {}, option: {}, "
"value: {})".format(section, option, value))
class CommandCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with all commands and descriptions."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if obj.hide or (obj.debug and not objreg.get('args').debug):
pass
else:
cmdlist.append((obj.name, obj.desc))
for name, cmd in config.section('aliases').items():
cmdlist.append((name, "Alias for '{}'".format(cmd)))
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
class HelpCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with help topics."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
self._init_commands()
self._init_settings()
def _init_commands(self):
"""Fill completion with :command entries."""
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if obj.hide or (obj.debug and not objreg.get('args').debug):
pass
else:
cmdlist.append((':' + obj.name, obj.desc))
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
def _init_settings(self):
"""Fill completion with section->option entries."""
cat = self.new_category("Settings")
for sectname, sectdata in configdata.DATA.items():
for optname in sectdata.keys():
try:
desc = sectdata.descriptions[optname]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
name = '{}->{}'.format(sectname, optname)
self.new_item(cat, name, desc)
class QuickmarkCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with all quickmarks."""
# pylint: disable=abstract-method
def __init__(self, match_field='url', parent=None):
super().__init__(parent)
cat = self.new_category("Quickmarks")
quickmarks = objreg.get('quickmark-manager').marks.items()
if match_field == 'url':
for qm_name, qm_url in quickmarks:
self.new_item(cat, qm_url, qm_name)
elif match_field == 'name':
for qm_name, qm_url in quickmarks:
self.new_item(cat, qm_name, qm_url)
else:
raise ValueError("Invalid value '{}' for match_field!".format(
match_field))

View File

@@ -0,0 +1,170 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Global instances of the completion models.
Module attributes:
_instances: An dict of available completions.
INITIALIZERS: A {usertypes.Completion: callable} dict of functions to
initialize completions.
"""
import functools
from PyQt5.QtCore import pyqtSlot
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.utils import objreg, usertypes, log, debug
from qutebrowser.config import configdata
_instances = {}
def _init_command_completion():
"""Initialize the command completion model."""
log.completion.debug("Initializing command completion.")
model = miscmodels.CommandCompletionModel()
_instances[usertypes.Completion.command] = model
def _init_helptopic_completion():
"""Initialize the helptopic completion model."""
log.completion.debug("Initializing helptopic completion.")
model = miscmodels.HelpCompletionModel()
_instances[usertypes.Completion.helptopic] = model
def _init_url_completion():
"""Initialize the URL completion model."""
log.completion.debug("Initializing URL completion.")
with debug.log_time(log.completion, 'URL completion init'):
model = urlmodel.UrlCompletionModel()
_instances[usertypes.Completion.url] = model
def _init_setting_completions():
"""Initialize setting completion models."""
log.completion.debug("Initializing setting completion.")
_instances[usertypes.Completion.section] = (
configmodel.SettingSectionCompletionModel())
_instances[usertypes.Completion.option] = {}
_instances[usertypes.Completion.value] = {}
for sectname in configdata.DATA:
model = configmodel.SettingOptionCompletionModel(sectname)
_instances[usertypes.Completion.option][sectname] = model
_instances[usertypes.Completion.value][sectname] = {}
for opt in configdata.DATA[sectname].keys():
model = configmodel.SettingValueCompletionModel(sectname, opt)
_instances[usertypes.Completion.value][sectname][opt] = model
@pyqtSlot()
def init_quickmark_completions():
"""Initialize quickmark completion models."""
log.completion.debug("Initializing quickmark completion.")
try:
_instances[usertypes.Completion.quickmark_by_name].deleteLater()
except KeyError:
pass
model = miscmodels.QuickmarkCompletionModel()
_instances[usertypes.Completion.quickmark_by_name] = model
@pyqtSlot()
def init_bookmark_completions():
"""Initialize bookmark completion models."""
log.completion.debug("Initializing bookmark completion.")
try:
_instances[usertypes.Completion.bookmark_by_url].deleteLater()
except KeyError:
pass
model = miscmodels.BookmarkCompletionModel()
_instances[usertypes.Completion.bookmark_by_url] = model
@pyqtSlot()
def init_session_completion():
"""Initialize session completion model."""
log.completion.debug("Initializing session completion.")
try:
_instances[usertypes.Completion.sessions].deleteLater()
except KeyError:
pass
model = miscmodels.SessionCompletionModel()
_instances[usertypes.Completion.sessions] = model
INITIALIZERS = {
usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion,
usertypes.Completion.url: _init_url_completion,
usertypes.Completion.section: _init_setting_completions,
usertypes.Completion.option: _init_setting_completions,
usertypes.Completion.value: _init_setting_completions,
usertypes.Completion.quickmark_by_name: init_quickmark_completions,
usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion,
}
def get(completion):
"""Get a certain completion. Initializes the completion if needed."""
try:
return _instances[completion]
except KeyError:
if completion in INITIALIZERS:
INITIALIZERS[completion]()
return _instances[completion]
else:
raise
def update(completions):
"""Update an already existing completion.
Args:
completions: An iterable of usertypes.Completions.
"""
did_run = []
for completion in completions:
if completion in _instances:
func = INITIALIZERS[completion]
if func not in did_run:
func()
did_run.append(func)
def init():
"""Initialize completions. Note this only connects signals."""
quickmark_manager = objreg.get('quickmark-manager')
quickmark_manager.changed.connect(
functools.partial(update, [usertypes.Completion.quickmark_by_name]))
bookmark_manager = objreg.get('bookmark-manager')
bookmark_manager.changed.connect(
functools.partial(update, [usertypes.Completion.bookmark_by_url]))
session_manager = objreg.get('session-manager')
session_manager.update_completion.connect(
functools.partial(update, [usertypes.Completion.sessions]))
history = objreg.get('web-history')
history.async_read_done.connect(
functools.partial(update, [usertypes.Completion.url]))

View File

@@ -0,0 +1,135 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Misc. CompletionModels."""
from qutebrowser.config import config, configdata
from qutebrowser.utils import objreg, log
from qutebrowser.commands import cmdutils
from qutebrowser.completion.models import base
class CommandCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with all commands and descriptions."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
obj.deprecated):
pass
else:
cmdlist.append((obj.name, obj.desc))
for name, cmd in config.section('aliases').items():
cmdlist.append((name, "Alias for '{}'".format(cmd)))
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
class HelpCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with help topics."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
self._init_commands()
self._init_settings()
def _init_commands(self):
"""Fill completion with :command entries."""
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
obj.deprecated):
pass
else:
cmdlist.append((':' + obj.name, obj.desc))
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
def _init_settings(self):
"""Fill completion with section->option entries."""
cat = self.new_category("Settings")
for sectname, sectdata in configdata.DATA.items():
for optname in sectdata.keys():
try:
desc = sectdata.descriptions[optname]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
name = '{}->{}'.format(sectname, optname)
self.new_item(cat, name, desc)
class QuickmarkCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with all quickmarks."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
cat = self.new_category("Quickmarks")
quickmarks = objreg.get('quickmark-manager').marks.items()
for qm_name, qm_url in quickmarks:
self.new_item(cat, qm_name, qm_url)
class BookmarkCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with all bookmarks."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
cat = self.new_category("Bookmarks")
bookmarks = objreg.get('bookmark-manager').marks.items()
for bm_url, bm_title in bookmarks:
self.new_item(cat, bm_url, bm_title)
class SessionCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with session names."""
# pylint: disable=abstract-method
def __init__(self, parent=None):
super().__init__(parent)
cat = self.new_category("Sessions")
try:
for name in objreg.get('session-manager').list_sessions():
if not name.startswith('_'):
self.new_item(cat, name)
except OSError:
log.completion.exception("Failed to list sessions!")

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -23,9 +23,9 @@ Contains:
CompletionFilterModel -- A QSortFilterProxyModel subclass for completions.
"""
from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex
from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex, Qt
from qutebrowser.utils import log, qtutils
from qutebrowser.utils import log, qtutils, debug
from qutebrowser.completion.models import base as completion
@@ -38,6 +38,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
srcmodel: The current source model.
Kept as attribute because calling `sourceModel` takes quite
a long time for some reason.
_sort_order: The order to use for sorting if using dumb_sort.
"""
def __init__(self, source, parent=None):
@@ -46,6 +47,15 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.srcmodel = source
self.pattern = ''
dumb_sort = self.srcmodel.DUMB_SORT
if dumb_sort is None:
# pylint: disable=invalid-name
self.lessThan = self.intelligentLessThan
self._sort_order = Qt.AscendingOrder
else:
self.setSortRole(completion.Role.sort)
self._sort_order = dumb_sort
def set_pattern(self, val):
"""Setter for pattern.
@@ -57,14 +67,15 @@ class CompletionFilterModel(QSortFilterProxyModel):
Args:
val: The value to set.
"""
self.pattern = val
self.invalidateFilter()
sortcol = 0
try:
self.srcmodel.sort(sortcol)
except NotImplementedError:
self.sort(sortcol)
self.invalidate()
with debug.log_time(log.completion, 'Setting filter pattern'):
self.pattern = val
self.invalidateFilter()
sortcol = 0
try:
self.srcmodel.sort(sortcol)
except NotImplementedError:
self.sort(sortcol)
self.invalidate()
def count(self):
"""Get the count of non-toplevel items currently visible.
@@ -121,17 +132,25 @@ class CompletionFilterModel(QSortFilterProxyModel):
True if self.pattern is contained in item, or if it's a root item
(category). False in all other cases
"""
if parent == QModelIndex():
if parent == QModelIndex() or not self.pattern:
return True
idx = self.srcmodel.index(row, 0, parent)
qtutils.ensure_valid(idx)
data = self.srcmodel.data(idx)
# TODO more sophisticated filtering
if not self.pattern:
return True
return self.pattern.casefold() in data.casefold()
def lessThan(self, lindex, rindex):
try:
return self.srcmodel.custom_filter(self.pattern, row, parent)
except NotImplementedError:
for col in self.srcmodel.columns_to_filter:
idx = self.srcmodel.index(row, col, parent)
if not idx.isValid():
# No entries in parent model
continue
data = self.srcmodel.data(idx)
if not data:
continue
elif self.pattern.casefold() in data.casefold():
return True
return False
def intelligentLessThan(self, lindex, rindex):
"""Custom sorting implementation.
Prefers all items which start with self.pattern. Other than that, uses
@@ -167,3 +186,9 @@ class CompletionFilterModel(QSortFilterProxyModel):
return False
else:
return left < right
def sort(self, column, order=None):
"""Extend sort to respect self._sort_order if no order was given."""
if order is None:
order = self._sort_order
super().sort(column, order)

View File

@@ -0,0 +1,176 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""CompletionModels for URLs."""
import datetime
from PyQt5.QtCore import pyqtSlot, Qt
from qutebrowser.utils import objreg, utils, qtutils, log
from qutebrowser.completion.models import base
from qutebrowser.config import config
class UrlCompletionModel(base.BaseCompletionModel):
"""A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command."""
# pylint: disable=abstract-method
URL_COLUMN = 0
TEXT_COLUMN = 1
TIME_COLUMN = 2
COLUMN_WIDTHS = (40, 50, 10)
DUMB_SORT = Qt.DescendingOrder
def __init__(self, parent=None):
super().__init__(parent)
self.columns_to_filter = [self.URL_COLUMN, self.TEXT_COLUMN]
self._quickmark_cat = self.new_category("Quickmarks")
self._bookmark_cat = self.new_category("Bookmarks")
self._history_cat = self.new_category("History")
quickmark_manager = objreg.get('quickmark-manager')
quickmarks = quickmark_manager.marks.items()
for qm_name, qm_url in quickmarks:
self.new_item(self._quickmark_cat, qm_url, qm_name)
quickmark_manager.added.connect(
lambda name, url: self.new_item(self._quickmark_cat, url, name))
quickmark_manager.removed.connect(self.on_quickmark_removed)
bookmark_manager = objreg.get('bookmark-manager')
bookmarks = bookmark_manager.marks.items()
for bm_url, bm_title in bookmarks:
self.new_item(self._bookmark_cat, bm_url, bm_title)
bookmark_manager.added.connect(
lambda name, url: self.new_item(self._bookmark_cat, url, name))
bookmark_manager.removed.connect(self.on_bookmark_removed)
self._history = objreg.get('web-history')
max_history = config.get('completion', 'web-history-max-items')
history = utils.newest_slice(self._history, max_history)
for entry in history:
self._add_history_entry(entry)
self._history.add_completion_item.connect(
self.on_history_item_added)
objreg.get('config').changed.connect(self.reformat_timestamps)
def _fmt_atime(self, atime):
"""Format an atime to a human-readable string."""
fmt = config.get('completion', 'timestamp-format')
if fmt is None:
return ''
try:
dt = datetime.datetime.fromtimestamp(atime)
except (ValueError, OSError, OverflowError):
# Different errors which can occur for too large values...
log.misc.error("Got invalid timestamp {}!".format(atime))
return '(invalid)'
else:
return dt.strftime(fmt)
def _add_history_entry(self, entry):
"""Add a new history entry to the completion."""
self.new_item(self._history_cat, entry.url.toDisplayString(), "",
self._fmt_atime(entry.atime), sort=int(entry.atime),
userdata=entry.url)
@config.change_filter('completion', 'timestamp-format')
def reformat_timestamps(self):
"""Reformat the timestamps if the config option was changed."""
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)
atime = url_item.data(base.Role.sort)
atime_item.setText(self._fmt_atime(atime))
@pyqtSlot(object)
def on_history_item_added(self, entry):
"""Slot called when a new history item was added."""
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)
url = url_item.data(base.Role.userdata)
if url == entry.url:
atime_item.setText(self._fmt_atime(entry.atime))
url_item.setData(int(entry.atime), base.Role.sort)
break
else:
self._add_history_entry(entry)
def _remove_item(self, data, category, column):
"""Helper function for on_quickmark_removed and on_bookmark_removed.
Args:
data: The item to search for.
category: The category to search in.
column: The column to use for matching.
"""
for i in range(category.rowCount()):
item = category.child(i, column)
if item.data(Qt.DisplayRole) == data:
category.removeRow(i)
break
@pyqtSlot(str)
def on_quickmark_removed(self, name):
"""Called when a quickmark has been removed by the user.
Args:
name: The name of the quickmark which has been removed.
"""
self._remove_item(name, self._quickmark_cat, self.TEXT_COLUMN)
@pyqtSlot(str)
def on_bookmark_removed(self, url):
"""Called when a bookmark has been removed by the user.
Args:
url: The url of the bookmark which has been removed.
"""
self._remove_item(url, self._bookmark_cat, self.URL_COLUMN)
def delete_cur_item(self, completion):
"""Delete the selected item.
Args:
completion: The Completion object to use.
"""
index = completion.currentIndex()
qtutils.ensure_valid(index)
url = index.data()
category = index.parent()
qtutils.ensure_valid(category)
if category.data() == 'Bookmarks':
bookmark_manager = objreg.get('bookmark-manager')
bookmark_manager.delete(url)
elif category.data() == 'Quickmarks':
quickmark_manager = objreg.get('quickmark-manager')
sibling = index.sibling(index.row(), self.TEXT_COLUMN)
qtutils.ensure_valid(sibling)
name = sibling.data()
quickmark_manager.quickmark_del(name)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -20,8 +20,8 @@
"""Configuration storage and config-related utilities.
This borrows a lot of ideas from configparser, but also has some things that
are fundamentally different. This is why nothing inherts from configparser, but
we borrow some methods and classes from there where it makes sense.
are fundamentally different. This is why nothing inherits from configparser,
but we borrow some methods and classes from there where it makes sense.
"""
import os
@@ -32,19 +32,19 @@ import configparser
import collections
import collections.abc
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QStandardPaths, QUrl
from PyQt5.QtWidgets import QMessageBox
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QSettings
from qutebrowser.config import configdata, configexc, textwrapper
from qutebrowser.config.parsers import ini, keyconf
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, objreg, utils, standarddir, log, qtutils
from qutebrowser.utils import (message, objreg, utils, standarddir, log,
qtutils, error, usertypes)
from qutebrowser.utils.usertypes import Completion
class change_filter: # pylint: disable=invalid-name
"""Decorator to register a new command handler.
"""Decorator to filter calls based on a config section/option matching.
This could also be a function, but as a class (with a "wrong" name) it's
much cleaner to implement.
@@ -52,15 +52,18 @@ class change_filter: # pylint: disable=invalid-name
Attributes:
_sectname: The section to be filtered.
_optname: The option to be filtered.
_function: Whether a function rather than a method is decorated.
"""
def __init__(self, sectname, optname=None):
def __init__(self, sectname, optname=None, function=False):
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
Args:
See class attributes.
sectname: The section to be filtered.
optname: The option to be filtered.
function: Whether a function rather than a method is decorated.
"""
if sectname not in configdata.DATA:
raise configexc.NoSectionError(sectname)
@@ -68,9 +71,10 @@ class change_filter: # pylint: disable=invalid-name
raise configexc.NoOptionError(optname, sectname)
self._sectname = sectname
self._optname = optname
self._function = function
def __call__(self, func):
"""Register the command before running the function.
"""Filter calls to the decorated function.
Gets called when a function should be decorated.
@@ -85,20 +89,34 @@ class change_filter: # pylint: disable=invalid-name
Return:
The decorated function.
"""
@pyqtSlot(str, str)
@functools.wraps(func)
def wrapper(wrapper_self, sectname=None, optname=None):
# pylint: disable=missing-docstring
if sectname is None and optname is None:
# Called directly, not from a config change event.
return func(wrapper_self)
elif sectname != self._sectname:
return
elif self._optname is not None and optname != self._optname:
return
else:
return func(wrapper_self)
if self._function:
@pyqtSlot(str, str)
@functools.wraps(func)
def wrapper(sectname=None, optname=None):
# pylint: disable=missing-docstring
if sectname is None and optname is None:
# Called directly, not from a config change event.
return func()
elif sectname != self._sectname:
return
elif self._optname is not None and optname != self._optname:
return
else:
return func()
else:
@pyqtSlot(str, str)
@functools.wraps(func)
def wrapper(wrapper_self, sectname=None, optname=None):
# pylint: disable=missing-docstring
if sectname is None and optname is None:
# Called directly, not from a config change event.
return func(wrapper_self)
elif sectname != self._sectname:
return
elif self._optname is not None and optname != self._optname:
return
else:
return func(wrapper_self)
return wrapper
@@ -113,17 +131,17 @@ def section(sect):
return objreg.get('config')[sect]
def init(args):
"""Initialize the config.
def _init_main_config(parent=None):
"""Initialize the main config.
Args:
args: The argparse namespace.
parent: The parent to pass to ConfigManager.
"""
confdir = standarddir.get(QStandardPaths.ConfigLocation, args)
args = objreg.get('args')
try:
app = objreg.get('app')
config_obj = ConfigManager(confdir, 'qutebrowser.conf', app)
except (configexc.Error, configparser.Error) as e:
config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf',
args.relaxed_config, parent=parent)
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
log.init.exception(e)
errstr = "Error while reading config:"
try:
@@ -131,38 +149,140 @@ def init(args):
e.section, e.option) # pylint: disable=no-member
except AttributeError:
pass
errstr += "\n{}".format(e)
msgbox = QMessageBox(QMessageBox.Critical,
"Error while reading config!", errstr)
msgbox.exec_()
errstr += "\n"
error.handle_fatal_exc(e, args, "Error while reading config!",
pre_text=errstr)
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
sys.exit(usertypes.Exit.err_config)
else:
objreg.register('config', config_obj)
if standarddir.config() is not None:
filename = os.path.join(standarddir.config(), 'qutebrowser.conf')
save_manager = objreg.get('save-manager')
save_manager.add_saveable(
'config', config_obj.save, config_obj.changed,
config_opt=('general', 'auto-save-config'), filename=filename)
for sect in config_obj.sections.values():
for opt in sect.values.values():
if opt.values['conf'] is None:
# Option added to built-in defaults but not in user's
# config yet
save_manager.save('config', explicit=True, force=True)
return
def _init_key_config(parent):
"""Initialize the key config.
Args:
parent: The parent to use for the KeyConfigParser.
"""
args = objreg.get('args')
try:
key_config = keyconf.KeyConfigParser(confdir, 'keys.conf')
except keyconf.KeyConfigError as e:
key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf',
args.relaxed_config,
parent=parent)
except (keyconf.KeyConfigError, UnicodeDecodeError) as e:
log.init.exception(e)
errstr = "Error while reading key config:\n"
if e.lineno is not None:
errstr += "In line {}: ".format(e.lineno)
errstr += str(e)
msgbox = QMessageBox(QMessageBox.Critical,
"Error while reading key config!", errstr)
msgbox.exec_()
error.handle_fatal_exc(e, args, "Error while reading key config!",
pre_text=errstr)
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
sys.exit(usertypes.Exit.err_key_config)
else:
objreg.register('key-config', key_config)
if standarddir.config() is not None:
save_manager = objreg.get('save-manager')
filename = os.path.join(standarddir.config(), 'keys.conf')
save_manager.add_saveable(
'key-config', key_config.save, key_config.config_dirty,
config_opt=('general', 'auto-save-config'), filename=filename,
dirty=key_config.is_dirty)
datadir = standarddir.get(QStandardPaths.DataLocation, args)
state_config = ini.ReadWriteConfigParser(datadir, 'state')
def _init_misc():
"""Initialize misc. config-related files."""
save_manager = objreg.get('save-manager')
state_config = ini.ReadWriteConfigParser(standarddir.data(), 'state')
for sect in ('general', 'geometry'):
try:
state_config.add_section(sect)
except configparser.DuplicateSectionError:
pass
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
state_config['general'].pop('fooled', None)
objreg.register('state-config', state_config)
save_manager.add_saveable('state-config', state_config.save)
# We need to import this here because lineparser needs config.
from qutebrowser.config.parsers import line
command_history = line.LineConfigParser(datadir, 'cmd-history',
('completion', 'history-length'))
from qutebrowser.misc import lineparser
command_history = lineparser.LimitLineParser(
standarddir.data(), 'cmd-history',
limit=('completion', 'cmd-history-max-items'),
parent=objreg.get('config'))
objreg.register('command-history', command_history)
save_manager.add_saveable('command-history', command_history.save,
command_history.changed)
# Set the QSettings path to something like
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
# doesn't overwrite our config.
#
# This fixes one of the corruption issues here:
# https://github.com/The-Compiler/qutebrowser/issues/515
if standarddir.config() is None:
path = os.devnull
else:
path = os.path.join(standarddir.config(), 'qsettings')
for fmt in (QSettings.NativeFormat, QSettings.IniFormat):
QSettings.setPath(fmt, QSettings.UserScope, path)
def init(parent=None):
"""Initialize the config.
Args:
parent: The parent to pass to QObjects which get initialized.
"""
_init_main_config(parent)
_init_key_config(parent)
_init_misc()
def _get_value_transformer(old, new):
"""Get a function which transforms a value for CHANGED_OPTIONS.
Args:
old: The old value - if the supplied value doesn't match this, it's
returned untransformed.
new: The new value.
Return:
A function which takes a value and transforms it.
"""
def transformer(val):
if val == old:
return new
else:
return val
return transformer
def _transform_position(val):
"""Transformer for position values."""
mapping = {
'north': 'top',
'south': 'bottom',
'west': 'left',
'east': 'right',
}
try:
return mapping[val]
except KeyError:
return val
class ConfigManager(QObject):
@@ -176,6 +296,11 @@ class ConfigManager(QObject):
RENAMED_SECTIONS: A mapping of renamed sections, {'oldname': 'newname'}
RENAMED_OPTIONS: A mapping of renamed options,
{('section', 'oldname'): 'newname'}
CHANGED_OPTIONS: A mapping of arbitrarily changed options,
{('section', 'option'): callable}.
The callable takes the old value and returns the new
one.
DELETED_OPTIONS: A (section, option) list of deleted options.
Attributes:
sections: The configuration data as an OrderedDict.
@@ -208,16 +333,32 @@ class ConfigManager(QObject):
('colors', 'tab.indicator.stop'): 'tabs.indicator.stop',
('colors', 'tab.indicator.error'): 'tabs.indicator.error',
('colors', 'tab.indicator.system'): 'tabs.indicator.system',
('colors', 'tab.seperator'): 'tabs.seperator',
('tabs', 'auto-hide'): 'hide-auto',
('completion', 'history-length'): 'cmd-history-max-items',
('colors', 'downloads.fg'): 'downloads.fg.start',
}
DELETED_OPTIONS = [
('colors', 'tab.separator'),
('colors', 'tabs.separator'),
('colors', 'completion.item.bg'),
('tabs', 'indicator-space'),
('tabs', 'hide-auto'),
('tabs', 'hide-always'),
]
CHANGED_OPTIONS = {
('content', 'cookies-accept'):
_get_value_transformer('default', 'no-3rdparty'),
('tabs', 'position'): _transform_position,
('ui', 'downloads-position'): _transform_position,
}
changed = pyqtSignal(str, str)
style_changed = pyqtSignal(str, str)
def __init__(self, configdir, fname, parent=None):
def __init__(self, configdir, fname, relaxed=False, parent=None):
super().__init__(parent)
self._initialized = False
self.sections = configdata.DATA
self.sections = configdata.data()
self._interpolation = configparser.ExtendedInterpolation()
self._proxies = {}
for sectname in self.sections.keys():
@@ -229,7 +370,7 @@ class ConfigManager(QObject):
else:
self._configdir = configdir
parser = ini.ReadConfigParser(configdir, fname)
self._from_cp(parser)
self._from_cp(parser, relaxed)
self._initialized = True
self._validate_all()
@@ -269,17 +410,20 @@ class ConfigManager(QObject):
lines = []
if not getattr(sect, 'descriptions', None):
return lines
for optname, option in sect.items():
lines.append('#')
if option.typ.typestr is None:
if option.typ.special:
typestr = ''
else:
typestr = ' ({})'.format(option.typ.typestr)
typestr = ' ({})'.format(option.typ.__class__.__name__)
lines.append("# {}{}:".format(optname, typestr))
try:
desc = self.sections[sectname].descriptions[optname]
except KeyError:
log.misc.exception("No description for {}.{}!".format(
log.config.exception("No description for {}.{}!".format(
sectname, optname))
continue
for descline in desc.splitlines():
@@ -338,27 +482,52 @@ class ConfigManager(QObject):
else:
return None
def _from_cp(self, cp):
def _from_cp(self, cp, relaxed=False):
"""Read the config from a configparser instance.
Args:
cp: The configparser instance to read the values from.
relaxed: Whether to ignore inexistent sections/options.
"""
for sectname in cp:
if sectname in self.RENAMED_SECTIONS:
sectname = self.RENAMED_SECTIONS[sectname]
if sectname is not 'DEFAULT' and sectname not in self.sections:
raise configexc.NoSectionError(sectname)
if not relaxed:
raise configexc.NoSectionError(sectname)
for sectname in self.sections:
real_sectname = self._get_real_sectname(cp, sectname)
if real_sectname is None:
continue
for k, v in cp[real_sectname].items():
if k.startswith(self.ESCAPE_CHAR):
k = k[1:]
if (sectname, k) in self.RENAMED_OPTIONS:
k = self.RENAMED_OPTIONS[sectname, k]
self._from_cp_section(sectname, cp, relaxed)
def _from_cp_section(self, sectname, cp, relaxed):
"""Read a single section from a configparser instance.
Args:
sectname: The name of the section to read.
cp: The configparser instance to read the values from.
relaxed: Whether to ignore inexistent options.
"""
real_sectname = self._get_real_sectname(cp, sectname)
if real_sectname is None:
return
for k, v in cp[real_sectname].items():
if k.startswith(self.ESCAPE_CHAR):
k = k[1:]
if (sectname, k) in self.DELETED_OPTIONS:
return
if (sectname, k) in self.RENAMED_OPTIONS:
k = self.RENAMED_OPTIONS[sectname, k]
if (sectname, k) in self.CHANGED_OPTIONS:
func = self.CHANGED_OPTIONS[(sectname, k)]
v = func(v)
try:
self.set('conf', sectname, k, v, validate=False)
except configexc.NoOptionError:
if relaxed:
pass
else:
raise
def _validate_all(self):
"""Validate all values set in self._from_cp."""
@@ -376,7 +545,7 @@ class ConfigManager(QObject):
def _changed(self, sectname, optname):
"""Notify other objects the config has changed."""
log.misc.debug("Config option changed: {} -> {}".format(
log.config.debug("Config option changed: {} -> {}".format(
sectname, optname))
if sectname in ('colors', 'fonts'):
self.style_changed.emit(sectname, optname)
@@ -400,7 +569,7 @@ class ConfigManager(QObject):
def items(self, sectname, raw=True):
"""Get a list of (optname, value) tuples for a section.
Implemented for configparser interpolation compatbility.
Implemented for configparser interpolation compatibility
Args:
sectname: The name of the section to get.
@@ -463,7 +632,7 @@ class ConfigManager(QObject):
The value of the option.
"""
if not self._initialized:
raise Exception("get got called before initialisation was "
raise Exception("get got called before initialization was "
"complete!")
try:
sect = self.sections[sectname]
@@ -482,50 +651,67 @@ class ConfigManager(QObject):
newval = val.typ.transform(newval)
return newval
@cmdutils.register(name='set', instance='config',
@cmdutils.register(name='set', instance='config', win_id='win_id',
completion=[Completion.section, Completion.option,
Completion.value])
def set_command(self, win_id: {'special': 'win_id'},
sectname: {'name': 'section'}=None,
optname: {'name': 'option'}=None, value=None, temp=False):
def set_command(self, win_id, section_=None, option=None, value=None,
temp=False, print_=False):
"""Set an option.
If the option name ends with '?', the value of the option is shown
instead.
If the option name ends with '!' and it is a boolean value, toggle it.
//
Wrapper for self.set() to output exceptions in the status bar.
Args:
sectname: The section where the option is in.
optname: The name of the option.
section_: The section where the option is in.
option: The name of the option.
value: The value to set.
temp: Set value temporarily.
print_: Print the value after setting.
"""
if sectname is not None and optname is None:
if section_ is not None and option is None:
raise cmdexc.CommandError(
"set: Either both section and option have to be given, or "
"neither!")
if sectname is None and optname is None:
if section_ is None and option is None:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
return
try:
if optname.endswith('?'):
val = self.get(sectname, optname[:-1], transformed=False)
message.info(win_id, "{} {} = {}".format(
sectname, optname[:-1], val), immediately=True)
else:
if value is None:
if option.endswith('?'):
option = option[:-1]
print_ = True
else:
try:
if option.endswith('!') and value is None:
option = option[:-1]
val = self.get(section_, option)
layer = 'temp' if temp else 'conf'
if isinstance(val, bool):
self.set(layer, section_, option, str(not val))
else:
raise cmdexc.CommandError(
"set: Attempted inversion of non-boolean value.")
elif value is not None:
layer = 'temp' if temp else 'conf'
self.set(layer, section_, option, value)
else:
raise cmdexc.CommandError("set: The following arguments "
"are required: value")
layer = 'temp' if temp else 'conf'
self.set(layer, sectname, optname, value)
except (configexc.Error, configparser.Error) as e:
raise cmdexc.CommandError("set: {} - {}".format(
e.__class__.__name__, e))
except (configexc.Error, configparser.Error) as e:
raise cmdexc.CommandError("set: {} - {}".format(
e.__class__.__name__, e))
if print_:
val = self.get(section_, option, transformed=False)
message.info(win_id, "{} {} = {}".format(
section_, option, val), immediately=True)
def set(self, layer, sectname, optname, value, validate=True):
"""Set an option.
@@ -560,14 +746,6 @@ class ConfigManager(QObject):
if self._initialized:
self._after_set(sectname, optname)
@cmdutils.register(instance='config', name='save')
def save_command(self):
"""Save the config file."""
try:
self.save()
except OSError as e:
raise cmdexc.CommandError("Could not save config: {}".format(e))
def save(self):
"""Save the config file."""
if self._configdir is None:

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -32,9 +32,9 @@ class ValidationError(Error):
"""Raised when a value for a config type was invalid.
Attributes:
section: Section in which the error occured (added when catching and
section: Section in which the error occurred (added when catching and
re-raising the exception).
option: Option in which the error occured.
option: Option in which the error occurred.
"""
def __init__(self, value, msg):
@@ -57,7 +57,7 @@ class NoOptionError(Error):
"""Raised when an option was not found."""
def __init__(self, option, section):
super().__init__("No option {!r} in section: {!r}".format(
super().__init__("No option {!r} in section {!r}".format(
option, section))
self.option = option
self.section = section

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -47,11 +47,15 @@ class ReadConfigParser(configparser.ConfigParser):
self.optionxform = lambda opt: opt # be case-insensitive
self._configdir = configdir
self._fname = fname
if self._configdir is None:
self._configfile = None
return
self._configfile = os.path.join(self._configdir, fname)
if not os.path.isfile(self._configfile):
return
log.init.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile, encoding='utf-8')
if self._configfile is not None:
self.read(self._configfile, encoding='utf-8')
def __repr__(self):
return utils.get_repr(self, constructor=True,
@@ -60,10 +64,12 @@ class ReadConfigParser(configparser.ConfigParser):
class ReadWriteConfigParser(ReadConfigParser):
"""ConfigParser subclass used for auxillary config files."""
"""ConfigParser subclass used for auxiliary config files."""
def save(self):
"""Save the config file."""
if self._configdir is None:
return
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile))

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,6 +21,7 @@
import collections
import os.path
import itertools
from PyQt5.QtCore import pyqtSignal, QObject
@@ -34,7 +35,7 @@ class KeyConfigError(Exception):
"""Raised on errors with the key config.
Attributes:
lineno: The config line in which the exception occured.
lineno: The config line in which the exception occurred.
"""
def __init__(self, msg=None):
@@ -42,6 +43,15 @@ class KeyConfigError(Exception):
self.lineno = None
class DuplicateKeychainError(KeyConfigError):
"""Error raised when there's a duplicate key binding."""
def __init__(self, keychain):
super().__init__("Duplicate key chain {}!".format(keychain))
self.keychain = keychain
class KeyConfigParser(QObject):
"""Parser for the keybind config.
@@ -50,25 +60,34 @@ class KeyConfigParser(QObject):
_configfile: The filename of the config or None.
_cur_section: The section currently being processed by _read().
_cur_command: The command currently being processed by _read().
is_dirty: Whether the config is currently dirty.
Class attributes:
UNBOUND_COMMAND: The special command used for unbound keybindings.
Signals:
changed: Emitted when the config has changed.
changed: Emitted when the internal data has changed.
arg: Name of the mode which was changed.
config_dirty: Emitted when the config should be re-saved.
"""
changed = pyqtSignal(str)
config_dirty = pyqtSignal()
UNBOUND_COMMAND = '<unbound>'
def __init__(self, configdir, fname, parent=None):
def __init__(self, configdir, fname, relaxed=False, parent=None):
"""Constructor.
Args:
configdir: The directory to save the configs in.
fname: The filename of the config.
relaxed: If given, unknwon commands are ignored.
"""
super().__init__(parent)
self.is_dirty = False
self._cur_section = None
self._cur_command = None
# Mapping of section name(s) to keybinding -> command dicts.
# Mapping of section name(s) to key binding -> command dicts.
self.keybindings = collections.OrderedDict()
if configdir is None:
self._configfile = None
@@ -77,7 +96,8 @@ class KeyConfigParser(QObject):
if self._configfile is None or not os.path.exists(self._configfile):
self._load_default()
else:
self._read()
self._read(relaxed)
self._load_default(only_new=True)
log.init.debug("Loaded bindings: {}".format(self.keybindings))
def __str__(self):
@@ -130,8 +150,8 @@ class KeyConfigParser(QObject):
data = str(self)
f.write(data)
@cmdutils.register(instance='key-config', maxsplit=1)
def bind(self, key, command, *, mode=None):
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True)
def bind(self, key, command, *, mode=None, force=False):
"""Bind a key to a command.
Args:
@@ -139,6 +159,7 @@ class KeyConfigParser(QObject):
command: The command to execute, with optional args.
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'
@@ -146,16 +167,20 @@ class KeyConfigParser(QObject):
for m in mode.split(','):
if m not in configdata.KEY_DATA:
raise cmdexc.CommandError("Invalid mode {}!".format(m))
split_cmd = command.split()
if split_cmd[0] not in cmdutils.cmd_dict:
raise cmdexc.CommandError("Invalid command {}!".format(
split_cmd[0]))
try:
self._add_binding(mode, key, command)
self._validate_command(command)
except KeyConfigError as e:
raise cmdexc.CommandError(str(e))
try:
self._add_binding(mode, key, command, force=force)
except DuplicateKeychainError as e:
raise cmdexc.CommandError("Duplicate keychain {} - use --force to "
"override!".format(str(e.keychain)))
except KeyConfigError as e:
raise cmdexc.CommandError(e)
for m in mode.split(','):
self.changed.emit(m)
self._mark_config_dirty()
@cmdutils.register(instance='key-config')
def unbind(self, key, mode=None):
@@ -183,8 +208,15 @@ class KeyConfigParser(QObject):
raise cmdexc.CommandError("Can't find binding '{}' in section "
"'{}'!".format(key, mode))
else:
if key in itertools.chain.from_iterable(
configdata.KEY_DATA[mode].values()):
try:
self._add_binding(mode, key, self.UNBOUND_COMMAND)
except DuplicateKeychainError:
pass
for m in mode.split(','):
self.changed.emit(m)
self._mark_config_dirty()
def _normalize_sectname(self, s):
"""Normalize a section string like 'foo, bar,baz' to 'bar,baz,foo'."""
@@ -198,20 +230,60 @@ class KeyConfigParser(QObject):
sections = '!' + sections
return sections
def _load_default(self):
"""Load the built-in default keybindings."""
def _load_default(self, *, only_new=False):
"""Load the built-in default key bindings.
Args:
only_new: If set, only keybindings which are completely unused
(same command/key not bound) are added.
"""
# {'sectname': {'keychain1': 'command', 'keychain2': 'command'}, ...}
bindings_to_add = collections.OrderedDict()
for sectname, sect in configdata.KEY_DATA.items():
sectname = self._normalize_sectname(sectname)
bindings_to_add[sectname] = collections.OrderedDict()
for command, keychains in sect.items():
for e in keychains:
if not only_new or self._is_new(sectname, command, e):
assert e not in bindings_to_add[sectname]
bindings_to_add[sectname][e] = command
for sectname, sect in bindings_to_add.items():
if not sect:
self.keybindings[sectname] = collections.OrderedDict()
if not only_new:
self.keybindings[sectname] = collections.OrderedDict()
else:
for command, keychains in sect.items():
for e in keychains:
self._add_binding(sectname, e, command)
for keychain, command in sect.items():
self._add_binding(sectname, keychain, command)
self.changed.emit(sectname)
def _read(self):
"""Read the config file from disk and parse it."""
if bindings_to_add:
self._mark_config_dirty()
def _is_new(self, sectname, command, keychain):
"""Check if a given binding is new.
A binding is considered new if both the command is not bound to any key
yet, and the key isn't used anywhere else in the same section.
"""
try:
bindings = self.keybindings[sectname]
except KeyError:
return True
if keychain in bindings:
return False
elif command in bindings.values():
return False
else:
return True
def _read(self, relaxed=False):
"""Read the config file from disk and parse it.
Args:
relaxed: Ignore unknown commands.
"""
try:
with open(self._configfile, 'r', encoding='utf-8') as f:
for i, line in enumerate(f):
@@ -230,45 +302,86 @@ class KeyConfigParser(QObject):
line = line.strip()
self._read_command(line)
except KeyConfigError as e:
e.lineno = i
raise
if relaxed:
continue
else:
e.lineno = i
raise
except OSError:
log.keyboard.exception("Failed to read keybindings!")
log.keyboard.exception("Failed to read key bindings!")
for sectname in self.keybindings:
self.changed.emit(sectname)
def _mark_config_dirty(self):
"""Mark the config as dirty."""
self.is_dirty = True
self.config_dirty.emit()
def _validate_command(self, line):
"""Check if a given command is valid."""
if line == self.UNBOUND_COMMAND:
return
commands = line.split(';;')
try:
first_cmd = commands[0].split(maxsplit=1)[0].strip()
cmd = cmdutils.cmd_dict[first_cmd]
if cmd.no_cmd_split:
commands = [line]
except (KeyError, IndexError):
pass
for cmd in commands:
if not cmd.strip():
raise KeyConfigError("Got empty command (line: {!r})!".format(
line))
commands = [c.split(maxsplit=1)[0].strip() for c in commands]
for cmd in commands:
if cmd not in cmdutils.cmd_dict:
raise KeyConfigError("Invalid command '{}'!".format(cmd))
def _read_command(self, line):
"""Read a command from a line."""
if self._cur_section is None:
raise KeyConfigError("Got command '{}' without getting a "
"section!".format(line))
else:
command = line.split(maxsplit=1)[0]
if command not in cmdutils.cmd_dict:
raise KeyConfigError("Invalid command '{}'!".format(command))
self._validate_command(line)
for rgx, repl in configdata.CHANGED_KEY_COMMANDS:
if rgx.match(line):
line = rgx.sub(repl, line)
self._mark_config_dirty()
break
self._cur_command = line
def _read_keybinding(self, line):
"""Read a keybinding from a line."""
"""Read a key binding from a line."""
if self._cur_command is None:
raise KeyConfigError("Got keybinding '{}' without getting a "
raise KeyConfigError("Got key binding '{}' without getting a "
"command!".format(line))
else:
assert self._cur_section is not None
self._add_binding(self._cur_section, line, self._cur_command)
def _add_binding(self, sectname, keychain, command):
def _add_binding(self, sectname, keychain, command, *, force=False):
"""Add a new binding from keychain to command in section sectname."""
log.keyboard.debug("Adding binding {} -> {} in mode {}.".format(
keychain, command, sectname))
if sectname not in self.keybindings:
self.keybindings[sectname] = collections.OrderedDict()
if keychain in self.get_bindings_for(sectname):
raise KeyConfigError("Duplicate keychain '{}'!".format(keychain))
if force or command == self.UNBOUND_COMMAND:
self.unbind(keychain, mode=sectname)
else:
raise DuplicateKeychainError(keychain)
section = self.keybindings[sectname]
if (command != self.UNBOUND_COMMAND and
section.get(keychain, None) == self.UNBOUND_COMMAND):
# re-binding an unbound keybinding
del section[keychain]
self.keybindings[sectname][keychain] = command
def get_bindings_for(self, section):
"""Get a dict with all merged keybindings for a section."""
"""Get a dict with all merged key bindings for a section."""
bindings = {}
for sectstring, d in self.keybindings.items():
if sectstring.startswith('!'):
@@ -284,4 +397,6 @@ class KeyConfigParser(QObject):
bindings.update(self.keybindings['all'])
except KeyError:
pass
bindings = {k: v for k, v in bindings.items()
if v != self.UNBOUND_COMMAND}
return bindings

View File

@@ -1,118 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Parser for line-based configurations like histories."""
import os
import os.path
import collections
from PyQt5.QtCore import pyqtSlot
from qutebrowser.utils import log, utils, objreg, qtutils
from qutebrowser.config import config
class LineConfigParser(collections.UserList):
"""Parser for configuration files which are simply line-based.
Attributes:
data: A list of lines.
_configdir: The directory to read the config from.
_configfile: The config file path.
_fname: Filename of the config.
_binary: Whether to open the file in binary mode.
_limit: The config section/option used to limit the maximum number of
lines.
"""
def __init__(self, configdir, fname, limit=None, binary=False):
"""Config constructor.
Args:
configdir: Directory to read the config from.
fname: Filename of the config file.
limit: Config tuple (section, option) which contains a limit.
binary: Whether to open the file in binary mode.
"""
super().__init__()
self._configdir = configdir
self._configfile = os.path.join(self._configdir, fname)
self._fname = fname
self._limit = limit
self._binary = binary
if not os.path.isfile(self._configfile):
self.data = []
else:
log.init.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile)
if limit is not None:
objreg.get('config').changed.connect(self.cleanup_file)
def __repr__(self):
return utils.get_repr(self, constructor=True,
configdir=self._configdir, fname=self._fname,
limit=self._limit, binary=self._binary)
def read(self, filename):
"""Read the data from a file."""
if self._binary:
with open(filename, 'rb') as f:
self.data = [line.rstrip(b'\n') for line in f.readlines()]
else:
with open(filename, 'r', encoding='utf-8') as f:
self.data = [line.rstrip('\n') for line in f.readlines()]
def write(self, fp, limit=-1):
"""Write the data to a file.
Args:
fp: A file object to write the data to.
limit: How many lines to write, or -1 for no limit.
"""
if limit == -1:
data = self.data
else:
data = self.data[-limit:]
if self._binary:
fp.write(b'\n'.join(data))
else:
fp.write('\n'.join(data))
def save(self):
"""Save the config file."""
limit = -1 if self._limit is None else config.get(*self._limit)
if limit == 0:
return
if not os.path.exists(self._configdir):
os.makedirs(self._configdir, 0o755)
log.destroy.debug("Saving config to {}".format(self._configfile))
with qtutils.savefile_open(self._configfile, self._binary) as f:
self.write(f, limit)
@pyqtSlot(str, str)
def cleanup_file(self, section, option):
"""Delete the file if the limit was changed to 0."""
if (section, option) != self._limit:
return
value = config.get(section, option)
if value == 0:
if os.path.exists(self._configfile):
os.remove(self._configfile)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -29,6 +29,7 @@ class Section:
"""Base class for KeyValue/ValueList sections.
Attributes:
_readonly: Whether this section is read-only.
values: An OrderedDict with key as index and value as value.
key: string
value: SettingValue
@@ -38,6 +39,7 @@ class Section:
def __init__(self):
self.values = None
self.descriptions = {}
self._readonly = False
def __getitem__(self, key):
"""Get the value for key.
@@ -99,13 +101,15 @@ class KeyValue(Section):
set of keys.
"""
def __init__(self, *defaults):
def __init__(self, *defaults, readonly=False):
"""Constructor.
Args:
*defaults: A (key, value, description) list of defaults.
readonly: Whether this config is readonly.
"""
super().__init__()
self._readonly = readonly
if not defaults:
return
self.values = collections.OrderedDict()
@@ -115,6 +119,8 @@ class KeyValue(Section):
self.descriptions[k] = desc
def setv(self, layer, key, value, interpolated):
if self._readonly:
raise ValueError("Trying to modify a read-only config!")
self.values[key].setv(layer, value, interpolated)
def dump_userconfig(self):
@@ -133,7 +139,7 @@ class ValueList(Section):
"""This class represents a section with a list key-value settings.
These are settings inside sections which don't have fixed keys, but instead
have a dynamic list of "key = value" pairs, like keybindings or
have a dynamic list of "key = value" pairs, like key bindings or
searchengines.
They basically consist of two different SettingValues.
@@ -143,17 +149,20 @@ class ValueList(Section):
keytype: The type to use for the key (only used for validating)
valtype: The type to use for the value.
_ordered_value_cache: A ChainMap-like OrderedDict of all values.
_readonly: Whether this section is read-only.
"""
def __init__(self, keytype, valtype, *defaults):
def __init__(self, keytype, valtype, *defaults, readonly=False):
"""Wrap types over default values. Take care when overriding this.
Args:
keytype: The type instance to be used for keys.
valtype: The type instance to be used for values.
*defaults: A (key, value) list of default values.
readonly: Whether this config is readonly.
"""
super().__init__()
self._readonly = readonly
self._ordered_value_cache = None
self.keytype = keytype
self.valtype = valtype
@@ -182,6 +191,8 @@ class ValueList(Section):
return self._ordered_value_cache
def setv(self, layer, key, value, interpolated):
if self._readonly:
raise ValueError("Trying to modify a read-only config!")
self.keytype.validate(key)
if key in self.layers[layer]:
self.layers[layer][key].setv(layer, value, interpolated)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -20,6 +20,7 @@
"""Utilities related to the look&feel of qutebrowser."""
import functools
import collections
import jinja2
import sip
@@ -42,8 +43,7 @@ def get_stylesheet(template_str):
colordict = ColorDict(config.section('colors'))
fontdict = FontDict(config.section('fonts'))
template = jinja2.Template(template_str)
return template.render(color=colordict, font=fontdict,
config=objreg.get('config'))
return template.render(color=colordict, font=fontdict)
def set_register_stylesheet(obj):
@@ -56,7 +56,7 @@ def set_register_stylesheet(obj):
Must have a STYLESHEET attribute.
"""
qss = get_stylesheet(obj.STYLESHEET)
log.style.vdebug("stylesheet for {}: {}".format(
log.config.vdebug("stylesheet for {}: {}".format(
obj.__class__.__name__, qss))
obj.setStyleSheet(qss)
objreg.get('config').changed.connect(
@@ -69,7 +69,7 @@ def update_stylesheet(obj):
obj.setStyleSheet(get_stylesheet(obj.STYLESHEET))
class ColorDict(dict):
class ColorDict(collections.UserDict):
"""A dict aimed at Qt stylesheet colors."""
@@ -89,12 +89,12 @@ class ColorDict(dict):
In all other cases, return the plain value.
"""
try:
val = super().__getitem__(key)
val = self.data[key]
except KeyError:
log.style.exception("No color defined for {}!")
log.config.exception("No color defined for {}!".format(key))
return ''
if isinstance(val, QColor):
# This could happen when accidentaly declarding something as
# This could happen when accidentally declaring something as
# QtColor instead of Color in the config, and it'd go unnoticed as
# the CSS is invalid then.
raise TypeError("QColor passed to ColorDict!")
@@ -106,7 +106,7 @@ class ColorDict(dict):
return val
class FontDict(dict):
class FontDict(collections.UserDict):
"""A dict aimed at Qt stylesheet fonts."""
@@ -123,7 +123,7 @@ class FontDict(dict):
In all other cases, return font: <value>.
"""
try:
val = super().__getitem__(key)
val = self.data[key]
except KeyError:
return ''
else:

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -26,7 +26,7 @@ class TextWrapper(textwrap.TextWrapper):
"""Text wrapper customized to be used in configs."""
def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
kw = {
'width': 72,
'replace_whitespace': False,
@@ -36,4 +36,4 @@ class TextWrapper(textwrap.TextWrapper):
'subsequent_indent': '# ',
}
kw.update(kwargs)
super().__init__(*args, **kw)
super().__init__(**kw)

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -26,7 +26,7 @@ class SettingValue:
"""Base class for setting values.
Intended to be subclassed by config value "types".
Intended to be sub-classed by config value "types".
Attributes:
typ: A BaseType subclass instance.
@@ -79,6 +79,7 @@ class SettingValue:
if val is not None:
return val
else: # pylint: disable=useless-else-on-loop
# https://bitbucket.org/logilab/pylint/issue/489/
raise ValueError("No valid config value found!")
def transformed(self):

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -22,189 +22,421 @@
Module attributes:
ATTRIBUTES: A mapping from internal setting names to QWebSetting enum
constants.
SETTERS: A mapping from setting names to QWebSetting setter method names.
settings: The global QWebSettings singleton instance.
"""
import os.path
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtCore import QStandardPaths
from qutebrowser.config import config
from qutebrowser.utils import usertypes, standarddir, objreg
from qutebrowser.utils import standarddir, objreg, log, utils, debug
MapType = usertypes.enum('MapType', ['attribute', 'setter', 'setter_none',
'static_setter'])
UNSET = object()
class Base:
"""Base class for QWebSetting wrappers.
Attributes:
_default: The default value of this setting.
"""
def __init__(self):
self._default = UNSET
def _get_qws(self, qws):
"""Get the QWebSettings object to use.
Args:
qws: The QWebSettings instance to use, or None to use the global
instance.
"""
if qws is None:
return QWebSettings.globalSettings()
else:
return qws
def save_default(self, qws=None):
"""Save the default value based on the currently set one.
This does nothing if no getter is configured for this setting.
Args:
qws: The QWebSettings instance to use, or None to use the global
instance.
Return:
The saved default value.
"""
try:
self._default = self.get(qws)
return self._default
except AttributeError:
return None
def restore_default(self, qws=None):
"""Restore the default value from the saved one.
This does nothing if the default has never been set.
Args:
qws: The QWebSettings instance to use, or None to use the global
instance.
"""
if self._default is not UNSET:
log.config.vdebug("Restoring default {!r}.".format(self._default))
self._set(self._default, qws=qws)
def get(self, qws=None):
"""Get the value of this setting.
Must be overridden by subclasses.
Args:
qws: The QWebSettings instance to use, or None to use the global
instance.
"""
raise NotImplementedError
def set(self, value, qws=None):
"""Set the value of this setting.
Args:
value: The value to set.
qws: The QWebSettings instance to use, or None to use the global
instance.
"""
if value is None:
self.restore_default(qws)
else:
self._set(value, qws=qws)
def _set(self, value, qws):
"""Inner function to set the value of this setting.
Must be overridden by subclasses.
Args:
value: The value to set.
qws: The QWebSettings instance to use, or None to use the global
instance.
"""
raise NotImplementedError
class Attribute(Base):
"""A setting set via QWebSettings::setAttribute.
Attributes:
self._attribute: A QWebSettings::WebAttribute instance.
"""
def __init__(self, attribute):
super().__init__()
self._attribute = attribute
def __repr__(self):
return utils.get_repr(
self, attribute=debug.qenum_key(QWebSettings, self._attribute),
constructor=True)
def get(self, qws=None):
return self._get_qws(qws).attribute(self._attribute)
def _set(self, value, qws=None):
self._get_qws(qws).setAttribute(self._attribute, value)
class Setter(Base):
"""A setting set via QWebSettings getter/setter methods.
This will pass the QWebSettings instance ("self") as first argument to the
methods, so self._getter/self._setter are the *unbound* methods.
Attributes:
_getter: The unbound QWebSettings method to get this value, or None.
_setter: The unbound QWebSettings method to set this value.
_args: An iterable of the arguments to pass to the setter/getter
(before the value, for the setter).
_unpack: Whether to unpack args (True) or pass them directly (False).
"""
def __init__(self, getter, setter, args=(), unpack=False):
super().__init__()
self._getter = getter
self._setter = setter
self._args = args
self._unpack = unpack
def __repr__(self):
return utils.get_repr(self, getter=self._getter, setter=self._setter,
args=self._args, unpack=self._unpack,
constructor=True)
def get(self, qws=None):
if self._getter is None:
raise AttributeError("No getter set!")
return self._getter(self._get_qws(qws), *self._args)
def _set(self, value, qws=None):
args = [self._get_qws(qws)]
args.extend(self._args)
if self._unpack:
args.extend(value)
else:
args.append(value)
self._setter(*args)
class NullStringSetter(Setter):
"""A setter for settings requiring a null QString as default.
This overrides save_default so None is saved for an empty string. This is
needed for the CSS media type, because it returns an empty Python string
when getting the value, but setting it to the default requires passing None
(a null QString) instead of an empty string.
"""
def save_default(self, qws=None):
try:
val = self.get(qws)
except AttributeError:
return None
if val == '':
self._set(None, qws=qws)
else:
self._set(val, qws=qws)
return val
class GlobalSetter(Setter):
"""A setting set via static QWebSettings getter/setter methods.
self._getter/self._setter are the *bound* methods.
"""
def get(self, qws=None):
if qws is not None:
raise ValueError("qws may not be set with GlobalSetters!")
if self._getter is None:
raise AttributeError("No getter set!")
return self._getter(*self._args)
def _set(self, value, qws=None):
if qws is not None:
raise ValueError("qws may not be set with GlobalSetters!")
args = list(self._args)
if self._unpack:
args.extend(value)
else:
args.append(value)
self._setter(*args)
class CookiePolicy(Base):
"""The ThirdPartyCookiePolicy setting is different from other settings."""
MAPPING = {
'all': QWebSettings.AlwaysAllowThirdPartyCookies,
'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies,
'never': QWebSettings.AlwaysBlockThirdPartyCookies,
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
}
def get(self, qws=None):
return config.get('content', 'cookies-accept')
def _set(self, value, qws=None):
QWebSettings.globalSettings().setThirdPartyCookiePolicy(
self.MAPPING[value])
MAPPINGS = {
'content': {
'allow-images':
(MapType.attribute, QWebSettings.AutoLoadImages),
Attribute(QWebSettings.AutoLoadImages),
'allow-javascript':
(MapType.attribute, QWebSettings.JavascriptEnabled),
Attribute(QWebSettings.JavascriptEnabled),
'javascript-can-open-windows':
(MapType.attribute, QWebSettings.JavascriptCanOpenWindows),
Attribute(QWebSettings.JavascriptCanOpenWindows),
'javascript-can-close-windows':
(MapType.attribute, QWebSettings.JavascriptCanCloseWindows),
Attribute(QWebSettings.JavascriptCanCloseWindows),
'javascript-can-access-clipboard':
(MapType.attribute, QWebSettings.JavascriptCanAccessClipboard),
Attribute(QWebSettings.JavascriptCanAccessClipboard),
#'allow-java':
# (MapType.attribute, QWebSettings.JavaEnabled),
# Attribute(QWebSettings.JavaEnabled),
'allow-plugins':
(MapType.attribute, QWebSettings.PluginsEnabled),
Attribute(QWebSettings.PluginsEnabled),
'webgl':
Attribute(QWebSettings.WebGLEnabled),
'css-regions':
Attribute(QWebSettings.CSSRegionsEnabled),
'hyperlink-auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled),
'local-content-can-access-remote-urls':
(MapType.attribute, QWebSettings.LocalContentCanAccessRemoteUrls),
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls':
(MapType.attribute, QWebSettings.LocalContentCanAccessFileUrls),
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
'cookies-accept':
CookiePolicy(),
},
'network': {
'dns-prefetch':
(MapType.attribute, QWebSettings.DnsPrefetchEnabled),
Attribute(QWebSettings.DnsPrefetchEnabled),
},
'input': {
'spatial-navigation':
(MapType.attribute, QWebSettings.SpatialNavigationEnabled),
Attribute(QWebSettings.SpatialNavigationEnabled),
'links-included-in-focus-chain':
(MapType.attribute, QWebSettings.LinksIncludedInFocusChain),
Attribute(QWebSettings.LinksIncludedInFocusChain),
},
'fonts': {
'web-family-standard':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.StandardFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.StandardFont]),
'web-family-fixed':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.FixedFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FixedFont]),
'web-family-serif':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.SerifFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SerifFont]),
'web-family-sans-serif':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.SansSerifFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SansSerifFont]),
'web-family-cursive':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.CursiveFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.CursiveFont]),
'web-family-fantasy':
(MapType.setter_none, lambda qws, v:
qws.setFontFamily(QWebSettings.FantasyFont, v)),
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FantasyFont]),
'web-size-minimum':
(MapType.setter, lambda qws, v:
qws.setFontSize(QWebSettings.MinimumFontSize, v)),
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.MinimumFontSize]),
'web-size-minimum-logical':
(MapType.setter, lambda qws, v:
qws.setFontSize(QWebSettings.MinimumLogicalFontSize, v)),
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]),
'web-size-default':
(MapType.setter, lambda qws, v:
qws.setFontSize(QWebSettings.DefaultFontSize, v)),
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.DefaultFontSize]),
'web-size-default-fixed':
(MapType.setter, lambda qws, v:
qws.setFontSize(QWebSettings.DefaultFixedFontSize, v)),
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]),
},
'ui': {
'zoom-text-only':
(MapType.attribute, QWebSettings.ZoomTextOnly),
Attribute(QWebSettings.ZoomTextOnly),
'frame-flattening':
(MapType.attribute, QWebSettings.FrameFlatteningEnabled),
Attribute(QWebSettings.FrameFlatteningEnabled),
'user-stylesheet':
(MapType.setter_none, lambda qws, v: qws.setUserStyleSheetUrl(v)),
Setter(getter=QWebSettings.userStyleSheetUrl,
setter=QWebSettings.setUserStyleSheetUrl),
'css-media-type':
(MapType.setter, lambda qws, v: qws.setCSSMediaType(v)),
NullStringSetter(getter=QWebSettings.cssMediaType,
setter=QWebSettings.setCSSMediaType),
'smooth-scrolling':
Attribute(QWebSettings.ScrollAnimatorEnabled),
#'accelerated-compositing':
# (MapType.attribute, QWebSettings.AcceleratedCompositingEnabled),
# Attribute(QWebSettings.AcceleratedCompositingEnabled),
#'tiled-backing-store':
# (MapType.attribute, QWebSettings.TiledBackingStoreEnabled),
# Attribute(QWebSettings.TiledBackingStoreEnabled),
},
'storage': {
'offline-storage-database':
(MapType.attribute, QWebSettings.OfflineStorageDatabaseEnabled),
Attribute(QWebSettings.OfflineStorageDatabaseEnabled),
'offline-web-application-storage':
(MapType.attribute,
QWebSettings.OfflineWebApplicationCacheEnabled),
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'local-storage':
(MapType.attribute, QWebSettings.LocalStorageEnabled),
Attribute(QWebSettings.LocalStorageEnabled),
'maximum-pages-in-cache':
(MapType.static_setter, lambda v:
QWebSettings.setMaximumPagesInCache(v)),
GlobalSetter(getter=QWebSettings.maximumPagesInCache,
setter=QWebSettings.setMaximumPagesInCache),
'object-cache-capacities':
(MapType.static_setter, lambda v:
QWebSettings.setObjectCacheCapacities(*v)),
GlobalSetter(getter=None,
setter=QWebSettings.setObjectCacheCapacities,
unpack=True),
'offline-storage-default-quota':
(MapType.static_setter, lambda v:
QWebSettings.setOfflineStorageDefaultQuota(v)),
GlobalSetter(getter=QWebSettings.offlineStorageDefaultQuota,
setter=QWebSettings.setOfflineStorageDefaultQuota),
'offline-web-application-cache-quota':
(MapType.static_setter, lambda v:
QWebSettings.setOfflineWebApplicationCacheQuota(v)),
GlobalSetter(
getter=QWebSettings.offlineWebApplicationCacheQuota,
setter=QWebSettings.setOfflineWebApplicationCacheQuota),
},
'general': {
'private-browsing':
(MapType.attribute, QWebSettings.PrivateBrowsingEnabled),
Attribute(QWebSettings.PrivateBrowsingEnabled),
'developer-extras':
(MapType.attribute, QWebSettings.DeveloperExtrasEnabled),
Attribute(QWebSettings.DeveloperExtrasEnabled),
'print-element-backgrounds':
(MapType.attribute, QWebSettings.PrintElementBackgrounds),
Attribute(QWebSettings.PrintElementBackgrounds),
'xss-auditing':
(MapType.attribute, QWebSettings.XSSAuditingEnabled),
Attribute(QWebSettings.XSSAuditingEnabled),
'site-specific-quirks':
(MapType.attribute, QWebSettings.SiteSpecificQuirksEnabled),
Attribute(QWebSettings.SiteSpecificQuirksEnabled),
'default-encoding':
(MapType.setter_none, lambda qws, v:
qws.setDefaultTextEncoding(v)),
Setter(getter=QWebSettings.defaultTextEncoding,
setter=QWebSettings.setDefaultTextEncoding),
}
}
settings = None
def _set_setting(typ, arg, value):
"""Set a QWebSettings setting.
Args:
typ: The type of the item.
arg: The argument (attribute/handler)
value: The value to set.
"""
if not isinstance(typ, MapType):
raise TypeError("Type {} is no MapType member!".format(typ))
if typ == MapType.attribute:
settings.setAttribute(arg, value)
elif typ == MapType.setter_none:
if value is None:
value = ""
arg(settings, value)
elif typ == MapType.setter and value is not None:
arg(settings, value)
elif typ == MapType.static_setter and value is not None:
arg(value)
def init():
"""Initialize the global QWebSettings."""
cachedir = standarddir.get(QStandardPaths.CacheLocation)
QWebSettings.setIconDatabasePath(cachedir)
QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cachedir, 'application-cache'))
datadir = standarddir.get(QStandardPaths.DataLocation)
QWebSettings.globalSettings().setLocalStoragePath(
os.path.join(datadir, 'local-storage'))
QWebSettings.setOfflineStoragePath(
os.path.join(datadir, 'offline-storage'))
cache_path = standarddir.cache()
data_path = standarddir.data()
if config.get('general', 'private-browsing') or cache_path is None:
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(cache_path)
if cache_path is not None:
QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cache_path, 'application-cache'))
if data_path is not None:
QWebSettings.globalSettings().setLocalStoragePath(
os.path.join(data_path, 'local-storage'))
QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage'))
global settings
settings = QWebSettings.globalSettings()
for sectname, section in MAPPINGS.items():
for optname, (typ, arg) in section.items():
for optname, mapping in section.items():
default = mapping.save_default()
log.config.vdebug("Saved default for {} -> {}: {!r}".format(
sectname, optname, default))
value = config.get(sectname, optname)
_set_setting(typ, arg, value)
log.config.vdebug("Setting {} -> {} to {!r}".format(
sectname, optname, value))
mapping.set(value)
objreg.get('config').changed.connect(update_settings)
def update_settings(section, option):
"""Update global settings when qwebsettings changed."""
try:
typ, arg = MAPPINGS[section][option]
except KeyError:
return
value = config.get(section, option)
_set_setting(typ, arg, value)
cache_path = standarddir.cache()
if (section, option) == ('general', 'private-browsing'):
if config.get('general', 'private-browsing') or cache_path is None:
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(cache_path)
else:
try:
mapping = MAPPINGS[section][option]
except KeyError:
return
value = config.get(section, option)
mapping.set(value)

View File

@@ -0,0 +1,67 @@
{% extends "base.html" %}
{% block style %}
{{ super() }}
#dirbrowserContainer {
background: #fff;
min-width: 35em;
max-width: 35em;
position: absolute;
top: 2em;
left: 1em;
padding: 10px;
border: 2px solid #eee;
-webkit-border-radius: 5px;
}
#dirbrowserTitleText {
font-size: 118%;
font-weight: bold;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
ul > li {
background-repeat: no-repeat;
background-size: 22px;
line-height: 22px;
padding-left: 25px;
}
ul > li {
background-image: url('{{folder_url}}');
}
ul.files > li {
background-image: url('{{file_url}}');
}
{% endblock %}
{% block content %}
<div id="dirbrowserContainer">
<div id="dirbrowserTitle">
<p id="dirbrowserTitleText">Browse directory: {{url}}</p>
</div>
{% if parent %}
<ul class="parent">
<li><a href="{{parent}}">..</a></li>
</ul>
{% endif %}
<ul class="folders">
{% for item in directories %}
<li><a href="file://{{item.absname}}">{{item.name}}</a></li>
{% endfor %}
</ul>
<ul class="files">
{% for item in files %}
<li><a href="file://{{item.absname}}">{{item.name}}</a></li>
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@@ -14,21 +14,23 @@ pre { margin: 2px; }
th, td { border: 1px solid grey; padding: 0px 5px; }
th { background: lightgrey; }
th pre { color: grey; text-align: left; }
.noscript, .noscript-text { color:red; }
.noscript-text { margin-bottom: 5cm; }
{% endblock %}
{% block content %}
<noscript><h1>View Only</h1><p>Changing settings requires javascript to be enabled</p></noscript>
<noscript><h1 class="noscript">View Only</h1><p class="noscript-text">Changing settings requires javascript to be enabled!</p></noscript>
<header><h1>{{ title }}</h1></header>
<table>
{% for section in config.DATA %}
<tr><th colspan="2"><h3>{{ section }}</h3><pre>{{ config.SECTION_DESC.get(section)|wordwrap(width=120) }}</pre></th></tr>
{% for d, e in config.DATA.get(section).items() %}
<tr>
<td>{{ d }} (Current: {{ e.value()|truncate(100) }})</td>
<td>{{ d }} (Current: {{ confget(section, d)|truncate(100) }})</td>
<td>
<input type="input"
onblur="cset('{{ section }}', '{{ d }}', this)"
value="{{ e.value() }}">
value="{{ confget(section, d) }}">
</input>
</td>
</tr>

View File

@@ -21,6 +21,6 @@ GNU General Public License for more details.
<p>
You should have received a copy of the GNU General Public License
along with this program. If not, see <a href="http://www.gnu.org/licenses/">
http://www.gnu.org/licenses/</a> or open <a href="qute:gpl">qute:gpl</a>.
http://www.gnu.org/licenses/</a> or open <a href="qute://gpl">qute://gpl</a>.
</p>
{% endblock %}

548
qutebrowser/img/file.svg Normal file
View File

@@ -0,0 +1,548 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="240.00000"
inkscape:export-xdpi="240.00000"
inkscape:export-filename="/home/jimmac/gfx/novell/pdes/trunk/docs/BIGmime-text.png"
sodipodi:docname="text-x-generic.svg"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/mimetypes"
inkscape:version="0.46"
sodipodi:version="0.32"
id="svg249"
height="48.000000px"
width="48.000000px"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective78" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6717"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient6715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient4542">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop4544" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop4546" />
</linearGradient>
<linearGradient
id="linearGradient15662">
<stop
id="stop15664"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop15666"
offset="1.0000000"
style="stop-color:#f8f8f8;stop-opacity:1.0000000;" />
</linearGradient>
<radialGradient
id="aigrd3"
cx="20.8921"
cy="64.5679"
r="5.257"
fx="20.8921"
fy="64.5679"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop15573" />
<stop
offset="1.0000000"
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
id="stop15575" />
</radialGradient>
<radialGradient
id="aigrd2"
cx="20.8921"
cy="114.5684"
r="5.256"
fx="20.8921"
fy="114.5684"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop15566" />
<stop
offset="1.0000000"
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
id="stop15568" />
</radialGradient>
<linearGradient
id="linearGradient269">
<stop
id="stop270"
offset="0.0000000"
style="stop-color:#a3a3a3;stop-opacity:1.0000000;" />
<stop
id="stop271"
offset="1.0000000"
style="stop-color:#4c4c4c;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient259">
<stop
id="stop260"
offset="0.0000000"
style="stop-color:#fafafa;stop-opacity:1.0000000;" />
<stop
id="stop261"
offset="1.0000000"
style="stop-color:#bbbbbb;stop-opacity:1.0000000;" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient269"
id="radialGradient15656"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.968273,0.000000,0.000000,1.032767,3.353553,0.646447)"
cx="8.8244190"
cy="3.7561285"
fx="8.8244190"
fy="3.7561285"
r="37.751713" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient259"
id="radialGradient15658"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(0.960493,1.041132)"
cx="33.966679"
cy="35.736916"
fx="33.966679"
fy="35.736916"
r="86.708450" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient15662"
id="radialGradient15668"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.968273,0.000000,0.000000,1.032767,3.353553,0.646447)"
cx="8.1435566"
cy="7.2678967"
fx="8.1435566"
fy="7.2678967"
r="38.158695" />
<radialGradient
r="5.256"
fy="114.5684"
fx="20.8921"
cy="114.5684"
cx="20.8921"
gradientTransform="matrix(0.229703,0.000000,0.000000,0.229703,4.613529,3.979808)"
gradientUnits="userSpaceOnUse"
id="radialGradient2283"
xlink:href="#aigrd2"
inkscape:collect="always" />
<radialGradient
r="5.257"
fy="64.5679"
fx="20.8921"
cy="64.5679"
cx="20.8921"
gradientTransform="matrix(0.229703,0.000000,0.000000,0.229703,4.613529,3.979808)"
gradientUnits="userSpaceOnUse"
id="radialGradient2285"
xlink:href="#aigrd3"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4542"
id="radialGradient4548"
cx="24.306795"
cy="42.07798"
fx="24.306795"
fy="42.07798"
r="15.821514"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.284916,0.000000,30.08928)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
inkscape:window-y="160"
inkscape:window-x="343"
inkscape:window-height="688"
inkscape:window-width="872"
inkscape:document-units="px"
inkscape:grid-bbox="true"
showgrid="false"
inkscape:current-layer="layer6"
inkscape:cy="24.318443"
inkscape:cx="25.938708"
inkscape:zoom="5.6568542"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
borderopacity="0.25490196"
bordercolor="#666666"
pagecolor="#ffffff"
id="base"
inkscape:showpageshadow="false" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Generic Text</dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>text</rdf:li>
<rdf:li>plaintext</rdf:li>
<rdf:li>regular</rdf:li>
<rdf:li>document</rdf:li>
</rdf:Bag>
</dc:subject>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>http://jimmac.musichall.cz</dc:source>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Shadow">
<g
style="display:inline"
transform="matrix(2.105461e-2,0,0,2.086758e-2,42.85172,41.1536)"
id="g6707">
<rect
style="opacity:0.40206185;color:black;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6709"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" />
<path
style="opacity:0.40206185;color:black;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
id="path6711"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
id="path6713"
d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
style="opacity:0.40206185;color:black;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
</g>
</g>
<g
style="display:inline"
inkscape:groupmode="layer"
inkscape:label="Base"
id="layer1">
<rect
style="color:#000000;fill:url(#radialGradient15658);fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient15656);stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15391"
width="34.875000"
height="40.920494"
x="6.6035528"
y="3.6464462"
ry="1.1490486" />
<rect
style="color:#000000;fill:none;fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#radialGradient15668);stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15660"
width="32.775887"
height="38.946384"
x="7.6660538"
y="4.5839462"
ry="0.14904857"
rx="0.14904857" />
<g
transform="translate(0.646447,-3.798933e-2)"
id="g2270">
<g
id="g1440"
style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#000000;stroke-miterlimit:4.0000000"
transform="matrix(0.229703,0.000000,0.000000,0.229703,4.967081,4.244972)">
<radialGradient
id="radialGradient1442"
cx="20.892099"
cy="114.56840"
r="5.2560000"
fx="20.892099"
fy="114.56840"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop1444" />
<stop
offset="1"
style="stop-color:#474747"
id="stop1446" />
</radialGradient>
<path
style="stroke:none"
d="M 23.428000,113.07000 C 23.428000,115.04300 21.828000,116.64200 19.855000,116.64200 C 17.881000,116.64200 16.282000,115.04200 16.282000,113.07000 C 16.282000,111.09600 17.882000,109.49700 19.855000,109.49700 C 21.828000,109.49700 23.428000,111.09700 23.428000,113.07000 z "
id="path1448" />
<radialGradient
id="radialGradient1450"
cx="20.892099"
cy="64.567902"
r="5.2570000"
fx="20.892099"
fy="64.567902"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop1452" />
<stop
offset="1"
style="stop-color:#474747"
id="stop1454" />
</radialGradient>
<path
style="stroke:none"
d="M 23.428000,63.070000 C 23.428000,65.043000 21.828000,66.643000 19.855000,66.643000 C 17.881000,66.643000 16.282000,65.043000 16.282000,63.070000 C 16.282000,61.096000 17.882000,59.497000 19.855000,59.497000 C 21.828000,59.497000 23.428000,61.097000 23.428000,63.070000 z "
id="path1456" />
</g>
<path
style="fill:url(#radialGradient2283);fill-rule:nonzero;stroke:none;stroke-miterlimit:4.0000000"
d="M 9.9950109,29.952326 C 9.9950109,30.405530 9.6274861,30.772825 9.1742821,30.772825 C 8.7208483,30.772825 8.3535532,30.405301 8.3535532,29.952326 C 8.3535532,29.498892 8.7210780,29.131597 9.1742821,29.131597 C 9.6274861,29.131597 9.9950109,29.499122 9.9950109,29.952326 z "
id="path15570" />
<path
style="fill:url(#radialGradient2285);fill-rule:nonzero;stroke:none;stroke-miterlimit:4.0000000"
d="M 9.9950109,18.467176 C 9.9950109,18.920380 9.6274861,19.287905 9.1742821,19.287905 C 8.7208483,19.287905 8.3535532,18.920380 8.3535532,18.467176 C 8.3535532,18.013742 8.7210780,17.646447 9.1742821,17.646447 C 9.6274861,17.646447 9.9950109,18.013972 9.9950109,18.467176 z "
id="path15577" />
</g>
<path
style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.98855311;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.017543854"
d="M 11.505723,5.4942766 L 11.505723,43.400869"
id="path15672"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.20467831"
d="M 12.500000,5.0205154 L 12.500000,43.038228"
id="path15674"
sodipodi:nodetypes="cc" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="Text"
style="display:inline">
<g
transform="matrix(0.909091,0.000000,0.000000,1.000000,2.363628,0.000000)"
id="g2253">
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15686"
width="22.000004"
height="1.0000000"
x="15.000002"
y="9.0000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15688"
width="22.000004"
height="1.0000000"
x="15.000002"
y="11.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15690"
width="22.000004"
height="1.0000000"
x="15.000002"
y="13.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15692"
width="22.000004"
height="1.0000000"
x="15.000002"
y="15.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15694"
width="22.000004"
height="1.0000000"
x="15.000002"
y="17.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15696"
width="22.000004"
height="1.0000000"
x="15.000002"
y="19.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15698"
width="22.000004"
height="1.0000000"
x="15.000002"
y="21.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15700"
width="22.000004"
height="1.0000000"
x="15.000002"
y="23.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15732"
width="9.9000053"
height="1.0000000"
x="14.999992"
y="25.000000"
rx="0.068204239"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15736"
width="22.000004"
height="1.0000000"
x="14.999992"
y="29.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15738"
width="22.000004"
height="1.0000000"
x="14.999992"
y="31.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15740"
width="22.000004"
height="1.0000000"
x="14.999992"
y="33.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15742"
width="22.000004"
height="1.0000000"
x="14.999992"
y="35.000000"
rx="0.15156493"
ry="0.065390877" />
<rect
style="color:#000000;fill:#9b9b9b;fill-opacity:0.54970759;fill-rule:nonzero;stroke:none;stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:0.081871338;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:block;overflow:visible"
id="rect15744"
width="15.400014"
height="1.0000000"
x="14.999992"
y="37.000000"
rx="0.10609552"
ry="0.065390877" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

424
qutebrowser/img/folder.svg Normal file
View File

@@ -0,0 +1,424 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48.000000px"
height="48.000000px"
id="svg97"
sodipodi:version="0.32"
inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/places"
sodipodi:docname="folder.svg"
inkscape:export-filename="/home/jimmac/Desktop/horlander-style3.png"
inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000"
inkscape:output_extension="org.inkscape.output.svg.inkscape">
<defs
id="defs3">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 24 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="48 : 24 : 1"
inkscape:persp3d-origin="24 : 16 : 1"
id="perspective68" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6717"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient6715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient9806">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop9808" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop9810" />
</linearGradient>
<linearGradient
id="linearGradient9766">
<stop
style="stop-color:#6194cb;stop-opacity:1;"
offset="0"
id="stop9768" />
<stop
style="stop-color:#729fcf;stop-opacity:1;"
offset="1"
id="stop9770" />
</linearGradient>
<linearGradient
id="linearGradient3096">
<stop
id="stop3098"
offset="0"
style="stop-color:#424242;stop-opacity:1;" />
<stop
id="stop3100"
offset="1.0000000"
style="stop-color:#777777;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient319"
inkscape:collect="always">
<stop
id="stop320"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop321"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient1789">
<stop
style="stop-color:#202020;stop-opacity:1.0000000;"
offset="0.0000000"
id="stop1790" />
<stop
style="stop-color:#b9b9b9;stop-opacity:1.0000000;"
offset="1.0000000"
id="stop1791" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient1789"
id="radialGradient238"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.055022,-2.734504e-2,0.177703,1.190929,-3.572177,-7.125301)"
cx="20.706017"
cy="37.517986"
fx="20.706017"
fy="37.517986"
r="30.905205" />
<linearGradient
id="linearGradient3983">
<stop
style="stop-color:#ffffff;stop-opacity:0.87628865;"
offset="0.0000000"
id="stop3984" />
<stop
style="stop-color:#fffffe;stop-opacity:0.0000000;"
offset="1.0000000"
id="stop3985" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3983"
id="linearGradient491"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.516844,0.000000,0.000000,0.708978,-0.879573,-1.318166)"
x1="6.2297964"
y1="13.773066"
x2="9.8980894"
y2="66.834053" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="46.689312"
x2="12.853771"
y1="32.567184"
x1="13.035696"
gradientTransform="matrix(1.317489,0.000000,0.000000,0.816256,-0.879573,-1.318166)"
id="linearGradient322"
xlink:href="#linearGradient319"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="6.1802502"
x2="15.514889"
y1="31.367750"
x1="18.112709"
id="linearGradient3104"
xlink:href="#linearGradient3096"
inkscape:collect="always" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient9766"
id="linearGradient9772"
x1="22.175976"
y1="36.987999"
x2="22.065331"
y2="32.050499"
gradientUnits="userSpaceOnUse" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient9806"
id="radialGradient9812"
cx="24.35099"
cy="41.591846"
fx="24.35099"
fy="41.591846"
r="19.136078"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.242494,1.565588e-16,31.50606)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
fill="#729fcf"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.10196078"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="4"
inkscape:cx="54.359127"
inkscape:cy="-13.803699"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1026"
inkscape:window-height="818"
inkscape:window-x="169"
inkscape:window-y="30"
inkscape:showpageshadow="false"
stroke="#3465a4" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Folder Icon</dc:title>
<dc:date />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:source>http://jimmac.musichall.cz</dc:source>
<dc:subject>
<rdf:Bag>
<rdf:li>folder</rdf:li>
<rdf:li>directory</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Folder"
inkscape:groupmode="layer">
<g
style="display:inline"
transform="matrix(2.262383e-2,0,0,2.086758e-2,43.38343,36.36962)"
id="g6707">
<rect
style="opacity:0.40206185;color:black;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="rect6709"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" />
<path
style="opacity:0.40206185;color:black;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M -219.61876,-150.68038 C -219.61876,-150.68038 -219.61876,327.65041 -219.61876,327.65041 C -76.744594,328.55086 125.78146,220.48075 125.78138,88.454235 C 125.78138,-43.572302 -33.655436,-150.68036 -219.61876,-150.68038 z "
id="path6711"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
id="path6713"
d="M -1559.2523,-150.68038 C -1559.2523,-150.68038 -1559.2523,327.65041 -1559.2523,327.65041 C -1702.1265,328.55086 -1904.6525,220.48075 -1904.6525,88.454235 C -1904.6525,-43.572302 -1745.2157,-150.68036 -1559.2523,-150.68038 z "
style="opacity:0.40206185;color:black;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
</g>
<path
d="M 4.5217805,38.687417 C 4.5435766,39.103721 4.9816854,39.520026 5.3979900,39.520026 L 36.725011,39.520026 C 37.141313,39.520026 37.535823,39.103721 37.514027,38.687417 L 36.577584,11.460682 C 36.555788,11.044379 36.117687,10.628066 35.701383,10.628066 L 22.430510,10.628066 C 21.945453,10.628066 21.196037,10.312477 21.028866,9.5214338 L 20.417475,6.6283628 C 20.262006,5.8926895 19.535261,5.5904766 19.118957,5.5904766 L 4.3400975,5.5904766 C 3.9237847,5.5904766 3.5292767,6.0067807 3.5510726,6.4230849 L 4.5217805,38.687417 z "
id="path216"
style="fill:url(#radialGradient238);fill-opacity:1.0000000;fill-rule:nonzero;stroke:url(#linearGradient3104);stroke-width:1.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
sodipodi:nodetypes="ccccccssssccc" />
<path
sodipodi:nodetypes="cc"
id="path9788"
d="M 5.2265927,22.5625 L 35.492173,22.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="cc"
id="path9784"
d="M 5.0421736,18.5625 L 35.489104,18.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 4.9806965,12.5625 L 35.488057,12.5625"
id="path9778"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.3861577,32.5625 L 35.494881,32.5625"
id="path9798"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9800"
d="M 5.5091398,34.5625 L 35.496893,34.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.0421736,16.5625 L 35.489104,16.5625"
id="path9782"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9780"
d="M 5.0114345,14.5625 L 35.48858,14.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="cc"
id="path9776"
d="M 4.9220969,10.5625 L 20.202912,10.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.99999982;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 4.8737534,8.5624999 L 19.657487,8.5624999"
id="path9774"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.3246666,28.5625 L 35.493876,28.5625"
id="path9794"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9792"
d="M 5.2880638,26.5625 L 35.493184,26.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.2265927,24.5625 L 35.492173,24.5625"
id="path9790"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.1958537,20.5625 L 35.491649,20.5625"
id="path9786"
sodipodi:nodetypes="cc" />
<path
sodipodi:nodetypes="cc"
id="path9796"
d="M 5.3246666,30.5625 L 35.493876,30.5625"
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
style="opacity:0.11363633;color:#000000;fill:#729fcf;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 5.5091398,36.5625 L 35.496893,36.5625"
id="path9802"
sodipodi:nodetypes="cc" />
<path
style="color:#000000;fill:url(#linearGradient491);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:1.2138050;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dashoffset:0.0000000;stroke-opacity:1.0000000;visibility:visible;display:block;overflow:visible;opacity:0.45142857"
d="M 6.0683430,38.864023 C 6.0846856,39.176251 5.8874317,39.384402 5.5697582,39.280326 L 5.5697582,39.280326 C 5.2520766,39.176251 5.0330270,38.968099 5.0166756,38.655870 L 4.0689560,6.5913839 C 4.0526131,6.2791558 4.2341418,6.0906134 4.5463699,6.0906134 L 18.968420,6.0429196 C 19.280648,6.0429196 19.900363,6.3433923 20.101356,7.3651014 L 20.674845,10.180636 C 20.247791,9.7153790 20.255652,9.7010175 20.037287,9.0239299 L 19.631192,7.7647478 C 19.412142,7.0371009 18.932991,6.9328477 18.620763,6.9328477 L 5.7329889,6.9328477 C 5.4207613,6.9328477 5.2235075,7.1409999 5.2398583,7.4532364 L 6.1778636,38.968099 L 6.0683430,38.864023 z "
id="path219"
sodipodi:nodetypes="cccccccccscccccc" />
<g
style="stroke-miterlimit:4.0000000;stroke-width:0.99946535;stroke:none;fill-rule:nonzero;fill-opacity:0.75706214;fill:#ffffff"
id="g220"
transform="matrix(1.040764,0.000000,5.449252e-2,1.040764,-8.670199,2.670594)"
inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
inkscape:export-xdpi="74.800003"
inkscape:export-ydpi="74.800003">
<path
style="fill-opacity:0.50847459;fill:#ffffff"
d="M 42.417183,8.5151772 C 42.422267,8.4180642 42.289022,8.2681890 42.182066,8.2681716 L 29.150665,8.2660527 C 29.150665,8.2660527 30.062379,8.8540072 31.352477,8.8622963 L 42.405974,8.9333167 C 42.417060,8.7215889 42.408695,8.6772845 42.417183,8.5151772 z "
id="path221"
sodipodi:nodetypes="cscscs" />
</g>
<path
style="color:#000000;fill:url(#linearGradient9772);fill-opacity:1.0;fill-rule:nonzero;stroke:#3465a4;stroke-width:1.0000000;stroke-linecap:butt;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-dashoffset:0.0000000;stroke-opacity:1;visibility:visible;display:block"
d="M 39.783532,39.510620 C 40.927426,39.466556 41.746608,38.414321 41.830567,37.189615 C 42.622354,25.640928 43.489927,15.957666 43.489927,15.957666 C 43.562082,15.710182 43.322016,15.462699 43.009787,15.462699 L 8.6386304,15.462699 C 8.6386304,15.462699 6.7883113,37.329591 6.7883113,37.329591 C 6.6737562,38.311657 6.3223038,39.134309 5.2384755,39.513304 L 39.783532,39.510620 z "
id="path233"
sodipodi:nodetypes="cscccscc"
inkscape:export-filename="/home/jimmac/ximian_art/icons/nautilus/suse93/gnome-fs-directory.png"
inkscape:export-xdpi="74.800003"
inkscape:export-ydpi="74.800003" />
<path
sodipodi:nodetypes="ccsscsc"
id="path304"
d="M 9.6202444,16.463921 L 42.411343,16.528735 L 40.837297,36.530714 C 40.752975,37.602225 40.386619,37.958929 38.964641,37.958929 C 37.093139,37.958929 10.286673,37.926522 7.569899,37.926522 C 7.8034973,37.605711 7.9036547,36.937899 7.9049953,36.92191 L 9.6202444,16.463921 z "
style="opacity:0.46590909;fill:none;fill-opacity:1.0000000;fill-rule:evenodd;stroke:url(#linearGradient322);stroke-width:0.99999970px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1.0000000" />
<path
sodipodi:nodetypes="ccccc"
id="path323"
d="M 9.6202481,16.223182 L 8.4536014,31.866453 C 8.4536014,31.866453 16.749756,27.718375 27.119949,27.718375 C 37.490142,27.718375 42.675239,16.223182 42.675239,16.223182 L 9.6202481,16.223182 z "
style="fill:#ffffff;fill-opacity:0.089285679;fill-rule:evenodd;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="pattern" />
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

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