Flushing Out USHF
Flushing out the Universal Syntax Highlighting Function.
Posted on: 220310
In this final chapter, I'm flushing the USHF out by doing performance upgrades. The main thing I want to do is adapt USHF to be usable in a live editor, not just on static <code> blocks. The way I've decided to go about this is to have USHF have a secondary method for returning the required data. The original method will build HTML containing the code, while the second method will generate an array of styling information per character.
The USHF was originally a small javascript function and ran fairly slow when run repeatedly due to the use of inner functions. This is the original function in all its glory...
function highlight(E,Z){ if(!Z){ return false; } var
I, C, T=E, A=[0],L=0, S=Z[L], U=false, O,
ty=function(v,d){ return ((typeof v).charAt(0)==d); },
to=function(n){ L=n; A.push(n); S=Z[n]; T=T.appendChild(
document.createElement(S.tg||'span') );
var cl=S.cl||''; if(cl){ T.setAttribute('class',cl); }
var st=S.st||''; if(st){ T.setAttribute('style',st); } },
idm=function(w){ if(!w){ return false; }
if(ty(S[w],'f')){ S[w](T); return false; } if(ty(S[w],'o')){
var k,l,x=null,v=T.textContent; if(ty(S.ip,'f')){ v=S.ip(v); }
for(k=0;k<S[w].length;k++){ for(l=1;l<S[w][k].length;l++){
if(v==S[w][k][l]){ x=S[w][k][0]; if(ty(x,'f')){ x(T); return true; }
if(ty(x,'s')){ if(x.charAt(0)=='.'){
T.setAttribute('class',x.substring(1)); } else {
T.setAttribute('style',x); } return true; }
} } } } return false; },
mak=function(o,n){ I+=n.length-1; var w=T.appendChild(
document.createElement(o.tg||'span') );
var cl=o.cl||''; if(cl){ w.setAttribute('class',cl); }
var st=o.st||''; if(st){ w.setAttribute('style',st); }
w.textContent=n; C=''; },
at=function(p,m){ return (src.substring(p,m.length)==m); },
ch=function(v){ var k; for(k=0;k<C.length;k++){
if(v.indexOf(C.charAt(k))<0){ return false; } }
return true; },
pat=function(p,m){ var n=src.substring(p,m.length),i=0,c,d;
for(;i<m.length;i++){ c=n.substring(i,1); d=m.substring(i,1);
switch(d){
case '0': if('0123456789'.indexOf(c)==-1){ return false; } break;
default: if(c!=d){ return false; }
} } return true; },
up=function(){ var n=S.nx||false; idm('id'); A.pop();
L=A[A.length-1]; S=Z[L]; T=T.parentNode; if(n){ to(n); } },
chl=function(){ var y,w,o,j,k;
forln(S.ch;j){ o=Z[S.ch[j]];
if(ty(o.cb,'s') && o.cb.indexOf(src.substring(I-1,1))==-1){ continue; }
if(ty(o.os,'s') && at(I,o.os)){ to(S.ch[j]); y=o.os.length;
C+=src.substring(I+1,y-1); I+=y-1; return true; }
if(ty(o.os,'o')){ forln(o.os;k){
if(at(i,o.os[k])){ to(o.ch[j]); y=o.os[k].length;
C+=src.substring(I+1,y-1); I+=y-1; return true; } } }
if(ty(o.sm,'o')){ forln(o.sm;k){ if(at(I,o.sm[k])){
if(ty(o.ca,'s') &&
o.ca.indexOf(src.substring(I+o.sm[k].length,1))==-1){ continue; }
mak(o,o.sm[k]); return true; } } }
if(ty(o.pm,'o')){ for(k=0;k<o.pm.length;k++){ if(pat(I,o.pm[k])){
if(ty(o.ca,'s') &&
o.ca.indexOf(src.substring(I+o.pm[k].length,1))==-1){ continue; }
mak(o,src.substring(I,o.pm[k].length)); return true; } } }
if(ty(o.oc,'s') && ch(o.oc)){ to(S.ch[j]); return true; } } },
/* end of var... */ src=E.textContent;
T.innerHTML=''; for(I=0;I<src.length;I++){ U=false; C=src.charAt(I);
while((ty(S.cx,'s') && ch(S.cx)) || (ty(S.cp,'s') && !ch(S.cp))
|| (ty(S.lm,'n') && T.textContent.length>S.lm-1)){ up(); }
if(ty(S.nd,'s') && at((I-S.nd.length)+1,S.nd)){ U=true; }
if(ty(S.nd,'o')){ forln(S.nd;j){
if(at((I-S.nd[j].length)+1,S.nd[j])){ U=true; break; } } }
if(ty(S.ch,'o')){ chl(); }
if(C){ T.innerHTML+=(C.replace(/&/g,'&').replace(/</g,'<')); }
if(U||idm('ib')){ up(); }
} while(A.length>1){ up(); }
It was an absolute mess.
I made a great deal of changes which can be seen on the Github link below. The first thing I did was format it nicely, make USHF an object with the inner functions as methods, and I added new conditions for child scopes; namely character ranges. The new source for USHF is much larger, well commented and formatted.
USHF now has three main methods: highlight to highlight static blocks of code and produce HTML syntax highlighting, and two methods for a live editor: render_start which sets the language and creates an object which can then be passed to render_line, which returns that same object, adding an array of highlighting information per character.
The static text highlighter takes a few seconds for very large sources as you might have noticed in this post, but the live editor version highlights one line at a time. This means that as the user is editing text, only the current line needs to be re-highlighted, making it very quick. I plan on writing a code editor soon that makes use of this...
Github link:
https://github.com/Motekye/USHF