Sublime Forum

Plugin state

#1

Whats the better way to share state.
I want to make plugin for js es6 modules - once pressed on import line it will open file, later on it should go back once key combination is pressed.

Right now i could achieve it with global variable, but it looks not so great.
I’m not a python developer, could you suggest proper way how to manage state.

Code sample

import sublime
import sublime_plugin

back_file_name = ''

class GoToFileCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        global back_file_name
        selectionRegion = self.view.sel()[0]
        lineRegion = self.view.line(selectionRegion.begin())
        line = self.view.substr(lineRegion)

        startIndex = line.find("'")
        endIndex = line.find("'", startIndex + 1)

        file_name = line[startIndex + 1 : endIndex]
        
        if (file_name.find('.js') == -1) :
            file_name = file_name + '.js'

        window = self.view.window()

        child_view = window.open_file(file_name)
        back_file_name = self.view.file_name()

class BackToFileCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        global back_file_name
        window = self.view.window()

        window.open_file(back_file_name)
        back_file_name = ''
0 Likes

#2

No, that’s how you do it. Using global is a good indicator as well. Always good to read. Use a mapping if you have more state.

state = {'last_file': None}
1 Like

#3

You may be interested in https://packagecontrol.io/packages/HyperClick

1 Like

#4

Global variables are indeed a way to do this (and sometimes they fit the bill perfectly) but there are some potential issues with them. For example, they go away when a plugin reloads or when you restart Sublime. So if you want to keep state that has to persist through something like that it’s not as reliable an option.

They can also be more work sometimes as well, depending on the state that you’re trying to save. For example in your example code every time go_to_file is executed, it sets what the “last file name” was, which means that if you run it in several files, you can only go back to whatever the last one was.

If you instead wanted to set it up so that every time you go back, it takes you back to the file that you were in when you ran the command that opened that file, then your global would have to keep a lot of state to associate what the previous file was for every file it opened (and keep track of when files close, etc).

Depending on the kind of data that you’re using, you can use settings to do the same thing; settings are allowed per view and also per window. Settings are persisted in the session/workspace information, so they automatically persist across plugin reloads and Sublime restarts without any extra work. Also since they associate with a view or a window, keeping state can be easier that way as well.

The caveat here is that it only works for values of simple types like strings, numbers, lists and dictionaries (or things that can be represented that way). So storing things like filenames is possible, but arbitrary object instances are not.

As an example of this, GoToFileCommand could contain code like this:

window = self.view.window()

child_view = window.open_file(file_name)
child_view.settings().set("back_file", back_file_name)

Now when the command opens a new file, that file records a setting that says what the file was when the command caused it to open. You can then adjust BackToFileCommand accordingly:

back_file_name = self.view.settings().get("back_file")
self.view.settings().set("back_file", "")

window = self.view.window()
if back_file_name:
    window.open_file(back_file_name)

Here the name of the file is pulled from the setting, the setting is cleared, and then if a file was taken from the setting, it’s opened. So that would mean that if you had two files open and you ran your command in both of them, each of those newly opened files could jump you back to the file that was active when you ran it without any extra work.

Your current example always assumes that only the most recent execution of go_to_file should be able to set the file to go back to. To do something like that you can associate the settings with the window that the file is in instead of the view that represents it. Then no matter what file you happen to be inside of, you can go back to the file as long as you’re in the same window.

In that case, GoToFileCommand would do:

window = self.view.window()

child_view = window.open_file(file_name)
window.settings().set("back_file", back_file_name)

Here the setting is persisted into the settings for the window instead, which means that for any file open in that window, only the last opened file is recorded. In this case you’d have to modify the code in the other command to get the settings from the window instead of self.view.

As a side note, keep in mind that an instance of a TextCommand is created for every file that’s opened, so in cases where you want to keep state between invocations of the same command you can just use standard instance variables in your command class:

import sublime
import sublime_plugin


class ExampleCommand(sublime_plugin.TextCommand):
    last_count = 0

    def run(self, edit):
        sublime.message_dialog("Last count: %d" % self.last_count)
        self.last_count = self.last_count + 1

Here the first time you run the command the count is 0, and it goes up for every time you run the command, but the count is separate based on the file.

In a similar fashion, there is one instance of a WindowCommand created per window and there’s always exactly one instance of an ApplicationCommand, so you can use those in a similar fashion.

Note however that in this case, this information will not persist through restarts or plugin reloads because the instances get re-created; you still need to use settings for things you want to hang on to.

2 Likes

#5

Wow. you are 100% correct
Thanks

0 Likes

#6

Thanks a lot for such detailed explanation.
It is very helpful and clear.
Really, great article.

0 Likes