mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-12 02:58:31 -05:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
827
src/vs/editor/common/model/textModel.ts
Normal file
827
src/vs/editor/common/model/textModel.ts
Normal file
@@ -0,0 +1,827 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { OrderGuaranteeEventEmitter, BulkListenerCallback } from 'vs/base/common/eventEmitter';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ModelLine, IModelLine, MinimalModelLine } from 'vs/editor/common/model/modelLine';
|
||||
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
|
||||
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { IndentRange, computeRanges } from 'vs/editor/common/model/indentRanges';
|
||||
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
|
||||
import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
|
||||
|
||||
const USE_MIMINAL_MODEL_LINE = true;
|
||||
|
||||
const LIMIT_FIND_COUNT = 999;
|
||||
export const LONG_LINE_BOUNDARY = 10000;
|
||||
|
||||
export interface ITextModelCreationData {
|
||||
readonly text: ITextSource;
|
||||
readonly options: editorCommon.TextModelResolvedOptions;
|
||||
}
|
||||
|
||||
export class TextModel implements editorCommon.ITextModel {
|
||||
private static MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
|
||||
private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
|
||||
private static MANY_MANY_LINES = 300 * 1000; // 300K lines
|
||||
|
||||
public static DEFAULT_CREATION_OPTIONS: editorCommon.ITextModelCreationOptions = {
|
||||
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
|
||||
insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,
|
||||
detectIndentation: false,
|
||||
defaultEOL: editorCommon.DefaultEndOfLine.LF,
|
||||
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
|
||||
};
|
||||
|
||||
public static createFromString(text: string, options: editorCommon.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS): TextModel {
|
||||
return new TextModel(RawTextSource.fromString(text), options);
|
||||
}
|
||||
|
||||
public static resolveCreationData(rawTextSource: IRawTextSource, options: editorCommon.ITextModelCreationOptions): ITextModelCreationData {
|
||||
const textSource = TextSource.fromRawTextSource(rawTextSource, options.defaultEOL);
|
||||
|
||||
let resolvedOpts: editorCommon.TextModelResolvedOptions;
|
||||
if (options.detectIndentation) {
|
||||
const guessedIndentation = guessIndentation(textSource.lines, options.tabSize, options.insertSpaces);
|
||||
resolvedOpts = new editorCommon.TextModelResolvedOptions({
|
||||
tabSize: guessedIndentation.tabSize,
|
||||
insertSpaces: guessedIndentation.insertSpaces,
|
||||
trimAutoWhitespace: options.trimAutoWhitespace,
|
||||
defaultEOL: options.defaultEOL
|
||||
});
|
||||
} else {
|
||||
resolvedOpts = new editorCommon.TextModelResolvedOptions({
|
||||
tabSize: options.tabSize,
|
||||
insertSpaces: options.insertSpaces,
|
||||
trimAutoWhitespace: options.trimAutoWhitespace,
|
||||
defaultEOL: options.defaultEOL
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
text: textSource,
|
||||
options: resolvedOpts
|
||||
};
|
||||
}
|
||||
|
||||
public addBulkListener(listener: BulkListenerCallback): IDisposable {
|
||||
return this._eventEmitter.addBulkListener(listener);
|
||||
}
|
||||
|
||||
protected readonly _eventEmitter: OrderGuaranteeEventEmitter;
|
||||
|
||||
/*protected*/ _lines: IModelLine[];
|
||||
protected _EOL: string;
|
||||
protected _isDisposed: boolean;
|
||||
protected _isDisposing: boolean;
|
||||
protected _options: editorCommon.TextModelResolvedOptions;
|
||||
protected _lineStarts: PrefixSumComputer;
|
||||
private _indentRanges: IndentRange[];
|
||||
|
||||
private _versionId: number;
|
||||
/**
|
||||
* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)
|
||||
*/
|
||||
private _alternativeVersionId: number;
|
||||
private _BOM: string;
|
||||
protected _mightContainRTL: boolean;
|
||||
protected _mightContainNonBasicASCII: boolean;
|
||||
|
||||
private readonly _shouldSimplifyMode: boolean;
|
||||
protected readonly _isTooLargeForTokenization: boolean;
|
||||
|
||||
constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions) {
|
||||
this._eventEmitter = new OrderGuaranteeEventEmitter();
|
||||
|
||||
const textModelData = TextModel.resolveCreationData(rawTextSource, creationOptions);
|
||||
|
||||
// !!! Make a decision in the ctor and permanently respect this decision !!!
|
||||
// If a model is too large at construction time, it will never get tokenized,
|
||||
// under no circumstances.
|
||||
this._isTooLargeForTokenization = (
|
||||
(textModelData.text.length > TextModel.MODEL_TOKENIZATION_LIMIT)
|
||||
|| (textModelData.text.lines.length > TextModel.MANY_MANY_LINES)
|
||||
);
|
||||
|
||||
this._shouldSimplifyMode = (
|
||||
this._isTooLargeForTokenization
|
||||
|| (textModelData.text.length > TextModel.MODEL_SYNC_LIMIT)
|
||||
);
|
||||
|
||||
this._options = new editorCommon.TextModelResolvedOptions(textModelData.options);
|
||||
this._constructLines(textModelData.text);
|
||||
this._setVersionId(1);
|
||||
this._isDisposed = false;
|
||||
this._isDisposing = false;
|
||||
}
|
||||
|
||||
protected _createModelLine(text: string, tabSize: number): IModelLine {
|
||||
if (USE_MIMINAL_MODEL_LINE && this._isTooLargeForTokenization) {
|
||||
return new MinimalModelLine(text, tabSize);
|
||||
}
|
||||
return new ModelLine(text, tabSize);
|
||||
}
|
||||
|
||||
protected _assertNotDisposed(): void {
|
||||
if (this._isDisposed) {
|
||||
throw new Error('Model is disposed!');
|
||||
}
|
||||
}
|
||||
|
||||
public isTooLargeForHavingARichMode(): boolean {
|
||||
return this._shouldSimplifyMode;
|
||||
}
|
||||
|
||||
public isTooLargeForTokenization(): boolean {
|
||||
return this._isTooLargeForTokenization;
|
||||
}
|
||||
|
||||
public getOptions(): editorCommon.TextModelResolvedOptions {
|
||||
this._assertNotDisposed();
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public updateOptions(_newOpts: editorCommon.ITextModelUpdateOptions): void {
|
||||
this._assertNotDisposed();
|
||||
let tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize;
|
||||
let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces;
|
||||
let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace;
|
||||
|
||||
let newOpts = new editorCommon.TextModelResolvedOptions({
|
||||
tabSize: tabSize,
|
||||
insertSpaces: insertSpaces,
|
||||
defaultEOL: this._options.defaultEOL,
|
||||
trimAutoWhitespace: trimAutoWhitespace
|
||||
});
|
||||
|
||||
if (this._options.equals(newOpts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let e = this._options.createChangeEvent(newOpts);
|
||||
this._options = newOpts;
|
||||
|
||||
if (e.tabSize) {
|
||||
let newTabSize = this._options.tabSize;
|
||||
for (let i = 0, len = this._lines.length; i < len; i++) {
|
||||
this._lines[i].updateTabSize(newTabSize);
|
||||
}
|
||||
}
|
||||
|
||||
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelOptionsChanged, e);
|
||||
}
|
||||
|
||||
public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void {
|
||||
this._assertNotDisposed();
|
||||
let lines = this._lines.map(line => line.text);
|
||||
let guessedIndentation = guessIndentation(lines, defaultTabSize, defaultInsertSpaces);
|
||||
this.updateOptions({
|
||||
insertSpaces: guessedIndentation.insertSpaces,
|
||||
tabSize: guessedIndentation.tabSize
|
||||
});
|
||||
}
|
||||
|
||||
private static _normalizeIndentationFromWhitespace(str: string, tabSize: number, insertSpaces: boolean): string {
|
||||
let spacesCnt = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (str.charAt(i) === '\t') {
|
||||
spacesCnt += tabSize;
|
||||
} else {
|
||||
spacesCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
let result = '';
|
||||
if (!insertSpaces) {
|
||||
let tabsCnt = Math.floor(spacesCnt / tabSize);
|
||||
spacesCnt = spacesCnt % tabSize;
|
||||
for (let i = 0; i < tabsCnt; i++) {
|
||||
result += '\t';
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < spacesCnt; i++) {
|
||||
result += ' ';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static normalizeIndentation(str: string, tabSize: number, insertSpaces: boolean): string {
|
||||
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(str);
|
||||
if (firstNonWhitespaceIndex === -1) {
|
||||
firstNonWhitespaceIndex = str.length;
|
||||
}
|
||||
return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), tabSize, insertSpaces) + str.substring(firstNonWhitespaceIndex);
|
||||
}
|
||||
|
||||
public normalizeIndentation(str: string): string {
|
||||
this._assertNotDisposed();
|
||||
return TextModel.normalizeIndentation(str, this._options.tabSize, this._options.insertSpaces);
|
||||
}
|
||||
|
||||
public getOneIndent(): string {
|
||||
this._assertNotDisposed();
|
||||
let tabSize = this._options.tabSize;
|
||||
let insertSpaces = this._options.insertSpaces;
|
||||
|
||||
if (insertSpaces) {
|
||||
let result = '';
|
||||
for (let i = 0; i < tabSize; i++) {
|
||||
result += ' ';
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return '\t';
|
||||
}
|
||||
}
|
||||
|
||||
public getVersionId(): number {
|
||||
this._assertNotDisposed();
|
||||
return this._versionId;
|
||||
}
|
||||
|
||||
public mightContainRTL(): boolean {
|
||||
return this._mightContainRTL;
|
||||
}
|
||||
|
||||
public mightContainNonBasicASCII(): boolean {
|
||||
return this._mightContainNonBasicASCII;
|
||||
}
|
||||
|
||||
public getAlternativeVersionId(): number {
|
||||
this._assertNotDisposed();
|
||||
return this._alternativeVersionId;
|
||||
}
|
||||
|
||||
private _ensureLineStarts(): void {
|
||||
if (!this._lineStarts) {
|
||||
const eolLength = this._EOL.length;
|
||||
const linesLength = this._lines.length;
|
||||
const lineStartValues = new Uint32Array(linesLength);
|
||||
for (let i = 0; i < linesLength; i++) {
|
||||
lineStartValues[i] = this._lines[i].text.length + eolLength;
|
||||
}
|
||||
this._lineStarts = new PrefixSumComputer(lineStartValues);
|
||||
}
|
||||
}
|
||||
|
||||
public getOffsetAt(rawPosition: IPosition): number {
|
||||
this._assertNotDisposed();
|
||||
let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, false);
|
||||
this._ensureLineStarts();
|
||||
return this._lineStarts.getAccumulatedValue(position.lineNumber - 2) + position.column - 1;
|
||||
}
|
||||
|
||||
public getPositionAt(offset: number): Position {
|
||||
this._assertNotDisposed();
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
this._ensureLineStarts();
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
|
||||
let lineLength = this._lines[out.index].text.length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1));
|
||||
}
|
||||
|
||||
protected _increaseVersionId(): void {
|
||||
this._setVersionId(this._versionId + 1);
|
||||
}
|
||||
|
||||
private _setVersionId(newVersionId: number): void {
|
||||
this._versionId = newVersionId;
|
||||
this._alternativeVersionId = this._versionId;
|
||||
}
|
||||
|
||||
protected _overwriteAlternativeVersionId(newAlternativeVersionId: number): void {
|
||||
this._alternativeVersionId = newAlternativeVersionId;
|
||||
}
|
||||
|
||||
public isDisposed(): boolean {
|
||||
return this._isDisposed;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
// Null out members, such that any use of a disposed model will throw exceptions sooner rather than later
|
||||
this._lines = null;
|
||||
this._EOL = null;
|
||||
this._BOM = null;
|
||||
|
||||
this._eventEmitter.dispose();
|
||||
}
|
||||
|
||||
private _emitContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): void {
|
||||
const e: textModelEvents.IModelContentChangedEvent = {
|
||||
changes: [{
|
||||
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
|
||||
rangeLength: rangeLength,
|
||||
text: text,
|
||||
}],
|
||||
eol: this._EOL,
|
||||
versionId: this.getVersionId(),
|
||||
isUndoing: isUndoing,
|
||||
isRedoing: isRedoing,
|
||||
isFlush: isFlush
|
||||
};
|
||||
if (!this._isDisposing) {
|
||||
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e);
|
||||
}
|
||||
}
|
||||
|
||||
protected _resetValue(newValue: ITextSource): void {
|
||||
this._constructLines(newValue);
|
||||
this._increaseVersionId();
|
||||
}
|
||||
|
||||
public equals(other: ITextSource): boolean {
|
||||
this._assertNotDisposed();
|
||||
if (this._BOM !== other.BOM) {
|
||||
return false;
|
||||
}
|
||||
if (this._EOL !== other.EOL) {
|
||||
return false;
|
||||
}
|
||||
if (this._lines.length !== other.lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0, len = this._lines.length; i < len; i++) {
|
||||
if (this._lines[i].text !== other.lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public setValue(value: string): void {
|
||||
this._assertNotDisposed();
|
||||
if (value === null) {
|
||||
// There's nothing to do
|
||||
return;
|
||||
}
|
||||
const textSource = TextSource.fromString(value, this._options.defaultEOL);
|
||||
this.setValueFromTextSource(textSource);
|
||||
}
|
||||
|
||||
public setValueFromTextSource(newValue: ITextSource): void {
|
||||
this._assertNotDisposed();
|
||||
if (newValue === null) {
|
||||
// There's nothing to do
|
||||
return;
|
||||
}
|
||||
var oldFullModelRange = this.getFullModelRange();
|
||||
var oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);
|
||||
var endLineNumber = this.getLineCount();
|
||||
var endColumn = this.getLineMaxColumn(endLineNumber);
|
||||
|
||||
this._resetValue(newValue);
|
||||
|
||||
this._emitModelRawContentChangedEvent(
|
||||
new textModelEvents.ModelRawContentChangedEvent(
|
||||
[
|
||||
new textModelEvents.ModelRawFlush()
|
||||
],
|
||||
this._versionId,
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
this._emitContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true);
|
||||
}
|
||||
|
||||
public getValue(eol?: editorCommon.EndOfLinePreference, preserveBOM: boolean = false): string {
|
||||
this._assertNotDisposed();
|
||||
var fullModelRange = this.getFullModelRange();
|
||||
var fullModelValue = this.getValueInRange(fullModelRange, eol);
|
||||
|
||||
if (preserveBOM) {
|
||||
return this._BOM + fullModelValue;
|
||||
}
|
||||
|
||||
return fullModelValue;
|
||||
}
|
||||
|
||||
public getValueLength(eol?: editorCommon.EndOfLinePreference, preserveBOM: boolean = false): number {
|
||||
this._assertNotDisposed();
|
||||
var fullModelRange = this.getFullModelRange();
|
||||
var fullModelValue = this.getValueLengthInRange(fullModelRange, eol);
|
||||
|
||||
if (preserveBOM) {
|
||||
return this._BOM.length + fullModelValue;
|
||||
}
|
||||
|
||||
return fullModelValue;
|
||||
}
|
||||
|
||||
public getValueInRange(rawRange: IRange, eol: editorCommon.EndOfLinePreference = editorCommon.EndOfLinePreference.TextDefined): string {
|
||||
this._assertNotDisposed();
|
||||
var range = this.validateRange(rawRange);
|
||||
|
||||
if (range.isEmpty()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.startLineNumber === range.endLineNumber) {
|
||||
return this._lines[range.startLineNumber - 1].text.substring(range.startColumn - 1, range.endColumn - 1);
|
||||
}
|
||||
|
||||
var lineEnding = this._getEndOfLine(eol),
|
||||
startLineIndex = range.startLineNumber - 1,
|
||||
endLineIndex = range.endLineNumber - 1,
|
||||
resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].text.substring(range.startColumn - 1));
|
||||
for (var i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i].text);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].text.substring(0, range.endColumn - 1));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
public getValueLengthInRange(rawRange: IRange, eol: editorCommon.EndOfLinePreference = editorCommon.EndOfLinePreference.TextDefined): number {
|
||||
this._assertNotDisposed();
|
||||
var range = this.validateRange(rawRange);
|
||||
|
||||
if (range.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (range.startLineNumber === range.endLineNumber) {
|
||||
return (range.endColumn - range.startColumn);
|
||||
}
|
||||
|
||||
let startOffset = this.getOffsetAt(new Position(range.startLineNumber, range.startColumn));
|
||||
let endOffset = this.getOffsetAt(new Position(range.endLineNumber, range.endColumn));
|
||||
return endOffset - startOffset;
|
||||
}
|
||||
|
||||
public isDominatedByLongLines(): boolean {
|
||||
this._assertNotDisposed();
|
||||
var smallLineCharCount = 0,
|
||||
longLineCharCount = 0,
|
||||
i: number,
|
||||
len: number,
|
||||
lines = this._lines,
|
||||
lineLength: number;
|
||||
|
||||
for (i = 0, len = this._lines.length; i < len; i++) {
|
||||
lineLength = lines[i].text.length;
|
||||
if (lineLength >= LONG_LINE_BOUNDARY) {
|
||||
longLineCharCount += lineLength;
|
||||
} else {
|
||||
smallLineCharCount += lineLength;
|
||||
}
|
||||
}
|
||||
|
||||
return (longLineCharCount > smallLineCharCount);
|
||||
}
|
||||
|
||||
public getLineCount(): number {
|
||||
this._assertNotDisposed();
|
||||
return this._lines.length;
|
||||
}
|
||||
|
||||
public getLineContent(lineNumber: number): string {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
return this._lines[lineNumber - 1].text;
|
||||
}
|
||||
|
||||
public getIndentLevel(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
return this._lines[lineNumber - 1].getIndentLevel();
|
||||
}
|
||||
|
||||
protected _resetIndentRanges(): void {
|
||||
this._indentRanges = null;
|
||||
}
|
||||
|
||||
private _getIndentRanges(): IndentRange[] {
|
||||
if (!this._indentRanges) {
|
||||
this._indentRanges = computeRanges(this);
|
||||
}
|
||||
return this._indentRanges;
|
||||
}
|
||||
|
||||
public getIndentRanges(): IndentRange[] {
|
||||
this._assertNotDisposed();
|
||||
let indentRanges = this._getIndentRanges();
|
||||
return IndentRange.deepCloneArr(indentRanges);
|
||||
}
|
||||
|
||||
private _toValidLineIndentGuide(lineNumber: number, indentGuide: number): number {
|
||||
let lineIndentLevel = this._lines[lineNumber - 1].getIndentLevel();
|
||||
if (lineIndentLevel === -1) {
|
||||
return indentGuide;
|
||||
}
|
||||
let maxIndentGuide = Math.ceil(lineIndentLevel / this._options.tabSize);
|
||||
return Math.min(maxIndentGuide, indentGuide);
|
||||
}
|
||||
|
||||
public getLineIndentGuide(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
let indentRanges = this._getIndentRanges();
|
||||
|
||||
for (let i = indentRanges.length - 1; i >= 0; i--) {
|
||||
let rng = indentRanges[i];
|
||||
|
||||
if (rng.startLineNumber === lineNumber) {
|
||||
return this._toValidLineIndentGuide(lineNumber, Math.ceil(rng.indent / this._options.tabSize));
|
||||
}
|
||||
if (rng.startLineNumber < lineNumber && lineNumber <= rng.endLineNumber) {
|
||||
return this._toValidLineIndentGuide(lineNumber, 1 + Math.floor(rng.indent / this._options.tabSize));
|
||||
}
|
||||
if (rng.endLineNumber + 1 === lineNumber) {
|
||||
let bestIndent = rng.indent;
|
||||
while (i > 0) {
|
||||
i--;
|
||||
rng = indentRanges[i];
|
||||
if (rng.endLineNumber + 1 === lineNumber) {
|
||||
bestIndent = rng.indent;
|
||||
}
|
||||
}
|
||||
return this._toValidLineIndentGuide(lineNumber, Math.ceil(bestIndent / this._options.tabSize));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getLinesContent(): string[] {
|
||||
this._assertNotDisposed();
|
||||
var r: string[] = [];
|
||||
for (var i = 0, len = this._lines.length; i < len; i++) {
|
||||
r[i] = this._lines[i].text;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public getEOL(): string {
|
||||
this._assertNotDisposed();
|
||||
return this._EOL;
|
||||
}
|
||||
|
||||
public setEOL(eol: editorCommon.EndOfLineSequence): void {
|
||||
this._assertNotDisposed();
|
||||
const newEOL = (eol === editorCommon.EndOfLineSequence.CRLF ? '\r\n' : '\n');
|
||||
if (this._EOL === newEOL) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
const oldFullModelRange = this.getFullModelRange();
|
||||
const oldModelValueLength = this.getValueLengthInRange(oldFullModelRange);
|
||||
const endLineNumber = this.getLineCount();
|
||||
const endColumn = this.getLineMaxColumn(endLineNumber);
|
||||
|
||||
this._EOL = newEOL;
|
||||
this._lineStarts = null;
|
||||
this._increaseVersionId();
|
||||
|
||||
this._emitModelRawContentChangedEvent(
|
||||
new textModelEvents.ModelRawContentChangedEvent(
|
||||
[
|
||||
new textModelEvents.ModelRawEOLChanged()
|
||||
],
|
||||
this._versionId,
|
||||
false,
|
||||
false
|
||||
)
|
||||
);
|
||||
|
||||
this._emitContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false);
|
||||
}
|
||||
|
||||
public getLineMinColumn(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
return 1;
|
||||
}
|
||||
|
||||
public getLineMaxColumn(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
return this._lines[lineNumber - 1].text.length + 1;
|
||||
}
|
||||
|
||||
public getLineFirstNonWhitespaceColumn(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
var result = strings.firstNonWhitespaceIndex(this._lines[lineNumber - 1].text);
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
public getLineLastNonWhitespaceColumn(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
|
||||
}
|
||||
|
||||
var result = strings.lastNonWhitespaceIndex(this._lines[lineNumber - 1].text);
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 2;
|
||||
}
|
||||
|
||||
public validateLineNumber(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1) {
|
||||
lineNumber = 1;
|
||||
}
|
||||
if (lineNumber > this._lines.length) {
|
||||
lineNumber = this._lines.length;
|
||||
}
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict Do NOT allow a position inside a high-low surrogate pair
|
||||
*/
|
||||
private _validatePosition(_lineNumber: number, _column: number, strict: boolean): Position {
|
||||
const lineNumber = Math.floor(typeof _lineNumber === 'number' ? _lineNumber : 1);
|
||||
const column = Math.floor(typeof _column === 'number' ? _column : 1);
|
||||
|
||||
if (lineNumber < 1) {
|
||||
return new Position(1, 1);
|
||||
}
|
||||
|
||||
if (lineNumber > this._lines.length) {
|
||||
return new Position(this._lines.length, this.getLineMaxColumn(this._lines.length));
|
||||
}
|
||||
|
||||
if (column <= 1) {
|
||||
return new Position(lineNumber, 1);
|
||||
}
|
||||
|
||||
const maxColumn = this.getLineMaxColumn(lineNumber);
|
||||
if (column >= maxColumn) {
|
||||
return new Position(lineNumber, maxColumn);
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
// If the position would end up in the middle of a high-low surrogate pair,
|
||||
// we move it to before the pair
|
||||
// !!At this point, column > 1
|
||||
const charCodeBefore = this._lines[lineNumber - 1].text.charCodeAt(column - 2);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
return new Position(lineNumber, column - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return new Position(lineNumber, column);
|
||||
}
|
||||
|
||||
public validatePosition(position: IPosition): Position {
|
||||
this._assertNotDisposed();
|
||||
return this._validatePosition(position.lineNumber, position.column, true);
|
||||
}
|
||||
|
||||
public validateRange(_range: IRange): Range {
|
||||
this._assertNotDisposed();
|
||||
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, false);
|
||||
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, false);
|
||||
|
||||
const startLineNumber = start.lineNumber;
|
||||
const startColumn = start.column;
|
||||
const endLineNumber = end.lineNumber;
|
||||
const endColumn = end.column;
|
||||
|
||||
const startLineText = this._lines[startLineNumber - 1].text;
|
||||
const endLineText = this._lines[endLineNumber - 1].text;
|
||||
|
||||
const charCodeBeforeStart = (startColumn > 1 ? startLineText.charCodeAt(startColumn - 2) : 0);
|
||||
const charCodeBeforeEnd = (endColumn > 1 && endColumn <= endLineText.length ? endLineText.charCodeAt(endColumn - 2) : 0);
|
||||
|
||||
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
|
||||
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
|
||||
|
||||
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
|
||||
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
|
||||
}
|
||||
|
||||
if (startLineNumber === endLineNumber && startColumn === endColumn) {
|
||||
// do not expand a collapsed range, simply move it to a valid location
|
||||
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn - 1);
|
||||
}
|
||||
|
||||
if (startInsideSurrogatePair && endInsideSurrogatePair) {
|
||||
// expand range at both ends
|
||||
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn + 1);
|
||||
}
|
||||
|
||||
if (startInsideSurrogatePair) {
|
||||
// only expand range at the start
|
||||
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn);
|
||||
}
|
||||
|
||||
// only expand range at the end
|
||||
return new Range(startLineNumber, startColumn, endLineNumber, endColumn + 1);
|
||||
}
|
||||
|
||||
public modifyPosition(rawPosition: IPosition, offset: number): Position {
|
||||
this._assertNotDisposed();
|
||||
return this.getPositionAt(this.getOffsetAt(rawPosition) + offset);
|
||||
}
|
||||
|
||||
public getFullModelRange(): Range {
|
||||
this._assertNotDisposed();
|
||||
var lineCount = this.getLineCount();
|
||||
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
|
||||
}
|
||||
|
||||
protected _emitModelRawContentChangedEvent(e: textModelEvents.ModelRawContentChangedEvent): void {
|
||||
if (this._isDisposing) {
|
||||
// Do not confuse listeners by emitting any event after disposing
|
||||
return;
|
||||
}
|
||||
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelRawContentChanged2, e);
|
||||
}
|
||||
|
||||
private _constructLines(textSource: ITextSource): void {
|
||||
const tabSize = this._options.tabSize;
|
||||
let rawLines = textSource.lines;
|
||||
let modelLines: IModelLine[] = new Array<IModelLine>(rawLines.length);
|
||||
|
||||
for (let i = 0, len = rawLines.length; i < len; i++) {
|
||||
modelLines[i] = this._createModelLine(rawLines[i], tabSize);
|
||||
}
|
||||
this._BOM = textSource.BOM;
|
||||
this._mightContainRTL = textSource.containsRTL;
|
||||
this._mightContainNonBasicASCII = !textSource.isBasicASCII;
|
||||
this._EOL = textSource.EOL;
|
||||
this._lines = modelLines;
|
||||
this._lineStarts = null;
|
||||
this._resetIndentRanges();
|
||||
}
|
||||
|
||||
private _getEndOfLine(eol: editorCommon.EndOfLinePreference): string {
|
||||
switch (eol) {
|
||||
case editorCommon.EndOfLinePreference.LF:
|
||||
return '\n';
|
||||
case editorCommon.EndOfLinePreference.CRLF:
|
||||
return '\r\n';
|
||||
case editorCommon.EndOfLinePreference.TextDefined:
|
||||
return this.getEOL();
|
||||
}
|
||||
throw new Error('Unknown EOL preference');
|
||||
}
|
||||
|
||||
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): editorCommon.FindMatch[] {
|
||||
this._assertNotDisposed();
|
||||
|
||||
let searchRange: Range;
|
||||
if (Range.isIRange(rawSearchScope)) {
|
||||
searchRange = this.validateRange(rawSearchScope);
|
||||
} else {
|
||||
searchRange = this.getFullModelRange();
|
||||
}
|
||||
|
||||
return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): editorCommon.FindMatch {
|
||||
this._assertNotDisposed();
|
||||
const searchStart = this.validatePosition(rawSearchStart);
|
||||
return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
|
||||
}
|
||||
|
||||
public findPreviousMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): editorCommon.FindMatch {
|
||||
this._assertNotDisposed();
|
||||
const searchStart = this.validatePosition(rawSearchStart);
|
||||
return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user