14. Plugins

Plugins are augmentations to Sublime Text implemented in Python. Plugins are used to add:

to the Sublime Text run-time environment.

In order to be considered a Plugin, a Python file has to be in the ROOT directory of a package (not inside a subdirectory), and it has to have an extension of .py.

Most of the remaining major sections of this documentation are about facilities that are available to both Plugins and Packages.

Note

There is a way to get a Plugin to use .py files that are in a subdirectory below the root of the Package. Many shipped and user-installed Packages demonstrate a clever way of doing this using the Python import system. This is covered in detail in Managing Package Complexity.

14.1. Creating New Commands

New Commands are created by creating classes inside Plugin or package code by creating a class that inherits from one of these classes:

  • sublime_plugin.TextCommand

  • sublime_plugin.WindowCommand

  • sublime_plugin.ApplicationCommand

The name of the Command is derived from the class name by:

  • lower-case the first letter of the class name

    • InsertTimestampCommand => insertTimestampCommand

  • replace remaining upper-case letters with ‘_’ + lower-case letter

    • insertTimestampCommand => insert_timestamp_command

  • if the resulting name ends with “_command”, then that portion is removed.

    • insert_timestamp_command => insert_timestamp

Thus insert_timestamp is the name of the Command created from the InsertTimestampCommand class name.

The name insert_timestamp exists in a single, global Command namespace within Sublime Text, so you can override Sublime Text Commands by creating code that gets loaded after the Command that is getting replaced.

It is a convention, not a requirement that the class names end with “...Command”. This practice makes it easier to find Commands (by searches) and recognize Commands with the eyes.

All Commands share the same namespace, so consider preventing name “collisions” by prefixing your Commands with the name or abbreviation of your package.

All Commands share a common set of methods.

A Command subclassed from sublime_plugin.TextCommand is the only way to get an Edit object. Each View object has exactly ONE instance of the Edit class. The self.view property indicates the View from which the Command was invoked. Thus, ONE Command object is created for each View, each of which has a view property referencing that View object.

Like TextCommand objects, sublime_plugin.WindowCommand objects know what window under which they are being executed by way of their window property.

There is only ever one instance of the Application object. All references to it refer to the same object. Instances of the sublime_plugin.ApplicationCommand class are used to do things that are not specific to a View or Window, e.g. changing global application settings such as font size.

The Command is “carried out” by calling its run() method, so at minimum, each Command you create will need to have a run() method defined.

Note

If your Python code is failing to compile, Sublime Text will not load it. This is also true if you pass erroneous arguments to certain functions. If a menu item is “attached” to your command, it will be disabled (grayed out).

Example:

This code in an example Plugin Text Command compiles and the menu items that are “attached” to it are enabled.

self.view.add_regions("test", rgn_list, "region.orangish", "bookmark",
    flags=sublime.DRAW_EMPTY | sublime.DRAW_NO_FILL)

But while this code compiles, it is assigning a string to an unrecognized keyword argument, and because of this, the the menu items that are “attached” to it are disabled (grayed out).

self.view.add_regions("test", rgn_list, wrong_name="region.orangish", "bookmark",
    flags=sublime.DRAW_EMPTY | sublime.DRAW_NO_FILL)

14.2. Other Command Methods

See https://www.sublimetext.com/docs/api_reference.html#sublime_plugin.TextCommand and don’t forget to also click on the Command link to show the inherited methods.

14.2.1. Inherited Methods

Any inherited method can be overridden, and are meant to be when their default behavior needs to be changed.

One example of this is the is_enabled() method which returns a Boolean indicating whether the command should be run-able at this time. An example of such an override definitions is:

def is_enabled(self) -> bool:
    scope = self.view.scope_name(self.view.sel()[-1].b).rstrip()
    print(f'Scope=[{scope}]')
    return ('source' in scope and 'comment' in scope)

Note that this overrides that method in the Command class itself. Observe that the method determines if “source” and “comment” are in the scope of the cursor and if so, then it is enabled. Otherwise not. So if you have this command attached to a Menu Item, for instance, if your cursor is not in a comment in a source file, that menu item will be disabled. If it is, then you can run it.

is_enabled() has the capability to block or allow running from:

  • menu

  • keymap

  • running through run_command('your_cmd_name')

14.3. Text Commands

When you are executing a Command (e.g. via a menu item or key combination) intended to use or change the contents of the text Buffer in some way, the only way to do so is with a Text Command. You create a Text Command by creating a class that inherits from sublime_plugin.TextCommand. At minimum, it will need a run() method that accepts self and edit as arguments. Example:

