Sublime Forum

How to show a simple progress indicator in sublime status_bar with long running command?

#1

Hey guys, could you recommend a good way to display a progress indicator from a long running task when running a WindowCommand?

Consider this code:

def dispatch_long_running_task(msg, long_running_task):
    def report(msg):
        sublime.status_message(msg)

    t = threading.Thread(target=long_running_task)
    t.start()

    report(f"{msg}")
    while t.is_alive():
        report(f"{msg}...")
        time.sleep(0.5)
        report(f"{msg}..")
        time.sleep(0.5)
        report(f"{msg}.")
        time.sleep(0.5)
    report("")

def dispatch_long_running_task2(msg, long_running_task):
    def report(msg):
        sublime.status_message(msg)

    t = threading.Thread(target=long_running_task)
    t.start()

    report(f"{msg}")
    t.join()
    report("")

class FooCommand(sublime_plugin.WindowCommand):
    def run(self, **kwargs):
        def callback():
            subprocess.run("ping www.google.es", shell=True)

        dispatch_long_running_task("Pinging", callback)
        print("Job completed...")

The idea is simple, I just want the typical 3 dots looping but for some reason the SublimeText status bar won’t be updated as intended, I’m trying to figure out why… :confused:

Thanks in advance

0 Likes

#2

If you are fine with adding a dependency, then sublime_lib seems to have something for that:
https://sublimetext.github.io/sublime_lib/#activity-indicator

I haven’t tried it myself, though.

1 Like

#3

Interesting… I wasn’t aware of that dependency. I’ve just tried adding that sublime_lib to my package’s dependencies.json and then I just tried:

import sublime
import threading
import subprocess
import sublime_plugin
from sublime_lib.st3.sublime_lib.activity_indicator import ActivityIndicator

class FooCommand(sublime_plugin.WindowCommand):
    def run(self, **kwargs):
        def callback():
            subprocess.run("ping www.google.es", shell=True)

        with ActivityIndicator(self.window, 'Example Command'):
            callback()

        print("Job completed...")

# from sublime_lib import ActivityIndicator

# class FooCommand(sublime_plugin.WindowCommand):
#     def run(self, **kwargs):

#         t = threading.Thread(target=long_running_task)
#         t.start()

#         with ActivityIndicator(self.window, "Loading"):
#             t.join()

#         print("Job completed...")

Nothing happens when I spawn this command… I’m using ST4 so either this plugin won’t play nicely with this ST version or I’m using it the wrong way :confused: .

Speaking of which, after installing i don’t see how i can use from sublime_lib import ActivityIndicator … instead you can see this long import path.

0 Likes

#4

A simplified version of sublime_lib’s activity indicator can be found at …

It needs to be instantiated and executed from within a thread and updates status bar text periodically via sublime.set_timeout()

1 Like

#5

@deathaxe Thanks to your explanation and by looking at the source code I’ve managed to make it work… One thing I’m still missing is why I need to import it like this:

from sublime_lib.st3.sublime_lib.activity_indicator import ActivityIndicator

rather than:

from sublime_lib import ActivityIndicator

Like I’ve seen in some examples… To install it I’ve just added the dependency into my package’s dependencies.json

{
    "*": {
        "*": [
            "sublime_lib"
        ]
    }
}

Maybe not the right way to install it?

0 Likes

#6

Throwing another example in the mix.

1 Like

#7

@UltraInstinct05 Nice, yeah… both ProgressBar and ActivityIndicator should do a decent job, personally I favour the later as is just simpler code, in addition you could just tweak it like:

import sublime


class ProgressBar:
    def __init__(self, label, width=10):
        self.label = label
        self.width = width

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.stop()

    def start(self):
        self.done = False
        self.update()

    def stop(self):
        sublime.status_message("")
        self.done = True

    def update(self, status=0):
        if self.done:
            return
        status = status % (2 * self.width)
        before = min(status, (2 * self.width) - status)
        after = self.width - before
        sublime.status_message(f"{self.label} [{' ' * before}={' ' * after}]")
        sublime.set_timeout_async(lambda: self.update(status + 1), 100)

so then using it becomes a piece of cake:

    def callback():
        with ProgressBar("Running"):
            self._my_long_running_job()

    t = threading.Thread(target=callback)
    t.start()

That said ActivityIndicator uses from threading import Lock in order to guard against simulatenous access to it which is nice.

Of course, you could even generalize both classes to make a single one where you could customize different attributes such as the type of display, etc… But to me either of them are just good enough. Plus, it’s not like I’m spawning bazillion long running commands at the same time and the main point was the UI being responsive all the time.

0 Likes

#8

ProgressBar is just an activity indicator also. It just displays an = bouncing between a left and right border. All those examples have more or less the same origin and have been modified meet personal needs or preferences.

ActivityIndicator used the same bouncing = sign some commits ago. Just changed it to reduce wasting expensive horizontal space in status bar.

from sublime_lib import ActivityIndicator works on my machine.

0 Likes