mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 18:22:34 -05:00
Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)
* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 * update distro * fix layering * update distro * fix tests
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
472
src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts
Normal file
472
src/vs/editor/common/viewModel/monospaceLineBreaksComputer.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user