AtteanX-Endpoint
view release on metacpan or search on metacpan
share/endpoint/www/js/editor.js view on Meta::CPAN
end = select.cursorPos(this.container, false);
if (!start || !end) return;
end = this.replaceRange(start, end, text);
select.setCursorPos(this.container, end);
webkitLastLineHack(this.container);
},
cursorCoords: function(start) {
var sel = select.cursorPos(this.container, start);
if (!sel) return null;
var off = sel.offset, node = sel.node, self = this;
function measureFromNode(node, xOffset) {
var y = -(document.body.scrollTop || document.documentElement.scrollTop || 0),
x = -(document.body.scrollLeft || document.documentElement.scrollLeft || 0) + xOffset;
forEach([node, window.frameElement], function(n) {
while (n) {x += n.offsetLeft; y += n.offsetTop;n = n.offsetParent;}
});
return {x: x, y: y, yBot: y + node.offsetHeight};
}
function withTempNode(text, f) {
var node = document.createElement("SPAN");
node.appendChild(document.createTextNode(text));
try {return f(node);}
finally {if (node.parentNode) node.parentNode.removeChild(node);}
}
while (off) {
node = node ? node.nextSibling : this.container.firstChild;
var txt = nodeText(node);
if (off < txt.length)
return withTempNode(txt.substr(0, off), function(tmp) {
tmp.style.position = "absolute"; tmp.style.visibility = "hidden";
tmp.className = node.className;
self.container.appendChild(tmp);
return measureFromNode(node, tmp.offsetWidth);
});
off -= txt.length;
}
if (node && isSpan(node))
return measureFromNode(node, node.offsetWidth);
else if (node && node.nextSibling && isSpan(node.nextSibling))
return measureFromNode(node.nextSibling, 0);
else
return withTempNode("\u200b", function(tmp) {
if (node) node.parentNode.insertBefore(tmp, node.nextSibling);
else self.container.insertBefore(tmp, self.container.firstChild);
return measureFromNode(tmp, 0);
});
},
reroutePasteEvent: function() {
if (this.capturingPaste || window.opera || (gecko && gecko >= 20101026)) return;
this.capturingPaste = true;
var te = window.frameElement.CodeMirror.textareaHack;
parent.focus();
te.value = "";
te.focus();
var self = this;
this.parent.setTimeout(function() {
self.capturingPaste = false;
window.focus();
if (self.selectionSnapshot) // IE hack
window.select.setBookmark(self.container, self.selectionSnapshot);
var text = te.value;
if (text) {
self.replaceSelection(text);
select.scrollToCursor(self.container);
}
}, 10);
},
replaceRange: function(from, to, text) {
var lines = asEditorLines(text);
lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
var lastLine = lines[lines.length - 1];
lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
var end = this.history.nodeAfter(to.node);
this.history.push(from.node, end, lines);
return {node: this.history.nodeBefore(end),
offset: lastLine.length};
},
getSearchCursor: function(string, fromCursor, caseFold) {
return new SearchCursor(this, string, fromCursor, caseFold);
},
// Re-indent the whole buffer
reindent: function() {
if (this.container.firstChild)
this.indentRegion(null, this.container.lastChild);
},
reindentSelection: function(direction) {
if (!select.somethingSelected()) {
this.indentAtCursor(direction);
}
else {
var start = select.selectionTopNode(this.container, true),
end = select.selectionTopNode(this.container, false);
if (start === false || end === false) return;
this.indentRegion(start, end, direction);
}
},
grabKeys: function(eventHandler, filter) {
this.frozen = eventHandler;
this.keyFilter = filter;
},
ungrabKeys: function() {
this.frozen = "leave";
},
setParser: function(name, parserConfig) {
Editor.Parser = window[name];
parserConfig = parserConfig || this.options.parserConfig;
if (parserConfig && Editor.Parser.configure)
Editor.Parser.configure(parserConfig);
if (this.container.firstChild) {
share/endpoint/www/js/editor.js view on Meta::CPAN
else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end
if (this.end()) event.stop();
}
// Only in Firefox is the default behavior for PgUp/PgDn correct.
else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp
if (this.pageUp()) event.stop();
}
else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgDn
if (this.pageDown()) event.stop();
}
else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
this.highlightParens(event.shiftKey, true);
event.stop();
}
else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
var cursor = select.selectionTopNode(this.container);
if (cursor === false || !this.container.firstChild) return;
if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
else {
var end = endOfLine(cursor, this.container);
select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
}
event.stop();
}
else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
select.scrollToNode(this.history.redo());
event.stop();
}
else if (code == 90 || (safari && code == 8)) { // Z, backspace
select.scrollToNode(this.history.undo());
event.stop();
}
else if (code == 83 && this.options.saveFunction) { // S
this.options.saveFunction();
event.stop();
}
else if (code == 86 && !mac) { // V
this.reroutePasteEvent();
}
}
},
// Check for characters that should re-indent the current line,
// and prevent Opera from handling enter and tab anyway.
keyPress: function(event) {
var electric = this.options.electricChars && Editor.Parser.electricChars, self = this;
// Hack for Opera, and Firefox on OS X, in which stopping a
// keydown event does not prevent the associated keypress event
// from happening, so we have to cancel enter and tab again
// here.
if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode || event.code, event))) ||
event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
(event.code == 32 && event.shiftKey && this.options.tabMode == "default"))
event.stop();
else if (mac && (event.ctrlKey || event.metaKey) && event.character == "v") {
this.reroutePasteEvent();
}
else if (electric && electric.indexOf(event.character) != -1)
this.parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
// Work around a bug where pressing backspace at the end of a
// line, or delete at the start, often causes the cursor to jump
// to the start of the line in Opera 10.60.
else if (brokenOpera) {
if (event.code == 8) { // backspace
var sel = select.selectionTopNode(this.container), self = this,
next = sel ? sel.nextSibling : this.container.firstChild;
if (sel !== false && next && isBR(next))
this.parent.setTimeout(function(){
if (select.selectionTopNode(self.container) == next)
select.focusAfterNode(next.previousSibling, self.container);
}, 20);
}
else if (event.code == 46) { // delete
var sel = select.selectionTopNode(this.container), self = this;
if (sel && isBR(sel)) {
this.parent.setTimeout(function(){
if (select.selectionTopNode(self.container) != sel)
select.focusAfterNode(sel, self.container);
}, 20);
}
}
}
// In 533.* WebKit versions, when the document is big, typing
// something at the end of a line causes the browser to do some
// kind of stupid heavy operation, creating delays of several
// seconds before the typed characters appear. This very crude
// hack inserts a temporary zero-width space after the cursor to
// make it not be at the end of the line.
else if (slowWebkit) {
var sel = select.selectionTopNode(this.container),
next = sel ? sel.nextSibling : this.container.firstChild;
// Doesn't work on empty lines, for some reason those always
// trigger the delay.
if (sel && next && isBR(next) && !isBR(sel)) {
var cheat = document.createTextNode("\u200b");
this.container.insertBefore(cheat, next);
this.parent.setTimeout(function() {
if (cheat.nodeValue == "\u200b") removeElement(cheat);
else cheat.nodeValue = cheat.nodeValue.replace("\u200b", "");
}, 20);
}
}
// Magic incantation that works abound a webkit bug when you
// can't type on a blank line following a line that's wider than
// the window.
if (webkit && !this.options.textWrapping)
setTimeout(function () {
var node = select.selectionTopNode(self.container, true);
if (node && node.nodeType == 3 && node.previousSibling && isBR(node.previousSibling)
&& node.nextSibling && isBR(node.nextSibling))
node.parentNode.replaceChild(document.createElement("BR"), node.previousSibling);
}, 50);
},
// Mark the node at the cursor dirty when a non-safe key is
// released.
keyUp: function(event) {
this.cursorActivity(isSafeKey(event.keyCode));
},
// Indent the line following a given <br>, or null for the first
// line. If given a <br> element, this must have been highlighted
// so that it has an indentation method. Returns the whitespace
// element that has been modified or created (if any).
indentLineAfter: function(start, direction) {
function whiteSpaceAfter(node) {
var ws = node ? node.nextSibling : self.container.firstChild;
if (!ws || !hasClass(ws, "whitespace")) return null;
return ws;
}
// whiteSpace is the whitespace span at the start of the line,
// or null if there is no such node.
var self = this, whiteSpace = whiteSpaceAfter(start);
var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
if (direction == "keep") {
if (start) {
var prevWS = whiteSpaceAfter(startOfLine(start.previousSibling))
if (prevWS) newIndent = prevWS.currentText.length;
}
}
else {
// Sometimes the start of the line can influence the correct
// indentation, so we retrieve it.
var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
// Ask the lexical context for the correct indentation, and
// compute how much this differs from the current indentation.
if (direction != null && this.options.tabMode == "shift")
newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
else if (start)
newIndent = start.indentation(nextChars, curIndent, direction);
else if (Editor.Parser.firstIndentation)
newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction);
}
var indentDiff = newIndent - curIndent;
// If there is too much, this is just a matter of shrinking a span.
if (indentDiff < 0) {
if (newIndent == 0) {
if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild || firstText, 0);
removeElement(whiteSpace);
whiteSpace = null;
}
share/endpoint/www/js/editor.js view on Meta::CPAN
var cur = select.selectionTopNode(this.container, true), start = cur;
if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
return false;
while (cur && !isBR(cur)) cur = cur.previousSibling;
var next = cur ? cur.nextSibling : this.container.firstChild;
if (next && next != start && next.isPart && hasClass(next, "whitespace"))
select.focusAfterNode(next, this.container);
else
select.focusAfterNode(cur, this.container);
select.scrollToCursor(this.container);
return true;
},
// Some browsers (Opera) don't manage to handle the end key
// properly in the face of vertical scrolling.
end: function() {
var cur = select.selectionTopNode(this.container, true);
if (cur === false) return false;
cur = endOfLine(cur, this.container);
if (!cur) return false;
select.focusAfterNode(cur.previousSibling, this.container);
select.scrollToCursor(this.container);
return true;
},
pageUp: function() {
var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
if (line === false || scrollAmount === false) return false;
// Try to keep one line on the screen.
scrollAmount -= 2;
for (var i = 0; i < scrollAmount; i++) {
line = this.prevLine(line);
if (line === false) break;
}
if (i == 0) return false; // Already at first line
select.setCursorPos(this.container, {node: line, offset: 0});
select.scrollToCursor(this.container);
return true;
},
pageDown: function() {
var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
if (line === false || scrollAmount === false) return false;
// Try to move to the last line of the current page.
scrollAmount -= 2;
for (var i = 0; i < scrollAmount; i++) {
var nextLine = this.nextLine(line);
if (nextLine === false) break;
line = nextLine;
}
if (i == 0) return false; // Already at last line
select.setCursorPos(this.container, {node: line, offset: 0});
select.scrollToCursor(this.container);
return true;
},
// Delay (or initiate) the next paren highlight event.
scheduleParenHighlight: function() {
if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
var self = this;
this.parenEvent = this.parent.setTimeout(function(){self.highlightParens();}, 300);
},
// Take the token before the cursor. If it contains a character in
// '()[]{}', search for the matching paren/brace/bracket, and
// highlight them in green for a moment, or red if no proper match
// was found.
highlightParens: function(jump, fromKey) {
var self = this;
// give the relevant nodes a colour.
function highlight(node, ok) {
if (!node) return;
if (self.options.markParen) {
self.options.markParen(node, ok);
}
else {
node.style.fontWeight = "bold";
node.style.color = ok ? "#8F8" : "#F88";
}
}
function unhighlight(node) {
if (!node) return;
if (self.options.unmarkParen) {
self.options.unmarkParen(node);
}
else {
node.style.fontWeight = "";
node.style.color = "";
}
}
if (!fromKey && self.highlighted) {
unhighlight(self.highlighted[0]);
unhighlight(self.highlighted[1]);
}
if (!window || !window.parent || !window.select) return;
// Clear the event property.
if (this.parenEvent) this.parent.clearTimeout(this.parenEvent);
this.parenEvent = null;
// Extract a 'paren' from a piece of text.
function paren(node) {
if (node.currentText) {
var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
return match && match[1];
}
}
// Determine the direction a paren is facing.
function forward(ch) {
return /[\(\[\{]/.test(ch);
}
var ch, cursor = select.selectionTopNode(this.container, true);
if (!cursor || !this.highlightAtCursor()) return;
cursor = select.selectionTopNode(this.container, true);
if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
return;
// We only look for tokens with the same className.
var className = cursor.className, dir = forward(ch), match = matching[ch];
// Since parts of the document might not have been properly
// highlighted, and it is hard to know in advance which part we
// have to scan, we just try, and when we find dirty nodes we
// abort, parse them, and re-try.
function tryFindMatch() {
var stack = [], ch, ok = true;
for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
if (forward(ch) == dir)
stack.push(ch);
else if (!stack.length)
ok = false;
else if (stack.pop() != matching[ch])
ok = false;
if (!stack.length) break;
}
else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
return {node: runner, status: "dirty"};
}
}
return {node: runner, status: runner && ok};
}
while (true) {
var found = tryFindMatch();
if (found.status == "dirty") {
this.highlight(found.node, endOfLine(found.node));
// Needed because in some corner cases a highlight does not
// reach a node.
found.node.dirty = false;
continue;
}
else {
highlight(cursor, found.status);
highlight(found.node, found.status);
if (fromKey)
self.parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
else
self.highlighted = [cursor, found.node];
if (jump && found.node)
select.focusAfterNode(found.node.previousSibling, this.container);
break;
}
}
},
// Adjust the amount of whitespace at the start of the line that
// the cursor is on so that it is indented properly.
indentAtCursor: function(direction) {
if (!this.container.firstChild) return;
// The line has to have up-to-date lexical information, so we
// highlight it first.
if (!this.highlightAtCursor()) return;
var cursor = select.selectionTopNode(this.container, false);
// If we couldn't determine the place of the cursor,
// there's nothing to indent.
if (cursor === false)
return;
select.markSelection();
this.indentLineAfter(startOfLine(cursor), direction);
select.selectMarked();
},
// Indent all lines whose start falls inside of the current
// selection.
indentRegion: function(start, end, direction) {
var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
if (!isBR(end)) end = endOfLine(end, this.container);
this.addDirtyNode(start);
do {
var next = endOfLine(current, this.container);
if (current) this.highlight(before, next, true);
this.indentLineAfter(current, direction);
before = current;
current = next;
} while (current != end);
select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
},
// Find the node that the cursor is in, mark it as dirty, and make
// sure a highlight pass is scheduled.
cursorActivity: function(safe) {
// pagehide event hack above
if (this.unloaded) {
window.document.designMode = "off";
window.document.designMode = "on";
this.unloaded = false;
}
if (internetExplorer) {
this.container.createTextRange().execCommand("unlink");
clearTimeout(this.saveSelectionSnapshot);
var self = this;
this.saveSelectionSnapshot = setTimeout(function() {
var snapshot = select.getBookmark(self.container);
if (snapshot) self.selectionSnapshot = snapshot;
}, 200);
}
var activity = this.options.cursorActivity;
if (!safe || activity) {
var cursor = select.selectionTopNode(this.container, false);
if (cursor === false || !this.container.firstChild) return;
cursor = cursor || this.container.firstChild;
if (activity) activity(cursor);
if (!safe) {
this.scheduleHighlight();
this.addDirtyNode(cursor);
}
}
},
reparseBuffer: function() {
forEach(this.container.childNodes, function(node) {node.dirty = true;});
if (this.container.firstChild)
this.addDirtyNode(this.container.firstChild);
},
// Add a node to the set of dirty nodes, if it isn't already in
// there.
addDirtyNode: function(node) {
node = node || this.container.firstChild;
if (!node) return;
for (var i = 0; i < this.dirty.length; i++)
if (this.dirty[i] == node) return;
if (node.nodeType != 3)
node.dirty = true;
this.dirty.push(node);
},
allClean: function() {
return !this.dirty.length;
},
// Cause a highlight pass to happen in options.passDelay
// milliseconds. Clear the existing timeout, if one exists. This
// way, the passes do not happen while the user is typing, and
// should as unobtrusive as possible.
scheduleHighlight: function() {
// Timeouts are routed through the parent window, because on
// some browsers designMode windows do not fire timeouts.
var self = this;
this.parent.clearTimeout(this.highlightTimeout);
this.highlightTimeout = this.parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
},
// Fetch one dirty node, and remove it from the dirty set.
getDirtyNode: function() {
while (this.dirty.length > 0) {
var found = this.dirty.pop();
// IE8 sometimes throws an unexplainable 'invalid argument'
// exception for found.parentNode
try {
// If the node has been coloured in the meantime, or is no
// longer in the document, it should not be returned.
while (found && found.parentNode != this.container)
found = found.parentNode;
if (found && (found.dirty || found.nodeType == 3))
return found;
} catch (e) {}
}
return null;
},
// Pick dirty nodes, and highlight them, until options.passTime
// milliseconds have gone by. The highlight method will continue
// to next lines as long as it finds dirty nodes. It returns
// information about the place where it stopped. If there are
// dirty nodes left after this function has spent all its lines,
// it shedules another highlight to finish the job.
highlightDirty: function(force) {
// Prevent FF from raising an error when it is firing timeouts
// on a page that's no longer loaded.
if (!window || !window.parent || !window.select) return false;
if (!this.options.readOnly) select.markSelection();
var start, endTime = force ? null : time() + this.options.passTime;
while ((time() < endTime || force) && (start = this.getDirtyNode())) {
var result = this.highlight(start, endTime);
if (result && result.node && result.dirty)
this.addDirtyNode(result.node.nextSibling);
}
if (!this.options.readOnly) select.selectMarked();
if (start) this.scheduleHighlight();
return this.dirty.length == 0;
},
// Creates a function that, when called through a timeout, will
// continuously re-parse the document.
documentScanner: function(passTime) {
var self = this, pos = null;
return function() {
// FF timeout weirdness workaround.
if (!window || !window.parent || !window.select) return;
// If the current node is no longer in the document... oh
// well, we start over.
if (pos && pos.parentNode != self.container)
pos = null;
select.markSelection();
var result = self.highlight(pos, time() + passTime, true);
select.selectMarked();
var newPos = result ? (result.node && result.node.nextSibling) : null;
pos = (pos == newPos) ? null : newPos;
self.delayScanning();
};
},
// Starts the continuous scanning process for this document after
// a given interval.
delayScanning: function() {
if (this.scanner) {
this.parent.clearTimeout(this.documentScan);
this.documentScan = this.parent.setTimeout(this.scanner, this.options.continuousScanning);
}
},
// The function that does the actual highlighting/colouring (with
// help from the parser and the DOM normalizer). Its interface is
// rather overcomplicated, because it is used in different
// situations: ensuring that a certain line is highlighted, or
// highlighting up to X milliseconds starting from a certain
// point. The 'from' argument gives the node at which it should
// start. If this is null, it will start at the beginning of the
// document. When a timestamp is given with the 'target' argument,
// it will stop highlighting at that time. If this argument holds
// a DOM node, it will highlight until it reaches that node. If at
// any time it comes across two 'clean' lines (no dirty nodes), it
// will stop, except when 'cleanLines' is true. maxBacktrack is
// the maximum number of lines to backtrack to find an existing
// parser instance. This is used to give up in situations where a
// highlight would take too long and freeze the browser interface.
highlight: function(from, target, cleanLines, maxBacktrack){
var container = this.container, self = this, active = this.options.activeTokens;
var endTime = (typeof target == "number" ? target : null);
if (!container.firstChild)
return false;
// Backtrack to the first node before from that has a partial
// parse stored.
while (from && (!from.parserFromHere || from.dirty)) {
if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
return false;
from = from.previousSibling;
}
// If we are at the end of the document, do nothing.
if (from && !from.nextSibling)
return false;
// Check whether a part (<span> node) and the corresponding token
// match.
function correctPart(token, part){
return !part.reduced && part.currentText == token.value && part.className == token.style;
}
// Shorten the text associated with a part by chopping off
// characters from the front. Note that only the currentText
// property gets changed. For efficiency reasons, we leave the
// nodeValue alone -- we set the reduced flag to indicate that
// this part must be replaced.
function shortenPart(part, minus){
part.currentText = part.currentText.substring(minus);
part.reduced = true;
}
// Create a part corresponding to a given token.
function tokenPart(token){
var part = makePartSpan(token.value);
part.className = token.style;
return part;
}
function maybeTouch(node) {
if (node) {
var old = node.oldNextSibling;
if (lineDirty || old === undefined || node.nextSibling != old)
share/endpoint/www/js/editor.js view on Meta::CPAN
parts.next();
}
else {
if (!isSpan(part))
throw "Parser out of sync. Expected SPAN.";
if (part.dirty)
lineDirty = true;
lineNodes++;
// If the part matches the token, we can leave it alone.
if (correctPart(token, part)){
part.dirty = false;
parts.next();
}
// Otherwise, we have to fix it.
else {
lineDirty = true;
// Insert the correct part.
var newPart = tokenPart(token);
container.insertBefore(newPart, part);
if (active) active(newPart, token, self);
var tokensize = token.value.length;
var offset = 0;
// Eat up parts until the text for this token has been
// removed, adjusting the stored selection info (see
// select.js) in the process.
while (tokensize > 0) {
part = parts.get();
var partsize = part.currentText.length;
select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
if (partsize > tokensize){
shortenPart(part, tokensize);
tokensize = 0;
}
else {
tokensize -= partsize;
offset += partsize;
parts.remove();
}
}
}
}
});
maybeTouch(from);
webkitLastLineHack(this.container);
// The function returns some status information that is used by
// hightlightDirty to determine whether and where it has to
// continue.
return {node: parts.getNonEmpty(),
dirty: lineDirty};
}
};
return Editor;
})();
addEventHandler(window, "load", function() {
var CodeMirror = window.frameElement.CodeMirror;
var e = CodeMirror.editor = new Editor(CodeMirror.options);
this.parent.setTimeout(method(CodeMirror, "init"), 0);
});
( run in 2.598 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )