more highlighting updates/comments, github theme updates, adding readme.

This commit is contained in:
Andrew Danger Lyon 2013-03-20 14:07:29 -07:00
parent 13821b414e
commit 3170af94ca
3 changed files with 205 additions and 26 deletions

130
README.md Normal file
View file

@ -0,0 +1,130 @@
highlight-lisp - Common Lisp syntax highlighter written in Javascript
=====================================================================
This is a syntax highlighter for Common Lisp written in Javascript. It is
completely themable via CSS (themes included).
The purpose of this is to make it really easy to embed beautiful Common Lisp
code into a website with minimal effort.
Usage
-----
Usage is simple. You include `highlight-lisp.js`, link to one of the CSS themes,
and call one of highlight-lisp's highlighting functions:
```html
<!-- Put these in your document somewhere, probably in the head, although the <script>
tag can probably go anywhere -->
<script type="text/javascript" src="/js/highlight-lisp/highlight-lisp.js"></script>
<link rel="stylesheet" href="/js/highlight-lisp/themes/github.css">
...
<pre><code class="lisp">(defun test-syntax-highlighter ()
"Docstring exaplaining what this function does."
(let ((hash (make-hash-table :test #'equal)))
...))</pre></code>
```
Once the HTML is set up, there are a few ways to initialize highlighting:
```js
// automatically highlight all <code class="lisp">...</code> blocks
HighlightLisp.highlight_auto();
// specify a class name:
HighlightLisp.highlight_auto({className: 'common-lisp'});
// highlight *every* code block
HighlightLisp.highlight_auto({className: null});
// manually highlight a code block
var code = document.getElementById('my-code-element');
HighlightLisp.highlight_element(code);
```
What gets highlighted
---------------------
- **Functions**. CSS class `function`
Anything starting with `(`: `(my-function ...)`
- **Known functions**. CSS class `function known`
Any function known by the highlighter: things like `make-hash-table`, `when`,
`format`, etc
- **Special functions**. CSS class `function known special`
Mainly `let`, `let\*`, `lambda`.
- **Symbol functions**. CSS class `function symbol`
Example: `#'my-function`
- **Known symbol functions**. CSS class `function symbol known`
Examples: `#'equalp`, `#'format`
- **Keywords**. CSS class `keyword`
Anything starting with `:` like `:this-is-a-keyword `
- **Known keywords**. CSS class `keyword known`
Known keywords are things like `:hash-keys`, `:supersede`, etc.
- **Symbols**. CSS class `symbol`
Anything starting with `'`: `'my-symbol`
- **Lambda-list operators**. CSS class `lambda-list`
Things like `&key`, `&body`, etc.
- **Numbers**. CSS class `number`
Any numbers: `69`, `-82.4`, `#xF047`, `#b11010`
- **Integers**. CSS class `number integer`
Simple numbers: `42`, `867`, etc. (no decimals)
- **Floats**. CSS class `number float`
Numbers with a decimal: `+47.82112`, `32.9` `3.` `.009`
- **Hex**. CSS class `number hex`
Hex numbers: `#x8090`, `#xc001`
- **Binary**. CSS class `number binary`
Example: `#b01101`
- **Variables**. By themselves, variables remain unhighlighted
- **Known variables**: CSS class `variable known`
Examples: `*package*`, `*standard-output*`, etc
- **Global variables**: CSS class `variable global`
Any symbol surrounded by `\*`: `*main-datastore*`, `*my-thread-local*`, etc
- **Constants**: CSS class `variable constant`
Any symbol surrounded by `+`: `+dt+`, `+contant-time+`, etc
- **nil/t**. CSS class `nil`
Any standalone `nil` or `t` will get this class
- **Comments**. CSS class `comment`
Example: `; this is a comment`
- **Strings**. CSS class `string`
Anthing inside `"`: `"This is a string."`
- **Parens**. CSS class `list`
May be overkill, but any `(` or `)` characters are classified.
On that note, things that *don't get highlighted/aren't properly highlighted*:
- Variables...things like `let` bindings or other symols within code that would
be interpreted as variables. Highlighting these would most likely be prohibitive
in terms of time (not the mention the return on investment). Feel free to patch!
- Some number notations. For instance `0.44d0`.
- Multi-line comments `#| ... |#` are unsupported
- Many constants (such as `pi`, `internal-time-units-per-second`) are classified
as functions, not known variables. This is because I pulled the list out of my
vim highlight script, and couldn't find a list of "Common Lisp standard
variables" to cross reference with. I pulled out the ones I know of and put them
into the known variables list, but there are no doubt more. If you see something
that is a known variable but gets treated as a known function, please open a
github issue.
Why
---
> Aren't there a bunch of Javascript syntax highlighters out there already?
Yes, but truth be told, most ignore lisp. You can write custom parsers for some
of them, but the APIs they provide didn't work well enough for me. [highlight.js](http://softwaremaniacs.org/soft/highlight/en/)
has a very nice lisp-highlighting mode, but I wanted more control over the
process.
For instance, `highlight-lisp` started as a [SyntaxHighlighter](http://alexgorbatchev.com/SyntaxHighlighter/)
brush, but I quickly realized that because of the limitations of Javascript not
allowing real [lookbehind regular expressions](http://www.regular-expressions.info/lookaround.html),
I needed more direct control over the search/replace process.
What I discovered was that given the proper tools, parsing lisp is *easy*
(especially after just releasing [markdown.cl](https://github.com/orthecreedence/markdown.cl))
and there's no need for a big highlighting framework. You plug in some regexes,
slap some <span class="..."> tags around certain things, and call it a day.
License
-------
As always, MIT.

View file

@ -7,10 +7,8 @@
* @licence MIT
*/
var highlight_lisp = function() {
// all of the following definitions were pulled straight from my syntax/lisp.vim
// all of the following functions were pulled straight from my syntax/lisp.vim
// file in my vim directory.
//
// this includes functions, known globals, known keywords, lambda list specials
var funcs =
'\\* find-method pprint-indent find-package pprint-linear find-restart ' +
'pprint-logical-block \\+ find-symbol pprint-newline finish-output ' +
@ -50,7 +48,7 @@ var highlight_lisp = function() {
'int-char restart-name bit-vector integer return bit-vector-p ' +
'integer-decode-float return-from bit-xor integer-length revappend block ' +
'integerp reverse boole interactive-stream-p room boole-1 intern rotatef ' +
'boole-2 internal-time-units-per-second round boole-and intersection ' +
'boole-2 round boole-and intersection ' +
'row-major-aref boole-andc1 invalid-method-error rplaca boole-andc2 ' +
'invoke-debugger rplacd boole-c1 invoke-restart safety boole-c2 ' +
'invoke-restart-interactively satisfies boole-clr isqrt sbit boole-eqv keyword ' +
@ -175,13 +173,14 @@ var highlight_lisp = function() {
'pathname-name with-output-to-string file-error pathname-type ' +
'with-package-iterator file-error-pathname pathname-version with-simple-restart ' +
'file-length pathnamep with-slots file-namestring peek-char ' +
'with-standard-io-syntax file-position phase write file-stream pi write-byte ' +
'with-standard-io-syntax file-position phase write file-stream write-byte ' +
'file-string-length plusp write-char file-write-date pop write-line fill ' +
'position write-sequence fill-pointer position-if write-string find ' +
'position-if-not write-to-string find-all-symbols pprint y-or-n-p find-class ' +
'pprint-dispatch yes-or-no-p find-if pprint-exit-if-list-exhausted zerop ' +
'find-if-not pprint-fill';
// common lisp global variables. also from lisp.vim
var standard_vars =
'\\*applyhook\\* \\*load-pathname\\* \\*print-pprint-dispatch\\* \\*break-on-signals\\* ' +
'\\*load-print\\* \\*print-pprint-dispatch\\* \\*break-on-signals\\* \\*load-truename\\* ' +
@ -195,8 +194,10 @@ var highlight_lisp = function() {
'\\*debugger-hook\\* \\*print-gensym\\* \\*read-suppress\\* \\*default-pathname-defaults\\* ' +
'\\*print-length\\* \\*readtable\\* \\*error-output\\* \\*print-level\\* \\*standard-input\\* ' +
'\\*evalhook\\* \\*print-lines\\* \\*standard-output\\* \\*features\\* \\*print-miser-width\\* ' +
'\\*terminal-io\\* \\*gensym-counter\\* \\*print-miser-width\\* \\*trace-output\\*';
'\\*terminal-io\\* \\*gensym-counter\\* \\*print-miser-width\\* \\*trace-output\\* ' +
'pi internal-time-units-per-second';
// common lisp known keywords
var keywords =
':abort :from-end :overwrite :adjustable :gensym :predicate :append :host ' +
':preserve-whitespace :array :if-does-not-exist :pretty :base :if-exists :print ' +
@ -248,13 +249,19 @@ var highlight_lisp = function() {
* Collections of search and replaces to make.
*/
var replace = [
// ---------------------------------------------------------------------
// strings (should !!ALWAYS!! be first, lest our <span> tags be destroyed...)
// ---------------------------------------------------------------------
{regex: /"([\s\S]*?)"/gm, replace: '<span class="string">"$1"</span>'},
// ---------------------------------------------------------------------
// comments
// ---------------------------------------------------------------------
{regex: /(;.*)(\n|$)/gm, replace: '<span class="comment">$1</span>$2'},
// ---------------------------------------------------------------------
// "special" (let/lambda)
// ---------------------------------------------------------------------
{
regex: new RegExp('.'+list_to_regex(special)+'(\\s)', 'gm'),
replace: function(fullmatch, fnname, whitespace) {
@ -270,7 +277,10 @@ var highlight_lisp = function() {
},
// ---------------------------------------------------------------------
// function matches
// ---------------------------------------------------------------------
// known functions
{
regex: new RegExp('.'+list_to_regex(funcs)+'(\\s)', 'gm'),
replace: function(fullmatch, fnname, whitespace) {
@ -284,6 +294,7 @@ var highlight_lisp = function() {
}
}
},
// symbol functions (#'my-fn)
{
regex: /(\s|[()])(#'(\w[\w_-]*))(\s|[()])/g,
replace: function(fullmatch, delim1, symfun, sym, delim2)
@ -293,14 +304,19 @@ var highlight_lisp = function() {
{
known = true;
}
return delim1 +'<span class="function'+ (known ? ' known' : '') +'">'+ symfun +'</span>'+ delim2;
return delim1 +'<span class="function symbol'+ (known ? ' known' : '') +'">'+ symfun +'</span>'+ delim2;
}
},
// ---------------------------------------------------------------------
// lambda keywords
// ---------------------------------------------------------------------
{regex: new RegExp('(\\s)'+list_to_regex(lambda)+'(\\s)', 'gm'), replace: '$1<span class="lambda-list">$2</span>$3'},
// ---------------------------------------------------------------------
// symbols/keywords/variables
// ---------------------------------------------------------------------
// known keywords
{regex: /(\s|[()])('\w[\w_-]*)(\s|[()])/g, replace: '$1<span class="symbol">$2</span>$3'},
{
regex: new RegExp('(\\s)'+list_to_regex(keywords)+'(\\s)', 'g'),
@ -308,6 +324,7 @@ var highlight_lisp = function() {
return whitespace + '<span class="keyword known">'+ keyword +'</span>'+ whitespace2;
}
},
// generic keywords
{
regex: /(\s|[()])(:\w[\w_-]*)/g,
replace: function(fullmatch, delim, keyword) {
@ -318,21 +335,32 @@ var highlight_lisp = function() {
return fullmatch;
}
},
// known variables
{
regex: new RegExp('(\\s|[()])'+list_to_regex(standard_vars)+'(\\s|[()])', 'g'),
replace: function(fullmatch, whitespace, varname, whitespace2) {
return whitespace + '<span class="variable known">'+ varname +'</span>'+ whitespace2;
}
},
// globals/constants
{regex: /(\s|[()])(\*\w[\w_-]*\*)(\s|[()])/g, replace: '$1<span class="variable global">$2</span>$3'},
{regex: /(\s|[()])(\+\w[\w_-]*\+)(\s|[()])/g, replace: '$1<span class="variable constant">$2</span>$3'},
// ---------------------------------------------------------------------
// numbers
// ---------------------------------------------------------------------
// binary
{regex: /(\s|[()])(#b[01]+)(\s|[()])/gi, replace: '$1<span class="number binary">$2</span>$3'},
// hex
{regex: /(\s|[()])(#x[\da-f]+)(\s|[()])/gi, replace: '$1<span class="number hex">$2</span>$3'},
// float
{regex: /(\s|[()])([+-]{0,1}(?:\d+\.\d+|\d+\.|\.\d+))(\s|[()])/g, replace: '$1<span class="number float">$2</span>$3'},
// integers
{regex: /(\s|[()])([+-]{0,1}\d+)(\s|[()])/g, replace: '$1<span class="number integer">$2</span>$3'},
// ---------------------------------------------------------------------
// misc parsers
// ---------------------------------------------------------------------
// t/nil
{regex: /(\s|[()])nil(\s|[()])/g, replace: '$1<span class="nil">nil</span>$2'},
{regex: /(\s|[()])t(\s|[()])/g, replace: '$1<span class="nil">t</span>$2'},
@ -341,13 +369,13 @@ var highlight_lisp = function() {
{regex: /\((\w[\w_-]*)(\s)/g, replace: '(<span class="function">$1</span>$2'},
// ()'s (should most probably be last, unless there's a good reason)
//{regex: /([()])/g, replace: '<span class="list">$1</span>'}
{regex: /([()])/g, replace: '<span class="list">$1</span>'}
];
/**
* Main highlight function.
*/
this.highlight = function(code_el)
this.highlight_element = function(code_el)
{
var html = code_el.innerHTML;
// can't have &...;'s running wild like a pack of animals...
@ -360,6 +388,27 @@ var highlight_lisp = function() {
html = html.replace(rep.regex, rep.replace);
}
code_el.innerHTML = html;
},
/**
* Automatically highlight all <code class="lisp"> blocks
*
* Takes an options arg, which can be used to specify the classname of the
* <code> tags you wish to highlight.
*/
this.highlight_auto = function(options)
{
options || (options = {});
var classname = options.className ? options.className : 'lisp';
var codes = document.getElementsByTagName('code');
for(var i = 0, n = codes.length; i < n; i++)
{
var code = codes[i];
if(code.className.match(classname))
{
this.highlight_element(code);
}
}
}
};

View file

@ -1,20 +1,20 @@
pre { white-space: pre; background-color: #f8f8f8; border: 1px solid #ccc; font-size: 13px; line-height: 19px; overflow: auto; padding: 6px 10px; border-radius: 3px; }
pre code {white-space: pre; margin: 0; padding: 0; background: none; border: none; overflow-x: auto; font-size: 13px;}
code {margin: 0 2px; padding: 0 5px; white-space: nowrap; font-family: Consolas, "Liberation Mono", Courier, monospace; background: #f8f8f8; border: 1px solid #eaeaea; border-radius: 3px;}
pre code.lisp {white-space: pre; margin: 0; padding: 0; background: none; border: none; overflow-x: auto; font-size: 13px;}
code.lisp {margin: 0 2px; padding: 0 5px; white-space: nowrap; font-family: Consolas, "Liberation Mono", Courier, monospace; background: #f8f8f8; border: 1px solid #eaeaea; border-radius: 3px;}
pre {color: #008080;}
pre .function {color: #008080;}
pre .function.known {color: #800603;}
pre .function.known.special {color: #2d2d2d; font-weight: bold;}
pre .keyword {color: #990073;}
pre .keyword.known {color: #990073;}
pre .symbol {color: #75a;}
pre .lambda-list {color: #966;}
pre .number {color: #800;}
pre .variable.known {color: #c3c;}
pre .variable.global {color: #939;}
pre .variable.constant {color: #229;}
pre .nil {color: #f00;}
pre code.lisp {color: #008080;}
pre code.lisp .function {color: #008080;}
pre code.lisp .function.known {color: #800603;}
pre code.lisp .function.known.special {color: #2d2d2d; font-weight: bold;}
pre code.lisp .keyword {color: #990073;}
pre code.lisp .keyword.known {color: #990073;}
pre code.lisp .symbol {color: #75a;}
pre code.lisp .lambda-list {color: #966;}
pre code.lisp .number {color: #800;}
pre code.lisp .variable.known {color: #c3c;}
pre code.lisp .variable.global {color: #939;}
pre code.lisp .variable.constant {color: #229;}
pre code.lisp .nil {color: #f00;}
pre .comment, pre .comment *, pre .comment .string {color: #aaa !important;}
pre .string, pre .string * {color: #d14 !important;}
pre code.lisp .comment, pre .comment *, pre .comment .string {color: #aaa !important;}
pre code.lisp .string, pre .string * {color: #d14 !important;}