#969 Add a Virtual Text API

Eric Promislow

We've been talking about what sort of features we could add if Scintilla had a virtual-text API. Our main use-case is to add descriptive segments to snippets. For example, if you were to add a JavaScript snippet for an if block, it would be
something like

if (<condition here="">) {


When the snippet is inserted into the document, the text inside
the angle brackets would be rendered in a style that makes
it clear that it isn't part of the document. It would be up to
the implementation to determine when an edit has occurred next to
a run of virtual text, so it could then delete it.

Our current thoughts are that we would use the indicator system
to specify how a run of virtual text gets displayed.
What we're looking for is a way of associating a run of text with
a point in the document, but not have the text count as part of
the actual text. For example, if you were at position 3 of the
above snippet (the open-paren) and asked for the character at
PositionAfter(3), you'd get the close-paren.

It looks like this info would have to be stored on LineLayout objects,
using a sparse array, as most lines would have no virtual text segments.

Thoughts? Anyone interested in working with us on implementing it?

  • Eric Promislow


  • Eric Promislow
    Eric Promislow

    My angle brackets got eaten (and I didn't bother previewing). That sample
    should be

    if (<condition here>) {
        <code here>
  • Neil Hodgson
    Neil Hodgson

    This sort of feature has been considered before and there are some different approaches depending on which features are important. A similar issue arose with IME composition where, on Qt, the undo state is manipulated to insert temporary text.

    The first feature to consider is whether the virtual text is just for one view or if it is stored on the document and visible on all views. Whether it looks like real text to most APIs or is only seen by a new set of virtual text APIs. Another is whether virtual text can add lines.

    The management of the lifetime of the virtual text is another aspect: it may be simpler to require the application to add the virtual text after each change or have Scintilla manage the virtual text. There could be a combination where the application has to add again after more difficult changes such as adding/removing lines or when two pieces of virtual text coalesce so there is no place for the caret. The behaviour of the caret and the selection also needs defining: can virtual text be selected? Does virtual text survive an undo before it was defined.

    (1) Deep addition
    If lines can be added then it may be easier to add to the document since I think it would be easier to implement this as a change to CellBuffer that changed the text and line positions that are seen by all higher level code.

    One implementation technique is to have a second undo stack that collects the virtual text additions and automatically undoes back to the start state when needed. That means that any redo state is preserved on the main undo stack. On top of this, there could be an edit list that was automatically applied after each change.

    (2) Mid-layer addition
    It may be possible to interpose an object between the Document and View that acts as a virtual Document with additions. This would allow different additions to each view but allow most code to see the virtual text.

    (3) Shallow addition
    LineLayout objects can be ephemeral, since they act as a cache, so are unsafe as primary storage of virtual text. They probably need to hold copies of the virtual text with another primary source. If virtual text snippets are prohibited from containing line ends, then specifying a list of snippets to add sounds possible. An API similar to that used for annotations would allow full specification of styles for each character in the snippets. The snippet list positions would be reasonably robust but there are potential problems such as when the real text between two snippets is removed which also removes the ability to move the caret between the two snippets. There are also API issues such as when the application wants to hit test mouse positions or display a tooltip next to text or similar.

  • Neil Hodgson
    Neil Hodgson

    An implementation should allow for at least 2 sets of virtual text: one for clients and one for Scintilla so that Scintilla can implement good IME support without stepping on the client's use of virtual text.

    Annotations look like the closest similar feature so the API could be similar. API proposal:
    VirtualTextAdd(position, c-string (NUL terminated)) -> cookie
    VirtualTextSetStyle(cookie, stylenumber)
    VirtualTextSetStyles(cookie, char *bytes) -- Set a style for each byte
    VirtualTextSetStyleOffset(stylenumber) -- push out of lower 256 styles
    plus getters

    Using cookies here to allow multiple additions at one location for different purposes and to handle the case when the real text between two pieces of virtual text is removed.

    Should add a new API for allocating style numbers beyond 256 so that different uses can cooperate. Currently SCI_ANNOTATIONSETSTYLEOFFSET assumes client code will coordinate allocation which is a burden for it and does not allow Scintilla to also allocate extra styles.

    Using styles >=256 for this will add to implementation costs but will avoid clashes between features.

  • Jason Haslam
    Jason Haslam

    +1 for enabling better IME support. This API looks good to me. I'd be happy to test this whenever it's ready by reimplementing Qt IME support with virtual text.

  • Todd Whiteman
    Todd Whiteman

    Seems like a good API. I had similar ideas - based on a combination of Annotations and Modern Indicators.

    Additional API methods I had thought would come in handy:

    VirtualTextNext(cookie) -> cookie // To find where the next indicator lives
    VirtualTextClearRange(int start, int end)

    For limitations, I had envisioned the following:
    1) no multiline virtual text - to avoid drawing complications (unless you think this is possible Neil?)
    2) originally I had no two VT's at the same position - but using cookies should solve that

  • Neil Hodgson
    Neil Hodgson

    With word wrap, it may not be possible to avoid all situations with virtual text appearing on multiple display lines. They could be treated as non-wrapping blocks although there could be problems with that approach as well.

    The main block of coding work is modifying point<->position calculations and the consequences of this such as ensuring that wrapping works and automatic scrolling on caret movement works. Another example: is an indicator that crosses virtual text drawn over the virtual text? If it avoids virtual text then there has to be code to split the indicator up.

    Next up is the virtual text data structure which depends on the envisaged number of active virtual text pieces. For your placeholder use, I'd see up to around 20 and for IME there'd be 1 or 2. Other uses may require many more. If, as well as using annotations for diagnostics, the columns for each diagnostic were used to place virtual text then there may be hundreds or thousands of pieces. This might look like:

     for (X* px=p->first; px != NULL«1»; px=px->ext«2») {

    «1» Info: Deprecated constant, replace with nullptr
    «2» Error: Field ext not a member of X

    For low numbers of pieces, a simple vector sorted by position or std::map, with each position changing for every addition or deletion of text may be OK. For larger numbers of pieces something like Partitioning may be needed to avoid changing many pieces with every text insertion.

    VirtualTextSetStyleOffset could be postponed as its not essential and could be part of a larger change for styles >= 256. Initially use styles near the end (255, ...) or style 39 for experimenting with this feature.

  • Neil Hodgson
    Neil Hodgson

    There's a good chance that you will want to display virtual text with underlines and boxes. This is normally done with indicators but indicators are applied to ranges of real text. Showing indicators over virtual text may therefore lead to adding another API and some more implementation work.