import sublime
import sublime_plugin

class InsertTimestampCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        pass

Once your run() method is in place, you will need to understand the tools available to you within that method.

Firstly, self.view will always be attached to the View that was in focus when your Command was executed. Reason: each View carries with it an instantiation of each available Command, and each Command so instantiated knows the View it is attached to.

14.3.1. Some Data Types You Need to Know About

The following are some data types you will need to know well because you will need to use them to access and modify text in Text Commands.

14.3.2. What Happens Inside the run() Method

Within a Text Command’s run() method:

  • the TextCommand object is 1st argument, usually self,

  • the View’s Edit object is the 2nd argument usually edit, and

  • the following are tools you will likely use often:

    • The applicable View is self.view.

    • The list of Selection Regions is self.view.selection...

    • ...though it is probably meant to be retrieved by rgn_list = self.view.sel() because self.view.selection is not in the documentation. Note: the order of this list is always from the top of the file down, and the regions never overlap.

    • The first Selection Region (caret if it is empty) is rgn_list[0]

    • Iterate through all Selection Regions by for rgn in rgn_list:

    • selected_text = self.view.substr(rgn)

    • Regions carry 2 Point objects: rgn.a and rgn.b.

    • Let’s say we have executed pt = rgn.a.

    • char_at_pt = self.view.substr(pt)

    • Is Region empty? rgn.empty()

    • The smaller of a and b is rgn.begin()

    • The larger of a and b is rgn.end()

    • The Point where the caret is always: rgn.b, even when rgn.b < rgn.a.

    • Number of characters represented: rgn.size()

    • Determine context of a Point: scope_name = self.view.scope_name(pt) which can tell whether an edit may be appropriate for where the caret is. (If the Command is being executed from a Menu item, the “context” filter available in Key Bindings may not be available in the menu, so you can still find out programmatically about the nature of the position of the caret and thus make the judgement programmatically.)

    • Does a Point match a context selector name? ok_to_edit = self.view.match_selector(pt, scope_name)

    • Does Region contain Point? rgn.contains(pt)

    • file_name = self.view.file_name() (None if it doesn’t exist on disk.)

    • Change target file: self.view.retarget(new_file_name)

    • Find Buffer’s encoding: enc_name = self.view.encoding()

    • Change Buffer’s encoding: self.view.set_encoding(enc_name)

    • Number of chars in file: char_count = self.view.size()

    • View’s private settings: private_settings = self.view.settings() (Changing these only changes the settings for that View.)

    • Get all lines involved: lines = self.view.lines(rgn)

    • Get line where a point is: line = self.view.line(rgn|pt)

    • Same but with newline: line = self.view.full_line(rgn|pt)

    • Word or phrase: word = self.view.word(rgn|pt)

    • Attributes of a Point: pt_classif = self.view.classify(pt)

    • Region that is visible in View: vis_rgn = self.view.visible_region()

    • Is Region visible in View? visible = vis_rgn.contains(rgn.a)

    • Number of changes made in this View: chg_count = self.view.change_count()

14.3.2.1. Editing the Buffer

Ways to edit the Buffer:

  • Delete any selected text by self.view.erase(edit, rgn)

  • Replace any selected text by self.view.replace(edit, rgn, new_text)

  • char_count_inserted = self.view.insert(edit, pt, new_text)

You can edit any part of the Buffer by using existing Region and Point objects or creating new ones, and doing things with them.

14.3.2.2. Change Position of Caret

