Sublime Forum

Structure of a sublime plugin, imports, shared library functions, etc

#1

I have been developing a plugin for a while now and I think I am missing some basic concepts, despite relative success. So here are a few questions that I hope somebody can answer, and if the answer is just a pointer to the right document, that would be perfect.

  • In python, a file is called a module and a folder full of modules is a package?
  • Is a sublime text plugin a module or a package? I suspect that the answer is ‘module’ because when I am editing files in my live plugin, sublime loads each file as it changes and refers to the file itself as a plugin.

reloading plugin sublemacspro.jove reloading plugin sublemacspro.sbp_goto_file

  • If each module is in fact its own plugin, is there any way to load those plugins individually?

  • If I split my code into smaller files, but want to share infrastructure between them, is that possible? In my sublemacspro plugin, I keep state on every active view, such as last touched time, but often more substantial data as well. It’s implemented as an EventListener. If I put that code into its own module and import it into other modules with actual TextCommand classes, is that event listener and view state duplicated in each module? I’d hate to be invoking difference instances of the same event listener N times just so my code can be better organized.

I am very confused. I tried to answer some of these questions just now, and here’s where I am at:

  1. There is only one instance of that event listener. That’s good.
  2. Importing a class from another module seems to work.
  3. However, I seem to end up with multiple versions of that class, possibly just 2 versions: one for modules that did “from sublemacspro.misc import *” before sublemacspro.misc was loaded, and one version after sublemacspro.misc was loaded by sublime.

reloading plugin sublemacspro.complete_all_buffers MISC 140391396522240 reloading plugin sublemacspro.jove JOVE 140391396522240 reloading plugin sublemacspro.mark_ring reloading plugin sublemacspro.misc MISC 140391395024112

This different ID means that I cannot do issubclass(SomeCommand, BaseClass) because they are two different classes. It also means class variables and the like will not be shared.

So is this sublime weirdness or just python weirdness I am not understanding? Is there a way for me to control the order that files are loaded in a sublime plugin? If I could specify the correct order, it might solve my problems.

0 Likes

#2

Well I may have discovered a few things just before giving up in frustration.

My current opinion is that everything at the top level is a “plugin” that is explicitly loaded by sublime. Anything further down in the hierarchy is not.

So if I move my shared modules down into a subpackage, I get the kind of sharing I seem to want and need.

0 Likes

#3

A module is a single python file.

A package is a directory with an __init__.py file. It is essentially a collection of modules.

The previous two terms did not involve sublime at all! They are Python-specific. However there’s also the unfortunate term “sublime package”, which, strictly speaking, is a subfolder of sublime’s Packages folder containing at least one sublime plugin.

A sublime plugin is any class that derives from a class inside the sublime_plugin module.

How many instances of your plugin are instantiated depends on which base class you derive from. If you derive from sublime_plugin.EventListener, exactly one instance will be instantiated at any time. If, for example, you would derive from sublime_plugin.ViewEventListener, your derived class would be instantiated for every view (into a file or scratch buffer).

Import statements do not affect the instantiations of your classes whatsoever.

If you want to track exactly when your instances are created and/or deleted, you can always define __init__ and __del__ methods.

Hope that helps!

1 Like

#4

@rwols, I have never seen anyone use the terminolgy like that, except for the Python-general stuff.

@canoeberry, you were pretty close with your observations.

A Sublime Text package is a sub-folder in (one of) the Packages folders that contains resource files. That includes plugins, color schemes, themes, syntax definitions, etc.

A plugin is a Python file in the package root, i.e. not in a sub-folder of the package, since ST does not auto-load those.

Inside plugins, you usually subclass one of the sublime_plugin-provided classes to either define a command or register an event listener.

EventListeners are instantiated exactly once (on plugin load time), as are ApplicationCommands. WindowCommands are instantiated for each window and TextCommands for each view, or even every time a command is run. I’m not so sure about that right now.
When storing view-specific data, it’s usually a good idea to use a global dictionary with the views’ ids. If the data should persist across restarts, you could also consider using view.settings().

Within a plugin, Importing a class that sub-classes a sublime_plugin class will cause it to be registered by the plugin loader and get instantiated. This is an attribute look-up, so if you assign a class to a different name (DifferentEventListener = MyEventListener), ST will create two event listener instances from the same class. The same happens if you import that class in two different plugins.

3 Likes

#5

Thanks to both of you for your comments.

I have everything working in this new organization of my code now and am pretty pleased about it. This is not the first time I tried to structure my code differently.

I remembered the __init__.py file after my original post. I thought it would be part of my solution but the mere fact that moving support files into a subdirectory means they are not autoloaded seems to have done the trick.

So neither of you (unless I missed it) addressed the fact that I got separate instances of a class definition. I can now see that that is a result of the autoloading of all top-level files. It’s not that I had two instances, but that I had one instance which I made use of, and then it was clobbered (in the module namespace) with another version when sublime autoloaded the same file that had been referenced by other top-level modules.

If view event listeners (which are new) are automatically created by sublime, I could use those for all my per view state, simply by registering them in a dict with __init__ and __del__. Right now I observe with a global event listener and register them “manually”. I might just stick with what I currently have.

I guess the final question is, Is there one global Python context or does my plugin exist in its own context? I think the answer is, One global context, using the name of the plugin for separating the namespaces. Because a piece of my plugin that I didn’t write references the Default package.

OK - it’s all coming together. So now I just have to wonder whether I can now split up my code in such a way that there are separate but cooperating pieces of functionality that people can pick and choose. For example, I’ve written a pretty sweet i-search package which others might find useful, but they don’t want the rest of the emacs like baggage. Perhaps just settings variables and context directives for all the key bindings is all I need for that.

0 Likes

#6

ViewEventListeners are also a good idea, yeah. I haven’t used them yet since they are somewhat new, but afaik they are built for situations like this and should work just fine (assuming your ST build is recent enough).

I don’t think I understand the remaining multiple instances question because I think I addressed that, but that’s not really important.

Yes, there is one global context and all plugins share the same process.

0 Likes