Merge branch 'objreg'

This commit is contained in:
Florian Bruhin 2014-09-25 08:08:08 +02:00
commit 7b8829286c
57 changed files with 1389 additions and 1188 deletions

View File

@ -53,4 +53,4 @@ defining-attr-methods=__init__,__new__,setUp
max-args=10
[TYPECHECK]
ignored-classes=WebElementWrapper,AnsiCodes
ignored-classes=WebElementWrapper,AnsiCodes,UnsetObject

View File

@ -669,7 +669,6 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|==============
|Command|Description
|<<debug-all-objects,debug-all-objects>>|Print a list of all objects to the debug log.
|<<debug-all-widgets,debug-all-widgets>>|Print a list of all widgets to debug log.
|<<debug-cache-stats,debug-cache-stats>>|Print LRU cache stats.
|<<debug-console,debug-console>>|Show the debugging console.
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
@ -679,10 +678,6 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
=== debug-all-objects
Print a list of all objects to the debug log.
[[debug-all-widgets]]
=== debug-all-widgets
Print a list of all widgets to debug log.
[[debug-cache-stats]]
=== debug-cache-stats
Print LRU cache stats.

View File

@ -44,7 +44,7 @@ from qutebrowser.browser import quickmarks, cookies, downloads, cache
from qutebrowser.widgets import mainwindow, console, crash
from qutebrowser.keyinput import modeparsers, keyparser, modeman
from qutebrowser.utils import (log, version, message, utilcmds, readline,
utils, qtutils, urlutils, debug)
utils, qtutils, urlutils, debug, objreg)
from qutebrowser.utils import usertypes as utypes
@ -53,21 +53,8 @@ class Application(QApplication):
"""Main application instance.
Attributes:
mainwindow: The MainWindow QWidget.
debugconsole: The ConsoleWidget for debugging.
commandrunner: The main CommandRunner instance.
searchrunner: The main SearchRunner instance.
config: The main ConfigManager
stateconfig: The "state" ReadWriteConfigParser instance.
cmd_history: The "cmd_history" LineConfigParser instance.
messagebridge: The global MessageBridge instance.
modeman: The global ModeManager instance.
cookiejar: The global CookieJar instance.
cache: The global DiskCache instance.
rl_bridge: The ReadlineBridge being used.
args: ArgumentParser instance.
_keyparsers: A mapping from modes to keyparsers.
_timers: List of used QTimers so they don't get GCed.
_args: ArgumentParser instance.
_commandrunner: The main CommandRunner instance.
_shutting_down: True if we're currently shutting down.
_quit_status: The current quitting status.
_crashdlg: The crash dialog currently open.
@ -93,22 +80,15 @@ class Application(QApplication):
'tabs': False,
'main': False,
}
self._timers = []
objreg.register('app', self)
self._shutting_down = False
self._keyparsers = None
self._crashdlg = None
self._crashlogfile = None
self.rl_bridge = None
self.messagebridge = None
self.stateconfig = None
self.modeman = None
self.cmd_history = None
self.config = None
self.keyconfig = None
sys.excepthook = self._exception_hook
self.args = args
self._args = args
objreg.register('args', args)
log.init.debug("Starting init...")
self._init_misc()
utils.actute_warning()
@ -118,6 +98,7 @@ class Application(QApplication):
self._handle_segfault()
log.init.debug("Initializing modes...")
self._init_modes()
mode_manager = objreg.get('mode-manager')
log.init.debug("Initializing websettings...")
websettings.init()
log.init.debug("Initializing quickmarks...")
@ -129,52 +110,59 @@ class Application(QApplication):
log.init.debug("Initializing utility commands...")
utilcmds.init()
log.init.debug("Initializing cookies...")
self.cookiejar = cookies.CookieJar(self)
cookie_jar = cookies.CookieJar(self)
objreg.register('cookie-jar', cookie_jar)
log.init.debug("Initializing cache...")
self.cache = cache.DiskCache(self)
diskcache = cache.DiskCache(self)
objreg.register('cache', diskcache)
log.init.debug("Initializing commands...")
self.commandrunner = runners.CommandRunner()
self._commandrunner = runners.CommandRunner()
log.init.debug("Initializing search...")
self.searchrunner = runners.SearchRunner(self)
search_runner = runners.SearchRunner(self)
objreg.register('search-runner', search_runner)
log.init.debug("Initializing downloads...")
self.downloadmanager = downloads.DownloadManager(self)
download_manager = downloads.DownloadManager(self)
objreg.register('download-manager', download_manager)
log.init.debug("Initializing main window...")
self.mainwindow = mainwindow.MainWindow()
self.modeman.mainwindow = self.mainwindow
main_window = mainwindow.MainWindow()
objreg.register('main-window', main_window)
log.init.debug("Initializing debug console...")
self.debugconsole = console.ConsoleWidget()
debug_console = console.ConsoleWidget()
objreg.register('debug-console', debug_console)
log.init.debug("Initializing eventfilter...")
self.installEventFilter(self.modeman)
self.installEventFilter(mode_manager)
self.setQuitOnLastWindowClosed(False)
log.init.debug("Connecting signals...")
self._connect_signals()
self.modeman.enter(utypes.KeyMode.normal, 'init')
modeman.enter(utypes.KeyMode.normal, 'init')
log.init.debug("Showing mainwindow...")
if not args.nowindow:
self.mainwindow.show()
main_window.show()
log.init.debug("Applying python hacks...")
self._python_hacks()
timer = QTimer.singleShot(0, self._process_init_args)
self._timers.append(timer)
QTimer.singleShot(0, self._process_init_args)
log.init.debug("Init done!")
if self._crashdlg is not None:
self._crashdlg.raise_()
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def _init_config(self):
"""Inizialize and read the config."""
if self.args.confdir is None:
if self._args.confdir is None:
confdir = utils.get_standard_dir(QStandardPaths.ConfigLocation)
elif self.args.confdir == '':
elif self._args.confdir == '':
confdir = None
else:
confdir = self.args.confdir
confdir = self._args.confdir
try:
self.config = config.ConfigManager(confdir, 'qutebrowser.conf',
self)
config_obj = config.ConfigManager(
confdir, 'qutebrowser.conf', self)
except (configtypes.ValidationError,
config.NoOptionError,
config.NoSectionError,
@ -194,9 +182,10 @@ class Application(QApplication):
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
else:
objreg.register('config', config_obj)
try:
self.keyconfig = keyconfparser.KeyConfigParser(
confdir, 'keys.conf')
key_config = keyconfparser.KeyConfigParser(confdir, 'keys.conf')
except keyconfparser.KeyConfigError as e:
log.init.exception(e)
errstr = "Error while reading key config:\n"
@ -208,13 +197,17 @@ class Application(QApplication):
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
self.stateconfig = iniparsers.ReadWriteConfigParser(confdir, 'state')
self.cmd_history = lineparser.LineConfigParser(
else:
objreg.register('key-config', key_config)
state_config = iniparsers.ReadWriteConfigParser(confdir, 'state')
objreg.register('state-config', state_config)
command_history = lineparser.LineConfigParser(
confdir, 'cmd_history', ('completion', 'history-length'))
objreg.register('command-history', command_history)
def _init_modes(self):
"""Inizialize the mode manager and the keyparsers."""
self._keyparsers = {
keyparsers = {
utypes.KeyMode.normal:
modeparsers.NormalKeyParser(self),
utypes.KeyMode.hint:
@ -230,30 +223,32 @@ class Application(QApplication):
utypes.KeyMode.yesno:
modeparsers.PromptKeyParser(self),
}
self.modeman = modeman.ModeManager(self)
self.modeman.register(utypes.KeyMode.normal,
self._keyparsers[utypes.KeyMode.normal].handle)
self.modeman.register(utypes.KeyMode.hint,
self._keyparsers[utypes.KeyMode.hint].handle)
self.modeman.register(utypes.KeyMode.insert,
self._keyparsers[utypes.KeyMode.insert].handle,
objreg.register('keyparsers', keyparsers)
mode_manager = modeman.ModeManager(self)
objreg.register('mode-manager', mode_manager)
mode_manager.register(utypes.KeyMode.normal,
keyparsers[utypes.KeyMode.normal].handle)
mode_manager.register(utypes.KeyMode.hint,
keyparsers[utypes.KeyMode.hint].handle)
mode_manager.register(utypes.KeyMode.insert,
keyparsers[utypes.KeyMode.insert].handle,
passthrough=True)
self.modeman.register(
mode_manager.register(
utypes.KeyMode.passthrough,
self._keyparsers[utypes.KeyMode.passthrough].handle,
keyparsers[utypes.KeyMode.passthrough].handle,
passthrough=True)
self.modeman.register(utypes.KeyMode.command,
self._keyparsers[utypes.KeyMode.command].handle,
mode_manager.register(utypes.KeyMode.command,
keyparsers[utypes.KeyMode.command].handle,
passthrough=True)
self.modeman.register(utypes.KeyMode.prompt,
self._keyparsers[utypes.KeyMode.prompt].handle,
mode_manager.register(utypes.KeyMode.prompt,
keyparsers[utypes.KeyMode.prompt].handle,
passthrough=True)
self.modeman.register(utypes.KeyMode.yesno,
self._keyparsers[utypes.KeyMode.yesno].handle)
mode_manager.register(utypes.KeyMode.yesno,
keyparsers[utypes.KeyMode.yesno].handle)
def _init_misc(self):
"""Initialize misc things."""
if self.args.version:
if self._args.version:
print(version.version())
print()
print()
@ -264,8 +259,10 @@ class Application(QApplication):
self.setOrganizationName("qutebrowser")
self.setApplicationName("qutebrowser")
self.setApplicationVersion(qutebrowser.__version__)
self.messagebridge = message.MessageBridge(self)
self.rl_bridge = readline.ReadlineBridge()
message_bridge = message.MessageBridge(self)
objreg.register('message-bridge', message_bridge)
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
def _handle_segfault(self):
"""Handle a segfault from a previous run."""
@ -328,11 +325,11 @@ class Application(QApplication):
# we make sure the GUI is refreshed here, so the start seems faster.
self.processEvents(QEventLoop.ExcludeUserInputEvents |
QEventLoop.ExcludeSocketNotifiers)
for cmd in self.args.command:
tabbed_browser = objreg.get('tabbed-browser')
for cmd in self._args.command:
if cmd.startswith(':'):
log.init.debug("Startup cmd {}".format(cmd))
self.commandrunner.run_safely_init(cmd.lstrip(':'))
self._commandrunner.run_safely_init(cmd.lstrip(':'))
else:
log.init.debug("Startup URL {}".format(cmd))
try:
@ -341,17 +338,17 @@ class Application(QApplication):
message.error("Error in startup argument '{}': {}".format(
cmd, e))
else:
self.mainwindow.tabs.tabopen(url)
tabbed_browser.tabopen(url)
if self.mainwindow.tabs.count() == 0:
if tabbed_browser.count() == 0:
log.init.debug("Opening startpage")
for urlstr in self.config.get('general', 'startpage'):
for urlstr in config.get('general', 'startpage'):
try:
url = urlutils.fuzzy_url(urlstr)
except urlutils.FuzzyUrlError as e:
message.error("Error when opening startpage: {}".format(e))
else:
self.mainwindow.tabs.tabopen(url)
tabbed_browser.tabopen(url)
def _python_hacks(self):
"""Get around some PyQt-oddities by evil hacks.
@ -365,63 +362,64 @@ class Application(QApplication):
timer = utypes.Timer(self, 'python_hacks')
timer.start(500)
timer.timeout.connect(lambda: None)
self._timers.append(timer)
objreg.register('python-hack-timer', timer)
def _connect_signals(self):
"""Connect all signals to their slots."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-statements, too-many-locals
# syntactic sugar
kp = self._keyparsers
status = self.mainwindow.status
completion = self.mainwindow.completion
tabs = self.mainwindow.tabs
cmd = self.mainwindow.status.cmd
completer = self.mainwindow.completion.completer
kp = objreg.get('keyparsers')
main_window = objreg.get('main-window')
status = main_window.status
completion = objreg.get('completion')
tabs = objreg.get('tabbed-browser')
cmd = objreg.get('status-command')
completer = objreg.get('completer')
search_runner = objreg.get('search-runner')
message_bridge = objreg.get('message-bridge')
mode_manager = objreg.get('mode-manager')
prompter = objreg.get('prompter')
command_history = objreg.get('command-history')
download_manager = objreg.get('download-manager')
config_obj = objreg.get('config')
key_config = objreg.get('key-config')
# misc
self.lastWindowClosed.connect(self.shutdown)
tabs.quit.connect(self.shutdown)
# status bar
self.modeman.entered.connect(status.on_mode_entered)
self.modeman.left.connect(status.on_mode_left)
self.modeman.left.connect(status.cmd.on_mode_left)
self.modeman.left.connect(status.prompt.prompter.on_mode_left)
mode_manager.entered.connect(status.on_mode_entered)
mode_manager.left.connect(status.on_mode_left)
mode_manager.left.connect(cmd.on_mode_left)
mode_manager.left.connect(prompter.on_mode_left)
# commands
cmd.got_cmd.connect(self.commandrunner.run_safely)
cmd.got_search.connect(self.searchrunner.search)
cmd.got_search_rev.connect(self.searchrunner.search_rev)
cmd.got_cmd.connect(self._commandrunner.run_safely)
cmd.got_search.connect(search_runner.search)
cmd.got_search_rev.connect(search_runner.search_rev)
cmd.returnPressed.connect(tabs.setFocus)
self.searchrunner.do_search.connect(tabs.search)
search_runner.do_search.connect(tabs.search)
kp[utypes.KeyMode.normal].keystring_updated.connect(
status.keystring.setText)
tabs.got_cmd.connect(self.commandrunner.run_safely)
# hints
kp[utypes.KeyMode.hint].fire_hint.connect(tabs.fire_hint)
kp[utypes.KeyMode.hint].filter_hints.connect(tabs.filter_hints)
kp[utypes.KeyMode.hint].keystring_updated.connect(tabs.handle_hint_key)
tabs.hint_strings_updated.connect(
kp[utypes.KeyMode.hint].on_hint_strings_updated)
tabs.got_cmd.connect(self._commandrunner.run_safely)
# messages
self.messagebridge.s_error.connect(status.disp_error)
self.messagebridge.s_info.connect(status.disp_temp_text)
self.messagebridge.s_set_text.connect(status.set_text)
self.messagebridge.s_maybe_reset_text.connect(
status.txt.maybe_reset_text)
self.messagebridge.s_set_cmd_text.connect(cmd.set_cmd_text)
self.messagebridge.s_question.connect(
status.prompt.prompter.ask_question, Qt.DirectConnection)
message_bridge.s_error.connect(status.disp_error)
message_bridge.s_info.connect(status.disp_temp_text)
message_bridge.s_set_text.connect(status.set_text)
message_bridge.s_maybe_reset_text.connect(status.txt.maybe_reset_text)
message_bridge.s_set_cmd_text.connect(cmd.set_cmd_text)
message_bridge.s_question.connect(prompter.ask_question,
Qt.DirectConnection)
# config
self.config.style_changed.connect(style.get_stylesheet.cache_clear)
for obj in (tabs, completion, self.mainwindow, self.cmd_history,
websettings, self.modeman, status, status.txt):
self.config.changed.connect(obj.on_config_changed)
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
for obj in (tabs, completion, main_window, command_history,
websettings, mode_manager, status, status.txt):
config_obj.changed.connect(obj.on_config_changed)
for obj in kp.values():
self.keyconfig.changed.connect(obj.on_keyconfig_changed)
key_config.changed.connect(obj.on_keyconfig_changed)
# statusbar
# FIXME some of these probably only should be triggered on mainframe
@ -444,7 +442,7 @@ class Application(QApplication):
tabs.cur_load_status_changed.connect(status.url.on_load_status_changed)
# command input / completion
self.modeman.left.connect(tabs.on_mode_left)
mode_manager.left.connect(tabs.on_mode_left)
cmd.clear_completion_selection.connect(
completion.on_clear_completion_selection)
cmd.hide_completion.connect(completion.hide)
@ -452,31 +450,51 @@ class Application(QApplication):
completer.change_completed_part.connect(cmd.on_change_completed_part)
# downloads
tabs.start_download.connect(self.downloadmanager.fetch)
tabs.download_get.connect(self.downloadmanager.get)
tabs.start_download.connect(download_manager.fetch)
def get_all_widgets(self):
def _get_widgets(self):
"""Get a string list of all widgets."""
lines = []
widgets = self.allWidgets()
widgets.sort(key=lambda e: repr(e))
lines.append("{} widgets".format(len(widgets)))
for w in widgets:
lines.append(repr(w))
return '\n'.join(lines)
return [repr(w) for w in widgets]
def get_all_objects(self, obj=None, depth=0, lines=None):
"""Get all children of an object recursively as a string."""
if lines is None:
lines = []
if obj is None:
obj = self
def _get_pyqt_objects(self, lines, obj, depth=0):
"""Recursive method for get_all_objects to get Qt objects."""
for kid in obj.findChildren(QObject):
lines.append(' ' * depth + repr(kid))
self.get_all_objects(kid, depth + 1, lines)
if depth == 0:
lines.insert(0, '{} objects:'.format(len(lines)))
return '\n'.join(lines)
self._get_pyqt_objects(lines, kid, depth + 1)
def _get_registered_objects(self):
"""Get all registered objects in all registries as a string."""
blocks = []
lines = []
for name, registry in objreg.meta_registry.items():
blocks.append((name, registry.dump_objects()))
for name, data in blocks:
lines.append("{} object registry - {} objects:".format(
name, len(data)))
for line in data:
lines.append(" {}".format(line))
return lines
def get_all_objects(self):
"""Get all children of an object recursively as a string."""
output = ['']
output += self._get_registered_objects()
output += ['']
pyqt_lines = []
self._get_pyqt_objects(pyqt_lines, self)
pyqt_lines = [' ' + e for e in pyqt_lines]
pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(
len(pyqt_lines)))
output += pyqt_lines
output += ['']
widget_lines = self._get_widgets()
widget_lines = [' ' + e for e in widget_lines]
widget_lines.insert(0, "Qt widgets - {} objects".format(
len(widget_lines)))
output += widget_lines
return '\n'.join(output)
def _recover_pages(self):
"""Try to recover all open pages.
@ -486,12 +504,12 @@ class Application(QApplication):
Return:
A list of open pages, or an empty list.
"""
try:
tabbed_browser = objreg.get('tabbed-browser')
except KeyError:
return []
pages = []
if self.mainwindow is None:
return pages
if self.mainwindow.tabs is None:
return pages
for tab in self.mainwindow.tabs.widgets():
for tab in tabbed_browser.widgets():
try:
url = tab.cur_url.toString(
QUrl.RemovePassword | QUrl.FullyEncoded)
@ -503,13 +521,14 @@ class Application(QApplication):
def _save_geometry(self):
"""Save the window geometry to the state config."""
data = bytes(self.mainwindow.saveGeometry())
state_config = objreg.get('state-config')
data = bytes(objreg.get('main-window').saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
try:
self.stateconfig.add_section('geometry')
state_config.add_section('geometry')
except configparser.DuplicateSectionError:
pass
self.stateconfig['geometry']['mainwindow'] = geom
state_config['geometry']['mainwindow'] = geom
def _destroy_crashlogfile(self):
"""Clean up the crash log file and delete it."""
@ -558,17 +577,11 @@ class Application(QApplication):
pages = []
try:
history = self.mainwindow.status.cmd.history[-5:]
history = objreg.get('status-command').history[-5:]
except Exception:
log.destroy.exception("Error while getting history: {}")
history = []
try:
widgets = self.get_all_widgets()
except Exception:
log.destroy.exception("Error while getting widgets")
widgets = ""
try:
objects = self.get_all_objects()
except Exception:
@ -581,7 +594,7 @@ class Application(QApplication):
log.destroy.exception("Error while preventing shutdown")
QApplication.closeAllWindows()
self._crashdlg = crash.ExceptionCrashDialog(pages, history, exc,
widgets, objects)
objects)
ret = self._crashdlg.exec_()
if ret == QDialog.Accepted: # restore
self.restart(shutdown=False, pages=pages)
@ -592,14 +605,14 @@ class Application(QApplication):
self._destroy_crashlogfile()
sys.exit(1)
@cmdutils.register(instance='', ignore_args=True)
@cmdutils.register(instance='app', ignore_args=True)
def restart(self, shutdown=True, pages=None):
"""Restart qutebrowser while keeping existing tabs open."""
# We don't use _recover_pages here as it's too forgiving when
# exceptions occur.
if pages is None:
pages = []
for tab in self.mainwindow.tabs.widgets():
for tab in objreg.get('tabbed-browser').widgets():
urlstr = tab.cur_url.toString(
QUrl.RemovePassword | QUrl.FullyEncoded)
if urlstr:
@ -635,7 +648,7 @@ class Application(QApplication):
if shutdown:
self.shutdown()
@cmdutils.register(instance='', split=False, debug=True)
@cmdutils.register(instance='app', split=False, debug=True)
def debug_pyeval(self, s):
"""Evaluate a python string and display the results as a webpage.
@ -653,23 +666,17 @@ class Application(QApplication):
except Exception: # pylint: disable=broad-except
out = traceback.format_exc()
qutescheme.pyeval_output = out
self.mainwindow.tabs.openurl(QUrl('qute:pyeval'), newtab=True)
objreg.get('tabbed-browser').openurl(QUrl('qute:pyeval'), newtab=True)
@cmdutils.register(instance='')
@cmdutils.register(instance='app')
def report(self):
"""Report a bug in qutebrowser."""
pages = self._recover_pages()
history = self.mainwindow.status.cmd.history[-5:]
widgets = self.get_all_widgets()
history = objreg.get('status-command').history[-5:]
objects = self.get_all_objects()
self._crashdlg = crash.ReportDialog(pages, history, widgets, objects)
self._crashdlg = crash.ReportDialog(pages, history, objects)
self._crashdlg.show()
@cmdutils.register(instance='', debug=True, name='debug-console')
def show_debugconsole(self):
"""Show the debugging console."""
self.debugconsole.show()
def interrupt(self, signum, _frame):
"""Handler for signals to gracefully shutdown (SIGINT/SIGTERM).
@ -721,7 +728,7 @@ class Application(QApplication):
return
self._shutting_down = True
log.destroy.debug("Shutting down with status {}...".format(status))
if self.mainwindow.status.prompt.prompter.shutdown():
if objreg.get('prompter').shutdown():
# If shutdown was called while we were asking a question, we're in
# a still sub-eventloop (which gets quitted now) and not in the
# main one.
@ -737,31 +744,57 @@ class Application(QApplication):
def _shutdown(self, status): # noqa
"""Second stage of shutdown."""
# pylint: disable=too-many-branches, too-many-statements
# FIXME refactor this
log.destroy.debug("Stage 2 of shutting down...")
# Remove eventfilter
if self.modeman is not None:
try:
log.destroy.debug("Removing eventfilter...")
self.removeEventFilter(self.modeman)
self.removeEventFilter(objreg.get('mode-manager'))
except KeyError:
pass
# Close all tabs
if self.mainwindow is not None:
try:
log.destroy.debug("Closing tabs...")
self.mainwindow.tabs.shutdown()
objreg.get('tabbed-browser').shutdown()
except KeyError:
pass
# Save everything
if hasattr(self, 'config') and self.config is not None:
try:
config_obj = objreg.get('config')
except KeyError:
log.destroy.debug("Config not initialized yet, so not saving "
"anything.")
else:
to_save = []
if self.config.get('general', 'auto-save-config'):
if hasattr(self, 'config'):
to_save.append(("config", self.config.save))
if hasattr(self, 'keyconfig'):
to_save.append(("keyconfig", self.keyconfig.save))
if config.get('general', 'auto-save-config'):
to_save.append(("config", config_obj.save))
try:
key_config = objreg.get('key-config')
except KeyError:
pass
else:
to_save.append(("keyconfig", key_config.save))
to_save += [("window geometry", self._save_geometry),
("quickmarks", quickmarks.save)]
if hasattr(self, 'cmd_history'):
to_save.append(("command history", self.cmd_history.save))
if hasattr(self, 'stateconfig'):
to_save.append(("window geometry", self.stateconfig.save))
if hasattr(self, 'cookiejar'):
to_save.append(("cookies", self.cookiejar.save))
try:
command_history = objreg.get('command-history')
except KeyError:
pass
else:
to_save.append(("command history", command_history.save))
try:
state_config = objreg.get('state-config')
except KeyError:
pass
else:
to_save.append(("window geometry", state_config.save))
try:
cookie_jar = objreg.get('cookie-jar')
except KeyError:
pass
else:
to_save.append(("cookies", cookie_jar.save))
for what, handler in to_save:
log.destroy.debug("Saving {} (handler: {})".format(
what, handler.__qualname__))
@ -770,9 +803,6 @@ class Application(QApplication):
except AttributeError as e:
log.destroy.warning("Could not save {}.".format(what))
log.destroy.debug(e)
else:
log.destroy.debug("Config not initialized yet, so not saving "
"anything.")
# Re-enable faulthandler to stdout, then remove crash log
log.destroy.debug("Deactiving crash log...")
self._destroy_crashlogfile()
@ -788,7 +818,7 @@ class Application(QApplication):
def exit(self, status):
"""Extend QApplication::exit to log the event."""
log.destroy.debug("Now calling QApplication::exit.")
if self.args.debug_exit:
if self._args.debug_exit:
print("Now logging late shutdown.", file=sys.stderr)
debug.trace_lines(True)
super().exit(status)

View File

@ -37,3 +37,8 @@ class DiskCache(QNetworkDiskCache):
cache_dir = utils.get_standard_dir(QStandardPaths.CacheLocation)
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
def __repr__(self):
return '<{} size={}, maxsize={}, path="{}">'.format(
self.__class__.__name__, self.cacheSize(), self.maximumCacheSize(),
self.cacheDirectory())

View File

@ -36,9 +36,9 @@ import pygments.formatters
from qutebrowser.commands import userscripts, cmdexc, cmdutils
from qutebrowser.config import config
from qutebrowser.browser import hints, quickmarks, webelem
from qutebrowser.browser import quickmarks, webelem
from qutebrowser.utils import (message, editor, usertypes, log, qtutils,
urlutils)
urlutils, objreg)
class CommandDispatcher:
@ -52,22 +52,34 @@ class CommandDispatcher:
cmdutils.register() decorators are run, currentWidget() will return None.
Attributes:
_tabs: The TabbedBrowser object.
_editor: The ExternalEditor object.
"""
def __init__(self, parent):
"""Constructor.
Args:
parent: The TabbedBrowser for this dispatcher.
"""
self._tabs = parent
def __init__(self):
self._editor = None
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def _count(self):
"""Convenience method to get the widget count."""
return objreg.get('tabbed-browser').count()
def _set_current_index(self, idx):
"""Convenience method to set the current widget index."""
return objreg.get('tabbed-browser').setCurrentIndex(idx)
def _current_index(self):
"""Convenience method to get the current widget index."""
return objreg.get('tabbed-browser').currentIndex()
def _current_url(self):
"""Convenience method to get the current url."""
return objreg.get('tabbed-browser').current_url()
def _current_widget(self):
"""Get the currently active widget from a command."""
widget = self._tabs.currentWidget()
widget = objreg.get('tabbed-browser').currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
return widget
@ -80,16 +92,37 @@ class CommandDispatcher:
tab: Whether to open in a new tab.
background: Whether to open in the background.
"""
tabbed_browser = objreg.get('tabbed-browser')
if tab and background:
raise cmdexc.CommandError("Only one of -t/-b can be given!")
elif tab:
self._tabs.tabopen(url, background=False, explicit=True)
tabbed_browser.tabopen(url, background=False, explicit=True)
elif background:
self._tabs.tabopen(url, background=True, explicit=True)
tabbed_browser.tabopen(url, background=True, explicit=True)
else:
widget = self._current_widget()
widget.openurl(url)
def _cntwidget(self, count=None):
"""Return a widget based on a count/idx.
Args:
count: The tab index, or None.
Return:
The current widget if count is None.
The widget with the given tab ID if count is given.
None if no widget was found.
"""
tabbed_browser = objreg.get('tabbed-browser')
if count is None:
return tabbed_browser.currentWidget()
elif 1 <= count <= self._count():
cmdutils.check_overflow(count + 1, 'int')
return tabbed_browser.widget(count - 1)
else:
return None
def _scroll_percent(self, perc=None, count=None, orientation=None):
"""Inner logic for scroll_percent_(x|y).
@ -118,7 +151,7 @@ class CommandDispatcher:
if idx is None:
return 0
elif idx == 0:
return self._tabs.count() - 1
return self._count() - 1
else:
return idx - 1
@ -134,18 +167,20 @@ class CommandDispatcher:
# gets called from tab_move which has delta set to None by default.
delta = 1
if direction == '-':
return self._tabs.currentIndex() - delta
return self._current_index() - delta
elif direction == '+':
return self._tabs.currentIndex() + delta
return self._current_index() + delta
def _tab_focus_last(self):
"""Select the tab which was last focused."""
if self._tabs.last_focused is None:
try:
tab = objreg.get('last-focused-tab')
except KeyError:
raise cmdexc.CommandError("No last focused tab!")
idx = self._tabs.indexOf(self._tabs.last_focused)
idx = objreg.get('tabbed-browser').indexOf(tab)
if idx == -1:
raise cmdexc.CommandError("Last focused tab vanished!")
self._tabs.setCurrentIndex(idx)
self._set_current_index(idx)
def _editor_cleanup(self, oshandle, filename):
"""Clean up temporary file when the editor was closed."""
@ -155,7 +190,7 @@ class CommandDispatcher:
except PermissionError:
raise cmdexc.CommandError("Failed to delete tempfile...")
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_close(self, count=None):
"""Close the current/[count]th tab.
@ -166,12 +201,12 @@ class CommandDispatcher:
quit: If last tab was closed and last-close in config is set to
quit.
"""
tab = self._tabs.cntwidget(count)
tab = self._cntwidget(count)
if tab is None:
return
self._tabs.close_tab(tab)
objreg.get('tabbed-browser').close_tab(tab)
@cmdutils.register(instance='mainwindow.tabs.cmd', name='open',
@cmdutils.register(instance='command-dispatcher', name='open',
split=False)
def openurl(self, url, bg=False, tab=False, count=None):
"""Open a URL in the current/[count]th tab.
@ -186,46 +221,44 @@ class CommandDispatcher:
url = urlutils.fuzzy_url(url)
except urlutils.FuzzyUrlError as e:
raise cmdexc.CommandError(e)
if tab:
self._tabs.tabopen(url, background=False, explicit=True)
elif bg:
self._tabs.tabopen(url, background=True, explicit=True)
if tab or bg:
self._open(url, tab, bg)
else:
curtab = self._tabs.cntwidget(count)
curtab = self._cntwidget(count)
if curtab is None:
if count is None:
# We want to open a URL in the current tab, but none exists
# yet.
self._tabs.tabopen(url)
objreg.get('tabbed-browser').tabopen(url)
else:
# Explicit count with a tab that doesn't exist.
return
else:
curtab.openurl(url)
@cmdutils.register(instance='mainwindow.tabs.cmd', name='reload')
@cmdutils.register(instance='command-dispatcher', name='reload')
def reloadpage(self, count=None):
"""Reload the current/[count]th tab.
Args:
count: The tab index to reload, or None.
"""
tab = self._tabs.cntwidget(count)
tab = self._cntwidget(count)
if tab is not None:
tab.reload()
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def stop(self, count=None):
"""Stop loading in the current/[count]th tab.
Args:
count: The tab index to stop, or None.
"""
tab = self._tabs.cntwidget(count)
tab = self._cntwidget(count)
if tab is not None:
tab.stop()
@cmdutils.register(instance='mainwindow.tabs.cmd', name='print')
@cmdutils.register(instance='command-dispatcher', name='print')
def printpage(self, preview=False, count=None):
"""Print the current/[count]th tab.
@ -237,7 +270,7 @@ class CommandDispatcher:
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
raise cmdexc.CommandError(
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
tab = self._tabs.cntwidget(count)
tab = self._cntwidget(count)
if tab is not None:
if preview:
diag = QPrintPreviewDialog()
@ -249,7 +282,7 @@ class CommandDispatcher:
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.open(lambda: tab.print(diag.printer()))
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def back(self, count=1):
"""Go back in the history of the current tab.
@ -259,7 +292,7 @@ class CommandDispatcher:
for _ in range(count):
self._current_widget().go_back()
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def forward(self, count=1):
"""Go forward in the history of the current tab.
@ -269,55 +302,6 @@ class CommandDispatcher:
for _ in range(count):
self._current_widget().go_forward()
@cmdutils.register(instance='mainwindow.tabs.cmd')
def hint(self, group=webelem.Group.all, target=hints.Target.normal,
*args: {'nargs': '*'}):
"""Start hinting.
Args:
group: The hinting mode to use.
- `all`: All clickable elements.
- `links`: Only links.
- `images`: Only images.
target: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `tab`: Open the link in a new tab.
- `tab-bg`: Open the link in a new background tab.
- `yank`: Yank the link to the clipboard.
- `yank-primary`: Yank the link to the primary selection.
- `fill`: Fill the commandline with the command given as
argument.
- `rapid`: Open the link in a new tab and stay in hinting mode.
- `download`: Download the link.
- `userscript`: Call an userscript with `$QUTE_URL` set to the
link.
- `spawn`: Spawn a command.
*args: Arguments for spawn/userscript/fill.
- With `spawn`: The executable and arguments to spawn.
`{hint-url}` will get replaced by the selected
URL.
- With `userscript`: The userscript to execute.
- With `fill`: The command to fill the statusbar with.
`{hint-url}` will get replaced by the selected
URL.
"""
widget = self._current_widget()
frame = widget.page().mainFrame()
if frame is None:
raise cmdexc.CommandError("No frame focused!")
widget.hintmanager.start(frame, self._tabs.current_url(), group,
target, *args)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
def follow_hint(self):
"""Follow the currently selected hint."""
self._current_widget().hintmanager.follow_hint()
def _navigate_incdec(self, url, tab, incdec):
"""Helper method for :navigate when `where' is increment/decrement.
@ -365,7 +349,7 @@ class CommandDispatcher:
url.setPath(new_path)
self._open(url, tab, background=False)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def navigate(self, where: ('prev', 'next', 'up', 'increment', 'decrement'),
tab=False):
"""Open typical prev/next links or navigate using the URL path.
@ -388,15 +372,14 @@ class CommandDispatcher:
"""
widget = self._current_widget()
frame = widget.page().currentFrame()
url = self._tabs.current_url()
url = self._current_url()
if frame is None:
raise cmdexc.CommandError("No frame focused!")
hintmanager = objreg.get('hintmanager', scope='tab')
if where == 'prev':
widget.hintmanager.follow_prevnext(frame, url, prev=True,
newtab=tab)
hintmanager.follow_prevnext(frame, url, prev=True, newtab=tab)
elif where == 'next':
widget.hintmanager.follow_prevnext(frame, url, prev=False,
newtab=tab)
hintmanager.follow_prevnext(frame, url, prev=False, newtab=tab)
elif where == 'up':
self._navigate_up(url, tab)
elif where in ('decrement', 'increment'):
@ -405,7 +388,7 @@ class CommandDispatcher:
raise ValueError("Got called with invalid value {} for "
"`where'.".format(where))
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True)
def scroll(self, dx: float, dy: float, count=1):
"""Scroll the current tab by 'count * dx/dy'.
@ -420,7 +403,7 @@ class CommandDispatcher:
cmdutils.check_overflow(dy, 'int')
self._current_widget().page().currentFrame().scroll(dx, dy)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True)
def scroll_perc(self, perc: float=None,
horizontal: {'flag': 'x'}=False, count=None):
"""Scroll to a specific percentage of the page.
@ -436,7 +419,7 @@ class CommandDispatcher:
self._scroll_percent(perc, count,
Qt.Horizontal if horizontal else Qt.Vertical)
@cmdutils.register(instance='mainwindow.tabs.cmd', hide=True)
@cmdutils.register(instance='command-dispatcher', hide=True)
def scroll_page(self, x: float, y: float, count=1):
"""Scroll the frame page-wise.
@ -453,7 +436,7 @@ class CommandDispatcher:
cmdutils.check_overflow(dy, 'int')
frame.scroll(dx, dy)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def yank(self, title=False, sel=False):
"""Yank the current URL/title to the clipboard or primary selection.
@ -463,9 +446,9 @@ class CommandDispatcher:
"""
clipboard = QApplication.clipboard()
if title:
s = self._tabs.tabText(self._tabs.currentIndex())
s = objreg.get('tabbed-browser').tabText(self._current_index())
else:
s = self._tabs.current_url().toString(
s = self._current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
if sel and clipboard.supportsSelection():
mode = QClipboard.Selection
@ -478,7 +461,7 @@ class CommandDispatcher:
what = 'Title' if title else 'URL'
message.info("{} yanked to {}".format(what, target))
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def zoom_in(self, count=1):
"""Increase the zoom level for the current tab.
@ -488,7 +471,7 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom(count)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def zoom_out(self, count=1):
"""Decrease the zoom level for the current tab.
@ -498,7 +481,7 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom(-count)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def zoom(self, zoom=None, count=None):
"""Set the zoom level for the current tab.
@ -516,53 +499,55 @@ class CommandDispatcher:
tab = self._current_widget()
tab.zoom_perc(level)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_only(self):
"""Close all tabs except for the current one."""
for tab in self._tabs.widgets():
tabbed_browser = objreg.get('tabbed-browser')
for tab in tabbed_browser.widgets():
if tab is self._current_widget():
continue
self._tabs.close_tab(tab)
tabbed_browser.close_tab(tab)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def undo(self):
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
if self._tabs.url_stack:
self._tabs.tabopen(self._tabs.url_stack.pop())
url_stack = objreg.get('url-stack', None)
if url_stack:
objreg.get('tabbed-browser').tabopen(url_stack.pop())
else:
raise cmdexc.CommandError("Nothing to undo!")
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_prev(self, count=1):
"""Switch to the previous tab, or switch [count] tabs back.
Args:
count: How many tabs to switch back.
"""
newidx = self._tabs.currentIndex() - count
newidx = self._current_index() - count
if newidx >= 0:
self._tabs.setCurrentIndex(newidx)
self._set_current_index(newidx)
elif config.get('tabs', 'wrap'):
self._tabs.setCurrentIndex(newidx % self._tabs.count())
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("First tab")
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_next(self, count=1):
"""Switch to the next tab, or switch [count] tabs forward.
Args:
count: How many tabs to switch forward.
"""
newidx = self._tabs.currentIndex() + count
if newidx < self._tabs.count():
self._tabs.setCurrentIndex(newidx)
newidx = self._current_index() + count
if newidx < self._count():
self._set_current_index(newidx)
elif config.get('tabs', 'wrap'):
self._tabs.setCurrentIndex(newidx % self._tabs.count())
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("Last tab")
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def paste(self, sel=False, tab=False, bg=False):
"""Open a page from the clipboard.
@ -588,7 +573,7 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
self._open(url, tab, bg)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_focus(self, index: (int, 'last')=None, count=None):
"""Select the tab given as argument/[count].
@ -602,17 +587,17 @@ class CommandDispatcher:
return
try:
idx = cmdutils.arg_or_count(index, count, default=1,
countzero=self._tabs.count())
countzero=self._count())
except ValueError as e:
raise cmdexc.CommandError(e)
cmdutils.check_overflow(idx + 1, 'int')
if 1 <= idx <= self._tabs.count():
self._tabs.setCurrentIndex(idx - 1)
if 1 <= idx <= self._count():
self._set_current_index(idx - 1)
else:
raise cmdexc.CommandError("There's no tab with index {}!".format(
idx))
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def tab_move(self, direction: ('+', '-')=None, count=None):
"""Move the current tab.
@ -633,24 +618,25 @@ class CommandDispatcher:
else:
raise cmdexc.CommandError("Invalid direction '{}'!".format(
direction))
if not 0 <= new_idx < self._tabs.count():
if not 0 <= new_idx < self._count():
raise cmdexc.CommandError("Can't move tab to position {}!".format(
new_idx))
tabbed_browser = objreg.get('tabbed-browser')
tab = self._current_widget()
cur_idx = self._tabs.currentIndex()
icon = self._tabs.tabIcon(cur_idx)
label = self._tabs.tabText(cur_idx)
cur_idx = self._current_index()
icon = tabbed_browser.tabIcon(cur_idx)
label = tabbed_browser.tabText(cur_idx)
cmdutils.check_overflow(cur_idx, 'int')
cmdutils.check_overflow(new_idx, 'int')
self._tabs.setUpdatesEnabled(False)
tabbed_browser.setUpdatesEnabled(False)
try:
self._tabs.removeTab(cur_idx)
self._tabs.insertTab(new_idx, tab, icon, label)
self._tabs.setCurrentIndex(new_idx)
tabbed_browser.removeTab(cur_idx)
tabbed_browser.insertTab(new_idx, tab, icon, label)
self._set_current_index(new_idx)
finally:
self._tabs.setUpdatesEnabled(True)
tabbed_browser.setUpdatesEnabled(True)
@cmdutils.register(instance='mainwindow.tabs.cmd', split=False)
@cmdutils.register(instance='command-dispatcher', split=False)
def spawn(self, *args):
"""Spawn a command in a shell.
@ -668,12 +654,12 @@ class CommandDispatcher:
log.procs.debug("Executing: {}".format(args))
subprocess.Popen(args)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def home(self):
"""Open main startpage in current tab."""
self.openurl(config.get('general', 'startpage')[0])
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def run_userscript(self, cmd, *args: {'nargs': '*'}):
"""Run an userscript given as argument.
@ -681,15 +667,14 @@ class CommandDispatcher:
cmd: The userscript to run.
args: Arguments to pass to the userscript.
"""
url = self._tabs.current_url()
userscripts.run(cmd, *args, url=url)
userscripts.run(cmd, *args, url=self._current_url())
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def quickmark_save(self):
"""Save the current page as a quickmark."""
quickmarks.prompt_save(self._tabs.current_url())
quickmarks.prompt_save(self._current_url())
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def quickmark_load(self, name, tab=False, bg=False):
"""Load a quickmark.
@ -705,7 +690,7 @@ class CommandDispatcher:
urlstr, url.errorString()))
self._open(url, tab, bg)
@cmdutils.register(instance='mainwindow.tabs.cmd', name='inspector')
@cmdutils.register(instance='command-dispatcher', name='inspector')
def toggle_inspector(self):
"""Toggle the web inspector."""
cur = self._current_widget()
@ -727,13 +712,13 @@ class CommandDispatcher:
else:
cur.inspector.show()
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def download_page(self):
"""Download the current page."""
page = self._current_widget().page()
self._tabs.download_get.emit(self._tabs.current_url(), page)
objreg.get('download-manager').get(self._current_url(), page)
@cmdutils.register(instance='mainwindow.tabs.cmd')
@cmdutils.register(instance='command-dispatcher')
def view_source(self):
"""Show the source of the current page."""
# pylint doesn't seem to like pygments...
@ -742,17 +727,16 @@ class CommandDispatcher:
if widget.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
frame = widget.page().currentFrame()
url = self._tabs.current_url()
html = frame.toHtml()
lexer = pygments.lexers.HtmlLexer()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
highlighted = pygments.highlight(html, lexer, formatter)
tab = self._tabs.tabopen(explicit=True)
tab.setHtml(highlighted, url)
tab = objreg.get('tabbed-browser').tabopen(explicit=True)
tab.setHtml(highlighted, self._current_url())
tab.viewing_source = True
@cmdutils.register(instance='mainwindow.tabs.cmd', name='help',
@cmdutils.register(instance='command-dispatcher', name='help',
completion=[usertypes.Completion.helptopic])
def show_help(self, topic=None):
r"""Show help about a command or setting.
@ -789,7 +773,7 @@ class CommandDispatcher:
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
self.openurl('qute://help/{}'.format(path))
@cmdutils.register(instance='mainwindow.tabs.cmd',
@cmdutils.register(instance='command-dispatcher',
modes=[usertypes.KeyMode.insert],
hide=True)
def open_editor(self):
@ -815,7 +799,7 @@ class CommandDispatcher:
text = str(elem)
else:
text = elem.evaluateJavaScript('this.value')
self._editor = editor.ExternalEditor(self._tabs)
self._editor = editor.ExternalEditor(objreg.get('tabbed-browser'))
self._editor.editing_finished.connect(
partial(self.on_editing_finished, elem))
self._editor.edit(text)

View File

@ -40,6 +40,10 @@ class CookieJar(QNetworkCookieJar):
cookies += QNetworkCookie.parseCookies(line)
self.setAllCookies(cookies)
def __repr__(self):
return '<{} count={}>'.format(
self.__class__.__name__, len(self.allCookies()))
def purge_old_cookies(self):
"""Purge expired cookies from the cookie jar."""
# Based on:

View File

@ -29,7 +29,8 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, http, usertypes, log, utils, qtutils
from qutebrowser.utils import (message, http, usertypes, log, utils, qtutils,
objreg)
class DownloadItem(QObject):
@ -42,15 +43,15 @@ class DownloadItem(QObject):
estimate the remaining time.
Attributes:
reply: The QNetworkReply associated with this download.
bytes_done: How many bytes there are already downloaded.
bytes_total: The total count of bytes.
None if the total is unknown.
speed: The current download speed, in bytes per second.
fileobj: The file object to download the file to.
filename: The filename of the download.
is_cancelled: Whether the download was cancelled.
speed_avg: A rolling average of speeds.
_bytes_done: How many bytes there are already downloaded.
_bytes_total: The total count of bytes.
None if the total is unknown.
_speed: The current download speed, in bytes per second.
_fileobj: The file object to download the file to.
_filename: The filename of the download.
_is_cancelled: Whether the download was cancelled.
_speed_avg: A rolling average of speeds.
_reply: The QNetworkReply associated with this download.
_last_done: The count of bytes which where downloaded when calculating
the speed the last time.
@ -76,18 +77,18 @@ class DownloadItem(QObject):
reply: The QNetworkReply to download.
"""
super().__init__(parent)
self.reply = reply
self.bytes_total = None
self.speed = 0
self._reply = reply
self._bytes_total = None
self._speed = 0
self.basename = '???'
samples = int(self.SPEED_AVG_WINDOW *
(1000 / self.SPEED_REFRESH_INTERVAL))
self.speed_avg = collections.deque(maxlen=samples)
self.fileobj = None
self.filename = None
self.is_cancelled = False
self._speed_avg = collections.deque(maxlen=samples)
self._fileobj = None
self._filename = None
self._is_cancelled = False
self._do_delayed_write = False
self.bytes_done = 0
self._bytes_done = 0
self._last_done = 0
reply.setReadBufferSize(16 * 1024 * 1024)
reply.downloadProgress.connect(self.on_download_progress)
@ -114,11 +115,11 @@ class DownloadItem(QObject):
Example: foo.pdf [699.2kB/s|0.34|16%|4.253/25.124]
"""
speed = utils.format_size(self.speed, suffix='B/s')
down = utils.format_size(self.bytes_done, suffix='B')
speed = utils.format_size(self._speed, suffix='B/s')
down = utils.format_size(self._bytes_done, suffix='B')
perc = self._percentage()
remaining = self._remaining_time()
if all(e is None for e in (perc, remaining, self.bytes_total)):
if all(e is None for e in (perc, remaining, self._bytes_total)):
return ('{name} [{speed:>10}|{down}]'.format(
name=self.basename, speed=speed, down=down))
if perc is None:
@ -129,7 +130,7 @@ class DownloadItem(QObject):
remaining = '?'
else:
remaining = utils.format_seconds(remaining)
total = utils.format_size(self.bytes_total, suffix='B')
total = utils.format_size(self._bytes_total, suffix='B')
return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
'{down}/{total}]'.format(name=self.basename, speed=speed,
remaining=remaining, perc=perc,
@ -137,36 +138,36 @@ class DownloadItem(QObject):
def _die(self, msg):
"""Abort the download and emit an error."""
self.reply.downloadProgress.disconnect()
self.reply.finished.disconnect()
self.reply.error.disconnect()
self.reply.readyRead.disconnect()
self.bytes_done = self.bytes_total
self._reply.downloadProgress.disconnect()
self._reply.finished.disconnect()
self._reply.error.disconnect()
self._reply.readyRead.disconnect()
self._bytes_done = self._bytes_total
self.timer.stop()
self.error.emit(msg)
self.reply.abort()
self.reply.deleteLater()
if self.fileobj is not None:
self._reply.abort()
self._reply.deleteLater()
if self._fileobj is not None:
try:
self.fileobj.close()
self._fileobj.close()
except OSError as e:
self.error.emit(e.strerror)
self.finished.emit()
def _percentage(self):
"""The current download percentage, or None if unknown."""
if self.bytes_total == 0 or self.bytes_total is None:
if self._bytes_total == 0 or self._bytes_total is None:
return None
else:
return 100 * self.bytes_done / self.bytes_total
return 100 * self._bytes_done / self._bytes_total
def _remaining_time(self):
"""The remaining download time in seconds, or None."""
if self.bytes_total is None or not self.speed_avg:
if self._bytes_total is None or not self._speed_avg:
# No average yet or we don't know the total size.
return None
remaining_bytes = self.bytes_total - self.bytes_done
avg = sum(self.speed_avg) / len(self.speed_avg)
remaining_bytes = self._bytes_total - self._bytes_done
avg = sum(self._speed_avg) / len(self._speed_avg)
if avg == 0:
# Download stalled
return None
@ -188,13 +189,13 @@ class DownloadItem(QObject):
"""Cancel the download."""
log.downloads.debug("cancelled")
self.cancelled.emit()
self.is_cancelled = True
self.reply.abort()
self.reply.deleteLater()
if self.fileobj is not None:
self.fileobj.close()
if self.filename is not None and os.path.exists(self.filename):
os.remove(self.filename)
self._is_cancelled = True
self._reply.abort()
self._reply.deleteLater()
if self._fileobj is not None:
self._fileobj.close()
if self._filename is not None and os.path.exists(self._filename):
os.remove(self._filename)
self.finished.emit()
def set_filename(self, filename):
@ -204,19 +205,19 @@ class DownloadItem(QObject):
filename: The full filename to save the download to.
None: special value to stop the download.
"""
if self.filename is not None:
if self._filename is not None:
raise ValueError("Filename was already set! filename: {}, "
"existing: {}".format(filename, self.filename))
"existing: {}".format(filename, self._filename))
filename = os.path.expanduser(filename)
if os.path.isabs(filename) and os.path.isdir(filename):
# We got an absolute directory from the user, so we save it under
# the default filename in that directory.
self.filename = os.path.join(filename, self.basename)
self._filename = os.path.join(filename, self.basename)
elif os.path.isabs(filename):
# We got an absolute filename from the user, so we save it under
# that filename.
self.filename = filename
self.basename = os.path.basename(self.filename)
self._filename = filename
self.basename = os.path.basename(self._filename)
else:
# We only got a filename (without directory) from the user, so we
# save it under that filename in the default directory.
@ -224,11 +225,11 @@ class DownloadItem(QObject):
if download_dir is None:
download_dir = utils.get_standard_dir(
QStandardPaths.DownloadLocation)
self.filename = os.path.join(download_dir, filename)
self._filename = os.path.join(download_dir, filename)
self.basename = filename
log.downloads.debug("Setting filename to {}".format(filename))
try:
self.fileobj = open(self.filename, 'wb')
self._fileobj = open(self._filename, 'wb')
if self._do_delayed_write:
# Downloading to the buffer in RAM has already finished so we
# write out the data and clean up now.
@ -245,10 +246,10 @@ class DownloadItem(QObject):
"""Write buffered data to disk and finish the QNetworkReply."""
log.downloads.debug("Doing delayed write...")
self._do_delayed_write = False
self.fileobj.write(self.reply.readAll())
self.fileobj.close()
self.reply.close()
self.reply.deleteLater()
self._fileobj.write(self._reply.readAll())
self._fileobj.close()
self._reply.close()
self._reply.deleteLater()
self.finished.emit()
log.downloads.debug("Download finished")
@ -262,8 +263,8 @@ class DownloadItem(QObject):
"""
if bytes_total == -1:
bytes_total = None
self.bytes_done = bytes_done
self.bytes_total = bytes_total
self._bytes_done = bytes_done
self._bytes_total = bytes_total
self.data_changed.emit()
@pyqtSlot()
@ -274,12 +275,12 @@ class DownloadItem(QObject):
doesn't mean the download (i.e. writing data to the disk) is finished
as well. Therefore, we can't close() the QNetworkReply in here yet.
"""
self.bytes_done = self.bytes_total
self._bytes_done = self._bytes_total
self.timer.stop()
if self.is_cancelled:
if self._is_cancelled:
return
log.downloads.debug("Reply finished, fileobj {}".format(self.fileobj))
if self.fileobj is None:
log.downloads.debug("Reply finished, fileobj {}".format(self._fileobj))
if self._fileobj is None:
# We'll handle emptying the buffer and cleaning up as soon as the
# filename is set.
self._do_delayed_write = True
@ -291,11 +292,11 @@ class DownloadItem(QObject):
@pyqtSlot()
def on_ready_read(self):
"""Read available data and save file when ready to read."""
if self.fileobj is None:
if self._fileobj is None:
# No filename has been set yet, so we don't empty the buffer.
return
try:
self.fileobj.write(self.reply.readAll())
self._fileobj.write(self._reply.readAll())
except OSError as e:
self._die(e.strerror)
@ -305,15 +306,15 @@ class DownloadItem(QObject):
if code == QNetworkReply.OperationCanceledError:
return
else:
self.error.emit(self.reply.errorString())
self.error.emit(self._reply.errorString())
@pyqtSlot()
def update_speed(self):
"""Recalculate the current download speed."""
delta = self.bytes_done - self._last_done
self.speed = delta * 1000 / self.SPEED_REFRESH_INTERVAL
self.speed_avg.append(self.speed)
self._last_done = self.bytes_done
delta = self._bytes_done - self._last_done
self._speed = delta * 1000 / self.SPEED_REFRESH_INTERVAL
self._speed_avg.append(self._speed)
self._last_done = self._bytes_done
self.data_changed.emit()
@ -363,7 +364,7 @@ class DownloadManager(QObject):
reply = page.networkAccessManager().get(req)
self.fetch(reply)
@cmdutils.register(instance='downloadmanager')
@cmdutils.register(instance='download-manager')
def cancel_download(self, count=1):
"""Cancel the first/[count]th download.
@ -409,7 +410,7 @@ class DownloadManager(QObject):
q.destroyed.connect(functools.partial(self.questions.remove, q))
self.questions.append(q)
download.cancelled.connect(q.abort)
message.instance().ask(q, blocking=False)
objreg.get('message-bridge').ask(q, blocking=False)
@pyqtSlot(DownloadItem)
def on_finished(self, download):

View File

@ -30,8 +30,8 @@ from PyQt5.QtWidgets import QApplication
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc
from qutebrowser.utils import usertypes, log, qtutils, message
from qutebrowser.commands import userscripts, cmdexc, cmdutils
from qutebrowser.utils import usertypes, log, qtutils, message, objreg
ElemTuple = collections.namedtuple('ElemTuple', 'elem, label')
@ -95,17 +95,9 @@ class HintManager(QObject):
_context: The HintContext for the current invocation.
Signals:
hint_strings_updated: Emitted when the possible hint strings changed.
arg: A list of hint strings.
mouse_event: Mouse event to be posted in the web view.
arg: A QMouseEvent
openurl: Open a new URL
arg 0: URL to open as QUrl.
arg 1: True if it should be opened in a new tab, else False.
set_open_target: Set a new target to open the links in.
download_get: Download an URL.
arg 0: The URL to download, as QUrl.
arg 1: The QWebPage to download the URL in.
"""
HINT_CSS = """
@ -135,11 +127,8 @@ class HintManager(QObject):
Target.spawn: "Spawn command via hint...",
}
hint_strings_updated = pyqtSignal(list)
mouse_event = pyqtSignal('QMouseEvent')
openurl = pyqtSignal('QUrl', bool)
set_open_target = pyqtSignal(str)
download_get = pyqtSignal('QUrl', 'QWebPage')
def __init__(self, parent=None):
"""Constructor.
@ -149,8 +138,8 @@ class HintManager(QObject):
"""
super().__init__(parent)
self._context = None
modeman.instance().left.connect(self.on_mode_left)
modeman.instance().entered.connect(self.on_mode_entered)
objreg.get('mode-manager').left.connect(self.on_mode_left)
objreg.get('mode-manager').entered.connect(self.on_mode_entered)
def _cleanup(self):
"""Clean up after hinting."""
@ -160,7 +149,7 @@ class HintManager(QObject):
except webelem.IsNullError:
pass
text = self.HINT_TEXTS[self._context.target]
message.instance().maybe_reset_text(text)
objreg.get('message-bridge').maybe_reset_text(text)
self._context = None
def _hint_strings(self, elems):
@ -270,8 +259,9 @@ class HintManager(QObject):
else:
display = 'none'
rect = elem.geometry()
return self.HINT_CSS.format(left=rect.x(), top=rect.y(),
config=config.instance(), display=display)
return self.HINT_CSS.format(
left=rect.x(), top=rect.y(), config=objreg.get('config'),
display=display)
def _draw_label(self, elem, string):
"""Draw a hint label over an element.
@ -361,7 +351,7 @@ class HintManager(QObject):
immediately=True)
return
qtutils.ensure_valid(url)
self.download_get.emit(url, elem.webFrame().page())
objreg.get('download-manager').get(url, elem.webFrame().page())
def _call_userscript(self, url):
"""Call an userscript from a hint."""
@ -494,7 +484,8 @@ class HintManager(QObject):
for e, string in zip(elems, strings):
label = self._draw_label(e, string)
self._context.elems[string] = ElemTuple(e, label)
self.hint_strings_updated.emit(strings)
keyparser = objreg.get('keyparsers')[usertypes.KeyMode.hint]
keyparser.update_bindings(strings)
def follow_prevnext(self, frame, baseurl, prev=False, newtab=False):
"""Click a "previous"/"next" element on the page.
@ -513,35 +504,64 @@ class HintManager(QObject):
if url is None:
raise cmdexc.CommandError("No {} links found!".format(
"prev" if prev else "forward"))
self.openurl.emit(url, newtab)
qtutils.ensure_valid(url)
if newtab:
objreg.get('tabbed-browser').tabopen(url, background=False)
else:
objreg.get('webview', scope='tab').openurl(url)
def start(self, mainframe, baseurl, group=webelem.Group.all,
target=Target.normal, *args):
@cmdutils.register(instance='hintmanager', scope='tab', name='hint')
def start(self, group=webelem.Group.all, target=Target.normal,
*args: {'nargs': '*'}):
"""Start hinting.
Args:
mainframe: The main QWebFrame.
baseurl: URL of the current page.
group: Which group of elements to hint.
target: What to do with the link. See attribute docstring.
*args: Arguments for userscript/download
group: The hinting mode to use.
Emit:
hint_strings_updated: Emitted to update keypraser.
- `all`: All clickable elements.
- `links`: Only links.
- `images`: Only images.
target: What to do with the selected element.
- `normal`: Open the link in the current tab.
- `tab`: Open the link in a new tab.
- `tab-bg`: Open the link in a new background tab.
- `yank`: Yank the link to the clipboard.
- `yank-primary`: Yank the link to the primary selection.
- `fill`: Fill the commandline with the command given as
argument.
- `rapid`: Open the link in a new tab and stay in hinting mode.
- `download`: Download the link.
- `userscript`: Call an userscript with `$QUTE_URL` set to the
link.
- `spawn`: Spawn a command.
*args: Arguments for spawn/userscript/fill.
- With `spawn`: The executable and arguments to spawn.
`{hint-url}` will get replaced by the selected
URL.
- With `userscript`: The userscript to execute.
- With `fill`: The command to fill the statusbar with.
`{hint-url}` will get replaced by the selected
URL.
"""
self._check_args(target, *args)
tabbed_browser = objreg.get('tabbed-browser')
widget = tabbed_browser.currentWidget()
if widget is None:
raise cmdexc.CommandError("No WebView available yet!")
mainframe = widget.page().mainFrame()
if mainframe is None:
# This should never happen since we check frame before calling
# start. But since we had a bug where frame is None in
# on_mode_left, we are extra careful here.
raise ValueError("start() was called with frame=None")
raise cmdexc.CommandError("No frame focused!")
self._check_args(target, *args)
self._context = HintContext()
self._context.target = target
self._context.baseurl = baseurl
self._context.baseurl = tabbed_browser.current_url()
self._context.frames = webelem.get_child_frames(mainframe)
self._context.args = args
self._init_elements(mainframe, group)
message.instance().set_text(self.HINT_TEXTS[target])
objreg.get('message-bridge').set_text(self.HINT_TEXTS[target])
self._connect_frame_signals()
try:
modeman.enter(usertypes.KeyMode.hint, 'HintManager.start')
@ -636,6 +656,7 @@ class HintManager(QObject):
if self._context.target != Target.rapid:
modeman.maybe_leave(usertypes.KeyMode.hint, 'followed')
@cmdutils.register(instance='hintmanager', scope='tab', hide=True)
def follow_hint(self):
"""Follow the currently selected hint."""
if not self._context.to_follow:

View File

@ -23,7 +23,7 @@ import functools
from PyQt5.QtCore import QObject
from qutebrowser.utils import debug, log
from qutebrowser.utils import debug, log, objreg
from qutebrowser.widgets import webview
@ -34,19 +34,12 @@ class SignalFilter(QObject):
Signals are only passed to the parent TabbedBrowser if they originated in
the currently shown widget.
Attributes:
_tabs: The QTabWidget associated with this SignalFilter.
Class attributes:
BLACKLIST: List of signal names which should not be logged.
"""
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress']
def __init__(self, tabs):
super().__init__(tabs)
self._tabs = tabs
def create(self, signal, tab):
"""Factory for partial _filter_signals functions.
@ -80,12 +73,13 @@ class SignalFilter(QObject):
The target signal if the sender was the current widget.
"""
log_signal = debug.signal_name(signal) not in self.BLACKLIST
tabbed_browser = objreg.get('tabbed-browser')
try:
tabidx = self._tabs.indexOf(tab)
tabidx = tabbed_browser.indexOf(tab)
except RuntimeError:
# The tab has been deleted already
return
if tabidx == self._tabs.currentIndex():
if tabidx == tabbed_browser.currentIndex():
if log_signal:
log.signals.debug("emitting: {} (tab {})".format(
debug.dbg_signal(signal, args), tabidx))

