cursorCommon.ts 22.6 KB
Newer Older
A
Alex Dima 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  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';
A
Alex Dima 已提交
7
import { onUnexpectedError } from 'vs/base/common/errors';
A
Alex Dima 已提交
8
import * as strings from 'vs/base/common/strings';
9
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption } from 'vs/editor/common/config/editorOptions';
A
Alex Dima 已提交
10 11 12 13
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
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';
14
import { ICommand, IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
A
Alex Dima 已提交
15
import { ITextModel, TextModelResolvedOptions } from 'vs/editor/common/model';
A
Alex Dima 已提交
16
import { TextModel } from 'vs/editor/common/model/textModel';
A
Alex Dima 已提交
17
import { LanguageIdentifier } from 'vs/editor/common/modes';
18
import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
A
Alex Dima 已提交
19
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
20
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
A
Alex Dima 已提交
21
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
A
Alex Dima 已提交
22 23

export interface IColumnSelectData {
24 25 26
	isReal: boolean;
	fromViewLineNumber: number;
	fromViewVisualColumn: number;
A
Alex Dima 已提交
27 28 29 30 31 32 33 34 35 36
	toViewLineNumber: number;
	toViewVisualColumn: number;
}

export const enum RevealTarget {
	Primary = 0,
	TopMost = 1,
	BottomMost = 2
}

37 38 39 40 41 42 43 44 45 46 47
/**
 * 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
}

A
Alex Dima 已提交
48 49 50 51 52 53 54 55 56
export interface ICursors {
	readonly context: CursorContext;
	getPrimaryCursor(): CursorState;
	getLastAddedCursorIndex(): number;
	getAll(): CursorState[];

	getColumnSelectData(): IColumnSelectData;
	setColumnSelectData(columnSelectData: IColumnSelectData): void;

A
Alex Dima 已提交
57
	setStates(source: string, reason: CursorChangeReason, states: PartialCursorState[] | null): void;
58 59
	reveal(source: string, horizontal: boolean, target: RevealTarget, scrollType: ScrollType): void;
	revealRange(source: string, revealHorizontal: boolean, viewRange: Range, verticalType: VerticalRevealType, scrollType: ScrollType): void;
60

A
Alex Dima 已提交
61
	scrollTo(desiredScrollTop: number): void;
62 63 64

	getPrevEditOperationType(): EditOperationType;
	setPrevEditOperationType(type: EditOperationType): void;
A
Alex Dima 已提交
65
}
A
Alex Dima 已提交
66 67 68 69

export interface CharacterMap {
	[char: string]: string;
}
70 71 72
export interface MultipleCharacterMap {
	[char: string]: string[];
}
A
Alex Dima 已提交
73

74 75
const autoCloseAlways = () => true;
const autoCloseNever = () => false;
A
Alex Dima 已提交
76 77
const autoCloseBeforeWhitespace = (chr: string) => (chr === ' ' || chr === '\t');

78 79 80 81 82 83 84 85
function appendEntry<K, V>(target: Map<K, V[]>, key: K, value: V): void {
	if (target.has(key)) {
		target.get(key)!.push(value);
	} else {
		target.set(key, [value]);
	}
}

A
Alex Dima 已提交
86 87 88
export class CursorConfiguration {
	_cursorMoveConfigurationBrand: void;

A
Alex Dima 已提交
89
	public readonly readOnly: boolean;
A
Alex Dima 已提交
90
	public readonly tabSize: number;
D
David Lechner 已提交
91
	public readonly indentSize: number;
A
Alex Dima 已提交
92 93
	public readonly insertSpaces: boolean;
	public readonly pageSize: number;
94
	public readonly lineHeight: number;
A
Alex Dima 已提交
95 96
	public readonly useTabStops: boolean;
	public readonly wordSeparators: string;
97
	public readonly emptySelectionClipboard: boolean;
98
	public readonly copyWithSyntaxHighlighting: boolean;
A
Alex Dima 已提交
99
	public readonly multiCursorMergeOverlapping: boolean;
100
	public readonly multiCursorPaste: 'spread' | 'full';
J
Jackson Kearl 已提交
101 102
	public readonly autoClosingBrackets: EditorAutoClosingStrategy;
	public readonly autoClosingQuotes: EditorAutoClosingStrategy;
103
	public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy;
104
	public readonly autoSurround: EditorAutoSurroundStrategy;
105
	public readonly autoIndent: boolean;
106 107
	public readonly autoClosingPairsOpen2: Map<string, StandardAutoClosingPairConditional[]>;
	public readonly autoClosingPairsClose2: Map<string, StandardAutoClosingPairConditional[]>;
108
	public readonly surroundingPairs: CharacterMap;
109
	public readonly shouldAutoCloseBefore: { quote: (ch: string) => boolean, bracket: (ch: string) => boolean };
110 111

	private readonly _languageIdentifier: LanguageIdentifier;
A
Alex Dima 已提交
112
	private _electricChars: { [key: string]: boolean; } | null;
A
Alex Dima 已提交
113

114
	public static shouldRecreate(e: ConfigurationChangedEvent): boolean {
A
Alex Dima 已提交
115
		return (
A
renames  
Alex Dima 已提交
116
			e.hasChanged(EditorOption.layoutInfo)
A
Alex Dima 已提交
117 118 119
			|| e.hasChanged(EditorOption.wordSeparators)
			|| e.hasChanged(EditorOption.emptySelectionClipboard)
			|| e.hasChanged(EditorOption.multiCursorMergeOverlapping)
120
			|| e.hasChanged(EditorOption.multiCursorPaste)
A
Alex Dima 已提交
121 122 123 124 125
			|| e.hasChanged(EditorOption.autoClosingBrackets)
			|| e.hasChanged(EditorOption.autoClosingQuotes)
			|| e.hasChanged(EditorOption.autoClosingOvertype)
			|| e.hasChanged(EditorOption.autoSurround)
			|| e.hasChanged(EditorOption.useTabStops)
A
Alex Dima 已提交
126
			|| e.hasChanged(EditorOption.lineHeight)
A
renames  
Alex Dima 已提交
127
			|| e.hasChanged(EditorOption.readOnly)
A
Alex Dima 已提交
128 129 130 131
		);
	}

	constructor(
A
Alex Dima 已提交
132
		languageIdentifier: LanguageIdentifier,
A
Alex Dima 已提交
133
		modelOptions: TextModelResolvedOptions,
A
Alex Dima 已提交
134
		configuration: IConfiguration
A
Alex Dima 已提交
135
	) {
136 137
		this._languageIdentifier = languageIdentifier;

A
Alex Dima 已提交
138
		const options = configuration.options;
A
renames  
Alex Dima 已提交
139
		const layoutInfo = options.get(EditorOption.layoutInfo);
A
Alex Dima 已提交
140

A
renames  
Alex Dima 已提交
141
		this.readOnly = options.get(EditorOption.readOnly);
A
Alex Dima 已提交
142
		this.tabSize = modelOptions.tabSize;
D
David Lechner 已提交
143
		this.indentSize = modelOptions.indentSize;
A
Alex Dima 已提交
144
		this.insertSpaces = modelOptions.insertSpaces;
A
Alex Dima 已提交
145 146
		this.lineHeight = options.get(EditorOption.lineHeight);
		this.pageSize = Math.max(1, Math.floor(layoutInfo.height / this.lineHeight) - 2);
A
Alex Dima 已提交
147 148 149 150 151
		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);
152
		this.multiCursorPaste = options.get(EditorOption.multiCursorPaste);
A
Alex Dima 已提交
153 154 155 156 157
		this.autoClosingBrackets = options.get(EditorOption.autoClosingBrackets);
		this.autoClosingQuotes = options.get(EditorOption.autoClosingQuotes);
		this.autoClosingOvertype = options.get(EditorOption.autoClosingOvertype);
		this.autoSurround = options.get(EditorOption.autoSurround);
		this.autoIndent = options.get(EditorOption.autoIndent);
A
Alex Dima 已提交
158

159 160
		this.autoClosingPairsOpen2 = new Map<string, StandardAutoClosingPairConditional[]>();
		this.autoClosingPairsClose2 = new Map<string, StandardAutoClosingPairConditional[]>();
A
Alex Dima 已提交
161
		this.surroundingPairs = {};
162
		this._electricChars = null;
A
Alex Dima 已提交
163

164
		this.shouldAutoCloseBefore = {
A
Alex Dima 已提交
165 166
			quote: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingQuotes),
			bracket: CursorConfiguration._getShouldAutoClose(languageIdentifier, this.autoClosingBrackets)
167 168
		};

A
Alex Dima 已提交
169 170
		let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier);
		if (autoClosingPairs) {
171
			for (const pair of autoClosingPairs) {
172 173 174 175
				appendEntry(this.autoClosingPairsOpen2, pair.open.charAt(pair.open.length - 1), pair);
				if (pair.close.length === 1) {
					appendEntry(this.autoClosingPairsClose2, pair.close, pair);
				}
A
Alex Dima 已提交
176 177 178 179 180
			}
		}

		let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier);
		if (surroundingPairs) {
181 182
			for (const pair of surroundingPairs) {
				this.surroundingPairs[pair.open] = pair.close;
A
Alex Dima 已提交
183 184
			}
		}
A
Alex Dima 已提交
185 186
	}

187 188 189 190 191
	public get electricChars() {
		if (!this._electricChars) {
			this._electricChars = {};
			let electricChars = CursorConfiguration._getElectricCharacters(this._languageIdentifier);
			if (electricChars) {
192 193
				for (const char of electricChars) {
					this._electricChars[char] = true;
194 195 196 197 198 199
				}
			}
		}
		return this._electricChars;
	}

A
Alex Dima 已提交
200
	public normalizeIndentation(str: string): string {
D
David Lechner 已提交
201
		return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces);
A
Alex Dima 已提交
202
	}
A
Alex Dima 已提交
203

A
Alex Dima 已提交
204
	private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] | null {
A
Alex Dima 已提交
205 206 207 208 209 210 211 212
		try {
			return LanguageConfigurationRegistry.getElectricCharacters(languageIdentifier.id);
		} catch (e) {
			onUnexpectedError(e);
			return null;
		}
	}

213
	private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): StandardAutoClosingPairConditional[] | null {
A
Alex Dima 已提交
214 215 216 217 218 219 220 221
		try {
			return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
		} catch (e) {
			onUnexpectedError(e);
			return null;
		}
	}

A
Alex Dima 已提交
222 223 224 225 226 227 228 229 230 231 232
	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;
		}
233 234
	}

A
Alex Dima 已提交
235
	private static _getLanguageDefinedShouldAutoClose(languageIdentifier: LanguageIdentifier): (ch: string) => boolean {
236
		try {
A
Alex Dima 已提交
237 238
			const autoCloseBeforeSet = LanguageConfigurationRegistry.getAutoCloseBeforeSet(languageIdentifier.id);
			return c => autoCloseBeforeSet.indexOf(c) !== -1;
239 240
		} catch (e) {
			onUnexpectedError(e);
A
Alex Dima 已提交
241
			return autoCloseNever;
242 243 244
		}
	}

A
Alex Dima 已提交
245
	private static _getSurroundingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] | null {
A
Alex Dima 已提交
246 247 248 249 250 251 252
		try {
			return LanguageConfigurationRegistry.getSurroundingPairs(languageIdentifier.id);
		} catch (e) {
			onUnexpectedError(e);
			return null;
		}
	}
A
Alex Dima 已提交
253 254
}

A
Alex Dima 已提交
255 256 257
/**
 * Represents a simple model (either the model or the view model).
 */
A
Alex Dima 已提交
258 259 260 261 262 263 264 265 266
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;
}

