reorganize file structure and use labjs for loading
This commit is contained in:
parent
40fa64995d
commit
72b556efaa
23 changed files with 632 additions and 56 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.DS_Store
|
||||
95
css/editor.css
Normal file
95
css/editor.css
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
.editor {
|
||||
position: absolute;
|
||||
border: 2px solid rgb(159, 159, 159);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor.focus {
|
||||
border: 2px solid #327fbd;;
|
||||
}
|
||||
|
||||
.scroller {
|
||||
position: absolute;
|
||||
left: 50px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
background: rgb(227, 227, 227);
|
||||
border-right: 1px solid rgb(159, 159, 159);
|
||||
color: rgb(136, 136, 136);
|
||||
font-family: Monaco, "Courier New";
|
||||
font-size: 12px;
|
||||
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.layer {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gutter-layer {
|
||||
right: 0;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.text-layer {
|
||||
font-family: Monaco, "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.line {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.line .keyword {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.line .string {
|
||||
color: rgb(3, 106, 7);
|
||||
}
|
||||
|
||||
.line .comment {
|
||||
font-style: italic;
|
||||
color: rgb(0, 102, 255);
|
||||
}
|
||||
|
||||
.line .number {
|
||||
color: rgb(0, 0, 205);
|
||||
}
|
||||
|
||||
.line .xml_pe {
|
||||
color: rgb(104, 104, 91);
|
||||
}
|
||||
|
||||
.marker-layer .selection {
|
||||
position: absolute;
|
||||
background: rgba(77, 151, 255, 0.33);
|
||||
}
|
||||
2
demo/LAB.js
Executable file
2
demo/LAB.js
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
// LAB.js (LABjs :: Loading And Blocking JavaScript) | v1.0.2rc1 (c) Kyle Simpson | MIT License
|
||||
(function(j){var p="string",w="head",H="body",Y="script",t="readyState",k="preloaddone",y="loadtrigger",I="srcuri",D="preload",Z="complete",z="done",A="which",J="preserve",E="onreadystatechange",ba="onload",K="hasOwnProperty",bb="script/cache",L="[object ",bv=L+"Function]",bw=L+"Array]",e=null,h=true,i=false,s=j.document,bx=j.location,bc=j.ActiveXObject,B=j.setTimeout,bd=j.clearTimeout,M=function(a){return s.getElementsByTagName(a)},N=Object.prototype.toString,O=function(){},q={},P={},be=/^[^?#]*\//.exec(bx.href)[0],bf=/^\w+\:\/\/\/?[^\/]+/.exec(be)[0],by=M(Y),bg=j.opera&&N.call(j.opera)==L+"Opera]",bh=(function(a){a[a]=a+"";return a[a]!=a+""})(new String("__count__")),u={cache:!(bh||bg),order:bh||bg,xhr:h,dupe:h,base:"",which:w};u[J]=i;u[D]=h;q[w]=M(w);q[H]=M(H);function Q(a){return N.call(a)===bv}function R(a,b){var c=/^\w+\:\/\//,d;if(typeof a!==p)a="";if(typeof b!==p)b="";d=(c.test(a)?"":b)+a;return((c.test(d)?"":(d.charAt(0)==="/"?bf:be))+d)}function bz(a){return(R(a).indexOf(bf)===0)}function bA(a){var b,c=-1;while(b=by[++c]){if(typeof b.src===p&&a===R(b.src)&&b.type!==bb)return h}return i}function F(v,l){v=!(!v);if(l==e)l=u;var bi=i,C=v&&l[D],bj=C&&l.cache,G=C&&l.order,bk=C&&l.xhr,bB=l[J],bC=l.which,bD=l.base,bl=O,S=i,x,r=h,m={},T=[],U=e;C=bj||bk||G;function bm(a,b){if((a[t]&&a[t]!==Z&&a[t]!=="loaded")||b[z]){return i}a[ba]=a[E]=e;return h}function V(a,b,c){c=!(!c);if(!c&&!(bm(a,b)))return;b[z]=h;for(var d in m){if(m[K](d)&&!(m[d][z]))return}bi=h;bl()}function bn(a){if(Q(a[y])){a[y]();a[y]=e}}function bE(a,b){if(!bm(a,b))return;b[k]=h;B(function(){q[b[A]].removeChild(a);bn(b)},0)}function bF(a,b){if(a[t]===4){a[E]=O;b[k]=h;B(function(){bn(b)},0)}}function W(b,c,d,g,f,n){var o=b[A];B(function(){if("item"in q[o]){if(!q[o][0]){B(arguments.callee,25);return}q[o]=q[o][0]}var a=s.createElement(Y);a.type=d;if(typeof g===p)a.charset=g;if(Q(f)){a[ba]=a[E]=function(){f(a,b)};a.src=c}q[o].insertBefore(a,(o===w?q[o].firstChild:e));if(typeof n===p){a.text=n;V(a,b,h)}},0)}function bo(a,b,c,d){P[a[I]]=h;W(a,b,c,d,V)}function bp(a,b,c,d){var g=arguments;if(r&&a[k]==e){a[k]=i;W(a,b,bb,d,bE)}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bp.apply(e,g)}}else if(!r){bo.apply(e,g)}}function bq(a,b,c,d){var g=arguments,f;if(r&&a[k]==e){a[k]=i;f=a.xhr=(bc?new bc("Microsoft.XMLHTTP"):new j.XMLHttpRequest());f[E]=function(){bF(f,a)};f.open("GET",b);f.send("")}else if(!r&&a[k]!=e&&!a[k]){a[y]=function(){bq.apply(e,g)}}else if(!r){P[a[I]]=h;W(a,b,c,d,e,a.xhr.responseText);a.xhr=e}}function br(a){if(a.allowDup==e)a.allowDup=l.dupe;var b=a.src,c=a.type,d=a.charset,g=a.allowDup,f=R(b,bD),n,o=bz(f);if(typeof c!==p)c="text/javascript";if(typeof d!==p)d=e;g=!(!g);if(!g&&((P[f]!=e)||(r&&m[f])||bA(f))){if(m[f]!=e&&m[f][k]&&!m[f][z]&&o){V(e,m[f],h)}return}if(m[f]==e)m[f]={};n=m[f];if(n[A]==e)n[A]=bC;n[z]=i;n[I]=f;S=h;if(!G&&bk&&o)bq(n,f,c,d);else if(!G&&bj)bp(n,f,c,d);else bo(n,f,c,d)}function bs(a){T.push(a)}function X(a){if(v&&!G)bs(a);if(!v||C)a()}function bt(a){var b=[],c;for(c=-1;++c<a.length;){if(N.call(a[c])===bw)b=b.concat(bt(a[c]));else b[b.length]=a[c]}return b}x={script:function(){bd(U);var a=bt(arguments),b=x,c;if(bB){for(c=-1;++c<a.length;){if(c===0){X(function(){br((typeof a[0]===p)?{src:a[0]}:a[0])})}else b=b.script(a[c]);b=b.wait()}}else{X(function(){for(c=-1;++c<a.length;){br((typeof a[c]===p)?{src:a[c]}:a[c])}})}U=B(function(){r=i},5);return b},wait:function(a){bd(U);r=i;if(!Q(a))a=O;var b=F(h,l),c=b.trigger,d=function(){try{a()}catch(err){}c()};delete b.trigger;var g=function(){if(S&&!bi)bl=d;else d()};if(v&&!S)bs(g);else X(g);return b}};x.block=x.wait;if(v){x.trigger=function(){var a,b=-1;while(a=T[++b])a();T=[]}}return x}function bu(a){var b,c={},d={"UseCachePreload":"cache","UseLocalXHR":"xhr","UsePreloading":D,"AlwaysPreserveOrder":J,"AllowDuplicates":"dupe"},g={"AppendTo":A,"BasePath":"base"};for(b in d)g[b]=d[b];c.order=!(!u.order);for(b in g){if(g[K](b)&&u[g[b]]!=e)c[g[b]]=(a[b]!=e)?a[b]:u[g[b]]}for(b in d){if(d[K](b))c[d[b]]=!(!c[d[b]])}if(!c[D])c.cache=c.order=c.xhr=i;c.which=(c.which===w||c.which===H)?c.which:w;return c}j.$LAB={setGlobalDefaults:function(a){u=bu(a)},setOptions:function(a){return F(i,bu(a))},script:function(){return F().script.apply(e,arguments)},wait:function(){return F().wait.apply(e,arguments)}};j.$LAB.block=j.$LAB.wait;(function(a,b,c){if(s[t]==e&&s[a]){s[t]="loading";s[a](b,c=function(){s.removeEventListener(b,c,i);s[t]=Z},i)}})("addEventListener","DOMContentLoaded")})(window);
|
||||
358
demo/LAB.src.js
Executable file
358
demo/LAB.src.js
Executable file
|
|
@ -0,0 +1,358 @@
|
|||
// LAB.js (LABjs :: Loading And Blocking JavaScript)
|
||||
// v1.0.2rc1 (c) Kyle Simpson
|
||||
// MIT License
|
||||
|
||||
(function(global){
|
||||
var sSTRING = "string", // constants used for compression optimization
|
||||
sHEAD = "head",
|
||||
sBODY = "body",
|
||||
sSCRIPT = "script",
|
||||
sREADYSTATE = "readyState",
|
||||
sPRELOADDONE = "preloaddone",
|
||||
sLOADTRIGGER = "loadtrigger",
|
||||
sSRCURI = "srcuri",
|
||||
sPRELOAD = "preload",
|
||||
sCOMPLETE = "complete",
|
||||
sDONE = "done",
|
||||
sWHICH = "which",
|
||||
sPRESERVE = "preserve",
|
||||
sONREADYSTATECHANGE = "onreadystatechange",
|
||||
sONLOAD = "onload",
|
||||
sHASOWNPROPERTY = "hasOwnProperty",
|
||||
sSCRIPTCACHE = "script/cache",
|
||||
sTYPEOBJ = "[object ",
|
||||
sTYPEFUNC = sTYPEOBJ+"Function]",
|
||||
sTYPEARRAY = sTYPEOBJ+"Array]",
|
||||
nNULL = null,
|
||||
bTRUE = true,
|
||||
bFALSE = false,
|
||||
oDOC = global.document,
|
||||
oWINLOC = global.location,
|
||||
oACTIVEX = global.ActiveXObject,
|
||||
fSETTIMEOUT = global.setTimeout,
|
||||
fCLEARTIMEOUT = global.clearTimeout,
|
||||
fGETELEMENTSBYTAGNAME = function(tn){return oDOC.getElementsByTagName(tn);},
|
||||
fOBJTOSTRING = Object.prototype.toString,
|
||||
fNOOP = function(){},
|
||||
append_to = {},
|
||||
all_scripts = {},
|
||||
PAGEROOT = /^[^?#]*\//.exec(oWINLOC.href)[0], // these ROOTs do not support file:/// usage, only http:// type usage
|
||||
DOCROOT = /^\w+\:\/\/\/?[^\/]+/.exec(PAGEROOT)[0], // optional third / in the protocol portion of this regex so that LABjs doesn't blow up when used in file:/// usage
|
||||
docScripts = fGETELEMENTSBYTAGNAME(sSCRIPT),
|
||||
|
||||
// Ah-ha hush that fuss, feature inference is used to detect specific browsers
|
||||
// because the techniques used in LABjs have no known feature detection. If
|
||||
// you know of a feature test please contact me ASAP. Feature inference is used
|
||||
// instead of user agent sniffing because the UA string can be easily
|
||||
// spoofed and is not adequate for such a mission critical part of the code.
|
||||
is_opera = global.opera && fOBJTOSTRING.call(global.opera) == sTYPEOBJ+"Opera]",
|
||||
is_gecko = (function(o) { o[o] = o+""; return o[o] != o+""; })(new String("__count__")),
|
||||
|
||||
global_defs = {
|
||||
cache:!(is_gecko||is_opera), // browsers like IE/Safari/Chrome can use the "cache" trick to preload
|
||||
order:is_gecko||is_opera, // FF/Opera preserve execution order with script tags automatically, so just add all scripts as fast as possible
|
||||
xhr:bTRUE, // use XHR trick to preload local scripts
|
||||
dupe:bTRUE, // allow duplicate scripts? defaults to true now 'cause is slightly more performant that way (less checks)
|
||||
base:"", // base path to prepend to all non-absolute-path scripts
|
||||
which:sHEAD // which DOM object ("head" or "body") to append scripts to
|
||||
}
|
||||
;
|
||||
global_defs[sPRESERVE] = bFALSE; // force preserve execution order of all loaded scripts (regardless of preloading)
|
||||
global_defs[sPRELOAD] = bTRUE; // use various tricks for "preloading" scripts
|
||||
|
||||
append_to[sHEAD] = fGETELEMENTSBYTAGNAME(sHEAD);
|
||||
append_to[sBODY] = fGETELEMENTSBYTAGNAME(sBODY);
|
||||
|
||||
function isFunc(func) { return fOBJTOSTRING.call(func) === sTYPEFUNC; }
|
||||
function canonicalScriptURI(src,base_path) {
|
||||
var regex = /^\w+\:\/\//, ret;
|
||||
if (typeof src !== sSTRING) src = "";
|
||||
if (typeof base_path !== sSTRING) base_path = "";
|
||||
ret = (regex.test(src) ? "" : base_path) + src;
|
||||
return ((regex.test(ret) ? "" : (ret.charAt(0) === "/" ? DOCROOT : PAGEROOT)) + ret);
|
||||
}
|
||||
function sameDomain(src) { return (canonicalScriptURI(src).indexOf(DOCROOT) === 0); }
|
||||
function scriptTagExists(uri) { // checks if a script uri has ever been loaded into this page's DOM
|
||||
var script, idx=-1;
|
||||
while (script = docScripts[++idx]) {
|
||||
if (typeof script.src === sSTRING && uri === canonicalScriptURI(script.src) && script.type !== sSCRIPTCACHE) return bTRUE;
|
||||
}
|
||||
return bFALSE;
|
||||
}
|
||||
function engine(queueExec,opts) {
|
||||
queueExec = !(!queueExec);
|
||||
if (opts == nNULL) opts = global_defs;
|
||||
|
||||
var ready = bFALSE,
|
||||
_use_preload = queueExec && opts[sPRELOAD],
|
||||
_use_cache_preload = _use_preload && opts.cache,
|
||||
_use_script_order = _use_preload && opts.order,
|
||||
_use_xhr_preload = _use_preload && opts.xhr,
|
||||
_auto_wait = opts[sPRESERVE],
|
||||
_which = opts.which,
|
||||
_base_path = opts.base,
|
||||
waitFunc = fNOOP,
|
||||
scripts_loading = bFALSE,
|
||||
publicAPI,
|
||||
|
||||
first_pass = bTRUE,
|
||||
scripts = {},
|
||||
exec = [],
|
||||
end_of_chain_check_interval = nNULL
|
||||
;
|
||||
|
||||
_use_preload = _use_cache_preload || _use_xhr_preload || _use_script_order; // if all flags are turned off, preload is moot so disable it
|
||||
|
||||
function isScriptLoaded(elem,scriptentry) {
|
||||
if ((elem[sREADYSTATE] && elem[sREADYSTATE]!==sCOMPLETE && elem[sREADYSTATE]!=="loaded") || scriptentry[sDONE]) { return bFALSE; }
|
||||
elem[sONLOAD] = elem[sONREADYSTATECHANGE] = nNULL; // prevent memory leak
|
||||
return bTRUE;
|
||||
}
|
||||
function handleScriptLoad(elem,scriptentry,skipReadyCheck) {
|
||||
skipReadyCheck = !(!skipReadyCheck); // used to override ready check when script text was injected from XHR preload
|
||||
if (!skipReadyCheck && !(isScriptLoaded(elem,scriptentry))) return;
|
||||
scriptentry[sDONE] = bTRUE;
|
||||
|
||||
for (var key in scripts) {
|
||||
if (scripts[sHASOWNPROPERTY](key) && !(scripts[key][sDONE])) return;
|
||||
}
|
||||
ready = bTRUE;
|
||||
waitFunc();
|
||||
}
|
||||
function loadTriggerExecute(scriptentry) {
|
||||
if (isFunc(scriptentry[sLOADTRIGGER])) {
|
||||
scriptentry[sLOADTRIGGER]();
|
||||
scriptentry[sLOADTRIGGER] = nNULL; // prevent memory leak
|
||||
}
|
||||
}
|
||||
function handleScriptPreload(elem,scriptentry) {
|
||||
if (!isScriptLoaded(elem,scriptentry)) return;
|
||||
scriptentry[sPRELOADDONE] = bTRUE;
|
||||
fSETTIMEOUT(function(){
|
||||
append_to[scriptentry[sWHICH]].removeChild(elem); // remove preload script node
|
||||
loadTriggerExecute(scriptentry);
|
||||
},0);
|
||||
}
|
||||
function handleXHRPreload(xhr,scriptentry) {
|
||||
if (xhr[sREADYSTATE] === 4) {
|
||||
xhr[sONREADYSTATECHANGE] = fNOOP; // fix a memory leak in IE
|
||||
scriptentry[sPRELOADDONE] = bTRUE;
|
||||
fSETTIMEOUT(function(){ loadTriggerExecute(scriptentry); },0);
|
||||
}
|
||||
}
|
||||
function createScriptTag(scriptentry,src,type,charset,onload,scriptText) {
|
||||
var _script_which = scriptentry[sWHICH];
|
||||
fSETTIMEOUT(function() { // this setTimeout waiting "hack" prevents a nasty race condition browser hang (IE) when the document.write("<script defer=true>") type dom-ready hack is present in the page
|
||||
if ("item" in append_to[_script_which]) { // check if ref is still a live node list
|
||||
if (!append_to[_script_which][0]) { // append_to node not yet ready
|
||||
fSETTIMEOUT(arguments.callee,25); // try again in a little bit -- note, will recall the anonymous functoin in the outer setTimeout, not the parent createScriptTag()
|
||||
return;
|
||||
}
|
||||
append_to[_script_which] = append_to[_script_which][0]; // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists
|
||||
}
|
||||
var scriptElem = oDOC.createElement(sSCRIPT);
|
||||
scriptElem.type = type;
|
||||
if (typeof charset === sSTRING) scriptElem.charset = charset;
|
||||
if (isFunc(onload)) { // load script via 'src' attribute, set onload/onreadystatechange listeners
|
||||
scriptElem[sONLOAD] = scriptElem[sONREADYSTATECHANGE] = function(){onload(scriptElem,scriptentry);};
|
||||
scriptElem.src = src;
|
||||
}
|
||||
// only for appending to <head>, fix a bug in IE6 if <base> tag is present -- otherwise, insertBefore(...,null) acts just like appendChild()
|
||||
append_to[_script_which].insertBefore(scriptElem,(_script_which===sHEAD?append_to[_script_which].firstChild:nNULL));
|
||||
if (typeof scriptText === sSTRING) { // script text already avaiable from XHR preload, so just inject it
|
||||
scriptElem.text = scriptText;
|
||||
handleScriptLoad(scriptElem,scriptentry,bTRUE); // manually call 'load' callback function, skipReadyCheck=true
|
||||
}
|
||||
},0);
|
||||
}
|
||||
function loadScriptElem(scriptentry,src,type,charset) {
|
||||
all_scripts[scriptentry[sSRCURI]] = bTRUE;
|
||||
createScriptTag(scriptentry,src,type,charset,handleScriptLoad);
|
||||
}
|
||||
function loadScriptCache(scriptentry,src,type,charset) {
|
||||
var args = arguments;
|
||||
if (first_pass && scriptentry[sPRELOADDONE] == nNULL) { // need to preload into cache
|
||||
scriptentry[sPRELOADDONE] = bFALSE;
|
||||
createScriptTag(scriptentry,src,sSCRIPTCACHE,charset,handleScriptPreload); // fake mimetype causes a fetch into cache, but no execution
|
||||
}
|
||||
else if (!first_pass && scriptentry[sPRELOADDONE] != nNULL && !scriptentry[sPRELOADDONE]) { // preload still in progress, make sure trigger is set for execution later
|
||||
scriptentry[sLOADTRIGGER] = function(){loadScriptCache.apply(nNULL,args);};
|
||||
}
|
||||
else if (!first_pass) { // preload done, so reload (from cache, hopefully!) as regular script element
|
||||
loadScriptElem.apply(nNULL,args);
|
||||
}
|
||||
}
|
||||
function loadScriptXHR(scriptentry,src,type,charset) {
|
||||
var args = arguments, xhr;
|
||||
if (first_pass && scriptentry[sPRELOADDONE] == nNULL) { // need to preload
|
||||
scriptentry[sPRELOADDONE] = bFALSE;
|
||||
xhr = scriptentry.xhr = (oACTIVEX ? new oACTIVEX("Microsoft.XMLHTTP") : new global.XMLHttpRequest());
|
||||
xhr[sONREADYSTATECHANGE] = function(){handleXHRPreload(xhr,scriptentry);};
|
||||
xhr.open("GET",src);
|
||||
xhr.send("");
|
||||
}
|
||||
else if (!first_pass && scriptentry[sPRELOADDONE] != nNULL && !scriptentry[sPRELOADDONE]) { // preload XHR still in progress, make sure trigger is set for execution later
|
||||
scriptentry[sLOADTRIGGER] = function(){loadScriptXHR.apply(nNULL,args);};
|
||||
}
|
||||
else if (!first_pass) { // preload done, so "execute" script via injection
|
||||
all_scripts[scriptentry[sSRCURI]] = bTRUE;
|
||||
createScriptTag(scriptentry,src,type,charset,nNULL,scriptentry.xhr.responseText);
|
||||
scriptentry.xhr = nNULL;
|
||||
}
|
||||
}
|
||||
function loadScript(o) {
|
||||
if (o.allowDup == nNULL) o.allowDup = opts.dupe;
|
||||
var src = o.src, type = o.type, charset = o.charset, allowDup = o.allowDup,
|
||||
src_uri = canonicalScriptURI(src,_base_path), scriptentry, same_domain = sameDomain(src_uri);
|
||||
if (typeof type !== sSTRING) type = "text/javascript";
|
||||
if (typeof charset !== sSTRING) charset = nNULL;
|
||||
allowDup = !(!allowDup);
|
||||
if (!allowDup &&
|
||||
(
|
||||
(all_scripts[src_uri] != nNULL) || (first_pass && scripts[src_uri]) || scriptTagExists(src_uri)
|
||||
)
|
||||
) {
|
||||
if (scripts[src_uri] != nNULL && scripts[src_uri][sPRELOADDONE] && !scripts[src_uri][sDONE] && same_domain) {
|
||||
// this script was preloaded via XHR, but is a duplicate, and dupes are not allowed
|
||||
handleScriptLoad(nNULL,scripts[src_uri],bTRUE); // mark the entry as done and check if chain group is done
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (scripts[src_uri] == nNULL) scripts[src_uri] = {};
|
||||
scriptentry = scripts[src_uri];
|
||||
if (scriptentry[sWHICH] == nNULL) scriptentry[sWHICH] = _which;
|
||||
scriptentry[sDONE] = bFALSE;
|
||||
scriptentry[sSRCURI] = src_uri;
|
||||
scripts_loading = bTRUE;
|
||||
|
||||
if (!_use_script_order && _use_xhr_preload && same_domain) loadScriptXHR(scriptentry,src_uri,type,charset);
|
||||
else if (!_use_script_order && _use_cache_preload) loadScriptCache(scriptentry,src_uri,type,charset);
|
||||
else loadScriptElem(scriptentry,src_uri,type,charset);
|
||||
}
|
||||
function onlyQueue(execBody) {
|
||||
exec.push(execBody);
|
||||
}
|
||||
function queueAndExecute(execBody) { // helper for publicAPI functions below
|
||||
if (queueExec && !_use_script_order) onlyQueue(execBody);
|
||||
if (!queueExec || _use_preload) execBody(); // if engine is either not queueing, or is queuing in preload mode, go ahead and execute
|
||||
}
|
||||
function serializeArgs(args) {
|
||||
var sargs = [], idx;
|
||||
for (idx=-1; ++idx<args.length;) {
|
||||
if (fOBJTOSTRING.call(args[idx]) === sTYPEARRAY) sargs = sargs.concat(serializeArgs(args[idx]));
|
||||
else sargs[sargs.length] = args[idx];
|
||||
}
|
||||
return sargs;
|
||||
}
|
||||
|
||||
publicAPI = {
|
||||
script:function() {
|
||||
fCLEARTIMEOUT(end_of_chain_check_interval);
|
||||
var args = serializeArgs(arguments), use_engine = publicAPI, idx;
|
||||
if (_auto_wait) {
|
||||
for (idx=-1; ++idx<args.length;) {
|
||||
if (idx===0) {
|
||||
queueAndExecute(function(){
|
||||
loadScript((typeof args[0] === sSTRING) ? {src:args[0]} : args[0]);
|
||||
});
|
||||
}
|
||||
else use_engine = use_engine.script(args[idx]);
|
||||
use_engine = use_engine.wait();
|
||||
}
|
||||
}
|
||||
else {
|
||||
queueAndExecute(function(){
|
||||
for (idx=-1; ++idx<args.length;) {
|
||||
loadScript((typeof args[idx] === sSTRING) ? {src:args[idx]} : args[idx]);
|
||||
}
|
||||
});
|
||||
}
|
||||
end_of_chain_check_interval = fSETTIMEOUT(function(){first_pass = bFALSE;},5); // hack to "detect" the end of the chain if a wait() is not the last call
|
||||
return use_engine;
|
||||
},
|
||||
wait:function(func) {
|
||||
fCLEARTIMEOUT(end_of_chain_check_interval);
|
||||
first_pass = bFALSE;
|
||||
if (!isFunc(func)) func = fNOOP;
|
||||
// On this current chain's waitFunc function, tack on call to trigger the queue for the *next* engine
|
||||
// in the chain, which will be executed when the current chain finishes loading
|
||||
var e = engine(bTRUE,opts), // 'bTRUE' tells the engine to be in queueing mode
|
||||
triggerNextChain = e.trigger, // store ref to e's trigger function for use by 'wfunc'
|
||||
wfunc = function(){ try { func(); } catch(err) {} triggerNextChain(); };
|
||||
delete e.trigger; // remove the 'trigger' property from e's public API, since only used internally
|
||||
var fn = function(){
|
||||
if (scripts_loading && !ready) waitFunc = wfunc;
|
||||
else wfunc();
|
||||
};
|
||||
|
||||
if (queueExec && !scripts_loading) onlyQueue(fn);
|
||||
else queueAndExecute(fn);
|
||||
return e;
|
||||
}
|
||||
};
|
||||
publicAPI.block = publicAPI.wait; // alias "block" to "wait" -- "block" is now deprecated
|
||||
if (queueExec) {
|
||||
// if queueing, return a function that the previous chain's waitFunc function can use to trigger this
|
||||
// engine's queue. NOTE: this trigger function is captured and removed from the public chain API before return
|
||||
publicAPI.trigger = function() {
|
||||
var fn, idx=-1;
|
||||
while (fn = exec[++idx]) fn();
|
||||
exec = [];
|
||||
};
|
||||
}
|
||||
return publicAPI;
|
||||
}
|
||||
function processOpts(opts) {
|
||||
var k, newOpts = {},
|
||||
boolOpts = {"UseCachePreload":"cache","UseLocalXHR":"xhr","UsePreloading":sPRELOAD,"AlwaysPreserveOrder":sPRESERVE,"AllowDuplicates":"dupe"},
|
||||
allOpts = {"AppendTo":sWHICH,"BasePath":"base"}
|
||||
;
|
||||
for (k in boolOpts) allOpts[k] = boolOpts[k];
|
||||
newOpts.order = !(!global_defs.order);
|
||||
for (k in allOpts) {
|
||||
if (allOpts[sHASOWNPROPERTY](k) && global_defs[allOpts[k]] != nNULL) newOpts[allOpts[k]] = (opts[k] != nNULL) ? opts[k] : global_defs[allOpts[k]];
|
||||
}
|
||||
for (k in boolOpts) { // normalize bool props to actual boolean values if not already
|
||||
if (boolOpts[sHASOWNPROPERTY](k)) newOpts[boolOpts[k]] = !(!newOpts[boolOpts[k]]);
|
||||
}
|
||||
if (!newOpts[sPRELOAD]) newOpts.cache = newOpts.order = newOpts.xhr = bFALSE; // turn off all flags if preloading is disabled
|
||||
newOpts.which = (newOpts.which === sHEAD || newOpts.which === sBODY) ? newOpts.which : sHEAD;
|
||||
return newOpts;
|
||||
}
|
||||
|
||||
global.$LAB = {
|
||||
setGlobalDefaults:function(gdefs) { // intentionally does not return an "engine" instance -- must call as stand-alone function call on $LAB
|
||||
global_defs = processOpts(gdefs);
|
||||
},
|
||||
setOptions:function(opts){ // set options per chain
|
||||
return engine(bFALSE,processOpts(opts));
|
||||
},
|
||||
script:function(){ // will load one or more scripts
|
||||
return engine().script.apply(nNULL,arguments);
|
||||
},
|
||||
wait:function(){ // will ensure that the chain's previous scripts are executed before execution of scripts in subsequent chain links
|
||||
return engine().wait.apply(nNULL,arguments);
|
||||
}
|
||||
};
|
||||
global.$LAB.block = global.$LAB.wait; // alias "block" to "wait" -- "block" is now deprecated
|
||||
|
||||
/* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
|
||||
NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?).
|
||||
|
||||
The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does
|
||||
proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked
|
||||
document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready.
|
||||
For instance, jQuery > 1.3.2 has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or
|
||||
affected by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs.
|
||||
*/
|
||||
(function(addEvent,domLoaded,handler){
|
||||
if (oDOC[sREADYSTATE] == nNULL && oDOC[addEvent]){
|
||||
oDOC[sREADYSTATE] = "loading";
|
||||
oDOC[addEvent](domLoaded,handler = function(){
|
||||
oDOC.removeEventListener(domLoaded,handler,bFALSE);
|
||||
oDOC[sREADYSTATE] = sCOMPLETE;
|
||||
},bFALSE);
|
||||
}
|
||||
})("addEventListener","DOMContentLoaded");
|
||||
|
||||
})(window);
|
||||
52
demo/editor.html
Normal file
52
demo/editor.html
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Editor</title>
|
||||
<meta name="author" content="Fabian Jakobs">
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
|
||||
#container {
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="../css/editor.css" type="text/css" charset="utf-8">
|
||||
|
||||
<script src="../src/lib.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/TextDocument.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/JavaScript.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/XML.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/Tokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/BackgroundTokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/CursorLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/GutterLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/TextLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/MarkerLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/TextInput.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/Editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/VirtualRenderer.js" type="text/javascript" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var editor = new Editor(new TextDocument("Juhu Kinners"), new VirtualRenderer("container"));
|
||||
|
||||
window.onresize = function() {
|
||||
editor.resize();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
191
demo/fLAB.js
Executable file
191
demo/fLAB.js
Executable file
|
|
@ -0,0 +1,191 @@
|
|||
// fLAB.js (file:// protocol adapter for LABjs 1.0+)
|
||||
// v0.2 (c) Kyle Simpson
|
||||
// MIT License
|
||||
|
||||
(function(global){
|
||||
var orig_$LAB = global.$LAB,
|
||||
oDOC = global.document,
|
||||
oDOCLOC = oDOC.location,
|
||||
local_filesystem = (oDOCLOC.protocol === "file:")
|
||||
;
|
||||
if (!orig_$LAB || !local_filesystem) return; // only adapt LABjs with fLABjs wrapper if LABjs exists and we're currently in local filesystem
|
||||
|
||||
var sUNDEF = "undefined", // constants used for compression optimization
|
||||
sSTRING = "string",
|
||||
sHEAD = "head",
|
||||
sBODY = "body",
|
||||
sFUNCTION = "function",
|
||||
sSCRIPT = "script",
|
||||
sSRCURI = "srcuri",
|
||||
sDONE = "done",
|
||||
sWHICH = "which",
|
||||
bTRUE = true,
|
||||
bFALSE = false,
|
||||
fSETTIMEOUT = global.setTimeout,
|
||||
fGETELEMENTSBYTAGNAME = function(tn){return oDOC.getElementsByTagName(tn);},
|
||||
fOBJTOSTRING = Object.prototype.toString,
|
||||
fNOOP = function(){},
|
||||
append_to = {},
|
||||
all_scripts = {},
|
||||
PAGEROOT = /^[^?#]*\//.exec(oDOCLOC.href)[0],
|
||||
DOCROOT = /^file:\/\/(localhost)?(\/[a-z]:)?/i.exec(PAGEROOT)[0],
|
||||
docScripts = fGETELEMENTSBYTAGNAME(sSCRIPT),
|
||||
|
||||
is_ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
|
||||
sync_script_loading = is_ie, // only IE is currently known to do synchronous loading of file:// scripts, others require core LABjs async functionality
|
||||
|
||||
global_defs = {
|
||||
dupe:bFALSE, // allow duplicate scripts?
|
||||
preserve:bFALSE, // preserve execution order of all loaded scripts (regardless of preloading)
|
||||
base:"", // base path to prepend to all non-absolute-path scripts
|
||||
which:sHEAD // which DOM object ("head" or "body") to append scripts to
|
||||
}
|
||||
;
|
||||
|
||||
append_to[sHEAD] = fGETELEMENTSBYTAGNAME(sHEAD);
|
||||
append_to[sBODY] = fGETELEMENTSBYTAGNAME(sBODY);
|
||||
|
||||
function canonicalScriptURI(src,base_path) {
|
||||
if (typeof src !== sSTRING) src = "";
|
||||
if (typeof base_path !== sSTRING) base_path = "";
|
||||
var ret = (/^file\:\/\//.test(src) ? "" : base_path) + src;
|
||||
return ((/^file\:\/\//.test(ret) ? "" : (ret.charAt(0) === "/" ? DOCROOT : PAGEROOT)) + ret);
|
||||
}
|
||||
function scriptTagExists(uri) { // checks if a script uri has ever been loaded into this page's DOM
|
||||
var i = 0, script;
|
||||
while (script = docScripts[i++]) {
|
||||
if (typeof script.src === sSTRING && uri === canonicalScriptURI(script.src)) return bTRUE;
|
||||
}
|
||||
return bFALSE;
|
||||
}
|
||||
function engine(opts) {
|
||||
if (typeof opts === sUNDEF) opts = global_defs;
|
||||
|
||||
var ready = bFALSE,
|
||||
_which = opts.which,
|
||||
_base_path = opts.base,
|
||||
waitFunc = fNOOP,
|
||||
scripts_loading = bFALSE,
|
||||
publicAPI,
|
||||
scripts = {},
|
||||
orig_engine = null
|
||||
;
|
||||
|
||||
function createScriptTag(scriptentry,src,type,charset) {
|
||||
if (append_to[scriptentry[sWHICH]][0] === null) { // append_to object not yet ready
|
||||
fSETTIMEOUT(arguments.callee,25);
|
||||
return;
|
||||
}
|
||||
var scriptElem = oDOC.createElement(sSCRIPT), fSETATTRIBUTE = function(attr,val){scriptElem.setAttribute(attr,val);};
|
||||
fSETATTRIBUTE("type",type);
|
||||
if (typeof charset === sSTRING) fSETATTRIBUTE("charset",charset);
|
||||
fSETATTRIBUTE("src",src);
|
||||
append_to[scriptentry[sWHICH]][0].appendChild(scriptElem);
|
||||
}
|
||||
function loadScript(o) {
|
||||
if (typeof o.allowDup === sUNDEF) o.allowDup = opts.dupe;
|
||||
var src = o.src, type = o.type, charset = o.charset, allowDup = o.allowDup,
|
||||
src_uri = canonicalScriptURI(src,_base_path), scriptentry;
|
||||
if (typeof type !== sSTRING) type = "text/javascript";
|
||||
if (typeof charset !== sSTRING) charset = null;
|
||||
allowDup = !(!allowDup);
|
||||
|
||||
if (!allowDup &&
|
||||
(
|
||||
(typeof all_scripts[src_uri] !== sUNDEF && all_scripts[src_uri] !== null) ||
|
||||
scriptTagExists(src_uri)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (typeof scripts[src_uri] === sUNDEF) scripts[src_uri] = {};
|
||||
scriptentry = scripts[src_uri];
|
||||
if (typeof scriptentry[sWHICH] === sUNDEF) scriptentry[sWHICH] = _which;
|
||||
scriptentry[sDONE] = bFALSE;
|
||||
scriptentry[sSRCURI] = src_uri;
|
||||
scripts_loading = bTRUE;
|
||||
|
||||
all_scripts[scriptentry[sSRCURI]] = bTRUE;
|
||||
createScriptTag(scriptentry,src_uri,type,charset);
|
||||
}
|
||||
function serializeArgs(args) {
|
||||
var sargs = [], i;
|
||||
for (i=0; i<args.length; i++) {
|
||||
if (fOBJTOSTRING.call(args[i]) === "[object Array]") sargs = sargs.concat(serializeArgs(args[i]));
|
||||
else sargs[sargs.length] = args[i];
|
||||
}
|
||||
return sargs;
|
||||
}
|
||||
|
||||
publicAPI = {
|
||||
script:function() {
|
||||
var args = serializeArgs(arguments), use_engine, i;
|
||||
for (i=0; i<args.length; i++) {
|
||||
if (typeof args[i] === sSTRING) args[i] = {src:canonicalScriptURI(args[i])};
|
||||
else if (typeof args[i].src !== sUNDEF) args[i].src = canonicalScriptURI(args[i].src);
|
||||
}
|
||||
|
||||
if (sync_script_loading) { // handle without core LABjs since we're sync loading
|
||||
use_engine = publicAPI;
|
||||
for (i=0; i<args.length; i++) loadScript(args[i]);
|
||||
}
|
||||
else { // pass off to core LABjs to handle async loading
|
||||
if (orig_engine === null) orig_engine = orig_$LAB.setOptions(opts.pubMap);
|
||||
use_engine = orig_engine = orig_engine.script.apply(null,args);
|
||||
}
|
||||
return use_engine;
|
||||
},
|
||||
wait:function(func) {
|
||||
var use_engine;
|
||||
if (typeof func !== sFUNCTION) func = fNOOP;
|
||||
if (sync_script_loading) { // execute immediately since we're sync loading
|
||||
use_engine = publicAPI;
|
||||
fSETTIMEOUT(func,0);
|
||||
}
|
||||
else { // pass off to core LABjs to handle since we're async loading
|
||||
if (orig_engine === null) orig_engine = orig_$LAB.setOptions(opts.pubMap);
|
||||
use_engine = orig_engine = orig_engine.wait(func);
|
||||
}
|
||||
return use_engine;
|
||||
}
|
||||
};
|
||||
publicAPI.block = publicAPI.wait; // alias "block" to "wait" -- "block" is now deprecated
|
||||
return publicAPI;
|
||||
}
|
||||
function processOpts(opts) {
|
||||
var k, newOpts = {},
|
||||
boolOpts = {"AlwaysPreserveOrder":"preserve","AllowDuplicates":"dupe"},
|
||||
allOpts = {"AppendTo":"which","BasePath":"base"}
|
||||
;
|
||||
for (k in boolOpts) allOpts[k] = boolOpts[k];
|
||||
for (k in allOpts) {
|
||||
if (allOpts.hasOwnProperty(k) && typeof global_defs[allOpts[k]] !== sUNDEF) newOpts[allOpts[k]] = (typeof opts[k] !== sUNDEF) ? opts[k] : global_defs[allOpts[k]];
|
||||
}
|
||||
for (k in boolOpts) { // normalize bool props to actual boolean values if not already
|
||||
if (boolOpts.hasOwnProperty(k)) newOpts[boolOpts[k]] = !(!newOpts[boolOpts[k]]);
|
||||
}
|
||||
newOpts.preload = newOpts.cache = newOpts.order = newOpts.xhr = bFALSE; // don't use any preloading techniques if working in file:///
|
||||
newOpts.which = (newOpts.which === sHEAD || newOpts.which === sBODY) ? newOpts.which : sHEAD;
|
||||
newOpts.pubMap = {};
|
||||
for (k in allOpts) {
|
||||
if (allOpts.hasOwnProperty(k)) newOpts.pubMap[k] = newOpts[allOpts[k]]; // create a hash of reverse mappings back to the public names, suitable to pass along to orig_$LAB.setOptions()
|
||||
}
|
||||
return newOpts;
|
||||
}
|
||||
|
||||
global.$LAB = {
|
||||
setGlobalDefaults:function(gdefs) { // intentionally does not return an "engine" instance -- must call as stand-alone function call on $LAB
|
||||
global_defs = processOpts(gdefs);
|
||||
},
|
||||
setOptions:function(opts){ // set options per chain
|
||||
return engine(processOpts(opts));
|
||||
},
|
||||
script:function(){ // will load one or more scripts
|
||||
return engine().script.apply(null,arguments);
|
||||
},
|
||||
wait:function(){ // will ensure that the chain's previous scripts are executed before execution of scripts in subsequent chain links
|
||||
return engine().wait.apply(null,arguments);
|
||||
}
|
||||
};
|
||||
global.$LAB.block = global.$LAB.wait; // alias "block" to "wait" -- "block" is now deprecated
|
||||
})(window);
|
||||
56
doc/Editor.mm
Normal file
56
doc/Editor.mm
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<map version="0.8.0">
|
||||
<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->
|
||||
<node CREATED="1270106801605" ID="Freemind_Link_1615101381" MODIFIED="1270106809036" TEXT="Editor">
|
||||
<node CREATED="1270106809716" ID="_" MODIFIED="1270106896494" POSITION="right" TEXT="core features">
|
||||
<node CREATED="1270106812606" ID="Freemind_Link_941515617" MODIFIED="1270106824625" TEXT="text input"/>
|
||||
<node CREATED="1270106825885" ID="Freemind_Link_871701424" MODIFIED="1270106831385" TEXT="key navigation"/>
|
||||
<node CREATED="1270106831861" ID="Freemind_Link_497186050" MODIFIED="1270106836217" TEXT="shortcuts"/>
|
||||
<node CREATED="1270106836717" ID="Freemind_Link_53741891" MODIFIED="1270106841480" TEXT="copy&paste"/>
|
||||
<node CREATED="1270106842020" ID="Freemind_Link_985111837" MODIFIED="1270106846968" TEXT="drag&drop"/>
|
||||
<node CREATED="1270106849069" ID="Freemind_Link_81832450" MODIFIED="1270106852800" TEXT="selection"/>
|
||||
<node CREATED="1270106853508" ID="Freemind_Link_1490464696" MODIFIED="1270106861072" TEXT="syntax highlighting"/>
|
||||
<node CREATED="1270106861565" ID="Freemind_Link_1400628964" MODIFIED="1270106864776" TEXT="line numbers"/>
|
||||
<node CREATED="1270106865228" ID="Freemind_Link_1547807601" MODIFIED="1270106879192" TEXT="breakpoints (gutter)"/>
|
||||
</node>
|
||||
<node CREATED="1270106901715" ID="Freemind_Link_621059230" MODIFIED="1270106915606" POSITION="right" TEXT="additional features">
|
||||
<node CREATED="1270106916179" ID="Freemind_Link_138436821" MODIFIED="1270106921566" TEXT="column highlight"/>
|
||||
<node CREATED="1271052933294" ID="Freemind_Link_725020862" MODIFIED="1271052942879" TEXT="comment selection"/>
|
||||
<node CREATED="1270106922066" ID="Freemind_Link_1177585742" MODIFIED="1270106928030" TEXT="rectangular selection"/>
|
||||
<node CREATED="1270106928514" ID="Freemind_Link_1778337415" MODIFIED="1270106933390" TEXT="undo/redo"/>
|
||||
<node CREATED="1270106936530" ID="Freemind_Link_1313653586" MODIFIED="1270106939485" TEXT="auto indent"/>
|
||||
<node CREATED="1270106940626" ID="Freemind_Link_785606540" MODIFIED="1270106943653" TEXT="match braces"/>
|
||||
<node CREATED="1270106944617" ID="Freemind_Link_265205410" MODIFIED="1270106951045" TEXT="auto insert of code blocks"/>
|
||||
<node CREATED="1270106951682" ID="Freemind_Link_1064064918" MODIFIED="1270106955581" TEXT="autocomplete"/>
|
||||
<node CREATED="1270106957130" ID="Freemind_Link_1118872285" MODIFIED="1270106964437" TEXT="mark-highlight-variables"/>
|
||||
<node CREATED="1270106965257" ID="Freemind_Link_977412851" MODIFIED="1270106967828" TEXT="code folding"/>
|
||||
<node CREATED="1270106969353" ID="Freemind_Link_1003992965" MODIFIED="1270106972165" TEXT="refactoring"/>
|
||||
</node>
|
||||
<node CREATED="1270106975057" FOLDED="true" ID="Freemind_Link_1471854995" MODIFIED="1270106981986" POSITION="right" TEXT="supported browsers">
|
||||
<node CREATED="1270106983313" ID="Freemind_Link_594954564" MODIFIED="1270106988068" TEXT="IE 6-9"/>
|
||||
<node CREATED="1270106988584" ID="Freemind_Link_1809785833" MODIFIED="1270107014323" TEXT="FF 3 >= 3"/>
|
||||
<node CREATED="1270107000488" ID="Freemind_Link_497511816" MODIFIED="1270107008460" TEXT="Webkit >= 4"/>
|
||||
<node CREATED="1270107015287" ID="Freemind_Link_417983587" MODIFIED="1270107017819" TEXT="Chrome"/>
|
||||
<node CREATED="1270107018191" ID="Freemind_Link_393314987" MODIFIED="1270107038858" TEXT="Opera 9 or >= 10?"/>
|
||||
</node>
|
||||
<node CREATED="1271052795069" ID="Freemind_Link_674657297" MODIFIED="1271052799982" POSITION="left" TEXT="Ideen">
|
||||
<node CREATED="1271052800596" ID="Freemind_Link_1094064295" MODIFIED="1271052804602" TEXT="jsTestDriver"/>
|
||||
<node CREATED="1271055103973" ID="Freemind_Link_295696948" MODIFIED="1271055106137" TEXT="lab.js"/>
|
||||
<node CREATED="1271052805542" FOLDED="true" ID="Freemind_Link_1473541975" MODIFIED="1271052813414" TEXT="Bindings">
|
||||
<node CREATED="1271052813802" ID="Freemind_Link_779313331" MODIFIED="1271052821938" TEXT="jquery"/>
|
||||
<node CREATED="1271052822527" ID="Freemind_Link_467300132" MODIFIED="1271052825604" TEXT="yql"/>
|
||||
<node CREATED="1271052825952" ID="Freemind_Link_622561431" MODIFIED="1271052828013" TEXT="qooxdoo"/>
|
||||
<node CREATED="1271052830859" ID="Freemind_Link_18779066" MODIFIED="1271052833496" TEXT="APF!"/>
|
||||
</node>
|
||||
<node CREATED="1271052839079" ID="Freemind_Link_439583810" MODIFIED="1271052852530" TEXT="Options">
|
||||
<node CREATED="1271052853166" ID="Freemind_Link_629899491" MODIFIED="1271052860101" TEXT="Tabs to spaces"/>
|
||||
<node CREATED="1271052862867" ID="Freemind_Link_746641062" MODIFIED="1271052866129" TEXT="Spaces to tabs"/>
|
||||
<node CREATED="1271052866517" ID="Freemind_Link_192666733" MODIFIED="1271052869210" TEXT="tab width"/>
|
||||
<node CREATED="1271052879027" ID="Freemind_Link_778966191" MODIFIED="1271052882825" TEXT="Language switch">
|
||||
<node CREATED="1271052883285" ID="Freemind_Link_436205918" MODIFIED="1271052895231" TEXT="highlighting"/>
|
||||
<node CREATED="1271052896052" ID="Freemind_Link_799407328" MODIFIED="1271052900474" TEXT="indent/outdent"/>
|
||||
<node CREATED="1271052900934" ID="Freemind_Link_62594174" MODIFIED="1271052907981" TEXT="comment block"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</map>
|
||||
52
editor.html
52
editor.html
|
|
@ -1,52 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Editor</title>
|
||||
<meta name="author" content="Fabian Jakobs">
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
|
||||
#container {
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="editor.css" type="text/css" charset="utf-8">
|
||||
|
||||
<script src="lib.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="TextDocument.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="JavaScript.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="XML.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="Tokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="BackgroundTokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="CursorLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="GutterLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="TextLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="MarkerLayer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="TextInput.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="Editor.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="VirtualRenderer.js" type="text/javascript" charset="utf-8"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var editor = new Editor(new TextDocument("Juhu Kinners"), new VirtualRenderer("container"));
|
||||
|
||||
window.onresize = function() {
|
||||
editor.resize();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -12,7 +12,10 @@
|
|||
<textarea id="text"></textarea>
|
||||
<input type="button" value="tokenize" id="go">
|
||||
|
||||
<script src="../BackgroundTokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/BackgroundTokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/Tokenizer.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/JavaScript.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var button = document.getElementById("go");
|
||||
|
|
@ -29,7 +32,7 @@ button.onclick = function()
|
|||
console.log("update", firstLine, lastLine);
|
||||
};
|
||||
|
||||
var tokenizer = new BackgroundTokenizer(onUpdate, onComplete);
|
||||
var tokenizer = new BackgroundTokenizer(new Tokenizer(JavaScript.RULES), onUpdate, onComplete);
|
||||
tokenizer.setLines(text.value.split(/[\n\r]/));
|
||||
tokenizer.start();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
Juhu Kinners
|
||||
</div>
|
||||
|
||||
<script src="../lib.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="../src/lib.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var el = document.getElementById("juhu");
|
||||
|
|
|
|||
85
src/BackgroundTokenizer.js
Normal file
85
src/BackgroundTokenizer.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
function BackgroundTokenizer(tokenizer, onUpdate, onComplete)
|
||||
{
|
||||
this.running = false;
|
||||
this.textLines = [];
|
||||
this.lines = [];
|
||||
this.currentLine = 0;
|
||||
this.tokenizer = tokenizer;
|
||||
|
||||
this.onUpdate = onUpdate || function(firstLine, lastLine) {};
|
||||
this.onComplete = onComplete || function() {};
|
||||
|
||||
var self = this;
|
||||
this._worker = function()
|
||||
{
|
||||
if (!self.running) {
|
||||
return;
|
||||
}
|
||||
|
||||
var workerStart = new Date();
|
||||
var startLine = self.currentLine;
|
||||
var textLines = self.textLines;
|
||||
|
||||
var processedLines = 0;
|
||||
|
||||
while (self.currentLine < textLines.length)
|
||||
{
|
||||
var line = textLines[self.currentLine];
|
||||
|
||||
var state = self.currentLine == 0 ? "start" : self.lines[self.currentLine-1].state;
|
||||
self.lines[self.currentLine] = self.tokenizer.getLineTokens(line, state);
|
||||
|
||||
// only check every 30 lines
|
||||
processedLines += 1;
|
||||
if ((processedLines % 30 == 0) && (new Date()-workerStart) > 20)
|
||||
{
|
||||
self.onUpdate(startLine, self.currentLine);
|
||||
return setTimeout(self._worker, 10);
|
||||
}
|
||||
|
||||
self.currentLine++;
|
||||
}
|
||||
|
||||
self.running = false;
|
||||
|
||||
self.onUpdate(startLine, textLines.length-1);
|
||||
self.onComplete();
|
||||
}
|
||||
};
|
||||
|
||||
BackgroundTokenizer.prototype.setLines = function(textLines)
|
||||
{
|
||||
this.textLines = textLines;
|
||||
this.lines = [];
|
||||
|
||||
this.stop();
|
||||
};
|
||||
|
||||
BackgroundTokenizer.prototype.start = function(startRow)
|
||||
{
|
||||
this.currentLine = Math.min(startRow || 0, this.currentLine, this.textLines.length);
|
||||
this.lines.splice(startRow, this.lines.length);
|
||||
|
||||
if (!this.running)
|
||||
{
|
||||
this.running = true;
|
||||
setTimeout(this._worker, 50);
|
||||
}
|
||||
};
|
||||
|
||||
BackgroundTokenizer.prototype.stop = function() {
|
||||
this.running = false;
|
||||
};
|
||||
|
||||
BackgroundTokenizer.prototype.getTokens = function(row)
|
||||
{
|
||||
if (this.lines[row]) {
|
||||
return this.lines[row].tokens;
|
||||
} else {
|
||||
var state = "start";
|
||||
if (row > 0 && this.lines[row-1]) {
|
||||
state = this.lines[row-1].state;
|
||||
}
|
||||
return this.tokenizer.getLineTokens(this.textLines[row] || "", state).tokens;
|
||||
}
|
||||
};
|
||||
69
src/CursorLayer.js
Normal file
69
src/CursorLayer.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
function CursorLayer(parentEl)
|
||||
{
|
||||
this.element = document.createElement("div");
|
||||
this.element.className = "layer cursor-layer";
|
||||
parentEl.appendChild(this.element);
|
||||
|
||||
this.cursor = document.createElement("div");
|
||||
this.cursor.className = "cursor";
|
||||
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
CursorLayer.prototype.setCursor = function(position)
|
||||
{
|
||||
this.position = {
|
||||
row: position.row,
|
||||
column: position.column
|
||||
};
|
||||
};
|
||||
|
||||
CursorLayer.prototype.hideCursor = function()
|
||||
{
|
||||
this.isVisible = false;
|
||||
if (this.cursor.parentNode) {
|
||||
this.cursor.parentNode.removeChild(this.cursor);
|
||||
}
|
||||
clearInterval(this.blinkId);
|
||||
};
|
||||
|
||||
CursorLayer.prototype.showCursor = function()
|
||||
{
|
||||
this.isVisible = true;
|
||||
this.element.appendChild(this.cursor);
|
||||
|
||||
var cursor = this.cursor;
|
||||
cursor.style.visibility = "visible";
|
||||
|
||||
this.blinkId = setInterval(function() {
|
||||
cursor.style.visibility = "hidden";
|
||||
setTimeout(function() {
|
||||
cursor.style.visibility = "visible";
|
||||
}, 400);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
CursorLayer.prototype.getPixelPosition = function() {
|
||||
return this.pixelPos || {left: 0, top:0};
|
||||
}
|
||||
|
||||
CursorLayer.prototype.update = function(config)
|
||||
{
|
||||
if (!this.position) return;
|
||||
|
||||
var cursorLeft = Math.round(this.position.column * config.characterWidth);
|
||||
var cursorTop = this.position.row * config.lineHeight;
|
||||
|
||||
this.pixelPos = {
|
||||
left: cursorLeft,
|
||||
top: cursorTop
|
||||
};
|
||||
|
||||
this.cursor.style.left = cursorLeft + "px";
|
||||
this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) + "px";
|
||||
this.cursor.style.height = config.lineHeight + "px";
|
||||
|
||||
if (this.isVisible) {
|
||||
this.element.appendChild(this.cursor);
|
||||
}
|
||||
};
|
||||
650
src/Editor.js
Normal file
650
src/Editor.js
Normal file
|
|
@ -0,0 +1,650 @@
|
|||
var keys = {
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
LEFT: 37,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
POS1: 36,
|
||||
END: 35,
|
||||
DELETE: 46,
|
||||
BACKSPACE: 8,
|
||||
TAB: 9,
|
||||
A: 65
|
||||
}
|
||||
|
||||
function KeyBinding(element, host)
|
||||
{
|
||||
lib.addListener(element, "keydown", function(e)
|
||||
{
|
||||
var key = e.keyCode;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case keys.A:
|
||||
if (e.metaKey)
|
||||
{
|
||||
host.selectAll();
|
||||
return lib.stopEvent(e);
|
||||
}
|
||||
break;
|
||||
|
||||
case keys.UP:
|
||||
if (e.shiftKey) {
|
||||
host.selectUp();
|
||||
} else {
|
||||
host.navigateUp();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.DOWN:
|
||||
if (e.shiftKey) {
|
||||
host.selectDown();
|
||||
} else {
|
||||
host.navigateDown();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.LEFT:
|
||||
if (e.metaKey && e.shiftKey) {
|
||||
host.selectLineStart();
|
||||
} else if (e.metaKey) {
|
||||
host.navigateLineStart();
|
||||
} else if (e.shiftKey) {
|
||||
host.selectLeft();
|
||||
} else {
|
||||
host.navigateLeft();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.RIGHT:
|
||||
if (e.metaKey && e.shiftKey) {
|
||||
host.selectLineEnd();
|
||||
} else if (e.metaKey) {
|
||||
host.navigateLineEnd();
|
||||
} else if (e.shiftKey) {
|
||||
host.selectRight();
|
||||
} else {
|
||||
host.navigateRight();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.PAGEDOWN:
|
||||
if (e.shiftKey) {
|
||||
host.selectPageDown();
|
||||
} else {
|
||||
host.scrollPageDown();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.PAGEUP:
|
||||
if (e.shiftKey) {
|
||||
host.selectPageUp();
|
||||
} else {
|
||||
host.scrollPageUp();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.POS1:
|
||||
if (e.shiftKey) {
|
||||
host.selectLineStart();
|
||||
} else {
|
||||
host.navigateLineStart();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.END:
|
||||
if (e.shiftKey) {
|
||||
host.selectLineEnd();
|
||||
} else {
|
||||
host.navigateLineEnd();
|
||||
}
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.DELETE:
|
||||
host.removeRight();
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.BACKSPACE:
|
||||
host.removeLeft();
|
||||
return lib.stopEvent(e);
|
||||
|
||||
case keys.TAB:
|
||||
host.onTextInput(" ");
|
||||
return lib.stopEvent(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function Editor(doc, renderer)
|
||||
{
|
||||
var container = renderer.getContainerElement();
|
||||
this.renderer = renderer;
|
||||
|
||||
this.textInput = new TextInput(container, this);
|
||||
new KeyBinding(container, this);
|
||||
|
||||
lib.addListener(container, "mousedown", lib.bind(this.onMouseDown, this));
|
||||
lib.addListener(container, "dblclick", lib.bind(this.onMouseDoubleClick, this));
|
||||
lib.addMouseWheelListener(container, lib.bind(this.onMouseWheel, this));
|
||||
lib.addTripleClickListener(container, lib.bind(this.selectCurrentLine, this));
|
||||
|
||||
this.doc = doc;
|
||||
doc.addChangeListener(lib.bind(this.onDocumentChange, this));
|
||||
renderer.setDocument(doc);
|
||||
|
||||
this.tokenizer = new BackgroundTokenizer(
|
||||
new Tokenizer(XML.RULES),
|
||||
lib.bind(this.onTokenizerUpdate, this)
|
||||
);
|
||||
|
||||
this.tokenizer.setLines(doc.lines);
|
||||
renderer.setTokenizer(this.tokenizer);
|
||||
|
||||
this.cursor = {
|
||||
row: 0,
|
||||
column: 0
|
||||
};
|
||||
|
||||
this.selectionAnchor = null;
|
||||
this.selectionLead = null;
|
||||
this.selection = null;
|
||||
|
||||
this.renderer.draw();
|
||||
}
|
||||
|
||||
Editor.prototype =
|
||||
{
|
||||
resize : function() {
|
||||
this.renderer.draw();
|
||||
},
|
||||
|
||||
updateCursor : function() {
|
||||
this.renderer.updateCursor(this.cursor);
|
||||
},
|
||||
|
||||
onFocus : function() {
|
||||
this.renderer.showCursor();
|
||||
this.renderer.visualizeFocus();
|
||||
},
|
||||
|
||||
onBlur : function() {
|
||||
this.renderer.hideCursor();
|
||||
this.renderer.visualizeBlur();
|
||||
},
|
||||
|
||||
onDocumentChange : function(startRow, endRow)
|
||||
{
|
||||
this.tokenizer.start(startRow);
|
||||
this.renderer.updateLines(startRow, endRow);
|
||||
},
|
||||
|
||||
onTokenizerUpdate : function(startRow, endRow) {
|
||||
this.renderer.updateLines(startRow, endRow);
|
||||
},
|
||||
|
||||
onMouseDown : function(e)
|
||||
{
|
||||
this.textInput.focus();
|
||||
|
||||
var pos = this.renderer.screenToTextCoordinates(e.pageX, e.pageY);
|
||||
this.moveCursorTo(pos.row, pos.column);
|
||||
this.setSelectionAnchor(pos.row, pos.column);
|
||||
this.renderer.scrollCursorIntoView();
|
||||
|
||||
var _self = this;
|
||||
var mousePageX, mousePageY;
|
||||
|
||||
var onMouseSelection = function(e) {
|
||||
mousePageX = e.pageX;
|
||||
mousePageY = e.pageY;
|
||||
};
|
||||
|
||||
var onMouseSelectionEnd = function() {
|
||||
clearInterval(timerId);
|
||||
};
|
||||
|
||||
var onSelectionInterval = function()
|
||||
{
|
||||
if (mousePageX === undefined || mousePageY === undefined) return;
|
||||
|
||||
selectionLead = _self.renderer.screenToTextCoordinates(mousePageX, mousePageY);
|
||||
|
||||
_self._moveSelection(function() {
|
||||
_self.moveCursorTo(selectionLead.row, selectionLead.column);
|
||||
});
|
||||
_self.renderer.scrollCursorIntoView();
|
||||
};
|
||||
|
||||
lib.capture(this.container, onMouseSelection, onMouseSelectionEnd);
|
||||
var timerId = setInterval(onSelectionInterval, 20 );
|
||||
|
||||
return lib.preventDefault(e);
|
||||
},
|
||||
|
||||
onMouseDoubleClick : function(e)
|
||||
{
|
||||
var line = this.doc.getLine(this.cursor.row);
|
||||
var column = this.cursor.column;
|
||||
|
||||
var tokenRe = /[a-zA-Z0-9_]+/g;
|
||||
var nonTokenRe = /[^a-zA-Z0-9_]+/g;
|
||||
|
||||
var inToken = false;
|
||||
if (column > 0) {
|
||||
inToken = !!line.charAt(column-1).match(tokenRe);
|
||||
}
|
||||
|
||||
if (!inToken) {
|
||||
inToken = !!line.charAt(column).match(tokenRe);
|
||||
}
|
||||
|
||||
var re = inToken ? tokenRe : nonTokenRe;
|
||||
|
||||
var start = column;
|
||||
if (start > 0)
|
||||
{
|
||||
do {
|
||||
start--;
|
||||
} while (start >= 0 && line.charAt(start).match(re))
|
||||
start++;
|
||||
}
|
||||
|
||||
var end = column;
|
||||
while (end < line.length && line.charAt(end).match(re)) {
|
||||
end++;
|
||||
}
|
||||
|
||||
this.setSelectionAnchor(this.cursor.row, start);
|
||||
this._moveSelection(function() {
|
||||
this.moveCursorTo(this.cursor.row, end);
|
||||
});
|
||||
},
|
||||
|
||||
onMouseWheel : function(e)
|
||||
{
|
||||
var delta = e.wheel;
|
||||
this.renderer.scrollToY(this.renderer.getScrollTop() - (delta * 15));
|
||||
return lib.preventDefault(e);
|
||||
},
|
||||
|
||||
getCopyText : function()
|
||||
{
|
||||
if (this.hasSelection()) {
|
||||
return this.doc.getTextRange(this.getSelectionRange());
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
onCut : function()
|
||||
{
|
||||
if (this.hasSelection())
|
||||
{
|
||||
this.cursor = this.doc.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
this.renderer.updateCursor(this.cursor);
|
||||
}
|
||||
},
|
||||
|
||||
onTextInput: function(text)
|
||||
{
|
||||
if (this.hasSelection())
|
||||
{
|
||||
this.cursor = this.doc.replace(this.getSelectionRange(), text);
|
||||
this.clearSelection();
|
||||
} else {
|
||||
this.cursor = this.doc.insert(this.cursor, text);
|
||||
}
|
||||
this.renderer.updateCursor(this.cursor);
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
removeRight : function()
|
||||
{
|
||||
if (this.hasSelection())
|
||||
{
|
||||
this.cursor = this.doc.remove(this.getSelectionRange());
|
||||
this.renderer.updateCursor(this.cursor);
|
||||
this.clearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
var rangeEnd = {
|
||||
row: this.cursor.row,
|
||||
column: this.cursor.column + 1
|
||||
}
|
||||
if (rangeEnd.column > this.doc.getLine(this.cursor.row).length) {
|
||||
rangeEnd.row += 1;
|
||||
rangeEnd.column = 0;
|
||||
}
|
||||
this.doc.remove({start: this.cursor, end: renageEnd});
|
||||
}
|
||||
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
removeLeft : function()
|
||||
{
|
||||
if (this.hasSelection())
|
||||
{
|
||||
this.cursor = this.doc.remove(this.getSelectionRange());
|
||||
this.clearSelection();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.cursor.row == 0 && this.cursor.column == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rangeStart = {
|
||||
row: this.cursor.row,
|
||||
column: this.cursor.column + -1
|
||||
}
|
||||
if (rangeStart.column < 0)
|
||||
{
|
||||
rangeStart.row -= 1;
|
||||
rangeStart.column = this.doc.getLine(this.cursor.row-1).length;
|
||||
}
|
||||
this.cursor = this.doc.remove({start: rangeStart, end: this.cursor});
|
||||
}
|
||||
|
||||
this.renderer.updateCursor(this.cursor);
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
onCompositionStart : function()
|
||||
{
|
||||
this.renderer.showComposition(this.cursor);
|
||||
this.onTextInput(" ");
|
||||
},
|
||||
|
||||
onCompositionUpdate : function(text) {
|
||||
this.renderer.setCompositionText(text);
|
||||
},
|
||||
|
||||
onCompositionEnd : function() {
|
||||
this.renderer.hideComposition();
|
||||
this.removeLeft();
|
||||
},
|
||||
|
||||
getFirstVisibleRow : function() {
|
||||
return this.renderer.getFirstVisibleRow();
|
||||
},
|
||||
|
||||
getLastVisibleRow : function() {
|
||||
return this.renderer.getLastVisibleRow();
|
||||
},
|
||||
|
||||
getPageDownRow : function() {
|
||||
return this.renderer.getLastVisibleRow() - 1;
|
||||
},
|
||||
|
||||
getPageUpRow : function()
|
||||
{
|
||||
var firstRow = this.renderer.getFirstVisibleRow();
|
||||
var lastRow = this.renderer.getLastVisibleRow();
|
||||
|
||||
return firstRow - (lastRow-firstRow) + 1;
|
||||
},
|
||||
|
||||
scrollPageDown : function() {
|
||||
this.renderer.scrollToRow(this.getPageDownRow());
|
||||
},
|
||||
|
||||
scrollPageUp : function() {
|
||||
this.renderer.scrollToRow(this.getPageUpRow());
|
||||
},
|
||||
|
||||
navigateUp : function()
|
||||
{
|
||||
this.clearSelection();
|
||||
this.moveCursorUp();
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
navigateDown : function() {
|
||||
this.clearSelection();
|
||||
this.moveCursorDown();
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
navigateLeft : function()
|
||||
{
|
||||
if (this.hasSelection()) {
|
||||
var selectionStart = this.getSelectionRange().start;
|
||||
this.moveCursorTo(selectionStart.row, selectionStart.column);
|
||||
} else {
|
||||
this.moveCursorLeft();
|
||||
}
|
||||
this.clearSelection();
|
||||
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
navigateRight : function()
|
||||
{
|
||||
if (this.hasSelection()) {
|
||||
var selectionEnd = this.getSelectionRange().end;
|
||||
this.moveCursorTo(selectionEnd.row, selectionEnd.column);
|
||||
} else {
|
||||
this.moveCursorRight();
|
||||
}
|
||||
this.clearSelection();
|
||||
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
navigateLineStart : function()
|
||||
{
|
||||
this.clearSelection();
|
||||
this.moveCursorLineStart();
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
navigateLineEnd : function()
|
||||
{
|
||||
this.clearSelection();
|
||||
this.moveCursorLineEnd();
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
moveCursorUp : function() {
|
||||
this.moveCursorBy(-1, 0);
|
||||
},
|
||||
|
||||
moveCursorDown : function() {
|
||||
this.moveCursorBy(1, 0);
|
||||
},
|
||||
|
||||
moveCursorLeft : function()
|
||||
{
|
||||
if (this.cursor.column == 0) {
|
||||
if (this.cursor.row > 0) {
|
||||
this.moveCursorTo(this.cursor.row-1, this.doc.getLine(this.cursor.row-1).length);
|
||||
}
|
||||
} else {
|
||||
this.moveCursorBy(0, -1);
|
||||
}
|
||||
},
|
||||
|
||||
moveCursorRight : function()
|
||||
{
|
||||
if (this.cursor.column == this.doc.getLine(this.cursor.row).length) {
|
||||
if (this.cursor.row < this.doc.getLength()-1) {
|
||||
this.moveCursorTo(this.cursor.row+1, 0);
|
||||
}
|
||||
} else {
|
||||
this.moveCursorBy(0, 1);
|
||||
}
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
moveCursorLineStart : function()
|
||||
{
|
||||
this.moveCursorTo(this.cursor.row, 0);
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
moveCursorLineEnd : function() {
|
||||
this.moveCursorTo(this.cursor.row, this.doc.getLine(this.cursor.row).length);
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
moveCursorBy : function(rows, chars) {
|
||||
this.moveCursorTo(this.cursor.row+rows, this.cursor.column+chars);
|
||||
},
|
||||
|
||||
moveCursorTo : function(row, column)
|
||||
{
|
||||
if (row >= this.doc.getLength()) {
|
||||
this.cursor.row = this.doc.getLength()-1;
|
||||
this.cursor.column = this.doc.getLine(this.cursor.row).length;
|
||||
} else if (row < 0) {
|
||||
this.cursor.row = 0;
|
||||
this.cursor.column = 0;
|
||||
} else {
|
||||
this.cursor.row = row;
|
||||
this.cursor.column = Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column));
|
||||
}
|
||||
this.updateCursor();
|
||||
},
|
||||
|
||||
hasSelection : function() {
|
||||
return !!this.selectionLead;
|
||||
},
|
||||
|
||||
setSelectionAnchor : function(row, column)
|
||||
{
|
||||
this.clearSelection();
|
||||
|
||||
this.selectionAnchor = {
|
||||
row: Math.min(this.doc.getLength()-1, Math.max(0, row)),
|
||||
column: Math.min(this.doc.getLine(this.cursor.row).length, Math.max(0, column))
|
||||
};
|
||||
|
||||
this.selectionLead = null;
|
||||
},
|
||||
|
||||
getSelectionRange : function()
|
||||
{
|
||||
var anchor = this.selectionAnchor;
|
||||
var lead = this.selectionLead;
|
||||
|
||||
if (!anchor) {
|
||||
return null;
|
||||
} else {
|
||||
if (anchor.row > lead.row || (anchor.row == lead.row && anchor.column > lead.column)) {
|
||||
return {
|
||||
start: lead,
|
||||
end: anchor
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
start: anchor,
|
||||
end: lead
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
clearSelection : function()
|
||||
{
|
||||
this.selectionLead = null;
|
||||
this.selectionAnchor = null;
|
||||
|
||||
if (this.selection) {
|
||||
this.renderer.removeMarker(this.selection);
|
||||
this.selection = null;
|
||||
}
|
||||
},
|
||||
|
||||
selectAll : function()
|
||||
{
|
||||
var lastRow = this.doc.getLength()-1;
|
||||
this.setSelectionAnchor(lastRow, this.doc.getLine(lastRow).length);
|
||||
|
||||
this._moveSelection(function() {
|
||||
this.moveCursorTo(0, 0);
|
||||
});
|
||||
},
|
||||
|
||||
_moveSelection : function(mover)
|
||||
{
|
||||
if (!this.selectionAnchor) {
|
||||
this.selectionAnchor = {
|
||||
row: this.cursor.row,
|
||||
column: this.cursor.column
|
||||
}
|
||||
}
|
||||
|
||||
mover.call(this);
|
||||
|
||||
this.selectionLead = {
|
||||
row: this.cursor.row,
|
||||
column: this.cursor.column
|
||||
}
|
||||
|
||||
if (this.selection) {
|
||||
this.renderer.removeMarker(this.selection);
|
||||
}
|
||||
this.selection = this.renderer.addMarker(this.getSelectionRange(), "selection");
|
||||
this.renderer.scrollCursorIntoView();
|
||||
},
|
||||
|
||||
selectUp : function() {
|
||||
this._moveSelection(this.moveCursorUp);
|
||||
},
|
||||
|
||||
selectDown : function() {
|
||||
this._moveSelection(this.moveCursorDown);
|
||||
},
|
||||
|
||||
selectRight : function() {
|
||||
this._moveSelection(this.moveCursorRight);
|
||||
},
|
||||
|
||||
selectLeft : function() {
|
||||
this._moveSelection(this.moveCursorLeft);
|
||||
},
|
||||
|
||||
selectLineStart : function() {
|
||||
this._moveSelection(this.moveCursorLineStart);
|
||||
},
|
||||
|
||||
selectLineEnd : function() {
|
||||
this._moveSelection(this.moveCursorLineEnd);
|
||||
},
|
||||
|
||||
selectPageDown : function()
|
||||
{
|
||||
var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow();
|
||||
var row = this.getPageDownRow() + Math.round(visibleRows / 2);
|
||||
|
||||
this.scrollPageDown();
|
||||
|
||||
this._moveSelection(function() {
|
||||
this.moveCursorTo(row, this.cursor.column);
|
||||
});
|
||||
},
|
||||
|
||||
selectPageUp : function()
|
||||
{
|
||||
var visibleRows = this.getLastVisibleRow() - this.getFirstVisibleRow();
|
||||
var row = this.getPageUpRow() + Math.round(visibleRows / 2);
|
||||
|
||||
this.scrollPageUp();
|
||||
|
||||
this._moveSelection(function() {
|
||||
this.moveCursorTo(row, this.cursor.column);
|
||||
});
|
||||
},
|
||||
|
||||
selectCurrentLine : function()
|
||||
{
|
||||
this.setSelectionAnchor(this.cursor.row, 0);
|
||||
this._moveSelection(function() {
|
||||
this.moveCursorTo(this.cursor.row+1, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
22
src/GutterLayer.js
Normal file
22
src/GutterLayer.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
function GutterLayer(parentEl)
|
||||
{
|
||||
this.element = document.createElement("div");
|
||||
this.element.className = "layer gutter-layer";
|
||||
parentEl.appendChild(this.element);
|
||||
}
|
||||
|
||||
GutterLayer.prototype.update = function(config)
|
||||
{
|
||||
var html = [];
|
||||
for (var i=config.firstRow; i<=config.lastRow; i++)
|
||||
{
|
||||
html.push(
|
||||
"<div class='gutter-cell' style='height:" + config.lineHeight + "px;'>",
|
||||
i,
|
||||
"</div>"
|
||||
);
|
||||
html.push("</div>");
|
||||
}
|
||||
|
||||
this.element.innerHTML = html.join("");
|
||||
};
|
||||
137
src/JavaScript.js
Normal file
137
src/JavaScript.js
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
(function() {
|
||||
|
||||
window.JavaScript = {};
|
||||
|
||||
var keywords = {
|
||||
"break" : 1,
|
||||
"case" : 1,
|
||||
"catch" : 1,
|
||||
"continue" : 1,
|
||||
"default" : 1,
|
||||
"delete" : 1,
|
||||
"do" : 1,
|
||||
"else" : 1,
|
||||
"finally" : 1,
|
||||
"for" : 1,
|
||||
"function" : 1,
|
||||
"if" : 1,
|
||||
"in" : 1,
|
||||
"instanceof" : 1,
|
||||
"new" : 1,
|
||||
"return" : 1,
|
||||
"switch" : 1,
|
||||
"throw" : 1,
|
||||
"try" : 1,
|
||||
"typeof" : 1,
|
||||
"var" : 1,
|
||||
"while" : 1,
|
||||
"with" : 1
|
||||
};
|
||||
|
||||
// regexp must not have capturing parentheses
|
||||
// regexps are ordered -> the first match is used
|
||||
|
||||
JavaScript.RULES = {
|
||||
start :
|
||||
[
|
||||
{
|
||||
token: "comment",
|
||||
regex: "\\/\\/.*$"
|
||||
},
|
||||
{
|
||||
token: "comment", // multi line comment in one line
|
||||
regex: "\\/\\*.*?\\*\\/"
|
||||
},
|
||||
{
|
||||
token: "comment", // multi line comment start
|
||||
regex: "\\/\\*.*$",
|
||||
next: "comment"
|
||||
},
|
||||
{
|
||||
token: "string", // single line
|
||||
regex: '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
|
||||
},
|
||||
{
|
||||
token: "string", // multi line string start
|
||||
regex: '["].*\\\\$',
|
||||
next: "qqstring"
|
||||
},
|
||||
{
|
||||
token: "string", // single line
|
||||
regex: "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
|
||||
},
|
||||
{
|
||||
token: "string", // multi line string start
|
||||
regex: "['].*\\\\$",
|
||||
next: "qstring"
|
||||
},
|
||||
{
|
||||
token: "number", // hex
|
||||
regex: "0[xX][0-9a-fA-F]+\\b"
|
||||
},
|
||||
{
|
||||
token: "number", // float
|
||||
regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
|
||||
},
|
||||
{
|
||||
token: function(value)
|
||||
{
|
||||
if (keywords[value]) {
|
||||
return "keyword";
|
||||
} else {
|
||||
return "identifier"
|
||||
}
|
||||
},
|
||||
regex: "[a-zA-Z_][a-zA-Z0-9_]*\\b"
|
||||
},
|
||||
{
|
||||
token: function(value) {
|
||||
//return parens[value];
|
||||
return "text";
|
||||
},
|
||||
regex: "[\\[\\]\\(\\)\\{\\}]"
|
||||
},
|
||||
{
|
||||
token: "text",
|
||||
regex: "\\s+"
|
||||
}
|
||||
],
|
||||
"comment":
|
||||
[
|
||||
{
|
||||
token: "comment", // closing comment
|
||||
regex: ".*?\\*\\/",
|
||||
next: "start"
|
||||
},
|
||||
{
|
||||
token: "comment", // comment spanning whole line
|
||||
regex: ".+"
|
||||
}
|
||||
],
|
||||
"qqstring":
|
||||
[
|
||||
{
|
||||
token: "string",
|
||||
regex: '(?:(?:\\\\.)|(?:[^"\\\\]))*?"',
|
||||
next: "start"
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
regex: '.+'
|
||||
}
|
||||
],
|
||||
"qstring":
|
||||
[
|
||||
{
|
||||
token: "string",
|
||||
regex: "(?:(?:\\\\.)|(?:[^'\\\\]))*?'",
|
||||
next: "start"
|
||||
},
|
||||
{
|
||||
token: "string",
|
||||
regex: '.+'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
})();
|
||||
97
src/MarkerLayer.js
Normal file
97
src/MarkerLayer.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
function MarkerLayer(parentEl)
|
||||
{
|
||||
this.element = document.createElement("div");
|
||||
this.element.className = "layer marker-layer";
|
||||
parentEl.appendChild(this.element);
|
||||
|
||||
this.markers = {};
|
||||
this._markerId = 1;
|
||||
}
|
||||
|
||||
MarkerLayer.prototype.addMarker = function(range, clazz)
|
||||
{
|
||||
var id = this._markerId++;
|
||||
this.markers[id] = {
|
||||
range: range,
|
||||
type: "line",
|
||||
clazz: clazz
|
||||
};
|
||||
|
||||
this.update();
|
||||
return id;
|
||||
};
|
||||
|
||||
MarkerLayer.prototype.removeMarker = function(markerId)
|
||||
{
|
||||
var marker = this.markers[markerId];
|
||||
if (marker) {
|
||||
delete(this.markers[markerId]);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
MarkerLayer.prototype.update = function(config)
|
||||
{
|
||||
var config = config || this.config;
|
||||
if (!config) return;
|
||||
|
||||
this.config = config;
|
||||
|
||||
var html = [];
|
||||
for (var key in this.markers)
|
||||
{
|
||||
var marker = this.markers[key];
|
||||
var range = marker.range;
|
||||
|
||||
if (range.start.row !== range.end.row)
|
||||
{
|
||||
if (range.start.row >= config.firstRow && range.start.row <= config.lastRow)
|
||||
{
|
||||
html.push(
|
||||
"<div class='", marker.clazz, "' style='",
|
||||
"height:", config.lineHeight, "px;",
|
||||
"width:", Math.round(config.width - (range.start.column * config.characterWidth)), "px;",
|
||||
"top:", (range.start.row-config.firstRow) * config.lineHeight, "px;",
|
||||
"left:", Math.round(range.start.column * config.characterWidth), "px;'></div>"
|
||||
);
|
||||
}
|
||||
|
||||
if (range.end.row >= config.firstRow && range.end.row <= config.lastRow)
|
||||
{
|
||||
html.push(
|
||||
"<div class='", marker.clazz, "' style='",
|
||||
"height:", config.lineHeight, "px;",
|
||||
"top:", (range.end.row-config.firstRow) * config.lineHeight, "px;",
|
||||
"width:", Math.round(range.end.column * config.characterWidth), "px;'></div>"
|
||||
);
|
||||
};
|
||||
|
||||
for (var row=range.start.row+1; row < range.end.row; row++)
|
||||
{
|
||||
if (row >= config.firstRow && row <= config.lastRow)
|
||||
{
|
||||
html.push(
|
||||
"<div class='", marker.clazz, "' style='",
|
||||
"height:", config.lineHeight, "px;",
|
||||
"width:", config.width, "px;",
|
||||
"top:", (row-config.firstRow) * config.lineHeight, "px;'></div>"
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (range.start.row >= config.firstRow && range.start.row <= config.lastRow)
|
||||
{
|
||||
html.push(
|
||||
"<div class='", marker.clazz, "' style='",
|
||||
"height:", config.lineHeight, "px;",
|
||||
"width:", Math.round((range.end.column - range.start.column) * config.characterWidth), "px;",
|
||||
"top:", (range.start.row-config.firstRow) * config.lineHeight, "px;",
|
||||
"left:", Math.round(range.start.column * config.characterWidth), "px;'></div>"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.element.innerHTML = html.join("");
|
||||
};
|
||||
222
src/TextDocument.js
Normal file
222
src/TextDocument.js
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
function TextDocument(text)
|
||||
{
|
||||
this.lines = this._split(text);
|
||||
this.modified = true;
|
||||
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
TextDocument.prototype =
|
||||
{
|
||||
_split : function(text) {
|
||||
return text.split(/[\n\r]/)
|
||||
},
|
||||
|
||||
addChangeListener : function(listener) {
|
||||
this.listeners.push(listener);
|
||||
},
|
||||
|
||||
fireChangeEvent : function(firstRow, lastRow)
|
||||
{
|
||||
for (var i=0; i < this.listeners.length; i++) {
|
||||
this.listeners[i](firstRow, lastRow);
|
||||
};
|
||||
},
|
||||
|
||||
getWidth : function()
|
||||
{
|
||||
if (this.modified)
|
||||
{
|
||||
this.modified = false;
|
||||
|
||||
var lines = this.lines;
|
||||
var longestLine = 0;
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
longestLine = Math.max(longestLine, lines[i].length);
|
||||
}
|
||||
this.width = longestLine;
|
||||
}
|
||||
return this.width;
|
||||
},
|
||||
|
||||
getLine : function(row) {
|
||||
return this.lines[row] || "";
|
||||
},
|
||||
|
||||
keywords : {
|
||||
"break" : 1,
|
||||
"case" : 1,
|
||||
"catch" : 1,
|
||||
"continue" : 1,
|
||||
"default" : 1,
|
||||
"delete" : 1,
|
||||
"do" : 1,
|
||||
"else" : 1,
|
||||
"finally" : 1,
|
||||
"for" : 1,
|
||||
"function" : 1,
|
||||
"if" : 1,
|
||||
"in" : 1,
|
||||
"instanceof" : 1,
|
||||
"new" : 1,
|
||||
"return" : 1,
|
||||
"switch" : 1,
|
||||
"throw" : 1,
|
||||
"try" : 1,
|
||||
"typeof" : 1,
|
||||
"var" : 1,
|
||||
"while" : 1,
|
||||
"with" : 1
|
||||
},
|
||||
|
||||
getLineTokens : function(row)
|
||||
{
|
||||
var tokens = [];
|
||||
|
||||
var re = /(?:(\s+)|("[^"]*")|('[^']*')|([\[\]\(\)\{\}])|([a-zA-Z_][a-zA-Z0-9_]*)|(\/\/.*)|(.))/g
|
||||
re.lastIndex = 0;
|
||||
|
||||
var match;
|
||||
var line = this.getLine(row);
|
||||
while (match = re.exec(line))
|
||||
{
|
||||
var token = {
|
||||
type: "text",
|
||||
value: match[0]
|
||||
}
|
||||
|
||||
if (match[2] || match[3]) {
|
||||
token.type = "string";
|
||||
} else if (match[5] && this.keywords[match[5]]) {
|
||||
token.type = "keyword";
|
||||
} else if (match[6]) {
|
||||
token.type = "comment";
|
||||
}
|
||||
|
||||
tokens.push(token);
|
||||
};
|
||||
|
||||
return tokens;
|
||||
},
|
||||
|
||||
getLength : function() {
|
||||
return this.lines.length;
|
||||
},
|
||||
|
||||
getTextRange : function(range)
|
||||
{
|
||||
if (range.start.row == range.end.row) {
|
||||
return this.lines[range.start.row].substring(range.start.column, range.end.column);
|
||||
} else {
|
||||
var lines = [];
|
||||
lines.push(this.lines[range.start.row].substring(range.start.column));
|
||||
lines.push.apply(lines, this.lines.slice(range.start.row+1, range.end.row));
|
||||
lines.push(this.lines[range.end.row].substring(0, range.end.column));
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
},
|
||||
|
||||
insert : function(position, text)
|
||||
{
|
||||
var end = this._insert(position, text);
|
||||
this.fireChangeEvent(
|
||||
position.row,
|
||||
position.row == end.row ? position.row : undefined
|
||||
);
|
||||
return end;
|
||||
},
|
||||
|
||||
_insert : function(position, text)
|
||||
{
|
||||
this.modified = true;
|
||||
|
||||
var newLines = this._split(text);
|
||||
|
||||
if (text == "\n")
|
||||
{
|
||||
var line = this.lines[position.row] || "";
|
||||
this.lines[position.row] = line.substring(0, position.column);
|
||||
this.lines.splice(position.row+1, 0, line.substring(position.column));
|
||||
|
||||
return {
|
||||
row: position.row + 1,
|
||||
column: 0
|
||||
};
|
||||
}
|
||||
else if (newLines.length == 1)
|
||||
{
|
||||
var line = this.lines[position.row] || "";
|
||||
this.lines[position.row] = line.substring(0, position.column) + text + line.substring(position.column);
|
||||
|
||||
return {
|
||||
row: position.row,
|
||||
column: position.column+text.length
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var line = this.lines[position.row] || "";
|
||||
|
||||
this.lines[position.row] = line.substring(0, position.column) + newLines[0];
|
||||
this.lines[position.row+1] = newLines[newLines.length-1] + line.substring(position.column);
|
||||
|
||||
if (newLines.length > 2)
|
||||
{
|
||||
var args = [position.row + 1, 0]
|
||||
args.push.apply(args, newLines.slice(1, -1));
|
||||
this.lines.splice.apply(this.lines, args);
|
||||
}
|
||||
|
||||
return {
|
||||
row: position.row + newLines.length - 1,
|
||||
column: newLines[newLines.length-1].length
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
remove : function(range)
|
||||
{
|
||||
var end = this._remove(range);
|
||||
|
||||
this.fireChangeEvent(
|
||||
range.start.row,
|
||||
range.end.row == range.start.row ? range.start.row : undefined
|
||||
);
|
||||
return end;
|
||||
},
|
||||
|
||||
_remove : function(range)
|
||||
{
|
||||
this.modified = true;
|
||||
|
||||
var firstRow = range.start.row;
|
||||
var lastRow = range.end.row;
|
||||
|
||||
var row =
|
||||
this.lines[firstRow].substring(0, range.start.column) +
|
||||
this.lines[lastRow].substring(range.end.column);
|
||||
|
||||
this.lines.splice(firstRow, lastRow-firstRow+1, row);
|
||||
|
||||
return range.start;
|
||||
},
|
||||
|
||||
replace : function(range, text)
|
||||
{
|
||||
this._remove(range);
|
||||
if (text) {
|
||||
var end = this._insert(range.start, text);
|
||||
} else {
|
||||
end = range.start;
|
||||
}
|
||||
|
||||
var lastRemoved = range.end.column == 0 ? range.end.column-1 : range.end.column;
|
||||
this.fireChangeEvent(
|
||||
range.start.row,
|
||||
lastRemoved == end.row ? lastRemoved : undefined
|
||||
);
|
||||
|
||||
return end;
|
||||
}
|
||||
}
|
||||
82
src/TextInput.js
Normal file
82
src/TextInput.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
function TextInput(parentNode, host) {
|
||||
|
||||
var text = document.createElement("textarea");
|
||||
var style = text.style;
|
||||
style.position = "absolute";
|
||||
style.left = "-10000px";
|
||||
style.top = "-10000px";
|
||||
parentNode.appendChild(text);
|
||||
|
||||
var inCompostion = false;
|
||||
|
||||
var onTextInput = function(e) {
|
||||
setTimeout(function() {
|
||||
if (!inCompostion) {
|
||||
if (text.value) host.onTextInput(text.value);
|
||||
text.value = "";
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
var onCompositionStart = function(e)
|
||||
{
|
||||
inCompostion = true;
|
||||
|
||||
if (text.value) host.onTextInput(text.value);
|
||||
text.value = "";
|
||||
|
||||
host.onCompositionStart();
|
||||
setTimeout(onCompositionUpdate, 0);
|
||||
}
|
||||
|
||||
var onCompositionUpdate = function() {
|
||||
host.onCompositionUpdate(text.value);
|
||||
}
|
||||
|
||||
var onCompositionEnd = function()
|
||||
{
|
||||
inCompostion = false;
|
||||
host.onCompositionEnd();
|
||||
onTextInput();
|
||||
}
|
||||
|
||||
var onCopy = function() {
|
||||
text.value = host.getCopyText();
|
||||
text.select();
|
||||
}
|
||||
|
||||
var onCut = function() {
|
||||
text.value = host.getCopyText();
|
||||
host.onCut();
|
||||
text.select();
|
||||
}
|
||||
|
||||
lib.addListener(text, "keypress", onTextInput, false);
|
||||
lib.addListener(text, "textInput", onTextInput, false);
|
||||
lib.addListener(text, "paste", onTextInput, false);
|
||||
lib.addListener(text, "propertychange", onTextInput, false);
|
||||
|
||||
lib.addListener(text, "copy", onCopy, false);
|
||||
lib.addListener(text, "cut", onCut, false);
|
||||
|
||||
lib.addListener(text, "compositionstart", onCompositionStart, false);
|
||||
lib.addListener(text, "compositionupdate", onCompositionUpdate, false);
|
||||
lib.addListener(text, "compositionend", onCompositionEnd, false);
|
||||
|
||||
lib.addListener(text, "blur", function() {
|
||||
host.onBlur();
|
||||
}, false);
|
||||
|
||||
lib.addListener(text, "focus", function() {
|
||||
host.onFocus();
|
||||
}, false);
|
||||
|
||||
|
||||
this.focus = function() {
|
||||
text.focus();
|
||||
}
|
||||
|
||||
this.blur = function() {
|
||||
this.blur();
|
||||
}
|
||||
};
|
||||
97
src/TextLayer.js
Normal file
97
src/TextLayer.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
function TextLayer(parentEl)
|
||||
{
|
||||
this.element = document.createElement("div");
|
||||
this.element.className = "layer text-layer";
|
||||
parentEl.appendChild(this.element);
|
||||
|
||||
this._measureSizes();
|
||||
}
|
||||
|
||||
TextLayer.prototype.setTokenizer = function(tokenizer) {
|
||||
this.tokenizer = tokenizer;
|
||||
};
|
||||
|
||||
TextLayer.prototype.getLineHeight = function() {
|
||||
return this.lineHeight;
|
||||
};
|
||||
|
||||
TextLayer.prototype.getCharacterWidth = function() {
|
||||
return this.characterWidth;
|
||||
};
|
||||
|
||||
TextLayer.prototype._measureSizes = function()
|
||||
{
|
||||
var measureNode = document.createElement("div");
|
||||
var style = measureNode.style;
|
||||
style.width = style.height = "auto";
|
||||
style.left = style.top = "-1000px";
|
||||
style.visibility = "hidden";
|
||||
style.position = "absolute";
|
||||
style.overflow = "visible";
|
||||
|
||||
measureNode.innerHTML = new Array(1000).join("Xy");
|
||||
this.element.appendChild(measureNode);
|
||||
|
||||
// in FF 3.6 monospace fonts can have a fixed sub pixel width.
|
||||
// that's why we have to measure many characters
|
||||
// Note: characterWidth can be a float!
|
||||
this.lineHeight = measureNode.offsetHeight;
|
||||
this.characterWidth = measureNode.offsetWidth / 2000;
|
||||
|
||||
this.element.removeChild(measureNode);
|
||||
};
|
||||
|
||||
TextLayer.prototype.updateLines = function(layerConfig, firstRow, lastRow)
|
||||
{
|
||||
var first = Math.max(firstRow, layerConfig.firstRow);
|
||||
var last = Math.min(lastRow, layerConfig.lastRow);
|
||||
|
||||
var lineElements = this.element.childNodes;
|
||||
|
||||
for (var i=first; i <= last; i++)
|
||||
{
|
||||
var html = [];
|
||||
this.renderLine(html, i);
|
||||
|
||||
var lineElement = lineElements[i-layerConfig.firstRow];
|
||||
lineElement.innerHTML = html.join("");
|
||||
};
|
||||
};
|
||||
|
||||
TextLayer.prototype.update = function(config)
|
||||
{
|
||||
var html = [];
|
||||
for (var i=config.firstRow; i<=config.lastRow; i++)
|
||||
{
|
||||
html.push(
|
||||
"<div class='line ",
|
||||
i % 2 == 0 ? "even" : "odd",
|
||||
"' style='height:" + this.lineHeight + "px;",
|
||||
"width:", config.width, "px'>"
|
||||
);
|
||||
this.renderLine(html, i),
|
||||
html.push("</div>");
|
||||
}
|
||||
|
||||
this.element.innerHTML = html.join("");
|
||||
};
|
||||
|
||||
TextLayer.prototype.renderLine = function(stringBuilder, row)
|
||||
{
|
||||
var tokens = this.tokenizer.getTokens(row);
|
||||
for (var i=0; i < tokens.length; i++)
|
||||
{
|
||||
var token = tokens[i];
|
||||
|
||||
var output = token.value.
|
||||
replace(/&/g, "&").
|
||||
replace(/</g, "<").
|
||||
replace(/\s/g, " ");
|
||||
|
||||
if (token.type !== "text") {
|
||||
stringBuilder.push("<span class='", token.type, "'>", output, "</span>");
|
||||
} else {
|
||||
stringBuilder.push(output);
|
||||
}
|
||||
};
|
||||
};
|
||||
77
src/Tokenizer.js
Normal file
77
src/Tokenizer.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
function Tokenizer(rules)
|
||||
{
|
||||
this.rules = rules;
|
||||
|
||||
this.regExps = {};
|
||||
for (var key in this.rules)
|
||||
{
|
||||
var state = this.rules[key];
|
||||
var ruleRegExps = [];
|
||||
|
||||
for (var i=0; i < state.length; i++) {
|
||||
ruleRegExps.push(state[i].regex);
|
||||
};
|
||||
|
||||
this.regExps[key] = new RegExp("(?:(" + ruleRegExps.join(")|(") + ")|(.))", "g");
|
||||
}
|
||||
};
|
||||
|
||||
Tokenizer.prototype.getLineTokens = function(line, startState)
|
||||
{
|
||||
var currentState = startState;
|
||||
var state = this.rules[currentState];
|
||||
var re = this.regExps[currentState];
|
||||
re.lastIndex = 0;
|
||||
|
||||
var match, tokens = [];
|
||||
|
||||
var lastIndex = 0;
|
||||
|
||||
while (match = re.exec(line))
|
||||
{
|
||||
var token = {
|
||||
type: "text",
|
||||
value: match[0]
|
||||
}
|
||||
|
||||
if (re.lastIndex == lastIndex) {
|
||||
throw new Error("tokenizer error")
|
||||
}
|
||||
lastIndex = re.lastIndex;
|
||||
|
||||
//console.log(match);
|
||||
|
||||
for (var i=0; i < state.length; i++)
|
||||
{
|
||||
if (match[i+1])
|
||||
{
|
||||
if (typeof state[i].token == "function") {
|
||||
token.type = state[i].token(match[0]);
|
||||
} else {
|
||||
token.type = state[i].token;
|
||||
}
|
||||
|
||||
if (state[i].next && state[i].next !== currentState)
|
||||
{
|
||||
currentState = state[i].next;
|
||||
var state = this.rules[currentState];
|
||||
var lastIndex = re.lastIndex;
|
||||
|
||||
var re = this.regExps[currentState];
|
||||
re.lastIndex = lastIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
tokens.push(token);
|
||||
};
|
||||
|
||||
//console.log(tokens, currentState)
|
||||
|
||||
return {
|
||||
tokens: tokens,
|
||||
state: currentState
|
||||
}
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue