Make the EditSession#computeWrapSplits way more simplier and add support for multipleWidthCharacter

This commit is contained in:
Julian Viereck 2011-01-27 01:42:00 +01:00
commit 61be32a3cf
3 changed files with 121 additions and 133 deletions

View file

@ -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) { this.$computeWrapSplits = function(textLine, wrapLimit, tabSize) {
textLine = textLine.trimRight(); textLine = textLine.trimRight();
if (textLine.length == 0) { if (textLine.length == 0) {
return []; return [];
} }
var reSpaces = /^\s*$/; var tabSize = this.getTabSize();
var tabLine = textLine.split("\t"); var splits = [];
var wrapSplits = []; var tokens = this.$getDisplayTokens(textLine);
var displayLength = tokens.length;
var lastSplit = 0, lastDocSplit = 0;
var screenColumn = 0; function addSplit(screenPos) {
var documentColumn = 0; var displayed = tokens.slice(lastSplit, screenPos);
var lastSplitColumn = 0;
var value; // The document size is the current size - the extra width for tabs
var addedNewSplit; // 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) { lastDocSplit += len;
var valueLenLeadingWhitespaces; splits.push(lastDocSplit);
value = value.substring(incrDocumentColumn); lastSplit = screenPos;
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;
} }
for (var i = 0; i < tabLine.length; i++) { while (displayLength - lastSplit > wrapLimit) {
value = tabLine[i]; // This is, where the split should be.
while (screenColumn + value.length >= wrapLimit) { var split = lastSplit + wrapLimit;
// Find the last space that we use to split the line.
var lastSpaceIdx =
value.substring(0, wrapLimit - screenColumn + 1).
lastIndexOf(" ");
// There is no space to break the line. // If there is a space or tab at this split position.
if (lastSpaceIdx == -1) { if (tokens[split] >= SPACE) {
// True if this is the first tabLine we face // Include all following spaces + tabs in this split as well.
// during this split. This means, there is no while (tokens[split] >= SPACE) {
// space or tab to make the split -> force split. split ++;
if (screenColumn == 0) { }
addSplit(wrapLimit); addSplit(split);
} } else {
// True if we've added some content in this split line // Search for the first non space/tab token.
// already. Add a split after this already existing content for (split; split != lastSplit; split--) {
// in the split. if (tokens[split] >= SPACE) {
else { split++;
addSplit(0); 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 { else {
addSplit(lastSpaceIdx); addSplit(lastSplit + wrapLimit);
} }
} }
}
return splits;
}
// Add new content to the current split. this.$getDisplayTokens = function(str) {
if (!addedNewSplit) { var arr = [];
screenColumn += value.length + tabSize; var tabSize = this.getTabSize();
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();
// True if value has some content. This content goes directly for (var i = 0; i < str.length; i++) {
// after the last split. var c = str.charCodeAt(i);
// If there is no content but we are in the last tabLine, we // Tab
// already know that there is no following tabLine and we can if (c == 9) {
// shortcut by using this if instead of the following else. arr.push(TAB);
if (value.length != 0 || i == tabLine.length - 1) { for (var n = 1; n < tabSize; n++) {
documentColumn = lastSplitColumn += lenBefore - value.length; arr.push(TAB_SPACE);
screenColumn += value.length; }
documentColumn += 1; // The tab after this for-loop. }
} // Space
// There is no value left after the split. This means, following else if(c == 32) {
// spaces and tabs have to be ignored. arr.push(SPACE);
else { }
// Tab for this for-loop. // CJK characters
documentColumn = lastSplitColumn += 1; else if (
c >= 0x3040 && c <= 0x309F || // Hiragana
// While the following tabLines are empty/only spaces, set c >= 0x30A0 && c <= 0x30FF || // Katakana
// the latest split position behind them. c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs
while ((i + 1) < tabLine.length - 1 c >= 0xF900 && c <= 0xFAFF ||
&& reSpaces.test(tabLine[i + 1])) c >= 0x3400 && c <= 0x4DBF
{ ) {
i++; arr.push(CHAR, CHAR_EXT);
documentColumn = lastSplitColumn += tabLine[i].length + 1; } else {
} arr.push(CHAR);
// 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;
} }
} }
if (lastSplitColumn) { return arr;
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;
};
/** /**
* Calculates the width of the a string on the screen while assuming that * 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) { this.$getStringScreenWidth = function(str) {
var screenColumn = 0; var screenColumn = 0;
var remaining = docColumn; var tabSize = this.getTabSize();
for (var i=0; i<str.length; i++) { for (var i=0; i<str.length; i++) {
var c = str.charCodeAt(i); var c = str.charCodeAt(i);
if (remaining > 0) { // tab
remaining -= 1; if (c == 9) {
// tab screenColumn += tabSize;
if (c == 9) { }
screenColumn += tabSize; // CJK characters
} else if (
// CJK characters c >= 0x3040 && c <= 0x309F || // Hiragana
else if ( c >= 0x30A0 && c <= 0x30FF || // Katakana
c >= 0x3040 && c <= 0x309F || // Hiragana c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs
c >= 0x30A0 && c <= 0x30FF || // Katakana c >= 0xF900 && c <= 0xFAFF ||
c >= 0x4E00 && c <= 0x9FFF || // Single CJK ideographs c >= 0x3400 && c <= 0x4DBF
c >= 0xF900 && c <= 0xFAFF || ) {
c >= 0x3400 && c <= 0x4DBF screenColumn += 2;
) {
screenColumn += 2;
} else {
screenColumn += 1;
}
} else { } else {
break; screenColumn += 1;
} }
} }
return screenColumn; 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) { this.getScreenLastRowColumn = function(screenRow, returnDocPosition) {
if (!this.$useWrapMode) { if (!this.$useWrapMode) {
return this.$getStringScreenWidth(this.getLine(screenRow)); return this.$getStringScreenWidth(this.getLine(screenRow));

View file

@ -289,7 +289,8 @@ var Test = {
var c = 0; var c = 0;
function computeAndAssert(line, assertEqual) { 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); assert.ok(splits.length == assertEqual.length);
for (var i = 0; i < splits.length; i++) { for (var i = 0; i < splits.length; i++) {
assert.ok(splits[i] == assertEqual[i]); assert.ok(splits[i] == assertEqual[i]);
@ -315,7 +316,7 @@ var Test = {
computeAndAssert("foo \t \tbar", [ 7 ]); computeAndAssert("foo \t \tbar", [ 7 ]);
// Ignore spaces/tabs at beginning of split. // 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() { "test: documentToScreen": function() {

View file

@ -264,6 +264,15 @@ var Test = {
session.setTabSize(2); session.setTabSize(2);
assert.equal(session.getWidth(), 3); assert.equal(session.getWidth(), 3);
assert.equal(session.getScreenWidth(), 4); 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);
} }
}; };