Sublime Forum

Dev Build 3118

#1

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()
16 Likes

Menlo Font Bug In Phantoms
Erasing Phantom by Region not working
#2

Thanks for the updates.

I just want to report a small bug I’ve found in the syntax highlighting and the ctrl + r shortcut to jump to things like functions.

In PHP when I have a function that begins with __get or __set, the rest of the function name isn’t recognised properly.
You can see what I’m talking about in the following screenshot.
The function names with the white highlighting don’t show up in the ctrl + r selections either.

I’m not sure exactly when this would have happened, but I only noticed it since version 3116.

Also, when can we expect a foreground colour style for the block cursor?
I want to set the foreground to black, but as you can see in the screenshot it stays as the colour of the syntax making it difficult to read.

I went into more detail here: inverse_caret_state foreground colour

0 Likes

#3

Wow, unexpected and very exciting !!!
Thanks Jon.

0 Likes

#4

@jps aren’t you using any automatic documentation system ? I was looking to integrate ST3 documentation with dash/zeal and I’m curious if the documentation is generated based on public methods of classes or you have to write everything manually.

If it’s manual then I will have to scan for changes more often that once per release.

0 Likes

#5

Build errors are now shown inline at the location where they occurred. This is controlled with the show_errors_inline setting.

I’m not seeing any differences in the Syntax Tests build system. (Tested on Windows)

EDIT: can see why - the Syntax Tests build system uses a different command to most normal build systems that execute external processes. exec.py in the Default package has been updated to use Phantoms, but run_syntax_tests.py hasn’t. :slightly_smiling:

0 Likes

#6

I have just submitted a PR for this: https://github.com/sublimehq/Packages/pull/504

1 Like

#7

Thanks, I didn’t realise we could submit pull requests; I’ll keep that in mind for the future :slightly_smiling:

0 Likes

#8

This is amazing and very promising. Thanks!

0 Likes

#9

@jps while you’re on these definition related functionality, please take a look at this issue:

Long story short, if you have same method defined in multiple files, go to definition should display current file first in the dropdown.

Thanks!

2 Likes

#10

Color Schemes: popupCss will be generated automatically if not present

Can anyone clarify this? what CSS is generated exactly? just background and foreground is used? what are the generated classnames

0 Likes

#11

The phantoms are pretty awesome. 3 remarks though:

  • It’s a bit unexpected that hitting [x] on a phantom closes all of them in all files, with no (apparent) way to bring them back.
  • The [x] it a bit rough visually, couldn’t we get a nice icon in there? I think you could even get away with having no [x] at all and just close on click.
  • Is there a css selector I can use in my theme to set a phantom off from the source code (like tweak the background)?
3 Likes

#12

It looks like Christmas is earlier than usual this year !

Phantoms are pretty cool, but that raises other feature requests :slightly_smiling:

In python the error-message phantom looks like:

do = da + 2
    di = do + 3
File "~/foo.py", line 2 [X]

The thing is I don’t really care about the file name and the line because the phantom is already in the good place.
I’m much more interested in the error message and the details:

  di = do + 3
  ^
IndentationError: unexpected indent

I made a more detailed post in the “API suggestion thread”: API Suggestions

6 Likes

#13

in the color scheme you can:

<key>popupCss</key>
<string><![CDATA[
    html {
        background-color: #404238; // your color here

but it will affect all popups, not just phantoms.

0 Likes

#14

I tried that, but it didn’t seem to affect the phantoms. I’ll try again :slight_smile:

0 Likes

#15

it worked for me after running the build system again. But a lot of color scheme changes sometimes need a complete ST restart to take effect.

0 Likes

#16

phantomCss (in the .tmTheme file) can be used to style phantoms. If it’s not present, popupCss will be used instead.

@gwenzek that’s a function of the regex specified in the build system. The file_regex in the .sublime-build can specify 4 capture groups, for file, line, column and message respectively. If the file_regex provides less than 4 captures, then the whole line is used as the message. Take a look at Makefile/Make.sublime-build for an example.

Edit: Actually, on further reading, file_regex isn’t flexible enough for this, as it requires everything to fit on one line. file_regex+line_regex can be used if file names and line numbers are on separate lines (e.g., similar to Find in Files results), but there’s no combination that will properly support error messages in the above format

5 Likes

Build system regex captures
Inline build errors for Python do not show useful information
Help with build system for Crystal specs
#17

Nice. I can’t test anything at the moment, so I need to ask.

How often is this method called? Once per view or whenever view settings change? Can viewlisteners unsubscribe from events that way?

Since we cannot subscribe to on_new (not sure why?), is it safe to assume that init is called whenever a view is opened or created, assuming the class method returned true? What are other supposedly window-level events?

(also, some image or video with phantom views would be cool :+1:)

0 Likes

#18

Currently, CSS is generated for the background and foreground color, along with a link color and class names error, deleted, success, inserted, warning and modified. The background is modified by lightening it slightly to help distinguish from the text area background. The link and various class colors are pulled from the color scheme by looking through the various colors in the scheme and then setting the color: rule to the closest match to:

  • a tags: #0000FF
  • .error, .deleted classes: #FF0000
  • .success, .inserted classes: #00FF00
  • .warning, .modified classes: #FFD900
4 Likes

Minihtml best practises to work alongside theme developers
How to achieve default style for phantom_css if popup_css is defined in color scheme?
#19

It’s called when views are created and when settings change. For example, if is_applicable returns True iff the syntax of a view is set to Python, then the listener will be created or destroyed as required when the user changes the syntax of the file.

5 Likes

#20

More question time!

Is there an event/method that is called if a view is unsubscribed from? The classmethod that does unsubscribing doesn’t have access to the particular instance and __del__ is not approproate for situations like these. If no, I’d like to request this.

In the changelog there was a mention of a cancel key. Is that supposed to be a command name or a shell command (I assume the former)?

What does View.is_primary() tell? If a view is a phantom view? So far, I only managed to get it return True on the several views I tried.

2 Likes