Implement completion models as functions.

First step of Completion Model/View revamping (#74). Rewrite the
completion models as functions that each return an instance of a
CompletionModel class.

Caching is removed from all models except the UrlModel. Models other
than the UrlModel can be generated very quickly so caching just adds
needless complexity and can lead to incorrect results if one forgets to
wire up a signal.
This commit is contained in:
Ryan Roden-Corrent 2016-09-16 15:41:54 -04:00
parent bef372e5f5
commit 08bb3f4f19
13 changed files with 431 additions and 814 deletions

View File

@ -72,20 +72,7 @@ class Completer(QObject):
Return:
A completion model or None.
"""
if completion == usertypes.Completion.option:
section = pos_args[0]
model = instances.get(completion).get(section)
elif completion == usertypes.Completion.value:
section = pos_args[0]
option = pos_args[1]
try:
model = instances.get(completion)[section][option]
except KeyError:
# No completion model for this section/option.
model = None
else:
model = instances.get(completion)
model = instances.get(completion)(*pos_args)
if model is None:
return None
else:
@ -109,7 +96,7 @@ class Completer(QObject):
log.completion.debug("After removing flags: {}".format(before_cursor))
if not before_cursor:
# '|' or 'set|'
model = instances.get(usertypes.Completion.command)
model = instances.get(usertypes.Completion.command)()
return sortfilter.CompletionFilterModel(source=model, parent=self)
try:
cmd = cmdutils.cmd_dict[before_cursor[0]]

View File

@ -28,7 +28,6 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
from qutebrowser.config import config, style
from qutebrowser.completion import completiondelegate
from qutebrowser.completion.models import base
from qutebrowser.utils import utils, usertypes, objreg
from qutebrowser.commands import cmdexc, cmdutils
@ -112,7 +111,7 @@ class CompletionView(QTreeView):
# objreg.get('config').changed.connect(self.init_command_completion)
objreg.get('config').changed.connect(self._on_config_changed)
self._column_widths = base.BaseCompletionModel.COLUMN_WIDTHS
self._column_widths = (30, 70, 0)
self._active = False
self._delegate = completiondelegate.CompletionItemDelegate(self)
@ -300,7 +299,7 @@ class CompletionView(QTreeView):
if pattern is not None:
model.set_pattern(pattern)
self._column_widths = model.srcmodel.COLUMN_WIDTHS
self._column_widths = model.srcmodel.column_widths
self._resize_columns()
self._maybe_update_geometry()

View File

@ -33,26 +33,27 @@ Role = usertypes.enum('Role', ['sort', 'userdata'], start=Qt.UserRole,
is_int=True)
class BaseCompletionModel(QStandardItemModel):
class CompletionModel(QStandardItemModel):
"""A simple QStandardItemModel adopted for completions.
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
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):
def __init__(self, dumb_sort=None, column_widths=(30, 70, 0),
columns_to_filter=None, delete_cur_item=None, parent=None):
super().__init__(parent)
self.setColumnCount(3)
self.columns_to_filter = [0]
self.columns_to_filter = columns_to_filter or [0]
self.dumb_sort = dumb_sort
self.column_widths = column_widths
self.delete_cur_item = delete_cur_item
def new_category(self, name, sort=None):
"""Add a new category to the model.
@ -103,10 +104,6 @@ class BaseCompletionModel(QStandardItemModel):
nameitem.setData(userdata, Role.userdata)
return nameitem, descitem, miscitem
def delete_cur_item(self, completion):
"""Delete the selected item."""
raise NotImplementedError
def flags(self, index):
"""Return the item flags for index.

View File

