From 9f7d84ef327f9f38d5b8486053e0e7c05f389e77 Mon Sep 17 00:00:00 2001 From: deathaxe Date: Fri, 12 Jan 2024 19:14:14 +0100 Subject: [PATCH] Display activity indicator while rebuilding tags --- plugins/activity_indicator.py | 123 ++++++++++++++++++++++++++++++++++ plugins/cmds.py | 76 ++++++++++----------- 2 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 plugins/activity_indicator.py diff --git a/plugins/activity_indicator.py b/plugins/activity_indicator.py new file mode 100644 index 0000000..62fd1c2 --- /dev/null +++ b/plugins/activity_indicator.py @@ -0,0 +1,123 @@ +import sublime +from threading import RLock + + +class ActivityIndicator: + """ + An animated text-based indicator to show that some activity is in progress. + + The `target` argument should be a :class:`sublime.View` or :class:`sublime.Window`. + The indicator will be shown in the status bar of that view or window. + If `label` is provided, then it will be shown next to the animation. + + :class:`ActivityIndicator` can be used as a context manager. + """ + + def __init__(self, label=None): + self.label = label + self.interval = 120 + self._lock = RLock() + self._running = False + self._ticks = 0 + self._view = None + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.stop() + + def clear(self): + if self._view: + self._view.erase_status("_ctags_activity") + self._view = None + + def start(self): + """ + Start displaying the indicator and animate it. + + :raise RuntimeError: if the indicator is already running. + """ + + with self._lock: + if self._running: + raise RuntimeError("Timer is already running") + self._running = True + self._ticks = 0 + self.update(self.render_indicator_text()) + sublime.set_timeout(self.tick, self.interval) + + def stop(self): + """ + Stop displaying the indicator. + + If the indicator is not running, do nothing. + """ + + with self._lock: + if self._running: + self._running = False + self.clear() + + def finish(self, message): + """ + Stop the indicator and display a final status message + + :param message: + The final status message to display + """ + + def clear(): + with self._lock: + self.clear() + + if self._running: + with self._lock: + self._running = False + self.update(message) + + sublime.set_timeout(clear, 4000) + + def tick(self): + """ + Invoke status bar update with specified interval. + """ + + if self._running: + self._ticks += 1 + self.update(self.render_indicator_text()) + sublime.set_timeout(self.tick, self.interval) + + def update(self, text): + """ + Update activity indicator and label in status bar. + + :param text: + The text to display in the status bar + """ + + view = sublime.active_window().active_view() + if view and view != self._view: + if self._view: + self._view.erase_status("_ctags_activity") + self._view = view + if self._view: + self._view.set_status("_ctags_activity", text) + + def render_indicator_text(self): + """ + Render activity indicator and label. + + :returns: + The activity indicator string to display in the status bar + """ + + text = "⣷⣯⣟⡿⢿⣻⣽⣾"[self._ticks % 8] + if self.label: + text += " " + self.label + return text + + def set_label(self, label): + with self._lock: + self.label = label diff --git a/plugins/cmds.py b/plugins/cmds.py index 0036347..9f5eb6c 100644 --- a/plugins/cmds.py +++ b/plugins/cmds.py @@ -15,6 +15,8 @@ import sublime_plugin from sublime import status_message, error_message +from .activity_indicator import ActivityIndicator + from .ctags import ( FILENAME, PATH_ORDER, @@ -822,49 +824,43 @@ def build_ctags(self, paths, command, tag_file, recursive, opts): :returns: None """ + with ActivityIndicator("CTags: Rebuilding tags...") as progress: + for i, path in enumerate(paths, start=1): + if len(paths) > 1: + progress.update( + "CTags: Rebuilding tags [%d/%d]..." % (i, len(paths)) + ) - def tags_building(tag_file): - """Display 'Building CTags' message in all views""" - print(("Building CTags for %s: Please be patient" % tag_file)) - in_main( - lambda: status_message( - "Building CTags for {0}: Please be" " patient".format(tag_file) - ) - )() - - def tags_built(tag_file): - """Display 'Finished Building CTags' message in all views""" - print(("Finished building %s" % tag_file)) - in_main(lambda: status_message("Finished building {0}".format(tag_file)))() - in_main(lambda: tags_cache[os.path.dirname(tag_file)].clear())() - - for path in paths: - tags_building(path) - - try: - result = build_ctags( - path=path, - tag_file=tag_file, - recursive=recursive, - opts=opts, - cmd=command, - ) - except IOError as e: - error_message(e.strerror) - return - except subprocess.CalledProcessError as e: - if sublime.platform() == "windows": - str_err = " ".join(e.output.decode("windows-1252").splitlines()) - else: - str_err = e.output.decode(locale.getpreferredencoding()).rstrip() + try: + result = build_ctags( + path=path, + tag_file=tag_file, + recursive=recursive, + opts=opts, + cmd=command, + ) + except IOError as e: + error_message(e.strerror) + return + except subprocess.CalledProcessError as e: + if sublime.platform() == "windows": + str_err = " ".join(e.output.decode("windows-1252").splitlines()) + else: + str_err = e.output.decode( + locale.getpreferredencoding() + ).rstrip() + + error_message(str_err) + return + except Exception as e: + error_message( + "An unknown error occured.\nCheck the console for info." + ) + raise e - error_message(str_err) - return - except Exception as e: - error_message("An unknown error occured.\nCheck the console for info.") - raise e + in_main(lambda: tags_cache[os.path.dirname(result)].clear())() - tags_built(result) + progress.finish("Finished building tags!") if tag_file in ctags_completions: del ctags_completions[tag_file] # clear the cached ctags list