View File

@ -31,7 +31,7 @@ from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
from qutebrowser.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, http, jinja, qtutils,
utils)
utils, objreg)
class BrowserPage(QWebPage):
@ -40,7 +40,6 @@ class BrowserPage(QWebPage):
Attributes:
_extension_handlers: Mapping of QWebPage extensions to their handlers.
_view: The QWebView associated with this page.
_networkmnager: The NetworkManager used.
Signals:
@ -51,8 +50,8 @@ class BrowserPage(QWebPage):
start_download = pyqtSignal('QNetworkReply*')
change_title = pyqtSignal(str)
def __init__(self, view):
super().__init__(view)
def __init__(self, parent=None):
super().__init__(parent)
self._extension_handlers = {
QWebPage.ErrorPageExtension: self._handle_errorpage,
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
@ -63,7 +62,6 @@ class BrowserPage(QWebPage):
self.printRequested.connect(self.on_print_requested)
self.downloadRequested.connect(self.on_download_requested)
self.unsupportedContent.connect(self.on_unsupported_content)
self._view = view
if PYQT_VERSION > 0x050300:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
@ -300,11 +298,11 @@ class BrowserPage(QWebPage):
message.error("Invalid link {} clicked!".format(urlstr))
log.webview.debug(url.errorString())
return False
if self._view.open_target == usertypes.ClickTarget.tab:
self._view.tabbedbrowser.tabopen(url, False)
if self.view().open_target == usertypes.ClickTarget.tab:
objreg.get('tabbed-browser').tabopen(url, False)
return False
elif self._view.open_target == usertypes.ClickTarget.tab_bg:
self._view.tabbedbrowser.tabopen(url, True)
elif self.view().open_target == usertypes.ClickTarget.tab_bg:
objreg.get('tabbed-browser').tabopen(url, True)
return False
else:
self.change_title.emit(urlstr)

View File

@ -22,10 +22,10 @@
import argparse
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtCore import QUrl
from qutebrowser.commands import cmdexc
from qutebrowser.utils import utils
from qutebrowser.utils import utils, objreg
SUPPRESS = argparse.SUPPRESS
@ -38,7 +38,11 @@ class ArgumentParserError(Exception):
class ArgumentParserExit(Exception):
"""Exception raised when the argument parser exitted."""
"""Exception raised when the argument parser exitted.
Attributes:
status: The exit status.
"""
def __init__(self, status, msg):
self.status = status
@ -54,14 +58,18 @@ class HelpAction(argparse.Action):
"""
def __call__(self, parser, _namespace, _values, _option_string=None):
QCoreApplication.instance().mainwindow.tabs.tabopen(
objreg.get('tabbed-browser').tabopen(
QUrl('qute://help/commands.html#{}'.format(parser.name)))
parser.exit()
class ArgumentParser(argparse.ArgumentParser):
"""Subclass ArgumentParser to be more suitable for runtime parsing."""
"""Subclass ArgumentParser to be more suitable for runtime parsing.
Attributes:
name: The command name.
"""
def __init__(self, name, *args, **kwargs):
self.name = name

View File

@ -91,21 +91,22 @@ class register: # pylint: disable=invalid-name
much cleaner to implement.
Attributes:
instance: The instance to be used as "self", as a dotted string.
name: The name (as string) or names (as list) of the command.
split: Whether to split the arguments.
hide: Whether to hide the command or not.
completion: Which completion to use for arguments, as a list of
strings.
modes/not_modes: List of modes to use/not use.
needs_js: If javascript is needed for this command.
debug: Whether this is a debugging command (only shown with --debug).
ignore_args: Whether to ignore the arguments of the function.
_instance: The object from the object registry to be used as "self".
_scope: The scope to get _instance for.
_name: The name (as string) or names (as list) of the command.
_split: Whether to split the arguments.
_hide: Whether to hide the command or not.
_completion: Which completion to use for arguments, as a list of
strings.
_modes/_not_modes: List of modes to use/not use.
_needs_js: If javascript is needed for this command.
_debug: Whether this is a debugging command (only shown with --debug).
_ignore_args: Whether to ignore the arguments of the function.
"""
def __init__(self, instance=None, name=None, split=True, hide=False,
completion=None, modes=None, not_modes=None, needs_js=False,
debug=False, ignore_args=False):
debug=False, ignore_args=False, scope='global'):
"""Save decorator arguments.
Gets called on parse-time with the decorator arguments.
@ -116,16 +117,17 @@ class register: # pylint: disable=invalid-name
# pylint: disable=too-many-arguments
if modes is not None and not_modes is not None:
raise ValueError("Only modes or not_modes can be given!")
self.name = name
self.split = split
self.hide = hide
self.instance = instance
self.completion = completion
self.modes = modes
self.not_modes = not_modes
self.needs_js = needs_js
self.debug = debug
self.ignore_args = ignore_args
self._name = name
self._split = split
self._hide = hide
self._instance = instance
self._scope = scope
self._completion = completion
self._modes = modes
self._not_modes = not_modes
self._needs_js = needs_js
self._debug = debug
self._ignore_args = ignore_args
if modes is not None:
for m in modes:
if not isinstance(m, usertypes.KeyMode):
@ -150,12 +152,12 @@ class register: # pylint: disable=invalid-name
Return:
A list of names, with the main name being the first item.
"""
if self.name is None:
if self._name is None:
return [func.__name__.lower().replace('_', '-')]
elif isinstance(self.name, str):
return [self.name]
elif isinstance(self._name, str):
return [self._name]
else:
return self.name
return self._name
def __call__(self, func):
"""Register the command before running the function.
@ -178,10 +180,11 @@ class register: # pylint: disable=invalid-name
if name in cmd_dict:
raise ValueError("{} is already registered!".format(name))
cmd = command.Command(
name=names[0], split=self.split, hide=self.hide,
instance=self.instance, completion=self.completion,
modes=self.modes, not_modes=self.not_modes, needs_js=self.needs_js,
is_debug=self.debug, ignore_args=self.ignore_args, handler=func)
name=names[0], split=self._split, hide=self._hide,
instance=self._instance, scope=self._scope,
completion=self._completion, modes=self._modes,
not_modes=self._not_modes, needs_js=self._needs_js,
is_debug=self._debug, ignore_args=self._ignore_args, handler=func)
for name in names:
cmd_dict[name] = cmd
aliases += names[1:]

View File

@ -22,11 +22,11 @@
import inspect
import collections
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import log, utils, message, debug, usertypes, docutils
from qutebrowser.utils import (log, utils, message, debug, usertypes, docutils,
objreg)
class Command:
@ -37,17 +37,19 @@ class Command:
name: The main name of the command.
split: Whether to split the arguments.
hide: Whether to hide the arguments or not.
count: Whether the command supports a count, or not.
desc: The description of the command.
instance: How to get to the "self" argument of the handler.
A dotted string as viewed from app.py, or None.
handler: The handler function to call.
completion: Completions to use for arguments, as a list of strings.
needs_js: Whether the command needs javascript enabled
debug: Whether this is a debugging command (only shown with --debug).
parser: The ArgumentParser to use to parse this command.
type_conv: A mapping of conversion functions for arguments.
name_conv: A mapping of argument names to parameter names.
_type_conv: A mapping of conversion functions for arguments.
_name_conv: A mapping of argument names to parameter names.
_needs_js: Whether the command needs javascript enabled
_modes: The modes the command can be executed in.
_not_modes: The modes the command can not be executed in.
_count: Whether the command supports a count, or not.
_instance: The object to bind 'self' to.
_scope: The scope to get _instance for in the object registry.
Class attributes:
AnnotationInfo: Named tuple for info from an annotation.
@ -60,17 +62,18 @@ class Command:
def __init__(self, name, split, hide, instance, completion, modes,
not_modes, needs_js, is_debug, ignore_args,
handler):
handler, scope):
# I really don't know how to solve this in a better way, I tried.
# pylint: disable=too-many-arguments,too-many-locals
self.name = name
self.split = split
self.hide = hide
self.instance = instance
self._instance = instance
self.completion = completion
self.modes = modes
self.not_modes = not_modes
self.needs_js = needs_js
self._modes = modes
self._not_modes = not_modes
self._scope = scope
self._needs_js = needs_js
self.debug = is_debug
self.ignore_args = ignore_args
self.handler = handler
@ -84,13 +87,13 @@ class Command:
self._check_func()
self.opt_args = collections.OrderedDict()
self.namespace = None
self.count = None
self._count = None
self.pos_args = []
has_count, desc, type_conv, name_conv = self._inspect_func()
self.has_count = has_count
self.desc = desc
self.type_conv = type_conv
self.name_conv = name_conv
self._type_conv = type_conv
self._name_conv = name_conv
def _check_prerequisites(self):
"""Check if the command is permitted to run currently.
@ -98,20 +101,18 @@ class Command:
Raise:
PrerequisitesError if the command can't be called currently.
"""
# We don't use modeman.instance() here to avoid a circular import
# of qutebrowser.keyinput.modeman.
curmode = QCoreApplication.instance().modeman.mode()
if self.modes is not None and curmode not in self.modes:
mode_names = '/'.join(mode.name for mode in self.modes)
curmode = objreg.get('mode-manager').mode()
if self._modes is not None and curmode not in self._modes:
mode_names = '/'.join(mode.name for mode in self._modes)
raise cmdexc.PrerequisitesError(
"{}: This command is only allowed in {} mode.".format(
self.name, mode_names))
elif self.not_modes is not None and curmode in self.not_modes:
mode_names = '/'.join(mode.name for mode in self.not_modes)
elif self._not_modes is not None and curmode in self._not_modes:
mode_names = '/'.join(mode.name for mode in self._not_modes)
raise cmdexc.PrerequisitesError(
"{}: This command is not allowed in {} mode.".format(
self.name, mode_names))
if self.needs_js and not QWebSettings.globalSettings().testAttribute(
if self._needs_js and not QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled):
raise cmdexc.PrerequisitesError(
"{}: This command needs javascript enabled.".format(self.name))
@ -119,10 +120,10 @@ class Command:
def _check_func(self):
"""Make sure the function parameters don't violate any rules."""
signature = inspect.signature(self.handler)
if 'self' in signature.parameters and self.instance is None:
if 'self' in signature.parameters and self._instance is None:
raise TypeError("{} is a class method, but instance was not "
"given!".format(self.name[0]))
elif 'self' not in signature.parameters and self.instance is not None:
elif 'self' not in signature.parameters and self._instance is not None:
raise TypeError("{} is not a class method, but instance was "
"given!".format(self.name[0]))
elif inspect.getfullargspec(self.handler).varkw is not None:
@ -301,11 +302,7 @@ class Command:
args: The positional argument list. Gets modified directly.
"""
assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
app = QCoreApplication.instance()
if self.instance == '':
obj = app
else:
obj = utils.dotted_getattr(app, self.instance)
obj = objreg.get(self._instance, scope=self._scope)
args.append(obj)
def _get_count_arg(self, param, args, kwargs):
@ -320,27 +317,27 @@ class Command:
raise TypeError("{}: count argument given with a command which "
"does not support count!".format(self.name))
if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
if self.count is not None:
args.append(self.count)
if self._count is not None:
args.append(self._count)
else:
args.append(param.default)
elif param.kind == inspect.Parameter.KEYWORD_ONLY:
if self.count is not None:
kwargs['count'] = self.count
if self._count is not None:
kwargs['count'] = self._count
else:
raise TypeError("{}: invalid parameter type {} for argument "
"'count'!".format(self.name, param.kind))
def _get_param_name_and_value(self, param):
"""Get the converted name and value for an inspect.Parameter."""
name = self.name_conv.get(param.name, param.name)
name = self._name_conv.get(param.name, param.name)
value = getattr(self.namespace, name)
if param.name in self.type_conv:
if param.name in self._type_conv:
# We convert enum types after getting the values from
# argparse, because argparse's choices argument is
# processed after type conversation, which is not what we
# want.
value = self.type_conv[param.name](value)
value = self._type_conv[param.name](value)
return name, value
def _get_call_args(self):
@ -355,13 +352,13 @@ class Command:
signature = inspect.signature(self.handler)
if self.ignore_args:
if self.instance is not None:
if self._instance is not None:
param = list(signature.parameters.values())[0]
self._get_self_arg(param, args)
return args, kwargs
for i, param in enumerate(signature.parameters.values()):
if i == 0 and self.instance is not None:
if i == 0 and self._instance is not None:
# Special case for 'self'.
self._get_self_arg(param, args)
continue
@ -407,7 +404,7 @@ class Command:
log.commands.debug("argparser exited with status {}: {}".format(
e.status, e))
return
self.count = count
self._count = count
posargs, kwargs = self._get_call_args()
self._check_prerequisites()
log.commands.debug('Calling {}'.format(

View File

@ -19,12 +19,12 @@
"""Module containing command managers (SearchRunner and CommandRunner)."""
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QCoreApplication, QUrl
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, log, utils
from qutebrowser.utils import message, log, utils, objreg
def replace_variables(arglist):
@ -32,8 +32,7 @@ def replace_variables(arglist):
args = []
for arg in arglist:
if arg == '{url}':
app = QCoreApplication.instance()
url = app.mainwindow.tabs.current_url().toString(
url = objreg.get('tabbed-browser').current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
args.append(url)
else:
@ -115,7 +114,7 @@ class SearchRunner(QObject):
"""
self._search(text, rev=True)
@cmdutils.register(instance='searchrunner', hide=True)
@cmdutils.register(instance='search-runner', hide=True)
def search_next(self, count=1):
"""Continue the search to the ([count]th) next term.
@ -129,7 +128,7 @@ class SearchRunner(QObject):
for _ in range(count):
self.do_search.emit(self._text, self._flags)
@cmdutils.register(instance='searchrunner', hide=True)
@cmdutils.register(instance='search-runner', hide=True)
def search_prev(self, count=1):
"""Continue the search to the ([count]th) previous term.