@ -17,142 +17,75 @@
# 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 the config."""
"""Functions that return config-related completion models."""
from PyQt5.QtCore import pyqtSlot, Qt
from qutebrowser.config import config, configdata
from qutebrowser.utils import log, qtutils, objreg
from qutebrowser.config import config, configdata, configexc
from qutebrowser.completion.models import base
class SettingSectionCompletionModel(base.BaseCompletionModel):
def section():
"""A CompletionModel filled with settings sections."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 70, 10)
def __init__(self, parent=None):
super().__init__(parent)
cat = self.new_category("Sections")
for name in configdata.DATA:
desc = configdata.SECTION_DESC[name].splitlines()[0].strip()
self.new_item(cat, name, desc)
model = base.CompletionModel(column_widths=(20, 70, 10))
cat = model.new_category("Sections")
for name in configdata.DATA:
desc = configdata.SECTION_DESC[name].splitlines()[0].strip()
model.new_item(cat, name, desc)
return model
class SettingOptionCompletionModel(base.BaseCompletionModel):
def option(sectname):
"""A CompletionModel filled with settings and their descriptions.
Attributes:
_misc_items: A dict of the misc. column items which will be set later.
_section: The config section this model shows.
Args:
sectname: The name of the config section this model shows.
"""
# https://github.com/qutebrowser/qutebrowser/issues/545
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 70, 10)
def __init__(self, section, parent=None):
super().__init__(parent)
cat = self.new_category(section)
sectdata = configdata.DATA[section]
self._misc_items = {}
self._section = section
objreg.get('config').changed.connect(self.update_misc_column)
for name in sectdata:
try:
desc = sectdata.descriptions[name]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
value = config.get(section, name, raw=True)
_valitem, _descitem, miscitem = self.new_item(cat, name, desc,
value)
self._misc_items[name] = miscitem
@pyqtSlot(str, str)
def update_misc_column(self, section, option):
"""Update misc column when config changed."""
if section != self._section:
return
model = base.CompletionModel(column_widths=(20, 70, 10))
cat = model.new_category(sectname)
try:
sectdata = configdata.DATA[sectname]
except KeyError:
return None
for name in sectdata:
try:
item = self._misc_items[option]
except KeyError:
log.completion.debug("Couldn't get item {}.{} from model!".format(
section, option))
# changed before init
return
val = config.get(section, option, raw=True)
idx = item.index()
qtutils.ensure_valid(idx)
ok = self.setData(idx, val, Qt.DisplayRole)
if not ok:
raise ValueError("Setting data failed! (section: {}, option: {}, "
"value: {})".format(section, option, val))
desc = sectdata.descriptions[name]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
val = config.get(sectname, name, raw=True)
model.new_item(cat, name, desc, val)
return model
class SettingValueCompletionModel(base.BaseCompletionModel):
def value(sectname, optname):
"""A CompletionModel filled with setting values.
Attributes:
_section: The config section this model shows.
_option: The config option this model shows.
Args:
sectname: The name of the config section this model shows.
optname: The name of the config option this model shows.
"""
# https://github.com/qutebrowser/qutebrowser/issues/545
# 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/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()
else:
if option is None:
raise ValueError("option may only be None for ValueList "
"sections, but {} is not!".format(section))
# Different type for each value (KeyValue)
vals = configdata.DATA[section][option].typ.complete()
if vals is not None:
cat = self.new_category("Completions", sort=1)
for (val, desc) in vals:
self.new_item(cat, val, desc)
@pyqtSlot(str, str)
def update_current_value(self, section, option):
"""Update current value when config changed."""
if (section, option) != (self._section, self._option):
return
value = config.get(section, option, raw=True)
if not value:
value = '""'
idx = self.cur_item.index()
qtutils.ensure_valid(idx)
ok = self.setData(idx, value, Qt.DisplayRole)
if not ok:
raise ValueError("Setting data failed! (section: {}, option: {}, "
"value: {})".format(section, option, value))
model = base.CompletionModel(column_widths=(20, 70, 10))
cur_cat = model.new_category("Current/Default", sort=0)
try:
val = config.get(sectname, optname, raw=True) or '""'
except (configexc.NoSectionError, configexc.NoOptionError):
return None
model.new_item(cur_cat, val, "Current value")
default_value = configdata.DATA[sectname][optname].default() or '""'
model.new_item(cur_cat, default_value, "Default value")
if hasattr(configdata.DATA[sectname], 'valtype'):
# Same type for all values (ValueList)
vals = configdata.DATA[sectname].valtype.complete()
else:
if optname is None:
raise ValueError("optname may only be None for ValueList "
"sections, but {} is not!".format(sectname))
# Different type for each value (KeyValue)
vals = configdata.DATA[sectname][optname].typ.complete()
if vals is not None:
cat = model.new_category("Completions", sort=1)
for (val, desc) in vals:
model.new_item(cat, val, desc)
return model

View File

@ -17,180 +17,37 @@
# 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: A dict of available completions.
INITIALIZERS: A {usertypes.Completion: callable} dict of functions to
initialize completions.
"""
import functools
"""Global instances of the completion models."""
from qutebrowser.utils import usertypes
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.utils import objreg, usertypes, log, debug
from qutebrowser.config import configdata, config
_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_tab_completion():
"""Initialize the tab completion model."""
log.completion.debug("Initializing tab completion.")
with debug.log_time(log.completion, 'tab completion init'):
model = miscmodels.TabCompletionModel()
_instances[usertypes.Completion.tab] = 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:
opt_model = configmodel.SettingOptionCompletionModel(sectname)
_instances[usertypes.Completion.option][sectname] = opt_model
_instances[usertypes.Completion.value][sectname] = {}
for opt in configdata.DATA[sectname]:
val_model = configmodel.SettingValueCompletionModel(sectname, opt)
_instances[usertypes.Completion.value][sectname][opt] = val_model
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
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
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
def _init_bind_completion():
"""Initialize the command completion model."""
log.completion.debug("Initializing bind completion.")
model = miscmodels.BindCompletionModel()
_instances[usertypes.Completion.bind] = model
INITIALIZERS = {
usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion,
usertypes.Completion.url: _init_url_completion,
usertypes.Completion.tab: _init_tab_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,
usertypes.Completion.bind: _init_bind_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)
@config.change_filter('aliases', function=True)
def _update_aliases():
"""Update completions that include command aliases."""
update([usertypes.Completion.command])
if completion == usertypes.Completion.command:
return miscmodels.command
if completion == usertypes.Completion.helptopic:
return miscmodels.helptopic
if completion == usertypes.Completion.tab:
return miscmodels.buffer
if completion == usertypes.Completion.quickmark_by_name:
return miscmodels.quickmark
if completion == usertypes.Completion.bookmark_by_url:
return miscmodels.bookmark
if completion == usertypes.Completion.sessions:
return miscmodels.session
if completion == usertypes.Completion.bind:
return miscmodels.bind
if completion == usertypes.Completion.section:
return configmodel.section
if completion == usertypes.Completion.option:
return configmodel.option
if completion == usertypes.Completion.value:
return configmodel.value
if completion == usertypes.Completion.url:
return urlmodel.url
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]))
keyconf = objreg.get('key-config')
keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.command]))
keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.bind]))
objreg.get('config').changed.connect(_update_aliases)
pass

