Sublime Forum

How does class initialization work in sublime text plugins?

#1

Hi,

I’m currently playing around with the sublime text API and I’m still trying to figure out how to use it properly.

I have a extension file with, a base class, let’s say:

class Foo(sublime_plugin.WindowCommand):
    bar = 1
    def run:
       print(bar)

Now working on complex script I don’t want to put all of my functions in the same class.
It also isn’t possible because of the way new commands are created, since I need a new class for every new commands, with them having probably different parent class(EventListener, TextCommand…). My problem is that I need all of these diferrent class (one for each command) to be able to access and modify the same variables from my base class Foo, so I need it to be instanciated only once.

In a classic python program I would know the instance of my class since it would only be created when I would write:

foo = Foo()

But what I understand is that in the sublime API, every child class of a sublile_plugin class is instanciated once at the first time sublime text try to access it, so I don’t really have control on how to properly link them to only one instance of my base class.

Can anyone help me understing how this works?

0 Likes

#2

I believe I had something similar that I needed done and I used a global variable to handle this. So in the language package I was developing I want to keep track of a certain structure in the file. There is a command to parse this structure, which fills in details, and then I can paste it in a variety of formats. At the top of the *.py file just after the usual import statements I have:

_interface = vhdl.Interface()

which declares an instantiation of the Interface() class.

Then in every method that needs to use this information I have to declare:

global _interface

in order to tell Python that this _interface object is not local to the method but should be using the global token.

I do not know if this is the best way to structure it, but it seems to work adequately for my needs. There is ever only one interface at a time that is being operated on, so I didn’t have to do anything particularly fancy.

0 Likes

#3

You can put global data in a module.

You can put view-specific data in a subclass of a ViewEventListener.

Unfortunately there’s no easy way to have window-specific or buffer-specific data. I just keep around a dictionary of window IDs to my data if needed.

Your TextCommand is instantiated every time your command is run via view.run_command(“foo”). Same for your WindowCommand and ApplicationCommand.

An EventListener is a singleton.

A ViewEventListener is instantiated per view and is tied to the lifetime of that view.

0 Likes

#4

There is always one instance of a TextCommand for every view, one instance of WindowCommand for every window and one instance of ApplicationCommand for the whole application created. The creation is triggered by the plugin being loaded, not by the command being run (in general, but see below).

That is, once a command instance is created for a thing, it remains in existence until that thing goes away; it doesn’t get created again for the same thing unless the plugin that provided the command gets reloaded.

That said, Sublime is a bit “lazy” about command instantiations sometimes, which can give the impression that it’s creating commands more often than you might think. The general assumption should be that you don’t know when exactly a command will be instantiated for any particular thing, but you can be assured that it will exist at least at the point where it’s executed and that it will remain in existence for conceivably forever barring plugin or package upgrades.

For example, given the following sample plugin:

import sublime
import sublime_plugin


class ExampleOneCommand(sublime_plugin.TextCommand):
    def __init__(self, view):
        super().__init__(view)
        print("New Text Command")

    def run(self, edit):
        pass


class ExampleTwoCommand(sublime_plugin.WindowCommand):
    def __init__(self, window):
        super().__init__(window)
        print("New Window Command")

    def run(self):
        pass


class ExampleThreeCommand(sublime_plugin.ApplicationCommand):
    def __init__(self):
        super().__init__()
        print("New Application Command")

    def run(self):
        pass

With this in place, starting Sublime with three windows and 4 open files displays this in the console:

plugins loaded
New Text Command
New Text Command
New Application Command

The numbers there don’t really add up; there aren’t even any WindowCommand instances created. However when you give the focus to one of the other windows, you can see more commands being created:

New Text Command
New Window Command
New Text Command
New Window Command

This behaviour only really holds when plugins are loaded/reloaded, which means that it tends to be lazy both at startup and when a plugin updates and new command instances need to be created.

Outside of that situation, every time you create a new tab a new TextCommand instance is created for it, and every time you open a new window a new WindowCommand instance is created for it; at least as far as I’ve personally witnessed, which is somewhat anecdotal.

In any case, running the command doesn’t necessarily create a new command unless that view or window didn’t already have an instance created for it:

>>> window.run_command("example_two")
>>> window.run_command("example_two")
>>> window.run_command("example_two")
>>> window.run_command("example_two")
>>> window.run_command("example_two")

This property allows commands to retain state between invocations, if you’re so inclined to do so, so long as you’re aware that there may be points where the persistent state is lost (at startup or package reload time). In cases like that it’s probably not a good idea to rely on this mechanism for persistence and you should use some other mechanism. The same could be said for creating global objects as well, though.

You would need to use some external global state if you needed more than one command to also have access to it, however. Without your intervention instances of classes created for different view or window objects don’t know about each other’s existence.

Possibly a static class variable could be useful in such a case, if your use case permits.

4 Likes

#5

I stand corrected!

0 Likes

#6

They don’t call me OdatNurd because I had a lot of friends in high school! :wink:

1 Like

#7

So that’s what I was thinking ; )
Thanks a lot for this in-depth analysis OdatNurd, it seems that you were experimenting a lot with the API !

As suggested by Remi I went for globals although I wanted to avoid it but it seems there’s no other options - Then I suddenly realised that my plugin wasn’t multiple-window accurate, so I did pretty much exactly what rwols suggested (a global dictionnary of window.id() linked to instances of my base class, with a new key created at each call from a new window.

You would need to use some external global state if you needed more than one command to also have access to it, however. Without your intervention instances of classes created for different view or window objects don’t know about each other’s existence.

Possibly a static class variable could be useful in such a case, if your use case permits.

I don’t know about static class. In my case I don’t need windows to be able to communicate - actually I need the opposite. I also don’t need to care about plugin / window reload as my globals variables are set at that very moment - which in my case is way better than having to deal with any kind of backup.

Thanks everyone for your help. As a final question, which might be less Sublime-specific, but do I have any interest going for

super().__init__(window)

instead of simply

self.window = window

?

0 Likes

#8

It’s generally very rare that you would need to define __init__ for a Command class, I think. If I recall correctly, the only time I’ve ever done it was while testing things such as this, actually.

In my own code in cases like this I like to chain to the super class version of the same method as a safety in case it changes upstream; I want to assume that my class works exactly like the default would except for any specific changes I make and this seems like the safest way to do that. It also acts as a reminder to myself when I look at the code what’s actually happening there.

I’m not a Python guru by any stretch though, so I don’t know what the general feelings are for one way versus the other if you were going for the “pythonic” style choice there.

0 Likes

#9

As you can read from my editted post, I actually need a init function since my global variables are window dependent - and need to be set only once the first command is sent.

Can I ask what project were you working on ?

0 Likes

#10

I tend to do a lot of smaller Sublime plugin type things, just as explorations (like the above) or as part of answers to questions here on the forum or on Stack Overflow (selected plugin examples are available in this repository).

Probably my biggest project to date is OverrideAudit though; or at least it’s the one that is currently publicly available.

1 Like

#11

Definetly a sublime nurd.
Will dig into this.

0 Likes