View File

@ -51,7 +51,7 @@ class _BlockingFIFOReader(QObject):
was requested.
Attributes:
filepath: The filename of the FIFO to read.
_filepath: The filename of the FIFO to read.
fifo: The file object which is being read.
Signals:
@ -65,7 +65,7 @@ class _BlockingFIFOReader(QObject):
def __init__(self, filepath, parent=None):
super().__init__(parent)
self.filepath = filepath
self._filepath = filepath
self.fifo = None
def read(self):
@ -74,7 +74,7 @@ class _BlockingFIFOReader(QObject):
# See http://www.outflux.net/blog/archives/2008/03/09/using-select-on-a-fifo/
# We also use os.open and os.fdopen rather than built-in open so we can
# add O_NONBLOCK.
fd = os.open(self.filepath, os.O_RDWR |
fd = os.open(self._filepath, os.O_RDWR |
os.O_NONBLOCK, # pylint: disable=no-member
encoding='utf-8')
self.fifo = os.fdopen(fd, 'r')
@ -96,8 +96,8 @@ class _BaseUserscriptRunner(QObject):
"""Common part between the Windows and the POSIX userscript runners.
Attributes:
filepath: The path of the file/FIFO which is being read.
proc: The QProcess which is being executed.
_filepath: The path of the file/FIFO which is being read.
_proc: The QProcess which is being executed.
Class attributes:
PROCESS_MESSAGES: A mapping of QProcess::ProcessError members to
@ -124,8 +124,8 @@ class _BaseUserscriptRunner(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.filepath = None
self.proc = None
self._filepath = None
self._proc = None
def _run_process(self, cmd, *args, env):
"""Start the given command via QProcess.
@ -135,27 +135,27 @@ class _BaseUserscriptRunner(QObject):
*args: The arguments to hand to the command
env: A dictionary of environment variables to add.
"""
self.proc = QProcess(self)
self._proc = QProcess(self)
procenv = QProcessEnvironment.systemEnvironment()
procenv.insert('QUTE_FIFO', self.filepath)
procenv.insert('QUTE_FIFO', self._filepath)
if env is not None:
for k, v in env.items():
procenv.insert(k, v)
self.proc.setProcessEnvironment(procenv)
self.proc.error.connect(self.on_proc_error)
self.proc.finished.connect(self.on_proc_finished)
self.proc.start(cmd, args)
self._proc.setProcessEnvironment(procenv)
self._proc.error.connect(self.on_proc_error)
self._proc.finished.connect(self.on_proc_finished)
self._proc.start(cmd, args)
def _cleanup(self):
"""Clean up the temporary file."""
try:
os.remove(self.filepath)
os.remove(self._filepath)
except PermissionError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
message.error("Failed to delete tempfile... ({})".format(e))
self.filepath = None
self.proc = None
self._filepath = None
self._proc = None
def run(self, cmd, *args, env=None):
"""Run the userscript given.
@ -192,14 +192,14 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
executed immediately when they arrive in the FIFO.
Attributes:
reader: The _BlockingFIFOReader instance.
thread: The QThread where reader runs.
_reader: The _BlockingFIFOReader instance.
_thread: The QThread where reader runs.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.reader = None
self.thread = None
self._reader = None
self._thread = None
def run(self, cmd, *args, env=None):
rundir = utils.get_standard_dir(QStandardPaths.RuntimeLocation)
@ -208,43 +208,43 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
# directory and place the FIFO there, which sucks. Since os.kfifo will
# raise an exception anyways when the path doesn't exist, it shouldn't
# be a big issue.
self.filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
os.mkfifo(self.filepath) # pylint: disable=no-member
self._filepath = tempfile.mktemp(prefix='userscript-', dir=rundir)
os.mkfifo(self._filepath) # pylint: disable=no-member
self.reader = _BlockingFIFOReader(self.filepath)
self.thread = QThread(self)
self.reader.moveToThread(self.thread)
self.reader.got_line.connect(self.got_cmd)
self.thread.started.connect(self.reader.read)
self.reader.finished.connect(self.on_reader_finished)
self.thread.finished.connect(self.on_thread_finished)
self._reader = _BlockingFIFOReader(self._filepath)
self._thread = QThread(self)
self._reader.moveToThread(self._thread)
self._reader.got_line.connect(self.got_cmd)
self._thread.started.connect(self._reader.read)
self._reader.finished.connect(self.on_reader_finished)
self._thread.finished.connect(self.on_thread_finished)
self._run_process(cmd, *args, env=env)
self.thread.start()
self._thread.start()
def on_proc_finished(self):
"""Interrupt the reader when the process finished."""
log.procs.debug("proc finished")
self.thread.requestInterruption()
self._thread.requestInterruption()
def on_proc_error(self, error):
"""Interrupt the reader when the process had an error."""
super().on_proc_error(error)
self.thread.requestInterruption()
self._thread.requestInterruption()
def on_reader_finished(self):
"""Quit the thread and clean up when the reader finished."""
log.procs.debug("reader finished")
self.thread.quit()
self.reader.fifo.close()
self.reader.deleteLater()
self._thread.quit()
self._reader.fifo.close()
self._reader.deleteLater()
super()._cleanup()
self.finished.emit()
def on_thread_finished(self):
"""Clean up the QThread object when the thread finished."""
log.procs.debug("thread finished")
self.thread.deleteLater()
self._thread.deleteLater()
class _WindowsUserscriptRunner(_BaseUserscriptRunner):
@ -258,17 +258,20 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
This also means the userscript *has* to use >> (append) rather than >
(overwrite) to write to the file!
Attributes:
_oshandle: The oshandle of the temp file.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.oshandle = None
self._oshandle = None
def _cleanup(self):
"""Clean up temporary files after the userscript finished."""
os.close(self.oshandle)
os.close(self._oshandle)
super()._cleanup()
self.oshandle = None
self._oshandle = None
def on_proc_finished(self):
"""Read back the commands when the process finished.
@ -277,7 +280,7 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
got_cmd: Emitted for every command in the file.
"""
log.procs.debug("proc finished")
with open(self.filepath, 'r', encoding='utf-8') as f:
with open(self._filepath, 'r', encoding='utf-8') as f:
for line in f:
self.got_cmd.emit(line.rstrip())
self._cleanup()
@ -290,7 +293,7 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
self.finished.emit()
def run(self, cmd, *args, env=None):
self.oshandle, self.filepath = tempfile.mkstemp(text=True)
self._oshandle, self._filepath = tempfile.mkstemp(text=True)
self._run_process(cmd, *args, env=env)

View File

@ -30,28 +30,23 @@ import functools
import configparser
import collections.abc
from PyQt5.QtCore import pyqtSignal, QObject, QCoreApplication
from PyQt5.QtCore import pyqtSignal, QObject
from qutebrowser.utils import log
from qutebrowser.config import configdata, iniparsers, configtypes, textwrapper
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message
from qutebrowser.utils import message, objreg
from qutebrowser.utils.usertypes import Completion
def instance():
"""Get the global config instance."""
return QCoreApplication.instance().config
def get(*args, **kwargs):
"""Convenience method to call get(...) of the config instance."""
return instance().get(*args, **kwargs)
return objreg.get('config').get(*args, **kwargs)
def section(sect):
"""Get a config section from the global config."""
return instance()[sect]
return objreg.get('config')[sect]
class NoSectionError(configparser.NoSectionError):

View File

@ -86,7 +86,7 @@ class BaseType:
"""A type used for a setting value.
Attributes:
none_ok: Whether to convert to None for an empty string.
_none_ok: Whether to convert to None for an empty string.
Class attributes:
valid_values: Possible values if they can be expressed as a fixed
@ -98,7 +98,7 @@ class BaseType:
valid_values = None
def __init__(self, none_ok=False):
self.none_ok = none_ok
self._none_ok = none_ok
def transform(self, value):
"""Transform the setting value.
@ -132,7 +132,7 @@ class BaseType:
NotImplementedError if self.valid_values is not defined and this
method should be overridden.
"""
if not value and self.none_ok:
if not value and self._none_ok:
return
if self.valid_values is not None:
if value not in self.valid_values:
@ -196,7 +196,7 @@ class String(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -224,7 +224,7 @@ class List(BaseType):
def validate(self, value):
vals = self.transform(value)
if None in vals:
if self.none_ok:
if self._none_ok:
pass
else:
raise ValidationError(value, "items may not be empty!")
@ -252,7 +252,7 @@ class Bool(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -290,7 +290,7 @@ class Int(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -321,7 +321,7 @@ class IntList(List):
vals = self.transform(value)
except ValueError:
raise ValidationError(value, "must be a list of integers!")
if None in vals and not self.none_ok:
if None in vals and not self._none_ok:
raise ValidationError(value, "items may not be empty!")
@ -352,7 +352,7 @@ class Float(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -395,7 +395,7 @@ class Perc(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty")
@ -442,7 +442,7 @@ class PercList(List):
try:
for val in vals:
if val is None:
if self.none_ok:
if self._none_ok:
continue
else:
raise ValidationError(value, "items may not be empty!")
@ -481,7 +481,7 @@ class PercOrInt(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -517,7 +517,7 @@ class Command(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -541,7 +541,7 @@ class ColorSystem(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -567,7 +567,7 @@ class QtColor(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -591,7 +591,7 @@ class CssColor(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -626,7 +626,7 @@ class QssColor(CssColor):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -662,7 +662,7 @@ class Font(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -729,7 +729,7 @@ class Regex(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -766,7 +766,7 @@ class RegexList(List):
except sre_constants.error as e:
raise ValidationError(value, "must be a list valid regexes - " +
str(e))
if not self.none_ok and None in vals:
if not self._none_ok and None in vals:
raise ValidationError(value, "items may not be empty!")
@ -778,7 +778,7 @@ class File(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -802,7 +802,7 @@ class Directory(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -903,7 +903,7 @@ class WebKitBytesList(List):
vals = super().transform(value)
for val in vals:
self.bytestype.validate(val)
if None in vals and not self.none_ok:
if None in vals and not self._none_ok:
raise ValidationError(value, "items may not be empty!")
if self.length is not None and len(vals) != self.length:
raise ValidationError(value, "exactly {} values need to be "
@ -926,7 +926,7 @@ class ShellCommand(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -975,7 +975,7 @@ class Proxy(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -1022,7 +1022,7 @@ class SearchEngineName(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -1034,7 +1034,7 @@ class SearchEngineUrl(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -1054,7 +1054,7 @@ class Encoding(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -1075,7 +1075,7 @@ class UserStyleSheet(File):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")
@ -1117,7 +1117,7 @@ class AutoSearch(BaseType):
def validate(self, value):
if not value:
if self.none_ok:
if self._none_ok:
return
else:
raise ValidationError(value, "may not be empty!")

View File

@ -32,6 +32,7 @@ class ReadConfigParser(configparser.ConfigParser):
Attributes:
_configdir: The directory to read the config from.
_fname: The filename of the config.
_configfile: The config file path.
"""
@ -45,12 +46,17 @@ class ReadConfigParser(configparser.ConfigParser):
super().__init__(interpolation=None, comment_prefixes='#')
self.optionxform = lambda opt: opt # be case-insensitive
self._configdir = configdir
self._fname = fname
self._configfile = os.path.join(self._configdir, fname)
if not os.path.isfile(self._configfile):
return
log.init.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile, encoding='utf-8')
def __repr__(self):
return '{}("{}", "{}")'.format(
self.__class__.__name__, self._configdir, self._fname)
class ReadWriteConfigParser(ReadConfigParser):

