summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2015-07-26 15:08:58 +0200
committerFlorian Bruhin <git@the-compiler.org>2015-07-26 15:08:58 +0200
commitb52a41ac6fc262421aeb612d9faaf74a48b8811b (patch)
tree809f91ada0580f2abdc28713298c08c64c8a5e3d
parentc750ff3f507b56a12861a252994b001166b82708 (diff)
parent91561e2c5b50de23b140e2b19843687ea853f536 (diff)
downloadqutebrowser-b52a41ac6fc262421aeb612d9faaf74a48b8811b.tar.gz
qutebrowser-b52a41ac6fc262421aeb612d9faaf74a48b8811b.zip
Merge branch 'master' of https://github.com/antoyo/qutebrowser into antoyo-master
Conflicts: .gitignore
-rw-r--r--.gitignore2
-rw-r--r--qutebrowser/app.py6
-rw-r--r--qutebrowser/browser/bookmarks.py124
-rw-r--r--qutebrowser/browser/commands.py25
-rw-r--r--qutebrowser/completion/completer.py11
-rw-r--r--qutebrowser/completion/completiondelegate.py3
-rw-r--r--qutebrowser/completion/models/base.py15
-rw-r--r--qutebrowser/completion/models/configmodel.py3
-rw-r--r--qutebrowser/completion/models/instances.py17
-rw-r--r--qutebrowser/completion/models/miscmodels.py26
-rw-r--r--qutebrowser/completion/models/sortfilter.py11
-rw-r--r--qutebrowser/completion/models/urlmodel.py80
-rw-r--r--qutebrowser/config/configdata.py2
-rw-r--r--qutebrowser/utils/usertypes.py3
14 files changed, 318 insertions, 10 deletions
diff --git a/.gitignore b/.gitignore
index d71a7a16b..cc23379f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
__pycache__
*.pyc
+*.swp
/build
/dist
/qutebrowser.egg-info
@@ -26,3 +27,4 @@ __pycache__
/.cache
/.testmondata
/.hypothesis
+TODO
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 0774fdfd7..c0946d154 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -44,7 +44,8 @@ import qutebrowser.resources # pylint: disable=unused-import
from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc
-from qutebrowser.browser import quickmarks, cookies, cache, adblock, history
+from qutebrowser.browser import (bookmarks, quickmarks, cookies, cache,
+ adblock, history)
from qutebrowser.browser.network import qutescheme, proxy, networkmanager
from qutebrowser.mainwindow import mainwindow
from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal
@@ -415,6 +416,9 @@ def _init_modules(args, crash_handler):
log.init.debug("Initializing quickmarks...")
quickmark_manager = quickmarks.QuickmarkManager(qApp)
objreg.register('quickmark-manager', quickmark_manager)
+ log.init.debug("Initializing bookmarks...")
+ bookmark_manager = bookmarks.BookmarkManager(qApp)
+ objreg.register('bookmark-manager', bookmark_manager)
log.init.debug("Initializing proxy...")
proxy.init()
log.init.debug("Initializing cookies...")
diff --git a/qutebrowser/browser/bookmarks.py b/qutebrowser/browser/bookmarks.py
new file mode 100644
index 000000000..966584c04
--- /dev/null
+++ b/qutebrowser/browser/bookmarks.py
@@ -0,0 +1,124 @@
+# 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/>.
+
+"""Manager for bookmarks.
+
+Note we violate our general QUrl rule by storing url strings in the bookmarks
+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 collections
+
+from PyQt5.QtCore import pyqtSignal, QUrl, QObject
+
+from qutebrowser.utils import message, standarddir, objreg
+from qutebrowser.misc import lineparser
+
+
+class BookmarkManager(QObject):
+
+ """Manager for bookmarks.
+
+ Attributes:
+ bookmarks: An OrderedDict of all bookmarks.
+ _lineparser: The LineParser used for the bookmarks, or None
+ (when qutebrowser is started with -c '').
+
+ Signals:
+ changed: Emitted when anything changed.
+ added: Emitted when a new bookmark was added.
+ arg 0: The title of the bookmark.
+ arg 1: The URL of the bookmark, as string.
+ removed: Emitted when an existing bookmark was removed.
+ arg 0: The title of the bookmark.
+ """
+
+ changed = pyqtSignal()
+ added = pyqtSignal(str, str)
+ removed = pyqtSignal(str)
+
+ def __init__(self, parent=None):
+ """Initialize and read bookmarks."""
+ super().__init__(parent)
+
+ self.bookmarks = collections.OrderedDict()
+
+ if standarddir.config() is None:
+ self._lineparser = None
+ else:
+ 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)
+ for line in self._lineparser:
+ if not line.strip():
+ # Ignore empty or whitespace-only lines.
+ continue
+
+ parts = line.split(maxsplit=1)
+ if len(parts) == 2:
+ self.bookmarks[parts[0]] = parts[1]
+ elif len(parts) == 1:
+ self.bookmarks[parts[0]] = ''
+
+ filename = os.path.join(standarddir.config(), 'bookmarks/urls')
+ objreg.get('save-manager').add_saveable(
+ 'bookmark-manager', self.save, self.changed,
+ filename=filename)
+
+ def save(self):
+ """Save the bookmarks to disk."""
+ if self._lineparser is not None:
+ self._lineparser.data = [' '.join(tpl)
+ for tpl in self.bookmarks.items()]
+ self._lineparser.save()
+
+ def add(self, win_id, url, title):
+ """Add a new bookmark.
+
+ Args:
+ win_id: The window ID to display the errors in.
+ url: The url to add as bookmark.
+ title: The title for the new bookmark.
+ """
+ urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
+
+ if urlstr in self.bookmarks:
+ message.error(win_id, "Bookmark already exists!")
+ else:
+ self.bookmarks[urlstr] = title
+ self.changed.emit()
+ self.added.emit(title, urlstr)
+ message.info(win_id, "Bookmark added")
+
+ def delete(self, url):
+ """Delete a bookmark.
+
+ Args:
+ url: The url of the bookmark to delete.
+ """
+ del self.bookmarks[url]
+ self.changed.emit()
+ self.removed.emit(url)
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 8cd1eaf6a..a14dd6d90 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -100,6 +100,10 @@ class CommandDispatcher:
msg += "!"
raise cmdexc.CommandError(msg)
+ def _current_title(self):
+ """Convenience method to get the current title."""
+ return self._current_widget().title()
+
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = self._tabbed_browser.currentWidget()
@@ -1053,6 +1057,27 @@ class CommandDispatcher:
url = objreg.get('quickmark-manager').get(name)
self._open(url, tab, bg, window)
+ @cmdutils.register(instance='command-dispatcher', scope='window')
+ def bookmark_add(self):
+ """Save the current page as a bookmark."""
+ bookmark_manager = objreg.get('bookmark-manager')
+ bookmark_manager.add(self._win_id, self._current_url(),
+ self._current_title())
+
+ @cmdutils.register(instance='command-dispatcher', scope='window',
+ maxsplit=0,
+ completion=[usertypes.Completion.bookmark_by_url])
+ def bookmark_load(self, url, tab=False, bg=False, window=False):
+ """Load a bookmark.
+
+ Args:
+ url: The url of the bookmark to load.
+ tab: Load the bookmark in a new tab.
+ bg: Load the bookmark in a new background tab.
+ window: Load the bookmark in a new window.
+ """
+ self._open(QUrl(url), tab, bg, window)
+
@cmdutils.register(instance='command-dispatcher', hide=True,
scope='window')
def follow_selected(self, tab=False):
diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
index 2d4165bbf..d1bc962f6 100644
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -22,7 +22,7 @@
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer
from qutebrowser.config import config
-from qutebrowser.commands import cmdutils, runners
+from qutebrowser.commands import cmdexc, cmdutils, runners
from qutebrowser.utils import usertypes, log, objreg, utils
from qutebrowser.completion.models import instances
@@ -481,3 +481,12 @@ class Completer(QObject):
"""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."""
+ try:
+ self.model().srcmodel.delete_cur_item(self._win_id)
+ except NotImplementedError:
+ raise cmdexc.CommandError("Cannot delete this item.")
diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py
index 3fe7e00ba..130454229 100644
--- a/qutebrowser/completion/completiondelegate.py
+++ b/qutebrowser/completion/completiondelegate.py
@@ -195,7 +195,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
if index.parent().isValid():
pattern = index.model().pattern
- if index.column() == 0 and pattern:
+ if(index.column() in index.model().srcmodel.columns_to_highlight
+ and pattern):
repl = r'<span class="highlight">\g<0></span>'
text = re.sub(re.escape(pattern), repl, self._opt.text,
flags=re.IGNORECASE)
diff --git a/qutebrowser/completion/models/base.py b/qutebrowser/completion/models/base.py
index b56444013..b7c5223d7 100644
--- a/qutebrowser/completion/models/base.py
+++ b/qutebrowser/completion/models/base.py
@@ -44,6 +44,7 @@ class BaseCompletionModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.setColumnCount(3)
+ self.columns_to_highlight = []
def new_category(self, name, sort=None):
"""Add a new category to the model.
@@ -95,6 +96,10 @@ class BaseCompletionModel(QStandardItemModel):
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.
@@ -121,3 +126,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
diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py
index 6d39fed7b..a44387a17 100644
--- a/qutebrowser/completion/models/configmodel.py
+++ b/qutebrowser/completion/models/configmodel.py
@@ -34,6 +34,7 @@ class SettingSectionCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
cat = self.new_category("Sections")
for name in configdata.DATA.keys():
desc = configdata.SECTION_DESC[name].splitlines()[0].strip()
@@ -53,6 +54,7 @@ class SettingOptionCompletionModel(base.BaseCompletionModel):
def __init__(self, section, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
cat = self.new_category(section)
sectdata = configdata.DATA[section]
self._misc_items = {}
@@ -106,6 +108,7 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
def __init__(self, section, option, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
self._section = section
self._option = option
objreg.get('config').changed.connect(self.update_current_value)
diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py
index e39ca1692..f8e89137c 100644
--- a/qutebrowser/completion/models/instances.py
+++ b/qutebrowser/completion/models/instances.py
@@ -108,6 +108,18 @@ def init_quickmark_completions():
@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 = _init_model(miscmodels.BookmarkCompletionModel, 'url')
+ _instances[usertypes.Completion.bookmark_by_url] = model
+
+
+@pyqtSlot()
def init_session_completion():
"""Initialize session completion model."""
log.completion.debug("Initializing session completion.")
@@ -128,6 +140,7 @@ INITIALIZERS = {
usertypes.Completion.value: _init_setting_completions,
usertypes.Completion.quickmark_by_url: init_quickmark_completions,
usertypes.Completion.quickmark_by_name: init_quickmark_completions,
+ usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion,
}
@@ -166,6 +179,10 @@ def init():
functools.partial(update, [usertypes.Completion.quickmark_by_url,
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]))
diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py
index f9af679ce..2ce9cd147 100644
--- a/qutebrowser/completion/models/miscmodels.py
+++ b/qutebrowser/completion/models/miscmodels.py
@@ -33,6 +33,7 @@ class CommandCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
@@ -56,6 +57,7 @@ class HelpCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
self._init_commands()
self._init_settings()
@@ -98,6 +100,7 @@ class QuickmarkCompletionModel(base.BaseCompletionModel):
def __init__(self, match_field='url', parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
cat = self.new_category("Quickmarks")
quickmarks = objreg.get('quickmark-manager').marks.items()
if match_field == 'url':
@@ -111,6 +114,28 @@ class QuickmarkCompletionModel(base.BaseCompletionModel):
match_field))
+class BookmarkCompletionModel(base.BaseCompletionModel):
+
+ """A CompletionModel filled with all bookmarks."""
+
+ # pylint: disable=abstract-method
+
+ def __init__(self, match_field='url', parent=None):
+ super().__init__(parent)
+ self.columns_to_highlight.append(0)
+ cat = self.new_category("Bookmarks")
+ bookmarks = objreg.get('bookmark-manager').bookmarks.items()
+ if match_field == 'url':
+ for bm_url, bm_title in bookmarks:
+ self.new_item(cat, bm_url, bm_title)
+ elif match_field == 'title':
+ for bm_url, bm_title in bookmarks:
+ self.new_item(cat, bm_title, bm_url)
+ else:
+ raise ValueError("Invalid value '{}' for match_field!".format(
+ match_field))
+
+
class SessionCompletionModel(base.BaseCompletionModel):
"""A CompletionModel filled with session names."""
@@ -119,6 +144,7 @@ class SessionCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
cat = self.new_category("Sessions")
try:
for name in objreg.get('session-manager').list_sessions():
diff --git a/qutebrowser/completion/models/sortfilter.py b/qutebrowser/completion/models/sortfilter.py
index 19802fa96..be9199153 100644
--- a/qutebrowser/completion/models/sortfilter.py
+++ b/qutebrowser/completion/models/sortfilter.py
@@ -137,12 +137,15 @@ class CompletionFilterModel(QSortFilterProxyModel):
# No entries in parent model
return False
data = self.srcmodel.data(idx)
- # TODO more sophisticated filtering
if not self.pattern:
return True
- if not data:
- return False
- return self.pattern.casefold() in data.casefold()
+
+ try:
+ return self.srcmodel.custom_filter(self.pattern, row, parent)
+ except NotImplementedError:
+ if not data:
+ return False
+ return self.pattern.casefold() in data.casefold()
def intelligentLessThan(self, lindex, rindex):
"""Custom sorting implementation.
diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py
index 1eb7e13d2..14d8b5252 100644
--- a/qutebrowser/completion/models/urlmodel.py
+++ b/qutebrowser/completion/models/urlmodel.py
@@ -23,14 +23,14 @@ import datetime
from PyQt5.QtCore import pyqtSlot, Qt
-from qutebrowser.utils import objreg, utils
+from qutebrowser.utils import message, objreg, utils
from qutebrowser.completion.models import base
from qutebrowser.config import config
class UrlCompletionModel(base.BaseCompletionModel):
- """A model which combines quickmarks and web history URLs.
+ """A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command."""
@@ -39,7 +39,11 @@ class UrlCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
+ self.columns_to_highlight.append(0)
+ self.columns_to_highlight.append(1)
+
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')
@@ -49,6 +53,13 @@ class UrlCompletionModel(base.BaseCompletionModel):
quickmark_manager.added.connect(self.on_quickmark_added)
quickmark_manager.removed.connect(self.on_quickmark_removed)
+ bookmark_manager = objreg.get('bookmark-manager')
+ bookmarks = bookmark_manager.bookmarks.items()
+ for bm_url, bm_title in bookmarks:
+ self._add_bookmark_entry(bm_title, bm_url)
+ bookmark_manager.added.connect(self.on_bookmark_added)
+ 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)
@@ -81,6 +92,24 @@ class UrlCompletionModel(base.BaseCompletionModel):
"""
self.new_item(self._quickmark_cat, url, name)
+ def _add_bookmark_entry(self, title, url):
+ """Add a new bookmark entry to the completion.
+
+ Args:
+ title: The title of the new bookmark.
+ url: The URL of the new bookmark.
+ """
+ self.new_item(self._bookmark_cat, url, title)
+
+ def custom_filter(self, pattern, row, parent):
+ """Filter by url and title."""
+ index0 = self.index(row, 0, parent)
+ index1 = self.index(row, 1, parent)
+ url = self.data(index0) or ''
+ title = self.data(index1) or ''
+ return (pattern.casefold() in url.casefold() or pattern.casefold() in
+ title.casefold())
+
@config.change_filter('completion', 'timestamp-format')
def reformat_timestamps(self):
"""Reformat the timestamps if the config option was changed."""
@@ -126,3 +155,50 @@ class UrlCompletionModel(base.BaseCompletionModel):
if name_item.data(Qt.DisplayRole) == name:
self._quickmark_cat.removeRow(i)
break
+
+ @pyqtSlot(str, str)
+ def on_bookmark_added(self, title, url):
+ """Called when a bookmark has been added by the user.
+
+ Args:
+ title: The title of the new bookmark.
+ url: The url of the new bookmark, as string.
+ """
+ self._add_bookmark_entry(title, url)
+
+ @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.
+ """
+ for i in range(self._bookmark_cat.rowCount()):
+ url_item = self._bookmark_cat.child(i, 0)
+ if url_item.data(Qt.DisplayRole) == url:
+ self._bookmark_cat.removeRow(i)
+ break
+
+ def delete_cur_item(self, win_id):
+ """Delete the selected item.
+
+ Args:
+ win_id: The current windows id.
+ """
+ completion = objreg.get('completion', scope='window',
+ window=win_id)
+ index = completion.currentIndex()
+ model = completion.model()
+ url = model.data(index)
+ category = index.parent()
+ if category.isValid():
+ if category.data() == 'Bookmarks':
+ bookmark_manager = objreg.get('bookmark-manager')
+ bookmark_manager.delete(url)
+ message.info(win_id, "Bookmarks deleted")
+ elif category.data() == 'Quickmarks':
+ quickmark_manager = objreg.get('quickmark-manager')
+ name = model.data(index.sibling(index.row(),
+ index.column() + 1))
+ quickmark_manager.quickmark_del(name)
+ message.info(win_id, "Quickmarks deleted")
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index 2a976be4f..c740b89b6 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -1274,6 +1274,7 @@ KEY_DATA = collections.OrderedDict([
('set-cmd-text -s :quickmark-load', ['b']),
('set-cmd-text -s :quickmark-load -t', ['B']),
('set-cmd-text -s :quickmark-load -w', ['wb']),
+ ('bookmark-add', ['M']),
('save', ['sf']),
('set-cmd-text -s :set', ['ss']),
('set-cmd-text -s :set -t', ['sl']),
@@ -1336,6 +1337,7 @@ KEY_DATA = collections.OrderedDict([
('command-history-next', ['<Ctrl-N>']),
('completion-item-prev', ['<Shift-Tab>', '<Up>']),
('completion-item-next', ['<Tab>', '<Down>']),
+ ('completion-item-del', ['<Ctrl-D>']),
('command-accept', RETURN_KEYS),
])),
diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py
index d1d6883a8..467e82633 100644
--- a/qutebrowser/utils/usertypes.py
+++ b/qutebrowser/utils/usertypes.py
@@ -237,7 +237,8 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
# Available command completions
Completion = enum('Completion', ['command', 'section', 'option', 'value',
'helptopic', 'quickmark_by_url',
- 'quickmark_by_name', 'url', 'sessions'])
+ 'quickmark_by_name', 'bookmark_by_url',
+ 'url', 'sessions'])
# Exit statuses for errors. Needs to be an int for sys.exit.