View File

@ -17,252 +17,139 @@
# 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."""
"""Functions that return miscellaneous completion models."""
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
from PyQt5.QtCore import Qt
from qutebrowser.browser import browsertab
from qutebrowser.config import config, configdata
from qutebrowser.utils import objreg, log, qtutils
from qutebrowser.commands import cmdutils
from qutebrowser.completion.models import base
class CommandCompletionModel(base.BaseCompletionModel):
def command():
"""A CompletionModel filled with non-hidden commands and descriptions."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# pylint: disable=abstract-method
COLUMN_WIDTHS = (20, 60, 20)
def __init__(self, parent=None):
super().__init__(parent)
cmdlist = _get_cmd_completions(include_aliases=True,
include_hidden=False)
cat = self.new_category("Commands")
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
model = base.CompletionModel(column_widths=(20, 60, 20))
cmdlist = _get_cmd_completions(include_aliases=True, include_hidden=False)
cat = model.new_category("Commands")
for (name, desc, misc) in cmdlist:
model.new_item(cat, name, desc, misc)
return model
class HelpCompletionModel(base.BaseCompletionModel):
def helptopic():
"""A CompletionModel filled with help topics."""
model = base.CompletionModel()
# https://github.com/qutebrowser/qutebrowser/issues/545
# pylint: disable=abstract-method
cmdlist = _get_cmd_completions(include_aliases=False, include_hidden=True,
prefix=':')
cat = model.new_category("Commands")
for (name, desc, misc) in cmdlist:
model.new_item(cat, name, desc, misc)
COLUMN_WIDTHS = (20, 60, 20)
def __init__(self, parent=None):
super().__init__(parent)
self._init_commands()
self._init_settings()
def _init_commands(self):
"""Fill completion with :command entries."""
cmdlist = _get_cmd_completions(include_aliases=False,
include_hidden=True, prefix=':')
cat = self.new_category("Commands")
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
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:
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)
cat = model.new_category("Settings")
for sectname, sectdata in configdata.DATA.items():
for optname in sectdata:
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)
model.new_item(cat, name, desc)
return model
class QuickmarkCompletionModel(base.BaseCompletionModel):
def quickmark():
"""A CompletionModel filled with all quickmarks."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# 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)
model = base.CompletionModel()
cat = model.new_category("Quickmarks")
quickmarks = objreg.get('quickmark-manager').marks.items()
for qm_name, qm_url in quickmarks:
model.new_item(cat, qm_name, qm_url)
return model
class BookmarkCompletionModel(base.BaseCompletionModel):
def bookmark():
"""A CompletionModel filled with all bookmarks."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# 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)
model = base.CompletionModel()
cat = model.new_category("Bookmarks")
bookmarks = objreg.get('bookmark-manager').marks.items()
for bm_url, bm_title in bookmarks:
model.new_item(cat, bm_url, bm_title)
return model
class SessionCompletionModel(base.BaseCompletionModel):
def session():
"""A CompletionModel filled with session names."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# 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!")
model = base.CompletionModel()
cat = model.new_category("Sessions")
try:
for name in objreg.get('session-manager').list_sessions():
if not name.startswith('_'):
model.new_item(cat, name)
except OSError:
log.completion.exception("Failed to list sessions!")
return model
class TabCompletionModel(base.BaseCompletionModel):
def buffer():
"""A model to complete on open tabs across all windows.
Used for switching the buffer command.
"""
idx_column = 0
url_column = 1
text_column = 2
IDX_COLUMN = 0
URL_COLUMN = 1
TEXT_COLUMN = 2
COLUMN_WIDTHS = (6, 40, 54)
DUMB_SORT = Qt.DescendingOrder
def __init__(self, parent=None):
super().__init__(parent)
self.columns_to_filter = [self.IDX_COLUMN, self.URL_COLUMN,
self.TEXT_COLUMN]
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
for i in range(tabbed_browser.count()):
tab = tabbed_browser.widget(i)
tab.url_changed.connect(self.rebuild)
tab.title_changed.connect(self.rebuild)
tab.shutting_down.connect(self.delayed_rebuild)
tabbed_browser.new_tab.connect(self.on_new_tab)
tabbed_browser.tabBar().tabMoved.connect(self.rebuild)
objreg.get("app").new_window.connect(self.on_new_window)
self.rebuild()
def on_new_window(self, window):
"""Add hooks to new windows."""
window.tabbed_browser.new_tab.connect(self.on_new_tab)
@pyqtSlot(browsertab.AbstractTab)
def on_new_tab(self, tab):
"""Add hooks to new tabs."""
tab.url_changed.connect(self.rebuild)
tab.title_changed.connect(self.rebuild)
tab.shutting_down.connect(self.delayed_rebuild)
self.rebuild()
@pyqtSlot()
def delayed_rebuild(self):
"""Fire a rebuild indirectly so widgets get a chance to update."""
QTimer.singleShot(0, self.rebuild)
@pyqtSlot()
def rebuild(self):
"""Rebuild completion model from current tabs.
Very lazy method of keeping the model up to date. We could connect to
signals for new tab, tab url/title changed, tab close, tab moved and
make sure we handled background loads too ... but iterating over a
few/few dozen/few hundred tabs doesn't take very long at all.
"""
window_count = 0
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if not tabbed_browser.shutting_down:
window_count += 1
if window_count < self.rowCount():
self.removeRows(window_count, self.rowCount() - window_count)
for i, win_id in enumerate(objreg.window_registry):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
continue
if i >= self.rowCount():
c = self.new_category("{}".format(win_id))
else:
c = self.item(i, 0)
c.setData("{}".format(win_id), Qt.DisplayRole)
if tabbed_browser.count() < c.rowCount():
c.removeRows(tabbed_browser.count(),
c.rowCount() - tabbed_browser.count())
for idx in range(tabbed_browser.count()):
tab = tabbed_browser.widget(idx)
if idx >= c.rowCount():
self.new_item(c, "{}/{}".format(win_id, idx + 1),
tab.url().toDisplayString(),
tabbed_browser.page_title(idx))
else:
c.child(idx, 0).setData("{}/{}".format(win_id, idx + 1),
Qt.DisplayRole)
c.child(idx, 1).setData(tab.url().toDisplayString(),
Qt.DisplayRole)
c.child(idx, 2).setData(tabbed_browser.page_title(idx),
Qt.DisplayRole)
def delete_cur_item(self, completion):
"""Delete the selected item.
Args:
completion: The Completion object to use.
"""
def delete_buffer(completion):
"""Close the selected tab."""
index = completion.currentIndex()
qtutils.ensure_valid(index)
category = index.parent()
qtutils.ensure_valid(category)
index = category.child(index.row(), self.IDX_COLUMN)
index = category.child(index.row(), idx_column)
win_id, tab_index = index.data().split('/')
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=int(win_id))
tabbed_browser.on_tab_close_requested(int(tab_index) - 1)
model = base.CompletionModel(
column_widths=(6, 40, 54),
dumb_sort=Qt.DescendingOrder,
delete_cur_item=delete_buffer,
columns_to_filter=[idx_column, url_column, text_column])
class BindCompletionModel(base.BaseCompletionModel):
for win_id in objreg.window_registry:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
continue
c = model.new_category("{}".format(win_id))
for idx in range(tabbed_browser.count()):
tab = tabbed_browser.widget(idx)
model.new_item(c, "{}/{}".format(win_id, idx + 1),
tab.url().toDisplayString(),
tabbed_browser.page_title(idx))
return model
"""A CompletionModel filled with all bindable commands and descriptions."""
# https://github.com/qutebrowser/qutebrowser/issues/545
# pylint: disable=abstract-method
def bind(_):
"""A CompletionModel filled with all bindable commands and descriptions.
COLUMN_WIDTHS = (20, 60, 20)
def __init__(self, parent=None):
super().__init__(parent)
cmdlist = _get_cmd_completions(include_hidden=True,
include_aliases=True)
cat = self.new_category("Commands")
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
Args:
_: the key being bound.
"""
# TODO: offer a 'Current binding' completion based on the key.
model = base.CompletionModel(column_widths=(20, 60, 20))
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
cat = model.new_category("Commands")
for (name, desc, misc) in cmdlist:
model.new_item(cat, name, desc, misc)
return model
def _get_cmd_completions(include_hidden, include_aliases, prefix=''):

