SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,861 @@
/*---------------------------------------------------------------------------------------------
* 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 nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import { onUnexpectedError } from 'vs/base/common/errors';
import { CursorCollection } from 'vs/editor/common/controller/cursorCollection';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection, ISelection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CursorColumns, CursorConfiguration, EditOperationResult, CursorContext, CursorState, RevealTarget, IColumnSelectData, ICursors } from 'vs/editor/common/controller/cursorCommon';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
import { TypeOperations } from 'vs/editor/common/controller/cursorTypeOperations';
import { TextModelEventType, ModelRawContentChangedEvent, RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import Event, { Emitter } from 'vs/base/common/event';
// import { ScreenReaderMessageGenerator } from "vs/editor/common/controller/accGenerator";
function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean {
for (let i = 0, len = events.length; i < len; i++) {
if (events[i].type === viewEvents.ViewEventType.ViewLineMappingChanged) {
return true;
}
}
return false;
}
export class CursorStateChangedEvent {
/**
* The new selections.
* The primary selection is always at index 0.
*/
readonly selections: Selection[];
/**
* Source of the call that caused the event.
*/
readonly source: string;
/**
* Reason.
*/
readonly reason: CursorChangeReason;
constructor(selections: Selection[], source: string, reason: CursorChangeReason) {
this.selections = selections;
this.source = source;
this.reason = reason;
}
}
/**
* A snapshot of the cursor and the model state
*/
export class CursorModelState {
public readonly modelVersionId: number;
public readonly cursorState: CursorState[];
constructor(model: editorCommon.IModel, cursor: Cursor) {
this.modelVersionId = model.getVersionId();
this.cursorState = cursor.getAll();
}
public equals(other: CursorModelState): boolean {
if (!other) {
return false;
}
if (this.modelVersionId !== other.modelVersionId) {
return false;
}
if (this.cursorState.length !== other.cursorState.length) {
return false;
}
for (let i = 0, len = this.cursorState.length; i < len; i++) {
if (!this.cursorState[i].equals(other.cursorState[i])) {
return false;
}
}
return true;
}
}
export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
private readonly _onDidChange: Emitter<CursorStateChangedEvent> = this._register(new Emitter<CursorStateChangedEvent>());
public readonly onDidChange: Event<CursorStateChangedEvent> = this._onDidChange.event;
private readonly _configuration: editorCommon.IConfiguration;
private readonly _model: editorCommon.IModel;
private readonly _viewModel: IViewModel;
public context: CursorContext;
private _cursors: CursorCollection;
private _isHandling: boolean;
private _isDoingComposition: boolean;
private _columnSelectData: IColumnSelectData;
constructor(configuration: editorCommon.IConfiguration, model: editorCommon.IModel, viewModel: IViewModel) {
super();
this._configuration = configuration;
this._model = model;
this._viewModel = viewModel;
this.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors = new CursorCollection(this.context);
this._isHandling = false;
this._isDoingComposition = false;
this._columnSelectData = null;
this._register(this._model.addBulkListener((events) => {
if (this._isHandling) {
return;
}
let hadContentChange = false;
let hadFlushEvent = false;
for (let i = 0, len = events.length; i < len; i++) {
const event = events[i];
const eventType = event.type;
if (eventType === TextModelEventType.ModelRawContentChanged2) {
hadContentChange = true;
const rawChangeEvent = <ModelRawContentChangedEvent>event.data;
hadFlushEvent = hadFlushEvent || rawChangeEvent.containsEvent(RawContentChangedType.Flush);
}
}
if (!hadContentChange) {
return;
}
this._onModelContentChanged(hadFlushEvent);
}));
this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => {
if (!containsLineMappingChanged(events)) {
return;
}
// Ensure valid state
this.setStates('viewModel', CursorChangeReason.NotSet, this.getAll());
}));
const updateCursorContext = () => {
this.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors.updateContext(this.context);
};
this._register(this._model.onDidChangeLanguage((e) => {
updateCursorContext();
}));
this._register(LanguageConfigurationRegistry.onDidChange(() => {
// TODO@Alex: react only if certain supports changed? (and if my model's mode changed)
updateCursorContext();
}));
this._register(model.onDidChangeOptions(() => {
updateCursorContext();
}));
this._register(this._configuration.onDidChange((e) => {
if (CursorConfiguration.shouldRecreate(e)) {
updateCursorContext();
}
}));
}
public dispose(): void {
this._cursors.dispose();
super.dispose();
}
// ------ some getters/setters
public getPrimaryCursor(): CursorState {
return this._cursors.getPrimaryCursor();
}
public getLastAddedCursorIndex(): number {
return this._cursors.getLastAddedCursorIndex();
}
public getAll(): CursorState[] {
return this._cursors.getAll();
}
public setStates(source: string, reason: CursorChangeReason, states: CursorState[]): void {
const oldState = new CursorModelState(this._model, this);
this._cursors.setStates(states);
this._cursors.normalize();
this._columnSelectData = null;
this._emitStateChangedIfNecessary(source, reason, oldState);
}
public setColumnSelectData(columnSelectData: IColumnSelectData): void {
this._columnSelectData = columnSelectData;
}
public reveal(horizontal: boolean, target: RevealTarget, scrollType: editorCommon.ScrollType): void {
this._revealRange(target, viewEvents.VerticalRevealType.Simple, horizontal, scrollType);
}
public revealRange(revealHorizontal: boolean, viewRange: Range, verticalType: viewEvents.VerticalRevealType, scrollType: editorCommon.ScrollType) {
this.emitCursorRevealRange(viewRange, verticalType, revealHorizontal, scrollType);
}
public scrollTo(desiredScrollTop: number): void {
this._viewModel.viewLayout.setScrollPositionSmooth({
scrollTop: desiredScrollTop
});
}
public saveState(): editorCommon.ICursorState[] {
let result: editorCommon.ICursorState[] = [];
const selections = this._cursors.getSelections();
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
result.push({
inSelectionMode: !selection.isEmpty(),
selectionStart: {
lineNumber: selection.selectionStartLineNumber,
column: selection.selectionStartColumn,
},
position: {
lineNumber: selection.positionLineNumber,
column: selection.positionColumn,
}
});
}
return result;
}
public restoreState(states: editorCommon.ICursorState[]): void {
let desiredSelections: ISelection[] = [];
for (let i = 0, len = states.length; i < len; i++) {
const state = states[i];
let positionLineNumber = 1;
let positionColumn = 1;
// Avoid missing properties on the literal
if (state.position && state.position.lineNumber) {
positionLineNumber = state.position.lineNumber;
}
if (state.position && state.position.column) {
positionColumn = state.position.column;
}
let selectionStartLineNumber = positionLineNumber;
let selectionStartColumn = positionColumn;
// Avoid missing properties on the literal
if (state.selectionStart && state.selectionStart.lineNumber) {
selectionStartLineNumber = state.selectionStart.lineNumber;
}
if (state.selectionStart && state.selectionStart.column) {
selectionStartColumn = state.selectionStart.column;
}
desiredSelections.push({
selectionStartLineNumber: selectionStartLineNumber,
selectionStartColumn: selectionStartColumn,
positionLineNumber: positionLineNumber,
positionColumn: positionColumn
});
}
this.setStates('restoreState', CursorChangeReason.NotSet, CursorState.fromModelSelections(desiredSelections));
this.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Immediate);
}
private _onModelContentChanged(hadFlushEvent: boolean): void {
if (hadFlushEvent) {
// a model.setValue() was called
this._cursors.dispose();
this._cursors = new CursorCollection(this.context);
this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null);
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
}
}
public getSelection(): Selection {
return this._cursors.getPrimaryCursor().modelState.selection;
}
public getColumnSelectData(): IColumnSelectData {
if (this._columnSelectData) {
return this._columnSelectData;
}
const primaryCursor = this._cursors.getPrimaryCursor();
const primaryPos = primaryCursor.viewState.position;
return {
toViewLineNumber: primaryPos.lineNumber,
toViewVisualColumn: CursorColumns.visibleColumnFromColumn2(this.context.config, this.context.viewModel, primaryPos)
};
}
public getSelections(): Selection[] {
return this._cursors.getSelections();
}
public getViewSelections(): Selection[] {
return this._cursors.getViewSelections();
}
public getPosition(): Position {
return this._cursors.getPrimaryCursor().modelState.position;
}
public setSelections(source: string, selections: ISelection[]): void {
this.setStates(source, CursorChangeReason.NotSet, CursorState.fromModelSelections(selections));
}
// ------ auxiliary handling logic
private _executeEditOperation(opResult: EditOperationResult): void {
if (!opResult) {
// Nothing to execute
return;
}
if (this._configuration.editor.readOnly) {
// Cannot execute when read only
return;
}
if (opResult.shouldPushStackElementBefore) {
this._model.pushStackElement();
}
const result = CommandExecutor.executeCommands(this._model, this._cursors.getSelections(), opResult.commands);
if (result) {
// The commands were applied correctly
this._interpretCommandResult(result);
}
if (opResult.shouldPushStackElementAfter) {
this._model.pushStackElement();
}
}
private _interpretCommandResult(cursorState: Selection[]): void {
if (!cursorState || cursorState.length === 0) {
return;
}
this._columnSelectData = null;
this._cursors.setSelections(cursorState);
this._cursors.normalize();
}
// -----------------------------------------------------------------------------------------------------------
// ----- emitting events
private _emitStateChangedIfNecessary(source: string, reason: CursorChangeReason, oldState: CursorModelState): boolean {
const newState = new CursorModelState(this._model, this);
if (newState.equals(oldState)) {
return false;
}
let isInEditableRange: boolean = true;
if (this._model.hasEditableRange()) {
const editableRange = this._model.getEditableRange();
if (!editableRange.containsPosition(newState.cursorState[0].modelState.position)) {
isInEditableRange = false;
}
}
const selections = this._cursors.getSelections();
const viewSelections = this._cursors.getViewSelections();
// Let the view get the event first.
this._emit([new viewEvents.ViewCursorStateChangedEvent(viewSelections, isInEditableRange)]);
// Only after the view has been notified, let the rest of the world know...
if (!oldState
|| oldState.cursorState.length !== newState.cursorState.length
|| newState.cursorState.some((newCursorState, i) => !newCursorState.modelState.equals(oldState.cursorState[i].modelState))
) {
this._onDidChange.fire(new CursorStateChangedEvent(selections, source || 'keyboard', reason));
}
return true;
}
private _revealRange(revealTarget: RevealTarget, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void {
const viewPositions = this._cursors.getViewPositions();
let viewPosition = viewPositions[0];
if (revealTarget === RevealTarget.TopMost) {
for (let i = 1; i < viewPositions.length; i++) {
if (viewPositions[i].isBefore(viewPosition)) {
viewPosition = viewPositions[i];
}
}
} else if (revealTarget === RevealTarget.BottomMost) {
for (let i = 1; i < viewPositions.length; i++) {
if (viewPosition.isBeforeOrEqual(viewPositions[i])) {
viewPosition = viewPositions[i];
}
}
} else {
if (viewPositions.length > 1) {
// no revealing!
return;
}
}
const viewRange = new Range(viewPosition.lineNumber, viewPosition.column, viewPosition.lineNumber, viewPosition.column);
this.emitCursorRevealRange(viewRange, verticalType, revealHorizontal, scrollType);
}
public emitCursorRevealRange(viewRange: Range, verticalType: viewEvents.VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType) {
this._emit([new viewEvents.ViewRevealRangeRequestEvent(viewRange, verticalType, revealHorizontal, scrollType)]);
}
// -----------------------------------------------------------------------------------------------------------
// ----- handlers beyond this point
public trigger(source: string, handlerId: string, payload: any): void {
const H = editorCommon.Handler;
if (handlerId === H.CompositionStart) {
this._isDoingComposition = true;
return;
}
if (handlerId === H.CompositionEnd) {
this._isDoingComposition = false;
return;
}
const oldState = new CursorModelState(this._model, this);
let cursorChangeReason = CursorChangeReason.NotSet;
// ensure valid state on all cursors
this._cursors.ensureValidState();
this._isHandling = true;
try {
switch (handlerId) {
case H.Type:
this._type(source, <string>payload.text);
break;
case H.ReplacePreviousChar:
this._replacePreviousChar(<string>payload.text, <number>payload.replaceCharCnt);
break;
case H.Paste:
cursorChangeReason = CursorChangeReason.Paste;
this._paste(<string>payload.text, <boolean>payload.pasteOnNewLine);
break;
case H.Cut:
this._cut();
break;
case H.Undo:
cursorChangeReason = CursorChangeReason.Undo;
this._interpretCommandResult(this._model.undo());
break;
case H.Redo:
cursorChangeReason = CursorChangeReason.Redo;
this._interpretCommandResult(this._model.redo());
break;
case H.ExecuteCommand:
this._externalExecuteCommand(<editorCommon.ICommand>payload);
break;
case H.ExecuteCommands:
this._externalExecuteCommands(<editorCommon.ICommand[]>payload);
break;
}
} catch (err) {
onUnexpectedError(err);
}
this._isHandling = false;
if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) {
this._revealRange(RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
}
}
private _type(source: string, text: string): void {
if (!this._isDoingComposition && source === 'keyboard') {
// If this event is coming straight from the keyboard, look for electric characters and enter
for (let i = 0, len = text.length; i < len; i++) {
let charCode = text.charCodeAt(i);
let chr: string;
if (strings.isHighSurrogate(charCode) && i + 1 < len) {
chr = text.charAt(i) + text.charAt(i + 1);
i++;
} else {
chr = text.charAt(i);
}
// Here we must interpret each typed character individually, that's why we create a new context
this._executeEditOperation(TypeOperations.typeWithInterceptors(this.context.config, this.context.model, this.getSelections(), chr));
}
} else {
this._executeEditOperation(TypeOperations.typeWithoutInterceptors(this.context.config, this.context.model, this.getSelections(), text));
}
}
private _replacePreviousChar(text: string, replaceCharCnt: number): void {
this._executeEditOperation(TypeOperations.replacePreviousChar(this.context.config, this.context.model, this.getSelections(), text, replaceCharCnt));
}
private _paste(text: string, pasteOnNewLine: boolean): void {
this._executeEditOperation(TypeOperations.paste(this.context.config, this.context.model, this.getSelections(), pasteOnNewLine, text));
}
private _cut(): void {
this._executeEditOperation(DeleteOperations.cut(this.context.config, this.context.model, this.getSelections()));
}
private _externalExecuteCommand(command: editorCommon.ICommand): void {
this._cursors.killSecondaryCursors();
this._executeEditOperation(new EditOperationResult([command], {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false
}));
}
private _externalExecuteCommands(commands: editorCommon.ICommand[]): void {
this._executeEditOperation(new EditOperationResult(commands, {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false
}));
}
}
interface IExecContext {
readonly model: editorCommon.IModel;
readonly selectionsBefore: Selection[];
readonly selectionStartMarkers: string[];
readonly positionMarkers: string[];
}
interface ICommandData {
operations: editorCommon.IIdentifiedSingleEditOperation[];
hadTrackedEditOperation: boolean;
}
interface ICommandsData {
operations: editorCommon.IIdentifiedSingleEditOperation[];
hadTrackedEditOperation: boolean;
}
class CommandExecutor {
public static executeCommands(model: editorCommon.IModel, selectionsBefore: Selection[], commands: editorCommon.ICommand[]): Selection[] {
const ctx: IExecContext = {
model: model,
selectionsBefore: selectionsBefore,
selectionStartMarkers: [],
positionMarkers: []
};
const result = this._innerExecuteCommands(ctx, commands);
for (let i = 0; i < ctx.selectionStartMarkers.length; i++) {
ctx.model._removeMarker(ctx.selectionStartMarkers[i]);
ctx.model._removeMarker(ctx.positionMarkers[i]);
}
return result;
}
private static _innerExecuteCommands(ctx: IExecContext, commands: editorCommon.ICommand[]): Selection[] {
if (this._arrayIsEmpty(commands)) {
return null;
}
const commandsData = this._getEditOperations(ctx, commands);
if (commandsData.operations.length === 0) {
return null;
}
const rawOperations = commandsData.operations;
const editableRange = ctx.model.getEditableRange();
const editableRangeStart = editableRange.getStartPosition();
const editableRangeEnd = editableRange.getEndPosition();
for (let i = 0, len = rawOperations.length; i < len; i++) {
const operationRange = rawOperations[i].range;
if (!editableRangeStart.isBeforeOrEqual(operationRange.getStartPosition()) || !operationRange.getEndPosition().isBeforeOrEqual(editableRangeEnd)) {
// These commands are outside of the editable range
return null;
}
}
const loserCursorsMap = this._getLoserCursorMap(rawOperations);
if (loserCursorsMap.hasOwnProperty('0')) {
// These commands are very messed up
console.warn('Ignoring commands');
return null;
}
// Remove operations belonging to losing cursors
let filteredOperations: editorCommon.IIdentifiedSingleEditOperation[] = [];
for (let i = 0, len = rawOperations.length; i < len; i++) {
if (!loserCursorsMap.hasOwnProperty(rawOperations[i].identifier.major.toString())) {
filteredOperations.push(rawOperations[i]);
}
}
// TODO@Alex: find a better way to do this.
// give the hint that edit operations are tracked to the model
if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) {
filteredOperations[0]._isTracked = true;
}
const selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: editorCommon.IIdentifiedSingleEditOperation[]): Selection[] => {
let groupedInverseEditOperations: editorCommon.IIdentifiedSingleEditOperation[][] = [];
for (let i = 0; i < ctx.selectionsBefore.length; i++) {
groupedInverseEditOperations[i] = [];
}
for (let i = 0; i < inverseEditOperations.length; i++) {
const op = inverseEditOperations[i];
if (!op.identifier) {
// perhaps auto whitespace trim edits
continue;
}
groupedInverseEditOperations[op.identifier.major].push(op);
}
const minorBasedSorter = (a: editorCommon.IIdentifiedSingleEditOperation, b: editorCommon.IIdentifiedSingleEditOperation) => {
return a.identifier.minor - b.identifier.minor;
};
let cursorSelections: Selection[] = [];
for (let i = 0; i < ctx.selectionsBefore.length; i++) {
if (groupedInverseEditOperations[i].length > 0) {
groupedInverseEditOperations[i].sort(minorBasedSorter);
cursorSelections[i] = commands[i].computeCursorState(ctx.model, {
getInverseEditOperations: () => {
return groupedInverseEditOperations[i];
},
getTrackedSelection: (id: string) => {
const idx = parseInt(id, 10);
const selectionStartMarker = ctx.model._getMarker(ctx.selectionStartMarkers[idx]);
const positionMarker = ctx.model._getMarker(ctx.positionMarkers[idx]);
return new Selection(selectionStartMarker.lineNumber, selectionStartMarker.column, positionMarker.lineNumber, positionMarker.column);
}
});
} else {
cursorSelections[i] = ctx.selectionsBefore[i];
}
}
return cursorSelections;
});
// Extract losing cursors
let losingCursors: number[] = [];
for (let losingCursorIndex in loserCursorsMap) {
if (loserCursorsMap.hasOwnProperty(losingCursorIndex)) {
losingCursors.push(parseInt(losingCursorIndex, 10));
}
}
// Sort losing cursors descending
losingCursors.sort((a: number, b: number): number => {
return b - a;
});
// Remove losing cursors
for (let i = 0; i < losingCursors.length; i++) {
selectionsAfter.splice(losingCursors[i], 1);
}
return selectionsAfter;
}
private static _arrayIsEmpty(commands: editorCommon.ICommand[]): boolean {
for (let i = 0, len = commands.length; i < len; i++) {
if (commands[i]) {
return false;
}
}
return true;
}
private static _getEditOperations(ctx: IExecContext, commands: editorCommon.ICommand[]): ICommandsData {
let operations: editorCommon.IIdentifiedSingleEditOperation[] = [];
let hadTrackedEditOperation: boolean = false;
for (let i = 0, len = commands.length; i < len; i++) {
if (commands[i]) {
const r = this._getEditOperationsFromCommand(ctx, i, commands[i]);
operations = operations.concat(r.operations);
hadTrackedEditOperation = hadTrackedEditOperation || r.hadTrackedEditOperation;
}
}
return {
operations: operations,
hadTrackedEditOperation: hadTrackedEditOperation
};
}
private static _getEditOperationsFromCommand(ctx: IExecContext, majorIdentifier: number, command: editorCommon.ICommand): ICommandData {
// This method acts as a transaction, if the command fails
// everything it has done is ignored
let operations: editorCommon.IIdentifiedSingleEditOperation[] = [];
let operationMinor = 0;
const addEditOperation = (selection: Range, text: string) => {
if (selection.isEmpty() && text === '') {
// This command wants to add a no-op => no thank you
return;
}
operations.push({
identifier: {
major: majorIdentifier,
minor: operationMinor++
},
range: selection,
text: text,
forceMoveMarkers: false,
isAutoWhitespaceEdit: command.insertsAutoWhitespace
});
};
let hadTrackedEditOperation = false;
const addTrackedEditOperation = (selection: Range, text: string) => {
hadTrackedEditOperation = true;
addEditOperation(selection, text);
};
const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => {
let selectionMarkerStickToPreviousCharacter: boolean;
let positionMarkerStickToPreviousCharacter: boolean;
if (selection.isEmpty()) {
// Try to lock it with surrounding text
if (typeof trackPreviousOnEmpty === 'boolean') {
selectionMarkerStickToPreviousCharacter = trackPreviousOnEmpty;
positionMarkerStickToPreviousCharacter = trackPreviousOnEmpty;
} else {
const maxLineColumn = ctx.model.getLineMaxColumn(selection.startLineNumber);
if (selection.startColumn === maxLineColumn) {
selectionMarkerStickToPreviousCharacter = true;
positionMarkerStickToPreviousCharacter = true;
} else {
selectionMarkerStickToPreviousCharacter = false;
positionMarkerStickToPreviousCharacter = false;
}
}
} else {
if (selection.getDirection() === SelectionDirection.LTR) {
selectionMarkerStickToPreviousCharacter = false;
positionMarkerStickToPreviousCharacter = true;
} else {
selectionMarkerStickToPreviousCharacter = true;
positionMarkerStickToPreviousCharacter = false;
}
}
const l = ctx.selectionStartMarkers.length;
ctx.selectionStartMarkers[l] = ctx.model._addMarker(0, selection.selectionStartLineNumber, selection.selectionStartColumn, selectionMarkerStickToPreviousCharacter);
ctx.positionMarkers[l] = ctx.model._addMarker(0, selection.positionLineNumber, selection.positionColumn, positionMarkerStickToPreviousCharacter);
return l.toString();
};
const editOperationBuilder: editorCommon.IEditOperationBuilder = {
addEditOperation: addEditOperation,
addTrackedEditOperation: addTrackedEditOperation,
trackSelection: trackSelection
};
try {
command.getEditOperations(ctx.model, editOperationBuilder);
} catch (e) {
e.friendlyMessage = nls.localize('corrupt.commands', "Unexpected exception while executing command.");
onUnexpectedError(e);
return {
operations: [],
hadTrackedEditOperation: false
};
}
return {
operations: operations,
hadTrackedEditOperation: hadTrackedEditOperation
};
}
private static _getLoserCursorMap(operations: editorCommon.IIdentifiedSingleEditOperation[]): { [index: string]: boolean; } {
// This is destructive on the array
operations = operations.slice(0);
// Sort operations with last one first
operations.sort((a: editorCommon.IIdentifiedSingleEditOperation, b: editorCommon.IIdentifiedSingleEditOperation): number => {
// Note the minus!
return -(Range.compareRangesUsingEnds(a.range, b.range));
});
// Operations can not overlap!
let loserCursorsMap: { [index: string]: boolean; } = {};
for (let i = 1; i < operations.length; i++) {
const previousOp = operations[i - 1];
const currentOp = operations[i];
if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) {
let loserMajor: number;
if (previousOp.identifier.major > currentOp.identifier.major) {
// previousOp loses the battle
loserMajor = previousOp.identifier.major;
} else {
loserMajor = currentOp.identifier.major;
}
loserCursorsMap[loserMajor.toString()] = true;
for (let j = 0; j < operations.length; j++) {
if (operations[j].identifier.major === loserMajor) {
operations.splice(j, 1);
if (j < i) {
i--;
}
j--;
}
}
if (i > 0) {
i--;
}
}
}
return loserCursorsMap;
}
}

View File

@@ -0,0 +1,256 @@
/*---------------------------------------------------------------------------------------------
* 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 { OneCursor } from 'vs/editor/common/controller/oneCursor';
import { Selection, ISelection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { CursorState, CursorContext } from 'vs/editor/common/controller/cursorCommon';
export class CursorCollection {
private context: CursorContext;
private primaryCursor: OneCursor;
private secondaryCursors: OneCursor[];
// An index which identifies the last cursor that was added / moved (think Ctrl+drag)
private lastAddedCursorIndex: number;
constructor(context: CursorContext) {
this.context = context;
this.primaryCursor = new OneCursor(context);
this.secondaryCursors = [];
this.lastAddedCursorIndex = 0;
}
public dispose(): void {
this.primaryCursor.dispose(this.context);
this.killSecondaryCursors();
}
public updateContext(context: CursorContext): void {
this.context = context;
}
public ensureValidState(): void {
this.primaryCursor.ensureValidState(this.context);
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
this.secondaryCursors[i].ensureValidState(this.context);
}
}
public readSelectionFromMarkers(): Selection[] {
let result: Selection[] = [];
result[0] = this.primaryCursor.readSelectionFromMarkers(this.context);
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i].readSelectionFromMarkers(this.context);
}
return result;
}
public getAll(): CursorState[] {
let result: CursorState[] = [];
result[0] = this.primaryCursor.asCursorState();
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i].asCursorState();
}
return result;
}
public getViewPositions(): Position[] {
let result: Position[] = [];
result[0] = this.primaryCursor.viewState.position;
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i].viewState.position;
}
return result;
}
public getSelections(): Selection[] {
let result: Selection[] = [];
result[0] = this.primaryCursor.modelState.selection;
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i].modelState.selection;
}
return result;
}
public getViewSelections(): Selection[] {
let result: Selection[] = [];
result[0] = this.primaryCursor.viewState.selection;
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i].viewState.selection;
}
return result;
}
public setSelections(selections: ISelection[]): void {
this.setStates(CursorState.fromModelSelections(selections));
}
public getPrimaryCursor(): CursorState {
return this.primaryCursor.asCursorState();
}
public setStates(states: CursorState[]): void {
if (states === null) {
return;
}
this.primaryCursor.setState(this.context, states[0].modelState, states[0].viewState);
this._setSecondaryStates(states.slice(1));
}
/**
* Creates or disposes secondary cursors as necessary to match the number of `secondarySelections`.
*/
private _setSecondaryStates(secondaryStates: CursorState[]): void {
const secondaryCursorsLength = this.secondaryCursors.length;
const secondaryStatesLength = secondaryStates.length;
if (secondaryCursorsLength < secondaryStatesLength) {
let createCnt = secondaryStatesLength - secondaryCursorsLength;
for (let i = 0; i < createCnt; i++) {
this._addSecondaryCursor();
}
} else if (secondaryCursorsLength > secondaryStatesLength) {
let removeCnt = secondaryCursorsLength - secondaryStatesLength;
for (let i = 0; i < removeCnt; i++) {
this._removeSecondaryCursor(this.secondaryCursors.length - 1);
}
}
for (let i = 0; i < secondaryStatesLength; i++) {
this.secondaryCursors[i].setState(this.context, secondaryStates[i].modelState, secondaryStates[i].viewState);
}
}
public killSecondaryCursors(): void {
this._setSecondaryStates([]);
}
private _addSecondaryCursor(): void {
this.secondaryCursors.push(new OneCursor(this.context));
this.lastAddedCursorIndex = this.secondaryCursors.length;
}
public getLastAddedCursorIndex(): number {
if (this.secondaryCursors.length === 0 || this.lastAddedCursorIndex === 0) {
return 0;
}
return this.lastAddedCursorIndex;
}
private _removeSecondaryCursor(removeIndex: number): void {
if (this.lastAddedCursorIndex >= removeIndex + 1) {
this.lastAddedCursorIndex--;
}
this.secondaryCursors[removeIndex].dispose(this.context);
this.secondaryCursors.splice(removeIndex, 1);
}
private _getAll(): OneCursor[] {
let result: OneCursor[] = [];
result[0] = this.primaryCursor;
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
result[i + 1] = this.secondaryCursors[i];
}
return result;
}
public normalize(): void {
if (this.secondaryCursors.length === 0) {
return;
}
let cursors = this._getAll();
interface SortedCursor {
index: number;
selection: Selection;
viewSelection: Selection;
}
let sortedCursors: SortedCursor[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
sortedCursors.push({
index: i,
selection: cursors[i].modelState.selection,
viewSelection: cursors[i].viewState.selection
});
}
sortedCursors.sort((a, b) => {
if (a.viewSelection.startLineNumber === b.viewSelection.startLineNumber) {
return a.viewSelection.startColumn - b.viewSelection.startColumn;
}
return a.viewSelection.startLineNumber - b.viewSelection.startLineNumber;
});
for (let sortedCursorIndex = 0; sortedCursorIndex < sortedCursors.length - 1; sortedCursorIndex++) {
const current = sortedCursors[sortedCursorIndex];
const next = sortedCursors[sortedCursorIndex + 1];
const currentViewSelection = current.viewSelection;
const nextViewSelection = next.viewSelection;
let shouldMergeCursors: boolean;
if (nextViewSelection.isEmpty() || currentViewSelection.isEmpty()) {
// Merge touching cursors if one of them is collapsed
shouldMergeCursors = nextViewSelection.getStartPosition().isBeforeOrEqual(currentViewSelection.getEndPosition());
} else {
// Merge only overlapping cursors (i.e. allow touching ranges)
shouldMergeCursors = nextViewSelection.getStartPosition().isBefore(currentViewSelection.getEndPosition());
}
if (shouldMergeCursors) {
const winnerSortedCursorIndex = current.index < next.index ? sortedCursorIndex : sortedCursorIndex + 1;
const looserSortedCursorIndex = current.index < next.index ? sortedCursorIndex + 1 : sortedCursorIndex;
const looserIndex = sortedCursors[looserSortedCursorIndex].index;
const winnerIndex = sortedCursors[winnerSortedCursorIndex].index;
const looserSelection = sortedCursors[looserSortedCursorIndex].selection;
const winnerSelection = sortedCursors[winnerSortedCursorIndex].selection;
if (!looserSelection.equalsSelection(winnerSelection)) {
const resultingRange = looserSelection.plusRange(winnerSelection);
const looserSelectionIsLTR = (looserSelection.selectionStartLineNumber === looserSelection.startLineNumber && looserSelection.selectionStartColumn === looserSelection.startColumn);
const winnerSelectionIsLTR = (winnerSelection.selectionStartLineNumber === winnerSelection.startLineNumber && winnerSelection.selectionStartColumn === winnerSelection.startColumn);
// Give more importance to the last added cursor (think Ctrl-dragging + hitting another cursor)
let resultingSelectionIsLTR: boolean;
if (looserIndex === this.lastAddedCursorIndex) {
resultingSelectionIsLTR = looserSelectionIsLTR;
this.lastAddedCursorIndex = winnerIndex;
} else {
// Winner takes it all
resultingSelectionIsLTR = winnerSelectionIsLTR;
}
let resultingSelection: Selection;
if (resultingSelectionIsLTR) {
resultingSelection = new Selection(resultingRange.startLineNumber, resultingRange.startColumn, resultingRange.endLineNumber, resultingRange.endColumn);
} else {
resultingSelection = new Selection(resultingRange.endLineNumber, resultingRange.endColumn, resultingRange.startLineNumber, resultingRange.startColumn);
}
sortedCursors[winnerSortedCursorIndex].selection = resultingSelection;
const resultingState = CursorState.fromModelSelection(resultingSelection);
cursors[winnerIndex].setState(this.context, resultingState.modelState, resultingState.viewState);
}
for (let j = 0; j < sortedCursors.length; j++) {
if (sortedCursors[j].index > looserIndex) {
sortedCursors[j].index--;
}
}
cursors.splice(looserIndex, 1);
sortedCursors.splice(looserSortedCursorIndex, 1);
this._removeSecondaryCursor(looserIndex - 1);
sortedCursorIndex--;
}
}
}
}

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* 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 { Selection } from 'vs/editor/common/core/selection';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { SingleCursorState, CursorColumns, CursorConfiguration, ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon';
export interface IColumnSelectResult {
viewStates: SingleCursorState[];
reversed: boolean;
toLineNumber: number;
toVisualColumn: number;
}
export class ColumnSelection {
private static _columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromLineNumber: number, fromVisibleColumn: number, toLineNumber: number, toVisibleColumn: number): IColumnSelectResult {
let lineCount = Math.abs(toLineNumber - fromLineNumber) + 1;
let reversed = (fromLineNumber > toLineNumber);
let isRTL = (fromVisibleColumn > toVisibleColumn);
let isLTR = (fromVisibleColumn < toVisibleColumn);
let result: SingleCursorState[] = [];
// console.log(`fromVisibleColumn: ${fromVisibleColumn}, toVisibleColumn: ${toVisibleColumn}`);
for (let i = 0; i < lineCount; i++) {
let lineNumber = fromLineNumber + (reversed ? -i : i);
let startColumn = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, fromVisibleColumn);
let endColumn = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, toVisibleColumn);
let visibleStartColumn = CursorColumns.visibleColumnFromColumn2(config, model, new Position(lineNumber, startColumn));
let visibleEndColumn = CursorColumns.visibleColumnFromColumn2(config, model, new Position(lineNumber, endColumn));
// console.log(`lineNumber: ${lineNumber}: visibleStartColumn: ${visibleStartColumn}, visibleEndColumn: ${visibleEndColumn}`);
if (isLTR) {
if (visibleStartColumn > toVisibleColumn) {
continue;
}
if (visibleEndColumn < fromVisibleColumn) {
continue;
}
}
if (isRTL) {
if (visibleEndColumn > fromVisibleColumn) {
continue;
}
if (visibleStartColumn < toVisibleColumn) {
continue;
}
}
result.push(new SingleCursorState(
new Range(lineNumber, startColumn, lineNumber, startColumn), 0,
new Position(lineNumber, endColumn), 0
));
}
return {
viewStates: result,
reversed: reversed,
toLineNumber: toLineNumber,
toVisualColumn: toVisibleColumn
};
}
public static columnSelect(config: CursorConfiguration, model: ICursorSimpleModel, fromViewSelection: Selection, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
const fromViewPosition = new Position(fromViewSelection.selectionStartLineNumber, fromViewSelection.selectionStartColumn);
const fromViewVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, fromViewPosition);
return ColumnSelection._columnSelect(config, model, fromViewPosition.lineNumber, fromViewVisibleColumn, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectLeft(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
if (toViewVisualColumn > 1) {
toViewVisualColumn--;
}
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectRight(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
let maxVisualViewColumn = 0;
let minViewLineNumber = Math.min(cursor.position.lineNumber, toViewLineNumber);
let maxViewLineNumber = Math.max(cursor.position.lineNumber, toViewLineNumber);
for (let lineNumber = minViewLineNumber; lineNumber <= maxViewLineNumber; lineNumber++) {
let lineMaxViewColumn = model.getLineMaxColumn(lineNumber);
let lineMaxVisualViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, new Position(lineNumber, lineMaxViewColumn));
maxVisualViewColumn = Math.max(maxVisualViewColumn, lineMaxVisualViewColumn);
}
if (toViewVisualColumn < maxVisualViewColumn) {
toViewVisualColumn++;
}
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
let linesCount = isPaged ? config.pageSize : 1;
toViewLineNumber -= linesCount;
if (toViewLineNumber < 1) {
toViewLineNumber = 1;
}
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
public static columnSelectDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, isPaged: boolean, toViewLineNumber: number, toViewVisualColumn: number): IColumnSelectResult {
let linesCount = isPaged ? config.pageSize : 1;
toViewLineNumber += linesCount;
if (toViewLineNumber > model.getLineCount()) {
toViewLineNumber = model.getLineCount();
}
return this.columnSelect(config, model, cursor.selection, toViewLineNumber, toViewVisualColumn);
}
}

View File

@@ -0,0 +1,553 @@
/*---------------------------------------------------------------------------------------------
* 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 { Position } from 'vs/editor/common/core/position';
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { ICommand, TextModelResolvedOptions, IConfiguration, IModel, ScrollType } from 'vs/editor/common/editorCommon';
import { TextModel } from 'vs/editor/common/model/textModel';
import { Selection, ISelection } from 'vs/editor/common/core/selection';
import { Range } from 'vs/editor/common/core/range';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { onUnexpectedError } from 'vs/base/common/errors';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { IAutoClosingPair } from 'vs/editor/common/modes/languageConfiguration';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
export interface IColumnSelectData {
toViewLineNumber: number;
toViewVisualColumn: number;
}
export const enum RevealTarget {
Primary = 0,
TopMost = 1,
BottomMost = 2
}
export interface ICursors {
readonly context: CursorContext;
getPrimaryCursor(): CursorState;
getLastAddedCursorIndex(): number;
getAll(): CursorState[];
getColumnSelectData(): IColumnSelectData;
setColumnSelectData(columnSelectData: IColumnSelectData): void;
setStates(source: string, reason: CursorChangeReason, states: CursorState[]): void;
reveal(horizontal: boolean, target: RevealTarget, scrollType: ScrollType): void;
revealRange(revealHorizontal: boolean, viewRange: Range, verticalType: VerticalRevealType, scrollType: ScrollType): void;
scrollTo(desiredScrollTop: number): void;
}
export interface CharacterMap {
[char: string]: string;
}
export class CursorConfiguration {
_cursorMoveConfigurationBrand: void;
public readonly readOnly: boolean;
public readonly tabSize: number;
public readonly insertSpaces: boolean;
public readonly oneIndent: string;
public readonly pageSize: number;
public readonly lineHeight: number;
public readonly useTabStops: boolean;
public readonly wordSeparators: string;
public readonly emptySelectionClipboard: boolean;
public readonly autoClosingBrackets: boolean;
public readonly autoIndent: boolean;
public readonly autoClosingPairsOpen: CharacterMap;
public readonly autoClosingPairsClose: CharacterMap;
public readonly surroundingPairs: CharacterMap;
public readonly electricChars: { [key: string]: boolean; };
public static shouldRecreate(e: IConfigurationChangedEvent): boolean {
return (
e.layoutInfo
|| e.wordSeparators
|| e.emptySelectionClipboard
|| e.autoClosingBrackets
|| e.useTabStops
|| e.lineHeight
|| e.readOnly
);
}
constructor(
languageIdentifier: LanguageIdentifier,
oneIndent: string,
modelOptions: TextModelResolvedOptions,
configuration: IConfiguration
) {
let c = configuration.editor;
this.readOnly = c.readOnly;
this.tabSize = modelOptions.tabSize;
this.insertSpaces = modelOptions.insertSpaces;
this.oneIndent = oneIndent;
this.pageSize = Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2;
this.lineHeight = c.lineHeight;
this.useTabStops = c.useTabStops;
this.wordSeparators = c.wordSeparators;
this.emptySelectionClipboard = c.emptySelectionClipboard;
this.autoClosingBrackets = c.autoClosingBrackets;
this.autoIndent = c.autoIndent;
this.autoClosingPairsOpen = {};
this.autoClosingPairsClose = {};
this.surroundingPairs = {};
this.electricChars = {};
let electricChars = CursorConfiguration._getElectricCharacters(languageIdentifier);
if (electricChars) {
for (let i = 0; i < electricChars.length; i++) {
this.electricChars[electricChars[i]] = true;
}
}
let autoClosingPairs = CursorConfiguration._getAutoClosingPairs(languageIdentifier);
if (autoClosingPairs) {
for (let i = 0; i < autoClosingPairs.length; i++) {
this.autoClosingPairsOpen[autoClosingPairs[i].open] = autoClosingPairs[i].close;
this.autoClosingPairsClose[autoClosingPairs[i].close] = autoClosingPairs[i].open;
}
}
let surroundingPairs = CursorConfiguration._getSurroundingPairs(languageIdentifier);
if (surroundingPairs) {
for (let i = 0; i < surroundingPairs.length; i++) {
this.surroundingPairs[surroundingPairs[i].open] = surroundingPairs[i].close;
}
}
}
public normalizeIndentation(str: string): string {
return TextModel.normalizeIndentation(str, this.tabSize, this.insertSpaces);
}
private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] {
try {
return LanguageConfigurationRegistry.getElectricCharacters(languageIdentifier.id);
} catch (e) {
onUnexpectedError(e);
return null;
}
}
private static _getAutoClosingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] {
try {
return LanguageConfigurationRegistry.getAutoClosingPairs(languageIdentifier.id);
} catch (e) {
onUnexpectedError(e);
return null;
}
}
private static _getSurroundingPairs(languageIdentifier: LanguageIdentifier): IAutoClosingPair[] {
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: IModel;
public readonly viewModel: IViewModel;
public readonly config: CursorConfiguration;
constructor(configuration: IConfiguration, model: IModel, viewModel: IViewModel) {
this.model = model;
this.viewModel = viewModel;
this.config = new CursorConfiguration(
this.model.getLanguageIdentifier(),
this.model.getOneIndent(),
this.model.getOptions(),
configuration
);
}
public validateViewPosition(viewPosition: Position, modelPosition: Position): Position {
return this.viewModel.coordinatesConverter.validateViewPosition(viewPosition, modelPosition);
}
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
return this.viewModel.coordinatesConverter.validateViewRange(viewRange, expectedModelRange);
}
public convertViewRangeToModelRange(viewRange: Range): Range {
return this.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange);
}
public convertViewPositionToModelPosition(lineNumber: number, column: number): Position {
return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, column));
}
public convertModelPositionToViewPosition(modelPosition: Position): Position {
return this.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
}
public convertModelRangeToViewRange(modelRange: Range): Range {
return this.viewModel.coordinatesConverter.convertModelRangeToViewRange(modelRange);
}
public getCurrentScrollTop(): number {
return this.viewModel.viewLayout.getCurrentScrollTop();
}
public getCompletelyVisibleViewRange(): Range {
return this.viewModel.getCompletelyVisibleViewRange();
}
public getCompletelyVisibleModelRange(): Range {
const viewRange = this.viewModel.getCompletelyVisibleViewRange();
return this.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange);
}
public getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range {
return this.viewModel.getCompletelyVisibleViewRangeAtScrollTop(scrollTop);
}
public getCompletelyVisibleModelRangeAtScrollTop(scrollTop: number): Range {
const viewRange = this.viewModel.getCompletelyVisibleViewRangeAtScrollTop(scrollTop);
return this.viewModel.coordinatesConverter.convertViewRangeToModelRange(viewRange);
}
public getVerticalOffsetForViewLine(viewLineNumber: number): number {
return this.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewLineNumber);
}
}
export class CursorState {
_cursorStateBrand: void;
public static fromModelState(modelState: SingleCursorState): CursorState {
return new CursorState(modelState, null);
}
public static fromViewState(viewState: SingleCursorState): CursorState {
return new CursorState(null, viewState);
}
public static fromModelSelection(modelSelection: ISelection): CursorState {
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: ISelection[]): CursorState[] {
let states: CursorState[] = [];
for (let i = 0, len = modelSelections.length; i < len; i++) {
states[i] = this.fromModelSelection(modelSelections[i]);
}
return states;
}
public static ensureInEditableRange(context: CursorContext, states: CursorState[]): CursorState[] {
const model = context.model;
if (!model.hasEditableRange()) {
return states;
}
const modelEditableRange = model.getEditableRange();
const viewEditableRange = context.convertModelRangeToViewRange(modelEditableRange);
let result: CursorState[] = [];
for (let i = 0, len = states.length; i < len; i++) {
const state = states[i];
if (state.modelState) {
const newModelState = CursorState._ensureInEditableRange(state.modelState, modelEditableRange);
result[i] = newModelState ? CursorState.fromModelState(newModelState) : state;
} else {
const newViewState = CursorState._ensureInEditableRange(state.viewState, viewEditableRange);
result[i] = newViewState ? CursorState.fromViewState(newViewState) : state;
}
}
return result;
}
private static _ensureInEditableRange(state: SingleCursorState, editableRange: Range): SingleCursorState {
const position = state.position;
if (position.lineNumber < editableRange.startLineNumber || (position.lineNumber === editableRange.startLineNumber && position.column < editableRange.startColumn)) {
return new SingleCursorState(
state.selectionStart, state.selectionStartLeftoverVisibleColumns,
new Position(editableRange.startLineNumber, editableRange.startColumn), 0
);
}
if (position.lineNumber > editableRange.endLineNumber || (position.lineNumber === editableRange.endLineNumber && position.column > editableRange.endColumn)) {
return new SingleCursorState(
state.selectionStart, state.selectionStartLeftoverVisibleColumns,
new Position(editableRange.endLineNumber, editableRange.endColumn), 0
);
}
return null;
}
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 commands: ICommand[];
readonly shouldPushStackElementBefore: boolean;
readonly shouldPushStackElementAfter: boolean;
constructor(
commands: ICommand[],
opts: {
shouldPushStackElementBefore: boolean;
shouldPushStackElementAfter: boolean;
}
) {
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 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);
}
public static visibleColumnFromColumn(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.nextTabStop(result, tabSize);
} else {
result = result + 1;
}
}
return result;
}
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;
for (let i = 0; i < lineLength; i++) {
let charCode = lineContent.charCodeAt(i);
let afterVisibleColumn: number;
if (charCode === CharCode.Tab) {
afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize);
} 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 {
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 nextTabStop(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 prevTabStop(column: number, tabSize: number): number {
return column - 1 - (column - 1) % tabSize;
}
}

View File

@@ -0,0 +1,218 @@
/*---------------------------------------------------------------------------------------------
* 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 { ReplaceCommand } from 'vs/editor/common/commands/replaceCommand';
import { CursorColumns, CursorConfiguration, ICursorSimpleModel, EditOperationResult } from 'vs/editor/common/controller/cursorCommon';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { MoveOperations } from 'vs/editor/common/controller/cursorMoveOperations';
import * as strings from 'vs/base/common/strings';
import { ICommand } from 'vs/editor/common/editorCommon';
export class DeleteOperations {
public static deleteRight(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, ICommand[]] {
let commands: ICommand[] = [];
let shouldPushStackElementBefore = false;
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
let deleteSelection: Range = selection;
if (deleteSelection.isEmpty()) {
let position = selection.getPosition();
let rightOfPosition = MoveOperations.right(config, model, position.lineNumber, position.column);
deleteSelection = new Range(
rightOfPosition.lineNumber,
rightOfPosition.column,
position.lineNumber,
position.column
);
}
if (deleteSelection.isEmpty()) {
// Probably at end of file => ignore
commands[i] = null;
continue;
}
if (deleteSelection.startLineNumber !== deleteSelection.endLineNumber) {
shouldPushStackElementBefore = true;
}
commands[i] = new ReplaceCommand(deleteSelection, '');
}
return [shouldPushStackElementBefore, commands];
}
private static _isAutoClosingPairDelete(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): boolean {
if (!config.autoClosingBrackets) {
return false;
}
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
const position = selection.getPosition();
if (!selection.isEmpty()) {
return false;
}
const lineText = model.getLineContent(position.lineNumber);
const character = lineText[position.column - 2];
if (!config.autoClosingPairsOpen.hasOwnProperty(character)) {
return false;
}
const afterCharacter = lineText[position.column - 1];
const closeCharacter = config.autoClosingPairsOpen[character];
if (afterCharacter !== closeCharacter) {
return false;
}
}
return true;
}
private static _runAutoClosingPairDelete(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, ICommand[]] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const position = selections[i].getPosition();
const deleteSelection = new Range(
position.lineNumber,
position.column - 1,
position.lineNumber,
position.column + 1
);
commands[i] = new ReplaceCommand(deleteSelection, '');
}
return [true, commands];
}
public static deleteLeft(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): [boolean, ICommand[]] {
if (this._isAutoClosingPairDelete(config, model, selections)) {
return this._runAutoClosingPairDelete(config, model, selections);
}
let commands: ICommand[] = [];
let shouldPushStackElementBefore = false;
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
let deleteSelection: Range = selection;
if (deleteSelection.isEmpty()) {
let position = selection.getPosition();
if (config.useTabStops && position.column > 1) {
let lineContent = model.getLineContent(position.lineNumber);
let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
let lastIndentationColumn = (
firstNonWhitespaceIndex === -1
? /* entire string is whitespace */lineContent.length + 1
: firstNonWhitespaceIndex + 1
);
if (position.column <= lastIndentationColumn) {
let fromVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, position);
let toVisibleColumn = CursorColumns.prevTabStop(fromVisibleColumn, config.tabSize);
let toColumn = CursorColumns.columnFromVisibleColumn2(config, model, position.lineNumber, toVisibleColumn);
deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column);
} else {
deleteSelection = new Range(position.lineNumber, position.column - 1, position.lineNumber, position.column);
}
} else {
let leftOfPosition = MoveOperations.left(config, model, position.lineNumber, position.column);
deleteSelection = new Range(
leftOfPosition.lineNumber,
leftOfPosition.column,
position.lineNumber,
position.column
);
}
}
if (deleteSelection.isEmpty()) {
// Probably at beginning of file => ignore
commands[i] = null;
continue;
}
if (deleteSelection.startLineNumber !== deleteSelection.endLineNumber) {
shouldPushStackElementBefore = true;
}
commands[i] = new ReplaceCommand(deleteSelection, '');
}
return [shouldPushStackElementBefore, commands];
}
public static cut(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (selection.isEmpty()) {
if (config.emptySelectionClipboard) {
// This is a full line cut
let position = selection.getPosition();
let startLineNumber: number,
startColumn: number,
endLineNumber: number,
endColumn: number;
if (position.lineNumber < model.getLineCount()) {
// Cutting a line in the middle of the model
startLineNumber = position.lineNumber;
startColumn = 1;
endLineNumber = position.lineNumber + 1;
endColumn = 1;
} else if (position.lineNumber > 1) {
// Cutting the last line & there are more than 1 lines in the model
startLineNumber = position.lineNumber - 1;
startColumn = model.getLineMaxColumn(position.lineNumber - 1);
endLineNumber = position.lineNumber;
endColumn = model.getLineMaxColumn(position.lineNumber);
} else {
// Cutting the single line that the model contains
startLineNumber = position.lineNumber;
startColumn = 1;
endLineNumber = position.lineNumber;
endColumn = model.getLineMaxColumn(position.lineNumber);
}
let deleteSelection = new Range(
startLineNumber,
startColumn,
endLineNumber,
endColumn
);
if (!deleteSelection.isEmpty()) {
commands[i] = new ReplaceCommand(deleteSelection, '');
} else {
commands[i] = null;
}
} else {
// Cannot cut empty selection
commands[i] = null;
}
} else {
commands[i] = new ReplaceCommand(selection, '');
}
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: true
});
}
}

View File

@@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* 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 { Position } from 'vs/editor/common/core/position';
import { Selection } from 'vs/editor/common/core/selection';
/**
* Describes the reason the cursor has changed its position.
*/
export enum CursorChangeReason {
/**
* Unknown or not set.
*/
NotSet = 0,
/**
* A `model.setValue()` was called.
*/
ContentFlush = 1,
/**
* The `model` has been changed outside of this cursor and the cursor recovers its position from associated markers.
*/
RecoverFromMarkers = 2,
/**
* There was an explicit user gesture.
*/
Explicit = 3,
/**
* There was a Paste.
*/
Paste = 4,
/**
* There was an Undo.
*/
Undo = 5,
/**
* There was a Redo.
*/
Redo = 6,
}
/**
* An event describing that the cursor position has changed.
*/
export interface ICursorPositionChangedEvent {
/**
* Primary cursor's position.
*/
readonly position: Position;
/**
* Secondary cursors' position.
*/
readonly secondaryPositions: Position[];
/**
* Reason.
*/
readonly reason: CursorChangeReason;
/**
* Source of the call that caused the event.
*/
readonly source: string;
}
/**
* An event describing that the cursor selection has changed.
*/
export interface ICursorSelectionChangedEvent {
/**
* The primary selection.
*/
readonly selection: Selection;
/**
* The secondary selections.
*/
readonly secondarySelections: Selection[];
/**
* Source of the call that caused the event.
*/
readonly source: string;
/**
* Reason.
*/
readonly reason: CursorChangeReason;
}

View File

