Monday, July 2, 2007

Jyntaxer

Everyone is making one of these. So, I thought I'd make one too using jquery.

First step in making something is to give it a name. I chose Jyntaxer because google didn't return a result (now, it will).

With JQuery, I can easily select $(".jyntaxer") and get .html() or .val() of the element. Then create a new element with .css("white-space", "pre") to make it look nice.

Since code usually contains <, > stuff, <textarea class="jyntaxer">some code with < > stuff</textarea> would be how normal user would use Jyntaxer.

Then I hit the wall: browser will render <textarea>&amp;</textarea> as &. If I query for that textarea.value, I get &. That means user has to preprocess their code before they can wrap their code in my magic <textarea class="jyntaxer">preprocessed code with &amp; properly escaped like &amp; => &amp;amp;</textarea>.

So, if user has to preprocess it, why not make the preprocessor highlight the syntax all together? Parsing strings in Javascript isn't fun.

So, Jyntaxer dies now

var KEYWORDS = ["abstract", "bool", "break", "case", "catch"
    , "char", "class", "const", "const_cast", "continue", "default"
    , "delete", "deprecated", "dllexport", "dllimport", "do", "double"
    , "dynamic_cast", "else", "enum", "explicit", "extern", "false", "float"
    , "for", "friend", "goto", "if", "inline", "int", "long", "mutable"
    , "naked", "namespace", "new", "noinline", "noreturn", "nothrow"
    , "novtable", "operator", "private", "property", "protected", "public"
    , "register", "reinterpret_cast", "return", "selectany", "short"
    , "signed", "sizeof", "static", "static_cast", "struct", "switch"
    , "template", "this", "thread", "throw", "true", "try", "typedef"
    , "typeid", "typename", "union", "unsigned", "using", "declaration"
    , "directive", "uuid", "virtual", "void", "volatile", "while", "typeof"
    , "as", "base", "by", "byte", "checked", "decimal", "delegate"
    , "descending", "event", "finally", "fixed", "foreach", "from", "group"
    , "implicit", "in", "interface", "internal", "into", "is", "lock"
    , "null", "object", "out", "override", "orderby", "params", "readonly"
    , "ref", "sbyte", "sealed", "stackalloc", "string", "select", "uint"
    , "ulong", "unchecked", "unsafe", "ushort", "var", "package"
    , "synchronized", "boolean", "implements", "import", "throws"
    , "instanceof", "transient", "extends", "final", "strictfp", "native"
    , "super", "debugger", "export", "function", "with", "NaN", "Infinity"
    , "require", "sub", "unless", "until", "use", "elsif", "BEGIN", "END"
    , "and", "assert", "def", "del", "elif", "except", "exec", "global"
    , "lambda", "not", "or", "pass", "print", "raise", "yield", "False"
    , "True", "Non", "e", "then", "end", "begin", "rescue", "ensure"
    , "module", "when", "undef", "next", "redo", "retry", "alias"
    , "defined", "done", "fi"];