A retrieved Region (e.g. via rgn = rgn_list[0] is ONLY A COPY of the current selection Region, so changing its values DOES NOT change the caret in the View. However, performing edits through the view DOES change where the caret is, and its new location can be retrieved by:

rgn_list = self.view.sel()
new_rgn = rgn_list[0]
new_caret_pt = new_rgn.b

Importantly: rgn_list itself has methods by which you can set any number of new regions in it. But to merely return to having 1 caret and setting its position:

rgn_list = self.view.sel()
saved_pos = rgn_list[0]
# some editing here
rgn_list.clear()
rgn_list.add(sublime.Region(saved_pos))

rgn_list.add_all(new_list_of_regions) can be used to establish a set of new regions with one call.

14.3.2.3. Rows and Columns

If a row or column is important to your TextCommand (both are zero-based):

  • row, col = self.view.rowcol(pt)

  • row, col = self.view.rowcol_utf8(pt)

  • row, col = self.view.rowcol_utf16(pt)

  • pt = self.view.text_point(row, col[, clamp_column=True])

  • pt = self.view.text_point_utf8(row, col[, clamp_column=True])

  • pt = self.view.text_point_utf16(row, col[, clamp_column=True])

where col is the number of Unicode characters to advance past the beginning of row, and clamp_column is whether col should be restricted to valid values for the given row.

14.3.2.4. Finding New Region(s)

You can search the Buffer using the following very powerful View methods.

14.3.2.4.1. sublime.FindFlags

Two of the API functions below use OR-ed FindFlags bits, which can be accessed of either of the first two Python expressions:

sublime.LITERAL    == sublime.FindFlags.NONE       == 0x00
sublime.LITERAL    == sublime.FindFlags.LITERAL    == 0x01 (literal == not regex)
sublime.IGNORECASE == sublime.FindFlags.IGNORECASE == 0x02
sublime.WHOLEWORD  == sublime.FindFlags.WHOLEWORD  == 0x04
sublime.REVERSE    == sublime.FindFlags.REVERSE    == 0x08 (search backwards)
sublime.WRAP       == sublime.FindFlags.WRAP       == 0x10 (continue at top after EOF)

Here is a compact list of API functions covered below:

  • first_rgn = view.find(regex_str, start_pt, ored_find_flags), and

  • rgn_list  = view.find_all(regex_str, ored_find_flags, fmt, extractions, within)

  • rgn_list  = view.find_by_selector(sel_name)

14.3.2.4.2. view.find()
view.find(
        pattern : str,
        start_pt: Point,
        flags   : int = sublime.FindFlags.NONE
        ) -> Region

Parameters:

pattern:

The regex or literal pattern to search by.

start_pt:

The Point to start searching from.

flags:

Controls various behaviors of find. See sublime.FindFlags.

Returns:

The first Region matching the provided pattern.

14.3.2.4.3. view.find_all()
view.find_all(
        pattern    : str,
        flags      : int = sublime.FindFlags.NONE,
        fmt        : Optional[str] = None,
        extractions: Optional[list[str]] = None,
        within     : Optional[Union[Region, list[sublime.Region]]] = None
        ) -> list[sublime.Region]

Find all occurrences of pattern in View Buffer based on flags within the range(s) specified by within if specified.

If the fmt string and extractions list (normally empty) are both provided, a Regex-find-and-replace operation is done with the fmt string and each formatted result is appended to the extractions list. The fmt argument does nothing unless extractions is also provided.

Parameters:

pattern:

The regex or literal pattern to search by. If FindFlags.LITERAL is not part of flags then it is a Regex.

flags:

Controls various behaviors of find. See sublime.FindFlags.

fmt:

When not None, this is a Regex Replacement String with special syntax allowing you to use parts of what was found in the formatted output. All matches in the extractions list will be formatted with the provided format string. Numbered and named backreferences to the parts of the matched string as $& (whole matched string), $1 (1st capture group), $2 (2nd capture group), .. ${99}, etc. are fully supported.

See Boost Replace String Syntax for complete details. (Numbered backreferences like \1, \2, etc. still work, but have been deprecated.)

extractions:

An optionally provided (normally empty) list to place the formatted contents of the find results into. This list is only populated (appended to) if the fmt string was also supplied.

within:

(4181) either sublime.Region or list[sublime.Region]. When not None, searching is limited to within the provided region(s).

Returns:

All (non-overlapping) Regions matching the pattern.

14.3.2.4.4. view.find_by_selector()
find_by_selector(
        selector: str
        ) -> list[sublime.Region]

Find all Regions in the Buffer matching the given selector.

Returns:

The list of matched Regions.

14.3.2.5. Running Other Commands

You can run other commands by: self.view.run_command(cmd_name[, {args}]).

14.3.3. Further Reading

There are hundreds of other things you can do, which are beyond the scope of this document, but you can learn a lot more by visiting https://www.sublimetext.com/docs/api_reference.html#sublime_plugin.TextCommand

Also, the following list offers a comprehensive Regex reference:

14.4. Adding Python Packages

Sometimes a Plugin you are developing might need to import a Python package that is not already in the installed environment.

14.4.1. Further Reading

https://forum.sublimetext.com/t/depending-on-an-extra-module-in-a-plugin/917

14.5. Example Basic Plugin with Functionality for Package Settings

Note: this example is 95% plumbing code. The meat of a plugin is in commands, and for the sake of completeness, one example command is shown at the end.

import sublime
import sublime_plugin


# =========================================================================
# Configuration
# =========================================================================


# Use name of parent directory as `_cfg_pkg_name`.
parent_dir, _ = os.path.split(os.path.realpath(__file__))
_, _cfg_pkg_name = os.path.split(parent_dir)
_cfg_pkg_settings_file = _cfg_pkg_name + '.sublime-settings'
del parent_dir, _

# Track on-settings-changed listener.
_cfg_on_settings_chgd_listener_id = '_bp_settings_changed_tag'

# Setting Names
_cfg_stg_name__font_size = 'fond_size'
_cfg_stg_name__debugging = 'debugging'

# =========================================================================
# Plugin Definitions
# =========================================================================


def bp_setting(key):
    """
    Get a package setting from a cached settings object.
    "bp" stands for "Basic Plugin".  You can any prefix, or none at all.
    This function expects the following objects to already exist:

    - ``bp_setting.obj``      a ``sublime.Settings`` object (looks like a dictionary)
    - ``bp_setting.default``  a dictionary object with named default values

    :param key:  name of setting whose value will be returned

    """
    assert bp_setting.default is not None, '`bp_setting.default` must exist before calling `bp_setting()`.'
    assert bp_setting.obj is not None, '`bp_setting.obj` must exist before calling `bp_setting()`.'
    default = bp_setting.default.get(key, None)
    return bp_setting.obj.get(key, default)


"""
Establish default settings to be used in case the end user deletes any settings.
``bp_setting.default`` dictionary object is needed by ``bp_setting()``
"""
bp_setting.default = {
    _cfg_stg_name__font_size: 10,
    _cfg_stg_name__debugging: False
}


def _load_cached_settings():
    """
    Load the settings file; every time you invoke ``sublime.load_settings()`` with
    the same settings file, you get the same object back.  This object can be saved
    to provide a settings cache for speedier access during package operation, or
    it can be reloaded as you need.
    """
    pc_setting.obj = sublime.load_settings(_cfg_pkg_settings_file)


def _on_pkg_settings_change():
    """
    Do now with the settings what you would do with them whenever the package settings
    file changes.
    """
    _load_cached_settings()


def _on_pkg_settings_change():
    """
    A function that does something with settings; code here would load the
    settings file, or otherwise access some sort of globally cached one, and then
    do something with said setting. Here we are just printing the value of the
    setting out.

    NOTE:

    As a settings listener target, this gets invoked any time any setting
    changes, so part of your logic here (if it matters) is to check and see
    if the setting you care about is the one that changed. Usually does not
    matter though.
    """
    _load_cached_settings()
    print(f"The configured font size is {bp_setting(_cfg_stg_name__font_size)}")


def plugin_loaded():
    """
    Event Hook:

    This gets executed by Sublime Text once after your plugin is loaded and the API is
    fully ready to go, as well as every time your plugin is saved (even when there
    are no changes).  This is a good place to initialize things like a settings file
    listener, for example. */
    """
    global _cfg_pkg_name
    _establish_default_settings()
    _load_cached_settings()

    # Indicate an interest in when settings change; the key here is important;
    # give it something unique to you (maybe namespace with your plugin name);
    # this is needed to remove the listener.
    bp_setting.obj.add_on_change(_cfg_on_settings_chgd_listener_id, _on_pkg_settings_change)

    # Announce if `debugging`.
    debugging = bp_setting(_cfg_stg_name__debugging)
    if debugging:
        print(f'{_cfg_pkg_name} loaded.')


def plugin_unloaded():
    """
    Event Hook:

    This gets executed by Sublime Text when your plugin is unloaded, which also
    happens when you save it and it reloads, and also when the package that it's in
    is disabled.  Here is a good place to make sure that you don't leak the settings
    listener.
    """
    # Get the settings object and then clear the listener away; this is done via
    # the API and the key that you used to register the listener.
    if bp_setting.obj:
        bp_setting.obj.clear_on_change(_cfg_on_settings_chgd_listener_id)

    # Announce if `debugging`.
    debugging = bp_setting(_cfg_stg_name__debugging)
    if debugging:
        print(f'{_cfg_pkg_name} unloaded.')


class MessageBoxCommand(sublime_plugin.WindowCommand):
    """
    This is only an example.  This is where the meat of the plugin would
    be implemented:  in commands.  Create commands that inherit from
    ``sublime_plugin.TextCommand`` to interact with the View and its Buffer.
    """
    def run(self, msg):
        sublime.message_dialog(msg)