Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get rid of objreg? #640

Open
The-Compiler opened this issue Apr 17, 2015 · 12 comments
Open

Get rid of objreg? #640

The-Compiler opened this issue Apr 17, 2015 · 12 comments
Labels
component: extensions Issues related to the (work in progress) extension API component: style / refactoring Issues related to coding styles or code that should be refactored. priority: 0 - high Issues which are currently the primary focus.

Comments

@The-Compiler
Copy link
Member

The-Compiler commented Apr 17, 2015

qutebrowser currently has a global object registry, where objects are registered and retrieved using a string as name - see the contributing documentation for some details about it. This is a stringly typed API and got us into various problems (like #1638, #1652, and bad testability).

This should be phased out and replaced by global objects where appropriate (like modeman.instance, config.instance) and objects passed as arguments elsewhere.

There seem to be various uses of the object registry right now:

Commands

We somehow need to get the proper instance to registered commands. Currently this is done via instance='some-string'[, scope={'tab','window'}] passed to @cmdutils.register, and Command then uses that and gets the instance via the object registry.

I'm still not 100% sure if this will work or not, but we probably could change that to something like this:

  • There are three different "instance registries" - a global one, one for each mainwindow, and one for each tab. Those are only accessible from commands.
  • The application/window/tab is responsible to register its children there.
  • When a command arrives, some central got_command signal is emitted.
  • There's a global/mainwindow/tab CommandDispatcher (?) which gets the got_command signal and calls all command handlers with the correct instances.

Global objects

These are the current global objects:

app, args, cache, command-history, config, cookie-jar, debug-console, host-blocker, ipc-server, js-bridge, key-config, quickmark-manager, save-manager, session-manager, state-config, web-history

Alternatives:

  • Pass stuff as arguments instead
  • Use global python objects with convenience methods (like config.get, etc.).
  • Attach them to the Application (e.g. args) and use QApplication.instance() (or a convenience function for that).

Getting other objects

Many of those probably could be replaced by instance variables or signals/slots.

@The-Compiler The-Compiler added enhancement component: style / refactoring Issues related to coding styles or code that should be refactored. labels Apr 17, 2015
@The-Compiler The-Compiler added the priority: 1 - middle Issues which should be done at some point, but aren't that important. label Oct 1, 2015
@The-Compiler The-Compiler added priority: 0 - high Issues which are currently the primary focus. and removed priority: 1 - middle Issues which should be done at some point, but aren't that important. labels Jul 15, 2016
rcorre added a commit to rcorre/qutebrowser that referenced this issue Jul 28, 2016
The CompletionView is the parent of the Completer, so there's no need
to use objreg to get it.
See qutebrowser#640.
rcorre added a commit to rcorre/qutebrowser that referenced this issue Jul 28, 2016
The CompletionView is the parent of the Completer, so there's no need
to use objreg to get it.
See qutebrowser#640.
@The-Compiler
Copy link
Member Author

I had some ideas on how to handle this for commands inside classes, which will
probably also solve #1620 (cc @rcorre).

  • When @cmdutils.register gets called on a class (maybe just look for an
    argument named self? Or have a second decorator?), it just stores something
    in a _qutebrowser_commands attribute on the class, without actually
    registering the command yet, as we don't know the instance.
  • objreg is no more at that point - instead there's only some kind of
    instance-registry for commands - a global one, one for each window, and one
    for each tab.
  • When using @cmdutils.register on methods, one needs to do
    cmdutils.register_instance(object[, window[, tab]]) or so after
    creating the object (like in an init() function). We hopefully don't need a
    name anymore here, and just can use the class object as key. This will
    enforce there's only one registered instance (or one per window/tab) too.
  • cmdutils.register_instance checks the _qutebrowser_commands attribute and
    then actually registers the commands, also knowing the right instance
  • We could have a cmdutils.check_instances() or so called at the end of
    initialization, which verifies that all registered commands actually have a
    registered instance.

This also means it's much easier to have some commands in a class when we need
to store some state, like I just imagined for #725:

class RepeatCommand:

    def __init__(self):
        self._timers = []

    @cmdutils.register()
    def repeat(self, times=None, command=None, stop=False):
        if stop:
            for timer in self._timers:
                timer.stop()
        else:
            # ...


def init():
    cmdutils.register_instance(RepeatCommand())

This could also be a quite nice way to register commands inside classes for
plugins in the future.

Compared to now:

class RepeatCommand:

    # ...

    @cmdutils.register(instance='repeat-command')
    def repeat(self, times=None, command=None, stop=False):
        # ...


def init():
    objreg.register('repeat-command', RepeatCommand())

This is slightly more verbose, but most importantly, it leaves an object in the
objreg we want to get rid of, and also one we don't need anywhere else anyways.
For plugins, this also needs an unique name (on the other hand, the command
name needs to be unique anyways...).

@Kingdread
Copy link
Contributor

When @cmdutils.register gets called on a class (maybe just look for an argument named self? Or have a second decorator?), it just stores something in a _qutebrowser_commands attribute on the class, without actually registering the command yet, as we don't know the instance.

That won't work in this way, as the class is not yet created when the class methods are defined, and the decorator won't have access to the class. We can only add some attribute to the function itself, and later iterate over all class members, taking the ones we want.

@The-Compiler
Copy link
Member Author

Oh, indeed - that still sounds like a good plan though.

@The-Compiler
Copy link
Member Author

@jberglinds asked for something refactoring-related to work on as a group in the KTH Sweden, I'm currently writing a mail proposing this issue. @rcorre, I noticed you started some work back in December in the byeobjreg branch but that seems abandoned - is this okay to take over, or do you plan to continue?

@rcorre
Copy link
Contributor

rcorre commented Feb 14, 2018 via email

@The-Compiler The-Compiler added this to Refactoring in Extensions Mar 21, 2018
@The-Compiler The-Compiler moved this from Refactoring to Backlog in Extensions Oct 15, 2018
@The-Compiler
Copy link
Member Author

I started working on #640 (comment) but this is going to be a bigger thing than I thought it'd be. For global objects this is fine, but for per-window/per-tab objects, we basically end up building another objreg-like thing, which is what I wanted to avoid 😆 Going to stop here for now, need to think some more about it first.

Patch so far:

diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py
index f95e984ca..3c9540f02 100644
--- a/qutebrowser/api/cmdutils.py
+++ b/qutebrowser/api/cmdutils.py
@@ -78,15 +78,11 @@ class register:  # noqa: N801,N806 pylint: disable=invalid-name
     """Decorator to register a new command handler.
 
     Attributes:
-        _instance: The object from the object registry to be used as "self".
         _name: The name (as string) or names (as list) of the command.
         _kwargs: The arguments to pass to Command.
     """
 
-    def __init__(self, *,
-                 instance: str = None,
-                 name: str = None,
-                 **kwargs: typing.Any) -> None:
+    def __init__(self, *, name: str = None, **kwargs: typing.Any) -> None:
         """Save decorator arguments.
 
         Gets called on parse-time with the decorator arguments.
@@ -94,7 +90,6 @@ class register:  # noqa: N801,N806 pylint: disable=invalid-name
         Args:
             See class attributes.
         """
-        self._instance = instance
         self._name = name
         self._kwargs = kwargs
 
@@ -118,8 +113,7 @@ class register:  # noqa: N801,N806 pylint: disable=invalid-name
             assert isinstance(self._name, str), self._name
             name = self._name
 
-        cmd = command.Command(name=name, instance=self._instance,
-                              handler=func, **self._kwargs)
+        cmd = command.Command(name=name, handler=func, **self._kwargs)
         cmd.register()
         return func
 
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 27848c4c1..29178335c 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -116,6 +116,7 @@ def run(args):
         app=qApp, quitter=quitter, args=args, parent=qApp)
     crash_handler.activate()
     objreg.register('crash-handler', crash_handler)