View File

@ -50,7 +50,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.pattern = ''
self.pattern_re = None
dumb_sort = self.srcmodel.DUMB_SORT
dumb_sort = self.srcmodel.dumb_sort
if dumb_sort is None:
# pylint: disable=invalid-name
self.lessThan = self.intelligentLessThan

View File

@ -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/>.
"""CompletionModels for URLs."""
"""Function to return the url completion model for the `open` command."""
import datetime
@ -27,166 +27,172 @@ from qutebrowser.utils import objreg, utils, qtutils, log
from qutebrowser.completion.models import base
from qutebrowser.config import config
_URL_COLUMN = 0
_TEXT_COLUMN = 1
_TIME_COLUMN = 2
_model = None
_history_cat = None
_quickmark_cat = None
_bookmark_cat = None
class UrlCompletionModel(base.BaseCompletionModel):
"""A model which combines bookmarks, quickmarks and web history URLs.
def _delete_url(completion):
index = completion.currentIndex()
qtutils.ensure_valid(index)
category = index.parent()
index = category.child(index.row(), _URL_COLUMN)
qtutils.ensure_valid(category)
if category.data() == 'Bookmarks':
bookmark_manager = objreg.get('bookmark-manager')
bookmark_manager.delete(index.data())
elif category.data() == 'Quickmarks':
quickmark_manager = objreg.get('quickmark-manager')
sibling = index.sibling(index.row(), _TEXT_COLUMN)
qtutils.ensure_valid(sibling)
name = sibling.data()
quickmark_manager.delete(name)
def _remove_oldest_history():
"""Remove the oldest history entry."""
_history_cat.removeRow(0)
def _add_history_entry(entry):
"""Add a new history entry to the completion."""
_model.new_item(_history_cat, entry.url.toDisplayString(),
entry.title, _fmt_atime(entry.atime),
sort=int(entry.atime), userdata=entry.url)
max_history = config.get('completion', 'web-history-max-items')
if max_history != -1 and _history_cat.rowCount() > max_history:
_remove_oldest_history()
@config.change_filter('completion', 'timestamp-format')
def _reformat_timestamps():
"""Reformat the timestamps if the config option was changed."""
for i in range(_history_cat.rowCount()):
url_item = _history_cat.child(i, _URL_COLUMN)
atime_item = _history_cat.child(i, _TIME_COLUMN)
atime = url_item.data(base.Role.sort)
atime_item.setText(_fmt_atime(atime))
@pyqtSlot(object)
def _on_history_item_added(entry):
"""Slot called when a new history item was added."""
for i in range(_history_cat.rowCount()):
url_item = _history_cat.child(i, _URL_COLUMN)
atime_item = _history_cat.child(i, _TIME_COLUMN)
title_item = _history_cat.child(i, _TEXT_COLUMN)
if url_item.data(base.Role.userdata) == entry.url:
atime_item.setText(_fmt_atime(entry.atime))
title_item.setText(entry.title)
url_item.setData(int(entry.atime), base.Role.sort)
break
else:
_add_history_entry(entry)
@pyqtSlot()
def _on_history_cleared():
_history_cat.removeRows(0, _history_cat.rowCount())
def _remove_item(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(name):
"""Called when a quickmark has been removed by the user.
Args:
name: The name of the quickmark which has been removed.
"""
_remove_item(name, _quickmark_cat, _TEXT_COLUMN)
@pyqtSlot(str)
def _on_bookmark_removed(urltext):
"""Called when a bookmark has been removed by the user.
Args:
urltext: The url of the bookmark which has been removed.
"""
_remove_item(urltext, _bookmark_cat, _URL_COLUMN)
def _fmt_atime(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 _init():
global _model, _quickmark_cat, _bookmark_cat, _history_cat
_model = base.CompletionModel(column_widths=(40, 50, 10),
dumb_sort=Qt.DescendingOrder,
delete_cur_item=_delete_url,
columns_to_filter=[_URL_COLUMN,
_TEXT_COLUMN])
_quickmark_cat = _model.new_category("Quickmarks")
_bookmark_cat = _model.new_category("Bookmarks")
_history_cat = _model.new_category("History")
quickmark_manager = objreg.get('quickmark-manager')
quickmarks = quickmark_manager.marks.items()
for qm_name, qm_url in quickmarks:
_model.new_item(_quickmark_cat, qm_url, qm_name)
quickmark_manager.added.connect(
lambda name, url: _model.new_item(_quickmark_cat, url, name))
quickmark_manager.removed.connect(_on_quickmark_removed)
bookmark_manager = objreg.get('bookmark-manager')
bookmarks = bookmark_manager.marks.items()
for bm_url, bm_title in bookmarks:
_model.new_item(_bookmark_cat, bm_url, bm_title)
bookmark_manager.added.connect(
lambda name, url: _model.new_item(_bookmark_cat, url, name))
bookmark_manager.removed.connect(_on_bookmark_removed)
history = objreg.get('web-history')
max_history = config.get('completion', 'web-history-max-items')
for entry in utils.newest_slice(history, max_history):
if not entry.redirect:
_add_history_entry(entry)
history.add_completion_item.connect(_on_history_item_added)
history.cleared.connect(_on_history_cleared)
objreg.get('config').changed.connect(_reformat_timestamps)
def url():
"""A _model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command.
"""
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')
self._max_history = config.get('completion', 'web-history-max-items')
history = utils.newest_slice(self._history, self._max_history)
for entry in history:
if not entry.redirect:
self._add_history_entry(entry)
self._history.add_completion_item.connect(self.on_history_item_added)
self._history.cleared.connect(self.on_history_cleared)
objreg.get('config').changed.connect(self.reformat_timestamps)
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 _remove_oldest_history(self):
"""Remove the oldest history entry."""
self._history_cat.removeRow(0)
def _add_history_entry(self, entry):
"""Add a new history entry to the completion."""
self.new_item(self._history_cat, entry.url.toDisplayString(),
entry.title,
self._fmt_atime(entry.atime), sort=int(entry.atime),
userdata=entry.url)
if (self._max_history != -1 and
self._history_cat.rowCount() > self._max_history):
self._remove_oldest_history()
@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)
title_item = self._history_cat.child(i, self.TEXT_COLUMN)
url = url_item.data(base.Role.userdata)
if url == entry.url:
atime_item.setText(self._fmt_atime(entry.atime))
title_item.setText(entry.title)
url_item.setData(int(entry.atime), base.Role.sort)
break
else:
self._add_history_entry(entry)
@pyqtSlot()
def on_history_cleared(self):
self._history_cat.removeRows(0, self._history_cat.rowCount())
def _remove_item(self, data, category, column):
"""Helper function for on_quickmark_removed and on_bookmark_removed.
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)
category = index.parent()
index = category.child(index.row(), self.URL_COLUMN)
url = index.data()
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.delete(name)
if not _model:
_init()
return _model

