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. Antonio | July 11, 2016 at 1:33 pm | Permalink

    Why doesn’t work with CSS or JS?

  2. Antonio | July 11, 2016 at 2:02 pm | Permalink

    Ignore my previous question.
    How can I manage multiple tags?

  3. Danny | August 7, 2016 at 5:17 pm | Permalink

    Antonio:
    Sorry I haven’t responded earlier. fancyText just applies Prism to the range; I would look at the Prism documentation to figure out how to handle multiple tags.
    –Danny

{ 1 } Trackback

  1. Hacking at 0300 : bililiteRange data | February 26, 2014 at 5:15 pm | Permalink

    […] fancyText does exactly that. See the […]

Post a Comment

Your email is never published nor shared. Required fields are marked *