Merge branch 'histcomplete'
This commit is contained in:
commit
94bc10405a
@ -136,6 +136,7 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* ZDarian
|
||||
* Peter Vilim
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Jimmy
|
||||
* rikn00
|
||||
* Patric Schmitz
|
||||
* Martin Zimmermann
|
||||
|
@ -60,9 +60,11 @@
|
||||
|==============
|
||||
|Setting|Description
|
||||
|<<completion-download-path-suggestion,download-path-suggestion>>|What to display in the download filename input.
|
||||
|<<completion-timestamp-format,timestamp-format>>|How to format timestamps (e.g. for history)
|
||||
|<<completion-show,show>>|Whether to show the autocompletion window.
|
||||
|<<completion-height,height>>|The height of the completion, in px or as percentage of the window.
|
||||
|<<completion-history-length,history-length>>|How many commands to save in the history.
|
||||
|<<completion-cmd-history-max-items,cmd-history-max-items>>|How many commands to save in the command history.
|
||||
|<<completion-web-history-max-items,web-history-max-items>>|How many URLs to show in the web history.
|
||||
|<<completion-quick-complete,quick-complete>>|Whether to move on to the next part when there's only one possible completion left.
|
||||
|<<completion-shrink,shrink>>|Whether to shrink the completion to be smaller than the configured size if there are no scrollbars.
|
||||
|==============
|
||||
@ -616,6 +618,12 @@ Valid values:
|
||||
|
||||
Default: +pass:[path]+
|
||||
|
||||
[[completion-timestamp-format]]
|
||||
=== timestamp-format
|
||||
How to format timestamps (e.g. for history)
|
||||
|
||||
Default: +pass:[%Y-%m-%d]+
|
||||
|
||||
[[completion-show]]
|
||||
=== show
|
||||
Whether to show the autocompletion window.
|
||||
@ -633,14 +641,22 @@ The height of the completion, in px or as percentage of the window.
|
||||
|
||||
Default: +pass:[50%]+
|
||||
|
||||
[[completion-history-length]]
|
||||
=== history-length
|
||||
How many commands to save in the history.
|
||||
[[completion-cmd-history-max-items]]
|
||||
=== cmd-history-max-items
|
||||
How many commands to save in the command history.
|
||||
|
||||
0: no history / -1: unlimited
|
||||
|
||||
Default: +pass:[100]+
|
||||
|
||||
[[completion-web-history-max-items]]
|
||||
=== web-history-max-items
|
||||
How many URLs to show in the web history.
|
||||
|
||||
0: no history / -1: unlimited
|
||||
|
||||
Default: +pass:[1000]+
|
||||
|
||||
[[completion-quick-complete]]
|
||||
=== quick-complete
|
||||
Whether to move on to the next part when there's only one possible completion left.
|
||||
|
@ -38,6 +38,7 @@ from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources # pylint: disable=unused-import
|
||||
from qutebrowser.completion.models import instances as completionmodels
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
from qutebrowser.browser import quickmarks, cookies, cache, adblock, history
|
||||
@ -165,6 +166,7 @@ class Application(QApplication):
|
||||
|
||||
def _init_modules(self):
|
||||
"""Initialize all 'modules' which need to be initialized."""
|
||||
# pylint: disable=too-many-statements
|
||||
log.init.debug("Initializing save manager...")
|
||||
save_manager = savemanager.SaveManager(self)
|
||||
objreg.register('save-manager', save_manager)
|
||||
@ -207,6 +209,8 @@ class Application(QApplication):
|
||||
log.init.debug("Initializing cache...")
|
||||
diskcache = cache.DiskCache(self)
|
||||
objreg.register('cache', diskcache)
|
||||
log.init.debug("Initializing completions...")
|
||||
completionmodels.init()
|
||||
if not session_manager.exists(self._args.session):
|
||||
log.init.debug("Initializing main window...")
|
||||
window = mainwindow.MainWindow()
|
||||
|
@ -280,7 +280,7 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='open',
|
||||
maxsplit=0, scope='window',
|
||||
completion=[usertypes.Completion.quickmark_by_url])
|
||||
completion=[usertypes.Completion.url])
|
||||
def openurl(self, url=None, bg=False, tab=False, window=False,
|
||||
count: {'special': 'count'}=None):
|
||||
"""Open a URL in the current/[count]th tab.
|
||||
|
@ -20,8 +20,9 @@
|
||||
"""Simple history which gets written to disk."""
|
||||
|
||||
import time
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl
|
||||
from PyQt5.QtWebKit import QWebHistoryInterface
|
||||
|
||||
from qutebrowser.utils import utils, objreg, standarddir
|
||||
@ -35,19 +36,21 @@ class HistoryEntry:
|
||||
|
||||
Attributes:
|
||||
atime: The time the page was accessed.
|
||||
url: The URL which was accessed as string
|
||||
url: The URL which was accessed as QUrl.
|
||||
url_string: The URL which was accessed as string.
|
||||
"""
|
||||
|
||||
def __init__(self, atime, url):
|
||||
self.atime = atime
|
||||
self.url = url
|
||||
self.atime = float(atime)
|
||||
self.url = QUrl(url)
|
||||
self.url_string = url
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, constructor=True, atime=self.atime,
|
||||
url=self.url)
|
||||
url=self.url.toDisplayString())
|
||||
|
||||
def __str__(self):
|
||||
return '{} {}'.format(int(self.atime), self.url)
|
||||
return '{} {}'.format(int(self.atime), self.url_string)
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, s):
|
||||
@ -61,41 +64,56 @@ class WebHistory(QWebHistoryInterface):
|
||||
|
||||
Attributes:
|
||||
_lineparser: The AppendLineParser used to save the history.
|
||||
_old_urls: A set of URLs read from the on-disk history.
|
||||
_history_dict: An OrderedDict of URLs read from the on-disk history.
|
||||
_new_history: A list of HistoryEntry items of the current session.
|
||||
_saved_count: How many HistoryEntries have been written to disk.
|
||||
_old_hit: How many times an URL was found in _old_urls.
|
||||
_old_miss: How many times an URL was not found in _old_urls.
|
||||
|
||||
Signals:
|
||||
item_about_to_be_added: Emitted before a new HistoryEntry is added.
|
||||
arg: The new HistoryEntry.
|
||||
item_added: Emitted after a new HistoryEntry is added.
|
||||
arg: The new HistoryEntry.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
item_about_to_be_added = pyqtSignal(HistoryEntry)
|
||||
item_added = pyqtSignal(HistoryEntry)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._lineparser = lineparser.AppendLineParser(
|
||||
standarddir.data(), 'history', parent=self)
|
||||
self._old_urls = set()
|
||||
self._history_dict = collections.OrderedDict()
|
||||
with self._lineparser.open():
|
||||
for line in self._lineparser:
|
||||
data = line.strip().split(maxsplit=1)
|
||||
data = line.rstrip().split(maxsplit=1)
|
||||
if not data:
|
||||
# empty line
|
||||
continue
|
||||
_time, url = data
|
||||
self._old_urls.add(url)
|
||||
atime, url = data
|
||||
# This de-duplicates history entries; only the latest
|
||||
# entry for each URL is kept. If you want to keep
|
||||
# information about previous hits change the items in
|
||||
# old_urls to be lists or change HistoryEntry to have a
|
||||
# list of atimes.
|
||||
self._history_dict[url] = HistoryEntry(atime, url)
|
||||
self._history_dict.move_to_end(url)
|
||||
self._new_history = []
|
||||
self._saved_count = 0
|
||||
self._old_hit = 0
|
||||
self._old_miss = 0
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'history', self.save, self.changed)
|
||||
'history', self.save, self.item_added)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, new_length=len(self._new_history))
|
||||
return utils.get_repr(self, length=len(self))
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._new_history[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._history_dict.values())
|
||||
|
||||
def __len__(self):
|
||||
return len(self._history_dict)
|
||||
|
||||
def get_recent(self):
|
||||
"""Get the most recent history entries."""
|
||||
old = self._lineparser.get_recent()
|
||||
@ -116,8 +134,11 @@ class WebHistory(QWebHistoryInterface):
|
||||
"""
|
||||
if not config.get('general', 'private-browsing'):
|
||||
entry = HistoryEntry(time.time(), url_string)
|
||||
self.item_about_to_be_added.emit(entry)
|
||||
self._new_history.append(entry)
|
||||
self.changed.emit()
|
||||
self._history_dict[url_string] = entry
|
||||
self._history_dict.move_to_end(url_string)
|
||||
self.item_added.emit(entry)
|
||||
|
||||
def historyContains(self, url_string):
|
||||
"""Called by WebKit to determine if an URL is contained in the history.
|
||||
@ -128,12 +149,7 @@ class WebHistory(QWebHistoryInterface):
|
||||
Return:
|
||||
True if the url is in the history, False otherwise.
|
||||
"""
|
||||
if url_string in self._old_urls:
|
||||
self._old_hit += 1
|
||||
return True
|
||||
else:
|
||||
self._old_miss += 1
|
||||
return url_string in (entry.url for entry in self._new_history)
|
||||
return url_string in self._history_dict
|
||||
|
||||
|
||||
def init():
|
||||
|
@ -21,12 +21,10 @@
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
from qutebrowser.completion.models import completion as models
|
||||
from qutebrowser.completion.models.sortfilter import (
|
||||
CompletionFilterModel as CFM)
|
||||
from qutebrowser.completion.models import instances
|
||||
|
||||
|
||||
class Completer(QObject):
|
||||
@ -34,7 +32,6 @@ class Completer(QObject):
|
||||
"""Completer which manages completions in a CompletionView.
|
||||
|
||||
Attributes:
|
||||
models: dict of available completion models.
|
||||
_cmd: The statusbar Command object this completer belongs to.
|
||||
_ignore_change: Whether to ignore the next completion update.
|
||||
_win_id: The window ID this completer is in.
|
||||
@ -53,15 +50,6 @@ class Completer(QObject):
|
||||
self._cmd.textEdited.connect(self.on_text_edited)
|
||||
self._ignore_change = False
|
||||
self._empty_item_idx = None
|
||||
|
||||
self._models = {
|
||||
usertypes.Completion.option: {},
|
||||
usertypes.Completion.value: {},
|
||||
}
|
||||
self._init_static_completions()
|
||||
self._init_setting_completions()
|
||||
self.init_quickmark_completions()
|
||||
self.init_session_completion()
|
||||
self._timer = QTimer()
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
@ -79,52 +67,6 @@ class Completer(QObject):
|
||||
window=self._win_id)
|
||||
return completion.model()
|
||||
|
||||
def _init_static_completions(self):
|
||||
"""Initialize the static completion models."""
|
||||
self._models[usertypes.Completion.command] = CFM(
|
||||
models.CommandCompletionModel(self), self)
|
||||
self._models[usertypes.Completion.helptopic] = CFM(
|
||||
models.HelpCompletionModel(self), self)
|
||||
|
||||
def _init_setting_completions(self):
|
||||
"""Initialize setting completion models."""
|
||||
self._models[usertypes.Completion.section] = CFM(
|
||||
models.SettingSectionCompletionModel(self), self)
|
||||
self._models[usertypes.Completion.option] = {}
|
||||
self._models[usertypes.Completion.value] = {}
|
||||
for sectname in configdata.DATA:
|
||||
model = models.SettingOptionCompletionModel(sectname, self)
|
||||
self._models[usertypes.Completion.option][sectname] = CFM(
|
||||
model, self)
|
||||
self._models[usertypes.Completion.value][sectname] = {}
|
||||
for opt in configdata.DATA[sectname].keys():
|
||||
model = models.SettingValueCompletionModel(sectname, opt, self)
|
||||
self._models[usertypes.Completion.value][sectname][opt] = CFM(
|
||||
model, self)
|
||||
|
||||
@pyqtSlot()
|
||||
def init_quickmark_completions(self):
|
||||
"""Initialize quickmark completion models."""
|
||||
try:
|
||||
self._models[usertypes.Completion.quickmark_by_url].deleteLater()
|
||||
self._models[usertypes.Completion.quickmark_by_name].deleteLater()
|
||||
except KeyError:
|
||||
pass
|
||||
self._models[usertypes.Completion.quickmark_by_url] = CFM(
|
||||
models.QuickmarkCompletionModel('url', self), self)
|
||||
self._models[usertypes.Completion.quickmark_by_name] = CFM(
|
||||
models.QuickmarkCompletionModel('name', self), self)
|
||||
|
||||
@pyqtSlot()
|
||||
def init_session_completion(self):
|
||||
"""Initialize session completion model."""
|
||||
try:
|
||||
self._models[usertypes.Completion.sessions].deleteLater()
|
||||
except KeyError:
|
||||
pass
|
||||
self._models[usertypes.Completion.sessions] = CFM(
|
||||
models.SessionCompletionModel(self), self)
|
||||
|
||||
def _get_completion_model(self, completion, parts, cursor_part):
|
||||
"""Get a completion model based on an enum member.
|
||||
|
||||
@ -138,17 +80,17 @@ class Completer(QObject):
|
||||
"""
|
||||
if completion == usertypes.Completion.option:
|
||||
section = parts[cursor_part - 1]
|
||||
model = self._models[completion].get(section)
|
||||
model = instances.get(completion).get(section)
|
||||
elif completion == usertypes.Completion.value:
|
||||
section = parts[cursor_part - 2]
|
||||
option = parts[cursor_part - 1]
|
||||
try:
|
||||
model = self._models[completion][section][option]
|
||||
model = instances.get(completion)[section][option]
|
||||
except KeyError:
|
||||
# No completion model for this section/option.
|
||||
model = None
|
||||
else:
|
||||
model = self._models.get(completion)
|
||||
model = instances.get(completion)
|
||||
return model
|
||||
|
||||
def _filter_cmdline_parts(self, parts, cursor_part):
|
||||
@ -198,7 +140,7 @@ class Completer(QObject):
|
||||
"{}".format(parts, cursor_part))
|
||||
if cursor_part == 0:
|
||||
# '|' or 'set|'
|
||||
return self._models[usertypes.Completion.command]
|
||||
return instances.get(usertypes.Completion.command)
|
||||
# delegate completion to command
|
||||
try:
|
||||
completions = cmdutils.cmd_dict[parts[0]].completion
|
||||
|
@ -195,7 +195,8 @@ class CompletionView(QTreeView):
|
||||
self.setModel(model)
|
||||
if sel_model is not None:
|
||||
sel_model.deleteLater()
|
||||
self.expandAll()
|
||||
for i in range(model.rowCount()):
|
||||
self.expand(model.index(i, 0))
|
||||
self._resize_columns()
|
||||
model.rowsRemoved.connect(self.maybe_resize_completion)
|
||||
model.rowsInserted.connect(self.maybe_resize_completion)
|
||||
|
@ -29,7 +29,8 @@ from PyQt5.QtGui import QStandardItemModel, QStandardItem
|
||||
from qutebrowser.utils import usertypes, qtutils
|
||||
|
||||
|
||||
Role = usertypes.enum('Role', ['sort'], start=Qt.UserRole, is_int=True)
|
||||
Role = usertypes.enum('Role', ['sort', 'userdata'], start=Qt.UserRole,
|
||||
is_int=True)
|
||||
|
||||
|
||||
class BaseCompletionModel(QStandardItemModel):
|
||||
@ -60,7 +61,8 @@ class BaseCompletionModel(QStandardItemModel):
|
||||
self.appendRow(cat)
|
||||
return cat
|
||||
|
||||
def new_item(self, cat, name, desc='', misc=None, sort=None):
|
||||
def new_item(self, cat, name, desc='', misc=None, sort=None,
|
||||
userdata=None):
|
||||
"""Add a new item to a category.
|
||||
|
||||
Args:
|
||||
@ -69,10 +71,14 @@ class BaseCompletionModel(QStandardItemModel):
|
||||
desc: The description of the item.
|
||||
misc: Misc text to display.
|
||||
sort: Data for the sort role (int).
|
||||
userdata: User data to be added for the first column.
|
||||
|
||||
Return:
|
||||
A (nameitem, descitem, miscitem) tuple.
|
||||
"""
|
||||
assert not isinstance(name, int)
|
||||
assert not isinstance(desc, int)
|
||||
assert not isinstance(misc, int)
|
||||
nameitem = QStandardItem(name)
|
||||
descitem = QStandardItem(desc)
|
||||
if misc is None:
|
||||
@ -85,6 +91,8 @@ class BaseCompletionModel(QStandardItemModel):
|
||||
cat.setChild(idx, 2, miscitem)
|
||||
if sort is not None:
|
||||
nameitem.setData(sort, Role.sort)
|
||||
if userdata is not None:
|
||||
nameitem.setData(userdata, Role.userdata)
|
||||
return nameitem, descitem, miscitem
|
||||
|
||||
def flags(self, index):
|
||||
|
@ -17,13 +17,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""CompletionModels for different usages."""
|
||||
"""CompletionModels for the config."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.utils import log, qtutils, objreg
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.completion.models import base
|
||||
|
||||
|
||||
@ -148,104 +147,3 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
|
||||
if not ok:
|
||||
raise ValueError("Setting data failed! (section: {}, option: {}, "
|
||||
"value: {})".format(section, option, value))
|
||||
|
||||
|
||||
class CommandCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all commands and descriptions."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
|
||||
obj.deprecated):
|
||||
pass
|
||||
else:
|
||||
cmdlist.append((obj.name, obj.desc))
|
||||
for name, cmd in config.section('aliases').items():
|
||||
cmdlist.append((name, "Alias for '{}'".format(cmd)))
|
||||
cat = self.new_category("Commands")
|
||||
for (name, desc) in sorted(cmdlist):
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
|
||||
class HelpCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with help topics."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._init_commands()
|
||||
self._init_settings()
|
||||
|
||||
def _init_commands(self):
|
||||
"""Fill completion with :command entries."""
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
|
||||
obj.deprecated):
|
||||
pass
|
||||
else:
|
||||
cmdlist.append((':' + obj.name, obj.desc))
|
||||
cat = self.new_category("Commands")
|
||||
for (name, desc) in sorted(cmdlist):
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
def _init_settings(self):
|
||||
"""Fill completion with section->option entries."""
|
||||
cat = self.new_category("Settings")
|
||||
for sectname, sectdata in configdata.DATA.items():
|
||||
for optname in sectdata.keys():
|
||||
try:
|
||||
desc = sectdata.descriptions[optname]
|
||||
except (KeyError, AttributeError):
|
||||
# Some stuff (especially ValueList items) don't have a
|
||||
# description.
|
||||
desc = ""
|
||||
else:
|
||||
desc = desc.splitlines()[0]
|
||||
name = '{}->{}'.format(sectname, optname)
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
|
||||
class QuickmarkCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all quickmarks."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, match_field='url', parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
cat = self.new_category("Quickmarks")
|
||||
quickmarks = objreg.get('quickmark-manager').marks.items()
|
||||
|
||||
if match_field == 'url':
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(cat, qm_url, qm_name)
|
||||
elif match_field == 'name':
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(cat, qm_name, qm_url)
|
||||
else:
|
||||
raise ValueError("Invalid value '{}' for match_field!".format(
|
||||
match_field))
|
||||
|
||||
|
||||
class SessionCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with session names."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
cat = self.new_category("Sessions")
|
||||
for name in objreg.get('session-manager').list_sessions():
|
||||
self.new_item(cat, name)
|
155
qutebrowser/completion/models/instances.py
Normal file
155
qutebrowser/completion/models/instances.py
Normal file
@ -0,0 +1,155 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Global instances of the completion models.
|
||||
|
||||
Module attributes:
|
||||
_instances: An dict of available completions.
|
||||
INITIALIZERS: A {usertypes.Completion: callable} dict of functions to
|
||||
initialize completions.
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
|
||||
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
|
||||
from qutebrowser.completion.models.sortfilter import (
|
||||
CompletionFilterModel as CFM)
|
||||
from qutebrowser.utils import objreg, usertypes, log, debug
|
||||
from qutebrowser.config import configdata
|
||||
|
||||
|
||||
_instances = {}
|
||||
|
||||
|
||||
def _init_command_completion():
|
||||
"""Initialize the command completion model."""
|
||||
log.completion.debug("Initializing command completion.")
|
||||
_instances[usertypes.Completion.command] = CFM(
|
||||
miscmodels.CommandCompletionModel())
|
||||
|
||||
|
||||
def _init_helptopic_completion():
|
||||
"""Initialize the helptopic completion model."""
|
||||
log.completion.debug("Initializing helptopic completion.")
|
||||
_instances[usertypes.Completion.helptopic] = CFM(
|
||||
miscmodels.HelpCompletionModel())
|
||||
|
||||
|
||||
def _init_url_completion():
|
||||
"""Initialize the URL completion model."""
|
||||
log.completion.debug("Initializing URL completion.")
|
||||
with debug.log_time(log.completion, 'URL completion init'):
|
||||
_instances[usertypes.Completion.url] = CFM(
|
||||
urlmodel.UrlCompletionModel(), dumb_sort=Qt.DescendingOrder)
|
||||
|
||||
|
||||
def _init_setting_completions():
|
||||
"""Initialize setting completion models."""
|
||||
log.completion.debug("Initializing setting completion.")
|
||||
_instances[usertypes.Completion.section] = CFM(
|
||||
configmodel.SettingSectionCompletionModel())
|
||||
_instances[usertypes.Completion.option] = {}
|
||||
_instances[usertypes.Completion.value] = {}
|
||||
for sectname in configdata.DATA:
|
||||
model = configmodel.SettingOptionCompletionModel(sectname)
|
||||
_instances[usertypes.Completion.option][sectname] = CFM(model)
|
||||
_instances[usertypes.Completion.value][sectname] = {}
|
||||
for opt in configdata.DATA[sectname].keys():
|
||||
model = configmodel.SettingValueCompletionModel(sectname, opt)
|
||||
_instances[usertypes.Completion.value][sectname][opt] = CFM(model)
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def init_quickmark_completions():
|
||||
"""Initialize quickmark completion models."""
|
||||
log.completion.debug("Initializing quickmark completion.")
|
||||
try:
|
||||
_instances[usertypes.Completion.quickmark_by_url].deleteLater()
|
||||
_instances[usertypes.Completion.quickmark_by_name].deleteLater()
|
||||
except KeyError:
|
||||
pass
|
||||
_instances[usertypes.Completion.quickmark_by_url] = CFM(
|
||||
miscmodels.QuickmarkCompletionModel('url'))
|
||||
_instances[usertypes.Completion.quickmark_by_name] = CFM(
|
||||
miscmodels.QuickmarkCompletionModel('name'))
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def init_session_completion():
|
||||
"""Initialize session completion model."""
|
||||
log.completion.debug("Initializing session completion.")
|
||||
try:
|
||||
_instances[usertypes.Completion.sessions].deleteLater()
|
||||
except KeyError:
|
||||
pass
|
||||
_instances[usertypes.Completion.sessions] = CFM(
|
||||
miscmodels.SessionCompletionModel())
|
||||
|
||||
|
||||
INITIALIZERS = {
|
||||
usertypes.Completion.command: _init_command_completion,
|
||||
usertypes.Completion.helptopic: _init_helptopic_completion,
|
||||
usertypes.Completion.url: _init_url_completion,
|
||||
usertypes.Completion.section: _init_setting_completions,
|
||||
usertypes.Completion.option: _init_setting_completions,
|
||||
usertypes.Completion.value: _init_setting_completions,
|
||||
usertypes.Completion.quickmark_by_url: init_quickmark_completions,
|
||||
usertypes.Completion.quickmark_by_name: init_quickmark_completions,
|
||||
usertypes.Completion.sessions: init_session_completion,
|
||||
}
|
||||
|
||||
|
||||
def get(completion):
|
||||
"""Get a certain completion. Initializes the completion if needed."""
|
||||
try:
|
||||
return _instances[completion]
|
||||
except KeyError:
|
||||
if completion in INITIALIZERS:
|
||||
INITIALIZERS[completion]()
|
||||
return _instances[completion]
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def update(completions):
|
||||
"""Update an already existing completion.
|
||||
|
||||
Args:
|
||||
completions: An iterable of usertypes.Completions.
|
||||
"""
|
||||
did_run = []
|
||||
for completion in completions:
|
||||
if completion in _instances:
|
||||
func = INITIALIZERS[completion]
|
||||
if func not in did_run:
|
||||
func()
|
||||
did_run.append(func)
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize completions. Note this only connects signals."""
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
quickmark_manager.changed.connect(
|
||||
functools.partial(update, [usertypes.Completion.quickmark_by_url,
|
||||
usertypes.Completion.quickmark_by_name]))
|
||||
session_manager = objreg.get('session-manager')
|
||||
session_manager.update_completion.connect(
|
||||
functools.partial(update, [usertypes.Completion.sessions]))
|
124
qutebrowser/completion/models/miscmodels.py
Normal file
124
qutebrowser/completion/models/miscmodels.py
Normal file
@ -0,0 +1,124 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Misc. CompletionModels."""
|
||||
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.utils import objreg
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.completion.models import base
|
||||
|
||||
|
||||
class CommandCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all commands and descriptions."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
|
||||
obj.deprecated):
|
||||
pass
|
||||
else:
|
||||
cmdlist.append((obj.name, obj.desc))
|
||||
for name, cmd in config.section('aliases').items():
|
||||
cmdlist.append((name, "Alias for '{}'".format(cmd)))
|
||||
cat = self.new_category("Commands")
|
||||
for (name, desc) in sorted(cmdlist):
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
|
||||
class HelpCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with help topics."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._init_commands()
|
||||
self._init_settings()
|
||||
|
||||
def _init_commands(self):
|
||||
"""Fill completion with :command entries."""
|
||||
assert cmdutils.cmd_dict
|
||||
cmdlist = []
|
||||
for obj in set(cmdutils.cmd_dict.values()):
|
||||
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
|
||||
obj.deprecated):
|
||||
pass
|
||||
else:
|
||||
cmdlist.append((':' + obj.name, obj.desc))
|
||||
cat = self.new_category("Commands")
|
||||
for (name, desc) in sorted(cmdlist):
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
def _init_settings(self):
|
||||
"""Fill completion with section->option entries."""
|
||||
cat = self.new_category("Settings")
|
||||
for sectname, sectdata in configdata.DATA.items():
|
||||
for optname in sectdata.keys():
|
||||
try:
|
||||
desc = sectdata.descriptions[optname]
|
||||
except (KeyError, AttributeError):
|
||||
# Some stuff (especially ValueList items) don't have a
|
||||
# description.
|
||||
desc = ""
|
||||
else:
|
||||
desc = desc.splitlines()[0]
|
||||
name = '{}->{}'.format(sectname, optname)
|
||||
self.new_item(cat, name, desc)
|
||||
|
||||
|
||||
class QuickmarkCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with all quickmarks."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, match_field='url', parent=None):
|
||||
super().__init__(parent)
|
||||
cat = self.new_category("Quickmarks")
|
||||
quickmarks = objreg.get('quickmark-manager').marks.items()
|
||||
if match_field == 'url':
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(cat, qm_url, qm_name)
|
||||
elif match_field == 'name':
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(cat, qm_name, qm_url)
|
||||
else:
|
||||
raise ValueError("Invalid value '{}' for match_field!".format(
|
||||
match_field))
|
||||
|
||||
|
||||
class SessionCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A CompletionModel filled with session names."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
cat = self.new_category("Sessions")
|
||||
for name in objreg.get('session-manager').list_sessions():
|
||||
self.new_item(cat, name)
|
@ -25,7 +25,7 @@ Contains:
|
||||
|
||||
from PyQt5.QtCore import QSortFilterProxyModel, QModelIndex, Qt
|
||||
|
||||
from qutebrowser.utils import log, qtutils
|
||||
from qutebrowser.utils import log, qtutils, debug
|
||||
from qutebrowser.completion.models import base as completion
|
||||
|
||||
|
||||
@ -65,14 +65,15 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
||||
Args:
|
||||
val: The value to set.
|
||||
"""
|
||||
self.pattern = val
|
||||
self.invalidateFilter()
|
||||
sortcol = 0
|
||||
try:
|
||||
self.srcmodel.sort(sortcol)
|
||||
except NotImplementedError:
|
||||
self.sort(sortcol)
|
||||
self.invalidate()
|
||||
with debug.log_time(log.completion, 'Setting filter pattern'):
|
||||
self.pattern = val
|
||||
self.invalidateFilter()
|
||||
sortcol = 0
|
||||
try:
|
||||
self.srcmodel.sort(sortcol)
|
||||
except NotImplementedError:
|
||||
self.sort(sortcol)
|
||||
self.invalidate()
|
||||
|
||||
def count(self):
|
||||
"""Get the count of non-toplevel items currently visible.
|
||||
@ -132,11 +133,15 @@ class CompletionFilterModel(QSortFilterProxyModel):
|
||||
if parent == QModelIndex():
|
||||
return True
|
||||
idx = self.srcmodel.index(row, 0, parent)
|
||||
qtutils.ensure_valid(idx)
|
||||
if not idx.isValid():
|
||||
# No entries in parent model
|
||||
return False
|
||||
data = self.srcmodel.data(idx)
|
||||
# TODO more sophisticated filtering
|
||||
if not self.pattern:
|
||||
return True
|
||||
if not data:
|
||||
return False
|
||||
return self.pattern.casefold() in data.casefold()
|
||||
|
||||
def intelligentLessThan(self, lindex, rindex):
|
||||
|
95
qutebrowser/completion/models/urlmodel.py
Normal file
95
qutebrowser/completion/models/urlmodel.py
Normal file
@ -0,0 +1,95 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""CompletionModels for URLs."""
|
||||
|
||||
import datetime
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
|
||||
from qutebrowser.utils import objreg, utils
|
||||
from qutebrowser.completion.models import base
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
class UrlCompletionModel(base.BaseCompletionModel):
|
||||
|
||||
"""A model which combines quickmarks and web history URLs.
|
||||
|
||||
Used for the `open` command."""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self._quickmark_cat = self.new_category("Quickmarks")
|
||||
self._history_cat = self.new_category("History")
|
||||
|
||||
quickmarks = objreg.get('quickmark-manager').marks.items()
|
||||
self._history = objreg.get('web-history')
|
||||
|
||||
for qm_name, qm_url in quickmarks:
|
||||
self.new_item(self._quickmark_cat, qm_url, qm_name)
|
||||
|
||||
max_history = config.get('completion', 'web-history-max-items')
|
||||
history = utils.newest_slice(self._history, max_history)
|
||||
|
||||
for entry in history:
|
||||
self._add_history_entry(entry)
|
||||
|
||||
self._history.item_about_to_be_added.connect(
|
||||
self.on_history_item_added)
|
||||
objreg.get('config').changed.connect(self.reformat_timestamps)
|
||||
|
||||
def _fmt_atime(self, atime):
|
||||
"""Format an atime to a human-readable string."""
|
||||
fmt = config.get('completion', 'timestamp-format')
|
||||
if fmt is None:
|
||||
return ''
|
||||
return datetime.datetime.fromtimestamp(atime).strftime(fmt)
|
||||
|
||||
def _add_history_entry(self, entry):
|
||||
"""Add a new history entry to the completion."""
|
||||
self.new_item(self._history_cat, entry.url.toDisplayString(), "",
|
||||
self._fmt_atime(entry.atime), sort=int(entry.atime),
|
||||
userdata=entry.url)
|
||||
|
||||
@config.change_filter('completion', 'timestamp-format')
|
||||
def reformat_timestamps(self):
|
||||
"""Reformat the timestamps if the config option was changed."""
|
||||
for i in range(self._history_cat.rowCount()):
|
||||
name_item = self._history_cat.child(i, 0)
|
||||
atime_item = self._history_cat.child(i, 2)
|
||||
atime = name_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()):
|
||||
name_item = self._history_cat.child(i, 0)
|
||||
atime_item = self._history_cat.child(i, 2)
|
||||
url = name_item.data(base.Role.userdata)
|
||||
if url == entry.url:
|
||||
atime_item.setText(self._fmt_atime(entry.atime))
|
||||
name_item.setData(int(entry.atime), base.Role.sort)
|
||||
break
|
||||
else:
|
||||
self._add_history_entry(entry)
|
@ -186,7 +186,7 @@ def _init_misc():
|
||||
from qutebrowser.misc import lineparser
|
||||
command_history = lineparser.LimitLineParser(
|
||||
standarddir.data(), 'cmd-history',
|
||||
limit=('completion', 'history-length'),
|
||||
limit=('completion', 'cmd-history-max-items'),
|
||||
parent=objreg.get('config'))
|
||||
objreg.register('command-history', command_history)
|
||||
save_manager.add_saveable('command-history', command_history.save,
|
||||
@ -259,6 +259,7 @@ class ConfigManager(QObject):
|
||||
('colors', 'tab.indicator.error'): 'tabs.indicator.error',
|
||||
('colors', 'tab.indicator.system'): 'tabs.indicator.system',
|
||||
('tabs', 'auto-hide'): 'hide-auto',
|
||||
('completion', 'history-length'): 'cmd-history-max-items',
|
||||
}
|
||||
DELETED_OPTIONS = [
|
||||
('colors', 'tab.seperator'),
|
||||
|
@ -309,6 +309,10 @@ DATA = collections.OrderedDict([
|
||||
SettingValue(typ.DownloadPathSuggestion(), 'path'),
|
||||
"What to display in the download filename input."),
|
||||
|
||||
('timestamp-format',
|
||||
SettingValue(typ.String(none_ok=True), '%Y-%m-%d'),
|
||||
"How to format timestamps (e.g. for history)"),
|
||||
|
||||
('show',
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
"Whether to show the autocompletion window."),
|
||||
@ -318,9 +322,14 @@ DATA = collections.OrderedDict([
|
||||
"The height of the completion, in px or as percentage of the "
|
||||
"window."),
|
||||
|
||||
('history-length',
|
||||
('cmd-history-max-items',
|
||||
SettingValue(typ.Int(minval=-1), '100'),
|
||||
"How many commands to save in the history.\n\n"
|
||||
"How many commands to save in the command history.\n\n"
|
||||
"0: no history / -1: unlimited"),
|
||||
|
||||
('web-history-max-items',
|
||||
SettingValue(typ.Int(minval=-1), '1000'),
|
||||
"How many URLs to show in the web history.\n\n"
|
||||
"0: no history / -1: unlimited"),
|
||||
|
||||
('quick-complete',
|
||||
|
@ -191,7 +191,6 @@ class MainWindow(QWidget):
|
||||
completion_obj = self._get_object('completion')
|
||||
tabs = self._get_object('tabbed-browser')
|
||||
cmd = self._get_object('status-command')
|
||||
completer = self._get_object('completer')
|
||||
search_runner = self._get_object('search-runner')
|
||||
message_bridge = self._get_object('message-bridge')
|
||||
mode_manager = self._get_object('mode-manager')
|
||||
@ -258,15 +257,6 @@ class MainWindow(QWidget):
|
||||
completion_obj.on_clear_completion_selection)
|
||||
cmd.hide_completion.connect(completion_obj.hide)
|
||||
|
||||
# quickmark completion
|
||||
quickmark_manager = objreg.get('quickmark-manager')
|
||||
quickmark_manager.changed.connect(completer.init_quickmark_completions)
|
||||
|
||||
# sessions completion
|
||||
session_manager = objreg.get('session-manager')
|
||||
session_manager.update_completion.connect(
|
||||
completer.init_session_completion)
|
||||
|
||||
@pyqtSlot()
|
||||
def resize_completion(self):
|
||||
"""Adjust completion according to config."""
|
||||
|
@ -19,7 +19,10 @@
|
||||
|
||||
"""Tests for qutebrowser.utils.debug."""
|
||||
|
||||
import re
|
||||
import time
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QStyle, QFrame
|
||||
@ -148,5 +151,21 @@ class TestDebug(unittest.TestCase):
|
||||
r"fake('foo\nbar')")
|
||||
|
||||
|
||||
class TestLogTime(unittest.TestCase):
|
||||
|
||||
"""Test log_time."""
|
||||
|
||||
def test_log_time(self):
|
||||
logger = logging.getLogger('qt-tests')
|
||||
with self.assertLogs(logger, logging.DEBUG) as logged:
|
||||
with debug.log_time(logger, action='foobar'):
|
||||
time.sleep(0.1)
|
||||
self.assertEqual(len(logged.records), 1)
|
||||
pattern = re.compile(r'^Foobar took ([\d.]*) seconds\.$')
|
||||
match = pattern.match(logged.records[0].msg)
|
||||
self.assertTrue(match)
|
||||
duration = float(match.group(1))
|
||||
self.assertAlmostEqual(duration, 0.1, delta=0.01)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -404,5 +404,57 @@ class ForceEncodingTests(unittest.TestCase):
|
||||
self.assertEqual(utils.force_encoding(text, 'ascii'), 'hell? w?rld')
|
||||
|
||||
|
||||
class NewestSliceTests(unittest.TestCase):
|
||||
|
||||
"""Test newest_slice."""
|
||||
|
||||
def test_count_minus_two(self):
|
||||
"""Test with a count of -2."""
|
||||
with self.assertRaises(ValueError):
|
||||
utils.newest_slice([], -2)
|
||||
|
||||
def test_count_minus_one(self):
|
||||
"""Test with a count of -1 (all elements)."""
|
||||
items = range(20)
|
||||
sliced = utils.newest_slice(items, -1)
|
||||
self.assertEqual(list(sliced), list(items))
|
||||
|
||||
def test_count_zero(self):
|
||||
"""Test with a count of 0 (no elements)."""
|
||||
items = range(20)
|
||||
sliced = utils.newest_slice(items, 0)
|
||||
self.assertEqual(list(sliced), [])
|
||||
|
||||
def test_count_much_smaller(self):
|
||||
"""Test with a count which is much smaller than the iterable."""
|
||||
items = range(20)
|
||||
sliced = utils.newest_slice(items, 5)
|
||||
self.assertEqual(list(sliced), [15, 16, 17, 18, 19])
|
||||
|
||||
def test_count_smaller(self):
|
||||
"""Test with a count which is exactly one smaller."""
|
||||
items = range(5)
|
||||
sliced = utils.newest_slice(items, 4)
|
||||
self.assertEqual(list(sliced), [1, 2, 3, 4])
|
||||
|
||||
def test_count_equal(self):
|
||||
"""Test with a count which is just as large as the iterable."""
|
||||
items = range(5)
|
||||
sliced = utils.newest_slice(items, 5)
|
||||
self.assertEqual(list(sliced), list(items))
|
||||
|
||||
def test_count_bigger(self):
|
||||
"""Test with a count which is one bigger than the iterable."""
|
||||
items = range(5)
|
||||
sliced = utils.newest_slice(items, 6)
|
||||
self.assertEqual(list(sliced), list(items))
|
||||
|
||||
def test_count_much_bigger(self):
|
||||
"""Test with a count which is much bigger than the iterable."""
|
||||
items = range(5)
|
||||
sliced = utils.newest_slice(items, 50)
|
||||
self.assertEqual(list(sliced), list(items))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -23,6 +23,8 @@ import re
|
||||
import sys
|
||||
import inspect
|
||||
import functools
|
||||
import datetime
|
||||
import contextlib
|
||||
|
||||
from PyQt5.QtCore import QEvent, QMetaMethod
|
||||
|
||||
@ -248,3 +250,12 @@ def format_call(func, args=None, kwargs=None, full=True):
|
||||
else:
|
||||
name = func.__name__
|
||||
return '{}({})'.format(name, _format_args(args, kwargs))
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def log_time(logger, action='operation'):
|
||||
started = datetime.datetime.now()
|
||||
yield
|
||||
finished = datetime.datetime.now()
|
||||
delta = (finished - started).total_seconds()
|
||||
logger.debug("{} took {} seconds.".format(action.capitalize(), delta))
|
||||
|
@ -237,7 +237,7 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
|
||||
# Available command completions
|
||||
Completion = enum('Completion', ['command', 'section', 'option', 'value',
|
||||
'helptopic', 'quickmark_by_url',
|
||||
'quickmark_by_name', 'sessions'])
|
||||
'quickmark_by_name', 'url', 'sessions'])
|
||||
|
||||
|
||||
class Question(QObject):
|
||||
|
@ -27,6 +27,7 @@ import os.path
|
||||
import collections
|
||||
import functools
|
||||
import contextlib
|
||||
import itertools
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QKeySequence, QColor
|
||||
@ -552,3 +553,22 @@ def force_encoding(text, encoding):
|
||||
This replaces all chars not encodable with question marks.
|
||||
"""
|
||||
return text.encode(encoding, errors='replace').decode(encoding)
|
||||
|
||||
|
||||
def newest_slice(iterable, count):
|
||||
"""Get an iterable for the n newest items of the given iterable.
|
||||
|
||||
Args:
|
||||
count: How many elements to get.
|
||||
0: get no items:
|
||||
n: get the n newest items
|
||||
-1: get all items
|
||||
"""
|
||||
if count < -1:
|
||||
raise ValueError("count can't be smaller than -1!")
|
||||
elif count == 0:
|
||||
return []
|
||||
elif count == -1 or len(iterable) < count:
|
||||
return iterable
|
||||
else:
|
||||
return itertools.islice(iterable, len(iterable) - count, len(iterable))
|
||||
|
Loading…
Reference in New Issue
Block a user