View File

@ -65,6 +65,7 @@ class KeyConfigParser(QObject):
"""
super().__init__(parent)
self._configdir = configdir
self._fname = fname
self._configfile = os.path.join(self._configdir, fname)
self._cur_section = None
self._cur_command = None
@ -97,6 +98,10 @@ class KeyConfigParser(QObject):
lines.append('')
return '\n'.join(lines) + '\n'
def __repr__(self):
return '{}("{}", "{}")'.format(
self.__class__.__name__, self._configdir, self._fname)
def _str_section_desc(self, sectname):
"""Get the section description string for sectname."""
wrapper = textwrapper.TextWrapper()
@ -119,7 +124,7 @@ class KeyConfigParser(QObject):
with open(self._configfile, 'w', encoding='utf-8') as f:
f.write(str(self))
@cmdutils.register(instance='keyconfig')
@cmdutils.register(instance='key-config')
def bind(self, key, *command, mode=None):
"""Bind a key to a command.
@ -144,7 +149,7 @@ class KeyConfigParser(QObject):
for m in mode.split(','):
self.changed.emit(m)
@cmdutils.register(instance='keyconfig')
@cmdutils.register(instance='key-config')
def unbind(self, key, mode=None):
"""Unbind a keychain.

View File

@ -35,6 +35,7 @@ class LineConfigParser:
data: A list of lines.
_configdir: The directory to read the config from.
_configfile: The config file path.
_fname: Filename of the config.
_binary: Whether to open the file in binary mode.
"""
@ -49,6 +50,7 @@ class LineConfigParser:
"""
self._configdir = configdir
self._configfile = os.path.join(self._configdir, fname)
self._fname = fname
self._limit = limit
self._binary = binary
if not os.path.isfile(self._configfile):
@ -57,6 +59,11 @@ class LineConfigParser:
log.init.debug("Reading config from {}".format(self._configfile))
self.read(self._configfile)
def __repr__(self):
return '{}("{}", "{}", limit={}, binary={})'.format(
self.__class__.__name__, self._configdir, self._fname, self._limit,
self._binary)
def __iter__(self):
"""Iterate over the set data."""
return self.data.__iter__()

View File

@ -25,7 +25,7 @@ import jinja2
from PyQt5.QtGui import QColor
from qutebrowser.config import config
from qutebrowser.utils import log
from qutebrowser.utils import log, objreg
@functools.lru_cache(maxsize=16)
@ -42,7 +42,7 @@ def get_stylesheet(template_str):
fontdict = FontDict(config.section('fonts'))
template = jinja2.Template(template_str)
return template.render(color=colordict, font=fontdict,
config=config.instance())
config=objreg.get('config'))
def set_register_stylesheet(obj):
@ -60,8 +60,8 @@ def set_register_stylesheet(obj):
log.style.vdebug("stylesheet for {}: {}".format(
obj.__class__.__name__, qss))
obj.setStyleSheet(qss)
config.instance().changed.connect(
functools.partial(_update_stylesheet, obj))
objreg.get('config').changed.connect(functools.partial(
_update_stylesheet, obj))
def _update_stylesheet(obj, _section, _option):

View File

@ -23,10 +23,10 @@ import re
import string
import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, QCoreApplication
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject
from qutebrowser.config import config
from qutebrowser.utils import usertypes, log, utils
from qutebrowser.utils import usertypes, log, utils, objreg
class BaseKeyParser(QObject):
@ -53,8 +53,8 @@ class BaseKeyParser(QObject):
Attributes:
bindings: Bound keybindings
special_bindings: Bound special bindings (<Foo>).
warn_on_keychains: Whether a warning should be logged when binding
keychains in a section which does not support them.
_warn_on_keychains: Whether a warning should be logged when binding
keychains in a section which does not support them.
_keystring: The currently entered key sequence
_timer: Timer for delayed execution.
_modename: The name of the input mode associated with this keyparser.
@ -83,7 +83,7 @@ class BaseKeyParser(QObject):
supports_count = supports_chains
self._supports_count = supports_count
self._supports_chains = supports_chains
self.warn_on_keychains = True
self._warn_on_keychains = True
self.bindings = {}
self.special_bindings = {}
@ -321,7 +321,7 @@ class BaseKeyParser(QObject):
self._modename = modename
self.bindings = {}
self.special_bindings = {}
keyconfparser = QCoreApplication.instance().keyconfig
keyconfparser = objreg.get('key-config')
for (key, cmd) in keyconfparser.get_bindings_for(modename).items():
if not cmd:
continue
@ -330,7 +330,7 @@ class BaseKeyParser(QObject):
self.special_bindings[keystr] = cmd
elif self._supports_chains:
self.bindings[key] = cmd
elif self.warn_on_keychains:
elif self._warn_on_keychains:
log.keyboard.warning(
"Ignoring keychain '{}' in mode '{}' because "
"keychains are not supported there.".format(key, modename))

View File

@ -29,17 +29,17 @@ class CommandKeyParser(BaseKeyParser):
"""KeyChainParser for command bindings.
Attributes:
commandrunner: CommandRunner instance.
_commandrunner: CommandRunner instance.
"""
def __init__(self, parent=None, supports_count=None,
supports_chains=False):
super().__init__(parent, supports_count, supports_chains)
self.commandrunner = runners.CommandRunner()
self._commandrunner = runners.CommandRunner()
def execute(self, cmdstr, _keytype, count=None):
try:
self.commandrunner.run(cmdstr, count)
self._commandrunner.run(cmdstr, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
message.error(e, immediately=True)
@ -65,11 +65,10 @@ class PassthroughKeyParser(CommandKeyParser):
warn: Whether to warn if an ignored key was bound.
"""
super().__init__(parent, supports_chains=False)
self.log = False
self.warn_on_keychains = warn
self._warn_on_keychains = warn
self.read_config(mode)
self._mode = mode
def __repr__(self):
return '<{} mode={}, warn={})'.format(
self.__class__.__name__, self._mode, self.warn_on_keychains)
self.__class__.__name__, self._mode, self._warn_on_keychains)

View File

