Sublime Forum

Running an ApplicationCommand via 'subl'?

#1

I’ve written an ApplicationCommand that I want to run from the shell prompt. That is, with “subl --command my_command”. I’ve written the plugin and it works great – if Sublime Text is already running. Otherwise Sublime Text gets started but the command doesn’t get run, probably because the --command parm is processed before the plugins are done loading. Is there anything I can do to launch Sublime Text and run a command after the plugins have loaded? I’d like to avoid doing something cheesy like “subl; sleep 2; subl --command my_command”. (Sublime Text 3 and OSX El Capitan, fwiw.)

3 Likes

#2

I’ve run into a similar issue with some of my plugins during initialization. Hopefully someone knows a workaround!

0 Likes

#3

I’ve looked around and decided that there’s no good solution, so I wrote myself one that’s better than nothing. This Python file can be used as a script on its own or as a library. It starts Sublime, waits for the plugin_host process to become ready, then runs a command. Unfortunately there’s really no good way to determine when the plugin_host has finished initializing so I just have a blind time delay. You may need to adjust this for your system.

THIS IS NOT A SUBLIME TEXT PLUGIN! It’s just a plain ol’ Python 2 script that runs outside the context of Sublime Text. (It has to be outside of Sublime, since its whole reason for existing is to solve the problem of the Sublime Text plugin_host not being ready to run scripts yet…)

Written for Sublime Text 3 build 3103 on OSX El Capitan. Hopefully it works cross-platform but I make no guarantees.

#! /usr/bin/env python
################################################################################
##
##  sublimate.py
##
################################################################################
import psutil
import subprocess
import time
import json
import sys
import optparse

class TimeoutError(Exception): pass

################################################################################
##  get_sublime
################################################################################
def get_sublime():
    """
    Get the Process object for Sublime Text.
    """
    for proc in psutil.process_iter():
        try:
            if proc.name() == 'Sublime Text':
                return proc
        except psutil.NoSuchProcess:
            pass
    return None

################################################################################
##  get_plugin_host
################################################################################
def get_plugin_host():
    """
    Get the Process object for Sublime Text's plugin_helper
    """
    for proc in psutil.process_iter():
        try:
            if proc.name() == 'plugin_host' and 'Sublime Text' in proc.exe():
                return proc
        except psutil.NoSuchProcess:
            pass
    return None

################################################################################
##  sublime_is_started
################################################################################
def sublime_is_started():
    """
    Return True if Sublime Text is running, otherwise False.
    """
    return bool(get_sublime())

################################################################################
##  plugin_host_is_ready
################################################################################
def plugin_host_is_ready(min_runtime=1.0):
    """
    Return True if the plugin_host has been running for at least
    min_runtime seconds, otherwise False.
    """
    proc = get_plugin_host()
    if proc:
        if time.time() - proc.create_time() >= min_runtime:
            return True
    return False

################################################################################
##  wait_for_plugin_host
################################################################################
def wait_for_plugin_host(min_runtime=1.0, timeout=5.0):
    """
    Wait for the plugin_host to start and run for at least min_runtime
    seconds.  Raise TimeoutError if it has not started within timeout
    seconds.
    """
    beg = time.time()
    while time.time() - beg < timeout:
        if plugin_host_is_ready(min_runtime):
            return
    raise TimeoutError("Sublime Text plugin_host did not start")

################################################################################
##  start
################################################################################
def start(cmd=None, **kwargs):
    """
    Start Sublime Text and wait for the plugin_host.  If the cmd arg is not
    None, run the given Sublime Text command with args as given in the
    kwargs argument.

    As a library module you can simply pass arguments in as kwargs. The
    start() function will convert them to JSON for you.

        import sublimate
        sublimate.start('open_file', file='/tmp/foobar.txt', contents='Lorem ipsum')
    """
    if not sublime_is_started():
        subprocess.call(['subl'])
    wait_for_plugin_host()
    if cmd:
        if kwargs:
            args = '%s %s'%(cmd, json.dumps(kwargs))
        else:
            args = cmd
        cmd = ['subl','--command',args]
    else:
        cmd = ['subl']
    subprocess.call(cmd)

################################################################################
##  __main__
################################################################################
if __name__ == '__main__':
    usage = "%prog [command [json_args]]"
    descr = """
Start Sublime Text and wait to be sure the plugin_host process is ready
before optionally running a plugin command with JSON-encoded arguments.
By itself it's a good way to ensure that the plugin_host is ready before
running 'subl' with a more complex set of command-line options.
""".strip()
    parser = optparse.OptionParser(usage=usage, description=descr)

    title = 'Example 1'
    descr = """
sublimate.py insert '{"characters":"abcdefg"}'
""".strip()
    group = optparse.OptionGroup(parser, title=title, description=descr)
    parser.add_option_group(group)

    title = 'Example 2'
    descr = """
sublimate.py && subl --new-window --add /tmp --command 'open_file {"file":"/tmp/foobar.txt","contents":"Lorem ipsum"}'
""".strip()
    group = optparse.OptionGroup(parser, title=title, description=descr)
    parser.add_option_group(group)

    (opts, args) = parser.parse_args()

    start(' '.join(sys.argv[1:]))
1 Like

#4

You could combine that with a Python plugin to have absolute certainty on when plugins have been loaded. Saving the plugin in the User package should ensure that most (if not all) other plugins have been loaded before your script, so even ST firing the plugin_loaded hooks before your plugin has run shouldn’t matter (in which case it would be called for your script immediately after it has been loaded).

0 Likes