Skip to content

Editor Plugins

Michael Schenk edited this page Oct 22, 2016 · 2 revisions

Plugins are simple to write. In a nutshell, you subscribe to key editor events and call gtm record <path/file> in response to those events.

The best way to learn is by example. Here's the Sublime plugin.

import sublime
import sublime_plugin
import sys
import time
import os
import subprocess
import re

gtm_settings = {}

def find_gtm_path():
    if sys.platform == 'win32':
        exe = 'gtm.exe'
        path_sep = ";"
        pf = os.path.join(os.environ.get("ProgramFiles", ""), "gtm")
        pfx86 = os.path.join(os.environ.get("ProgramFiles(x86)", ""), "gtm")
        default_path = pf + path_sep + pfx86
    else:
        exe = 'gtm'
        path_sep = ":"
        default_path = "/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin/"

    env_path = set(os.environ['PATH'].split(path_sep))
    paths = env_path.union(set(default_path.split(path_sep)))

    if os.path.isfile(exe):
        return exe
    else:
        for p in paths:
            f = os.path.join(p, exe)
            if os.path.isfile(f):
                return f

    return None

def plugin_loaded():
    global gtm_settings
    gtm_settings = sublime.load_settings('gtm.sublime-settings')
    gtm_settings.add_on_change('gtm_status_bar', set_status_bar)
    set_status_bar()

def set_status_bar():
    if gtm_settings.get('gtm_status_bar', True):
        GTM.status_option = '--status'
        print("Enabling reporting time in status bar")
    else:
        GTM.status_option = ''
        print("Disabling reporting time in status bar")

class GTM(sublime_plugin.EventListener):
    gtm_ver_req = '>= 1.1.0'

    update_interval = 30
    last_update = 0
    last_path = None
    status_option = ''

    no_gtm_err = ("GTM executable not found.\n\n"
                  "Install GTM and/or update your system path. "
                  "Make sure to restart Sublime after install.\n\n"
                  "See https://github.com/git-time-metric/gtm/blob/master/README.md")

    record_err = ("GTM error saving time.\n\n"
                  "Install GTM and/or update the system path. "
                  "Make sure to restart Sublime after install.\n\n"
                  "See https://github.com/git-time-metric/gtm/blob/master/README.md")

    ver_warn = ("GTM executable is out of date.\n\n"
                "The plug-in may not work properly. "
                "Please install the latest GTM version and restart Sublime.\n\n"
                "See https://github.com/git-time-metric/gtm/blob/master/README.md")

    gtm_path = find_gtm_path()

    if not gtm_path:
        sublime.error_message(no_gtm_err)
    else:
        p = subprocess.Popen('"{0}" verify "{1}"'.format(gtm_path, gtm_ver_req),
                             shell=True,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT)
        output = p.stdout.read()
        version_ok = 'true' in output.decode('utf-8')
        if not version_ok:
            sublime.error_message(ver_warn)

    def on_post_save_async(self, view):
        self.record(view, view.file_name())

    def on_modified_async(self, view):
        self.record(view, view.file_name())

    def on_selection_modified_async(self, view):
        self.record(view, view.file_name())

    def on_activated_async(self, view):
        self.record(view, view.file_name())

    def record(self, view, path):

        if GTM.gtm_path and path and (
            path != GTM.last_path or
            time.time() - GTM.last_update > GTM.update_interval ):

            GTM.last_update = time.time()
            GTM.last_path = path

            cmd = '"{0}" record {1} "{2}"'.format(GTM.gtm_path,
                                                  GTM.status_option,
                                                  path)

            try:
                cmd_output = subprocess.check_output(cmd, shell=True)
                if GTM.status_option:
                    view.set_status(
                        "gtm-statusbar",
                        GTM.format_status(cmd_output))
                else:
                    view.erase_status("gtm-statusbar")
            except subprocess.CalledProcessError as e:
                sublime.error_message(GTM.record_err)

    def format_status(t):
        return t.decode('utf-8').strip()

First thing to determine is what events to subscribe to for the editor. Typically you want to listen for saving, gain focus, keyboard and sometimes mouse events. The specific event names and when they are triggered will vary slightly by editor.

However you want to only call gtm record when necessary. If we are looking at the same file from the last event do nothing if it has been less than 30 seconds, otherwise call gtm record <path/file> and save the current path/file to compare to the next triggered event.

Keep in mind the gtm record command is fast and you shouldn't notice any delays.

Another concern when writing plug-ins is how you determine where the gtm executable is installed. Try using the logic in find_gtm_path() if possible.

Lastly let the user know when you can't find the gtm executable and that they may need to install gtm or update their path and where to go for further information.

That's it!