@ -29,7 +29,7 @@ from PyQt5.QtWidgets import QApplication
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
class ModeLockedError(Exception):
@ -37,25 +37,20 @@ class ModeLockedError(Exception):
"""Exception raised when the mode is currently locked."""
def instance():
"""Get the global modeman instance."""
return QApplication.instance().modeman
def enter(mode, reason=None):
"""Enter the mode 'mode'."""
instance().enter(mode, reason)
objreg.get('mode-manager').enter(mode, reason)
def leave(mode, reason=None):
"""Leave the mode 'mode'."""
instance().leave(mode, reason)
objreg.get('mode-manager').leave(mode, reason)
def maybe_enter(mode, reason=None):
"""Convenience method to enter 'mode' without exceptions."""
try:
instance().enter(mode, reason)
objreg.get('mode-manager').enter(mode, reason)
except ModeLockedError:
pass
@ -63,7 +58,7 @@ def maybe_enter(mode, reason=None):
def maybe_leave(mode, reason=None):
"""Convenience method to leave 'mode' without exceptions."""
try:
instance().leave(mode, reason)
objreg.get('mode-manager').leave(mode, reason)
except ValueError as e:
# This is rather likely to happen, so we only log to debug log.
log.modes.debug(e)
@ -75,7 +70,6 @@ class ModeManager(QObject):
Attributes:
passthrough: A list of modes in which to pass through events.
mainwindow: The mainwindow object
locked: Whether current mode is locked. This means the current mode can
still be left (then locked will be reset), but no new mode can
be entered while the current mode is active.
@ -99,7 +93,6 @@ class ModeManager(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.mainwindow = None
self.locked = False
self._handlers = {}
self.passthrough = []
@ -219,7 +212,7 @@ class ModeManager(QObject):
log.modes.debug("New mode stack: {}".format(self._mode_stack))
self.entered.emit(mode)
@cmdutils.register(instance='modeman', hide=True)
@cmdutils.register(instance='mode-manager', hide=True)
def enter_mode(self, mode):
"""Enter a key mode.
@ -252,7 +245,7 @@ class ModeManager(QObject):
self._mode_stack))
self.left.emit(mode)
@cmdutils.register(instance='modeman', name='leave-mode',
@cmdutils.register(instance='mode-manager', name='leave-mode',
not_modes=[usertypes.KeyMode.normal], hide=True)
def leave_current_mode(self):
"""Leave the mode we're currently in."""
@ -289,7 +282,8 @@ class ModeManager(QObject):
# We already handled this same event at some point earlier, so
# we're not interested in it anymore.
return False
if QApplication.instance().activeWindow() is not self.mainwindow:
if (QApplication.instance().activeWindow() is not
objreg.get('main-window')):
# Some other window (print dialog, etc.) is focused so we pass
# the event through.
return False

View File

@ -23,12 +23,12 @@ Module attributes:
STARTCHARS: Possible chars for starting a commandline input.
"""
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtCore import pyqtSlot, Qt
from qutebrowser.utils import message
from qutebrowser.config import config
from qutebrowser.keyinput import keyparser
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
STARTCHARS = ":/?"
@ -80,25 +80,17 @@ class HintKeyParser(keyparser.CommandKeyParser):
"""KeyChainParser for hints.
Signals:
fire_hint: When a hint keybinding was completed.
Arg: the keystring/hint string pressed.
filter_hints: When the filter text changed.
Arg: the text to filter hints with.
Attributes:
_filtertext: The text to filter with.
_last_press: The nature of the last keypress, a LastPress member.
"""
fire_hint = pyqtSignal(str)
filter_hints = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent, supports_count=False, supports_chains=True)
self._filtertext = ''
self._last_press = LastPress.none
self.read_config('hint')
self.keystring_updated.connect(self.on_keystring_updated)
def _handle_special_key(self, e):
"""Override _handle_special_key to handle string filtering.
@ -112,11 +104,11 @@ class HintKeyParser(keyparser.CommandKeyParser):
True if event has been handled, False otherwise.
Emit:
filter_hints: Emitted when filter string has changed.
keystring_updated: Emitted when keystring has been changed.
"""
log.keyboard.debug("Got special key 0x{:x} text {}".format(
e.key(), e.text()))
hintmanager = objreg.get('hintmanager', scope='tab')
if e.key() == Qt.Key_Backspace:
log.keyboard.debug("Got backspace, mode {}, filtertext '{}', "
"keystring '{}'".format(self._last_press,
@ -124,7 +116,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
self._keystring))
if self._last_press == LastPress.filtertext and self._filtertext:
self._filtertext = self._filtertext[:-1]
self.filter_hints.emit(self._filtertext)
hintmanager.filter_hints(self._filtertext)
return True
elif self._last_press == LastPress.keystring and self._keystring:
self._keystring = self._keystring[:-1]
@ -138,7 +130,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
return super()._handle_special_key(e)
else:
self._filtertext += e.text()
self.filter_hints.emit(self._filtertext)
hintmanager.filter_hints(self._filtertext)
self._last_press = LastPress.filtertext
return True
@ -167,24 +159,25 @@ class HintKeyParser(keyparser.CommandKeyParser):
return self._handle_special_key(e)
def execute(self, cmdstr, keytype, count=None):
"""Handle a completed keychain.
Emit:
fire_hint: Emitted if keytype is chain
"""
"""Handle a completed keychain."""
if not isinstance(keytype, self.Type):
raise TypeError("Type {} is no Type member!".format(keytype))
if keytype == self.Type.chain:
self.fire_hint.emit(cmdstr)
objreg.get('hintmanager', scope='tab').fire(cmdstr)
else:
# execute as command
super().execute(cmdstr, keytype, count)
def on_hint_strings_updated(self, strings):
"""Handler for HintManager's hint_strings_updated.
def update_bindings(self, strings):
"""Update bindings when the hint strings changed.
Args:
strings: A list of hint strings.
"""
self.bindings = {s: s for s in strings}
self._filtertext = ''
@pyqtSlot(str)
def on_keystring_updated(self, keystr):
"""Update hintmanager when the keystring was updated."""
objreg.get('hintmanager', scope='tab').handle_partial_key(keystr)

View File

@ -19,11 +19,11 @@
"""CompletionModels for different usages."""
from PyQt5.QtCore import pyqtSlot, Qt, QCoreApplication
from PyQt5.QtCore import pyqtSlot, Qt
from qutebrowser.config import config, configdata
from qutebrowser.models import basecompletion
from qutebrowser.utils import log, qtutils
from qutebrowser.utils import log, qtutils, objreg
from qutebrowser.commands import cmdutils
@ -155,8 +155,7 @@ class CommandCompletionModel(basecompletion.BaseCompletionModel):
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if obj.hide or (obj.debug and not
QCoreApplication.instance().args.debug):
if obj.hide or (obj.debug and not objreg.get('args').debug):
pass
else:
cmdlist.append((obj.name, obj.desc))
@ -183,8 +182,7 @@ class HelpCompletionModel(basecompletion.BaseCompletionModel):
assert cmdutils.cmd_dict
cmdlist = []
for obj in set(cmdutils.cmd_dict.values()):
if obj.hide or (obj.debug and not
QCoreApplication.instance().args.debug):
if obj.hide or (obj.debug and not objreg.get('args').debug):
pass
else:
cmdlist.append((':' + obj.name, obj.desc))

View File

@ -34,14 +34,12 @@ class CompletionFilterModel(QSortFilterProxyModel):
"""Subclass of QSortFilterProxyModel with custom sorting/filtering.
Attributes:
srcmodel: The source model of this QSFPM.
_pattern: The pattern to filter with.
"""
def __init__(self, source, parent=None):
super().__init__(parent)
super().setSourceModel(source)
self.srcmodel = source
self._pattern = ''
def set_pattern(self, val):
@ -59,7 +57,7 @@ class CompletionFilterModel(QSortFilterProxyModel):
self.invalidateFilter()
sortcol = 0
try:
self.srcmodel.sort(sortcol)
self.sourceModel().sort(sortcol)
except NotImplementedError:
self.sort(sortcol)
self.invalidate()
@ -109,13 +107,12 @@ class CompletionFilterModel(QSortFilterProxyModel):
qtutils.ensure_valid(index)
index = self.mapToSource(index)
qtutils.ensure_valid(index)
self.srcmodel.mark_item(index, text)
self.sourceModel().mark_item(index, text)
def setSourceModel(self, model):
"""Override QSortFilterProxyModel's setSourceModel to clear pattern."""
log.completion.debug("Setting source model: {}".format(model))
self.set_pattern('')
self.srcmodel = model
super().setSourceModel(model)
def filterAcceptsRow(self, row, parent):
@ -133,9 +130,9 @@ class CompletionFilterModel(QSortFilterProxyModel):
"""
if parent == QModelIndex():
return True
idx = self.srcmodel.index(row, 0, parent)
idx = self.sourceModel().index(row, 0, parent)
qtutils.ensure_valid(idx)
data = self.srcmodel.data(idx)
data = self.sourceModel().data(idx)
# TODO more sophisticated filtering
if not self._pattern:
return True
@ -157,14 +154,16 @@ class CompletionFilterModel(QSortFilterProxyModel):
qtutils.ensure_valid(lindex)
qtutils.ensure_valid(rindex)
left_sort = self.srcmodel.data(lindex, role=completion.Role.sort)
right_sort = self.srcmodel.data(rindex, role=completion.Role.sort)
srcmodel = self.sourceModel()
left_sort = srcmodel.data(lindex, role=completion.Role.sort)
right_sort = srcmodel.data(rindex, role=completion.Role.sort)
if left_sort is not None and right_sort is not None:
return left_sort < right_sort
left = self.srcmodel.data(lindex)
right = self.srcmodel.data(rindex)
left = srcmodel.data(lindex)
right = srcmodel.data(rindex)
leftstart = left.startswith(self._pattern)
rightstart = right.startswith(self._pattern)

View File

@ -21,10 +21,9 @@
from PyQt5.QtCore import (pyqtSlot, Qt, QVariant, QAbstractListModel,
QModelIndex)
from PyQt5.QtWidgets import QApplication
from qutebrowser.config import config
from qutebrowser.utils import usertypes, qtutils
from qutebrowser.utils import usertypes, qtutils, objreg
Role = usertypes.enum('Role', 'item', start=Qt.UserRole, is_int=True)
@ -40,14 +39,14 @@ class DownloadModel(QAbstractListModel):
def __init__(self, parent=None):
super().__init__(parent)
self.downloadmanager = QApplication.instance().downloadmanager
self.downloadmanager.download_about_to_be_added.connect(
download_manager = objreg.get('download-manager')
download_manager.download_about_to_be_added.connect(
lambda idx: self.beginInsertRows(QModelIndex(), idx, idx))
self.downloadmanager.download_added.connect(self.endInsertRows)
self.downloadmanager.download_about_to_be_finished.connect(
download_manager.download_added.connect(self.endInsertRows)
download_manager.download_about_to_be_finished.connect(
lambda idx: self.beginRemoveRows(QModelIndex(), idx, idx))
self.downloadmanager.download_finished.connect(self.endRemoveRows)
self.downloadmanager.data_changed.connect(self.on_data_changed)
download_manager.download_finished.connect(self.endRemoveRows)
download_manager.data_changed.connect(self.on_data_changed)
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
@ -82,7 +81,7 @@ class DownloadModel(QAbstractListModel):
if index.parent().isValid() or index.column() != 0:
return QVariant()
item = self.downloadmanager.downloads[index.row()]
item = objreg.get('download-manager').downloads[index.row()]
if role == Qt.DisplayRole:
data = str(item)
elif role == Qt.ForegroundRole:
@ -106,4 +105,4 @@ class DownloadModel(QAbstractListModel):
if parent.isValid():
# We don't have children
return 0
return len(self.downloadmanager.downloads)
return len(objreg.get('download-manager').downloads)

View File

@ -30,7 +30,7 @@ else:
SSL_AVAILABLE = QSslSocket.supportsSsl()
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, utils
from qutebrowser.utils import message, log, usertypes, utils, objreg
from qutebrowser.network import qutescheme, schemehandler
@ -51,13 +51,17 @@ class NetworkManager(QNetworkAccessManager):
self._scheme_handlers = {
'qute': qutescheme.QuteSchemeHandler(),
}
# We have a shared cookie jar and cache - we restore their parents so
# we don't take ownership of them.
app = QCoreApplication.instance()
self.setCookieJar(app.cookiejar)
self.setCache(app.cache)
# We have a shared cookie jar and cache , so we don't want the
# NetworkManager to take ownership of them.
app.cookiejar.setParent(app)
app.cache.setParent(app)
cookie_jar = objreg.get('cookie-jar')
self.setCookieJar(cookie_jar)
cookie_jar.setParent(app)
cache = objreg.get('cache')
self.setCache(cache)
cache.setParent(app)
if SSL_AVAILABLE:
self.sslErrors.connect(self.on_ssl_errors)
self.authenticationRequired.connect(self.on_authentication_required)

View File

@ -29,6 +29,7 @@ from PyQt5.QtCore import Qt
from qutebrowser.keyinput import basekeyparser
from qutebrowser.test import stubs, helpers
from qutebrowser.utils import objreg
CONFIG = {'input': {'timeout': 100}}
@ -42,6 +43,10 @@ BINDINGS = {'test': {'<Ctrl-a>': 'ctrla',
'test2': {'foo': 'bar', '<Ctrl+X>': 'ctrlx'}}
fake_keyconfig = mock.Mock(spec=['get_bindings_for'])
fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
def setUpModule():
"""Mock out some imports in basekeyparser."""
basekeyparser.QObject = mock.Mock()
@ -53,14 +58,6 @@ def tearDownModule():
logging.disable(logging.NOTSET)
def _get_fake_application():
"""Construct a fake QApplication with a keyconfig."""
app = stubs.FakeQApplication()
app.keyconfig = mock.Mock(spec=['get_bindings_for'])
app.keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s]
return app
class SplitCountTests(unittest.TestCase):
"""Test the _split_count method.
@ -109,9 +106,12 @@ class ReadConfigTests(unittest.TestCase):
"""Test reading the config."""
def setUp(self):
basekeyparser.QCoreApplication = _get_fake_application()
objreg.register('key-config', fake_keyconfig)
basekeyparser.usertypes.Timer = mock.Mock()
def tearDown(self):
objreg.delete('key-config')
def test_read_config_invalid(self):
"""Test reading config without setting it before."""
kp = basekeyparser.BaseKeyParser()
@ -145,12 +145,15 @@ class SpecialKeysTests(unittest.TestCase):
'qutebrowser.keyinput.basekeyparser.usertypes.Timer',
autospec=True)
patcher.start()
objreg.register('key-config', fake_keyconfig)
self.addCleanup(patcher.stop)
basekeyparser.QCoreApplication = _get_fake_application()
self.kp = basekeyparser.BaseKeyParser()
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def tearDown(self):
objreg.delete('key-config')
def test_valid_key(self):
"""Test a valid special keyevent."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier))
@ -181,7 +184,7 @@ class KeyChainTests(unittest.TestCase):
def setUp(self):
"""Set up mocks and read the test config."""
basekeyparser.QCoreApplication = _get_fake_application()
objreg.register('key-config', fake_keyconfig)
self.timermock = mock.Mock()
basekeyparser.usertypes.Timer = mock.Mock(return_value=self.timermock)
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
@ -189,6 +192,9 @@ class KeyChainTests(unittest.TestCase):
self.kp.execute = mock.Mock()
self.kp.read_config('test')
def tearDown(self):
objreg.delete('key-config')
def test_valid_special_key(self):
"""Test valid special key."""
self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier))
@ -246,7 +252,7 @@ class CountTests(unittest.TestCase):
"""Test execute() with counts."""
def setUp(self):
basekeyparser.QCoreApplication = _get_fake_application()
objreg.register('key-config', fake_keyconfig)
basekeyparser.usertypes.Timer = mock.Mock()
self.kp = basekeyparser.BaseKeyParser(supports_chains=True,
supports_count=True)
@ -296,6 +302,9 @@ class CountTests(unittest.TestCase):
self.kp.execute.assert_called_once_with('ccc', self.kp.Type.chain, 23)
self.assertEqual(self.kp._keystring, '')
def tearDown(self):
objreg.delete('key-config')
if __name__ == '__main__':
unittest.main()

View File

@ -19,6 +19,8 @@
"""Tests for qutebrowser.utils.editor."""
# pylint: disable=protected-access
import os
import os.path
import unittest
@ -59,7 +61,7 @@ class ArgTests(unittest.TestCase):
editor.config = stubs.ConfigStub(
{'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}})
self.editor.edit("")
self.editor.proc.start.assert_called_with("bin", [])
self.editor._proc.start.assert_called_with("bin", [])
def test_start_args(self):
"""Test starting editor with static arguments."""
@ -67,7 +69,7 @@ class ArgTests(unittest.TestCase):
{'general': {'editor': ['bin', 'foo', 'bar'],
'editor-encoding': 'utf-8'}})
self.editor.edit("")
self.editor.proc.start.assert_called_with("bin", ["foo", "bar"])
self.editor._proc.start.assert_called_with("bin", ["foo", "bar"])
def test_placeholder(self):
"""Test starting editor with placeholder argument."""
@ -75,9 +77,9 @@ class ArgTests(unittest.TestCase):
{'general': {'editor': ['bin', 'foo', '{}', 'bar'],
'editor-encoding': 'utf-8'}})
self.editor.edit("")
filename = self.editor.filename
self.editor.proc.start.assert_called_with("bin",
["foo", filename, "bar"])
filename = self.editor._filename
self.editor._proc.start.assert_called_with("bin",
["foo", filename, "bar"])
def test_in_arg_placeholder(self):
"""Test starting editor with placeholder argument inside argument."""
@ -85,7 +87,7 @@ class ArgTests(unittest.TestCase):
{'general': {'editor': ['bin', 'foo{}bar'],
'editor-encoding': 'utf-8'}})
self.editor.edit("")
self.editor.proc.start.assert_called_with("bin", ["foo{}bar"])
self.editor._proc.start.assert_called_with("bin", ["foo{}bar"])
def tearDown(self):
self.editor._cleanup() # pylint: disable=protected-access
@ -107,7 +109,7 @@ class FileHandlingTests(unittest.TestCase):
def test_file_handling_closed_ok(self):
"""Test file handling when closing with an exitstatus == 0."""
self.editor.edit("")
filename = self.editor.filename
filename = self.editor._filename
self.assertTrue(os.path.exists(filename))
self.editor.on_proc_closed(0, QProcess.NormalExit)
self.assertFalse(os.path.exists(filename))
@ -115,7 +117,7 @@ class FileHandlingTests(unittest.TestCase):
def test_file_handling_closed_error(self):
"""Test file handling when closing with an exitstatus != 0."""
self.editor.edit("")
filename = self.editor.filename
filename = self.editor._filename
self.assertTrue(os.path.exists(filename))
self.editor.on_proc_closed(1, QProcess.NormalExit)
self.assertFalse(os.path.exists(filename))
@ -123,7 +125,7 @@ class FileHandlingTests(unittest.TestCase):
def test_file_handling_closed_crash(self):
"""Test file handling when closing with a crash."""
self.editor.edit("")
filename = self.editor.filename
filename = self.editor._filename
self.assertTrue(os.path.exists(filename))
self.editor.on_proc_error(QProcess.Crashed)
self.editor.on_proc_closed(0, QProcess.CrashExit)
@ -150,7 +152,7 @@ class TextModifyTests(unittest.TestCase):
Args:
text: The text to write to the file.
"""
filename = self.editor.filename
filename = self.editor._filename
with open(filename, 'w', encoding='utf-8') as f:
f.write(text)
@ -160,7 +162,7 @@ class TextModifyTests(unittest.TestCase):
Return:
The text which was read.
"""
filename = self.editor.filename
filename = self.editor._filename
with open(filename, 'r', encoding='utf-8') as f:
data = f.read()
return data

View File

@ -171,18 +171,18 @@ class RAMHandlerTests(BaseTest):
"""Test handler with exactly as much records as it can hold."""
self.logger.debug("One")
self.logger.debug("Two")
self.assertEqual(len(self.handler.data), 2)
self.assertEqual(self.handler.data[0].msg, "One")
self.assertEqual(self.handler.data[1].msg, "Two")
self.assertEqual(len(self.handler._data), 2)
self.assertEqual(self.handler._data[0].msg, "One")
self.assertEqual(self.handler._data[1].msg, "Two")
def test_overflow(self):
"""Test handler with more records as it can hold."""
self.logger.debug("One")
self.logger.debug("Two")
self.logger.debug("Three")
self.assertEqual(len(self.handler.data), 2)
self.assertEqual(self.handler.data[0].msg, "Two")
self.assertEqual(self.handler.data[1].msg, "Three")
self.assertEqual(len(self.handler._data), 2)
self.assertEqual(self.handler._data[0].msg, "Two")
self.assertEqual(self.handler._data[1].msg, "Three")
def test_dump_log(self):
"""Test dump_log()."""

View File

@ -19,6 +19,8 @@
"""Tests for qutebrowser.utils.readline."""
# pylint: disable=protected-access
import inspect
import unittest
from unittest import mock
@ -105,7 +107,7 @@ class ReadlineBridgeTest(unittest.TestCase):
self._set_selected_text("delete test")
self.bridge.rl_unix_line_discard()
self.qle.home.assert_called_with(True)
self.assertEqual(self.bridge.deleted[self.qle], "delete test")
self.assertEqual(self.bridge._deleted[self.qle], "delete test")
self.qle.del_.assert_called_with()
self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test")
@ -115,7 +117,7 @@ class ReadlineBridgeTest(unittest.TestCase):
self._set_selected_text("delete test")
self.bridge.rl_kill_line()
self.qle.end.assert_called_with(True)
self.assertEqual(self.bridge.deleted[self.qle], "delete test")
self.assertEqual(self.bridge._deleted[self.qle], "delete test")
self.qle.del_.assert_called_with()
self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test")
@ -125,7 +127,7 @@ class ReadlineBridgeTest(unittest.TestCase):
self._set_selected_text("delete test")
self.bridge.rl_unix_word_rubout()
self.qle.cursorWordBackward.assert_called_with(True)
self.assertEqual(self.bridge.deleted[self.qle], "delete test")
self.assertEqual(self.bridge._deleted[self.qle], "delete test")
self.qle.del_.assert_called_with()
self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test")
@ -135,7 +137,7 @@ class ReadlineBridgeTest(unittest.TestCase):
self._set_selected_text("delete test")
self.bridge.rl_kill_word()
self.qle.cursorWordForward.assert_called_with(True)
self.assertEqual(self.bridge.deleted[self.qle], "delete test")
self.assertEqual(self.bridge._deleted[self.qle], "delete test")
self.qle.del_.assert_called_with()
self.bridge.rl_yank()
self.qle.insert.assert_called_with("delete test")

View File

@ -72,17 +72,17 @@ class DefaultTests(unittest.TestCase):
def test_simple(self):
"""Test default with a numeric argument."""
nl = usertypes.NeighborList([1, 2, 3], default=2)
self.assertEqual(nl.idx, 1)
self.assertEqual(nl._idx, 1)
def test_none(self):
"""Test default 'None'."""
nl = usertypes.NeighborList([1, 2, None], default=None)
self.assertEqual(nl.idx, 2)
self.assertEqual(nl._idx, 2)
def test_unset(self):
"""Test unset default value."""
nl = usertypes.NeighborList([1, 2, 3])
self.assertIsNone(nl.idx)
self.assertIsNone(nl._idx)
class EmptyTests(unittest.TestCase):
@ -130,48 +130,48 @@ class ItemTests(unittest.TestCase):
def test_curitem(self):
"""Test curitem()."""
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
self.assertEqual(self.nl.curitem(), 3)
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
def test_nextitem(self):
"""Test nextitem()."""
self.assertEqual(self.nl.nextitem(), 4)
self.assertEqual(self.nl.idx, 3)
self.assertEqual(self.nl._idx, 3)
self.assertEqual(self.nl.nextitem(), 5)
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
def test_previtem(self):
"""Test previtem()."""
self.assertEqual(self.nl.previtem(), 2)
self.assertEqual(self.nl.idx, 1)
self.assertEqual(self.nl._idx, 1)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_firstitem(self):
"""Test firstitem()."""
self.assertEqual(self.nl.firstitem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_lastitem(self):
"""Test lastitem()."""
self.assertEqual(self.nl.lastitem(), 5)
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
def test_reset(self):
"""Test reset()."""
self.nl.nextitem()
self.assertEqual(self.nl.idx, 3)
self.assertEqual(self.nl._idx, 3)
self.nl.reset()
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
def test_getitem(self):
"""Test getitem()."""
self.assertEqual(self.nl.getitem(2), 5)
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
self.nl.reset()
self.assertEqual(self.nl.getitem(-2), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
class OneTests(unittest.TestCase):
@ -189,51 +189,51 @@ class OneTests(unittest.TestCase):
"""Test out of bounds previtem() with mode=wrap."""
self.nl._mode = usertypes.NeighborList.Modes.wrap
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_first_block(self):
"""Test out of bounds previtem() with mode=block."""
self.nl._mode = usertypes.NeighborList.Modes.block
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_first_raise(self):
"""Test out of bounds previtem() with mode=raise."""
self.nl._mode = usertypes.NeighborList.Modes.exception
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
with self.assertRaises(IndexError):
self.nl.previtem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_last_wrap(self):
"""Test out of bounds nextitem() with mode=wrap."""
self.nl._mode = usertypes.NeighborList.Modes.wrap
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_last_block(self):
"""Test out of bounds nextitem() with mode=block."""
self.nl._mode = usertypes.NeighborList.Modes.block
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_last_raise(self):
"""Test out of bounds nextitem() with mode=raise."""
self.nl._mode = usertypes.NeighborList.Modes.exception
self.nl.lastitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
with self.assertRaises(IndexError):
self.nl.nextitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
class BlockTests(unittest.TestCase):
@ -252,16 +252,16 @@ class BlockTests(unittest.TestCase):
def test_first(self):
"""Test ouf of bounds previtem()."""
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_last(self):
"""Test ouf of bounds nextitem()."""
self.nl.lastitem()
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
self.assertEqual(self.nl.nextitem(), 5)
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
class WrapTests(unittest.TestCase):
@ -279,16 +279,16 @@ class WrapTests(unittest.TestCase):
def test_first(self):
"""Test ouf of bounds previtem()."""
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
self.assertEqual(self.nl.previtem(), 5)
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
def test_last(self):
"""Test ouf of bounds nextitem()."""
self.nl.lastitem()
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
class RaiseTests(unittest.TestCase):
@ -307,18 +307,18 @@ class RaiseTests(unittest.TestCase):
def test_first(self):
"""Test ouf of bounds previtem()."""
self.nl.firstitem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
with self.assertRaises(IndexError):
self.nl.previtem()
self.assertEqual(self.nl.idx, 0)
self.assertEqual(self.nl._idx, 0)
def test_last(self):
"""Test ouf of bounds nextitem()."""
self.nl.lastitem()
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
with self.assertRaises(IndexError):
self.nl.nextitem()
self.assertEqual(self.nl.idx, 4)
self.assertEqual(self.nl._idx, 4)
class SnapInTests(unittest.TestCase):
@ -336,29 +336,29 @@ class SnapInTests(unittest.TestCase):
"""Test fuzzyval with snapping to a bigger value."""
self.nl.fuzzyval = 7
self.assertEqual(self.nl.nextitem(), 9)
self.assertEqual(self.nl.idx, 1)
self.assertEqual(self.nl._idx, 1)
self.assertEqual(self.nl.nextitem(), 1)
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
def test_smaller(self):
"""Test fuzzyval with snapping to a smaller value."""
self.nl.fuzzyval = 7
self.assertEqual(self.nl.previtem(), 5)
self.assertEqual(self.nl.idx, 3)
self.assertEqual(self.nl._idx, 3)
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
def test_equal_bigger(self):
"""Test fuzzyval with matching value, snapping to a bigger value."""
self.nl.fuzzyval = 20
self.assertEqual(self.nl.nextitem(), 9)
self.assertEqual(self.nl.idx, 1)
self.assertEqual(self.nl._idx, 1)
def test_equal_smaller(self):
"""Test fuzzyval with matching value, snapping to a smaller value."""
self.nl.fuzzyval = 5
self.assertEqual(self.nl.previtem(), 1)
self.assertEqual(self.nl.idx, 2)
self.assertEqual(self.nl._idx, 2)
if __name__ == '__main__':

View File

@ -23,7 +23,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from qutebrowser.config import config, configdata
from qutebrowser.commands import cmdutils
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
from qutebrowser.models import completion as models
from qutebrowser.models.completionfilter import CompletionFilterModel as CFM
@ -33,8 +33,7 @@ class Completer(QObject):
"""Completer which manages completions in a CompletionView.
Attributes:
view: The CompletionView associated with this completer.
ignore_change: Whether to ignore the next completion update.
_ignore_change: Whether to ignore the next completion update.
_models: dict of available completion models.
Signals:
@ -48,10 +47,9 @@ class Completer(QObject):
change_completed_part = pyqtSignal(str, bool)
def __init__(self, view):
super().__init__(view)
self.view = view
self.ignore_change = False
def __init__(self, parent=None):
super().__init__(parent)
self._ignore_change = False
self._models = {
usertypes.Completion.option: {},
@ -60,6 +58,13 @@ class Completer(QObject):
self._init_static_completions()
self._init_setting_completions()
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def _model(self):
"""Convienience method to get the current completion model."""
return objreg.get('completion').model()
def _init_static_completions(self):
"""Initialize the static completion models."""
self._models[usertypes.Completion.command] = CFM(
@ -69,6 +74,7 @@ class Completer(QObject):
def _init_setting_completions(self):
"""Initialize setting completion models."""
config_obj = objreg.get('config')
self._models[usertypes.Completion.section] = CFM(
models.SettingSectionCompletionModel(self), self)
self._models[usertypes.Completion.option] = {}
@ -77,13 +83,13 @@ class Completer(QObject):
model = models.SettingOptionCompletionModel(sectname, self)
self._models[usertypes.Completion.option][sectname] = CFM(
model, self)
config.instance().changed.connect(model.on_config_changed)
config_obj.changed.connect(model.on_config_changed)
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)
config.instance().changed.connect(model.on_config_changed)
config_obj.changed.connect(model.on_config_changed)
def _get_new_completion(self, parts, cursor_part):
"""Get a new completion model.
@ -161,7 +167,7 @@ class Completer(QObject):
indexes = selected.indexes()
if not indexes:
return
model = self.view.model()
model = self._model()
data = model.data(indexes[0])
if data is None:
return
@ -171,9 +177,9 @@ class Completer(QObject):
# and go on to the next part.
self.change_completed_part.emit(data, True)
else:
self.ignore_change = True
self._ignore_change = True
self.change_completed_part.emit(data, False)
self.ignore_change = False
self._ignore_change = False
@pyqtSlot(str, list, int)
def on_update_completion(self, prefix, parts, cursor_part):
@ -185,40 +191,42 @@ class Completer(QObject):
text: The new text
cursor_part: The part the cursor is currently over.
"""
if self.ignore_change:
if self._ignore_change:
log.completion.debug("Ignoring completion update")
return
completion = objreg.get('completion')
if prefix != ':':
# This is a search or gibberish, so we don't need to complete
# anything (yet)
# FIXME complete searchs
self.view.hide()
completion.hide()
return
model = self._get_new_completion(parts, cursor_part)
if model != self.view.model():
if model != self._model():
if model is None:
self.view.hide()
completion.hide()
else:
self.view.set_model(model)
completion.set_model(model)
if model is None:
log.completion.debug("No completion model for {}.".format(parts))
return
pattern = parts[cursor_part] if parts else ''
self.view.model().set_pattern(pattern)
self._model().set_pattern(pattern)
log.completion.debug(
"New completion for {}: {}, with pattern '{}'".format(
parts, model.srcmodel.__class__.__name__, pattern))
parts, model.sourceModel().__class__.__name__, pattern))
if self.view.model().count() == 0:
self.view.hide()
if self._model().count() == 0:
completion.hide()
return
self.view.model().mark_all_items(pattern)
if self.view.enabled:
self.view.show()
self._model().mark_all_items(pattern)
if completion.enabled:
completion.show()

