mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-10 10:12:34 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
|
||||
import { toUint32Array } from 'vs/editor/common/core/uint';
|
||||
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
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 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 {
|
||||
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) {
|
||||
wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
|
||||
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
|
||||
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1);
|
||||
}
|
||||
if (hardWrappingIndent === WrappingIndent.Indent) {
|
||||
wrappedTextIndent += '\t';
|
||||
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
|
||||
}
|
||||
// Force sticking to beginning of line if indentColumn > 66% breakingColumn
|
||||
if (wrappedTextIndentVisibleColumn > 1 / 2 * 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 (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 _prefixSums: PrefixSumComputer;
|
||||
private _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);
|
||||
}
|
||||
}
|
||||
264
src/vs/editor/common/viewModel/prefixSumComputer.ts
Normal file
264
src/vs/editor/common/viewModel/prefixSumComputer.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { toUint32 } from 'vs/editor/common/core/uint';
|
||||
|
||||
export class PrefixSumIndexOfResult {
|
||||
_prefixSumIndexOfResultBrand: void;
|
||||
|
||||
index: number;
|
||||
remainder: number;
|
||||
|
||||
constructor(index: number, remainder: number) {
|
||||
this.index = index;
|
||||
this.remainder = remainder;
|
||||
}
|
||||
}
|
||||
|
||||
export class PrefixSumComputer {
|
||||
|
||||
/**
|
||||
* values[i] is the value at index i
|
||||
*/
|
||||
private values: Uint32Array;
|
||||
|
||||
/**
|
||||
* prefixSum[i] = SUM(heights[j]), 0 <= j <= i
|
||||
*/
|
||||
private prefixSum: Uint32Array;
|
||||
|
||||
/**
|
||||
* prefixSum[i], 0 <= i <= prefixSumValidIndex can be trusted
|
||||
*/
|
||||
private prefixSumValidIndex: Int32Array;
|
||||
|
||||
constructor(values: Uint32Array) {
|
||||
this.values = values;
|
||||
this.prefixSum = new Uint32Array(values.length);
|
||||
this.prefixSumValidIndex = new Int32Array(1);
|
||||
this.prefixSumValidIndex[0] = -1;
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this.values.length;
|
||||
}
|
||||
|
||||
public insertValues(insertIndex: number, insertValues: Uint32Array): boolean {
|
||||
insertIndex = toUint32(insertIndex);
|
||||
const oldValues = this.values;
|
||||
const oldPrefixSum = this.prefixSum;
|
||||
const insertValuesLen = insertValues.length;
|
||||
|
||||
if (insertValuesLen === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.values = new Uint32Array(oldValues.length + insertValuesLen);
|
||||
this.values.set(oldValues.subarray(0, insertIndex), 0);
|
||||
this.values.set(oldValues.subarray(insertIndex), insertIndex + insertValuesLen);
|
||||
this.values.set(insertValues, insertIndex);
|
||||
|
||||
if (insertIndex - 1 < this.prefixSumValidIndex[0]) {
|
||||
this.prefixSumValidIndex[0] = insertIndex - 1;
|
||||
}
|
||||
|
||||
this.prefixSum = new Uint32Array(this.values.length);
|
||||
if (this.prefixSumValidIndex[0] >= 0) {
|
||||
this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex[0] + 1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public changeValue(index: number, value: number): boolean {
|
||||
index = toUint32(index);
|
||||
value = toUint32(value);
|
||||
|
||||
if (this.values[index] === value) {
|
||||
return false;
|
||||
}
|
||||
this.values[index] = value;
|
||||
if (index - 1 < this.prefixSumValidIndex[0]) {
|
||||
this.prefixSumValidIndex[0] = index - 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public removeValues(startIndex: number, cnt: number): boolean {
|
||||
startIndex = toUint32(startIndex);
|
||||
cnt = toUint32(cnt);
|
||||
|
||||
const oldValues = this.values;
|
||||
const oldPrefixSum = this.prefixSum;
|
||||
|
||||
if (startIndex >= oldValues.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let maxCnt = oldValues.length - startIndex;
|
||||
if (cnt >= maxCnt) {
|
||||
cnt = maxCnt;
|
||||
}
|
||||
|
||||
if (cnt === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.values = new Uint32Array(oldValues.length - cnt);
|
||||
this.values.set(oldValues.subarray(0, startIndex), 0);
|
||||
this.values.set(oldValues.subarray(startIndex + cnt), startIndex);
|
||||
|
||||
this.prefixSum = new Uint32Array(this.values.length);
|
||||
if (startIndex - 1 < this.prefixSumValidIndex[0]) {
|
||||
this.prefixSumValidIndex[0] = startIndex - 1;
|
||||
}
|
||||
if (this.prefixSumValidIndex[0] >= 0) {
|
||||
this.prefixSum.set(oldPrefixSum.subarray(0, this.prefixSumValidIndex[0] + 1));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public getTotalValue(): number {
|
||||
if (this.values.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this._getAccumulatedValue(this.values.length - 1);
|
||||
}
|
||||
|
||||
public getAccumulatedValue(index: number): number {
|
||||
if (index < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
index = toUint32(index);
|
||||
return this._getAccumulatedValue(index);
|
||||
}
|
||||
|
||||
private _getAccumulatedValue(index: number): number {
|
||||
if (index <= this.prefixSumValidIndex[0]) {
|
||||
return this.prefixSum[index];
|
||||
}
|
||||
|
||||
let startIndex = this.prefixSumValidIndex[0] + 1;
|
||||
if (startIndex === 0) {
|
||||
this.prefixSum[0] = this.values[0];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
if (index >= this.values.length) {
|
||||
index = this.values.length - 1;
|
||||
}
|
||||
|
||||
for (let i = startIndex; i <= index; i++) {
|
||||
this.prefixSum[i] = this.prefixSum[i - 1] + this.values[i];
|
||||
}
|
||||
this.prefixSumValidIndex[0] = Math.max(this.prefixSumValidIndex[0], index);
|
||||
return this.prefixSum[index];
|
||||
}
|
||||
|
||||
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
|
||||
accumulatedValue = Math.floor(accumulatedValue); //@perf
|
||||
|
||||
// Compute all sums (to get a fully valid prefixSum)
|
||||
this.getTotalValue();
|
||||
|
||||
let low = 0;
|
||||
let high = this.values.length - 1;
|
||||
let mid: number;
|
||||
let midStop: number;
|
||||
let midStart: number;
|
||||
|
||||
while (low <= high) {
|
||||
mid = low + ((high - low) / 2) | 0;
|
||||
|
||||
midStop = this.prefixSum[mid];
|
||||
midStart = midStop - this.values[mid];
|
||||
|
||||
if (accumulatedValue < midStart) {
|
||||
high = mid - 1;
|
||||
} else if (accumulatedValue >= midStop) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart);
|
||||
}
|
||||
}
|
||||
|
||||
export class PrefixSumComputerWithCache {
|
||||
|
||||
private readonly _actual: PrefixSumComputer;
|
||||
private _cacheAccumulatedValueStart: number = 0;
|
||||
private _cache: PrefixSumIndexOfResult[] = null;
|
||||
|
||||
constructor(values: Uint32Array) {
|
||||
this._actual = new PrefixSumComputer(values);
|
||||
this._bustCache();
|
||||
}
|
||||
|
||||
private _bustCache(): void {
|
||||
this._cacheAccumulatedValueStart = 0;
|
||||
this._cache = null;
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this._actual.getCount();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
1096
src/vs/editor/common/viewModel/splitLinesCollection.ts
Normal file
1096
src/vs/editor/common/viewModel/splitLinesCollection.ts
Normal file
File diff suppressed because it is too large
Load Diff
195
src/vs/editor/common/viewModel/viewEventHandler.ts
Normal file
195
src/vs/editor/common/viewModel/viewEventHandler.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class ViewEventHandler extends Disposable {
|
||||
|
||||
private _shouldRender: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._shouldRender = true;
|
||||
}
|
||||
|
||||
public shouldRender(): boolean {
|
||||
return this._shouldRender;
|
||||
}
|
||||
|
||||
public forceShouldRender(): void {
|
||||
this._shouldRender = true;
|
||||
}
|
||||
|
||||
protected setShouldRender(): void {
|
||||
this._shouldRender = true;
|
||||
}
|
||||
|
||||
public onDidRender(): void {
|
||||
this._shouldRender = false;
|
||||
}
|
||||
|
||||
// --- begin event handlers
|
||||
|
||||
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onRevealRangeRequest(e: viewEvents.ViewRevealRangeRequestEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onTokensColorsChanged(e: viewEvents.ViewTokensColorsChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- end event handlers
|
||||
|
||||
public handleEvents(events: viewEvents.ViewEvent[]): void {
|
||||
|
||||
let shouldRender = false;
|
||||
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
let e = events[i];
|
||||
|
||||
switch (e.type) {
|
||||
|
||||
case viewEvents.ViewEventType.ViewConfigurationChanged:
|
||||
if (this.onConfigurationChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewCursorStateChanged:
|
||||
if (this.onCursorStateChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewDecorationsChanged:
|
||||
if (this.onDecorationsChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewFlushed:
|
||||
if (this.onFlushed(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewFocusChanged:
|
||||
if (this.onFocusChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewLineMappingChanged:
|
||||
if (this.onLineMappingChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewLinesChanged:
|
||||
if (this.onLinesChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewLinesDeleted:
|
||||
if (this.onLinesDeleted(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewLinesInserted:
|
||||
if (this.onLinesInserted(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewRevealRangeRequest:
|
||||
if (this.onRevealRangeRequest(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewScrollChanged:
|
||||
if (this.onScrollChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewTokensChanged:
|
||||
if (this.onTokensChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewTokensColorsChanged:
|
||||
if (this.onTokensColorsChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case viewEvents.ViewEventType.ViewZonesChanged:
|
||||
if (this.onZonesChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case viewEvents.ViewEventType.ViewThemeChanged:
|
||||
if (this.onThemeChanged(e)) {
|
||||
shouldRender = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.info('View received unknown event: ');
|
||||
console.info(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRender) {
|
||||
this._shouldRender = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
299
src/vs/editor/common/viewModel/viewModel.ts
Normal file
299
src/vs/editor/common/viewModel/viewModel.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { INewScrollPosition, IModelDecoration, EndOfLinePreference, IViewState } from 'vs/editor/common/editorCommon';
|
||||
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ViewEvent, IViewEventListener } from 'vs/editor/common/view/viewEvents';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable';
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
|
||||
export interface IViewWhitespaceViewportData {
|
||||
readonly id: number;
|
||||
readonly afterLineNumber: number;
|
||||
readonly verticalOffset: number;
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
export class Viewport {
|
||||
readonly _viewportBrand: void;
|
||||
|
||||
readonly top: number;
|
||||
readonly left: number;
|
||||
readonly width: number;
|
||||
readonly height: number;
|
||||
|
||||
constructor(top: number, left: number, width: number, height: number) {
|
||||
this.top = top | 0;
|
||||
this.left = left | 0;
|
||||
this.width = width | 0;
|
||||
this.height = height | 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewLayout {
|
||||
|
||||
readonly scrollable: Scrollable;
|
||||
|
||||
onMaxLineWidthChanged(width: number): void;
|
||||
|
||||
getScrollWidth(): number;
|
||||
getScrollHeight(): number;
|
||||
|
||||
getCurrentScrollLeft(): number;
|
||||
getCurrentScrollTop(): number;
|
||||
getCurrentViewport(): Viewport;
|
||||
|
||||
getFutureViewport(): Viewport;
|
||||
|
||||
validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition;
|
||||
setScrollPositionNow(position: INewScrollPosition): void;
|
||||
setScrollPositionSmooth(position: INewScrollPosition): void;
|
||||
deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void;
|
||||
|
||||
getLinesViewportData(): IPartialViewLinesViewportData;
|
||||
getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData;
|
||||
getWhitespaces(): IEditorWhitespace[];
|
||||
|
||||
saveState(): IViewState;
|
||||
restoreState(state: IViewState): void;
|
||||
|
||||
isAfterLines(verticalOffset: number): boolean;
|
||||
getLineNumberAtVerticalOffset(verticalOffset: number): number;
|
||||
getVerticalOffsetForLineNumber(lineNumber: number): number;
|
||||
getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData;
|
||||
|
||||
// --------------- Begin vertical whitespace management
|
||||
|
||||
/**
|
||||
* Reserve rendering space.
|
||||
* @return an identifier that can be later used to remove or change the whitespace.
|
||||
*/
|
||||
addWhitespace(afterLineNumber: number, ordinal: number, height: number): number;
|
||||
/**
|
||||
* Change the properties of a whitespace.
|
||||
*/
|
||||
changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean;
|
||||
/**
|
||||
* Remove rendering space
|
||||
*/
|
||||
removeWhitespace(id: number): boolean;
|
||||
/**
|
||||
* Get the layout information for whitespaces currently in the viewport
|
||||
*/
|
||||
getWhitespaceViewportData(): IViewWhitespaceViewportData[];
|
||||
|
||||
// TODO@Alex whitespace management should work via a change accessor sort of thing
|
||||
onHeightMaybeChanged(): void;
|
||||
|
||||
// --------------- End vertical whitespace management
|
||||
}
|
||||
|
||||
export interface ICoordinatesConverter {
|
||||
// View -> Model conversion and related methods
|
||||
convertViewPositionToModelPosition(viewPosition: Position): Position;
|
||||
convertViewRangeToModelRange(viewRange: Range): Range;
|
||||
convertViewSelectionToModelSelection(viewSelection: Selection): Selection;
|
||||
validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position;
|
||||
validateViewRange(viewRange: Range, expectedModelRange: Range): Range;
|
||||
|
||||
// Model -> View conversion and related methods
|
||||
convertModelPositionToViewPosition(modelPosition: Position): Position;
|
||||
convertModelRangeToViewRange(modelRange: Range): Range;
|
||||
convertModelSelectionToViewSelection(modelSelection: Selection): Selection;
|
||||
modelPositionIsVisible(modelPosition: Position): boolean;
|
||||
}
|
||||
|
||||
export interface IViewModel {
|
||||
|
||||
addEventListener(listener: IViewEventListener): IDisposable;
|
||||
|
||||
readonly coordinatesConverter: ICoordinatesConverter;
|
||||
|
||||
readonly viewLayout: IViewLayout;
|
||||
|
||||
/**
|
||||
* Gives a hint that a lot of requests are about to come in for these line numbers.
|
||||
*/
|
||||
setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void;
|
||||
|
||||
getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[];
|
||||
getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData;
|
||||
getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData;
|
||||
getCompletelyVisibleViewRange(): Range;
|
||||
getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range;
|
||||
|
||||
getTabSize(): number;
|
||||
getLineCount(): number;
|
||||
getLineContent(lineNumber: number): string;
|
||||
getLineIndentGuide(lineNumber: number): number;
|
||||
getLineMinColumn(lineNumber: number): number;
|
||||
getLineMaxColumn(lineNumber: number): number;
|
||||
getLineFirstNonWhitespaceColumn(lineNumber: number): number;
|
||||
getLineLastNonWhitespaceColumn(lineNumber: number): number;
|
||||
getAllOverviewRulerDecorations(): ViewModelDecoration[];
|
||||
getValueInRange(range: Range, eol: EndOfLinePreference): string;
|
||||
|
||||
getModelLineMaxColumn(modelLineNumber: number): number;
|
||||
validateModelPosition(modelPosition: IPosition): Position;
|
||||
|
||||
deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position;
|
||||
getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string;
|
||||
getHTMLToCopy(ranges: Range[], emptySelectionClipboard: boolean): string;
|
||||
}
|
||||
|
||||
export class MinimapLinesRenderingData {
|
||||
public readonly tabSize: number;
|
||||
public readonly data: ViewLineData[];
|
||||
|
||||
constructor(
|
||||
tabSize: number,
|
||||
data: ViewLineData[]
|
||||
) {
|
||||
this.tabSize = tabSize;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewLineData {
|
||||
_viewLineDataBrand: void;
|
||||
|
||||
/**
|
||||
* The content at this view line.
|
||||
*/
|
||||
public readonly content: string;
|
||||
/**
|
||||
* The minimum allowed column at this view line.
|
||||
*/
|
||||
public readonly minColumn: number;
|
||||
/**
|
||||
* The maximum allowed column at this view line.
|
||||
*/
|
||||
public readonly maxColumn: number;
|
||||
/**
|
||||
* The tokens at this view line.
|
||||
*/
|
||||
public readonly tokens: ViewLineToken[];
|
||||
|
||||
constructor(
|
||||
content: string,
|
||||
minColumn: number,
|
||||
maxColumn: number,
|
||||
tokens: ViewLineToken[]
|
||||
) {
|
||||
this.content = content;
|
||||
this.minColumn = minColumn;
|
||||
this.maxColumn = maxColumn;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewLineRenderingData {
|
||||
/**
|
||||
* The minimum allowed column at this view line.
|
||||
*/
|
||||
public readonly minColumn: number;
|
||||
/**
|
||||
* The maximum allowed column at this view line.
|
||||
*/
|
||||
public readonly maxColumn: number;
|
||||
/**
|
||||
* The content at this view line.
|
||||
*/
|
||||
public readonly content: string;
|
||||
/**
|
||||
* If set to false, it is guaranteed that `content` contains only LTR chars.
|
||||
*/
|
||||
public readonly mightContainRTL: boolean;
|
||||
/**
|
||||
* If set to false, it is guaranteed that `content` contains only basic ASCII chars.
|
||||
*/
|
||||
public readonly mightContainNonBasicASCII: boolean;
|
||||
/**
|
||||
* The tokens at this view line.
|
||||
*/
|
||||
public readonly tokens: ViewLineToken[];
|
||||
/**
|
||||
* Inline decorations at this view line.
|
||||
*/
|
||||
public readonly inlineDecorations: InlineDecoration[];
|
||||
/**
|
||||
* The tab size for this view model.
|
||||
*/
|
||||
public readonly tabSize: number;
|
||||
|
||||
constructor(
|
||||
minColumn: number,
|
||||
maxColumn: number,
|
||||
content: string,
|
||||
mightContainRTL: boolean,
|
||||
mightContainNonBasicASCII: boolean,
|
||||
tokens: ViewLineToken[],
|
||||
inlineDecorations: InlineDecoration[],
|
||||
tabSize: number
|
||||
) {
|
||||
this.minColumn = minColumn;
|
||||
this.maxColumn = maxColumn;
|
||||
this.content = content;
|
||||
this.mightContainRTL = mightContainRTL;
|
||||
this.mightContainNonBasicASCII = mightContainNonBasicASCII;
|
||||
this.tokens = tokens;
|
||||
this.inlineDecorations = inlineDecorations;
|
||||
this.tabSize = tabSize;
|
||||
}
|
||||
}
|
||||
|
||||
export class InlineDecoration {
|
||||
_inlineDecorationBrand: void;
|
||||
|
||||
readonly range: Range;
|
||||
readonly inlineClassName: string;
|
||||
readonly insertsBeforeOrAfter: boolean;
|
||||
|
||||
constructor(range: Range, inlineClassName: string, insertsBeforeOrAfter: boolean) {
|
||||
this.range = range;
|
||||
this.inlineClassName = inlineClassName;
|
||||
this.insertsBeforeOrAfter = insertsBeforeOrAfter;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewModelDecoration {
|
||||
_viewModelDecorationBrand: void;
|
||||
|
||||
public range: Range;
|
||||
public readonly source: IModelDecoration;
|
||||
|
||||
constructor(source: IModelDecoration) {
|
||||
this.range = null;
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewEventsCollector {
|
||||
|
||||
private _events: ViewEvent[];
|
||||
private _eventsLen = 0;
|
||||
|
||||
constructor() {
|
||||
this._events = [];
|
||||
this._eventsLen = 0;
|
||||
}
|
||||
|
||||
public emit(event: ViewEvent) {
|
||||
this._events[this._eventsLen++] = event;
|
||||
}
|
||||
|
||||
public finalize(): ViewEvent[] {
|
||||
let result = this._events;
|
||||
this._events = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
195
src/vs/editor/common/viewModel/viewModelDecorations.ts
Normal file
195
src/vs/editor/common/viewModel/viewModelDecorations.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { InlineDecoration, ViewModelDecoration, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
|
||||
export interface IDecorationsViewportData {
|
||||
/**
|
||||
* decorations in the viewport.
|
||||
*/
|
||||
readonly decorations: ViewModelDecoration[];
|
||||
/**
|
||||
* inline decorations grouped by each line in the viewport.
|
||||
*/
|
||||
readonly inlineDecorations: InlineDecoration[][];
|
||||
}
|
||||
|
||||
export class ViewModelDecorations implements IDisposable {
|
||||
|
||||
private readonly editorId: number;
|
||||
private readonly model: editorCommon.IModel;
|
||||
private readonly configuration: editorCommon.IConfiguration;
|
||||
private readonly _coordinatesConverter: ICoordinatesConverter;
|
||||
|
||||
private _decorationsCache: { [decorationId: string]: ViewModelDecoration; };
|
||||
|
||||
private _cachedModelDecorationsResolver: IDecorationsViewportData;
|
||||
private _cachedModelDecorationsResolverViewRange: Range;
|
||||
|
||||
constructor(editorId: number, model: editorCommon.IModel, configuration: editorCommon.IConfiguration, coordinatesConverter: ICoordinatesConverter) {
|
||||
this.editorId = editorId;
|
||||
this.model = model;
|
||||
this.configuration = configuration;
|
||||
this._coordinatesConverter = coordinatesConverter;
|
||||
this._decorationsCache = Object.create(null);
|
||||
this._clearCachedModelDecorationsResolver();
|
||||
}
|
||||
|
||||
private _clearCachedModelDecorationsResolver(): void {
|
||||
this._cachedModelDecorationsResolver = null;
|
||||
this._cachedModelDecorationsResolverViewRange = null;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._decorationsCache = null;
|
||||
this._clearCachedModelDecorationsResolver();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._decorationsCache = Object.create(null);
|
||||
this._clearCachedModelDecorationsResolver();
|
||||
}
|
||||
|
||||
public onModelDecorationsChanged(e: IModelDecorationsChangedEvent): void {
|
||||
let changedDecorations = e.changedDecorations;
|
||||
for (let i = 0, len = changedDecorations.length; i < len; i++) {
|
||||
let changedDecoration = changedDecorations[i];
|
||||
let myDecoration = this._decorationsCache[changedDecoration];
|
||||
if (!myDecoration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
myDecoration.range = null;
|
||||
}
|
||||
|
||||
let removedDecorations = e.removedDecorations;
|
||||
if (this._decorationsCache !== null && this._decorationsCache !== undefined) {
|
||||
for (let i = 0, len = removedDecorations.length; i < len; i++) {
|
||||
let removedDecoration = removedDecorations[i];
|
||||
delete this._decorationsCache[removedDecoration];
|
||||
}
|
||||
}
|
||||
|
||||
this._clearCachedModelDecorationsResolver();
|
||||
}
|
||||
|
||||
public onLineMappingChanged(): void {
|
||||
this._decorationsCache = Object.create(null);
|
||||
|
||||
this._clearCachedModelDecorationsResolver();
|
||||
}
|
||||
|
||||
private _getOrCreateViewModelDecoration(modelDecoration: editorCommon.IModelDecoration): ViewModelDecoration {
|
||||
let id = modelDecoration.id;
|
||||
let r = this._decorationsCache[id];
|
||||
if (!r) {
|
||||
r = new ViewModelDecoration(modelDecoration);
|
||||
this._decorationsCache[id] = r;
|
||||
}
|
||||
if (r.range === null) {
|
||||
const modelRange = modelDecoration.range;
|
||||
if (modelDecoration.options.isWholeLine) {
|
||||
let start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1));
|
||||
let end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)));
|
||||
r.range = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
} else {
|
||||
r.range = this._coordinatesConverter.convertModelRangeToViewRange(modelRange);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public getAllOverviewRulerDecorations(): ViewModelDecoration[] {
|
||||
let modelDecorations = this.model.getAllDecorations(this.editorId, this.configuration.editor.readOnly);
|
||||
let result: ViewModelDecoration[] = [], resultLen = 0;
|
||||
for (let i = 0, len = modelDecorations.length; i < len; i++) {
|
||||
let modelDecoration = modelDecorations[i];
|
||||
let decorationOptions = modelDecoration.options;
|
||||
|
||||
if (!decorationOptions.overviewRuler.color) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
|
||||
result[resultLen++] = viewModelDecoration;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData {
|
||||
var cacheIsValid = true;
|
||||
cacheIsValid = cacheIsValid && (this._cachedModelDecorationsResolver !== null);
|
||||
cacheIsValid = cacheIsValid && (viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange));
|
||||
if (!cacheIsValid) {
|
||||
this._cachedModelDecorationsResolver = this._getDecorationsViewportData(viewRange);
|
||||
this._cachedModelDecorationsResolverViewRange = viewRange;
|
||||
}
|
||||
return this._cachedModelDecorationsResolver;
|
||||
}
|
||||
|
||||
private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData {
|
||||
let viewportModelRange = this._coordinatesConverter.convertViewRangeToModelRange(viewportRange);
|
||||
let startLineNumber = viewportRange.startLineNumber;
|
||||
let endLineNumber = viewportRange.endLineNumber;
|
||||
let modelDecorations = this.model.getDecorationsInRange(viewportModelRange, this.editorId, this.configuration.editor.readOnly);
|
||||
|
||||
let decorationsInViewport: ViewModelDecoration[] = [], decorationsInViewportLen = 0;
|
||||
let inlineDecorations: InlineDecoration[][] = [];
|
||||
for (let j = startLineNumber; j <= endLineNumber; j++) {
|
||||
inlineDecorations[j - startLineNumber] = [];
|
||||
}
|
||||
|
||||
for (let i = 0, len = modelDecorations.length; i < len; i++) {
|
||||
let modelDecoration = modelDecorations[i];
|
||||
let decorationOptions = modelDecoration.options;
|
||||
|
||||
let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
|
||||
let viewRange = viewModelDecoration.range;
|
||||
|
||||
decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration;
|
||||
|
||||
if (decorationOptions.inlineClassName) {
|
||||
let inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, false);
|
||||
let intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber);
|
||||
let intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber);
|
||||
for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) {
|
||||
inlineDecorations[j - startLineNumber].push(inlineDecoration);
|
||||
}
|
||||
}
|
||||
if (decorationOptions.beforeContentClassName) {
|
||||
if (startLineNumber <= viewRange.startLineNumber && viewRange.startLineNumber <= endLineNumber) {
|
||||
// TODO: What happens if the startLineNumber and startColumn is at the end of a line?
|
||||
let inlineDecoration = new InlineDecoration(
|
||||
new Range(viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn + 1),
|
||||
decorationOptions.beforeContentClassName,
|
||||
true
|
||||
);
|
||||
inlineDecorations[viewRange.startLineNumber - startLineNumber].push(inlineDecoration);
|
||||
}
|
||||
}
|
||||
if (decorationOptions.afterContentClassName) {
|
||||
if (startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber && viewRange.endColumn > 1) {
|
||||
let inlineDecoration = new InlineDecoration(
|
||||
new Range(viewRange.endLineNumber, viewRange.endColumn - 1, viewRange.endLineNumber, viewRange.endColumn),
|
||||
decorationOptions.afterContentClassName,
|
||||
true
|
||||
);
|
||||
inlineDecorations[viewRange.endLineNumber - startLineNumber].push(inlineDecoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
decorations: decorationsInViewport,
|
||||
inlineDecorations: inlineDecorations
|
||||
};
|
||||
}
|
||||
}
|
||||
559
src/vs/editor/common/viewModel/viewModelImpl.ts
Normal file
559
src/vs/editor/common/viewModel/viewModelImpl.ts
Normal file
@@ -0,0 +1,559 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { EmitterEvent } from 'vs/base/common/eventEmitter';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { TokenizationRegistry, ColorId, LanguageId } from 'vs/editor/common/modes';
|
||||
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
|
||||
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, ViewEventsCollector } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { SplitLinesCollection, IViewModelLinesCollection, IdentityLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
|
||||
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
|
||||
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
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 model: editorCommon.IModel;
|
||||
private readonly lines: IViewModelLinesCollection;
|
||||
public readonly coordinatesConverter: ICoordinatesConverter;
|
||||
public readonly viewLayout: ViewLayout;
|
||||
|
||||
private readonly decorations: ViewModelDecorations;
|
||||
|
||||
private _isDisposing: boolean;
|
||||
private _centeredViewLine: number;
|
||||
|
||||
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: editorCommon.IModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
|
||||
super();
|
||||
|
||||
this.editorId = editorId;
|
||||
this.configuration = configuration;
|
||||
this.model = model;
|
||||
|
||||
if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) {
|
||||
|
||||
this.lines = new IdentityLinesCollection(this.model);
|
||||
|
||||
} else {
|
||||
const conf = this.configuration.editor;
|
||||
|
||||
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
|
||||
conf.wrappingInfo.wordWrapBreakBeforeCharacters,
|
||||
conf.wrappingInfo.wordWrapBreakAfterCharacters,
|
||||
conf.wrappingInfo.wordWrapBreakObtrusiveCharacters
|
||||
);
|
||||
|
||||
this.lines = new SplitLinesCollection(
|
||||
this.model,
|
||||
hardWrappingLineMapperFactory,
|
||||
this.model.getOptions().tabSize,
|
||||
conf.wrappingInfo.wrappingColumn,
|
||||
conf.fontInfo.typicalFullwidthCharacterWidth / conf.fontInfo.typicalHalfwidthCharacterWidth,
|
||||
conf.wrappingInfo.wrappingIndent
|
||||
);
|
||||
}
|
||||
|
||||
this.coordinatesConverter = this.lines.createCoordinatesConverter();
|
||||
|
||||
this.viewLayout = this._register(new ViewLayout(this.configuration, this.getLineCount(), scheduleAtNextAnimationFrame));
|
||||
|
||||
this._register(this.viewLayout.onDidScroll((e) => {
|
||||
this._emit([new viewEvents.ViewScrollChangedEvent(e)]);
|
||||
}));
|
||||
|
||||
this._isDisposing = false;
|
||||
this._centeredViewLine = -1;
|
||||
|
||||
this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.coordinatesConverter);
|
||||
|
||||
this._register(this.model.addBulkListener((events: EmitterEvent[]) => {
|
||||
if (this._isDisposing) {
|
||||
// Disposing the lines might end up sending model decoration changed events
|
||||
// ...we no longer care about them...
|
||||
return;
|
||||
}
|
||||
let eventsCollector = new ViewEventsCollector();
|
||||
this._onModelEvents(eventsCollector, events);
|
||||
this._emit(eventsCollector.finalize());
|
||||
}));
|
||||
|
||||
this._register(this.configuration.onDidChange((e) => {
|
||||
const eventsCollector = new ViewEventsCollector();
|
||||
this._onConfigurationChanged(eventsCollector, e);
|
||||
this._emit(eventsCollector.finalize());
|
||||
}));
|
||||
|
||||
this._register(MinimapTokensColorTracker.getInstance().onDidChange(() => {
|
||||
this._emit([new viewEvents.ViewTokensColorsChangedEvent()]);
|
||||
}));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposing = true;
|
||||
this.decorations.dispose();
|
||||
this.lines.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _onConfigurationChanged(eventsCollector: ViewEventsCollector, e: IConfigurationChangedEvent): void {
|
||||
|
||||
// We might need to restore the current centered view range, so save it (if available)
|
||||
const previousCenteredModelRange = this.getCenteredRangeInViewport();
|
||||
let revealPreviousCenteredModelRange = false;
|
||||
|
||||
const conf = this.configuration.editor;
|
||||
|
||||
if (this.lines.setWrappingSettings(conf.wrappingInfo.wrappingIndent, conf.wrappingInfo.wrappingColumn, conf.fontInfo.typicalFullwidthCharacterWidth / conf.fontInfo.typicalHalfwidthCharacterWidth)) {
|
||||
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
this.decorations.onLineMappingChanged();
|
||||
this.viewLayout.onFlushed(this.getLineCount());
|
||||
|
||||
if (this.viewLayout.getCurrentScrollTop() !== 0) {
|
||||
// Never change the scroll position from 0 to something else...
|
||||
revealPreviousCenteredModelRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.readOnly) {
|
||||
// Must read again all decorations due to readOnly filtering
|
||||
this.decorations.reset();
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
}
|
||||
|
||||
eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e));
|
||||
this.viewLayout.onConfigurationChanged(e);
|
||||
|
||||
if (revealPreviousCenteredModelRange && previousCenteredModelRange) {
|
||||
// modelLine -> viewLine
|
||||
const newCenteredViewRange = this.coordinatesConverter.convertModelRangeToViewRange(previousCenteredModelRange);
|
||||
|
||||
// Send a reveal event to restore the centered content
|
||||
eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(
|
||||
newCenteredViewRange,
|
||||
viewEvents.VerticalRevealType.Center,
|
||||
false,
|
||||
editorCommon.ScrollType.Immediate
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private _onModelEvents(eventsCollector: ViewEventsCollector, events: EmitterEvent[]): void {
|
||||
|
||||
// A quick check if there are model content change events incoming
|
||||
// in order to update the configuration and reset the centered view line
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const eventType = events[i].type;
|
||||
if (eventType === textModelEvents.TextModelEventType.ModelRawContentChanged2) {
|
||||
// There is a content change event
|
||||
this._centeredViewLine = -1;
|
||||
this.configuration.setMaxLineNumber(this.model.getLineCount());
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let hadOtherModelChange = false;
|
||||
let hadModelLineChangeThatChangedLineMapping = false;
|
||||
|
||||
for (let i = 0, len = events.length; i < len; i++) {
|
||||
const _e = events[i];
|
||||
const type = _e.type;
|
||||
const data = _e.data;
|
||||
|
||||
switch (type) {
|
||||
|
||||
case textModelEvents.TextModelEventType.ModelRawContentChanged2: {
|
||||
const e = <textModelEvents.ModelRawContentChangedEvent>data;
|
||||
const changes = e.changes;
|
||||
const versionId = e.versionId;
|
||||
|
||||
for (let j = 0, lenJ = changes.length; j < lenJ; j++) {
|
||||
const change = changes[j];
|
||||
|
||||
switch (change.changeType) {
|
||||
case textModelEvents.RawContentChangedType.Flush: {
|
||||
this.lines.onModelFlushed();
|
||||
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
|
||||
this.decorations.reset();
|
||||
this.viewLayout.onFlushed(this.getLineCount());
|
||||
hadOtherModelChange = true;
|
||||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LinesDeleted: {
|
||||
const linesDeletedEvent = this.lines.onModelLinesDeleted(versionId, change.fromLineNumber, change.toLineNumber);
|
||||
if (linesDeletedEvent !== null) {
|
||||
eventsCollector.emit(linesDeletedEvent);
|
||||
this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);
|
||||
}
|
||||
hadOtherModelChange = true;
|
||||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LinesInserted: {
|
||||
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail.split('\n'));
|
||||
if (linesInsertedEvent !== null) {
|
||||
eventsCollector.emit(linesInsertedEvent);
|
||||
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
|
||||
}
|
||||
hadOtherModelChange = true;
|
||||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.LineChanged: {
|
||||
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, change.detail);
|
||||
hadModelLineChangeThatChangedLineMapping = lineMappingChanged;
|
||||
if (linesChangedEvent) {
|
||||
eventsCollector.emit(linesChangedEvent);
|
||||
}
|
||||
if (linesInsertedEvent) {
|
||||
eventsCollector.emit(linesInsertedEvent);
|
||||
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
|
||||
}
|
||||
if (linesDeletedEvent) {
|
||||
eventsCollector.emit(linesDeletedEvent);
|
||||
this.viewLayout.onLinesDeleted(linesDeletedEvent.fromLineNumber, linesDeletedEvent.toLineNumber);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case textModelEvents.RawContentChangedType.EOLChanged: {
|
||||
// Nothing to do. The new version will be accepted below
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.lines.acceptVersionId(versionId);
|
||||
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelTokensChanged: {
|
||||
const e = <textModelEvents.IModelTokensChangedEvent>data;
|
||||
|
||||
let viewRanges: { fromLineNumber: number; toLineNumber: number; }[] = [];
|
||||
for (let j = 0, lenJ = e.ranges.length; j < lenJ; j++) {
|
||||
const modelRange = e.ranges[j];
|
||||
const viewStartLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.fromLineNumber, 1)).lineNumber;
|
||||
const viewEndLineNumber = this.coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.toLineNumber, this.model.getLineMaxColumn(modelRange.toLineNumber))).lineNumber;
|
||||
viewRanges[j] = {
|
||||
fromLineNumber: viewStartLineNumber,
|
||||
toLineNumber: viewEndLineNumber
|
||||
};
|
||||
}
|
||||
eventsCollector.emit(new viewEvents.ViewTokensChangedEvent(viewRanges));
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelLanguageChanged: {
|
||||
// That's ok, a model tokens changed event will follow shortly
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelContentChanged: {
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelOptionsChanged: {
|
||||
// A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here
|
||||
if (this.lines.setTabSize(this.model.getOptions().tabSize)) {
|
||||
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
this.decorations.onLineMappingChanged();
|
||||
this.viewLayout.onFlushed(this.getLineCount());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelDecorationsChanged: {
|
||||
const e = <textModelEvents.IModelDecorationsChangedEvent>data;
|
||||
this.decorations.onModelDecorationsChanged(e);
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
break;
|
||||
}
|
||||
case textModelEvents.TextModelEventType.ModelDispose: {
|
||||
// Ignore, since the editor will take care of this and destroy the view shortly
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.info('View received unknown event: ');
|
||||
console.info(type, data);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {
|
||||
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
this.decorations.onLineMappingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public setHiddenAreas(ranges: Range[]): void {
|
||||
let eventsCollector = new ViewEventsCollector();
|
||||
let lineMappingChanged = this.lines.setHiddenAreas(ranges);
|
||||
if (lineMappingChanged) {
|
||||
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
|
||||
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
|
||||
this.decorations.onLineMappingChanged();
|
||||
this.viewLayout.onFlushed(this.getLineCount());
|
||||
}
|
||||
this._emit(eventsCollector.finalize());
|
||||
}
|
||||
|
||||
public getCenteredRangeInViewport(): Range {
|
||||
if (this._centeredViewLine === -1) {
|
||||
// Never got rendered or not rendered since last content change event
|
||||
return null;
|
||||
}
|
||||
let viewLineNumber = this._centeredViewLine;
|
||||
let currentCenteredViewRange = new Range(viewLineNumber, this.getLineMinColumn(viewLineNumber), viewLineNumber, this.getLineMaxColumn(viewLineNumber));
|
||||
return this.coordinatesConverter.convertViewRangeToModelRange(currentCenteredViewRange);
|
||||
}
|
||||
|
||||
public getCompletelyVisibleViewRange(): Range {
|
||||
const partialData = this.viewLayout.getLinesViewportData();
|
||||
const startViewLineNumber = partialData.completelyVisibleStartLineNumber;
|
||||
const endViewLineNumber = partialData.completelyVisibleEndLineNumber;
|
||||
|
||||
return new Range(
|
||||
startViewLineNumber, this.getLineMinColumn(startViewLineNumber),
|
||||
endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)
|
||||
);
|
||||
}
|
||||
|
||||
public getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range {
|
||||
const partialData = this.viewLayout.getLinesViewportDataAtScrollTop(scrollTop);
|
||||
const startViewLineNumber = partialData.completelyVisibleStartLineNumber;
|
||||
const endViewLineNumber = partialData.completelyVisibleEndLineNumber;
|
||||
|
||||
return new Range(
|
||||
startViewLineNumber, this.getLineMinColumn(startViewLineNumber),
|
||||
endViewLineNumber, this.getLineMaxColumn(endViewLineNumber)
|
||||
);
|
||||
}
|
||||
|
||||
public getTabSize(): number {
|
||||
return this.model.getOptions().tabSize;
|
||||
}
|
||||
|
||||
public getLineCount(): number {
|
||||
return this.lines.getViewLineCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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._centeredViewLine = centeredLineNumber;
|
||||
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
|
||||
}
|
||||
|
||||
public getLineIndentGuide(lineNumber: number): number {
|
||||
return this.lines.getViewLineIndentGuide(lineNumber);
|
||||
}
|
||||
|
||||
public getLineContent(lineNumber: number): string {
|
||||
return this.lines.getViewLineContent(lineNumber);
|
||||
}
|
||||
|
||||
public getLineMinColumn(lineNumber: number): number {
|
||||
return this.lines.getViewLineMinColumn(lineNumber);
|
||||
}
|
||||
|
||||
public getLineMaxColumn(lineNumber: number): number {
|
||||
return this.lines.getViewLineMaxColumn(lineNumber);
|
||||
}
|
||||
|
||||
public getLineFirstNonWhitespaceColumn(lineNumber: number): number {
|
||||
var result = strings.firstNonWhitespaceIndex(this.getLineContent(lineNumber));
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
public getLineLastNonWhitespaceColumn(lineNumber: number): number {
|
||||
var result = strings.lastNonWhitespaceIndex(this.getLineContent(lineNumber));
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 2;
|
||||
}
|
||||
|
||||
public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] {
|
||||
return this.decorations.getDecorationsViewportData(visibleRange).decorations;
|
||||
}
|
||||
|
||||
public getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData {
|
||||
let mightContainRTL = this.model.mightContainRTL();
|
||||
let mightContainNonBasicASCII = this.model.mightContainNonBasicASCII();
|
||||
let tabSize = this.getTabSize();
|
||||
let lineData = this.lines.getViewLineData(lineNumber);
|
||||
let allInlineDecorations = this.decorations.getDecorationsViewportData(visibleRange).inlineDecorations;
|
||||
let inlineDecorations = allInlineDecorations[lineNumber - visibleRange.startLineNumber];
|
||||
|
||||
return new ViewLineRenderingData(
|
||||
lineData.minColumn,
|
||||
lineData.maxColumn,
|
||||
lineData.content,
|
||||
mightContainRTL,
|
||||
mightContainNonBasicASCII,
|
||||
lineData.tokens,
|
||||
inlineDecorations,
|
||||
tabSize
|
||||
);
|
||||
}
|
||||
|
||||
public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {
|
||||
let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed);
|
||||
return new MinimapLinesRenderingData(
|
||||
this.getTabSize(),
|
||||
result
|
||||
);
|
||||
}
|
||||
|
||||
public getAllOverviewRulerDecorations(): ViewModelDecoration[] {
|
||||
return this.decorations.getAllOverviewRulerDecorations();
|
||||
}
|
||||
|
||||
public getValueInRange(range: Range, eol: editorCommon.EndOfLinePreference): string {
|
||||
var modelRange = this.coordinatesConverter.convertViewRangeToModelRange(range);
|
||||
return this.model.getValueInRange(modelRange, eol);
|
||||
}
|
||||
|
||||
public getModelLineMaxColumn(modelLineNumber: number): number {
|
||||
return this.model.getLineMaxColumn(modelLineNumber);
|
||||
}
|
||||
|
||||
public validateModelPosition(position: IPosition): Position {
|
||||
return this.model.validatePosition(position);
|
||||
}
|
||||
|
||||
public deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position {
|
||||
const modelAnchor = this.coordinatesConverter.convertViewPositionToModelPosition(viewAnchorPosition);
|
||||
if (this.model.getEOL().length === 2) {
|
||||
// This model uses CRLF, so the delta must take that into account
|
||||
if (deltaOffset < 0) {
|
||||
deltaOffset -= lineFeedCnt;
|
||||
} else {
|
||||
deltaOffset += lineFeedCnt;
|
||||
}
|
||||
}
|
||||
|
||||
const modelAnchorOffset = this.model.getOffsetAt(modelAnchor);
|
||||
const resultOffset = modelAnchorOffset + deltaOffset;
|
||||
return this.model.getPositionAt(resultOffset);
|
||||
}
|
||||
|
||||
public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string {
|
||||
let newLineCharacter = this.model.getEOL();
|
||||
|
||||
if (ranges.length === 1) {
|
||||
let range: Range = ranges[0];
|
||||
if (range.isEmpty()) {
|
||||
if (emptySelectionClipboard) {
|
||||
let modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
|
||||
return this.model.getLineContent(modelLineNumber) + newLineCharacter;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return this.getValueInRange(range, editorCommon.EndOfLinePreference.TextDefined);
|
||||
} else {
|
||||
ranges = ranges.slice(0).sort(Range.compareRangesUsingStarts);
|
||||
let result: string[] = [];
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
result.push(this.getValueInRange(ranges[i], editorCommon.EndOfLinePreference.TextDefined));
|
||||
}
|
||||
|
||||
return result.join(newLineCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
public getHTMLToCopy(viewRanges: Range[], emptySelectionClipboard: boolean): string {
|
||||
if (this.model.getLanguageIdentifier().id === LanguageId.PlainText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (viewRanges.length !== 1) {
|
||||
// no multiple selection support at this time
|
||||
return null;
|
||||
}
|
||||
|
||||
let range = this.coordinatesConverter.convertViewRangeToModelRange(viewRanges[0]);
|
||||
if (range.isEmpty()) {
|
||||
if (!emptySelectionClipboard) {
|
||||
// nothing to copy
|
||||
return null;
|
||||
}
|
||||
let lineNumber = range.startLineNumber;
|
||||
range = new Range(lineNumber, this.model.getLineMinColumn(lineNumber), lineNumber, this.model.getLineMaxColumn(lineNumber));
|
||||
}
|
||||
|
||||
const fontInfo = this.configuration.editor.fontInfo;
|
||||
const colorMap = this._getColorMap();
|
||||
|
||||
return (
|
||||
`<div style="`
|
||||
+ `color: ${colorMap[ColorId.DefaultForeground]};`
|
||||
+ `background-color: ${colorMap[ColorId.DefaultBackground]};`
|
||||
+ `font-family: ${fontInfo.fontFamily};`
|
||||
+ `font-weight: ${fontInfo.fontWeight};`
|
||||
+ `font-size: ${fontInfo.fontSize}px;`
|
||||
+ `line-height: ${fontInfo.lineHeight}px;`
|
||||
+ `white-space: pre;`
|
||||
+ `">`
|
||||
+ this._getHTMLToCopy(range, colorMap)
|
||||
+ '</div>'
|
||||
);
|
||||
}
|
||||
|
||||
private _getHTMLToCopy(modelRange: Range, colorMap: string[]): string {
|
||||
const startLineNumber = modelRange.startLineNumber;
|
||||
const startColumn = modelRange.startColumn;
|
||||
const endLineNumber = modelRange.endLineNumber;
|
||||
const endColumn = modelRange.endColumn;
|
||||
|
||||
const tabSize = this.getTabSize();
|
||||
|
||||
let result = '';
|
||||
|
||||
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
|
||||
const lineTokens = this.model.getLineTokens(lineNumber);
|
||||
const lineContent = lineTokens.getLineContent();
|
||||
const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0);
|
||||
const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length);
|
||||
|
||||
if (lineContent === '') {
|
||||
result += '<br>';
|
||||
} else {
|
||||
result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getColorMap(): string[] {
|
||||
let colorMap = TokenizationRegistry.getColorMap();
|
||||
let result: string[] = [null];
|
||||
for (let i = 1, len = colorMap.length; i < len; i++) {
|
||||
result[i] = Color.Format.CSS.formatHex(colorMap[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user