mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-09 16:24:45 +01:00
579 lines
20 KiB
TypeScript
579 lines
20 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { CharCode } from 'vs/base/common/charCode';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import * as strings from 'vs/base/common/strings';
|
|
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingEditStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
|
import { Position } from 'vs/editor/common/core/position';
|
|
import { Range } from 'vs/editor/common/core/range';
|
|
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
|
import { ICommand, IConfiguration } from 'vs/editor/common/editorCommon';
|
|
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
|
|
import { TextModel } from 'vs/editor/common/model/textModel';
|
|
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
|
import { AutoClosingPairs, IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration';
|
|
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
|
import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
|
|
import { Constants } from 'vs/base/common/uint';
|
|
|
|
export interface IColumnSelectData {
|
|
isReal: boolean;
|
|
fromViewLineNumber: number;
|
|
fromViewVisualColumn: number;
|
|
toViewLineNumber: number;
|
|
toViewVisualColumn: number;
|
|
}
|
|
|
|
export const enum RevealTarget {
|
|
Primary = 0,
|
|
TopMost = 1,
|
|
BottomMost = 2
|
|
}
|
|
|
|
/**
|
|
* This is an operation type that will be recorded for undo/redo purposes.
|
|
* The goal is to introduce an undo stop when the controller switches between different operation types.
|
|
*/
|
|
export const enum EditOperationType {
|
|
Other = 0,
|
|
Typing = 1,
|
|
DeletingLeft = 2,
|
|
DeletingRight = 3
|
|
}
|
|
|
|
export interface CharacterMap {
|
|
[char: string]: string;
|
|
}
|
|
export interface MultipleCharacterMap {
|
|
[char: string]: string[];
|
|
}
|
|
|
|
const autoCloseAlways = () => true;
|
|
const autoCloseNever = () => false;
|
|
const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t');
|
|
|
|
export class CursorConfiguration {
|
|
_cursorMoveConfigurationBrand: void;
|
|
|
|
public readonly readOnly: boolean;
|
|
public readonly tabSize: number;
|
|
public readonly indentSize: number;
|
|
public readonly insertSpaces: boolean;
|
|
public readonly stickyTabStops: boolean;
|
|
public readonly pageSize: number;
|
|
public readonly lineHeight: number;
|
|
public readonly useTabStops: boolean;
|
|
public readonly wordSeparators: string;
|
|
public readonly emptySelectionClipboard: boolean;
|
|
public readonly copyWithSyntaxHighlighting: boolean;
|
|
public readonly multiCursorMergeOverlapping: boolean;
|
|
public readonly multiCursorPaste: 'spread' | 'full';
|
|
public readonly autoClosingBrackets: EditorAutoClosingStrategy;
|
|
public readonly autoClosingQuotes: EditorAutoClosingStrategy;
|
|
public readonly autoClosingDelete: EditorAutoClosingEditStrategy;
|
|
public readonly autoClosingOvertype: EditorAutoClosingEditStrategy;
|
|
public readonly autoSurround: EditorAutoSurroundStrategy;
|
|
public readonly autoIndent: EditorAutoIndentStrategy;
|
|
public readonly autoClosingPairs: AutoClosingPairs;
|
|
public readonly surroundingPairs: CharacterMap;
|
|
public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean };
|
|
|
|
private readonly _languageIdentifier: LanguageIdentifier;
|
|
private _electricChars: { [key: string]: boolean; } | null;
|
|
|
|
public static shouldRecreate(e: ConfigurationChangedEvent): boolean {
|
|
return (
|
|
e.hasChanged(EditorOption.layoutInfo)
|
|
|| e.hasChanged(EditorOption.wordSeparators)
|
|
|| e.hasChanged(EditorOption.emptySelectionClipboard)
|
|
|| e.hasChanged(EditorOption.multiCursorMergeOverlapping)
|
|
|| e.hasChanged(EditorOption.multiCursorPaste)
|
|
|| e.hasChanged(EditorOption.autoClosingBrackets)
|
|
|| e.hasChanged(EditorOption.autoClosingQuotes)
|
|
|| e.hasChanged(EditorOption.autoClosingDelete)
|
|
|| e.hasChanged(EditorOption.autoClosingOvertype)
|
|
|| e.hasChanged(EditorOption.autoSurround)
|
|
|| e.hasChanged(EditorOption.useTabStops)
|
|
|| e.hasChanged(EditorOption.lineHeight)
|
|
|| e.hasChanged(EditorOption.readOnly)
|
|
);
|
|
}
|
|
|
|
constructor(
|
|
languageIdentifier: LanguageIdentifier,
|
|
modelOptions: TextModelResolvedOptions,
|
|
configuration: IConfiguration
|
|
) {
|
|
this._languageIdentifier = languageIdentifier;
|
|
|
|
const options = configuration.options;
|
|
const layoutInfo = options.get(EditorOption.layoutInfo);
|
|
|
|
this.readOnly = options.get(EditorOption.readOnly);
|
|
this.tabSize = modelOptions.tabSize;
|
|
this.indentSize = modelOptions.indentSize;
|
|
this.insertSpaces = modelOptions.insertSpaces;
|
|
this.stickyTabStops = options.get(EditorOption.stickyTabStops);
|
|
this.lineHeight = options.get(EditorOption.lineHeight);
|
|
this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
|
|
this.useTabStops = options.get(EditorOption.useTabStops);
|
|
this.wordSeparators = options.get(EditorOption.wordSeparators);
|
|
this.emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard);
|
|
this.copyWithSyntaxHighlighting = options.get(EditorOption.copyWithSyntaxHighlighting);
|
|
this.multiCursorMergeOverlapping = options.get(EditorOption.multiCursorMergeOverlapping);
|
|
this.multiCursorPaste = options.get(EditorOption.multiCursorPaste);
|
|
this.autoClosingBrackets = options.get(EditorOption.autoClosingBrackets);
|
|
this.autoClosingQuotes = options.get(EditorOption.autoClosingQuotes);
|
|
this.autoClosingDelete = options.get(EditorOption.autoClosingDelete);
|
|
this.autoClosingOvertype = options.get(EditorOption.autoClosingOvertype);
|
|
this.autoSurround = options.get(EditorOption.autoSurround);
|
|
this.autoIndent = options.get(EditorOption.autoIndent);
|
|
|
|
this.surroundingPairs = {};
|
|
this._electricChars = null;
|
|
|
|
this.shouldAutoCloseBefore = {
|
|
quote: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingQuotes),
|
|
bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets)
|
|
};
|
|
|
|
this.autoClosingPairs = LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
|
|
|
|
let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier);
|
|
if (surroundingPairs) {
|
|
for (const pair of surroundingPairs) {
|
|
this.surroundingPairs[pair.open] = pair.close;
|
|
}
|
|
}
|
|
}
|
|
|
|
public get electricChars() {
|
|
if (!this._electricChars) {
|
|
this._electricChars = {};
|
|
let electricChars = CursorConfiguration._getElectricCharacters(this._languageIdentifier);
|
|
if (electricChars) {
|
|
for (const char of electricChars) {
|
|
this._electricChars[char] = true;
|
|
}
|
|
}
|
|
}
|
|
return this._electricChars;
|
|
}
|
|
|
|
public normalizeIndentation(str: string): string {
|
|
return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces);
|
|
}
|
|
|
|
private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] | null {
|
|
try {
|
|
return LanguageConfigurationRegistry.getElectricCharacters(languageIdentifier.id);
|
|
} catch (e) {
|
|
onUnexpectedError(e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static _getShouldAutoClose(languageIdentifier: LanguageIdentifier, autoCloseConfig: EditorAutoClosingStrategy): (ch: string) => boolean {
|
|
switch (autoCloseConfig) {
|
|
case 'beforeWhitespace':
|
|
return autoCloseBeforeWhitespace;
|
|
case 'languageDefined':
|
|
return CursorConfiguration._getLanguageDefinedShouldAutoClose(languageIdentifier);
|
|
case 'always':
|
|
return autoCloseAlways;
|
|
case 'never':
|
|
return autoCloseNever;
|
|
}
|
|
}
|
|
|
|
private static _getLanguageDefinedShouldAutoClose(languageIdentifier: LanguageIdentifier): (ch: string) => boolean {
|
|
try {
|
|
const autoCloseBeforeSet = LanguageConfigurationRegistry.getAutoCloseBeforeSet(languageIdentifier.id);
|
|
return c => autoCloseBeforeSet.indexOf(c) !== -1;
|
|
} catch (e) {
|
|
onUnexpectedError(e);
|
|
return autoCloseNever;
|
|
}
|
|
}
|
|
|
|
private static _getSurroundingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] | null {
|
|
try {
|
|
return LanguageConfigurationRegistry.getSurroundingPairs(languageIdentifier.id);
|
|
} catch (e) {
|
|
onUnexpectedError(e);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a simple model (either the model or the view model).
|
|
*/
|
|
export interface ICursorSimpleModel {
|
|
getLineCount(): number;
|
|
getLineContent(lineNumber: number): string;
|
|
getLineMinColumn(lineNumber: number): number;
|
|
getLineMaxColumn(lineNumber: number): number;
|
|
getLineFirstNonWhitespaceColumn(lineNumber: number): number;
|
|
getLineLastNonWhitespaceColumn(lineNumber: number): number;
|
|
}
|
|
|
|
/**
|
|
* Represents the cursor state on either the model or on the view model.
|
|
*/
|
|
export class SingleCursorState {
|
|
_singleCursorStateBrand: void;
|
|
|
|
// --- selection can start as a range (think double click and drag)
|
|
public readonly selectionStart: Range;
|
|
public readonly selectionStartLeftoverVisibleColumns: number;
|
|
public readonly position: Position;
|
|
public readonly leftoverVisibleColumns: number;
|
|
public readonly selection: Selection;
|
|
|
|
constructor(
|
|
selectionStart: Range,
|
|
selectionStartLeftoverVisibleColumns: number,
|
|
position: Position,
|
|
leftoverVisibleColumns: number,
|
|
) {
|
|
this.selectionStart = selectionStart;
|
|
this.selectionStartLeftoverVisibleColumns = selectionStartLeftoverVisibleColumns;
|
|
this.position = position;
|
|
this.leftoverVisibleColumns = leftoverVisibleColumns;
|
|
this.selection = SingleCursorState._computeSelection(this.selectionStart, this.position);
|
|
}
|
|
|
|
public equals(other: SingleCursorState) {
|
|
return (
|
|
this.selectionStartLeftoverVisibleColumns === other.selectionStartLeftoverVisibleColumns
|
|
&& this.leftoverVisibleColumns === other.leftoverVisibleColumns
|
|
&& this.position.equals(other.position)
|
|
&& this.selectionStart.equalsRange(other.selectionStart)
|
|
);
|
|
}
|
|
|
|
public hasSelection(): boolean {
|
|
return (!this.selection.isEmpty() || !this.selectionStart.isEmpty());
|
|
}
|
|
|
|
public move(inSelectionMode: boolean, lineNumber: number, column: number, leftoverVisibleColumns: number): SingleCursorState {
|
|
if (inSelectionMode) {
|
|
// move just position
|
|
return new SingleCursorState(
|
|
this.selectionStart,
|
|
this.selectionStartLeftoverVisibleColumns,
|
|
new Position(lineNumber, column),
|
|
leftoverVisibleColumns
|
|
);
|
|
} else {
|
|
// move everything
|
|
return new SingleCursorState(
|
|
new Range(lineNumber, column, lineNumber, column),
|
|
leftoverVisibleColumns,
|
|
new Position(lineNumber, column),
|
|
leftoverVisibleColumns
|
|
);
|
|
}
|
|
}
|
|
|
|
private static _computeSelection(selectionStart: Range, position: Position): Selection {
|
|
let startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number;
|
|
if (selectionStart.isEmpty()) {
|
|
startLineNumber = selectionStart.startLineNumber;
|
|
startColumn = selectionStart.startColumn;
|
|
endLineNumber = position.lineNumber;
|
|
endColumn = position.column;
|
|
} else {
|
|
if (position.isBeforeOrEqual(selectionStart.getStartPosition())) {
|
|
startLineNumber = selectionStart.endLineNumber;
|
|
startColumn = selectionStart.endColumn;
|
|
endLineNumber = position.lineNumber;
|
|
endColumn = position.column;
|
|
} else {
|
|
startLineNumber = selectionStart.startLineNumber;
|
|
startColumn = selectionStart.startColumn;
|
|
endLineNumber = position.lineNumber;
|
|
endColumn = position.column;
|
|
}
|
|
}
|
|
return new Selection(
|
|
startLineNumber,
|
|
startColumn,
|
|
endLineNumber,
|
|
endColumn
|
|
);
|
|
}
|
|
}
|
|
|
|
export class CursorContext {
|
|
_cursorContextBrand: void;
|
|
|
|
public readonly model: ITextModel;
|
|
public readonly coordinatesConverter: ICoordinatesConverter;
|
|
public readonly cursorConfig: CursorConfiguration;
|
|
|
|
constructor(model: ITextModel, coordinatesConverter: ICoordinatesConverter, cursorConfig: CursorConfiguration) {
|
|
this.model = model;
|
|
this.coordinatesConverter = coordinatesConverter;
|
|
this.cursorConfig = cursorConfig;
|
|
}
|
|
}
|
|
|
|
export class PartialModelCursorState {
|
|
readonly modelState: SingleCursorState;
|
|
readonly viewState: null;
|
|
|
|
constructor(modelState: SingleCursorState) {
|
|
this.modelState = modelState;
|
|
this.viewState = null;
|
|
}
|
|
}
|
|
|
|
export class PartialViewCursorState {
|
|
readonly modelState: null;
|
|
readonly viewState: SingleCursorState;
|
|
|
|
constructor(viewState: SingleCursorState) {
|
|
this.modelState = null;
|
|
this.viewState = viewState;
|
|
}
|
|
}
|
|
|
|
export type PartialCursorState = CursorState | PartialModelCursorState | PartialViewCursorState;
|
|
|
|
export class CursorState {
|
|
_cursorStateBrand: void;
|
|
|
|
public static fromModelState(modelState: SingleCursorState): PartialModelCursorState {
|
|
return new PartialModelCursorState(modelState);
|
|
}
|
|
|
|
public static fromViewState(viewState: SingleCursorState): PartialViewCursorState {
|
|
return new PartialViewCursorState(viewState);
|
|
}
|
|
|
|
public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState {
|
|
const selectionStartLineNumber = modelSelection.selectionStartLineNumber;
|
|
const selectionStartColumn = modelSelection.selectionStartColumn;
|
|
const positionLineNumber = modelSelection.positionLineNumber;
|
|
const positionColumn = modelSelection.positionColumn;
|
|
const modelState = new SingleCursorState(
|
|
new Range(selectionStartLineNumber, selectionStartColumn, selectionStartLineNumber, selectionStartColumn), 0,
|
|
new Position(positionLineNumber, positionColumn), 0
|
|
);
|
|
return CursorState.fromModelState(modelState);
|
|
}
|
|
|
|
public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] {
|
|
let states: PartialModelCursorState[] = [];
|
|
for (let i = 0, len = modelSelections.length; i < len; i++) {
|
|
states[i] = this.fromModelSelection(modelSelections[i]);
|
|
}
|
|
return states;
|
|
}
|
|
|
|
readonly modelState: SingleCursorState;
|
|
readonly viewState: SingleCursorState;
|
|
|
|
constructor(modelState: SingleCursorState, viewState: SingleCursorState) {
|
|
this.modelState = modelState;
|
|
this.viewState = viewState;
|
|
}
|
|
|
|
public equals(other: CursorState): boolean {
|
|
return (this.viewState.equals(other.viewState) && this.modelState.equals(other.modelState));
|
|
}
|
|
}
|
|
|
|
export class EditOperationResult {
|
|
_editOperationResultBrand: void;
|
|
|
|
readonly type: EditOperationType;
|
|
readonly commands: Array<ICommand | null>;
|
|
readonly shouldPushStackElementBefore: boolean;
|
|
readonly shouldPushStackElementAfter: boolean;
|
|
|
|
constructor(
|
|
type: EditOperationType,
|
|
commands: Array<ICommand | null>,
|
|
opts: {
|
|
shouldPushStackElementBefore: boolean;
|
|
shouldPushStackElementAfter: boolean;
|
|
}
|
|
) {
|
|
this.type = type;
|
|
this.commands = commands;
|
|
this.shouldPushStackElementBefore = opts.shouldPushStackElementBefore;
|
|
this.shouldPushStackElementAfter = opts.shouldPushStackElementAfter;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Common operations that work and make sense both on the model and on the view model.
|
|
*/
|
|
export class CursorColumns {
|
|
|
|
public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number {
|
|
const lineContentLength = lineContent.length;
|
|
const endOffset = column - 1 < lineContentLength ? column - 1 : lineContentLength;
|
|
|
|
let result = 0;
|
|
let i = 0;
|
|
while (i < endOffset) {
|
|
const codePoint = strings.getNextCodePoint(lineContent, endOffset, i);
|
|
i += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
|
|
|
if (codePoint === CharCode.Tab) {
|
|
result = CursorColumns.nextRenderTabStop(result, tabSize);
|
|
} else {
|
|
let graphemeBreakType = strings.getGraphemeBreakType(codePoint);
|
|
while (i < endOffset) {
|
|
const nextCodePoint = strings.getNextCodePoint(lineContent, endOffset, i);
|
|
const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint);
|
|
if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
|
|
break;
|
|
}
|
|
i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
|
graphemeBreakType = nextGraphemeBreakType;
|
|
}
|
|
if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) {
|
|
result = result + 2;
|
|
} else {
|
|
result = result + 1;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static toStatusbarColumn(lineContent: string, column: number, tabSize: number): number {
|
|
const lineContentLength = lineContent.length;
|
|
const endOffset = column - 1 < lineContentLength ? column - 1 : lineContentLength;
|
|
|
|
let result = 0;
|
|
let i = 0;
|
|
while (i < endOffset) {
|
|
const codePoint = strings.getNextCodePoint(lineContent, endOffset, i);
|
|
i += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
|
|
|
if (codePoint === CharCode.Tab) {
|
|
result = CursorColumns.nextRenderTabStop(result, tabSize);
|
|
} else {
|
|
result = result + 1;
|
|
}
|
|
}
|
|
|
|
return result + 1;
|
|
}
|
|
|
|
public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number {
|
|
return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize);
|
|
}
|
|
|
|
public static columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number {
|
|
if (visibleColumn <= 0) {
|
|
return 1;
|
|
}
|
|
|
|
const lineLength = lineContent.length;
|
|
|
|
let beforeVisibleColumn = 0;
|
|
let beforeColumn = 1;
|
|
let i = 0;
|
|
while (i < lineLength) {
|
|
const codePoint = strings.getNextCodePoint(lineContent, lineLength, i);
|
|
i += (codePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
|
|
|
let afterVisibleColumn: number;
|
|
if (codePoint === CharCode.Tab) {
|
|
afterVisibleColumn = CursorColumns.nextRenderTabStop(beforeVisibleColumn, tabSize);
|
|
} else {
|
|
let graphemeBreakType = strings.getGraphemeBreakType(codePoint);
|
|
while (i < lineLength) {
|
|
const nextCodePoint = strings.getNextCodePoint(lineContent, lineLength, i);
|
|
const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint);
|
|
if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
|
|
break;
|
|
}
|
|
i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
|
graphemeBreakType = nextGraphemeBreakType;
|
|
}
|
|
if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) {
|
|
afterVisibleColumn = beforeVisibleColumn + 2;
|
|
} else {
|
|
afterVisibleColumn = beforeVisibleColumn + 1;
|
|
}
|
|
}
|
|
const afterColumn = i + 1;
|
|
|
|
if (afterVisibleColumn >= visibleColumn) {
|
|
const beforeDelta = visibleColumn - beforeVisibleColumn;
|
|
const afterDelta = afterVisibleColumn - visibleColumn;
|
|
if (afterDelta < beforeDelta) {
|
|
return afterColumn;
|
|
} else {
|
|
return beforeColumn;
|
|
}
|
|
}
|
|
|
|
beforeVisibleColumn = afterVisibleColumn;
|
|
beforeColumn = afterColumn;
|
|
}
|
|
|
|
// walked the entire string
|
|
return lineLength + 1;
|
|
}
|
|
|
|
public static columnFromVisibleColumn2(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, visibleColumn: number): number {
|
|
let result = this.columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.tabSize);
|
|
|
|
let minColumn = model.getLineMinColumn(lineNumber);
|
|
if (result < minColumn) {
|
|
return minColumn;
|
|
}
|
|
|
|
let maxColumn = model.getLineMaxColumn(lineNumber);
|
|
if (result > maxColumn) {
|
|
return maxColumn;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
|
|
*/
|
|
public static nextRenderTabStop(visibleColumn: number, tabSize: number): number {
|
|
return visibleColumn + tabSize - visibleColumn % tabSize;
|
|
}
|
|
|
|
/**
|
|
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
|
|
*/
|
|
public static nextIndentTabStop(visibleColumn: number, indentSize: number): number {
|
|
return visibleColumn + indentSize - visibleColumn % indentSize;
|
|
}
|
|
|
|
/**
|
|
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
|
|
*/
|
|
public static prevRenderTabStop(column: number, tabSize: number): number {
|
|
return column - 1 - (column - 1) % tabSize;
|
|
}
|
|
|
|
/**
|
|
* ATTENTION: This works with 0-based columns (as opposed to the regular 1-based columns)
|
|
*/
|
|
public static prevIndentTabStop(column: number, indentSize: number): number {
|
|
return column - 1 - (column - 1) % indentSize;
|
|
}
|
|
}
|
|
|
|
export function isQuote(ch: string): boolean {
|
|
return (ch === '\'' || ch === '"' || ch === '`');
|
|
}
|