Sublime Forum

Run process in background

#1

Hey there,

I’m pretty sure someone has encountered this problem already, but unfortunately I can’t seem to find any related topics. Here’s my problem:

I’m writing a plugin that executes an external command. If executed from the terminal, this command yields several lines one at a time. I’d like to be able to launch my plugin so that these results are displayed in my “tests” panel on-line, but all I can get is all lines after the execution is completed along with other info lines I append to the console somewhere. What is worse, Sublime hangs while the process is in progress, which sucks big time. So the questions are:

  • How do I execute the process in the background so that Sublime doesn’t hang?
  • How do I output results line by line as soon as they are produced?

Thanks in advance!

Here’s my code structure in case you need it.

class MyCommand(sublime_plugin.TextCommand):
    def run(self, edit, **kwargs):
        self.show_tests_panel()
        if len(self.view.file_name()) > 0:
            # some magic ... 
            # my guess is I need threading here, 
            # but I have no experience with that
            self.some_function()

    def some_function():
        # blah blah
        # this part below should probably be rewritten
        p = subprocess.Popen(command, stdout=subprocess.PIPE)
        out, _ = p.communicate()
        self.append_data(out)

    def append_data(self, data):
        self.output_view.set_read_only(False)
        edit = self.output_view.begin_edit()
        self.output_view.insert(edit, self.output_view.size(), data)
        self.output_view.end_edit(edit)
        self.output_view.set_read_only(True)

    def show_tests_panel(self):
        if not hasattr(self, 'output_view'):
            self.output_view = self.view.window().
                                   get_output_panel("tests")
        self.clear_test_view()
        self.view.window().run_command("show_panel", 
                                       {"panel": "output.tests"})

    def clear_test_view(self):
        self.output_view.set_read_only(False)
        edit = self.output_view.begin_edit()
        self.output_view.erase(edit, 
                               sublime.Region(0, self.output_view.size()))
        self.output_view.end_edit(edit)
        self.output_view.set_read_only(True)  
1 Like

#2

I’m dealing with the same issue right now. I haven’t gotten it to work exactly how I want it to yet, but I’ve made some minor progress as shown in How to execute a function immediately?

Image
 


 
Another thread that might contain useful information for you is How to run part of my plugin in the second thread properly?
 


 
Definitely post back if you figure anything out!

0 Likes

#3

Thank you @fico! I’m writing for Sublime Text 2, so no set_async_timeout for me. But I managed to make my plugin work (thanks to you and this article), here’s how:

class MyCommand(sublime_plugin.TextCommand):
    def run(self):
        self.show_tests_panel()
        if len(self.view.file_name()) > 0:
            # do things...
            self.thread = MyWorker()
            self.thread.start()
            self.handle_thread()

    def handle_thread(self):
        if self.thread.is_alive():
            while not self.thread.queue.empty():
                line = self.thread.queue.get()
                self.append_data(line)
            sublime.set_timeout(self.handle_thread, 100)
        while not self.thread.queue.empty():
            line = self.thread.queue.get()
            self.append_data(line)
        if self.thread.result == True:
            self.append_data('\nFinished successfully.\n')
        elif self.thread.result == False:
            self.append_data('\nFinished with an error.\n')
            self.append_data(thread.error)

    def append_data(self, data):
        self.output_view.set_read_only(False)
        edit = self.output_view.begin_edit()
        self.output_view.insert(edit, self.output_view.size(), data)
        self.output_view.end_edit(edit)
        self.output_view.set_read_only(True)

    def show_tests_panel(self):
        if not hasattr(self, 'output_view'):
            self.output_view = self.view.window().
                                   get_output_panel("tests")
        self.clear_test_view()
        self.view.window().run_command("show_panel", 
                                       {"panel": "output.tests"})

    def clear_test_view(self):
        self.output_view.set_read_only(False)
        edit = self.output_view.begin_edit()
        self.output_view.erase(edit, 
                               sublime.Region(0, 
                                              self.output_view.size()))
        self.output_view.end_edit(edit)
        self.output_view.set_read_only(True)  


class MyWorker(threading.Thread):
    def __init__(self):
        self.result = None
        self.queue = Queue.Queue()
        threading.Thread.__init__(self)

    def run(self):
        try:
            self.queue.put('Something started\n')

            def execute():
                popen = subprocess.Popen(command, stdout=subprocess.PIPE)
                stdout_lines = iter(popen.stdout.readline, "")
                for stdout_line in stdout_lines:
                    yield stdout_line

                popen.stdout.close()

            for line in execute():
                self.queue.put(line)

            self.result = True
        except Exception as e:
            self.result = False
            self.error = str(e)

So now everything processed in MyWorker is performed in the background, and I get results in self.output_view as soon as they appear (well, almost).

2 Likes

#4

You can have a look at the ExecuteCommand in Default package:
Install PackageResourceViewer
Extract package (follow readme instructions)
Open packages folder - “Preferences -> Browse Packages” and locate the source

Or take a look at the extracted code https://gist.github.com/astrauka/c0c0c4269e5d94ca6b7090c84fa48cf6

0 Likes