@@ -0,0 +1,762 @@
/*---------------------------------------------------------------------------------------------
* 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 { SingleCursorState, ICursorSimpleModel, CursorState, CursorContext } from 'vs/editor/common/controller/cursorCommon';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { MoveOperations } from 'vs/editor/common/controller/cursorMoveOperations';
import { WordOperations } from 'vs/editor/common/controller/cursorWordOperations';
import * as types from 'vs/base/common/types';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
export class CursorMoveCommands {
public static addCursorDown(context: CursorContext, cursors: CursorState[]): CursorState[] {
let result: CursorState[] = [], resultLen = 0;
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[resultLen++] = new CursorState(cursor.modelState, cursor.viewState);
result[resultLen++] = CursorState.fromViewState(MoveOperations.translateDown(context.config, context.viewModel, cursor.viewState));
}
return result;
}
public static addCursorUp(context: CursorContext, cursors: CursorState[]): CursorState[] {
let result: CursorState[] = [], resultLen = 0;
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[resultLen++] = new CursorState(cursor.modelState, cursor.viewState);
result[resultLen++] = CursorState.fromViewState(MoveOperations.translateUp(context.config, context.viewModel, cursor.viewState));
}
return result;
}
public static moveToBeginningOfLine(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = this._moveToLineStart(context, cursor, inSelectionMode);
}
return result;
}
private static _moveToLineStart(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
const currentViewStateColumn = cursor.viewState.position.column;
const currentModelStateColumn = cursor.modelState.position.column;
const isFirstLineOfWrappedLine = currentViewStateColumn === currentModelStateColumn;
const currentViewStatelineNumber = cursor.viewState.position.lineNumber;
const firstNonBlankColumn = context.viewModel.getLineFirstNonWhitespaceColumn(currentViewStatelineNumber);
const isBeginningOfViewLine = currentViewStateColumn === firstNonBlankColumn;
if (!isFirstLineOfWrappedLine && !isBeginningOfViewLine) {
return this._moveToLineStartByView(context, cursor, inSelectionMode);
} else {
return this._moveToLineStartByModel(context, cursor, inSelectionMode);
}
}
private static _moveToLineStartByView(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
return CursorState.fromViewState(
MoveOperations.moveToBeginningOfLine(context.config, context.viewModel, cursor.viewState, inSelectionMode)
);
}
private static _moveToLineStartByModel(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
return CursorState.fromModelState(
MoveOperations.moveToBeginningOfLine(context.config, context.model, cursor.modelState, inSelectionMode)
);
}
public static moveToEndOfLine(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = this._moveToLineEnd(context, cursor, inSelectionMode);
}
return result;
}
private static _moveToLineEnd(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
const viewStatePosition = cursor.viewState.position;
const viewModelMaxColumn = context.viewModel.getLineMaxColumn(viewStatePosition.lineNumber);
const isEndOfViewLine = viewStatePosition.column === viewModelMaxColumn;
const modelStatePosition = cursor.modelState.position;
const modelMaxColumn = context.model.getLineMaxColumn(modelStatePosition.lineNumber);
const isEndLineOfWrappedLine = viewModelMaxColumn - viewStatePosition.column === modelMaxColumn - modelStatePosition.column;
if (isEndOfViewLine || isEndLineOfWrappedLine) {
return this._moveToLineEndByModel(context, cursor, inSelectionMode);
} else {
return this._moveToLineEndByView(context, cursor, inSelectionMode);
}
}
private static _moveToLineEndByView(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
return CursorState.fromViewState(
MoveOperations.moveToEndOfLine(context.config, context.viewModel, cursor.viewState, inSelectionMode)
);
}
private static _moveToLineEndByModel(context: CursorContext, cursor: CursorState, inSelectionMode: boolean): CursorState {
return CursorState.fromModelState(
MoveOperations.moveToEndOfLine(context.config, context.model, cursor.modelState, inSelectionMode)
);
}
public static expandLineSelection(context: CursorContext, cursors: CursorState[]): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewSelection = cursor.viewState.selection;
const startLineNumber = viewSelection.startLineNumber;
const lineCount = context.viewModel.getLineCount();
let endLineNumber = viewSelection.endLineNumber;
let endColumn: number;
if (endLineNumber === lineCount) {
endColumn = context.viewModel.getLineMaxColumn(lineCount);
} else {
endLineNumber++;
endColumn = 1;
}
result[i] = CursorState.fromViewState(new SingleCursorState(
new Range(startLineNumber, 1, startLineNumber, 1), 0,
new Position(endLineNumber, endColumn), 0
));
}
return result;
}
public static moveToBeginningOfBuffer(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromModelState(MoveOperations.moveToBeginningOfBuffer(context.config, context.model, cursor.modelState, inSelectionMode));
}
return result;
}
public static moveToEndOfBuffer(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromModelState(MoveOperations.moveToEndOfBuffer(context.config, context.model, cursor.modelState, inSelectionMode));
}
return result;
}
public static selectAll(context: CursorContext, cursor: CursorState): CursorState {
if (context.model.hasEditableRange()) {
// Toggle between selecting editable range and selecting the entire buffer
const editableRange = context.model.getEditableRange();
const selection = cursor.modelState.selection;
if (!selection.equalsRange(editableRange)) {
// Selection is not editable range => select editable range
return CursorState.fromModelState(new SingleCursorState(
new Range(editableRange.startLineNumber, editableRange.startColumn, editableRange.startLineNumber, editableRange.startColumn), 0,
new Position(editableRange.endLineNumber, editableRange.endColumn), 0
));
}
}
const lineCount = context.model.getLineCount();
const maxColumn = context.model.getLineMaxColumn(lineCount);
return CursorState.fromModelState(new SingleCursorState(
new Range(1, 1, 1, 1), 0,
new Position(lineCount, maxColumn), 0
));
}
public static line(context: CursorContext, cursor: CursorState, inSelectionMode: boolean, _position: IPosition, _viewPosition: IPosition): CursorState {
const position = context.model.validatePosition(_position);
const viewPosition = (
_viewPosition
? context.validateViewPosition(new Position(_viewPosition.lineNumber, _viewPosition.column), position)
: context.convertModelPositionToViewPosition(position)
);
if (!inSelectionMode || !cursor.modelState.hasSelection()) {
// Entering line selection for the first time
const lineCount = context.model.getLineCount();
let selectToLineNumber = position.lineNumber + 1;
let selectToColumn = 1;
if (selectToLineNumber > lineCount) {
selectToLineNumber = lineCount;
selectToColumn = context.model.getLineMaxColumn(selectToLineNumber);
}
return CursorState.fromModelState(new SingleCursorState(
new Range(position.lineNumber, 1, selectToLineNumber, selectToColumn), 0,
new Position(selectToLineNumber, selectToColumn), 0
));
}
// Continuing line selection
const enteringLineNumber = cursor.modelState.selectionStart.getStartPosition().lineNumber;
if (position.lineNumber < enteringLineNumber) {
return CursorState.fromViewState(cursor.viewState.move(
cursor.modelState.hasSelection(), viewPosition.lineNumber, 1, 0
));
} else if (position.lineNumber > enteringLineNumber) {
const lineCount = context.viewModel.getLineCount();
let selectToViewLineNumber = viewPosition.lineNumber + 1;
let selectToViewColumn = 1;
if (selectToViewLineNumber > lineCount) {
selectToViewLineNumber = lineCount;
selectToViewColumn = context.viewModel.getLineMaxColumn(selectToViewLineNumber);
}
return CursorState.fromViewState(cursor.viewState.move(
cursor.modelState.hasSelection(), selectToViewLineNumber, selectToViewColumn, 0
));
} else {
const endPositionOfSelectionStart = cursor.modelState.selectionStart.getEndPosition();
return CursorState.fromModelState(cursor.modelState.move(
cursor.modelState.hasSelection(), endPositionOfSelectionStart.lineNumber, endPositionOfSelectionStart.column, 0
));
}
}
public static word(context: CursorContext, cursor: CursorState, inSelectionMode: boolean, _position: IPosition): CursorState {
const position = context.model.validatePosition(_position);
return CursorState.fromModelState(WordOperations.word(context.config, context.model, cursor.modelState, inSelectionMode, position));
}
public static cancelSelection(context: CursorContext, cursor: CursorState): CursorState {
if (!cursor.modelState.hasSelection()) {
return new CursorState(cursor.modelState, cursor.viewState);
}
const lineNumber = cursor.viewState.position.lineNumber;
const column = cursor.viewState.position.column;
return CursorState.fromViewState(new SingleCursorState(
new Range(lineNumber, column, lineNumber, column), 0,
new Position(lineNumber, column), 0
));
}
public static moveTo(context: CursorContext, cursor: CursorState, inSelectionMode: boolean, _position: IPosition, _viewPosition: IPosition): CursorState {
const position = context.model.validatePosition(_position);
const viewPosition = (
_viewPosition
? context.validateViewPosition(new Position(_viewPosition.lineNumber, _viewPosition.column), position)
: context.convertModelPositionToViewPosition(position)
);
return CursorState.fromViewState(cursor.viewState.move(inSelectionMode, viewPosition.lineNumber, viewPosition.column, 0));
}
public static move(context: CursorContext, cursors: CursorState[], args: CursorMove.ParsedArguments): CursorState[] {
const inSelectionMode = args.select;
const value = args.value;
switch (args.direction) {
case CursorMove.Direction.Left: {
if (args.unit === CursorMove.Unit.HalfLine) {
// Move left by half the current line length
return this._moveHalfLineLeft(context, cursors, inSelectionMode);
} else {
// Move left by `moveParams.value` columns
return this._moveLeft(context, cursors, inSelectionMode, value);
}
}
case CursorMove.Direction.Right: {
if (args.unit === CursorMove.Unit.HalfLine) {
// Move right by half the current line length
return this._moveHalfLineRight(context, cursors, inSelectionMode);
} else {
// Move right by `moveParams.value` columns
return this._moveRight(context, cursors, inSelectionMode, value);
}
}
case CursorMove.Direction.Up: {
if (args.unit === CursorMove.Unit.WrappedLine) {
// Move up by view lines
return this._moveUpByViewLines(context, cursors, inSelectionMode, value);
} else {
// Move up by model lines
return this._moveUpByModelLines(context, cursors, inSelectionMode, value);
}
}
case CursorMove.Direction.Down: {
if (args.unit === CursorMove.Unit.WrappedLine) {
// Move down by view lines
return this._moveDownByViewLines(context, cursors, inSelectionMode, value);
} else {
// Move down by model lines
return this._moveDownByModelLines(context, cursors, inSelectionMode, value);
}
}
case CursorMove.Direction.WrappedLineStart: {
// Move to the beginning of the current view line
return this._moveToViewMinColumn(context, cursors, inSelectionMode);
}
case CursorMove.Direction.WrappedLineFirstNonWhitespaceCharacter: {
// Move to the first non-whitespace column of the current view line
return this._moveToViewFirstNonWhitespaceColumn(context, cursors, inSelectionMode);
}
case CursorMove.Direction.WrappedLineColumnCenter: {
// Move to the "center" of the current view line
return this._moveToViewCenterColumn(context, cursors, inSelectionMode);
}
case CursorMove.Direction.WrappedLineEnd: {
// Move to the end of the current view line
return this._moveToViewMaxColumn(context, cursors, inSelectionMode);
}
case CursorMove.Direction.WrappedLineLastNonWhitespaceCharacter: {
// Move to the last non-whitespace column of the current view line
return this._moveToViewLastNonWhitespaceColumn(context, cursors, inSelectionMode);
}
case CursorMove.Direction.ViewPortTop: {
// Move to the nth line start in the viewport (from the top)
const cursor = cursors[0];
const visibleModelRange = context.getCompletelyVisibleModelRange();
const modelLineNumber = this._firstLineNumberInRange(context.model, visibleModelRange, value);
const modelColumn = context.model.getLineFirstNonWhitespaceColumn(modelLineNumber);
return [this._moveToModelPosition(context, cursor, inSelectionMode, modelLineNumber, modelColumn)];
}
case CursorMove.Direction.ViewPortBottom: {
// Move to the nth line start in the viewport (from the bottom)
const cursor = cursors[0];
const visibleModelRange = context.getCompletelyVisibleModelRange();
const modelLineNumber = this._lastLineNumberInRange(context.model, visibleModelRange, value);
const modelColumn = context.model.getLineFirstNonWhitespaceColumn(modelLineNumber);
return [this._moveToModelPosition(context, cursor, inSelectionMode, modelLineNumber, modelColumn)];
}
case CursorMove.Direction.ViewPortCenter: {
// Move to the line start in the viewport center
const cursor = cursors[0];
const visibleModelRange = context.getCompletelyVisibleModelRange();
const modelLineNumber = Math.round((visibleModelRange.startLineNumber + visibleModelRange.endLineNumber) / 2);
const modelColumn = context.model.getLineFirstNonWhitespaceColumn(modelLineNumber);
return [this._moveToModelPosition(context, cursor, inSelectionMode, modelLineNumber, modelColumn)];
}
case CursorMove.Direction.ViewPortIfOutside: {
// Move to a position inside the viewport
const visibleViewRange = context.getCompletelyVisibleViewRange();
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = this.findPositionInViewportIfOutside(context, cursor, visibleViewRange, inSelectionMode);
}
return result;
}
}
return null;
}
public static findPositionInViewportIfOutside(context: CursorContext, cursor: CursorState, visibleViewRange: Range, inSelectionMode: boolean): CursorState {
let viewLineNumber = cursor.viewState.position.lineNumber;
if (visibleViewRange.startLineNumber <= viewLineNumber && viewLineNumber <= visibleViewRange.endLineNumber - 1) {
// Nothing to do, cursor is in viewport
return new CursorState(cursor.modelState, cursor.viewState);
} else {
if (viewLineNumber > visibleViewRange.endLineNumber - 1) {
viewLineNumber = visibleViewRange.endLineNumber - 1;
}
if (viewLineNumber < visibleViewRange.startLineNumber) {
viewLineNumber = visibleViewRange.startLineNumber;
}
const viewColumn = context.viewModel.getLineFirstNonWhitespaceColumn(viewLineNumber);
return this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
}
/**
* Find the nth line start included in the range (from the start).
*/
private static _firstLineNumberInRange(model: ICursorSimpleModel, range: Range, count: number): number {
let startLineNumber = range.startLineNumber;
if (range.startColumn !== model.getLineMinColumn(startLineNumber)) {
// Move on to the second line if the first line start is not included in the range
startLineNumber++;
}
return Math.min(range.endLineNumber, startLineNumber + count - 1);
}
/**
* Find the nth line start included in the range (from the end).
*/
private static _lastLineNumberInRange(model: ICursorSimpleModel, range: Range, count: number): number {
let startLineNumber = range.startLineNumber;
if (range.startColumn !== model.getLineMinColumn(startLineNumber)) {
// Move on to the second line if the first line start is not included in the range
startLineNumber++;
}
return Math.max(startLineNumber, range.endLineNumber - count + 1);
}
private static _moveLeft(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromViewState(MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns));
}
return result;
}
private static _moveHalfLineLeft(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const halfLine = Math.round(context.viewModel.getLineContent(viewLineNumber).length / 2);
result[i] = CursorState.fromViewState(MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, halfLine));
}
return result;
}
private static _moveRight(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, noOfColumns: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromViewState(MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns));
}
return result;
}
private static _moveHalfLineRight(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const halfLine = Math.round(context.viewModel.getLineContent(viewLineNumber).length / 2);
result[i] = CursorState.fromViewState(MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, halfLine));
}
return result;
}
private static _moveDownByViewLines(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, linesCount: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromViewState(MoveOperations.moveDown(context.config, context.viewModel, cursor.viewState, inSelectionMode, linesCount));
}
return result;
}
private static _moveDownByModelLines(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, linesCount: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromModelState(MoveOperations.moveDown(context.config, context.model, cursor.modelState, inSelectionMode, linesCount));
}
return result;
}
private static _moveUpByViewLines(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, linesCount: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromViewState(MoveOperations.moveUp(context.config, context.viewModel, cursor.viewState, inSelectionMode, linesCount));
}
return result;
}
private static _moveUpByModelLines(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean, linesCount: number): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
result[i] = CursorState.fromModelState(MoveOperations.moveUp(context.config, context.model, cursor.modelState, inSelectionMode, linesCount));
}
return result;
}
private static _moveToViewPosition(context: CursorContext, cursor: CursorState, inSelectionMode: boolean, toViewLineNumber: number, toViewColumn: number): CursorState {
return CursorState.fromViewState(cursor.viewState.move(inSelectionMode, toViewLineNumber, toViewColumn, 0));
}
private static _moveToModelPosition(context: CursorContext, cursor: CursorState, inSelectionMode: boolean, toModelLineNumber: number, toModelColumn: number): CursorState {
return CursorState.fromModelState(cursor.modelState.move(inSelectionMode, toModelLineNumber, toModelColumn, 0));
}
private static _moveToViewMinColumn(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const viewColumn = context.viewModel.getLineMinColumn(viewLineNumber);
result[i] = this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
return result;
}
private static _moveToViewFirstNonWhitespaceColumn(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const viewColumn = context.viewModel.getLineFirstNonWhitespaceColumn(viewLineNumber);
result[i] = this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
return result;
}
private static _moveToViewCenterColumn(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const viewColumn = Math.round((context.viewModel.getLineMaxColumn(viewLineNumber) + context.viewModel.getLineMinColumn(viewLineNumber)) / 2);
result[i] = this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
return result;
}
private static _moveToViewMaxColumn(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const viewColumn = context.viewModel.getLineMaxColumn(viewLineNumber);
result[i] = this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
return result;
}
private static _moveToViewLastNonWhitespaceColumn(context: CursorContext, cursors: CursorState[], inSelectionMode: boolean): CursorState[] {
let result: CursorState[] = [];
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewLineNumber = cursor.viewState.position.lineNumber;
const viewColumn = context.viewModel.getLineLastNonWhitespaceColumn(viewLineNumber);
result[i] = this._moveToViewPosition(context, cursor, inSelectionMode, viewLineNumber, viewColumn);
}
return result;
}
}
export namespace CursorMove {
const isCursorMoveArgs = function (arg: any): boolean {
if (!types.isObject(arg)) {
return false;
}
let cursorMoveArg: RawArguments = arg;
if (!types.isString(cursorMoveArg.to)) {
return false;
}
if (!types.isUndefined(cursorMoveArg.select) && !types.isBoolean(cursorMoveArg.select)) {
return false;
}
if (!types.isUndefined(cursorMoveArg.by) && !types.isString(cursorMoveArg.by)) {
return false;
}
if (!types.isUndefined(cursorMoveArg.value) && !types.isNumber(cursorMoveArg.value)) {
return false;
}
return true;
};
export const description = <ICommandHandlerDescription>{
description: 'Move cursor to a logical position in the view',
args: [
{
name: 'Cursor move argument object',
description: `Property-value pairs that can be passed through this argument:
* 'to': A mandatory logical position value providing where to move the cursor.
\`\`\`
'left', 'right', 'up', 'down'
'wrappedLineStart', 'wrappedLineEnd', 'wrappedLineColumnCenter'
'wrappedLineFirstNonWhitespaceCharacter', 'wrappedLineLastNonWhitespaceCharacter',
'viewPortTop', 'viewPortCenter', 'viewPortBottom', 'viewPortIfOutside'
\`\`\`
* 'by': Unit to move. Default is computed based on 'to' value.
\`\`\`
'line', 'wrappedLine', 'character', 'halfLine'
\`\`\`
* 'value': Number of units to move. Default is '1'.
* 'select': If 'true' makes the selection. Default is 'false'.
`,
constraint: isCursorMoveArgs
}
]
};
/**
* Positions in the view for cursor move command.
*/
export const RawDirection = {
Left: 'left',
Right: 'right',
Up: 'up',
Down: 'down',
WrappedLineStart: 'wrappedLineStart',
WrappedLineFirstNonWhitespaceCharacter: 'wrappedLineFirstNonWhitespaceCharacter',
WrappedLineColumnCenter: 'wrappedLineColumnCenter',
WrappedLineEnd: 'wrappedLineEnd',
WrappedLineLastNonWhitespaceCharacter: 'wrappedLineLastNonWhitespaceCharacter',
ViewPortTop: 'viewPortTop',
ViewPortCenter: 'viewPortCenter',
ViewPortBottom: 'viewPortBottom',
ViewPortIfOutside: 'viewPortIfOutside'
};
/**
* Units for Cursor move 'by' argument
*/
export const RawUnit = {
Line: 'line',
WrappedLine: 'wrappedLine',
Character: 'character',
HalfLine: 'halfLine'
};
/**
* Arguments for Cursor move command
*/
export interface RawArguments {
to: string;
select?: boolean;
by?: string;
value?: number;
};
export function parse(args: RawArguments): ParsedArguments {
if (!args.to) {
// illegal arguments
return null;
}
let direction: Direction;
switch (args.to) {
case RawDirection.Left:
direction = Direction.Left;
break;
case RawDirection.Right:
direction = Direction.Right;
break;
case RawDirection.Up:
direction = Direction.Up;
break;
case RawDirection.Down:
direction = Direction.Down;
break;
case RawDirection.WrappedLineStart:
direction = Direction.WrappedLineStart;
break;
case RawDirection.WrappedLineFirstNonWhitespaceCharacter:
direction = Direction.WrappedLineFirstNonWhitespaceCharacter;
break;
case RawDirection.WrappedLineColumnCenter:
direction = Direction.WrappedLineColumnCenter;
break;
case RawDirection.WrappedLineEnd:
direction = Direction.WrappedLineEnd;
break;
case RawDirection.WrappedLineLastNonWhitespaceCharacter:
direction = Direction.WrappedLineLastNonWhitespaceCharacter;
break;
case RawDirection.ViewPortTop:
direction = Direction.ViewPortTop;
break;
case RawDirection.ViewPortBottom:
direction = Direction.ViewPortBottom;
break;
case RawDirection.ViewPortCenter:
direction = Direction.ViewPortCenter;
break;
case RawDirection.ViewPortIfOutside:
direction = Direction.ViewPortIfOutside;
break;
default:
// illegal arguments
return null;
}
let unit = Unit.None;
switch (args.by) {
case RawUnit.Line:
unit = Unit.Line;
break;
case RawUnit.WrappedLine:
unit = Unit.WrappedLine;
break;
case RawUnit.Character:
unit = Unit.Character;
break;
case RawUnit.HalfLine:
unit = Unit.HalfLine;
break;
}
return {
direction: direction,
unit: unit,
select: (!!args.select),
value: (args.value || 1)
};
}
export interface ParsedArguments {
direction: Direction;
unit: Unit;
select: boolean;
value: number;
};
export const enum Direction {
Left,
Right,
Up,
Down,
WrappedLineStart,
WrappedLineFirstNonWhitespaceCharacter,
WrappedLineColumnCenter,
WrappedLineEnd,
WrappedLineLastNonWhitespaceCharacter,
ViewPortTop,
ViewPortCenter,
ViewPortBottom,
ViewPortIfOutside,
};
export const enum Unit {
None,
Line,
WrappedLine,
Character,
HalfLine,
};
}

