Search code examples
javascriptjquerysyntax-highlightingparentheses

Highlight parentheses inside input box


I am trying to highlight parentheses depending on their level and if they are matched or not. So first level gets the following class paren_1, second gets paren_2 and so on. I want to highlight the set of parentheses next to the caret too, if there are any. Meaning if you have the caret next to a parenthesis (openeing or closing) it should highlight said parenthesis and its corresponding one. My (broken) implementation of this is shown in the fiddle.

This works fairly well. Problems are the following

  1. If there is HTML in the input string, everything breaks apart. I tried escaping the html before sending it in using jQuery('<div />').text(text).html(), which works, but ruins the caret position.
  2. If there are several parentheses on the "same level" and the caret is next to one set, it highlights more than it should.
  3. If there is an unmatched parenthesis, it should be highlighted in red or something similar. This is not working either and I have no idea how to implement it. I have tried my best but everything has failed.

JSFiddle: http://jsfiddle.net/yWzWV/1/

Note to you all: I am in no way very good with either javascript or jquery, so you'll have to excuse me if this code makes your eyes bleed.

Thanks in advance!


Solution

  • Problem is solved, but I've encountered another. Here's the fiddle with the fixes: http://jsfiddle.net/Axvgf/

    Here's the changed method:

    function colorize(text, pos) {
        var i = 0, current_times = 0;
        var startc = '(', endc = ')';
        var current = -1;
    
        var entities = {'>': '&gt;','<':'&lt;'}; 
        var p2 = 0;
        var regex = new RegExp(Object.keys(entities).join("|"),'g');   
        var converted = text.replace(regex, function(x, j) {
            if(pos > j) p2 += entities[x].length - 1; 
            return entities[x];
        });
    
        pos += p2;
        var parens = [], indices = [], o = {};
        var newText = converted.replace(/((?:\\)*)([()])/g, function(full, escape, x, idx) {
            var len = escape.split(/\\/g).length - 1;
            if (len % 2 == 0) {
                indices.push(idx);
                if (x == startc) ++i;
                o[idx] = { selected: false, type: x, depth: i, idx: idx, pair: -1, extra: escape };
                if (idx == pos) o[idx].selected = true;
                if (x == startc) parens.push(idx);
                else {
                    if (parens.length > 0) {
                        var p = parens.pop();
                        o[idx].pair = p;
                        if (o[p].selected) o[idx].selected = true;
                        o[p].pair = idx;
                        if (o[idx].selected) o[p].selected = true;
                    }
                    --i
                }
            }
        });
        newtext = converted;     
        indices = indices.sort(function(x,y) { return Number(y) - Number(x); });
        indices.forEach(function(i) {
            newtext = newtext.substr(0,i) + o[i].extra +
            "<span class='" + (o[i].pair == -1 ? "unmatched " : "paren_" + (o[i].depth % 5)) + 
            (o[i].selected ? " selected_paren": "") + "'>" + o[i].type + "</span>" + 
            newtext.substr(i + 1 + o[i].extra.length)
        });
        return newtext;
    }