A
Alex Dima 已提交
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
/**
 * 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());
	}

A
Alex Dima 已提交
306
	public move(inSelectionMode: boolean, lineNumber: number, column: number, leftoverVisibleColumns: number): SingleCursorState {
A
Alex Dima 已提交
307 308 309 310 311
		if (inSelectionMode) {
			// move just position
			return new SingleCursorState(
				this.selectionStart,
				this.selectionStartLeftoverVisibleColumns,
A
Alex Dima 已提交
312
				new Position(lineNumber, column),
A
Alex Dima 已提交
313 314 315 316 317
				leftoverVisibleColumns
			);
		} else {
			// move everything
			return new SingleCursorState(
A
Alex Dima 已提交
318
				new Range(lineNumber, column, lineNumber, column),
A
Alex Dima 已提交
319
				leftoverVisibleColumns,
A
Alex Dima 已提交
320
				new Position(lineNumber, column),
A
Alex Dima 已提交
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
				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
		);
	}
}

A
Alex Dima 已提交
355 356 357
export class CursorContext {
	_cursorContextBrand: void;

A
Alex Dima 已提交
358
	public readonly model: ITextModel;
359
	public readonly viewModel: IViewModel;
A
Alex Dima 已提交
360 361
	public readonly config: CursorConfiguration;

A
Alex Dima 已提交
362
	constructor(configuration: IConfiguration, model: ITextModel, viewModel: IViewModel) {
A
Alex Dima 已提交
363
		this.model = model;
364
		this.viewModel = viewModel;
A
Alex Dima 已提交
365 366 367 368
		this.config = new CursorConfiguration(
			this.model.getLanguageIdentifier(),
			this.model.getOptions(),
			configuration
369
		);
A
Alex Dima 已提交
370 371 372
	}

	public validateViewPosition(viewPosition: Position, modelPosition: Position): Position {
373
		return this.viewModel.coordinatesConverter.validateViewPosition(viewPosition, modelPosition);
A
Alex Dima 已提交
374 375 376
	}

	public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
377
		return this.viewModel.coordinatesConverter.validateViewRange(viewRange, expectedModelRange);
A
Alex Dima 已提交
378 379
	}

380
	public convertViewRangeToModelRange(viewRange: Range): Range {
381
		return this.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange);
382 383
	}

A
Alex Dima 已提交
384
	public convertViewPositionToModelPosition(lineNumber: number, column: number): Position {
385
		return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, column));
A
Alex Dima 已提交
386 387 388
	}

	public convertModelPositionToViewPosition(modelPosition: Position): Position {
389
		return this.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
A
Alex Dima 已提交
390 391 392
	}

	public convertModelRangeToViewRange(modelRange: Range): Range {
393
		return this.viewModel.coordinatesConverter.convertModelRangeToViewRange(modelRange);
A
Alex Dima 已提交
394 395
	}

396 397
	public getCurrentScrollTop(): number {
		return this.viewModel.viewLayout.getCurrentScrollTop();
A
Alex Dima 已提交
398 399 400
	}

	public getCompletelyVisibleViewRange(): Range {
401
		return this.viewModel.getCompletelyVisibleViewRange();
A
Alex Dima 已提交
402 403 404
	}

	public getCompletelyVisibleModelRange(): Range {
405 406
		const viewRange = this.viewModel.getCompletelyVisibleViewRange();
		return this.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange);
A
Alex Dima 已提交
407 408 409
	}

	public getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range {
410
		return this.viewModel.getCompletelyVisibleViewRangeAtScrollTop(scrollTop);
A
Alex Dima 已提交
411 412 413
	}

	public getVerticalOffsetForViewLine(viewLineNumber: number): number {
414
		return this.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewLineNumber);
A
Alex Dima 已提交
415 416 417
	}
}

A
Alex Dima 已提交
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
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;

A
Alex Dima 已提交
440 441 442
export class CursorState {
	_cursorStateBrand: void;

A
Alex Dima 已提交
443 444
	public static fromModelState(modelState: SingleCursorState): PartialModelCursorState {
		return new PartialModelCursorState(modelState);
445 446
	}

A
Alex Dima 已提交
447 448
	public static fromViewState(viewState: SingleCursorState): PartialViewCursorState {
		return new PartialViewCursorState(viewState);
449 450
	}

A
Alex Dima 已提交
451
	public static fromModelSelection(modelSelection: ISelection): PartialModelCursorState {
A
Alex Dima 已提交
452 453 454 455 456 457 458 459 460 461 462
		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);
	}

463
	public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] {
A
Alex Dima 已提交
464
		let states: PartialModelCursorState[] = [];
A
Alex Dima 已提交
465 466 467 468 469 470
		for (let i = 0, len = modelSelections.length; i < len; i++) {
			states[i] = this.fromModelSelection(modelSelections[i]);
		}
		return states;
	}

A
Alex Dima 已提交
471 472 473 474 475 476 477
	readonly modelState: SingleCursorState;
	readonly viewState: SingleCursorState;

	constructor(modelState: SingleCursorState, viewState: SingleCursorState) {
		this.modelState = modelState;
		this.viewState = viewState;
	}
478 479

	public equals(other: CursorState): boolean {
480
		return (this.viewState.equals(other.viewState) && this.modelState.equals(other.modelState));
481
	}
A
Alex Dima 已提交
482 483
}

484 485 486
export class EditOperationResult {
	_editOperationResultBrand: void;

487
	readonly type: EditOperationType;
488
	readonly commands: Array<ICommand | null>;
A
Alex Dima 已提交
489 490 491 492
	readonly shouldPushStackElementBefore: boolean;
	readonly shouldPushStackElementAfter: boolean;

	constructor(
493
		type: EditOperationType,
494
		commands: Array<ICommand | null>,
A
Alex Dima 已提交
495
		opts: {
A
Alex Dima 已提交
496 497 498 499
			shouldPushStackElementBefore: boolean;
			shouldPushStackElementAfter: boolean;
		}
	) {
500
		this.type = type;
501
		this.commands = commands;
A
Alex Dima 已提交
502 503
		this.shouldPushStackElementBefore = opts.shouldPushStackElementBefore;
		this.shouldPushStackElementAfter = opts.shouldPushStackElementAfter;
A
Alex Dima 已提交
504 505 506
	}
}

A
Alex Dima 已提交
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
/**
 * Common operations that work and make sense both on the model and on the view model.
 */