View File

@ -64,6 +64,13 @@ class DocstringParser:
"""Generate documentation based on a docstring of a command handler.
The docstring needs to follow the format described in HACKING.
Attributes:
_state: The current state of the parser state machine.
_cur_arg_name: The name of the argument we're currently handling.
short_desc: The short description of the function.
long_desc: The long description of the function.
arg_descs: A dict of argument names to their descriptions
"""
State = usertypes.enum('State', 'short', 'desc', 'desc_hidden',
@ -75,12 +82,13 @@ class DocstringParser:
Args:
func: The function to parse the docstring for.
"""
self.state = self.State.short
self._state = self.State.short
self._cur_arg_name = None
self.short_desc = []
self.long_desc = []
self.arg_descs = collections.OrderedDict()
self.cur_arg_name = None
self.handlers = {
doc = inspect.getdoc(func)
handlers = {
self.State.short: self._parse_short,
self.State.desc: self._parse_desc,
self.State.desc_hidden: self._skip,
@ -88,9 +96,8 @@ class DocstringParser:
self.State.arg_inside: self._parse_arg_inside,
self.State.misc: self._skip,
}
doc = inspect.getdoc(func)
for line in doc.splitlines():
handler = self.handlers[self.state]
handler = handlers[self._state]
stop = handler(line)
if stop:
break
@ -101,41 +108,41 @@ class DocstringParser:
def _process_arg(self, line):
"""Helper method to process a line like 'fooarg: Blah blub'."""
self.cur_arg_name, argdesc = line.split(':', maxsplit=1)
self.cur_arg_name = self.cur_arg_name.strip().lstrip('*')
self.arg_descs[self.cur_arg_name] = [argdesc.strip()]
self._cur_arg_name, argdesc = line.split(':', maxsplit=1)
self._cur_arg_name = self._cur_arg_name.strip().lstrip('*')
self.arg_descs[self._cur_arg_name] = [argdesc.strip()]
def _skip(self, line):
"""Handler to ignore everything until we get 'Args:'."""
if line.startswith('Args:'):
self.state = self.State.arg_start
self._state = self.State.arg_start
def _parse_short(self, line):
"""Parse the short description (first block) in the docstring."""
if not line:
self.state = self.State.desc
self._state = self.State.desc
else:
self.short_desc.append(line.strip())
def _parse_desc(self, line):
"""Parse the long description in the docstring."""
if line.startswith('Args:'):
self.state = self.State.arg_start
self._state = self.State.arg_start
elif line.startswith('Emit:') or line.startswith('Raise:'):
self.state = self.State.misc
self._state = self.State.misc
elif line.strip() == '//':
self.state = self.State.desc_hidden
self._state = self.State.desc_hidden
elif line.strip():
self.long_desc.append(line.strip())
def _parse_arg_start(self, line):
"""Parse first argument line."""
self._process_arg(line)
self.state = self.State.arg_inside
self._state = self.State.arg_inside
def _parse_arg_inside(self, line):
"""Parse subsequent argument lines."""
argname = self.cur_arg_name
argname = self._cur_arg_name
if re.match(r'^[A-Z][a-z]+:$', line):
if not self.arg_descs[argname][-1].strip():
self.arg_descs[argname] = self.arg_descs[argname][:-1]

View File

@ -30,22 +30,29 @@ from qutebrowser.utils import message, log
class ExternalEditor(QObject):
"""Class to simplify editing a text in an external editor."""
"""Class to simplify editing a text in an external editor.
Attributes:
_text: The current text before the editor is opened.
_oshandle: The OS level handle to the tmpfile.
_filehandle: The file handle to the tmpfile.
_proc: The QProcess of the editor.
"""
editing_finished = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.text = None
self.oshandle = None
self.filename = None
self.proc = None
self._text = None
self._oshandle = None
self._filename = None
self._proc = None
def _cleanup(self):
"""Clean up temporary files after the editor closed."""
os.close(self.oshandle)
os.close(self._oshandle)
try:
os.remove(self.filename)
os.remove(self._filename)
except PermissionError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
@ -72,7 +79,7 @@ class ExternalEditor(QObject):
exitcode))
return
encoding = config.get('general', 'editor-encoding')
with open(self.filename, 'r', encoding=encoding) as f:
with open(self._filename, 'r', encoding=encoding) as f:
text = ''.join(f.readlines())
log.procs.debug("Read back: {}".format(text))
self.editing_finished.emit(text)
@ -105,19 +112,19 @@ class ExternalEditor(QObject):
Emit:
editing_finished with the new text if editing finshed successfully.
"""
if self.text is not None:
if self._text is not None:
raise ValueError("Already editing a file!")
self.text = text
self.oshandle, self.filename = tempfile.mkstemp(text=True)
self._text = text
self._oshandle, self._filename = tempfile.mkstemp(text=True)
if text:
encoding = config.get('general', 'editor-encoding')
with open(self.filename, 'w', encoding=encoding) as f:
with open(self._filename, 'w', encoding=encoding) as f:
f.write(text)
self.proc = QProcess(self)
self.proc.finished.connect(self.on_proc_closed)
self.proc.error.connect(self.on_proc_error)
self._proc = QProcess(self)
self._proc.finished.connect(self.on_proc_closed)
self._proc.error.connect(self.on_proc_error)
editor = config.get('general', 'editor')
executable = editor[0]
args = [self.filename if arg == '{}' else arg for arg in editor[1:]]
args = [self._filename if arg == '{}' else arg for arg in editor[1:]]
log.procs.debug("Calling \"{}\" with args {}".format(executable, args))
self.proc.start(executable, args)
self._proc.start(executable, args)

View File

@ -28,13 +28,17 @@ from qutebrowser.utils import utils
class Loader(jinja2.BaseLoader):
"""Jinja loader which uses utils.read_file to load templates."""
"""Jinja loader which uses utils.read_file to load templates.
Attributes:
_subdir: The subdirectory to find templates in.
"""
def __init__(self, subdir):
self.subdir = subdir
self._subdir = subdir
def get_source(self, _env, template):
path = os.path.join(self.subdir, template)
path = os.path.join(self._subdir, template)
try:
source = utils.read_file(path)
except FileNotFoundError:

View File

@ -295,21 +295,21 @@ class LogFilter(logging.Filter):
comma-separated list instead.
Attributes:
names: A list of names that should be logged.
_names: A list of names that should be logged.
"""
def __init__(self, names):
super().__init__()
self.names = names
self._names = names
def filter(self, record):
"""Determine if the specified record is to be logged."""
if self.names is None:
if self._names is None:
return True
if record.levelno > logging.DEBUG:
# More important than DEBUG, so we won't filter at all
return True
for name in self.names:
for name in self._names:
if record.name == name:
return True
elif not record.name.startswith(name):
@ -327,21 +327,21 @@ class RAMHandler(logging.Handler):
uses a simple list rather than a deque.
Attributes:
data: A deque containing the logging records.
_data: A deque containing the logging records.
"""
def __init__(self, capacity):
super().__init__()
self.html_formatter = None
if capacity != -1:
self.data = collections.deque(maxlen=capacity)
self._data = collections.deque(maxlen=capacity)
else:
self.data = collections.deque()
self._data = collections.deque()
def emit(self, record):
if record.levelno >= logging.DEBUG:
# We don't log VDEBUG to RAM.
self.data.append(record)
self._data.append(record)
def dump_log(self, html=False):
"""Dump the complete formatted log data as as string.
@ -352,7 +352,7 @@ class RAMHandler(logging.Handler):
fmt = self.html_formatter.format if html else self.format
self.acquire()
try:
records = list(self.data)
records = list(self._data)
finally:
self.release()
for record in records:
@ -362,7 +362,12 @@ class RAMHandler(logging.Handler):
class HTMLFormatter(logging.Formatter):
"""Formatter for HTML-colored log messages, similiar to colorlog."""
"""Formatter for HTML-colored log messages, similiar to colorlog.
Attributes:
_log_colors: The colors to use for logging levels.
_colordict: The colordict passed to the logger.
"""
def __init__(self, fmt, datefmt, log_colors):
"""Constructor.

View File

@ -19,14 +19,9 @@
"""Message singleton so we don't have to define unneeded signals."""
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QObject, QTimer
from PyQt5.QtCore import pyqtSignal, QObject, QTimer
from qutebrowser.utils import usertypes, log
def instance():
"""Get the global messagebridge instance."""
return QCoreApplication.instance().messagebridge
from qutebrowser.utils import usertypes, log, objreg
def error(message, immediately=False):
@ -35,7 +30,7 @@ def error(message, immediately=False):
Args:
See MessageBridge.error.
"""
instance().error(message, immediately)
objreg.get('message-bridge').error(message, immediately)
def info(message, immediately=True):
@ -44,12 +39,12 @@ def info(message, immediately=True):
Args:
See MessageBridge.info.
"""
instance().info(message, immediately)
objreg.get('message-bridge').info(message, immediately)
def set_cmd_text(txt):
"""Convienience function to Set the statusbar command line to a text."""
instance().set_cmd_text(txt)
objreg.get('message-bridge').set_cmd_text(txt)
def ask(message, mode, default=None):
@ -67,7 +62,7 @@ def ask(message, mode, default=None):
q.text = message
q.mode = mode
q.default = default
instance().ask(q, blocking=True)
objreg.get('message-bridge').ask(q, blocking=True)
q.deleteLater()
return q.answer
@ -77,7 +72,7 @@ def alert(message):
q = usertypes.Question()
q.text = message
q.mode = usertypes.PromptMode.alert
instance().ask(q, blocking=True)
objreg.get('message-bridge').ask(q, blocking=True)
q.deleteLater()
@ -92,7 +87,7 @@ def ask_async(message, mode, handler, default=None):
"""
if not isinstance(mode, usertypes.PromptMode):
raise TypeError("Mode {} is no PromptMode member!".format(mode))
bridge = instance()
bridge = objreg.get('message-bridge')
q = usertypes.Question(bridge)
q.text = message
q.mode = mode
@ -111,7 +106,7 @@ def confirm_async(message, yes_action, no_action=None, default=None):
no_action: Callable to be called when the user answered no.
default: True/False to set a default value, or None.
"""
bridge = instance()
bridge = objreg.get('message-bridge')
q = usertypes.Question(bridge)
q.text = message
q.mode = usertypes.PromptMode.yesno

144
qutebrowser/utils/objreg.py Normal file
View File

@ -0,0 +1,144 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014 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/>.
"""The global object registry and related utility functions."""
import collections
import functools
from PyQt5.QtCore import QObject
class UnsetObject:
"""Class for an unset object.
Only used (rather than object) so we can tell pylint to shut up about it.
"""
__slots__ = ()
class RegistryUnavailableError(Exception):
"""Exception raised when a certain registry does not exist yet."""
pass
_UNSET = UnsetObject()
class ObjectRegistry(collections.UserDict):
"""A registry of long-living objects in qutebrowser.
Inspired by the eric IDE code (E5Gui/E5Application.py).
"""
def __setitem__(self, name, obj):
"""Register an object in the object registry.
Sets a slot to remove QObjects when they are destroyed.
"""
if isinstance(obj, QObject):
obj.destroyed.connect(functools.partial(self.on_destroyed, name))
super().__setitem__(name, obj)
def on_destroyed(self, name):
"""Remove a destroyed QObject."""
try:
del self[name]
except KeyError:
pass
def dump_objects(self):
"""Dump all objects as a list of strings."""
lines = []
for name, obj in self.data.items():
lines.append("{}: {}".format(name, repr(obj)))
return lines
# The registry for global objects
global_registry = ObjectRegistry()
# The object registry of object registries.
meta_registry = ObjectRegistry()
meta_registry['global'] = global_registry
def _get_registry(scope):
"""Get the correct registry for a given scope."""
if scope == 'global':
return global_registry
elif scope == 'tab':
widget = get('tabbed-browser').currentWidget()
if widget is None:
raise RegistryUnavailableError(scope)
return widget.registry
elif scope == 'meta':
return meta_registry
else:
raise ValueError("Invalid scope '{}'!".format(scope))
def get(name, default=_UNSET, scope='global'):
"""Helper function to get an object.
Args:
default: A default to return if the object does not exist.
"""
reg = _get_registry(scope)
try:
return reg[name]
except KeyError:
if default is not _UNSET:
return default
else:
raise
def register(name, obj, update=False, scope=None, registry=None):
"""Helper function to register an object.
Args:
name: The name the object will be registered as.
obj: The object to register.
update: If True, allows to update an already registered object.
"""
if scope is not None and registry is not None:
raise ValueError("scope ({}) and registry ({}) can't be given at the "
"same time!".format(scope, registry))
if registry is not None:
reg = registry
else:
if scope is None:
scope = 'global'
reg = _get_registry(scope)
if not update and name in reg:
raise KeyError("Object '{}' is already registered ({})!".format(
name, repr(reg[name])))
reg[name] = obj
def delete(name, scope='global'):
"""Helper function to unregister an object."""
reg = _get_registry(scope)
del reg[name]

View File

@ -30,11 +30,14 @@ class ReadlineBridge:
"""Bridge which provides readline-like commands for the current QLineEdit.
Attributes:
deleted: Mapping from widgets to their last deleted text.
_deleted: Mapping from widgets to their last deleted text.
"""
def __init__(self):
self.deleted = {}
self._deleted = {}
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def _widget(self):
"""Get the currently active QLineEdit."""
@ -44,7 +47,7 @@ class ReadlineBridge:
else:
return None
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_backward_char(self):
"""Move back a character.
@ -56,7 +59,7 @@ class ReadlineBridge:
return
widget.cursorBackward(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_forward_char(self):
"""Move forward a character.
@ -68,7 +71,7 @@ class ReadlineBridge:
return
widget.cursorForward(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_backward_word(self):
"""Move back to the start of the current or previous word.
@ -80,7 +83,7 @@ class ReadlineBridge:
return
widget.cursorWordBackward(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_forward_word(self):
"""Move forward to the end of the next word.
@ -92,7 +95,7 @@ class ReadlineBridge:
return
widget.cursorWordForward(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_beginning_of_line(self):
"""Move to the start of the line.
@ -104,7 +107,7 @@ class ReadlineBridge:
return
widget.home(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_end_of_line(self):
"""Move to the end of the line.
@ -116,7 +119,7 @@ class ReadlineBridge:
return
widget.end(False)
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_unix_line_discard(self):
"""Remove chars backward from the cursor to the beginning of the line.
@ -127,10 +130,10 @@ class ReadlineBridge:
if widget is None:
return
widget.home(True)
self.deleted[widget] = widget.selectedText()
self._deleted[widget] = widget.selectedText()
widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_kill_line(self):
"""Remove chars from the cursor to the end of the line.
@ -141,10 +144,10 @@ class ReadlineBridge:
if widget is None:
return
widget.end(True)
self.deleted[widget] = widget.selectedText()
self._deleted[widget] = widget.selectedText()
widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_unix_word_rubout(self):
"""Remove chars from the cursor to the beginning of the word.
@ -155,10 +158,10 @@ class ReadlineBridge:
if widget is None:
return
widget.cursorWordBackward(True)
self.deleted[widget] = widget.selectedText()
self._deleted[widget] = widget.selectedText()
widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_kill_word(self):
"""Remove chars from the cursor to the end of the current word.
@ -169,10 +172,10 @@ class ReadlineBridge:
if widget is None:
return
widget.cursorWordForward(True)
self.deleted[widget] = widget.selectedText()
self._deleted[widget] = widget.selectedText()
widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_yank(self):
"""Paste the most recently deleted text.
@ -180,11 +183,11 @@ class ReadlineBridge:
This acts like readline's yank.
"""
widget = self._widget()
if widget is None or widget not in self.deleted:
if widget is None or widget not in self._deleted:
return
widget.insert(self.deleted[widget])
widget.insert(self._deleted[widget])
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_delete_char(self):
"""Delete the character after the cursor.
@ -196,7 +199,7 @@ class ReadlineBridge:
return
widget.del_()
@cmdutils.register(instance='rl_bridge', hide=True,
@cmdutils.register(instance='readline-bridge', hide=True,
modes=[typ.KeyMode.command, typ.KeyMode.prompt])
def rl_backward_delete_char(self):
"""Delete the character before the cursor.

View File

@ -23,6 +23,7 @@ Module attributes:
_UNSET: Used as default argument in the constructor so default can be None.
"""
import itertools
import operator
import collections.abc
import enum as pyenum
@ -35,6 +36,9 @@ from qutebrowser.utils import log, qtutils
_UNSET = object()
tab_id_gen = itertools.count(0)
def enum(name, *items, start=1, is_int=False):
"""Factory for simple enumerations.
@ -51,18 +55,6 @@ def enum(name, *items, start=1, is_int=False):
return base(name, enums)
class EnumBase(type):
"""Metaclass for enums to provide __getitem__ for reverse mapping."""
def __init__(cls, name, base, fields):
super().__init__(name, base, fields)
cls._mapping = dict((v, k) for k, v in fields.items())
def __getitem__(cls, key):
return cls._mapping[key]
class NeighborList(collections.abc.Sequence):
"""A list of items which saves it current position.
@ -71,8 +63,8 @@ class NeighborList(collections.abc.Sequence):
Modes: Different modes, see constructor documentation.
Attributes:
idx: The current position in the list.
fuzzyval: The value which is currently set but not in the list.
_idx: The current position in the list.
_items: A list of all items, accessed through item property.
_mode: The current mode.
"""
@ -98,9 +90,9 @@ class NeighborList(collections.abc.Sequence):
self._items = list(items)
self._default = default
if default is not _UNSET:
self.idx = self._items.index(default)
self._idx = self._items.index(default)
else:
self.idx = None
self._idx = None
self._mode = mode
self.fuzzyval = None
@ -128,7 +120,7 @@ class NeighborList(collections.abc.Sequence):
items = [(idx, e) for (idx, e) in enumerate(self._items)
if op(e, self.fuzzyval)]
close_item = min(items, key=lambda tpl: abs(self.fuzzyval - tpl[1]))
self.idx = close_item[0]
self._idx = close_item[0]
return self.fuzzyval not in self._items
def _get_new_item(self, offset):
@ -145,21 +137,21 @@ class NeighborList(collections.abc.Sequence):
exception.
"""
try:
if self.idx + offset >= 0:
new = self._items[self.idx + offset]
if self._idx + offset >= 0:
new = self._items[self._idx + offset]
else:
raise IndexError
except IndexError:
if self._mode == self.Modes.block:
new = self.curitem()
elif self._mode == self.Modes.wrap:
self.idx += offset
self.idx %= len(self.items)
self._idx += offset
self._idx %= len(self.items)
new = self.curitem()
elif self._mode == self.Modes.exception:
raise
else:
self.idx += offset
self._idx += offset
return new
@property
@ -181,7 +173,7 @@ class NeighborList(collections.abc.Sequence):
exception.
"""
log.misc.debug("{} items, idx {}, offset {}".format(
len(self._items), self.idx, offset))
len(self._items), self._idx, offset))
if not self._items:
raise IndexError("No items found!")
if self.fuzzyval is not None:
@ -198,8 +190,8 @@ class NeighborList(collections.abc.Sequence):
def curitem(self):
"""Get the current item in the list."""
if self.idx is not None:
return self._items[self.idx]
if self._idx is not None:
return self._items[self._idx]
else:
raise IndexError("No current item!")
@ -215,14 +207,14 @@ class NeighborList(collections.abc.Sequence):
"""Get the first item in the list."""
if not self._items:
raise IndexError("No items found!")
self.idx = 0
self._idx = 0
return self.curitem()
def lastitem(self):
"""Get the last item in the list."""
if not self._items:
raise IndexError("No items found!")
self.idx = len(self._items) - 1
self._idx = len(self._items) - 1
return self.curitem()
def reset(self):
@ -230,7 +222,7 @@ class NeighborList(collections.abc.Sequence):
if self._default is _UNSET:
raise ValueError("No default set!")
else:
self.idx = self._items.index(self._default)
self._idx = self._items.index(self._default)
return self.curitem()

View File

@ -25,9 +25,9 @@ import functools
from PyQt5.QtCore import QCoreApplication
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
from qutebrowser.commands import runners, cmdexc, cmdutils
from qutebrowser.config import config, style
from qutebrowser.config import style
_timers = []
@ -86,13 +86,6 @@ def debug_crash(typ: ('exception', 'segfault')='exception'):
raise Exception("Forced crash")
@cmdutils.register(debug=True)
def debug_all_widgets():
"""Print a list of all widgets to debug log."""
s = QCoreApplication.instance().get_all_widgets()
log.misc.debug(s)
@cmdutils.register(debug=True)
def debug_all_objects():
"""Print a list of all objects to the debug log."""
@ -103,7 +96,13 @@ def debug_all_objects():
@cmdutils.register(debug=True)
def debug_cache_stats():
"""Print LRU cache stats."""
config_info = config.instance().get.cache_info()
config_info = objreg.get('config').get.cache_info()
style_info = style.get_stylesheet.cache_info()
log.misc.debug('config: {}'.format(config_info))
log.misc.debug('style: {}'.format(style_info))
@cmdutils.register(debug=True)
def debug_console():
"""Show the debugging console."""
objreg.get('debug-console').show()

View File

@ -532,8 +532,8 @@ class prevent_exceptions: # pylint: disable=invalid-name
much cleaner to implement.
Attributes:
retval: The value to return in case of an exception.
predicate: The condition which needs to be True to prevent exceptions
_retval: The value to return in case of an exception.
_predicate: The condition which needs to be True to prevent exceptions
"""
def __init__(self, retval, predicate=True):
@ -544,8 +544,8 @@ class prevent_exceptions: # pylint: disable=invalid-name
Args:
See class attributes.
"""
self.retval = retval
self.predicate = predicate
self._retval = retval
self._predicate = predicate
def __call__(self, func):
"""Gets called when a function should be decorated.
@ -556,10 +556,10 @@ class prevent_exceptions: # pylint: disable=invalid-name
Return:
The decorated function.
"""
if not self.predicate:
if not self._predicate:
return func
retval = self.retval
retval = self._retval
@functools.wraps(func)
def wrapper(*args, **kwargs): # pylint: disable=missing-docstring

View File

@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel
from qutebrowser.commands import cmdutils
from qutebrowser.config import config, style
from qutebrowser.widgets import completiondelegate
from qutebrowser.utils import completer, usertypes, qtutils
from qutebrowser.utils import completer, usertypes, qtutils, objreg
class CompletionView(QTreeView):
@ -45,7 +45,6 @@ class CompletionView(QTreeView):
COLUMN_WIDTHS: A list of column widths, in percent.
Attributes:
completer: The Completer instance to use.
enabled: Whether showing the CompletionView is enabled.
_height: The height to use for the CompletionView.
_height_perc: Either None or a percentage if height should be relative.
@ -92,7 +91,9 @@ class CompletionView(QTreeView):
def __init__(self, parent=None):
super().__init__(parent)
self.completer = completer.Completer(self)
objreg.register('completion', self)
completer_obj = completer.Completer()
objreg.register('completer', completer_obj)
self.enabled = config.get('completion', 'show')
self._delegate = completiondelegate.CompletionItemDelegate(self)
@ -210,13 +211,13 @@ class CompletionView(QTreeView):
selmod.clearSelection()
selmod.clearCurrentIndex()
@cmdutils.register(instance='mainwindow.completion', hide=True,
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command])
def completion_item_prev(self):
"""Select the previous completion item."""
self._next_prev_item(prev=True)
@cmdutils.register(instance='mainwindow.completion', hide=True,
@cmdutils.register(instance='completion', hide=True,
modes=[usertypes.KeyMode.command])
def completion_item_next(self):
"""Select the next completion item."""
@ -225,7 +226,7 @@ class CompletionView(QTreeView):
def selectionChanged(self, selected, deselected):
"""Extend selectionChanged to call completers selection_changed."""
super().selectionChanged(selected, deselected)
self.completer.selection_changed(selected, deselected)
objreg.get('completer').selection_changed(selected, deselected)
def resizeEvent(self, e):
"""Extend resizeEvent to adjust column size."""

View File

@ -33,7 +33,14 @@ from qutebrowser.widgets import misc
class ConsoleLineEdit(misc.CommandLineEdit):
"""A QLineEdit which executes entered code and provides a history."""
"""A QLineEdit which executes entered code and provides a history.
Attributes:
_history: The command history of executed commands.
_more: A flag which is set when more input is expected.
_buffer: The buffer for multiline commands.
_interpreter: The InteractiveInterpreter to execute code with.
"""
write = pyqtSignal(str)
@ -56,7 +63,7 @@ class ConsoleLineEdit(misc.CommandLineEdit):
'self': parent,
}
self._interpreter = code.InteractiveInterpreter(interpreter_locals)
self.history = cmdhistory.History()
self._history = cmdhistory.History()
self.returnPressed.connect(self.execute)
self.setText('')
@ -67,10 +74,10 @@ class ConsoleLineEdit(misc.CommandLineEdit):
@pyqtSlot(str)
def execute(self):
"""Execute the line of code which was entered."""
self.history.stop()
self._history.stop()
text = self.text()
if text:
self.history.append(text)
self._history.append(text)
self.push(text)
self.setText('')
@ -95,10 +102,10 @@ class ConsoleLineEdit(misc.CommandLineEdit):
def history_prev(self):
"""Go back in the history."""
try:
if not self.history.is_browsing():
item = self.history.start(self.text().strip())
if not self._history.is_browsing():
item = self._history.start(self.text().strip())
else:
item = self.history.previtem()
item = self._history.previtem()
except (cmdhistory.HistoryEmptyError,
cmdhistory.HistoryEndReachedError):
return
@ -106,10 +113,10 @@ class ConsoleLineEdit(misc.CommandLineEdit):
def history_next(self):
"""Go forward in the history."""
if not self.history.is_browsing():
if not self._history.is_browsing():
return
try:
item = self.history.nextitem()
item = self._history.nextitem()
except cmdhistory.HistoryEndReachedError:
return
self.setText(item)
@ -151,6 +158,9 @@ class ConsoleTextEdit(QTextEdit):
self.setFont(config.get('fonts', 'debug-console'))
self.setFocusPolicy(Qt.NoFocus)
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def on_config_changed(self, section, option):
"""Update font when config changed."""
if section == 'fonts' and option == 'debug-console':
@ -159,22 +169,32 @@ class ConsoleTextEdit(QTextEdit):
class ConsoleWidget(QWidget):
"""A widget with an interactive Python console."""
"""A widget with an interactive Python console.
Attributes:
_lineedit: The line edit in the console.
_output: The output widget in the console.
_vbox: The layout which contains everything.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.lineedit = ConsoleLineEdit(self)
self.output = ConsoleTextEdit()
self.lineedit.write.connect(self.output.append)
self.vbox = QVBoxLayout()
self.vbox.setSpacing(0)
self.vbox.addWidget(self.output)
self.vbox.addWidget(self.lineedit)
self.setLayout(self.vbox)
self.lineedit.setFocus()
self._lineedit = ConsoleLineEdit(self)
self._output = ConsoleTextEdit()
self._lineedit.write.connect(self._output.append)
self._vbox = QVBoxLayout()
self._vbox.setSpacing(0)
self._vbox.addWidget(self._output)
self._vbox.addWidget(self._lineedit)
self.setLayout(self._vbox)
self._lineedit.setFocus()
def __repr__(self):
return '<{}, visible={}>'.format(
self.__class__.__name__, self.isVisible())
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""Update font when config changed."""
self.lineedit.on_config_changed(section, option)
self.output.on_config_changed(section, option)
self._lineedit.on_config_changed(section, option)
self._output.on_config_changed(section, option)

