Search code examples
javascriptjquerycodemirror

Codemirror: Retrieve character index from span tag


So I have a codemirror instance on the left of a document, and an iframe on the right. When the code is updated in the editor, it is written to the iframe.

During this rewrite, I add an index to each and every element that is created using jQuery's $.data function so that whenever the user hovers their mouse over the element it can be highlighted in the editor.

So far I have managed to pick out the required element's position in the editor in terms of where its generated <span class="cm-tag cm-bracket"> tag is and give it a class of cm-custom-highlight.

My question is - is there any way to turn an instance of a source span tag into an actual selection within the editor?

Update: Answered my own question - see below! You can check out my resulting code here.


Solution

  • I answered my own question! How about that?

    Turns out CodeMirror has a neat little list of nodes in its display container. All I needed to do was loop through CodeMirror.display.renderedView[lineNumber].measure.map and test each text node's parentNode property to see if it was the same as the span I had highlighted.

    The map array is structured like so:

    [
        0: 0
        1: 1
        2: text
        3: 1
        4: 5
        ...
    ]
    

    Every text node here refers to a piece of code in the editor and the numbers before and after refer to its character index, so it was pretty easy to find the index that I needed:

    var span = $('span.cm-custom-highlight', CodeMirror.display.lineDiv),
        lineNumber = span.closest('.CodeMirror-line').closest('div[style]').index(),
        lineView = CodeMirror.display.renderedView[lineNumber].measure.map,
        char = 0;
    
    for(var i in lineView.measure.map)
    {
        if(!char &&
            typeof lineView.measure.map[i] == 'object' &&
            lineView.measure.map[i].parentNode && span[0] == lineView.measure.map[i].parentNode)
        {
            char = lineView.measure.map[i - 1];
        }
    }
    

    Sure it's a little messy, but it gets the job done nicely.