export class CursorColumns {

	public static isLowSurrogate(model: ICursorSimpleModel, lineNumber: number, charOffset: number): boolean {
		let lineContent = model.getLineContent(lineNumber);
		if (charOffset < 0 || charOffset >= lineContent.length) {
			return false;
		}
		return strings.isLowSurrogate(lineContent.charCodeAt(charOffset));
	}

	public static isHighSurrogate(model: ICursorSimpleModel, lineNumber: number, charOffset: number): boolean {
		let lineContent = model.getLineContent(lineNumber);
		if (charOffset < 0 || charOffset >= lineContent.length) {
			return false;
		}
		return strings.isHighSurrogate(lineContent.charCodeAt(charOffset));
	}

	public static isInsideSurrogatePair(model: ICursorSimpleModel, lineNumber: number, column: number): boolean {
		return this.isHighSurrogate(model, lineNumber, column - 2);
	}

A
Alex Dima 已提交
532
	public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number {
A
Alex Dima 已提交
533 534 535 536 537 538 539 540 541
		let endOffset = lineContent.length;
		if (endOffset > column - 1) {
			endOffset = column - 1;
		}

		let result = 0;
		for (let i = 0; i < endOffset; i++) {
			let charCode = lineContent.charCodeAt(i);
			if (charCode === CharCode.Tab) {
A
Alex Dima 已提交
542
				result = this.nextRenderTabStop(result, tabSize);
543 544
			} else if (strings.isFullWidthCharacter(charCode)) {
				result = result + 2;
A
Alex Dima 已提交
545 546 547 548 549 550 551
			} else {
				result = result + 1;
			}
		}
		return result;
	}

552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
	public static toStatusbarColumn(lineContent: string, column: number, tabSize: number): number {
		let endOffset = lineContent.length;
		if (endOffset > column - 1) {
			endOffset = column - 1;
		}

		let result = 0;
		for (let i = 0; i < endOffset; i++) {
			let charCode = lineContent.charCodeAt(i);
			if (charCode === CharCode.Tab) {
				result = this.nextRenderTabStop(result, tabSize);
			} else {
				if (strings.isHighSurrogate(charCode)) {
					result = result + 1;
					i = i + 1;
				} else {
					result = result + 1;
				}
			}
		}
		return result + 1;
	}

A
Alex Dima 已提交
575
	public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number {
A
Alex Dima 已提交
576
		return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize);
A
Alex Dima 已提交
577 578
	}

