diff --git a/Makefile.dryice.js b/Makefile.dryice.js index 74694852..ed148868 100755 --- a/Makefile.dryice.js +++ b/Makefile.dryice.js @@ -91,7 +91,7 @@ copy({ { root: aceHome + '/lib', include: /.*\.js$/, - exclude: /tests?\/|theme\/|mode\/|ace\/worker\/host\.js/ + exclude: /tests?\/|theme\/|mode\/|ace\/worker\/worker\.js/ }, { base: aceHome + '/lib/', path: 'ace/theme/textmate.js' }, { base: aceHome + '/lib/', path: 'ace/mode/text.js' }, @@ -128,6 +128,17 @@ copy({ dest: data }); +// Create the compressed and uncompressed output files +copy({ + source: data, + //filter: copy.filter.uglifyjs, + dest: 'build/ace.js' +}); +//copy({ +// source: data, +// dest: 'build/ace-uncompressed.js' +//}); + copy({ source: [ 'build_support/editor.html' @@ -135,27 +146,51 @@ copy({ dest: 'build/editor.html' }); -// Create the compressed and uncompressed output files -copy({ - source: data, - //filter: copy.filter.uglifyjs, - dest: 'build/ace.js' -}); -copy({ - source: data, - dest: 'build/ace-uncompressed.js' -}); // Create worker bootstrap code copy({ - source: "lib/ace/worker/host.js", + source: "lib/ace/worker/worker.js", filter: [function(data) { - return data + "\nimportScripts('ace-uncompressed.js')"; + return data + "\nimportScripts('ace.js')"; }], - dest: 'build/host.js' + dest: 'build/worker.js' }); +var eclipseTheme = copy.createDataObject(); +copy({ + source: [{ + root: aceHome + '/lib', + include: "ace/theme/eclipse.js" + }], + filter: [ copy.filter.moduleDefines ], + dest: eclipseTheme +}); +copy({ + source: [{ + root: aceHome + '/lib', + include: "ace/theme/eclipse.css" + }], + filter: [ copy.filter.addDefines ], + dest: eclipseTheme +}); +copy({ + source: eclipseTheme, + dest: 'build/theme/eclipse.js' +}); +[ + "clouds", "clouds_midnight", "cobalt", "dawn", "idle_fingers", "kr_theme", + "mono_industrial", "monokai", "pastel_on_dark", "twilight" +].forEach(function(theme) { + copy({ + source: [{ + root: aceHome + '/lib', + include: "ace/theme/" + theme + ".js" + }], + filter: [ copy.filter.moduleDefines ], + dest: "build/theme/" + theme + ".js" + }); +}); function internalRequire(ignore) { var exports = {}; diff --git a/build_support/boot.js b/build_support/boot.js index df2ab0ff..66733344 100644 --- a/build_support/boot.js +++ b/build_support/boot.js @@ -38,42 +38,47 @@ // don't define it in a worker. if (window.document) { - var deps = [ "pilot/fixoldbrowsers", "pilot/plugin_manager", "pilot/settings", - "pilot/environment" ]; + require("pilot/fixoldbrowsers"); + require("pilot/settings"); + + var Event = require("pilot/event"); + var Dom = require("pilot/dom"); + var EditSession = require("ace/edit_session").EditSession; + var JavaScriptMode = require("ace/mode/javascript").Mode; + var UndoManager = require("ace/undomanager").UndoManager; + var Editor = require("ace/editor").Editor; + var Renderer = require("ace/virtual_renderer").VirtualRenderer; + var Theme = require("ace/theme/textmate"); + + var catalog = require("pilot/plugin_manager").catalog; + catalog.registerPlugins([ "pilot/index" ]); - require(deps, function() { - var catalog = require("pilot/plugin_manager").catalog; - catalog.registerPlugins([ "pilot/index" ]); - }); var ace = { edit: function(el) { if (typeof(el) == "string") { el = document.getElementById(el); } + + var doc = new EditSession(Dom.getInnerText(el)); + doc.setMode(new JavaScriptMode()); + doc.setUndoManager(new UndoManager()); + el.innerHTML = ''; + + var editor = new Editor(new Renderer(el, "ace/theme/textmate")); + editor.setSession(doc); + var env = require("pilot/environment").create(); - var catalog = require("pilot/plugin_manager").catalog; catalog.startupPlugins({ env: env }).then(function() { - var EditSession = require("ace/edit_session").EditSession; - var JavaScriptMode = require("ace/mode/javascript").Mode; - var UndoManager = require("ace/undomanager").UndoManager; - var Editor = require("ace/editor").Editor; - var Renderer = require("ace/virtual_renderer").VirtualRenderer; - var theme = require("ace/theme/textmate"); - - var doc = new EditSession(el.innerHTML); - el.innerHTML = ''; - doc.setMode(new JavaScriptMode()); - doc.setUndoManager(new UndoManager()); env.document = doc; - env.editor = new Editor(new Renderer(el, theme)); - env.editor.setSession(doc); - env.editor.resize(); - window.addEventListener("resize", function() { - env.editor.resize(); - }, false); + env.editor = env; + editor.resize(); + Event.addListener(window, "resize", function() { + editor.resize(); + }); el.env = env; }); + return editor; } }; } \ No newline at end of file diff --git a/build_support/editor.html b/build_support/editor.html index 8ff3e8d9..e7c024e3 100644 --- a/build_support/editor.html +++ b/build_support/editor.html @@ -17,11 +17,13 @@ } } - + + diff --git a/demo/startup.js b/demo/startup.js index 1b5a958f..6caf3f4c 100644 --- a/demo/startup.js +++ b/demo/startup.js @@ -44,7 +44,7 @@ exports.launch = function(env) { var event = require("pilot/event"); var Editor = require("ace/editor").Editor; var Renderer = require("ace/virtual_renderer").VirtualRenderer; - var theme = require("ace/theme/textmate"); + var theme = require("ace/theme/textmate"); var EditSession = require("ace/edit_session").EditSession; var JavaScriptMode = require("ace/mode/javascript").Mode; var CssMode = require("ace/mode/css").Mode; @@ -58,43 +58,34 @@ exports.launch = function(env) { var vim = require("ace/keyboard/keybinding/vim").Vim; var emacs = require("ace/keyboard/keybinding/emacs").Emacs; var HashHandler = require("ace/keyboard/hash_handler").HashHandler; - + + var keybindings = { + // Null = use "default" keymapping + ace: null, + vim: vim, + emacs: emacs, + // This is a way to define simple keyboard remappings + custom: new HashHandler({ + "gotoright": "Tab" + }) + } + var docs = {}; + // Make the lorem ipsum text a little bit longer. + var loreIpsum = document.getElementById("plaintext").innerHTML; + for (var i = 0; i < 5; i++) { + loreIpsum += loreIpsum; + } + docs.plain = new EditSession(loreIpsum); + docs.plain.setUseWrapMode(true); + docs.plain.setMode(new TextMode()); + docs.plain.setUndoManager(new UndoManager()); + docs.js = new EditSession(document.getElementById("jstext").innerHTML); docs.js.setMode(new JavaScriptMode()); docs.js.setUndoManager(new UndoManager()); - - if (false && window.Worker) { - var worker = new WorkerClient("../..", ["ace", "pilot"], "ace/worker/mirror", "Mirror"); - worker.call("setValue", [docs.js.getValue()]); - - docs.js.getDocument().on("change", function(e) { - e.range = { - start: e.data.range.start, - end: e.data.range.end - }; - worker.emit("change", e); - }); - - worker.on("jslint", function(results) { - var errors = []; - for (var i=0; i -
- - - + + + + + + + + - - - - +
+ + + + + + + + + + + + + + + + + + +
- - + + - - - - - - - - + + + - + + + + +
-
- - + +} +// A magic character: >> ぁ << + + diff --git a/lib/ace/css/editor.css b/lib/ace/css/editor.css index 5b545728..df204e58 100644 --- a/lib/ace/css/editor.css +++ b/lib/ace/css/editor.css @@ -73,6 +73,18 @@ height: 100%; } +.ace_editor textarea { + position: "absolute"; + z-index: -1; + opacity: 0; + width: 10px; + height: 30px; + border: none; + resize: none; + outline: none; + overflow: hidden; +} + .ace_layer { z-index: 1; position: absolute; diff --git a/lib/ace/document.js b/lib/ace/document.js index 7de5cd9b..c5c99b4e 100644 --- a/lib/ace/document.js +++ b/lib/ace/document.js @@ -64,9 +64,9 @@ var Document = function(text) { this.getValue = function() { return this.$lines.join(this.getNewLineCharacter()); }; - + // check for IE split bug - if ("aaa".split(/a/).length == 0) + if ("aaa".split(/a/).length == 0) this.$split = function(text) { return text.replace(/\r\n|\r/g, "\n").split("\n"); } @@ -75,7 +75,7 @@ var Document = function(text) { return text.split(/\r\n|\r|\n/); }; - + this.$detectNewLine = function(text) { var match = text.match(/^.*?(\r?\n)/m); if (match) { @@ -84,7 +84,7 @@ var Document = function(text) { this.$autoNewLine = "\n"; } }; - + this.getNewLineCharacter = function() { switch (this.$newLineMode) { case "windows": @@ -109,24 +109,24 @@ var Document = function(text) { this.getNewLineMode = function() { return this.$newLineMode; }; - + this.isNewLine = function(text) { return (text == "\r\n" || text == "\r" || text == "\n"); }; /** - * Get a verbatim copy of the given line as it is in the document + * Get a verbatim copy of the given line as it is in the document */ this.getLine = function(row) { return this.$lines[row] || ""; }; - + this.getLines = function(firstRow, lastRow) { return this.$lines.slice(firstRow, lastRow+1); }; - + /** - * Returns all lines in the document as string array. Warning: The caller + * Returns all lines in the document as string array. Warning: The caller * should not modify this array! */ this.getAllLines = function() { @@ -157,15 +157,15 @@ var Document = function(text) { position.row = Math.max(0, length - 1); position.column = this.getLine(length-1).length; } - return position; + return position; } this.insert = function(position, text) { if (text.length == 0) return position; - + position = this.$clipPosition(position); - + if (this.getLength() <= 1) this.$detectNewLine(text); @@ -182,56 +182,56 @@ var Document = function(text) { this.insertNewLine(end); if (newLines.length > 2) this.insertLines(position.row+1, newLines.slice(1, newLines.length-1)); - + var end = this.insertInLine({row: position.row + newLines.length - 1, column: 0}, newLines[newLines.length-1]); } return end; }; - + this.insertLines = function(row, lines) { if (lines.length == 0) return {row: row, column: 0}; - + var args = [row, 0]; args.push.apply(args, lines); this.$lines.splice.apply(this.$lines, args); - + var range = new Range(row, 0, row + lines.length - 1, 0); var delta = { action: "insertLines", range: range, lines: lines }; - this._dispatchEvent("change", { data: delta }); + this._dispatchEvent("change", { data: delta }); return range.end; }, - + this.insertNewLine = function(position) { position = this.$clipPosition(position); var line = this.$lines[position.row] || ""; this.$lines[position.row] = line.substring(0, position.column); this.$lines.splice(position.row + 1, 0, line.substring(position.column, line.length)); - + var end = { row : position.row + 1, column : 0 }; - + var delta = { action: "insertText", range: Range.fromPoints(position, end), text: this.getNewLineCharacter() }; this._dispatchEvent("change", { data: delta }); - + return end; }; - + this.insertInLine = function(position, text) { if (text.length == 0) return position; - + var line = this.$lines[position.row] || ""; this.$lines[position.row] = line.substring(0, position.column) + text + line.substring(position.column); @@ -240,14 +240,14 @@ var Document = function(text) { row : position.row, column : position.column + text.length }; - + var delta = { action: "insertText", range: Range.fromPoints(position, end), text: text }; this._dispatchEvent("change", { data: delta }); - + return end; }; @@ -258,12 +258,12 @@ var Document = function(text) { if (range.isEmpty()) return range.start; - + var firstRow = range.start.row; var lastRow = range.end.row; if (range.isMultiLine()) { - + // TODO removeInLine can be optimized away! this.removeInLine(lastRow, 0, range.end.column); if (lastRow - firstRow >= 2) @@ -276,17 +276,17 @@ var Document = function(text) { } return range.start; }; - + this.removeInLine = function(row, startColumn, endColumn) { if (startColumn == endColumn) return; - + var range = new Range(row, startColumn, row, endColumn); var line = this.getLine(row); var removed = line.substring(startColumn, endColumn); var newLine = line.substring(0, startColumn) + line.substring(endColumn, line.length); this.$lines.splice(row, 1, newLine); - + var delta = { action: "removeText", range: range, @@ -295,18 +295,18 @@ var Document = function(text) { this._dispatchEvent("change", { data: delta }); return range.start; }; - + /** * Removes a range of full lines - * + * * @param firstRow {Integer} The first row to be removed * @param lastRow {Integer} The last row to be removed * @return {String[]} The removed lines */ this.removeLines = function(firstRow, lastRow) { - var range = new Range(firstRow, 0, lastRow, this.$lines[lastRow].length); + var range = new Range(firstRow, 0, lastRow, this.$lines[lastRow].length); var removed = this.$lines.splice(firstRow, lastRow - firstRow + 1); - + var delta = { action: "removeLines", range: range, @@ -316,16 +316,16 @@ var Document = function(text) { this._dispatchEvent("change", { data: delta }); return removed; }; - + this.removeNewLine = function(row) { var firstLine = this.getLine(row); var secondLine = this.getLine(row+1); - + var range = new Range(row, firstLine.length, row+1, 0); var line = firstLine + secondLine; - + this.$lines.splice(row, 2, line); - + var delta = { action: "removeText", range: range, @@ -333,16 +333,16 @@ var Document = function(text) { }; this._dispatchEvent("change", { data: delta }); }; - + this.replace = function(range, text) { if (text.length == 0 && range.isEmpty()) return range.start; - + // Shortcut: If the text we want to insert is the same as it is already // in the document, we don't have to replace anything. if (text == this.getTextRange(range)) return range.end; - + this.remove(range); if (text) { var end = this.insert(range.start, text); @@ -350,15 +350,15 @@ var Document = function(text) { else { end = range.start; } - + return end; }; - + this.applyDeltas = function(deltas) { for (var i=0; i=0; i--) { var delta = deltas[i]; var range = Range.fromPoints(delta.range.start, delta.range.end); - + if (delta.action == "insertLines") this.removeLines(range.start.row, range.end.row) else if (delta.action == "insertText") @@ -383,7 +383,7 @@ var Document = function(text) { this.insertLines(range.start.row, delta.lines) else if (delta.action == "removeText") this.insert(range.start, delta.text) - } + } }; }).call(Document.prototype); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 5ae0998c..9e907b3d 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -51,13 +51,14 @@ var EditSession = function(text, mode) { this.$modified = true; this.selection = new Selection(this); this.$breakpoints = []; + this.$wrapData = []; if (text instanceof Document) { this.setDocument(text) } else { this.setDocument(new Document(text)); } - + if (mode) this.setMode(mode); }; @@ -70,15 +71,15 @@ var EditSession = function(text, mode) { this.setDocument = function(doc) { if (this.doc) throw new Error("Document is already set"); - + this.doc = doc; doc.on("change", this.onChange.bind(this)); }; - + this.getDocument = function() { return this.doc; }; - + this.onChange = function(e) { var delta = e.data; this.$modified = true; @@ -86,6 +87,8 @@ var EditSession = function(text, mode) { this.$deltas.push(delta); this.$informUndoManager.schedule(); } + + this.$updateWrapDataOnChange(e); this._dispatchEvent("change", e); }; @@ -98,7 +101,7 @@ var EditSession = function(text, mode) { this.toString = function() { return this.doc.getValue(); }; - + this.getSelection = function() { return this.selection; }; @@ -164,7 +167,7 @@ var EditSession = function(text, mode) { this.getTabSize = function() { return this.$tabSize; }; - + this.isTabStop = function(position) { return this.$useSoftTabs && (position.column % this.$tabSize == 0); }; @@ -201,7 +204,7 @@ var EditSession = function(text, mode) { }; /** - * Error: + * Error: * { * row: 12, * column: 2, //can be undefined @@ -225,7 +228,7 @@ var EditSession = function(text, mode) { this.getAnnotations = function() { return this.$annotations; }; - + this.clearAnnotations = function() { this.$annotations = []; this._dispatchEvent("changeAnnotation", {}); @@ -239,12 +242,12 @@ var EditSession = function(text, mode) { this.$autoNewLine = "\n"; } }; - + this.tokenRe = /^[\w\d]+/g; this.nonTokenRe = /^(?:[^\w\d|[\u3040-\u309F]|[\u30A0-\u30FF]|[\u4E00-\u9FFF\uF900-\uFAFF\u3400-\u4DBF])+/g this.getWordRange = function(row, column) { var line = this.getLine(row); - + var inToken = false; if (column > 0) { inToken = !!line.charAt(column - 1).match(this.tokenRe); @@ -287,7 +290,7 @@ var EditSession = function(text, mode) { if (this.$worker) this.$worker.terminate(); - + if (window.Worker) this.$worker = mode.createWorker(this); @@ -324,8 +327,8 @@ var EditSession = function(text, mode) { return this.screenWidth; }; - this.$computeWidth = function() { - if (this.$modified) { + this.$computeWidth = function(force) { + if (this.$modified || force) { this.$modified = false; var lines = this.doc.getAllLines(); @@ -344,17 +347,22 @@ var EditSession = function(text, mode) { longestScreenLine = Math.max(longestScreenLine, len); } this.width = longestLine; - this.screenWidth = longestScreenLine; + + if (this.$useWrapMode) { + this.screenWidth = this.$wrapLimit; + } else { + this.screenWidth = longestScreenLine; + } } }; /** - * Get a verbatim copy of the given line as it is in the document + * Get a verbatim copy of the given line as it is in the document */ this.getLine = function(row) { return this.doc.getLine(row); }; - + /** * Get a line as it is displayed on screen. Tabs are replaced by spaces. */ @@ -470,16 +478,16 @@ var EditSession = function(text, mode) { this.insert = function(position, text) { return this.doc.insert(position, text); }; - + /** * @param rows Array[Integer] sorted list of rows */ - this.multiRowInsert = function(rows, column, text) { + this.multiRowInsert = function(rows, column, text) { for (var i=rows.length-1; i>=0; i--) { var row = rows[i]; if (row >= this.doc.getLength()) continue; - + var diff = column - this.doc.getLine(row).length; if ( diff > 0) { var padded = lang.stringRepeat(" ", diff) + text; @@ -489,10 +497,10 @@ var EditSession = function(text, mode) { padded = text; offset = 0; } - + var end = this.insert({row: row, column: column+offset}, padded); } - + return { rows: end ? end.row - rows[0] : 0, columns: end ? end.column - column : 0 @@ -506,29 +514,29 @@ var EditSession = function(text, mode) { this.multiRowRemove = function(rows, range) { if (range.start.row !== rows[0]) throw new TypeError("range must start in the first row!"); - + var height = range.end.row - rows[0]; for (var i=rows.length-1; i>=0; i--) { var row = rows[i]; if (row >= this.doc.getLength()) continue; - + var end = this.remove(new Range(row, range.start.column, row+height, range.end.column)); } }; - + this.undoChanges = function(deltas) { if (!deltas.length) return; - + this.$fromUndo = true; this.doc.revertDeltas(deltas); this.$fromUndo = false; - + // update the selection var firstDelta = deltas[0]; var lastDelta = deltas[deltas.length-1]; - + this.selection.clearSelection(); if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") this.selection.moveCursorToPosition(firstDelta.range.start); @@ -539,15 +547,15 @@ var EditSession = function(text, mode) { this.redoChanges = function(deltas) { if (!deltas.length) return; - + this.$fromUndo = true; this.doc.applyDeltas(deltas); this.$fromUndo = false; - + // update the selection var firstDelta = deltas[0]; var lastDelta = deltas[deltas.length-1]; - + this.selection.clearSelection(); if (firstDelta.action == "insertText" || firstDelta.action == "insertLines") this.selection.setSelectionRange(Range.fromPoints(firstDelta.range.start, lastDelta.range.end)); @@ -571,15 +579,15 @@ var EditSession = function(text, mode) { var rowRange = range.collapseRows(); var deleteRange = new Range(0, 0, 0, 0); var size = this.getTabSize(); - + for (var i = rowRange.start.row; i <= rowRange.end.row; ++i) { var line = this.getLine(i); - + deleteRange.start.row = i; deleteRange.end.row = i; for (var j = 0; j < size; ++j) if (line.charAt(j) != ' ') - break; + break; if (j < size && line.charAt(j) == '\t') { deleteRange.start.column = j; deleteRange.end.column = j + 1; @@ -594,7 +602,7 @@ var EditSession = function(text, mode) { this.remove(deleteRange); } return range; - } + } this.moveLinesUp = function(firstRow, lastRow) { if (firstRow <= 0) return 0; @@ -627,87 +635,499 @@ var EditSession = function(text, mode) { return Math.max(0, Math.min(row, this.doc.getLength()-1)); }; - this.documentToScreenColumn = function(row, docColumn) { + // WRAPMODE + this.$wrapLimit = 80; + this.$useWrapMode = false; + + this.setUseWrapMode = function(useWrapMode) { + if (useWrapMode != this.$useWrapMode) { + this.$useWrapMode = useWrapMode; + this.$updateWrapData(0, this.getLength() - 1); + this.$modified = true; + this._dispatchEvent("changeWrapMode"); + } + }; + + this.getUseWrapMode = function() { + return this.$useWrapMode; + }; + + this.setWrapLimit = function(wrapLimit) { + if (wrapLimit != this.$wrapLimit) { + this.$wrapLimit = wrapLimit; + this.$updateWrapData(0, this.getLength() - 1); + this._dispatchEvent("changeWrapMode"); + } + }; + + this.getWrapLimit = function() { + return this.$wrapLimit; + }; + + this.$updateWrapDataOnChange = function(e) { + if (!this.$useWrapMode) { + return; + } + + var action = e.data.action; + var firstRow = e.data.range.start.row, + lastRow = e.data.range.end.row; + + if (action.indexOf("Lines") != -1) { + if (action == "insertLines") { + lastRow = firstRow + e.data.lines.length; + } else { + firstRow = lastRow - e.data.lines.length; + } + } + + if (firstRow != lastRow) { + var len = lastRow - firstRow; + if (action.indexOf("remove") != -1) { + this.$wrapData.splice(firstRow, len); + lastRow = firstRow; + } else { + var args = [firstRow, 0]; + for (var i = 0; i < len; i++) args.push([]); + this.$wrapData.splice.apply(this.$wrapData, args); + } + } + + this.$updateWrapData(firstRow, lastRow); + }; + + this.$updateWrapData = function(firstRow, lastRow) { + var lines = this.doc.getAllLines(); + var tabSize = this.getTabSize(); + var wrapData = this.$wrapData; + var wrapLimit = this.$wrapLimit; + + // Remove lines that are no longer there. + wrapData.splice(lines.length, wrapData.length - lines.length); + + for (var row = firstRow; row <= lastRow; row++) { + wrapData[row] = + this.$computeWrapSplits(lines[row], wrapLimit, tabSize); + } + }; + + // "Tokens" + var CHAR = 1, + CHAR_EXT = 2, + SPACE = 3, + TAB = 4, + TAB_SPACE = 5; + + this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) { + textLine = textLine.trimRight(); + if (textLine.length == 0) { + return []; + } + + var tabSize = this.getTabSize(); + var splits = []; + var tokens = this.$getDisplayTokens(textLine); + var displayLength = tokens.length; + var lastSplit = 0, lastDocSplit = 0; + + function addSplit(screenPos) { + var displayed = tokens.slice(lastSplit, screenPos); + + // The document size is the current size - the extra width for tabs + // and multipleWidth characters. + var len = displayed.length; + displayed.join(""). + // Get all the tabs. + replace(/4/g, function(m) { + len -= tabSize - 1; + }). + // Get all the multipleWidth characters. + replace(/2/g, function(m) { + len -= 1; + }); + + lastDocSplit += len; + splits.push(lastDocSplit); + lastSplit = screenPos; + } + + while (displayLength - lastSplit > wrapLimit) { + // This is, where the split should be. + var split = lastSplit + wrapLimit; + + // If there is a space or tab at this split position. + if (tokens[split] >= SPACE) { + // Include all following spaces + tabs in this split as well. + while (tokens[split] >= SPACE) { + split ++; + } + addSplit(split); + } else { + // Search for the first non space/tab token. + for (split; split != lastSplit - 1; split--) { + if (tokens[split] >= SPACE) { + split++; + break; + } + } + // If we found one, then add the split. + if (split > lastSplit) { + addSplit(split); + } + // No space or tab around? Well, force a split then. + else { + addSplit(lastSplit + wrapLimit); + } + } + } + return splits; + } + + this.$getDisplayTokens = function(str) { + var arr = []; var tabSize = this.getTabSize(); + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + // Tab + if (c == 9) { + arr.push(TAB); + for (var n = 1; n < tabSize; n++) { + arr.push(TAB_SPACE); + } + } + // Space + else if(c == 32) { + arr.push(SPACE); + } + // CJK characters + else if ( + c >= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + arr.push(CHAR, CHAR_EXT); + } else { + arr.push(CHAR); + } + } + return arr; + } + + /** + * Calculates the width of the a string on the screen while assuming that + * the string starts at the first column on the screen. + * + * @param string str String to calculate the screen width of + * @return int number of columns for str on screen. + */ + this.$getStringScreenWidth = function(str) { var screenColumn = 0; - var remaining = docColumn; - var line = this.getLine(row); + var tabSize = this.getTabSize(); - for (var i=0; i 0) { - remaining -= 1; - // tab - if (c == 9) { - screenColumn += tabSize; - } - // CJK characters + for (var i=0; i= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + screenColumn += 2; + } else { + screenColumn += 1; + } + } + + return screenColumn; + } + + this.getRowHeight = function(config, row) { + var rows; + if (!this.$useWrapMode) { + rows = 1; + } else { + rows = this.$wrapData[row].length + 1; + } + + return rows * config.lineHeight; + } + + this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { + if (!this.$useWrapMode) { + return this.$getStringScreenWidth(this.getLine(screenRow)); + } + + var rowData = this.$screenToDocumentRow(screenRow); + var docRow = rowData[0], + row = rowData[1]; + + var start, end; + if (this.$wrapData[docRow][row]) { + start = (this.$wrapData[docRow][row - 1] || 0); + end = this.$wrapData[docRow][row]; + returnDocPosition && end--; + } else { + end = this.getLine(docRow).length; + start = (this.$wrapData[docRow][row - 1] || 0); + } + if (!returnDocPosition) { + return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end)); + } else { + return end; + } + }; + + this.getDocumentLastRowColumn = function(docRow, docColumn) { + if (!this.$useWrapMode) { + return this.getLine(docRow).length; + } + + var screenRow = this.documentToScreenRow(docRow, docColumn); + return this.getScreenLastRowColumn(screenRow, true); + } + + this.getScreenFirstRowColumn = function(screenRow) { + if (!this.$useWrapMode) { + return 0; + } + + var rowData = this.$screenToDocumentRow(screenRow); + var docRow = rowData[0], + row = rowData[1]; + + return this.$wrapData[docRow][row - 1] || 0; + }; + + this.getRowSplitData = function(row) { + if (!this.$useWrapMode) { + return undefined; + } else { + return this.$wrapData[row]; + } + }; + + /** + * + * @returns array + * - array[0]: The documentRow aquivalent. + * - array[1]: The screenRowOffset to the first documentRow on the screen. + */ + this.$screenToDocumentRow = function(row) { + if (!this.$useWrapMode) { + return [row, 0]; + } + + var wrapData = this.$wrapData, linesCount = this.getLength(); + var docRow = 0; + while (docRow < linesCount && row >= wrapData[docRow].length + 1) { + row -= wrapData[docRow].length + 1; + docRow ++; + } + + return [docRow, row]; + }; + + this.screenToDocumentRow = function(screenRow) { + return this.$screenToDocumentRow(screenRow)[0]; + }; + + this.screenToDocumentColumn = function(screenRow, screenColumn) { + return this.screenToDocumentPosition(screenRow, screenColumn).column; + }; + + this.screenToDocumentPosition = function(row, column) { + var line; + var docRow; + var docColumn; + var remaining = column; + if (!this.$useWrapMode) { + docRow = row; + row = 0; + docColumn = 0; + line = this.getLine(docRow); + } else { + var wrapData = this.$wrapData, linesCount = this.getLength(); + + var rowData = this.$screenToDocumentRow(row); + row = rowData[1]; + docRow = rowData[0]; + + if (docRow >= linesCount) { + return { + row: docRow, + column: 0 + }; + } + docColumn = wrapData[docRow][row - 1] || 0; + line = this.getLine(docRow).substring(docColumn); + } + + var tabSize = this.getTabSize(); + for(var i = 0; i < line.length; i++) { + var c = line.charCodeAt(i); + + if (remaining > 0) { + docColumn += 1; + // tab + if (c == 9) { + if (remaining >= tabSize) { + remaining -= tabSize; + } else { + remaining = 0; + docColumn -= 1; + } + } + // CJK characters else if ( c >= 0x3040 && c <= 0x309F || // Hiragana c >= 0x30A0 && c <= 0x30FF || // Katakana c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs - c >= 0xF900 && c <= 0xFAFF || - c >= 0x3400 && c <= 0x4DBF - ) { - screenColumn += 2; - } else { - screenColumn += 1; - } - } else { - break; - } - } - - return screenColumn; - }; - - this.screenToDocumentColumn = function(row, screenColumn) { - var tabSize = this.getTabSize(); - - var docColumn = 0; - var remaining = screenColumn; - var line = this.getLine(row); - - for(var i=0; i 0) { - docColumn += 1; - // tab - if (c == 9) { - if (remaining >= tabSize) { - remaining -= tabSize; - } else { - remaining = 0; - docColumn -= 1; - } - } - // CJK characters - else if ( - c >= 0x3040 && c <= 0x309F || // Hiragana - c >= 0x30A0 && c <= 0x30FF || // Katakana - c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs - c >= 0xF900 && c <= 0xFAFF || + c >= 0xF900 && c <= 0xFAFF || c >= 0x3400 && c <= 0x4DBF ) { if (remaining >= 2) { - remaining -= 2; - } else { - remaining = 0; - docColumn -= 1; - } - } else { - remaining -= 1; - } - } else { - break; - } - } - - return docColumn; + remaining -= 2; + } else { + remaining = 0; + docColumn -= 1; + } + } else { + remaining -= 1; + } + } else { + break; + } + } + + // Clamp docColumn. + if (docRow < linesCount && wrapData[docRow][row]) { + if (docColumn >= wrapData[docRow][row]) { + // We remove one character at the end such that the docColumn + // position returned is not associated to the next row on the + // screen. + docColumn = wrapData[docRow][row] - 1; + } + } else if (this.getLine(docRow)) { + docColumn = Math.min(docColumn, this.getLine(docRow).length); + } + + return { + row: docRow, + column: docColumn + }; }; + this.documentToScreenColumn = function(row, docColumn) { + return this.documentToScreenPosition(row, docColumn).column; + }; + + /** + * + * @return array[2] + * - array[0]: The number of the row on the screen (aka screenRow) + * - array[1]: The number of rows from the first docRow on the screen + * (aka screenRowOffset); + */ + this.$documentToScreenRow = function(docRow, docColumn) { + if (!this.$useWrapMode) { + return [docRow, 0]; + } + + var wrapData = this.$wrapData; + var screenRow = 0; + + // Handle special case where the row is outside of the range of lines. + if (docRow > wrapData.length - 1) { + return [ + this.getScreenLength(), + wrapData[wrapData.length - 1].length - 1 + ]; + } + + for (var i = 0; i < docRow; i++) { + screenRow += wrapData[i].length + 1; + } + + var screenRowOffset = 0; + while (docColumn >= wrapData[docRow][screenRowOffset]) { + screenRow ++; + screenRowOffset++; + } + + return [screenRow, screenRowOffset]; + } + + this.documentToScreenRow = function(docRow, docColumn) { + return this.$documentToScreenRow(docRow, docColumn)[0]; + } + + this.documentToScreenPosition = function(pos, column) { + var str; + var tabSize = this.getTabSize(); + + // Normalize the passed in arguments. + var row; + if (column != null) { + row = pos; + } else { + row = pos.row; + column = pos.column; + } + + if (!this.$useWrapMode) { + str = this.getLine(row).substring(0, column); + column = this.$getStringScreenWidth(str); + return { + row: row, + column: column + }; + } + if (row >= this.getLength()) { + return { + row: screenRow, + column: 0 + }; + } + + var split; + var wrapRowData = this.$wrapData[row]; + var screenRow, screenRowOffset; + var screenColumn; + var rowData = this.$documentToScreenRow(row, column); + screenRow = rowData[0]; + screenRowOffset = rowData[1]; + + str = this.getLine(row).substring( + wrapRowData[screenRowOffset - 1] || 0, column); + screenColumn = this.$getStringScreenWidth(str); + + return { + row: screenRow, + column: screenColumn + }; + }; + + this.getScreenLength = function() { + if (!this.$useWrapMode) { + return this.getLength(); + } + + var screenRows = 0; + for (var row = 0; row < this.$wrapData.length; row++) { + screenRows += this.$wrapData[row].length + 1; + } + return screenRows; + } + }).call(EditSession.prototype); exports.EditSession = EditSession; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 39e5ca4e..5e8f2a01 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -22,6 +22,7 @@ * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -55,7 +56,7 @@ var Editor =function(renderer, session) { this.container = container; this.renderer = renderer; - this.textInput = new TextInput(container, this); + this.textInput = new TextInput(renderer.getTextAreaContainer(), this); this.keyBinding = new KeyBinding(this); var self = this; event.addListener(container, "mousedown", function(e) { @@ -128,6 +129,7 @@ var Editor =function(renderer, session) { this.session.removeEventListener("change", this.$onDocumentChange); this.session.removeEventListener("changeMode", this.$onDocumentModeChange); this.session.removeEventListener("changeTabSize", this.$onDocumentChangeTabSize); + this.session.removeEventListener("changeWrapMode", this.$onDocumentChangeWrapMode); this.session.removeEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); this.session.removeEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); @@ -150,14 +152,16 @@ var Editor =function(renderer, session) { this.$onDocumentChangeTabSize = this.renderer.updateText.bind(this.renderer); session.addEventListener("changeTabSize", this.$onDocumentChangeTabSize); + this.$onDocumentChangeWrapMode = this.renderer.updateFull.bind(this.renderer); + session.addEventListener("changeWrapMode", this.$onDocumentChangeWrapMode); + this.$onDocumentChangeBreakpoint = this.onDocumentChangeBreakpoint.bind(this); this.session.addEventListener("changeBreakpoint", this.$onDocumentChangeBreakpoint); - + this.$onDocumentChangeAnnotation = this.onDocumentChangeAnnotation.bind(this); this.session.addEventListener("changeAnnotation", this.$onDocumentChangeAnnotation); this.selection = session.getSelection(); - this.$desiredColumn = 0; this.$onCursorChange = this.onCursorChange.bind(this); this.selection.addEventListener("changeCursor", this.$onCursorChange); @@ -175,7 +179,7 @@ var Editor =function(renderer, session) { this.onDocumentChangeAnnotation(); this.renderer.scrollToRow(session.getScrollTopRow()); this.renderer.updateFull(); - + this._dispatchEvent("changeSession", { session: session, oldSession: oldSession @@ -258,11 +262,11 @@ var Editor =function(renderer, session) { this.onDocumentChange = function(e) { var delta = e.data; var range = delta.range; - + this.bgTokenizer.start(range.start.row); if (range.start.row == range.end.row) var lastRow = range.end.row; - else + else lastRow = Infinity; this.renderer.updateLines(range.start.row, lastRow); @@ -277,6 +281,11 @@ var Editor =function(renderer, session) { this.onCursorChange = function(e) { this.$highlightBrackets(); + + // move text input over the cursor + // this is required for iOS and IME + this.renderer.moveTextAreaToCursor(this.textInput.getElement()); + this.renderer.updateCursor(this.getCursorPosition(), this.$overwrite); if (!this.$blockScrolling && (!e || !e.blockScrolling)) { @@ -414,13 +423,11 @@ var Editor =function(renderer, session) { this.onMouseDoubleClick = function(e) { this.selection.selectWord(); this.$clickSelection = this.getSelectionRange(); - this.$updateDesiredColumn(); }; this.onMouseTripleClick = function(e) { this.selection.selectLine(); this.$clickSelection = this.getSelectionRange(); - this.$updateDesiredColumn(); }; this.onMouseWheel = function(e) { @@ -684,7 +691,6 @@ var Editor =function(renderer, session) { var range = this.session.outdentRows(selection.getRange()); selection.setSelectionRange(range, selection.isBackwards()); - this.$updateDesiredColumn(); }; this.toggleCommentLines = function() { @@ -874,17 +880,14 @@ var Editor =function(renderer, session) { this.clearSelection = function() { this.selection.clearSelection(); - this.$updateDesiredColumn(); }; this.moveCursorTo = function(row, column) { this.selection.moveCursorTo(row, column); - this.$updateDesiredColumn(); }; this.moveCursorToPosition = function(pos) { this.selection.moveCursorToPosition(pos); - this.$updateDesiredColumn(); }; @@ -903,34 +906,16 @@ var Editor =function(renderer, session) { this.navigateTo = function(row, column) { this.clearSelection(); this.moveCursorTo(row, column); - this.$updateDesiredColumn(column); }; this.navigateUp = function(times) { this.selection.clearSelection(); - this.selection.moveCursorBy(-(times || 1), 0); - - if (this.$desiredColumn) { - var cursor = this.getCursorPosition(); - var column = this.session.screenToDocumentColumn(cursor.row, this.$desiredColumn); - this.selection.moveCursorTo(cursor.row, column); - } + this.selection.moveCursorBy(-1, 0); }; this.navigateDown = function(times) { this.selection.clearSelection(); - this.selection.moveCursorBy(times || 1, 0); - - if (this.$desiredColumn) { - var cursor = this.getCursorPosition(); - var column = this.session.screenToDocumentColumn(cursor.row, this.$desiredColumn); - this.selection.moveCursorTo(cursor.row, column); - } - }; - - this.$updateDesiredColumn = function() { - var cursor = this.getCursorPosition(); - this.$desiredColumn = this.session.documentToScreenColumn(cursor.row, cursor.column); + this.selection.moveCursorBy(1, 0); }; this.navigateLeft = function(times) { @@ -999,7 +984,6 @@ var Editor =function(renderer, session) { this.$tryReplace(range, replacement); if (range !== null) this.selection.setSelectionRange(range); - this.$updateDesiredColumn(); }, this.replaceAll = function(replacement, options) { @@ -1018,7 +1002,6 @@ var Editor =function(renderer, session) { this.$tryReplace(ranges[i], replacement); if (ranges[0] !== null) this.selection.setSelectionRange(ranges[0]); - this.$updateDesiredColumn(); }, this.$tryReplace = function(range, replacement) { @@ -1071,7 +1054,6 @@ var Editor =function(renderer, session) { var range = this.$search.find(this.session); if (range) { this.gotoLine(range.end.row+1, range.end.column); - this.$updateDesiredColumn(); this.selection.setSelectionRange(range); } }; diff --git a/lib/ace/keyboard/state_handler.js b/lib/ace/keyboard/state_handler.js index 5861ad23..b887cd4d 100644 --- a/lib/ace/keyboard/state_handler.js +++ b/lib/ace/keyboard/state_handler.js @@ -197,7 +197,7 @@ StateHandler.prototype = { // If we pressed any command key but no other key, then ignore the input. // Otherwise "shift-" is added to the buffer, and later on "shift-g" // which results in "shift-shift-g" which doesn't make senese. - if (hashId != 0 && (key == "" || String.fromCharCode(0))) { + if (hashId != 0 && (key == "" || key == String.fromCharCode(0))) { return null; } diff --git a/lib/ace/keyboard/textinput.js b/lib/ace/keyboard/textinput.js index 459f9e0e..f4ecac17 100644 --- a/lib/ace/keyboard/textinput.js +++ b/lib/ace/keyboard/textinput.js @@ -38,39 +38,13 @@ define(function(require, exports, module) { var event = require("pilot/event"); +var useragent = require("pilot/useragent"); var TextInput = function(parentNode, host) { var text = document.createElement("textarea"); - var style = text.style; - style.position = "fixed"; - style.left = "-10000px"; - style.top = "-10000px"; parentNode.appendChild(text); - // move text input over the cursor - // this is required for iOS and IME - style.left = "0px"; - style.top = "0px"; - style.zIndex = -1; - style.opacity = 0; - style.width = "10px"; - style.height = "30px"; - - var changeCursor = function() { - var cursor = host.getCursorPosition(); - var pos = host.renderer.textToScreenCoordinates(cursor.row, cursor.column); - var epos = parentNode.getBoundingClientRect(); - style.left = (pos.pageX - epos.left - 6) + "px"; - style.top = (pos.pageY - epos.top + 52) + "px"; - }; - - host.addEventListener("changeSession", function(e) { - if (e.oldSession) - e.oldSession.getSelection().removeEventListener("changeCursor", changeCursor); - e.session.getSelection().addEventListener("changeCursor", changeCursor); - }); - var PLACEHOLDER = String.fromCharCode(0); sendText(); @@ -97,6 +71,7 @@ var TextInput = function(parentNode, host) { } var onTextInput = function(e) { + if (useragent.isIE && text.value.charCodeAt(0) > 128) return; setTimeout(function() { if (!inCompostion) sendText(); @@ -105,40 +80,62 @@ var TextInput = function(parentNode, host) { var onCompositionStart = function(e) { inCompostion = true; - sendText(); - text.value = ""; + if (!useragent.isIE) { + sendText(); + text.value = ""; + }; host.onCompositionStart(); - setTimeout(onCompositionUpdate, 0); + if (!useragent.isGecko) setTimeout(onCompositionUpdate, 0); }; var onCompositionUpdate = function() { + if (!inCompostion) return; host.onCompositionUpdate(text.value); }; var onCompositionEnd = function() { inCompostion = false; host.onCompositionEnd(); - onTextInput(); + setTimeout(function () { + sendText(); + }, 0); }; - var onCopy = function() { + var onCopy = function(e) { copied = true; - text.value = host.getCopyText(); + var copyText = host.getCopyText(); + if(copyText) + text.value = copyText; + else + e.preventDefault(); text.select(); - copied = true; setTimeout(sendText, 0); }; - var onCut = function() { + var onCut = function(e) { copied = true; - text.value = host.getCopyText(); - host.onCut(); + var copyText = host.getCopyText(); + if(copyText) { + text.value = copyText; + host.onCut(); + } else + e.preventDefault(); text.select(); setTimeout(sendText, 0); }; event.addCommandKeyListener(text, host.onCommandKey.bind(host)); event.addListener(text, "keypress", onTextInput); + if (useragent.isIE) { + var keytable = { 13:1, 27:1 }; + event.addListener(text, "keyup", function (e) { + if (inCompostion && (!text.value || keytable[e.keyCode])) setTimeout(onCompositionEnd, 0); + if ((text.value.charCodeAt(0)|0) < 129) { + return; + }; + inCompostion ? onCompositionUpdate() : onCompositionStart(); + }); + }; event.addListener(text, "textInput", onTextInput); event.addListener(text, "paste", function(e) { // Some browsers support the event.clipboardData API. Use this to get @@ -153,13 +150,20 @@ var TextInput = function(parentNode, host) { onTextInput(); } }); - event.addListener(text, "propertychange", onTextInput); + if (!useragent.isIE) { + event.addListener(text, "propertychange", onTextInput); + }; event.addListener(text, "copy", onCopy); event.addListener(text, "cut", onCut); event.addListener(text, "compositionstart", onCompositionStart); - event.addListener(text, "compositionupdate", onCompositionUpdate); + if (useragent.isGecko) { + event.addListener(text, "text", onCompositionUpdate); + }; + if (useragent.isWebKit) { + event.addListener(text, "keyup", onCompositionUpdate); + }; event.addListener(text, "compositionend", onCompositionEnd); event.addListener(text, "blur", function() { @@ -180,6 +184,10 @@ var TextInput = function(parentNode, host) { this.blur = function() { text.blur(); }; + + this.getElement = function() { + return text; + }; }; exports.TextInput = TextInput; diff --git a/lib/ace/layer/cursor.js b/lib/ace/layer/cursor.js index 60a6938e..ffc2130c 100644 --- a/lib/ace/layer/cursor.js +++ b/lib/ace/layer/cursor.js @@ -20,6 +20,7 @@ * * Contributor(s): * Fabian Jakobs + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -57,10 +58,9 @@ var Cursor = function(parentEl) { }; this.setCursor = function(position, overwrite) { - this.position = { - row : position.row, - column : this.session.documentToScreenColumn(position.row, position.column) - }; + this.position = + this.session.documentToScreenPosition(position); + if (overwrite) { dom.addCssClass(this.cursor, "ace_overwrite"); } else { @@ -100,7 +100,7 @@ var Cursor = function(parentEl) { }, 1000); }; - this.getPixelPosition = function() { + this.getPixelPosition = function(onScreen) { if (!this.config || !this.position) { return { left : 0, @@ -108,10 +108,12 @@ var Cursor = function(parentEl) { }; } - var cursorLeft = Math.round(this.position.column * this.config.characterWidth); - var cursorTop = this.position.row * this.config.lineHeight; + var pos = this.position; + var cursorLeft = Math.round(pos.column * this.config.characterWidth); + var cursorTop = (pos.row - (onScreen ? this.config.firstRowScreen : 0)) * + this.config.lineHeight; - return { + return { left : cursorLeft, top : cursorTop }; @@ -123,17 +125,10 @@ var Cursor = function(parentEl) { this.config = config; - var cursorLeft = Math.round(this.position.column * config.characterWidth); - var cursorTop = this.position.row * config.lineHeight; + this.pixelPos = this.getPixelPosition(true); - this.pixelPos = { - left : cursorLeft, - top : cursorTop - }; - - this.cursor.style.left = cursorLeft + "px"; - this.cursor.style.top = (cursorTop - (config.firstRow * config.lineHeight)) - + "px"; + this.cursor.style.left = this.pixelPos.left + "px"; + this.cursor.style.top = this.pixelPos.top + "px"; this.cursor.style.width = config.characterWidth + "px"; this.cursor.style.height = config.lineHeight + "px"; diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index 13c85bad..a7cd82f1 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -20,6 +20,7 @@ * * Contributor(s): * Fabian Jakobs + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -51,12 +52,16 @@ var Gutter = function(parentEl) { (function() { + this.setSession = function(session) { + this.session = session; + }; + this.addGutterDecoration = function(row, className){ - if (!this.$decorations[row]) + if (!this.$decorations[row]) this.$decorations[row] = ""; this.$decorations[row] += " ace_" + className; } - + this.removeGutterDecoration = function(row, className){ this.$decorations[row] = this.$decorations[row].replace(" ace_" + className, ""); }; @@ -101,7 +106,7 @@ var Gutter = function(parentEl) { this.$breakpoints[i] ? " ace_breakpoint " : " ", annotation.className, "' title='", annotation.text.join("\n"), - "' style='height:", config.lineHeight, "px;'>", (i+1), ""); + "' style='height:", this.session.getRowHeight(config, i), "px;'>", (i+1), ""); html.push(""); } this.element = dom.setInnerHtml(this.element, html.join("")); diff --git a/lib/ace/layer/marker.js b/lib/ace/layer/marker.js index 027212b4..30f777ed 100644 --- a/lib/ace/layer/marker.js +++ b/lib/ace/layer/marker.js @@ -20,6 +20,7 @@ * * Contributor(s): * Fabian Jakobs + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -87,6 +88,8 @@ var Marker = function(parentEl) { var range = marker.range.clipRows(config.firstRow, config.lastRow); if (range.isEmpty()) continue; + range = range.toScreenRange(this.session); + if (range.isMultiLine()) { if (marker.type == "text") { this.drawTextMarker(html, range, marker.clazz, config); @@ -101,11 +104,16 @@ var Marker = function(parentEl) { this.element = dom.setInnerHtml(this.element, html.join("")); }; - this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { + this.$getTop = function(row, layerConfig) { + return (row - layerConfig.firstRowScreen) * layerConfig.lineHeight; + }; + this.drawTextMarker = function(stringBuilder, range, clazz, layerConfig) { // selection start var row = range.start.row; - var lineRange = new Range(row, range.start.column, row, this.session.getLine(row).length); + + var lineRange = new Range(row, range.start.column, + row, this.session.getScreenLastRowColumn(row)); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); // selection end @@ -116,18 +124,16 @@ var Marker = function(parentEl) { for (var row = range.start.row + 1; row < range.end.row; row++) { lineRange.start.row = row; lineRange.end.row = row; - lineRange.end.column = this.session.getLine(row).length; // account for endofline characters + lineRange.end.column = this.session.getScreenLastRowColumn(row); this.drawSingleLineMarker(stringBuilder, lineRange, clazz, layerConfig, 1); } }; this.drawMultiLineMarker = function(stringBuilder, range, clazz, layerConfig) { - var range = range.toScreenRange(this.session); - // from selection start to the end of the line var height = layerConfig.lineHeight; var width = Math.round(layerConfig.width - (range.start.column * layerConfig.characterWidth)); - var top = (range.start.row - layerConfig.firstRow) * layerConfig.lineHeight; + var top = this.$getTop(range.start.row, layerConfig); var left = Math.round(range.start.column * layerConfig.characterWidth); stringBuilder.push( @@ -139,7 +145,7 @@ var Marker = function(parentEl) { ); // from start of the last line to the selection end - var top = (range.end.row - layerConfig.firstRow) * layerConfig.lineHeight; + var top = this.$getTop(range.end.row, layerConfig); var width = Math.round(range.end.column * layerConfig.characterWidth); stringBuilder.push( @@ -153,7 +159,7 @@ var Marker = function(parentEl) { var height = (range.end.row - range.start.row - 1) * layerConfig.lineHeight; if (height < 0) return; - var top = (range.start.row + 1 - layerConfig.firstRow) * layerConfig.lineHeight; + var top = this.$getTop(range.start.row + 1, layerConfig); stringBuilder.push( "
oldConfig.lastRow) { var fragment = this.$renderLinesFragment(config, oldConfig.lastRow + 1, config.lastRow); el.appendChild(fragment); @@ -214,7 +223,7 @@ var Text = function(parentEl) { var lineEl = document.createElement("div"); lineEl.className = "ace_line"; var style = lineEl.style; - style.height = this.$characterSize.height + "px"; + style.height = this.session.getRowHeight(config, row) + "px"; style.width = config.width + "px"; var html = []; @@ -231,14 +240,12 @@ var Text = function(parentEl) { this.config = config; var html = []; - var tokens = this.tokenizer.getTokens(config.firstRow, config.lastRow); - for (var i=config.firstRow; i<=config.lastRow; i++) { - html.push("
"); - this.$renderLine(html, i, tokens[i-config.firstRow].tokens), html.push("
"); - } + var tokens = this.tokenizer.getTokens(config.firstRow, config.lastRow) + var fragment = this.$renderLinesFragment(config, config.firstRow, config.lastRow); - this.element = dom.setInnerHtml(this.element, html.join("")); + // Clear the current content of the element and add the rendered fragment. + this.element.innerHTML = ""; + this.element.appendChild(fragment); }; this.$textToken = { @@ -258,7 +265,7 @@ var Text = function(parentEl) { var space = new Array(space.length+1).join(self.SPACE_CHAR); return "" + space + ""; } - + }; } else { @@ -266,27 +273,63 @@ var Text = function(parentEl) { var spaceReplace = " "; } + var _self = this; var characterWidth = this.config.characterWidth; - - for ( var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - - var output = token.value + function addToken(token, value) { + var output = value .replace(/&/g, "&") .replace(/" + c + "" }); - - if (!this.$textToken[token.type]) { + + if (!_self.$textToken[token.type]) { var classes = "ace_" + token.type.replace(/\./g, " ace_"); stringBuilder.push("", output, ""); } else { stringBuilder.push(output); } + } + + var splits = this.session.getRowSplitData(row); + var chars = 0, split = 0, splitChars; + + if (!splits || splits.length == 0) { + splitChars = Number.MAX_VALUE; + } else { + splitChars = splits[0]; + } + + stringBuilder.push("
"); + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + var value = token.value; + + if (chars + value.length < splitChars) { + addToken(token, value); + chars += value.length; + } else { + while (chars + value.length >= splitChars) { + addToken(token, value.substring(0, splitChars - chars)); + value = value.substring(splitChars - chars); + chars = splitChars; + stringBuilder.push("
", + "
"); + split ++; + splitChars = splits[split] || Number.MAX_VALUE; + } + if (value.length != 0) { + chars += value.length; + addToken(token, value); + } + } }; if (this.showInvisibles) { @@ -296,6 +339,7 @@ var Text = function(parentEl) { stringBuilder.push("" + this.EOF_CHAR + ""); } } + stringBuilder.push("
"); }; }).call(Text.prototype); diff --git a/lib/ace/mode/javascript_worker.js b/lib/ace/mode/javascript_worker.js index cef00273..df2251b4 100644 --- a/lib/ace/mode/javascript_worker.js +++ b/lib/ace/mode/javascript_worker.js @@ -17,13 +17,13 @@ oop.inherits(JavaScriptWorker, Mirror); this.onUpdate = function() { var value = this.doc.getValue(); - var start = new Date(); +// var start = new Date(); var parser = require("ace/narcissus/jsparse"); try { parser.parse(value); } catch(e) { - console.log("narcissus") - console.log(e); +// console.log("narcissus") +// console.log(e); sender.emit("narcissus", { row: e.lineno-1, column: null, // TODO convert e.cursor @@ -32,14 +32,14 @@ oop.inherits(JavaScriptWorker, Mirror); }); return; } finally { - console.log("parse time: " + (new Date() - start)); +// console.log("parse time: " + (new Date() - start)); } - var start = new Date(); - console.log("jslint") +// var start = new Date(); +// console.log("jslint") lint(value, {undef: false, onevar: false, passfail: false}); this.sender.emit("jslint", lint.errors); - console.log("lint time: " + (new Date() - start)); +// console.log("lint time: " + (new Date() - start)); } }).call(JavaScriptWorker.prototype); diff --git a/lib/ace/range.js b/lib/ace/range.js index e6727053..4c613699 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -137,7 +137,7 @@ var Range = function(startRow, startColumn, endRow, endColumn) { this.clone = function() { return Range.fromPoints(this.start, this.end); }; - + this.collapseRows = function() { if (this.end.column == 0) return new Range(this.start.row, 0, Math.max(this.start.row, this.end.row-1), 0) @@ -145,10 +145,14 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return new Range(this.start.row, 0, this.end.row, 0) }; - this.toScreenRange = function(doc) { + this.toScreenRange = function(session) { + var screenPosStart = + session.documentToScreenPosition(this.start); + var screenPosEnd = + session.documentToScreenPosition(this.end); return new Range( - this.start.row, doc.documentToScreenColumn(this.start.row, this.start.column), - this.end.row, doc.documentToScreenColumn(this.end.row, this.end.column) + screenPosStart.row, screenPosStart.column, + screenPosEnd.row, screenPosEnd.column ); }; diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 832f0598..2adc54be 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -20,6 +20,7 @@ * * Contributor(s): * Fabian Jakobs + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -165,11 +166,19 @@ var Selection = function(doc) { this.setSelectionRange = function(range, reverse) { if (reverse) { this.setSelectionAnchor(range.end.row, range.end.column); - this.selectTo(range.start.row, range.start.column); + this.selectTo(range.start.row, range.start.column); } else { this.setSelectionAnchor(range.start.row, range.start.column); this.selectTo(range.end.row, range.end.column); } + this.$updateDesiredColumn(); + }; + + this.$updateDesiredColumn = function() { + var cursor = this.getCursor(); + if (cursor) { + this.$desiredColumn = this.doc.documentToScreenColumn(cursor.row, cursor.column); + } }; this.$moveSelection = function(mover) { @@ -248,7 +257,7 @@ var Selection = function(doc) { var column = cursor.column; var range = this.doc.getWordRange(cursor.row, column); this.setSelectionRange(range); - + /*this.setSelectionAnchor(cursor.row, start); this.$moveSelection(function() { this.moveCursorTo(cursor.row, end); @@ -272,7 +281,7 @@ var Selection = function(doc) { this.moveCursorLeft = function() { if (this.selectionLead.column == 0) { - // cursor is a line start + // cursor is a line (start if (this.selectionLead.row > 0) { this.moveCursorTo(this.selectionLead.row - 1, this.doc .getLine(this.selectionLead.row - 1).length); @@ -309,19 +318,27 @@ var Selection = function(doc) { this.moveCursorLineStart = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; - var beforeCursor = this.doc.getLine(row).slice(0, column); + var screenRow = this.doc.documentToScreenRow(row, column); + var firstRowColumn = this.doc.getScreenFirstRowColumn(screenRow); + var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column); var leadingSpace = beforeCursor.match(/^\s*/); - if (leadingSpace[0].length == 0) - this.moveCursorTo(row, this.doc.getLine(row).match(/^\s*/)[0].length); - else if (leadingSpace[0].length >= column) - this.moveCursorTo(row, 0); - else - this.moveCursorTo(row, leadingSpace[0].length); + if (leadingSpace[0].length == 0) { + var lastRowColumn = this.doc.getDocumentLastRowColumn(row, column); + leadingSpace = this.doc.getLine(row). + substring(firstRowColumn, lastRowColumn). + match(/^\s*/); + this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length); + } else if (leadingSpace[0].length >= column) { + this.moveCursorTo(row, firstRowColumn); + } else { + this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length); + } }; this.moveCursorLineEnd = function() { - this.moveCursorTo(this.selectionLead.row, - this.doc.getLine(this.selectionLead.row).length); + var selLead = this.selectionLead; + this.moveCursorTo(selLead.row, + this.doc.getDocumentLastRowColumn(selLead.row, selLead.column)); }; this.moveCursorFileEnd = function() { @@ -387,7 +404,21 @@ var Selection = function(doc) { }; this.moveCursorBy = function(rows, chars) { - this.moveCursorTo(this.selectionLead.row + rows, this.selectionLead.column + chars); + if (this.doc.getUseWrapMode()) { + var screenPos = this.doc.documentToScreenPosition( + this.selectionLead.row, this.selectionLead.column); + var screenCol = + (chars == 0 && this.$desiredColumn) || screenPos.column; + + var docPos = this.doc.screenToDocumentPosition( + screenPos.row + rows, screenCol); + this.moveCursorTo(docPos.row, docPos.column + chars, chars == 0); + } else { + var docColumn = + (chars == 0 && this.$desiredColumn) || this.selectionLead.column; + this.moveCursorTo( + this.selectionLead.row + rows, docColumn + chars, chars == 0); + } }; @@ -395,20 +426,17 @@ var Selection = function(doc) { this.moveCursorTo(position.row, position.column); }; - this.moveCursorTo = function(row, column) { + this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) { var cursor = this.$clipPositionToDocument(row, column); // only dispatch change if the cursor actually changed if (cursor.row !== this.selectionLead.row || cursor.column !== this.selectionLead.column) { this.selectionLead = cursor; + !preventUpdateDesiredColumn && this.$updateDesiredColumn(column); this._dispatchEvent("changeCursor", { data: this.getCursor() }); } }; - this.moveCursorUp = function() { - this.moveCursorBy(-1, 0); - }; - this.$clipPositionToDocument = function(row, column) { var pos = {}; diff --git a/lib/ace/test/all.js b/lib/ace/test/all.js index 00df7b59..46b5b55e 100644 --- a/lib/ace/test/all.js +++ b/lib/ace/test/all.js @@ -57,5 +57,5 @@ async.concat( require("./mode/javascript_tokenizer_test"), require("./mode/text_test"), require("./mode/xml_test"), - require("./mode/xml_tokenizer_test") + require("./mode/xml_tokenizer_test") ).exec(); diff --git a/lib/ace/test/change_document_test.js b/lib/ace/test/change_document_test.js index d00c2af9..9414bdc0 100644 --- a/lib/ace/test/change_document_test.js +++ b/lib/ace/test/change_document_test.js @@ -47,10 +47,10 @@ var EditSession = require("../edit_session").EditSession, assert = require("./assertions"); var Test = { - + setUp : function(next) { this.session1 = new EditSession(["abc", "def"]); - this.session2 = new EditSession(["ghi", "jkl"]); + this.session2 = new EditSession(["ghi", "jkl"]); this.editor = new Editor(new MockRenderer()); next(); }, diff --git a/lib/ace/test/document_test.js b/lib/ace/test/document_test.js index c46602cd..5441893a 100644 --- a/lib/ace/test/document_test.js +++ b/lib/ace/test/document_test.js @@ -20,6 +20,7 @@ * * Contributor(s): * Fabian Jakobs + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -36,10 +37,11 @@ * ***** END LICENSE BLOCK ***** */ var Document = require("../document").Document, + EditSession = require("../edit_session").EditSession, Range = require("../range").Range, assert = require("./assertions"), async = require("asyncjs"); - + var Test = { "test: insert text in line" : function() { @@ -47,194 +49,194 @@ var Test = { var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insert({row: 0, column: 1}, "juhu"); assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["1juhu2", "34"].join("\n")); }, - + "test: insert new line" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); doc.insertNewLine({row: 0, column: 1}); assert.equal(doc.getValue(), ["1", "2", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["1", "2", "34"].join("\n")); }, - + "test: insert lines at the beginning" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insertLines(0, ["aa", "bb"]); assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["aa", "bb", "12", "34"].join("\n")); }, - + "test: insert lines at the end" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insertLines(2, ["aa", "bb"]); assert.equal(doc.getValue(), ["12", "34", "aa", "bb"].join("\n")); }, - + "test: insert lines in the middle" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insertLines(1, ["aa", "bb"]); assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["12", "aa", "bb", "34"].join("\n")); }, - + "test: insert multi line string at the start" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insert({row: 0, column: 0}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["aa", "bb", "cc12", "34"].join("\n")); }, - + "test: insert multi line string at the end" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insert({row: 2, column: 0}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["12", "34aa", "bb", "cc"].join("\n")); }, - + "test: insert multi line string in the middle" : function() { var doc = new Document(["12", "34"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.insert({row: 0, column: 1}, "aa\nbb\ncc"); assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["12", "34"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["1aa", "bb", "cc2", "34"].join("\n")); }, - + "test: delete in line" : function() { var doc = new Document(["1234", "5678"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.remove(new Range(0, 1, 0, 3)); assert.equal(doc.getValue(), ["14", "5678"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["1234", "5678"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["14", "5678"].join("\n")); }, "test: delete new line" : function() { var doc = new Document(["1234", "5678"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.remove(new Range(0, 4, 1, 0)); assert.equal(doc.getValue(), ["12345678"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["1234", "5678"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["12345678"].join("\n")); }, - + "test: delete multi line range line" : function() { var doc = new Document(["1234", "5678", "abcd"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.remove(new Range(0, 2, 2, 2)); assert.equal(doc.getValue(), ["12cd"].join("\n")); - - var d = deltas.concat(); + + var d = deltas.concat(); doc.revertDeltas(d); assert.equal(doc.getValue(), ["1234", "5678", "abcd"].join("\n")); - + doc.applyDeltas(d); assert.equal(doc.getValue(), ["12cd"].join("\n")); }, - + "test: delete full lines" : function() { var doc = new Document(["1234", "5678", "abcd"]); - + var deltas = []; doc.on("change", function(e) { deltas.push(e.data); }); - + doc.remove(new Range(1, 0, 3, 0)); assert.equal(doc.getValue(), ["1234", ""].join("\n")); }, - + "test: remove lines should return the removed lines" : function() { var doc = new Document(["1234", "5678", "abcd"]); - + var removed = doc.removeLines(1, 2); assert.equal(removed.join("\n"), ["5678", "abcd"].join("\n")); }, @@ -246,7 +248,7 @@ var Test = { "test: should handle windows style new lines" : function() { var doc = new Document(["1", "2", "3"].join("\r\n")); - + doc.setNewLineMode("unix"); assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); }, @@ -259,24 +261,108 @@ var Test = { "test: set new line mode to 'unix' should use '\n' as new lines": function() { var doc = new Document(["1", "2", "3"].join("\r\n")); - + doc.setNewLineMode("unix"); assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); }, "test: set new line mode to 'auto' should detect the incoming nl type": function() { var doc = new Document(["1", "2", "3"].join("\n")); - + doc.setNewLineMode("auto"); assert.equal(doc.getValue(), ["1", "2", "3"].join("\n")); var doc = new Document(["1", "2", "3"].join("\r\n")); - + doc.setNewLineMode("auto"); assert.equal(doc.getValue(), ["1", "2", "3"].join("\r\n")); doc.replace(new Range(0, 0, 2, 1), ["4", "5", "6"].join("\n")); assert.equal(["4", "5", "6"].join("\n"), doc.getValue()); + }, + + "test: wrapLine split function" : function() { + var splits; + var computeWrapSplits = EditSession.prototype.$computeWrapSplits; + var c = 0; + + function computeAndAssert(line, assertEqual, wrapLimit, tabSize) { + wrapLimit = wrapLimit || 12; + tabSize = tabSize || 4; + splits = computeWrapSplits.call(EditSession.prototype, line, wrapLimit, tabSize); + console.log("String:", line, "Result:", splits, "Expected:", assertEqual); + assert.ok(splits.length == assertEqual.length); + for (var i = 0; i < splits.length; i++) { + assert.ok(splits[i] == assertEqual[i]); + } + } + + // Basic splitting. + computeAndAssert("foo bar foo bar", [ 12 ]); + computeAndAssert("foo bar f bar", [ 12 ]); + computeAndAssert("foo bar f r", [ 14 ]); + computeAndAssert("foo bar foo bar foo bara foo", [12, 25]); + + // Don't split if there is only whitespaces/tabs at the end of the line. + computeAndAssert("foo foo foo \t \t", [ ]); + + // If there is no space to split, force split. + computeAndAssert("foooooooooooooo", [ 12 ]); + computeAndAssert("fooooooooooooooooooooooooooo", [12, 24]); + computeAndAssert("foo bar fooooooooooobooooooo", [8, 20]); + + // Basic splitting + tabs. + computeAndAssert("foo \t\tbar", [ 6 ]); + computeAndAssert("foo \t \tbar", [ 7 ]); + + // Ignore spaces/tabs at beginning of split. + computeAndAssert("foo \t \t \t \t bar", [ 14 ]); + + // Test wrapping for asian characters. + computeAndAssert("ぁぁ", [1], 2); + computeAndAssert(" ぁぁ", [1, 2], 2); + computeAndAssert(" ぁ\tぁ", [1, 3], 2); + computeAndAssert(" ぁぁ\tぁ", [1, 4], 4); + }, + + "test: documentToScreen": function() { + var tabSize = 4; + var wrapLimit = 12; + var session = new EditSession(["foo bar foo bar"]); + session.setUseWrapMode(true); + session.setWrapLimit(12); + + assert.position(session.documentToScreenPosition(0, 11), 0, 11); + assert.position(session.documentToScreenPosition(0, 12), 1, 0); + + session = new EditSession(["ぁぁa"]); + session.setUseWrapMode(true); + session.setWrapLimit(2); + assert.position(session.documentToScreenPosition(0, 1), 1, 0); + assert.position(session.documentToScreenPosition(0, 2), 2, 0); + assert.position(session.documentToScreenPosition(0, 4), 2, 1); + }, + + "test: screenToDocument": function() { + var tabSize = 4; + var wrapLimit = 12; + var session = new EditSession(["foo bar foo bar"]); + session.setUseWrapMode(true); + session.setWrapLimit(12); + + assert.position(session.screenToDocumentPosition(1, 0), 0, 12); + assert.position(session.screenToDocumentPosition(0, 11), 0, 11); + // Check if the position is clamped the right way. + assert.position(session.screenToDocumentPosition(0, 12), 0, 11); + assert.position(session.screenToDocumentPosition(0, 20), 0, 11); + + session = new EditSession(["ぁ a"]); + session.setUseWrapMode(true); + assert.position(session.screenToDocumentPosition(0, 1), 0, 0); + assert.position(session.screenToDocumentPosition(0, 2), 0, 1); + assert.position(session.screenToDocumentPosition(0, 3), 0, 2); + assert.position(session.screenToDocumentPosition(0, 4), 0, 3); + assert.position(session.screenToDocumentPosition(0, 5), 0, 3); } }; @@ -285,4 +371,4 @@ module.exports = require("asyncjs/test").testcase(Test); if (module === require.main) { require("../../../support/paths"); exports.exec() -} \ No newline at end of file +} diff --git a/lib/ace/test/edit_session_test.js b/lib/ace/test/edit_session_test.js index 1cf721fa..cce711ae 100644 --- a/lib/ace/test/edit_session_test.js +++ b/lib/ace/test/edit_session_test.js @@ -43,7 +43,7 @@ var EditSession = require("ace/edit_session").EditSession, Range = require("ace/range").Range, assert = require("./assertions"), async = require("asyncjs"); - + var Test = { "test: find matching opening bracket" : function() { @@ -81,16 +81,16 @@ var Test = { "test: move lines down" : function() { var session = new EditSession(["a1", "a2", "a3", "a4"]); - + session.moveLinesDown(0, 1); assert.equal(session.getValue(), ["a3", "a1", "a2", "a4"].join("\n")); - + session.moveLinesDown(1, 2); assert.equal(session.getValue(), ["a3", "a4", "a1", "a2"].join("\n")); - + session.moveLinesDown(2, 3); assert.equal(session.getValue(), ["a3", "a4", "a1", "a2"].join("\n")); - + session.moveLinesDown(2, 2); assert.equal(session.getValue(), ["a3", "a4", "a2", "a1"].join("\n")); }, @@ -100,13 +100,13 @@ var Test = { session.moveLinesUp(2, 3); assert.equal(session.getValue(), ["a1", "a3", "a4", "a2"].join("\n")); - + session.moveLinesUp(1, 2); assert.equal(session.getValue(), ["a3", "a4", "a1", "a2"].join("\n")); - + session.moveLinesUp(0, 1); assert.equal(session.getValue(), ["a3", "a4", "a1", "a2"].join("\n")); - + session.moveLinesUp(2, 2); assert.equal(session.getValue(), ["a3", "a1", "a4", "a2"].join("\n")); }, @@ -177,57 +177,57 @@ var Test = { assert.equal(session.screenToDocumentColumn(0, 15), 12); assert.equal(session.screenToDocumentColumn(0, 19), 13); }, - + "test: insert text in multiple rows": function() { var session = new EditSession(["12", "", "abcd"]); - + var inserted = session.multiRowInsert([0, 1, 2], 2, "juhu 1"); assert.equal(inserted.rows, 0); assert.equal(inserted.columns, 6); - + assert.equal(session.getValue(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); }, "test: undo insert text in multiple rows": function() { var session = new EditSession(["12", "", "abcd"]); - + var undoManager = new UndoManager(); session.setUndoManager(undoManager); - + session.multiRowInsert([0, 1, 2], 2, "juhu 1"); session.$informUndoManager.call(); assert.equal(session.getValue(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); undoManager.undo(); assert.equal(session.getValue(), ["12", "", "abcd"].join("\n")); - + undoManager.redo(); assert.equal(session.getValue(), ["12juhu 1", " juhu 1", "abjuhu 1cd"].join("\n")); }, - + "test: insert new line in multiple rows": function() { var session = new EditSession(["12", "", "abcd"]); - + var inserted = session.multiRowInsert([0, 1, 2], 2, "\n"); assert.equal(inserted.rows, 1); assert.equal(session.getValue(), ["12\n", " \n", "ab\ncd"].join("\n")); }, - + "test: insert multi line text in multiple rows": function() { var session = new EditSession(["12", "", "abcd"]); - + var inserted = session.multiRowInsert([0, 1, 2], 2, "juhu\n12"); assert.equal(inserted.rows, 1); assert.equal(session.getValue(), ["12juhu\n12", " juhu\n12", "abjuhu\n12cd"].join("\n")); }, - + "test: remove right in multiple rows" : function() { var session = new EditSession(["12", "", "abcd"]); session.multiRowRemove([0, 1, 2], new Range(0, 2, 0, 3)); assert.equal(session.getValue(), ["12", "", "abd"].join("\n")); }, - + "test: undo remove right in multiple rows" : function() { var session = new EditSession(["12", "", "abcd"]); var undoManager = new UndoManager(); @@ -236,33 +236,43 @@ var Test = { session.multiRowRemove([0, 1, 2], new Range(0, 1, 0, 3)); session.$informUndoManager.call(); assert.equal(session.getValue(), ["1", "", "ad"].join("\n")); - + undoManager.undo(); assert.equal(session.getValue(), ["12", "", "abcd"].join("\n")); - + undoManager.redo(); assert.equal(session.getValue(), ["1", "", "ad"].join("\n")); }, - + "test get longest line" : function() { var session = new EditSession(["12"]); session.setTabSize(4); assert.equal(session.getWidth(), 2); assert.equal(session.getScreenWidth(), 2); - + session.doc.insertNewLine(0); session.doc.insertLines(1, ["123"]); assert.equal(session.getWidth(), 3); assert.equal(session.getScreenWidth(), 3); - + session.doc.insertNewLine(0); session.doc.insertLines(1, ["\t\t"]); + assert.equal(session.getWidth(), 3); assert.equal(session.getScreenWidth(), 8); - + session.setTabSize(2); assert.equal(session.getWidth(), 3); assert.equal(session.getScreenWidth(), 4); + }, + + "test getDisplayString": function() { + var session = new EditSession(["12"]); + session.setTabSize(4); + + assert.equal(session.$getDisplayTokens("\t").length, 4); + assert.equal(session.$getDisplayTokens("abc").length, 3); + assert.equal(session.$getDisplayTokens("abc\t").length, 7); } }; diff --git a/lib/ace/test/mockrenderer.js b/lib/ace/test/mockrenderer.js index 412a418a..b076cc78 100644 --- a/lib/ace/test/mockrenderer.js +++ b/lib/ace/test/mockrenderer.js @@ -50,6 +50,8 @@ MockRenderer = function(visibleRowCount) { firstVisibleRow : 0, lastVisibleRow : this.visibleRowCount }; + + this.isMockRenderer = true; }; @@ -117,10 +119,10 @@ MockRenderer.prototype.addMarker = function() { MockRenderer.prototype.setBreakpoints = function() { }; -MockRenderer.prototype.updateFull = function() { +MockRenderer.prototype.updateFull = function() { }; -MockRenderer.prototype.updateText = function() { +MockRenderer.prototype.updateText = function() { }; MockRenderer.prototype.showCursor = function() { @@ -132,5 +134,12 @@ MockRenderer.prototype.visualizeFocus = function() { MockRenderer.prototype.setAnnotations = function() { }; +MockRenderer.prototype.textToScreenCoordinates = function() { + return { + pageX: 0, + pageY: 0 + } +}; + return MockRenderer; }); diff --git a/lib/ace/test/range_test.js b/lib/ace/test/range_test.js index 8415f135..74bd136f 100644 --- a/lib/ace/test/range_test.js +++ b/lib/ace/test/range_test.js @@ -132,32 +132,32 @@ var Test = { var range = range.extend(2, 5); assert.range(range, 2, 5, 2, 30); - + var range = range.extend(2, 35); assert.range(range, 2, 5, 2, 35); - + var range = range.extend(2, 15); assert.range(range, 2, 5, 2, 35); - + var range = range.extend(1, 4); assert.range(range, 1, 4, 2, 35); - + var range = range.extend(6, 10); assert.range(range, 1, 4, 6, 10); }, - + "test: collapse rows" : function() { var range = new Range(0, 2, 1, 2); - assert.range(range.collapseRows(), 0, 0, 1, 0); + assert.range(range.collapseRows(), 0, 0, 1, 0); var range = new Range(2, 2, 3, 1); - assert.range(range.collapseRows(), 2, 0, 3, 0); + assert.range(range.collapseRows(), 2, 0, 3, 0); var range = new Range(2, 2, 3, 0); - assert.range(range.collapseRows(), 2, 0, 2, 0); + assert.range(range.collapseRows(), 2, 0, 2, 0); var range = new Range(2, 0, 2, 0); - assert.range(range.collapseRows(), 2, 0, 2, 0); + assert.range(range.collapseRows(), 2, 0, 2, 0); } }; diff --git a/lib/ace/test/text_edit_test.js b/lib/ace/test/text_edit_test.js index 0ae2615e..9ef70da5 100644 --- a/lib/ace/test/text_edit_test.js +++ b/lib/ace/test/text_edit_test.js @@ -126,19 +126,19 @@ var Test = { "test: indent selected lines" : function() { var session = new EditSession(["a12345", "b12345", "c12345"].join("\n")); var editor = new Editor(new MockRenderer(), session); - + editor.moveCursorTo(1, 0); editor.getSelection().selectDown(); - + editor.indent(); assert.equal(["a12345", " b12345", "c12345"].join("\n"), session.toString()); }, - + "test: no auto indent if cursor is before the {" : function() { var session = new EditSession("{", new JavaScriptMode()); var editor = new Editor(new MockRenderer(), session); - - editor.moveCursorTo(0, 0); + + editor.moveCursorTo(0, 0); editor.onTextInput("\n"); assert.equal(["", "{"].join("\n"), session.toString()); }, @@ -149,7 +149,7 @@ var Test = { editor.moveCursorTo(0, 5); editor.getSelection().selectDown(); - editor.getSelection().selectDown(); + editor.getSelection().selectDown(); editor.blockOutdent(); assert.equal(session.toString(), [" a12345", "b12345", " c12345"].join("\n")); @@ -208,21 +208,21 @@ var Test = { assert.equal([" abc", "cde"].join("\n"), session.toString()); assert.range(editor.getSelectionRange(), 0, 0, 1, 1); }, - + "test: toggle comment lines twice should return the original text" : function() { var session = new EditSession([" abc", "cde", "fg"], new JavaScriptMode()); var editor = new Editor(new MockRenderer(), session); - + editor.moveCursorTo(0, 0); editor.getSelection().selectDown(); editor.getSelection().selectDown(); - + editor.toggleCommentLines(); editor.toggleCommentLines(); - + assert.equal([" abc", "cde", "fg"].join("\n"), session.toString()); }, - + "test: comment lines - if the selection end is at the line start it should stay there": function() { //select down @@ -366,71 +366,71 @@ var Test = { editor.onTextInput("\t"); assert.equal(session.toString(), "\t"); }, - + "test: undo/redo for delete line" : function() { var session = new EditSession(["111", "222", "333"]); var undoManager = new UndoManager(); session.setUndoManager(undoManager); - + var initialText = session.toString(); var editor = new Editor(new MockRenderer(), session); - + editor.removeLines(); var step1 = session.toString(); assert.equal(step1, "222\n333"); session.$informUndoManager.call(); - + editor.removeLines(); var step2 = session.toString(); assert.equal(step2, "333"); session.$informUndoManager.call(); - + editor.removeLines(); var step3 = session.toString(); assert.equal(step3, ""); session.$informUndoManager.call(); - - + + undoManager.undo(); session.$informUndoManager.call(); assert.equal(session.toString(), step2); - + undoManager.undo(); session.$informUndoManager.call(); assert.equal(session.toString(), step1); - + undoManager.undo(); session.$informUndoManager.call(); assert.equal(session.toString(), initialText); - + undoManager.undo(); session.$informUndoManager.call(); assert.equal(session.toString(), initialText); }, - + "test: remove left should remove character left of the cursor" : function() { var session = new EditSession(["123", "456"]); - + var editor = new Editor(new MockRenderer(), session); editor.moveCursorTo(1, 1); editor.removeLeft(); assert.equal(session.toString(), "123\n56"); }, - + "test: remove left should remove line break if cursor is at line start" : function() { var session = new EditSession(["123", "456"]); - + var editor = new Editor(new MockRenderer(), session); editor.moveCursorTo(1, 0); editor.removeLeft(); assert.equal(session.toString(), "123456"); }, - + "test: remove left should remove tabsize spaces if cursor is on a tab stop and preceeded by spaces" : function() { var session = new EditSession(["123", " 456"]); session.setUseSoftTabs(true); session.setTabSize(4); - + var editor = new Editor(new MockRenderer(), session); editor.moveCursorTo(1, 8); editor.removeLeft(); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 134fe487..6478b0f1 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -22,6 +22,7 @@ * Contributor(s): * Fabian Jakobs * Irakli Gozalishvili (http://jeditoolkit.com) + * Julian Viereck * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -115,12 +116,11 @@ var VirtualRenderer = function(container, theme) { this.$loop = new RenderLoop(this.$renderChanges.bind(this)); this.$loop.schedule(this.CHANGE_FULL); - this.$updatePrintMargin(); this.setPadding(4); + this.$updatePrintMargin(); }; (function() { - this.showGutter = true; this.CHANGE_CURSOR = 1; @@ -138,8 +138,8 @@ var VirtualRenderer = function(container, theme) { this.session = session; this.$cursorLayer.setSession(session); this.$markerLayer.setSession(session); + this.$gutterLayer.setSession(session); this.$textLayer.setSession(session); - this.$loop.schedule(this.CHANGE_FULL); }; @@ -184,11 +184,11 @@ var VirtualRenderer = function(container, theme) { /** * Triggers resize of the editor */ - this.onResize = function() { + this.onResize = function(force) { var changes = this.CHANGE_SIZE; var height = dom.getInnerHeight(this.container); - if (this.$size.height != height) { + if (force || this.$size.height != height) { this.$size.height = height; this.scroller.style.height = height + "px"; @@ -201,7 +201,7 @@ var VirtualRenderer = function(container, theme) { } var width = dom.getInnerWidth(this.container); - if (this.$size.width != width) { + if (force || this.$size.width != width) { this.$size.width = width; var gutterWidth = this.showGutter ? this.$gutter.offsetWidth : 0; @@ -264,10 +264,7 @@ var VirtualRenderer = function(container, theme) { return; this.$gutter.style.display = show ? "block" : "none"; this.showGutter = show; - // set fake width to make onResize work - this.$size.width = -1 - this.onResize(); - this.$gutterLayer.update(this.layerConfig) + this.onResize(true); } this.$updatePrintMargin = function() { @@ -285,7 +282,7 @@ var VirtualRenderer = function(container, theme) { } var style = this.$printMarginEl.style; - style.left = (this.characterWidth * this.$printMarginColumn) + "px"; + style.left = ((this.characterWidth * this.$printMarginColumn) + this.$padding * 2) + "px"; style.visibility = this.$showPrintMargin ? "visible" : "hidden"; }; @@ -297,6 +294,19 @@ var VirtualRenderer = function(container, theme) { return this.content; }; + this.getTextAreaContainer = function() { + return this.scroller; + }; + + this.moveTextAreaToCursor = function(textarea) { + var pos = this.$cursorLayer.getPixelPosition(); + if (!pos) + return; + + textarea.style.left = (pos.left + this.$padding) + "px"; + textarea.style.top = pos.top + "px"; + }; + this.getFirstVisibleRow = function() { return (this.layerConfig || {}).firstRow || 0; }; @@ -325,6 +335,7 @@ var VirtualRenderer = function(container, theme) { this.$padding = padding; this.content.style.padding = "0 " + padding + "px"; this.$loop.schedule(this.CHANGE_FULL); + this.$updatePrintMargin(); }; this.onScroll = function(e) { @@ -332,14 +343,14 @@ var VirtualRenderer = function(container, theme) { }; this.$updateScrollBar = function() { - this.scrollBar.setInnerHeight(this.session.getLength() * this.lineHeight); + this.scrollBar.setInnerHeight(this.session.getScreenLength() * this.lineHeight); this.scrollBar.setScrollTop(this.scrollTop); }; this.$renderChanges = function(changes) { if (!changes || !this.session || !this.$tokenizer) return; - + // text, scrolling and resize changes can cause the view port size to change if (!this.layerConfig || changes & this.CHANGE_FULL || @@ -357,6 +368,7 @@ var VirtualRenderer = function(container, theme) { this.$markerLayer.update(this.layerConfig); this.$cursorLayer.update(this.layerConfig); this.$updateScrollBar(); + this.scrollCursorIntoView(); return; } @@ -397,20 +409,36 @@ var VirtualRenderer = function(container, theme) { }; this.$computeLayerConfig = function() { + var session = this.session; + var offset = this.scrollTop % this.lineHeight; var minHeight = this.$size.scrollerHeight + this.lineHeight; var longestLine = this.$getLongestLine(); var widthChanged = !this.layerConfig ? true : (this.layerConfig.width != longestLine); - var lineCount = Math.ceil(minHeight / this.lineHeight); + var lineCount = Math.ceil(minHeight / this.lineHeight) - 1; var firstRow = Math.max(0, Math.round((this.scrollTop - offset) / this.lineHeight)); - var lastRow = Math.max(0, Math.min(this.session.getLength(), firstRow + lineCount) - 1); + var lastRow = firstRow + lineCount; + + // Map lines on the screen to lines in the document. + var firstRowScreen, firstRowHeight; + var lineHeight = { lineHeight: this.lineHeight }; + firstRow = session.screenToDocumentRow(firstRow); + firstRowScreen = session.documentToScreenRow(firstRow); + firstRowHeight = session.getRowHeight(lineHeight, firstRow); + + lastRow = Math.min(session.screenToDocumentRow(lastRow), session.getLength() - 1); + minHeight = this.$size.scrollerHeight + session.getRowHeight(lineHeight, lastRow)+ + firstRowHeight; + + offset = this.scrollTop - firstRowScreen * this.lineHeight; var layerConfig = this.layerConfig = { width : longestLine, padding : this.$padding, firstRow : firstRow, + firstRowScreen: firstRowScreen, lastRow : lastRow, lineHeight : this.lineHeight, characterWidth : this.characterWidth, @@ -545,7 +573,7 @@ var VirtualRenderer = function(container, theme) { }; this.scrollToY = function(scrollTop) { - var maxHeight = this.session.getLength() * this.lineHeight - this.$size.scrollerHeight; + var maxHeight = this.session.getScreenLength() * this.lineHeight - this.$size.scrollerHeight; var scrollTop = Math.max(0, Math.min(maxHeight, scrollTop)); if (this.scrollTop !== scrollTop) { @@ -574,17 +602,15 @@ var VirtualRenderer = function(container, theme) { var row = Math.floor((pageY + this.scrollTop - canvasPos.top) / this.lineHeight); - return { - row : row, - column : this.session.screenToDocumentColumn(Math.max(0, Math.min(row, this.session.getLength()-1)), col) - }; + return this.session.screenToDocumentPosition(row, Math.max(col, 0)); }; this.textToScreenCoordinates = function(row, column) { var canvasPos = this.scroller.getBoundingClientRect(); + var pos = this.session.documentToScreenPosition(row, column); - var x = this.$padding + Math.round(this.session.documentToScreenColumn(row, column) * this.characterWidth); - var y = row * this.lineHeight; + var x = this.$padding + Math.round(pos.column * this.characterWidth); + var y = pos.row * this.lineHeight; return { pageX: canvasPos.left + x - this.getScrollLeft(), @@ -601,7 +627,6 @@ var VirtualRenderer = function(container, theme) { }; this.showComposition = function(position) { - console.log("show composition") if (!this.$composition) { this.$composition = document.createElement("div"); this.$composition.className = "ace_composition"; @@ -609,19 +634,18 @@ var VirtualRenderer = function(container, theme) { } this.$composition.innerHTML = " "; - + var pos = this.$cursorLayer.getPixelPosition(); var style = this.$composition.style; style.top = pos.top + "px"; style.left = (pos.left + this.$padding) + "px"; style.height = this.lineHeight + "px"; - //style.width = this.characterWidth + "px"; this.hideCursor(); }; this.setCompositionText = function(text) { - this.$composition.innerText = this.$composition.textContent = text; + dom.setInnerText(this.$composition, text); }; this.hideComposition = function() { @@ -629,7 +653,7 @@ var VirtualRenderer = function(container, theme) { if (!this.$composition) return; - + var style = this.$composition.style; style.top = "-10000px"; style.left = "-10000px"; diff --git a/lib/ace/worker/host.js b/lib/ace/worker/worker.js similarity index 100% rename from lib/ace/worker/host.js rename to lib/ace/worker/worker.js diff --git a/lib/ace/worker/worker_client.js b/lib/ace/worker/worker_client.js index 5e8e3c28..40e41050 100644 --- a/lib/ace/worker/worker_client.js +++ b/lib/ace/worker/worker_client.js @@ -16,10 +16,10 @@ var WorkerClient = function(baseUrl, topLevelNamespaces, module, classname) { this.callbacks = []; if (require.packaged) { - var worker = this.$worker = new Worker("host.js"); + var worker = this.$worker = new Worker("worker.js"); } else { - var workerUrl = require.nameToUrl("ace/worker/host", null, "_"); + var workerUrl = require.nameToUrl("ace/worker/worker", null, "_"); var worker = this.$worker = new Worker(workerUrl); var tlns = {}; diff --git a/package.json b/package.json index be75d1ac..39869a33 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "asyncjs": ">=0.0.2", "jsdom": ">=0.1.23", "htmlparser": ">=1.7.2", - "dryice": ">=0.1.0" + "dryice": ">=0.2.1" }, "licenses": [{ "type": "MPL", diff --git a/support/paths.js b/support/paths.js index b081ec7a..c0dcee83 100644 --- a/support/paths.js +++ b/support/paths.js @@ -1,4 +1,6 @@ require("./requireJS-node"); require.paths.unshift(__dirname + "/../lib"); require.paths.unshift(__dirname + "/pilot/lib"); -require.paths.unshift(__dirname); \ No newline at end of file +require.paths.unshift(__dirname + "/async/lib"); +require.paths.unshift(__dirname + "/jsdom/lib"); +require.paths.unshift(__dirname); diff --git a/support/pilot b/support/pilot index af903446..5474f171 160000 --- a/support/pilot +++ b/support/pilot @@ -1 +1 @@ -Subproject commit af90344687c8486892b44d5decc4c2181df3a6cf +Subproject commit 5474f1715dcc199a0c69211c89b594b9f569d9ed