+    cmdutils.register_instance(crash_handler)
 
     signal_handler = crashsignal.SignalHandler(app=qApp, quitter=quitter,
                                                parent=qApp)
@@ -424,6 +425,7 @@ def _init_modules(args, crash_handler):
     log.init.debug("Initializing save manager...")
     save_manager = savemanager.SaveManager(qApp)
     objreg.register('save-manager', save_manager)
+    cmdutils.register_instance(save_manager)
     configinit.late_init(save_manager)
 
     log.init.debug("Checking backend requirements...")
@@ -440,7 +442,7 @@ def _init_modules(args, crash_handler):
 
     log.init.debug("Initializing readline-bridge...")
     readline_bridge = readline.ReadlineBridge()
-    objreg.register('readline-bridge', readline_bridge)
+    cmdutils.register_instance(readline_bridge)
 
     try:
         log.init.debug("Initializing sql...")
diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py
index 5422a9984..709fae1f7 100644
--- a/qutebrowser/commands/command.py
+++ b/qutebrowser/commands/command.py
@@ -69,16 +69,15 @@ class Command:
                  both)
         no_replace_variables: Don't replace variables like {url}
         modes: The modes the command can be executed in.
+        instance: The object to bind 'self' to.
         _qute_args: The saved data from @cmdutils.argument
         _count: The count set for the command.
-        _instance: The object to bind 'self' to.
-        _scope: The scope to get _instance for in the object registry.
     """
 
     def __init__(self, *, handler, name, instance=None, maxsplit=None,
                  modes=None, not_modes=None, debug=False, deprecated=False,
-                 no_cmd_split=False, star_args_optional=False, scope='global',
-                 backend=None, no_replace_variables=False):
+                 no_cmd_split=False, star_args_optional=False, backend=None,
+                 no_replace_variables=False):
         if modes is not None and not_modes is not None:
             raise ValueError("Only modes or not_modes can be given!")
         if modes is not None:
@@ -93,15 +92,11 @@ class Command:
             self.modes = set(usertypes.KeyMode).difference(not_modes)
         else:
             self.modes = set(usertypes.KeyMode)
-        if scope != 'global' and instance is None:
-            raise ValueError("Setting scope without setting instance makes "
-                             "no sense!")
 
         self.name = name
         self.maxsplit = maxsplit
         self.deprecated = deprecated
-        self._instance = instance
-        self._scope = scope
+        self.instance = None
         self._star_args_optional = star_args_optional
         self.debug = debug
         self.handler = handler
@@ -154,14 +149,8 @@ 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:
-            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:
-            raise TypeError("{} is not a class method, but instance was "
-                            "given!".format(self.name[0]))
-        elif any(param.kind == inspect.Parameter.VAR_KEYWORD
-                 for param in signature.parameters.values()):
+        if any(param.kind == inspect.Parameter.VAR_KEYWORD
+               for param in signature.parameters.values()):
             raise TypeError("{}: functions with varkw arguments are not "
                             "supported!".format(self.name[0]))
 
@@ -341,18 +330,7 @@ class Command:
             args: The positional argument list. Gets modified directly.
         """
         assert param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
