Sublime Forum

[SOLVED] Where and how do I store internal package settings?

#1

Hi.

I am just starting out with Sublime Text Package Development, so I have some basic questions. In a package that I am trying to write, I need to collect some paths of installed applications. Now, once I collect these paths I want to store them somewhere, so they are available once I run a command that needs them or even the next time I start Sublime.

I tried to store them in a sublime-settings file, but that results in having them written to a sublime-settings file in the user folder. These are settings though that should not be altered by the user, so they should rather be stored somewhere internally within the Package directory itself.

What is the recommended way of doing this?

Thank you!

0 Likes

#2

The settings mechanism in Sublime allows a sublime-settings file to appear in any package that wants one (and anywhere inside of the package as well), but using save_settings() always stores the result in the User package.

The general idea is that since multiple settings files of the same name are automatically loaded and combined together, the package should carry a settings file that has the default values for any settings that it supports, and the user can apply their changes to a file in their User package to override only the settings they require.

It’s a little unclear from your description why it’s a bad thing that the user be able to modify the settings that you’re gathering (for example, what if your automatic detection doesn’t work or finds the wrong thing?).

Presuming you want to keep your data away from prying eyes (easily), technically speaking you can write a file anywhere you want. Assuming you don’t want to write it into the User package, you could choose to put it into the Cache folder instead (with the proviso that you’re prepared for the file to be missing and regenerate it), or if your package was named say Bob you could create a Bob folder in Packages (if it doesn’t already exist) and write the file into there instead.

I don’t think there’s a recommendation per se expect for what works for you and the goal you’re trying to achieve, though.

1 Like

#3

That’s a fair point. While I don’t want to hide the files from the user, I still would prefer to let the user change these settings through dedicated commands. So have an auto-detect command in place and then offer a command with a file picker to add application paths manually in case the auto detect fails.

So, I do something like

with open(os.path.join(packageDir, 'appPaths.json'), 'w') as file:
      file.write(json.dumps(appDict))

? And then read from that json file again in my commands? Is that what you mean?
Or is there a dedicated Sublime API method to achieve the same thing in a more native manner? I am also new to python, so I am somewhat still in the dark when it comes to using it.

Thanks!

0 Likes

#4

Something to keep in mind is that it’s somewhat of a “power user” kind of thing in Sublime to seek out and edit a sublime-settings file yourself (say with a file browser or via View Package File from the command palette) and generally that would be exposed by adding an item to the menu and/or the command palette that opens the appropriate file for you.

So in that sense, it may be enough to have two settings files; one that’s “hidden” and one that’s "public’; the hidden one would be a settings file stored in the User package but not exposed and the other one would be made more traditionally available. In that scenario you’re more or less setting up a power user with the ability to easily tweak the file (which they could probably figure out anyway) without going to any extra work.

On the flip side, something in favor of not storing the file in the User package is that it’s fairly common to syncrhonize your User package across multiple computers (including multiple operating systems) to keep all the copies of Sublime you use running the same. In such a case, storing configuration like the one you’re mentioning here may be a bad thing if the data it contains is somehow machine specific.

More or less, yep. Presuming that you obtain packageDir as something like os.path.join(sublime.packages_path(), "MyPackageName") (basically, you don’t want to write directly to sublime.packages_path()).

An alternative is to use sublime.cache_path() instead; that keeps things outside of the Packages folder but still associated with Sublime itself.

If you use packages_path(), you should definitely use a folder named for your package, since that’s essentially your “namespace” and is an area that nothing else should be touching. In the case of cache_path() it’s generally a good idea to do the same, for similar reasons.

In either case you also need to take the step of making sure the folder exists. If you’re working on your package locally, then that’s definitely the case. However if you distribute your package via Package Control at some point, it generally ends up getting installed as a sublime-package file instead (unless you tell Package Control not to do that), in which case the folder won’t be there unless you create it first,

The sublime.load_resource() method can be used to load the content of a package (wether it is in a folder or inside of a sublime-package file), so you could use that in order to load the file when you need to. There’s no real analoge for saving files into your package though, since generally speaking the most common things to be saved into a package would be settings (and that happens automatically).

To use it, you could do something like sublime.load_resource("Packages/MyPackage/appPaths.json") to give you the content of the file in one call (or load_binary_resource(), if the file is not text). It will throw an exception if the file can’t be found. You can also use sublime.find_resources() in order to determine if the file exists; sublime.find_resources("appData.json") would return a list of every file by that name that exists in a package, for example.

Something to note is that load_resource() will work with files from the Cache folder, but that’s sort of an implementation detail that may or may not go away, so it may not be a good idea to rely on it.

One last thing is that rather than loading the file in your commands, you could define a plugin_loaded() function in your plugin and load the file there. That method gets called every time a plugin reloads, which is the first time it’s loaded at startup, whenever you modify it, and when the package that it’s in gets removed from the ignored_packages setting. Assuming the data doesn’t change very often, that would let you load the file once instead of every time a command that needs it is executed.

0 Likes

#5

Thank you so much @OdatNurd for taking the time and reply in such detail. This is very valuable information for me.

Yes, having a public and a hidden file was my plan. Reading the pros and cons, it seems to make sense to me to not store this hidden file in the User folder, as it – as you correctly point – contains very machine specific data.

Yes, that’s how I did it.

Yes, I want to eventually distribute it as a sublime-package file.

I don’t quite understand, can I still write into files within this (zip packaged) sublime-package file or would I have to keep it unpackaged in this case?

This sounds good, but I also don’t quite understand where to this would load the data then. Would I just load it into some global top level variable that all the commands then have access to?

Thanks!

0 Likes

#6

Packages in Sublime can be installed as both sublime-package files and folders in the Packages folder at the same time. Files in Something.sublime-package and Packages/Something are both considered to be coming from the same place, which is generally transparent to you.

That’s why sublime.load_resource() exists and takes a file name of the sort Packages/Something/somefile.txt. As far as that method is concerned it doesn’t care which location the file comes from. Using that allows you to be sure that no matter how your package is installed, it will still find the correct file to open.

So in that regard, you can have Package Control install your package as a sublime-package file (which is the default unless you tell it otherwise) and still create the folder in Packages manually if you need to store extra files of any sort. Since the files there are considered as being part of your package, it’s a folder that’s “owned” by your package, if you will.

You generally only need to have your package be installed unpacked if it contains something that needs to be externally accessible, such as if it contains an executable that you’re going to launch, a needed library for something outside of the package, etc. All else being equal, being installed as a sublime-package file is better because it allows you to override a resource if you need to.

Yeah, pretty much. You can structure it how you like, but the general gist is that plugin_loaded() is guaranteed to be called for any plugin as it’s loading, and at a point where accessing the Sublime API is allowed, so it’s a good place to include initialization like that.

Keep in mind that in Python a global is really only global to the file that it’s defined in; it doesn’t “leak out” to be visible elsewhere unless something explicitly tries to get at it,

So, plugin_loaded() could set up a global that has the loaded data if the file exists, and your command to find and create the file can both save the file there and update the global to contain the data that it found. Then all commands that need access to it can get at it directly.

1 Like

#7

Perfect, I think I could follow all your explanations.

Again, thanks so much for taking the time to explain all this to me. Also, just to let you know, your YouTube channel was what got me started with Sublime Text Plugin development in the first place. Absolutely fantastic resource. Discovered it a few weeks ago and pretty much binge watched the entire channel already. Now I feel ready to tackle my first package!

2 Likes