Python Syntax Highlighting for star-light
A couple of years ago I plugged star-light, a syntax highlighter that’s entirely client-side. I’ve been happy with it, but wanted a Python mode for it. I was going to post some other code this evening, and then decided that I should just make the Python mode myself.
This led to fun with regular expressions.
I probably shouldn’t call it “fun”, though. In any case, I eventually got something that works relatively well:
@some_decorator()
def hello_world(arg, **kw):
import pdb;
if 1==1:
print "Hello World"
some_func('foo')
some_other_func(list("string"))
else:
"""
No, we should never get to this condition.
"grocer's quotes"
Padding text.
"""
pass
return dict(message="Hello World printed")
I spent a lot longer on it than I would have liked, primarily due to the regular expression for multi-line comments:
var PYBLOCK_COMMENT1 = new RegExp(
'"{3}' + // opening triplet
'(' + // open paren for main optional expression
'([^"]*)' + // match not-" 0-inf times
'("{1,2}[^"]+"{1,2})*' + // match "x1 followed by one or more not-"
// followed by "x1, all 0-inf times
'([^"]*)' + // match not-" 0-inf times
')*' + // close paren, make main expression optional
'"{3}' + // closing triplet
'');
If by some chance you’re using star-light, it’s relatively easy to add this mode. First, add this line to bindings.xml
<binding id="star-python.htc|star-light.htc" extends="#behavior"/>
Then add this line to star-light.css
:
pre.python {
behavior: url(star-python.htc) url(star-light.htc);
-moz-binding: url(bindings.xml#star-python.htc|star-light.htc);
}
Finally, get this code and save it alongside the other modes as star-python.htc
:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
star-light - version 1.0.2 (2005/06/06)
Copyright 2005, Dean Edwards
Python mode Copyright 2009, Tadhg O'Higgins
License: http://creativecommons.org/licenses/LGPL/2.1/
-->
<public:component xmlns:public="urn:HTMLComponent" lightweight="true">
<public:attach event="ondocumentready" handler="init"/>
<script type="text/javascript">
//<![CDATA[
var PUNCTUATION = new RegExp("!=|[\-+*|\~^/%=<>\\]\[{}\\(\),.:]")
var BUILTIN = "__import__|abs|apply|basestring|bool|buffer|" +
"callable|chr|classmethod|cmp|coerce|compile|complex|" +
"delattr|dict|dir|divmod|" +
"enumerate|eval|execfile|file|filter|float|" +
"getattr|globals|hasattr|hash|help|hex|" +
"id|input|int|intern|isinstance|issubclass|iter|" +
"len|list|locals|long|" +
"map|max|min|" +
"object|oct|open|ord|pow|property|" +
"range|raw_input|reduce|reload|repr|round|" +
"setattr|slice|staticmethod|sum|super|str|tuple|type|" +
"unichr|unicode|vars|" +
"xrange|" +
"zip";
var RESERVED = "ArithmeticError|AssertionError|AttributeError|" +
"DeprecationWarning|" +
"EOFError|Ellipsis|EnvironmentError|Exception|False|" +
"FloatingPointError|FutureWarning|" +
"IOError|ImportError|IndentationError|IndexError|" +
"KeyError|KeyboardInterrupt|LookupError|" +
"MemoryError|NameError|None|NotImplemented|" +
"NotImplementedError|" +
"OSError|OverflowError|OverflowWarning|" +
"PendingDeprecationWarning|" +
"ReferenceError|RuntimeError|RuntimeWarning|" +
"StandardError|StopIteration|SyntaxError|SyntaxWarning|" +
"SystemError|SystemExit|TabError|True|TypeError|" +
"UnboundLocalError|UnicodeDecodeError|UnicodeEncodeError|" +
"UnicodeError|UnicodeTranslateError|UserWarning|ValueError|" +
"Warning|WindowsError|" +
"ZeroDivisionError|" +
"and|assert|break|" +
"class|continue|def|del|" +
"elif|else|except|exec|finally|for|from|" +
"global|" +
"if|import|in|is|" +
"lambda|" +
"not|" +
"or|pass|print|" +
"raise|return|" +
"try|" +
"while|" +
"yield";
var PYLINE_COMMENT = /#[^\n]*\n/
var PYBLOCK_COMMENT1 = new RegExp(
'"{3}' + // opening triplet
'(' + // open paren for main optional expression
'([^"]*)' + // match not-" 0-inf times
'("{1,2}[^"]+"{1,2})*' + // match "x1 followed by one or more not-"
// followed by "x1, all 0-inf times
'([^"]*)' + // match not-" 0-inf times
')*' + // close paren, make main expression optional
'"{3}' + // closing triplet
'');
var PYBLOCK_COMMENT2 = new RegExp(
"'{3}" + // opening triplet
"(" + // open paren for main optional expression
"([^']*)" + // match not-' 0-inf times
"('{1,2}[^']+'{1,2})*" + // match 'x1 followed by one or more not-'
// followed by 'x1, all 0-inf times
"([^']*)" + // match not-' 0-inf times
")*" + // close paren, make main expression optional
"'{3}" + // closing triplet
"");
var DECORATOR = new RegExp(
/\@[^\(]+/
);
function init() {
// default text colour
style.color = "black";
// escape character
parser.escapeChar = "\\";
// comments
parser.add(PYLINE_COMMENT, "color:green");
parser.add(PYBLOCK_COMMENT1, "color:green; font-weight:bold;");
parser.add(PYBLOCK_COMMENT2, "color:green; font-weight:bold;");
//python decorators
parser.add(DECORATOR, "color:orange;font-weight:bold;");
// regular expressions
parser.add(/([^\w\$\/'"*)])(\/[^\/\n\r\*][^\/\n\r]*\/g?i?)/, "color:maroon", "$2<span>$3</span>");
// strings
parser.add(STRING1, "color:maroon");
parser.add(STRING2, "color:maroon");
// numbers
parser.add(NUMBER, "color:maroon");
// urls/email
urls = true;
email = true;
tabStop = 4;
// python builtins
parser.add(BUILTIN, "color:teal; font-weight:bold;");
// python reserved words
parser.add(RESERVED, "color:blue; font-weight:bold");
// python punctuation:
parser.add(PUNCTUATION, "color:red;font-weight:bold");
// other:
parser.add(/\w+/, "color:black");
};
//]]>
</script>
</public:component>