View File

@@ -0,0 +1,246 @@
/*---------------------------------------------------------------------------------------------
* 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 { SingleCursorState, CursorColumns, CursorConfiguration, ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
export class CursorPosition {
_cursorPositionBrand: void;
public readonly lineNumber: number;
public readonly column: number;
public readonly leftoverVisibleColumns: number;
constructor(lineNumber: number, column: number, leftoverVisibleColumns: number) {
this.lineNumber = lineNumber;
this.column = column;
this.leftoverVisibleColumns = leftoverVisibleColumns;
}
}
export class MoveOperations {
public static left(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
if (column > model.getLineMinColumn(lineNumber)) {
if (CursorColumns.isLowSurrogate(model, lineNumber, column - 2)) {
// character before column is a low surrogate
column = column - 2;
} else {
column = column - 1;
}
} else if (lineNumber > 1) {
lineNumber = lineNumber - 1;
column = model.getLineMaxColumn(lineNumber);
}
return new CursorPosition(lineNumber, column, 0);
}
public static moveLeft(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, noOfColumns: number): SingleCursorState {
let lineNumber: number,
column: number;
if (cursor.hasSelection() && !inSelectionMode) {
// If we are in selection mode, move left without selection cancels selection and puts cursor at the beginning of the selection
lineNumber = cursor.selection.startLineNumber;
column = cursor.selection.startColumn;
} else {
let r = MoveOperations.left(config, model, cursor.position.lineNumber, cursor.position.column - (noOfColumns - 1));
lineNumber = r.lineNumber;
column = r.column;
}
return cursor.move(inSelectionMode, lineNumber, column, 0);
}
public static right(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number): CursorPosition {
if (column < model.getLineMaxColumn(lineNumber)) {
if (CursorColumns.isHighSurrogate(model, lineNumber, column - 1)) {
// character after column is a high surrogate
column = column + 2;
} else {
column = column + 1;
}
} else if (lineNumber < model.getLineCount()) {
lineNumber = lineNumber + 1;
column = model.getLineMinColumn(lineNumber);
}
return new CursorPosition(lineNumber, column, 0);
}
public static moveRight(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, noOfColumns: number): SingleCursorState {
let lineNumber: number,
column: number;
if (cursor.hasSelection() && !inSelectionMode) {
// If we are in selection mode, move right without selection cancels selection and puts cursor at the end of the selection
lineNumber = cursor.selection.endLineNumber;
column = cursor.selection.endColumn;
} else {
let r = MoveOperations.right(config, model, cursor.position.lineNumber, cursor.position.column + (noOfColumns - 1));
lineNumber = r.lineNumber;
column = r.column;
}
return cursor.move(inSelectionMode, lineNumber, column, 0);
}
public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition {
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
lineNumber = lineNumber + count;
var lineCount = model.getLineCount();
if (lineNumber > lineCount) {
lineNumber = lineCount;
if (allowMoveOnLastLine) {
column = model.getLineMaxColumn(lineNumber);
} else {
column = Math.min(model.getLineMaxColumn(lineNumber), column);
if (CursorColumns.isInsideSurrogatePair(model, lineNumber, column)) {
column = column - 1;
}
}
} else {
column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn);
if (CursorColumns.isInsideSurrogatePair(model, lineNumber, column)) {
column = column - 1;
}
}
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
}
public static moveDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, linesCount: number): SingleCursorState {
let lineNumber: number,
column: number;
if (cursor.hasSelection() && !inSelectionMode) {
// If we are in selection mode, move down acts relative to the end of selection
lineNumber = cursor.selection.endLineNumber;
column = cursor.selection.endColumn;
} else {
lineNumber = cursor.position.lineNumber;
column = cursor.position.column;
}
let r = MoveOperations.down(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true);
return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
}
public static translateDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState {
let selection = cursor.selection;
let selectionStart = MoveOperations.down(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
let position = MoveOperations.down(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
return new SingleCursorState(
new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column),
selectionStart.leftoverVisibleColumns,
new Position(position.lineNumber, position.column),
position.leftoverVisibleColumns
);
}
public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition {
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
lineNumber = lineNumber - count;
if (lineNumber < 1) {
lineNumber = 1;
if (allowMoveOnFirstLine) {
column = model.getLineMinColumn(lineNumber);
} else {
column = Math.min(model.getLineMaxColumn(lineNumber), column);
if (CursorColumns.isInsideSurrogatePair(model, lineNumber, column)) {
column = column - 1;
}
}
} else {
column = CursorColumns.columnFromVisibleColumn2(config, model, lineNumber, currentVisibleColumn);
if (CursorColumns.isInsideSurrogatePair(model, lineNumber, column)) {
column = column - 1;
}
}
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
}
public static moveUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, linesCount: number): SingleCursorState {
let lineNumber: number,
column: number;
if (cursor.hasSelection() && !inSelectionMode) {
// If we are in selection mode, move up acts relative to the beginning of selection
lineNumber = cursor.selection.startLineNumber;
column = cursor.selection.startColumn;
} else {
lineNumber = cursor.position.lineNumber;
column = cursor.position.column;
}
let r = MoveOperations.up(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true);
return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns);
}
public static translateUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState {
let selection = cursor.selection;
let selectionStart = MoveOperations.up(config, model, selection.selectionStartLineNumber, selection.selectionStartColumn, cursor.selectionStartLeftoverVisibleColumns, 1, false);
let position = MoveOperations.up(config, model, selection.positionLineNumber, selection.positionColumn, cursor.leftoverVisibleColumns, 1, false);
return new SingleCursorState(
new Range(selectionStart.lineNumber, selectionStart.column, selectionStart.lineNumber, selectionStart.column),
selectionStart.leftoverVisibleColumns,
new Position(position.lineNumber, position.column),
position.leftoverVisibleColumns
);
}
public static moveToBeginningOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
let lineNumber = cursor.position.lineNumber;
let minColumn = model.getLineMinColumn(lineNumber);
let firstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(lineNumber) || minColumn;
let column: number;
let relevantColumnNumber = cursor.position.column;
if (relevantColumnNumber === firstNonBlankColumn) {
column = minColumn;
} else {
column = firstNonBlankColumn;
}
return cursor.move(inSelectionMode, lineNumber, column, 0);
}
public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
let lineNumber = cursor.position.lineNumber;
let maxColumn = model.getLineMaxColumn(lineNumber);
return cursor.move(inSelectionMode, lineNumber, maxColumn, 0);
}
public static moveToBeginningOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
return cursor.move(inSelectionMode, 1, 1, 0);
}
public static moveToEndOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState {
let lastLineNumber = model.getLineCount();
let lastColumn = model.getLineMaxColumn(lastLineNumber);
return cursor.move(inSelectionMode, lastLineNumber, lastColumn, 0);
}
}

View File

@@ -0,0 +1,775 @@
/*---------------------------------------------------------------------------------------------
* 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 { onUnexpectedError } from 'vs/base/common/errors';
import { ReplaceCommand, ReplaceCommandWithoutChangingPosition, ReplaceCommandWithOffsetCursorState } from 'vs/editor/common/commands/replaceCommand';
import { CursorColumns, CursorConfiguration, ICursorSimpleModel, EditOperationResult } from 'vs/editor/common/controller/cursorCommon';
import { Range } from 'vs/editor/common/core/range';
import { ICommand, ITokenizedModel } from 'vs/editor/common/editorCommon';
import * as strings from 'vs/base/common/strings';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import { Selection } from 'vs/editor/common/core/selection';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { IndentAction } from 'vs/editor/common/modes/languageConfiguration';
import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand';
import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
import { getMapForWordSeparators, WordCharacterClass } from 'vs/editor/common/controller/wordCharacterClassifier';
export class TypeOperations {
public static indent(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = new ShiftCommand(selections[i], {
isUnshift: false,
tabSize: config.tabSize,
oneIndent: config.oneIndent,
useTabStops: config.useTabStops
});
}
return commands;
}
public static outdent(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = new ShiftCommand(selections[i], {
isUnshift: true,
tabSize: config.tabSize,
oneIndent: config.oneIndent,
useTabStops: config.useTabStops
});
}
return commands;
}
public static shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string {
count = count || 1;
let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, config.tabSize);
let newIndentation = '';
for (let i = 0; i < desiredIndentCount; i++) {
newIndentation += '\t';
}
return newIndentation;
}
public static unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string {
count = count || 1;
let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, config.tabSize);
let newIndentation = '';
for (let i = 0; i < desiredIndentCount; i++) {
newIndentation += '\t';
}
return newIndentation;
}
private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = new ReplaceCommand(selections[i], text[i]);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: true
});
}
private static _simplePaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
let position = selection.getPosition();
if (pasteOnNewLine && text.indexOf('\n') !== text.length - 1) {
pasteOnNewLine = false;
}
if (pasteOnNewLine && selection.startLineNumber !== selection.endLineNumber) {
pasteOnNewLine = false;
}
if (pasteOnNewLine && selection.startColumn === model.getLineMinColumn(selection.startLineNumber) && selection.endColumn === model.getLineMaxColumn(selection.startLineNumber)) {
pasteOnNewLine = false;
}
if (pasteOnNewLine) {
// Paste entire line at the beginning of line
let typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1);
commands[i] = new ReplaceCommand(typeSelection, text);
} else {
commands[i] = new ReplaceCommand(selection, text);
}
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: true
});
}
private static _distributePasteToCursors(selections: Selection[], pasteOnNewLine: boolean, text: string): string[] {
if (pasteOnNewLine) {
return null;
}
if (selections.length === 1) {
return null;
}
for (let i = 0; i < selections.length; i++) {
if (selections[i].startLineNumber !== selections[i].endLineNumber) {
return null;
}
}
let pastePieces = text.split(/\r\n|\r|\n/);
if (pastePieces.length !== selections.length) {
return null;
}
return pastePieces;
}
public static paste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], pasteOnNewLine: boolean, text: string): EditOperationResult {
const distributedPaste = this._distributePasteToCursors(selections, pasteOnNewLine, text);
if (distributedPaste) {
selections = selections.sort(Range.compareRangesUsingStarts);
return this._distributedPaste(config, model, selections, distributedPaste);
} else {
return this._simplePaste(config, model, selections, text, pasteOnNewLine);
}
}
private static _goodIndentForLine(config: CursorConfiguration, model: ITokenizedModel, lineNumber: number): string {
let action;
let indentation;
let expectedIndentAction = LanguageConfigurationRegistry.getInheritIndentForLine(model, lineNumber, false);
if (expectedIndentAction) {
action = expectedIndentAction.action;
indentation = expectedIndentAction.indentation;
} else if (lineNumber > 1) {
let lastLineNumber = lineNumber - 1;
for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) {
let lineText = model.getLineContent(lastLineNumber);
let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText);
if (nonWhitespaceIdx >= 0) {
break;
}
}
if (lastLineNumber < 1) {
// No previous line with content found
return null;
}
let maxColumn = model.getLineMaxColumn(lastLineNumber);
let expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn));
if (expectedEnterAction) {
indentation = expectedEnterAction.indentation;
action = expectedEnterAction.enterAction;
if (action) {
indentation += action.appendText;
}
}
}
if (action) {
if (action === IndentAction.Indent) {
indentation = TypeOperations.shiftIndent(config, indentation);
}
if (action === IndentAction.Outdent) {
indentation = TypeOperations.unshiftIndent(config, indentation);
}
indentation = config.normalizeIndentation(indentation);
}
if (!indentation) {
return null;
}
return indentation;
}
private static _replaceJumpToNextIndent(config: CursorConfiguration, model: ICursorSimpleModel, selection: Selection, insertsAutoWhitespace: boolean): ReplaceCommand {
let typeText = '';
let position = selection.getStartPosition();
if (config.insertSpaces) {
let visibleColumnFromColumn = CursorColumns.visibleColumnFromColumn2(config, model, position);
let tabSize = config.tabSize;
let spacesCnt = tabSize - (visibleColumnFromColumn % tabSize);
for (let i = 0; i < spacesCnt; i++) {
typeText += ' ';
}
} else {
typeText = '\t';
}
return new ReplaceCommand(selection, typeText, insertsAutoWhitespace);
}
public static tab(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (selection.isEmpty()) {
let lineText = model.getLineContent(selection.startLineNumber);
if (/^\s*$/.test(lineText) && model.isCheapToTokenize(selection.startLineNumber)) {
let goodIndent = this._goodIndentForLine(config, model, selection.startLineNumber);
goodIndent = goodIndent || '\t';
let possibleTypeText = config.normalizeIndentation(goodIndent);
if (!strings.startsWith(lineText, possibleTypeText)) {
commands[i] = new ReplaceCommand(new Range(selection.startLineNumber, 1, selection.startLineNumber, lineText.length + 1), possibleTypeText, true);
continue;
}
}
commands[i] = this._replaceJumpToNextIndent(config, model, selection, true);
} else {
if (selection.startLineNumber === selection.endLineNumber) {
let lineMaxColumn = model.getLineMaxColumn(selection.startLineNumber);
if (selection.startColumn !== 1 || selection.endColumn !== lineMaxColumn) {
// This is a single line selection that is not the entire line
commands[i] = this._replaceJumpToNextIndent(config, model, selection, false);
continue;
}
}
commands[i] = new ShiftCommand(selection, {
isUnshift: false,
tabSize: config.tabSize,
oneIndent: config.oneIndent,
useTabStops: config.useTabStops
});
}
}
return commands;
}
public static replacePreviousChar(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], txt: string, replaceCharCnt: number): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (!selection.isEmpty()) {
// looks like https://github.com/Microsoft/vscode/issues/2773
// where a cursor operation occurred before a canceled composition
// => ignore composition
commands[i] = null;
continue;
}
let pos = selection.getPosition();
let startColumn = Math.max(1, pos.column - replaceCharCnt);
let range = new Range(pos.lineNumber, startColumn, pos.lineNumber, pos.column);
commands[i] = new ReplaceCommand(range, txt);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false
});
}
private static _typeCommand(range: Range, text: string, keepPosition: boolean): ICommand {
if (keepPosition) {
return new ReplaceCommandWithoutChangingPosition(range, text, true);
} else {
return new ReplaceCommand(range, text, true);
}
}
private static _enter(config: CursorConfiguration, model: ITokenizedModel, keepPosition: boolean, range: Range): ICommand {
if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
let lineText = model.getLineContent(range.startLineNumber);
let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
}
let r = LanguageConfigurationRegistry.getEnterAction(model, range);
if (r) {
let enterAction = r.enterAction;
let indentation = r.indentation;
if (enterAction.indentAction === IndentAction.None) {
// Nothing special
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition);
} else if (enterAction.indentAction === IndentAction.Indent) {
// Indent once
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition);
} else if (enterAction.indentAction === IndentAction.IndentOutdent) {
// Ultra special
let normalIndent = config.normalizeIndentation(indentation);
let increasedIndent = config.normalizeIndentation(indentation + enterAction.appendText);
let typeText = '\n' + increasedIndent + '\n' + normalIndent;
if (keepPosition) {
return new ReplaceCommandWithoutChangingPosition(range, typeText, true);
} else {
return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true);
}
} else if (enterAction.indentAction === IndentAction.Outdent) {
let actualIndentation = TypeOperations.unshiftIndent(config, indentation);
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + enterAction.appendText), keepPosition);
}
}
// no enter rules applied, we should check indentation rules then.
let ir = LanguageConfigurationRegistry.getIndentForEnter(model, range, {
unshiftIndent: (indent) => {
return TypeOperations.unshiftIndent(config, indent);
},
shiftIndent: (indent) => {
return TypeOperations.shiftIndent(config, indent);
},
normalizeIndentation: (indent) => {
return config.normalizeIndentation(indent);
}
}, config.autoIndent);
let lineText = model.getLineContent(range.startLineNumber);
let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
if (ir) {
let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition());
let oldEndColumn = range.endColumn;
let beforeText = '\n';
if (indentation !== config.normalizeIndentation(ir.beforeEnter)) {
beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n';
range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn);
}
let newLineContent = model.getLineContent(range.endLineNumber);
let firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent);
if (firstNonWhitespace >= 0) {
range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1));
} else {
range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
}
if (keepPosition) {
return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true);
} else {
let offset = 0;
if (oldEndColumn <= firstNonWhitespace + 1) {
if (!config.insertSpaces) {
oldEndViewColumn = Math.ceil(oldEndViewColumn / config.tabSize);
}
offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0);
}
return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true);
}
} else {
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
}
}
private static _isAutoIndentType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]): boolean {
if (!config.autoIndent) {
return false;
}
for (let i = 0, len = selections.length; i < len; i++) {
if (!model.isCheapToTokenize(selections[i].getEndPosition().lineNumber)) {
return false;
}
}
return true;
}
private static _runAutoIndentType(config: CursorConfiguration, model: ITokenizedModel, range: Range, ch: string): ICommand {
let currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
let actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(model, range, ch, {
shiftIndent: (indentation) => {
return TypeOperations.shiftIndent(config, indentation);
},
unshiftIndent: (indentation) => {
return TypeOperations.unshiftIndent(config, indentation);
},
});
if (actualIndentation === null) {
return null;
}
if (actualIndentation !== config.normalizeIndentation(currentIndentation)) {
let firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber);
if (firstNonWhitespace === 0) {
return TypeOperations._typeCommand(
new Range(range.startLineNumber, 0, range.endLineNumber, range.endColumn),
config.normalizeIndentation(actualIndentation) + ch,
false
);
} else {
return TypeOperations._typeCommand(
new Range(range.startLineNumber, 0, range.endLineNumber, range.endColumn),
config.normalizeIndentation(actualIndentation) +
model.getLineContent(range.startLineNumber).substring(firstNonWhitespace - 1, range.startColumn - 1) + ch,
false
);
}
}
return null;
}
private static _isAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.autoClosingPairsClose.hasOwnProperty(ch)) {
return false;
}
const isEqualPair = (ch === config.autoClosingPairsClose[ch]);
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (!selection.isEmpty()) {
return false;
}
const position = selection.getPosition();
const lineText = model.getLineContent(position.lineNumber);
const afterCharacter = lineText.charAt(position.column - 1);
if (afterCharacter !== ch) {
return false;
}
if (isEqualPair) {
const lineTextBeforeCursor = lineText.substr(0, position.column - 1);
const chCntBefore = this._countNeedlesInHaystack(lineTextBeforeCursor, ch);
if (chCntBefore % 2 === 0) {
return false;
}
}
}
return true;
}
private static _countNeedlesInHaystack(haystack: string, needle: string): number {
let cnt = 0;
let lastIndex = -1;
while ((lastIndex = haystack.indexOf(needle, lastIndex + 1)) !== -1) {
cnt++;
}
return cnt;
}
private static _runAutoClosingCloseCharType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
const position = selection.getPosition();
const typeSelection = new Range(position.lineNumber, position.column, position.lineNumber, position.column + 1);
commands[i] = new ReplaceCommand(typeSelection, ch);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false
});
}
private static _isAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.autoClosingPairsOpen.hasOwnProperty(ch)) {
return false;
}
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (!selection.isEmpty()) {
return false;
}
const position = selection.getPosition();
const lineText = model.getLineContent(position.lineNumber);
// Do not auto-close ' or " after a word character
if ((ch === '\'' || ch === '"') && position.column > 1) {
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
const characterBeforeCode = lineText.charCodeAt(position.column - 2);
const characterBeforeType = wordSeparators.get(characterBeforeCode);
if (characterBeforeType === WordCharacterClass.Regular) {
return false;
}
}
// Only consider auto closing the pair if a space follows or if another autoclosed pair follows
const characterAfter = lineText.charAt(position.column - 1);
if (characterAfter) {
const thisBraceIsSymmetric = (config.autoClosingPairsOpen[ch] === ch);
let isBeforeCloseBrace = false;
for (let otherCloseBrace in config.autoClosingPairsClose) {
const otherBraceIsSymmetric = (config.autoClosingPairsOpen[otherCloseBrace] === otherCloseBrace);
if (!thisBraceIsSymmetric && otherBraceIsSymmetric) {
continue;
}
if (characterAfter === otherCloseBrace) {
isBeforeCloseBrace = true;
break;
}
}
if (!isBeforeCloseBrace && !/\s/.test(characterAfter)) {
return false;
}
}
if (!model.isCheapToTokenize(position.lineNumber)) {
// Do not force tokenization
return false;
}
model.forceTokenization(position.lineNumber);
const lineTokens = model.getLineTokens(position.lineNumber);
let shouldAutoClosePair = false;
try {
shouldAutoClosePair = LanguageConfigurationRegistry.shouldAutoClosePair(ch, lineTokens, position.column);
} catch (e) {
onUnexpectedError(e);
}
if (!shouldAutoClosePair) {
return false;
}
}
return true;
}
private static _runAutoClosingOpenCharType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
const closeCharacter = config.autoClosingPairsOpen[ch];
commands[i] = new ReplaceCommandWithOffsetCursorState(selection, ch + closeCharacter, 0, -closeCharacter.length);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: false
});
}
private static _isSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): boolean {
if (!config.autoClosingBrackets || !config.surroundingPairs.hasOwnProperty(ch)) {
return false;
}
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
if (selection.isEmpty()) {
return false;
}
let selectionContainsOnlyWhitespace = true;
for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) {
const lineText = model.getLineContent(lineNumber);
const startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0);
const endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length);
const selectedText = lineText.substring(startIndex, endIndex);
if (/[^ \t]/.test(selectedText)) {
// this selected text contains something other than whitespace
selectionContainsOnlyWhitespace = false;
break;
}
}
if (selectionContainsOnlyWhitespace) {
return false;
}
}
return true;
}
private static _runSurroundSelectionType(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const selection = selections[i];
const closeCharacter = config.surroundingPairs[ch];
commands[i] = new SurroundSelectionCommand(selection, ch, closeCharacter);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: true
});
}
private static _isTypeInterceptorElectricChar(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]) {
if (selections.length === 1 && model.isCheapToTokenize(selections[0].getEndPosition().lineNumber)) {
return true;
}
return false;
}
private static _typeInterceptorElectricChar(config: CursorConfiguration, model: ITokenizedModel, selection: Selection, ch: string): EditOperationResult {
if (!config.electricChars.hasOwnProperty(ch) || !selection.isEmpty()) {
return null;
}
let position = selection.getPosition();
model.forceTokenization(position.lineNumber);
let lineTokens = model.getLineTokens(position.lineNumber);
let electricAction: IElectricAction;
try {
electricAction = LanguageConfigurationRegistry.onElectricCharacter(ch, lineTokens, position.column);
} catch (e) {
onUnexpectedError(e);
}
if (!electricAction) {
return null;
}
if (electricAction.appendText) {
const command = new ReplaceCommandWithOffsetCursorState(selection, ch + electricAction.appendText, 0, -electricAction.appendText.length);
return new EditOperationResult([command], {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: true
});
}
if (electricAction.matchOpenBracket) {
let endColumn = (lineTokens.getLineContent() + ch).lastIndexOf(electricAction.matchOpenBracket) + 1;
let match = model.findMatchingBracketUp(electricAction.matchOpenBracket, {
lineNumber: position.lineNumber,
column: endColumn
});
if (match) {
if (match.startLineNumber === position.lineNumber) {
// matched something on the same line => no change in indentation
return null;
}
let matchLine = model.getLineContent(match.startLineNumber);
let matchLineIndentation = strings.getLeadingWhitespace(matchLine);
let newIndentation = config.normalizeIndentation(matchLineIndentation);
let lineText = model.getLineContent(position.lineNumber);
let lineFirstNonBlankColumn = model.getLineFirstNonWhitespaceColumn(position.lineNumber) || position.column;
let prefix = lineText.substring(lineFirstNonBlankColumn - 1, position.column - 1);
let typeText = newIndentation + prefix + ch;
let typeSelection = new Range(position.lineNumber, 1, position.lineNumber, position.column);
const command = new ReplaceCommand(typeSelection, typeText);
return new EditOperationResult([command], {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: true
});
}
}
return null;
}
public static typeWithInterceptors(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], ch: string): EditOperationResult {
if (ch === '\n') {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = TypeOperations._enter(config, model, false, selections[i]);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: false,
});
}
if (this._isAutoIndentType(config, model, selections)) {
let indentCommand = this._runAutoIndentType(config, model, selections[0], ch);
if (indentCommand) {
return new EditOperationResult([indentCommand], {
shouldPushStackElementBefore: true,
shouldPushStackElementAfter: false,
});
}
}
if (this._isAutoClosingCloseCharType(config, model, selections, ch)) {
return this._runAutoClosingCloseCharType(config, model, selections, ch);
}
if (this._isAutoClosingOpenCharType(config, model, selections, ch)) {
return this._runAutoClosingOpenCharType(config, model, selections, ch);
}
if (this._isSurroundSelectionType(config, model, selections, ch)) {
return this._runSurroundSelectionType(config, model, selections, ch);
}
// Electric characters make sense only when dealing with a single cursor,
// as multiple cursors typing brackets for example would interfer with bracket matching
if (this._isTypeInterceptorElectricChar(config, model, selections)) {
const r = this._typeInterceptorElectricChar(config, model, selections[0], ch);
if (r) {
return r;
}
}
return this.typeWithoutInterceptors(config, model, selections, ch);
}
public static typeWithoutInterceptors(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[], str: string): EditOperationResult {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = new ReplaceCommand(selections[i], str);
}
return new EditOperationResult(commands, {
shouldPushStackElementBefore: false,
shouldPushStackElementAfter: false
});
}
public static lineInsertBefore(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
let lineNumber = selections[i].positionLineNumber;
if (lineNumber === 1) {
commands[i] = new ReplaceCommandWithoutChangingPosition(new Range(1, 1, 1, 1), '\n');
} else {
lineNumber--;
let column = model.getLineMaxColumn(lineNumber);
commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column));
}
}
return commands;
}
public static lineInsertAfter(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
const lineNumber = selections[i].positionLineNumber;
let column = model.getLineMaxColumn(lineNumber);
commands[i] = this._enter(config, model, false, new Range(lineNumber, column, lineNumber, column));
}
return commands;
}
public static lineBreakInsert(config: CursorConfiguration, model: ITokenizedModel, selections: Selection[]): ICommand[] {
let commands: ICommand[] = [];
for (let i = 0, len = selections.length; i < len; i++) {
commands[i] = this._enter(config, model, true, selections[i]);
}
return commands;
}
}

View File

@@ -0,0 +1,444 @@
/*---------------------------------------------------------------------------------------------
* 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 { SingleCursorState, CursorConfiguration, ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon';
import { Position } from 'vs/editor/common/core/position';
import { WordCharacterClassifier, WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier';
import * as strings from 'vs/base/common/strings';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
interface IFindWordResult {
/**
* The index where the word starts.
*/
start: number;
/**
* The index where the word ends.
*/
end: number;
/**
* The word type.
*/
wordType: WordType;
}
const enum WordType {
None = 0,
Regular = 1,
Separator = 2
}
export const enum WordNavigationType {
WordStart = 0,
WordEnd = 1
}
export class WordOperations {
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IFindWordResult {
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
return { start: start, end: end, wordType: wordType };
}
private static _findPreviousWordOnLine(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): IFindWordResult {
let lineContent = model.getLineContent(position.lineNumber);
return this._doFindPreviousWordOnLine(lineContent, wordSeparators, position);
}
private static _doFindPreviousWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult {
let wordType = WordType.None;
for (let chIndex = position.column - 2; chIndex >= 0; chIndex--) {
let chCode = lineContent.charCodeAt(chIndex);
let chClass = wordSeparators.get(chCode);
if (chClass === WordCharacterClass.Regular) {
if (wordType === WordType.Separator) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
wordType = WordType.Regular;
} else if (chClass === WordCharacterClass.WordSeparator) {
if (wordType === WordType.Regular) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
wordType = WordType.Separator;
} else if (chClass === WordCharacterClass.Whitespace) {
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
}
}
}
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0));
}
return null;
}
private static _findEndOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number {
let len = lineContent.length;
for (let chIndex = startIndex; chIndex < len; chIndex++) {
let chCode = lineContent.charCodeAt(chIndex);
let chClass = wordSeparators.get(chCode);
if (chClass === WordCharacterClass.Whitespace) {
return chIndex;
}
if (wordType === WordType.Regular && chClass === WordCharacterClass.WordSeparator) {
return chIndex;
}
if (wordType === WordType.Separator && chClass === WordCharacterClass.Regular) {
return chIndex;
}
}
return len;
}
private static _findNextWordOnLine(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): IFindWordResult {
let lineContent = model.getLineContent(position.lineNumber);
return this._doFindNextWordOnLine(lineContent, wordSeparators, position);
}
private static _doFindNextWordOnLine(lineContent: string, wordSeparators: WordCharacterClassifier, position: Position): IFindWordResult {
let wordType = WordType.None;
let len = lineContent.length;
for (let chIndex = position.column - 1; chIndex < len; chIndex++) {
let chCode = lineContent.charCodeAt(chIndex);
let chClass = wordSeparators.get(chCode);
if (chClass === WordCharacterClass.Regular) {
if (wordType === WordType.Separator) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
wordType = WordType.Regular;
} else if (chClass === WordCharacterClass.WordSeparator) {
if (wordType === WordType.Regular) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
wordType = WordType.Separator;
} else if (chClass === WordCharacterClass.Whitespace) {
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
}
}
}
if (wordType !== WordType.None) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len);
}
return null;
}
private static _findStartOfWord(lineContent: string, wordSeparators: WordCharacterClassifier, wordType: WordType, startIndex: number): number {
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
let chCode = lineContent.charCodeAt(chIndex);
let chClass = wordSeparators.get(chCode);
if (chClass === WordCharacterClass.Whitespace) {
return chIndex + 1;
}
if (wordType === WordType.Regular && chClass === WordCharacterClass.WordSeparator) {
return chIndex + 1;
}
if (wordType === WordType.Separator && chClass === WordCharacterClass.Regular) {
return chIndex + 1;
}
}
return 0;
}
public static moveWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
let lineNumber = position.lineNumber;
let column = position.column;
if (column === 1) {
if (lineNumber > 1) {
lineNumber = lineNumber - 1;
column = model.getLineMaxColumn(lineNumber);
}
}
let prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, column));
if (wordNavigationType === WordNavigationType.WordStart) {
if (prevWordOnLine) {
column = prevWordOnLine.start + 1;
} else {
column = 1;
}
} else {
if (prevWordOnLine && column <= prevWordOnLine.end + 1) {
prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, prevWordOnLine.start + 1));
}
if (prevWordOnLine) {
column = prevWordOnLine.end + 1;
} else {
column = 1;
}
}
return new Position(lineNumber, column);
}
public static moveWordRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position, wordNavigationType: WordNavigationType): Position {
let lineNumber = position.lineNumber;
let column = position.column;
if (column === model.getLineMaxColumn(lineNumber)) {
if (lineNumber < model.getLineCount()) {
lineNumber = lineNumber + 1;
column = 1;
}
}
let nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, column));
if (wordNavigationType === WordNavigationType.WordEnd) {
if (nextWordOnLine) {
column = nextWordOnLine.end + 1;
} else {
column = model.getLineMaxColumn(lineNumber);
}
} else {
if (nextWordOnLine && column >= nextWordOnLine.start + 1) {
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1));
}
if (nextWordOnLine) {
column = nextWordOnLine.start + 1;
} else {
column = model.getLineMaxColumn(lineNumber);
}
}
return new Position(lineNumber, column);
}
private static _deleteWordLeftWhitespace(model: ICursorSimpleModel, position: Position): Range {
const lineContent = model.getLineContent(position.lineNumber);
const startIndex = position.column - 2;
const lastNonWhitespace = strings.lastNonWhitespaceIndex(lineContent, startIndex);
if (lastNonWhitespace + 1 < startIndex) {
return new Range(position.lineNumber, lastNonWhitespace + 2, position.lineNumber, position.column);
}
return null;
}
public static deleteWordLeft(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
if (!selection.isEmpty()) {
return selection;
}
const position = new Position(selection.positionLineNumber, selection.positionColumn);
let lineNumber = position.lineNumber;
let column = position.column;
if (lineNumber === 1 && column === 1) {
// Ignore deleting at beginning of file
return null;
}
if (whitespaceHeuristics) {
let r = this._deleteWordLeftWhitespace(model, position);
if (r) {
return r;
}
}
let prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, position);
if (wordNavigationType === WordNavigationType.WordStart) {
if (prevWordOnLine) {
column = prevWordOnLine.start + 1;
} else {
if (column > 1) {
column = 1;
} else {
lineNumber--;
column = model.getLineMaxColumn(lineNumber);
}
}
} else {
if (prevWordOnLine && column <= prevWordOnLine.end + 1) {
prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, prevWordOnLine.start + 1));
}
if (prevWordOnLine) {
column = prevWordOnLine.end + 1;
} else {
if (column > 1) {
column = 1;
} else {
lineNumber--;
column = model.getLineMaxColumn(lineNumber);
}
}
}
return new Range(lineNumber, column, position.lineNumber, position.column);
}
private static _findFirstNonWhitespaceChar(str: string, startIndex: number): number {
let len = str.length;
for (let chIndex = startIndex; chIndex < len; chIndex++) {
let ch = str.charAt(chIndex);
if (ch !== ' ' && ch !== '\t') {
return chIndex;
}
}
return len;
}
private static _deleteWordRightWhitespace(model: ICursorSimpleModel, position: Position): Range {
const lineContent = model.getLineContent(position.lineNumber);
const startIndex = position.column - 1;
const firstNonWhitespace = this._findFirstNonWhitespaceChar(lineContent, startIndex);
if (startIndex + 1 < firstNonWhitespace) {
// bingo
return new Range(position.lineNumber, position.column, position.lineNumber, firstNonWhitespace + 1);
}
return null;
}
public static deleteWordRight(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, selection: Selection, whitespaceHeuristics: boolean, wordNavigationType: WordNavigationType): Range {
if (!selection.isEmpty()) {
return selection;
}
const position = new Position(selection.positionLineNumber, selection.positionColumn);
let lineNumber = position.lineNumber;
let column = position.column;
const lineCount = model.getLineCount();
const maxColumn = model.getLineMaxColumn(lineNumber);
if (lineNumber === lineCount && column === maxColumn) {
// Ignore deleting at end of file
return null;
}
if (whitespaceHeuristics) {
let r = this._deleteWordRightWhitespace(model, position);
if (r) {
return r;
}
}
let nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, position);
if (wordNavigationType === WordNavigationType.WordEnd) {
if (nextWordOnLine) {
column = nextWordOnLine.end + 1;
} else {
if (column < maxColumn || lineNumber === lineCount) {
column = maxColumn;
} else {
lineNumber++;
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, 1));
if (nextWordOnLine) {
column = nextWordOnLine.start + 1;
} else {
column = model.getLineMaxColumn(lineNumber);
}
}
}
} else {
if (nextWordOnLine && column >= nextWordOnLine.start + 1) {
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1));
}
if (nextWordOnLine) {
column = nextWordOnLine.start + 1;
} else {
if (column < maxColumn || lineNumber === lineCount) {
column = maxColumn;
} else {
lineNumber++;
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, 1));
if (nextWordOnLine) {
column = nextWordOnLine.start + 1;
} else {
column = model.getLineMaxColumn(lineNumber);
}
}
}
}
return new Range(lineNumber, column, position.lineNumber, position.column);
}
public static word(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, position: Position): SingleCursorState {
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
let prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position);
let isInPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 <= prevWord.end);
let nextWord = WordOperations._findNextWordOnLine(wordSeparators, model, position);
let isInNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 <= nextWord.end);
if (!inSelectionMode || !cursor.hasSelection()) {
// Entering word selection for the first time
let startColumn: number;
let endColumn: number;
if (isInPrevWord) {
startColumn = prevWord.start + 1;
endColumn = prevWord.end + 1;
} else if (isInNextWord) {
startColumn = nextWord.start + 1;
endColumn = nextWord.end + 1;
} else {
if (prevWord) {
startColumn = prevWord.end + 1;
} else {
startColumn = 1;
}
if (nextWord) {
endColumn = nextWord.start + 1;
} else {
endColumn = model.getLineMaxColumn(position.lineNumber);
}
}
return new SingleCursorState(
new Range(position.lineNumber, startColumn, position.lineNumber, endColumn), 0,
new Position(position.lineNumber, endColumn), 0
);
}
let startColumn: number;
let endColumn: number;
if (isInPrevWord) {
startColumn = prevWord.start + 1;
endColumn = prevWord.end + 1;
} else if (isInNextWord) {
startColumn = nextWord.start + 1;
endColumn = nextWord.end + 1;
} else {
startColumn = position.column;
endColumn = position.column;
}
let lineNumber = position.lineNumber;
let column: number;
if (position.isBeforeOrEqual(cursor.selectionStart.getStartPosition())) {
column = startColumn;
let possiblePosition = new Position(lineNumber, column);
if (cursor.selectionStart.containsPosition(possiblePosition)) {
column = cursor.selectionStart.endColumn;
}
} else {
column = endColumn;
let possiblePosition = new Position(lineNumber, column);
if (cursor.selectionStart.containsPosition(possiblePosition)) {
column = cursor.selectionStart.startColumn;
}
}
return cursor.move(cursor.hasSelection(), lineNumber, column, 0);
}
}

