diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 96d937829..21af74da5 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -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]] diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 490fcd6c0..f12027340 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -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() diff --git a/qutebrowser/completion/models/base.py b/qutebrowser/completion/models/base.py index b1cad276a..43f3a1b48 100644 --- a/qutebrowser/completion/models/base.py +++ b/qutebrowser/completion/models/base.py @@ -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. diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index c9e9850d1..5ed2a47d0 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -17,142 +17,75 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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 diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py index f7eaaca86..ce109ae7a 100644 --- a/qutebrowser/completion/models/instances.py +++ b/qutebrowser/completion/models/instances.py @@ -17,180 +17,37 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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 diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 5ab381c43..bcbb94177 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -17,252 +17,139 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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=''): diff --git a/qutebrowser/completion/models/sortfilter.py b/qutebrowser/completion/models/sortfilter.py index e2db88b9e..92dd1b2a0 100644 --- a/qutebrowser/completion/models/sortfilter.py +++ b/qutebrowser/completion/models/sortfilter.py @@ -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 diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 98f68c08c..d8b7f42a2 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""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 diff --git a/tests/unit/completion/test_column_widths.py b/tests/unit/completion/test_column_widths.py deleted file mode 100644 index 21456ed37..000000000 --- a/tests/unit/completion/test_column_widths.py +++ /dev/null @@ -1,50 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2015-2017 Alexander Cogneau -# -# 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 . - -"""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 diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index d18e6c125..937107f75 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -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 |', usertypes.Completion.command, ''), - (':bind foo|', usertypes.Completion.command, 'foo'), - (':bind | foo', None, ''), - (':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 |', 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 |', usertypes.Completion.command, '', ['']), + (':bind foo|', usertypes.Completion.command, 'foo', ['']), + (':bind | foo', None, '', []), + (':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 |', usertypes.Completion.command, '', + ['']), pytest.param(':bind --mode caret |', 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 diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 9a8de3cad..88f4aed20 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -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) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index ff00a11a9..9870bb4db 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -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) diff --git a/tests/unit/completion/test_sortfilter.py b/tests/unit/completion/test_sortfilter.py index 2d4a4e25d..c972a5ec9 100644 --- a/tests/unit/completion/test_sortfilter.py +++ b/tests/unit/completion/test_sortfilter.py @@ -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)