Continuing the discussion from API Suggestions Thread:
you can make a lightweight “on_after_modified” callback by using sublime.set_timeout
:
here is an example using an input panel, but the same principle applies: https://github.com/rosshadden/sublime-xpath/blob/e5a09a8624a19bda5321cd421d3f39eb18daba7c/sublime_input.py#L60-74
API Suggestion Discussion: On after modified event
API Suggestions
on_modified_async on_selection_modified_async is the same thing, async or not makes no difference, is like dispatching threads.
That is not what I described, and the ugly hack of set_timeout does not help and keeps adding overhead. Even more because theres no clear_timeout. And in any case you will still need an infinite loop running in a thread, that is not desirable to check if stopped moving.
We need performant apis, not ugly hacks. I developed a good amount of packages, that use on_modified_async and on_selection_modified and to get some reasonable performance without jumping cursors when moving or laggy typing is very tricky and prone to error. Almost no body got this right.
This is really all you need, no threads or anything. Still quite some boilerplate if it’s recurring.
from collections import defaultdict
from functools import partial
import sublime
import sublime_plugin
class AfterModifiedListener(sublime_plugin.EventListener):
# set this to >0 in your subclass
mod_timeout = 0
sel_timeout = 0
clean_interval = 10 * 60 * 1000
def __init__(self):
self.mod_view_counter = defaultdict(int)
self.sel_view_counter = defaultdict(int)
sublime.set_timeout_async(self._clean_counters, self.clean_interval)
def _clean_counters(self, view):
for counter in (self.mod_view_counter, self.sel_view_counter):
for id_ in list(counter): # clone key list
if not counter[id_]:
del counter[id_]
sublime.set_timeout_async(self._clean_counters, self.clean_interval)
def on_modified_async(self, view):
if self.timeout:
self.mod_view_counter[view.id()] += 1
cb = partial(self.mod_step, view)
sublime.set_timeout_async(cb, self.timeout)
def on_selection_modified_async(self, view):
if self.timeout:
self.mod_view_counter[view.id()] += 1
cb = partial(self.sel_step, view)
sublime.set_timeout_async(cb, self.timeout)
def mod_step(self, counter, view):
self.mod_view_counter[view.id()] -= 1
if not self.mod_view_counter:
self.on_after_modified(view)
def sel_step(self, counter, view):
self.sel_view_counter[view.id()] -= 1
if not self.sel_view_counter:
self.on_after_selection_modified(view)
# override these
def on_after_modified(self, view):
pass
def on_after_selection_modified(self, view):
pass
class TestListener(AfterModifiedListener):
mod_timeout = 1000
def on_after_modified(self, view):
pass # do stuff here
It’s also not really a hack but a reasonable algorithm for delaying actions until the user idles.
Edit: I’m unsure if calling sublime.set_timeout_async
in __init__
will succeed when ST is starting because event listeners are instantiated when the module is loaded, which can be before the API is available. Things get a little tricky then.