View File

@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* 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 { SingleCursorState, CursorContext, CursorState } from 'vs/editor/common/controller/cursorCommon';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
export class OneCursor {
public modelState: SingleCursorState;
public viewState: SingleCursorState;
private _selStartMarker: string;
private _selEndMarker: string;
constructor(context: CursorContext) {
this._setState(
context,
new SingleCursorState(new Range(1, 1, 1, 1), 0, new Position(1, 1), 0),
new SingleCursorState(new Range(1, 1, 1, 1), 0, new Position(1, 1), 0)
);
}
public dispose(context: CursorContext): void {
context.model._removeMarker(this._selStartMarker);
context.model._removeMarker(this._selEndMarker);
}
public asCursorState(): CursorState {
return new CursorState(this.modelState, this.viewState);
}
public readSelectionFromMarkers(context: CursorContext): Selection {
const start = context.model._getMarker(this._selStartMarker);
const end = context.model._getMarker(this._selEndMarker);
if (this.modelState.selection.getDirection() === SelectionDirection.LTR) {
return new Selection(start.lineNumber, start.column, end.lineNumber, end.column);
}
return new Selection(end.lineNumber, end.column, start.lineNumber, start.column);
}
public ensureValidState(context: CursorContext): void {
this._setState(context, this.modelState, this.viewState);
}
public setState(context: CursorContext, modelState: SingleCursorState, viewState: SingleCursorState): void {
this._setState(context, modelState, viewState);
}
private _setState(context: CursorContext, modelState: SingleCursorState, viewState: SingleCursorState): void {
if (!modelState) {
// We only have the view state => compute the model state
const selectionStart = context.model.validateRange(
context.convertViewRangeToModelRange(viewState.selectionStart)
);
const position = context.model.validatePosition(
context.convertViewPositionToModelPosition(viewState.position.lineNumber, viewState.position.column)
);
modelState = new SingleCursorState(selectionStart, viewState.selectionStartLeftoverVisibleColumns, position, viewState.leftoverVisibleColumns);
} else {
// Validate new model state
const selectionStart = context.model.validateRange(modelState.selectionStart);
const selectionStartLeftoverVisibleColumns = modelState.selectionStart.equalsRange(selectionStart) ? modelState.selectionStartLeftoverVisibleColumns : 0;
const position = context.model.validatePosition(
modelState.position
);
const leftoverVisibleColumns = modelState.position.equals(position) ? modelState.leftoverVisibleColumns : 0;
modelState = new SingleCursorState(selectionStart, selectionStartLeftoverVisibleColumns, position, leftoverVisibleColumns);
}
if (!viewState) {
// We only have the model state => compute the view state
const viewSelectionStart1 = context.convertModelPositionToViewPosition(new Position(modelState.selectionStart.startLineNumber, modelState.selectionStart.startColumn));
const viewSelectionStart2 = context.convertModelPositionToViewPosition(new Position(modelState.selectionStart.endLineNumber, modelState.selectionStart.endColumn));
const viewSelectionStart = new Range(viewSelectionStart1.lineNumber, viewSelectionStart1.column, viewSelectionStart2.lineNumber, viewSelectionStart2.column);
const viewPosition = context.convertModelPositionToViewPosition(modelState.position);
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
} else {
// Validate new view state
const viewSelectionStart = context.validateViewRange(viewState.selectionStart, modelState.selectionStart);
const viewPosition = context.validateViewPosition(viewState.position, modelState.position);
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
}
if (this.modelState && this.viewState && this.modelState.equals(modelState) && this.viewState.equals(viewState)) {
// No-op, early return
return;
}
this.modelState = modelState;
this.viewState = viewState;
this._selStartMarker = this._ensureMarker(context, this._selStartMarker, this.modelState.selection.startLineNumber, this.modelState.selection.startColumn, true);
this._selEndMarker = this._ensureMarker(context, this._selEndMarker, this.modelState.selection.endLineNumber, this.modelState.selection.endColumn, false);
}
private _ensureMarker(context: CursorContext, markerId: string, lineNumber: number, column: number, stickToPreviousCharacter: boolean): string {
if (!markerId) {
return context.model._addMarker(0, lineNumber, column, stickToPreviousCharacter);
} else {
context.model._changeMarker(markerId, lineNumber, column);
context.model._changeMarkerStickiness(markerId, stickToPreviousCharacter);
return markerId;
}
}
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* 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 { CharCode } from 'vs/base/common/charCode';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
export const enum WordCharacterClass {
Regular = 0,
Whitespace = 1,
WordSeparator = 2
}
export class WordCharacterClassifier extends CharacterClassifier<WordCharacterClass> {
constructor(wordSeparators: string) {
super(WordCharacterClass.Regular);
for (let i = 0, len = wordSeparators.length; i < len; i++) {
this.set(wordSeparators.charCodeAt(i), WordCharacterClass.WordSeparator);
}
this.set(CharCode.Space, WordCharacterClass.Whitespace);
this.set(CharCode.Tab, WordCharacterClass.Whitespace);
}
}
function once<R>(computeFn: (input: string) => R): (input: string) => R {
let cache: { [key: string]: R; } = {}; // TODO@Alex unbounded cache
return (input: string): R => {
if (!cache.hasOwnProperty(input)) {
cache[input] = computeFn(input);
}
return cache[input];
};
}
export const getMapForWordSeparators = once<WordCharacterClassifier>(
(input) => new WordCharacterClassifier(input)
);