Files
vscode-extension-samples/vim-sample/src/controller.ts

275 lines
7.4 KiB
TypeScript
Raw Normal View History

2016-07-15 12:39:15 +02:00
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
2016-07-25 16:59:01 +02:00
import * as vscode from 'vscode';
2016-07-15 12:39:15 +02:00
import {
TextEditorCursorStyle,
Position,
Range,
Selection,
TextEditor,
TextEditorRevealType
} from 'vscode';
import {Words} from './words';
import {MotionState, Motion} from './motions';
import {Mode, IController, DeleteRegister, Command} from './common';
import {Mappings} from './mappings';
export interface ITypeResult {
hasConsumedInput: boolean;
executeEditorCommand: Command;
}
export class Controller implements IController {
private _currentMode: Mode;
private _currentInput: string;
private _motionState: MotionState;
private _isVisual: boolean;
public get motionState(): MotionState { return this._motionState; }
public findMotion(input: string): Motion { return Mappings.findMotion(input); }
public isMotionPrefix(input: string): boolean { return Mappings.isMotionPrefix(input); }
private _deleteRegister:DeleteRegister;
public setDeleteRegister(register:DeleteRegister): void { this._deleteRegister = register; }
public getDeleteRegister(): DeleteRegister { return this._deleteRegister; }
constructor() {
this._motionState = new MotionState();
this._deleteRegister = null;
this.setVisual(false);
this.setMode(Mode.NORMAL);
}
public setWordSeparators(wordSeparators: string): void {
this._motionState.wordCharacterClass = Words.createWordCharacters(wordSeparators);
}
public ensureNormalModePosition(editor: TextEditor): void {
if (this._currentMode !== Mode.NORMAL) {
return;
}
2016-07-28 09:56:09 +02:00
if (this._isVisual) {
return;
}
2016-07-15 12:39:15 +02:00
let sel = editor.selection;
let pos = sel.active;
let doc = editor.document;
let lineContent = doc.lineAt(pos.line).text;
if (lineContent.length === 0) {
return;
}
let maxCharacter = lineContent.length - 1;
if (pos.character > maxCharacter) {
2016-07-28 09:56:09 +02:00
setPositionAndReveal(editor, pos.line, maxCharacter);
2016-07-15 12:39:15 +02:00
}
}
public hasInput(): boolean {
return this._currentInput.length > 0;
}
public clearInput(): void {
this._currentInput = '';
}
public getMode(): Mode {
return this._currentMode;
}
public setMode(newMode: Mode): void {
if (newMode !== this._currentMode) {
this._currentMode = newMode;
this._motionState.cursorDesiredCharacter = -1; // uninitialized
this._currentInput = '';
}
}
public setVisual(newVisual: boolean): void {
if (this._isVisual !== newVisual) {
this._isVisual = newVisual;
}
}
public getVisual(): boolean {
return this._isVisual;
}
public getCursorStyle(): TextEditorCursorStyle {
if (this._currentMode === Mode.NORMAL) {
if (/^([1-9]\d*)?(r|c)/.test(this._currentInput)) {
return TextEditorCursorStyle.Underline;
}
return TextEditorCursorStyle.Block;
}
if (this._currentMode === Mode.REPLACE) {
return TextEditorCursorStyle.Underline;
}
return TextEditorCursorStyle.Line;
}
private _getModeLabel(): string {
if (this._currentMode === Mode.NORMAL) {
if (this._isVisual) {
return '-- VISUAL --';
}
return '-- NORMAL --';
}
if (this._currentMode === Mode.REPLACE) {
if (this._isVisual) {
return '-- (replace) VISUAL --';
}
return '-- REPLACE --';
}
if (this._isVisual) {
return '-- (insert) VISUAL --';
}
return '-- INSERT --';
}
public getStatusText(): string {
let label = this._getModeLabel();
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
}
2016-07-25 16:59:01 +02:00
public type(editor: TextEditor, text: string): Thenable<ITypeResult> {
2016-07-15 12:39:15 +02:00
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
2016-07-25 16:59:01 +02:00
return Promise.resolve({
2016-07-15 12:39:15 +02:00
hasConsumedInput: false,
executeEditorCommand: null
2016-07-25 16:59:01 +02:00
});
2016-07-15 12:39:15 +02:00
}
if (this._currentMode === Mode.REPLACE) {
let pos = editor.selection.active;
editor.edit((builder) => {
builder.replace(new Range(pos.line, pos.character, pos.line, pos.character + 1), text);
}).then(() => {
setPositionAndReveal(editor, pos.line, pos.character + 1);
});
2016-07-25 16:59:01 +02:00
return Promise.resolve({
2016-07-15 12:39:15 +02:00
hasConsumedInput: true,
executeEditorCommand: null
2016-07-25 16:59:01 +02:00
});
2016-07-15 12:39:15 +02:00
}
this._currentInput += text;
return this._interpretNormalModeInput(editor);
}
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
return false;
}
if (this._currentMode === Mode.REPLACE) {
let pos = editor.selection.active;
editor.edit((builder) => {
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
});
return true;
}
// Not supporting IME building in NORMAL mode
return true;
}
2016-07-25 16:59:01 +02:00
private _interpretNormalModeInput(editor: TextEditor): Thenable<ITypeResult> {
if (this._currentInput.startsWith(':')) {
return vscode.window.showInputBox({value: 'tabm'}).then((value) => {
2016-07-25 17:57:54 +02:00
let result = this._findMapping(value || '', editor);
2016-07-25 17:42:04 +02:00
return Promise.resolve(result);
2016-07-25 16:59:01 +02:00
});
}
let result = this._findMapping(this._currentInput, editor);
return Promise.resolve(result);
}
private _findMapping(input: string, editor: TextEditor): ITypeResult {
let command = Mappings.findCommand(input);
2016-07-15 12:39:15 +02:00
if (command) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: command
};
}
2016-07-25 16:59:01 +02:00
let operator = Mappings.findOperator(input);
2016-07-15 12:39:15 +02:00
if (operator) {
if (this._isVisual) {
if (operator.runVisual(this, editor)) {
this._currentInput = '';
}
} else {
// Mode.NORMAL
if (operator.runNormal(this, editor)) {
this._currentInput = '';
}
}
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
2016-07-25 16:59:01 +02:00
let motionCommand = Mappings.findMotionCommand(input, this._isVisual);
2016-07-15 12:39:15 +02:00
if (motionCommand) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: motionCommand
};
}
2016-07-25 16:59:01 +02:00
let motion = Mappings.findMotion(input);
2016-07-15 12:39:15 +02:00
if (motion) {
let newPos = motion.run(editor.document, editor.selection.active, this._motionState);
if (this._isVisual) {
setSelectionAndReveal(editor, this._motionState.anchor, newPos.line, newPos.character);
} else {
// Mode.NORMAL
setPositionAndReveal(editor, newPos.line, newPos.character);
}
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
// is it motion building
2016-07-25 16:59:01 +02:00
if (this.isMotionPrefix(input)) {
2016-07-15 12:39:15 +02:00
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
2016-07-25 17:42:04 +02:00
// INVALID INPUT - beep!!
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: null
};
2016-07-15 12:39:15 +02:00
}
}
function setSelectionAndReveal(editor:TextEditor, anchor:Position, line: number, char: number): void {
editor.selection = new Selection(anchor, new Position(line, char));
revealPosition(editor, line, char);
}
function setPositionAndReveal(editor: TextEditor, line: number, char: number): void {
editor.selection = new Selection(new Position(line, char), new Position(line, char));
revealPosition(editor, line, char);
}
function revealPosition(editor: TextEditor, line: number, char: number): void {
editor.revealRange(new Range(line, char, line, char), TextEditorRevealType.Default);
}