Sublime Forum

Callback for "exec" builtin sublime window command

#1

There’s a builtin “exec” window command which is quite handy. It can run a shell command asynchronously and displays the output in a panel. However I’d like to run more code once the “exec” command has completed. Essentially need a callback. Is it possible to do so and how?

0 Likes

#2

The built-in command does not provide that sort of functionality out of the box, but it is still possible if you modify how it works. Using PackageResourceViewer you can open Default/exec.py, which is the file that implements the exec command to see what it’s doing.

The command starts running at the run method and finishes up by executing the finish method, which captures the final output for the panel, among other things. So you could capture the callback as a parameter to the run method (it then becomes an argument to the exec command) and invoke it in finish, or some such.

Directly modifying the command in this manner is possible, but your override will take precedence over the built in version, for all code that executes a task (e.g. build systems) and not just what you’re working on. Further, when Sublime updates the exec command to fix bugs or add new features, your override will mask them.

So all things considered, it’s probably best to either implement your own command by duplicating the code from exec.py and giving the class a new name, or use the code there as a model for how to create your own custom command.

1 Like

#3

You can subclass the built-in ExecCommand class and patch it as appropriate. I’m on mobile so no details.

2 Likes

#4

@OdatNurd the PackageResourceViewer is very nice, thanks a lot.

Yeah, there’s a lot of code in ExecCommand. It derives from ProcessListener, which defines an event on_finished. I think the best course of action for me is to inherit from ExecCommand and override on_finished.

@FichteFoll How would I go about deriving from ExecCommand? Just doing

class MyCommand(ExecCommand):
    ...

doesn’t work. What imports do I need?

0 Likes

#5

I believe you want something like:

from Default.exec import ExecCommand
1 Like

#6

That works perfectly, thanks for the help!

0 Likes

#7

I’ve been trying to achieve the same thing as this, however, sublime enforces that the run command must be JSON serialisable (I believe). I assume this is a change since this post was originally made.

Could I ask what might be the suggested method to achieve this in the current version of sublime?

Thanks.

0 Likes

#8

Can you share what it is you’re trying to do that doesn’t work? The arguments to any command can’t be complex objects, if that’s what you mean by JSON Serializable, but that’s not a new thing.

0 Likes

#9

Hi @OdatNurd,

Yeah, maybe I miss understood. This is what I took as the suggested method above.

import sublime
import sublime_plugin

from Default.exec import ExecCommand


class ExecCallbackCommand(ExecCommand):

    def run(self, *args, on_complete=None, **kwargs):
        super().run(*args, **kwargs)
        self.on_complete = on_complete

    def finish(*args, **kwargs):
        super().finish(*args, **kwargs)
        if self.on_complete:
            on_complete()

And then call in a plugin, something like:

def callback():
    print("calledback")

self.window.run_command(
    'exec_callback',
    args={
        'cmd': ['ls', '.'],
        'working_dir': wd,
        'on_complete': callback,
    }
)

Maybe I misunderstood.

Thanks for the help and quick response.

0 Likes

#10

What I said above is not fully clear just in the context of this particular thread (at the time there were some others as well) so apologies for that.

The original suggestion above was to provide a modified exec command that takes extra arguments in the form of another command and the arguments for that command, and then use the API to run_command() that particular command.

Generally speaking though, subclassing ExecCommand is a better way to go, but you go about it differently than you’re trying above:

import sublime
import sublime_plugin

from Default.exec import ExecCommand


class MyExecCommand(ExecCommand):
    def finish(self, proc):
        super().finish(proc)

        # Your code here

        # If you want to know if the build succeeded or not, use the
        # exit code.
        exit_code = proc.exit_code()
        if exit_code == 0 or exit_code is None:
            print("Build suceeded")
        else:
            print("Build failed with code %d" % exit_code)

        # If you want to see what errors the command captured
        print(self.output_view.find_all_results())

        # The same as above, but each item also contains the error message
        print(self.output_view.find_all_results_with_text())

This plugin creates a new command named my_exec that is a drop in replacement for exec. In the base class, the finish() method is called when the external program finishes executing (this is where [Finished] is added to the output panel).

Here our subclass overrides that and calls into the super, and then you can take whatever next action you want afterwards. The example above shows how to determine if the command “suceeded” or not, and how to see the errors that were captured by the file_regex and line_regex you provide (if any).

When you’re done you have your own command to use in place of exec that does whatever you want.

0 Likes