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).
Simple example:
view.add_phantom("test", view.sel()[0], "Hello, World!", sublime.LAYOUT_BLOCK)
To remove the added phantom, either delete the region it’s associated with, or call:
view.erase_phantoms("test")
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()