Search code examples
javascriptcodemirrorcodemirror-modes

How to highlight substrings in CodeMirror based on substring positions


I'm trying to use CodeMirror (http://codemirror.net/) as a basic text editor with some extra functionalities. One of them is highlighting specific words or groups of words as specified by their positions in the original string. I have an external structure storing the list of substring positions I want to highlight. This structure is an Array where each element represents a text line and contains an array of objects with the positions of the substring to be highlighted. As an example, I have this text string:

The moon
is pale and round
as is
also the sun

The words to be highlighted are "moon", "pale", "round" and "sun". Thus, the highlighting structure is like this:

[
    [ { iStart:4, iEnd:7 } ], // "moon"
    [ { iStart:3, iEnd:6 }, { iStart:12, iEnd:16 } ], // "pale" and "round"
    [], 
    [ { iStart:9, iEnd:11 } ] // "sun"

]

In order to achieve this, I first tried writing a custom language mode but was not successful (mainly because I didn't know how to manage the fact that CodeMirror seems to use tokens, not lines, and I obviously need to know the line where the current token is located so as to retrieve the right data from the highlight structure).

Then I tried writing an external function that applies the highlighting by just adding SPAN tags manually, like this:

function highlightText()
{
    console.log( "highlightText()" );
    // Get a reference to the text lines in the code editor
    var codeLines = $("#editorContainer .CodeMirror-code pre>span" );
    for( var i=0; i<colorSegments.length; i++ ){
        // If there's text to be highlighted in this line...
        if( colorSegments[i] && colorSegments[i].length > 0 ){
            // Get the right element and do so
            var lineElement = codeLines[i];
            highlightWordsInLine( lineElement, colorSegments[i] );
        }
    }
}

function highlightWordsInLine(element, positions) {     
    // Get the raw text
    var str = $( element ).text();
    // Build a new string with highlighting tags.
    // Start 
    var out = str.substr(0, positions[0].iStart);
    for( var j=0; j<positions.length; j++ ){
        var position = positions[j];
        // Apply the highlighting tag
        out += '<span class="cm-s-ambiance cm-relation">';
        out += str.substr( position.iStart, position.iEnd - position.iStart + 1);
        out += '</span>';
        // Do not forget to incluide unhighlighted text in between
        if( j < positions.length-1 ){
            out += str.substr(  position.iEnd - position.iStart + 1, positions[j+1].iStart );
        }
    }
    // Wrap up to end of line
    out +=  str.substr( position.iEnd + 1);

    // Reset the html element value including applied highlight tags
    element.innerHTML = out;
}

I understand this is a quite dirty approach, and it actually doesn't work 100% since some of the text in the code editor becomes unselectable and other bugs, but at least I got some success at controlling the highlighting.

So my question is, what's the right way to do this? If I should stick to the language-mode approach, how would I do it?

I've also been suggested to give a look to Ace (http://ace.c9.io/#nav=higlighter), but it doesn't look like it would support handling highlighting based on string positions instead of keyword lists or regexp rules.

Thanks in advance.


Solution

  • The markText method is designed to make this kind of thing easy.