# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2016 Florian Bruhin (The Compiler) # # 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 . """Misc. CompletionModels.""" from collections import defaultdict from PyQt5.QtCore import Qt, QTimer, pyqtSlot from qutebrowser.browser.webkit import webview from qutebrowser.config import config, configdata from qutebrowser.utils import objreg, log, qtutils, utils from qutebrowser.commands import cmdutils from qutebrowser.completion.models import base class CommandCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all commands and descriptions.""" # https://github.com/The-Compiler/qutebrowser/issues/545 # pylint: disable=abstract-method COLUMN_WIDTHS = (20, 60, 20) def __init__(self, parent=None): super().__init__(parent) assert cmdutils.cmd_dict cmdlist = [] for obj in set(cmdutils.cmd_dict.values()): if (obj.hide or (obj.debug and not objreg.get('args').debug) or obj.deprecated): pass else: cmdlist.append((obj.name, obj.desc)) for name, cmd in config.section('aliases').items(): cmdlist.append((name, "Alias for '{}'".format(cmd))) cat = self.new_category("Commands") # map each command to its bound keys and show these in the misc column keyconf = objreg.get('key-config') cmd_to_keys = defaultdict(list) for key, cmd in keyconf.get_bindings_for('normal').items(): # put special bindings last if utils.is_special_key(key): cmd_to_keys[cmd].append(key) else: cmd_to_keys[cmd].insert(0, key) for (name, desc) in sorted(cmdlist): self.new_item(cat, name, desc, ', '.join(cmd_to_keys[name])) class HelpCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with help topics.""" # https://github.com/The-Compiler/qutebrowser/issues/545 # pylint: disable=abstract-method def __init__(self, parent=None): super().__init__(parent) self._init_commands() self._init_settings() def _init_commands(self): """Fill completion with :command entries.""" assert cmdutils.cmd_dict cmdlist = [] for obj in set(cmdutils.cmd_dict.values()): if (obj.hide or (obj.debug and not objreg.get('args').debug) or obj.deprecated): pass else: cmdlist.append((':' + obj.name, obj.desc)) cat = self.new_category("Commands") for (name, desc) in sorted(cmdlist): self.new_item(cat, name, desc) def _init_settings(self): """Fill completion with section->option entries.""" cat = self.new_category("Settings") for sectname, sectdata in configdata.DATA.items(): for optname in sectdata: try: desc = sectdata.descriptions[optname] except (KeyError, AttributeError): # Some stuff (especially ValueList items) don't have a # description. desc = "" else: desc = desc.splitlines()[0] name = '{}->{}'.format(sectname, optname) self.new_item(cat, name, desc) class QuickmarkCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all quickmarks.""" # https://github.com/The-Compiler/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) class BookmarkCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with all bookmarks.""" # https://github.com/The-Compiler/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) class SessionCompletionModel(base.BaseCompletionModel): """A CompletionModel filled with session names.""" # https://github.com/The-Compiler/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!") class TabCompletionModel(base.BaseCompletionModel): """A model to complete on open tabs across all windows. Used for switching the buffer command. """ # https://github.com/The-Compiler/qutebrowser/issues/545 # pylint: disable=abstract-method 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.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_text_changed.connect(self.rebuild) tab.shutting_down.connect(self.delayed_rebuild) tabbed_browser.new_tab.connect(self.on_new_tab) 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(webview.WebView) def on_new_tab(self, tab): """Add hooks to new tabs.""" tab.url_text_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. """ index = completion.currentIndex() qtutils.ensure_valid(index) category = index.parent() qtutils.ensure_valid(category) index = category.child(index.row(), self.IDX_COLUMN) win_id, tab_index = index.data().split('/') tabbed_browser = objreg.get('tabbed-browser', scope='window', window=int(win_id)) tabbed_browser.on_tab_close_requested(int(tab_index) - 1)