function randint(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

//returns background style information of the node.
//if not defined, return parent's background style.
function getbg(node) {
    var bg = node.css("background-color");
    if(bg && bg != "transparent") {
        return bg;
    }
    return getbg(node.parent());
}

function getfg(bg) {
    var rgb = bg.match(/\d+/g);
    var fg = "rgb(";
    $.each(rgb, function (key, val) {
            fg += (Math.abs(randint(0, randint(0, 255) - Number(val, 10) + randint(0, 100))) + ',');
            });
    return fg.substring(0, fg.length - 1) + ')';
}

var JYN_STR_BEG = "0000JyntaxerStrBegin" + randint(1000, 9999);
var JYN_STR_END = "0000JyntaxerStrEnd" + randint(1000, 9999);

//node has raw code. converts it to html
function mkhtml(code) {

    var html = code.html();

    //html = html.replace(/(["'].*["'])/g
    //        , " " + JYN_STR_BEG + " $1 " + JYN_STR_END + " ");
    html = html.replace(/([a-zA-Z_]\w+)/g,
        function (str, p1, offset, s) {
            for(var i=0; i<KEYWORDS.length; i++) {
                if(p1 === KEYWORDS[i]) {
                    return "<span class=\"keyword\">"+p1+"</span>";
                }
            }
            return p1;
        });
    //html = html.replace(/\b(\d+)/g, "<span class=\"number\">$1</span>");
    //html = html.replace(new RegExp(JYN_STR_BEG, "g"), "<span class=\"string\">")
    //    .replace(new RegExp(JYN_STR_END, "g"), "</span>");

    //html = html.replace(/([^\w<>'" \s])/g, "<span class=\"operator\">$1</span>");
    //html = html.replace(/\n/g, "<br/>");
    //html = html.replace(/<.*\s(?!.*>)/g, "&nbsp;");
    var fg = getfg(getbg(code));

    html = $("<div class=\"jyntaxered\">" + html + "</div>");
    html.css("color", fg);
    $("#msg").append('<p>' + new Date() + ' : ' + code.attr("id") + '</p>');
    $("#msg").append($(html).each(function() { do_style($(this)); }));
    return html;
}

function do_style(node) {
    node.css("white-space", "pre");
    node.children(".keyword").css("color", "#ff0");
    node.children(".number").css("color", "#0ff");
    node.children(".string").css("color", "#f0f");
    node.children(".operator").css("color", "#00f");
}

//generates syntax highlighted code
function button_click(button, code) {
    if(button.attr("value") === "Jyntaxer") {
        code.css("display", "none");
        code.siblings(".jyntaxered:eq(0)").remove();
        mkhtml(code)
            .each(function () { do_style($(this)); }).insertAfter(code);

        button.attr("value", "Back");
    } else {
        code.css("display", "inline");
        code.siblings(".jyntaxered:eq(0)").css("display", "none");
        button.attr("value", "Jyntaxer");
    }
}

function show_code(button) {
    var code = button.siblings(".jyntaxer:eq(0)");
    var html = button.siblings(".jyntaxered:eq(0)");
    code.css("display", "inline");
    html.css("display", "none");
}
function show_html(button) {
    var code = button.siblings(".jyntaxer:eq(0)");
    var html = button.siblings(".jyntaxered:eq(0)");
    code.css("display", "none");
    html.css("display", "inline");
}

function attach_button(code) {
    $('<input type="button" class="jyntaxer_widget" value="Jyntaxer"/><br/>')
        .toggle(function() { show_html($(this)); }
                , function() { show_code($(this)); })
        .insertBefore(code);
}

function prepare_html(code) {
    code.wrap('<div class="jyntaxered_wrap"></div>');
    mkhtml(code).css("display", "none")
        .each(function () { do_style($(this)); }).insertAfter(code);
}

function is_outermost(node) {
    var parents = node.parents();
    var outermost = true;
    $.each(node.parents(), function (key, val) {
        if($(val).attr("class") === "jyntaxer") {
            outermost = false;
            return false;
        }});
    return outermost;
}

function jyntaxer() {
    $(".jyntaxer").filter(function () {
            return is_outermost($(this));
        }).each(function () {
            var code = $(this);
            prepare_html(code);
            attach_button(code);
            });
}

$(document).ready(function(){
    jyntaxer();
});

Html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-us" xml:lang="en-us" >
<head>
    <title>
        Jyntaxer : Javascript Syntax Highlight Thingy
    </title>
    <script type="text/javascript" src="jquery-latest.js"></script>
    <script type="text/javascript" src="jyntaxer.js"></script>
</head>
<body style="background: #aaa;">
<div id="container">
<p>
Hi this is some text.
</p>
<textarea id="python" class="jyntaxer" style="background-color: #fff;" cols="80" rows="20">
import sys

def html_escape(text
        , html_entities = {
            '&':'&amp;'
            , '<':'&lt;'
            , '>':'&gt;'
            #, "'":'''
            #, '"':'&quote;'
            , ' ':'&nbsp;'
            , '\t':'&nbsp;' } ):
    "replaces possible html entities in the text"
    l = []
    for c in text:
        l.append(html_entities.get(c,c))
    return "".join(l)

def main(argv=None):
    if argv is None:
        argv = sys.argv
    f = open(argv[1], 'r')
    print html_escape(f.read())

if __name__ == "__main__":
    main()
</textarea>
<p>
Hi this is some text.
</p>
<textarea id="cpp" class="jyntaxer" cols="80" rows="20">
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
int blah12 = 3435.453;
struct ScopeGuard: boost::function<void ()>, boost::noncopyable
{
  typedef boost::function<void ()> F;
  ScopeGuard () {}
  template <typename T> explicit ScopeGuard (T const & f) : F (f) {}
  ~ScopeGuard () { try { if (*this) operator()(); } catch (...) {} }
};
</textarea>
<p>
Hi this is some text.
</p>
</div>
<div id="msg"></div>
</body>
</html>

No comments:

Post a Comment