1

I'm trying to make a simple syntax highlighter with JavaScript, but I always end up having the same problem. The program works as follows: as the user types enter (without the shift key) the program will replace the keyword var with another one with red color (this is still so basic). The problem is that whenever you press enter, the text gets highlighted but the cursor returns to the first word of the first line. How do you think I can prevent this from happening?

<div class="container">
    <pre class="text"><code contenteditable="true" id="format">
    </code></pre>
</div>

JS

var editor = document.getElementById('format');
var npatt = / *var +/igm
editor.addEventListener('keyup', highlight);

function highlight(e){
    var content = editor.innerHTML;
    if(e.which === 13 && e.shiftKey===false){
        editor.innerHTML = content.replace(npatt, '<span style="color:red">var</span>&nbsp;');
        console.log(editor.innerHTML);
    }
}
Drew Gaynor
  • 8,292
  • 5
  • 40
  • 53
Noctisdark
  • 350
  • 3
  • 17
  • Maybe the cursor moves because the existing content is being completely replaced by the syntax-highlighted content, so the cursor position is meaningless and resets to the default position? – Ryan Oct 16 '15 at 17:54
  • can you post a JSfiddle or a snippet? – Alvaro Silvino Oct 16 '15 at 17:55
  • Yes, but how can I fix that ?, I mean that when I hit enter the replaced text shoud be on the first line (or whatever line) and the cursor in the next line ? – Noctisdark Oct 16 '15 at 17:56

1 Answers1

2

Moving the cursor to the end of a contenteditable element can be done according to the method in this answer. That approach uses the window.getSelection() method to find the cursor position.

I made a few changes to your code.

  1. Added a test check to see if the regular expression even matches the content to avoid calling replace and setting editor.innerHTML on every Enter keystroke as the original code did.
  2. Added a call to the cursorManager.setEndOfContenteditable method (from the answer referenced above) to reset the cursor to the end of the editor after the replace operation.

Here is the updated code.

var editor = document.getElementById('format');
var npatt = / *var +/igm;

editor.addEventListener('keyup', highlight);

function highlight(e){
    var content = editor.innerHTML;

    if(e.which === 13 && e.shiftKey === false && npatt.test(content)) {
        editor.innerHTML = content.replace(npatt, '<span style="color:red">var</span>&nbsp;');
        cursorManager.setEndOfContenteditable(editor);
    }
}

And here is a working example.

var editor = document.getElementById('format');
var npatt = / *var +/igm;

editor.addEventListener('keyup', highlight);

function highlight(e){
    var content = editor.innerHTML;
  
    if(e.which === 13 && e.shiftKey === false && npatt.test(content)) {
        editor.innerHTML = content.replace(npatt, '<span style="color:red">var</span>&nbsp;');
        cursorManager.setEndOfContenteditable(editor);
    }
}

//Code to set the cursor position modified from this answer: https://stackoverflow.com/a/19588665/830125
//Namespace management idea from http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {

    //From: http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];

    //From: https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
    Array.prototype.contains = function(obj) {
        var i = this.length;
        while (i--) {
            if (this[i] === obj) {
                return true;
            }
        }
        return false;
    }

    //Basic idea from: https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
    function canContainText(node) {
        if(node.nodeType == 1) { //is an element node
            return !voidNodeTags.contains(node.nodeName);
        } else { //is not an element node
            return false;
        }
    };

    function getLastChildElement(el){
        var lc = el.lastChild;
        while(lc && lc.nodeType != 1) {
            if(lc.previousSibling)
                lc = lc.previousSibling;
            else
                break;
        }
        return lc;
    }

    //Based on Nico Burns's answer
    cursorManager.setEndOfContenteditable = function(contentEditableElement)
    {
        var range,selection;
        if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
        {    
            range = document.createRange();//Create a range (a range is a like the selection but invisible)
            range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            selection = window.getSelection();//get the selection object (allows you to change selection)
            selection.removeAllRanges();//remove any selections already made
            selection.addRange(range);//make the range you have just created the visible selection
        }
        else if(document.selection)//IE 8 and lower
        { 
            range = document.body.createTextRange();//Create a range (a range is a like the selection but invisible)
            range.moveToElementText(contentEditableElement);//Select the entire contents of the element with the range
            range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start
            range.select();//Select the range (make it the visible selection
        }
    }

}( window.cursorManager = window.cursorManager || {}));
<div class="container">
    <pre class="text"><code contenteditable="true" id="format">
    </code></pre>
</div>
Community
  • 1
  • 1
Drew Gaynor
  • 8,292
  • 5
  • 40
  • 53
  • Thank you, thank you thank you !!, you saved my life, I never thought this could be accomplished ! THANK YOU – Noctisdark Oct 16 '15 at 19:24