From 61be32a3cfbae4009dd9673cc02971954b4df319 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 27 Jan 2011 01:42:00 +0100 Subject: [PATCH] Make the EditSession#computeWrapSplits way more simplier and add support for multipleWidthCharacter --- lib/ace/edit_session.js | 240 ++++++++++++++---------------- lib/ace/test/document_test.js | 5 +- lib/ace/test/edit_session_test.js | 9 ++ 3 files changed, 121 insertions(+), 133 deletions(-) diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index 8af5f169..fde1e05c 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -711,138 +711,110 @@ var EditSession = function(text, mode) { } }; + // "Tokens" + var CHAR = 1, + CHAR_EXT = 2, + SPACE = 3, + TAB = 4, + TAB_SPACE = 5; + this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) { textLine = textLine.trimRight(); if (textLine.length == 0) { return []; } - var reSpaces = /^\s*$/; - var tabLine = textLine.split("\t"); - var wrapSplits = []; + var tabSize = this.getTabSize(); + var splits = []; + var tokens = this.$getDisplayTokens(textLine); + var displayLength = tokens.length; + var lastSplit = 0, lastDocSplit = 0; - var screenColumn = 0; - var documentColumn = 0; - var lastSplitColumn = 0; + function addSplit(screenPos) { + var displayed = tokens.slice(lastSplit, screenPos); - var value; - var addedNewSplit; + // The document size is the current size - the extra width for tabs + // and multipleWidth characters. + var len = displayed.length; + displayed.join(""). + // Get all the tabs. + replace(/4/g, function(m) { + len -= tabSize - 1; + }). + // Get all the multipleWidth characters. + replace(/2/g, function(m) { + len -= 1; + }); - function addSplit(incrDocumentColumn) { - var valueLenLeadingWhitespaces; - value = value.substring(incrDocumentColumn); - valueLenLeadingWhitespaces = value.length; - value = value.trimLeft(); - - incrDocumentColumn += valueLenLeadingWhitespaces - value.length; - - // If there was a split saved before, then add it to the resulting - // array. It's not possible to store the split column here directly, - // as there migth be tabs/spaces following the current column that - // will cause the split position to get incremented. - if (lastSplitColumn) { - wrapSplits.push(lastSplitColumn); - } - - lastSplitColumn = documentColumn += incrDocumentColumn; - screenColumn = 0; - - addedNewSplit = true; + lastDocSplit += len; + splits.push(lastDocSplit); + lastSplit = screenPos; } - for (var i = 0; i < tabLine.length; i++) { - value = tabLine[i]; - while (screenColumn + value.length >= wrapLimit) { - // Find the last space that we use to split the line. - var lastSpaceIdx = - value.substring(0, wrapLimit - screenColumn + 1). - lastIndexOf(" "); + while (displayLength - lastSplit > wrapLimit) { + // This is, where the split should be. + var split = lastSplit + wrapLimit; - // There is no space to break the line. - if (lastSpaceIdx == -1) { - // True if this is the first tabLine we face - // during this split. This means, there is no - // space or tab to make the split -> force split. - if (screenColumn == 0) { - addSplit(wrapLimit); - } - // True if we've added some content in this split line - // already. Add a split after this already existing content - // in the split. - else { - addSplit(0); + // If there is a space or tab at this split position. + if (tokens[split] >= SPACE) { + // Include all following spaces + tabs in this split as well. + while (tokens[split] >= SPACE) { + split ++; + } + addSplit(split); + } else { + // Search for the first non space/tab token. + for (split; split != lastSplit; split--) { + if (tokens[split] >= SPACE) { + split++; + break; } } - // There is at least one space to wrap the line at. + // If we found one, then add the split. + if (split != lastSplit) { + addSplit(split); + } + // No space or tab around? Well, force a split then. else { - addSplit(lastSpaceIdx); + addSplit(lastSplit + wrapLimit); } } + } + return splits; + } - // Add new content to the current split. - if (!addedNewSplit) { - screenColumn += value.length + tabSize; - documentColumn += value.length + 1; - } - // True if at least one new split was added during the while loop. - else { - var lenBefore = value.length; - value = value.trimLeft(); + this.$getDisplayTokens = function(str) { + var arr = []; + var tabSize = this.getTabSize(); - // True if value has some content. This content goes directly - // after the last split. - // If there is no content but we are in the last tabLine, we - // already know that there is no following tabLine and we can - // shortcut by using this if instead of the following else. - if (value.length != 0 || i == tabLine.length - 1) { - documentColumn = lastSplitColumn += lenBefore - value.length; - screenColumn += value.length; - documentColumn += 1; // The tab after this for-loop. - } - // There is no value left after the split. This means, following - // spaces and tabs have to be ignored. - else { - // Tab for this for-loop. - documentColumn = lastSplitColumn += 1; - - // While the following tabLines are empty/only spaces, set - // the latest split position behind them. - while ((i + 1) < tabLine.length - 1 - && reSpaces.test(tabLine[i + 1])) - { - i++; - documentColumn = lastSplitColumn += tabLine[i].length + 1; - } - - // The tabLine[i + 1] is not empty. However, the trailing - // spaces have still to be removed. - value = tabLine[i + 1]; - lenBefore = value.length; - value = value.trimLeft(); - documentColumn = lastSplitColumn += lenBefore - value.length; - tabLine[i + 1] = value; // Store the trimed value. - } - addedNewSplit = false; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + // Tab + if (c == 9) { + arr.push(TAB); + for (var n = 1; n < tabSize; n++) { + arr.push(TAB_SPACE); + } + } + // Space + else if(c == 32) { + arr.push(SPACE); + } + // CJK characters + else if ( + c >= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + arr.push(CHAR, CHAR_EXT); + } else { + arr.push(CHAR); } } - if (lastSplitColumn) { - wrapSplits.push(lastSplitColumn); - } - - return wrapSplits; - }; - - this.getRowHeight = function(config, row) { - var rows; - if (!this.$useWrapMode) { - rows = 1; - } else { - rows = this.$wrapData[row].length + 1; - } - - return rows * config.lineHeight; - }; - + return arr; + } /** * Calculates the width of the a string on the screen while assuming that @@ -853,36 +825,42 @@ var EditSession = function(text, mode) { */ this.$getStringScreenWidth = function(str) { var screenColumn = 0; - var remaining = docColumn; + var tabSize = this.getTabSize(); for (var i=0; i 0) { - remaining -= 1; - // tab - if (c == 9) { - screenColumn += tabSize; - } - // CJK characters - else if ( - c >= 0x3040 && c <= 0x309F || // Hiragana - c >= 0x30A0 && c <= 0x30FF || // Katakana - c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs - c >= 0xF900 && c <= 0xFAFF || - c >= 0x3400 && c <= 0x4DBF - ) { - screenColumn += 2; - } else { - screenColumn += 1; - } + // tab + if (c == 9) { + screenColumn += tabSize; + } + // CJK characters + else if ( + c >= 0x3040 && c <= 0x309F || // Hiragana + c >= 0x30A0 && c <= 0x30FF || // Katakana + c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs + c >= 0xF900 && c <= 0xFAFF || + c >= 0x3400 && c <= 0x4DBF + ) { + screenColumn += 2; } else { - break; + screenColumn += 1; } } return screenColumn; } + this.getRowHeight = function(config, row) { + var rows; + if (!this.$useWrapMode) { + rows = 1; + } else { + rows = this.$wrapData[row].length + 1; + } + + return rows * config.lineHeight; + } + this.getScreenLastRowColumn = function(screenRow, returnDocPosition) { if (!this.$useWrapMode) { return this.$getStringScreenWidth(this.getLine(screenRow)); diff --git a/lib/ace/test/document_test.js b/lib/ace/test/document_test.js index b2705f4e..cfa3586d 100644 --- a/lib/ace/test/document_test.js +++ b/lib/ace/test/document_test.js @@ -289,7 +289,8 @@ var Test = { var c = 0; function computeAndAssert(line, assertEqual) { - splits = computeWrapSplits(line, wrapLimit, tabSize); + splits = computeWrapSplits.call(EditSession.prototype, line, wrapLimit, tabSize); + // console.log("String:", line, "Result:", splits, "Expected:", assertEqual); assert.ok(splits.length == assertEqual.length); for (var i = 0; i < splits.length; i++) { assert.ok(splits[i] == assertEqual[i]); @@ -315,7 +316,7 @@ var Test = { computeAndAssert("foo \t \tbar", [ 7 ]); // Ignore spaces/tabs at beginning of split. - computeAndAssert("foo \t \t \t \t bar", [ 14]); + computeAndAssert("foo \t \t \t \t bar", [ 14 ]); }, "test: documentToScreen": function() { diff --git a/lib/ace/test/edit_session_test.js b/lib/ace/test/edit_session_test.js index 6ef7e4d7..5aa5cbfc 100644 --- a/lib/ace/test/edit_session_test.js +++ b/lib/ace/test/edit_session_test.js @@ -264,6 +264,15 @@ var Test = { session.setTabSize(2); assert.equal(session.getWidth(), 3); assert.equal(session.getScreenWidth(), 4); + }, + + "test getDisplayString": function() { + var session = new EditSession(["12"]); + session.setTabSize(4); + + assert.equal(session.$getDisplayTokens("\t").length, 4); + assert.equal(session.$getDisplayTokens("abc").length, 3); + assert.equal(session.$getDisplayTokens("abc\t").length, 7); } };