-        if self._scope == 'global':
-            tab_id = None
-            win_id = None
-        elif self._scope == 'tab':
-            tab_id = 'current'
-        elif self._scope == 'window':
-            tab_id = None
-        else:
-            raise ValueError("Invalid scope {}!".format(self._scope))
-        obj = objreg.get(self._instance, scope=self._scope, window=win_id,
-                         tab=tab_id)
-        args.append(obj)
+        args.append(self._instance)
 
     def _get_count_arg(self, param, args, kwargs):
         """Add the count argument to a function call.
@@ -428,6 +406,15 @@ class Command:
 
         return value
 
+    def _check_instance(self, signature):
+        """Make sure we have an instance set if we need one."""
+        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:
+            raise TypeError("{} is not a class method, but instance was "
+                            "given!".format(self.name[0]))
+
     def _get_call_args(self, win_id):
         """Get arguments for a function call.
 
@@ -440,10 +427,11 @@ class Command:
         args = []
         kwargs = {}
         signature = inspect.signature(self.handler)
+        self._check_instance(signature)
 
         for i, param in enumerate(signature.parameters.values()):
             arg_info = self.get_arg_info(param)
-            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(win_id, param, args)
                 continue
diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py
index bd17f5664..03bdef1d9 100644
--- a/qutebrowser/keyinput/macros.py
+++ b/qutebrowser/keyinput/macros.py
@@ -44,7 +44,7 @@ class MacroRecorder:
         self._macro_count = {}
         self._last_register = None
 
-    @cmdutils.register(instance='macro-recorder', name='record-macro')
+    @cmdutils.register(name='record-macro')
     @cmdutils.argument('win_id', win_id=True)
     def record_macro_command(self, win_id, register=None):
         """Start or stop recording a macro.
@@ -69,7 +69,7 @@ class MacroRecorder:
         self._macros[register] = []
         self._recording_macro = register
 
-    @cmdutils.register(instance='macro-recorder', name='run-macro')
+    @cmdutils.register(name='run-macro')
     @cmdutils.argument('win_id', win_id=True)
     @cmdutils.argument('count', count=True)
     def run_macro_command(self, win_id, count=1, register=None):
@@ -113,3 +113,4 @@ def init():
     """Initialize the MacroRecorder."""
     macro_recorder = MacroRecorder()
     objreg.register('macro-recorder', macro_recorder)
+    cmdutils.register_instance(macro_recorder)
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index c06700b6c..f77ba2999 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -67,6 +67,7 @@ def init(win_id, parent):
     KM = usertypes.KeyMode  # noqa: N801,N806 pylint: disable=invalid-name
     modeman = ModeManager(win_id, parent)
     objreg.register('mode-manager', modeman, scope='window', window=win_id)
+    cmdutils.register_instance(modeman, scope='window')
     keyparsers = {
         KM.normal:
             modeparsers.NormalKeyParser(win_id, modeman),
@@ -272,7 +273,7 @@ class ModeManager(QObject):
         self.mode = mode
         self.entered.emit(mode, self._win_id)
 
-    @cmdutils.register(instance='mode-manager', scope='window')
+    @cmdutils.register()
     def enter_mode(self, mode):
         """Enter a key mode.
 
@@ -320,8 +321,7 @@ class ModeManager(QObject):
             self.enter(self._prev_mode,
                        reason='restore mode before {}'.format(mode.name))
 
-    @cmdutils.register(instance='mode-manager', name='leave-mode',
-                       not_modes=[usertypes.KeyMode.normal], scope='window')
+    @cmdutils.register(name='leave-mode', not_modes=[usertypes.KeyMode.normal])
     def leave_current_mode(self):
         """Leave the mode we're currently in."""
         if self.mode == usertypes.KeyMode.normal:
@@ -352,7 +352,7 @@ class ModeManager(QObject):
         handler = handlers[event.type()]
         return handler(event)
 
-    @cmdutils.register(instance='mode-manager', scope='window')
+    @cmdutils.register()
     def clear_keychain(self):
         """Clear the currently entered key chain."""
         self._parsers[self.mode].clear_keystring()
diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py
index 13a368f05..f3b2c8b3a 100644
--- a/qutebrowser/mainwindow/statusbar/bar.py
+++ b/qutebrowser/mainwindow/statusbar/bar.py
@@ -24,6 +24,7 @@ import attr
 from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer
 from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy
 
+from qutebrowser.api import cmdutils
 from qutebrowser.browser import browsertab
 from qutebrowser.config import config
 from qutebrowser.utils import usertypes, log, objreg, utils
@@ -174,6 +175,7 @@ class StatusBar(QWidget):
         self._stack.addWidget(self.cmd)
         objreg.register('status-command', self.cmd, scope='window',
                         window=win_id)
+        cmdutils.register_instance(self.cmd, scope='window')
 
         self.txt = textwidget.Text()
         self._stack.addWidget(self.txt)
diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py
index de42cda4e..39af23cab 100644
--- a/qutebrowser/mainwindow/statusbar/command.py
+++ b/qutebrowser/mainwindow/statusbar/command.py
@@ -113,8 +113,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
         self.setFocus()
         self.show_cmd.emit()
 
-    @cmdutils.register(instance='status-command', name='set-cmd-text',
-                       scope='window', maxsplit=0)
+    @cmdutils.register(name='set-cmd-text', maxsplit=0)
     @cmdutils.argument('count', count=True)
     def set_cmd_text_command(self, text, count=None, space=False, append=False,
                              run_on_count=False):
@@ -148,8 +147,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
         else:
             self.set_cmd_text(text)
 
-    @cmdutils.register(instance='status-command',
-                       modes=[usertypes.KeyMode.command], scope='window')
+    @cmdutils.register(modes=[usertypes.KeyMode.command])
     def command_history_prev(self):
         """Go back in the commandline history."""
         try:
@@ -163,8 +161,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
         if item:
             self.set_cmd_text(item)
 
-    @cmdutils.register(instance='status-command',
-                       modes=[usertypes.KeyMode.command], scope='window')
+    @cmdutils.register(modes=[usertypes.KeyMode.command])
     def command_history_next(self):
         """Go forward in the commandline history."""
         if not self.history.is_browsing():
@@ -176,8 +173,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
         if item:
             self.set_cmd_text(item)
 
-    @cmdutils.register(instance='status-command',
-                       modes=[usertypes.KeyMode.command], scope='window')
+    @cmdutils.register(modes=[usertypes.KeyMode.command])
     def command_accept(self, rapid=False):
         """Execute the command currently in the commandline.
 
@@ -196,7 +192,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
         if not was_search:
             self.got_cmd[str].emit(text[1:])
 
-    @cmdutils.register(instance='status-command', scope='window')
+    @cmdutils.register()
     def edit_command(self, run=False):
         """Open an editor to modify the current command.
 
diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py
index 7890380e8..4ed3a54dd 100644
--- a/qutebrowser/misc/crashsignal.py
+++ b/qutebrowser/misc/crashsignal.py
@@ -146,7 +146,7 @@ class CrashHandler(QObject):
         else:
             earlyinit.init_faulthandler(self._crash_log_file)
 
-    @cmdutils.register(instance='crash-handler')
+    @cmdutils.register()
     def report(self):
         """Report a bug in qutebrowser."""
         pages = self._recover_pages()
diff --git a/qutebrowser/misc/readline.py b/qutebrowser/misc/readline.py
index 14c25cd6d..bdd4a20f6 100644
--- a/qutebrowser/misc/readline.py
+++ b/qutebrowser/misc/readline.py
@@ -48,8 +48,7 @@ class ReadlineBridge:
         else:
             return None
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_backward_char(self):
         """Move back a character.
 
@@ -60,8 +59,7 @@ class ReadlineBridge:
             return
         widget.cursorBackward(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_forward_char(self):
         """Move forward a character.
 
