diff options
author | goatchurch <devnull@localhost> | 2009-06-28 21:22:16 +0100 |
---|---|---|
committer | goatchurch <devnull@localhost> | 2009-06-28 21:22:16 +0100 |
commit | db5e315db022cd253a9f7224637228bf62449ec6 (patch) | |
tree | 31c8b19c459bcbb95fad3c7c4706bd504062b625 | |
parent | 4c87ce59d350b3d8c3458f538ea6c1b6e5cfa455 (diff) | |
download | troggle-db5e315db022cd253a9f7224637228bf62449ec6.tar.gz troggle-db5e315db022cd253a9f7224637228bf62449ec6.tar.bz2 troggle-db5e315db022cd253a9f7224637228bf62449ec6.zip |
[svn] forgot to add directory
-rw-r--r-- | media/CodeMirror-0.62/bigtest.html | 1296 | ||||
-rw-r--r-- | media/CodeMirror-0.62/htmltest.html | 53 | ||||
-rw-r--r-- | media/CodeMirror-0.62/index.html | 182 | ||||
-rw-r--r-- | media/CodeMirror-0.62/manual.html | 622 |
4 files changed, 2153 insertions, 0 deletions
diff --git a/media/CodeMirror-0.62/bigtest.html b/media/CodeMirror-0.62/bigtest.html new file mode 100644 index 0000000..04ebcda --- /dev/null +++ b/media/CodeMirror-0.62/bigtest.html @@ -0,0 +1,1296 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <script src="js/codemirror.js" type="text/javascript"></script> + <title>CodeMirror: JavaScript demonstration</title> + <link rel="stylesheet" type="text/css" href="css/docs.css"/> + </head> + <body style="padding: 20px;"> + +<p>This page demonstrates <a href="index.html">CodeMirror</a>'s +JavaScript parser. Note that the ugly buttons at the top are not are +not part of CodeMirror proper -- they demonstrate the way it can be +embedded in a web-application.</p> + +<div class="border"> +<textarea id="code" cols="120" rows="30"> +/* The Editor object manages the content of the editable frame. It + * catches events, colours nodes, and indents lines. This file also + * holds some functions for transforming arbitrary DOM structures into + * plain sequences of <span> and <br> elements + */ + +// Make sure a string does not contain two consecutive 'collapseable' +// whitespace characters. +function makeWhiteSpace(n) { + var buffer = [], nb = true; + for (; n > 0; n--) { + buffer.push((nb || n == 1) ? nbsp : " "); + nb = !nb; + } + return buffer.join(""); +} + +// Create a set of white-space characters that will not be collapsed +// by the browser, but will not break text-wrapping either. +function fixSpaces(string) { + if (string.charAt(0) == " ") string = nbsp + string.slice(1); + return string.replace(/[\t \u00a0]{2,}/g, function(s) {return makeWhiteSpace(s.length);}); +} + +function cleanText(text) { + return text.replace(/\u00a0/g, " ").replace(/\u200b/g, ""); +} + +// Create a SPAN node with the expected properties for document part +// spans. +function makePartSpan(value, doc) { + var text = value; + if (value.nodeType == 3) text = value.nodeValue; + else value = doc.createTextNode(text); + + var span = doc.createElement("SPAN"); + span.isPart = true; + span.appendChild(value); + span.currentText = text; + return span; +} + +// On webkit, when the last BR of the document does not have text +// behind it, the cursor can not be put on the line after it. This +// makes pressing enter at the end of the document occasionally do +// nothing (or at least seem to do nothing). To work around it, this +// function makes sure the document ends with a span containing a +// zero-width space character. The traverseDOM iterator filters such +// character out again, so that the parsers won't see them. This +// function is called from a few strategic places to make sure the +// zwsp is restored after the highlighting process eats it. +var webkitLastLineHack = webkit ? + function(container) { + var last = container.lastChild; + if (!last || !last.isPart || last.textContent != "\u200b") + container.appendChild(makePartSpan("\u200b", container.ownerDocument)); + } : function() {}; + +var Editor = (function(){ + // The HTML elements whose content should be suffixed by a newline + // when converting them to flat text. + var newlineElements = {"P": true, "DIV": true, "LI": true}; + + function asEditorLines(string) { + return fixSpaces(string.replace(/\t/g, " ").replace(/\u00a0/g, " ")).replace(/\r\n?/g, "\n").split("\n"); + } + + // Helper function for traverseDOM. Flattens an arbitrary DOM node + // into an array of textnodes and <br> tags. + function simplifyDOM(root) { + var doc = root.ownerDocument; + var result = []; + var leaving = true; + + function simplifyNode(node) { + if (node.nodeType == 3) { + var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/[\r\u200b]/g, "").replace(/\n/g, " ")); + if (text.length) leaving = false; + result.push(node); + } + else if (node.nodeName == "BR" && node.childNodes.length == 0) { + leaving = true; + result.push(node); + } + else { + forEach(node.childNodes, simplifyNode); + if (!leaving && newlineElements.hasOwnProperty(node.nodeName)) { + leaving = true; + result.push(doc.createElement("BR")); + } + } + } + + simplifyNode(root); + return result; + } + + // Creates a MochiKit-style iterator that goes over a series of DOM + // nodes. The values it yields are strings, the textual content of + // the nodes. It makes sure that all nodes up to and including the + // one whose text is being yielded have been 'normalized' to be just + // <span> and <br> elements. + // See the story.html file for some short remarks about the use of + // continuation-passing style in this iterator. + function traverseDOM(start){ + function yield(value, c){cc = c; return value;} + function push(fun, arg, c){return function(){return fun(arg, c);};} + function stop(){cc = stop; throw StopIteration;}; + var cc = push(scanNode, start, stop); + var owner = start.ownerDocument; + var nodeQueue = []; + + // Create a function that can be used to insert nodes after the + // one given as argument. + function pointAt(node){ + var parent = node.parentNode; + var next = node.nextSibling; + return function(newnode) { + parent.insertBefore(newnode, next); + }; + } + var point = null; + + // Insert a normalized node at the current point. If it is a text + // node, wrap it in a <span>, and give that span a currentText + // property -- this is used to cache the nodeValue, because + // directly accessing nodeValue is horribly slow on some browsers. + // The dirty property is used by the highlighter to determine + // which parts of the document have to be re-highlighted. + function insertPart(part){ + var text = "\n"; + if (part.nodeType == 3) { + select.snapshotChanged(); + part = makePartSpan(part, owner); + text = part.currentText; + } + part.dirty = true; + nodeQueue.push(part); + point(part); + return text; + } + + // Extract the text and newlines from a DOM node, insert them into + // the document, and yield the textual content. Used to replace + // non-normalized nodes. + function writeNode(node, c){ + var toYield = []; + forEach(simplifyDOM(node), function(part) { + toYield.push(insertPart(part)); + }); + return yield(toYield.join(""), c); + } + + // Check whether a node is a normalized <span> element. + function partNode(node){ + if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) { + node.currentText = node.firstChild.nodeValue; + return !/[\n\t\r]/.test(node.currentText); + } + return false; + } + + // Handle a node. Add its successor to the continuation if there + // is one, find out whether the node is normalized. If it is, + // yield its content, otherwise, normalize it (writeNode will take + // care of yielding). + function scanNode(node, c){ + if (node.nextSibling) + c = push(scanNode, node.nextSibling, c); + + if (partNode(node)){ + nodeQueue.push(node); + return yield(node.currentText, c); + } + else if (node.nodeName == "BR") { + nodeQueue.push(node); + return yield("\n", c); + } + else { + point = pointAt(node); + removeElement(node); + return writeNode(node, c); + } + } + + // MochiKit iterators are objects with a next function that + // returns the next value or throws StopIteration when there are + // no more values. + return {next: function(){return cc();}, nodes: nodeQueue}; + } + + // Determine the text size of a processed node. + function nodeSize(node) { + if (node.nodeName == "BR") + return 1; + else + return node.currentText.length; + } + + // Search backwards through the top-level nodes until the next BR or + // the start of the frame. + function startOfLine(node) { + while (node && node.nodeName != "BR") node = node.previousSibling; + return node; + } + function endOfLine(node, container) { + if (!node) node = container.firstChild; + else if (node.nodeName == "BR") node = node.nextSibling; + + while (node && node.nodeName != "BR") node = node.nextSibling; + return node; + } + + // Replace all DOM nodes in the current selection with new ones. + // Needed to prevent issues in IE where the old DOM nodes can be + // pasted back into the document, still holding their old undo + // information. + function scrubPasted(container, start, start2) { + var end = select.selectionTopNode(container, true), + doc = container.ownerDocument; + if (start != null && start.parentNode != container) start = start2; + if (start === false) start = null; + if (start == end || !end || !container.firstChild) return; + + var clear = traverseDOM(start ? start.nextSibling : container.firstChild); + while (end.parentNode == container) try{clear.next();}catch(e){break;} + forEach(clear.nodes, function(node) { + var newNode = node.nodeName == "BR" ? doc.createElement("BR") : makePartSpan(node.currentText, doc); + container.replaceChild(newNode, node); + }); + } + + // Client interface for searching the content of the editor. Create + // these by calling CodeMirror.getSearchCursor. To use, call + // findNext on the resulting object -- this returns a boolean + // indicating whether anything was found, and can be called again to + // skip to the next find. Use the select and replace methods to + // actually do something with the found locations. + function SearchCursor(editor, string, fromCursor) { + this.editor = editor; + this.history = editor.history; + this.history.commit(); + + // Are we currently at an occurrence of the search string? + this.atOccurrence = false; + // The object stores a set of nodes coming after its current + // position, so that when the current point is taken out of the + // DOM tree, we can still try to continue. + this.fallbackSize = 15; + var cursor; + // Start from the cursor when specified and a cursor can be found. + if (fromCursor && (cursor = select.cursorPos(this.editor.container))) { + this.line = cursor.node; + this.offset = cursor.offset; + } + else { + this.line = null; + this.offset = 0; + } + this.valid = !!string; + + // Create a matcher function based on the kind of string we have. + var target = string.split("\n"), self = this;; + this.matches = (target.length == 1) ? + // For one-line strings, searching can be done simply by calling + // indexOf on the current line. + function() { + var match = cleanText(self.history.textAfter(self.line).slice(self.offset)).indexOf(string); + if (match > -1) + return {from: {node: self.line, offset: self.offset + match}, + to: {node: self.line, offset: self.offset + match + string.length}}; + } : + // Multi-line strings require internal iteration over lines, and + // some clunky checks to make sure the first match ends at the + // end of the line and the last match starts at the start. + function() { + var firstLine = cleanText(self.history.textAfter(self.line).slice(self.offset)); + var match = firstLine.lastIndexOf(target[0]); + if (match == -1 || match != firstLine.length - target[0].length) + return false; + var startOffset = self.offset + match; + + var line = self.history.nodeAfter(self.line); + for (var i = 1; i < target.length - 1; i++) { + if (cleanText(self.history.textAfter(line)) != target[i]) + return false; + line = self.history.nodeAfter(line); + } + + if (cleanText(self.history.textAfter(line)).indexOf(target[target.length - 1]) != 0) + return false; + + return {from: {node: self.line, offset: startOffset}, + to: {node: line, offset: target[target.length - 1].length}}; + }; + } + + SearchCursor.prototype = { + findNext: function() { + if (!this.valid) return false; + this.atOccurrence = false; + var self = this; + + // Go back to the start of the document if the current line is + // no longer in the DOM tree. + if (this.line && !this.line.parentNode) { + this.line = null; + this.offset = 0; + } + + // Set the cursor's position one character after the given + // position. + function saveAfter(pos) { + if (self.history.textAfter(pos.node).length < pos.offset) { + self.line = pos.node; + self.offset = pos.offset + 1; + } + else { + self.line = self.history.nodeAfter(pos.node); + self.offset = 0; + } + } + + while (true) { + var match = this.matches(); + // Found the search string. + if (match) { + this.atOccurrence = match; + saveAfter(match.from); + return true; + } + this.line = this.history.nodeAfter(this.line); + this.offset = 0; + // End of document. + if (!this.line) { + this.valid = false; + return false; + } + } + }, + + select: function() { + if (this.atOccurrence) { + select.setCursorPos(this.editor.container, this.atOccurrence.from, this.atOccurrence.to); + select.scrollToCursor(this.editor.container); + } + }, + + replace: function(string) { + if (this.atOccurrence) { + var end = this.editor.replaceRange(this.atOccurrence.from, this.atOccurrence.to, string); + this.line = end.node; + this.offset = end.offset; + this.atOccurrence = false; + } + } + }; + + // The Editor object is the main inside-the-iframe interface. + function Editor(options) { + this.options = options; + window.indentUnit = options.indentUnit; + this.parent = parent; + this.doc = document; + var container = this.container = this.doc.body; + this.win = window; + this.history = new History(container, options.undoDepth, options.undoDelay, + this, options.onChange); + var self = this; + + if (!Editor.Parser) + throw "No parser loaded."; + if (options.parserConfig && Editor.Parser.configure) + Editor.Parser.configure(options.parserConfig); + + if (!options.readOnly) + select.setCursorPos(container, {node: null, offset: 0}); + + this.dirty = []; + if (options.content) + this.importCode(options.content); + else // FF acts weird when the editable document is completely empty + container.appendChild(this.doc.createElement("BR")); + + if (!options.readOnly) { + if (options.continuousScanning !== false) { + this.scanner = this.documentScanner(options.linesPerPass); + this.delayScanning(); + } + + function setEditable() { + // In IE, designMode frames can not run any scripts, so we use + // contentEditable instead. + if (document.body.contentEditable != undefined && internetExplorer) + document.body.contentEditable = "true"; + else + document.designMode = "on"; + + document.documentElement.style.borderWidth = "0"; + if (!options.textWrapping) + container.style.whiteSpace = "nowrap"; + } + + // If setting the frame editable fails, try again when the user + // focus it (happens when the frame is not visible on + // initialisation, in Firefox). + try { + setEditable(); + } + catch(e) { + var focusEvent = addEventHandler(document, "focus", function() { + focusEvent(); + setEditable(); + }, true); + } + + addEventHandler(document, "keydown", method(this, "keyDown")); + addEventHandler(document, "keypress", method(this, "keyPress")); + addEventHandler(document, "keyup", method(this, "keyUp")); + + function cursorActivity() {self.cursorActivity(false);} + addEventHandler(document.body, "mouseup", cursorActivity); + addEventHandler(document.body, "paste", function(event) { + cursorActivity(); + if (internetExplorer) { + var text = null; + try {text = window.clipboardData.getData("Text");}catch(e){} + if (text != null) { + self.replaceSelection(text); + event.stop(); + } + else { + var start = select.selectionTopNode(self.container, true), + start2 = start && start.previousSibling; + setTimeout(function(){scrubPasted(self.container, start, start2);}, 0); + } + } + }); + addEventHandler(document.body, "cut", cursorActivity); + + if (this.options.autoMatchParens) + addEventHandler(document.body, "click", method(this, "scheduleParenBlink")); + } + } + + function isSafeKey(code) { + return (code >= 16 && code <= 18) || // shift, control, alt + (code >= 33 && code <= 40); // arrows, home, end + } + + Editor.prototype = { + // Import a piece of code into the editor. + importCode: function(code) { + this.history.push(null, null, asEditorLines(code)); + this.history.reset(); + }, + + // Extract the code from the editor. + getCode: function() { + if (!this.container.firstChild) + return ""; + + var accum = []; + select.markSelection(this.win); + forEach(traverseDOM(this.container.firstChild), method(accum, "push")); + webkitLastLineHack(this.container); + select.selectMarked(); + return cleanText(accum.join("")); + }, + + checkLine: function(node) { + if (node === false || !(node == null || node.parentNode == this.container)) + throw parent.CodeMirror.InvalidLineHandle; + }, + + cursorPosition: function(start) { + if (start == null) start = true; + var pos = select.cursorPos(this.container, start); + if (pos) return {line: pos.node, character: pos.offset}; + else return {line: null, character: 0}; + }, + + firstLine: function() { + return null; + }, + + lastLine: function() { + if (this.container.lastChild) return startOfLine(this.container.lastChild); + else return null; + }, + + nextLine: function(line) { + this.checkLine(line); + var end = endOfLine(line, this.container); + return end || false; + }, + + prevLine: function(line) { + this.checkLine(line); + if (line == null) return false; + return startOfLine(line.previousSibling); + }, + + selectLines: function(startLine, startOffset, endLine, endOffset) { + this.checkLine(startLine); + var start = {node: startLine, offset: startOffset}, end = null; + if (endOffset !== undefined) { + this.checkLine(endLine); + end = {node: endLine, offset: endOffset}; + } + select.setCursorPos(this.container, start, end); + select.scrollToCursor(this.container); + }, + + lineContent: function(line) { + this.checkLine(line); + var accum = []; + for (line = line ? line.nextSibling : this.container.firstChild; + line && line.nodeName != "BR"; line = line.nextSibling) + accum.push(nodeText(line)); + return cleanText(accum.join("")); + }, + + setLineContent: function(line, content) { + this.history.commit(); + this.replaceRange({node: line, offset: 0}, + {node: line, offset: this.history.textAfter(line).length}, + content); + this.addDirtyNode(line); + this.scheduleHighlight(); + }, + + insertIntoLine: function(line, position, content) { + var before = null; + if (position == "end") { + before = endOfLine(line, this.container); + } + else { + for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) { + if (position == 0) { + before = cur; + break; + } + var text = (cur.innerText || cur.textContent || cur.nodeValue || ""); + if (text.length > position) { + before = cur.nextSibling; + content = text.slice(0, position) + content + text.slice(position); + removeElement(cur); + break; + } + position -= text.length; + } + } + + var lines = asEditorLines(content), doc = this.container.ownerDocument; + for (var i = 0; i < lines.length; i++) { + if (i > 0) this.container.insertBefore(doc.createElement("BR"), before); + this.container.insertBefore(makePartSpan(lines[i], doc), before); + } + this.addDirtyNode(line); + this.scheduleHighlight(); + }, + + // Retrieve the selected text. + selectedText: function() { + var h = this.history; + h.commit(); + + var start = select.cursorPos(this.container, true), + end = select.cursorPos(this.container, false); + if (!start || !end) return ""; + + if (start.node == end.node) + return h.textAfter(start.node).slice(start.offset, end.offset); + + var text = [h.textAfter(start.node).slice(start.offset)]; + for (pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos)) + text.push(h.textAfter(pos)); + text.push(h.textAfter(end.node).slice(0, end.offset)); + return cleanText(text.join("\n")); + }, + + // Replace the selection with another piece of text. + replaceSelection: function(text) { + this.history.commit(); + var start = select.cursorPos(this.container, true), + end = select.cursorPos(this.container, false); + if (!start || !end) return; + + end = this.replaceRange(start, end, text); + select.setCursorPos(this.container, start, end); + }, + + 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) { + return new SearchCursor(this, string, fromCursor); + }, + + // Re-indent the whole buffer + reindent: function() { + if (this.container.firstChild) + this.indentRegion(null, this.container.lastChild); + }, + + grabKeys: function(eventHandler, filter) { + this.frozen = eventHandler; + this.keyFilter = filter; + }, + ungrabKeys: function() { + this.frozen = "leave"; + this.keyFilter = null; + }, + + // Intercept enter and tab, and assign their new functions. + keyDown: function(event) { + if (this.frozen == "leave") this.frozen = null; + if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode))) { + event.stop(); + this.frozen(event); + return; + } + + var code = event.keyCode; + // Don't scan when the user is typing. + this.delayScanning(); + // Schedule a paren-highlight event, if configured. + if (this.options.autoMatchParens) + this.scheduleParenBlink(); + + if (code == 13) { // enter + if (event.ctrlKey) { + this.reparseBuffer(); + } + else { + select.insertNewlineAtCursor(this.win); + this.indentAtCursor(); + select.scrollToCursor(this.container); + } + event.stop(); + } + else if (code == 9 && this.options.tabMode != "default") { // tab + this.handleTab(!event.ctrlKey && !event.shiftKey); + event.stop(); + } + else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space + this.handleTab(true); + event.stop(); + } + else if ((code == 219 || code == 221) && event.ctrlKey) { + this.blinkParens(event.shiftKey); + event.stop(); + } + else if (event.metaKey && (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 { + end = endOfLine(cursor, this.container); + select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container); + } + event.stop(); + } + else if (event.ctrlKey || event.metaKey) { + if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y + select.scrollToNode(this.history.redo()); + event.stop(); + } + else if (code == 90 || code == 8) { // Z, backspace + select.scrollToNode(this.history.undo()); + event.stop(); + } + else if (code == 83 && this.options.saveFunction) { // S + this.options.saveFunction(); + event.stop(); + } + } + }, + + // Check for characters that should re-indent the current line, + // and prevent Opera from handling enter and tab anyway. + keyPress: function(event) { + var electric = /indent|default/.test(this.options.tabMode) && Editor.Parser.electricChars; + // 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 == 13 || (event.code == 9 && this.options.tabMode != "default") || + (event.keyCode == 32 && event.shiftKey && this.options.tabMode == "default")) + event.stop(); + else if (electric && electric.indexOf(event.character) != -1) + this.parent.setTimeout(method(this, "indentAtCursor"), 0); + }, + + // 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) { + // whiteSpace is the whitespace span at the start of the line, + // or null if there is no such node. + var whiteSpace = start ? start.nextSibling : this.container.firstChild; + if (whiteSpace && !hasClass(whiteSpace, "whitespace")) + whiteSpace = null; + + // Sometimes the start of the line can influence the correct + // indentation, so we retrieve it. + var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild); + 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. + var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0; + 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, 0); + removeElement(whiteSpace); + whiteSpace = null; + } + else { + select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true); + whiteSpace.currentText = makeWhiteSpace(newIndent); + whiteSpace.firstChild.nodeValue = whiteSpace.currentText; + } + } + // Not enough... + else if (indentDiff > 0) { + // If there is whitespace, we grow it. + if (whiteSpace) { + whiteSpace.currentText = makeWhiteSpace(newIndent); + whiteSpace.firstChild.nodeValue = whiteSpace.currentText; + } + // Otherwise, we have to add a new whitespace node. + else { + whiteSpace = makePartSpan(makeWhiteSpace(newIndent), this.doc); + whiteSpace.className = "whitespace"; + if (start) insertAfter(whiteSpace, start); + else this.container.insertBefore(whiteSpace, this.container.firstChild); + } + if (firstText) select.snapshotMove(firstText.firstChild, whiteSpace.firstChild, curIndent, false, true); + } + if (indentDiff != 0) this.addDirtyNode(start); + return whiteSpace; + }, + + // Re-highlight the selected part of the document. + highlightAtCursor: function() { + var pos = select.selectionTopNode(this.container, true); + var to = select.selectionTopNode(this.container, false); + if (pos === false || to === false) return; + + select.markSelection(this.win); + if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false) + return false; + select.selectMarked(); + return true; + }, + + // When tab is pressed with text selected, the whole selection is + // re-indented, when nothing is selected, the line with the cursor + // is re-indented. + handleTab: function(direction) { + if (this.options.tabMode == "spaces") { + select.insertTabAtCursor(this.win); + } + else if (!select.somethingSelected(this.win)) { + 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); + } + }, + + // Delay (or initiate) the next paren blink event. + scheduleParenBlink: function() { + if (this.parenEvent) this.parent.clearTimeout(this.parenEvent); + var self = this; + this.parenEvent = this.parent.setTimeout(function(){self.blinkParens();}, 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. + blinkParens: function(jump) { + // 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, self = this, 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 && runner.nodeName == "SPAN" && (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 || runner.nodeName != "SPAN" && runner.nodeName != "BR") { + return {node: runner, status: "dirty"}; + } + } + return {node: runner, status: runner && ok}; + } + // Temporarily give the relevant nodes a colour. + function blink(node, ok) { + node.style.fontWeight = "bold"; + node.style.color = ok ? "#8F8" : "#F88"; + self.parent.setTimeout(function() {node.style.fontWeight = ""; node.style.color = "";}, 500); + } + + while (true) { + var found = tryFindMatch(); + if (found.status == "dirty") { + this.highlight(found.node, 1); + // Needed because in some corner cases a highlight does not + // reach a node. + found.node.dirty = false; + continue; + } + else { + blink(cursor, found.status); + if (found.node) { + blink(found.node, found.status); + if (jump) 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; + var lineStart = startOfLine(cursor); + var whiteSpace = this.indentLineAfter(lineStart, direction); + if (cursor == lineStart && whiteSpace) + cursor = whiteSpace; + // This means the indentation has probably messed up the cursor. + if (cursor == whiteSpace) + select.focusAfterNode(cursor, this.container); + }, + + // 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 (end.nodeName != "BR") end = endOfLine(end, this.container); + + do { + if (current) this.highlight(before, current, true); + this.indentLineAfter(current, direction); + before = current; + current = endOfLine(current, this.container); + } 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) { + if (internetExplorer) { + this.container.createTextRange().execCommand("unlink"); + this.selectionSnapshot = select.selectionCoords(this.win); + } + + 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); + }, + + // 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.linesPerPass lines have been highlighted. The highlight + // method will continue to next lines as long as it finds dirty + // nodes. It returns an object indicating the amount of lines + // left, and 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.select) return; + + var lines = force ? Infinity : this.options.linesPerPass; + if (!this.options.readOnly) select.markSelection(this.win); + var start; + while (lines > 0 && (start = this.getDirtyNode())){ + var result = this.highlight(start, lines); + if (result) { + lines = result.left; + if (result.node && result.dirty) + this.addDirtyNode(result.node); + } + } + 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(linesPer) { + var self = this, pos = null; + return function() { + // 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(self.win); + var result = self.highlight(pos, linesPer, 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 lines 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 number of lines is given with the 'target' argument, it + // will highlight no more than that amount of lines. If this + // argument holds a DOM node, it will highlight until it reaches + // that node. If at any time it comes across a 'clean' line (no + // dirty nodes), it will stop, except when 'cleanLines' is true. + highlight: function(from, target, cleanLines, maxBacktrack){ + var container = this.container, self = this, active = this.options.activeTokens; + var lines = (typeof target == "number" ? target : null); + + if (!container.firstChild) + return; + // Backtrack to the first node before from that has a partial + // parse stored. + while (from && (!from.parserFromHere || from.dirty)) { + from = from.previousSibling; + if (maxBacktrack != null && from.nodeName == "BR" && (--maxBacktrack) < 0) + return false; + } + // If we are at the end of the document, do nothing. + if (from && !from.nextSibling) + return; + + // 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, self.doc); + part.className = token.style; + return part; + } + + function maybeTouch(node) { + if (node) { + if (node.nextSibling != node.oldNextSibling) { + self.history.touch(node); + node.oldNextSibling = node.nextSibling; + } + } + else { + if (self.container.firstChild != self.container.oldFirstChild) { + self.history.touch(node); + self.container.oldFirstChild = self.container.firstChild; + } + } + } + + // Get the token stream. If from is null, we start with a new + // parser from the start of the frame, otherwise a partial parse + // is resumed. + var traversal = traverseDOM(from ? from.nextSibling : container.firstChild), + stream = stringStream(traversal), + parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream); + + // parts is an interface to make it possible to 'delay' fetching + // the next DOM node until we are completely done with the one + // before it. This is necessary because often the next node is + // not yet available when we want to proceed past the current + // one. + var parts = { + current: null, + // Fetch current node. + get: function(){ + if (!this.current) + this.current = traversal.nodes.shift(); + return this.current; + }, + // Advance to the next part (do not fetch it yet). + next: function(){ + this.current = null; + }, + // Remove the current part from the DOM tree, and move to the + // next. + remove: function(){ + container.removeChild(this.get()); + this.current = null; + }, + // Advance to the next part that is not empty, discarding empty + // parts. + getNonEmpty: function(){ + var part = this.get(); + // Allow empty nodes when they are alone on a line, needed + // for the FF cursor bug workaround (see select.js, + // insertNewlineAtCursor). + while (part && part.nodeName == "SPAN" && part.currentText == "") { + var old = part; + this.remove(); + part = this.get(); + // Adjust selection information, if any. See select.js for details. + select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0); + } + return part; + } + }; + + var lineDirty = false, prevLineDirty = true, lineNodes = 0; + + // This forEach loops over the tokens from the parsed stream, and + // at the same time uses the parts object to proceed through the + // corresponding DOM nodes. + forEach(parsed, function(token){ + var part = parts.getNonEmpty(); + + if (token.value == "\n"){ + // The idea of the two streams actually staying synchronized + // is such a long shot that we explicitly check. + if (part.nodeName != "BR") + throw "Parser out of sync. Expected BR."; + + if (part.dirty || !part.indentation) lineDirty = true; + maybeTouch(from); + from = part; + + // Every <br> gets a copy of the parser state and a lexical + // context assigned to it. The first is used to be able to + // later resume parsing from this point, the second is used + // for indentation. + part.parserFromHere = parsed.copy(); + part.indentation = token.indentation; + part.dirty = false; + + // If the target argument wasn't an integer, go at least + // until that node. + if (lines == null && part == target) throw StopIteration; + + // A clean line with more than one node means we are done. + // Throwing a StopIteration is the way to break out of a + // MochiKit forEach loop. + if ((lines != null && --lines <= 0) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines)) + throw StopIteration; + prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0; + parts.next(); + } + else { + if (part.nodeName != "SPAN") + 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 {left: lines, + node: parts.getNonEmpty(), + dirty: lineDirty}; + } + }; + + return Editor; +})(); + + addEventHandler(window, "load", function() { + var CodeMirror = window.frameElement.CodeMirror; + CodeMirror.editor = new Editor(CodeMirror.options); + this.parent.setTimeout(method(CodeMirror, "init"), 0); + }); +</textarea> +</div> + +<script type="text/javascript"> + var textarea = document.getElementById('code'); + var editor = new CodeMirror(CodeMirror.replace(textarea), { + height: "750px", + width: "100%", + content: textarea.value, + parserfile: ["tokenizejavascript.js", "parsejavascript.js"], + stylesheet: "css/jscolors.css", + path: "js/", + autoMatchParens: true, + initCallback: function(editor){editor.win.document.body.lastChild.scrollIntoView();} + }); +</script> + + </body> +</html> diff --git a/media/CodeMirror-0.62/htmltest.html b/media/CodeMirror-0.62/htmltest.html new file mode 100644 index 0000000..a737522 --- /dev/null +++ b/media/CodeMirror-0.62/htmltest.html @@ -0,0 +1,53 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <script src="js/codemirror.js" type="text/javascript"></script> + <title>CodeMirror: HTML/XML demonstration</title> + <link rel="stylesheet" type="text/css" href="css/docs.css"/> + <style type="text/css"> + .CodeMirror-line-numbers { + width: 2.2em; + color: #aaa; + background-color: #eee; + text-align: right; + padding-right: .3em; + font-size: 10pt; + font-family: monospace; + padding-top: .4em; + } + </style> + </head> + <body style="padding: 20px;"> + +<p>This is a simple demonstration of the XML/HTML indentation module +for <a href="index.html">CodeMirror</a>. The <a +href="js/parsexml.js">javascript</a> file contains some comments with +more information.</p> + +<div style="border: 1px solid black; padding: 0px;"> +<textarea id="code" cols="120" rows="30"> +<html style="color: green"> + <!-- this is a comment --> + <head> + <title>HTML Example</title> + </head> + <body> + The indentation tries to be <em>somewhat &quot;do what + I mean&quot;</em>... but might not match your style. + </body> +</html> +</textarea> +</div> + +<script type="text/javascript"> + var editor = CodeMirror.fromTextArea('code', { + height: "350px", + parserfile: "parsexml.js", + stylesheet: "css/xmlcolors.css", + path: "js/", + continuousScanning: 500, + lineNumbers: true, + textWrapping: false + }); +</script> + </body> +</html> diff --git a/media/CodeMirror-0.62/index.html b/media/CodeMirror-0.62/index.html new file mode 100644 index 0000000..58356bd --- /dev/null +++ b/media/CodeMirror-0.62/index.html @@ -0,0 +1,182 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>CodeMirror: In-browser code editing</title> + + <link rel="stylesheet" type="text/css" href="css/docs.css"/> + <style type="text/css"> + div.top {text-align: center;} + div.top h1 {margin-bottom: 0;} + div.top h2 {margin-top: 0; margin-bottom: 1.5em;} + div.donate span {cursor: pointer; text-decoration: underline;} + div.donate {font-size: 70%; margin-top: 1em; width: 155px; padding: 10px; border: 1px solid #c44;} + </style> + </head> + <body> + +<div class="top"> + <h1>CodeMirror</h1> + <h2 class="underline">In-browser code editing made almost bearable</h2> +</div> +<div style="float: right; padding-left: 10px;"> + <form action="https://www.paypal.com/cgi-bin/webscr" method="post" id="paypal"> + <input type="hidden" name="cmd" value="_s-xclick"/> + <input type="hidden" name="hosted_button_id" value="3701544"/> + </form> + <img src="css/people.jpg" alt=""/><br/> + <div class="donate"> + Make a donation: + <ul style="margin: 0 2em; padding: 0"> + <li><span onclick="document.getElementById('paypal').submit();">Paypal</span></li> + <li><span onclick="document.getElementById('bankinfo').style.display = 'block';">Bank</span></li> + </ul> + <div id="bankinfo" style="display: none; font-size: 80%;"> + Bank: <i>Rabobank</i><br/> + Country: <i>Netherlands</i><br/> + SWIFT: <i>RABONL2U</i><br/> + Account: <i>147850770</i><br/> + Name: <i>Marijn Haverbeke</i><br/> + IBAN: <i>NL26 RABO 0147 8507 70</i> + </div> + </div> +</div> + +<p>CodeMirror is a JavaScript library that can be used to create a +relatively pleasant editor interface for code-like content ― +computer programs, HTML markup, and similar. If a parser has been +written for the language you are editing (see below for a list of +supported languages), the code will be coloured, and the editor will +help you with indentation.</p> + +<p>To get a look at CodeMirror, see the test pages for the various +parsers...</p> + +<ul> + <li><a href="jstest.html">JavaScript</a></li> + <li><a href="htmltest.html">XML/HTML</a></li> + <li><a href="csstest.html">CSS</a></li> + <li><a href="sparqltest.html">SPARQL</a></li> + <li><a href="mixedtest.html">HTML mixed-mode</a></li> + <li><a href="contrib/php/index.html">HTML+PHP mixed-mode</a> (courtesy of <a href="contrib/php/LICENSE">Yahoo!</a>)</li> + <li><a href="contrib/python/index.html">Python</a> (courtesy of <a href="contrib/python/LICENSE">Timothy Farrell</a>)</li> + <li><a href="contrib/lua/index.html">Lua</a> (courtesy of <a href="http://francio.pl/">Franciszek Wawrzak</a>)</li> + <li><a href="http://stuff.hantl.cz/ruby-in-codemirror/">Ruby</a> (by Michal Hantl, <a href="http://github.com/hakunin/ruby-in-codemirror/tree/master">unfinished</a>)</li> +</ul> + +<p>Or take a look at some real-world uses of the system...</p> + +<ul> + <li><a href="http://github.com/darwin/firerainbow">FireRainbow: syntax colouring for Firebug</a></li> + <li><a href="http://dev.freebaseapps.com/">Freebase's Acre IDE</a></li> + <li><a href="http://kml-samples.googlecode.com/svn/trunk/interactive/index.html">Google Earth KML sampler</a></li> + <li><a href="http://eloquentjavascript.net/chapter1.html">Eloquent JavaScript's console</a></li> + <li><a href="http://demo.qooxdoo.org/current/playground/#Hello_World">The qooxdoo playground</a></li> + <li><a href="http://billmill.org/static/canvastutorial/index.html">A cool tutorial about the <canvas> element</a></li> + <li><a href="http://orc.csres.utexas.edu/tryorc.shtml">An online IDE for the Orc programming language</a></li> + <li><a href="http://code.google.com/apis/ajax/playground">Google's API playground</a></li> +</ul> + +<h2>Releases</h2> + +<p class="rel"><em>30-05-2009</em>: <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-0.62.zip">Version +0.62</a>: Introduces <a href="contrib/python/index.html">Python</a> +and <a href="contrib/lua/index.html">Lua</a> parsers. Add +<code>setParser</code> (on-the-fly mode changing) and +<code>clearHistory</code> methods. Make parsing passes time-based +instead of lines-based (see the <code>passTime</code> option).</p> + +<p class="rel"><em>04-03-2009</em>: <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-0.61.zip">Version +0.61</a>: Add line numbers support (see <code>lineNumbers</code> +option in <a href="manual.html">manual</a>). Support a mode where tab +'shifts' indentation instead of resetting it (see +<code>tabMode="shift"</code>). Add <code>indentUnit</code> option to +configure indentation depths. Make it possible to grab the editor's +keyboard input, which is useful when popping up dialogs (see +<code>grabKeys</code>/<code>ungrabKeys</code>). Fix a lot of small +bugs, among which the various issues related to pasting in Internet +Explorer.</p> + +<p class="rel"><em>29-12-2008</em>: <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-0.60.zip">Version +0.60</a>: This release makes lots of internal changes, so test before +you upgrade. More robust selection-preservation on IE, allowing styles +with different font sizes. New <code>activeTokens</code> and +<code>cursorActivity</code> callbacks, and a more powerful, line-based +interface for inspecting and manipulating the content of the editor +(see <a href="manual.html">manual</a>). Fixes the +<code>replaceSelection</code> problem in IE, and a lot of other, +smaller issues.</p> + +<p class="rel"><em>28-09-2008</em>: <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-0.58.zip">Version +0.58</a>: Add parsers for SPARQL and HTML-mixed-mode (nests CSS and JS +parsers). Also: bracket highlighting, a 'dumb tabs' mode, an +<code>onChange</code> callback, and heaps of bugfixes. See the manual +for details on the new features.</p> + +<p class="rel"><em>04-07-2008</em>: <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-0.57.zip">Version +0.57</a>: A CSS parser and a nice tokenizer framework, bugfixes in the +XML parser, a few browser-issue workarounds, one of which should fix +the age-old Firefox cursor-showing-on-wrong line bug.</p> + +<h2 id="supported">Supported browsers</h2> + +<p>At this time, the following browsers are supported:</p> + +<ul> + <li>Firefox 1.5 or higher</li> + <li>Internet Explorer 6 or higher</li> + <li>Safari 3 or higher</li> + <li>Opera 9.52 or higher</li> + <li>Chrome</li> +</ul> + +<p>Making it work on other browsers that have decent support for the +W3C DOM model should not be too hard, but I am not actively testing +against those.</p> + +<h2>Getting the code</h2> + +<p>All of CodeMirror is released under a <a +href="LICENSE">zlib-style</a> license. To get it, you can download the +<a href="http://marijn.haverbeke.nl/codemirror/codemirror.zip">latest +release</a> or the current <a +href="http://marijn.haverbeke.nl/codemirror/codemirror-latest.zip">development +snapshot</a> as zip files, or use the <a +href="http://www.darcs.net/">darcs</a> version control system to get +the repository:</p> + +<pre class="code">darcs get http://marijn.haverbeke.nl/codemirror</pre> + +<p>This second method is recommended if you are planning to hack on +CodeMirror ― it makes it easy to record your patches and share them +with me. To see the repository online, visit the <a +href="http://marijn.haverbeke.nl/darcsweb.cgi?r=CodeMirror">CodeMirror +darcsweb</a>.</p> + +<h2>Support</h2> + +<p>There is a <a +href="http://groups.google.com/group/codemirror">Google group</a> (a +sort of mailing list/newsgroup thingy) for discussion and news related +to CodeMirror. You can also e-mail me directly: <a +href="mailto:marijnh@gmail.com">Marijn Haverbeke</a>.</p> + +<h2>Documentation</h2> + +<ul> + <li>The <a href="manual.html">manual</a> is all most users will need + to read (or skim).</li> + <li>If you're interested in working on the code, <a + href="story.html">this document</a> about CodeMirror's architecture + will be useful.</li> + <li>The <a + href="http://marijn.haverbeke.nl/darcsweb.cgi?r=CodeMirror;a=tree">source + code</a> is, for the most part, rather well commented, so if all + else fails, you can try reading it.</li> +</ul> + + </body> +</html> diff --git a/media/CodeMirror-0.62/manual.html b/media/CodeMirror-0.62/manual.html new file mode 100644 index 0000000..cc8131f --- /dev/null +++ b/media/CodeMirror-0.62/manual.html @@ -0,0 +1,622 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>CodeMirror user manual</title> + <link rel="stylesheet" type="text/css" href="css/docs.css"/> + </head> + <body> + <h1 class="underline">CodeMirror user manual</h1> + + <h2>Contents</h2> + + <ul> + <li><a href="#useage">Basic Useage</a></li> + <li><a href="#configuration">Configuration</a></li> + <li><a href="#parsers">Parsers</a></li> + <li><a href="#programming">Programming Interface</a></li> + <li><a href="#writeparser">Writing a Parser</a></li> + </ul> + + <h2 id="useage">Basic Usage</h2> + + <p>Inside the editor, the tab key is used to re-indent the current + selection (or the current line when nothing is selected), and + pressing enter will, apart from inserting a line break, + automatically indent the new line. Pressing control-enter will + cause the whole buffer to be re-coloured, which can be helpful + when some colouring has become out-of-date without the editor + noticing it.</p> + + <p>The editor sports an undo/redo system, accessible with + control-z (undo) and control-y (redo). Safari will not allow + client scripts to capture control-z presses, but you can use + control-backspace instead on that browser.</p> + + <p>The key-combination control-[ triggers a paren-blink: If the + cursor is directly after a '(', ')', '[', ']', '{', or '}', the + editor looks for the matching character, and highlights these + characters for a moment. There is an option to enable this to + happen any time the user types something or moves the cursor.</p> + + <p>To use CodeMirror in a document, you should add a script tag to + load <a href="js/codemirror.js"><code>codemirror.js</code></a>. This + adds two objects to your environment, <code>CodeMirror</code> and + <code>CodeMirrorConfig</code>. The first is the interface to the + editor, the second can be used to configure it. (Note that this is + the only name-space pollution you can expect from CodeMirror -- + all other cruft is kept inside the IFRAMEs that it creates when + you open an editor.)</p> + + <p>To add an editor to a document, you must choose a place, a + parser, and a style-sheet for it. For example, to append an + XML editor to the body of the document, you do:</p> + + <pre class="code">var editor = new CodeMirror(document.body, { + parserfile: "parsexml.js", + stylesheet: "xmlcolors.css" +});</pre> + + <p>The first argument to the <code>CodeMirror</code> constructor + can be a DOM node, in which case the editor gets appended to that + node, or a function, which will be called with the IFRAME node as + argument, and which is expected to place that node somewhere in + the document.</p> + + <p>The second (optional) argument is an object that specifies + options. A set of default options (see below) is present in the + <code>CodeMirrorConfig</code> object, but each instance of the + editor can be given a set of specific options to override these + defaults. In this case, we specified that the parser should be + loaded from the <a + href="js/parsexml.js"><code>"parsexml.js"</code></a> file, and + that <a href="css/xmlcolors.css"><code>"xmlcolors.css"</code></a> + should be used to specify the colours of the code.</p> + + <p>Another example:</p> + + <pre class="code">var editor = new CodeMirror(CodeMirror.replace("inputfield"), { + parserfile: ["tokenizejavascript.js", "parsejavascript.js"], + path: "lib/codemirror/js/", + stylesheet: "lib/codemirror/css/jscolors.css", + content: document.getElementById("inputfield").value +});</pre> + + <p>Here we use the utility function + <code>CodeMirror.replace</code> to create a function that will + replace a node in the current document (given either directly or + by ID) with the editor. We also select the JavaScript parser this + time, and give a <code>path</code> option to tell the editor that + its files are not located in the same directory as the current + HTML page, but in <code>"lib/codemirror/"</code>.</p> + + <p>There is a function + <code>CodeMirror.isProbablySupported()</code> that causes some + 1998-style browser detection to happen, returning + <code>false</code> if CodeMirror is probably not supported on the + browser, <code>true</code> if it probably is, and + <code>null</code> if it has no idea. As the name suggests, this is + not something you can rely on, but it's usually better than + nothing.</p> + + <p>Another utility function, <code>CodeMirror.fromTextArea</code>, + will, given a textarea node or the id of such a node, hide the + textarea and replace it with a CodeMirror frame. If the textarea + was part of a form, an <code>onsubmit</code> handler will be + registered with this form, which will load the content of the + editor into the textarea, so that it can be submitted as normal. + This function optionally takes a configuration object as second + argument.</p> + + <pre class="code">var editor = CodeMirror.fromTextArea("inputfield", { + parserfile: ["tokenizejavascript.js", "parsejavascript.js"], + path: "lib/codemirror/js/", + stylesheet: "lib/codemirror/css/jscolors.css" +});</pre> + + <p>The reason that the script path has to be configured is that + CodeMirror will load in a bunch of extra files when an editor is + created (the parser script, among others). To be able to do this, + it has to know where to find them. These are all the JavaScript + files that are part of CodeMirror itself:</p> + + <dl> + <dt><a href="js/codemirror.js"><code>codemirror.js</code></a></dt> + <dd>Main interface, takes care of default configuration and the + definition of editor frames. Include this in your HTML + document.</dd> + <dt><a href="js/editor.js"><code>editor.js</code></a></dt> <dd>The + code that takes care of reacting to user input, colouring text, + and indenting lines.</dd> + <dt><a href="js/util.js"><code>util.js</code></a></dt> <dd>A few + generic utility functions.</dd> + <dt><a + href="js/undo.js"><code>undo.js</code></a></dt> + <dd>Implements the undo history for the editor.</dd> + <dt><a + href="js/stringstream.js"><code>stringstream.js</code></a></dt> + <dd>Objects for wrapping the textual input to the parser.</dd> + <dt><a href="js/select.js"><code>select.js</code></a></dt> <dd>Some + helper utilities for working with selected text and cursor + positions.</dd> + <dt><a href="js/tokenize.js"><code>tokenize.js</code></a></dt> + <dd>Helper framework for writing tokenisers.</dd> + </dl> + + <p>Most of these are rather full of comments, which can be useful + when you are trying to figure out how they work, but wastes a lot + of bandwidth in a production system. Take a look at the + description of the <code>basefiles</code> option below if you want + to concatenate and minimise the library.</p> + + <p>Apart from these, there are files that implement the various + parsers. These all start with either <code>parse</code> or + <code>tokenize</code>.</p> + + <h2 id="configuration">Configuration</h2> + + <p>There are three ways to configure CodeMirror:</p> + + <ul> + <li>If you define a global <code>CodeMirrorConfig</code> object + before loading <a + href="js/codemirror.js"><code>codemirror.js</code></a>, the + configuration options in that object will override the + defaults.</li> + <li>By assigning to the properties of the + <code>CodeMirrorConfig</code> object, configuration defaults can + be overridden after loading <a + href="js/codemirror.js"><code>codemirror.js</code></a>.</li> + <li>The <code>CodeMirror</code> constructor can be given a second + argument, an object, which will override some options for just + that editor. Options not mentioned in this object will default to + the values in the <code>CodeMirrorConfig</code> object.</li> + </ul> + + <p>The options that can be specified are these (most have sensible + defaults specified in <a + href="js/codemirror.js"><code>codemirror.js</code></a>):</p> + + <dl> + + <dt><code>stylesheet</code></dt><dd>The file name of the style-sheet + that should be used to colour the code in the editor frame. See <a + href="css/jscolors.css"><code>jscolors.css</code></a> for an + example.</dd> + + <dt><code>path</code></dt><dd>The path that is prefixed to + script file names when they are loaded into an IFRAME. (Note that + this is not applied to the style-sheet file name.)</dd> + + <dt><code>parserfile</code></dt><dd>A file name string, or an + array of strings that name the files containing the parser. See + below for the interface that such a parser should + implement.</dd> + + <dt><code>basefiles</code></dt><dd>An array of strings naming + the files containing the base CodeMirror functionality. Defaults + to <code>["util.js", "stringstream.js", "select.js", "undo.js", + "editor.js", "tokenize.js"]</code>, but if you put them all into + a single file to reduce latency, or add some functionality, you + might have to adjust that.</dd> + + <dt><code>iframeClass</code></dt><dd>Set this to a string to + give the IFRAME node created for the editor a custom CSS class. + Defaults to <code>null</code>.</dd> + + <dt><code>passDelay</code></dt><dd>Gives the amount of + milliseconds between colouring passes. Defaults to 200.</dd> + + <dt><code>passTime</code></dt><dd>Specifies the maximum amount + of time that the highlighter will spend in one shot. Setting + this too high will cause the editor to 'freeze' the browser for + noticeable intervals. Defaults to 50.</dd> + + <dt><code>continuousScanning</code></dt><dd>Configure continuous + scanning of the document. When <code>false</code>, scanning is + disabled. When set to a number, say <code>N</code>, a + 'background' process will scan the document for + <code>passTime</code> (see above) milliseconds every + <code>N</code> milliseconds, regardless of whether anything + changed. This makes sure non-local changes propagate through the + document, and will help keep everything consistent. It does add + extra processing cost, even for an idle editor. Default value is + <code>false</code>.</dd> + + <dt><code>autoMatchParens</code></dt><dd>When <code>true</code>, + will cause parens to be matched every time a key is pressed or + the user clicks on the document. Defaults to <code>false</code>. + Might be expensive for big documents.</dd> + + <dt><code>saveFunction</code></dt><dd>If given a function + value, that function will be invoked when the user presses + control-s. You should advise your Opera users to use + control-shift-s instead, since plain control-s will bring up the + 'save page' dialog. Defaults to <code>null</code>.</dd> + + <dt><code>undoDepth</code></dt><dd>Maximum length of the undo + history. Default is 50.</dd> + + <dt><code>onChange</code></dt><dd>An optional function of zero + arguments that gets called whenever the document is changed. + Happens at undo-commit time, not instantaniously.</dd> + + <dt><code>undoDelay</code></dt><dd>When nothing is done in the + editor for this amount of milliseconds, pending changes get + added to the undo history. Setting this lower will give the undo + functionality a finer granularity. Defaults to 800.</dd> + + <dt><code>width</code>, <code>height</code></dt><dd>The size of + the editor frame, given as a style-sheet quantities (for example + <code>"600px"</code> or <code>"100%"</code>).</dd> + + <dt><code>disableSpellcheck</code></dt><dd>Should the editor + disable spell-checking on browsers that support it (Firefox 2+). + Default is <code>true</code>, since for most code spell-checking + is useless.</dd> + + <dt><code>textWrapping</code></dt><dd>Can be used to disable or + enable text-wrapping in the editor frame. Default is + <code>true</code>.</dd> + + <dt><code>lineNumbers</code></dt><dd>Show line numbers to the + left of the editor. This requires you to specify a style for the + <code>CodeMirror-line-numbers</code> CSS class (in the outer + document) to configure the width, font, colors, etcetera for the + line-number DIV. You have to make sure that lines in the + numbering element have the same height as lines in the editor. + This is most easily done by giving them both the same font and + an absolute ('pt' or 'px') font size. This option defaults to + <code>false</code>. When enabling this, you have to disable + <code>textWrapping</code>, since the line numbers don't take + wrapped lines into account.</dd> + + <dt><code>indentUnit</code></dt><dd>An integer that specifies + the amount of spaces one 'level' of indentation should add. + Default is <code>2</code>.</dd> + + <dt><code>tabMode</code></dt><dd>Determines what the effect of + pressing tab is. Possibilities are: + <dl> + <dt><code>"indent"</code></dt><dd>The default. Causes tab to + adjust the indentation of the selection or current line using + the parser's rules.</dd> + <dt><code>"spaces"</code></dt><dd>Pressing tab simply inserts + four spaces.</dd> + <dt><code>"default"</code></dt><dd>CodeMirror does not + interfere with the tab key, but leaves it to the browser to + handle it. Binds shift-space to regular indentation + behaviour.</dd> + <dt><code>"shift"</code></dt><dd>Pressing tab indents the + current line (or selection) one <code>indentUnit</code> + deeper, pressing shift-tab or ctrl-tab (whichever your browser + does not interfere with), un-indents it.</dd> + </dl></dd> + + <dt><code>reindentOnLoad</code></dt><dd>When <code>true</code>, + this causes the content of the editor to be reindented + immediately when the editor loads. Defaults to + <code>false</code>.</dd> + + <dt><code>readOnly</code></dt><dd>When set to <code>true</code>, + the document is not editable.</dd> + + <dt><code>initCallback</code></dt><dd>If set to a function, this + will be called (with the editor object as its argument) after + the editor has finished initialising.</dd> + + <dt><code>cursorActivity</code></dt><dd>A function that is + called every time the cursor moves, with the top-level node that + the cursor is inside or next to as an argument. Can be used to + have some controls react to the context of the cursor.</dd> + + <dt><code>activeTokens</code></dt><dd>Can be set to a function + that will be called with <code>(spanNode, tokenObject, + editor)</code> arguments whenever a new token node is being + added to the document. Can be used to do things like add event + handlers to nodes. Should <em>not</em> change the DOM structure + of the node (so no turning the span into a link), since this + will greatly confuse the editor.</dd> + + <dt id="parserConfig"><code>parserConfig</code></dt><dd>An + object value that is passed along to the parser to configure it. + What this object should look like depends on the parser + used.</dd> + + <dt><code>content</code></dt><dd>The starting content of the + editor. You'll probably not want to provide a global default for + this, but add it to the <code>options</code> object passed to + individual editors as they are created.</dd> + + </dl> + + <h2 id="parsers">Parsers</h2> + + <p>(If you want to use a CodeMirror parser to highlight a piece of + text, without creating an editor, see <a + href="highlight.html">this example</a>, and the <code><a + href="js/highlight.js">highlight.js</a></code> script.)</p> + + <p>The following parsers come with the distribution of CodeMirror:</p> + + <dl> + <dt><code><a href="js/parsexml.js">parsexml.js</a></code> (<a + href="htmltest.html">demo</a>)</dt><dd>A HTML/XML parser. Takes + a <code>useHTMLKludges</code> configuration option (see the + <code><a href="#parserConfig">parserConfig</a></code> option + above), which specifies whether the content of the editor is + HTML or XML, and things like self-closing tags (<code>br</code>, + <code>img</code>) exist. This defaults to <code>true</code>. + Example colours for the styles that this parser uses are defined + in <code><a + href="css/xmlcolors.css">css/xmlcolors.css</a></code>.</dd> + + <dt><code><a + href="js/tokenizejavascript.js">tokenizejavascript.js</a></code>, + <code><a + href="js/parsejavascript.js">parseejavascript.js</a></code> (<a + href="jstest.html">demo</a>)</dt><dd>The JavaScript parser. + Example colours in <code><a + href="css/jscolors.css">css/jscolors.css</a></code></dd> + + <dt><code><a href="js/parsecss.js">parsecss.js</a></code> (<a + href="csstest.html">demo</a>)</dt><dd>A CSS parser. Styles in + <code><a + href="css/csscolors.css">css/csscolors.css</a></code></dd> + + <dt><code><a + href="js/parsehtmlmixed.js">parsehtmlmixed.js</a></code> (<a + href="mixedtest.html">demo</a>)</dt><dd>A mixed-mode HTML + parser. Requires the XML, JavaScript, and CSS parsers to also be + loaded, so your <code>parserfile</code> option looks something + like <code>["parsexml.js", "parsecss.js", + "tokenizejavascript.js", "parsejavascript.js", + "parsehtmlmixed.js"]</code>.</dd> + + <dt><code><a href="js/parsesparql.js">parsesparql.js</a></code> + (<a href="sparqltest.html">demo</a>)</dt><dd>Parses the <a + href="http://en.wikipedia.org/wiki/SPARQL">SPARQL</a> query + language. Example styles in <code><a + href="css/sparqlcolors.css">css/sparqlcolors.css</a></code></dd> + + <dt><code><a + href="js/parsedummy.js">parsedummy.js</a></code></dt><dd>A + 'dummy' parser to make it possible to edit plain text, or + documents for which no suitable parser exists.</dd> + + <dt><code><a + href="contrib/php/js/parsephp.js">contrib/php/js/parsephp.js</a></code> + (<a href="contrib/php/index.html">demo</a>)</dt><dd>PHP + parser.</dd> + + <dt><code><a + href="contrib/python/js/parsepython.js">contrib/python/js/parsepython.js</a></code> + (<a href="contrib/python/index.html">demo</a>)</dt><dd>Python + parser.</dd> + + <dt><code><a href="contrib/lua/js/parselua.js">contrib/lua/js/parselua.js</a></code> + (<a href="contrib/lua/index.html">demo</a>)</dt><dd>Lua + parser.</dd> + + </dl> + + <h2 id="programming">Programming Interface</h2> + + <p>To be as flexible as possible, CodeMirror implements a very + plain editable field, without any accompanying buttons, bells, and + whistles. <code>CodeMirror</code> objects do, however, provide a + number of methods that make it possible to add extra functionality + around the editor. <a + href="js/mirrorframe.js"><code>mirrorframe.js</code></a> provides a + basic example of their usage.</p> + + <dl> + + <dt><code>getCode()</code> → + <code>string</code></dt><dd>Returns the current content of the + editor, as a string.</dd> + + <dt><code>setCode(string)</code></dt><dd>Replaces the current + content of the editor with the given value.</dd> + + <dt><code>focus()</code></dt><dd>Gives focus to the editor + frame.</dd> + + <dt><code>currentLine()</code> → + <code>number</code></dt><dd>Returns the line on which the cursor + is currently sitting. <span class="warn">(Deprecated, see the + line-based interface below)</span></dd> + + <dt><code>jumpToLine(number)</code></dt><dd>Moves the cursor to + the start of the given line. <span + class="warn">(Deprecated)</span></dd> + + <dt><code>selection()</code> → + <code>string</code></dt><dd>Returns the text that is currently + selected in the editor.</dd> + + <dt><code>replaceSelection(string)</code></dt><dd>Replaces the + currently selected text with the given string. Will also cause + the editor frame to gain focus.</dd> + + <dt><code>reindent()</code></dt><dd>Automatically re-indent the + whole document.</dd> + + <dt><code>reindentSelection()</code></dt><dd>Automatically + re-indent the selected lines.</dd> + + <dt><code>getSearchCursor(string, atCursor)</code> → + <code>cursor</code></dt><dd>The first argument indicates the + string that should be searched for, and the second indicates + whether searching should start at the cursor (<code>true</code>) + or at the start of the document (<code>false</code>). Returns an + object that provides an interface for searching. Call its + <code>findNext()</code> method to search for an occurrence of + the given string. This returns <code>true</code> if something is + found, or <code>false</code> if the end of document is reached. + When an occurrence has been found, you can call + <code>select()</code> to select it, or + <code>replace(string)</code> to replace it with a given string. + Note that letting the user change the document, or + programmatically changing it in any way except for calling + <code>replace</code> on the cursor itself, might cause a cursor + object to skip back to the beginning of the document.</dd> + + <dt><code>undo()</code></dt><dd>Undo one changeset, if available.</dd> + <dt><code>redo()</code></dt><dd>Redo one changeset, if available.</dd> + <dt><code>historySize() → object</code></dt><dd>Get a + <code>{undo, redo}</code> object holding the sizes of the undo + and redo histories.</dd> + <dt><code>clearHistory()</code></dt><dd>Drop all history + information.</dd> + + <dt><code>grabKeys(callback, filter)</code></dt><dd>Route + keyboard input in the editor to a callback function. This + function is given a slightly normalised (see + <code>normalizeEvent</code> in <a + href="js/util.js"><code>util.js</code></a>) <code>keydown</code> + event object. If a second argument is given, this will be used + to determine which events to apply the callback to. It should + take a key code (as in <code>event.keyCode</code>), and return a + boolean, where <code>true</code> means the event should be + routed to the callback, and <code>false</code> leaves the key to + perform its normal behaviour.</dd> + <dt><code>ungrabKeys()</code></dt><dd>Revert the effect of + <code>grabKeys</code>.</dd> + + <dt><code>setParser(name)</code></dt><dd>Change the active + parser. To use this you'll have to load more than one parser + (put the one you want to use as default at the end of the list). + Then call this function with a string containing the name of the + parser you want to switch to (see the parser script file to find + the name, it'll be something like <code>CSSParser</code>).</dd> + </dl> + + <p>For detailed interaction with the content of the editor, + CodeMirror exposes a line-oriented interface, which allows you to + inspect and manipulate the document line by line. Line handles + should be considered opaque (they are in fact the <code>BR</code> + nodes at the start of the line), except that the value + <code>false</code> (but <em>not</em> <code>null</code>) always + denotes an invalid value. Since changing the document might cause + some line handles to become invalid, every function that takes + them as argument can throw + <code>CodeMirror.InvalidLineHandle</code>. These are the relevant + methods:</p> + + <dl> + <dt><code>cursorPosition(start)</code> → + <code>object</code></dt><dd>Retrieve a <code>{line, + character}</code> object representing the cursor position. + <code>start</code> defaults to <code>true</code> and determines + if the startpoint or the endpoint of the selection is used.</dd> + <dt><code>firstLine()</code> → + <code>handle</code></dt><dd>Get the first line of the + document.</dd> + <dt><code>lastLine()</code> → + <code>handle</code></dt><dd>The last line.</dd> + <dt><code>nextLine(handle)</code> → + <code>handle</code></dt><dd>Get the line after the given one, or + <code>false</code> if that was the last line.</dd> + <dt><code>prevLine(handle)</code> → + <code>handle</code></dt><dd>Find the line before the given one, + return <code>false</code> if that was the first line.</dd> + <dt><code>nthLine(number)</code> → + <code>handle</code></dt><dd>Find the Nth line of the document. + Note that the first line counts as one, not zero. Returns + <code>false</code> if there is no such line.</dd> + <dt><code>lineContent(handle)</code> → + <code>string</code></dt><dd>Retrieve the content of the + line.</dd> + <dt><code>setLineContent(handle, string)</code></dt><dd>Replace + the content of the line with the given string.</dd> + <dt><code>lineNumber(handle)</code> → + <code>number</code></dt><dd>Ask which line of the document + (1-based) the given line is.</dd> + <dt><code>selectLines(startHandle, startOffset, + endHandle, endOffset)</code></dt><dd>Move the selection to a + specific point. <code>endHandle</code> and + <code>endOffset</code> can be omitted to just place the cursor + somewhere without selecting any text.</dd> + <dt><code>insertIntoLine(handle, position, + text)</code></dt><dd>Insert a piece of text into a line. + <code>position</code> can be an integer, specifying the position + in the line where the text should be inserted, or the string + <code>"end"</code>, for the end of the line.</dd> + </dl> + + <h2 id="writeparser">Writing a Parser</h2> + + <p>A parser is implemented by one or more files (see + <code>parserfile</code> above) which, when loaded, add a + <code>Parser</code> object to the <code>Editor</code> object + defined by <a href="js/editor.js"><code>editor.js</code></a>. This + object must support the following interface:</p> + + <dl> + + <dt><code>make(stream)</code></dt><dd>A function that, given a + string stream (see <a + href="js/stringstream.js"><code>stringstream.js</code></a>), + creates a parser. The behaviour of this parser is described + below.</dd> + + <dt><code>electricChars</code></dt><dd>An optional string + containing the characters that, when typed, should cause the + indentation of the current line to be recomputed (for example + <code>"{}"</code> for c-like languages).</dd> + + <dt><code>configure(object)</code></dt><dd>An optional function + that can be used to configure the parser. If it exists, and an + editor is given a <code>parserConfig</code> option, it will be + called with the value of that option.</dd> + + <dt><code>firstIndentation(chars, current, + direction)</code></dt><dd>An optional function that is used to + determine the proper indentation of the first line of a + document. When not provided, <code>0</code> is used.</dd> + </dl> + + <p>When the <code>make</code> method is called with a string + stream, it should return a MochiKit-style iterator: an object with + a <code>next</code> method, which will raise + <code>StopIteration</code> when it is at its end (see <a + href="http://bob.pythonmac.org/archives/2005/07/06/iteration-in-javascript/">this</a> + for details). This iterator, when called, will consume input from + the string stream, and produce a token object.</p> + + <p>Token objects represent a single significant piece of the text + that is being edited. A token object must have a + <code>value</code> property holding the text it stands for, and a + <code>style</code> property with the CSS class that should be used + to colour this element. This can be anything, except that any + whitespace at the start of a line should <em>always</em> have + class <code>"whitespace"</code>: The editor must be able to + recognize these when it indents lines. Furthermore, each newline + character <em>must</em> have its own separate token, which has an + <code>indentation</code> property holding a function that can be + used to determine the proper indentation level for the next line. + This function optionally takes the text in the first token of the + next line, the current indentation of the line, and the + 'direction' of the indentation as arguments, which it can use to + adjust the indentation level. The direction argument is only + useful for modes in which lines do not have a fixed indentation, + and can be modified by multiple tab presses. It is + <code>null</code> for 'default' indentations (like what happens + when the user presses enter), <code>true</code> for regular tab + presses, and <code>false</code> for control-tab or shift-tab.</p> + + <p>So far this should be pretty easy. The hard part is that this + iterator must also have a <code>copy</code> method. This method, + called without arguments, returns a function representing the + current state of the parser. When this state function is later + called with a string stream as its argument, it returns a parser + object that resumes parsing using the old state and the new input + stream. It may assume that only one parser is active at a time, + and can clobber the state of the old parser if it wants.</p> + + <p>For examples, see <a + href="js/parsejavascript.js"><code>parsejavascript.js</code></a>, + <a href="js/parsexml.js"><code>parsexml.js</code></a>, and <a + href="js/parsecss.js"><code>parsecss.js</code></a>.</p> + + </body> +</html> |