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);