Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)

* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2

* update distro

* fix layering

* update distro

* fix tests
This commit is contained in:
Anthony Dresser
2020-01-22 13:42:37 -08:00
committed by GitHub
parent 977111eb21
commit bd7aac8ee0
895 changed files with 24651 additions and 14520 deletions

View File

@@ -1,293 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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);
}
}

View File

@@ -0,0 +1,472 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
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);
}
}
}
let arrPool1: number[] = [];
let arrPool2: number[] = [];
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(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
tabSize = tabSize | 0; //@perf
wrappingColumn = +wrappingColumn; //@perf
let requests: string[] = [];
let previousBreakingData: (LineBreakData | null)[] = [];
return {
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
requests.push(lineText);
previousBreakingData.push(previousLineBreakData);
},
finalize: () => {
const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth; //@perf
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);
}
}
arrPool1.length = 0;
arrPool2.length = 0;
return result;
}
};
}
}
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(prevCharCode, prevCharCodeClass, charCode, 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(prevCharCode, prevCharCodeClass, charCode, 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(prevCharCode, prevCharCodeClass, charCode, 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(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass): boolean {
return (
charCode !== CharCode.Space
&& (
(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;
}

View File

@@ -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;
}
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
@@ -10,13 +11,13 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer';
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
export class OutputPosition {
_outputPositionBrand: void;
outputLineIndex: number;
outputOffset: number;
@@ -26,15 +27,56 @@ export class OutputPosition {
}
}
export interface ILineMapping {
getOutputLineCount(): number;
getWrappedLinesIndent(): string;
getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number;
getOutputPositionOfInputOffset(inputOffset: number): OutputPosition;
export class LineBreakData {
constructor(
public breakOffsets: number[],
public breakOffsetsVisibleColumn: number[],
public wrappedTextIndentLength: number
) { }
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
if (outputLineIndex === 0) {
return outputOffset;
} else {
return breakOffsets[outputLineIndex - 1] + outputOffset;
}
}
public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition {
let low = 0;
let high = breakOffsets.length - 1;
let mid = 0;
let midStart = 0;
while (low <= high) {
mid = low + ((high - low) / 2) | 0;
const midStop = breakOffsets[mid];
midStart = mid > 0 ? breakOffsets[mid - 1] : 0;
if (inputOffset < midStart) {
high = mid - 1;
} else if (inputOffset >= midStop) {
low = mid + 1;
} else {
break;
}
}
return new OutputPosition(mid, inputOffset - midStart);
}
}
export interface ILineMapperFactory {
createLineMapping(lineText: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMapping | null;
export interface ILineBreaksComputer {
/**
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
*/
addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void;
finalize(): (LineBreakData | null)[];
}
export interface ILineBreaksComputerFactory {
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
}
export interface ISimpleModel {
@@ -50,6 +92,7 @@ export interface ISplitLine {
isVisible(): boolean;
setVisible(isVisible: boolean): ISplitLine;
getLineBreakData(): LineBreakData | null;
getViewLineCount(): number;
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
@@ -66,19 +109,19 @@ export interface ISplitLine {
export interface IViewModelLinesCollection extends IDisposable {
createCoordinatesConverter(): ICoordinatesConverter;
setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean;
setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean;
setTabSize(newTabSize: number): boolean;
getHiddenAreas(): Range[];
setHiddenAreas(_ranges: Range[]): boolean;
createLineBreaksComputer(): ILineBreaksComputer;
onModelFlushed(): void;
onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null;
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null;
onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null;
onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
acceptVersionId(versionId: number): void;
getViewLineCount(): number;
warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void;
getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[];
getViewLineContent(viewLineNumber: number): string;
@@ -107,9 +150,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
}
public convertViewRangeToModelRange(viewRange: Range): Range {
let start = this._lines.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
let end = this._lines.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
return this._lines.convertViewRangeToModelRange(viewRange);
}
public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position {
@@ -117,9 +158,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
}
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
const validViewStart = this._lines.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
const validViewEnd = this._lines.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
return this._lines.validateViewRange(viewRange, expectedModelRange);
}
// Model -> View conversion and related methods
@@ -135,7 +174,6 @@ export class CoordinatesConverter implements ICoordinatesConverter {
public modelPositionIsVisible(modelPosition: Position): boolean {
return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column);
}
}
const enum IndentGuideRepeatOption {
@@ -144,33 +182,129 @@ const enum IndentGuideRepeatOption {
BlockAll = 2
}
class LineNumberMapper {
private _counts: number[];
private _isValid: boolean;
private _validEndIndex: number;
private _modelToView: number[];
private _viewToModel: number[];
constructor(viewLineCounts: number[]) {
this._counts = viewLineCounts;
this._isValid = false;
this._validEndIndex = -1;
this._modelToView = [];
this._viewToModel = [];
}
private _invalidate(index: number): void {
this._isValid = false;
this._validEndIndex = Math.min(this._validEndIndex, index - 1);
}
private _ensureValid(): void {
if (this._isValid) {
return;
}
for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) {
const viewLineCount = this._counts[i];
const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0);
this._modelToView[i] = viewLinesAbove + viewLineCount;
for (let j = 0; j < viewLineCount; j++) {
this._viewToModel[viewLinesAbove + j] = i;
}
}
// trim things
this._modelToView.length = this._counts.length;
this._viewToModel.length = this._modelToView[this._modelToView.length - 1];
// mark as valid
this._isValid = true;
this._validEndIndex = this._counts.length - 1;
}
public changeValue(index: number, value: number): void {
if (this._counts[index] === value) {
// no change
return;
}
this._counts[index] = value;
this._invalidate(index);
}
public removeValues(start: number, deleteCount: number): void {
this._counts.splice(start, deleteCount);
this._invalidate(start);
}
public insertValues(insertIndex: number, insertArr: number[]): void {
this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr);
this._invalidate(insertIndex);
}
public getTotalValue(): number {
this._ensureValid();
return this._viewToModel.length;
}
public getAccumulatedValue(index: number): number {
this._ensureValid();
return this._modelToView[index];
}
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
this._ensureValid();
const modelLineIndex = this._viewToModel[accumulatedValue];
const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0);
return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove);
}
}
export class SplitLinesCollection implements IViewModelLinesCollection {
private readonly model: ITextModel;
private _validModelVersionId: number;
private wrappingColumn: number;
private columnsForFullWidthChar: number;
private wrappingIndent: WrappingIndent;
private readonly _domLineBreaksComputerFactory: ILineBreaksComputerFactory;
private readonly _monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory;
private fontInfo: FontInfo;
private tabSize: number;
private wrappingColumn: number;
private wrappingIndent: WrappingIndent;
private wrappingAlgorithm: 'monospace' | 'dom';
private lines!: ISplitLine[];
private prefixSumComputer!: PrefixSumComputerWithCache;
private readonly linePositionMapperFactory: ILineMapperFactory;
private prefixSumComputer!: LineNumberMapper;
private hiddenAreasIds!: string[];
constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) {
constructor(
model: ITextModel,
domLineBreaksComputerFactory: ILineBreaksComputerFactory,
monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,
fontInfo: FontInfo,
tabSize: number,
wrappingAlgorithm: 'monospace' | 'dom',
wrappingColumn: number,
wrappingIndent: WrappingIndent,
) {
this.model = model;
this._validModelVersionId = -1;
this._domLineBreaksComputerFactory = domLineBreaksComputerFactory;
this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory;
this.fontInfo = fontInfo;
this.tabSize = tabSize;
this.wrappingAlgorithm = wrappingAlgorithm;
this.wrappingColumn = wrappingColumn;
this.columnsForFullWidthChar = columnsForFullWidthChar;
this.wrappingIndent = wrappingIndent;
this.linePositionMapperFactory = linePositionMapperFactory;
this._constructLines(true);
this._constructLines(/*resetHiddenAreas*/true, null);
}
public dispose(): void {
@@ -181,19 +315,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new CoordinatesConverter(this);
}
private _ensureValidState(): void {
let modelVersion = this.model.getVersionId();
if (modelVersion !== this._validModelVersionId) {
// This is pretty bad, it means we lost track of the model...
throw new Error(`ViewModel is out of sync with Model!`);
}
if (this.lines.length !== this.model.getLineCount()) {
// This is pretty bad, it means we lost track of the model...
this._constructLines(false);
}
}
private _constructLines(resetHiddenAreas: boolean): void {
private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void {
this.lines = [];
if (resetHiddenAreas) {
@@ -201,8 +323,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
let linesContent = this.model.getLinesContent();
let lineCount = linesContent.length;
let values = new Uint32Array(lineCount);
const lineCount = linesContent.length;
const lineBreaksComputer = this.createLineBreaksComputer();
for (let i = 0; i < lineCount; i++) {
lineBreaksComputer.addRequest(linesContent[i], previousLineBreaks ? previousLineBreaks[i] : null);
}
const linesBreaks = lineBreaksComputer.finalize();
let values: number[] = [];
let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts);
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
@@ -220,14 +348,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd);
let line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
let line = createSplitLine(linesBreaks[i], !isInHiddenArea);
values[i] = line.getViewLineCount();
this.lines[i] = line;
}
this._validModelVersionId = this.model.getVersionId();
this.prefixSumComputer = new PrefixSumComputerWithCache(values);
this.prefixSumComputer = new LineNumberMapper(values);
}
public getHiddenAreas(): Range[] {
@@ -351,27 +479,51 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
this.tabSize = newTabSize;
this._constructLines(false);
this._constructLines(/*resetHiddenAreas*/false, null);
return true;
}
public setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean {
if (this.wrappingIndent === wrappingIndent && this.wrappingColumn === wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar) {
public setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean {
const equalFontInfo = this.fontInfo.equals(fontInfo);
const equalWrappingAlgorithm = (this.wrappingAlgorithm === wrappingAlgorithm);
const equalWrappingColumn = (this.wrappingColumn === wrappingColumn);
const equalWrappingIndent = (this.wrappingIndent === wrappingIndent);
if (equalFontInfo && equalWrappingAlgorithm && equalWrappingColumn && equalWrappingIndent) {
return false;
}
this.wrappingIndent = wrappingIndent;
this.wrappingColumn = wrappingColumn;
this.columnsForFullWidthChar = columnsForFullWidthChar;
const onlyWrappingColumnChanged = (equalFontInfo && equalWrappingAlgorithm && !equalWrappingColumn && equalWrappingIndent);
this._constructLines(false);
this.fontInfo = fontInfo;
this.wrappingAlgorithm = wrappingAlgorithm;
this.wrappingColumn = wrappingColumn;
this.wrappingIndent = wrappingIndent;
let previousLineBreaks: ((LineBreakData | null)[]) | null = null;
if (onlyWrappingColumnChanged) {
previousLineBreaks = [];
for (let i = 0, len = this.lines.length; i < len; i++) {
previousLineBreaks[i] = this.lines[i].getLineBreakData();
}
}
this._constructLines(/*resetHiddenAreas*/false, previousLineBreaks);
return true;
}
public createLineBreaksComputer(): ILineBreaksComputer {
const lineBreaksComputerFactory = (
this.wrappingAlgorithm === 'dom'
? this._domLineBreaksComputerFactory
: this._monospaceLineBreaksComputerFactory
);
return lineBreaksComputerFactory.createLineBreaksComputer(this.fontInfo, this.tabSize, this.wrappingColumn, this.wrappingIndent);
}
public onModelFlushed(): void {
this._constructLines(true);
this._constructLines(/*resetHiddenAreas*/true, null);
}
public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
@@ -390,7 +542,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber);
}
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null {
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
if (versionId <= this._validModelVersionId) {
// Here we check for versionId in case the lines were reconstructed in the meantime.
// We don't want to apply stale change events on top of a newer read model state.
@@ -411,10 +563,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let totalOutputLineCount = 0;
let insertLines: ISplitLine[] = [];
let insertPrefixSumValues = new Uint32Array(text.length);
let insertPrefixSumValues: number[] = [];
for (let i = 0, len = text.length; i < len; i++) {
let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
for (let i = 0, len = lineBreaks.length; i < len; i++) {
let line = createSplitLine(lineBreaks[i], !isInHiddenArea);
insertLines.push(line);
let outputLineCount = line.getViewLineCount();
@@ -430,7 +582,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1);
}
public onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
public onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
if (versionId <= this._validModelVersionId) {
// Here we check for versionId in case the lines were reconstructed in the meantime.
// We don't want to apply stale change events on top of a newer read model state.
@@ -441,7 +593,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let oldOutputLineCount = this.lines[lineIndex].getViewLineCount();
let isVisible = this.lines[lineIndex].isVisible();
let line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible);
let line = createSplitLine(lineBreakData, isVisible);
this.lines[lineIndex] = line;
let newOutputLineCount = this.lines[lineIndex].getViewLineCount();
@@ -488,7 +640,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineCount(): number {
this._ensureValidState();
return this.prefixSumComputer.getTotalValue();
}
@@ -496,22 +647,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
if (viewLineNumber < 1) {
return 1;
}
let viewLineCount = this.getViewLineCount();
const viewLineCount = this.getViewLineCount();
if (viewLineNumber > viewLineCount) {
return viewLineCount;
}
return viewLineNumber;
}
/**
* Gives a hint that a lot of requests are about to come in for these line numbers.
*/
public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void {
this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1);
return viewLineNumber | 0;
}
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
minLineNumber = this._toValidViewLineNumber(minLineNumber);
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
@@ -531,7 +674,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
@@ -602,7 +744,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineContent(viewLineNumber: number): string {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -612,7 +753,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineLength(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -622,7 +762,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineMinColumn(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -632,7 +771,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineMaxColumn(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -642,7 +780,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineData(viewLineNumber: number): ViewLineData {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -652,7 +789,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
@@ -691,7 +827,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@@ -719,8 +854,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column);
}
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
}
public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@@ -732,8 +872,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return this.model.validatePosition(new Position(lineIndex + 1, inputColumn));
}
public convertViewRangeToModelRange(viewRange: Range): Range {
const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position {
this._ensureValidState();
const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
const inputLineNumber = validPosition.lineNumber;
@@ -898,6 +1043,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
return InvisibleIdentitySplitLine.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
return null;
}
public getViewLineCount(): number {
return 1;
}
@@ -926,6 +1075,7 @@ class VisibleIdentitySplitLine implements ISplitLine {
false,
1,
lineContent.length + 1,
0,
lineTokens.inflate()
);
}
@@ -968,6 +1118,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
return VisibleIdentitySplitLine.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
return null;
}
public getViewLineCount(): number {
return 0;
}
@@ -1011,18 +1165,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
export class SplitLine implements ISplitLine {
private readonly positionMapper: ILineMapping;
private readonly outputLineCount: number;
private readonly wrappedIndent: string;
private readonly wrappedIndentLength: number;
private readonly _lineBreakData: LineBreakData;
private _isVisible: boolean;
constructor(positionMapper: ILineMapping, isVisible: boolean) {
this.positionMapper = positionMapper;
this.wrappedIndent = this.positionMapper.getWrappedLinesIndent();
this.wrappedIndentLength = this.wrappedIndent.length;
this.outputLineCount = this.positionMapper.getOutputLineCount();
constructor(lineBreakData: LineBreakData, isVisible: boolean) {
this._lineBreakData = lineBreakData;
this._isVisible = isVisible;
}
@@ -1035,22 +1182,26 @@ export class SplitLine implements ISplitLine {
return this;
}
public getLineBreakData(): LineBreakData | null {
return this._lineBreakData;
}
public getViewLineCount(): number {
if (!this._isVisible) {
return 0;
}
return this.outputLineCount;
return this._lineBreakData.breakOffsets.length;
}
private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number {
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, 0);
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, 0);
}
private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
if (outputLineIndex + 1 === this.outputLineCount) {
if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) {
return model.getLineMaxColumn(modelLineNumber) - 1;
}
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0);
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex + 1, 0);
}
public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string {
@@ -1067,7 +1218,7 @@ export class SplitLine implements ISplitLine {
});
if (outputLineIndex > 0) {
r = this.wrappedIndent + r;
r = spaces(this._lineBreakData.wrappedTextIndentLength) + r;
}
return r;
@@ -1082,7 +1233,7 @@ export class SplitLine implements ISplitLine {
let r = endOffset - startOffset;
if (outputLineIndex > 0) {
r = this.wrappedIndent.length + r;
r = this._lineBreakData.wrappedTextIndentLength + r;
}
return r;
@@ -1093,7 +1244,7 @@ export class SplitLine implements ISplitLine {
throw new Error('Not supported');
}
if (outputLineIndex > 0) {
return this.wrappedIndentLength + 1;
return this._lineBreakData.wrappedTextIndentLength + 1;
}
return 1;
}
@@ -1121,25 +1272,28 @@ export class SplitLine implements ISplitLine {
});
if (outputLineIndex > 0) {
lineContent = this.wrappedIndent + lineContent;
lineContent = spaces(this._lineBreakData.wrappedTextIndentLength) + lineContent;
}
let minColumn = (outputLineIndex > 0 ? this.wrappedIndentLength + 1 : 1);
let minColumn = (outputLineIndex > 0 ? this._lineBreakData.wrappedTextIndentLength + 1 : 1);
let maxColumn = lineContent.length + 1;
let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount());
let deltaStartIndex = 0;
if (outputLineIndex > 0) {
deltaStartIndex = this.wrappedIndentLength;
deltaStartIndex = this._lineBreakData.wrappedTextIndentLength;
}
let lineTokens = model.getLineTokens(modelLineNumber);
const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]);
return new ViewLineData(
lineContent,
continuesWithWrappedLine,
minColumn,
maxColumn,
startVisibleColumn,
lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex)
);
}
@@ -1165,25 +1319,25 @@ export class SplitLine implements ISplitLine {
}
let adjustedColumn = outputColumn - 1;
if (outputLineIndex > 0) {
if (adjustedColumn < this.wrappedIndentLength) {
if (adjustedColumn < this._lineBreakData.wrappedTextIndentLength) {
adjustedColumn = 0;
} else {
adjustedColumn -= this.wrappedIndentLength;
adjustedColumn -= this._lineBreakData.wrappedTextIndentLength;
}
}
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1;
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, adjustedColumn) + 1;
}
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
if (!this._isVisible) {
throw new Error('Not supported');
}
let r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
let r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
let outputLineIndex = r.outputLineIndex;
let outputColumn = r.outputOffset + 1;
if (outputLineIndex > 0) {
outputColumn += this.wrappedIndentLength;
outputColumn += this._lineBreakData.wrappedTextIndentLength;
}
// console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn);
@@ -1194,21 +1348,33 @@ export class SplitLine implements ISplitLine {
if (!this._isVisible) {
throw new Error('Not supported');
}
const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
const r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
return (deltaLineNumber + r.outputLineIndex);
}
}
function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine {
let positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
if (positionMapper === null) {
let _spaces: string[] = [''];
function spaces(count: number): string {
if (count >= _spaces.length) {
for (let i = 1; i <= count; i++) {
_spaces[i] = _makeSpaces(i);
}
}
return _spaces[count];
}
function _makeSpaces(count: number): string {
return new Array(count + 1).join(' ');
}
function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine {
if (lineBreakData === null) {
// No mapping needed
if (isVisible) {
return VisibleIdentitySplitLine.INSTANCE;
}
return InvisibleIdentitySplitLine.INSTANCE;
} else {
return new SplitLine(positionMapper, isVisible);
return new SplitLine(lineBreakData, isVisible);
}
}
@@ -1294,10 +1460,22 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return false;
}
public setWrappingSettings(_wrappingIndent: WrappingIndent, _wrappingColumn: number, _columnsForFullWidthChar: number): boolean {
public setWrappingSettings(_fontInfo: FontInfo, _wrappingAlgorithm: 'monospace' | 'dom', _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean {
return false;
}
public createLineBreaksComputer(): ILineBreaksComputer {
let result: null[] = [];
return {
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
result.push(null);
},
finalize: () => {
return result;
}
};
}
public onModelFlushed(): void {
}
@@ -1305,11 +1483,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber);
}
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, _text: string[]): viewEvents.ViewLinesInsertedEvent | null {
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber);
}
public onModelLineChanged(_versionId: number, lineNumber: number, _newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
public onModelLineChanged(_versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null];
}
@@ -1320,9 +1498,6 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return this.model.getLineCount();
}
public warmUpLookupCache(_viewStartLineNumber: number, _viewEndLineNumber: number): void {
}
public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo {
return {
startLineNumber: viewLineNumber,
@@ -1364,6 +1539,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
false,
1,
lineContent.length + 1,
0,
lineTokens.inflate()
);
}

