Dev Build 3118 is out now at https://www.sublimetext.com/3dev
3118 introduces Phantoms into the API, allowing computed data to be embedded within a text view. You can see it in action via compile errors, which are now shown inline.
Each phantom has HTML content, a region where it’s shown, and a layout: either LAYOUT_INLINE (phantom is drawn in the flow), LAYOUT_BLOCK (phantom is draw below the region, taking the full width when applicable) or LAYOUT_BELOW (phantom is drawn below the region, but not left-aligned).
view.add_phantom("test", view.sel(), "Hello, World!", sublime.LAYOUT_BLOCK)
To remove the added phantom, either delete the region it’s associated with, or call:
In the common case where the phantoms are generated as a function of the buffer content, you can use a sublime.PhantomSet to make keeping track of them simpler: pass a list of sublime.Phantom objects (their ctor is the same as view.add_phantom, but without the initial string parameter) to a PhantomSet’s update() function, and it’ll take care of adding and erasing phantoms in the view to match.
Also in 3118 is the notion of a ViewEventListener: these are like an EventListener, but are created one per-view, to make keeping track of per-view state simpler. They receive view level events (e.g., on_modified), but not window level ones (e.g., on_new). If your ViewEventListener should only be created for some files, but not all, then you can override the is_applicable(cls, settings) class level method. An example is below.
I haven’t updated the docs yet, but will do so in the next few days. In the mean time, here is an example that shows Phantoms and ViewEventListeners. Save it as a new plugin, then start editing a plain text file with content along the lines of ```how many characters is this =>`
import sublime import sublime_plugin class ViewCalculator(sublime_plugin.ViewEventListener): def __init__(self, view): self.view = view self.phantom_set = sublime.PhantomSet(view) self.timeout_scheduled = False self.needs_update = False self.update_phantoms() @classmethod def is_applicable(cls, settings): syntax = settings.get('syntax') return syntax == 'Packages/Text/Plain text.tmLanguage' def update_phantoms(self): phantoms =  # Don't do any calculations on 1MB or larger files if self.view.size() < 2**20: candidates = self.view.find_all('=>') for r in candidates: line_region = self.view.line(r.a) line = self.view.substr(line_region) idx = r.a - line_region.a if idx != -1: val = len(line[0:idx].strip()) op_pt = line_region.a + idx phantoms.append(sublime.Phantom( sublime.Region(op_pt + 2), str(val), sublime.LAYOUT_INLINE)) self.phantom_set.update(phantoms) def handle_timeout(self): self.timeout_scheduled = False if self.needs_update: self.needs_update = False self.update_phantoms() def on_modified(self): # Call update_phantoms(), but not any more than 10 times a second if self.timeout_scheduled: self.needs_update = True else: sublime.set_timeout(lambda: self.handle_timeout(), 100) self.update_phantoms()