@@ -72,8 +70,7 @@ class ReadlineBridge:
             return
         widget.cursorForward(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_backward_word(self):
         """Move back to the start of the current or previous word.
 
@@ -84,8 +81,7 @@ class ReadlineBridge:
             return
         widget.cursorWordBackward(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_forward_word(self):
         """Move forward to the end of the next word.
 
@@ -96,8 +92,7 @@ class ReadlineBridge:
             return
         widget.cursorWordForward(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_beginning_of_line(self):
         """Move to the start of the line.
 
@@ -108,8 +103,7 @@ class ReadlineBridge:
             return
         widget.home(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_end_of_line(self):
         """Move to the end of the line.
 
@@ -120,8 +114,7 @@ class ReadlineBridge:
             return
         widget.end(False)
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(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.
 
@@ -134,8 +127,7 @@ class ReadlineBridge:
         self._deleted[widget] = widget.selectedText()
         widget.del_()
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_kill_line(self):
         """Remove chars from the cursor to the end of the line.
 
@@ -173,8 +165,7 @@ class ReadlineBridge:
         self._deleted[widget] = widget.selectedText()
         widget.del_()
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_unix_word_rubout(self):
         """Remove chars from the cursor to the beginning of the word.
 
@@ -183,8 +174,7 @@ class ReadlineBridge:
         """
         self._rubout([' '])
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_unix_filename_rubout(self):
         """Remove chars from the cursor to the previous path separator.
 
@@ -192,8 +182,7 @@ class ReadlineBridge:
         """
         self._rubout([' ', '/'])
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_backward_kill_word(self):
         """Remove chars from the cursor to the beginning of the word.
 
@@ -207,8 +196,7 @@ class ReadlineBridge:
         self._deleted[widget] = widget.selectedText()
         widget.del_()
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_kill_word(self):
         """Remove chars from the cursor to the end of the current word.
 
@@ -221,8 +209,7 @@ class ReadlineBridge:
         self._deleted[widget] = widget.selectedText()
         widget.del_()
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_yank(self):
         """Paste the most recently deleted text.
 
@@ -233,8 +220,7 @@ class ReadlineBridge:
             return
         widget.insert(self._deleted[widget])
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_delete_char(self):
         """Delete the character after the cursor.
 
@@ -245,8 +231,7 @@ class ReadlineBridge:
             return
         widget.del_()
 
-    @cmdutils.register(instance='readline-bridge',
-                       modes=[typ.KeyMode.command, typ.KeyMode.prompt])
+    @cmdutils.register(modes=[typ.KeyMode.command, typ.KeyMode.prompt])
     def rl_backward_delete_char(self):
         """Delete the character before the cursor.
 
diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py
index 9985c5191..ab647a34b 100644
--- a/qutebrowser/misc/savemanager.py
+++ b/qutebrowser/misc/savemanager.py
@@ -178,8 +178,7 @@ class SaveManager(QObject):
             except OSError as e:
                 message.error("Failed to auto-save {}: {}".format(key, e))
 
-    @cmdutils.register(instance='save-manager', name='save',
-                       star_args_optional=True)
+    @cmdutils.register(name='save', star_args_optional=True)
     def save_command(self, *what):
         """Save configs and state.
 
diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py
index 357d5be64..ef93b9a36 100644
--- a/qutebrowser/misc/sessions.py
+++ b/qutebrowser/misc/sessions.py
@@ -60,6 +60,7 @@ def init(parent=None):
 
     session_manager = SessionManager(base_path, parent)
     objreg.register('session-manager', session_manager)
+    cmdutils.register_instance(session_manager)
 
 
 class SessionError(Exception):
@@ -465,7 +466,7 @@ class SessionManager(QObject):
                 sessions.append(base)
         return sorted(sessions)
 
-    @cmdutils.register(instance='session-manager')
+    @cmdutils.register()
     @cmdutils.argument('name', completion=miscmodels.session)
     def session_load(self, name, clear=False, temp=False, force=False,
                      delete=False):
@@ -506,7 +507,7 @@ class SessionManager(QObject):
                     log.sessions.debug(
                         "Loaded & deleted session {}.".format(name))
 
-    @cmdutils.register(instance='session-manager')
+    @cmdutils.register()
     @cmdutils.argument('name', completion=miscmodels.session)
     @cmdutils.argument('win_id', win_id=True)
     @cmdutils.argument('with_private', flag='p')
@@ -554,7 +555,7 @@ class SessionManager(QObject):
             else:
                 message.info("Saved session {}.".format(name))
 
-    @cmdutils.register(instance='session-manager')
+    @cmdutils.register()
     @cmdutils.argument('name', completion=miscmodels.session)
     def session_delete(self, name, force=False):
         """Delete a session.
diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py
index f2318ab46..3aa40b135 100644
--- a/tests/unit/api/test_cmdutils.py
+++ b/tests/unit/api/test_cmdutils.py
@@ -130,7 +130,8 @@ class TestRegister:
 
     def test_instance(self):
         """Make sure the instance gets passed to Command."""
-        @cmdutils.register(instance='foobar')
+        # FIXME update
+        @cmdutils.register()
         def fun(self):
             """Blah."""
         assert objects.commands['fun']._instance == 'foobar'
@@ -416,7 +417,8 @@ class TestRun:
         sure the backend checking happens before resolving the instance, so we
         display an error instead of crashing.
         """
-        @cmdutils.register(instance='doesnotexist',
+        # FIXME update
+        @cmdutils.register()
                            backend=usertypes.Backend.QtWebEngine)
         def fun(self):
             """Blah."""

@The-Compiler The-Compiler added the component: extensions Issues related to the (work in progress) extension API label Nov 30, 2018
The-Compiler added a commit that referenced this issue Oct 13, 2019
This means we can set command_only=True for command-dispatcher

See #640
The-Compiler added a commit that referenced this issue Oct 13, 2019
The-Compiler added a commit that referenced this issue Oct 13, 2019
The-Compiler added a commit that referenced this issue Oct 13, 2019
The-Compiler added a commit that referenced this issue Oct 13, 2019
The-Compiler added a commit that referenced this issue Oct 13, 2019
@The-Compiler
Copy link
Member Author

Pushed a first bunch of refactorings.

Before:

global object registry - 25 objects:
    quitter: <qutebrowser.app.Quitter object at 0x7fe1dc4ab490>
    config-commands: <qutebrowser.config.configcommands.ConfigCommands object at 0x7fe1c8a8a310>
    args: Namespace(backend=None, basedir='/tmp/qutebrowser-basedir-6la2yff2', color=True, command=['expired.badssl.com'], config_py=None, debug=True, debug_flags=[], enable_webengine_inspector=False, force_color=False, json_args=None, json_logging=False, logfilter=None, loglevel='info', loglines=2000, no_err_windows=False, nowindow=False, override_restore=False, qt_arg=None, qt_flag=None, session=None, target=None, temp_basedir=True, temp_basedir_restarted=None, temp_settings=[], url=[], version=False)
    app: <qutebrowser.app.Application>
    crash-handler: <qutebrowser.misc.crashsignal.CrashHandler object at 0x7fe1c8a53b90>
    signal-handler: <qutebrowser.misc.crashsignal.SignalHandler object at 0x7fe1c8a53c30>
    save-manager: <qutebrowser.misc.savemanager.SaveManager saveables=OrderedDict([('yaml-config', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename=None name='yaml-config' save_handler=<bound method YamlConfig._save of <qutebrowser.config.configfiles.YamlConfig object at 0x7fe1c8a539b0>> save_on_exit=False>), ('state-config', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename=None name='state-config' save_handler=<bound method StateConfig._save of <qutebrowser.config.configfiles.StateConfig object at 0x7fe1c8aa4e50>> save_on_exit=True>), ('command-history', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=True filename=None name='command-history' save_handler=<bound method LimitLineParser.save of qutebrowser.misc.lineparser.LimitLineParser(binary=False, configdir='/tmp/qutebrowser-basedir-6la2yff2/data', fname='cmd-history', limit='completion.cmd_history_max_items')> save_on_exit=False>), ('quickmark-manager', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename='/tmp/qutebrowser-basedir-6la2yff2/config/quickmarks' name='quickmark-manager' save_handler=<bound method UrlMarkManager.save of <qutebrowser.browser.urlmarks.QuickmarkManager object at 0x7fe1c88c3410>> save_on_exit=False>), ('bookmark-manager', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename='/tmp/qutebrowser-basedir-6la2yff2/config/bookmarks/urls' name='bookmark-manager' save_handler=<bound method UrlMarkManager.save of <qutebrowser.browser.urlmarks.BookmarkManager object at 0x7fe1c88c35f0>> save_on_exit=False>), ('cookies', <qutebrowser.misc.savemanager.Saveable config_opt='content.cookies.store' dirty=False filename=None name='cookies' save_handler=<bound method CookieJar.save of <qutebrowser.browser.webkit.cookies.CookieJar count=0>> save_on_exit=False>)])>
    prompt-queue: <qutebrowser.mainwindow.prompt.PromptQueue loops=0 question=None queue=0>
    proxy-factory: <qutebrowser.browser.network.proxy.ProxyFactory object at 0x7fe1c88c3050>
    readline-bridge: <qutebrowser.misc.readline.ReadlineBridge>
    web-history: <qutebrowser.browser.history.WebHistory length=2>
    command-history: qutebrowser.misc.lineparser.LimitLineParser(binary=False, configdir='/tmp/qutebrowser-basedir-6la2yff2/data', fname='cmd-history', limit='completion.cmd_history_max_items')
    session-manager: <qutebrowser.misc.sessions.SessionManager object at 0x7fe1c88c3370>
    quickmark-manager: <qutebrowser.browser.urlmarks.QuickmarkManager object at 0x7fe1c88c3410>
    bookmark-manager: <qutebrowser.browser.urlmarks.BookmarkManager object at 0x7fe1c88c35f0>
    cookie-jar: <qutebrowser.browser.webkit.cookies.CookieJar count=0>
    ram-cookie-jar: <qutebrowser.browser.webkit.cookies.RAMCookieJar count=0>
    cache: <qutebrowser.browser.webkit.cache.DiskCache maxsize=52428800 path='/tmp/qutebrowser-basedir-6la2yff2/cache/http/' size=0>
    qtnetwork-download-manager: <qutebrowser.browser.qtnetworkdownloads.DownloadManager downloads=0>
    greasemonkey: <qutebrowser.browser.greasemonkey.GreasemonkeyManager object at 0x7fe1c88c3d70>
    macro-recorder: <qutebrowser.keyinput.macros.MacroRecorder object at 0x7fe1c89599d0>
    webengine-download-manager: <qutebrowser.browser.webengine.webenginedownloads.DownloadManager downloads=0>
    event-filter: <qutebrowser.app.EventFilter object at 0x7fe1c8874870>
    last-visible-main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>
    last-focused-main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>

window-0 object registry - 13 objects:
    main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>
    tab-registry: {0: <qutebrowser.browser.webengine.webenginetab.WebEngineTab tab_id=0 url='about:blank'>}
    message-bridge: <qutebrowser.utils.message.MessageBridge>
    download-model: <qutebrowser.browser.downloads.DownloadModel object at 0x7fe1bcf6e870>
    tabbed-browser: <qutebrowser.mainwindow.tabbedbrowser.TabbedBrowser count=1>
    command-dispatcher: <qutebrowser.browser.commands.CommandDispatcher>
    statusbar: <qutebrowser.mainwindow.statusbar.bar.StatusBar>
    status-command: <qutebrowser.mainwindow.statusbar.command.Command>
    completion: <qutebrowser.completion.completionwidget.CompletionView>
    mode-manager: <qutebrowser.keyinput.modeman.ModeManager mode=<KeyMode.normal: 1>>
    hintmanager: <qutebrowser.browser.hints.HintManager object at 0x7fe1bcf06cd0>
    keyparsers: {<KeyMode.normal: 1>: <qutebrowser.keyinput.modeparsers.NormalKeyParser>, <KeyMode.hint: 2>: <qutebrowser.keyinput.modeparsers.HintKeyParser>, <KeyMode.insert: 6>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.insert: 6>>, <KeyMode.passthrough: 7>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.passthrough: 7>>, <KeyMode.command: 3>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.command: 3>>, <KeyMode.prompt: 5>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.prompt: 5>>, <KeyMode.yesno: 4>: <qutebrowser.keyinput.modeparsers.PromptKeyParser>, <KeyMode.caret: 8>: <qutebrowser.keyinput.modeparsers.CaretKeyParser>, <KeyMode.set_mark: 9>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.jump_mark: 10>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.record_macro: 11>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.run_macro: 12>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>}
    prompt-container: <qutebrowser.mainwindow.prompt.PromptContainer win_id=0>

    tab-0 object registry - 1 objects:
        tab: <qutebrowser.browser.webengine.webenginetab.WebEngineTab tab_id=0 url='about:blank'>

after:

global object registry - 16 objects:
    quitter: <qutebrowser.app.Quitter object at 0x7f9484a83370> (for commands only)
    config-commands: <qutebrowser.config.configcommands.ConfigCommands object at 0x7f9471266910> (for commands only)
    crash-handler: <qutebrowser.misc.crashsignal.CrashHandler object at 0x7f94712e3a50> (for commands only)
    save-manager: <qutebrowser.misc.savemanager.SaveManager saveables=OrderedDict([('yaml-config', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename=None name='yaml-config' save_handler=<bound method YamlConfig._save of <qutebrowser.config.configfiles.YamlConfig object at 0x7f94712e3870>> save_on_exit=False>), ('state-config', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename=None name='state-config' save_handler=<bound method StateConfig._save of <qutebrowser.config.configfiles.StateConfig object at 0x7f94712669d0>> save_on_exit=True>), ('command-history', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=True filename=None name='command-history' save_handler=<bound method LimitLineParser.save of qutebrowser.misc.lineparser.LimitLineParser(binary=False, configdir='/tmp/qutebrowser-basedir-qwpr1fox/data', fname='cmd-history', limit='completion.cmd_history_max_items')> save_on_exit=False>), ('quickmark-manager', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename='/tmp/qutebrowser-basedir-qwpr1fox/config/quickmarks' name='quickmark-manager' save_handler=<bound method UrlMarkManager.save of <qutebrowser.browser.urlmarks.QuickmarkManager object at 0x7f94712a02d0>> save_on_exit=False>), ('bookmark-manager', <qutebrowser.misc.savemanager.Saveable config_opt=None dirty=False filename='/tmp/qutebrowser-basedir-qwpr1fox/config/bookmarks/urls' name='bookmark-manager' save_handler=<bound method UrlMarkManager.save of <qutebrowser.browser.urlmarks.BookmarkManager object at 0x7f94712a04b0>> save_on_exit=False>), ('cookies', <qutebrowser.misc.savemanager.Saveable config_opt='content.cookies.store' dirty=False filename=None name='cookies' save_handler=<bound method CookieJar.save of <qutebrowser.browser.webkit.cookies.CookieJar count=0>> save_on_exit=False>)])>
    readline-bridge: <qutebrowser.misc.readline.ReadlineBridge> (for commands only)
    web-history: <qutebrowser.browser.history.WebHistory length=1>
    command-history: qutebrowser.misc.lineparser.LimitLineParser(binary=False, configdir='/tmp/qutebrowser-basedir-qwpr1fox/data', fname='cmd-history', limit='completion.cmd_history_max_items')
    session-manager: <qutebrowser.misc.sessions.SessionManager object at 0x7f94712a0230> (for commands only)
    quickmark-manager: <qutebrowser.browser.urlmarks.QuickmarkManager object at 0x7f94712a02d0>
    bookmark-manager: <qutebrowser.browser.urlmarks.BookmarkManager object at 0x7f94712a04b0>
    qtnetwork-download-manager: <qutebrowser.browser.qtnetworkdownloads.DownloadManager downloads=0>
    greasemonkey: <qutebrowser.browser.greasemonkey.GreasemonkeyManager object at 0x7f94712a0c30>
    macro-recorder: <qutebrowser.keyinput.macros.MacroRecorder object at 0x7f94711aecd0> (for commands only)
    webengine-download-manager: <qutebrowser.browser.webengine.webenginedownloads.DownloadManager downloads=0>
    last-visible-main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>
    last-focused-main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>

window-0 object registry - 12 objects:
    main-window: <qutebrowser.mainwindow.mainwindow.MainWindow>
    tab-registry: {0: <qutebrowser.browser.webengine.webenginetab.WebEngineTab tab_id=0 url='about:blank'>}
    message-bridge: <qutebrowser.utils.message.MessageBridge>
    download-model: <qutebrowser.browser.downloads.DownloadModel object at 0x7f946436b730> (for commands only)
    tabbed-browser: <qutebrowser.mainwindow.tabbedbrowser.TabbedBrowser count=1>
    command-dispatcher: <qutebrowser.browser.commands.CommandDispatcher> (for commands only)
    status-command: <qutebrowser.mainwindow.statusbar.command.Command>
    completion: <qutebrowser.completion.completionwidget.CompletionView> (for commands only)
    mode-manager: <qutebrowser.keyinput.modeman.ModeManager mode=<KeyMode.normal: 1>>
    hintmanager: <qutebrowser.browser.hints.HintManager object at 0x7f9464304e10> (for commands only)
    keyparsers: {<KeyMode.normal: 1>: <qutebrowser.keyinput.modeparsers.NormalKeyParser>, <KeyMode.hint: 2>: <qutebrowser.keyinput.modeparsers.HintKeyParser>, <KeyMode.insert: 6>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.insert: 6>>, <KeyMode.passthrough: 7>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.passthrough: 7>>, <KeyMode.command: 3>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.command: 3>>, <KeyMode.prompt: 5>: <qutebrowser.keyinput.modeparsers.PassthroughKeyParser mode=<KeyMode.prompt: 5>>, <KeyMode.yesno: 4>: <qutebrowser.keyinput.modeparsers.PromptKeyParser>, <KeyMode.caret: 8>: <qutebrowser.keyinput.modeparsers.CaretKeyParser>, <KeyMode.set_mark: 9>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.jump_mark: 10>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.record_macro: 11>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>, <KeyMode.run_macro: 12>: <qutebrowser.keyinput.modeparsers.RegisterKeyParser>}
    prompt-container: <qutebrowser.mainwindow.prompt.PromptContainer win_id=0> (for commands only)

    tab-0 object registry - 1 objects:
        tab: <qutebrowser.browser.webengine.webenginetab.WebEngineTab tab_id=0 url='about:blank'>

Next step: Cleaning up MainWindow objects similar to what we did with tab objects, so those can be used to store children objects (and hopefully be exposed via the extension API in a nice way too).

@user202729
Copy link
Contributor

Instead of checking tab1.tab_id == tab2.tab_id would it be easier to do tab1 == tab2 or tab1 is tab2? https://github.com/qutebrowser/qutebrowser/blob/master/qutebrowser/browser/hints.py#L611

@The-Compiler
Copy link
Member Author

I don't think it matters much, unless we want to get rid of win_id/tab_id completely (which I don't think is feasible).

The-Compiler added a commit that referenced this issue Nov 24, 2019
This gets rid of an exception/abort when tests are finished with the new PyQt
exit scheme.

See #5017, #640
The-Compiler added a commit that referenced this issue Nov 25, 2019
The-Compiler added a commit that referenced this issue Nov 25, 2019
The-Compiler added a commit that referenced this issue Nov 25, 2019
The-Compiler added a commit that referenced this issue Nov 25, 2019
This also removes it from objreg, see #640.
The-Compiler added a commit that referenced this issue Jan 12, 2020
The-Compiler added a commit that referenced this issue Jan 13, 2020
@The-Compiler The-Compiler added this to Focus in Roadmap Apr 29, 2020
@The-Compiler The-Compiler moved this from Focus to Backlog in Roadmap May 4, 2020
The-Compiler added a commit that referenced this issue Dec 6, 2022
With Qt 6.4, QtWebEngine closes/reopens the main window to switch the
RHI rendering mode when a QWebEngineView gets added:
https://github.com/qt/qtbase/blob/v6.4.1/src/widgets/kernel/qwidget.cpp#L10706-L10709

To avoid this, we need to make sure we only call .show() *after* adding
a tab, similarly to what Qt did too:
https://code.qt.io/cgit/qt/qtwebengine.git/commit/?id=d7e0fd5304ebdb12c6f809cdbcf8193b49b9ecd2

See #7504

----

This commit handles changes around app.py/mainwindow.py, which was
probably the most complex one of the whole set (and also involving the
oldest code I bet...).

This changes a couple of intertwined things:

- app._process_args() now needs to take track of the window it opened
  (if any), so that it can call .show() on it *after* opening pages.
  * If a session was loaded instead, sessions.py takes care of showing
    it.
  * The setActiveWindow call was also moved for simplicitly. Before, it
    was called even with --nowindow given, but that doesn't really make
    much sense.
  * I'm not actually sure why the .setActiveWindow() call is there. Qt
    docs say "Warning: This function does not set the keyboard focus to
    the active widget. Call QWidget::activateWindow() instead.".
    It was added back in 2014: ae44aa0
    ("Set initial focused window correctly."). It's possible it's not
    needed anymore, but that's for a separate commit.

- app.process_pos_args() now gets a MainWindow (rather than win_id)
  from mainwindow.get_window(), and is responsible for calling .show()
  and .maybe_raise() on it when nothing else does (like open_url called
  from it).
  * To preserve existing behavior (and not fail tests), it also still
    calls .maybe_raise() when a command was given. Maybe we should get
    rid of this to fix #5094, but that's for a separate commit.
  * Note it does *not* call .show(). That's taken care of later in
    _process_args() already.

- app.open_url() can directly show the window, because it already opens
  a URL. It now still returns the MainWindow object for consistency.
  Also gets rid of some questionable objreg usage just to access a
  tabbed browser, as a tiny step towards #640.

- Similarly, app._open_startpage() could have stayed, but now also
  takes a MainWindow and uses objreg a bit less.

- open_desktopservices_url also takes care of the show/raise (as this
  gets called isolated from Qt), and also avoids objreg stuff where an
  attribute access would have sufficed...

- mainwindow.get_window() now doesn't show the window anymore, and
  returns the mainwindow object instead of a window ID.
  * However, this means it can't actually raise the window anymore
    (it needs to be shown first).

- To keep the decision about whether to raise the window in a central
  place, MainWindow now has a new should_raise attribute and
  maybe_raise() method. mainwindow.get_window() sets should_raise
  accordingly, and the caller is responsible to call .maybe_raise()
  after showing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: extensions Issues related to the (work in progress) extension API component: style / refactoring Issues related to coding styles or code that should be refactored. priority: 0 - high Issues which are currently the primary focus.
Projects
Extensions
  
Backlog
Roadmap
  
Backlog
Development

No branches or pull requests

4 participants