View File

@ -29,8 +29,7 @@ from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
QVBoxLayout, QHBoxLayout)
from qutebrowser.config import config
from qutebrowser.utils import version, log, utils
from qutebrowser.utils import version, log, utils, objreg
class _CrashDialog(QDialog):
@ -131,8 +130,8 @@ class _CrashDialog(QDialog):
except Exception:
self._crash_info.append(("Version info", traceback.format_exc()))
try:
self._crash_info.append(("Config",
config.instance().dump_userconfig()))
conf = objreg.get('config')
self._crash_info.append(("Config", conf.dump_userconfig()))
except Exception:
self._crash_info.append(("Config", traceback.format_exc()))
@ -176,18 +175,16 @@ class ExceptionCrashDialog(_CrashDialog):
_pages: A list of the open pages (URLs as strings)
_cmdhist: A list with the command history (as strings)
_exc: An exception tuple (type, value, traceback)
_widgets: A list of active widgets as string.
_objects: A list of all QObjects as string.
"""
def __init__(self, pages, cmdhist, exc, widgets, objects, parent=None):
def __init__(self, pages, cmdhist, exc, objects, parent=None):
self._pages = pages
self._cmdhist = cmdhist
self._exc = exc
self._btn_quit = None
self._btn_restore = None
self._btn_pastebin = None
self._widgets = widgets
self._objects = objects
super().__init__(parent)
self.setModal(True)
@ -225,7 +222,6 @@ class ExceptionCrashDialog(_CrashDialog):
("Commandline args", ' '.join(sys.argv[1:])),
("Open Pages", '\n'.join(self._pages)),
("Command history", '\n'.join(self._cmdhist)),
("Widgets", self._widgets),
("Objects", self._objects),
]
try:
@ -285,16 +281,14 @@ class ReportDialog(_CrashDialog):
_btn_pastebin: The pastebin button.
_pages: A list of the open pages (URLs as strings)
_cmdhist: A list with the command history (as strings)
_widgets: A list of active widgets as string.
_objects: A list of all QObjects as string.
"""
def __init__(self, pages, cmdhist, widgets, objects, parent=None):
def __init__(self, pages, cmdhist, objects, parent=None):
self._pages = pages
self._cmdhist = cmdhist
self._btn_ok = None
self._btn_pastebin = None
self._widgets = widgets
self._objects = objects
super().__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
@ -324,7 +318,6 @@ class ReportDialog(_CrashDialog):
("Commandline args", ' '.join(sys.argv[1:])),
("Open Pages", '\n'.join(self._pages)),
("Command history", '\n'.join(self._cmdhist)),
("Widgets", self._widgets),
("Objects", self._objects),
]
try:

View File

