Skip to content

Simple syntax-highlighting editor with Prism

Prism is a great Javascript syntax highlighter, and it's fast enough to be used on the fly for active editing; Lea Verou (the author) does this with Dabblet. Combined with bililiteRange to handle some of the subtleties of dealing with contenteditable elements, a syntax-highlighting editor is relatively easy.

Simplest usage: bililiteRange.fancyText(element, Prism.highlightelement);.

See the demo.

See the code on github.

There's a fair amount of flakiness; contenteditable elements are still handled inconsistently. Newlines are changed into actual elements (Firefox adds <br>s; Chrome adds a whole new <div>), even though a <pre> doesn't need that; it would interpret newlines the way we want. So we have to intercept those and just insert the newline. Firefox insists on changing newlines into <br>'s even in pasted text, and Chrome's clipboard implementation includes the carriage return character, so we have to intercept paste events and insert the text appropriately mangled.

Usage

bililiteRange.fancyText(pre-element,[highlighter]);

sets up pre-element to be an editor; meaning that it listens for input events and re-highlights the code each time. The element can be any element, though only <pre> elements are targeted by the Prism CSS, and if you are writing code, whitespace is presumably important. So using <pre> makes the most sense. In order to allow editing, it should have contenteditable set, though that is not enforced.

highlighter is a function highlighter(pre-element) that does something to the element that leaves its underlying text unchanged; intended to be Prism.highlightelement. If undefined, just sets up the keydown and paste event listeners as above without doing anything else.

var editor = bililiteRange.fancyText(textarea,[highlighter]);

Replaces textarea with a <pre>, calls Prism.editor on that, and returns it. The original textarea is deleted.

var editor = bililiteRange.fancyText(pre-element, highlighter, threshold);

Even though Prism is fast, highlighting large texts with every keystroke can be slow. threshold is the number of milliseconds to "debounce" the input events; highlighting will only be done after that many milliseconds without any input.

Notes

Originally I had this with the Prism highlighter hard-wired in, as a Prism plugin. I realized that most of the code has nothing to do with Prism per se, so I could use dependency injection to make this a general-purpose highlighting editor; for instance, use

bililiteRange.fancyText(el, function(el){
  el.innerHTML = '<span class=line>'+el.innerHTML.split('\n').join('</span>\n<span class=line>')+'</span>';
});

to add line numbers with CSS.

Edited 2013-12-26: Prism requires an editable <pre> or other element, not a <textarea>. But that creates problems with progressive enhancement; I'd like my form to work without Javascript and <pre>'s don't get submitted. So ideally, the original HTML should have a <textarea> and the Javascript should replace it with a <pre contenteditable>. In that case, clearly the Javascript is working and you can get the text out of the editor to add it to the submitted form.

Prism.editor now does that. If the input element is a <textarea>, it replaces it with a <pre contenteditable> that has the same attributes (including id) and returns the new element.

Edited 2013-12-17: added threshold parameter.

{ 3 } Comments


  1. Fatal error: Uncaught Error: Call to undefined function ereg() in /home/public/blog/wp-content/themes/barthelme/functions.php:178 Stack trace: #0 /home/public/blog/wp-content/themes/barthelme/comments.php(34): barthelme_commenter_link() #1 /home/public/blog/wp-includes/comment-template.php(1469): require('/home/public/bl...') #2 /home/public/blog/wp-content/themes/barthelme/single.php(44): comments_template() #3 /home/public/blog/wp-includes/template-loader.php(74): include('/home/public/bl...') #4 /home/public/blog/wp-blog-header.php(19): require_once('/home/public/bl...') #5 /home/public/blog/index.php(17): require('/home/public/bl...') #6 {main} thrown in /home/public/blog/wp-content/themes/barthelme/functions.php on line 178