Sublime Forum

API Suggestion Discussion: On after modified event

#1

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

1 Like

API Suggestions
#2

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.

1 Like

#3

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.

2 Likes

unlisted #4
0 Likes