View File

@@ -36,6 +36,9 @@ export class ViewEventHandler extends Disposable {
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
return false;
}
public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean {
return false;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
return false;
}
@@ -69,6 +72,9 @@ export class ViewEventHandler extends Disposable {
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return false;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
return false;
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
return false;
}
@@ -78,9 +84,6 @@ export class ViewEventHandler extends Disposable {
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return false;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
return false;
}
// --- end event handlers
@@ -99,6 +102,12 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewContentSizeChanged:
if (this.onContentSizeChanged(e)) {
shouldRender = true;
}
break;
case viewEvents.ViewEventType.ViewCursorStateChanged:
if (this.onCursorStateChanged(e)) {
shouldRender = true;
@@ -171,6 +180,12 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewThemeChanged:
if (this.onThemeChanged(e)) {
shouldRender = true;
}
break;
case viewEvents.ViewEventType.ViewTokensColorsChanged:
if (this.onTokensColorsChanged(e)) {
shouldRender = true;
@@ -183,12 +198,6 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewThemeChanged:
if (this.onThemeChanged(e)) {
shouldRender = true;
}
break;
default:
console.info('View received unknown event: ');
console.info(e);

View File

@@ -41,7 +41,7 @@ export class Viewport {
export interface IViewLayout {
readonly scrollable: Scrollable;
getScrollable(): Scrollable;
onMaxLineWidthChanged(width: number): void;
@@ -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 {

View File

@@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings';
import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IConfiguration, IViewState } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness, TextModelResolvedOptions } from 'vs/editor/common/model';
import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
@@ -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';
@@ -31,7 +30,7 @@ const USE_IDENTITY_LINES_COLLECTION = true;
export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel {
private readonly editorId: number;
private readonly configuration: editorCommon.IConfiguration;
private readonly configuration: IConfiguration;
private readonly model: ITextModel;
private readonly _tokenizeViewportSoon: RunOnceScheduler;
private hasFocus: boolean;
@@ -43,7 +42,14 @@ 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: IConfiguration,
model: ITextModel,
domLineBreaksComputerFactory: ILineBreaksComputerFactory,
monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,
scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable
) {
super();
this.editorId = editorId;
@@ -61,25 +67,19 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} else {
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 wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm);
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const wrappingIndent = options.get(EditorOption.wrappingIndent);
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
);
this.lines = new SplitLinesCollection(
this.model,
hardWrappingLineMapperFactory,
domLineBreaksComputerFactory,
monospaceLineBreaksComputerFactory,
fontInfo,
this.model.getOptions().tabSize,
wrappingAlgorithm,
wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
wrappingIndent
);
}
@@ -100,6 +100,15 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
}));
this._register(this.viewLayout.onDidContentSizeChange((e) => {
try {
const eventsCollector = this._beginEmit();
eventsCollector.emit(new viewEvents.ViewContentSizeChangedEvent(e));
} finally {
this._endEmit();
}
}));
this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter);
this._registerModelEvents();
@@ -155,11 +164,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
let restorePreviousViewportStart = false;
const options = this.configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const fontInfo = options.get(EditorOption.fontInfo);
const wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm);
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const wrappingIndent = options.get(EditorOption.wrappingIndent);
if (this.lines.setWrappingSettings(wrappingIndent, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth)) {
if (this.lines.setWrappingSettings(fontInfo, wrappingAlgorithm, wrappingInfo.wrappingColumn, wrappingIndent)) {
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
@@ -200,8 +210,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 +250,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 +262,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);
@@ -422,7 +456,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
);
}
public saveState(): editorCommon.IViewState {
public saveState(): IViewState {
const compatViewState = this.viewLayout.saveState();
const scrollTop = compatViewState.scrollTop;
@@ -437,7 +471,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
};
}
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
public reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; } {
if (typeof state.firstPosition === 'undefined') {
// This is a view state serialized by an older version
return this._reduceRestoreStateCompatibility(state);
@@ -452,7 +486,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
};
}
private _reduceRestoreStateCompatibility(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
private _reduceRestoreStateCompatibility(state: IViewState): { scrollLeft: number; scrollTop: number; } {
return {
scrollLeft: state.scrollLeft,
scrollTop: state.scrollTopWithoutViewZones!
@@ -475,8 +509,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 +578,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
mightContainNonBasicASCII,
lineData.tokens,
inlineDecorations,
tabSize
tabSize,
lineData.startVisibleColumn
);
}