.. include:: /include/substitutions.txt .. include:: /include/external_links.txt .. include:: /include/custom_roles.txt .. _plugins: ******* Plugins ******* Plugins are augmentations to Sublime |nbsp| Text implemented in Python. Plugins are used to add: - :ref:`Commands` (:term:`actions `) and - event handlers (:term:`reactions `) to the Sublime |nbsp| 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 :doc:`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 :ref:`managing package complexity`. .. _creating new commands: Creating New Commands ********************* New Commands are created by creating classes inside :term:`Plugin` or :term:`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 |nbsp| Text, so you can override Sublime |nbsp| 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 :term:`Command` you create will need to have a ``run()`` method defined. .. note:: If your Python code is failing to compile, Sublime |nbsp| 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. .. code-block:: python 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). .. code-block:: python self.view.add_regions("test", rgn_list, wrong_name="region.orangish", "bookmark", flags=sublime.DRAW_EMPTY | sublime.DRAW_NO_FILL) 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. 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: .. code-block:: python 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')`` Text Commands ************* When you are executing a :term:`Command` (e.g. via a menu item or key combination) intended to use or change the contents of the text :term:`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: .. code-block:: python 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 :term:`View` that was in focus when your :term:`Command` was executed. Reason: each :term:`View` carries with it an instantiation of each available Command, and each Command so instantiated knows the View it is attached to. 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. - :term:`Point` - :term:`Region` - :term:`Selection` - :term:`View` What Happens Inside the ``run()`` Method ======================================== Within a Text Command's ``run()`` method: - the :term:`TextCommand` object is 1st argument, usually ``self``, - the :term:`View`'s :term:`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 (:term:`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 :term:`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 :term:`Point` where the :term:`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 :term:`caret` is. (If the Command is being executed from a Menu item, the "context" filter available in :ref:`Key Bindings` may not be available in the menu, so you can still find out programmatically about the nature of the position of the :term:`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 :term:`View`: ``chg_count = self.view.change_count()`` 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. Change Position of :term:`Caret` -------------------------------- A retrieved :term:`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 :term:`caret` in the :term:`View`. However, performing edits through the view DOES change where the :term:`caret` is, and its new location can be retrieved by: .. code-block:: python 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 :term:`caret` and setting its position: .. code-block:: python 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. Rows and Columns ---------------- If a row or column is important to your :term:`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``. Finding New Region(s) --------------------- You can search the Buffer using the following *very* powerful View methods. 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: .. code-block:: py 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)`` view.find() ~~~~~~~~~~~ .. parsed-literal:: view.find( pattern : str, start_pt: :term:`Point`, flags : int = `sublime.FindFlags`.NONE ) -> :term:`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 :term:`Region` matching the provided pattern. view.find_all() ~~~~~~~~~~~~~~~ .. parsed-literal:: view.find_all( pattern : str, flags : int = `sublime.FindFlags`_.NONE, fmt : Optional[str] = None, extractions: Optional[list[str]] = None, within : Optional[Union[:term:`Region`, list[sublime.\ :term:`Region`]]] = None ) -> list[sublime.\ :term:`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.\ :term:`Region` or list[sublime.\ :term:`Region`]. When not ``None``, searching is limited to within the provided region(s). Returns: All (non-overlapping) :term:`Regions ` matching the pattern. view.find_by_selector() ~~~~~~~~~~~~~~~~~~~~~~~ .. parsed-literal:: find_by_selector( selector: str ) -> list[sublime.\ :term:`Region`] Find all :term:`Regions ` in the :term:`Buffer` matching the given :term:`selector`. Returns: The list of matched :term:`Regions `. Running Other Commands ---------------------- You can run other commands by: ``self.view.run_command(cmd_name[, {args}])``. 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: - `Boost Perl Regex Syntax`_ - `Boost Replace String Syntax`_ - `RegexBuddy Replace Reference`_ (list "Boost" using drop-down so you can see which behaviors belong to the Boost library) Adding Python Packages ********************** Sometimes a Plugin you are developing might need to import a Python package that is not already in the installed environment. Further Reading =============== https://forum.sublimetext.com/t/depending-on-an-extra-module-in-a-plugin/917 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. .. include:: /include/basic_plugin.py