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);
    }
}
3
  • 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? Commented Oct 16, 2015 at 17:54
  • can you post a JSfiddle or a snippet? Commented Oct 16, 2015 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 ? Commented Oct 16, 2015 at 17:56

1 Answer 1

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>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, thank you thank you !!, you saved my life, I never thought this could be accomplished ! THANK YOU

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.