Merge branch 'objreg'
This commit is contained in:
commit
7b8829286c
@ -53,4 +53,4 @@ defining-attr-methods=__init__,__new__,setUp
|
||||
max-args=10
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-classes=WebElementWrapper,AnsiCodes
|
||||
ignored-classes=WebElementWrapper,AnsiCodes,UnsetObject
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:]
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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!")
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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__()
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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()."""
|
||||
|
@ -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")
|
||||
|
@ -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__':
|
||||
|
@ -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()
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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.
|
||||
|
@ -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
144
qutebrowser/utils/objreg.py
Normal 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]
|
@ -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.
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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__)
|
||||
|
@ -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)
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user