@ -22,12 +22,12 @@
import binascii
import base64
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QCoreApplication, QTimer
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from qutebrowser.commands import cmdutils
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, qtutils
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
from qutebrowser.widgets import tabbedbrowser, completion, downloads
from qutebrowser.widgets.statusbar import bar
@ -40,9 +40,9 @@ class MainWindow(QWidget):
signals.
Attributes:
tabs: The TabbedBrowser widget.
status: The StatusBar widget.
downloadview: The DownloadView widget.
_downloadview: The DownloadView widget.
_tabbed_browser: The TabbedBrowser widget.
_vbox: The main QVBoxLayout.
"""
@ -50,9 +50,9 @@ class MainWindow(QWidget):
super().__init__(parent)
self.setWindowTitle('qutebrowser')
state_config = objreg.get('state-config')
try:
stateconf = QCoreApplication.instance().stateconfig
data = stateconf['geometry']['mainwindow']
data = state_config['geometry']['mainwindow']
log.init.debug("Restoring mainwindow from {}".format(data))
geom = base64.b64decode(data, validate=True)
except KeyError:
@ -77,15 +77,16 @@ class MainWindow(QWidget):
self._vbox.setContentsMargins(0, 0, 0, 0)
self._vbox.setSpacing(0)
self.downloadview = downloads.DownloadView()
self._vbox.addWidget(self.downloadview)
self.downloadview.show()
self._downloadview = downloads.DownloadView()
self._vbox.addWidget(self._downloadview)
self._downloadview.show()
self.tabs = tabbedbrowser.TabbedBrowser()
self.tabs.title_changed.connect(self.setWindowTitle)
self._vbox.addWidget(self.tabs)
self._tabbed_browser = tabbedbrowser.TabbedBrowser()
self._tabbed_browser.title_changed.connect(self.setWindowTitle)
objreg.register('tabbed-browser', self._tabbed_browser)
self._vbox.addWidget(self._tabbed_browser)
self.completion = completion.CompletionView(self)
self._completion = completion.CompletionView(self)
self.status = bar.StatusBar()
self._vbox.addWidget(self.status)
@ -93,9 +94,7 @@ class MainWindow(QWidget):
# When we're here the statusbar might not even really exist yet, so
# resizing will fail. Therefore, we use singleShot QTimers to make sure
# we defer this until everything else is initialized.
QTimer.singleShot(0, lambda: self.completion.resize_completion.connect(
self.resize_completion))
QTimer.singleShot(0, self.resize_completion)
QTimer.singleShot(0, self._connect_resize_completion)
#self.retranslateUi(MainWindow)
#self.tabWidget.setCurrentIndex(0)
#QtCore.QMetaObject.connectSlotsByName(MainWindow)
@ -103,6 +102,11 @@ class MainWindow(QWidget):
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
def _connect_resize_completion(self):
"""Connect the resize_completion signal and resize it once."""
self._completion.resize_completion.connect(self.resize_completion)
self.resize_completion()
def _set_default_geometry(self):
"""Set some sensible default geometry."""
self.setGeometry(QRect(50, 50, 800, 600))
@ -126,8 +130,8 @@ class MainWindow(QWidget):
# Shrink to content size if needed and shrinking is enabled
if config.get('completion', 'shrink'):
contents_height = (
self.completion.viewportSizeHint().height() +
self.completion.horizontalScrollBar().sizeHint().height())
self._completion.viewportSizeHint().height() +
self._completion.horizontalScrollBar().sizeHint().height())
if contents_height <= height:
height = contents_height
else:
@ -140,9 +144,9 @@ class MainWindow(QWidget):
bottomright = self.status.geometry().topRight()
rect = QRect(topleft, bottomright)
if rect.isValid():
self.completion.setGeometry(rect)
self._completion.setGeometry(rect)
@cmdutils.register(instance='mainwindow', name=['quit', 'q'])
@cmdutils.register(instance='main-window', name=['quit', 'q'])
def close(self):
"""Quit qutebrowser.
@ -160,13 +164,13 @@ class MainWindow(QWidget):
"""
super().resizeEvent(e)
self.resize_completion()
self.downloadview.updateGeometry()
self.tabs.tabBar().refresh()
self._downloadview.updateGeometry()
self._tabbed_browser.tabBar().refresh()
def closeEvent(self, e):
"""Override closeEvent to display a confirmation if needed."""
confirm_quit = config.get('ui', 'confirm-quit')
count = self.tabs.count()
count = self._tabbed_browser.count()
if confirm_quit == 'never':
e.accept()
elif confirm_quit == 'multiple-tabs' and count <= 1:

View File

@ -98,7 +98,11 @@ class CommandLineEdit(QLineEdit):
class _CommandValidator(QValidator):
"""Validator to prevent the : from getting deleted."""
"""Validator to prevent the : from getting deleted.
Attributes:
prompt: The current prompt.
"""
def __init__(self, parent=None):
super().__init__(parent)

View File

@ -25,9 +25,8 @@ import datetime
from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
from qutebrowser.keyinput import modeman
from qutebrowser.config import config, style
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
from qutebrowser.widgets.statusbar import (command, progress, keystring,
percentage, url, prompt)
from qutebrowser.widgets.statusbar import text as textwidget
@ -41,12 +40,12 @@ class StatusBar(QWidget):
"""The statusbar at the bottom of the mainwindow.
Attributes:
cmd: The Command widget in the statusbar.
txt: The Text widget in the statusbar.
keystring: The KeyString widget in the statusbar.
percentage: The Percentage widget in the statusbar.
url: The UrlText widget in the statusbar.
prog: The Progress widget in the statusbar.
_cmd: The Command widget in the statusbar.
_hbox: The main QHBoxLayout.
_stack: The QStackedLayout with cmd/txt widgets.
_text_queue: A deque of (error, text) tuples to be displayed.
@ -132,8 +131,9 @@ class StatusBar(QWidget):
self._hbox.addLayout(self._stack)
self._stack.setContentsMargins(0, 0, 0, 0)
self.cmd = command.Command()
self._stack.addWidget(self.cmd)
self._cmd = command.Command()
objreg.register('status-command', self._cmd)
self._stack.addWidget(self._cmd)
self.txt = textwidget.Text()
self._stack.addWidget(self.txt)
@ -147,8 +147,8 @@ class StatusBar(QWidget):
self._stack.addWidget(self.prompt)
self._previous_widget = PreviousWidget.none
self.cmd.show_cmd.connect(self._show_cmd_widget)
self.cmd.hide_cmd.connect(self._hide_cmd_widget)
self._cmd.show_cmd.connect(self._show_cmd_widget)
self._cmd.hide_cmd.connect(self._hide_cmd_widget)
self._hide_cmd_widget()
self.prompt.show_prompt.connect(self._show_prompt_widget)
self.prompt.hide_prompt.connect(self._hide_prompt_widget)
@ -260,7 +260,7 @@ class StatusBar(QWidget):
if self._text_pop_timer.isActive():
self._timer_was_active = True
self._text_pop_timer.stop()
self._stack.setCurrentWidget(self.cmd)
self._stack.setCurrentWidget(self._cmd)
def _hide_cmd_widget(self):
"""Show temporary text instead of command widget."""
@ -376,7 +376,7 @@ class StatusBar(QWidget):
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
"""Mark certain modes in the commandline."""
if mode in modeman.instance().passthrough:
if mode in objreg.get('mode-manager').passthrough:
text = "-- {} MODE --".format(mode.name.upper())
self.txt.set_text(self.txt.Text.normal, text)
if mode == usertypes.KeyMode.insert:
@ -385,7 +385,7 @@ class StatusBar(QWidget):
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Clear marked mode."""
if mode in modeman.instance().passthrough:
if mode in objreg.get('mode-manager').passthrough:
self.txt.set_text(self.txt.Text.normal, '')
if mode == usertypes.KeyMode.insert:
self._set_insert_active(False)

View File

@ -19,14 +19,14 @@
"""The commandline in the statusbar."""
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QCoreApplication, QUrl
from PyQt5.QtWidgets import QSizePolicy, QApplication
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
from PyQt5.QtWidgets import QSizePolicy
from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.commands import runners, cmdexc, cmdutils
from qutebrowser.widgets import misc
from qutebrowser.models import cmdhistory
from qutebrowser.utils import usertypes, log
from qutebrowser.utils import usertypes, log, objreg
class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
@ -34,9 +34,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
"""The commandline part of the statusbar.
Attributes:
cursor_part: The part the cursor is currently over.
parts: A list of strings with the split commandline
prefix: The prefix currently entered.
_cursor_part: The part the cursor is currently over.
Signals:
got_cmd: Emitted when a command is triggered by the user.
@ -73,8 +71,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
def __init__(self, parent=None):
misc.CommandLineEdit.__init__(self, parent)
misc.MinimalLineEditMixin.__init__(self)
self.cursor_part = 0
self.history.history = QApplication.instance().cmd_history.data
self._cursor_part = 0
self.history.history = objreg.get('command-history').data
self._empty_item_idx = None
self.textEdited.connect(self.on_text_edited)
self.cursorPositionChanged.connect(self._update_cursor_part)
@ -124,7 +122,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
for i, part in enumerate(self.split()):
if cursor_pos <= len(part):
# foo| bar
self.cursor_part = i
self._cursor_part = i
if spaces:
self._empty_item_idx = i
else:
@ -132,14 +130,14 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
break
cursor_pos -= (len(part) + 1) # FIXME are spaces always 1 char?
log.completion.debug("cursor_part {}, spaces {}".format(
self.cursor_part, spaces))
self._cursor_part, spaces))
return
@pyqtSlot()
def on_cursor_position_changed(self):
"""Update completion when the cursor position changed."""
self.update_completion.emit(self.prefix(), self.split(),
self.cursor_part)
self._cursor_part)
@pyqtSlot(str)
def set_cmd_text(self, text):
@ -156,11 +154,11 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
if old_text != text:
# We want the completion to pop out here.
self.update_completion.emit(self.prefix(), self.split(),
self.cursor_part)
self._cursor_part)
self.setFocus()
self.show_cmd.emit()
@cmdutils.register(instance='mainwindow.status.cmd', name='set-cmd-text')
@cmdutils.register(instance='status-command', name='set-cmd-text')
def set_cmd_text_command(self, text):
"""Preset the statusbar to some text.
@ -172,8 +170,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
Args:
text: The commandline to set.
"""
app = QCoreApplication.instance()
url = app.mainwindow.tabs.current_url().toString(
url = objreg.get('tabbed-browser').current_url().toString(
QUrl.FullyEncoded | QUrl.RemovePassword)
# FIXME we currently replace the URL in any place in the arguments,
# rather than just replacing it if it is a dedicated argument. We could
@ -197,16 +194,16 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
"""
parts = self.split()
log.completion.debug("changing part {} to '{}'".format(
self.cursor_part, newtext))
parts[self.cursor_part] = newtext
self._cursor_part, newtext))
parts[self._cursor_part] = newtext
# We want to place the cursor directly after the part we just changed.
cursor_str = self.prefix() + ' '.join(parts[:self.cursor_part + 1])
cursor_str = self.prefix() + ' '.join(parts[:self._cursor_part + 1])
if immediate:
# If we should complete immediately, we want to move the cursor by
# one more char, to get to the next field.
cursor_str += ' '
text = self.prefix() + ' '.join(parts)
if immediate and self.cursor_part == len(parts) - 1:
if immediate and self._cursor_part == len(parts) - 1:
# If we should complete immediately and we're completing the last
# part in the commandline, we automatically add a space.
text += ' '
@ -216,7 +213,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.setFocus()
self.show_cmd.emit()
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
def command_history_prev(self):
"""Go back in the commandline history."""
@ -231,7 +228,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
if item:
self.set_cmd_text(item)
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
def command_history_next(self):
"""Go forward in the commandline history."""
@ -244,7 +241,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
if item:
self.set_cmd_text(item)
@cmdutils.register(instance='mainwindow.status.cmd', hide=True,
@cmdutils.register(instance='status-command', hide=True,
modes=[usertypes.KeyMode.command])
def command_accept(self):
"""Execute the command currently in the commandline.

View File

@ -24,6 +24,7 @@ from PyQt5.QtWidgets import QHBoxLayout, QWidget, QLineEdit
from qutebrowser.widgets import misc
from qutebrowser.widgets.statusbar import textbase, prompter
from qutebrowser.utils import objreg
class PromptLineEdit(misc.MinimalLineEditMixin, QLineEdit):
@ -40,7 +41,6 @@ class Prompt(QWidget):
"""The prompt widget shown in the statusbar.
Attributes:
prompter: The Prompter instance assosciated with this Prompt.
txt: The TextBase instance (QLabel) used to display the prompt text.
lineedit: The MinimalLineEdit instance (QLineEdit) used for the input.
_hbox: The QHBoxLayout used to display the text and prompt.
@ -55,6 +55,7 @@ class Prompt(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
objreg.register('prompt', self)
self._hbox = QHBoxLayout(self)
self._hbox.setContentsMargins(0, 0, 0, 0)
self._hbox.setSpacing(5)
@ -65,7 +66,8 @@ class Prompt(QWidget):
self.lineedit = PromptLineEdit()
self._hbox.addWidget(self.lineedit)
self.prompter = prompter.Prompter(self)
prompter_obj = prompter.Prompter()
objreg.register('prompter', prompter_obj)
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)

View File

@ -26,7 +26,7 @@ from PyQt5.QtWidgets import QLineEdit
from qutebrowser.keyinput import modeman
from qutebrowser.commands import cmdutils
from qutebrowser.utils import usertypes, log, qtutils
from qutebrowser.utils import usertypes, log, qtutils, objreg
PromptContext = collections.namedtuple('PromptContext',
@ -57,18 +57,16 @@ class Prompter:
up the *new* question.
Attributes:
question: A Question object with the question to be asked to the user.
_question: A Question object with the question to be asked to the user.
_loops: A list of local EventLoops to spin in when blocking.
_queue: A deque of waiting questions.
_prompt: The associated Prompt widget.
"""
def __init__(self, prompt):
self.question = None
def __init__(self):
self._question = None
self._loops = []
self._queue = collections.deque()
self._busy = False
self._prompt = prompt
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
@ -87,11 +85,12 @@ class Prompter:
"""Get a PromptContext based on the current state."""
if not self._busy:
return None
ctx = PromptContext(question=self.question,
text=self._prompt.txt.text(),
input_text=self._prompt.lineedit.text(),
echo_mode=self._prompt.lineedit.echoMode(),
input_visible=self._prompt.lineedit.isVisible())
prompt = objreg.get('prompt')
ctx = PromptContext(question=self._question,
text=prompt.txt.text(),
input_text=prompt.lineedit.text(),
echo_mode=prompt.lineedit.echoMode(),
input_visible=prompt.lineedit.isVisible())
return ctx
def _restore_ctx(self, ctx):
@ -103,19 +102,20 @@ class Prompter:
Return: True if a context was restored, False otherwise.
"""
log.statusbar.debug("Restoring context {}".format(ctx))
prompt = objreg.get('prompt')
if ctx is None:
self._prompt.hide_prompt.emit()
prompt.hide_prompt.emit()
self._busy = False
return False
self.question = ctx.question
self._prompt.txt.setText(ctx.text)
self._prompt.lineedit.setText(ctx.input_text)
self._prompt.lineedit.setEchoMode(ctx.echo_mode)
self._prompt.lineedit.setVisible(ctx.input_visible)
self._question = ctx.question
prompt.txt.setText(ctx.text)
prompt.lineedit.setText(ctx.input_text)
prompt.lineedit.setEchoMode(ctx.echo_mode)
prompt.lineedit.setVisible(ctx.input_visible)
return True
def _display_question(self):
"""Display the question saved in self.question.
"""Display the question saved in self._question.
Return:
The mode which should be entered.
@ -123,36 +123,37 @@ class Prompter:
Raise:
ValueError if the set PromptMode is invalid.
"""
if self.question.mode == usertypes.PromptMode.yesno:
if self.question.default is None:
prompt = objreg.get('prompt')
if self._question.mode == usertypes.PromptMode.yesno:
if self._question.default is None:
suffix = ""
elif self.question.default:
elif self._question.default:
suffix = " (yes)"
else:
suffix = " (no)"
self._prompt.txt.setText(self.question.text + suffix)
self._prompt.lineedit.hide()
prompt.txt.setText(self._question.text + suffix)
prompt.lineedit.hide()
mode = usertypes.KeyMode.yesno
elif self.question.mode == usertypes.PromptMode.text:
self._prompt.txt.setText(self.question.text)
if self.question.default:
self._prompt.lineedit.setText(self.question.default)
self._prompt.lineedit.show()
elif self._question.mode == usertypes.PromptMode.text:
prompt.txt.setText(self._question.text)
if self._question.default:
prompt.lineedit.setText(self._question.default)
prompt.lineedit.show()
mode = usertypes.KeyMode.prompt
elif self.question.mode == usertypes.PromptMode.user_pwd:
self._prompt.txt.setText(self.question.text)
if self.question.default:
self._prompt.lineedit.setText(self.question.default)
self._prompt.lineedit.show()
elif self._question.mode == usertypes.PromptMode.user_pwd:
prompt.txt.setText(self._question.text)
if self._question.default:
prompt.lineedit.setText(self._question.default)
prompt.lineedit.show()
mode = usertypes.KeyMode.prompt
elif self.question.mode == usertypes.PromptMode.alert:
self._prompt.txt.setText(self.question.text + ' (ok)')
self._prompt.lineedit.hide()
elif self._question.mode == usertypes.PromptMode.alert:
prompt.txt.setText(self._question.text + ' (ok)')
prompt.lineedit.hide()
mode = usertypes.KeyMode.prompt
else:
raise ValueError("Invalid prompt mode!")
self._prompt.lineedit.setFocus()
self._prompt.show_prompt.emit()
prompt.lineedit.setFocus()
prompt.show_prompt.emit()
self._busy = True
return mode
@ -176,16 +177,17 @@ class Prompter:
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Clear and reset input when the mode was left."""
prompt = objreg.get('prompt')
if mode in (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno):
self._prompt.txt.setText('')
self._prompt.lineedit.clear()
self._prompt.lineedit.setEchoMode(QLineEdit.Normal)
self._prompt.hide_prompt.emit()
prompt.txt.setText('')
prompt.lineedit.clear()
prompt.lineedit.setEchoMode(QLineEdit.Normal)
prompt.hide_prompt.emit()
self._busy = False
if self.question.answer is None and not self.question.is_aborted:
self.question.cancel()
if self._question.answer is None and not self._question.is_aborted:
self._question.cancel()
@cmdutils.register(instance='mainwindow.status.prompt.prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True,
modes=[usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno])
def prompt_accept(self):
@ -196,58 +198,59 @@ class Prompter:
This executes the next action depending on the question mode, e.g. asks
for the password or leaves the mode.
"""
if (self.question.mode == usertypes.PromptMode.user_pwd and
self.question.user is None):
prompt = objreg.get('prompt')
if (self._question.mode == usertypes.PromptMode.user_pwd and
self._question.user is None):
# User just entered an username
self.question.user = self._prompt.lineedit.text()
self._prompt.txt.setText("Password:")
self._prompt.lineedit.clear()
self._prompt.lineedit.setEchoMode(QLineEdit.Password)
elif self.question.mode == usertypes.PromptMode.user_pwd:
self._question.user = prompt.lineedit.text()
prompt.txt.setText("Password:")
prompt.lineedit.clear()
prompt.lineedit.setEchoMode(QLineEdit.Password)
elif self._question.mode == usertypes.PromptMode.user_pwd:
# User just entered a password
password = self._prompt.lineedit.text()
self.question.answer = (self.question.user, password)
password = prompt.lineedit.text()
self._question.answer = (self._question.user, password)
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
self.question.done()
elif self.question.mode == usertypes.PromptMode.text:
self._question.done()
elif self._question.mode == usertypes.PromptMode.text:
# User just entered text.
self.question.answer = self._prompt.lineedit.text()
self._question.answer = prompt.lineedit.text()
modeman.leave(usertypes.KeyMode.prompt, 'prompt accept')
self.question.done()
elif self.question.mode == usertypes.PromptMode.yesno:
self._question.done()
elif self._question.mode == usertypes.PromptMode.yesno:
# User wants to accept the default of a yes/no question.
self.question.answer = self.question.default
self._question.answer = self._question.default
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
self.question.done()
elif self.question.mode == usertypes.PromptMode.alert:
self._question.done()
elif self._question.mode == usertypes.PromptMode.alert:
# User acknowledged an alert
self.question.answer = None
self._question.answer = None
modeman.leave(usertypes.KeyMode.prompt, 'alert accept')
self.question.done()
self._question.done()
else:
raise ValueError("Invalid question mode!")
@cmdutils.register(instance='mainwindow.status.prompt.prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True,
modes=[usertypes.KeyMode.yesno])
def prompt_yes(self):
"""Answer yes to a yes/no prompt."""
if self.question.mode != usertypes.PromptMode.yesno:
if self._question.mode != usertypes.PromptMode.yesno:
# We just ignore this if we don't have a yes/no question.
return
self.question.answer = True
self._question.answer = True
modeman.leave(usertypes.KeyMode.yesno, 'yesno accept')
self.question.done()
self._question.done()
@cmdutils.register(instance='mainwindow.status.prompt.prompter', hide=True,
@cmdutils.register(instance='prompter', hide=True,
modes=[usertypes.KeyMode.yesno])
def prompt_no(self):
"""Answer no to a yes/no prompt."""
if self.question.mode != usertypes.PromptMode.yesno:
if self._question.mode != usertypes.PromptMode.yesno:
# We just ignore this if we don't have a yes/no question.
return
self.question.answer = False
self._question.answer = False
modeman.leave(usertypes.KeyMode.yesno, 'prompt accept')
self.question.done()
self._question.done()
@pyqtSlot(usertypes.Question, bool)
def ask_question(self, question, blocking):
@ -277,16 +280,17 @@ class Prompter:
# restore it after exec, if exec gets called multiple times.
context = self._get_ctx()
self.question = question
self._question = question
mode = self._display_question()
question.aborted.connect(lambda: modeman.maybe_leave(mode, 'aborted'))
mode_manager = objreg.get('mode-manager')
try:
modeman.enter(mode, 'question asked')
except modeman.ModeLockedError:
if modeman.instance().mode() != usertypes.KeyMode.prompt:
if mode_manager.mode() != usertypes.KeyMode.prompt:
question.abort()
return None
modeman.instance().locked = True
mode_manager.locked = True
if blocking:
loop = qtutils.EventLoop()
self._loops.append(loop)
@ -299,6 +303,6 @@ class Prompter:
# questions.
if self._queue:
self._pop_later()
return self.question.answer
return self._question.answer
else:
question.completed.connect(self._pop_later)

View File

@ -27,11 +27,11 @@ from PyQt5.QtGui import QIcon
from PyQt5.QtWebKitWidgets import QWebPage
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.commands import cmdexc
from qutebrowser.keyinput import modeman
from qutebrowser.widgets import tabwidget, webview
from qutebrowser.browser import signalfilter, commands
from qutebrowser.utils import log, message, usertypes, utils, qtutils
from qutebrowser.utils import log, message, usertypes, utils, qtutils, objreg
class TabbedBrowser(tabwidget.TabWidget):
@ -53,9 +53,7 @@ class TabbedBrowser(tabwidget.TabWidget):
_tab_insert_idx_left: Where to insert a new tab with
tabbar -> new-tab-position set to 'left'.
_tab_insert_idx_right: Same as above, for 'right'.
url_stack: Stack of URLs of closed tabs.
cmd: A TabCommandDispatcher instance.
last_focused: The tab which was focused last.
_url_stack: Stack of URLs of closed tabs.
Signals:
cur_progress: Progress of the current tab changed (loadProgress).
@ -69,8 +67,6 @@ class TabbedBrowser(tabwidget.TabWidget):
arg 1: x-position in %.
arg 2: y-position in %.
cur_load_status_changed: Loading status of current tab changed.
hint_strings_updated: Hint strings were updated.
arg: A list of hint strings.
quit: The last tab was closed, quit application.
resized: Emitted when the browser window has resized, so the completion
widget can adjust its size to it.
@ -80,7 +76,6 @@ class TabbedBrowser(tabwidget.TabWidget):
current_tab_changed: The current tab changed to the emitted WebView.
title_changed: Emitted when the application title should be changed.
arg: The new title as string.
download_get: Emitted when a QUrl should be downloaded.
"""
cur_progress = pyqtSignal(int)
@ -92,8 +87,6 @@ class TabbedBrowser(tabwidget.TabWidget):
cur_scroll_perc_changed = pyqtSignal(int, int)
cur_load_status_changed = pyqtSignal(str)
start_download = pyqtSignal('QNetworkReply*')
download_get = pyqtSignal('QUrl', 'QWebPage')
hint_strings_updated = pyqtSignal(list)
quit = pyqtSignal()
resized = pyqtSignal('QRect')
got_cmd = pyqtSignal(str)
@ -108,10 +101,11 @@ class TabbedBrowser(tabwidget.TabWidget):
self.currentChanged.connect(self.on_current_changed)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self._tabs = []
self.url_stack = []
self._url_stack = []
objreg.register('url-stack', self._url_stack)
self._filter = signalfilter.SignalFilter(self)
self.cmd = commands.CommandDispatcher(self)
self.last_focused = None
dispatcher = commands.CommandDispatcher()
objreg.register('command-dispatcher', dispatcher)
self._now_focused = None
# FIXME adjust this to font size
self.setIconSize(QSize(12, 12))
@ -154,10 +148,6 @@ class TabbedBrowser(tabwidget.TabWidget):
self._filter.create(self.cur_load_status_changed, tab))
tab.url_text_changed.connect(
functools.partial(self.on_url_text_changed, tab))
# hintmanager
tab.hintmanager.hint_strings_updated.connect(self.hint_strings_updated)
tab.hintmanager.download_get.connect(self.download_get)
tab.hintmanager.openurl.connect(self.openurl)
self.cur_load_started.connect(self.on_cur_load_started)
# downloads
page.start_download.connect(self.start_download)
@ -175,25 +165,6 @@ class TabbedBrowser(tabwidget.TabWidget):
page.windowCloseRequested.connect(
functools.partial(self.on_window_close_requested, tab))
def cntwidget(self, count=None):
"""Return a widget based on a count/idx.
Args:
count: The tab index, or None.
Return:
The current widget if count is None.
The widget with the given tab ID if count is given.
None if no widget was found.
"""
if count is None:
return self.currentWidget()
elif 1 <= count <= self.count():
cmdutils.check_overflow(count + 1, 'int')
return self.widget(count - 1)
else:
return None
def current_url(self):
"""Get the URL of the current tab.
@ -263,11 +234,11 @@ class TabbedBrowser(tabwidget.TabWidget):
tab))
if tab is self._now_focused:
self._now_focused = None
if tab is self.last_focused:
self.last_focused = None
if tab is objreg.get('last-focused-tab', None):
objreg.delete('last-focused-tab')
if not tab.cur_url.isEmpty():
qtutils.ensure_valid(tab.cur_url)
self.url_stack.append(tab.cur_url)
self._url_stack.append(tab.cur_url)
tab.shutdown()
self._tabs.remove(tab)
self.removeTab(idx)
@ -393,21 +364,6 @@ class TabbedBrowser(tabwidget.TabWidget):
# We first want QWebPage to refresh.
QTimer.singleShot(0, check_scroll_pos)
@pyqtSlot(str)
def handle_hint_key(self, keystr):
"""Handle a new hint keypress."""
self.currentWidget().hintmanager.handle_partial_key(keystr)
@pyqtSlot(str)
def fire_hint(self, keystr):
"""Fire a completed hint."""
self.currentWidget().hintmanager.fire(keystr)
@pyqtSlot(str)
def filter_hints(self, filterstr):
"""Filter displayed hints."""
self.currentWidget().hintmanager.filter_hints(filterstr)
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""Update tab config when config was changed."""
@ -522,11 +478,12 @@ class TabbedBrowser(tabwidget.TabWidget):
@pyqtSlot(int)
def on_current_changed(self, idx):
"""Set last_focused and leave hinting mode when focus changed."""
"""Set last-focused-tab and leave hinting mode when focus changed."""
tab = self.widget(idx)
tab.setFocus()
modeman.maybe_leave(usertypes.KeyMode.hint, 'tab changed')
self.last_focused = self._now_focused
if self._now_focused is not None:
objreg.register('last-focused-tab', self._now_focused, update=True)
self._now_focused = tab
self.current_tab_changed.emit(tab)
self.title_changed.emit('{} - qutebrowser'.format(self.tabText(idx)))

View File

@ -28,11 +28,10 @@ import functools
from PyQt5.QtCore import pyqtSlot, Qt, QSize, QRect, QPoint, QTimer
from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle,
QStyle, QStylePainter, QStyleOptionTab,
QApplication)
QStyle, QStylePainter, QStyleOptionTab)
from PyQt5.QtGui import QIcon, QPalette, QColor
from qutebrowser.utils import qtutils
from qutebrowser.utils import qtutils, objreg
from qutebrowser.config import config
@ -43,7 +42,7 @@ class TabWidget(QTabWidget):
"""The tabwidget used for TabbedBrowser."""
def __init__(self, parent):
def __init__(self, parent=None):
super().__init__(parent)
bar = TabBar()
self.setTabBar(bar)
@ -210,7 +209,7 @@ class TabBar(QTabBar):
confwidth = str(config.get('tabs', 'width'))
if confwidth.endswith('%'):
perc = int(confwidth.rstrip('%'))
width = QApplication.instance().mainwindow.width() * perc / 100
width = objreg.get('main-window').width() * perc / 100
else:
width = int(confwidth)
size = QSize(max(minimum_size.width(), width), height)

View File

@ -26,7 +26,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.utils import message, log, usertypes, utils, qtutils
from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg
from qutebrowser.browser import webpage, hints, webelem
from qutebrowser.commands import cmdexc
@ -43,9 +43,6 @@ class WebView(QWebView):
Attributes:
hintmanager: The HintManager instance for this view.
tabbedbrowser: The TabbedBrowser this WebView is part of.
We need this rather than signals to make createWindow
work.
progress: loading progress of this page.
scroll_pos: The current scroll position as (x%, y%) tuple.
statusbar_message: The current javscript statusbar message.
@ -54,7 +51,7 @@ class WebView(QWebView):
open_target: Where to open the next tab ("normal", "tab", "tab_bg")
viewing_source: Whether the webview is currently displaying source
code.
_page: The QWebPage behind the view
registry: The ObjectRegistry associated with this tab.
_cur_url: The current URL (accessed via cur_url property).
_has_ssl_errors: Whether SSL errors occured during loading.
_zoom: A NeighborList with the zoom levels.
@ -77,11 +74,10 @@ class WebView(QWebView):
load_status_changed = pyqtSignal(str)
url_text_changed = pyqtSignal(str)
def __init__(self, parent):
def __init__(self, parent=None):
super().__init__(parent)
self.load_status = LoadStatus.none
self._check_insertmode = False
self.tabbedbrowser = parent
self.inspector = None
self.scroll_pos = (-1, -1)
self.statusbar_message = ''
@ -94,16 +90,21 @@ class WebView(QWebView):
self._cur_url = None
self.cur_url = QUrl()
self.progress = 0
self._page = webpage.BrowserPage(self)
self.setPage(self._page)
self.hintmanager = hints.HintManager(self)
self.hintmanager.mouse_event.connect(self.on_mouse_event)
self.hintmanager.set_open_target.connect(self.set_force_open_target)
self._page.linkHovered.connect(self.linkHovered)
self._page.mainFrame().loadStarted.connect(self.on_load_started)
self._page.change_title.connect(self.titleChanged)
self.registry = objreg.ObjectRegistry()
objreg.register('webview', self, registry=self.registry)
page = webpage.BrowserPage(self)
self.setPage(page)
hintmanager = hints.HintManager(self)
hintmanager.mouse_event.connect(self.on_mouse_event)
hintmanager.set_open_target.connect(self.set_force_open_target)
objreg.register('hintmanager', hintmanager, registry=self.registry)
tab_id = next(usertypes.tab_id_gen)
objreg.register('tab-{}'.format(tab_id), self.registry, scope='meta')
page.linkHovered.connect(self.linkHovered)
page.mainFrame().loadStarted.connect(self.on_load_started)
page.change_title.connect(self.titleChanged)
self.urlChanged.connect(self.on_url_changed)
self._page.mainFrame().loadFinished.connect(self.on_load_finished)
page.mainFrame().loadFinished.connect(self.on_load_finished)
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
self.page().statusBarMessage.connect(
lambda msg: setattr(self, 'statusbar_message', msg))
@ -358,7 +359,8 @@ class WebView(QWebView):
self._set_load_status(LoadStatus.error)
if not config.get('input', 'auto-insert-mode'):
return
if modeman.instance().mode() == usertypes.KeyMode.insert or not ok:
cur_mode = objreg.get('mode-manager').mode()
if cur_mode == usertypes.KeyMode.insert or not ok:
return
frame = self.page().currentFrame()
try:
@ -403,7 +405,7 @@ class WebView(QWebView):
if wintype == QWebPage.WebModalDialog:
log.webview.warning("WebModalDialog requested, but we don't "
"support that!")
return self.tabbedbrowser.tabopen()
return objreg.get('tabbed-browser').tabopen()
def paintEvent(self, e):
"""Extend paintEvent to emit a signal if the scroll position changed.