View File

@ -1,50 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2017 Alexander Cogneau <alexander.cogneau@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/>.
"""Tests for qutebrowser.completion.models column widths."""
import pytest
from qutebrowser.completion.models.base import BaseCompletionModel
from qutebrowser.completion.models.configmodel import (
SettingOptionCompletionModel, SettingSectionCompletionModel,
SettingValueCompletionModel)
from qutebrowser.completion.models.miscmodels import (
CommandCompletionModel, HelpCompletionModel, QuickmarkCompletionModel,
BookmarkCompletionModel, SessionCompletionModel)
from qutebrowser.completion.models.urlmodel import UrlCompletionModel
CLASSES = [BaseCompletionModel, SettingOptionCompletionModel,
SettingOptionCompletionModel, SettingSectionCompletionModel,
SettingValueCompletionModel, CommandCompletionModel,
HelpCompletionModel, QuickmarkCompletionModel,
BookmarkCompletionModel, SessionCompletionModel, UrlCompletionModel]
@pytest.mark.parametrize("model", CLASSES)
def test_list_size(model):
"""Test if there are 3 items in the COLUMN_WIDTHS property."""
assert len(model.COLUMN_WIDTHS) == 3
@pytest.mark.parametrize("model", CLASSES)
def test_column_width_sum(model):
"""Test if the sum of the widths asserts to 100."""
assert sum(model.COLUMN_WIDTHS) == 100

