未验证 提交 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 {
}
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 {
const indent = indents[lineIndex];
let result = '';
const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
for (let i = 1; i <= indent; i++) {
const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`;
left += indentWidth;
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
break;
if (indent >= 1) {
const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1));
let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0;
for (let i = 1; i <= indent; i++) {
const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentWidth}px"></div>`;
left += indentWidth;
if (left > scrollWidth || (this._maxIndentLeft > 0 && left > this._maxIndentLeft)) {
break;
}
}
}
......
......@@ -213,6 +213,7 @@ export class ViewLine implements IVisibleLine {
lineData.tokens,
actualInlineDecorations,
lineData.tabSize,
lineData.startVisibleColumn,
options.spaceWidth,
options.stopRenderingLineAfter,
options.renderWhitespace,
......
......@@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { withNullAsUndefined } from 'vs/base/common/types';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
let EDITOR_ID = 0;
......@@ -1329,7 +1330,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
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.onDidChangeLanguage((e) => {
......
......@@ -2144,6 +2144,7 @@ class InlineViewZonesComputer extends ViewZonesComputer {
lineTokens,
actualDecorations,
tabSize,
0,
fontInfo.spaceWidth,
options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace),
......
......@@ -780,6 +780,7 @@ export class DiffReview extends Disposable {
lineTokens,
[],
tabSize,
0,
fontInfo.spaceWidth,
options.get(EditorOption.stopRenderingLineAfter),
options.get(EditorOption.renderWhitespace),
......
......@@ -264,19 +264,14 @@ export interface IEditorOptions {
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
/**
* Configure word wrapping characters. A break will be introduced before these characters.
* Defaults to '{([+'.
* Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
*/
wordWrapBreakBeforeCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters.
* Defaults to ' \t})]?|&,;'.
* Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
*/
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.
* Defaults to 10000.
......@@ -3154,7 +3149,6 @@ export const enum EditorOption {
wordWrap,
wordWrapBreakAfterCharacters,
wordWrapBreakBeforeCharacters,
wordWrapBreakObtrusiveCharacters,
wordWrapColumn,
wordWrapMinified,
wrappingIndent,
......@@ -3681,16 +3675,12 @@ export const EditorOptions = {
)),
wordWrapBreakAfterCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters',
' \t})]?|/&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
)),
wordWrapBreakBeforeCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters',
'([{‘“〈《「『【〔([{「£¥$£¥++'
)),
wordWrapBreakObtrusiveCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakObtrusiveCharacters, 'wordWrapBreakObtrusiveCharacters',
'.'
)),
wordWrapColumn: register(new EditorIntOption(
EditorOption.wordWrapColumn, 'wordWrapColumn',
80, 1, Constants.MAX_SAFE_SMALL_INTEGER,
......
......@@ -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).
*/
private _asciiMap: Uint8Array;
protected _asciiMap: Uint8Array;
/**
* The entire map (sparse array).
*/
private _map: Map<number, number>;
protected _map: Map<number, number>;
private _defaultValue: number;
protected _defaultValue: number;
constructor(_defaultValue: T) {
let defaultValue = toUint8(_defaultValue);
......
......@@ -511,7 +511,93 @@ export class PieceTreeBase {
}
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 {
......@@ -728,7 +814,7 @@ export class PieceTreeBase {
// #endregion
// #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._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = '';
......@@ -826,7 +912,7 @@ export class PieceTreeBase {
this.computeBufferMetadata();
}
delete(offset: number, cnt: number): void {
public delete(offset: number, cnt: number): void {
this._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = '';
......@@ -899,7 +985,7 @@ export class PieceTreeBase {
this.computeBufferMetadata();
}
insertContentToNodeLeft(value: string, node: TreeNode) {
private insertContentToNodeLeft(value: string, node: TreeNode) {
// we are inserting content to the beginning of node
let nodesToDel: TreeNode[] = [];
if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) {
......@@ -934,7 +1020,7 @@ export class PieceTreeBase {
this.deleteNodes(nodesToDel);
}
insertContentToNodeRight(value: string, node: TreeNode) {
private insertContentToNodeRight(value: string, node: TreeNode) {
// we are inserting to the right of this node.
if (this.adjustCarriageReturnFromNext(value, node)) {
// move \n to the new node.
......@@ -952,9 +1038,9 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode);
}
positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
private positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
private positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
private positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
let piece = node.piece;
let bufferIndex = node.piece.bufferIndex;
let lineStarts = this._buffers[bufferIndex].lineStarts;
......@@ -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.
// 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) {
......@@ -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;
return lineStarts[cursor.line] + cursor.column;
}
deleteNodes(nodes: TreeNode[]): void {
private deleteNodes(nodes: TreeNode[]): void {
for (let i = 0; i < nodes.length; i++) {
rbDelete(this, nodes[i]);
}
}
createNewPieces(text: string): Piece[] {
private createNewPieces(text: string): Piece[] {
if (text.length > AverageBufferSize) {
// 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
......@@ -1128,11 +1214,11 @@ export class PieceTreeBase {
return [newPiece];
}
getLinesRawContent(): string {
public getLinesRawContent(): string {
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 ret = '';
......@@ -1204,7 +1290,7 @@ export class PieceTreeBase {
return ret;
}
computeBufferMetadata() {
private computeBufferMetadata() {
let x = this.root;
let lfCnt = 1;
......@@ -1222,7 +1308,7 @@ export class PieceTreeBase {
}
// #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 pos = this.positionInBuffer(node, accumulatedValue);
let lineCnt = pos.line - piece.start.line;
......@@ -1239,7 +1325,7 @@ export class PieceTreeBase {
return { index: lineCnt, remainder: pos.column };
}
getAccumulatedValue(node: TreeNode, index: number) {
private getAccumulatedValue(node: TreeNode, index: number) {
if (index < 0) {
return 0;
}
......@@ -1253,7 +1339,7 @@ export class PieceTreeBase {
}
}
deleteNodeTail(node: TreeNode, pos: BufferCursor) {
private deleteNodeTail(node: TreeNode, pos: BufferCursor) {
const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt;
const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
......@@ -1277,7 +1363,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, size_delta, lf_delta);
}
deleteNodeHead(node: TreeNode, pos: BufferCursor) {
private deleteNodeHead(node: TreeNode, pos: BufferCursor) {
const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt;
const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
......@@ -1299,7 +1385,7 @@ export class PieceTreeBase {
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 originalStartPos = piece.start;
const originalEndPos = piece.end;
......@@ -1334,7 +1420,7 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode);
}
appendToNode(node: TreeNode, value: string): void {
private appendToNode(node: TreeNode, value: string): void {
if (this.adjustCarriageReturnFromNext(value, node)) {
value += '\n';
}
......@@ -1374,7 +1460,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, value.length, lf_delta);
}
nodeAt(offset: number): NodePosition {
private nodeAt(offset: number): NodePosition {
let x = this.root;
let cache = this._searchCache.get(offset);
if (cache) {
......@@ -1409,7 +1495,7 @@ export class PieceTreeBase {
return null!;
}
nodeAt2(lineNumber: number, column: number): NodePosition {
private nodeAt2(lineNumber: number, column: number): NodePosition {
let x = this.root;
let nodeStartOffset = 0;
......@@ -1476,7 +1562,7 @@ export class PieceTreeBase {
return null!;
}
nodeCharCodeAt(node: TreeNode, offset: number): number {
private nodeCharCodeAt(node: TreeNode, offset: number): number {
if (node.piece.lineFeedCnt < 1) {
return -1;
}
......@@ -1485,7 +1571,7 @@ export class PieceTreeBase {
return buffer.buffer.charCodeAt(newOffset);
}
offsetOfNode(node: TreeNode): number {
private offsetOfNode(node: TreeNode): number {
if (!node) {
return 0;
}
......@@ -1504,11 +1590,11 @@ export class PieceTreeBase {
// #endregion
// #region CRLF
shouldCheckCRLF() {
private shouldCheckCRLF() {
return !(this._EOLNormalized && this._EOL === '\n');
}
startWithLF(val: string | TreeNode): boolean {
private startWithLF(val: string | TreeNode): boolean {
if (typeof val === 'string') {
return val.charCodeAt(0) === 10;
}
......@@ -1532,7 +1618,7 @@ export class PieceTreeBase {
return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10;
}
endWithCR(val: string | TreeNode): boolean {
private endWithCR(val: string | TreeNode): boolean {
if (typeof val === 'string') {
return val.charCodeAt(val.length - 1) === 13;
}
......@@ -1544,7 +1630,7 @@ export class PieceTreeBase {
return this.nodeCharCodeAt(val, val.piece.length - 1) === 13;
}
validateCRLFWithPrevNode(nextNode: TreeNode) {
private validateCRLFWithPrevNode(nextNode: TreeNode) {
if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) {
let node = nextNode.prev();
if (this.endWithCR(node)) {
......@@ -1553,7 +1639,7 @@ export class PieceTreeBase {
}
}
validateCRLFWithNextNode(node: TreeNode) {
private validateCRLFWithNextNode(node: TreeNode) {
if (this.shouldCheckCRLF() && this.endWithCR(node)) {
let nextNode = node.next();
if (this.startWithLF(nextNode)) {
......@@ -1562,7 +1648,7 @@ export class PieceTreeBase {
}
}
fixCRLF(prev: TreeNode, next: TreeNode) {
private fixCRLF(prev: TreeNode, next: TreeNode) {
let nodesToDel: TreeNode[] = [];
// update node
let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts;
......@@ -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)) {
let nextNode = node.next();
if (this.startWithLF(nextNode)) {
......@@ -1667,7 +1753,7 @@ export class PieceTreeBase {
return callback(node) && this.iterate(node.right, callback);
}
getNodeContent(node: TreeNode) {
private getNodeContent(node: TreeNode) {
if (node === SENTINEL) {
return '';
}
......@@ -1695,7 +1781,7 @@ export class PieceTreeBase {
* /
* z
*/
rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
......@@ -1727,7 +1813,7 @@ export class PieceTreeBase {
* \
* z
*/
rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
......@@ -1751,7 +1837,7 @@ export class PieceTreeBase {
return z;
}
getContentOfSubTree(node: TreeNode): string {
private getContentOfSubTree(node: TreeNode): string {
let str = '';
this.iterate(node, node => {
......
......@@ -66,6 +66,7 @@ export class RenderLineInput {
public readonly lineTokens: IViewLineTokens;
public readonly lineDecorations: LineDecoration[];
public readonly tabSize: number;
public readonly startVisibleColumn: number;
public readonly spaceWidth: number;
public readonly stopRenderingLineAfter: number;
public readonly renderWhitespace: RenderWhitespace;
......@@ -89,6 +90,7 @@ export class RenderLineInput {
lineTokens: IViewLineTokens,
lineDecorations: LineDecoration[],
tabSize: number,
startVisibleColumn: number,
spaceWidth: number,
stopRenderingLineAfter: number,
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
......@@ -106,6 +108,7 @@ export class RenderLineInput {
this.lineTokens = lineTokens;
this.lineDecorations = lineDecorations;
this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
this.spaceWidth = spaceWidth;
this.stopRenderingLineAfter = stopRenderingLineAfter;
this.renderWhitespace = (
......@@ -154,6 +157,7 @@ export class RenderLineInput {
&& this.containsRTL === other.containsRTL
&& this.fauxIndentLength === other.fauxIndentLength
&& this.tabSize === other.tabSize
&& this.startVisibleColumn === other.startVisibleColumn
&& this.spaceWidth === other.spaceWidth
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.renderWhitespace === other.renderWhitespace
......@@ -368,7 +372,9 @@ class ResolvedRenderLineInput {
public readonly isOverflowing: boolean,
public readonly parts: LinePart[],
public readonly containsForeignElements: ForeignElementType,
public readonly fauxIndentLength: number,
public readonly tabSize: number,
public readonly startVisibleColumn: number,
public readonly containsRTL: boolean,
public readonly spaceWidth: number,
public readonly renderWhitespace: RenderWhitespace,
......@@ -395,7 +401,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
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;
if (input.lineDecorations.length > 0) {
......@@ -425,7 +431,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
isOverflowing,
tokens,
containsForeignElements,
input.fauxIndentLength,
input.tabSize,
input.startVisibleColumn,
input.containsRTL,
input.spaceWidth,
input.renderWhitespace,
......@@ -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;.
* 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 tokenIndex = 0;
......@@ -555,21 +563,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
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 currentSelectionIndex = 0;
let currentSelection = selections && selections[currentSelectionIndex];
let tmpIndent = startVisibleColumn % tabSize;
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
......@@ -729,7 +726,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const len = input.len;
const isOverflowing = input.isOverflowing;
const parts = input.parts;
const fauxIndentLength = input.fauxIndentLength;
const tabSize = input.tabSize;
const startVisibleColumn = input.startVisibleColumn;
const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth;
const renderWhitespace = input.renderWhitespace;
......@@ -738,7 +737,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0;
let tabsCharDelta = 0;
let visibleColumn = startVisibleColumn;
let charOffsetInPart = 0;
let prevPartContentCnt = 0;
......@@ -764,18 +763,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
let partContentCnt = 0;
{
let _charIndex = charIndex;
let _tabsCharDelta = tabsCharDelta;
let _visibleColumn = visibleColumn;
for (; _charIndex < partEndIndex; _charIndex++) {
const charCode = lineContent.charCodeAt(_charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
_tabsCharDelta += insertSpacesCount - 1;
partContentCnt += insertSpacesCount;
} else {
// must be CharCode.Space
partContentCnt++;
const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
partContentCnt += charWidth;
if (_charIndex >= fauxIndentLength) {
_visibleColumn += charWidth;
}
}
}
......@@ -793,29 +788,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
let charWidth: number;
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
if (insertSpacesCount > 0) {
if (!canUseHalfwidthRightwardsArrow || insertSpacesCount > 1) {
sb.write1(0x2192); // RIGHTWARDS ARROW
} else {
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
}
insertSpacesCount--;
charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
sb.write1(0x2192); // RIGHTWARDS ARROW
} else {
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
}
while (insertSpacesCount > 0) {
for (let space = 2; space <= charWidth; space++) {
sb.write1(0xA0); // &nbsp;
insertSpacesCount--;
}
} else {
// must be CharCode.Space
} else { // must be CharCode.Space
charWidth = 1;
sb.write1(0xB7); // &middot;
}
charOffsetInPart++;
charOffsetInPart += charWidth;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
}
prevPartContentCnt = partContentCnt;
......@@ -833,63 +829,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
let producedCharacters = 1;
let charWidth = 1;
switch (charCode) {
case CharCode.Tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
while (insertSpacesCount > 0) {
producedCharacters = (tabSize - (visibleColumn % tabSize));
charWidth = producedCharacters;
for (let space = 1; space <= producedCharacters; space++) {
sb.write1(0xA0); // &nbsp;
partContentCnt++;
insertSpacesCount--;
}
break;
case CharCode.Space:
sb.write1(0xA0); // &nbsp;
partContentCnt++;
break;
case CharCode.LessThan:
sb.appendASCIIString('&lt;');
partContentCnt++;
break;
case CharCode.GreaterThan:
sb.appendASCIIString('&gt;');
partContentCnt++;
break;
case CharCode.Ampersand:
sb.appendASCIIString('&amp;');
partContentCnt++;
break;
case CharCode.Null:
sb.appendASCIIString('&#00;');
partContentCnt++;
break;
case CharCode.UTF8_BOM:
case CharCode.LINE_SEPARATOR_2028:
sb.write1(0xFFFD);
partContentCnt++;
break;
default:
if (strings.isFullWidthCharacter(charCode)) {
tabsCharDelta++;
charWidth++;
}
if (renderControlCharacters && charCode < 32) {
sb.write1(9216 + charCode);
partContentCnt++;
} else {
sb.write1(charCode);
partContentCnt++;
}
}
charOffsetInPart++;
charOffsetInPart += producedCharacters;
partContentCnt += producedCharacters;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
}
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 {
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 {
* The maximum allowed column at this view line.
*/
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.
*/
......@@ -184,12 +188,14 @@ export class ViewLineData {
continuesWithWrappedLine: boolean,
minColumn: number,
maxColumn: number,
startVisibleColumn: number,
tokens: IViewLineTokens
) {
this.content = content;
this.continuesWithWrappedLine = continuesWithWrappedLine;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
this.startVisibleColumn = startVisibleColumn;
this.tokens = tokens;
}
}
......@@ -231,6 +237,10 @@ export class ViewLineRenderingData {
* The tab size for this view model.
*/
public readonly tabSize: number;
/**
* The visible column at the start of the line (after the fauxIndent)
*/
public readonly startVisibleColumn: number;
constructor(
minColumn: number,
......@@ -241,7 +251,8 @@ export class ViewLineRenderingData {
mightContainNonBasicASCII: boolean,
tokens: IViewLineTokens,
inlineDecorations: InlineDecoration[],
tabSize: number
tabSize: number,
startVisibleColumn: number
) {
this.minColumn = minColumn;
this.maxColumn = maxColumn;
......@@ -254,6 +265,7 @@ export class ViewLineRenderingData {
this.tokens = tokens;
this.inlineDecorations = inlineDecorations;
this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
}
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {
......
......@@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { ITheme } from 'vs/platform/theme/common/themeService';
......@@ -43,7 +42,13 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
public readonly viewLayout: ViewLayout;
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();
this.editorId = editorId;
......@@ -63,20 +68,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const options = this.configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
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);
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
);
this.lines = new SplitLinesCollection(
this.model,
hardWrappingLineMapperFactory,
lineMapperFactory,
this.model.getOptions().tabSize,
wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
......@@ -200,8 +196,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const changes = e.changes;
const versionId = e.versionId;
for (let j = 0, lenJ = changes.length; j < lenJ; j++) {
const change = changes[j];
// Do a first pass to compute line mappings, and a second pass to actually interpret them
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) {
case textModelEvents.RawContentChangedType.Flush: {
......@@ -222,7 +236,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break;
}
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) {
eventsCollector.emit(linesInsertedEvent);
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
......@@ -231,7 +248,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break;
}
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;
if (linesChangedEvent) {
eventsCollector.emit(linesChangedEvent);
......@@ -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.
*/
public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
this.viewportStartLine = 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);
......@@ -546,7 +564,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
mightContainNonBasicASCII,
lineData.tokens,
inlineDecorations,
tabSize
tabSize,
lineData.startVisibleColumn
);
}
......
......@@ -381,7 +381,6 @@ export class LineCommentCommand implements editorCommon.ICommand {
return res;
}
// TODO@Alex -> duplicated in characterHardWrappingLineMapper
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
if (isTab) {
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
......
......@@ -125,6 +125,7 @@ export class Colorizer {
[],
tabSize,
0,
0,
-1,
'none',
false,
......@@ -193,6 +194,7 @@ function _fakeColorize(lines: string[], tabSize: number): string {
[],
tabSize,
0,
0,
-1,
'none',
false,
......@@ -230,6 +232,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport:
[],
tabSize,
0,
0,
-1,
'none',
false,
......
......@@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
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 {
withTestCodeEditor(lines, {}, (editor, cursor) => {
......@@ -200,7 +201,7 @@ suite('SideEditing', () => {
function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void {
const model = TextModel.createFromString(LINES.join('\n'));
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);
cursor.setSelections('tests', [selection]);
......
......@@ -25,6 +25,7 @@ import { IRelaxedTextModelCreationOptions, createTextModel } from 'vs/editor/tes
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
const H = Handler;
......@@ -152,7 +153,7 @@ suite('Editor Controller - Cursor', () => {
thisModel = createTextModel(text);
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);
});
......@@ -776,7 +777,7 @@ suite('Editor Controller - Cursor', () => {
'var newer = require("gulp-newer");',
].join('\n'));
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);
moveTo(cursor, 1, 4, false);
......@@ -816,7 +817,7 @@ suite('Editor Controller - Cursor', () => {
'<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n'));
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);
moveTo(cursor, 10, 10, false);
......@@ -880,7 +881,7 @@ suite('Editor Controller - Cursor', () => {
'<property id="SomeThing" key="SomeKey" value="00X"/>',
].join('\n'));
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);
moveTo(cursor, 10, 10, false);
......@@ -929,7 +930,7 @@ suite('Editor Controller - Cursor', () => {
'var newer = require("gulp-newer");',
].join('\n'));
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);
moveTo(cursor, 1, 4, false);
......@@ -2074,7 +2075,7 @@ suite('Editor Controller - Regression tests', () => {
wordWrap: 'wordWrapColumn',
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);
moveTo(cursor, 1, 43, false);
......@@ -3834,7 +3835,7 @@ function usingCursor(opts: ICursorOpts, callback: (model: TextModel, cursor: Cur
let model = createTextModel(opts.text.join('\n'), opts.modelOpts, opts.languageIdentifier);
model.forceTokenization(model.getLineCount());
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);
callback(model, cursor);
......
......@@ -13,6 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection';
import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
suite('Cursor move command test', () => {
......@@ -32,7 +33,7 @@ suite('Cursor move command test', () => {
thisModel = TextModel.createFromString(text);
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);
});
......
......@@ -38,6 +38,7 @@ suite('viewLineRenderer.renderLine', () => {
[],
tabSize,
0,
0,
-1,
'none',
false,
......@@ -88,6 +89,7 @@ suite('viewLineRenderer.renderLine', () => {
[],
tabSize,
0,
0,
-1,
'none',
false,
......@@ -140,6 +142,7 @@ suite('viewLineRenderer.renderLine', () => {
]),
[],
4,
0,
10,
6,
'boundary',
......@@ -232,6 +235,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'boundary',
......@@ -295,6 +299,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -358,6 +363,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -398,6 +404,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -429,6 +436,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -530,6 +538,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -569,6 +578,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -599,6 +609,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -646,6 +657,7 @@ suite('viewLineRenderer.renderLine', () => {
lineParts,
[],
4,
0,
10,
-1,
'none',
......@@ -728,6 +740,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens(tokens),
[],
4,
0,
10,
-1,
renderWhitespace,
......@@ -754,6 +767,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(21, 3)]),
[new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)],
4,
0,
10,
-1,
'none',
......@@ -794,6 +808,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular)
],
4,
0,
10,
-1,
'none',
......@@ -1209,6 +1224,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(2, 8, 'c', InlineDecorationType.Regular),
],
4,
0,
10,
-1,
'none',
......@@ -1250,6 +1266,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(4, 3)]),
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
4,
0,
10,
-1,
'all',
......@@ -1283,6 +1300,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(4, 3)]),
[new LineDecoration(2, 3, 'before', InlineDecorationType.Before)],
4,
0,
10,
-1,
'all',
......@@ -1317,6 +1335,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(0, 3)]),
[new LineDecoration(1, 2, 'before', InlineDecorationType.Before)],
4,
0,
10,
-1,
'all',
......@@ -1347,6 +1366,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(7, 3)]),
[new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)],
2,
0,
10,
10000,
'none',
......@@ -1381,6 +1401,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(0, 1, 'after', InlineDecorationType.After),
],
2,
0,
10,
10000,
'none',
......@@ -1414,6 +1435,7 @@ suite('viewLineRenderer.renderLine 2', () => {
new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After),
],
4,
0,
10,
10000,
'none',
......@@ -1445,6 +1467,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(15, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1475,6 +1498,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(15, 3)]),
[],
4,
0,
10,
10000,
'all',
......@@ -1511,6 +1535,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(53, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1541,6 +1566,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(100, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1573,6 +1599,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(105, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1604,6 +1631,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(59, 3)]),
[],
4,
0,
10,
10000,
'boundary',
......@@ -1633,6 +1661,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(194, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1666,6 +1695,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens([createPart(194, 3)]),
[],
4,
0,
10,
10000,
'none',
......@@ -1695,6 +1725,7 @@ suite('viewLineRenderer.renderLine 2', () => {
createViewLineTokens(parts),
[],
tabSize,
0,
10,
-1,
'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 @@
*--------------------------------------------------------------------------------------------*/
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';
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', () => {
test('PrefixSumComputer', () => {
......
......@@ -9,14 +9,12 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { toUint32Array } from 'vs/base/common/uint';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import * as modes from 'vs/editor/common/modes';
import { NULL_STATE } from 'vs/editor/common/modes/nullMode';
import { CharacterHardWrappingLineMapperFactory, CharacterHardWrappingLineMapping } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ILineMapping, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer';
import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
......@@ -25,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
suite('Editor ViewModel - SplitLinesCollection', () => {
test('SplitLine', () => {
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.getViewLineContent(model1, 1, 0), 'My First Line');
......@@ -54,38 +52,38 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
}
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.getViewLineContent(model1, 1, 0), 'My First Line');
assert.equal(line1.getViewLineContent(model1, 1, 1), '\tMy Second Line');
assert.equal(line1.getViewLineContent(model1, 1, 2), '\tAnd another one');
assert.equal(line1.getViewLineContent(model1, 1, 1), ' My Second Line');
assert.equal(line1.getViewLineContent(model1, 1, 2), ' And another one');
assert.equal(line1.getViewLineMaxColumn(model1, 1, 0), 14);
assert.equal(line1.getViewLineMaxColumn(model1, 1, 1), 16);
assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 17);
for (let col = 1; col <= 14; col++) {
assert.equal(line1.getModelColumnOfViewPosition(0, col), col, 'getInputColumnOfOutputPosition(0, ' + col + ')');
}
for (let col = 1; col <= 1; col++) {
assert.equal(line1.getModelColumnOfViewPosition(1, 1), 13 + col, 'getInputColumnOfOutputPosition(1, ' + col + ')');
}
for (let col = 2; col <= 16; col++) {
assert.equal(line1.getModelColumnOfViewPosition(1, col), 13 + col - 1, 'getInputColumnOfOutputPosition(1, ' + col + ')');
}
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.equal(line1.getViewLineMaxColumn(model1, 1, 1), 19);
assert.equal(line1.getViewLineMaxColumn(model1, 1, 2), 20);
let actualViewColumnMapping: number[][] = [];
for (let lineIndex = 0; lineIndex < line1.getViewLineCount(); lineIndex++) {
let actualLineViewColumnMapping: number[] = [];
for (let col = 1; col <= line1.getViewLineMaxColumn(model1, 1, lineIndex); col++) {
actualLineViewColumnMapping.push(line1.getModelColumnOfViewPosition(lineIndex, col));
}
actualViewColumnMapping.push(actualLineViewColumnMapping);
}
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++) {
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++) {
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++) {
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', () => {
const fontInfo = config.options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = config.options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = config.options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = config.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingIndent = config.options.get(EditorOption.wrappingIndent);
const hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
wordWrapBreakAfterCharacters
);
const model = TextModel.createFromString([
......@@ -115,7 +111,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => {
const linesCollection = new SplitLinesCollection(
model,
hardWrappingLineMapperFactory,
lineBreaksComputerFactory,
model.getOptions().tabSize,
wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
......@@ -616,12 +612,12 @@ suite('SplitLinesCollection', () => {
]
},
{
content: ' world");',
minColumn: 4,
maxColumn: 12,
content: ' world");',
minColumn: 13,
maxColumn: 21,
tokens: [
{ endIndex: 9, value: 15 },
{ endIndex: 11, value: 16 },
{ endIndex: 18, value: 15 },
{ endIndex: 20, value: 16 },
]
},
{
......@@ -658,28 +654,28 @@ suite('SplitLinesCollection', () => {
]
},
{
content: ' world, this is a ',
minColumn: 4,
maxColumn: 21,
content: ' world, this is a ',
minColumn: 13,
maxColumn: 30,
tokens: [
{ endIndex: 20, value: 28 },
{ endIndex: 29, value: 28 },
]
},
{
content: ' somewhat longer ',
minColumn: 4,
maxColumn: 20,
content: ' somewhat longer ',
minColumn: 13,
maxColumn: 29,
tokens: [
{ endIndex: 19, value: 28 },
{ endIndex: 28, value: 28 },
]
},
{
content: ' line");',
minColumn: 4,
maxColumn: 11,
content: ' line");',
minColumn: 13,
maxColumn: 20,
tokens: [
{ endIndex: 8, value: 28 },
{ endIndex: 10, value: 29 },
{ endIndex: 17, value: 28 },
{ endIndex: 19, value: 29 },
]
},
{
......@@ -749,13 +745,11 @@ suite('SplitLinesCollection', () => {
const fontInfo = configuration.options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = configuration.options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = configuration.options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = configuration.options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingIndent = configuration.options.get(EditorOption.wrappingIndent);
const factory = new CharacterHardWrappingLineMapperFactory(
const factory = new MonospaceLineBreaksComputerFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
wordWrapBreakAfterCharacters
);
const linesCollection = new SplitLinesCollection(
......@@ -778,15 +772,16 @@ function pos(lineNumber: number, column: number): Position {
return new Position(lineNumber, column);
}
function createSplitLine(splitLengths: number[], wrappedLinesPrefix: string, isVisible: boolean = true): SplitLine {
return new SplitLine(createLineMapping(splitLengths, wrappedLinesPrefix), isVisible);
function createSplitLine(splitLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number, isVisible: boolean = true): SplitLine {
return new SplitLine(createLineBreakData(splitLengths, breakingOffsetsVisibleColumn, wrappedTextIndentWidth), isVisible);
}
function createLineMapping(breakingLengths: number[], wrappedLinesPrefix: string): ILineMapping {
return new CharacterHardWrappingLineMapping(
new PrefixSumComputer(toUint32Array(breakingLengths)),
wrappedLinesPrefix
);
function createLineBreakData(breakingLengths: number[], breakingOffsetsVisibleColumn: number[], wrappedTextIndentWidth: number): LineBreakData {
let sums: number[] = [];
for (let i = 0; i < breakingLengths.length; i++) {
sums[i] = (i > 0 ? sums[i - 1] : 0) + breakingLengths[i];
}
return new LineBreakData(sums, breakingOffsetsVisibleColumn, wrappedTextIndentWidth);
}
function createModel(text: string): ISimpleModel {
......
......@@ -7,6 +7,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { TextModel } from 'vs/editor/common/model/textModel';
import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl';
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 {
const EDITOR_ID = 1;
......@@ -15,7 +16,7 @@ export function testViewModel(text: string[], options: IEditorOptions, callback:
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);
......
......@@ -2709,19 +2709,14 @@ declare namespace monaco.editor {
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
/**
* Configure word wrapping characters. A break will be introduced before these characters.
* Defaults to '{([+'.
* Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'.
*/
wordWrapBreakBeforeCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters.
* Defaults to ' \t})]?|&,;'.
* Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
*/
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.
* Defaults to 10000.
......
......@@ -11,6 +11,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
import * as marked from 'vs/base/common/marked/marked';
import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources';
import { EndOfLinePreference } from 'vs/editor/common/model';
export class WalkThroughModel extends EditorModel {
......@@ -111,7 +112,7 @@ export class WalkThroughInput extends EditorInput {
return '';
};
const markdown = ref.object.textEditorModel.getLinesContent().join('\n');
const markdown = ref.object.textEditorModel.getValue(EndOfLinePreference.LF);
marked(markdown, { renderer });
return Promise.all(snippets)
......
......@@ -39,6 +39,7 @@ import { Dimension, size } from 'vs/base/browser/dom';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
import { domEvent } from 'vs/base/browser/event';
import { EndOfLinePreference } from 'vs/editor/common/model';
export const WALK_THROUGH_FOCUS = new RawContextKey<boolean>('interactivePlaygroundFocus', false);
......@@ -278,7 +279,7 @@ export class WalkThroughPart extends BaseEditor {
return;
}
const content = model.main.textEditorModel.getLinesContent().join('\n');
const content = model.main.textEditorModel.getValue(EndOfLinePreference.LF);
if (!strings.endsWith(input.getResource().path, '.md')) {
this.content.innerHTML = content;
this.updateSizeClasses();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册