未验证 提交 222b31ea 编写于 作者: A Alexandru Dima 提交者: GitHub

Re-engineer the wrapping algorithm & more (#88405)

Re-engineer the wrapping algorithm & more
...@@ -57,12 +57,3 @@ export function toUint32(v: number): number { ...@@ -57,12 +57,3 @@ export function toUint32(v: number): number {
} }
return v | 0; return v | 0;
} }
export function toUint32Array(arr: number[]): Uint32Array {
const len = arr.length;
const r = new Uint32Array(len);
for (let i = 0; i < len; i++) {
r[i] = toUint32(arr[i]);
}
return r;
}
...@@ -136,14 +136,16 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { ...@@ -136,14 +136,16 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
const indent = indents[lineIndex]; const indent = indents[lineIndex];
let result = ''; let result = '';
const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1)); if (indent >= 1) {
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0; const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
for (let i = 1; i <= indent; i++) { let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); for (let i = 1; i <= indent; i++) {
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`; const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
left += indentWidth; result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`;
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) { left += indentWidth;
break; if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
break;
}
} }
} }
......
...@@ -213,6 +213,7 @@ export class ViewLine implements IVisibleLine { ...@@ -213,6 +213,7 @@ export class ViewLine implements IVisibleLine {
lineData.tokens, lineData.tokens,
actualInlineDecorations, actualInlineDecorations,
lineData.tabSize, lineData.tabSize,
lineData.startVisibleColumn,
options.spaceWidth, options.spaceWidth,
options.stopRenderingLineAfter, options.stopRenderingLineAfter,
options.renderWhitespace, options.renderWhitespace,
......
...@@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati ...@@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { withNullAsUndefined } from 'vs/base/common/types'; import { withNullAsUndefined } from 'vs/base/common/types';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
let EDITOR_ID = 0; let EDITOR_ID = 0;
...@@ -1329,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ...@@ -1329,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
model.onBeforeAttached(); model.onBeforeAttached();
const viewModel = new ViewModel(this._id, this._configuration, model, (callback) => dom.scheduleAtNextAnimationFrame(callback)); const viewModel = new ViewModel(this._id, this._configuration, model, MonospaceLineBreaksComputerFactory.create(this._configuration.options), (callback) => dom.scheduleAtNextAnimationFrame(callback));
listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e))); listenersToRemove.push(model.onDidChangeDecorations((e) => this._onDidChangeModelDecorations.fire(e)));
listenersToRemove.push(model.onDidChangeLanguage((e) => { listenersToRemove.push(model.onDidChangeLanguage((e) => {
......
...@@ -2144,6 +2144,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { ...@@ -2144,6 +2144,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
lineTokens, lineTokens,
actualDecorations, actualDecorations,
tabSize, tabSize,
0,
fontInfo.spaceWidth, fontInfo.spaceWidth,
options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderWhitespace),
......
...@@ -780,6 +780,7 @@ export class DiffReview extends Disposable { ...@@ -780,6 +780,7 @@ export class DiffReview extends Disposable {
lineTokens, lineTokens,
[], [],
tabSize, tabSize,
0,
fontInfo.spaceWidth, fontInfo.spaceWidth,
options.get(EditorOption.stopRenderingLineAfter), options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace), options.get(EditorOption.renderWhitespace),
......
...@@ -264,19 +264,14 @@ export interface IEditorOptions { ...@@ -264,19 +264,14 @@ export interface IEditorOptions {
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
/** /**
* Configure word wrapping characters. A break will be introduced before these characters. * Configure word wrapping characters. A break will be introduced before these characters.
* Defaults to '{([+'. * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
*/ */
wordWrapBreakBeforeCharacters?: string; wordWrapBreakBeforeCharacters?: string;
/** /**
* Configure word wrapping characters. A break will be introduced after these characters. * Configure word wrapping characters. A break will be introduced after these characters.
* Defaults to ' \t})]?|&,;'. * Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
*/ */
wordWrapBreakAfterCharacters?: string; wordWrapBreakAfterCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found.
* Defaults to '.'.
*/
wordWrapBreakObtrusiveCharacters?: string;
/** /**
* Performance guard: Stop rendering a line after x characters. * Performance guard: Stop rendering a line after x characters.
* Defaults to 10000. * Defaults to 10000.
...@@ -3154,7 +3149,6 @@ export const enum EditorOption { ...@@ -3154,7 +3149,6 @@ export const enum EditorOption {
wordWrap, wordWrap,
wordWrapBreakAfterCharacters, wordWrapBreakAfterCharacters,
wordWrapBreakBeforeCharacters, wordWrapBreakBeforeCharacters,
wordWrapBreakObtrusiveCharacters,
wordWrapColumn, wordWrapColumn,
wordWrapMinified, wordWrapMinified,
wrappingIndent, wrappingIndent,
...@@ -3681,16 +3675,12 @@ export const EditorOptions = { ...@@ -3681,16 +3675,12 @@ export const EditorOptions = {
)), )),
wordWrapBreakAfterCharacters: register(new EditorStringOption( wordWrapBreakAfterCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters', EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters',
' \t})]?|/&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」', ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
)), )),
wordWrapBreakBeforeCharacters: register(new EditorStringOption( wordWrapBreakBeforeCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters', EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters',
'([{‘“〈《「『【〔([{「£¥$£¥++' '([{‘“〈《「『【〔([{「£¥$£¥++'
)), )),
wordWrapBreakObtrusiveCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakObtrusiveCharacters, 'wordWrapBreakObtrusiveCharacters',
'.'
)),
wordWrapColumn: register(new EditorIntOption( wordWrapColumn: register(new EditorIntOption(
EditorOption.wordWrapColumn, 'wordWrapColumn', EditorOption.wordWrapColumn, 'wordWrapColumn',
80, 1, Constants.MAX_SAFE_SMALL_INTEGER, 80, 1, Constants.MAX_SAFE_SMALL_INTEGER,
......
...@@ -12,14 +12,14 @@ export class CharacterClassifier<T extends number> { ...@@ -12,14 +12,14 @@ export class CharacterClassifier<T extends number> {
/** /**
* Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code). * Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code).
*/ */
private _asciiMap: Uint8Array; protected _asciiMap: Uint8Array;
/** /**
* The entire map (sparse array). * The entire map (sparse array).
*/ */
private _map: Map<number, number>; protected _map: Map<number, number>;
private _defaultValue: number; protected _defaultValue: number;
constructor(_defaultValue: T) { constructor(_defaultValue: T) {
let defaultValue = toUint8(_defaultValue); let defaultValue = toUint8(_defaultValue);
......
...@@ -511,7 +511,93 @@ export class PieceTreeBase { ...@@ -511,7 +511,93 @@ export class PieceTreeBase {
} }
public getLinesContent(): string[] { public getLinesContent(): string[] {
return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/); let lines: string[] = [];
let linesLength = 0;
let currentLine = '';
let danglingCR = false;
this.iterate(this.root, node => {
if (node === SENTINEL) {
return true;
}
const piece = node.piece;
let pieceLength = piece.length;
if (pieceLength === 0) {
return true;
}
const buffer = this._buffers[piece.bufferIndex].buffer;
const lineStarts = this._buffers[piece.bufferIndex].lineStarts;
const pieceStartLine = piece.start.line;
const pieceEndLine = piece.end.line;
let pieceStartOffset = lineStarts[pieceStartLine] + piece.start.column;
if (danglingCR) {
if (buffer.charCodeAt(pieceStartOffset) === CharCode.LineFeed) {
// pretend the \n was in the previous piece..
pieceStartOffset++;
pieceLength--;
}
lines[linesLength++] = currentLine;
currentLine = '';
danglingCR = false;
if (pieceLength === 0) {
return true;
}
}
if (pieceStartLine === pieceEndLine) {
// this piece has no new lines
if (!this._EOLNormalized && buffer.charCodeAt(pieceStartOffset + pieceLength - 1) === CharCode.CarriageReturn) {
danglingCR = true;
currentLine += buffer.substr(pieceStartOffset, pieceLength - 1);
} else {
currentLine += buffer.substr(pieceStartOffset, pieceLength);
}
return true;
}
// add the text before the first line start in this piece
currentLine += (
this._EOLNormalized
? buffer.substring(pieceStartOffset, Math.max(pieceStartOffset, lineStarts[pieceStartLine + 1] - this._EOLLength))
: buffer.substring(pieceStartOffset, lineStarts[pieceStartLine + 1]).replace(/(\r\n|\r|\n)$/, '')
);
lines[linesLength++] = currentLine;
for (let line = pieceStartLine + 1; line < pieceEndLine; line++) {
currentLine = (
this._EOLNormalized
? buffer.substring(lineStarts[line], lineStarts[line + 1] - this._EOLLength)
: buffer.substring(lineStarts[line], lineStarts[line + 1]).replace(/(\r\n|\r|\n)$/, '')
);
lines[linesLength++] = currentLine;
}
if (!this._EOLNormalized && buffer.charCodeAt(lineStarts[pieceEndLine] + piece.end.column - 1) === CharCode.CarriageReturn) {
danglingCR = true;
if (piece.end.column === 0) {
// The last line ended with a \r, let's undo the push, it will be pushed by next iteration
linesLength--;
} else {
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column - 1);
}
} else {
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column);
}
return true;
});
if (danglingCR) {
lines[linesLength++] = currentLine;
currentLine = '';
}
lines[linesLength++] = currentLine;
return lines;
} }
public getLength(): number { public getLength(): number {
...@@ -728,7 +814,7 @@ export class PieceTreeBase { ...@@ -728,7 +814,7 @@ export class PieceTreeBase {
// #endregion // #endregion
// #region Piece Table // #region Piece Table
insert(offset: number, value: string, eolNormalized: boolean = false): void { public insert(offset: number, value: string, eolNormalized: boolean = false): void {
this._EOLNormalized = this._EOLNormalized && eolNormalized; this._EOLNormalized = this._EOLNormalized && eolNormalized;
this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = ''; this._lastVisitedLine.value = '';
...@@ -826,7 +912,7 @@ export class PieceTreeBase { ...@@ -826,7 +912,7 @@ export class PieceTreeBase {
this.computeBufferMetadata(); this.computeBufferMetadata();
} }
delete(offset: number, cnt: number): void { public delete(offset: number, cnt: number): void {
this._lastVisitedLine.lineNumber = 0; this._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = ''; this._lastVisitedLine.value = '';
...@@ -899,7 +985,7 @@ export class PieceTreeBase { ...@@ -899,7 +985,7 @@ export class PieceTreeBase {
this.computeBufferMetadata(); this.computeBufferMetadata();
} }
insertContentToNodeLeft(value: string, node: TreeNode) { private insertContentToNodeLeft(value: string, node: TreeNode) {
// we are inserting content to the beginning of node // we are inserting content to the beginning of node
let nodesToDel: TreeNode[] = []; let nodesToDel: TreeNode[] = [];
if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) { if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) {
...@@ -934,7 +1020,7 @@ export class PieceTreeBase { ...@@ -934,7 +1020,7 @@ export class PieceTreeBase {
this.deleteNodes(nodesToDel); this.deleteNodes(nodesToDel);
} }
insertContentToNodeRight(value: string, node: TreeNode) { private insertContentToNodeRight(value: string, node: TreeNode) {
// we are inserting to the right of this node. // we are inserting to the right of this node.
if (this.adjustCarriageReturnFromNext(value, node)) { if (this.adjustCarriageReturnFromNext(value, node)) {
// move \n to the new node. // move \n to the new node.
...@@ -952,9 +1038,9 @@ export class PieceTreeBase { ...@@ -952,9 +1038,9 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode); this.validateCRLFWithPrevNode(newNode);
} }
positionInBuffer(node: TreeNode, remainder: number): BufferCursor; private positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null; private positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null { private positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
let piece = node.piece; let piece = node.piece;
let bufferIndex = node.piece.bufferIndex; let bufferIndex = node.piece.bufferIndex;
let lineStarts = this._buffers[bufferIndex].lineStarts; let lineStarts = this._buffers[bufferIndex].lineStarts;
...@@ -1002,7 +1088,7 @@ export class PieceTreeBase { ...@@ -1002,7 +1088,7 @@ export class PieceTreeBase {
}; };
} }
getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number { private getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number {
// we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start. // we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start.
// now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1 // now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1
if (end.column === 0) { if (end.column === 0) {
...@@ -1032,18 +1118,18 @@ export class PieceTreeBase { ...@@ -1032,18 +1118,18 @@ export class PieceTreeBase {
} }
} }
offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number { private offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number {
let lineStarts = this._buffers[bufferIndex].lineStarts; let lineStarts = this._buffers[bufferIndex].lineStarts;
return lineStarts[cursor.line] + cursor.column; return lineStarts[cursor.line] + cursor.column;
} }
deleteNodes(nodes: TreeNode[]): void { private deleteNodes(nodes: TreeNode[]): void {
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {
rbDelete(this, nodes[i]); rbDelete(this, nodes[i]);
} }
} }
createNewPieces(text: string): Piece[] { private createNewPieces(text: string): Piece[] {
if (text.length > AverageBufferSize) { if (text.length > AverageBufferSize) {
// the content is large, operations like substring, charCode becomes slow // the content is large, operations like substring, charCode becomes slow
// so here we split it into smaller chunks, just like what we did for CR/LF normalization // so here we split it into smaller chunks, just like what we did for CR/LF normalization
...@@ -1128,11 +1214,11 @@ export class PieceTreeBase { ...@@ -1128,11 +1214,11 @@ export class PieceTreeBase {
return [newPiece]; return [newPiece];
} }
getLinesRawContent(): string { public getLinesRawContent(): string {
return this.getContentOfSubTree(this.root); return this.getContentOfSubTree(this.root);
} }
getLineRawContent(lineNumber: number, endOffset: number = 0): string { public getLineRawContent(lineNumber: number, endOffset: number = 0): string {
let x = this.root; let x = this.root;
let ret = ''; let ret = '';
...@@ -1204,7 +1290,7 @@ export class PieceTreeBase { ...@@ -1204,7 +1290,7 @@ export class PieceTreeBase {
return ret; return ret;
} }
computeBufferMetadata() { private computeBufferMetadata() {
let x = this.root; let x = this.root;
let lfCnt = 1; let lfCnt = 1;
...@@ -1222,7 +1308,7 @@ export class PieceTreeBase { ...@@ -1222,7 +1308,7 @@ export class PieceTreeBase {
} }
// #region node operations // #region node operations
getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } { private getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } {
let piece = node.piece; let piece = node.piece;
let pos = this.positionInBuffer(node, accumulatedValue); let pos = this.positionInBuffer(node, accumulatedValue);
let lineCnt = pos.line - piece.start.line; let lineCnt = pos.line - piece.start.line;
...@@ -1239,7 +1325,7 @@ export class PieceTreeBase { ...@@ -1239,7 +1325,7 @@ export class PieceTreeBase {
return { index: lineCnt, remainder: pos.column }; return { index: lineCnt, remainder: pos.column };
} }
getAccumulatedValue(node: TreeNode, index: number) { private getAccumulatedValue(node: TreeNode, index: number) {
if (index < 0) { if (index < 0) {
return 0; return 0;
} }
...@@ -1253,7 +1339,7 @@ export class PieceTreeBase { ...@@ -1253,7 +1339,7 @@ export class PieceTreeBase {
} }
} }
deleteNodeTail(node: TreeNode, pos: BufferCursor) { private deleteNodeTail(node: TreeNode, pos: BufferCursor) {
const piece = node.piece; const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt; const originalLFCnt = piece.lineFeedCnt;
const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
...@@ -1277,7 +1363,7 @@ export class PieceTreeBase { ...@@ -1277,7 +1363,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, size_delta, lf_delta); updateTreeMetadata(this, node, size_delta, lf_delta);
} }
deleteNodeHead(node: TreeNode, pos: BufferCursor) { private deleteNodeHead(node: TreeNode, pos: BufferCursor) {
const piece = node.piece; const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt; const originalLFCnt = piece.lineFeedCnt;
const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
...@@ -1299,7 +1385,7 @@ export class PieceTreeBase { ...@@ -1299,7 +1385,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, size_delta, lf_delta); updateTreeMetadata(this, node, size_delta, lf_delta);
} }
shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) { private shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) {
const piece = node.piece; const piece = node.piece;
const originalStartPos = piece.start; const originalStartPos = piece.start;
const originalEndPos = piece.end; const originalEndPos = piece.end;
...@@ -1334,7 +1420,7 @@ export class PieceTreeBase { ...@@ -1334,7 +1420,7 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode); this.validateCRLFWithPrevNode(newNode);
} }
appendToNode(node: TreeNode, value: string): void { private appendToNode(node: TreeNode, value: string): void {
if (this.adjustCarriageReturnFromNext(value, node)) { if (this.adjustCarriageReturnFromNext(value, node)) {
value += '\n'; value += '\n';
} }
...@@ -1374,7 +1460,7 @@ export class PieceTreeBase { ...@@ -1374,7 +1460,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, value.length, lf_delta); updateTreeMetadata(this, node, value.length, lf_delta);
} }
nodeAt(offset: number): NodePosition { private nodeAt(offset: number): NodePosition {
let x = this.root; let x = this.root;
let cache = this._searchCache.get(offset); let cache = this._searchCache.get(offset);
if (cache) { if (cache) {
...@@ -1409,7 +1495,7 @@ export class PieceTreeBase { ...@@ -1409,7 +1495,7 @@ export class PieceTreeBase {
return null!; return null!;
} }
nodeAt2(lineNumber: number, column: number): NodePosition { private nodeAt2(lineNumber: number, column: number): NodePosition {
let x = this.root; let x = this.root;
let nodeStartOffset = 0; let nodeStartOffset = 0;
...@@ -1476,7 +1562,7 @@ export class PieceTreeBase { ...@@ -1476,7 +1562,7 @@ export class PieceTreeBase {
return null!; return null!;
} }
nodeCharCodeAt(node: TreeNode, offset: number): number { private nodeCharCodeAt(node: TreeNode, offset: number): number {
if (node.piece.lineFeedCnt < 1) { if (node.piece.lineFeedCnt < 1) {
return -1; return -1;
} }
...@@ -1485,7 +1571,7 @@ export class PieceTreeBase { ...@@ -1485,7 +1571,7 @@ export class PieceTreeBase {
return buffer.buffer.charCodeAt(newOffset); return buffer.buffer.charCodeAt(newOffset);
} }
offsetOfNode(node: TreeNode): number { private offsetOfNode(node: TreeNode): number {
if (!node) { if (!node) {
return 0; return 0;
} }
...@@ -1504,11 +1590,11 @@ export class PieceTreeBase { ...@@ -1504,11 +1590,11 @@ export class PieceTreeBase {
// #endregion // #endregion
// #region CRLF // #region CRLF
shouldCheckCRLF() { private shouldCheckCRLF() {
return !(this._EOLNormalized && this._EOL === '\n'); return !(this._EOLNormalized && this._EOL === '\n');
} }
startWithLF(val: string | TreeNode): boolean { private startWithLF(val: string | TreeNode): boolean {
if (typeof val === 'string') { if (typeof val === 'string') {
return val.charCodeAt(0) === 10; return val.charCodeAt(0) === 10;
} }
...@@ -1532,7 +1618,7 @@ export class PieceTreeBase { ...@@ -1532,7 +1618,7 @@ export class PieceTreeBase {
return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10; return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10;
} }
endWithCR(val: string | TreeNode): boolean { private endWithCR(val: string | TreeNode): boolean {
if (typeof val === 'string') { if (typeof val === 'string') {
return val.charCodeAt(val.length - 1) === 13; return val.charCodeAt(val.length - 1) === 13;
} }
...@@ -1544,7 +1630,7 @@ export class PieceTreeBase { ...@@ -1544,7 +1630,7 @@ export class PieceTreeBase {
return this.nodeCharCodeAt(val, val.piece.length - 1) === 13; return this.nodeCharCodeAt(val, val.piece.length - 1) === 13;
} }
validateCRLFWithPrevNode(nextNode: TreeNode) { private validateCRLFWithPrevNode(nextNode: TreeNode) {
if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) { if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) {
let node = nextNode.prev(); let node = nextNode.prev();
if (this.endWithCR(node)) { if (this.endWithCR(node)) {
...@@ -1553,7 +1639,7 @@ export class PieceTreeBase { ...@@ -1553,7 +1639,7 @@ export class PieceTreeBase {
} }
} }
validateCRLFWithNextNode(node: TreeNode) { private validateCRLFWithNextNode(node: TreeNode) {
if (this.shouldCheckCRLF() && this.endWithCR(node)) { if (this.shouldCheckCRLF() && this.endWithCR(node)) {
let nextNode = node.next(); let nextNode = node.next();
if (this.startWithLF(nextNode)) { if (this.startWithLF(nextNode)) {
...@@ -1562,7 +1648,7 @@ export class PieceTreeBase { ...@@ -1562,7 +1648,7 @@ export class PieceTreeBase {
} }
} }
fixCRLF(prev: TreeNode, next: TreeNode) { private fixCRLF(prev: TreeNode, next: TreeNode) {
let nodesToDel: TreeNode[] = []; let nodesToDel: TreeNode[] = [];
// update node // update node
let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts; let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts;
...@@ -1617,7 +1703,7 @@ export class PieceTreeBase { ...@@ -1617,7 +1703,7 @@ export class PieceTreeBase {
} }
} }
adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean { private adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean {
if (this.shouldCheckCRLF() && this.endWithCR(value)) { if (this.shouldCheckCRLF() && this.endWithCR(value)) {
let nextNode = node.next(); let nextNode = node.next();
if (this.startWithLF(nextNode)) { if (this.startWithLF(nextNode)) {
...@@ -1667,7 +1753,7 @@ export class PieceTreeBase { ...@@ -1667,7 +1753,7 @@ export class PieceTreeBase {
return callback(node) && this.iterate(node.right, callback); return callback(node) && this.iterate(node.right, callback);
} }
getNodeContent(node: TreeNode) { private getNodeContent(node: TreeNode) {
if (node === SENTINEL) { if (node === SENTINEL) {
return ''; return '';
} }
...@@ -1695,7 +1781,7 @@ export class PieceTreeBase { ...@@ -1695,7 +1781,7 @@ export class PieceTreeBase {
* / * /
* z * z
*/ */
rbInsertRight(node: TreeNode | null, p: Piece): TreeNode { private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red); let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL; z.left = SENTINEL;
z.right = SENTINEL; z.right = SENTINEL;
...@@ -1727,7 +1813,7 @@ export class PieceTreeBase { ...@@ -1727,7 +1813,7 @@ export class PieceTreeBase {
* \ * \
* z * z
*/ */
rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode { private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red); let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL; z.left = SENTINEL;
z.right = SENTINEL; z.right = SENTINEL;
...@@ -1751,7 +1837,7 @@ export class PieceTreeBase { ...@@ -1751,7 +1837,7 @@ export class PieceTreeBase {
return z; return z;
} }
getContentOfSubTree(node: TreeNode): string { private getContentOfSubTree(node: TreeNode): string {
let str = ''; let str = '';
this.iterate(node, node => { this.iterate(node, node => {
......
...@@ -66,6 +66,7 @@ export class RenderLineInput { ...@@ -66,6 +66,7 @@ export class RenderLineInput {
public readonly lineTokens: IViewLineTokens; public readonly lineTokens: IViewLineTokens;
public readonly lineDecorations: LineDecoration[]; public readonly lineDecorations: LineDecoration[];
public readonly tabSize: number; public readonly tabSize: number;
public readonly startVisibleColumn: number;
public readonly spaceWidth: number; public readonly spaceWidth: number;
public readonly stopRenderingLineAfter: number; public readonly stopRenderingLineAfter: number;
public readonly renderWhitespace: RenderWhitespace; public readonly renderWhitespace: RenderWhitespace;
...@@ -89,6 +90,7 @@ export class RenderLineInput { ...@@ -89,6 +90,7 @@ export class RenderLineInput {
lineTokens: IViewLineTokens, lineTokens: IViewLineTokens,
lineDecorations: LineDecoration[], lineDecorations: LineDecoration[],
tabSize: number, tabSize: number,
startVisibleColumn: number,
spaceWidth: number, spaceWidth: number,
stopRenderingLineAfter: number, stopRenderingLineAfter: number,
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all', renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
...@@ -106,6 +108,7 @@ export class RenderLineInput { ...@@ -106,6 +108,7 @@ export class RenderLineInput {
this.lineTokens = lineTokens; this.lineTokens = lineTokens;
this.lineDecorations = lineDecorations; this.lineDecorations = lineDecorations;
this.tabSize = tabSize; this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
this.spaceWidth = spaceWidth; this.spaceWidth = spaceWidth;
this.stopRenderingLineAfter = stopRenderingLineAfter; this.stopRenderingLineAfter = stopRenderingLineAfter;
this.renderWhitespace = ( this.renderWhitespace = (
...@@ -154,6 +157,7 @@ export class RenderLineInput { ...@@ -154,6 +157,7 @@ export class RenderLineInput {
&& this.containsRTL === other.containsRTL && this.containsRTL === other.containsRTL
&& this.fauxIndentLength === other.fauxIndentLength && this.fauxIndentLength === other.fauxIndentLength
&& this.tabSize === other.tabSize && this.tabSize === other.tabSize
&& this.startVisibleColumn === other.startVisibleColumn
&& this.spaceWidth === other.spaceWidth && this.spaceWidth === other.spaceWidth
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.renderWhitespace === other.renderWhitespace && this.renderWhitespace === other.renderWhitespace
...@@ -368,7 +372,9 @@ class ResolvedRenderLineInput { ...@@ -368,7 +372,9 @@ class ResolvedRenderLineInput {
public readonly isOverflowing: boolean, public readonly isOverflowing: boolean,
public readonly parts: LinePart[], public readonly parts: LinePart[],
public readonly containsForeignElements: ForeignElementType, public readonly containsForeignElements: ForeignElementType,
public readonly fauxIndentLength: number,
public readonly tabSize: number, public readonly tabSize: number,
public readonly startVisibleColumn: number,
public readonly containsRTL: boolean, public readonly containsRTL: boolean,
public readonly spaceWidth: number, public readonly spaceWidth: number,
public readonly renderWhitespace: RenderWhitespace, public readonly renderWhitespace: RenderWhitespace,
...@@ -395,7 +401,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput ...@@ -395,7 +401,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len); let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) { if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary); tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
} }
let containsForeignElements = ForeignElementType.None; let containsForeignElements = ForeignElementType.None;
if (input.lineDecorations.length > 0) { if (input.lineDecorations.length > 0) {
...@@ -425,7 +431,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput ...@@ -425,7 +431,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
isOverflowing, isOverflowing,
tokens, tokens,
containsForeignElements, containsForeignElements,
input.fauxIndentLength,
input.tabSize, input.tabSize,
input.startVisibleColumn,
input.containsRTL, input.containsRTL,
input.spaceWidth, input.spaceWidth,
input.renderWhitespace, input.renderWhitespace,
...@@ -537,7 +545,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: ...@@ -537,7 +545,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;. * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
* The rendering phase will generate `style="width:..."` for these tokens. * The rendering phase will generate `style="width:..."` for these tokens.
*/ */
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] { function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
let result: LinePart[] = [], resultLen = 0; let result: LinePart[] = [], resultLen = 0;
let tokenIndex = 0; let tokenIndex = 0;
...@@ -555,21 +563,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW ...@@ -555,21 +563,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent); lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
} }
let tmpIndent = 0;
for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
if (chCode === CharCode.Tab) {
tmpIndent = tabSize;
} else if (strings.isFullWidthCharacter(chCode)) {
tmpIndent += 2;
} else {
tmpIndent++;
}
}
tmpIndent = tmpIndent % tabSize;
let wasInWhitespace = false; let wasInWhitespace = false;
let currentSelectionIndex = 0; let currentSelectionIndex = 0;
let currentSelection = selections && selections[currentSelectionIndex]; let currentSelection = selections && selections[currentSelectionIndex];
let tmpIndent = startVisibleColumn % tabSize;
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) { for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex); const chCode = lineContent.charCodeAt(charIndex);
...@@ -729,7 +726,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render ...@@ -729,7 +726,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const len = input.len; const len = input.len;
const isOverflowing = input.isOverflowing; const isOverflowing = input.isOverflowing;
const parts = input.parts; const parts = input.parts;
const fauxIndentLength = input.fauxIndentLength;
const tabSize = input.tabSize; const tabSize = input.tabSize;
const startVisibleColumn = input.startVisibleColumn;
const containsRTL = input.containsRTL; const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth; const spaceWidth = input.spaceWidth;
const renderWhitespace = input.renderWhitespace; const renderWhitespace = input.renderWhitespace;
...@@ -738,7 +737,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render ...@@ -738,7 +737,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const characterMapping = new CharacterMapping(len + 1, parts.length); const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0; let charIndex = 0;
let tabsCharDelta = 0; let visibleColumn = startVisibleColumn;
let charOffsetInPart = 0; let charOffsetInPart = 0;
let prevPartContentCnt = 0; let prevPartContentCnt = 0;
...@@ -764,18 +763,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render ...@@ -764,18 +763,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
let partContentCnt = 0; let partContentCnt = 0;
{ {
let _charIndex = charIndex; let _charIndex = charIndex;
let _tabsCharDelta = tabsCharDelta; let _visibleColumn = visibleColumn;
for (; _charIndex < partEndIndex; _charIndex++) { for (; _charIndex < partEndIndex; _charIndex++) {
const charCode = lineContent.charCodeAt(_charIndex); const charCode = lineContent.charCodeAt(_charIndex);
const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
if (charCode === CharCode.Tab) { partContentCnt += charWidth;
let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize; if (_charIndex >= fauxIndentLength) {
_tabsCharDelta += insertSpacesCount - 1; _visibleColumn += charWidth;
partContentCnt += insertSpacesCount;
} else {
// must be CharCode.Space
partContentCnt++;
} }
} }
} }
...@@ -793,29 +788,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render ...@@ -793,29 +788,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
for (; charIndex < partEndIndex; charIndex++) { for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset); characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex); const charCode = lineContent.charCodeAt(charIndex);
let charWidth: number;
if (charCode === CharCode.Tab) { if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1; if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
if (insertSpacesCount > 0) { sb.write1(0x2192); // RIGHTWARDS ARROW
if (!canUseHalfwidthRightwardsArrow || insertSpacesCount > 1) { } else {
sb.write1(0x2192); // RIGHTWARDS ARROW sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
} else {
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
}
insertSpacesCount--;
} }
while (insertSpacesCount > 0) { for (let space = 2; space <= charWidth; space++) {
sb.write1(0xA0); // &nbsp; sb.write1(0xA0); // &nbsp;
insertSpacesCount--;
} }
} else {
// must be CharCode.Space } else { // must be CharCode.Space
charWidth = 1;
sb.write1(0xB7); // &middot; sb.write1(0xB7); // &middot;
} }
charOffsetInPart++; charOffsetInPart += charWidth;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
} }
prevPartContentCnt = partContentCnt; prevPartContentCnt = partContentCnt;
...@@ -833,63 +829,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render ...@@ -833,63 +829,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset); characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex); const charCode = lineContent.charCodeAt(charIndex);
let producedCharacters = 1;
let charWidth = 1;
switch (charCode) { switch (charCode) {
case CharCode.Tab: case CharCode.Tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; producedCharacters = (tabSize - (visibleColumn % tabSize));
tabsCharDelta += insertSpacesCount - 1; charWidth = producedCharacters;
charOffsetInPart += insertSpacesCount - 1; for (let space = 1; space <= producedCharacters; space++) {
while (insertSpacesCount > 0) {
sb.write1(0xA0); // &nbsp; sb.write1(0xA0); // &nbsp;
partContentCnt++;
insertSpacesCount--;
} }
break; break;
case CharCode.Space: case CharCode.Space:
sb.write1(0xA0); // &nbsp; sb.write1(0xA0); // &nbsp;
partContentCnt++;
break; break;
case CharCode.LessThan: case CharCode.LessThan:
sb.appendASCIIString('&lt;'); sb.appendASCIIString('&lt;');
partContentCnt++;
break; break;
case CharCode.GreaterThan: case CharCode.GreaterThan:
sb.appendASCIIString('&gt;'); sb.appendASCIIString('&gt;');
partContentCnt++;
break; break;
case CharCode.Ampersand: case CharCode.Ampersand:
sb.appendASCIIString('&amp;'); sb.appendASCIIString('&amp;');
partContentCnt++;
break; break;
case CharCode.Null: case CharCode.Null:
sb.appendASCIIString('&#00;'); sb.appendASCIIString('&#00;');
partContentCnt++;
break; break;
case CharCode.UTF8_BOM: case CharCode.UTF8_BOM:
case CharCode.LINE_SEPARATOR_2028: case CharCode.LINE_SEPARATOR_2028:
sb.write1(0xFFFD); sb.write1(0xFFFD);
partContentCnt++;
break; break;
default: default:
if (strings.isFullWidthCharacter(charCode)) { if (strings.isFullWidthCharacter(charCode)) {
tabsCharDelta++; charWidth++;
} }
if (renderControlCharacters && charCode < 32) { if (renderControlCharacters && charCode < 32) {
sb.write1(9216 + charCode); sb.write1(9216 + charCode);
partContentCnt++;
} else { } else {
sb.write1(charCode); sb.write1(charCode);
partContentCnt++;
} }
} }
charOffsetInPart++; charOffsetInPart += producedCharacters;
partContentCnt += producedCharacters;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
} }
prevPartContentCnt = partContentCnt; prevPartContentCnt = partContentCnt;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { toUint32Array } from 'vs/base/common/uint';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';
const enum CharacterClass {
NONE = 0,
BREAK_BEFORE = 1,
BREAK_AFTER = 2,
BREAK_OBTRUSIVE = 3,
BREAK_IDEOGRAPHIC = 4 // for Han and Kana.
}
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
constructor(BREAK_BEFORE: string, BREAK_AFTER: string, BREAK_OBTRUSIVE: string) {
super(CharacterClass.NONE);
for (let i = 0; i < BREAK_BEFORE.length; i++) {
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
}
for (let i = 0; i < BREAK_AFTER.length; i++) {
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
}
for (let i = 0; i < BREAK_OBTRUSIVE.length; i++) {
this.set(BREAK_OBTRUSIVE.charCodeAt(i), CharacterClass.BREAK_OBTRUSIVE);
}
}
public get(charCode: number): CharacterClass {
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
if (
(charCode >= 0x3040 && charCode <= 0x30FF)
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
) {
return CharacterClass.BREAK_IDEOGRAPHIC;
}
return super.get(charCode);
}
}
export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory {
private readonly classifier: WrappingCharacterClassifier;
constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) {
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, breakObtrusiveChars);
}
// TODO@Alex -> duplicated in lineCommentCommand
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
currentVisibleColumn = +currentVisibleColumn; //@perf
tabSize = +tabSize; //@perf
columnSize = +columnSize; //@perf
if (isTab) {
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
}
return currentVisibleColumn + columnSize;
}
public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null {
if (breakingColumn === -1) {
return null;
}
tabSize = +tabSize; //@perf
breakingColumn = +breakingColumn; //@perf
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
hardWrappingIndent = +hardWrappingIndent; //@perf
let wrappedTextIndentVisibleColumn = 0;
let wrappedTextIndent = '';
let firstNonWhitespaceIndex = -1;
if (hardWrappingIndent !== WrappingIndent.None) {
firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
if (firstNonWhitespaceIndex !== -1) {
// Track existing indent
wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1);
}
// Increase indent of continuation lines, if desired
let numberOfAdditionalTabs = 0;
if (hardWrappingIndent === WrappingIndent.Indent) {
numberOfAdditionalTabs = 1;
} else if (hardWrappingIndent === WrappingIndent.DeepIndent) {
numberOfAdditionalTabs = 2;
}
for (let i = 0; i < numberOfAdditionalTabs; i++) {
wrappedTextIndent += '\t';
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
}
// Force sticking to beginning of line if no character would fit except for the indentation
if (wrappedTextIndentVisibleColumn + columnsForFullWidthChar > breakingColumn) {
wrappedTextIndent = '';
wrappedTextIndentVisibleColumn = 0;
}
}
}
let classifier = this.classifier;
let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened
let breakingLengths: number[] = []; // The length of each broken-up line text
let breakingLengthsIndex: number = 0; // The count of breaks already done
let visibleColumn = 0; // Visible column since the beginning of the current line
let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable)
let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset`
let obtrusiveBreakOffset = -1; // Last index of a character that indicates a break should happen before it (less desirable)
let obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset`
let len = lineText.length;
for (let i = 0; i < len; i++) {
// At this point, there is a certainty that the character before `i` fits on the current line,
// but the character at `i` might not fit
let charCode = lineText.charCodeAt(i);
let charCodeIsTab = (charCode === CharCode.Tab);
let charCodeClass = classifier.get(charCode);
if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
// => advance visibleColumn by 1 and advance to next char code...
visibleColumn = visibleColumn + 1;
continue;
}
if (charCodeClass === CharacterClass.BREAK_BEFORE) {
// This is a character that indicates that a break should happen before it
// Since we are certain the character before `i` fits, there's no extra checking needed,
// just mark it as a nice breaking opportunity
niceBreakOffset = i;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
// CJK breaking : before break
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) {
let prevCode = lineText.charCodeAt(i - 1);
let prevClass = classifier.get(prevCode);
if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket
niceBreakOffset = i;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
let charColumnSize = 1;
if (strings.isFullWidthCharacter(charCode)) {
charColumnSize = columnsForFullWidthChar;
}
// Advance visibleColumn with character at `i`
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize);
if (visibleColumn > breakingColumn && i !== 0) {
// We need to break at least before character at `i`:
// - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i))
// - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i))
// - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i))
let breakBeforeOffset: number;
let restoreVisibleColumnFrom: number;
if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) {
// We will break before `niceBreakLastOffset`
breakBeforeOffset = niceBreakOffset;
restoreVisibleColumnFrom = niceBreakVisibleColumn;
} else if (obtrusiveBreakOffset !== -1 && obtrusiveBreakVisibleColumn <= breakingColumn) {
// We will break before `obtrusiveBreakLastOffset`
breakBeforeOffset = obtrusiveBreakOffset;
restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn;
} else {
// We will break before `i`
breakBeforeOffset = i;
restoreVisibleColumnFrom = wrappedTextIndentVisibleColumn;
}
// Break before character at `breakBeforeOffset`
breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset;
lastBreakingOffset = breakBeforeOffset;
// Re-establish visibleColumn by taking character at `i` into account
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize);
// Reset markers
niceBreakOffset = -1;
niceBreakVisibleColumn = 0;
obtrusiveBreakOffset = -1;
obtrusiveBreakVisibleColumn = 0;
}
// At this point, there is a certainty that the character at `i` fits on the current line
if (niceBreakOffset !== -1) {
// Advance niceBreakVisibleColumn
niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
}
if (obtrusiveBreakOffset !== -1) {
// Advance obtrusiveBreakVisibleColumn
obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
}
if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) {
// This is a character that indicates that a break should happen after it
niceBreakOffset = i + 1;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
// CJK breaking : after break
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) {
let nextCode = lineText.charCodeAt(i + 1);
let nextClass = classifier.get(nextCode);
if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period
niceBreakOffset = i + 1;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
if (charCodeClass === CharacterClass.BREAK_OBTRUSIVE) {
// This is an obtrusive character that indicates that a break should happen after it
obtrusiveBreakOffset = i + 1;
obtrusiveBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
if (breakingLengthsIndex === 0) {
return null;
}
// Add last segment
breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;
return new CharacterHardWrappingLineMapping(
new PrefixSumComputer(toUint32Array(breakingLengths)),
wrappedTextIndent
);
}
}
export class CharacterHardWrappingLineMapping implements ILineMapping {
private readonly _prefixSums: PrefixSumComputer;
private readonly _wrappedLinesIndent: string;
constructor(prefixSums: PrefixSumComputer, wrappedLinesIndent: string) {
this._prefixSums = prefixSums;
this._wrappedLinesIndent = wrappedLinesIndent;
}
public getOutputLineCount(): number {
return this._prefixSums.getCount();
}
public getWrappedLinesIndent(): string {
return this._wrappedLinesIndent;
}
public getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number {
if (outputLineIndex === 0) {
return outputOffset;
} else {
return this._prefixSums.getAccumulatedValue(outputLineIndex - 1) + outputOffset;
}
}
public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition {
let r = this._prefixSums.getIndexOf(inputOffset);
return new OutputPosition(r.index, r.remainder);
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
const enum CharacterClass {
NONE = 0,
BREAK_BEFORE = 1,
BREAK_AFTER = 2,
BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
}
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
super(CharacterClass.NONE);
for (let i = 0; i < BREAK_BEFORE.length; i++) {
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
}
for (let i = 0; i < BREAK_AFTER.length; i++) {
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
}
}
public get(charCode: number): CharacterClass {
if (charCode >= 0 && charCode < 256) {
return <CharacterClass>this._asciiMap[charCode];
} else {
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
if (
(charCode >= 0x3040 && charCode <= 0x30FF)
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
) {
return CharacterClass.BREAK_IDEOGRAPHIC;
}
return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
}
}
}
export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
return new MonospaceLineBreaksComputerFactory(
options.get(EditorOption.wordWrapBreakBeforeCharacters),
options.get(EditorOption.wordWrapBreakAfterCharacters)
);
}
private readonly classifier: WrappingCharacterClassifier;
constructor(breakBeforeChars: string, breakAfterChars: string) {
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
}
public createLineBreaksComputer(tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
tabSize = tabSize | 0; //@perf
wrappingColumn = +wrappingColumn; //@perf
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
let requests: string[] = [];
let previousBreakingData: (LineBreakData | null)[] = [];
return {
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
requests.push(lineText);
previousBreakingData.push(previousLineBreakData);
},
finalize: () => {
let result: (LineBreakData | null)[] = [];
for (let i = 0, len = requests.length; i < len; i++) {
const previousLineBreakData = previousBreakingData[i];
if (previousLineBreakData) {
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
} else {
result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
}
}
return result;
}
};
}
}
let arrPool1: number[] = [];
let arrPool2: number[] = [];
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
if (firstLineBreakColumn === -1) {
return null;
}
const len = lineText.length;
if (len <= 1) {
return null;
}
const prevBreakingOffsets = previousBreakingData.breakOffsets;
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn;
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
let breakingOffsets: number[] = arrPool1;
let breakingOffsetsVisibleColumn: number[] = arrPool2;
let breakingOffsetsCount: number = 0;
let breakingColumn = firstLineBreakColumn;
const prevLen = prevBreakingOffsets.length;
let prevIndex = 0;
if (prevIndex >= 0) {
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
while (prevIndex + 1 < prevLen) {
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
if (distance >= bestDistance) {
break;
}
bestDistance = distance;
prevIndex++;
}
}
while (prevIndex < prevLen) {
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
let breakOffset = 0;
let breakOffsetVisibleColumn = 0;
let forcedBreakOffset = 0;
let forcedBreakOffsetVisibleColumn = 0;
// initially, we search as much as possible to the right (if it fits)
if (prevBreakoffsetVisibleColumn <= breakingColumn) {
let visibleColumn = prevBreakoffsetVisibleColumn;
let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1);
let prevCharCodeClass = classifier.get(prevCharCode);
let entireLineFits = true;
for (let i = prevBreakOffset; i < len; i++) {
const charStartOffset = i;
const charCode = lineText.charCodeAt(i);
let charCodeClass: number;
let charWidth: number;
if (strings.isHighSurrogate(charCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i++;
charCodeClass = CharacterClass.NONE;
charWidth = 2;
} else {
charCodeClass = classifier.get(charCode);
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
}
if (canBreak(prevCharCodeClass, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
}
visibleColumn += charWidth;
// check if adding character at `i` will go over the breaking column
if (visibleColumn > breakingColumn) {
// We need to break at least before character at `i`:
forcedBreakOffset = charStartOffset;
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
// Cannot break at `breakOffset` => reset it if it was set
breakOffset = 0;
}
entireLineFits = false;
break;
}
prevCharCode = charCode;
prevCharCodeClass = charCodeClass;
}
if (entireLineFits) {
// there is no more need to break => stop the outer loop!
if (breakingOffsetsCount > 0) {
// Add last segment
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
breakingOffsetsCount++;
}
break;
}
}
if (breakOffset === 0) {
// must search left
let visibleColumn = prevBreakoffsetVisibleColumn;
let charCode = lineText.charCodeAt(prevBreakOffset);
let charCodeClass = classifier.get(charCode);
let hitATabCharacter = false;
for (let i = prevBreakOffset - 1; i >= 0; i--) {
const charStartOffset = i + 1;
const prevCharCode = lineText.charCodeAt(i);
if (prevCharCode === CharCode.Tab) {
// cannot determine the width of a tab when going backwards, so we must go forwards
hitATabCharacter = true;
break;
}
let prevCharCodeClass: number;
let prevCharWidth: number;
if (strings.isLowSurrogate(prevCharCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i--;
prevCharCodeClass = CharacterClass.NONE;
prevCharWidth = 2;
} else {
prevCharCodeClass = classifier.get(prevCharCode);
prevCharWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1);
}
if (visibleColumn <= breakingColumn) {
if (forcedBreakOffset === 0) {
forcedBreakOffset = charStartOffset;
forcedBreakOffsetVisibleColumn = visibleColumn;
}
if (visibleColumn <= breakingColumn - wrappedLineBreakColumn) {
// went too far!
break;
}
if (canBreak(prevCharCodeClass, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
break;
}
}
visibleColumn -= prevCharWidth;
charCode = prevCharCode;
charCodeClass = prevCharCodeClass;
}
if (breakOffset !== 0) {
const remainingWidthOfNextLine = wrappedLineBreakColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn);
if (remainingWidthOfNextLine <= tabSize) {
const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset);
let charWidth: number;
if (strings.isHighSurrogate(charCodeAtForcedBreakOffset)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
charWidth = 2;
} else {
charWidth = computeCharWidth(charCodeAtForcedBreakOffset, forcedBreakOffsetVisibleColumn, tabSize, columnsForFullWidthChar);
}
if (remainingWidthOfNextLine - charWidth < 0) {
// it is not worth it to break at breakOffset, it just introduces an extra needless line!
breakOffset = 0;
}
}
}
if (hitATabCharacter) {
// cannot determine the width of a tab when going backwards, so we must go forwards from the previous break
prevIndex--;
continue;
}
}
if (breakOffset === 0) {
// Could not find a good breaking point
breakOffset = forcedBreakOffset;
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
}
breakingOffsets[breakingOffsetsCount] = breakOffset;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
breakingOffsetsCount++;
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) {
prevIndex++;
}
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
while (prevIndex + 1 < prevLen) {
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
if (distance >= bestDistance) {
break;
}
bestDistance = distance;
prevIndex++;
}
}
if (breakingOffsetsCount === 0) {
return null;
}
// Doing here some object reuse which ends up helping a huge deal with GC pauses!
breakingOffsets.length = breakingOffsetsCount;
breakingOffsetsVisibleColumn.length = breakingOffsetsCount;
arrPool1 = previousBreakingData.breakOffsets;
arrPool2 = previousBreakingData.breakOffsetsVisibleColumn;
previousBreakingData.breakOffsets = breakingOffsets;
previousBreakingData.breakOffsetsVisibleColumn = breakingOffsetsVisibleColumn;
previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength;
return previousBreakingData;
}
function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
if (firstLineBreakColumn === -1) {
return null;
}
const len = lineText.length;
if (len <= 1) {
return null;
}
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
let breakingOffsets: number[] = [];
let breakingOffsetsVisibleColumn: number[] = [];
let breakingOffsetsCount: number = 0;
let breakOffset = 0;
let breakOffsetVisibleColumn = 0;
let breakingColumn = firstLineBreakColumn;
let prevCharCode = lineText.charCodeAt(0);
let prevCharCodeClass = classifier.get(prevCharCode);
let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
let startOffset = 1;
if (strings.isHighSurrogate(prevCharCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
visibleColumn += 1;
prevCharCode = lineText.charCodeAt(1);
prevCharCodeClass = classifier.get(prevCharCode);
startOffset++;
}
for (let i = startOffset; i < len; i++) {
const charStartOffset = i;
const charCode = lineText.charCodeAt(i);
let charCodeClass: number;
let charWidth: number;
if (strings.isHighSurrogate(charCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i++;
charCodeClass = CharacterClass.NONE;
charWidth = 2;
} else {
charCodeClass = classifier.get(charCode);
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
}
if (canBreak(prevCharCodeClass, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
}
visibleColumn += charWidth;
// check if adding character at `i` will go over the breaking column
if (visibleColumn > breakingColumn) {
// We need to break at least before character at `i`:
if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
// Cannot break at `breakOffset`, must break at `i`
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn - charWidth;
}
breakingOffsets[breakingOffsetsCount] = breakOffset;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
breakingOffsetsCount++;
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
breakOffset = 0;
}
prevCharCode = charCode;
prevCharCodeClass = charCodeClass;
}
if (breakingOffsetsCount === 0) {
return null;
}
// Add last segment
breakingOffsets[breakingOffsetsCount] = len;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn;
return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
}
function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
if (charCode === CharCode.Tab) {
return (tabSize - (visibleColumn % tabSize));
}
if (strings.isFullWidthCharacter(charCode)) {
return columnsForFullWidthChar;
}
return 1;
}
function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
return (tabSize - (visibleColumn % tabSize));
}
/**
* Kinsoku Shori : Don't break after a leading character, like an open bracket
* Kinsoku Shori : Don't break before a trailing character, like a period
*/
function canBreak(prevCharCodeClass: CharacterClass, charCodeClass: CharacterClass): boolean {
return (
(prevCharCodeClass === CharacterClass.BREAK_AFTER)
|| (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
|| (charCodeClass === CharacterClass.BREAK_BEFORE)
|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
);
}
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number {
let wrappedTextIndentLength = 0;
if (wrappingIndent !== WrappingIndent.None) {
const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
if (firstNonWhitespaceIndex !== -1) {
// Track existing indent
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
wrappedTextIndentLength += charWidth;
}
// Increase indent of continuation lines, if desired
const numberOfAdditionalTabs = (wrappingIndent === WrappingIndent.DeepIndent ? 2 : wrappingIndent === WrappingIndent.Indent ? 1 : 0);
for (let i = 0; i < numberOfAdditionalTabs; i++) {
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
wrappedTextIndentLength += charWidth;
}
// Force sticking to beginning of line if no character would fit except for the indentation
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) {
wrappedTextIndentLength = 0;
}
}
}
return wrappedTextIndentLength;
}
...@@ -187,73 +187,3 @@ export class PrefixSumComputer { ...@@ -187,73 +187,3 @@ export class PrefixSumComputer {
return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart); return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart);
} }
} }
export class PrefixSumComputerWithCache {
private readonly _actual: PrefixSumComputer;
private _cacheAccumulatedValueStart: number = 0;
private _cache: PrefixSumIndexOfResult[] | null = null;
constructor(values: Uint32Array) {
this._actual = new PrefixSumComputer(values);
this._bustCache();
}
private _bustCache(): void {
this._cacheAccumulatedValueStart = 0;
this._cache = null;
}
public insertValues(insertIndex: number, insertValues: Uint32Array): void {
if (this._actual.insertValues(insertIndex, insertValues)) {
this._bustCache();
}
}
public changeValue(index: number, value: number): void {
if (this._actual.changeValue(index, value)) {
this._bustCache();
}
}
public removeValues(startIndex: number, cnt: number): void {
if (this._actual.removeValues(startIndex, cnt)) {
this._bustCache();
}
}
public getTotalValue(): number {
return this._actual.getTotalValue();
}
public getAccumulatedValue(index: number): number {
return this._actual.getAccumulatedValue(index);
}
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
accumulatedValue = Math.floor(accumulatedValue); //@perf
if (this._cache !== null) {
let cacheIndex = accumulatedValue - this._cacheAccumulatedValueStart;
if (cacheIndex >= 0 && cacheIndex < this._cache.length) {
// Cache hit!
return this._cache[cacheIndex];
}
}
// Cache miss!
return this._actual.getIndexOf(accumulatedValue);
}
/**
* Gives a hint that a lot of requests are about to come in for these accumulated values.
*/
public warmUpCache(accumulatedValueStart: number, accumulatedValueEnd: number): void {
let newCache: PrefixSumIndexOfResult[] = [];
for (let accumulatedValue = accumulatedValueStart; accumulatedValue <= accumulatedValueEnd; accumulatedValue++) {
newCache[accumulatedValue - accumulatedValueStart] = this.getIndexOf(accumulatedValue);
}
this._cache = newCache;
this._cacheAccumulatedValueStart = accumulatedValueStart;
}
}
...@@ -174,6 +174,10 @@ export class ViewLineData { ...@@ -174,6 +174,10 @@ export class ViewLineData {
* The maximum allowed column at this view line. * The maximum allowed column at this view line.
*/ */
public readonly maxColumn: number; public readonly maxColumn: number;
/**
* The visible column at the start of the line (after the fauxIndent).
*/
public readonly startVisibleColumn: number;
/** /**
* The tokens at this view line. * The tokens at this view line.
*/ */
...@@ -184,12 +188,14 @@ export class ViewLineData { ...@@ -184,12 +188,14 @@ export class ViewLineData {
continuesWithWrappedLine: boolean, continuesWithWrappedLine: boolean,
minColumn: number, minColumn: number,
maxColumn: number, maxColumn: number,
startVisibleColumn: number,
tokens: IViewLineTokens tokens: IViewLineTokens
) { ) {
this.content = content; this.content = content;
this.continuesWithWrappedLine = continuesWithWrappedLine; this.continuesWithWrappedLine = continuesWithWrappedLine;
this.minColumn = minColumn; this.minColumn = minColumn;
this.maxColumn = maxColumn; this.maxColumn = maxColumn;
this.startVisibleColumn = startVisibleColumn;
this.tokens = tokens; this.tokens = tokens;
} }
} }
...@@ -231,6 +237,10 @@ export class ViewLineRenderingData { ...@@ -231,6 +237,10 @@ export class ViewLineRenderingData {
* The tab size for this view model. * The tab size for this view model.
*/ */
public readonly tabSize: number; public readonly tabSize: number;
/**
* The visible column at the start of the line (after the fauxIndent)
*/
public readonly startVisibleColumn: number;
constructor( constructor(
minColumn: number, minColumn: number,
...@@ -241,7 +251,8 @@ export class ViewLineRenderingData { ...@@ -241,7 +251,8 @@ export class ViewLineRenderingData {
mightContainNonBasicASCII: boolean, mightContainNonBasicASCII: boolean,
tokens: IViewLineTokens, tokens: IViewLineTokens,
inlineDecorations: InlineDecoration[], inlineDecorations: InlineDecoration[],
tabSize: number tabSize: number,
startVisibleColumn: number
) { ) {
this.minColumn = minColumn; this.minColumn = minColumn;
this.maxColumn = maxColumn; this.maxColumn = maxColumn;
...@@ -254,6 +265,7 @@ export class ViewLineRenderingData { ...@@ -254,6 +265,7 @@ export class ViewLineRenderingData {
this.tokens = tokens; this.tokens = tokens;
this.inlineDecorations = inlineDecorations; this.inlineDecorations = inlineDecorations;
this.tabSize = tabSize; this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
} }
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean { public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {
......
...@@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; ...@@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker'; import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker';
import * as viewEvents from 'vs/editor/common/view/viewEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations'; import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { ITheme } from 'vs/platform/theme/common/themeService'; import { ITheme } from 'vs/platform/theme/common/themeService';
...@@ -43,7 +42,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -43,7 +42,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
public readonly viewLayout: ViewLayout; public readonly viewLayout: ViewLayout;
private readonly decorations: ViewModelDecorations; private readonly decorations: ViewModelDecorations;
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { constructor(
editorId: number,
configuration: editorCommon.IConfiguration,
model: ITextModel,
lineMapperFactory: ILineBreaksComputerFactory,
scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable
) {
super(); super();
this.editorId = editorId; this.editorId = editorId;
...@@ -63,20 +68,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -63,20 +68,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const options = this.configuration.options; const options = this.configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo); const wrappingInfo = options.get(EditorOption.wrappingInfo);
const fontInfo = options.get(EditorOption.fontInfo); const fontInfo = options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingIndent = options.get(EditorOption.wrappingIndent); const wrappingIndent = options.get(EditorOption.wrappingIndent);
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
);
this.lines = new SplitLinesCollection( this.lines = new SplitLinesCollection(
this.model, this.model,
hardWrappingLineMapperFactory, lineMapperFactory,
this.model.getOptions().tabSize, this.model.getOptions().tabSize,
wrappingInfo.wrappingColumn, wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
...@@ -200,8 +196,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -200,8 +196,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const changes = e.changes; const changes = e.changes;
const versionId = e.versionId; const versionId = e.versionId;
for (let j = 0, lenJ = changes.length; j < lenJ; j++) { // Do a first pass to compute line mappings, and a second pass to actually interpret them
const change = changes[j]; const lineBreaksComputer = this.lines.createLineBreaksComputer();
for (const change of changes) {
switch (change.changeType) {
case textModelEvents.RawContentChangedType.LinesInserted: {
for (const line of change.detail) {
lineBreaksComputer.addRequest(line, null);
}
break;
}
case textModelEvents.RawContentChangedType.LineChanged: {
lineBreaksComputer.addRequest(change.detail, null);
break;
}
}
}
const lineBreaks = lineBreaksComputer.finalize();
let lineBreaksOffset = 0;
for (const change of changes) {
switch (change.changeType) { switch (change.changeType) {
case textModelEvents.RawContentChangedType.Flush: { case textModelEvents.RawContentChangedType.Flush: {
...@@ -222,7 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -222,7 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break; break;
} }
case textModelEvents.RawContentChangedType.LinesInserted: { case textModelEvents.RawContentChangedType.LinesInserted: {
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail); const insertedLineBreaks = lineBreaks.slice(lineBreaksOffset, lineBreaksOffset + change.detail.length);
lineBreaksOffset += change.detail.length;
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);
if (linesInsertedEvent !== null) { if (linesInsertedEvent !== null) {
eventsCollector.emit(linesInsertedEvent); eventsCollector.emit(linesInsertedEvent);
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
...@@ -231,7 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -231,7 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break; break;
} }
case textModelEvents.RawContentChangedType.LineChanged: { case textModelEvents.RawContentChangedType.LineChanged: {
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, change.detail); const changedLineBreakData = lineBreaks[lineBreaksOffset];
lineBreaksOffset++;
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData);
hadModelLineChangeThatChangedLineMapping = lineMappingChanged; hadModelLineChangeThatChangedLineMapping = lineMappingChanged;
if (linesChangedEvent) { if (linesChangedEvent) {
eventsCollector.emit(linesChangedEvent); eventsCollector.emit(linesChangedEvent);
...@@ -475,8 +495,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -475,8 +495,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
* Gives a hint that a lot of requests are about to come in for these line numbers. * Gives a hint that a lot of requests are about to come in for these line numbers.
*/ */
public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void { public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
this.viewportStartLine = startLineNumber; this.viewportStartLine = startLineNumber;
let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber))); let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber)));
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges); this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
...@@ -546,7 +564,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel ...@@ -546,7 +564,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
mightContainNonBasicASCII, mightContainNonBasicASCII,
lineData.tokens, lineData.tokens,
inlineDecorations, inlineDecorations,
tabSize tabSize,
lineData.startVisibleColumn
); );
} }
......
...@@ -381,7 +381,6 @@ export class LineCommentCommand implements editorCommon.ICommand { ...@@ -381,7 +381,6 @@ export class LineCommentCommand implements editorCommon.ICommand {
return res; return res;
} }
// TODO@Alex -> duplicated in characterHardWrappingLineMapper
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number { private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
if (isTab) { if (isTab) {
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize)); return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
......
...@@ -125,6 +125,7 @@ export class Colorizer { ...@@ -125,6 +125,7 @@ export class Colorizer {
[], [],
tabSize, tabSize,
0, 0,
0,
-1, -1,
'none', 'none',
false, false,
...@@ -193,6 +194,7 @@ function _fakeColorize(lines: string[], tabSize: number): string { ...@@ -193,6 +194,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
[], [],
tabSize, tabSize,
0, 0,
0,
-1, -1,
'none', 'none',
false, false,
...@@ -230,6 +232,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: ...@@ -230,6 +232,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport:
[], [],
tabSize, tabSize,
0, 0,
0,
-1, -1,
'none', 'none',
false, false,
......
...@@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; ...@@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void { function testCommand(lines: string[], selections: Selection[], edits: IIdentifiedSingleEditOperation[], expectedLines: string[], expectedSelections: Selection[]): void {
withTestCodeEditor(lines, {}, (editor, cursor) => { withTestCodeEditor(lines, {}, (editor, cursor) => {
...@@ -200,7 +201,7 @@ suite('SideEditing', () => { ...@@ -200,7 +201,7 @@ suite('SideEditing', () => {
function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void {
const model = TextModel.createFromString(LINES.join('\n')); const model = TextModel.createFromString(LINES.join('\n'));
const config = new TestConfiguration({}); const config = new TestConfiguration({});
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
cursor.setSelections('tests', [selection]); cursor.setSelections('tests', [selection]);
......
...@@ -25,6 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes ...@@ -25,6 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes
import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules'; import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
const H = Handler; const H = Handler;
...@@ -152,7 +153,7 @@ suite('Editor Controller - Cursor', () => { ...@@ -152,7 +153,7 @@ suite('Editor Controller - Cursor', () => {
thisModel = createTextModel(text); thisModel = createTextModel(text);
thisConfiguration = new TestConfiguration({}); thisConfiguration = new TestConfiguration({});
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!); thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!);
thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel);
}); });
...@@ -776,7 +777,7 @@ suite('Editor Controller - Cursor', () => { ...@@ -776,7 +777,7 @@ suite('Editor Controller - Cursor', () => {
'var newer = require("gulp-newer");', 'var newer = require("gulp-newer");',
].join('\n')); ].join('\n'));
const config = new TestConfiguration({}); const config = new TestConfiguration({});
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
moveTo(cursor, 1, 4, false); moveTo(cursor, 1, 4, false);
...@@ -816,7 +817,7 @@ suite('Editor Controller - Cursor', () => { ...@@ -816,7 +817,7 @@ suite('Editor Controller - Cursor', () => {
'<property id="SomeThing" key="SomeKey" value="00X"/>', '<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n')); ].join('\n'));
const config = new TestConfiguration({}); const config = new TestConfiguration({});
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
moveTo(cursor, 10, 10, false); moveTo(cursor, 10, 10, false);
...@@ -880,7 +881,7 @@ suite('Editor Controller - Cursor', () => { ...@@ -880,7 +881,7 @@ suite('Editor Controller - Cursor', () => {
'<property id="SomeThing" key="SomeKey" value="00X"/>', '<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n')); ].join('\n'));
const config = new TestConfiguration({}); const config = new TestConfiguration({});
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
moveTo(cursor, 10, 10, false); moveTo(cursor, 10, 10, false);
...@@ -929,7 +930,7 @@ suite('Editor Controller - Cursor', () => { ...@@ -929,7 +930,7 @@ suite('Editor Controller - Cursor', () => {
'var newer = require("gulp-newer");', 'var newer = require("gulp-newer");',
].join('\n')); ].join('\n'));
const config = new TestConfiguration({}); const config = new TestConfiguration({});
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
moveTo(cursor, 1, 4, false); moveTo(cursor, 1, 4, false);
...@@ -2074,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => { ...@@ -2074,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => {
wordWrap: 'wordWrapColumn', wordWrap: 'wordWrapColumn',
wordWrapColumn: 100 wordWrapColumn: 100
}); });
const viewModel = new ViewModel(0, config, model, null!); const viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
const cursor = new Cursor(config, model, viewModel); const cursor = new Cursor(config, model, viewModel);
moveTo(cursor, 1, 43, false); moveTo(cursor, 1, 43, false);
...@@ -3834,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur ...@@ -3834,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur
let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier); let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier);
model.forceTokenization(model.getLineCount()); model.forceTokenization(model.getLineCount());
let config = new TestConfiguration(opts.editorOpts || {}); let config = new TestConfiguration(opts.editorOpts || {});
let viewModel = new ViewModel(0, config, model, null!); let viewModel = new ViewModel(0, config, model, MonospaceLineBreaksComputerFactory.create(config.options), null!);
let cursor = new Cursor(config, model, viewModel); let cursor = new Cursor(config, model, viewModel);
callback(model, cursor); callback(model, cursor);
......
...@@ -13,6 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; ...@@ -13,6 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
suite('Cursor move command test', () => { suite('Cursor move command test', () => {
...@@ -32,7 +33,7 @@ suite('Cursor move command test', () => { ...@@ -32,7 +33,7 @@ suite('Cursor move command test', () => {
thisModel = TextModel.createFromString(text); thisModel = TextModel.createFromString(text);
thisConfiguration = new TestConfiguration({}); thisConfiguration = new TestConfiguration({});
thisViewModel = new ViewModel(0, thisConfiguration, thisModel, null!); thisViewModel = new ViewModel(0, thisConfiguration, thisModel, MonospaceLineBreaksComputerFactory.create(thisConfiguration.options), null!);
thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel); thisCursor = new Cursor(thisConfiguration, thisModel, thisViewModel);
}); });
......
...@@ -38,6 +38,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -38,6 +38,7 @@ suite('viewLineRenderer.renderLine', () => {
[], [],
tabSize, tabSize,
0, 0,
0,
-1, -1,
'none', 'none',
false, false,
...@@ -88,6 +89,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -88,6 +89,7 @@ suite('viewLineRenderer.renderLine', () => {
[], [],
tabSize, tabSize,
0, 0,
0,
-1, -1,
'none', 'none',
false, false,
...@@ -140,6 +142,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -140,6 +142,7 @@ suite('viewLineRenderer.renderLine', () => {
]), ]),
[], [],
4, 4,
0,
10, 10,
6, 6,
'boundary', 'boundary',
...@@ -232,6 +235,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -232,6 +235,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'boundary', 'boundary',
...@@ -295,6 +299,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -295,6 +299,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -358,6 +363,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -358,6 +363,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -398,6 +404,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -398,6 +404,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -429,6 +436,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -429,6 +436,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -530,6 +538,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -530,6 +538,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -569,6 +578,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -569,6 +578,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -599,6 +609,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -599,6 +609,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -646,6 +657,7 @@ suite('viewLineRenderer.renderLine', () => { ...@@ -646,6 +657,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts, lineParts,
[], [],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -728,6 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -728,6 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens(tokens), createViewLineTokens(tokens),
[], [],
4, 4,
0,
10, 10,
-1, -1,
renderWhitespace, renderWhitespace,
...@@ -754,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -754,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(21, 3)]), createViewLineTokens([createPart(21, 3)]),
[new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)], [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -794,6 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -794,6 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular) new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular)
], ],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -1209,6 +1224,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1209,6 +1224,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(2, 8, 'c', InlineDecorationType.Regular), new LineDecoration(2, 8, 'c', InlineDecorationType.Regular),
], ],
4, 4,
0,
10, 10,
-1, -1,
'none', 'none',
...@@ -1250,6 +1266,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1250,6 +1266,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(4, 3)]), createViewLineTokens([createPart(4, 3)]),
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
4, 4,
0,
10, 10,
-1, -1,
'all', 'all',
...@@ -1283,6 +1300,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1283,6 +1300,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(4, 3)]), createViewLineTokens([createPart(4, 3)]),
[new LineDecoration(2, 3, 'before', InlineDecorationType.Before)], [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)],
4, 4,
0,
10, 10,
-1, -1,
'all', 'all',
...@@ -1317,6 +1335,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1317,6 +1335,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(0, 3)]), createViewLineTokens([createPart(0, 3)]),
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
4, 4,
0,
10, 10,
-1, -1,
'all', 'all',
...@@ -1347,6 +1366,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1347,6 +1366,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(7, 3)]), createViewLineTokens([createPart(7, 3)]),
[new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)],
2, 2,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1381,6 +1401,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1381,6 +1401,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(0, 1, 'after', InlineDecorationType.After), new LineDecoration(0, 1, 'after', InlineDecorationType.After),
], ],
2, 2,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1414,6 +1435,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1414,6 +1435,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After), new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After),
], ],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1445,6 +1467,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1445,6 +1467,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(15, 3)]), createViewLineTokens([createPart(15, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1475,6 +1498,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1475,6 +1498,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(15, 3)]), createViewLineTokens([createPart(15, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'all', 'all',
...@@ -1511,6 +1535,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1511,6 +1535,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(53, 3)]), createViewLineTokens([createPart(53, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1541,6 +1566,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1541,6 +1566,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(100, 3)]), createViewLineTokens([createPart(100, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1573,6 +1599,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1573,6 +1599,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(105, 3)]), createViewLineTokens([createPart(105, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1604,6 +1631,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1604,6 +1631,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(59, 3)]), createViewLineTokens([createPart(59, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'boundary', 'boundary',
...@@ -1633,6 +1661,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1633,6 +1661,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(194, 3)]), createViewLineTokens([createPart(194, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1666,6 +1695,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1666,6 +1695,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(194, 3)]), createViewLineTokens([createPart(194, 3)]),
[], [],
4, 4,
0,
10, 10,
10000, 10000,
'none', 'none',
...@@ -1695,6 +1725,7 @@ suite('viewLineRenderer.renderLine 2', () => { ...@@ -1695,6 +1725,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens(parts), createViewLineTokens(parts),
[], [],
tabSize, tabSize,
0,
10, 10,
-1, -1,
'none', 'none',
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import { ILineMapperFactory, ILineMapping } from 'vs/editor/common/viewModel/splitLinesCollection';
function assertLineMapping(factory: ILineMapperFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): ILineMapping | null {
// Create version of `annotatedText` with line break markers removed
let rawText = '';
let currentLineIndex = 0;
let lineIndices: number[] = [];
for (let i = 0, len = annotatedText.length; i < len; i++) {
if (annotatedText.charAt(i) === '|') {
currentLineIndex++;
} else {
rawText += annotatedText.charAt(i);
lineIndices[rawText.length - 1] = currentLineIndex;
}
}
const mapper = factory.createLineMapping(rawText, tabSize, breakAfter, 2, wrappingIndent);
// Insert line break markers again, according to algorithm
let actualAnnotatedText = '';
if (mapper) {
let previousLineIndex = 0;
for (let i = 0, len = rawText.length; i < len; i++) {
let r = mapper.getOutputPositionOfInputOffset(i);
if (previousLineIndex !== r.outputLineIndex) {
previousLineIndex = r.outputLineIndex;
actualAnnotatedText += '|';
}
actualAnnotatedText += rawText.charAt(i);
}
} else {
// No wrapping
actualAnnotatedText = rawText;
}
assert.equal(actualAnnotatedText, annotatedText);
return mapper;
}
suite('Editor ViewModel - CharacterHardWrappingLineMapper', () => {
test('CharacterHardWrappingLineMapper', () => {
let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.');
// Empty string
assertLineMapping(factory, 4, 5, '');
// No wrapping if not necessary
assertLineMapping(factory, 4, 5, 'aaa');
assertLineMapping(factory, 4, 5, 'aaaaa');
assertLineMapping(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
// Acts like hard wrapping if no char found
assertLineMapping(factory, 4, 5, 'aaaaa|a');
// Honors obtrusive wrapping character
assertLineMapping(factory, 4, 5, 'aaaaa|.');
assertLineMapping(factory, 4, 5, 'aaaaa|a.|aaa.|aa');
assertLineMapping(factory, 4, 5, 'aaaaa|a..|aaa.|aa');
assertLineMapping(factory, 4, 5, 'aaaaa|a...|aaa.|aa');
assertLineMapping(factory, 4, 5, 'aaaaa|a....|aaa.|aa');
// Honors tabs when computing wrapping position
assertLineMapping(factory, 4, 5, '\t');
assertLineMapping(factory, 4, 5, '\ta|aa');
assertLineMapping(factory, 4, 5, '\ta|\ta|a');
assertLineMapping(factory, 4, 5, 'aa\ta');
assertLineMapping(factory, 4, 5, 'aa\ta|a');
// Honors wrapping before characters (& gives it priority)
assertLineMapping(factory, 4, 5, 'aaa.|aa');
assertLineMapping(factory, 4, 5, 'aaa|(.aa');
// Honors wrapping after characters (& gives it priority)
assertLineMapping(factory, 4, 5, 'aaa))|).aaa');
assertLineMapping(factory, 4, 5, 'aaa))|)|.aaaa');
assertLineMapping(factory, 4, 5, 'aaa)|()|.aaa');
assertLineMapping(factory, 4, 5, 'aaa(|()|.aaa');
assertLineMapping(factory, 4, 5, 'aa.(|()|.aaa');
assertLineMapping(factory, 4, 5, 'aa.|(.)|.aaa');
});
test('CharacterHardWrappingLineMapper - CJK and Kinsoku Shori', () => {
let factory = new CharacterHardWrappingLineMapperFactory('(', ')', '.');
assertLineMapping(factory, 4, 5, 'aa \u5b89|\u5b89');
assertLineMapping(factory, 4, 5, '\u3042 \u5b89|\u5b89');
assertLineMapping(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89');
assertLineMapping(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89');
assertLineMapping(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89');
assertLineMapping(factory, 4, 5, 'aa |(\u5b89aa|\u5b89');
});
test('CharacterHardWrappingLineMapper - WrappingIndent.Same', () => {
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
assertLineMapping(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same);
});
test('issue #16332: Scroll bar overlaying on top of text', () => {
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
assertLineMapping(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent);
});
test('issue #35162: wrappingIndent not consistently working', () => {
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
let mapper = assertLineMapping(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent);
assert.equal(mapper!.getWrappedLinesIndent(), ' \t');
});
test('issue #75494: surrogate pairs', () => {
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
assertLineMapping(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', WrappingIndent.Same);
});
test('CharacterHardWrappingLineMapper - WrappingIndent.DeepIndent', () => {
let factory = new CharacterHardWrappingLineMapperFactory('', ' ', '');
let mapper = assertLineMapping(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent);
assert.equal(mapper!.getWrappedLinesIndent(), ' \t\t');
});
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { WrappingIndent, EditorOptions } from 'vs/editor/common/config/editorOptions';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
import { ILineBreaksComputerFactory, LineBreakData } from 'vs/editor/common/viewModel/splitLinesCollection';
function parseAnnotatedText(annotatedText: string): { text: string; indices: number[]; } {
let text = '';
let currentLineIndex = 0;
let indices: number[] = [];
for (let i = 0, len = annotatedText.length; i < len; i++) {
if (annotatedText.charAt(i) === '|') {
currentLineIndex++;
} else {
text += annotatedText.charAt(i);
indices[text.length - 1] = currentLineIndex;
}
}
return { text: text, indices: indices };
}
function toAnnotatedText(text: string, lineBreakData: LineBreakData | null): string {
// Insert line break markers again, according to algorithm
let actualAnnotatedText = '';
if (lineBreakData) {
let previousLineIndex = 0;
for (let i = 0, len = text.length; i < len; i++) {
let r = LineBreakData.getOutputPositionOfInputOffset(lineBreakData.breakOffsets, i);
if (previousLineIndex !== r.outputLineIndex) {
previousLineIndex = r.outputLineIndex;
actualAnnotatedText += '|';
}
actualAnnotatedText += text.charAt(i);
}
} else {
// No wrapping
actualAnnotatedText = text;
}
return actualAnnotatedText;
}
function getLineBreakData(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, text: string, previousLineBreakData: LineBreakData | null): LineBreakData | null {
const lineBreaksComputer = factory.createLineBreaksComputer(tabSize, breakAfter, columnsForFullWidthChar, wrappingIndent);
const previousLineBreakDataClone = previousLineBreakData ? new LineBreakData(previousLineBreakData.breakOffsets.slice(0), previousLineBreakData.breakOffsetsVisibleColumn.slice(0), previousLineBreakData.wrappedTextIndentLength) : null;
lineBreaksComputer.addRequest(text, previousLineBreakDataClone);
return lineBreaksComputer.finalize()[0];
}
function assertLineBreaks(factory: ILineBreaksComputerFactory, tabSize: number, breakAfter: number, annotatedText: string, wrappingIndent = WrappingIndent.None): LineBreakData | null {
// Create version of `annotatedText` with line break markers removed
const text = parseAnnotatedText(annotatedText).text;
const lineBreakData = getLineBreakData(factory, tabSize, breakAfter, 2, wrappingIndent, text, null);
const actualAnnotatedText = toAnnotatedText(text, lineBreakData);
assert.equal(actualAnnotatedText, annotatedText);
return lineBreakData;
}
suite('Editor ViewModel - MonospaceLineBreaksComputer', () => {
test('MonospaceLineBreaksComputer', () => {
let factory = new MonospaceLineBreaksComputerFactory('(', '\t).');
// Empty string
assertLineBreaks(factory, 4, 5, '');
// No wrapping if not necessary
assertLineBreaks(factory, 4, 5, 'aaa');
assertLineBreaks(factory, 4, 5, 'aaaaa');
assertLineBreaks(factory, 4, -1, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
// Acts like hard wrapping if no char found
assertLineBreaks(factory, 4, 5, 'aaaaa|a');
// Honors wrapping character
assertLineBreaks(factory, 4, 5, 'aaaaa|.');
assertLineBreaks(factory, 4, 5, 'aaaaa|a.|aaa.|aa');
assertLineBreaks(factory, 4, 5, 'aaaaa|a..|aaa.|aa');
assertLineBreaks(factory, 4, 5, 'aaaaa|a...|aaa.|aa');
assertLineBreaks(factory, 4, 5, 'aaaaa|a....|aaa.|aa');
// Honors tabs when computing wrapping position
assertLineBreaks(factory, 4, 5, '\t');
assertLineBreaks(factory, 4, 5, '\t|aaa');
assertLineBreaks(factory, 4, 5, '\t|a\t|aa');
assertLineBreaks(factory, 4, 5, 'aa\ta');
assertLineBreaks(factory, 4, 5, 'aa\t|aa');
// Honors wrapping before characters (& gives it priority)
assertLineBreaks(factory, 4, 5, 'aaa.|aa');
assertLineBreaks(factory, 4, 5, 'aaa(.|aa');
// Honors wrapping after characters (& gives it priority)
assertLineBreaks(factory, 4, 5, 'aaa))|).aaa');
assertLineBreaks(factory, 4, 5, 'aaa))|).|aaaa');
assertLineBreaks(factory, 4, 5, 'aaa)|().|aaa');
assertLineBreaks(factory, 4, 5, 'aaa(|().|aaa');
assertLineBreaks(factory, 4, 5, 'aa.(|().|aaa');
assertLineBreaks(factory, 4, 5, 'aa.(.|).aaa');
});
function assertIncrementalLineBreaks(factory: ILineBreaksComputerFactory, text: string, tabSize: number, breakAfter1: number, annotatedText1: string, breakAfter2: number, annotatedText2: string, wrappingIndent = WrappingIndent.None): void {
// sanity check the test
assert.equal(text, parseAnnotatedText(annotatedText1).text);
assert.equal(text, parseAnnotatedText(annotatedText2).text);
// check that the direct mapping is ok for 1
const directLineBreakData1 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, null);
assert.equal(toAnnotatedText(text, directLineBreakData1), annotatedText1);
// check that the direct mapping is ok for 2
const directLineBreakData2 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, null);
assert.equal(toAnnotatedText(text, directLineBreakData2), annotatedText2);
// check that going from 1 to 2 is ok
const lineBreakData2from1 = getLineBreakData(factory, tabSize, breakAfter2, 2, wrappingIndent, text, directLineBreakData1);
assert.equal(toAnnotatedText(text, lineBreakData2from1), annotatedText2);
assert.deepEqual(lineBreakData2from1, directLineBreakData2);
// check that going from 2 to 1 is ok
const lineBreakData1from2 = getLineBreakData(factory, tabSize, breakAfter1, 2, wrappingIndent, text, directLineBreakData2);
assert.equal(toAnnotatedText(text, lineBreakData1from2), annotatedText1);
assert.deepEqual(lineBreakData1from2, directLineBreakData1);
}
test('MonospaceLineBreaksComputer incremental 1', () => {
let factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
assertIncrementalLineBreaks(
factory, 'just some text and more', 4,
10, 'just some |text and |more',
15, 'just some text |and more'
);
assertIncrementalLineBreaks(
factory, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.', 4,
47, 'Cu scripserit suscipiantur eos, in affert |pericula contentiones sed, cetero sanctus et |pro. Ius vidit magna regione te, sit ei |elaboraret liberavisse. Mundi verear eu mea, |eam vero scriptorem in, vix in menandri |assueverit. Natum definiebas cu vim. Vim |doming vocibus efficiantur id. In indoctum |deseruisse voluptatum vim, ad debitis verterem |sed.',
142, 'Cu scripserit suscipiantur eos, in affert pericula contentiones sed, cetero sanctus et pro. Ius vidit magna regione te, sit ei elaboraret |liberavisse. Mundi verear eu mea, eam vero scriptorem in, vix in menandri assueverit. Natum definiebas cu vim. Vim doming vocibus efficiantur |id. In indoctum deseruisse voluptatum vim, ad debitis verterem sed.',
);
assertIncrementalLineBreaks(
factory, 'An his legere persecuti, oblique delicata efficiantur ex vix, vel at graecis officiis maluisset. Et per impedit voluptua, usu discere maiorum at. Ut assum ornatus temporibus vis, an sea melius pericula. Ea dicunt oblique phaedrum nam, eu duo movet nobis. His melius facilis eu, vim malorum temporibus ne. Nec no sale regione, meliore civibus placerat id eam. Mea alii fabulas definitionem te, agam volutpat ad vis, et per bonorum nonumes repudiandae.', 4,
57, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt |oblique phaedrum nam, eu duo movet nobis. His melius |facilis eu, vim malorum temporibus ne. Nec no sale |regione, meliore civibus placerat id eam. Mea alii |fabulas definitionem te, agam volutpat ad vis, et per |bonorum nonumes repudiandae.',
58, 'An his legere persecuti, oblique delicata efficiantur ex |vix, vel at graecis officiis maluisset. Et per impedit |voluptua, usu discere maiorum at. Ut assum ornatus |temporibus vis, an sea melius pericula. Ea dicunt oblique |phaedrum nam, eu duo movet nobis. His melius facilis eu, |vim malorum temporibus ne. Nec no sale regione, meliore |civibus placerat id eam. Mea alii fabulas definitionem te,| agam volutpat ad vis, et per bonorum nonumes repudiandae.'
);
assertIncrementalLineBreaks(
factory, '\t\t"owner": "vscode",', 4,
14, '\t\t"owner|": |"vscod|e",',
16, '\t\t"owner":| |"vscode"|,',
WrappingIndent.Same
);
assertIncrementalLineBreaks(
factory, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬', 4,
51, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬',
50, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇|&|👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬',
WrappingIndent.Same
);
assertIncrementalLineBreaks(
factory, '🐇👬&🌞🌖', 4,
5, '🐇👬&|🌞🌖',
4, '🐇👬|&|🌞🌖',
WrappingIndent.Same
);
assertIncrementalLineBreaks(
factory, '\t\tfunc(\'🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬\', WrappingIndent.Same);', 4,
26, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);',
27, '\t\tfunc|(\'🌞🏇🍼🌞🏇🍼🐇&|👬🌖🌞👬🌖🌞🏇🍼🐇|👬\', |WrappingIndent.|Same);',
WrappingIndent.Same
);
assertIncrementalLineBreaks(
factory, 'factory, "xtxtfunc(x"🌞🏇🍼🌞🏇🍼🐇&👬🌖🌞👬🌖🌞🏇🍼🐇👬x"', 4,
16, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼|🐇&|👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"',
17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"',
WrappingIndent.Same
);
});
test('MonospaceLineBreaksComputer - CJK and Kinsoku Shori', () => {
let factory = new MonospaceLineBreaksComputerFactory('(', '\t)');
assertLineBreaks(factory, 4, 5, 'aa \u5b89|\u5b89');
assertLineBreaks(factory, 4, 5, '\u3042 \u5b89|\u5b89');
assertLineBreaks(factory, 4, 5, '\u3042\u3042|\u5b89\u5b89');
assertLineBreaks(factory, 4, 5, 'aa |\u5b89)\u5b89|\u5b89');
assertLineBreaks(factory, 4, 5, 'aa \u3042|\u5b89\u3042)|\u5b89');
assertLineBreaks(factory, 4, 5, 'aa |(\u5b89aa|\u5b89');
});
test('MonospaceLineBreaksComputer - WrappingIndent.Same', () => {
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
assertLineBreaks(factory, 4, 38, ' *123456789012345678901234567890123456|7890', WrappingIndent.Same);
});
test('issue #16332: Scroll bar overlaying on top of text', () => {
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
assertLineBreaks(factory, 4, 24, 'a/ very/long/line/of/tex|t/that/expands/beyon|d/your/typical/line/|of/code/', WrappingIndent.Indent);
});
test('issue #35162: wrappingIndent not consistently working', () => {
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
let mapper = assertLineBreaks(factory, 4, 24, ' t h i s |i s |a l |o n |g l |i n |e', WrappingIndent.Indent);
assert.equal(mapper!.wrappedTextIndentLength, ' '.length);
});
test('issue #75494: surrogate pairs', () => {
let factory = new MonospaceLineBreaksComputerFactory('\t', ' ');
assertLineBreaks(factory, 4, 49, '🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼🐇👬🌖🌞🏇🍼|🐇👬', WrappingIndent.Same);
});
test('issue #75494: surrogate pairs overrun 1', () => {
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
assertLineBreaks(factory, 4, 4, '🐇👬|&|🌞🌖', WrappingIndent.Same);
});
test('issue #75494: surrogate pairs overrun 2', () => {
const factory = new MonospaceLineBreaksComputerFactory(EditorOptions.wordWrapBreakBeforeCharacters.defaultValue, EditorOptions.wordWrapBreakAfterCharacters.defaultValue);
assertLineBreaks(factory, 4, 17, 'factory, |"xtxtfunc|(x"🌞🏇🍼🌞🏇🍼🐇|&👬🌖🌞👬🌖🌞🏇🍼|🐇👬x"', WrappingIndent.Same);
});
test('MonospaceLineBreaksComputer - WrappingIndent.DeepIndent', () => {
let factory = new MonospaceLineBreaksComputerFactory('', '\t ');
let mapper = assertLineBreaks(factory, 4, 26, ' W e A r e T e s t |i n g D e |e p I n d |e n t a t |i o n', WrappingIndent.DeepIndent);
assert.equal(mapper!.wrappedTextIndentLength, ' '.length);
});
});
...@@ -4,9 +4,18 @@ ...@@ -4,9 +4,18 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { toUint32Array } from 'vs/base/common/uint'; import { toUint32 } from 'vs/base/common/uint';
import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer'; import { PrefixSumComputer, PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
function toUint32Array(arr: number[]): Uint32Array {
const len = arr.length;
const r = new Uint32Array(len);
for (let i = 0; i < len; i++) {
r[i] = toUint32(arr[i]);
}
return r;
}
suite('Editor ViewModel - PrefixSumComputer', () => { suite('Editor ViewModel - PrefixSumComputer', () => {
test('PrefixSumComputer', () => { test('PrefixSumComputer', () => {
......
...@@ -9,14 +9,12 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; ...@@ -9,14 +9,12 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { toUint32Array } from 'vs/base/common/uint';
import { EndOfLinePreference } from 'vs/editor/common/model'; import { EndOfLinePreference } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import * as modes from 'vs/editor/common/modes'; import * as modes from 'vs/editor/common/modes';
import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
import { CharacterHardWrappingLineMapperFactory, CharacterHardWrappingLineMapping } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ILineMapping, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorOption } from 'vs/editor/common/config/editorOptions';
...@@ -25,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; ...@@ -25,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
suite('Editor ViewModel - SplitLinesCollection', () => { suite('Editor ViewModel - SplitLinesCollection', () => {
test('SplitLine', () => { test('SplitLine', () => {
let model1 = createModel('My First LineMy Second LineAnd another one'); let model1 = createModel('My First LineMy Second LineAnd another one');
let line1 = createSplitLine([13, 14, 15], ''); let line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 0);
assert.equal(line1.getViewLineCount(), 3); assert.equal(line1.getViewLineCount(), 3);
assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line');
...@@ -54,38 +52,38 @@ suite('Editor ViewModel - SplitLinesCollection', () => { ...@@ -54,38 +52,38 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
} }
model1 = createModel('My First LineMy Second LineAnd another one'); model1 = createModel('My First LineMy Second LineAnd another one');
line1 = createSplitLine([13, 14, 15], '\t'); line1 = createSplitLine([13, 14, 15], [13, 13 + 14, 13 + 14 + 15], 4);
assert.equal(line1.getViewLineCount(), 3); assert.equal(line1.getViewLineCount(), 3);
assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line'); assert.equal(line1.getViewLineContent(model1, 1, 0), 'My First Line');
assert.equal(line1.getViewLineContent(model1, 1, 1), '\tMy Second Line'); assert.equal(line1.getViewLineContent(model1, 1, 1), ' My Second Line');
assert.equal(line1.getViewLineContent(model1, 1, 2), '\tAnd another one'); assert.equal(line1.getViewLineContent(model1, 1, 2), ' And another one');
assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14); assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14);
assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 16); assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 19);
assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 17); assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 20);
for (let col = 1; col <= 14; col++) {
assert.equal(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')'); let actualViewColumnMapping: number[][] = [];
} for (let lineIndex = 0; lineIndex < line1.getViewLineCount(); lineIndex++) {
for (let col = 1; col <= 1; col++) { let actualLineViewColumnMapping: number[] = [];
assert.equal(line1.getModelColumnOfViewPosition(1, 1), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')'); for (let col = 1; col <= line1.getViewLineMaxColumn(model1, 1, lineIndex); col++) {
} actualLineViewColumnMapping.push(line1.getModelColumnOfViewPosition(lineIndex, col));
for (let col = 2; col <= 16; col++) { }
assert.equal(line1.getModelColumnOfViewPosition(1, col), 13 + col - 1, 'getInputColumnOfOutputPosition(1, ' + col + ')'); actualViewColumnMapping.push(actualLineViewColumnMapping);
}
for (let col = 1; col <= 1; col++) {
assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col, 'getInputColumnOfOutputPosition(2, ' + col + ')');
}
for (let col = 2; col <= 17; col++) {
assert.equal(line1.getModelColumnOfViewPosition(2, col), 13 + 14 + col - 1, 'getInputColumnOfOutputPosition(2, ' + col + ')');
} }
assert.deepEqual(actualViewColumnMapping, [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[14, 14, 14, 14, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28],
[28, 28, 28, 28, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
]);
for (let col = 1; col <= 13; col++) { for (let col = 1; col <= 13; col++) {
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), 'getOutputPositionOfInputPosition(' + col + ')'); assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(0, col), '6.getOutputPositionOfInputPosition(' + col + ')');
} }
for (let col = 1 + 13; col <= 14 + 13; col++) { for (let col = 1 + 13; col <= 14 + 13; col++) {
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 1 + col - 13), 'getOutputPositionOfInputPosition(' + col + ')'); assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(1, 4 + col - 13), '7.getOutputPositionOfInputPosition(' + col + ')');
} }
for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) { for (let col = 1 + 13 + 14; col <= 15 + 14 + 13; col++) {
assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 1 + col - 13 - 14), 'getOutputPositionOfInputPosition(' + col + ')'); assert.deepEqual(line1.getViewPositionOfModelPosition(0, col), pos(2, 4 + col - 13 - 14), '8.getOutputPositionOfInputPosition(' + col + ')');
} }
}); });
...@@ -95,13 +93,11 @@ suite('Editor ViewModel - SplitLinesCollection', () => { ...@@ -95,13 +93,11 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
const fontInfo = config.options.get(EditorOption.fontInfo); const fontInfo = config.options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters); const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = config.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingIndent = config.options.get(EditorOption.wrappingIndent); const wrappingIndent = config.options.get(EditorOption.wrappingIndent);
const hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(
wordWrapBreakBeforeCharacters, wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters, wordWrapBreakAfterCharacters
wordWrapBreakObtrusiveCharacters
); );
const model = TextModel.createFromString([ const model = TextModel.createFromString([
...@@ -115,7 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { ...@@ -115,7 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
const linesCollection = new SplitLinesCollection( const linesCollection = new SplitLinesCollection(
model, model,
hardWrappingLineMapperFactory, lineBreaksComputerFactory,
model.getOptions().tabSize, model.getOptions().tabSize,
wrappingInfo.wrappingColumn, wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
...@@ -616,12 +612,12 @@ suite('SplitLinesCollection', () => { ...@@ -616,12 +612,12 @@ suite('SplitLinesCollection', () => {
] ]
}, },
{ {
content: ' world");', content: ' world");',
minColumn: 4, minColumn: 13,
maxColumn: 12, maxColumn: 21,
tokens: [ tokens: [
{ endIndex: 9, value: 15 }, { endIndex: 18, value: 15 },
{ endIndex: 11, value: 16 }, { endIndex: 20, value: 16 },
] ]
}, },
{ {
...@@ -658,28 +654,28 @@ suite('SplitLinesCollection', () => { ...@@ -658,28 +654,28 @@ suite('SplitLinesCollection', () => {
] ]
}, },
{ {
content: ' world, this is a ', content: ' world, this is a ',
minColumn: 4, minColumn: 13,
maxColumn: 21, maxColumn: 30,
tokens: [ tokens: [
{ endIndex: 20, value: 28 }, { endIndex: 29, value: 28 },
] ]
}, },
{ {
content: ' somewhat longer ', content: ' somewhat longer ',
minColumn: 4, minColumn: 13,
maxColumn: 20, maxColumn: 29,
tokens: [ tokens: [
{ endIndex: 19, value: 28 }, { endIndex: 28, value: 28 },
] ]
}, },
{ {
content: ' line");', content: ' line");',
minColumn: 4, minColumn: 13,
maxColumn: 11, maxColumn: 20,
tokens: [ tokens: [
{ endIndex: 8, value: 28 }, { endIndex: 17, value: 28 },
{ endIndex: 10, value: 29 }, { endIndex: 19, value: 29 },
] ]
}, },
{ {
...@@ -749,13 +745,11 @@ suite('SplitLinesCollection', () => { ...@@ -749,13 +745,11 @@ suite('SplitLinesCollection', () => {
const fontInfo = configuration.options.get(EditorOption.fontInfo); const fontInfo = configuration.options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters); const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters); const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = configuration.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent); const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent);
const factory = new CharacterHardWrappingLineMapperFactory( const factory = new MonospaceLineBreaksComputerFactory(
wordWrapBreakBeforeCharacters, wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters, wordWrapBreakAfterCharacters
wordWrapBreakObtrusiveCharacters
); );
const linesCollection = new SplitLinesCollection( const linesCollection = new SplitLinesCollection(
...@@ -778,15 +772,16 @@ function pos(lineNumber: number, column: number): Position { ...@@ -778,15 +772,16 @@ function pos(lineNumber: number, column: number): Position {
return new Position(lineNumber, column); return new Position(lineNumber, column);
} }
function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isVisible: boolean = true): SplitLine { function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine {
return new SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible); return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible);
} }
function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping { function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData {
return new CharacterHardWrappingLineMapping( let sums: number[] = [];
new PrefixSumComputer(toUint32Array(breakingLengths)), for (let i = 0; i < breakingLengths.length; i++) {
wrappedLinesPrefix sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i];
); }
return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth);
} }
function createModel(text: string): ISimpleModel { function createModel(text: string): ISimpleModel {
......
...@@ -7,6 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; ...@@ -7,6 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void {
const EDITOR_ID = 1; const EDITOR_ID = 1;
...@@ -15,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback: ...@@ -15,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback:
let model = TextModel.createFromString(text.join('\n')); let model = TextModel.createFromString(text.join('\n'));
let viewModel = new ViewModel(EDITOR_ID, configuration, model, null!); let viewModel = new ViewModel(EDITOR_ID, configuration, model, MonospaceLineBreaksComputerFactory.create(configuration.options), null!);
callback(viewModel, model); callback(viewModel, model);
......
...@@ -2709,19 +2709,14 @@ declare namespace monaco.editor { ...@@ -2709,19 +2709,14 @@ declare namespace monaco.editor {
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
/** /**
* Configure word wrapping characters. A break will be introduced before these characters. * Configure word wrapping characters. A break will be introduced before these characters.
* Defaults to '{([+'. * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
*/ */
wordWrapBreakBeforeCharacters?: string; wordWrapBreakBeforeCharacters?: string;
/** /**
* Configure word wrapping characters. A break will be introduced after these characters. * Configure word wrapping characters. A break will be introduced after these characters.
* Defaults to ' \t})]?|&,;'. * Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
*/ */
wordWrapBreakAfterCharacters?: string; wordWrapBreakAfterCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found.
* Defaults to '.'.
*/
wordWrapBreakObtrusiveCharacters?: string;
/** /**
* Performance guard: Stop rendering a line after x characters. * Performance guard: Stop rendering a line after x characters.
* Defaults to 10000. * Defaults to 10000.
......
...@@ -11,6 +11,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; ...@@ -11,6 +11,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
import * as marked from 'vs/base/common/marked/marked'; import * as marked from 'vs/base/common/marked/marked';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/resources';
import { EndOfLinePreference } from 'vs/editor/common/model';
export class WalkThroughModel extends EditorModel { export class WalkThroughModel extends EditorModel {
...@@ -111,7 +112,7 @@ export class WalkThroughInput extends EditorInput { ...@@ -111,7 +112,7 @@ export class WalkThroughInput extends EditorInput {
return ''; return '';
}; };
const markdown = ref.object.textEditorModel.getLinesContent().join('\n'); const markdown = ref.object.textEditorModel.getValue(EndOfLinePreference.LF);
marked(markdown, { renderer }); marked(markdown, { renderer });
return Promise.all(snippets) return Promise.all(snippets)
......
...@@ -39,6 +39,7 @@ import { Dimension, size } from 'vs/base/browser/dom'; ...@@ -39,6 +39,7 @@ import { Dimension, size } from 'vs/base/browser/dom';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { domEvent } from 'vs/base/browser/event'; import { domEvent } from 'vs/base/browser/event';
import { EndOfLinePreference } from 'vs/editor/common/model';
export const WALK_THROUGH_FOCUS = new RawContextKey<boolean>('interactivePlaygroundFocus', false); export const WALK_THROUGH_FOCUS = new RawContextKey<boolean>('interactivePlaygroundFocus', false);
...@@ -278,7 +279,7 @@ export class WalkThroughPart extends BaseEditor { ...@@ -278,7 +279,7 @@ export class WalkThroughPart extends BaseEditor {
return; return;
} }
const content = model.main.textEditorModel.getLinesContent().join('\n'); const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF);
if (!strings.endsWith(input.getResource().path, '.md')) { if (!strings.endsWith(input.getResource().path, '.md')) {
this.content.innerHTML = content; this.content.innerHTML = content;
this.updateSizeClasses(); this.updateSizeClasses();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册