A
Alex Dima 已提交
579
	public static columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number {
A
Alex Dima 已提交
580 581 582 583 584 585 586 587 588 589 590 591
		if (visibleColumn <= 0) {
			return 1;
		}

		const lineLength = lineContent.length;

		let beforeVisibleColumn = 0;
		for (let i = 0; i < lineLength; i++) {
			let charCode = lineContent.charCodeAt(i);

			let afterVisibleColumn: number;
			if (charCode === CharCode.Tab) {
A
Alex Dima 已提交
592
				afterVisibleColumn = this.nextRenderTabStop(beforeVisibleColumn, tabSize);
593 594
			} else if (strings.isFullWidthCharacter(charCode)) {
				afterVisibleColumn = beforeVisibleColumn + 2;
A
Alex Dima 已提交
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
			} else {
				afterVisibleColumn = beforeVisibleColumn + 1;
			}

			if (afterVisibleColumn >= visibleColumn) {
				let prevDelta = visibleColumn - beforeVisibleColumn;
				let afterDelta = afterVisibleColumn - visibleColumn;
				if (afterDelta < prevDelta) {
					return i + 2;
				} else {
					return i + 1;
				}
			}

			beforeVisibleColumn = afterVisibleColumn;
		}

		// walked the entire string
		return lineLength + 1;
	}

	public static columnFromVisibleColumn2(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, visibleColumn: number): number {
A
Alex Dima 已提交
617
		let result = this.columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.tabSize);
A
Alex Dima 已提交
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634

		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)
	 */
A
Alex Dima 已提交
635 636 637 638 639 640 641 642
	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 {
D
David Lechner 已提交
643
		return visibleColumn + indentSize - visibleColumn % indentSize;
A
Alex Dima 已提交
644 645 646 647 648
	}

	/**
	 * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
	 */
A
Alex Dima 已提交
649 650 651 652 653 654 655 656
	public static prevRenderTabStop(column: number, tabSize: number): number {
		return column - 1 - (column - 1) % tabSize;
	}

	/**
	 * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
	 */
	public static prevIndentTabStop(column: number, indentSize: number): number {
D
David Lechner 已提交
657
		return column - 1 - (column - 1) % indentSize;
A
Alex Dima 已提交
658 659
	}
}
A
Alex Dima 已提交
660 661 662 663

export function isQuote(ch: string): boolean {
	return (ch === '\'' || ch === '"' || ch === '`');
}