View File

@ -34,11 +34,11 @@ class FakeCompletionModel(QStandardItemModel):
"""Stub for a completion model."""
DUMB_SORT = None
def __init__(self, kind, parent=None):
def __init__(self, kind, *pos_args, parent=None):
super().__init__(parent)
self.kind = kind
self.pos_args = [*pos_args]
self.dumb_sort = None
class CompletionWidgetStub(QObject):
@ -74,17 +74,8 @@ def instances(monkeypatch):
"""Mock the instances module so get returns a fake completion model."""
# populate a model for each completion type, with a nested structure for
# option and value completion
instances = {kind: FakeCompletionModel(kind)
for kind in usertypes.Completion}
instances[usertypes.Completion.option] = {
'general': FakeCompletionModel(usertypes.Completion.option),
}
instances[usertypes.Completion.value] = {
'general': {
'editor': FakeCompletionModel(usertypes.Completion.value),
}
}
monkeypatch.setattr(completer, 'instances', instances)
get = lambda kind: lambda *args: FakeCompletionModel(kind, *args)
monkeypatch.setattr(completer, 'instances', get)
@pytest.fixture(autouse=True)
@ -140,47 +131,52 @@ def _set_cmd_prompt(cmd, txt):
cmd.setCursorPosition(txt.index('|'))
@pytest.mark.parametrize('txt, kind, pattern', [
(':nope|', usertypes.Completion.command, 'nope'),
(':nope |', None, ''),
(':set |', usertypes.Completion.section, ''),
(':set gen|', usertypes.Completion.section, 'gen'),
(':set general |', usertypes.Completion.option, ''),
(':set what |', None, ''),
(':set general editor |', usertypes.Completion.value, ''),
(':set general editor gv|', usertypes.Completion.value, 'gv'),
(':set general editor "gvim -f"|', usertypes.Completion.value, 'gvim -f'),
(':set general editor "gvim |', usertypes.Completion.value, 'gvim'),
(':set general huh |', None, ''),
(':help |', usertypes.Completion.helptopic, ''),
(':help |', usertypes.Completion.helptopic, ''),
(':open |', usertypes.Completion.url, ''),
(':bind |', None, ''),
(':bind <c-x> |', usertypes.Completion.command, ''),
(':bind <c-x> foo|', usertypes.Completion.command, 'foo'),
(':bind <c-x>| foo', None, '<c-x>'),
(':set| general ', usertypes.Completion.command, 'set'),
(':|set general ', usertypes.Completion.command, 'set'),
(':set gene|ral ignore-case', usertypes.Completion.section, 'general'),
(':|', usertypes.Completion.command, ''),
(': |', usertypes.Completion.command, ''),
('/|', None, ''),
(':open -t|', None, ''),
(':open --tab|', None, ''),
(':open -t |', usertypes.Completion.url, ''),
(':open --tab |', usertypes.Completion.url, ''),
(':open | -t', usertypes.Completion.url, ''),
(':tab-detach |', None, ''),
(':bind --mode=caret <c-x> |', usertypes.Completion.command, ''),
@pytest.mark.parametrize('txt, kind, pattern, pos_args', [
(':nope|', usertypes.Completion.command, 'nope', []),
(':nope |', None, '', []),
(':set |', usertypes.Completion.section, '', []),
(':set gen|', usertypes.Completion.section, 'gen', []),
(':set general |', usertypes.Completion.option, '', ['general']),
(':set what |', usertypes.Completion.option, '', ['what']),
(':set general editor |', usertypes.Completion.value, '',
['general', 'editor']),
(':set general editor gv|', usertypes.Completion.value, 'gv',
['general', 'editor']),
(':set general editor "gvim -f"|', usertypes.Completion.value, 'gvim -f',
['general', 'editor']),
(':set general editor "gvim |', usertypes.Completion.value, 'gvim',
['general', 'editor']),
(':set general huh |', usertypes.Completion.value, '', ['general', 'huh']),
(':help |', usertypes.Completion.helptopic, '', []),
(':help |', usertypes.Completion.helptopic, '', []),
(':open |', usertypes.Completion.url, '', []),
(':bind |', None, '', []),
(':bind <c-x> |', usertypes.Completion.command, '', ['<c-x>']),
(':bind <c-x> foo|', usertypes.Completion.command, 'foo', ['<c-x>']),
(':bind <c-x>| foo', None, '<c-x>', []),
(':set| general ', usertypes.Completion.command, 'set', []),
(':|set general ', usertypes.Completion.command, 'set', []),
(':set gene|ral ignore-case', usertypes.Completion.section, 'general', []),
(':|', usertypes.Completion.command, '', []),
(': |', usertypes.Completion.command, '', []),
('/|', None, '', []),
(':open -t|', None, '', []),
(':open --tab|', None, '', []),
(':open -t |', usertypes.Completion.url, '', []),
(':open --tab |', usertypes.Completion.url, '', []),
(':open | -t', usertypes.Completion.url, '', []),
(':tab-detach |', None, '', []),
(':bind --mode=caret <c-x> |', usertypes.Completion.command, '',
['<c-x>']),
pytest.param(':bind --mode caret <c-x> |', usertypes.Completion.command,
'', marks=pytest.mark.xfail(reason='issue #74')),
(':set -t -p |', usertypes.Completion.section, ''),
(':open -- |', None, ''),
(':gibberish nonesense |', None, ''),
('/:help|', None, ''),
'', [], marks=pytest.mark.xfail(reason='issue #74')),
(':set -t -p |', usertypes.Completion.section, '', []),
(':open -- |', None, '', []),
(':gibberish nonesense |', None, '', []),
('/:help|', None, '', []),
('::bind|', usertypes.Completion.command, ':bind'),
])
def test_update_completion(txt, kind, pattern, status_command_stub,
def test_update_completion(txt, kind, pattern, pos_args, status_command_stub,
completer_obj, completion_widget_stub):
"""Test setting the completion widget's model based on command text."""
# this test uses | as a placeholder for the current cursor position
@ -192,7 +188,9 @@ def test_update_completion(txt, kind, pattern, status_command_stub,
if kind is None:
assert args[0] is None
else:
assert args[0].srcmodel.kind == kind
model = args[0].srcmodel
assert model.kind == kind
assert model.pos_args == pos_args
assert args[1] == pattern

View File

@ -71,7 +71,7 @@ def completionview(qtbot, status_command_stub, config_stub, win_registry,
def test_set_model(completionview):
"""Ensure set_model actually sets the model and expands all categories."""
model = base.BaseCompletionModel()
model = base.CompletionModel()
filtermodel = sortfilter.CompletionFilterModel(model)
for i in range(3):
model.appendRow(QStandardItem(str(i)))
@ -82,7 +82,7 @@ def test_set_model(completionview):
def test_set_pattern(completionview):
model = sortfilter.CompletionFilterModel(base.BaseCompletionModel())
model = sortfilter.CompletionFilterModel(base.CompletionModel())
model.set_pattern = unittest.mock.Mock()
completionview.set_model(model, 'foo')
model.set_pattern.assert_called_with('foo')
@ -148,7 +148,7 @@ def test_completion_item_focus(which, tree, expected, completionview, qtbot):
successive movement. None implies no signal should be
emitted.
"""
model = base.BaseCompletionModel()
model = base.CompletionModel()
for catdata in tree:
cat = QStandardItem()
model.appendRow(cat)
@ -176,7 +176,7 @@ def test_completion_item_focus_no_model(which, completionview, qtbot):
"""
with qtbot.assertNotEmitted(completionview.selection_changed):
completionview.completion_item_focus(which)
model = base.BaseCompletionModel()
model = base.CompletionModel()
filtermodel = sortfilter.CompletionFilterModel(model,
parent=completionview)
completionview.set_model(filtermodel)
@ -200,7 +200,7 @@ def test_completion_show(show, rows, quick_complete, completionview,
config_stub.data['completion']['show'] = show
config_stub.data['completion']['quick-complete'] = quick_complete
model = base.BaseCompletionModel()
model = base.CompletionModel()
for name in rows:
cat = QStandardItem()
model.appendRow(cat)

View File

@ -56,6 +56,9 @@ def _check_completions(model, expected):
misc = actual_cat.child(j, 2)
actual_item = (name.text(), desc.text(), misc.text())
assert actual_item in expected_cat
# sanity-check the column_widths
assert len(model.column_widths) == 3
assert sum(model.column_widths) == 100
def _patch_cmdutils(monkeypatch, stubs, symbol):
@ -184,7 +187,7 @@ def test_command_completion(qtmodeltester, monkeypatch, stubs, config_stub,
key_config_stub.set_bindings_for('normal', {'s': 'stop',
'rr': 'roll',
'ro': 'rock'})
model = miscmodels.CommandCompletionModel()
model = miscmodels.command()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -212,7 +215,7 @@ def test_help_completion(qtmodeltester, monkeypatch, stubs, key_config_stub):
key_config_stub.set_bindings_for('normal', {'s': 'stop', 'rr': 'roll'})
_patch_cmdutils(monkeypatch, stubs, module + '.cmdutils')
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
model = miscmodels.HelpCompletionModel()
model = miscmodels.helptopic()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -236,7 +239,7 @@ def test_help_completion(qtmodeltester, monkeypatch, stubs, key_config_stub):
def test_quickmark_completion(qtmodeltester, quickmarks):
"""Test the results of quickmark completion."""
model = miscmodels.QuickmarkCompletionModel()
model = miscmodels.quickmark()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -251,7 +254,7 @@ def test_quickmark_completion(qtmodeltester, quickmarks):
def test_bookmark_completion(qtmodeltester, bookmarks):
"""Test the results of bookmark completion."""
model = miscmodels.BookmarkCompletionModel()
model = miscmodels.bookmark()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -275,7 +278,7 @@ def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks,
"""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d',
'web-history-max-items': 2}
model = urlmodel.UrlCompletionModel()
model = urlmodel.url()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -303,7 +306,7 @@ def test_url_completion_delete_bookmark(qtmodeltester, config_stub,
"""Test deleting a bookmark from the url completion model."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d',
'web-history-max-items': 2}
model = urlmodel.UrlCompletionModel()
model = urlmodel.url()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -321,7 +324,7 @@ def test_url_completion_delete_quickmark(qtmodeltester, config_stub,
"""Test deleting a bookmark from the url completion model."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d',
'web-history-max-items': 2}
model = urlmodel.UrlCompletionModel()
model = urlmodel.url()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -335,7 +338,7 @@ def test_url_completion_delete_quickmark(qtmodeltester, config_stub,
def test_session_completion(qtmodeltester, session_manager_stub):
session_manager_stub.sessions = ['default', '1', '2']
model = miscmodels.SessionCompletionModel()
model = miscmodels.session()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -354,7 +357,7 @@ def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry,
tabbed_browser_stubs[1].tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
model = miscmodels.TabCompletionModel()
model = miscmodels.buffer()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -381,7 +384,7 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, qtbot, app_stub,
tabbed_browser_stubs[1].tabs = [
fake_web_tab(QUrl('https://wiki.archlinux.org'), 'ArchWiki', 0),
]
model = miscmodels.TabCompletionModel()
model = miscmodels.buffer()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -398,7 +401,7 @@ def test_setting_section_completion(qtmodeltester, monkeypatch, stubs):
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
_patch_config_section_desc(monkeypatch, stubs,
module + '.configdata.SECTION_DESC')
model = configmodel.SettingSectionCompletionModel()
model = configmodel.section()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -418,7 +421,7 @@ def test_setting_option_completion(qtmodeltester, monkeypatch, stubs,
config_stub.data = {'ui': {'gesture': 'off',
'mind': 'on',
'voice': 'sometimes'}}
model = configmodel.SettingOptionCompletionModel('ui')
model = configmodel.option('ui')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -440,7 +443,7 @@ def test_setting_option_completion_valuelist(qtmodeltester, monkeypatch, stubs,
'DEFAULT': 'https://duckduckgo.com/?q={}'
}
}
model = configmodel.SettingOptionCompletionModel('searchengines')
model = configmodel.option('searchengines')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -454,7 +457,7 @@ def test_setting_value_completion(qtmodeltester, monkeypatch, stubs,
module = 'qutebrowser.completion.models.configmodel'
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
config_stub.data = {'general': {'volume': '0'}}
model = configmodel.SettingValueCompletionModel('general', 'volume')
model = configmodel.value('general', 'volume')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -486,7 +489,7 @@ def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
key_config_stub.set_bindings_for('normal', {'s': 'stop',
'rr': 'roll',
'ro': 'rock'})
model = miscmodels.BindCompletionModel()
model = miscmodels.bind('s')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)

View File

@ -33,7 +33,7 @@ def _create_model(data):
tuple in the sub-list represents an item, and each value in the
tuple represents the item data for that column
"""
model = base.BaseCompletionModel()
model = base.CompletionModel()
for catdata in data:
cat = model.new_category('')
for itemdata in catdata:
@ -74,7 +74,7 @@ def _extract_model_data(model):
('4', 'blah', False),
])
def test_filter_accepts_row(pattern, data, expected):
source_model = base.BaseCompletionModel()
source_model = base.CompletionModel()
cat = source_model.new_category('test')
source_model.new_item(cat, data)
@ -119,8 +119,8 @@ def test_first_last_item(tree, first, last):
def test_set_source_model():
"""Ensure setSourceModel sets source_model and clears the pattern."""
model1 = base.BaseCompletionModel()
model2 = base.BaseCompletionModel()
model1 = base.CompletionModel()
model2 = base.CompletionModel()
filter_model = sortfilter.CompletionFilterModel(model1)
filter_model.set_pattern('foo')
# sourceModel() is cached as srcmodel, so make sure both match
@ -202,7 +202,7 @@ def test_count(tree, expected):
def test_set_pattern(pattern, dumb_sort, filter_cols, before, after):
"""Validate the filtering and sorting results of set_pattern."""
model = _create_model(before)
model.DUMB_SORT = dumb_sort
model.dumb_sort = dumb_sort
model.columns_to_filter = filter_cols
filter_model = sortfilter.CompletionFilterModel(model)
filter_model.set_pattern(pattern)