Since this is a recurring problem with writing plugins that listen to changes to the buffer (selection) but do not want to execute on every change, I’ve written a small self-sufficient decorator that you can use on such events to debounce its execution. It may or may not make it into the
dependency library available to Package Control at some point.
See the post linked below for the latest version.
from functools import partial, wraps import sublime import sublime_plugin def _debounced_callback(view, old_change_count, callback): if view.is_valid() and view.change_count() == old_change_count: callback() def debounced(delay_in_ms, sync=False): """Delay calls to on_(selection_)modified(_async) event hooks until they weren't triggered for n ms. Works on both `EventListener` and `ViewEventListener` classes. Calls are only made when the `view` is still "valid" according to ST's API, so it's not necessary to check it in the wrapped function. """ def decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): view = self.view if hasattr(self, 'view') else args if not view.is_valid(): return change_count = view.change_count() callback = partial(func, self, *args, **kwargs) set_timeout = sublime.set_timeout if sync else sublime.set_timeout_async set_timeout(partial(_debounced_callback, view, change_count, callback), delay_in_ms) return wrapper return decorator class DebouncedListener(sublime_plugin.EventListener): @debounced(500) def on_modified_async(self, view): print("debounced on EventListener", view.id()) class DebouncedViewListener(sublime_plugin.ViewEventListener): @debounced(1000) def on_modified_async(self): print("debounced on ViewEventListener", self.view.id())