From c010348eae895dcaccbed9b6595cb8d915ee7c60 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 02:34:15 +0200 Subject: [PATCH 01/53] First iteration of code folding - not much working yet. --- demo/demo.js | 12 ++++ lib/ace/edit_session.js | 126 +++++++++++++++++++++++++++++++----- lib/ace/editor.js | 63 ++++++++++-------- lib/ace/layer/gutter.js | 7 +- lib/ace/layer/text.js | 39 +++++++++-- lib/ace/virtual_renderer.js | 6 +- 6 files changed, 198 insertions(+), 55 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 9c64de10..67ee5c86 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -147,6 +147,18 @@ exports.launch = function(env) { var container = document.getElementById("editor"); env.editor = new Editor(new Renderer(container, theme)); + // BEGING TESTING + var Range = require("ace/range").Range; + docs.js.addFold(new Range(1,0, 2, 999), "foo..."); + window.s = docs.js; + window.e = env.editor; + setTimeout(function() { + env.editor.selection.addEventListener("changeCursor", function() { + console.log(env.editor.selection.getRange() + ""); + }) + }, 500) + // END TESTING + var modes = { text: new TextMode(), textile: new TextileMode(), diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 2ba5e9ab..55d48473 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -67,6 +67,12 @@ var EditSession = function(text, mode) { this.setMode(mode); else this.setMode(new TextMode()); + + // Set the initial foldData content. + var foldData = this.$foldData = []; + for (var row = 0; row < this.doc.$lines.length; row++) { + foldData.push(false); + } }; @@ -94,8 +100,7 @@ var EditSession = function(text, mode) { this.$informUndoManager.schedule(); } - this.$updateWrapDataOnChange(e); - + this.$updateInternalDataOnChange(e); this.bgTokenizer.start(delta.range.start.row); this._dispatchEvent("change", e); }; @@ -118,7 +123,7 @@ var EditSession = function(text, mode) { this.getState = function(row) { return this.bgTokenizer.getState(row); }; - + this.getTokens = function(firstRow, lastRow) { return this.bgTokenizer.getTokens(firstRow, lastRow); }; @@ -411,7 +416,7 @@ var EditSession = function(text, mode) { } else { this.bgTokenizer.setTokenizer(tokenizer); } - + this.bgTokenizer.setDocument(this.getDocument()); this.bgTokenizer.start(0); @@ -848,11 +853,9 @@ var EditSession = function(text, mode) { }; }; - this.$updateWrapDataOnChange = function(e) { - if (!this.$useWrapMode) { - return; - } - + // TODO: Really want to keep this name? + this.$updateInternalDataOnChange = function(e) { + var useWrapMode = this.$useWrapMode; var len; var action = e.data.action; var firstRow = e.data.range.start.row, @@ -871,20 +874,35 @@ var EditSession = function(text, mode) { if (len != 0) { if (action.indexOf("remove") != -1) { - this.$wrapData.splice(firstRow, len); + useWrapMode && this.$wrapData.splice(firstRow, len); + // TODO: More checking needed here. + this.$foldData.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); + var args; + if (useWrapMode) { + args = [firstRow, 0]; + for (var i = 0; i < len; i++) args.push([]); + this.$wrapData.splice.apply(this.$wrapData, args); + } + + args = [firstRow, 0]; + for (var i = 0; i < len; i++) args.push(false); + // TODO: More checking needed here. + this.$foldData.splice.apply(this.$foldData, args); } } - if (this.$wrapData.length != this.doc.$lines.length) { + if (useWrapMode && this.$wrapData.length != this.doc.$lines.length) { console.error("The length of doc.$lines and $wrapData have to be the same!"); } + if (this.$foldData.length != this.doc.$lines.length) { + console.error("The length of doc.$lines and $foldData have to be the same!"); + } - this.$updateWrapData(firstRow, lastRow); + // TODO: + // this.$updateFoldData(firstRow, lastRow); + useWrapMode && this.$updateWrapData(firstRow, lastRow); }; this.$updateWrapData = function(firstRow, lastRow) { @@ -1312,7 +1330,7 @@ var EditSession = function(text, mode) { } return screenRows; } - + // For every keystroke this gets called once per char in the whole doc!! // Wouldn't hurt to make it a bit faster for c >= 0x1100 function isFullWidth(c) { @@ -1350,8 +1368,84 @@ var EditSession = function(text, mode) { c >= 0xFE68 && c <= 0xFE6B || c >= 0xFF01 && c <= 0xFF60 || c >= 0xFFE0 && c <= 0xFFE6; + }; + + + // == Folding Code ======================================================== + + /** + * Simple fold-data struct. + **/ + function Fold(range, placeholder) { + this.placeholder = placeholder; + this.range = range; + this.start = range.start; + this.end = range.end; } + /** + * Adds a new fold. + */ + this.addFold = function(range, placeholder) { + var startRow = range.start.row, + endRow = range.end.row, + foldData = this.$foldData; + + // In case there is no fold data for the start row yet. + if (!Array.isArray(foldData[startRow])) { + foldData[startRow] = []; + } + + var fold = new Fold(range, placeholder); + foldData[startRow].push(fold); + + // Mark all lines folded by this fold as folded. + for (var row = startRow + 1; row <= endRow; row++) { + foldData[row] = fold; + } + + // TODO: Recalculate wrapData + // TODO: Recalculate width etc. + // TODO: Mark as dirty etc. + + // Notify that fold data has changed. + this._dispatchEvent("changeFold"); + }; + + /** + * Checks if a given documentRow is folded. This is true if there are some + * folded parts such that some parts of the line is still visible. + **/ + this.isRowFolded = function(docRow) { + return this.$foldData[docRow]; + }; + + this.getRowLastFold = function(docRow) { + var fold = this.$foldData[docRow]; + if (!fold) { + return false; + } else if (Array.isArray(fold)) { + return fold[fold.length - 1]; + } else { + return fold; + } + }; + + this.getRowFoldEnd = function(docRow) { + return (this.$foldData[docRow] + ? this.getRowLastFold(docRow).end.row + : docRow); + }; + + /** + * Checks if a given documentRow is visible or not. Not beeing visible means + * it's folded completly. + **/ + this.isRowVisible = function(docRow) { + var fold = this.$foldData[docRow]; + return !fold || Array.isArray(fold); + }; + }).call(EditSession.prototype); exports.EditSession = EditSession; diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 691ca58b..51d86da7 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -62,7 +62,7 @@ var Editor =function(renderer, session) { this.textInput = new TextInput(renderer.getTextAreaContainer(), this); this.keyBinding = new KeyBinding(this); - + // TODO detect touch event support if (useragent.isIPad) { //this.$mouseHandler = new TouchHandler(this); @@ -125,6 +125,7 @@ var Editor =function(renderer, session) { this.session.removeEventListener("changeTabSize", this.$onChangeTabSize); this.session.removeEventListener("changeWrapLimit", this.$onChangeWrapLimit); this.session.removeEventListener("changeWrapMode", this.$onChangeWrapMode); + this.session.removeEventListener("onChangeFold", this.$onChangeFold); this.session.removeEventListener("changeFrontMarker", this.$onChangeFrontMarker); this.session.removeEventListener("changeBackMarker", this.$onChangeBackMarker); this.session.removeEventListener("changeBreakpoint", this.$onChangeBreakpoint); @@ -159,18 +160,21 @@ var Editor =function(renderer, session) { this.$onChangeWrapMode = this.onChangeWrapMode.bind(this); session.addEventListener("changeWrapMode", this.$onChangeWrapMode); + this.$onChangeFold = this.onChangeFold.bind(this); + session.addEventListener("changeFold", this.$onChangeFold); + this.$onChangeFrontMarker = this.onChangeFrontMarker.bind(this); this.session.addEventListener("changeFrontMarker", this.$onChangeFrontMarker); - + this.$onChangeBackMarker = this.onChangeBackMarker.bind(this); this.session.addEventListener("changeBackMarker", this.$onChangeBackMarker); - + this.$onChangeBreakpoint = this.onChangeBreakpoint.bind(this); this.session.addEventListener("changeBreakpoint", this.$onChangeBreakpoint); this.$onChangeAnnotation = this.onChangeAnnotation.bind(this); this.session.addEventListener("changeAnnotation", this.$onChangeAnnotation); - + this.$onCursorChange = this.onCursorChange.bind(this); this.session.addEventListener("changeOverwrite", this.$onCursorChange); @@ -251,7 +255,7 @@ var Editor =function(renderer, session) { // to be on the save side we do both // except for IE var _self = this; - if (!useragent.isIE) { + if (!useragent.isIE) { setTimeout(function() { _self.textInput.focus(); }); @@ -311,7 +315,7 @@ var Editor =function(renderer, session) { this.$updateHighlightActiveLine = function() { var session = this.getSession(); - + if (session.$highlightLineMarker) { session.removeMarker(session.$highlightLineMarker); } @@ -326,7 +330,7 @@ var Editor =function(renderer, session) { this.onSelectionChange = function(e) { var session = this.getSession(); - + if (session.$selectionMarker) { session.removeMarker(session.$selectionMarker); } @@ -347,11 +351,11 @@ var Editor =function(renderer, session) { this.onChangeFrontMarker = function() { this.renderer.updateFrontMarkers(); }; - + this.onChangeBackMarker = function() { this.renderer.updateBackMarkers(); }; - + this.onChangeBreakpoint = function() { this.renderer.setBreakpoints(this.session.getBreakpoints()); }; @@ -372,6 +376,11 @@ var Editor =function(renderer, session) { this.renderer.onResize(true); }; + this.onChangeFold = function() { + // TODO: This might be too much updating. Okay for now. + this.render.updateFull(); + }; + this.getCopyText = function() { if (!this.selection.isEmpty()) { return this.session.getTextRange(this.getSelectionRange()); @@ -397,7 +406,7 @@ var Editor =function(renderer, session) { var session = this.session; var mode = session.getMode(); - + var cursor = this.getCursorPosition(); text = text.replace("\t", this.session.getTabString()); @@ -420,14 +429,14 @@ var Editor =function(renderer, session) { var lineIndent = mode.getNextLineIndent(lineState, line.slice(0, cursor.column), session.getTabString()); var end = session.insert(cursor, text); - var lineState = session.getState(cursor.row); + // TODO disabled multiline auto indent // possibly doing the indent before inserting the text // if (cursor.row !== end.row) { if (session.getDocument().isNewLine(text)) { this.moveCursorTo(cursor.row+1, 0); - + var size = session.getTabSize(); var minIndent = Number.MAX_VALUE; @@ -462,7 +471,7 @@ var Editor =function(renderer, session) { if (shouldOutdent) { mode.autoOutdent(lineState, session, cursor.row); } - }; + }; } this.onTextInput = function(text) { @@ -591,7 +600,7 @@ var Editor =function(renderer, session) { this.session.remove(this.getSelectionRange()); this.clearSelection(); }; - + this.removeWordRight = function() { if (this.$readOnly) return; @@ -602,7 +611,7 @@ var Editor =function(renderer, session) { this.session.remove(this.getSelectionRange()); this.clearSelection(); }; - + this.removeWordLeft = function() { if (this.$readOnly) return; @@ -613,7 +622,7 @@ var Editor =function(renderer, session) { this.session.remove(this.getSelectionRange()); this.clearSelection(); }; - + this.removeToLineStart = function() { if (this.$readOnly) return; @@ -624,7 +633,7 @@ var Editor =function(renderer, session) { this.session.remove(this.getSelectionRange()); this.clearSelection(); }; - + this.removeToLineEnd = function() { if (this.$readOnly) return; @@ -639,30 +648,30 @@ var Editor =function(renderer, session) { this.splitLine = function() { if (this.$readOnly) return; - + if (!this.selection.isEmpty()) { this.session.remove(this.getSelectionRange()); this.clearSelection(); } - + var cursor = this.getCursorPosition(); this.insert("\n"); this.moveCursorToPosition(cursor); }; - + this.transposeLetters = function() { if (this.$readOnly) return; - + if (!this.selection.isEmpty()) { return; } - + var cursor = this.getCursorPosition(); var column = cursor.column; if (column == 0) return; - + var line = this.session.getLine(cursor.row); if (column < line.length) { var swap = line.charAt(column) + line.charAt(column-1); @@ -674,7 +683,7 @@ var Editor =function(renderer, session) { } this.session.replace(range, swap); }; - + this.indent = function() { if (this.$readOnly) return; @@ -887,7 +896,7 @@ var Editor =function(renderer, session) { this.scrollToLine = function(line, center) { this.renderer.scrollToLine(line, center); }; - + this.centerSelection = function() { var range = this.getSelectionRange(); var line = Math.floor(range.start.row + (range.end.row - range.start.row) / 2); @@ -912,7 +921,7 @@ var Editor =function(renderer, session) { this.selection.selectAll(); this.$blockScrolling -= 1; }; - + this.clearSelection = function() { this.selection.clearSelection(); }; @@ -1039,7 +1048,7 @@ var Editor =function(renderer, session) { this.$blockScrolling += 1; for (var i = ranges.length - 1; i >= 0; --i) this.$tryReplace(ranges[i], replacement); - + this.selection.setSelectionRange(selection); this.$blockScrolling -= 1; }, diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index b4354b5b..e5fb1c9c 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -73,12 +73,12 @@ var Gutter = function(parentEl) { this.setAnnotations = function(annotations) { // iterate over sparse array - this.$annotations = []; + this.$annotations = []; for (var row in annotations) if (annotations.hasOwnProperty(row)) { var rowAnnotations = annotations[row]; if (!rowAnnotations) continue; - + var rowInfo = this.$annotations[row] = { text: [] }; @@ -111,6 +111,9 @@ var Gutter = function(parentEl) { annotation.className, "' title='", annotation.text.join("\n"), "' style='height:", this.session.getRowHeight(config, i), "px;'>", (i+1), ""); + + + i = this.session.getRowFoldEnd(i); } this.element = dom.setInnerHtml(this.element, html.join("")); this.element.style.height = config.minHeight + "px"; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index a4fdee3d..50bf0f5c 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -186,18 +186,30 @@ var Text = function(parentEl) { var first = Math.max(firstRow, config.firstRow); var last = Math.min(lastRow, config.lastRow); - var lineElements = this.element.childNodes; - var tokens = this.session.getTokens(first, last); + var lineElements = this.element.childNodes, + lineElementsIdx = 0; + + for (var row = config.firstRow; row < first; row++) { + lineElementsIdx ++; + if (this.session.isRowFolded(row)) { + var fold = this.session.getRowLastFold(row); + row = fold.end.row; + } + } + for (var i=first; i<=last; i++) { - var lineElement = lineElements[i - config.firstRow]; + var lineElement = lineElements[lineElementsIdx++]; if (!lineElement) continue; var html = []; - this.$renderLine(html, i, tokens[i-first].tokens); + var tokens = this.session.getTokens(i, i); + this.$renderLine(html, i, tokens[0].tokens); lineElement = dom.setInnerHtml(lineElement, html.join("")); lineElement.style.height = this.session.getRowHeight(config, i) + "px"; + + i = this.session.getRowFoldEnd(i); } }; @@ -238,17 +250,23 @@ var Text = function(parentEl) { this.$renderLinesFragment = function(config, firstRow, lastRow) { var fragment = document.createDocumentFragment(); - var tokens = this.session.getTokens(firstRow, lastRow); for (var row=firstRow; row<=lastRow; row++) { var lineEl = dom.createElement("div"); + lineEl.className = "ace_line"; var style = lineEl.style; style.height = this.session.getRowHeight(config, row) + "px"; style.width = config.width + "px"; var html = []; - if (tokens.length > row-firstRow) - this.$renderLine(html, row, tokens[row-firstRow].tokens); + // Get the tokens per line as there might be some lines in between + // beeing folded. + // OPTIMIZE: If there is a long block of unfolded lines, just make + // this call once for that big block of unfolded lines. + var tokens = this.session.getTokens(row, row); + if (tokens.length == 1) + this.$renderLine(html, row, tokens[0].tokens); + // don't use setInnerHtml since we are working with an empty DIV lineEl.innerHTML = html.join(""); fragment.appendChild(lineEl); @@ -276,6 +294,13 @@ var Text = function(parentEl) { }; this.$renderLine = function(stringBuilder, row, tokens) { + // Nothing to do if the entire line is folded. + // TODO: Remove this, once the folding feature is done. Only for + // developing stuff at the moment. + if (!this.session.isRowVisible(row)) { + throw "Calling renderLine on folded line doesn't make sense?"; + } + var _self = this, characterWidth = this.config.characterWidth, screenColumn = 0; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 6ab0f8f1..d7f66b2d 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -275,7 +275,7 @@ var VirtualRenderer = function(container, theme) { this.getPrintMarginColumn = function() { return this.$printMarginColumn; }; - + this.getShowGutter = function(){ return this.showGutter; } @@ -590,7 +590,7 @@ var VirtualRenderer = function(container, theme) { // the editor is not visible if (this.$size.scrollerHeight === 0) return; - + var pos = this.$cursorLayer.getPixelPosition(); var left = pos.left + this.$padding; @@ -644,7 +644,7 @@ var VirtualRenderer = function(container, theme) { for (var l = 1; l < line; l++) { offset += this.session.getRowHeight(lineHeight, l-1); } - + if (center) { offset -= this.$size.scrollerHeight / 2; } From e0d01ffa95a40c50efa98df0d844d9384130d213 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 02:39:41 +0200 Subject: [PATCH 02/53] Refacored text rendering code a little bit. About to lift of code folding rendering. --- demo/demo.js | 2 +- lib/ace/edit_session.js | 2 +- lib/ace/layer/text.js | 124 +++++++++++++++++++++++++--------------- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 67ee5c86..896518bf 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -149,7 +149,7 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; - docs.js.addFold(new Range(1,0, 2, 999), "foo..."); + docs.js.addFold(new Range(1, 10, 2, 999), "foo..."); window.s = docs.js; window.e = env.editor; setTimeout(function() { diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 55d48473..7a2d0dbc 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1417,7 +1417,7 @@ var EditSession = function(text, mode) { * folded parts such that some parts of the line is still visible. **/ this.isRowFolded = function(docRow) { - return this.$foldData[docRow]; + return !!this.$foldData[docRow]; }; this.getRowLastFold = function(docRow) { diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 50bf0f5c..68c350cc 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -270,6 +270,8 @@ var Text = function(parentEl) { // don't use setInnerHtml since we are working with an empty DIV lineEl.innerHTML = html.join(""); fragment.appendChild(lineEl); + + row = this.session.getRowFoldEnd(row); } return fragment; }; @@ -293,58 +295,61 @@ var Text = function(parentEl) { "lparen": true }; - this.$renderLine = function(stringBuilder, row, tokens) { - // Nothing to do if the entire line is folded. - // TODO: Remove this, once the folding feature is done. Only for - // developing stuff at the moment. - if (!this.session.isRowVisible(row)) { - throw "Calling renderLine on folded line doesn't make sense?"; + this.$renderToken = function(stringBuilder, screenColumn, token, value) { + var self = this; + var replaceReg = /\t|&|<|( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])|[\u1100-\u115F]|[\u11A3-\u11A7]|[\u11FA-\u11FF]|[\u2329-\u232A]|[\u2E80-\u2E99]|[\u2E9B-\u2EF3]|[\u2F00-\u2FD5]|[\u2FF0-\u2FFB]|[\u3000-\u303E]|[\u3041-\u3096]|[\u3099-\u30FF]|[\u3105-\u312D]|[\u3131-\u318E]|[\u3190-\u31BA]|[\u31C0-\u31E3]|[\u31F0-\u321E]|[\u3220-\u3247]|[\u3250-\u32FE]|[\u3300-\u4DBF]|[\u4E00-\uA48C]|[\uA490-\uA4C6]|[\uA960-\uA97C]|[\uAC00-\uD7A3]|[\uD7B0-\uD7C6]|[\uD7CB-\uD7FB]|[\uF900-\uFAFF]|[\uFE10-\uFE19]|[\uFE30-\uFE52]|[\uFE54-\uFE66]|[\uFE68-\uFE6B]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g; + var replaceFunc = function(c, a, b, tabIdx, idx4) { + if (c.charCodeAt(0) == 32) { + return new Array(c.length+1).join(" "); + } else if (c == "\t") { + var tabSize = self.session. + getScreenTabSize(screenColumn + tabIdx); + screenColumn += tabSize - 1; + return self.$tabStrings[tabSize]; + } else if (c == "&") { + return "&"; + } else if (c == "<") { + return "<"; + } else if (c.match(/[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/)) { + if (self.showInvisibles) { + var space = new Array(c.length+1).join(self.SPACE_CHAR); + return "" + space + ""; + } else { + return " "; + } + } else { + screenColumn += 1; + return "" + c + ""; + } } - var _self = this, + var output = value.replace(replaceReg, replaceFunc); + + if (!this.$textToken[token.type]) { + var classes = "ace_" + token.type.replace(/\./g, " ace_"); + stringBuilder.push("", output, ""); + } + else { + stringBuilder.push(output); + } + return value.length; + } + + this.$renderLineCore = function(stringBuilder, lastRow, tokens, splits) { + var chars = 0, + split = 0, + splitChars, characterWidth = this.config.characterWidth, - screenColumn = 0; + screenColumn = 0, + self = this; function addToken(token, value) { - var output = value - .replace(/\t|&|<|( +)|([\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000])|[\u1100-\u115F]|[\u11A3-\u11A7]|[\u11FA-\u11FF]|[\u2329-\u232A]|[\u2E80-\u2E99]|[\u2E9B-\u2EF3]|[\u2F00-\u2FD5]|[\u2FF0-\u2FFB]|[\u3000-\u303E]|[\u3041-\u3096]|[\u3099-\u30FF]|[\u3105-\u312D]|[\u3131-\u318E]|[\u3190-\u31BA]|[\u31C0-\u31E3]|[\u31F0-\u321E]|[\u3220-\u3247]|[\u3250-\u32FE]|[\u3300-\u4DBF]|[\u4E00-\uA48C]|[\uA490-\uA4C6]|[\uA960-\uA97C]|[\uAC00-\uD7A3]|[\uD7B0-\uD7C6]|[\uD7CB-\uD7FB]|[\uF900-\uFAFF]|[\uFE10-\uFE19]|[\uFE30-\uFE52]|[\uFE54-\uFE66]|[\uFE68-\uFE6B]|[\uFF01-\uFF60]|[\uFFE0-\uFFE6]/g, function(c, a, b, tabIdx, idx4) { - if (c.charCodeAt(0) == 32) { - return new Array(c.length+1).join(" "); - } else if (c == "\t") { - var tabSize = _self.session. - getScreenTabSize(screenColumn + tabIdx); - screenColumn += tabSize - 1; - return _self.$tabStrings[tabSize]; - } else if (c == "&") { - return "&"; - } else if (c == "<") { - return "<"; - } else if (c.match(/[\v\f \u00a0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000]/)) { - if (this.showInvisibles) { - var space = new Array(c.length+1).join(self.SPACE_CHAR); - return "" + space + ""; - } else { - return " " - } - } else { - screenColumn += 1; - return "" + c + "" - } - }); - screenColumn += value.length; - - if (!_self.$textToken[token.type]) { - var classes = "ace_" + token.type.replace(/\./g, " ace_"); - stringBuilder.push("", output, ""); - } - else { - stringBuilder.push(output); - } + screenColumn += self.$renderToken( + stringBuilder, screenColumn, token, value); } - var splits = this.session.getRowSplitData(row); - var chars = 0, split = 0, splitChars; - if (!splits || splits.length == 0) { splitChars = Number.MAX_VALUE; } else { @@ -383,15 +388,42 @@ var Text = function(parentEl) { }; if (this.showInvisibles) { - if (row !== this.session.getLength() - 1) { + if (lastRow !== this.session.getLength() - 1) { stringBuilder.push("" + this.EOL_CHAR + ""); } else { stringBuilder.push("" + this.EOF_CHAR + ""); } } stringBuilder.push(""); + } + + this.$renderLine = function(stringBuilder, row, tokens) { + // Nothing to do if the entire line is folded. + // TODO: Remove this, once the folding feature is done. Only for + // developing stuff at the moment. + if (!this.session.isRowVisible(row)) { + throw "Calling renderLine on folded line doesn't make sense?"; + } + + // Check if the line to render is folded or not. If not, things are + // simple, otherwise, we need to fake some things... + if (!this.session.isRowFolded(row)) { + var splits = this.session.getRowSplitData(row); + this.$renderLineCore(stringBuilder, row, tokens, splits) + } else { + this.$renderFoldLine(stringBuilder, row, tokens, splits); + } }; + this.$renderFoldLine = function(stringBuilder, row, tokens) { + // TODO: Build a fake tokens array! + // TODO: Build a fake splits array! + var splits + var tokens; + var lastRow; + this.$renderLineCore(stringBuilder, lastRow, tokens, splits); + } + }).call(Text.prototype); exports.Text = Text; From 6579a99530984653103d9e07969e06542faf6e94 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 02:40:05 +0200 Subject: [PATCH 03/53] Code folding text rendering took off! --- demo/demo.js | 3 +- lib/ace/edit_session.js | 5 +++ lib/ace/layer/text.js | 75 +++++++++++++++++++++++++++++++++++---- lib/ace/theme/textmate.js | 6 ++++ 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 896518bf..eb227809 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -149,7 +149,8 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; - docs.js.addFold(new Range(1, 10, 2, 999), "foo..."); + docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); + docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); window.s = docs.js; window.e = env.editor; setTimeout(function() { diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 7a2d0dbc..926491c1 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1397,6 +1397,7 @@ var EditSession = function(text, mode) { } var fold = new Fold(range, placeholder); + // TODO: The folds have to be ordered by range.start! foldData[startRow].push(fold); // Mark all lines folded by this fold as folded. @@ -1446,6 +1447,10 @@ var EditSession = function(text, mode) { return !fold || Array.isArray(fold); }; + this.getRowFoldData = function(docRow) { + return this.$foldData[docRow]; + } + }).call(EditSession.prototype); exports.EditSession = EditSession; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 68c350cc..a11966ae 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -416,13 +416,76 @@ var Text = function(parentEl) { }; this.$renderFoldLine = function(stringBuilder, row, tokens) { - // TODO: Build a fake tokens array! + var session = this.session, + folds = session.getRowFoldData(row); + + var renderTokens = []; + + function addTokens(tokens, from, to) { + var idx = 0, col = 0; + while ((col + tokens[idx].value.length) < from) { + col += tokens[idx].value.length + idx++ + + if (idx == tokens.length) { + return; + } + } + if (col != from) { + var value = tokens[idx].value.substring(from - col); + // Check if the token value is longer then the from...to spacing. + if (value.length > (to - from)) { + value = value.substring(0, to - from); + } + + renderTokens.push({ + type: tokens[idx].type, + value: value + }); + + col = from + value.length; + idx += 1 + } + + while (col < to) { + var value = tokens[idx].value; + if (value.length + col > to) { + value = value.substring(0, to - col); + } + renderTokens.push({ + type: tokens[idx].type, + value: value + }); + col += value.length; + idx += 1; + } + } + + var lastCol = 0; + for (var i = 0; i < folds.length; i++) { + var fold = folds[i]; + addTokens(tokens, lastCol, fold.start.column); + renderTokens.push({ + type: "fold", + value: fold.placeholder + }); + + if (fold.start.row != fold.end.row) { + row = fold.end.row; + tokens = this.tokenizer.getTokens(row, row)[0].tokens; + folds = session.getRowFoldData(row); + i = -1; + } + + lastCol = fold.end.column; + } + // Add the rest of the line + addTokens(tokens, lastCol, session.getLine(row).length); + // TODO: Build a fake splits array! - var splits - var tokens; - var lastRow; - this.$renderLineCore(stringBuilder, lastRow, tokens, splits); - } + var splits = false; + this.$renderLineCore(stringBuilder, row, renderTokens, splits); + }; }).call(Text.prototype); diff --git a/lib/ace/theme/textmate.js b/lib/ace/theme/textmate.js index 337a8c57..15f37177 100644 --- a/lib/ace/theme/textmate.js +++ b/lib/ace/theme/textmate.js @@ -106,6 +106,12 @@ define(function(require, exports, module) { color: white;\ }\ \ +.ace-tm .ace_line .ace_fold {\ + background-color: #E4E4E4;\ + border: 1px solid rgba(0, 0, 0, 0.3);\ + border-radius: 5px;\ +}\ +\ .ace-tm .ace_line .ace_support.ace_function {\ color: rgb(60, 76, 114);\ }\ From cc795cd0e584981f4c5e59bfffb6b2c84306362d Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 02:43:57 +0200 Subject: [PATCH 04/53] Make EditSession.getScreenLength work with folded lines. --- lib/ace/edit_session.js | 134 ++++++++++++++++++++++++++++++++++++---- lib/ace/layer/text.js | 8 ++- 2 files changed, 126 insertions(+), 16 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 926491c1..b72d1fbb 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1116,13 +1116,18 @@ var EditSession = function(text, mode) { * - array[0]: The documentRow equivalent. * - array[1]: The screenRowOffset to the first documentRow on the screen. */ - this.$screenToDocumentRow = function(row) { + this.$screenToDocumentRow = function(screenRow, column) { if (!this.$useWrapMode) { - return [row, 0]; +// for (var row = 0; row < screenRow; i++) { +// +// } + return [screenRow, 0]; } + // TODO: WrapMode + Code Folding. var wrapData = this.$wrapData, linesCount = this.getLength(); - var docRow = 0; + var docRow = 0, + row = screenRow; while (docRow < linesCount && row >= wrapData[docRow].length + 1) { row -= wrapData[docRow].length + 1; docRow ++; @@ -1241,9 +1246,26 @@ var EditSession = function(text, mode) { */ this.$documentToScreenRow = function(docRow, docColumn) { if (!this.$useWrapMode) { - return [docRow, 0]; + var row = docRow; + row -= this.getFoldedRowLength(0, docRow - 1); + + if (this.isRowFolded(docRow)) { + var folds = this.getFoldData(docRow), + idx = 0; + + if (folds[0].end.row == docRow) { + idx += 1; + } + + for (idx; idx < folds.length; idx++) { + + } + } + return [row, 0]; } + // TODO: WrapMode + Folding + var wrapData = this.$wrapData; var screenRow = 0; @@ -1285,6 +1307,13 @@ var EditSession = function(text, mode) { column = pos.column; } + // NEW CODE PATH: + var res = this.doc2Screen(row, column); + return { + row: res[0], + column: res[1] + }; + if (!this.$useWrapMode) { str = this.getLine(row).substring(0, column); column = this.$getStringScreenWidth(str); @@ -1309,6 +1338,7 @@ var EditSession = function(text, mode) { var screenColumn; var screenRowOffset = rowData[1]; + // TODO: WrapMode + Code Folding. str = this.getLine(row).substring( wrapRowData[screenRowOffset - 1] || 0, column); screenColumn = this.$getStringScreenWidth(str); @@ -1320,14 +1350,17 @@ var EditSession = function(text, mode) { }; this.getScreenLength = function() { + var length = this.getLength(); if (!this.$useWrapMode) { - return this.getLength(); + screenRows = length; + } else { + var screenRows = 0; + for (var row = 0; row < this.$wrapData.length; row++) { + screenRows += this.$wrapData[row].length + 1; + } } - var screenRows = 0; - for (var row = 0; row < this.$wrapData.length; row++) { - screenRows += this.$wrapData[row].length + 1; - } + screenRows -= this.getFoldedRowLength(0, length - 1); return screenRows; } @@ -1370,7 +1403,6 @@ var EditSession = function(text, mode) { c >= 0xFFE0 && c <= 0xFFE6; }; - // == Folding Code ======================================================== /** @@ -1381,6 +1413,8 @@ var EditSession = function(text, mode) { this.range = range; this.start = range.start; this.end = range.end; + + this.sameLine = range.start.row == range.end.row; } /** @@ -1401,10 +1435,19 @@ var EditSession = function(text, mode) { foldData[startRow].push(fold); // Mark all lines folded by this fold as folded. - for (var row = startRow + 1; row <= endRow; row++) { + for (var row = startRow + 1; row < endRow; row++) { foldData[row] = fold; } + // If this fold folds more then one line, then add the fold at the + // beginning of the foldData[endRow] as well. + if (!fold.sameLine) { + if (!Array.isArray(foldData[endRow])) { + foldData[endRow] = []; + } + foldData[endRow].splice(0, 0, fold); + } + // TODO: Recalculate wrapData // TODO: Recalculate width etc. // TODO: Mark as dirty etc. @@ -1424,7 +1467,7 @@ var EditSession = function(text, mode) { this.getRowLastFold = function(docRow) { var fold = this.$foldData[docRow]; if (!fold) { - return false; + return null; } else if (Array.isArray(fold)) { return fold[fold.length - 1]; } else { @@ -1432,6 +1475,22 @@ var EditSession = function(text, mode) { } }; + this.getRowFirstFold = function(docRow, skipLastWrap) { + var fold = this.$foldData[docRow]; + if (!fold) { + return null; + } else if (Array.isArray(fold)) { + if (skipLastWrap) { + if (fold[0].row.start != docRow) { + return fold[1] || null; + } + } + return fold[0]; + } else { + return fold; + } + }; + this.getRowFoldEnd = function(docRow) { return (this.$foldData[docRow] ? this.getRowLastFold(docRow).end.row @@ -1447,10 +1506,59 @@ var EditSession = function(text, mode) { return !fold || Array.isArray(fold); }; - this.getRowFoldData = function(docRow) { + this.getFoldData = function(docRow) { return this.$foldData[docRow]; } + /** + * Returns the number of folded lines between start and end row. + */ + this.getFoldedRowLength = function(start, end) { + var row = start; + var count = 0; + while (row <= end) { + row = this.findRowFoldedForward(row, end); + if (row != null) { + var fold = this.getRowLastFold(row); + if (!fold.sameLine) { + if (row == start) { + count += fold.end.row - start; + } else { + count += fold.end.row - fold.start.row; + } + row = fold.end.row + 1; + } + } else { + break; + } + } + return count; + }; + + this.findRowFoldedForward = function(start, limit) { + if (limit == null) { + limit = this.getLength(); + } + for (var row = start; row != limit; row++) { + if (this.isRowFolded(row)) { + return row; + } + } + return null; + } + + this.findRowFoldedBackwards = function(start, limit) { + if (limit == null) { + limit = -1; + } + for (var row = start; row != limit; row--) { + if (this.isRowFolded(row)) { + return row; + } + } + return null; + } + }).call(EditSession.prototype); exports.EditSession = EditSession; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index a11966ae..3a5abb8c 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -417,7 +417,7 @@ var Text = function(parentEl) { this.$renderFoldLine = function(stringBuilder, row, tokens) { var session = this.session, - folds = session.getRowFoldData(row); + folds = session.getFoldData(row); var renderTokens = []; @@ -473,8 +473,10 @@ var Text = function(parentEl) { if (fold.start.row != fold.end.row) { row = fold.end.row; tokens = this.tokenizer.getTokens(row, row)[0].tokens; - folds = session.getRowFoldData(row); - i = -1; + folds = session.getFoldData(row); + // Skip the first fold as it's only referencing to the current + // fold that is already rendered. + i = 0; } lastCol = fold.end.column; From 1726444b96bfc5e1b26ad3f5f6600ceae5b01976 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 02:48:23 +0200 Subject: [PATCH 05/53] Rewrite of session.documentToScreen code --- lib/ace/edit_session.js | 134 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index b72d1fbb..d0f5212b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1045,15 +1045,16 @@ var EditSession = function(text, mode) { return screenColumn; } - this.getRowHeight = function(config, row) { - var rows; + this.getRowLength = function(row) { if (!this.$useWrapMode || !this.$wrapData[row]) { - rows = 1; + return 1; } else { - rows = this.$wrapData[row].length + 1; + return this.$wrapData[row].length + 1; } + } - return rows * config.lineHeight; + this.getRowHeight = function(config, row) { + return this.getRowLength(row) * config.lineHeight; } this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { @@ -1291,9 +1292,130 @@ var EditSession = function(text, mode) { } this.documentToScreenRow = function(docRow, docColumn) { - return this.$documentToScreenRow(docRow, docColumn)[0]; + // NEW CODE PATH: + var res = this.doc2Screen(docRow, docColumn); + return res[0]; + // return this.$documentToScreenRow(docRow, docColumn)[0]; } + this.$buildWrappedTextLine = function(row, endRow, endColumn) { + var textLine = ""; + + // Build a one line string for the current fold sequence that starts + // at foldStartRow. + + while_loop: + while (true) { + var folds = this.getFoldData(row); + var line = this.getLine(row); + + var lastEnd = 0; + var startIdx = 0; + if (folds[0].start.row != row) { + startIdx = 1; + lastEnd = folds[0].end.column; + } + fold = null; + for (var i = startIdx; i < folds.length; i++) { + fold = folds[i]; + // STOP + if (row == endRow) { + if (endColumn <= fold.start.column) { + textLine += line.substring(lastEnd, endColumn); + break while_loop; + } else if (!fold.sameLine || endColumn < fold.end.column) { + textLine += line.substring(lastEnd, fold.start.column); + break while_loop; + } + } + textLine += line.substring(lastEnd, fold.start.column); + textLine += fold.placeholder; + if (fold.sameLine) { + lastEnd = fold.end.column; + } else { + row = fold.end.row; + // STOP + if (row == endRow && endColumn <= fold.end.column) { + break while_loop; + } + } + } + if (row > endRow) { + break; + } + if (row == endRow && (!fold || fold.sameLine)) { + if (fold) { + textLine += line.substring(lastEnd, endColumn); + } + break; + } + } + return textLine; + } + + this.doc2Screen = function(docRow, docColumn) { + var screenRow = 0, + screenColumn = 0, + foldStartRow = null, + fold = null; + + // Clamp the docRow position in case it's inside of a folded block. + if (!this.isRowVisible(docRow)) { + // As the line is not visible, getFoldData will return only a + // single fold and also not an array. + fold = this.getFoldData(docRow); + docRow = fold.start.row; + docColumn = fold.start.column; + fold = null; + } + + for (var row = 0; row < docRow; row++) { + var fold = this.getRowLastFold(row); + if (fold && !fold.sameLine) { + if (foldStartRow == null) { + foldStartRow = row; + } + row = fold.end.row - 1; + } else { + screenRow += this.getRowLength(row); + foldStartRow = null; + } + } + + var textLine = ""; + + // Check if the final row we want to reach is inside of some folds. + if (!this.isRowFolded(docRow)) { + foldStartRow = null; + } else if (!foldStartRow) { + foldStartRow = docRow; + } + + var wrapDataRow; + if (foldStartRow) { + textLine = this.$buildWrappedTextLine( + foldStartRow, docRow, docColumn); + } else { + textLine = this.getLine(docRow).substring(0, docColumn); + foldStartRow = docRow; + } + + if (this.$useWrapMode) { + var wrapData = this.$wrapData[wrapDataRow]; + var screenRowOffset = 0; + while (docColumn >= wrapData[screenRowOffset]) { + screenRow ++; + screenRowOffset++; + } + textLine = textLine.substring( + wrapData[screenRowOffset - 1] || 0, textLine.length); + } + return [screenRow, this.$getStringScreenWidth(textLine), textLine]; + // return { + // row: screenRow, + // column: screenColumn + // } + this.documentToScreenPosition = function(pos, column) { var str; var tabSize = this.getTabSize(); From 12bfd2ee0c9b2b62a031ba5f8a6c6542f0368275 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 03:15:10 +0200 Subject: [PATCH 06/53] Clean up a little bit and make current existing unit test run again --- lib/ace/edit_session.js | 127 ++++------------------------------------ 1 file changed, 13 insertions(+), 114 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index d0f5212b..117ae04b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1238,64 +1238,8 @@ var EditSession = function(text, mode) { 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) { - var row = docRow; - row -= this.getFoldedRowLength(0, docRow - 1); - - if (this.isRowFolded(docRow)) { - var folds = this.getFoldData(docRow), - idx = 0; - - if (folds[0].end.row == docRow) { - idx += 1; - } - - for (idx; idx < folds.length; idx++) { - - } - } - return [row, 0]; - } - - // TODO: WrapMode + Folding - - 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.length == 0 ? 0 : (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) { - // NEW CODE PATH: - var res = this.doc2Screen(docRow, docColumn); - return res[0]; - // return this.$documentToScreenRow(docRow, docColumn)[0]; + return this.documentToScreenPosition(docRow, docColumn).row; } this.$buildWrappedTextLine = function(row, endRow, endColumn) { @@ -1363,10 +1307,9 @@ var EditSession = function(text, mode) { if (!this.isRowVisible(docRow)) { // As the line is not visible, getFoldData will return only a // single fold and also not an array. - fold = this.getFoldData(docRow); - docRow = fold.start.row; - docColumn = fold.start.column; - fold = null; + var lfold = this.getFoldData(docRow); + docRow = lfold.start.row; + docColumn = lfold.start.column; } for (var row = 0; row < docRow; row++) { @@ -1382,26 +1325,21 @@ var EditSession = function(text, mode) { } } + // Calculate the text line that is displayed in docRow on the screen. var textLine = ""; - // Check if the final row we want to reach is inside of some folds. + // Check if the final row we want to reach is inside of a fold. if (!this.isRowFolded(docRow)) { - foldStartRow = null; - } else if (!foldStartRow) { - foldStartRow = docRow; - } - - var wrapDataRow; - if (foldStartRow) { - textLine = this.$buildWrappedTextLine( - foldStartRow, docRow, docColumn); - } else { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; + } else { + textLine = this.$buildWrappedTextLine( + (foldStartRow != null ? foldStartRow : docRow), docRow, docColumn); } + // Clamp textLine if in wrapMode. if (this.$useWrapMode) { - var wrapData = this.$wrapData[wrapDataRow]; + var wrapData = this.$wrapData[foldStartRow]; var screenRowOffset = 0; while (docColumn >= wrapData[screenRowOffset]) { screenRow ++; @@ -1410,16 +1348,11 @@ var EditSession = function(text, mode) { textLine = textLine.substring( wrapData[screenRowOffset - 1] || 0, textLine.length); } + return [screenRow, this.$getStringScreenWidth(textLine), textLine]; - // return { - // row: screenRow, - // column: screenColumn - // } + } this.documentToScreenPosition = function(pos, column) { - var str; - var tabSize = this.getTabSize(); - // Normalize the passed in arguments. var row; if (column != null) { @@ -1435,40 +1368,6 @@ var EditSession = function(text, mode) { row: res[0], column: res[1] }; - - if (!this.$useWrapMode) { - str = this.getLine(row).substring(0, column); - column = this.$getStringScreenWidth(str); - return { - row: row, - column: column - }; - } - - var rowData = this.$documentToScreenRow(row, column); - var screenRow = rowData[0]; - - if (row >= this.getLength()) { - return { - row: screenRow, - column: 0 - }; - } - - var split; - var wrapRowData = this.$wrapData[row]; - var screenColumn; - var screenRowOffset = rowData[1]; - - // TODO: WrapMode + Code Folding. - str = this.getLine(row).substring( - wrapRowData[screenRowOffset - 1] || 0, column); - screenColumn = this.$getStringScreenWidth(str); - - return { - row: screenRow, - column: screenColumn - }; }; this.getScreenLength = function() { From aea1bc31ea42a8fba2c18a99d6b4b3f1e97b1201 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 15:05:11 +0200 Subject: [PATCH 07/53] Add fold documentToScreen unit tests and fixed some small bugs --- demo/demo.js | 1 + lib/ace/edit_session.js | 260 +++++++++++++++++++---------------- lib/ace/edit_session_test.js | 54 ++++++++ lib/ace/test/all_browser.js | 50 +++---- 4 files changed, 221 insertions(+), 144 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index eb227809..1291db1a 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -149,6 +149,7 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; + docs.js.addFold(new Range(0, 13, 0, 18), "args..."); docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); window.s = docs.js; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 117ae04b..a82b050d 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -22,6 +22,7 @@ * Contributor(s): * Fabian Jakobs * Mihai Sucan + * 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,6 +56,7 @@ var EditSession = function(text, mode) { this.$backMarkers = {}; this.$markerId = 1; this.$wrapData = []; + this.$foldData = []; if (text instanceof Document) { this.setDocument(text); @@ -875,8 +877,8 @@ var EditSession = function(text, mode) { if (len != 0) { if (action.indexOf("remove") != -1) { useWrapMode && this.$wrapData.splice(firstRow, len); - // TODO: More checking needed here. - this.$foldData.splice(firstRow, len); + // TODO: Remove no longer needed folds here. + // TODO: Update row data on folds. lastRow = firstRow; } else { var args; @@ -886,10 +888,8 @@ var EditSession = function(text, mode) { this.$wrapData.splice.apply(this.$wrapData, args); } - args = [firstRow, 0]; - for (var i = 0; i < len; i++) args.push(false); - // TODO: More checking needed here. - this.$foldData.splice.apply(this.$foldData, args); + // TODO: Expand folds here if needed. + // TODO: Update row data on folds. } } @@ -1041,7 +1041,7 @@ var EditSession = function(text, mode) { screenColumn += 1; } } - + return screenColumn; } @@ -1242,7 +1242,7 @@ var EditSession = function(text, mode) { return this.documentToScreenPosition(docRow, docColumn).row; } - this.$buildWrappedTextLine = function(row, endRow, endColumn) { + this.$buildFoldedTextLine = function(row, endRow, endColumn) { var textLine = ""; // Build a one line string for the current fold sequence that starts @@ -1273,6 +1273,13 @@ var EditSession = function(text, mode) { } } textLine += line.substring(lastEnd, fold.start.column); + + // Stop if the end position is inside of this fold. + if (endRow < fold.end.row || + endRow == fold.end.row && endColumn < fold.end.column + ) { + break while_loop; + } textLine += fold.placeholder; if (fold.sameLine) { lastEnd = fold.end.column; @@ -1333,8 +1340,10 @@ var EditSession = function(text, mode) { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; } else { - textLine = this.$buildWrappedTextLine( - (foldStartRow != null ? foldStartRow : docRow), docRow, docColumn); + textLine = this.$buildFoldedTextLine( + (foldStartRow != null ? foldStartRow : docRow), + docRow, + docColumn); } // Clamp textLine if in wrapMode. @@ -1381,7 +1390,13 @@ var EditSession = function(text, mode) { } } - screenRows -= this.getFoldedRowLength(0, length - 1); + var foldData = this.$foldData; + for (var i = 0; i < foldData.length; i++) { + var foldRow = foldData[i]; + if (foldRow.linkOut) { + screenRows -= foldRow.linkOut.end.row - foldRow.linkOut.start.row; + } + } return screenRows; } @@ -1438,35 +1453,86 @@ var EditSession = function(text, mode) { this.sameLine = range.start.row == range.end.row; } + function FoldRow(row) { + this.row = row; + this.linkIn = null; + this.linkOut = null; + this.inRow = []; + } + + (function() { + this.addInRowFold = function(fold) { + if (!fold.sameLine || fold.start.row != this.row) { + throw "NOPE: Can't add fold here!"; + } + this.inRow.push(fold); + this.inRow.sort(function(a, b) { + return a.start.column - b.start.column; + }); + } + + this.addFold = function(fold) { + if (fold.sameLine) { + this.addInRowFold(fold) + } else if (fold.start.row == this.row) { + if (this.linkOut) { + throw "There is already a linkOut fold"; + } + this.linkOut = fold; + } else if (fold.end.row == this.row) { + if (this.linkIn) { + throw "There is already a linkIn fold"; + } + this.linkIn = fold; + } else { + throw "Trying to add fold to FoldRow that doesn't have a matching row"; + } + } + }).call(FoldRow.prototype); + + this.getFoldRow = function(docRow, startFoldRow) { + var foldData = this.$foldData; + for (var i = foldData.indexOf(startFoldRow) || 0; i < foldData.length; i++) { + var foldRow = foldData[i]; + if (foldRow.row == docRow) { + return foldRow; + } else if (foldRow.row > docRow) { + return null; + } + } + return null; + } + + this.getFoldRowOrCreate = function(docRow, startFoldRow) { + var foldRow = this.getFoldRow(docRow, startFoldRow); + if (!foldRow) { + foldRow = new FoldRow(startRow); + this.$foldData.push(foldRow); + this.$foldData.sort(function(a, b) { + return a.row - b.row; + }); + } + } + /** * Adds a new fold. */ this.addFold = function(range, placeholder) { var startRow = range.start.row, endRow = range.end.row, - foldData = this.$foldData; + foldData = this.$foldData, + foldRow = null; - // In case there is no fold data for the start row yet. - if (!Array.isArray(foldData[startRow])) { - foldData[startRow] = []; - } + foldRow = this.getFoldRowOrCreate(startRow); var fold = new Fold(range, placeholder); - // TODO: The folds have to be ordered by range.start! - foldData[startRow].push(fold); + foldRow.addFold(fold); - // Mark all lines folded by this fold as folded. - for (var row = startRow + 1; row < endRow; row++) { - foldData[row] = fold; - } - - // If this fold folds more then one line, then add the fold at the - // beginning of the foldData[endRow] as well. + // If this fold folds more then one line, then add this fold as the + // linkOut of the FoldRow at endRow. if (!fold.sameLine) { - if (!Array.isArray(foldData[endRow])) { - foldData[endRow] = []; - } - foldData[endRow].splice(0, 0, fold); + foldRow = this.getFoldRowOrCreate(endRow, foldRow); + foldRow.addFold(fold); } // TODO: Recalculate wrapData @@ -1481,104 +1547,60 @@ var EditSession = function(text, mode) { * Checks if a given documentRow is folded. This is true if there are some * folded parts such that some parts of the line is still visible. **/ - this.isRowFolded = function(docRow) { - return !!this.$foldData[docRow]; + this.isRowFolded = function(docRow, startFoldRow) { + return !!this.getFoldRow(docRow, startFoldRow); }; - this.getRowLastFold = function(docRow) { - var fold = this.$foldData[docRow]; - if (!fold) { - return null; - } else if (Array.isArray(fold)) { - return fold[fold.length - 1]; - } else { - return fold; - } - }; - - this.getRowFirstFold = function(docRow, skipLastWrap) { - var fold = this.$foldData[docRow]; - if (!fold) { - return null; - } else if (Array.isArray(fold)) { - if (skipLastWrap) { - if (fold[0].row.start != docRow) { - return fold[1] || null; - } - } - return fold[0]; - } else { - return fold; - } - }; - - this.getRowFoldEnd = function(docRow) { - return (this.$foldData[docRow] - ? this.getRowLastFold(docRow).end.row + this.getRowFoldEnd = function(docRow, startFoldRow) { + var foldRow = this.getFoldRow(docRow, startFoldRow); + return (foldRow + ? (foldRow.linkOut ? foldRow.linkOut.end.row : docRow) : docRow); }; - /** - * Checks if a given documentRow is visible or not. Not beeing visible means - * it's folded completly. - **/ - this.isRowVisible = function(docRow) { - var fold = this.$foldData[docRow]; - return !fold || Array.isArray(fold); - }; + this.buildFoldRowLine = function(docRow, startFoldRow) { + var foldRow = this.getFoldRow(docRow, startFoldRow); + var folds = []; + if (!foldRow) { + throw "Passed in docRow doesn't have a foldRow"; + } + if (foldRow.linkIn) { + throw "Can't build a single fold line starting at a FoldRow that is linked in!"; + } - this.getFoldData = function(docRow) { - return this.$foldData[docRow]; + var foldData = this.$foldData; + for (var i = foldData.indexOf(foldRow); foldRow; i++) { + if (foldRow.linkIn) { + folds.push(foldRow.linkIn); + } + + foldRow = foldRow.linkOut; + } } - /** - * Returns the number of folded lines between start and end row. - */ - this.getFoldedRowLength = function(start, end) { - var row = start; - var count = 0; - while (row <= end) { - row = this.findRowFoldedForward(row, end); - if (row != null) { - var fold = this.getRowLastFold(row); - if (!fold.sameLine) { - if (row == start) { - count += fold.end.row - start; - } else { - count += fold.end.row - fold.start.row; - } - row = fold.end.row + 1; - } - } else { - break; - } - } - return count; - }; - - this.findRowFoldedForward = function(start, limit) { - if (limit == null) { - limit = this.getLength(); - } - for (var row = start; row != limit; row++) { - if (this.isRowFolded(row)) { - return row; - } - } - return null; - } - - this.findRowFoldedBackwards = function(start, limit) { - if (limit == null) { - limit = -1; - } - for (var row = start; row != limit; row--) { - if (this.isRowFolded(row)) { - return row; - } - } - return null; - } +// this.findRowFoldedForward = function(start, limit) { +// if (limit == null) { +// limit = this.getLength(); +// } +// for (var row = start; row != limit; row++) { +// if (this.isRowFolded(row)) { +// return row; +// } +// } +// return null; +// } +// +// this.findRowFoldedBackwards = function(start, limit) { +// if (limit == null) { +// limit = -1; +// } +// for (var row = start; row != limit; row--) { +// if (this.isRowFolded(row)) { +// return row; +// } +// } +// return null; +// } }).call(EditSession.prototype); diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 948802a3..d9d6caf9 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_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 @@ -49,6 +50,21 @@ var MockRenderer = require("ace/test/mockrenderer"); var Range = require("ace/range").Range; var assert = require("ace/test/assertions"); +function createFoldTestSession() { + var lines = [ + "function foo(items) {", + " for (var i=0; i"; } - + node.innerHTML = msg; log.appendChild(node); From 7b58e782a7fc3b18bb2e87902fbc6910a76417dd Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 21:51:39 +0200 Subject: [PATCH 08/53] Rewrote the handling of . Now there is one FoldLine per folded line/row as it's displayed on the screen. --- lib/ace/edit_session.js | 304 +++++++++++++++++++---------------- lib/ace/edit_session_test.js | 3 + lib/ace/layer/gutter.js | 2 +- lib/ace/layer/text.js | 29 ++-- 4 files changed, 180 insertions(+), 158 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index a82b050d..db03e9a7 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -896,9 +896,6 @@ var EditSession = function(text, mode) { if (useWrapMode && this.$wrapData.length != this.doc.$lines.length) { console.error("The length of doc.$lines and $wrapData have to be the same!"); } - if (this.$foldData.length != this.doc.$lines.length) { - console.error("The length of doc.$lines and $foldData have to be the same!"); - } // TODO: // this.$updateFoldData(firstRow, lastRow); @@ -1242,65 +1239,45 @@ var EditSession = function(text, mode) { return this.documentToScreenPosition(docRow, docColumn).row; } - this.$buildFoldedTextLine = function(row, endRow, endColumn) { + this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) { var textLine = ""; // Build a one line string for the current fold sequence that starts // at foldStartRow. + var lastEnd = 0, + line = this.getLine(foldLine.start.row), + comp, + folds = foldLine.folds, + fold; - while_loop: - while (true) { - var folds = this.getFoldData(row); - var line = this.getLine(row); + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; - var lastEnd = 0; - var startIdx = 0; - if (folds[0].start.row != row) { - startIdx = 1; - lastEnd = folds[0].end.column; + comp = fold.compare(endRow, endColumn); + // This fold is after the endRow/Column. + if (comp == -1) { + textLine += line.substring(lastEnd, endColumn); + return textLine; } - fold = null; - for (var i = startIdx; i < folds.length; i++) { - fold = folds[i]; - // STOP - if (row == endRow) { - if (endColumn <= fold.start.column) { - textLine += line.substring(lastEnd, endColumn); - break while_loop; - } else if (!fold.sameLine || endColumn < fold.end.column) { - textLine += line.substring(lastEnd, fold.start.column); - break while_loop; - } - } + // The endRow/Column is inside of the current fold. + else if (comp == 0) { textLine += line.substring(lastEnd, fold.start.column); + return textLine; + } - // Stop if the end position is inside of this fold. - if (endRow < fold.end.row || - endRow == fold.end.row && endColumn < fold.end.column - ) { - break while_loop; - } - textLine += fold.placeholder; - if (fold.sameLine) { - lastEnd = fold.end.column; - } else { - row = fold.end.row; - // STOP - if (row == endRow && endColumn <= fold.end.column) { - break while_loop; - } - } - } - if (row > endRow) { - break; - } - if (row == endRow && (!fold || fold.sameLine)) { - if (fold) { - textLine += line.substring(lastEnd, endColumn); - } - break; + textLine += line.substring(lastEnd, fold.start.column); + textLine += fold.placeholder; + + if (fold.sameLine) { + lastEnd = fold.end.column; + } else { + row = fold.end.row; + + line = this.getLine(row); + lastEnd = fold.end.column; } } + textLine += line.substring(lastEnd, endColumn); return textLine; } @@ -1308,40 +1285,53 @@ var EditSession = function(text, mode) { var screenRow = 0, screenColumn = 0, foldStartRow = null, - fold = null; + fold = null, + folds, + comp, + foldLine = null; // Clamp the docRow position in case it's inside of a folded block. - if (!this.isRowVisible(docRow)) { - // As the line is not visible, getFoldData will return only a - // single fold and also not an array. - var lfold = this.getFoldData(docRow); - docRow = lfold.start.row; - docColumn = lfold.start.column; + foldLine = this.getFoldLine(docRow); + if (foldLine) { + folds = foldLine.folds; + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; + comp = fold.compare(docRow, docColumn); + if (comp == 0) { + docRow = fold.start.row; + docColumn = fold.start.column; + break; + } else if (comp == -1) { + break; + } + } + foldLine = null; } for (var row = 0; row < docRow; row++) { - var fold = this.getRowLastFold(row); - if (fold && !fold.sameLine) { - if (foldStartRow == null) { - foldStartRow = row; + foldLine = this.getFoldLine(row); + if (foldLine) { + if (foldLine.end.row >= docRow) { + break; } - row = fold.end.row - 1; + row = foldLine.end.row; + screenRow += foldLine.getRowLength(); } else { screenRow += this.getRowLength(row); - foldStartRow = null; } } // Calculate the text line that is displayed in docRow on the screen. var textLine = ""; + foldLine = this.getFoldLine(docRow); // Check if the final row we want to reach is inside of a fold. - if (!this.isRowFolded(docRow)) { + if (!foldLine) { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; } else { textLine = this.$buildFoldedTextLine( - (foldStartRow != null ? foldStartRow : docRow), + foldLine, docRow, docColumn); } @@ -1453,67 +1443,87 @@ var EditSession = function(text, mode) { this.sameLine = range.start.row == range.end.row; } - function FoldRow(row) { - this.row = row; - this.linkIn = null; - this.linkOut = null; - this.inRow = []; + Fold.prototype.contains = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return false; + } + return this.range.contains(row, column); + } + + Fold.prototype.compare = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } + return this.range.compare(row, column); + } + + function FoldLine(fold) { + this.folds = [fold]; + this.range = fold.range.clone(); + this.start = this.range.start; + this.end = this.range.end; } (function() { - this.addInRowFold = function(fold) { - if (!fold.sameLine || fold.start.row != this.row) { - throw "NOPE: Can't add fold here!"; - } - this.inRow.push(fold); - this.inRow.sort(function(a, b) { - return a.start.column - b.start.column; - }); - } - this.addFold = function(fold) { if (fold.sameLine) { - this.addInRowFold(fold) - } else if (fold.start.row == this.row) { - if (this.linkOut) { - throw "There is already a linkOut fold"; + if (fold.start.row < this.startRow || fold.endRow > this.endRow) { + throw "Can't add a fold to this FoldLine as it has no connection"; } - this.linkOut = fold; - } else if (fold.end.row == this.row) { - if (this.linkIn) { - throw "There is already a linkIn fold"; - } - this.linkIn = fold; + this.folds.push(fold); + this.folds.sort(function(a, b) { + if (a.sameLine && b.sameLine) { + if (a.start.row == b.start.row) { + return a.start.colun - b.start.column; + } else { + return a.start.row - b.start.row; + } + } else if (!a.sameLine && !b.sameLine) { + return a.end.row - b.start.row; + } else if (!a.sameLine) { + return a.end.row - b.start.row; + } else if (!b.sameLine) { + return b.end.row - a.start.row; + } + }); + } else if (fold.start.row == this.endRow) { + this.folds.push(fold); + this.end.row = fold.end.row; + this.end.column = fold.end.column; + } else if (fold.end.row == this.startRow) { + this.folds.unshift(fold); + this.start.row = fold.start.row; + this.start.column = fold.start.column; } else { throw "Trying to add fold to FoldRow that doesn't have a matching row"; } } - }).call(FoldRow.prototype); - this.getFoldRow = function(docRow, startFoldRow) { + this.getRowLength = function() { + // TODO: Add support for wrapped lines here. + return 1; + } + + this.getSplitData = function() { + // TODO: Add support for wrapped lines here. + return undefined; + } + }).call(FoldLine.prototype); + + this.getFoldLine = function(docRow, startFoldLine) { var foldData = this.$foldData; - for (var i = foldData.indexOf(startFoldRow) || 0; i < foldData.length; i++) { - var foldRow = foldData[i]; - if (foldRow.row == docRow) { - return foldRow; - } else if (foldRow.row > docRow) { + var i = Math.max(foldData.indexOf(startFoldLine), 0); + for (i; i < foldData.length; i++) { + var foldLine = foldData[i]; + if (foldLine.start.row >= docRow || foldLine.end.row >= docRow) { + return foldLine; + } else if (foldLine.end.row > docRow) { return null; } } return null; } - this.getFoldRowOrCreate = function(docRow, startFoldRow) { - var foldRow = this.getFoldRow(docRow, startFoldRow); - if (!foldRow) { - foldRow = new FoldRow(startRow); - this.$foldData.push(foldRow); - this.$foldData.sort(function(a, b) { - return a.row - b.row; - }); - } - } - /** * Adds a new fold. */ @@ -1523,16 +1533,46 @@ var EditSession = function(text, mode) { foldData = this.$foldData, foldRow = null; - foldRow = this.getFoldRowOrCreate(startRow); - var fold = new Fold(range, placeholder); - foldRow.addFold(fold); + var added = false; - // If this fold folds more then one line, then add this fold as the - // linkOut of the FoldRow at endRow. - if (!fold.sameLine) { - foldRow = this.getFoldRowOrCreate(endRow, foldRow); - foldRow.addFold(fold); + // For now we assume that no two folds are created for the same range! + for (var i = 0; i < foldData.length; i++) { + var foldLine = foldData[i]; + if (endRow == foldLine.start.row) { + foldLine.addFold(fold); + added = true; + break; + } else if (startRow == foldLine.end.row) { + foldLine.addFold(fold); + added = true; + if (!fold.sameLine) { + // Check if we might have to merge two FoldLines. + foldLineNext = foldData[i + 1]; + if (foldLineNext && foldLineNext.start.row == endRow) { + // We need to merge! + var nextFolds = foldLineNext.folds; + for (var i = 0; i < nextFolds.length; i++) { + foldLine.addFold(nextFolds[i]); + } + // Remove the foldLineNext - no longer needed, as + // it's merged now with foldLine. + foldData.splice(foldData.indexOf(foldLineNext), 1); + break; + } + } + break; + } else if (endRow <= foldLine.start.row) { + break; + } + } + + if (!added) { + foldLine = new FoldLine(fold); + foldData.push(foldLine); + foldData.sort(function(a, b) { + return a.start.row - b.start.row; + }); } // TODO: Recalculate wrapData @@ -1548,36 +1588,16 @@ var EditSession = function(text, mode) { * folded parts such that some parts of the line is still visible. **/ this.isRowFolded = function(docRow, startFoldRow) { - return !!this.getFoldRow(docRow, startFoldRow); + return !!this.getFoldLine(docRow, startFoldRow); }; this.getRowFoldEnd = function(docRow, startFoldRow) { - var foldRow = this.getFoldRow(docRow, startFoldRow); - return (foldRow - ? (foldRow.linkOut ? foldRow.linkOut.end.row : docRow) - : docRow); + var foldLine = this.getFoldLine(docRow, startFoldRow); + return (foldLine + ? foldLine.end.row + : docRow) }; - this.buildFoldRowLine = function(docRow, startFoldRow) { - var foldRow = this.getFoldRow(docRow, startFoldRow); - var folds = []; - if (!foldRow) { - throw "Passed in docRow doesn't have a foldRow"; - } - if (foldRow.linkIn) { - throw "Can't build a single fold line starting at a FoldRow that is linked in!"; - } - - var foldData = this.$foldData; - for (var i = foldData.indexOf(foldRow); foldRow; i++) { - if (foldRow.linkIn) { - folds.push(foldRow.linkIn); - } - - foldRow = foldRow.linkOut; - } - } - // this.findRowFoldedForward = function(start, limit) { // if (limit == null) { // limit = this.getLength(); diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index d9d6caf9..42cb4536 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -407,6 +407,9 @@ module.exports = { assertDoc2Screen(2, 25, 1, 32); assertDoc2Screen(2, 26, 1, 33); assertDoc2Screen(2, 99, 1, 40); + + // Test one position after the folds. Should be all like normal. + assertDoc2Screen(3, 0, 2, 0); } }; diff --git a/lib/ace/layer/gutter.js b/lib/ace/layer/gutter.js index e5fb1c9c..84a36ca2 100644 --- a/lib/ace/layer/gutter.js +++ b/lib/ace/layer/gutter.js @@ -21,7 +21,7 @@ * * Contributor(s): * Fabian Jakobs - * Julian Viereck + * 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 diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 3a5abb8c..eac5dc0e 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -21,7 +21,7 @@ * * Contributor(s): * Fabian Jakobs - * Julian Viereck + * Julian Viereck * Mihai Sucan * * Alternatively, the contents of this file may be used under the terms of @@ -401,9 +401,9 @@ var Text = function(parentEl) { // Nothing to do if the entire line is folded. // TODO: Remove this, once the folding feature is done. Only for // developing stuff at the moment. - if (!this.session.isRowVisible(row)) { - throw "Calling renderLine on folded line doesn't make sense?"; - } +// if (!this.session.isRowVisible(row)) { +// throw "Calling renderLine on folded line doesn't make sense?"; +// } // Check if the line to render is folded or not. If not, things are // simple, otherwise, we need to fake some things... @@ -417,7 +417,8 @@ var Text = function(parentEl) { this.$renderFoldLine = function(stringBuilder, row, tokens) { var session = this.session, - folds = session.getFoldData(row); + foldLine = session.getFoldLine(row), + folds = foldLine.folds; var renderTokens = []; @@ -461,9 +462,11 @@ var Text = function(parentEl) { } } - var lastCol = 0; + var lastCol = 0, + fold = null; + for (var i = 0; i < folds.length; i++) { - var fold = folds[i]; + fold = folds[i]; addTokens(tokens, lastCol, fold.start.column); renderTokens.push({ type: "fold", @@ -471,21 +474,17 @@ var Text = function(parentEl) { }); if (fold.start.row != fold.end.row) { - row = fold.end.row; - tokens = this.tokenizer.getTokens(row, row)[0].tokens; - folds = session.getFoldData(row); - // Skip the first fold as it's only referencing to the current - // fold that is already rendered. - i = 0; + tokens = this.tokenizer.getTokens( + fold.end.row, fold.end.row)[0].tokens; } lastCol = fold.end.column; } // Add the rest of the line - addTokens(tokens, lastCol, session.getLine(row).length); + addTokens(tokens, lastCol, session.getLine(foldLine.end.row).length); // TODO: Build a fake splits array! - var splits = false; + var splits = foldLine.getSplitData(); this.$renderLineCore(stringBuilder, row, renderTokens, splits); }; From a12f83c3d97455bd5cb1cd22563d8ba03df39cc3 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 21:57:21 +0200 Subject: [PATCH 09/53] Fix bugs in FoldLine.addFold and simplify --- demo/demo.js | 2 +- lib/ace/edit_session.js | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 1291db1a..6fa0acd8 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -150,8 +150,8 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; docs.js.addFold(new Range(0, 13, 0, 18), "args..."); - docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); + docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); window.s = docs.js; window.e = env.editor; setTimeout(function() { diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index db03e9a7..46ef0909 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1472,25 +1472,13 @@ var EditSession = function(text, mode) { } this.folds.push(fold); this.folds.sort(function(a, b) { - if (a.sameLine && b.sameLine) { - if (a.start.row == b.start.row) { - return a.start.colun - b.start.column; - } else { - return a.start.row - b.start.row; - } - } else if (!a.sameLine && !b.sameLine) { - return a.end.row - b.start.row; - } else if (!a.sameLine) { - return a.end.row - b.start.row; - } else if (!b.sameLine) { - return b.end.row - a.start.row; - } + return -a.compare(b.start.row, b.start.column); }); - } else if (fold.start.row == this.endRow) { + } else if (fold.start.row == this.end.row) { this.folds.push(fold); this.end.row = fold.end.row; this.end.column = fold.end.column; - } else if (fold.end.row == this.startRow) { + } else if (fold.end.row == this.start.row) { this.folds.unshift(fold); this.start.row = fold.start.row; this.start.column = fold.start.column; From 3e7567b5210d34e61524778b5c470dfeaffd3063 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 22:01:22 +0200 Subject: [PATCH 10/53] Fix bug in TextLayer.updateLines due to FoldLine refactoring --- lib/ace/layer/text.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index eac5dc0e..aabefabd 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -191,10 +191,7 @@ var Text = function(parentEl) { for (var row = config.firstRow; row < first; row++) { lineElementsIdx ++; - if (this.session.isRowFolded(row)) { - var fold = this.session.getRowLastFold(row); - row = fold.end.row; - } + row = this.session.getRowFoldEnd(row); } for (var i=first; i<=last; i++) { From b8437cde601d193e52666425b3d67b1d254ac6df Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sat, 23 Apr 2011 22:04:38 +0200 Subject: [PATCH 11/53] Fix EditSession.getScreenLength due to FoldLine refactoring --- demo/demo.js | 2 +- lib/ace/edit_session.js | 389 +++++++++++++++++++++++++---------- lib/ace/edit_session_test.js | 101 ++++++--- lib/ace/layer/text.js | 35 ++-- lib/ace/virtual_renderer.js | 2 +- 5 files changed, 360 insertions(+), 169 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 6fa0acd8..efab6bb4 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -149,7 +149,7 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; - docs.js.addFold(new Range(0, 13, 0, 18), "args..."); + // docs.js.addFold(new Range(0, 13, 0, 18), "args..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); window.s = docs.js; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 46ef0909..e99ae17a 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -463,7 +463,7 @@ var EditSession = function(text, mode) { for ( var i = 0; i < lines.length; i++) { var line = lines[i], len = line.length, - screenLen = this.$getStringScreenWidth(line); + screenLen = this.$getStringScreenWidth(line)[0]; longestLine = Math.max(longestLine, len); longestScreenLine = Math.max(longestScreenLine, screenLen); } @@ -1014,19 +1014,68 @@ var EditSession = function(text, mode) { return arr; } + this.$getStringDisplayData = function(str, maxScreenColumn, column, offset) { + if (maxScreenColumn == null) { + maxScreenColumn = str.length * Math.max(this.getTabSize(), 2); + } + column = column || 0; + + var arr = [], + tabSize, + c; + + for (column; column < str.length; column++) { + c = str.charCodeAt(column); + // Tab + if (c == 9) { + tabSize = this.getScreenTabSize(arr.length + offset); + arr.push(TAB); + for (var n = 1; n < tabSize; n++) { + arr.push(TAB_SPACE); + } + } + // Space + else if(c == 32) { + arr.push(SPACE); + } + // full width characters + else if (isFullWidth(c)) { + arr.push(CHAR, CHAR_EXT); + } else { + arr.push(CHAR); + } + if ((arr.length + offset) > maxScreenColumn) { + column --; + break; + } + } + return { + tokens: arr, + column: column + }; + } + /** * 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. + * @return array + * [0]: number of columns for str on screen. + * [1]: docColumn position that was read until (useful with screenColumn) */ - this.$getStringScreenWidth = function(str) { - var screenColumn = 0; - var tabSize = this.getTabSize(); + this.$getStringScreenWidth = function(str, maxScreenColumn, screenColumn) { + if (maxScreenColumn == 0) { + return [0, 0]; + } + if (maxScreenColumn == null) { + maxScreenColumn = str.length * Math.max(this.getTabSize(), 2); + } + screenColumn = screenColumn || 0; - for (var i=0; i maxScreenColumn) { + break + } } - return screenColumn; + return [screenColumn, column]; } this.getRowLength = function(row) { @@ -1056,7 +1108,7 @@ var EditSession = function(text, mode) { this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { if (!this.$useWrapMode) { - return this.$getStringScreenWidth(this.getLine(screenRow)); + return this.$getStringScreenWidth(this.getLine(screenRow))[0]; } var rowData = this.$screenToDocumentRow(screenRow); @@ -1073,7 +1125,7 @@ var EditSession = function(text, mode) { start = (this.$wrapData[docRow][row - 1] || 0); } if (!returnDocPosition) { - return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end)); + return this.$getStringScreenWidth(this.getLine(docRow).substring(start, end))[0]; } else { return end; } @@ -1134,14 +1186,6 @@ var EditSession = function(text, mode) { return [docRow, row]; }; - this.screenToDocumentRow = function(screenRow) { - return this.$screenToDocumentRow(screenRow)[0]; - }; - - this.screenToDocumentColumn = function(screenRow, screenColumn) { - return this.screenToDocumentPosition(screenRow, screenColumn).column; - }; - /** * Returns the width of a tab character at screenColumn. */ @@ -1149,7 +1193,108 @@ var EditSession = function(text, mode) { return this.$tabSize - screenColumn % this.$tabSize; }; + this.screen2Doc = function(screenRow, screenColumn) { + var line, + docRow = 0, + docColumn = 0, column, + foldLine, + lastFoldLine, + foldLineRowLength; + + var row = 0, + rowLength; + + while (row <= screenRow) { + foldLine = this.getFoldLine(docRow, lastFoldLine); + if (foldLine) { + lastFoldLine = foldLine; + rowLength = foldLine.getRowLength(); + } else { + rowLength = this.getRowLength(row); + } + if (row + rowLength - 1 >= screenRow) { + break; + } else { + docRow ++; + row += rowLength; + if (foldLine) { + docRow = foldLine.end.row + 1; + } + } + } + + var splits = null; + if (foldLine) { + splits = foldLine.getSplitData(); + docColumn = splits[screenRow - row] || 0; + walkScreenColumn = 0; + + foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + var data, str; + if (placeholder) { + data = this.$getStringScreenWidth(placeholder, null, walkScreenColumn); + if (data[0] > screenColumn) { + return true; // Stop walk. + } + docColumn += data[1]; + } else { + if (isNewRow) { + line = this.getLine(row); + } + str = line.substring(lastColumn, column); + data = this.$getStringScreenWidth(str, screenColumn, walkScreenColumn); + docColumn += data[1]; + if (data[0] == screenColumn) { + return true; // Stop walk. + } + } + walkScreenColumn += data[0]; + }.bind(this)); + } else { + line = this.getLine(docRow); + splits = this.$wrapData[docRow]; + if (this.$useWrapMode && splits) { + docColumn = splits[screenRow - row - 1] || 0; + line = line.substring(docColumn); + } + + docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; + + // Need to do some clamping action here. + if (splits) { + column = this.$wrapData[docRow][screenRow - row] + if (docColumn >= column) { + // We remove one character at the end such that the docColumn + // position returned is not associated to the next row on the + // screen. + docColumn = column - 1; + } + } else { + docColumn = Math.min(docColumn, line.length); + } + } + return [docRow, docColumn, screenRow, row] + } + + this.screenToDocumentRow = function(screenRow, screenColumn) { + if (screenColumn == null) { + screenColumn = 0; + // throw "Need to pass in screenColumn"; + } + return this.$screenToDocumentRow(screenRow)[0]; + }; + + this.screenToDocumentColumn = function(screenRow, screenColumn) { + return this.screenToDocumentPosition(screenRow, screenColumn).column; + }; + this.screenToDocumentPosition = function(row, column) { + var ret = this.screen2Doc(row, column); + return { + row: ret[0], + column: ret[1] + }; + var line; var docRow; var docColumn; @@ -1179,38 +1324,7 @@ var EditSession = function(text, mode) { var tabSize, screenColumn = 0; - for(var i = 0; i < line.length; i++) { - var c = line.charCodeAt(i); - - if (remaining > 0) { - docColumn += 1; - // tab - if (c == 9) { - tabSize = this.getScreenTabSize(screenColumn); - if (remaining >= tabSize) { - remaining -= tabSize; - screenColumn += tabSize; - } else { - remaining = 0; - docColumn -= 1; - } - } - // full width characters - else if (isFullWidth(c)) { - if (remaining >= 2) { - remaining -= 2; - } else { - remaining = 0; - docColumn -= 1; - } - } else { - screenColumn += 1; - remaining -= 1; - } - } else { - break; - } - } + docColumn += this.$getStringScreenWidth(line, column)[1]; // Clamp docColumn. if (this.$useWrapMode) { @@ -1231,55 +1345,47 @@ var EditSession = function(text, mode) { }; }; - this.documentToScreenColumn = function(row, docColumn) { - return this.documentToScreenPosition(row, docColumn).column; - }; - - this.documentToScreenRow = function(docRow, docColumn) { - return this.documentToScreenPosition(docRow, docColumn).row; - } - - this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) { - var textLine = ""; - - // Build a one line string for the current fold sequence that starts - // at foldStartRow. - var lastEnd = 0, - line = this.getLine(foldLine.start.row), - comp, - folds = foldLine.folds, - fold; - - for (var i = 0; i < folds.length; i++) { - fold = folds[i]; - - comp = fold.compare(endRow, endColumn); - // This fold is after the endRow/Column. - if (comp == -1) { - textLine += line.substring(lastEnd, endColumn); - return textLine; - } - // The endRow/Column is inside of the current fold. - else if (comp == 0) { - textLine += line.substring(lastEnd, fold.start.column); - return textLine; - } - - textLine += line.substring(lastEnd, fold.start.column); - textLine += fold.placeholder; - - if (fold.sameLine) { - lastEnd = fold.end.column; - } else { - row = fold.end.row; - - line = this.getLine(row); - lastEnd = fold.end.column; - } - } - textLine += line.substring(lastEnd, endColumn); - return textLine; - } +// this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) { +// var textLine = ""; +// +// // Build a one line string for the current fold sequence that starts +// // at foldStartRow. +// var lastEnd = 0, +// line = this.getLine(foldLine.start.row), +// comp, +// folds = foldLine.folds, +// fold; +// +// for (var i = 0; i < folds.length; i++) { +// fold = folds[i]; +// +// comp = fold.compare(endRow, endColumn); +// // This fold is after the endRow/Column. +// if (comp == -1) { +// textLine += line.substring(lastEnd, endColumn); +// return textLine; +// } +// // The endRow/Column is inside of the current fold. +// else if (comp == 0) { +// textLine += line.substring(lastEnd, fold.start.column); +// return textLine; +// } +// +// textLine += line.substring(lastEnd, fold.start.column); +// textLine += fold.placeholder; +// +// if (fold.sameLine) { +// lastEnd = fold.end.column; +// } else { +// row = fold.end.row; +// +// line = this.getLine(row); +// lastEnd = fold.end.column; +// } +// } +// textLine += line.substring(lastEnd, endColumn); +// return textLine; +// } this.doc2Screen = function(docRow, docColumn) { var screenRow = 0, @@ -1288,7 +1394,7 @@ var EditSession = function(text, mode) { fold = null, folds, comp, - foldLine = null; + foldLine = null, lastFoldLine = null; // Clamp the docRow position in case it's inside of a folded block. foldLine = this.getFoldLine(docRow); @@ -1309,8 +1415,9 @@ var EditSession = function(text, mode) { } for (var row = 0; row < docRow; row++) { - foldLine = this.getFoldLine(row); + foldLine = this.getFoldLine(row, lastFoldLine); if (foldLine) { + lastFoldLine = foldLine; if (foldLine.end.row >= docRow) { break; } @@ -1323,17 +1430,24 @@ var EditSession = function(text, mode) { // Calculate the text line that is displayed in docRow on the screen. var textLine = ""; - - foldLine = this.getFoldLine(docRow); + foldLine = this.getFoldLine(docRow, lastFoldLine); // Check if the final row we want to reach is inside of a fold. if (!foldLine) { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; } else { - textLine = this.$buildFoldedTextLine( - foldLine, - docRow, - docColumn); + // Build the textline using the FoldLine walker. + var line; + foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + if (placeholder) { + textLine += placeholder; + } else { + if (isNewRow) { + line = this.getLine(row); + } + textLine += line.substring(lastColumn, column); + } + }.bind(this), docRow, docColumn) } // Clamp textLine if in wrapMode. @@ -1348,7 +1462,7 @@ var EditSession = function(text, mode) { wrapData[screenRowOffset - 1] || 0, textLine.length); } - return [screenRow, this.$getStringScreenWidth(textLine), textLine]; + return [screenRow, this.$getStringScreenWidth(textLine)[0], textLine]; } this.documentToScreenPosition = function(pos, column) { @@ -1369,6 +1483,14 @@ var EditSession = function(text, mode) { }; }; + this.documentToScreenColumn = function(row, docColumn) { + return this.documentToScreenPosition(row, docColumn).column; + }; + + this.documentToScreenRow = function(docRow, docColumn) { + return this.documentToScreenPosition(docRow, docColumn).row; + }; + this.getScreenLength = function() { var length = this.getLength(); if (!this.$useWrapMode) { @@ -1382,10 +1504,8 @@ var EditSession = function(text, mode) { var foldData = this.$foldData; for (var i = 0; i < foldData.length; i++) { - var foldRow = foldData[i]; - if (foldRow.linkOut) { - screenRows -= foldRow.linkOut.end.row - foldRow.linkOut.start.row; - } + var foldLine = foldData[i]; + screenRows -= foldLine.end.row - foldLine.start.row; } return screenRows; } @@ -1494,7 +1614,48 @@ var EditSession = function(text, mode) { this.getSplitData = function() { // TODO: Add support for wrapped lines here. - return undefined; + return []; + } + + this.walk = function(callback, endRow, endColumn) { + var lastEnd = 0, + folds = this.folds, + fold, + comp, stop, isNewRow = true; + + if (endRow == null) { + endRow = this.end.row; + endColumn = this.end.column; + } + + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; + + comp = fold.compare(endRow, endColumn); + // This fold is after the endRow/Column. + if (comp == -1) { + callback(null, endRow, endColumn, lastEnd, isNewRow); + return; + } + // The endRow/Column is inside of the current fold. + else if (comp == 0) { + callback(null, endRow, fold.start.column, lastEnd, isNewRow); + return; + } + + stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); + stop = stop || callback(fold.placeholder); + + if (stop) { + return; + } + + // Note the new lastEnd might not be on the same line. However, + // it's the callback's job to recognize this. + isNewRow = !fold.sameLine; + lastEnd = fold.end.column; + } + callback(null, endRow, endColumn, lastEnd, isNewRow); } }).call(FoldLine.prototype); @@ -1503,7 +1664,7 @@ var EditSession = function(text, mode) { var i = Math.max(foldData.indexOf(startFoldLine), 0); for (i; i < foldData.length; i++) { var foldLine = foldData[i]; - if (foldLine.start.row >= docRow || foldLine.end.row >= docRow) { + if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) { return foldLine; } else if (foldLine.end.row > docRow) { return null; diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 42cb4536..a9cfc490 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -65,6 +65,66 @@ function createFoldTestSession() { return session; } + +var foldTests = { + "fold-same-row": [ + [0, 0, 0, 0], + [0, 13, 0, 13], + [0, 14, 0, 13], + [0, 17, 0, 13], + [0, 18, 0, 20], + [0, 19, 0, 20] + ], + + "fold-on-some-other-row": [ + [1, 0, 1, 0], + [1, 10, 1, 10], + [1, 11, 1, 10], + [1, 99, 1, 10], + + [2, 0, 1, 10], + [2, 9, 1, 10], + [2, 10, 1, 16], + [2, 11, 1, 17] + ], + + "fold-in-the-same-row+fold-other-row": [ + [2, 19, 1, 25], + [2, 20, 1, 26], + [2, 21, 1, 26], + + [2, 24, 1, 26], + [2, 25, 1, 32], + [2, 26, 1, 33], + [2, 99, 1, 40], + ], + + "fold-after": [ + [3, 0, 2, 0] + ] +}; + +function runFoldTests(unitFunc) { + var session = createFoldTestSession(); + function assertScreen2Doc(screenRow, screenCol, docRow, docCol) { + assert.position( + session.screenToDocumentPosition(docRow, docCol), + screenRow, screenCol + ); + } + + var foldTests = this.foldTests, + tests, test, testArray; + + for (tests in foldTests) { + testArray = foldTests[tests]; + for (var i = 0; i < testArray.length; i++) { + test = testArray[i]; + unitFunc(test[0], test[1], test[2], test[3]); + } + } +} + module.exports = { "test: find matching opening bracket" : function() { @@ -372,44 +432,23 @@ module.exports = { }, "test fold documentToScreen": function() { - var session = createFoldTestSession(); function assertDoc2Screen(docRow, docCol, screenRow, screenCol) { assert.position( session.documentToScreenPosition(docRow, docCol), screenRow, screenCol ); } + runFoldTests(assertDoc2Screen); + }, - // One fold ending in the same row. - assertDoc2Screen(0, 0, 0, 0); - assertDoc2Screen(0, 13, 0, 13); - assertDoc2Screen(0, 14, 0, 13); - assertDoc2Screen(0, 17, 0, 13); - assertDoc2Screen(0, 18, 0, 20); - - // Fold ending on some other row. - assertDoc2Screen(1, 0, 1, 0); - assertDoc2Screen(1, 10, 1, 10); - assertDoc2Screen(1, 11, 1, 10); - assertDoc2Screen(1, 99, 1, 10); - - assertDoc2Screen(2, 0, 1, 10); - assertDoc2Screen(2, 9, 1, 10); - assertDoc2Screen(2, 10, 1, 16); - assertDoc2Screen(2, 11, 1, 17); - - // Fold in the same row with fold over more then one row in the same row. - assertDoc2Screen(2, 19, 1, 25); - assertDoc2Screen(2, 20, 1, 26); - assertDoc2Screen(2, 21, 1, 26); - - assertDoc2Screen(2, 24, 1, 26); - assertDoc2Screen(2, 25, 1, 32); - assertDoc2Screen(2, 26, 1, 33); - assertDoc2Screen(2, 99, 1, 40); - - // Test one position after the folds. Should be all like normal. - assertDoc2Screen(3, 0, 2, 0); + "test fold screenToDocument": function() { + function assertScreen2Doc(docRow, docCol, screenRow, screenCol) { + assert.position( + session.screenToDocumentPosition(screenRow, screenCol), + docRow, docCol + ); + } + runFoldTests(assertScreen2Doc); } }; diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index aabefabd..548ef4bd 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -415,9 +415,7 @@ var Text = function(parentEl) { this.$renderFoldLine = function(stringBuilder, row, tokens) { var session = this.session, foldLine = session.getFoldLine(row), - folds = foldLine.folds; - - var renderTokens = []; + renderTokens = []; function addTokens(tokens, from, to) { var idx = 0, col = 0; @@ -459,26 +457,19 @@ var Text = function(parentEl) { } } - var lastCol = 0, - fold = null; - - for (var i = 0; i < folds.length; i++) { - fold = folds[i]; - addTokens(tokens, lastCol, fold.start.column); - renderTokens.push({ - type: "fold", - value: fold.placeholder - }); - - if (fold.start.row != fold.end.row) { - tokens = this.tokenizer.getTokens( - fold.end.row, fold.end.row)[0].tokens; + foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + if (placeholder) { + renderTokens.push({ + type: "fold", + value: placeholder + }); + } else { + if (isNewRow) { + tokens = this.tokenizer.getTokens(row, row)[0].tokens; + } + addTokens(tokens, lastColumn, column); } - - lastCol = fold.end.column; - } - // Add the rest of the line - addTokens(tokens, lastCol, session.getLine(foldLine.end.row).length); + }.bind(this), foldLine.end.row, this.session.getLine(foldLine.end.row).length); // TODO: Build a fake splits array! var splits = foldLine.getSplitData(); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index d7f66b2d..7e15cf81 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -477,7 +477,7 @@ var VirtualRenderer = function(container, theme) { // Map lines on the screen to lines in the document. var firstRowScreen, firstRowHeight; var lineHeight = { lineHeight: this.lineHeight }; - firstRow = session.screenToDocumentRow(firstRow); + firstRow = session.screenToDocumentRow(firstRow, 0); firstRowScreen = session.documentToScreenRow(firstRow); firstRowHeight = session.getRowHeight(lineHeight, firstRow); From 068e289f1703d86f18ea1ab60d4401c2bed5e404 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 24 Apr 2011 22:37:03 +0200 Subject: [PATCH 12/53] Get fold EditSession.screenToDocument working + unit tested --- demo/demo.js | 2 +- lib/ace/edit_session.js | 219 ++++------------------------------- lib/ace/edit_session_test.js | 124 ++++++++++---------- 3 files changed, 83 insertions(+), 262 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index efab6bb4..6fa0acd8 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -149,7 +149,7 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; - // docs.js.addFold(new Range(0, 13, 0, 18), "args..."); + docs.js.addFold(new Range(0, 13, 0, 18), "args..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); window.s = docs.js; diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index e99ae17a..4ba33fb1 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1000,11 +1000,11 @@ var EditSession = function(text, mode) { arr.push(TAB_SPACE); } } - // Space - else if(c == 32) { - arr.push(SPACE); - } - // full width characters + // Space + else if(c == 32) { + arr.push(SPACE); + } + // full width characters else if (isFullWidth(c)) { arr.push(CHAR, CHAR_EXT); } else { @@ -1014,47 +1014,6 @@ var EditSession = function(text, mode) { return arr; } - this.$getStringDisplayData = function(str, maxScreenColumn, column, offset) { - if (maxScreenColumn == null) { - maxScreenColumn = str.length * Math.max(this.getTabSize(), 2); - } - column = column || 0; - - var arr = [], - tabSize, - c; - - for (column; column < str.length; column++) { - c = str.charCodeAt(column); - // Tab - if (c == 9) { - tabSize = this.getScreenTabSize(arr.length + offset); - arr.push(TAB); - for (var n = 1; n < tabSize; n++) { - arr.push(TAB_SPACE); - } - } - // Space - else if(c == 32) { - arr.push(SPACE); - } - // full width characters - else if (isFullWidth(c)) { - arr.push(CHAR, CHAR_EXT); - } else { - arr.push(CHAR); - } - if ((arr.length + offset) > maxScreenColumn) { - column --; - break; - } - } - return { - tokens: arr, - column: column - }; - } - /** * Calculates the width of the a string on the screen while assuming that * the string starts at the first column on the screen. @@ -1069,29 +1028,30 @@ var EditSession = function(text, mode) { return [0, 0]; } if (maxScreenColumn == null) { - maxScreenColumn = str.length * Math.max(this.getTabSize(), 2); + maxScreenColumn = screenColumn + + str.length * Math.max(this.getTabSize(), 2); } screenColumn = screenColumn || 0; - var c; + var c, column; for (column = 0; column < str.length; column++) { c = str.charCodeAt(column); // tab if (c == 9) { screenColumn += this.getScreenTabSize(screenColumn); } - // full width characters + // full width characters else if (isFullWidth(c)) { - screenColumn += 2; - } else { - screenColumn += 1; - } + screenColumn += 2; + } else { + screenColumn += 1; + } if (screenColumn > maxScreenColumn) { break } - } + } - return [screenColumn, column]; + return [screenColumn, column]; } this.getRowLength = function(row) { @@ -1160,32 +1120,6 @@ var EditSession = function(text, mode) { } }; - /** - * - * @returns array - * - array[0]: The documentRow equivalent. - * - array[1]: The screenRowOffset to the first documentRow on the screen. - */ - this.$screenToDocumentRow = function(screenRow, column) { - if (!this.$useWrapMode) { -// for (var row = 0; row < screenRow; i++) { -// -// } - return [screenRow, 0]; - } - - // TODO: WrapMode + Code Folding. - var wrapData = this.$wrapData, linesCount = this.getLength(); - var docRow = 0, - row = screenRow; - while (docRow < linesCount && row >= wrapData[docRow].length + 1) { - row -= wrapData[docRow].length + 1; - docRow ++; - } - - return [docRow, row]; - }; - /** * Returns the width of a tab character at screenColumn. */ @@ -1233,12 +1167,14 @@ var EditSession = function(text, mode) { var data, str; if (placeholder) { data = this.$getStringScreenWidth(placeholder, null, walkScreenColumn); - if (data[0] > screenColumn) { + if (data[0] > screenColumn || data[1] == 0) { return true; // Stop walk. } docColumn += data[1]; } else { + docColumn = lastColumn; if (isNewRow) { + docRow = row; line = this.getLine(row); } str = line.substring(lastColumn, column); @@ -1248,8 +1184,8 @@ var EditSession = function(text, mode) { return true; // Stop walk. } } - walkScreenColumn += data[0]; - }.bind(this)); + walkScreenColumn = data[0]; + }.bind(this), foldLine.end.row, this.getLine(foldLine.end.row).length); } else { line = this.getLine(docRow); splits = this.$wrapData[docRow]; @@ -1294,99 +1230,8 @@ var EditSession = function(text, mode) { row: ret[0], column: ret[1] }; - - var line; - var docRow; - var docColumn; - var remaining = column; - var linesCount = this.getLength(); - if (!this.$useWrapMode) { - docRow = row >= linesCount? linesCount-1 : (row < 0 ? 0 : row); - row = 0; - docColumn = 0; - line = this.getLine(docRow); - } else { - var wrapData = this.$wrapData; - - var docRow = 0; - while (docRow < linesCount && row >= wrapData[docRow].length + 1) { - row -= wrapData[docRow].length + 1; - docRow ++; - } - - if (docRow >= linesCount) { - docRow = linesCount-1 - row = wrapData[docRow].length; - } - docColumn = wrapData[docRow][row - 1] || 0; - line = this.getLine(docRow).substring(docColumn); - } - - var tabSize, - screenColumn = 0; - docColumn += this.$getStringScreenWidth(line, column)[1]; - - // Clamp docColumn. - if (this.$useWrapMode) { - column = wrapData[docRow][row] - if (docColumn >= column) { - // We remove one character at the end such that the docColumn - // position returned is not associated to the next row on the - // screen. - docColumn = column - 1; - } - } else if (line) { - docColumn = Math.min(docColumn, line.length); - } - - return { - row: docRow, - column: docColumn - }; }; -// this.$buildFoldedTextLine = function(foldLine, endRow, endColumn) { -// var textLine = ""; -// -// // Build a one line string for the current fold sequence that starts -// // at foldStartRow. -// var lastEnd = 0, -// line = this.getLine(foldLine.start.row), -// comp, -// folds = foldLine.folds, -// fold; -// -// for (var i = 0; i < folds.length; i++) { -// fold = folds[i]; -// -// comp = fold.compare(endRow, endColumn); -// // This fold is after the endRow/Column. -// if (comp == -1) { -// textLine += line.substring(lastEnd, endColumn); -// return textLine; -// } -// // The endRow/Column is inside of the current fold. -// else if (comp == 0) { -// textLine += line.substring(lastEnd, fold.start.column); -// return textLine; -// } -// -// textLine += line.substring(lastEnd, fold.start.column); -// textLine += fold.placeholder; -// -// if (fold.sameLine) { -// lastEnd = fold.end.column; -// } else { -// row = fold.end.row; -// -// line = this.getLine(row); -// lastEnd = fold.end.column; -// } -// } -// textLine += line.substring(lastEnd, endColumn); -// return textLine; -// } - this.doc2Screen = function(docRow, docColumn) { var screenRow = 0, screenColumn = 0, @@ -1747,30 +1592,6 @@ var EditSession = function(text, mode) { : docRow) }; -// this.findRowFoldedForward = function(start, limit) { -// if (limit == null) { -// limit = this.getLength(); -// } -// for (var row = start; row != limit; row++) { -// if (this.isRowFolded(row)) { -// return row; -// } -// } -// return null; -// } -// -// this.findRowFoldedBackwards = function(start, limit) { -// if (limit == null) { -// limit = -1; -// } -// for (var row = start; row != limit; row--) { -// if (this.isRowFolded(row)) { -// return row; -// } -// } -// return null; -// } - }).call(EditSession.prototype); exports.EditSession = EditSession; diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index a9cfc490..d7811f7d 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -65,66 +65,6 @@ function createFoldTestSession() { return session; } - -var foldTests = { - "fold-same-row": [ - [0, 0, 0, 0], - [0, 13, 0, 13], - [0, 14, 0, 13], - [0, 17, 0, 13], - [0, 18, 0, 20], - [0, 19, 0, 20] - ], - - "fold-on-some-other-row": [ - [1, 0, 1, 0], - [1, 10, 1, 10], - [1, 11, 1, 10], - [1, 99, 1, 10], - - [2, 0, 1, 10], - [2, 9, 1, 10], - [2, 10, 1, 16], - [2, 11, 1, 17] - ], - - "fold-in-the-same-row+fold-other-row": [ - [2, 19, 1, 25], - [2, 20, 1, 26], - [2, 21, 1, 26], - - [2, 24, 1, 26], - [2, 25, 1, 32], - [2, 26, 1, 33], - [2, 99, 1, 40], - ], - - "fold-after": [ - [3, 0, 2, 0] - ] -}; - -function runFoldTests(unitFunc) { - var session = createFoldTestSession(); - function assertScreen2Doc(screenRow, screenCol, docRow, docCol) { - assert.position( - session.screenToDocumentPosition(docRow, docCol), - screenRow, screenCol - ); - } - - var foldTests = this.foldTests, - tests, test, testArray; - - for (tests in foldTests) { - testArray = foldTests[tests]; - for (var i = 0; i < testArray.length; i++) { - test = testArray[i]; - unitFunc(test[0], test[1], test[2], test[3]); - } - } -} - module.exports = { "test: find matching opening bracket" : function() { @@ -432,23 +372,83 @@ module.exports = { }, "test fold documentToScreen": function() { + var session = createFoldTestSession(); function assertDoc2Screen(docRow, docCol, screenRow, screenCol) { assert.position( session.documentToScreenPosition(docRow, docCol), screenRow, screenCol ); } - runFoldTests(assertDoc2Screen); + + // One fold ending in the same row. + assertDoc2Screen(0, 0, 0, 0); + assertDoc2Screen(0, 13, 0, 13); + assertDoc2Screen(0, 14, 0, 13); + assertDoc2Screen(0, 17, 0, 13); + assertDoc2Screen(0, 18, 0, 20); + + // Fold ending on some other row. + assertDoc2Screen(1, 0, 1, 0); + assertDoc2Screen(1, 10, 1, 10); + assertDoc2Screen(1, 11, 1, 10); + assertDoc2Screen(1, 99, 1, 10); + + assertDoc2Screen(2, 0, 1, 10); + assertDoc2Screen(2, 9, 1, 10); + assertDoc2Screen(2, 10, 1, 16); + assertDoc2Screen(2, 11, 1, 17); + + // Fold in the same row with fold over more then one row in the same row. + assertDoc2Screen(2, 19, 1, 25); + assertDoc2Screen(2, 20, 1, 26); + assertDoc2Screen(2, 21, 1, 26); + + assertDoc2Screen(2, 24, 1, 26); + assertDoc2Screen(2, 25, 1, 32); + assertDoc2Screen(2, 26, 1, 33); + assertDoc2Screen(2, 99, 1, 40); + + // Test one position after the folds. Should be all like normal. + assertDoc2Screen(3, 0, 2, 0); }, "test fold screenToDocument": function() { + var session = createFoldTestSession(); function assertScreen2Doc(docRow, docCol, screenRow, screenCol) { assert.position( session.screenToDocumentPosition(screenRow, screenCol), docRow, docCol ); } - runFoldTests(assertScreen2Doc); + + // One fold ending in the same row. + assertScreen2Doc(0, 0, 0, 0); + assertScreen2Doc(0, 13, 0, 13); + assertScreen2Doc(0, 13, 0, 14); + assertScreen2Doc(0, 18, 0, 20); + assertScreen2Doc(0, 19, 0, 21); + + // Fold ending on some other row. + assertScreen2Doc(1, 0, 1, 0); + assertScreen2Doc(1, 10, 1, 10); + assertScreen2Doc(1, 10, 1, 11); + + assertScreen2Doc(1, 10, 1, 15); + assertScreen2Doc(2, 10, 1, 16); + assertScreen2Doc(2, 11, 1, 17); + + // Fold in the same row with fold over more then one row in the same row. + assertScreen2Doc(2, 19, 1, 25); + assertScreen2Doc(2, 20, 1, 26); + assertScreen2Doc(2, 20, 1, 27); + + assertScreen2Doc(2, 20, 1, 31); + assertScreen2Doc(2, 25, 1, 32); + assertScreen2Doc(2, 26, 1, 33); + assertScreen2Doc(2, 33, 1, 99); + + // Test one position after the folds. Should be all like normal. + assertScreen2Doc(3, 0, 2, 0); } }; From 273a1e41d1bf4065d5a15fc1271d11f2791a09e6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 24 Apr 2011 22:42:13 +0200 Subject: [PATCH 13/53] Remove 'border' from ace_fold as it misaligned the lines. Fix screenToDocumentRow --- lib/ace/edit_session.js | 2 +- lib/ace/theme/textmate.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 4ba33fb1..92faf946 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1217,7 +1217,7 @@ var EditSession = function(text, mode) { screenColumn = 0; // throw "Need to pass in screenColumn"; } - return this.$screenToDocumentRow(screenRow)[0]; + return this.screenToDocumentPosition(screenRow, screenColumn).row; }; this.screenToDocumentColumn = function(screenRow, screenColumn) { diff --git a/lib/ace/theme/textmate.js b/lib/ace/theme/textmate.js index 15f37177..2c43c9dd 100644 --- a/lib/ace/theme/textmate.js +++ b/lib/ace/theme/textmate.js @@ -108,8 +108,7 @@ define(function(require, exports, module) { \ .ace-tm .ace_line .ace_fold {\ background-color: #E4E4E4;\ - border: 1px solid rgba(0, 0, 0, 0.3);\ - border-radius: 5px;\ + border-radius: 3px;\ }\ \ .ace-tm .ace_line .ace_support.ace_function {\ From 88f55c958ad541d466af7948fc0ddfa24f06bed0 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 24 Apr 2011 22:46:22 +0200 Subject: [PATCH 14/53] Fix active line highlighter in folded row --- lib/ace/editor.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/ace/editor.js b/lib/ace/editor.js index 51d86da7..d8a57998 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -322,8 +322,14 @@ var Editor =function(renderer, session) { session.$highlightLineMarker = null; if (this.getHighlightActiveLine() && (this.getSelectionStyle() != "line" || !this.selection.isMultiLine())) { - var cursor = this.getCursorPosition(); - var range = new Range(cursor.row, 0, cursor.row+1, 0); + var cursor = this.getCursorPosition(), + foldLine = this.session.getFoldLine(cursor.row); + var range; + if (foldLine) { + range = new Range(foldLine.start.row, 0, foldLine.end.row + 1, 0); + } else { + range = new Range(cursor.row, 0, cursor.row+1, 0); + } session.$highlightLineMarker = session.addMarker(range, "ace_active_line", "line"); } }; From 8c63cd7ff50c5df6fc3c445bfc433120e0b9e3de Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Sun, 24 Apr 2011 23:59:59 +0200 Subject: [PATCH 15/53] Get move(Word)Right/left working with folds --- lib/ace/edit_session.js | 84 +++++++++++++++++++++++++++++++++++++++++ lib/ace/selection.js | 47 ++++++++++++++++++----- 2 files changed, 121 insertions(+), 10 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 92faf946..984e0163 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1502,8 +1502,92 @@ var EditSession = function(text, mode) { } callback(null, endRow, endColumn, lastEnd, isNewRow); } + + this.getStringAt = function(session, row, column, trim) { + var fold, lastFold, cmp, str; + lastFold = { + end: { column: 0 } + }; + for (var i = 0; i < this.folds.length; i++) { + fold = this.folds[i]; + cmp = fold.compare(row, column); + if (cmp == -1) { + str = session.getLine(fold.start.row). + substring(lastFold.end.column, fold.start.column); + break; + } else if (cmp == 0) { + return null; + } + lastFold = fold; + } + if (!str) { + str = session.getLine(fold.start.row). + substring(lastFold.end.column); + } + if (trim == -1) { + return str.substring(0, column - lastFold.end.column); + } else if (trim == 1) { + return str.substring(column - lastFold.end.column) + } else { + return str; + } + } }).call(FoldLine.prototype); + this.getFoldAt = function(row, column, side) { + var foldLine = this.getFoldLine(row); + if (foldLine) { + var folds = foldLine.folds, + fold; + + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; + if (fold.range.contains(row, column)) { + if (side == -1) { + if (fold.start.row != row || fold.start.column != column) { + return fold; + } + } else if (side == 1) { + if (fold.end.row != row || fold.end.column != column) { + return fold; + } + } else if (side == 0) { + return fold; + } + return null; + } + } + } else { + return null; + } + } + + /** + * Returns the string between folds at the given position. + * E.g. + * foob|arwolrd -> "bar" + * foobarwol|rd -> "world" + * foobarwolrd -> + * + * where | means the position of row/column + * + * The trim option determs if the return string should be trimed according + * to the "side" passed with the trim value: + * + * E.g. + * foob|arwolrd -trim=-1> "b" + * foobarwol|rd -trim=+1> "rld" + * fo|obarwolrd -trim=00> "foo" + */ + this.getFoldStringAt = function(row, column, trim) { + var foldLine = this.getFoldLine(row); + if (!foldLine) { + return null; + } else { + return foldLine.getStringAt(this, row, column, trim); + } + } + this.getFoldLine = function(docRow, startFoldLine) { var foldData = this.$foldData; var i = Math.max(foldData.indexOf(startFoldLine), 0); diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 1be40c41..6a5bd7b8 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -262,8 +262,12 @@ var Selection = function(session) { }; this.moveCursorLeft = function() { - var cursor = this.selectionLead.getPosition(); - if (cursor.column == 0) { + var cursor = this.selectionLead.getPosition(), + fold; + + if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { + this.moveCursorTo(fold.start.row, fold.start.column); + } else if (cursor.column == 0) { // cursor is a line (start if (cursor.row > 0) { this.moveCursorTo(cursor.row - 1, this.doc.getLine(cursor.row - 1).length); @@ -279,7 +283,11 @@ var Selection = function(session) { }; this.moveCursorRight = function() { - if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { + var cursor = this.selectionLead.getPosition(), + fold; + if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { + this.moveCursorTo(fold.end.row, fold.end.column); + } else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { if (this.selectionLead.row < this.doc.getLength() - 1) { this.moveCursorTo(this.selectionLead.row + 1, 0); } @@ -339,7 +347,11 @@ var Selection = function(session) { this.session.nonTokenRe.lastIndex = 0; this.session.tokenRe.lastIndex = 0; - if (column == line.length) { + var fold; + if (fold = this.session.getFoldAt(row, column, 1)) { + this.moveCursorTo(fold.end.row, fold.end.column); + return; + } else if (column == line.length) { this.moveCursorRight(); return; } @@ -358,18 +370,33 @@ var Selection = function(session) { this.moveCursorWordLeft = function() { var row = this.selectionLead.row; var column = this.selectionLead.column; - var line = this.doc.getLine(row); - var leftOfCursor = lang.stringReverse(line.substring(0, column)); - var match; - this.session.nonTokenRe.lastIndex = 0; - this.session.tokenRe.lastIndex = 0; + var fold; + if (fold = this.session.getFoldAt(row, column, -1)) { + this.moveCursorTo(fold.start.row, fold.start.column); + return; + } if (column == 0) { this.moveCursorLeft(); return; } - else if (match = this.session.nonTokenRe.exec(leftOfCursor)) { + + var str = null; + var foldLine = this.session.getFoldLine(row); + if (foldLine) { + str = foldLine.getStringAt(this.session, row, column, -1); + } + if (str == null) { + str = this.doc.getLine(row).substring(0, column) + } + var leftOfCursor = lang.stringReverse(str); + + var match; + this.session.nonTokenRe.lastIndex = 0; + this.session.tokenRe.lastIndex = 0; + + if (match = this.session.nonTokenRe.exec(leftOfCursor)) { column -= this.session.nonTokenRe.lastIndex; this.session.nonTokenRe.lastIndex = 0; } From 145030972cf0dd7f77e39f99645c1039bd293657 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 00:10:38 +0200 Subject: [PATCH 16/53] Remove EditSession.getScreenFirstRowColumn() func and clean up a little bit --- lib/ace/edit_session.js | 20 -------------------- lib/ace/selection.js | 2 +- lib/ace/virtual_renderer.js | 2 +- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 984e0163..24f773d4 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1092,26 +1092,10 @@ var EditSession = function(text, mode) { }; 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; @@ -1213,10 +1197,6 @@ var EditSession = function(text, mode) { } this.screenToDocumentRow = function(screenRow, screenColumn) { - if (screenColumn == null) { - screenColumn = 0; - // throw "Need to pass in screenColumn"; - } return this.screenToDocumentPosition(screenRow, screenColumn).row; }; diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 6a5bd7b8..e7c141c7 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -306,7 +306,7 @@ var Selection = function(session) { var row = this.selectionLead.row; var column = this.selectionLead.column; var screenRow = this.session.documentToScreenRow(row, column); - var firstRowColumn = this.session.getScreenFirstRowColumn(screenRow); + var firstRowColumn = this.session.screenToDocumentColumn(screenRow, 0) var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column); var leadingSpace = beforeCursor.match(/^\s*/); if (leadingSpace[0].length == 0) { diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 7e15cf81..964e7b27 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -481,7 +481,7 @@ var VirtualRenderer = function(container, theme) { firstRowScreen = session.documentToScreenRow(firstRow); firstRowHeight = session.getRowHeight(lineHeight, firstRow); - lastRow = Math.min(session.screenToDocumentRow(lastRow), session.getLength() - 1); + lastRow = Math.min(session.screenToDocumentRow(lastRow, 0), session.getLength() - 1); minHeight = this.$size.scrollerHeight + session.getRowHeight(lineHeight, lastRow)+ firstRowHeight; From c22d80398ad3ef298af1b77f17dbe1f8e2ff45e6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 00:51:04 +0200 Subject: [PATCH 17/53] Add handling for simple adding/removing of content to realign the folds --- lib/ace/edit_session.js | 79 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 24f773d4..c1005e7b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -879,6 +879,15 @@ var EditSession = function(text, mode) { useWrapMode && this.$wrapData.splice(firstRow, len); // TODO: Remove no longer needed folds here. // TODO: Update row data on folds. + + var foldLines = this.$foldData; + for (var i = 0; i < foldLines.length; i++) { + var foldLine = foldLines[i]; + if (foldLine.start.row >= firstRow + len) { + foldLine.shiftRow(-len); + } + } + lastRow = firstRow; } else { var args; @@ -889,7 +898,45 @@ var EditSession = function(text, mode) { } // TODO: Expand folds here if needed. - // TODO: Update row data on folds. + // TODO: Split foldLine in case there are new lines added in + // between of a foldLine. + var foldLines = this.$foldData; + for (var i = 0; i < foldLines.length; i++) { + var foldLine = foldLines[i]; + if (foldLine.start.row >= firstRow) { + foldLine.shiftRow(len); + } + } + } + } else { + len = Math.abs(e.data.range.start.column - e.data.range.end.column); + var column; + if (action.indexOf("insert") != -1) { + column = e.data.range.start.column; + } else { + column = e.data.range.end.column; + len = -len; + } + var foldLine = this.getFoldLine(firstRow); + if (foldLine) { + // TODO: Adding new characters into a fold range should + // expand/remove the fold. + // for (var i = 0; i <= ) + var ret = foldLine.getNextFoldTo(firstRow, column); + if (ret) { + if (ret.kind == "inside") { + // TODO + } else if (ret.fold.start.row == firstRow){ + var folds = foldLine.folds; + for (var i = folds.indexOf(ret.fold); i < folds.length; i++) { + folds[i].start.column += len; + if (!folds[i].sameLine) { + break; + } + folds[i].end.column += len; + } + } + } } } @@ -1410,6 +1457,15 @@ var EditSession = function(text, mode) { } (function() { + this.shiftRow = function(shift) { + this.start.row += shift; + this.end.row += shift; + this.folds.forEach(function(fold) { + fold.start.row += shift; + fold.end.row += shift; + }); + } + this.addFold = function(fold) { if (fold.sameLine) { if (fold.start.row < this.startRow || fold.endRow > this.endRow) { @@ -1483,11 +1539,32 @@ var EditSession = function(text, mode) { callback(null, endRow, endColumn, lastEnd, isNewRow); } + this.getNextFoldTo = function(row, column) { + var fold; + for (var i = 0; i < this.folds.length; i++) { + fold = this.folds[i]; + cmp = fold.compare(row, column); + if (cmp == -1) { + return { + fold: fold, + kind: "after" + }; + } else if (cmp == 0) { + return { + fold: fold, + kind: "inside" + } + } + } + return null; + } + this.getStringAt = function(session, row, column, trim) { var fold, lastFold, cmp, str; lastFold = { end: { column: 0 } }; + // TODO: Refactor to use getNextFoldTo function. for (var i = 0; i < this.folds.length; i++) { fold = this.folds[i]; cmp = fold.compare(row, column); From 4beecc086a16dd7bb64bd718257d527fa464f2f6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 01:06:42 +0200 Subject: [PATCH 18/53] Fix bug in TextLayer.updateLines --- lib/ace/edit_session.js | 4 ++++ lib/ace/layer/text.js | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index c1005e7b..17f95525 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1498,6 +1498,10 @@ var EditSession = function(text, mode) { return []; } + this.containsRow = function(row) { + return row >= this.start.row && row <= this.end.row; + } + this.walk = function(callback, endRow, endColumn) { var lastEnd = 0, folds = this.folds, diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 548ef4bd..60bc771a 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -190,8 +190,15 @@ var Text = function(parentEl) { lineElementsIdx = 0; for (var row = config.firstRow; row < first; row++) { + var foldLine = this.session.getFoldLine(row); + if (foldLine) { + if (foldLine.containsRow(first)) { + break; + } else { + row = foldLine.end.row; + } + } lineElementsIdx ++; - row = this.session.getRowFoldEnd(row); } for (var i=first; i<=last; i++) { From 15d2c501173b2b22af2221751b10a2b73e9efd8c Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 01:10:12 +0200 Subject: [PATCH 19/53] Fix bug adding new characters right before a fold caused the chars to get added inside of the fold --- lib/ace/edit_session.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 17f95525..a0aadb5b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -924,8 +924,13 @@ var EditSession = function(text, mode) { // for (var i = 0; i <= ) var ret = foldLine.getNextFoldTo(firstRow, column); if (ret) { - if (ret.kind == "inside") { - // TODO + var fold = ret.fold; + if (ret.kind == "inside" + && fold.start.column != column + && fold.start.row != firstRow) + { + // TODO: Implement adding new characters inside of an + // fold. This should extend/remove the fold etc. } else if (ret.fold.start.row == firstRow){ var folds = foldLine.folds; for (var i = folds.indexOf(ret.fold); i < folds.length; i++) { From 56f74f13688eeb8fe57cf38d40d4547ecad6d7e6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 11:14:25 +0200 Subject: [PATCH 20/53] Remove Fold.compare/contains functions. Rename Fold.sameLine to Fold.sameRow. Add new functions to Range that repalce the Fold.compare/contains functions but are more explicit on where to drop the start/end. --- lib/ace/edit_session.js | 67 +++++++++++------------------------------ lib/ace/range.js | 63 ++++++++++++++++++++++++++++++++++++++ lib/ace/selection.js | 8 ++--- 3 files changed, 85 insertions(+), 53 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index a0aadb5b..b3297b13 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -935,7 +935,7 @@ var EditSession = function(text, mode) { var folds = foldLine.folds; for (var i = folds.indexOf(ret.fold); i < folds.length; i++) { folds[i].start.column += len; - if (!folds[i].sameLine) { + if (!folds[i].sameRow) { break; } folds[i].end.column += len; @@ -1274,21 +1274,10 @@ var EditSession = function(text, mode) { foldLine = null, lastFoldLine = null; // Clamp the docRow position in case it's inside of a folded block. - foldLine = this.getFoldLine(docRow); - if (foldLine) { - folds = foldLine.folds; - for (var i = 0; i < folds.length; i++) { - fold = folds[i]; - comp = fold.compare(docRow, docColumn); - if (comp == 0) { - docRow = fold.start.row; - docColumn = fold.start.column; - break; - } else if (comp == -1) { - break; - } - } - foldLine = null; + fold = this.getFoldAt(docRow, docColumn, -1); + if (fold) { + docRow = fold.start.row; + docColumn = fold.start.column; } for (var row = 0; row < docRow; row++) { @@ -1437,21 +1426,7 @@ var EditSession = function(text, mode) { this.start = range.start; this.end = range.end; - this.sameLine = range.start.row == range.end.row; - } - - Fold.prototype.contains = function(row, column) { - if (this.end.row == row && this.end.column == column) { - return false; - } - return this.range.contains(row, column); - } - - Fold.prototype.compare = function(row, column) { - if (this.end.row == row && this.end.column == column) { - return 1; - } - return this.range.compare(row, column); + this.sameRow = range.start.row == range.end.row; } function FoldLine(fold) { @@ -1472,13 +1447,13 @@ var EditSession = function(text, mode) { } this.addFold = function(fold) { - if (fold.sameLine) { + if (fold.sameRow) { if (fold.start.row < this.startRow || fold.endRow > this.endRow) { throw "Can't add a fold to this FoldLine as it has no connection"; } this.folds.push(fold); this.folds.sort(function(a, b) { - return -a.compare(b.start.row, b.start.column); + return -a.range.compareEnd(b.start.row, b.start.column); }); } else if (fold.start.row == this.end.row) { this.folds.push(fold); @@ -1521,7 +1496,7 @@ var EditSession = function(text, mode) { for (var i = 0; i < folds.length; i++) { fold = folds[i]; - comp = fold.compare(endRow, endColumn); + comp = fold.range.compareEnd(endRow, endColumn); // This fold is after the endRow/Column. if (comp == -1) { callback(null, endRow, endColumn, lastEnd, isNewRow); @@ -1542,7 +1517,7 @@ var EditSession = function(text, mode) { // Note the new lastEnd might not be on the same line. However, // it's the callback's job to recognize this. - isNewRow = !fold.sameLine; + isNewRow = !fold.sameRow; lastEnd = fold.end.column; } callback(null, endRow, endColumn, lastEnd, isNewRow); @@ -1552,7 +1527,7 @@ var EditSession = function(text, mode) { var fold; for (var i = 0; i < this.folds.length; i++) { fold = this.folds[i]; - cmp = fold.compare(row, column); + cmp = fold.range.compareEnd(row, column); if (cmp == -1) { return { fold: fold, @@ -1576,7 +1551,7 @@ var EditSession = function(text, mode) { // TODO: Refactor to use getNextFoldTo function. for (var i = 0; i < this.folds.length; i++) { fold = this.folds[i]; - cmp = fold.compare(row, column); + cmp = fold.range.compareEnd(row, column); if (cmp == -1) { str = session.getLine(fold.start.row). substring(lastFold.end.column, fold.start.column); @@ -1609,18 +1584,12 @@ var EditSession = function(text, mode) { for (var i = 0; i < folds.length; i++) { fold = folds[i]; if (fold.range.contains(row, column)) { - if (side == -1) { - if (fold.start.row != row || fold.start.column != column) { - return fold; - } - } else if (side == 1) { - if (fold.end.row != row || fold.end.column != column) { - return fold; - } - } else if (side == 0) { - return fold; + if (side == -1 && fold.range.isEnd(row, column)) { + return null; + } else if (side == 1 && fold.range.isStart(row, column)) { + return null } - return null; + return fold; } } } else { @@ -1690,7 +1659,7 @@ var EditSession = function(text, mode) { } else if (startRow == foldLine.end.row) { foldLine.addFold(fold); added = true; - if (!fold.sameLine) { + if (!fold.sameRow) { // Check if we might have to merge two FoldLines. foldLineNext = foldData[i + 1]; if (foldLineNext && foldLineNext.start.row == endRow) { diff --git a/lib/ace/range.js b/lib/ace/range.js index 4c613699..4c51067f 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -60,6 +60,47 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return this.compare(row, column) == 0; }; + this.isEnd = function(row, column) { + return this.end.row == row && this.end.column == column; + } + + this.isStart = function(row, column) { + return this.start.row == row && this.start.column == column; + } + + this.inside = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column) || this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + + this.insideStart = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isEnd(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + + this.insideEnd = function(row, column) { + if (this.compare(row, column) == 0) { + if (this.isStart(row, column)) { + return false; + } else { + return true; + } + } + return false; + } + this.compare = function(row, column) { if (!this.isMultiLine()) { if (row === this.start.row) { @@ -82,6 +123,28 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return 0; }; + /** + * Like .compare(), but if isStart is true, return -1; + */ + this.compareStart = function(row, column) { + if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + } + + /** + * Like .compare(), but if isEnd is true, return 1; + */ + this.compareEnd = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else { + return this.compare(row, column); + } + } + this.clipRows = function(firstRow, lastRow) { if (this.end.row > lastRow) { var end = { diff --git a/lib/ace/selection.js b/lib/ace/selection.js index e7c141c7..70a5fafe 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -265,7 +265,7 @@ var Selection = function(session) { var cursor = this.selectionLead.getPosition(), fold; - if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { + if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { this.moveCursorTo(fold.start.row, fold.start.column); } else if (cursor.column == 0) { // cursor is a line (start @@ -285,7 +285,7 @@ var Selection = function(session) { this.moveCursorRight = function() { var cursor = this.selectionLead.getPosition(), fold; - if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { + if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { this.moveCursorTo(fold.end.row, fold.end.column); } else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { if (this.selectionLead.row < this.doc.getLength() - 1) { @@ -348,7 +348,7 @@ var Selection = function(session) { this.session.tokenRe.lastIndex = 0; var fold; - if (fold = this.session.getFoldAt(row, column, 1)) { + if (fold = this.session.getFoldAt(row, column, -1)) { this.moveCursorTo(fold.end.row, fold.end.column); return; } else if (column == line.length) { @@ -372,7 +372,7 @@ var Selection = function(session) { var column = this.selectionLead.column; var fold; - if (fold = this.session.getFoldAt(row, column, -1)) { + if (fold = this.session.getFoldAt(row, column, 1)) { this.moveCursorTo(fold.start.row, fold.start.column); return; } From 0e0f3a1db35646802523bb4659155408f7d7afb7 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 12:24:27 +0200 Subject: [PATCH 21/53] Add unit test for inserting one-line text on folded lines --- lib/ace/edit_session.js | 76 ++++++++++++++++++++++++------------ lib/ace/edit_session_test.js | 53 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 25 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index b3297b13..b4d93ca0 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -861,7 +861,9 @@ var EditSession = function(text, mode) { var len; var action = e.data.action; var firstRow = e.data.range.start.row, - lastRow = e.data.range.end.row; + lastRow = e.data.range.end.row, + start = e.data.range.start, + end = e.data.range.end; if (action.indexOf("Lines") != -1) { if (action == "insertLines") { @@ -900,6 +902,15 @@ var EditSession = function(text, mode) { // TODO: Expand folds here if needed. // TODO: Split foldLine in case there are new lines added in // between of a foldLine. + + // If some new line is added inside of a foldLine, then split + // the fold line up. + var foldLine = this.getFoldLine(firstRow); + if (foldLine && foldLine.range.inside(start.row, start.column)) { + var foldLine = foldLine.split(start.row, start.column); + + } + var foldLines = this.$foldData; for (var i = 0; i < foldLines.length; i++) { var foldLine = foldLines[i]; @@ -909,8 +920,8 @@ var EditSession = function(text, mode) { } } } else { - len = Math.abs(e.data.range.start.column - e.data.range.end.column); var column; + len = Math.abs(e.data.range.start.column - e.data.range.end.column); if (action.indexOf("insert") != -1) { column = e.data.range.start.column; } else { @@ -919,29 +930,7 @@ var EditSession = function(text, mode) { } var foldLine = this.getFoldLine(firstRow); if (foldLine) { - // TODO: Adding new characters into a fold range should - // expand/remove the fold. - // for (var i = 0; i <= ) - var ret = foldLine.getNextFoldTo(firstRow, column); - if (ret) { - var fold = ret.fold; - if (ret.kind == "inside" - && fold.start.column != column - && fold.start.row != firstRow) - { - // TODO: Implement adding new characters inside of an - // fold. This should extend/remove the fold etc. - } else if (ret.fold.start.row == firstRow){ - var folds = foldLine.folds; - for (var i = folds.indexOf(ret.fold); i < folds.length; i++) { - folds[i].start.column += len; - if (!folds[i].sameRow) { - break; - } - folds[i].end.column += len; - } - } - } + foldLine.addRemoveChars(firstRow, column, len); } } @@ -1455,6 +1444,13 @@ var EditSession = function(text, mode) { this.folds.sort(function(a, b) { return -a.range.compareEnd(b.start.row, b.start.column); }); + if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) { + this.end.row = fold.end.row; + this.end.column = fold.end.column; + } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) { + this.start.row = fold.start.row; + this.start.column = fold.start.column; + } } else if (fold.start.row == this.end.row) { this.folds.push(fold); this.end.row = fold.end.row; @@ -1573,6 +1569,36 @@ var EditSession = function(text, mode) { return str; } } + + this.addRemoveChars = function(row, column, len) { + var ret = this.getNextFoldTo(row, column), + fold, folds; + if (ret) { + fold = ret.fold; + if (ret.kind == "inside" + && fold.start.column != column + && fold.start.row != row) + { + // TODO: Implement adding new characters inside of an + // fold. This should extend/remove the fold etc. + } else if (fold.start.row == row) { + folds = this.folds; + var i = folds.indexOf(fold); + if (i == 0) { + this.start.column += len; + } + for (i; i < folds.length; i++) { + fold = folds[i]; + fold.start.column += len; + if (!fold.sameRow) { + return; + } + fold.end.column += len; + } + this.end.column += len; + } + } + } }).call(FoldLine.prototype); this.getFoldAt = function(row, column, side) { diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index d7811f7d..cc36b6aa 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -449,6 +449,59 @@ module.exports = { // Test one position after the folds. Should be all like normal. assertScreen2Doc(3, 0, 2, 0); + }, + + "test fold one-line text insert": function() { + // These are mostly test for the FoldLine.addRemoveChars function. + var session = createFoldTestSession(), + foldLines = session.$foldData; + function insert(row, column, text) { + session.insert({row: row, column: column}, text); + } + + var foldLine, fold, folds; + // First line. + foldLine = session.$foldData[0]; + fold = foldLine.folds[0]; + + insert(0, 0, "F"); + assert.range(foldLine.range, 0, 14, 0, 19); + assert.range(fold.range, 0, 14, 0, 19); + insert(0, 14, "F"); + assert.range(foldLine.range, 0, 15, 0, 20); + assert.range(fold.range, 0, 15, 0, 20); + insert(0, 20, "F"); + assert.range(foldLine.range, 0, 15, 0, 20); + assert.range(fold.range, 0, 15, 0, 20); + + // Second line. + foldLine = session.$foldData[1]; + folds = foldLine.folds; + + insert(1, 0, "F"); + assert.range(foldLine.range, 1, 11, 2, 25); + assert.range(folds[0].range, 1, 11, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + insert(1, 11, "F"); + assert.range(foldLine.range, 1, 12, 2, 25); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + insert(2, 10, "F"); + assert.range(foldLine.range, 1, 12, 2, 26); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 21, 2, 26); + + insert(2, 21, "F"); + assert.range(foldLine.range, 1, 12, 2, 27); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 22, 2, 27); + + insert(2, 27, "F"); + assert.range(foldLine.range, 1, 12, 2, 27); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 22, 2, 27); } }; From e9f3ba7b56a7e0a7b6959f08e74c9cd38c20daff Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 13:29:23 +0200 Subject: [PATCH 22/53] Add support for multi line insertion + unit tests --- lib/ace/edit_session.js | 84 +++++++++++++++++++++++++++++------- lib/ace/edit_session_test.js | 39 +++++++++++++++++ lib/ace/range.js | 10 +++++ 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index b4d93ca0..b263d1d2 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -905,15 +905,32 @@ var EditSession = function(text, mode) { // If some new line is added inside of a foldLine, then split // the fold line up. + var foldLines = this.$foldData; var foldLine = this.getFoldLine(firstRow); - if (foldLine && foldLine.range.inside(start.row, start.column)) { - var foldLine = foldLine.split(start.row, start.column); + var idx = 0; + if (foldLine) { + var cmp = foldLine.range.compareInside(start.row, start.column) + // Inside of the foldLine range. Need to split stuff up. + if (cmp == 0) { + // TODO: Handle case where inseration is inside of fold! + foldLine = foldLine.split(start.row, start.column); + foldLine.shiftRow(len); + foldLine.addRemoveChars( + lastRow, 0, end.column - start.column); + this.$addFoldLine(foldLine); + } else + // Infront of the foldLine but same row. Need to shift column. + if (cmp == -1) { + foldLine.addRemoveChars(firstRow, 0, end.column - start.column); + foldLine.shiftRow(len); + } + // Nothing to do if the insert is after the foldLine. + idx = foldLines.indexOf(foldLine) + 1; } - var foldLines = this.$foldData; - for (var i = 0; i < foldLines.length; i++) { - var foldLine = foldLines[i]; + for (idx; idx < foldLines.length; idx++) { + var foldLine = foldLines[idx]; if (foldLine.start.row >= firstRow) { foldLine.shiftRow(len); } @@ -1418,11 +1435,24 @@ var EditSession = function(text, mode) { this.sameRow = range.start.row == range.end.row; } - function FoldLine(fold) { - this.folds = [fold]; - this.range = fold.range.clone(); - this.start = this.range.start; - this.end = this.range.end; + /** + * Creates a new FoldLine. If the an array is passed in, the folds are + * expected to be sorted already. + */ + function FoldLine(folds) { + if (Array.isArray(folds)) { + var last = folds[folds.length - 1] + this.folds = folds; + this.range = new Range(folds[0].start.row, folds[0].start.column, + last.end.row, last.end.column); + this.start = this.range.start; + this.end = this.range.end; + } else { + this.folds = [folds]; + this.range = folds.range.clone(); + this.start = this.range.start; + this.end = this.range.end; + } } (function() { @@ -1599,8 +1629,29 @@ var EditSession = function(text, mode) { } } } + + this.split = function(row, column) { + var fold = this.getNextFoldTo(row, column).fold, + folds = this.folds; + if (!fold) { + return null; + } + var i = folds.indexOf(fold); + var foldBefore = folds[i - 1]; + this.end.row = foldBefore.end.row; + this.end.column = foldBefore.end.column; + + // Remove the folds after row/column and create a new FoldLine + // containing these removed folds. + var folds = folds.splice(i, folds.length - i); + return new FoldLine(folds); + } }).call(FoldLine.prototype); + FoldLine.prototype.__defineGetter__("rangeDBG", function() { + return this.range + ""; + }) + this.getFoldAt = function(row, column, side) { var foldLine = this.getFoldLine(row); if (foldLine) { @@ -1663,6 +1714,13 @@ var EditSession = function(text, mode) { return null; } + this.$addFoldLine = function(foldLine) { + this.$foldData.push(foldLine); + this.$foldData.sort(function(a, b) { + return a.start.row - b.start.row; + }); + } + /** * Adds a new fold. */ @@ -1707,11 +1765,7 @@ var EditSession = function(text, mode) { } if (!added) { - foldLine = new FoldLine(fold); - foldData.push(foldLine); - foldData.sort(function(a, b) { - return a.start.row - b.start.row; - }); + this.$addFoldLine(new FoldLine(fold)); } // TODO: Recalculate wrapData diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index cc36b6aa..d065a36b 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -502,6 +502,45 @@ module.exports = { assert.range(foldLine.range, 1, 12, 2, 27); assert.range(folds[0].range, 1, 12, 2, 10); assert.range(folds[1].range, 2, 22, 2, 27); + }, + + "test fold multi-line insert": function() { + var session = createFoldTestSession(), + foldLines = session.$foldData; + function insert(row, column, text) { + session.insert({row: row, column: column}, text); + } + + var foldLines = session.$foldData, foldLine, fold, folds; + + insert(0, 0, "\nfoo"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 2, 10, 3, 25); + + insert(2, 0, "\nbar"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 3, 13, 4, 25); + + insert(3, 10, "\nfoo"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 25); + + insert(5, 10, "\nbar"); + assert.equal(foldLines.length, 3); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 10); + assert.range(foldLines[2].range, 6, 13, 6, 18); + + insert(6, 18, "\nfoo"); + assert.equal(foldLines.length, 3); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 10); + assert.range(foldLines[2].range, 6, 13, 6, 18); + + // TODO: Add test for inseration inside of folds. } }; diff --git a/lib/ace/range.js b/lib/ace/range.js index 4c51067f..b1987534 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -145,6 +145,16 @@ var Range = function(startRow, startColumn, endRow, endColumn) { } } + this.compareInside = function(row, column) { + if (this.end.row == row && this.end.column == column) { + return 1; + } else if (this.start.row == row && this.start.column == column) { + return -1; + } else { + return this.compare(row, column); + } + } + this.clipRows = function(firstRow, lastRow) { if (this.end.row > lastRow) { var end = { From 9ff0df758a4e6aef81f84e4bacc1394af4fb9af2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 13:42:58 +0200 Subject: [PATCH 23/53] Refactor FoldLine.getStringAt out of fold line as the FoldLine should not be session depending. --- lib/ace/edit_session.js | 68 +++++++++++++++++++---------------------- lib/ace/selection.js | 6 +--- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index b263d1d2..279519f8 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -883,6 +883,8 @@ var EditSession = function(text, mode) { // TODO: Update row data on folds. var foldLines = this.$foldData; + + for (var i = 0; i < foldLines.length; i++) { var foldLine = foldLines[i]; if (foldLine.start.row >= firstRow + len) { @@ -900,8 +902,6 @@ var EditSession = function(text, mode) { } // TODO: Expand folds here if needed. - // TODO: Split foldLine in case there are new lines added in - // between of a foldLine. // If some new line is added inside of a foldLine, then split // the fold line up. @@ -1569,37 +1569,6 @@ var EditSession = function(text, mode) { return null; } - this.getStringAt = function(session, row, column, trim) { - var fold, lastFold, cmp, str; - lastFold = { - end: { column: 0 } - }; - // TODO: Refactor to use getNextFoldTo function. - for (var i = 0; i < this.folds.length; i++) { - fold = this.folds[i]; - cmp = fold.range.compareEnd(row, column); - if (cmp == -1) { - str = session.getLine(fold.start.row). - substring(lastFold.end.column, fold.start.column); - break; - } else if (cmp == 0) { - return null; - } - lastFold = fold; - } - if (!str) { - str = session.getLine(fold.start.row). - substring(lastFold.end.column); - } - if (trim == -1) { - return str.substring(0, column - lastFold.end.column); - } else if (trim == 1) { - return str.substring(column - lastFold.end.column) - } else { - return str; - } - } - this.addRemoveChars = function(row, column, len) { var ret = this.getNextFoldTo(row, column), fold, folds; @@ -1691,12 +1660,39 @@ var EditSession = function(text, mode) { * foobarwol|rd -trim=+1> "rld" * fo|obarwolrd -trim=00> "foo" */ - this.getFoldStringAt = function(row, column, trim) { - var foldLine = this.getFoldLine(row); + this.getFoldStringAt = function(row, column, trim, foldLine) { + var foldLine = foldLine || this.getFoldLine(row); if (!foldLine) { return null; } else { - return foldLine.getStringAt(this, row, column, trim); + var fold, lastFold, cmp, str; + lastFold = { + end: { column: 0 } + }; + // TODO: Refactor to use getNextFoldTo function. + for (var i = 0; i < foldLine.folds.length; i++) { + fold = foldLine.folds[i]; + cmp = fold.range.compareEnd(row, column); + if (cmp == -1) { + str = this.getLine(fold.start.row). + substring(lastFold.end.column, fold.start.column); + break; + } else if (cmp == 0) { + return null; + } + lastFold = fold; + } + if (!str) { + str = this.getLine(fold.start.row). + substring(lastFold.end.column); + } + if (trim == -1) { + return str.substring(0, column - lastFold.end.column); + } else if (trim == 1) { + return str.substring(column - lastFold.end.column) + } else { + return str; + } } } diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 70a5fafe..e711c514 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -382,11 +382,7 @@ var Selection = function(session) { return; } - var str = null; - var foldLine = this.session.getFoldLine(row); - if (foldLine) { - str = foldLine.getStringAt(this.session, row, column, -1); - } + var str = this.session.getFoldStringAt(row, column, -1); if (str == null) { str = this.doc.getLine(row).substring(0, column) } From ccfe285babfe89efe96f0d325e2ca0f1216964cf Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 14:29:02 +0200 Subject: [PATCH 24/53] Implement EditSession.removeFold. --- lib/ace/edit_session.js | 44 +++++++++++++++++++++++++++++++++++++++++ lib/ace/editor.js | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 279519f8..7c0d1c91 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1772,6 +1772,50 @@ var EditSession = function(text, mode) { this._dispatchEvent("changeFold"); }; + this.removeFold = function(fold, foldLine) { + if (foldLine.folds.indexOf(fold) == -1) { + throw "FoldLine doesn't contain fold."; + } + + var foldLines = this.$foldData, + folds = foldLine.folds; + // Simple case where there is only one fold in the FoldLine such that + // the entire fold line can get removed directly. + if (folds.length == 1) { + foldLines.splice(foldLines.indexOf(foldLine), 1); + } else + // If the fold is the last fold of the foldLine, just remove it. + if (foldLine.range.isEnd(fold.end.row, fold.end.column)) { + folds.pop(); + foldLine.end = folds[folds.length - 1].end; + } else + // If the fold is the first fold of the foldLine, just remove it. + if (foldLine.range.isStart(fold.start.row, fold.start.column)) { + folds.shift(); + foldLine.start = folds[0].start; + } else + // We know there are more then 2 folds and the fold is not at the edge. + // This means, the fold is somewhere in between. + // + // If the fold is in one row, we just can remove it. + if (fold.sameRow) { + folds.splice(folds.indexOf(fold), 1); + } else + // The fold goes over more then one row. This means remvoing this fold + // will cause the fold line to get splitted up. + { + var newFoldLine = foldLine.split(fold.start.row, fold.start.column); + newFoldLine.folds.shift(); + newFoldLine.start = newFoldLine.folds[0].start; + this.$addFoldLine(newFoldLine); + } + + // TODO: Update wrapData. + + // Notify that fold data has changed. + this._dispatchEvent("changeFold"); + } + /** * Checks if a given documentRow is folded. This is true if there are some * folded parts such that some parts of the line is still visible. diff --git a/lib/ace/editor.js b/lib/ace/editor.js index d8a57998..5fc99c51 100644 --- a/lib/ace/editor.js +++ b/lib/ace/editor.js @@ -384,7 +384,7 @@ var Editor =function(renderer, session) { this.onChangeFold = function() { // TODO: This might be too much updating. Okay for now. - this.render.updateFull(); + this.renderer.updateFull(); }; this.getCopyText = function() { From ae20be743f254eec5e4d8ed2420466e8ee96c334 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 15:50:33 +0200 Subject: [PATCH 25/53] Add EditSession.getFoldsInRange function + unit tests --- lib/ace/edit_session.js | 58 ++++++++++++++++++++++++++++++------ lib/ace/edit_session_test.js | 23 ++++++++++++++ 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 7c0d1c91..abf9d187 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1427,6 +1427,7 @@ var EditSession = function(text, mode) { * Simple fold-data struct. **/ function Fold(range, placeholder) { + this.foldLine = null; this.placeholder = placeholder; this.range = range; this.start = range.start; @@ -1441,18 +1442,20 @@ var EditSession = function(text, mode) { */ function FoldLine(folds) { if (Array.isArray(folds)) { - var last = folds[folds.length - 1] this.folds = folds; - this.range = new Range(folds[0].start.row, folds[0].start.column, - last.end.row, last.end.column); - this.start = this.range.start; - this.end = this.range.end; } else { - this.folds = [folds]; - this.range = folds.range.clone(); - this.start = this.range.start; - this.end = this.range.end; + folds = this.folds = [ folds ]; } + + var last = folds[folds.length - 1] + this.range = new Range(folds[0].start.row, folds[0].start.column, + last.end.row, last.end.column); + this.start = this.range.start; + this.end = this.range.end; + + this.folds.forEach(function(fold) { + fold.foldLine = this; + }, this); } (function() { @@ -1643,6 +1646,43 @@ var EditSession = function(text, mode) { } } + /** + * Returns all folds in the given range. Note, that this will return folds + * + */ + this.getFoldsInRange = function(range) { + var start = range.start, + end = range.end; + var foldLines = this.$foldData, + folds, + fold; + var cmp, + foundFolds = []; + for (var i = 0; i < foldLines.length; i++) { + cmp = foldLines[i].range.compare(start.row, start.column + 1); + if (cmp == 1) { + break; + } else if (cmp == -1) { + cmp = foldLines[i].range.compare(end.row, end.column - 1); + if (cmp == -1) { + break; + } + } + + folds = foldLines[i].folds; + for (var j = 0; j < folds.length; j++) { + fold = folds[j]; + cmp = fold.range.compare(end.row, end.column + 1); + if (cmp == -1) { + break; + } else { + foundFolds.push(fold); + } + } + } + return foundFolds; + } + /** * Returns the string between folds at the given position. * E.g. diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index d065a36b..ca12ab69 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -541,6 +541,29 @@ module.exports = { assert.range(foldLines[2].range, 6, 13, 6, 18); // TODO: Add test for inseration inside of folds. + }, + + "test getFoldsInRange()": function() { + var session = createFoldTestSession(), + foldLines = session.$foldData; + folds = foldLines[0].folds.concat(foldLines[1].folds); + + function test(startRow, startColumn, endColumn, endRow, folds) { + var r = new Range(startRow, startColumn, endColumn, endRow); + var retFolds = session.getFoldsInRange(r); + + assert.ok(retFolds.length == folds.length); + for (var i = 0; i < retFolds.length; i++) { + assert.equal(retFolds[i].range + "", folds[i].range + ""); + } + } + + test(0, 0, 0, 13, [ ]); + test(0, 0, 0, 14, [ folds[0] ]); + test(0, 0, 0, 18, [ folds[0] ]); + test(0, 0, 1, 10, [ folds[0] ]); + test(0, 0, 1, 11, [ folds[0], folds[1] ]); + test(0, 18, 1, 11, [ folds[1] ]); } }; From dac1b38064c8a284aae04e884be383626a71a601 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 21:27:33 +0200 Subject: [PATCH 26/53] Folds realign when inserting/deleting any kind of content from the document. Adds a ton of unit tests. Fixes bugs in EditSession.getFoldsInRange and add .toString() functions to make debugging way easier. --- lib/ace/edit_session.js | 149 +++++++++++++++----- lib/ace/edit_session_test.js | 265 +++++++++++++++++++++++------------ 2 files changed, 284 insertions(+), 130 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index abf9d187..16d4ee08 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -57,6 +57,13 @@ var EditSession = function(text, mode) { this.$markerId = 1; this.$wrapData = []; this.$foldData = []; + this.$foldData.toString = function() { + var str = ""; + this.forEach(function(foldLine) { + str += "\n" + foldLine.toString(); + }); + return str; + } if (text instanceof Document) { this.setDocument(text); @@ -140,14 +147,17 @@ var EditSession = function(text, mode) { if (undoManager) { var self = this; - this.$informUndoManager = lang.deferredCall(function() { + this.$syncInformUndoManager = function() { + self.$informUndoManager.cancel(); if (self.$deltas.length > 0) undoManager.execute({ action : "aceupdate", args : [self.$deltas, self] }); self.$deltas = []; - }); + } + this.$informUndoManager = + lang.deferredCall(this.$syncInformUndoManager); } }; @@ -865,6 +875,8 @@ var EditSession = function(text, mode) { start = e.data.range.start, end = e.data.range.end; + console.log("onChange", action, e.data.range + ""); + if (action.indexOf("Lines") != -1) { if (action == "insertLines") { lastRow = firstRow + (e.data.lines.length); @@ -879,15 +891,28 @@ var EditSession = function(text, mode) { if (len != 0) { if (action.indexOf("remove") != -1) { useWrapMode && this.$wrapData.splice(firstRow, len); - // TODO: Remove no longer needed folds here. - // TODO: Update row data on folds. var foldLines = this.$foldData; + var folds = this.getFoldsInRange(e.data.range); + this.removeFolds(folds); + var foldLine = this.getFoldLine(lastRow); + var idx = 0; + if (foldLine) { + foldLine.addRemoveChars(end.row, end.column, start.column - end.column); + foldLine.shiftRow(-len); - for (var i = 0; i < foldLines.length; i++) { - var foldLine = foldLines[i]; - if (foldLine.start.row >= firstRow + len) { + var foldLineBefore = this.getFoldLine(firstRow); + if (foldLineBefore && foldLineBefore !== foldLine) { + foldLineBefore.merge(foldLine); + foldLine = foldLineBefore; + } + idx = foldLines.indexOf(foldLine) + 1; + } + + for (idx; idx < foldLines.length; idx++) { + var foldLine = foldLines[idx]; + if (foldLine.start.row >= lastRow) { foldLine.shiftRow(-len); } } @@ -901,8 +926,6 @@ var EditSession = function(text, mode) { this.$wrapData.splice.apply(this.$wrapData, args); } - // TODO: Expand folds here if needed. - // If some new line is added inside of a foldLine, then split // the fold line up. var foldLines = this.$foldData; @@ -918,7 +941,6 @@ var EditSession = function(text, mode) { foldLine.shiftRow(len); foldLine.addRemoveChars( lastRow, 0, end.column - start.column); - this.$addFoldLine(foldLine); } else // Infront of the foldLine but same row. Need to shift column. if (cmp == -1) { @@ -937,17 +959,24 @@ var EditSession = function(text, mode) { } } } else { + // Realign folds. E.g. if you add some new chars before a fold, the + // fold should "move" to the right. var column; len = Math.abs(e.data.range.start.column - e.data.range.end.column); - if (action.indexOf("insert") != -1) { - column = e.data.range.start.column; - } else { - column = e.data.range.end.column; + if (action.indexOf("remove") != -1) { + // Get all the folds in the change range and remove them. + this.removeFolds(this.getFoldsInRange(e.data.range)); len = -len; } +// if (action.indexOf("insert") != -1) { +// column = start.column; +// } else { +// column = end.column; +// len = -len; +// } var foldLine = this.getFoldLine(firstRow); if (foldLine) { - foldLine.addRemoveChars(firstRow, column, len); + foldLine.addRemoveChars(firstRow, start.column, len); } } @@ -1436,11 +1465,16 @@ var EditSession = function(text, mode) { this.sameRow = range.start.row == range.end.row; } + Fold.prototype.toString = function() { + return '"' + this.placeholder + '" ' + this.range.toString(); + } + /** * Creates a new FoldLine. If the an array is passed in, the folds are * expected to be sorted already. */ - function FoldLine(folds) { + function FoldLine(foldData, folds) { + this.foldData = foldData; if (Array.isArray(folds)) { this.folds = folds; } else { @@ -1495,6 +1529,7 @@ var EditSession = function(text, mode) { } else { throw "Trying to add fold to FoldRow that doesn't have a matching row"; } + fold.foldLine = this; } this.getRowLength = function() { @@ -1553,7 +1588,7 @@ var EditSession = function(text, mode) { } this.getNextFoldTo = function(row, column) { - var fold; + var fold, cmp; for (var i = 0; i < this.folds.length; i++) { fold = this.folds[i]; cmp = fold.range.compareEnd(row, column); @@ -1605,6 +1640,8 @@ var EditSession = function(text, mode) { this.split = function(row, column) { var fold = this.getNextFoldTo(row, column).fold, folds = this.folds; + var foldData = this.foldData; + if (!fold) { return null; } @@ -1615,8 +1652,34 @@ var EditSession = function(text, mode) { // Remove the folds after row/column and create a new FoldLine // containing these removed folds. - var folds = folds.splice(i, folds.length - i); - return new FoldLine(folds); + folds = folds.splice(i, folds.length - i); + + + + var newFoldLine = new FoldLine(foldData, folds); + foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine); + return newFoldLine; + } + + this.merge = function(foldLineNext) { + var folds = foldLineNext.folds; + for (var i = 0; i < folds.length; i++) { + this.addFold(folds[i]); + } + // Remove the foldLineNext - no longer needed, as + // it's merged now with foldLineNext. + var foldData = this.foldData; + foldData.splice(foldData.indexOf(foldLineNext), 1); + } + + this.toString = function() { + var ret = [this.range.toString() + ": [" ]; + + this.folds.forEach(function(fold) { + ret.push(" " + fold.toString()); + }); + ret.push("]") + return ret.join("\n"); } }).call(FoldLine.prototype); @@ -1661,7 +1724,7 @@ var EditSession = function(text, mode) { for (var i = 0; i < foldLines.length; i++) { cmp = foldLines[i].range.compare(start.row, start.column + 1); if (cmp == 1) { - break; + continue; } else if (cmp == -1) { cmp = foldLines[i].range.compare(end.row, end.column - 1); if (cmp == -1) { @@ -1672,12 +1735,21 @@ var EditSession = function(text, mode) { folds = foldLines[i].folds; for (var j = 0; j < folds.length; j++) { fold = folds[j]; - cmp = fold.range.compare(end.row, end.column + 1); - if (cmp == -1) { + cmp = fold.range.compare(end.row, end.column - 1); + if (cmp == 1) { + cmp = fold.range.compare(start.row, start.column + 1); + if (cmp == 1) { + continue; + } + } else if (cmp == -1) { break; } else { - foundFolds.push(fold); + cmp = fold.range.compare(start.row, start.column + 1); + if (cmp == 1) { + continue; + } } + foundFolds.push(fold); } } return foundFolds; @@ -1784,13 +1856,7 @@ var EditSession = function(text, mode) { foldLineNext = foldData[i + 1]; if (foldLineNext && foldLineNext.start.row == endRow) { // We need to merge! - var nextFolds = foldLineNext.folds; - for (var i = 0; i < nextFolds.length; i++) { - foldLine.addFold(nextFolds[i]); - } - // Remove the foldLineNext - no longer needed, as - // it's merged now with foldLine. - foldData.splice(foldData.indexOf(foldLineNext), 1); + foldLine.merge(foldLineNext); break; } } @@ -1801,7 +1867,7 @@ var EditSession = function(text, mode) { } if (!added) { - this.$addFoldLine(new FoldLine(fold)); + this.$addFoldLine(new FoldLine(this.$foldData, fold)); } // TODO: Recalculate wrapData @@ -1812,10 +1878,8 @@ var EditSession = function(text, mode) { this._dispatchEvent("changeFold"); }; - this.removeFold = function(fold, foldLine) { - if (foldLine.folds.indexOf(fold) == -1) { - throw "FoldLine doesn't contain fold."; - } + this.removeFold = function(fold) { + var foldLine = fold.foldLine; var foldLines = this.$foldData, folds = foldLine.folds; @@ -1827,12 +1891,14 @@ var EditSession = function(text, mode) { // If the fold is the last fold of the foldLine, just remove it. if (foldLine.range.isEnd(fold.end.row, fold.end.column)) { folds.pop(); - foldLine.end = folds[folds.length - 1].end; + foldLine.end.row = folds[folds.length - 1].end.row; + foldLine.end.column = folds[folds.length - 1].end.column; } else // If the fold is the first fold of the foldLine, just remove it. if (foldLine.range.isStart(fold.start.row, fold.start.column)) { folds.shift(); - foldLine.start = folds[0].start; + foldLine.start.row = folds[0].start.row; + foldLine.start.column = folds[0].start.column; } else // We know there are more then 2 folds and the fold is not at the edge. // This means, the fold is somewhere in between. @@ -1846,7 +1912,8 @@ var EditSession = function(text, mode) { { var newFoldLine = foldLine.split(fold.start.row, fold.start.column); newFoldLine.folds.shift(); - newFoldLine.start = newFoldLine.folds[0].start; + foldLine.start.row = folds[0].start.row; + foldLine.start.column = folds[0].start.column; this.$addFoldLine(newFoldLine); } @@ -1856,6 +1923,12 @@ var EditSession = function(text, mode) { this._dispatchEvent("changeFold"); } + this.removeFolds = function(folds) { + folds.forEach(function(fold) { + this.removeFold(fold); + }, this); + } + /** * Checks if a given documentRow is folded. This is true if there are some * folded parts such that some parts of the line is still visible. diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index ca12ab69..0ff93650 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -59,6 +59,7 @@ function createFoldTestSession() { "}" ]; var session = new EditSession(lines.join("\n")); + session.setUndoManager(new UndoManager()); session.addFold(new Range(0, 13, 0, 18), "args..."); session.addFold(new Range(1, 10, 2, 10), "foo..."); session.addFold(new Range(2, 20, 2, 25), "bar..."); @@ -451,98 +452,6 @@ module.exports = { assertScreen2Doc(3, 0, 2, 0); }, - "test fold one-line text insert": function() { - // These are mostly test for the FoldLine.addRemoveChars function. - var session = createFoldTestSession(), - foldLines = session.$foldData; - function insert(row, column, text) { - session.insert({row: row, column: column}, text); - } - - var foldLine, fold, folds; - // First line. - foldLine = session.$foldData[0]; - fold = foldLine.folds[0]; - - insert(0, 0, "F"); - assert.range(foldLine.range, 0, 14, 0, 19); - assert.range(fold.range, 0, 14, 0, 19); - insert(0, 14, "F"); - assert.range(foldLine.range, 0, 15, 0, 20); - assert.range(fold.range, 0, 15, 0, 20); - insert(0, 20, "F"); - assert.range(foldLine.range, 0, 15, 0, 20); - assert.range(fold.range, 0, 15, 0, 20); - - // Second line. - foldLine = session.$foldData[1]; - folds = foldLine.folds; - - insert(1, 0, "F"); - assert.range(foldLine.range, 1, 11, 2, 25); - assert.range(folds[0].range, 1, 11, 2, 10); - assert.range(folds[1].range, 2, 20, 2, 25); - - insert(1, 11, "F"); - assert.range(foldLine.range, 1, 12, 2, 25); - assert.range(folds[0].range, 1, 12, 2, 10); - assert.range(folds[1].range, 2, 20, 2, 25); - - insert(2, 10, "F"); - assert.range(foldLine.range, 1, 12, 2, 26); - assert.range(folds[0].range, 1, 12, 2, 10); - assert.range(folds[1].range, 2, 21, 2, 26); - - insert(2, 21, "F"); - assert.range(foldLine.range, 1, 12, 2, 27); - assert.range(folds[0].range, 1, 12, 2, 10); - assert.range(folds[1].range, 2, 22, 2, 27); - - insert(2, 27, "F"); - assert.range(foldLine.range, 1, 12, 2, 27); - assert.range(folds[0].range, 1, 12, 2, 10); - assert.range(folds[1].range, 2, 22, 2, 27); - }, - - "test fold multi-line insert": function() { - var session = createFoldTestSession(), - foldLines = session.$foldData; - function insert(row, column, text) { - session.insert({row: row, column: column}, text); - } - - var foldLines = session.$foldData, foldLine, fold, folds; - - insert(0, 0, "\nfoo"); - assert.equal(foldLines.length, 2); - assert.range(foldLines[0].range, 1, 16, 1, 21); - assert.range(foldLines[1].range, 2, 10, 3, 25); - - insert(2, 0, "\nbar"); - assert.equal(foldLines.length, 2); - assert.range(foldLines[0].range, 1, 16, 1, 21); - assert.range(foldLines[1].range, 3, 13, 4, 25); - - insert(3, 10, "\nfoo"); - assert.equal(foldLines.length, 2); - assert.range(foldLines[0].range, 1, 16, 1, 21); - assert.range(foldLines[1].range, 4, 6, 5, 25); - - insert(5, 10, "\nbar"); - assert.equal(foldLines.length, 3); - assert.range(foldLines[0].range, 1, 16, 1, 21); - assert.range(foldLines[1].range, 4, 6, 5, 10); - assert.range(foldLines[2].range, 6, 13, 6, 18); - - insert(6, 18, "\nfoo"); - assert.equal(foldLines.length, 3); - assert.range(foldLines[0].range, 1, 16, 1, 21); - assert.range(foldLines[1].range, 4, 6, 5, 10); - assert.range(foldLines[2].range, 6, 13, 6, 18); - - // TODO: Add test for inseration inside of folds. - }, - "test getFoldsInRange()": function() { var session = createFoldTestSession(), foldLines = session.$foldData; @@ -564,6 +473,178 @@ module.exports = { test(0, 0, 1, 10, [ folds[0] ]); test(0, 0, 1, 11, [ folds[0], folds[1] ]); test(0, 18, 1, 11, [ folds[1] ]); + test(2, 0, 2, 13, [ folds[1] ]); + test(2, 10, 2, 20, [ ]); + test(2, 10, 2, 11, [ ]); + }, + + "test fold one-line text insert": function() { + // These are mostly test for the FoldLine.addRemoveChars function. + var session = createFoldTestSession(), + undoManager = session.getUndoManager(), + foldLines = session.$foldData; + function insert(row, column, text) { + session.insert({row: row, column: column}, text); + + // Force the session to store all changes made to the document NOW + // on the undoManager's queue. Otherwise we can't undo in separate + // steps later. + session.$syncInformUndoManager(); + } + + var foldLine, fold, folds; + // First line. + foldLine = session.$foldData[0]; + fold = foldLine.folds[0]; + + insert(0, 0, "0"); + assert.range(foldLine.range, 0, 14, 0, 19); + assert.range(fold.range, 0, 14, 0, 19); + insert(0, 14, "1"); + assert.range(foldLine.range, 0, 15, 0, 20); + assert.range(fold.range, 0, 15, 0, 20); + insert(0, 20, "2"); + assert.range(foldLine.range, 0, 15, 0, 20); + assert.range(fold.range, 0, 15, 0, 20); + + // Second line. + foldLine = session.$foldData[1]; + folds = foldLine.folds; + + insert(1, 0, "3"); + assert.range(foldLine.range, 1, 11, 2, 25); + assert.range(folds[0].range, 1, 11, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + insert(1, 11, "4"); + assert.range(foldLine.range, 1, 12, 2, 25); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + insert(2, 10, "5"); + assert.range(foldLine.range, 1, 12, 2, 26); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 21, 2, 26); + + insert(2, 21, "6"); + assert.range(foldLine.range, 1, 12, 2, 27); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 22, 2, 27); + + insert(2, 27, "7"); + assert.range(foldLine.range, 1, 12, 2, 27); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 22, 2, 27); + + // UNDO = REMOVE + undoManager.undo(); // 6 + assert.range(foldLine.range, 1, 12, 2, 27); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 22, 2, 27); + + undoManager.undo(); // 5 + assert.range(foldLine.range, 1, 12, 2, 26); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 21, 2, 26); + + undoManager.undo(); // 4 + assert.range(foldLine.range, 1, 12, 2, 25); + assert.range(folds[0].range, 1, 12, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + undoManager.undo(); // 3 + assert.range(foldLine.range, 1, 11, 2, 25); + assert.range(folds[0].range, 1, 11, 2, 10); + assert.range(folds[1].range, 2, 20, 2, 25); + + undoManager.undo(); // Beginning first line. + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 0, 15, 0, 20); + assert.range(foldLines[1].range, 1, 10, 2, 25); + + foldLine = session.$foldData[0]; + fold = foldLine.folds[0]; + + undoManager.undo(); // 2 + assert.range(foldLine.range, 0, 15, 0, 20); + assert.range(fold.range, 0, 15, 0, 20); + + undoManager.undo(); // 1 + assert.range(foldLine.range, 0, 14, 0, 19); + assert.range(fold.range, 0, 14, 0, 19); + + undoManager.undo(); // 0 + assert.range(foldLine.range, 0, 13, 0, 18); + assert.range(fold.range, 0, 13, 0, 18); + }, + + "test fold multi-line insert/remove": function() { + var session = createFoldTestSession(), + undoManager = session.getUndoManager(), + foldLines = session.$foldData; + function insert(row, column, text) { + session.insert({row: row, column: column}, text); + // Force the session to store all changes made to the document NOW + // on the undoManager's queue. Otherwise we can't undo in separate + // steps later. + session.$syncInformUndoManager(); + } + + var foldLines = session.$foldData, foldLine, fold, folds; + + insert(0, 0, "\nfo0"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 2, 10, 3, 25); + + insert(2, 0, "\nba1"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 3, 13, 4, 25); + + insert(3, 10, "\nfo2"); + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 25); + + insert(5, 10, "\nba3"); + assert.equal(foldLines.length, 3); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 10); + assert.range(foldLines[2].range, 6, 13, 6, 18); + + insert(6, 18, "\nfo4"); + assert.equal(foldLines.length, 3); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 10); + assert.range(foldLines[2].range, 6, 13, 6, 18); + + undoManager.undo(); // 3 + assert.equal(foldLines.length, 3); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 10); + assert.range(foldLines[2].range, 6, 13, 6, 18); + + undoManager.undo(); // 2 + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 4, 6, 5, 25); + + undoManager.undo(); // 1 + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 3, 13, 4, 25); + + undoManager.undo(); // 0 + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 1, 16, 1, 21); + assert.range(foldLines[1].range, 2, 10, 3, 25); + + undoManager.undo(); // Beginning + assert.equal(foldLines.length, 2); + assert.range(foldLines[0].range, 0, 13, 0, 18); + assert.range(foldLines[1].range, 1, 10, 2, 25); + // TODO: Add test for inseration inside of folds. } }; From a1497c097dd969cbb1c5130279870f59c9abffc1 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 22:06:54 +0200 Subject: [PATCH 27/53] Add Range.compareRange function used by EditSession.getFoldsInRange. --- lib/ace/edit_session.js | 41 ++++++++++++++++--------------- lib/ace/edit_session_test.js | 1 + lib/ace/range.js | 47 ++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 16d4ee08..8360b2ca 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1714,6 +1714,7 @@ var EditSession = function(text, mode) { * */ this.getFoldsInRange = function(range) { + range = range.clone(); var start = range.start, end = range.end; var foldLines = this.$foldData, @@ -1721,33 +1722,35 @@ var EditSession = function(text, mode) { fold; var cmp, foundFolds = []; + + start.column += 1; + end.column -= 1; + for (var i = 0; i < foldLines.length; i++) { - cmp = foldLines[i].range.compare(start.row, start.column + 1); - if (cmp == 1) { + cmp = foldLines[i].range.compareRange(range); + // Range is before foldLine. No intersection. This means, + // there might be other foldLines that intersect. + if (cmp == 2) { continue; - } else if (cmp == -1) { - cmp = foldLines[i].range.compare(end.row, end.column - 1); - if (cmp == -1) { - break; - } + } else + // Range is after foldLine. There can't be any other foldLines then, + // so let's give up. + if (cmp == -2) { + break; } folds = foldLines[i].folds; for (var j = 0; j < folds.length; j++) { fold = folds[j]; - cmp = fold.range.compare(end.row, end.column - 1); - if (cmp == 1) { - cmp = fold.range.compare(start.row, start.column + 1); - if (cmp == 1) { - continue; - } - } else if (cmp == -1) { + cmp = fold.range.compareRange(range); + if (cmp == -2) { + break; + } else if (cmp == 2) { + continue; + } else + // WTF-state: Can happen due to -1/+1 to start/end column. + if (cmp == 42) { break; - } else { - cmp = fold.range.compare(start.row, start.column + 1); - if (cmp == 1) { - continue; - } } foundFolds.push(fold); } diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 0ff93650..a22855e4 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -476,6 +476,7 @@ module.exports = { test(2, 0, 2, 13, [ folds[1] ]); test(2, 10, 2, 20, [ ]); test(2, 10, 2, 11, [ ]); + test(2, 19, 2, 20, [ ]); }, "test fold one-line text insert": function() { diff --git a/lib/ace/range.js b/lib/ace/range.js index b1987534..8f22285d 100644 --- a/lib/ace/range.js +++ b/lib/ace/range.js @@ -60,6 +60,53 @@ var Range = function(startRow, startColumn, endRow, endColumn) { return this.compare(row, column) == 0; }; + /** + * Compares this range (A) with another range (B), where B is the passed in + * range. + * + * Return values: + * -2: (B) is infront of (A) and doesn't intersect with (A) + * -1: (B) begins before (A) but ends inside of (A) + * 0: (B) is completly inside of (A) OR (A) is complety inside of (B) + * +1: (B) begins inside of (A) but ends outside of (A) + * +2: (B) is after (A) and doesn't intersect with (A) + * + * 42: FTW state: (B) ends in (A) but starts outside of (A) + */ + this.compareRange = function(range) { + var cmp, + end = range.end, + start = range.start; + + cmp = this.compare(end.row, end.column); + if (cmp == 1) { + cmp = this.compare(start.row, start.column); + if (cmp == 1) { + return 2; + } else if (cmp == 0) { + return 1; + } else { + return 0; + } + } else if (cmp == -1) { + return -2; + } else { + cmp = this.compare(start.row, start.column); + if (cmp == -1) { + return -1; + } else if (cmp == 1) { + return 42; + } else { + return 0; + } + } + } + + this.containsRange = function(range) { + var cmp = this.compareRange(range); + return (cmp == -1 || cmp == 0 || cmp == 1); + } + this.isEnd = function(row, column) { return this.end.row == row && this.end.column == column; } From dd121334a6eaee98d553c6da85636bab702922d2 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 22:48:06 +0200 Subject: [PATCH 28/53] Add basic commands to fold/unfold sections using key shortcuts. --- demo/demo.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/demo/demo.js b/demo/demo.js index 6fa0acd8..7103d3e7 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -463,6 +463,36 @@ exports.launch = function(env) { alert("Fake Print File"); } }); + + canon.addCommand({ + name: "fold", + bindKey: { + win: "Alt-L", + mac: "Alt-L", + sender: "editor" + }, + exec: function() { + var session = env.editor.session, + range = env.editor.selection.getRange(), + placeHolder = session.getTextRange(range).substring(0, 3) + "..."; + + session.addFold(range, placeHolder); + } + }); + + canon.addCommand({ + name: "undfold", + bindKey: { + win: "Alt-Shift-L", + mac: "Alt-Shift-L", + sender: "editor" + }, + exec: function() { + var session = env.editor.session, + range = env.editor.selection.getRange(); + session.removeFolds(session.getFoldsInRange(range)); + } + }); }; }); From 2be238660304825d2b496bdb89fd4f1352deeec6 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 22:54:32 +0200 Subject: [PATCH 29/53] Fix EditSession.removeFolds: Using the folds of a foldLine might cause unexpected behavior. Clone the passed in array. --- lib/ace/edit_session.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 8360b2ca..addbe66a 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -968,12 +968,6 @@ var EditSession = function(text, mode) { this.removeFolds(this.getFoldsInRange(e.data.range)); len = -len; } -// if (action.indexOf("insert") != -1) { -// column = start.column; -// } else { -// column = end.column; -// len = -len; -// } var foldLine = this.getFoldLine(firstRow); if (foldLine) { foldLine.addRemoveChars(firstRow, start.column, len); @@ -1927,7 +1921,15 @@ var EditSession = function(text, mode) { } this.removeFolds = function(folds) { - folds.forEach(function(fold) { + // We need to clone the folds array passed in as it might be the folds + // array of a fold line and as we call this.removeFold(fold), folds + // are removed from folds and changes the current index. + var cloneFolds = []; + for (var i = 0; i < folds.length; i++) { + cloneFolds.push(folds[i]); + } + + cloneFolds.forEach(function(fold) { this.removeFold(fold); }, this); } From 155115035c902d0f5b273fa1672d4964b2c2437c Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Mon, 25 Apr 2011 23:25:08 +0200 Subject: [PATCH 30/53] Fix rendering bugs. Make SVG session default for testing. --- demo/demo.js | 2 ++ index.html | 3 ++- lib/ace/layer/text.js | 15 +++++---------- lib/ace/virtual_renderer.js | 5 ++++- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 7103d3e7..66bc9c6b 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -152,6 +152,8 @@ exports.launch = function(env) { docs.js.addFold(new Range(0, 13, 0, 18), "args..."); docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); + + docs.svg.addFold(new Range(1, 0, 7, 0), "fold..."); window.s = docs.js; window.e = env.editor; setTimeout(function() { diff --git a/index.html b/index.html index c79abe0f..a4dcc834 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,7 @@ diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index 60bc771a..a94a368c 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -231,11 +231,11 @@ var Text = function(parentEl) { var el = this.element; if (oldConfig.firstRow < config.firstRow) - for (var row=oldConfig.firstRow; row config.lastRow) - for (var row=config.lastRow+1; row<=oldConfig.lastRow; row++) + for (var row=config.lastRow+1; row<=oldConfig.lastRow; row = this.session.getRowFoldEnd(row) + 1) el.removeChild(el.lastChild); if (config.firstRow < oldConfig.firstRow) { @@ -402,13 +402,6 @@ var Text = function(parentEl) { } this.$renderLine = function(stringBuilder, row, tokens) { - // Nothing to do if the entire line is folded. - // TODO: Remove this, once the folding feature is done. Only for - // developing stuff at the moment. -// if (!this.session.isRowVisible(row)) { -// throw "Calling renderLine on folded line doesn't make sense?"; -// } - // Check if the line to render is folded or not. If not, things are // simple, otherwise, we need to fake some things... if (!this.session.isRowFolded(row)) { @@ -474,7 +467,9 @@ var Text = function(parentEl) { if (isNewRow) { tokens = this.tokenizer.getTokens(row, row)[0].tokens; } - addTokens(tokens, lastColumn, column); + if (tokens.length != 0) { + addTokens(tokens, lastColumn, column); + } } }.bind(this), foldLine.end.row, this.session.getLine(foldLine.end.row).length); diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 964e7b27..01b6ca1c 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -478,7 +478,7 @@ var VirtualRenderer = function(container, theme) { var firstRowScreen, firstRowHeight; var lineHeight = { lineHeight: this.lineHeight }; firstRow = session.screenToDocumentRow(firstRow, 0); - firstRowScreen = session.documentToScreenRow(firstRow); + firstRowScreen = session.documentToScreenRow(firstRow, 0); firstRowHeight = session.getRowHeight(lineHeight, firstRow); lastRow = Math.min(session.screenToDocumentRow(lastRow, 0), session.getLength() - 1); @@ -500,6 +500,9 @@ var VirtualRenderer = function(container, theme) { height : this.$size.scrollerHeight }; + // For debugging. + // console.log(JSON.stringify(layerConfig)); + this.$gutterLayer.element.style.marginTop = (-offset) + "px"; this.content.style.marginTop = (-offset) + "px"; this.content.style.width = longestLine + "px"; From af6dcc2253ae04323917ab41247b9a7335f6120e Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 26 Apr 2011 00:07:18 +0200 Subject: [PATCH 31/53] Fix a lot of screen dimension calculation functions. --- lib/ace/edit_session.js | 111 +++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 48 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index addbe66a..0ac4be4e 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -471,11 +471,25 @@ var EditSession = function(text, mode) { var longestScreenLine = 0; for ( var i = 0; i < lines.length; i++) { - var line = lines[i], - len = line.length, - screenLen = this.$getStringScreenWidth(line)[0]; + var foldLine = this.getFoldLine(i), + line, len; + + line = lines[i]; + if (foldLine) { + var end = foldLine.range.end; + line = this.getFoldDisplayLine(foldLine, end.row, line.length); + // Continue after the foldLine.end.row. All the lines in + // between are folded. + i = end.row; + } + len = line.length; longestLine = Math.max(longestLine, len); - longestScreenLine = Math.max(longestScreenLine, screenLen); + if (!this.$useWrapMode) { + longestScreenLine = Math.max( + longestScreenLine, + this.$getStringScreenWidth(line)[0] + ); + } } this.width = longestLine; @@ -875,7 +889,7 @@ var EditSession = function(text, mode) { start = e.data.range.start, end = e.data.range.end; - console.log("onChange", action, e.data.range + ""); +// console.log("onChange", action, e.data.range + ""); if (action.indexOf("Lines") != -1) { if (action == "insertLines") { @@ -1147,34 +1161,16 @@ var EditSession = function(text, mode) { return this.getRowLength(row) * config.lineHeight; } - this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { - if (!this.$useWrapMode) { - return this.$getStringScreenWidth(this.getLine(screenRow))[0]; - } - - 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))[0]; - } else { - return end; - } + this.getScreenLastRowColumn = function(screenRow) { + // Note: This won't work if someone has more then + // 1.7976931348623158e+307 characters in one row. But I think we can + // live with this limitation ;) + return this.screenToDocumentColumn(screenRow, Number.MAX_VALUE / 10) }; this.getDocumentLastRowColumn = function(docRow, docColumn) { var screenRow = this.documentToScreenRow(docRow, docColumn); - return this.getScreenLastRowColumn(screenRow, true); + return this.getScreenLastRowColumn(screenRow); } this.getRowSplitData = function(row) { @@ -1201,7 +1197,7 @@ var EditSession = function(text, mode) { foldLineRowLength; var row = 0, - rowLength; + rowLength = 0; while (row <= screenRow) { foldLine = this.getFoldLine(docRow, lastFoldLine); @@ -1209,7 +1205,7 @@ var EditSession = function(text, mode) { lastFoldLine = foldLine; rowLength = foldLine.getRowLength(); } else { - rowLength = this.getRowLength(row); + rowLength = this.getRowLength(docRow); } if (row + rowLength - 1 >= screenRow) { break; @@ -1294,6 +1290,17 @@ var EditSession = function(text, mode) { }; this.doc2Screen = function(docRow, docColumn) { + var wrapData; + if (this.$useWrapMode) { + wrapData = this.$wrapData; + if (docRow > wrapData.length - 1) { + return [ + this.getScreenLength(), + wrapData.length == 0 ? 0 : (wrapData[wrapData.length - 1].length - 1) + ]; + } + } + var screenRow = 0, screenColumn = 0, foldStartRow = null, @@ -1331,30 +1338,19 @@ var EditSession = function(text, mode) { textLine = this.getLine(docRow).substring(0, docColumn); foldStartRow = docRow; } else { - // Build the textline using the FoldLine walker. - var line; - foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { - if (placeholder) { - textLine += placeholder; - } else { - if (isNewRow) { - line = this.getLine(row); - } - textLine += line.substring(lastColumn, column); - } - }.bind(this), docRow, docColumn) + textLine = this.getFoldDisplayLine(foldLine, docRow, docColumn); } // Clamp textLine if in wrapMode. if (this.$useWrapMode) { - var wrapData = this.$wrapData[foldStartRow]; + var wrapRow = wrapData[foldStartRow]; var screenRowOffset = 0; - while (docColumn >= wrapData[screenRowOffset]) { + while (docColumn >= wrapRow[screenRowOffset]) { screenRow ++; screenRowOffset++; } textLine = textLine.substring( - wrapData[screenRowOffset - 1] || 0, textLine.length); + wrapRow[screenRowOffset - 1] || 0, textLine.length); } return [screenRow, this.$getStringScreenWidth(textLine)[0], textLine]; @@ -1868,10 +1864,9 @@ var EditSession = function(text, mode) { } // TODO: Recalculate wrapData - // TODO: Recalculate width etc. - // TODO: Mark as dirty etc. // Notify that fold data has changed. + this.$modified = true; this._dispatchEvent("changeFold"); }; @@ -1917,6 +1912,7 @@ var EditSession = function(text, mode) { // TODO: Update wrapData. // Notify that fold data has changed. + this.$modified = true; this._dispatchEvent("changeFold"); } @@ -1932,6 +1928,7 @@ var EditSession = function(text, mode) { cloneFolds.forEach(function(fold) { this.removeFold(fold); }, this); + this.$modified = true; } /** @@ -1949,6 +1946,24 @@ var EditSession = function(text, mode) { : docRow) }; + this.getFoldDisplayLine = function(foldLine, endRow, endColumn) { + // Build the textline using the FoldLine walker. + var line = "", + textLine = ""; + + foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + if (placeholder) { + textLine += placeholder; + } else { + if (isNewRow) { + line = this.getLine(row); + } + textLine += line.substring(lastColumn, column); + } + }.bind(this), endRow, endColumn); + return textLine; + } + }).call(EditSession.prototype); exports.EditSession = EditSession; From b6772e8ef2034635c8f5f10cb91143d3e665f1fa Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 26 Apr 2011 00:12:23 +0200 Subject: [PATCH 32/53] Add documentation for EditSesion.getFoldAt and change meaning of side. --- lib/ace/edit_session.js | 15 ++++++++++----- lib/ace/selection.js | 8 ++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 0ac4be4e..203cb330 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1310,7 +1310,7 @@ var EditSession = function(text, mode) { foldLine = null, lastFoldLine = null; // Clamp the docRow position in case it's inside of a folded block. - fold = this.getFoldAt(docRow, docColumn, -1); + fold = this.getFoldAt(docRow, docColumn, 1); if (fold) { docRow = fold.start.row; docColumn = fold.start.column; @@ -1677,6 +1677,11 @@ var EditSession = function(text, mode) { return this.range + ""; }) + /** + * Looks up a fold at a given row/column. Possible values for side: + * -1: ignore a fold if fold.start = row/column + * +1: ignore a fold if fold.end = row/column + */ this.getFoldAt = function(row, column, side) { var foldLine = this.getFoldLine(row); if (foldLine) { @@ -1686,10 +1691,10 @@ var EditSession = function(text, mode) { for (var i = 0; i < folds.length; i++) { fold = folds[i]; if (fold.range.contains(row, column)) { - if (side == -1 && fold.range.isEnd(row, column)) { - return null; - } else if (side == 1 && fold.range.isStart(row, column)) { - return null + if (side == 1 && fold.range.isEnd(row, column)) { + continue; + } else if (side == -1 && fold.range.isStart(row, column)) { + continue; } return fold; } diff --git a/lib/ace/selection.js b/lib/ace/selection.js index e711c514..a39fdeb0 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -265,7 +265,7 @@ var Selection = function(session) { var cursor = this.selectionLead.getPosition(), fold; - if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { + if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { this.moveCursorTo(fold.start.row, fold.start.column); } else if (cursor.column == 0) { // cursor is a line (start @@ -285,7 +285,7 @@ var Selection = function(session) { this.moveCursorRight = function() { var cursor = this.selectionLead.getPosition(), fold; - if (fold = this.session.getFoldAt(cursor.row, cursor.column, -1)) { + if (fold = this.session.getFoldAt(cursor.row, cursor.column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); } else if (this.selectionLead.column == this.doc.getLine(this.selectionLead.row).length) { if (this.selectionLead.row < this.doc.getLength() - 1) { @@ -348,7 +348,7 @@ var Selection = function(session) { this.session.tokenRe.lastIndex = 0; var fold; - if (fold = this.session.getFoldAt(row, column, -1)) { + if (fold = this.session.getFoldAt(row, column, 1)) { this.moveCursorTo(fold.end.row, fold.end.column); return; } else if (column == line.length) { @@ -372,7 +372,7 @@ var Selection = function(session) { var column = this.selectionLead.column; var fold; - if (fold = this.session.getFoldAt(row, column, 1)) { + if (fold = this.session.getFoldAt(row, column, -1)) { this.moveCursorTo(fold.start.row, fold.start.column); return; } From 47207a6b9fe0313fa65cff6c3088e0b0c1fd721b Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 26 Apr 2011 00:15:11 +0200 Subject: [PATCH 33/53] Reenable unit test again. --- lib/ace/test/all_browser.js | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 99e4267f..cc0b672a 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -10,27 +10,27 @@ var failed = 0 var log = document.getElementById("log") var tests = [ - // require("ace/editor_change_document_test"), - // require("ace/editor_navigation_test"), - // require("ace/editor_highlight_selected_word_test"), - // require("ace/editor_text_edit_test"), - // require("ace/document_test"), - require("ace/edit_session_test") //, - // require("ace/test/event_emitter_test"), - // require("ace/range_test"), - // require("ace/search_test"), - // require("ace/selection_test"), - // require("ace/virtual_renderer_test"), - // require("ace/anchor_test"), - // require("ace/mode/css_test"), - // require("ace/mode/css_tokenizer_test"), - // require("ace/mode/html_test"), - // require("ace/mode/html_tokenizer_test"), - // require("ace/mode/javascript_test"), - // require("ace/mode/javascript_tokenizer_test"), - // require("ace/mode/text_test"), - // require("ace/mode/xml_test"), - // require("ace/mode/xml_tokenizer_test") + require("ace/editor_change_document_test"), + require("ace/editor_navigation_test"), + require("ace/editor_highlight_selected_word_test"), + require("ace/editor_text_edit_test"), + require("ace/document_test"), + require("ace/edit_session_test"), + require("ace/test/event_emitter_test"), + require("ace/range_test"), + require("ace/search_test"), + require("ace/selection_test"), + require("ace/virtual_renderer_test"), + require("ace/anchor_test"), + require("ace/mode/css_test"), + require("ace/mode/css_tokenizer_test"), + require("ace/mode/html_test"), + require("ace/mode/html_tokenizer_test"), + require("ace/mode/javascript_test"), + require("ace/mode/javascript_tokenizer_test"), + require("ace/mode/text_test"), + require("ace/mode/xml_test"), + require("ace/mode/xml_tokenizer_test") ] async.list(tests) From ff72467c108bc7fb91726730a35aa8dc59d177d9 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Wed, 27 Apr 2011 21:40:13 +0200 Subject: [PATCH 34/53] Remove FoldLine.getRowLength and .getRowSplits. Use already existing functions which simplifies the code. --- lib/ace/edit_session.js | 159 ++++++++++++++++++++++------------- lib/ace/edit_session_test.js | 3 +- lib/ace/layer/text.js | 2 +- lib/ace/test/all_browser.js | 40 ++++----- 4 files changed, 122 insertions(+), 82 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 203cb330..c922f115 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1002,29 +1002,31 @@ var EditSession = function(text, mode) { var tabSize = this.getTabSize(); var wrapData = this.$wrapData; var wrapLimit = this.$wrapLimit; + var tokens; for (var row = firstRow; row <= lastRow; row++) { + tokens = this.$getDisplayTokens(lang.stringTrimRight(lines[row])); wrapData[row] = - this.$computeWrapSplits(lines[row], wrapLimit, tabSize); + this.$computeWrapSplits(tokens, wrapLimit, tabSize); } }; // "Tokens" var CHAR = 1, CHAR_EXT = 2, - SPACE = 3, - TAB = 4, - TAB_SPACE = 5; + PLACEHOLDER_START = 3, + PLACEHOLDER_BODY = 4, + SPACE = 10, + TAB = 11, + TAB_SPACE = 12; - this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) { - textLine = lang.stringTrimRight(textLine); - if (textLine.length == 0) { + this.$computeWrapSplits = function(tokens, wrapLimit, tabSize) { + if (tokens.length == 0) { return []; } var tabSize = this.getTabSize(); var splits = []; - var tokens = this.$getDisplayTokens(textLine); var displayLength = tokens.length; var lastSplit = 0, lastDocSplit = 0; @@ -1035,11 +1037,11 @@ var EditSession = function(text, mode) { // and multipleWidth characters. var len = displayed.length; displayed.join(""). - // Get all the tabs spaces. - replace(/5/g, function(m) { + // Get all the TAB_SPACEs. + replace(/12/g, function(m) { len -= 1; }). - // Get all the multipleWidth characters. + // Get all the CHAR_EXT/multipleWidth characters. replace(/2/g, function(m) { len -= 1; }); @@ -1053,43 +1055,103 @@ var EditSession = function(text, mode) { // This is, where the split should be. var split = lastSplit + wrapLimit; - // If there is a space or tab at this split position. + // If there is a space or tab at this split position, then making + // a split is simple. 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. + continue; + } + + // === ELSE === + // Search for the first non space/tab token backwards. + 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); + continue; + } + + // === ELSE === + split = lastSplit + wrapLimit; + // No space or tab around? Well, force a split then. + // Check if the split position is inside of a placeholder. + // Placeholder are not splitable! + if (tokens[split] == PLACEHOLDER_START + || tokens[split] == PLACEHOLDER_BODY) + { + // Seek the start of the placeholder and do the split + // before the placeholder. By definition there always + // a PLACEHOLDER_START between split and lastSplit. for (split; split != lastSplit - 1; split--) { - if (tokens[split] >= SPACE) { - split++; + if (tokens[split] == PLACEHOLDER_START) { + // split++; << No incremental here as we want to + // have the position before the Placeholder. break; } } - // If we found one, then add the split. + + // If the PLACEHOLDER_START is not the index of the + // last split, then we can do the split if (split > lastSplit) { addSplit(split); + continue; } - // No space or tab around? Well, force a split then. - else { - addSplit(lastSplit + wrapLimit); + + // If the PLACEHOLDER_START IS the index of the last + // split, then we have to place the split after the + // placeholder. So, let's seek for the end of the placeholder. + split = lastSplit + wrapLimit; + for (split; split < tokens.length; split++) { + if (tokens[split] != PLACEHOLDER_START && + tokens[split] != PLACEHOLDER_BODY) + { + break; + } } + + // If spilt == tokens.length, then the placeholder is the last + // thing in the line and adding a new split doesn't make sense. + if (split == tokens.length) { + break; // Breaks the while-loop. + } + + // Finally, add the split... + addSplit(split); + continue; } + + // === ELSE === + // The split is inside of a CHAR or CHAR_EXT token. Forcing + // a split here is all right. + addSplit(lastSplit + wrapLimit); } return splits; } - this.$getDisplayTokens = function(str) { + /** + * @param + * offset: The offset in screenColumn at which position str starts. + * Important for calculating the realTabSize. + */ + this.$getDisplayTokens = function(str, offset) { var arr = []; var tabSize; + offset = offset || 0; for (var i = 0; i < str.length; i++) { var c = str.charCodeAt(i); // Tab if (c == 9) { - tabSize = this.getScreenTabSize(arr.length); + tabSize = this.getScreenTabSize(arr.length + offset); arr.push(TAB); for (var n = 1; n < tabSize; n++) { arr.push(TAB_SPACE); @@ -1193,34 +1255,25 @@ var EditSession = function(text, mode) { docRow = 0, docColumn = 0, column, foldLine, - lastFoldLine, foldLineRowLength; var row = 0, rowLength = 0; while (row <= screenRow) { - foldLine = this.getFoldLine(docRow, lastFoldLine); - if (foldLine) { - lastFoldLine = foldLine; - rowLength = foldLine.getRowLength(); - } else { - rowLength = this.getRowLength(docRow); - } + rowLength = this.getRowLength(docRow); if (row + rowLength - 1 >= screenRow) { break; } else { - docRow ++; row += rowLength; - if (foldLine) { - docRow = foldLine.end.row + 1; - } + docRow = this.getRowFoldEnd(docRow) + 1; } } var splits = null; + splits = this.$wrapData[docRow] || []; + foldLine = this.getFoldLine(docRow); if (foldLine) { - splits = foldLine.getSplitData(); docColumn = splits[screenRow - row] || 0; walkScreenColumn = 0; @@ -1249,7 +1302,6 @@ var EditSession = function(text, mode) { }.bind(this), foldLine.end.row, this.getLine(foldLine.end.row).length); } else { line = this.getLine(docRow); - splits = this.$wrapData[docRow]; if (this.$useWrapMode && splits) { docColumn = splits[screenRow - row - 1] || 0; line = line.substring(docColumn); @@ -1258,7 +1310,7 @@ var EditSession = function(text, mode) { docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; // Need to do some clamping action here. - if (splits) { + if (this.$useWrapMode) { column = this.$wrapData[docRow][screenRow - row] if (docColumn >= column) { // We remove one character at the end such that the docColumn @@ -1291,6 +1343,7 @@ var EditSession = function(text, mode) { this.doc2Screen = function(docRow, docColumn) { var wrapData; + // Special case in wrapMode if the doc is at the end of the document. if (this.$useWrapMode) { wrapData = this.$wrapData; if (docRow > wrapData.length - 1) { @@ -1307,7 +1360,7 @@ var EditSession = function(text, mode) { fold = null, folds, comp, - foldLine = null, lastFoldLine = null; + foldLine = null; // Clamp the docRow position in case it's inside of a folded block. fold = this.getFoldAt(docRow, docColumn, 1); @@ -1316,23 +1369,19 @@ var EditSession = function(text, mode) { docColumn = fold.start.column; } - for (var row = 0; row < docRow; row++) { - foldLine = this.getFoldLine(row, lastFoldLine); - if (foldLine) { - lastFoldLine = foldLine; - if (foldLine.end.row >= docRow) { - break; - } - row = foldLine.end.row; - screenRow += foldLine.getRowLength(); - } else { - screenRow += this.getRowLength(row); + var rowEnd, row = 0; + while (row < docRow) { + rowEnd = this.getRowFoldEnd(row); + if (rowEnd >= docRow) { + break; } + screenRow += this.getRowLength(row); + row = rowEnd + 1; } // Calculate the text line that is displayed in docRow on the screen. var textLine = ""; - foldLine = this.getFoldLine(docRow, lastFoldLine); + foldLine = this.getFoldLine(docRow); // Check if the final row we want to reach is inside of a fold. if (!foldLine) { textLine = this.getLine(docRow).substring(0, docColumn); @@ -1522,16 +1571,6 @@ var EditSession = function(text, mode) { fold.foldLine = this; } - this.getRowLength = function() { - // TODO: Add support for wrapped lines here. - return 1; - } - - this.getSplitData = function() { - // TODO: Add support for wrapped lines here. - return []; - } - this.containsRow = function(row) { return row >= this.start.row && row <= this.end.row; } diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index a22855e4..a7d37ef0 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -264,7 +264,8 @@ module.exports = { function computeAndAssert(line, assertEqual, wrapLimit, tabSize) { wrapLimit = wrapLimit || 12; tabSize = tabSize || 4; - var splits = EditSession.prototype.$computeWrapSplits(line, wrapLimit, tabSize); + var tokens = EditSession.prototype.$getDisplayTokens(line); + var splits = EditSession.prototype.$computeWrapSplits(tokens, wrapLimit, tabSize); // console.log("String:", line, "Result:", splits, "Expected:", assertEqual); assert.ok(splits.length == assertEqual.length); for (var i = 0; i < splits.length; i++) { diff --git a/lib/ace/layer/text.js b/lib/ace/layer/text.js index a94a368c..68f7fef5 100644 --- a/lib/ace/layer/text.js +++ b/lib/ace/layer/text.js @@ -474,7 +474,7 @@ var Text = function(parentEl) { }.bind(this), foldLine.end.row, this.session.getLine(foldLine.end.row).length); // TODO: Build a fake splits array! - var splits = foldLine.getSplitData(); + var splits = this.session.$wrapData[row]; this.$renderLineCore(stringBuilder, row, renderTokens, splits); }; diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index cc0b672a..819476a8 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -10,27 +10,27 @@ var failed = 0 var log = document.getElementById("log") var tests = [ - require("ace/editor_change_document_test"), - require("ace/editor_navigation_test"), - require("ace/editor_highlight_selected_word_test"), - require("ace/editor_text_edit_test"), - require("ace/document_test"), + // require("ace/editor_change_document_test"), + // require("ace/editor_navigation_test"), + // require("ace/editor_highlight_selected_word_test"), + // require("ace/editor_text_edit_test"), + // require("ace/document_test"), require("ace/edit_session_test"), - require("ace/test/event_emitter_test"), - require("ace/range_test"), - require("ace/search_test"), - require("ace/selection_test"), - require("ace/virtual_renderer_test"), - require("ace/anchor_test"), - require("ace/mode/css_test"), - require("ace/mode/css_tokenizer_test"), - require("ace/mode/html_test"), - require("ace/mode/html_tokenizer_test"), - require("ace/mode/javascript_test"), - require("ace/mode/javascript_tokenizer_test"), - require("ace/mode/text_test"), - require("ace/mode/xml_test"), - require("ace/mode/xml_tokenizer_test") + // require("ace/test/event_emitter_test"), + // require("ace/range_test"), + // require("ace/search_test"), + // require("ace/selection_test"), + // require("ace/virtual_renderer_test"), + // require("ace/anchor_test"), + // require("ace/mode/css_test"), + // require("ace/mode/css_tokenizer_test"), + // require("ace/mode/html_test"), + // require("ace/mode/html_tokenizer_test"), + // require("ace/mode/javascript_test"), + // require("ace/mode/javascript_tokenizer_test"), + // require("ace/mode/text_test"), + // require("ace/mode/xml_test"), + // require("ace/mode/xml_tokenizer_test") ] async.list(tests) From 46a877e5635ba85b2dcb5c83aa9b991b354aaa55 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 13:18:46 +0200 Subject: [PATCH 35/53] Checkpointing fold-wrap-data work. Mostly done --- lib/ace/edit_session.js | 180 ++++++++++++++++++++++------------- lib/ace/edit_session_test.js | 80 ++++++++++++++++ 2 files changed, 193 insertions(+), 67 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index c922f115..9bbb6957 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -477,7 +477,7 @@ var EditSession = function(text, mode) { line = lines[i]; if (foldLine) { var end = foldLine.range.end; - line = this.getFoldDisplayLine(foldLine, end.row, line.length); + line = this.getFoldDisplayLine(foldLine); // Continue after the foldLine.end.row. All the lines in // between are folded. i = end.row; @@ -1003,9 +1003,30 @@ var EditSession = function(text, mode) { var wrapData = this.$wrapData; var wrapLimit = this.$wrapLimit; var tokens; + var foldLine; for (var row = firstRow; row <= lastRow; row++) { - tokens = this.$getDisplayTokens(lang.stringTrimRight(lines[row])); + foldLine = this.getFoldLine(row); + if (!foldLine) { + tokens = this.$getDisplayTokens(lang.stringTrimRight(lines[row])); + } else { + tokens = []; + foldLine.walk(function(placeholder, row, column, lastColumn) { + var walkTokens; + if (placeholder) { + walkTokens = this.$getDisplayTokens(placeholder, tokens.length); + walkTokens[0] = PLACEHOLDER_START; + for (var i = 1; i < walkTokens.length; i++) { + walkTokens[i] = PLACEHOLDER_BODY; + } + } else { + walkTokens = this.$getDisplayTokens( + lines[row].substring(lastColumn, column), + tokens.length); + } + tokens = tokens.concat(walkTokens); + }.bind(this), row, lines[row].length); + } wrapData[row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize); } @@ -1251,14 +1272,16 @@ var EditSession = function(text, mode) { }; this.screen2Doc = function(screenRow, screenColumn) { - var line, - docRow = 0, - docColumn = 0, column, - foldLine, - foldLineRowLength; - - var row = 0, - rowLength = 0; + var line; + var docRow = 0; + var docColumn = 0; + var column; + var foldLine; + var foldLineRowLength; + var row = 0; + var rowLength = 0; + var splits = null; + var split = 0; while (row <= screenRow) { rowLength = this.getRowLength(docRow); @@ -1270,58 +1293,37 @@ var EditSession = function(text, mode) { } } - var splits = null; splits = this.$wrapData[docRow] || []; foldLine = this.getFoldLine(docRow); - if (foldLine) { - docColumn = splits[screenRow - row] || 0; - walkScreenColumn = 0; + line = foldLine + ? this.getFoldDisplayLine(foldLine) + : this.getLine(docRow); - foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { - var data, str; - if (placeholder) { - data = this.$getStringScreenWidth(placeholder, null, walkScreenColumn); - if (data[0] > screenColumn || data[1] == 0) { - return true; // Stop walk. - } - docColumn += data[1]; - } else { - docColumn = lastColumn; - if (isNewRow) { - docRow = row; - line = this.getLine(row); - } - str = line.substring(lastColumn, column); - data = this.$getStringScreenWidth(str, screenColumn, walkScreenColumn); - docColumn += data[1]; - if (data[0] == screenColumn) { - return true; // Stop walk. - } - } - walkScreenColumn = data[0]; - }.bind(this), foldLine.end.row, this.getLine(foldLine.end.row).length); - } else { - line = this.getLine(docRow); - if (this.$useWrapMode && splits) { - docColumn = splits[screenRow - row - 1] || 0; - line = line.substring(docColumn); - } - - docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; - - // Need to do some clamping action here. - if (this.$useWrapMode) { - column = this.$wrapData[docRow][screenRow - row] - if (docColumn >= column) { - // We remove one character at the end such that the docColumn - // position returned is not associated to the next row on the - // screen. - docColumn = column - 1; - } - } else { - docColumn = Math.min(docColumn, line.length); - } + if (this.$useWrapMode) { + docColumn = split = splits[screenRow - row - 1] || 0; + line = line.substring(split); } + + docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; + + if (foldLine) { + var position = foldLine.idxToPosition(docColumn); + return [position.row, position.column]; + } + + // Need to do some clamping action here. + if (this.$useWrapMode) { + column = splits[screenRow - row] + if (docColumn >= column) { + // We remove one character at the end such that the docColumn + // position returned is not associated to the next row on the + // screen. + docColumn = column - 1; + } + } else { + docColumn = Math.min(docColumn, line.length); + } + return [docRow, docColumn, screenRow, row] } @@ -1388,13 +1390,14 @@ var EditSession = function(text, mode) { foldStartRow = docRow; } else { textLine = this.getFoldDisplayLine(foldLine, docRow, docColumn); + foldStartRow = foldLine.start.row; } // Clamp textLine if in wrapMode. if (this.$useWrapMode) { var wrapRow = wrapData[foldStartRow]; var screenRowOffset = 0; - while (docColumn >= wrapRow[screenRowOffset]) { + while (textLine.length >= wrapRow[screenRowOffset]) { screenRow ++; screenRowOffset++; } @@ -1532,6 +1535,9 @@ var EditSession = function(text, mode) { } (function() { + /** + * Note: This doesn't update wrapData! + */ this.shiftRow = function(shift) { this.start.row += shift; this.end.row += shift; @@ -1602,7 +1608,7 @@ var EditSession = function(text, mode) { } stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); - stop = stop || callback(fold.placeholder); + stop = stop || callback(fold.placeholder, null, null, lastEnd); if (stop) { return; @@ -1645,8 +1651,7 @@ var EditSession = function(text, mode) { && fold.start.column != column && fold.start.row != row) { - // TODO: Implement adding new characters inside of an - // fold. This should extend/remove the fold etc. + throw "Moving characters inside of a fold should never be reached"; } else if (fold.start.row == row) { folds = this.folds; var i = folds.indexOf(fold); @@ -1683,8 +1688,6 @@ var EditSession = function(text, mode) { // containing these removed folds. folds = folds.splice(i, folds.length - i); - - var newFoldLine = new FoldLine(foldData, folds); foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine); return newFoldLine; @@ -1710,6 +1713,35 @@ var EditSession = function(text, mode) { ret.push("]") return ret.join("\n"); } + + this.idxToPosition = function(idx) { + var lastFoldEndColumn = 0; + var fold; + + for (var i = 0; i < this.folds.length; i++) { + var fold = this.folds[i]; + + idx -= fold.start.column - lastFoldEndColumn; + if (idx < 0) { + return { + row: fold.start.row, + column: fold.start.column + idx + }; + } + + idx -= fold.placeholder.length; + if (idx < 0) { + return fold.start; + } + + lastFoldEndColumn = fold.end.column; + } + + return { + row: this.end.row, + column: this.end.column + idx + }; + } }).call(FoldLine.prototype); FoldLine.prototype.__defineGetter__("rangeDBG", function() { @@ -1864,6 +1896,7 @@ var EditSession = function(text, mode) { this.$foldData.sort(function(a, b) { return a.start.row - b.start.row; }); + return foldLine; } /** @@ -1874,13 +1907,14 @@ var EditSession = function(text, mode) { endRow = range.end.row, foldData = this.$foldData, foldRow = null; + var foldLine; var fold = new Fold(range, placeholder); var added = false; // For now we assume that no two folds are created for the same range! for (var i = 0; i < foldData.length; i++) { - var foldLine = foldData[i]; + foldLine = foldData[i]; if (endRow == foldLine.start.row) { foldLine.addFold(fold); added = true; @@ -1904,10 +1938,13 @@ var EditSession = function(text, mode) { } if (!added) { - this.$addFoldLine(new FoldLine(this.$foldData, fold)); + foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold)); } // TODO: Recalculate wrapData + if (this.$useWrapMode) { + this.$updateWrapData(foldLine.start.row, foldLine.start.row); + } // Notify that fold data has changed. this.$modified = true; @@ -1916,6 +1953,8 @@ var EditSession = function(text, mode) { this.removeFold = function(fold) { var foldLine = fold.foldLine; + var startRow = foldLine.start.row; + var endRow = foldLine.end.row; var foldLines = this.$foldData, folds = foldLine.folds; @@ -1953,7 +1992,9 @@ var EditSession = function(text, mode) { this.$addFoldLine(newFoldLine); } - // TODO: Update wrapData. + if (this.$useWrapMode) { + this.$updateWrapData(startRow, endRow); + } // Notify that fold data has changed. this.$modified = true; @@ -1991,6 +2032,11 @@ var EditSession = function(text, mode) { }; this.getFoldDisplayLine = function(foldLine, endRow, endColumn) { + if (endRow == null) { + endRow = foldLine.end.row; + endColumn = this.getLine(endRow).length; + } + // Build the textline using the FoldLine walker. var line = "", textLine = ""; diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index a7d37ef0..2cefd40c 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -43,6 +43,7 @@ if (typeof process !== "undefined") { define(function(require, exports, module) { +var lang = require("pilot/lang"); var EditSession = require("ace/edit_session").EditSession; var Editor = require("ace/editor").Editor; var UndoManager = require("ace/undomanager").UndoManager; @@ -264,6 +265,7 @@ module.exports = { function computeAndAssert(line, assertEqual, wrapLimit, tabSize) { wrapLimit = wrapLimit || 12; tabSize = tabSize || 4; + line = lang.stringTrimRight(line); var tokens = EditSession.prototype.$getDisplayTokens(line); var splits = EditSession.prototype.$computeWrapSplits(tokens, wrapLimit, tabSize); // console.log("String:", line, "Result:", splits, "Expected:", assertEqual); @@ -373,6 +375,48 @@ module.exports = { assert.equal(session.doc.getValue(), ["", "foo"].join("\n")); }, + "test fold getFoldDisplayLine": function() { + var session = createFoldTestSession(); + function assertDisplayLine(foldLine, str) { + var line = session.getLine(foldLine.end.row); + var displayLine = + session.getFoldDisplayLine(foldLine, foldLine.end.row, line.length); + assert.equal(displayLine, str); + } + + assertDisplayLine(session.$foldData[0], "function foo(args...) {") + assertDisplayLine(session.$foldData[1], " for (vfoo...ert(items[bar...\"juhu\");"); + }, + + "test foldLine idxToPosition": function() { + var session = createFoldTestSession(); + + function assertIdx2Pos(foldLineIdx, idx, row, column) { + var foldLine = session.$foldData[foldLineIdx]; + assert.position(foldLine.idxToPosition(idx), row, column); + } + +// "function foo(items) {", +// " for (var i=0; i Date: Thu, 28 Apr 2011 13:43:00 +0200 Subject: [PATCH 36/53] Splitting edit_session.js up - moving the folding code parts into edit_session/fold_line.js|folding.js. --- lib/ace/edit_session.js | 564 +----------------------------- lib/ace/edit_session/fold_line.js | 276 +++++++++++++++ lib/ace/edit_session/folding.js | 370 ++++++++++++++++++++ 3 files changed, 648 insertions(+), 562 deletions(-) create mode 100644 lib/ace/edit_session/fold_line.js create mode 100644 lib/ace/edit_session/folding.js diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 9bbb6957..151bcc7b 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1492,569 +1492,9 @@ var EditSession = function(text, mode) { c >= 0xFFE0 && c <= 0xFFE6; }; - // == Folding Code ======================================================== - - /** - * Simple fold-data struct. - **/ - function Fold(range, placeholder) { - this.foldLine = null; - this.placeholder = placeholder; - this.range = range; - this.start = range.start; - this.end = range.end; - - this.sameRow = range.start.row == range.end.row; - } - - Fold.prototype.toString = function() { - return '"' + this.placeholder + '" ' + this.range.toString(); - } - - /** - * Creates a new FoldLine. If the an array is passed in, the folds are - * expected to be sorted already. - */ - function FoldLine(foldData, folds) { - this.foldData = foldData; - if (Array.isArray(folds)) { - this.folds = folds; - } else { - folds = this.folds = [ folds ]; - } - - var last = folds[folds.length - 1] - this.range = new Range(folds[0].start.row, folds[0].start.column, - last.end.row, last.end.column); - this.start = this.range.start; - this.end = this.range.end; - - this.folds.forEach(function(fold) { - fold.foldLine = this; - }, this); - } - - (function() { - /** - * Note: This doesn't update wrapData! - */ - this.shiftRow = function(shift) { - this.start.row += shift; - this.end.row += shift; - this.folds.forEach(function(fold) { - fold.start.row += shift; - fold.end.row += shift; - }); - } - - this.addFold = function(fold) { - if (fold.sameRow) { - if (fold.start.row < this.startRow || fold.endRow > this.endRow) { - throw "Can't add a fold to this FoldLine as it has no connection"; - } - this.folds.push(fold); - this.folds.sort(function(a, b) { - return -a.range.compareEnd(b.start.row, b.start.column); - }); - if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) { - this.end.row = fold.end.row; - this.end.column = fold.end.column; - } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) { - this.start.row = fold.start.row; - this.start.column = fold.start.column; - } - } else if (fold.start.row == this.end.row) { - this.folds.push(fold); - this.end.row = fold.end.row; - this.end.column = fold.end.column; - } else if (fold.end.row == this.start.row) { - this.folds.unshift(fold); - this.start.row = fold.start.row; - this.start.column = fold.start.column; - } else { - throw "Trying to add fold to FoldRow that doesn't have a matching row"; - } - fold.foldLine = this; - } - - this.containsRow = function(row) { - return row >= this.start.row && row <= this.end.row; - } - - this.walk = function(callback, endRow, endColumn) { - var lastEnd = 0, - folds = this.folds, - fold, - comp, stop, isNewRow = true; - - if (endRow == null) { - endRow = this.end.row; - endColumn = this.end.column; - } - - for (var i = 0; i < folds.length; i++) { - fold = folds[i]; - - comp = fold.range.compareEnd(endRow, endColumn); - // This fold is after the endRow/Column. - if (comp == -1) { - callback(null, endRow, endColumn, lastEnd, isNewRow); - return; - } - // The endRow/Column is inside of the current fold. - else if (comp == 0) { - callback(null, endRow, fold.start.column, lastEnd, isNewRow); - return; - } - - stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); - stop = stop || callback(fold.placeholder, null, null, lastEnd); - - if (stop) { - return; - } - - // Note the new lastEnd might not be on the same line. However, - // it's the callback's job to recognize this. - isNewRow = !fold.sameRow; - lastEnd = fold.end.column; - } - callback(null, endRow, endColumn, lastEnd, isNewRow); - } - - this.getNextFoldTo = function(row, column) { - var fold, cmp; - for (var i = 0; i < this.folds.length; i++) { - fold = this.folds[i]; - cmp = fold.range.compareEnd(row, column); - if (cmp == -1) { - return { - fold: fold, - kind: "after" - }; - } else if (cmp == 0) { - return { - fold: fold, - kind: "inside" - } - } - } - return null; - } - - this.addRemoveChars = function(row, column, len) { - var ret = this.getNextFoldTo(row, column), - fold, folds; - if (ret) { - fold = ret.fold; - if (ret.kind == "inside" - && fold.start.column != column - && fold.start.row != row) - { - throw "Moving characters inside of a fold should never be reached"; - } else if (fold.start.row == row) { - folds = this.folds; - var i = folds.indexOf(fold); - if (i == 0) { - this.start.column += len; - } - for (i; i < folds.length; i++) { - fold = folds[i]; - fold.start.column += len; - if (!fold.sameRow) { - return; - } - fold.end.column += len; - } - this.end.column += len; - } - } - } - - this.split = function(row, column) { - var fold = this.getNextFoldTo(row, column).fold, - folds = this.folds; - var foldData = this.foldData; - - if (!fold) { - return null; - } - var i = folds.indexOf(fold); - var foldBefore = folds[i - 1]; - this.end.row = foldBefore.end.row; - this.end.column = foldBefore.end.column; - - // Remove the folds after row/column and create a new FoldLine - // containing these removed folds. - folds = folds.splice(i, folds.length - i); - - var newFoldLine = new FoldLine(foldData, folds); - foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine); - return newFoldLine; - } - - this.merge = function(foldLineNext) { - var folds = foldLineNext.folds; - for (var i = 0; i < folds.length; i++) { - this.addFold(folds[i]); - } - // Remove the foldLineNext - no longer needed, as - // it's merged now with foldLineNext. - var foldData = this.foldData; - foldData.splice(foldData.indexOf(foldLineNext), 1); - } - - this.toString = function() { - var ret = [this.range.toString() + ": [" ]; - - this.folds.forEach(function(fold) { - ret.push(" " + fold.toString()); - }); - ret.push("]") - return ret.join("\n"); - } - - this.idxToPosition = function(idx) { - var lastFoldEndColumn = 0; - var fold; - - for (var i = 0; i < this.folds.length; i++) { - var fold = this.folds[i]; - - idx -= fold.start.column - lastFoldEndColumn; - if (idx < 0) { - return { - row: fold.start.row, - column: fold.start.column + idx - }; - } - - idx -= fold.placeholder.length; - if (idx < 0) { - return fold.start; - } - - lastFoldEndColumn = fold.end.column; - } - - return { - row: this.end.row, - column: this.end.column + idx - }; - } - }).call(FoldLine.prototype); - - FoldLine.prototype.__defineGetter__("rangeDBG", function() { - return this.range + ""; - }) - - /** - * Looks up a fold at a given row/column. Possible values for side: - * -1: ignore a fold if fold.start = row/column - * +1: ignore a fold if fold.end = row/column - */ - this.getFoldAt = function(row, column, side) { - var foldLine = this.getFoldLine(row); - if (foldLine) { - var folds = foldLine.folds, - fold; - - for (var i = 0; i < folds.length; i++) { - fold = folds[i]; - if (fold.range.contains(row, column)) { - if (side == 1 && fold.range.isEnd(row, column)) { - continue; - } else if (side == -1 && fold.range.isStart(row, column)) { - continue; - } - return fold; - } - } - } else { - return null; - } - } - - /** - * Returns all folds in the given range. Note, that this will return folds - * - */ - this.getFoldsInRange = function(range) { - range = range.clone(); - var start = range.start, - end = range.end; - var foldLines = this.$foldData, - folds, - fold; - var cmp, - foundFolds = []; - - start.column += 1; - end.column -= 1; - - for (var i = 0; i < foldLines.length; i++) { - cmp = foldLines[i].range.compareRange(range); - // Range is before foldLine. No intersection. This means, - // there might be other foldLines that intersect. - if (cmp == 2) { - continue; - } else - // Range is after foldLine. There can't be any other foldLines then, - // so let's give up. - if (cmp == -2) { - break; - } - - folds = foldLines[i].folds; - for (var j = 0; j < folds.length; j++) { - fold = folds[j]; - cmp = fold.range.compareRange(range); - if (cmp == -2) { - break; - } else if (cmp == 2) { - continue; - } else - // WTF-state: Can happen due to -1/+1 to start/end column. - if (cmp == 42) { - break; - } - foundFolds.push(fold); - } - } - return foundFolds; - } - - /** - * Returns the string between folds at the given position. - * E.g. - * foob|arwolrd -> "bar" - * foobarwol|rd -> "world" - * foobarwolrd -> - * - * where | means the position of row/column - * - * The trim option determs if the return string should be trimed according - * to the "side" passed with the trim value: - * - * E.g. - * foob|arwolrd -trim=-1> "b" - * foobarwol|rd -trim=+1> "rld" - * fo|obarwolrd -trim=00> "foo" - */ - this.getFoldStringAt = function(row, column, trim, foldLine) { - var foldLine = foldLine || this.getFoldLine(row); - if (!foldLine) { - return null; - } else { - var fold, lastFold, cmp, str; - lastFold = { - end: { column: 0 } - }; - // TODO: Refactor to use getNextFoldTo function. - for (var i = 0; i < foldLine.folds.length; i++) { - fold = foldLine.folds[i]; - cmp = fold.range.compareEnd(row, column); - if (cmp == -1) { - str = this.getLine(fold.start.row). - substring(lastFold.end.column, fold.start.column); - break; - } else if (cmp == 0) { - return null; - } - lastFold = fold; - } - if (!str) { - str = this.getLine(fold.start.row). - substring(lastFold.end.column); - } - if (trim == -1) { - return str.substring(0, column - lastFold.end.column); - } else if (trim == 1) { - return str.substring(column - lastFold.end.column) - } else { - return str; - } - } - } - - this.getFoldLine = function(docRow, startFoldLine) { - var foldData = this.$foldData; - var i = Math.max(foldData.indexOf(startFoldLine), 0); - for (i; i < foldData.length; i++) { - var foldLine = foldData[i]; - if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) { - return foldLine; - } else if (foldLine.end.row > docRow) { - return null; - } - } - return null; - } - - this.$addFoldLine = function(foldLine) { - this.$foldData.push(foldLine); - this.$foldData.sort(function(a, b) { - return a.start.row - b.start.row; - }); - return foldLine; - } - - /** - * Adds a new fold. - */ - this.addFold = function(range, placeholder) { - var startRow = range.start.row, - endRow = range.end.row, - foldData = this.$foldData, - foldRow = null; - var foldLine; - - var fold = new Fold(range, placeholder); - var added = false; - - // For now we assume that no two folds are created for the same range! - for (var i = 0; i < foldData.length; i++) { - foldLine = foldData[i]; - if (endRow == foldLine.start.row) { - foldLine.addFold(fold); - added = true; - break; - } else if (startRow == foldLine.end.row) { - foldLine.addFold(fold); - added = true; - if (!fold.sameRow) { - // Check if we might have to merge two FoldLines. - foldLineNext = foldData[i + 1]; - if (foldLineNext && foldLineNext.start.row == endRow) { - // We need to merge! - foldLine.merge(foldLineNext); - break; - } - } - break; - } else if (endRow <= foldLine.start.row) { - break; - } - } - - if (!added) { - foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold)); - } - - // TODO: Recalculate wrapData - if (this.$useWrapMode) { - this.$updateWrapData(foldLine.start.row, foldLine.start.row); - } - - // Notify that fold data has changed. - this.$modified = true; - this._dispatchEvent("changeFold"); - }; - - this.removeFold = function(fold) { - var foldLine = fold.foldLine; - var startRow = foldLine.start.row; - var endRow = foldLine.end.row; - - var foldLines = this.$foldData, - folds = foldLine.folds; - // Simple case where there is only one fold in the FoldLine such that - // the entire fold line can get removed directly. - if (folds.length == 1) { - foldLines.splice(foldLines.indexOf(foldLine), 1); - } else - // If the fold is the last fold of the foldLine, just remove it. - if (foldLine.range.isEnd(fold.end.row, fold.end.column)) { - folds.pop(); - foldLine.end.row = folds[folds.length - 1].end.row; - foldLine.end.column = folds[folds.length - 1].end.column; - } else - // If the fold is the first fold of the foldLine, just remove it. - if (foldLine.range.isStart(fold.start.row, fold.start.column)) { - folds.shift(); - foldLine.start.row = folds[0].start.row; - foldLine.start.column = folds[0].start.column; - } else - // We know there are more then 2 folds and the fold is not at the edge. - // This means, the fold is somewhere in between. - // - // If the fold is in one row, we just can remove it. - if (fold.sameRow) { - folds.splice(folds.indexOf(fold), 1); - } else - // The fold goes over more then one row. This means remvoing this fold - // will cause the fold line to get splitted up. - { - var newFoldLine = foldLine.split(fold.start.row, fold.start.column); - newFoldLine.folds.shift(); - foldLine.start.row = folds[0].start.row; - foldLine.start.column = folds[0].start.column; - this.$addFoldLine(newFoldLine); - } - - if (this.$useWrapMode) { - this.$updateWrapData(startRow, endRow); - } - - // Notify that fold data has changed. - this.$modified = true; - this._dispatchEvent("changeFold"); - } - - this.removeFolds = function(folds) { - // We need to clone the folds array passed in as it might be the folds - // array of a fold line and as we call this.removeFold(fold), folds - // are removed from folds and changes the current index. - var cloneFolds = []; - for (var i = 0; i < folds.length; i++) { - cloneFolds.push(folds[i]); - } - - cloneFolds.forEach(function(fold) { - this.removeFold(fold); - }, this); - this.$modified = true; - } - - /** - * Checks if a given documentRow is folded. This is true if there are some - * folded parts such that some parts of the line is still visible. - **/ - this.isRowFolded = function(docRow, startFoldRow) { - return !!this.getFoldLine(docRow, startFoldRow); - }; - - this.getRowFoldEnd = function(docRow, startFoldRow) { - var foldLine = this.getFoldLine(docRow, startFoldRow); - return (foldLine - ? foldLine.end.row - : docRow) - }; - - this.getFoldDisplayLine = function(foldLine, endRow, endColumn) { - if (endRow == null) { - endRow = foldLine.end.row; - endColumn = this.getLine(endRow).length; - } - - // Build the textline using the FoldLine walker. - var line = "", - textLine = ""; - - foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { - if (placeholder) { - textLine += placeholder; - } else { - if (isNewRow) { - line = this.getLine(row); - } - textLine += line.substring(lastColumn, column); - } - }.bind(this), endRow, endColumn); - return textLine; - } - }).call(EditSession.prototype); +require("ace/edit_session/folding").Folding.call(EditSession.prototype); + exports.EditSession = EditSession; }); diff --git a/lib/ace/edit_session/fold_line.js b/lib/ace/edit_session/fold_line.js new file mode 100644 index 00000000..8939287d --- /dev/null +++ b/lib/ace/edit_session/fold_line.js @@ -0,0 +1,276 @@ +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var Range = require("ace/range").Range; + +/** + * If the an array is passed in, the folds are expected to be sorted already. + */ +function FoldLine(foldData, folds) { + this.foldData = foldData; + if (Array.isArray(folds)) { + this.folds = folds; + } else { + folds = this.folds = [ folds ]; + } + + var last = folds[folds.length - 1] + this.range = new Range(folds[0].start.row, folds[0].start.column, + last.end.row, last.end.column); + this.start = this.range.start; + this.end = this.range.end; + + this.folds.forEach(function(fold) { + fold.foldLine = this; + }, this); +} + +(function() { + /** + * Note: This doesn't update wrapData! + */ + this.shiftRow = function(shift) { + this.start.row += shift; + this.end.row += shift; + this.folds.forEach(function(fold) { + fold.start.row += shift; + fold.end.row += shift; + }); + } + + this.addFold = function(fold) { + if (fold.sameRow) { + if (fold.start.row < this.startRow || fold.endRow > this.endRow) { + throw "Can't add a fold to this FoldLine as it has no connection"; + } + this.folds.push(fold); + this.folds.sort(function(a, b) { + return -a.range.compareEnd(b.start.row, b.start.column); + }); + if (this.range.compareEnd(fold.start.row, fold.start.column) > 0) { + this.end.row = fold.end.row; + this.end.column = fold.end.column; + } else if (this.range.compareStart(fold.end.row, fold.end.column) < 0) { + this.start.row = fold.start.row; + this.start.column = fold.start.column; + } + } else if (fold.start.row == this.end.row) { + this.folds.push(fold); + this.end.row = fold.end.row; + this.end.column = fold.end.column; + } else if (fold.end.row == this.start.row) { + this.folds.unshift(fold); + this.start.row = fold.start.row; + this.start.column = fold.start.column; + } else { + throw "Trying to add fold to FoldRow that doesn't have a matching row"; + } + fold.foldLine = this; + } + + this.containsRow = function(row) { + return row >= this.start.row && row <= this.end.row; + } + + this.walk = function(callback, endRow, endColumn) { + var lastEnd = 0, + folds = this.folds, + fold, + comp, stop, isNewRow = true; + + if (endRow == null) { + endRow = this.end.row; + endColumn = this.end.column; + } + + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; + + comp = fold.range.compareEnd(endRow, endColumn); + // This fold is after the endRow/Column. + if (comp == -1) { + callback(null, endRow, endColumn, lastEnd, isNewRow); + return; + } + // The endRow/Column is inside of the current fold. + else if (comp == 0) { + callback(null, endRow, fold.start.column, lastEnd, isNewRow); + return; + } + + stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); + stop = stop || callback(fold.placeholder, null, null, lastEnd); + + if (stop) { + return; + } + + // Note the new lastEnd might not be on the same line. However, + // it's the callback's job to recognize this. + isNewRow = !fold.sameRow; + lastEnd = fold.end.column; + } + callback(null, endRow, endColumn, lastEnd, isNewRow); + } + + this.getNextFoldTo = function(row, column) { + var fold, cmp; + for (var i = 0; i < this.folds.length; i++) { + fold = this.folds[i]; + cmp = fold.range.compareEnd(row, column); + if (cmp == -1) { + return { + fold: fold, + kind: "after" + }; + } else if (cmp == 0) { + return { + fold: fold, + kind: "inside" + } + } + } + return null; + } + + this.addRemoveChars = function(row, column, len) { + var ret = this.getNextFoldTo(row, column), + fold, folds; + if (ret) { + fold = ret.fold; + if (ret.kind == "inside" + && fold.start.column != column + && fold.start.row != row) + { + throw "Moving characters inside of a fold should never be reached"; + } else if (fold.start.row == row) { + folds = this.folds; + var i = folds.indexOf(fold); + if (i == 0) { + this.start.column += len; + } + for (i; i < folds.length; i++) { + fold = folds[i]; + fold.start.column += len; + if (!fold.sameRow) { + return; + } + fold.end.column += len; + } + this.end.column += len; + } + } + } + + this.split = function(row, column) { + var fold = this.getNextFoldTo(row, column).fold, + folds = this.folds; + var foldData = this.foldData; + + if (!fold) { + return null; + } + var i = folds.indexOf(fold); + var foldBefore = folds[i - 1]; + this.end.row = foldBefore.end.row; + this.end.column = foldBefore.end.column; + + // Remove the folds after row/column and create a new FoldLine + // containing these removed folds. + folds = folds.splice(i, folds.length - i); + + var newFoldLine = new FoldLine(foldData, folds); + foldData.splice(foldData.indexOf(this) + 1, 0, newFoldLine); + return newFoldLine; + } + + this.merge = function(foldLineNext) { + var folds = foldLineNext.folds; + for (var i = 0; i < folds.length; i++) { + this.addFold(folds[i]); + } + // Remove the foldLineNext - no longer needed, as + // it's merged now with foldLineNext. + var foldData = this.foldData; + foldData.splice(foldData.indexOf(foldLineNext), 1); + } + + this.toString = function() { + var ret = [this.range.toString() + ": [" ]; + + this.folds.forEach(function(fold) { + ret.push(" " + fold.toString()); + }); + ret.push("]") + return ret.join("\n"); + } + + this.idxToPosition = function(idx) { + var lastFoldEndColumn = 0; + var fold; + + for (var i = 0; i < this.folds.length; i++) { + var fold = this.folds[i]; + + idx -= fold.start.column - lastFoldEndColumn; + if (idx < 0) { + return { + row: fold.start.row, + column: fold.start.column + idx + }; + } + + idx -= fold.placeholder.length; + if (idx < 0) { + return fold.start; + } + + lastFoldEndColumn = fold.end.column; + } + + return { + row: this.end.row, + column: this.end.column + idx + }; + } +}).call(FoldLine.prototype); + +exports.FoldLine = FoldLine; +}); \ No newline at end of file diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js new file mode 100644 index 00000000..033f6a85 --- /dev/null +++ b/lib/ace/edit_session/folding.js @@ -0,0 +1,370 @@ +/* vim:ts=4:sts=4:sw=4: + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * 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 + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { + +var FoldLine = require("ace/edit_session/fold_line").FoldLine; + +/** + * Simple fold-data struct. + **/ +function Fold(range, placeholder) { + this.foldLine = null; + this.placeholder = placeholder; + this.range = range; + this.start = range.start; + this.end = range.end; + + this.sameRow = range.start.row == range.end.row; +} + +Fold.prototype.toString = function() { + return '"' + this.placeholder + '" ' + this.range.toString(); +} + +function Folding() { + /** + * Looks up a fold at a given row/column. Possible values for side: + * -1: ignore a fold if fold.start = row/column + * +1: ignore a fold if fold.end = row/column + */ + this.getFoldAt = function(row, column, side) { + var foldLine = this.getFoldLine(row); + if (foldLine) { + var folds = foldLine.folds, + fold; + + for (var i = 0; i < folds.length; i++) { + fold = folds[i]; + if (fold.range.contains(row, column)) { + if (side == 1 && fold.range.isEnd(row, column)) { + continue; + } else if (side == -1 && fold.range.isStart(row, column)) { + continue; + } + return fold; + } + } + } else { + return null; + } + } + + /** + * Returns all folds in the given range. Note, that this will return folds + * + */ + this.getFoldsInRange = function(range) { + range = range.clone(); + var start = range.start, + end = range.end; + var foldLines = this.$foldData, + folds, + fold; + var cmp, + foundFolds = []; + + start.column += 1; + end.column -= 1; + + for (var i = 0; i < foldLines.length; i++) { + cmp = foldLines[i].range.compareRange(range); + // Range is before foldLine. No intersection. This means, + // there might be other foldLines that intersect. + if (cmp == 2) { + continue; + } else + // Range is after foldLine. There can't be any other foldLines then, + // so let's give up. + if (cmp == -2) { + break; + } + + folds = foldLines[i].folds; + for (var j = 0; j < folds.length; j++) { + fold = folds[j]; + cmp = fold.range.compareRange(range); + if (cmp == -2) { + break; + } else if (cmp == 2) { + continue; + } else + // WTF-state: Can happen due to -1/+1 to start/end column. + if (cmp == 42) { + break; + } + foundFolds.push(fold); + } + } + return foundFolds; + } + + /** + * Returns the string between folds at the given position. + * E.g. + * foob|arwolrd -> "bar" + * foobarwol|rd -> "world" + * foobarwolrd -> + * + * where | means the position of row/column + * + * The trim option determs if the return string should be trimed according + * to the "side" passed with the trim value: + * + * E.g. + * foob|arwolrd -trim=-1> "b" + * foobarwol|rd -trim=+1> "rld" + * fo|obarwolrd -trim=00> "foo" + */ + this.getFoldStringAt = function(row, column, trim, foldLine) { + var foldLine = foldLine || this.getFoldLine(row); + if (!foldLine) { + return null; + } else { + var fold, lastFold, cmp, str; + lastFold = { + end: { column: 0 } + }; + // TODO: Refactor to use getNextFoldTo function. + for (var i = 0; i < foldLine.folds.length; i++) { + fold = foldLine.folds[i]; + cmp = fold.range.compareEnd(row, column); + if (cmp == -1) { + str = this.getLine(fold.start.row). + substring(lastFold.end.column, fold.start.column); + break; + } else if (cmp == 0) { + return null; + } + lastFold = fold; + } + if (!str) { + str = this.getLine(fold.start.row). + substring(lastFold.end.column); + } + if (trim == -1) { + return str.substring(0, column - lastFold.end.column); + } else if (trim == 1) { + return str.substring(column - lastFold.end.column) + } else { + return str; + } + } + } + + this.getFoldLine = function(docRow, startFoldLine) { + var foldData = this.$foldData; + var i = Math.max(foldData.indexOf(startFoldLine), 0); + for (i; i < foldData.length; i++) { + var foldLine = foldData[i]; + if (foldLine.start.row <= docRow && foldLine.end.row >= docRow) { + return foldLine; + } else if (foldLine.end.row > docRow) { + return null; + } + } + return null; + } + + this.$addFoldLine = function(foldLine) { + this.$foldData.push(foldLine); + this.$foldData.sort(function(a, b) { + return a.start.row - b.start.row; + }); + return foldLine; + } + + /** + * Adds a new fold. + */ + this.addFold = function(range, placeholder) { + var startRow = range.start.row, + endRow = range.end.row, + foldData = this.$foldData, + foldRow = null; + var foldLine; + + var fold = new Fold(range, placeholder); + var added = false; + + // For now we assume that no two folds are created for the same range! + for (var i = 0; i < foldData.length; i++) { + foldLine = foldData[i]; + if (endRow == foldLine.start.row) { + foldLine.addFold(fold); + added = true; + break; + } else if (startRow == foldLine.end.row) { + foldLine.addFold(fold); + added = true; + if (!fold.sameRow) { + // Check if we might have to merge two FoldLines. + foldLineNext = foldData[i + 1]; + if (foldLineNext && foldLineNext.start.row == endRow) { + // We need to merge! + foldLine.merge(foldLineNext); + break; + } + } + break; + } else if (endRow <= foldLine.start.row) { + break; + } + } + + if (!added) { + foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold)); + } + + // TODO: Recalculate wrapData + if (this.$useWrapMode) { + this.$updateWrapData(foldLine.start.row, foldLine.start.row); + } + + // Notify that fold data has changed. + this.$modified = true; + this._dispatchEvent("changeFold"); + }; + + this.removeFold = function(fold) { + var foldLine = fold.foldLine; + var startRow = foldLine.start.row; + var endRow = foldLine.end.row; + + var foldLines = this.$foldData, + folds = foldLine.folds; + // Simple case where there is only one fold in the FoldLine such that + // the entire fold line can get removed directly. + if (folds.length == 1) { + foldLines.splice(foldLines.indexOf(foldLine), 1); + } else + // If the fold is the last fold of the foldLine, just remove it. + if (foldLine.range.isEnd(fold.end.row, fold.end.column)) { + folds.pop(); + foldLine.end.row = folds[folds.length - 1].end.row; + foldLine.end.column = folds[folds.length - 1].end.column; + } else + // If the fold is the first fold of the foldLine, just remove it. + if (foldLine.range.isStart(fold.start.row, fold.start.column)) { + folds.shift(); + foldLine.start.row = folds[0].start.row; + foldLine.start.column = folds[0].start.column; + } else + // We know there are more then 2 folds and the fold is not at the edge. + // This means, the fold is somewhere in between. + // + // If the fold is in one row, we just can remove it. + if (fold.sameRow) { + folds.splice(folds.indexOf(fold), 1); + } else + // The fold goes over more then one row. This means remvoing this fold + // will cause the fold line to get splitted up. + { + var newFoldLine = foldLine.split(fold.start.row, fold.start.column); + newFoldLine.folds.shift(); + foldLine.start.row = folds[0].start.row; + foldLine.start.column = folds[0].start.column; + this.$addFoldLine(newFoldLine); + } + + if (this.$useWrapMode) { + this.$updateWrapData(startRow, endRow); + } + + // Notify that fold data has changed. + this.$modified = true; + this._dispatchEvent("changeFold"); + } + + this.removeFolds = function(folds) { + // We need to clone the folds array passed in as it might be the folds + // array of a fold line and as we call this.removeFold(fold), folds + // are removed from folds and changes the current index. + var cloneFolds = []; + for (var i = 0; i < folds.length; i++) { + cloneFolds.push(folds[i]); + } + + cloneFolds.forEach(function(fold) { + this.removeFold(fold); + }, this); + this.$modified = true; + } + + /** + * Checks if a given documentRow is folded. This is true if there are some + * folded parts such that some parts of the line is still visible. + **/ + this.isRowFolded = function(docRow, startFoldRow) { + return !!this.getFoldLine(docRow, startFoldRow); + }; + + this.getRowFoldEnd = function(docRow, startFoldRow) { + var foldLine = this.getFoldLine(docRow, startFoldRow); + return (foldLine + ? foldLine.end.row + : docRow) + }; + + this.getFoldDisplayLine = function(foldLine, endRow, endColumn) { + if (endRow == null) { + endRow = foldLine.end.row; + endColumn = this.getLine(endRow).length; + } + + // Build the textline using the FoldLine walker. + var line = "", + textLine = ""; + + foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + if (placeholder) { + textLine += placeholder; + } else { + if (isNewRow) { + line = this.getLine(row); + } + textLine += line.substring(lastColumn, column); + } + }.bind(this), endRow, endColumn); + return textLine; + } +} + +exports.Folding = Folding; + +}); From 353c6a4f4a414c0b1348ffec6721abb1b6285550 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 13:57:12 +0200 Subject: [PATCH 37/53] Fix bug in updateWrapData. Rendering still sometimes misaligned --- demo/demo.js | 3 +++ index.html | 2 +- lib/ace/edit_session.js | 38 ++++++++++++++++++++++--------------- lib/ace/virtual_renderer.js | 2 +- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 66bc9c6b..f180bfdc 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -21,6 +21,7 @@ * Contributor(s): * Fabian Jakobs * Kevin Dangoor (kdangoor@mozilla.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 @@ -154,6 +155,8 @@ exports.launch = function(env) { docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); docs.svg.addFold(new Range(1, 0, 7, 0), "fold..."); + + docs.plain.addFold(new Range(0, 90, 2, 30), "fold"); window.s = docs.js; window.e = env.editor; setTimeout(function() { diff --git a/index.html b/index.html index a4dcc834..7acf756a 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,7 @@ diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 151bcc7b..6df87f11 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1005,30 +1005,38 @@ var EditSession = function(text, mode) { var tokens; var foldLine; - for (var row = firstRow; row <= lastRow; row++) { + var row = firstRow; + while (row <= lastRow) { foldLine = this.getFoldLine(row); if (!foldLine) { tokens = this.$getDisplayTokens(lang.stringTrimRight(lines[row])); } else { tokens = []; - foldLine.walk(function(placeholder, row, column, lastColumn) { - var walkTokens; - if (placeholder) { - walkTokens = this.$getDisplayTokens(placeholder, tokens.length); - walkTokens[0] = PLACEHOLDER_START; - for (var i = 1; i < walkTokens.length; i++) { - walkTokens[i] = PLACEHOLDER_BODY; + foldLine.walk( + function(placeholder, row, column, lastColumn) { + var walkTokens; + if (placeholder) { + walkTokens = this.$getDisplayTokens( + placeholder, tokens.length); + walkTokens[0] = PLACEHOLDER_START; + for (var i = 1; i < walkTokens.length; i++) { + walkTokens[i] = PLACEHOLDER_BODY; + } + } else { + walkTokens = this.$getDisplayTokens( + lines[row].substring(lastColumn, column), + tokens.length); } - } else { - walkTokens = this.$getDisplayTokens( - lines[row].substring(lastColumn, column), - tokens.length); - } - tokens = tokens.concat(walkTokens); - }.bind(this), row, lines[row].length); + tokens = tokens.concat(walkTokens); + }.bind(this), + foldLine.end.row, + lines[foldLine.end.row].length + ); } wrapData[row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize); + + row = this.getRowFoldEnd(row) + 1; } }; diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index 01b6ca1c..d1d6c3a0 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -501,7 +501,7 @@ var VirtualRenderer = function(container, theme) { }; // For debugging. - // console.log(JSON.stringify(layerConfig)); + console.log(JSON.stringify(layerConfig)); this.$gutterLayer.element.style.marginTop = (-offset) + "px"; this.content.style.marginTop = (-offset) + "px"; From 1d9dbe6d3eeba88955e7f85743956427f9618d14 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 14:06:03 +0200 Subject: [PATCH 38/53] Fix render misalignment. Looks good so far. --- demo/demo.js | 2 +- lib/ace/virtual_renderer.js | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index f180bfdc..14a661a1 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -157,7 +157,7 @@ exports.launch = function(env) { docs.svg.addFold(new Range(1, 0, 7, 0), "fold..."); docs.plain.addFold(new Range(0, 90, 2, 30), "fold"); - window.s = docs.js; + window.s = docs.plain; window.e = env.editor; setTimeout(function() { env.editor.selection.addEventListener("changeCursor", function() { diff --git a/lib/ace/virtual_renderer.js b/lib/ace/virtual_renderer.js index d1d6c3a0..9c813ddc 100644 --- a/lib/ace/virtual_renderer.js +++ b/lib/ace/virtual_renderer.js @@ -478,6 +478,14 @@ var VirtualRenderer = function(container, theme) { var firstRowScreen, firstRowHeight; var lineHeight = { lineHeight: this.lineHeight }; firstRow = session.screenToDocumentRow(firstRow, 0); + + // Check if firstRow is inside of a foldLine. If true, then use the first + // row of the foldLine. + var foldLine = session.getFoldLine(firstRow); + if (foldLine) { + firstRow = foldLine.start.row; + } + firstRowScreen = session.documentToScreenRow(firstRow, 0); firstRowHeight = session.getRowHeight(lineHeight, firstRow); @@ -501,7 +509,7 @@ var VirtualRenderer = function(container, theme) { }; // For debugging. - console.log(JSON.stringify(layerConfig)); + // console.log(JSON.stringify(layerConfig)); this.$gutterLayer.element.style.marginTop = (-offset) + "px"; this.content.style.marginTop = (-offset) + "px"; From 0687d8a5e83e83be1b9291e3aed67707ba18b88e Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 15:53:21 +0200 Subject: [PATCH 39/53] When running the unit tests in the browser, print log to the console. --- lib/ace/test/all_browser.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 819476a8..1566cfde 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -53,7 +53,11 @@ async.list(tests) else var err = test.err + console.error(msg); + console.error(err); msg += "
" + err + "
"; + } else { + console.log(msg); } node.innerHTML = msg; @@ -77,6 +81,9 @@ async.list(tests) (passed ? "Passed tests: " + passed + "
" : ""), (failed ? "Failed tests: " + failed + "
" : "") ].join("") + console.log("Total number of tests: " + (passed + failed)); + console.log("Passed tests: " + passed); + console.log("Failed tests: " + failed); }) }); From 825cf26478a4a09325eb3dd655293d27c06f13e0 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 15:53:50 +0200 Subject: [PATCH 40/53] Final iteration of wrapMode + folding: Addind unit tests, fixing bugs. --- demo/demo.js | 12 ++--- lib/ace/edit_session.js | 52 ++++++++++--------- lib/ace/edit_session/fold_line.js | 13 ++--- lib/ace/edit_session/folding.js | 19 +++++-- lib/ace/edit_session_test.js | 84 +++++++++++++++++++++++++++---- lib/ace/undomanager.js | 2 +- 6 files changed, 129 insertions(+), 53 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 14a661a1..5bb3e576 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -150,13 +150,13 @@ exports.launch = function(env) { // BEGING TESTING var Range = require("ace/range").Range; - docs.js.addFold(new Range(0, 13, 0, 18), "args..."); - docs.js.addFold(new Range(2, 20, 2, 25), "bar..."); - docs.js.addFold(new Range(1, 10, 2, 10), "foo..."); + docs.js.addFold("args...", new Range(0, 13, 0, 18)); + docs.js.addFold("bar...", new Range(2, 20, 2, 25)); + docs.js.addFold("foo...", new Range(1, 10, 2, 10)); - docs.svg.addFold(new Range(1, 0, 7, 0), "fold..."); + docs.svg.addFold("fold...", new Range(1, 0, 7, 0)); - docs.plain.addFold(new Range(0, 90, 2, 30), "fold"); + docs.plain.addFold("fold", new Range(0, 90, 2, 30)); window.s = docs.plain; window.e = env.editor; setTimeout(function() { @@ -481,7 +481,7 @@ exports.launch = function(env) { range = env.editor.selection.getRange(), placeHolder = session.getTextRange(range).substring(0, 3) + "..."; - session.addFold(range, placeHolder); + session.addFold(placeHolder, range); } }); diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 6df87f11..4a824832 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1006,6 +1006,7 @@ var EditSession = function(text, mode) { var foldLine; var row = firstRow; + lastRow = Math.min(lastRow, lines.length - 1); while (row <= lastRow) { foldLine = this.getFoldLine(row); if (!foldLine) { @@ -1030,8 +1031,14 @@ var EditSession = function(text, mode) { tokens = tokens.concat(walkTokens); }.bind(this), foldLine.end.row, - lines[foldLine.end.row].length + lines[foldLine.end.row].length + 1 ); + // Remove spaces/tabs from the back of the token array. + while (tokens.length != 0 + && tokens[tokens.length - 1] >= SPACE) + { + tokens.pop(); + } } wrapData[row] = this.$computeWrapSplits(tokens, wrapLimit, tabSize); @@ -1096,24 +1103,9 @@ var EditSession = function(text, mode) { } // === ELSE === - // Search for the first non space/tab token backwards. - 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); - continue; - } - - // === ELSE === - split = lastSplit + wrapLimit; - // No space or tab around? Well, force a split then. - // Check if the split position is inside of a placeholder. - // Placeholder are not splitable! + // Check if split is inside of a placeholder. Placeholder are + // not splitable. Therefore, seek the beginning of the placeholder + // and try to place the split beofre the placeholder's start. if (tokens[split] == PLACEHOLDER_START || tokens[split] == PLACEHOLDER_BODY) { @@ -1140,8 +1132,7 @@ var EditSession = function(text, mode) { // placeholder. So, let's seek for the end of the placeholder. split = lastSplit + wrapLimit; for (split; split < tokens.length; split++) { - if (tokens[split] != PLACEHOLDER_START && - tokens[split] != PLACEHOLDER_BODY) + if (tokens[split] != PLACEHOLDER_BODY) { break; } @@ -1159,8 +1150,23 @@ var EditSession = function(text, mode) { } // === ELSE === - // The split is inside of a CHAR or CHAR_EXT token. Forcing - // a split here is all right. + // Search for the first non space/tab/placeholder token backwards. + for (split; split != lastSplit - 1; split--) { + if (tokens[split] >= PLACEHOLDER_START) { + split++; + break; + } + } + // If we found one, then add the split. + if (split > lastSplit) { + addSplit(split); + continue; + } + + // === ELSE === + split = lastSplit + wrapLimit; + // The split is inside of a CHAR or CHAR_EXT token and no space + // around -> force a split. addSplit(lastSplit + wrapLimit); } return splits; diff --git a/lib/ace/edit_session/fold_line.js b/lib/ace/edit_session/fold_line.js index 8939287d..1a5cb7a3 100644 --- a/lib/ace/edit_session/fold_line.js +++ b/lib/ace/edit_session/fold_line.js @@ -123,22 +123,19 @@ function FoldLine(foldData, folds) { for (var i = 0; i < folds.length; i++) { fold = folds[i]; - comp = fold.range.compareEnd(endRow, endColumn); + comp = fold.range.compareStart(endRow, endColumn); // This fold is after the endRow/Column. if (comp == -1) { callback(null, endRow, endColumn, lastEnd, isNewRow); return; } - // The endRow/Column is inside of the current fold. - else if (comp == 0) { - callback(null, endRow, fold.start.column, lastEnd, isNewRow); - return; - } stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); - stop = stop || callback(fold.placeholder, null, null, lastEnd); + stop = !stop && callback(fold.placeholder, null, null, lastEnd); - if (stop) { + // If the user requested to stop the walk or endRow/endColumn is + // inside of this fold (comp == 0), then end here. + if (stop || comp == 0) { return; } diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index 033f6a85..ec2d16f1 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -38,6 +38,7 @@ define(function(require, exports, module) { +var Range = require("ace/range").Range; var FoldLine = require("ace/edit_session/fold_line").FoldLine; /** @@ -212,11 +213,19 @@ function Folding() { /** * Adds a new fold. */ - this.addFold = function(range, placeholder) { - var startRow = range.start.row, - endRow = range.end.row, - foldData = this.$foldData, - foldRow = null; + this.addFold = function(placeholder, startRow, startColumn, endRow, endColumn) { + var range; + // Normalize parameters. + if (!(startRow instanceof Range)) { + range = new Range(startRow, startColumn, endRow, endColumn); + } else { + range = startRow; + startRow = range.start.row; + endRow = range.end.row; + } + + var foldData = this.$foldData; + var foldRow = null; var foldLine; var fold = new Fold(range, placeholder); diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 2cefd40c..6f65c54e 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -61,9 +61,9 @@ function createFoldTestSession() { ]; var session = new EditSession(lines.join("\n")); session.setUndoManager(new UndoManager()); - session.addFold(new Range(0, 13, 0, 18), "args..."); - session.addFold(new Range(1, 10, 2, 10), "foo..."); - session.addFold(new Range(2, 20, 2, 25), "bar..."); + session.addFold("args...", new Range(0, 13, 0, 18)); + session.addFold("foo...", new Range(1, 10, 2, 10)); + session.addFold("bar...", new Range(2, 20, 2, 25)); return session; } @@ -697,7 +697,7 @@ module.exports = { function assertArray(a, b) { assert.ok(a.length == b.length); for (var i = 0; i < a.length; i++) { - assert.ok(a[i] == b[i]); + assert.equal(a[i], b[i]); } } @@ -707,6 +707,11 @@ module.exports = { line2 && assertArray(wrapData[2], line2); } + function removeFoldAssertWrap(docRow, docColumn, line0, line1, line2) { + session.removeFold(session.getFoldAt(docRow, docColumn)); + assertWrap(line0, line1, line2); + } + var lines = [ "foo bar foo bar", "foo bar foo bar", @@ -714,19 +719,78 @@ module.exports = { ]; var session = new EditSession(lines.join("\n")); - var wrapData = session.$wrapData; session.setUseWrapMode(true); session.$wrapLimit = 7; session.$updateWrapData(0, 2); + var wrapData = session.$wrapData; - assertWrap([7], [7], [7]); - session.addFold(new Range(0, 13, 0, 18), "args..."); + // Do a simple assertion without folds to check basic functionallity. + assertWrap([8], [8], [8]); + // --- Do in line folding --- + + // Adding a fold. The split position is inside of the fold. As placeholder + // are not splitable, the split should be before the split. + session.addFold("woot", 0, 4, 0, 16); + assertWrap([4], [8], [8]); + + // Remove the fold again which should reset the wrapData. + removeFoldAssertWrap(0, 4, [8], [8], [8]); + + session.addFold("woot", 0, 6, 0, 9); + assertWrap([6, 13], [8], [8]); + removeFoldAssertWrap(0, 6, [8], [8], [8]); + + // The fold fits into the wrap limit - no split expected. + session.addFold("woot", 0, 3, 0, 16); + assertWrap([], [8], [8]); + removeFoldAssertWrap(0, 4, [8], [8], [8]); + + // Fold after split position should be all fine. + session.addFold("woot", 0, 8, 0, 16); + assertWrap([8], [8], [8]); + removeFoldAssertWrap(0, 8, [8], [8], [8]); + + // Fold's placeholder is far too long for wrapSplit. + session.addFold("woot0123456789", 0, 8, 0, 16); + assertWrap([8], [8], [8]); + removeFoldAssertWrap(0, 8, [8], [8], [8]); + + // Fold's placeholder is far too long for wrapSplit + // + content at the end of the line + session.addFold("woot0123456789", 0, 7, 0, 8); + assertWrap([7, 21], [8], [8]); + removeFoldAssertWrap(0, 8, [8], [8], [8]); + + session.addFold("woot0123456789", 0, 7, 0, 8); + session.addFold("woot0123456789", 0, 8, 0, 9); + assertWrap([7, 21, 35], [8], [8]); + session.removeFold(session.getFoldAt(0, 7)); + removeFoldAssertWrap(0, 8, [8], [8], [8]); + + session.addFold("woot0123456789", 0, 7, 0, 8); + session.addFold("woot0123456789", 0, 14, 0, 16); + assertWrap([7, 21, 27], [8], [8]); + session.removeFold(session.getFoldAt(0, 7)); + removeFoldAssertWrap(0, 14, [8], [8], [8]); + + // --- Do some multiline folding --- + + // Add a fold over two lines. Note, that the wrapData[1] stays the + // same. This is an implementation detail and expected behavior. + session.addFold("woot", 0, 8, 1, 16); + assertWrap([8], [8 /* See comments */], [8]); + removeFoldAssertWrap(0, 8, [8], [8], [8]); + + session.addFold("woot", 0, 9, 1, 11); + assertWrap([8, 14], [8 /* See comments */], [8]); + removeFoldAssertWrap(0, 9, [8], [8], [8]); + + session.addFold("woot", 0, 9, 1, 16); + assertWrap([8], [8 /* See comments */], [8]); + removeFoldAssertWrap(0, 9, [8], [8], [8]); - session.addFold(new Range(1, 10, 2, 10), "foo..."); - session.addFold(new Range(2, 20, 2, 25), "bar..."); return session; - } }; diff --git a/lib/ace/undomanager.js b/lib/ace/undomanager.js index e157032b..5cb8e78b 100644 --- a/lib/ace/undomanager.js +++ b/lib/ace/undomanager.js @@ -66,7 +66,7 @@ var UndoManager = function() { this.$undoStack.push(deltas); } }; - + this.reset = function() { this.$undoStack = []; this.$redoStack = []; From 61fc8d04c4cb8ad074cca52b41e90c3fb82f988d Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 16:11:40 +0200 Subject: [PATCH 41/53] Add support for folded lines in Selection.selectLine --- lib/ace/edit_session.js | 7 ++++++- lib/ace/edit_session/folding.js | 17 ++++++++++++++++- lib/ace/selection.js | 22 ++++++++++++++++++---- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 4a824832..ec2ef3d1 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1268,7 +1268,12 @@ var EditSession = function(text, mode) { this.getDocumentLastRowColumn = function(docRow, docColumn) { var screenRow = this.documentToScreenRow(docRow, docColumn); return this.getScreenLastRowColumn(screenRow); - } + }; + + this.getDocumentLastRowColumnPosition = function(docRow, docColumn) { + var screenRow = this.documentToScreenRow(docRow, docColumn); + return this.screenToDocumentPosition(screenRow, Number.MAX_VALUE / 10); + }; this.getRowSplitData = function(row) { if (!this.$useWrapMode) { diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index ec2d16f1..c183bc58 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -371,7 +371,22 @@ function Folding() { } }.bind(this), endRow, endColumn); return textLine; - } + }; + + this.getDisplayLine = function(row, endColumn) { + var foldLine = this.getFoldLine(row); + + if (!foldLine) { + if (endColumn == null) { + return this.doc.$lines[row]; + } else { + var line = this.doc.$lines[row]; + return line.substring(endColumn); + } + } else { + return this.getFoldDisplayLine(foldLine, row, endColumn); + } + }; } exports.Folding = Folding; diff --git a/lib/ace/selection.js b/lib/ace/selection.js index a39fdeb0..2a91ff01 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -247,9 +247,19 @@ var Selection = function(session) { }; this.selectLine = function() { - this.setSelectionAnchor(this.selectionLead.row, 0); + var rowStart = this.selectionLead.row; + var rowEnd; + + var foldLine = this.session.getFoldLine(rowStart); + if (foldLine) { + rowStart = foldLine.start.row; + rowEnd = foldLine.end.row; + } else { + rowEnd = rowStart; + } + this.setSelectionAnchor(rowStart, 0); this.$moveSelection(function() { - this.moveCursorTo(this.selectionLead.row + 1, 0); + this.moveCursorTo(rowEnd + 1, 0); }); }; @@ -310,7 +320,6 @@ var Selection = function(session) { var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column); var leadingSpace = beforeCursor.match(/^\s*/); if (leadingSpace[0].length == 0) { - var lastRowColumn = this.session.getDocumentLastRowColumn(row, column); leadingSpace = this.doc.getLine(row). substring(firstRowColumn, lastRowColumn). match(/^\s*/); @@ -324,7 +333,12 @@ var Selection = function(session) { this.moveCursorLineEnd = function() { var lead = this.selectionLead; - this.moveCursorTo(lead.row, this.session.getDocumentLastRowColumn(lead.row, lead.column)); + var lastRowColumnPosition = + this.session.getDocumentLastRowColumnPosition(lead.row, lead.column); + this.moveCursorTo( + lastRowColumnPosition.row, + lastRowColumnPosition.column + ); }; this.moveCursorFileEnd = function() { From 5573252193675b13ffe39444e7de83e9097ae177 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 18:21:36 +0200 Subject: [PATCH 42/53] Make Selection.moveCursorFirstLine work again. Fixing some bugs on the way. --- lib/ace/edit_session.js | 10 ++++---- lib/ace/edit_session/fold_line.js | 18 +++++++++++++- lib/ace/edit_session/folding.js | 39 +++++++++++++++++++------------ lib/ace/selection.js | 29 +++++++++++++++-------- lib/ace/test/all_browser.js | 30 ++++++++++++------------ 5 files changed, 80 insertions(+), 46 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index ec2ef3d1..0eaa721c 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -1325,11 +1325,6 @@ var EditSession = function(text, mode) { docColumn += this.$getStringScreenWidth(line, screenColumn)[1]; - if (foldLine) { - var position = foldLine.idxToPosition(docColumn); - return [position.row, position.column]; - } - // Need to do some clamping action here. if (this.$useWrapMode) { column = splits[screenRow - row] @@ -1343,6 +1338,11 @@ var EditSession = function(text, mode) { docColumn = Math.min(docColumn, line.length); } + if (foldLine) { + var position = foldLine.idxToPosition(docColumn); + return [position.row, position.column]; + } + return [docRow, docColumn, screenRow, row] } diff --git a/lib/ace/edit_session/fold_line.js b/lib/ace/edit_session/fold_line.js index 1a5cb7a3..9bde99a5 100644 --- a/lib/ace/edit_session/fold_line.js +++ b/lib/ace/edit_session/fold_line.js @@ -131,7 +131,7 @@ function FoldLine(foldData, folds) { } stop = callback(null, fold.start.row, fold.start.column, lastEnd, isNewRow); - stop = !stop && callback(fold.placeholder, null, null, lastEnd); + stop = !stop && callback(fold.placeholder, fold.start.row, fold.start.column, lastEnd); // If the user requested to stop the walk or endRow/endColumn is // inside of this fold (comp == 0), then end here. @@ -267,6 +267,22 @@ function FoldLine(foldData, folds) { column: this.end.column + idx }; } + +// this.positionToIdx = function(pos) { +// var row = pos.row; +// var column = pos.column; +// var idx = 0; +// +// this.walk(function(placeholder, row, column, lastColumn) { +// if (placeholder +// var range = new Range(row, lastColumn, row, column); +// var cmp = range.compareEnd(row, column); +// +// if (cmp == 0) { +// idx += +// } +// }); +// } }).call(FoldLine.prototype); exports.FoldLine = FoldLine; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index c183bc58..e2d8cec2 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -350,41 +350,50 @@ function Folding() { : docRow) }; - this.getFoldDisplayLine = function(foldLine, endRow, endColumn) { + this.getFoldDisplayLine = function(foldLine, endRow, endColumn, startRow, startColumn) { + if (startRow == null) { + startRow = foldLine.start.row; + startColumn = 0; + } + if (endRow == null) { endRow = foldLine.end.row; endColumn = this.getLine(endRow).length; } // Build the textline using the FoldLine walker. - var line = "", - textLine = ""; + var line = ""; + var lines = this.doc.$lines; + var textLine = ""; foldLine.walk(function(placeholder, row, column, lastColumn, isNewRow) { + if (row < startRow) { + return; + } else if (row == startRow) { + if (column < startColumn) { + return; + } + lastColumn = Math.max(startColumn, lastColumn); + } if (placeholder) { textLine += placeholder; } else { - if (isNewRow) { - line = this.getLine(row); - } - textLine += line.substring(lastColumn, column); + textLine += lines[row].substring(lastColumn, column); } }.bind(this), endRow, endColumn); return textLine; }; - this.getDisplayLine = function(row, endColumn) { + this.getDisplayLine = function(row, endColumn, startRow, startColumn) { var foldLine = this.getFoldLine(row); if (!foldLine) { - if (endColumn == null) { - return this.doc.$lines[row]; - } else { - var line = this.doc.$lines[row]; - return line.substring(endColumn); - } + var line; + line = this.doc.$lines[row]; + return line.substring(startColumn || 0, endColumn || line.length); } else { - return this.getFoldDisplayLine(foldLine, row, endColumn); + return this.getFoldDisplayLine( + foldLine, row, endColumn, startRow, startColumn); } }; } diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 2a91ff01..3e86a210 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -316,18 +316,27 @@ var Selection = function(session) { var row = this.selectionLead.row; var column = this.selectionLead.column; var screenRow = this.session.documentToScreenRow(row, column); - var firstRowColumn = this.session.screenToDocumentColumn(screenRow, 0) - var beforeCursor = this.doc.getLine(row).slice(firstRowColumn, column); + + // Determ the doc-position of the first character at the screen line. + var firstColumnPosition = + this.session.screenToDocumentPosition(screenRow, 0); + + // Determ the string "before" the cursor. + var beforeCursor = this.session.getDisplayLine( + row, column, + firstColumnPosition.row, firstColumnPosition.column); + + // var leadingSpace = beforeCursor.match(/^\s*/); - if (leadingSpace[0].length == 0) { - 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); + if (leadingSpace[0].length == 0 + || leadingSpace[0].length >= column - firstColumnPosition.column) + { + this.moveCursorTo( + firstColumnPosition.row, firstColumnPosition.column); } else { - this.moveCursorTo(row, firstRowColumn + leadingSpace[0].length); + this.moveCursorTo( + firstColumnPosition.row, + firstColumnPosition.column + leadingSpace[0].length); } }; diff --git a/lib/ace/test/all_browser.js b/lib/ace/test/all_browser.js index 1566cfde..698d3907 100644 --- a/lib/ace/test/all_browser.js +++ b/lib/ace/test/all_browser.js @@ -16,21 +16,21 @@ var tests = [ // require("ace/editor_text_edit_test"), // require("ace/document_test"), require("ace/edit_session_test"), - // require("ace/test/event_emitter_test"), - // require("ace/range_test"), - // require("ace/search_test"), - // require("ace/selection_test"), - // require("ace/virtual_renderer_test"), - // require("ace/anchor_test"), - // require("ace/mode/css_test"), - // require("ace/mode/css_tokenizer_test"), - // require("ace/mode/html_test"), - // require("ace/mode/html_tokenizer_test"), - // require("ace/mode/javascript_test"), - // require("ace/mode/javascript_tokenizer_test"), - // require("ace/mode/text_test"), - // require("ace/mode/xml_test"), - // require("ace/mode/xml_tokenizer_test") +// require("ace/test/event_emitter_test"), +// require("ace/range_test"), +// require("ace/search_test"), + require("ace/selection_test"), +// require("ace/virtual_renderer_test"), +// require("ace/anchor_test"), +// require("ace/mode/css_test"), +// require("ace/mode/css_tokenizer_test"), +// require("ace/mode/html_test"), +// require("ace/mode/html_tokenizer_test"), +// require("ace/mode/javascript_test"), +// require("ace/mode/javascript_tokenizer_test"), +// require("ace/mode/text_test"), +// require("ace/mode/xml_test"), +// require("ace/mode/xml_tokenizer_test") ] async.list(tests) From e356ea8cae28d3ce64dfe9c29b5f97526e9ef1ca Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 18:32:49 +0200 Subject: [PATCH 43/53] Ensure selection can't be inside of a fold. --- lib/ace/selection.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ace/selection.js b/lib/ace/selection.js index 3e86a210..e9d1d12b 100644 --- a/lib/ace/selection.js +++ b/lib/ace/selection.js @@ -443,6 +443,12 @@ var Selection = function(session) { }; this.moveCursorTo = function(row, column, preventUpdateDesiredColumn) { + // Ensure the row/column is not inside of a fold. + var fold = this.session.getFoldAt(row, column, 1); + if (fold) { + row = fold.start.row; + column = fold.start.column; + } this.selectionLead.setPosition(row, column); if (!preventUpdateDesiredColumn) this.$updateDesiredColumn(this.selectionLead.column); From d575f47ebcdf15639f014ee3f9692a4c97de1059 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 19:24:12 +0200 Subject: [PATCH 44/53] Add checks to addFold. --- lib/ace/edit_session/folding.js | 52 +++++++++++++++++++++++---- lib/ace/edit_session_test.js | 63 +++++++++++++++++++++++++-------- 2 files changed, 95 insertions(+), 20 deletions(-) diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index e2d8cec2..d3514930 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -212,9 +212,19 @@ function Folding() { /** * Adds a new fold. + * + * @returns + * The new created Fold object or an existing fold object in case the + * passed in range fits an existing fold exactly. */ this.addFold = function(placeholder, startRow, startColumn, endRow, endColumn) { var range; + var foldData = this.$foldData; + var foldRow = null; + var foldLine; + var fold; + var added = false; + // Normalize parameters. if (!(startRow instanceof Range)) { range = new Range(startRow, startColumn, endRow, endColumn); @@ -224,12 +234,41 @@ function Folding() { endRow = range.end.row; } - var foldData = this.$foldData; - var foldRow = null; - var foldLine; + // --- Some checking --- + if (placeholder.length < 2) { + throw "Placeholder has to be at least 2 characters"; + } - var fold = new Fold(range, placeholder); - var added = false; + if (startRow == endRow && endColumn - startColumn < 2) { + throw "The range has to be at least 2 characters width"; + } + + fold = this.getFoldAt(startRow, startColumn, 1); + if (fold + && fold.range.isEnd(endRow, endColumn) + && fold.range.isStart(startRow, startColumn)) + { + return fold; + } + + if (this.getFoldAt(startRow, startColumn, 1) + || this.getFoldAt(endRow, endColumn, -1)) + { + throw "A fold can't start or end inside of an already existing fold"; + } + + if (endRow >= this.doc.$lines.length) { + throw "End of fold is outside of the document."; + } + + if (endColumn > this.getLine(endRow).length + || startColumn > this.getLine(startRow).length) + { + throw "End of fold is outside of the document."; + } + + // --- Start adding the fold --- + fold = new Fold(range, placeholder); // For now we assume that no two folds are created for the same range! for (var i = 0; i < foldData.length; i++) { @@ -260,7 +299,6 @@ function Folding() { foldLine = this.$addFoldLine(new FoldLine(this.$foldData, fold)); } - // TODO: Recalculate wrapData if (this.$useWrapMode) { this.$updateWrapData(foldLine.start.row, foldLine.start.row); } @@ -268,6 +306,8 @@ function Folding() { // Notify that fold data has changed. this.$modified = true; this._dispatchEvent("changeFold"); + + return fold; }; this.removeFold = function(fold) { diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index 6f65c54e..bc5b09fb 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -731,7 +731,7 @@ module.exports = { // Adding a fold. The split position is inside of the fold. As placeholder // are not splitable, the split should be before the split. - session.addFold("woot", 0, 4, 0, 16); + session.addFold("woot", 0, 4, 0, 15); assertWrap([4], [8], [8]); // Remove the fold again which should reset the wrapData. @@ -742,35 +742,35 @@ module.exports = { removeFoldAssertWrap(0, 6, [8], [8], [8]); // The fold fits into the wrap limit - no split expected. - session.addFold("woot", 0, 3, 0, 16); + session.addFold("woot", 0, 3, 0, 15); assertWrap([], [8], [8]); removeFoldAssertWrap(0, 4, [8], [8], [8]); // Fold after split position should be all fine. - session.addFold("woot", 0, 8, 0, 16); + session.addFold("woot", 0, 8, 0, 15); assertWrap([8], [8], [8]); removeFoldAssertWrap(0, 8, [8], [8], [8]); // Fold's placeholder is far too long for wrapSplit. - session.addFold("woot0123456789", 0, 8, 0, 16); + session.addFold("woot0123456789", 0, 8, 0, 15); assertWrap([8], [8], [8]); removeFoldAssertWrap(0, 8, [8], [8], [8]); // Fold's placeholder is far too long for wrapSplit // + content at the end of the line - session.addFold("woot0123456789", 0, 7, 0, 8); - assertWrap([7, 21], [8], [8]); + session.addFold("woot0123456789", 0, 6, 0, 8); + assertWrap([6, 20], [8], [8]); removeFoldAssertWrap(0, 8, [8], [8], [8]); - session.addFold("woot0123456789", 0, 7, 0, 8); - session.addFold("woot0123456789", 0, 8, 0, 9); - assertWrap([7, 21, 35], [8], [8]); + session.addFold("woot0123456789", 0, 6, 0, 8); + session.addFold("woot0123456789", 0, 8, 0, 10); + assertWrap([6, 20, 34], [8], [8]); session.removeFold(session.getFoldAt(0, 7)); removeFoldAssertWrap(0, 8, [8], [8], [8]); - session.addFold("woot0123456789", 0, 7, 0, 8); - session.addFold("woot0123456789", 0, 14, 0, 16); - assertWrap([7, 21, 27], [8], [8]); + session.addFold("woot0123456789", 0, 7, 0, 9); + session.addFold("woot0123456789", 0, 13, 0, 15); + assertWrap([7, 21, 25], [8], [8]); session.removeFold(session.getFoldAt(0, 7)); removeFoldAssertWrap(0, 14, [8], [8], [8]); @@ -778,7 +778,7 @@ module.exports = { // Add a fold over two lines. Note, that the wrapData[1] stays the // same. This is an implementation detail and expected behavior. - session.addFold("woot", 0, 8, 1, 16); + session.addFold("woot", 0, 8, 1, 15); assertWrap([8], [8 /* See comments */], [8]); removeFoldAssertWrap(0, 8, [8], [8], [8]); @@ -786,11 +786,46 @@ module.exports = { assertWrap([8, 14], [8 /* See comments */], [8]); removeFoldAssertWrap(0, 9, [8], [8], [8]); - session.addFold("woot", 0, 9, 1, 16); + session.addFold("woot", 0, 9, 1, 15); assertWrap([8], [8 /* See comments */], [8]); removeFoldAssertWrap(0, 9, [8], [8], [8]); return session; + }, + + "test add fold": function() { + var session = createFoldTestSession(); + var fold; + + function tryAddFold( + placeholder, + startRow, startColumn, endRow, endColumn, shouldFail) + { + var fail = false; + try { + fold = session.addFold(placeholder, + startRow, startColumn, endRow, endColumn); + } catch (e) { + fail = true; + } + if (fail != shouldFail) { + throw "Expected to get an exception"; + } + } + + tryAddFold("foo", 0, 13, 0, 17, true); + tryAddFold("foo", 0, 14, 0, 18, true); + tryAddFold("foo", 0, 13, 0, 18, false); + assert.equal(session.$foldData[0].folds.length, 1); + + tryAddFold("f", 0, 13, 0, 18, true); + tryAddFold("foo", 0, 18, 0, 21, false); + assert.equal(session.$foldData[0].folds.length, 2); + session.removeFold(fold); + + tryAddFold("foo", 0, 18, 0, 22, true); + tryAddFold("foo", 0, 18, 0, 19, true); + tryAddFold("foo", 0, 22, 1, 10, true); } }; From f695c253f517673a601b3894d6414002a7824cf9 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 20:04:45 +0200 Subject: [PATCH 45/53] Add sub folding feature. addFold can take a Fold object now. --- demo/demo.js | 2 +- lib/ace/edit_session/folding.js | 50 +++++++++++++++++++++++++++++---- lib/ace/edit_session_test.js | 29 +++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 5bb3e576..5f0cfaab 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -495,7 +495,7 @@ exports.launch = function(env) { exec: function() { var session = env.editor.session, range = env.editor.selection.getRange(); - session.removeFolds(session.getFoldsInRange(range)); + session.expandFolds(session.getFoldsInRange(range)); } }); }; diff --git a/lib/ace/edit_session/folding.js b/lib/ace/edit_session/folding.js index d3514930..bbf0717b 100644 --- a/lib/ace/edit_session/folding.js +++ b/lib/ace/edit_session/folding.js @@ -52,6 +52,7 @@ function Fold(range, placeholder) { this.end = range.end; this.sameRow = range.start.row == range.end.row; + this.subFolds = []; } Fold.prototype.toString = function() { @@ -223,15 +224,25 @@ function Folding() { var foldRow = null; var foldLine; var fold; + var argsFold; + var folds; var added = false; + if (placeholder instanceof Fold) { + argsFold = placeholder; + startRow = argsFold.range; + placeholder = argsFold.placeholder; + } + // Normalize parameters. if (!(startRow instanceof Range)) { range = new Range(startRow, startColumn, endRow, endColumn); } else { range = startRow; startRow = range.start.row; + startColumn = range.start.column; endRow = range.end.row; + endColumn = range.end.column; } // --- Some checking --- @@ -251,10 +262,14 @@ function Folding() { return fold; } - if (this.getFoldAt(startRow, startColumn, 1) - || this.getFoldAt(endRow, endColumn, -1)) - { - throw "A fold can't start or end inside of an already existing fold"; + fold = this.getFoldAt(startRow, startColumn, 1); + if (fold && !fold.range.isStart(startRow, startColumn)) { + throw "A fold can't start inside of an already existing fold"; + } + + fold = this.getFoldAt(endRow, endColumn, -1); + if (fold && !fold.range.isEnd(endRow, endColumn)) { + throw "A fold can't end inside of an already existing fold"; } if (endRow >= this.doc.$lines.length) { @@ -268,9 +283,18 @@ function Folding() { } // --- Start adding the fold --- - fold = new Fold(range, placeholder); + // Use the passed in fold or create a new one. + fold = argsFold || new Fold(range, placeholder); + + // Check if there are folds in the range we create the new fold for. + folds = this.getFoldsInRange(range); + if (folds.length > 0) { + // Remove the folds from fold data. + this.removeFolds(folds); + // Add the removed folds as subfolds on the new fold. + fold.subFolds = folds; + } - // For now we assume that no two folds are created for the same range! for (var i = 0; i < foldData.length; i++) { foldLine = foldData[i]; if (endRow == foldLine.start.row) { @@ -375,6 +399,20 @@ function Folding() { this.$modified = true; } + this.expandFold = function(fold) { + this.removeFold(fold); + fold.subFolds.forEach(function(fold) { + this.addFold(fold); + }, this); + fold.subFolds = []; + } + + this.expandFolds = function(folds) { + folds.forEach(function(fold) { + this.expandFold(fold); + }, this); + } + /** * Checks if a given documentRow is folded. This is true if there are some * folded parts such that some parts of the line is still visible. diff --git a/lib/ace/edit_session_test.js b/lib/ace/edit_session_test.js index bc5b09fb..8efd0d60 100644 --- a/lib/ace/edit_session_test.js +++ b/lib/ace/edit_session_test.js @@ -826,6 +826,35 @@ module.exports = { tryAddFold("foo", 0, 18, 0, 22, true); tryAddFold("foo", 0, 18, 0, 19, true); tryAddFold("foo", 0, 22, 1, 10, true); + }, + + "test add subfolds": function() { + var session = createFoldTestSession(); + var fold, oldFold; + var foldData = session.$foldData; + + oldFold = foldData[0].folds[0]; + + fold = session.addFold("fold0", 0, 10, 0, 21); + assert.equal(foldData[0].folds.length, 1); + assert.equal(fold.subFolds.length, 1); + assert.equal(fold.subFolds[0], oldFold); + + session.expandFold(fold); + assert.equal(foldData[0].folds.length, 1); + assert.equal(foldData[0].folds[0], oldFold); + assert.equal(fold.subFolds.length, 0); + + fold = session.addFold("fold0", 0, 13, 2, 10); + assert.equal(foldData.length, 1); + assert.equal(fold.subFolds.length, 2); + assert.equal(fold.subFolds[0], oldFold); + + session.expandFold(fold); + assert.equal(foldData.length, 2); + assert.equal(foldData[0].folds.length, 1); + assert.equal(foldData[0].folds[0], oldFold); + assert.equal(fold.subFolds.length, 0); } }; From a385637b9e74461bd90eadeb428827bc2558e598 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 28 Apr 2011 21:01:09 +0200 Subject: [PATCH 46/53] Add removing folds to undo queue. --- demo/demo.js | 2 +- index.html | 2 +- lib/ace/document.js | 5 ++-- lib/ace/edit_session.js | 64 +++++++++++++++++++++++++++++++---------- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/demo/demo.js b/demo/demo.js index 5f0cfaab..e0fca7a4 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -157,7 +157,7 @@ exports.launch = function(env) { docs.svg.addFold("fold...", new Range(1, 0, 7, 0)); docs.plain.addFold("fold", new Range(0, 90, 2, 30)); - window.s = docs.plain; + window.s = docs.js; window.e = env.editor; setTimeout(function() { env.editor.selection.addEventListener("changeCursor", function() { diff --git a/index.html b/index.html index 7acf756a..6a7a0ea7 100644 --- a/index.html +++ b/index.html @@ -14,9 +14,9 @@