17. Packages
Packages are your primary mechanism for acquiring and adding features and capabilities to Sublime Text that are not shipped with it. You can acquire Packages from the Sublime Text New Package Control Hub, from 3rd parties, or you can create them yourself if you are so inclined. See Package Distribution below for more information on where and how you can get Packages.
Packages are simply a set of Resource Files used by Sublime Text, gathered into a single directory. That directory’s name defines the Package’s name.
A Package Resource is simply a Resource File that is part of a Package and therefore in a Package directory.
Another (more compact) form they can come in (e.g. the shipped Packages and most
user-installed Packages are in this form) is in a .sublime-package file,
which is simply a .zip file containing all the files of that package, preserving
the Package’s directory structure.
Compressed or not, any package file can be viewed using:
Files retrieved from a compressed file will be read-only, but may be saved as editable files using.
(They remain read-only until they are closed and then re-opened again from where they were saved.)
Be advised: if there is a .sublime-package, as well as a loose file in a
<data_path>/Packages/pkg_name/ directory, the contents of the
loose file will override the ENTIRE file stored in the .sublime-package file.
See Overriding below for more details.
Sublime Text ships with over 50 Packages pre-installed, and many, many user-created Packages are available through several channels, the easiest of which is using the Package Control Package, which provides very easy management of the Packages you have installed with any given Sublime-Text installation.
17.1. Sublime Text Packages vs Python Packages
As it turns out, the name “Package” in Sublime Text’s Python context is indeed aptly chosen: from Sublime Text’s perspective, a Package is a Python Namespace Package, which is a Python package which typically has more than one directory containing its contents. This is how Sublime Text Packages can live in multiple directories as is shown below.
In Sublime Text’s Python run-time environment, the <data_path>/Packages/
directory is added to Python’s sys.path list, which provides a set of directories
where Python goes looking for a package when it is told:
import xxxxxx
It will look for xxxxxx as a module or directory name. In the case of
Sublime Text Packages, it is always a directory name, and can be in a number
of locations as explained below.
Note: From your Sublime Text Package, you can do relative importing, which is ONLY legal from within a Python package, and generates an exception when it is used outside of a Python package.
from . import sibling_module
This is also why the <data_path>/User/ directory is sometimes referred to as the “User Package”.
API Environments Overview describes the environment Plugins are run in. The 3rd paragraph describes the relationship of Plugins to the Package.
17.2. Locations
The concept of a “Packages Directory” is really 5 possible locations for any given Package Resource File.
Terms:
- Default Package:
Loaded first;
Default.sublime-package[1]Represented location:
<data_path>/Packages/Default/Actual location:
<executable_path>/Packages/
- shipped Packages:
Loaded next (alphabetically by Package name);
<pkg_name>.sublime-packagefiles shipped with Sublime Text are stored here. (Do not change this directory’s contents.) [1]Represented location:
<data_path>/Packages/pkg_name/Actual location:
<executable_path>/Packages/
- installed Packages:
Loaded next (alphabetically by Package name); Packages installed by user are stored here when they are designed to remain in
.sublime-packageformat. [1]Represented location:
<data_path>/Packages/pkg_name/Actual location:
<data_path>/Installed Packages/(not editable)
- unpacked Packages:
Loaded next (alphabetically by Package name); Packages installed by the user when designed to be unpacked (loose files) by Package Control, as well as user-created Packages can be stored here.
Represented location:
<data_path>/Packages/pkg_name/Actual location: same (on disk, Resource files editable)
(See note below.)
- User Package:
Loaded last; <data_path>/User/. Customizations unique to each user are stored here. Path representation:
Packages/User/.Represented location:
<data_path>/Packages/User/Actual location: same (on disk, Resource files editable)
If you are a power user, your customizations will go in the latter 2 directories.
The User Package can be used to develop new packages while they are in
experimental stages. Once in a more complete state, they can be moved to
<data_path>/Packages/pkg_name/.
17.3. Overriding
The above loading sequence is how “overrides” or customized behavior are implemented.
Note carefully that when the Package (including the User Package) has a file like
Default (Windows).sublime-keymap or Main.sublime-menu, it does not replace
the whole Keymap or Main Menu. In these cases, the overriding happens at a finer
level of granularity: these files contain JSON data structures (e.g. a list or tree
structure [respectively] of dictionary objects), and the value of each entry in the
data structure overwrites the already-loaded value for the entry with the same key
and hierarchy location in the data structure. So you can think of the contents of
both files as being “merged”, where due to the loading sequence outlined above, the
values loaded from the Packages lower in the list overwrite the same values from
Packages higher in the list. It is this sequence that governs what overrides what.
Note that the above-described “merge” override behavior is what happens between the
Package (including the User Package) and the Sublime Text application, and is
restricted to just some (not all) of the Resource Files
that contain JSONC format data:
.sublime-commands
.sublime-completions
.sublime-keymap
.sublime-menu
.sublime-mousemap
.sublime-settings
But this does not apply to any of the other types of Resource Files:
.sublime-build
.sublime-color-scheme
.tmTheme
.sublime-macro
.tmPreferences
.py
.sublime-snippet
.sublime-syntax
.tmLanguage
.sublime-theme
17.3.1. Overriding Whole Files from a Zipped Package
Once you understand the “merge-type” overriding described above, it is important to understand that there is another type of overriding (whole-file overriding) that is triggered when:
the Package being overridden is in archive form (i.e. with
.sublime-packagefile extension, which will be either an installed Package or a shipped Package, andthe override file is placed in an unpacked Package directory (i.e. in <data_path>
/Packages/pkg_name/with an identical relative path and name).
In this case, Sublime Text literally ignores that same file from the
.sublime-package file and replaces it with loose file in the unpacked Package
directory.
In this case, the loose-file directory <data_path>/Packages/pkg_name/
is called an “Override Package”.
Illustrated:
<executable_path>/Packages/<pkg_name>.sublime-package, or
<data_path>/Packages/Installed Packages/<pkg_name>.sublime-package ----+
| loose file with
| same name and
| same relative
| subdirectory
<data_path>/Packages/<pkg_name>/... <--------------------------------+
When the Override Package directory is empty, it doesn’t change anything. But for
each occurrence of a file with the exact same relative subdirectory and filename as
in the .sublime-package file, Sublime Edit ignores the .sublime-package file
and uses the loose file in the Override Package instead.
An interesting aspect of Override Packages is that Python plugins therein are able to
use relative import paths for accessing other modules in the corresponding
.sublime-package file as if they were part of it.
Example: You want to add functionality to the Python Package’s def (function)
Snippet. Its default content is provided
in <executable_path>/Packages/Python.sublime-package.
The way to find the Snippet file that is involved, make a copy of the
Python.sublime-package file in a safe, empty directory, change its extension to
.zip and then extract the contents out of the file. Scan the package Snippet
files for
<tabTrigger>def</tabTrigger>or just>def<
and you will find it in the ./Snippets/ subdirectory as function.sublime-snippet.
Make a copy of that file and place it in
<data_path>
/Packages/Python/Snippets/
then modify it to suit you. Sublime Text reloads it as soon as you save it so the turn-around time on modifications-to-testing is remarkably short.
Note
The following two Packages provide a convenient way to create an override of a single Package file. The Commands they provide that do this are:
PackageResourceViewer: Open Resource
OverrideAudit: Create Override
Both of these commands:
open the file in an editable form, with an “apparent” path <data_path>
/Packages/pkg_name/resource_file, even though that file (and directory) may not exist yet, anduse the
Ctrl+S(Save file) operation to create the file if it is not already there, as well as create the new <data_path>/Packages/pkg_name/directory if it did not already exist.
Note
Some packages are configured to be installed as loose files in the
<data_path>/Packages/pkg_name/. When this is the case, once they are
installed, the only instance of that Package is as loose files in that directory.
When this is the case, editing of those Package Resource files (when they
are not created by yourself) is not recommended because those files get replaced
when the Package is updated. If you wish to override only certain files in an
unpacked Package, you can create a .sublime-package file out of it by
zipping its contents, preserving the directory structure under the
<data_path>
/Packages/pkg_name/
directory, naming the zipped file
pkg_name.sublime-package
and moving the newly-created file into the
<data_path>
/Installed Packages/
directory. Then in the original (unpacked) directory (which now becomes
an Override Package because of the existence of the
<pkg_name>.sublime-package file where Sublime Text will load it), keep only
the files you wish to override.
Caution
A precaution about Override Packages: if/when the overridden file(s) in the
corresponding .sublime-package is updated, you will not be notified.
The OverrideAudit package provides monitoring of override files and will notify you when the file it overrides has been updated.
17.4. Creating a New Package
You create a new package by simply creating a directory under
<data_path>/Packages/.
This directory is conveniently accessible via .
What you put into that directory determines how that Package will supplement Sublime Text’s features and capabilities.
There are a number of different categories of functionality Packages can have. A
review of the Package Distribution Hub <Package Control Hub_> shows Packages that:
do things (which involve Plugins),
supply one or more Completions and/or Snippets files,
supply a new language syntax (involves color highlighting),
supply a Build System,
supply a Color Scheme,
supply a Theme,
supply reusable libraries for other Package developers, and
combinations of the above.
If you plan to share your Package with the Sublime Text community, some functionality overlap is understandably restricted by Submitting a Package, but you can always make more than one Package.
17.4.1. Python Version
As of Sublime Text build 4200, the default Python version used by a package is 3.3. However, Python 3.8 is available. To get your Package to use Python 3.8, place a file in the top directory of the package containing just 3 bytes: “3.8”.
cd MyPackage/
ls .py*
.python-version
cat .python-version
3.8
17.4.2. Managing Complexity
As functionality is added to Packages being developed, they can become quite complex, or perhaps they start off having a complex design right from the beginning. For increased maintainability it can be useful to move related groups of related functionality into their own Python modules for the same reason any complex Python program is broken down into modules.
Simpler Packages can factor out related functionality into their own sibling
(top-level) Python modules which make use of each others’ facilities via the from .
import sibling_module statement.
More complex Packages sometimes factor out related functionality as Subpackages for the same reason that Python supports Subpackages within Packages: modularity and maintainability—each Subpackage and each Python module has its own sphere of responsibility, which reduces complexity within each Subpackage. The method of doing this is exactly the same as implementing any Python Subpackage. Usually developers will want to limit doing this to when it makes sense to do so: when the subpackage is relatively self-contained, providing its own data and/or services to the main Package, and (preferably) only depending upon external Packages that ship with Python.
17.4.2.1. Caveat for Subpackages
There is an important caveat to using Subpackages in a Sublime Text Package if
you have the Package Control Package installed (which is true for most
Sublime Text users). The reason for this is that the Package
Control Package automatically updates installed Packages while Sublime Text
is running, and when it does this, it does not re-start the Python Interpreter,
but instead uses importlib.reload() to reload the top-level Python modules
(.py files) in each installed Package that gets updated. If an updated Package
has Subpackages that might have been updated, those Subpackages do not get
automatically reloaded (with their possibly-updated logic) until Sublime Text
restarts. For Packages that will never change again, this is not an issue. However,
for packages that get updated periodically, when the update happens, until
Sublime Text is restarted, there is a risk of such Packages being in an
inconsistent state and possibly broken unless the Package itself addresses this
caveat internally.
Here is how to do it.
Let’s say your Package is called ProComment and is structured like this:
ProComment/
procomment.py <-- central Plugin
lib/
__init__.py
debug.py
utils.py
src/
__init__.py
core.py
contexts.py
func_parser.py
commands/
__init__.py
insert_comment.py
insert_datestamp.py
insert_timestamp.py
Your central (top-level) Plugin is named procomment.py and you have 3 Subpackages:
lib, src/ and src/commands/. In your central Plugin, implement something
like this:
""" procomment.py """
import importlib
import sys
import os
from typing import Tuple
# =========================================================================
# Data
# =========================================================================
_cfg_compressed_pkg_ext = '.sublime-package'
# Use name of parent directory as `package_name`.
module_path, _ = os.path.splitext(os.path.realpath(__file__))
parent_dir, submodule_name = os.path.split(module_path)
_, package_name = os.path.split(parent_dir)
if package_name.endswith(_cfg_compressed_pkg_ext):
package_name = package_name[:-len(_cfg_compressed_pkg_ext)]
del _
this_module_name = f'{package_name}.{submodule_name}'
_reload_indent_level = -1
# Can't use `debugging = is_debugging(DebugBits.IMPORTING)` here because
# the import required to support it causes a circular import.
debugging = True
if debugging:
print(f'{this_module_name} >>> module execution')
# =========================================================================
# Load / Reload
# =========================================================================
def reload(dotted_subpkg: str, submodules: Tuple[str, ...] = ()):
"""
Reload each module in `submodules` only if previously loaded. This is a
precondition of calling ``importlib.reload()`` but is also for efficiency:
- if Sublime Text is just starting, nothing important happens here (because
the cached modules will not have been added to ``sys.modules`` yet), and
and the various ``import`` statements do the loading in the usual way;
- if ``Package Control`` is updating this Package (or the central Plugin
was just saved during development), then this function recursively
reloads each loaded module, and the ``import`` statements then do
nothing since each target module will already be in ``sys.modules``.
Note: The below works on the basis that :file:`<sublime_data>/Packages`
directory was placed in ``sys.path`` by Sublime Text. So the
module names being constructed below have to look like this:
MyPackage.subdir.module
MyPackage.subdir.subdir.module
etc.
:param dotted_subpkg: dotted directory portion of module names that
will be found in the keys of ``sys.modules``.
Example: 'MyPackage.src.commands'
:param submodules: tuple of submodule names; CAUTION: if there
is just one submodule, ('module_name') is NOT
NOT A TUPLE and will cause that module to NOT
be reloaded! It must be ('module_name',) with
a comma to make it a tuple.
"""
global _reload_indent_level
_reload_indent_level += 1
indent = ' ' * _reload_indent_level
if debugging:
if _reload_indent_level == 0:
print('vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv')
print(f'{indent}reload(): >>> {dotted_subpkg=} {submodules=}')
if not submodules:
# Called from top-level Plugin.
module_name = dotted_subpkg
if module_name in sys.modules:
if debugging:
print(f'{indent}Reloading({module_name})')
importlib.reload(sys.modules[module_name])
else:
# Called from subpackage.
for submodule in submodules:
module_name = f'{dotted_subpkg}.{submodule}'
if module_name in sys.modules:
if debugging:
print(f'{indent}Reloading({module_name})')
importlib.reload(sys.modules[module_name])
if debugging:
print(f'{indent}reload(): <<<')
if _reload_indent_level == 0:
print('^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^')
_reload_indent_level -= 1
reload(package_name + '.lib') # Recurse into .lib/ subpackage.
reload(package_name + '.src') # Recurse into .src/ subpackage.
from .lib import *
from .src import *
# =========================================================================
# Events
# =========================================================================
def plugin_loaded():
core.on_plugin_loaded()
def plugin_unloaded():
core.on_plugin_unloaded()
if debugging:
print(f'{this_module_name} <<<')
Then in each __init__.py file for each Subpackage:
""" lib/__init__.py """
from ..procomment import reload
from .debug import IntFlag, DebugBits, is_debugging
debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
print(f'{__package__} >>> module execution')
reload(__package__, ('debug', 'utils'))
from . import utils
__all__ = [
'debug',
'utils'
]
if debugging:
print(f'{__package__} <<<')
""" src/__init__.py """
from ..procomment import reload
from ..lib.debug import IntFlag, DebugBits, is_debugging
debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
print(f'{__package__} >>> module execution')
reload(__package__, ('core', 'contexts', 'func_parser'))
reload(__package__ + '.commands') # Recurse into .commands/ subpackage.
from . import core
from .contexts import *
from .commands import *
__all__ = [
'core',
'func_parser',
# events/contexts
"ProCommentContextEventListener",
# commands/*
"ProCommentInsertDatestampCommand",
"ProCommentInsertTimestampCommand",
'ProCommentInsertCommentCommand',
]
if debugging:
print(f'{__package__} <<<')
""" src/commands/__init__.py """
from ...procomment import reload
from ...lib.debug import IntFlag, DebugBits, is_debugging
debugging = is_debugging(DebugBits.IMPORTING)
if debugging:
print(f' {__package__} >>> module execution')
reload(__package__, ('insert_datestamp', 'insert_timestamp', 'insert_comment'))
from .insert_comment import ProCommentInsertCommentCommand
from .insert_datestamp import ProCommentInsertDatestampCommand
from .insert_timestamp import ProCommentInsertTimestampCommand
__all__ = [
# Comment Commands
'ProCommentInsertCommentCommand',
# Datestamp/Timestamp Commands
'ProCommentInsertDatestampCommand',
'ProCommentInsertTimestampCommand',
]
if debugging:
print(f' {__package__} <<<')
Note
The __package__ variable for each Package contains the “package name” as it
is known by the Python run time, specifically sys.modules and Python’s import
mechanism. For the source Package represented by src/commands/__init__.py,
for example, it is this string: ProComment.src.commands—which is exactly
what the reload() function needs in its first argument.
Note carefully that src/__init__.py explicitly recurses into the src/commands/
directory like this—after all its modules at that level were reloaded:
reload(__package__ + '.commands') # Recurse into .commands/ subpackage.
This pattern then causes a “cascade” of all the modules being reloaded: when
procomment.py gets either loaded (for the first time), or reloaded (by Package
Control, or saved during development).
The end result of this pattern is:
When Sublime Text starts, all Subpackages get loaded via a cascade of
importstatements.When the
Package ControlPackage updates your Package and reloads the central Plugin (or when you save your central Plugin during development), all the modules in each Subpackage get reloaded via a cascade ofreload()calls.
If you would like to verify/trace that visually, you can see the cascade of importing
and reloading in the debug output (from the code above) into the Console Panel when
Sublime Text is first started. This is when the loading is mostly being
done by import statements:
reloading plugin ProComment.procomment
ProComment.procomment >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.lib' submodules=()
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.src' submodules=()
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.lib >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.lib' submodules=('debug', 'utils')
Reloading(ProComment.lib.debug)
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.lib <<<
ProComment.src >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.src' submodules=('core', 'contexts', 'func_parser')
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.src.commands' submodules=()
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.src.commands >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.src.commands' submodules=('insert_datestamp', 'insert_timestamp', 'insert_comment')
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.src.commands <<<
ProComment.src <<<
ProComment.procomment <<<
And this is the debug output each time procomment.py is saved during development,
or when the Package Control Package updates the package while Sublime Text
is running). This is when the loading is being done entirely by the cascade of calls
to the reload() function:
reloading plugin ProComment.procomment
ProComment.procomment >>> module execution
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.lib' submodules=()
Reloading(ProComment.lib)
reload(): >>> dotted_subpkg='ProComment.lib' submodules=('debug', 'utils')
Reloading(ProComment.lib.debug)
Reloading(ProComment.lib.utils)
reload(): <<<
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
reload(): >>> dotted_subpkg='ProComment.src' submodules=()
Reloading(ProComment.src)
ProComment.src >>> module execution
reload(): >>> dotted_subpkg='ProComment.src' submodules=('core', 'contexts', 'func_parser')
Reloading(ProComment.src.core)
Reloading(ProComment.src.contexts)
Reloading(ProComment.src.func_parser)
reload(): <<<
reload(): >>> dotted_subpkg='ProComment.src.commands' submodules=()
Reloading(ProComment.src.commands)
ProComment.src.commands >>> module execution
reload(): >>> dotted_subpkg='ProComment.src.commands' submodules=('insert_datestamp', 'insert_timestamp', 'insert_comment')
Reloading(ProComment.src.commands.insert_datestamp)
Reloading(ProComment.src.commands.insert_timestamp)
Reloading(ProComment.src.commands.insert_comment)
reload(): <<<
ProComment.src.commands <<<
reload(): <<<
ProComment.src <<<
reload(): <<<
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ProComment.procomment <<<
When you turn debugging output off, and all of the above happens silently and happily in the background.
Tip
Don’t have a module in a subpackage inherit a sibling module through a relative
path like this (example uses a module in the ./lib/ subpackage)
from ..lib inherit sibling_module
Reason: before the lib module is fully imported from a parent module, that module
will try to import the lib module again through the relative path, and this
causes the importing execution path to get very complicated very quickly.
Policy: ensure sibling modules import from each other without involving the containing subpackage again, e.g. by using a relative path like this instead.
from . inherit sibling_module
17.6. Further Reading
The more you know about Python’s Import System, the more you will understand some of the power of Sublime Text Packages and their facilities brought to bear by Python itself (e.g. their ability to use modular subpackages to cleverly manage Package complexity, and thereby make a Package more understandable, and thereby more maintainable).
See also: