Run format on repo

This commit is contained in:
Matt Bierner
2022-11-04 14:27:02 -07:00
parent 0db7faa03c
commit 2f83557a56
97 changed files with 26331 additions and 26329 deletions

View File

@ -1,51 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MotionState, Motion } from './motions';
export enum Mode {
INSERT,
NORMAL,
REPLACE
}
export interface ModifierKeys {
ctrl?: boolean;
alt?: boolean;
shifit?: boolean;
}
export class DeleteRegister {
public isWholeLine: boolean;
public content: string;
constructor(isWholeLine: boolean, content: string) {
this.isWholeLine = isWholeLine;
this.content = content;
}
}
export interface IController {
motionState: MotionState;
setMode(mode: Mode): void;
setVisual(newVisual: boolean): void;
findMotion(input: string): Motion;
isMotionPrefix(input: string): boolean;
setDeleteRegister(register: DeleteRegister): void;
getDeleteRegister(): DeleteRegister;
}
export abstract class AbstractCommandDescriptor {
public abstract createCommand(args?: any): Command;
}
export interface Command {
commandId: string;
args?: any[];
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MotionState, Motion } from './motions';
export enum Mode {
INSERT,
NORMAL,
REPLACE
}
export interface ModifierKeys {
ctrl?: boolean;
alt?: boolean;
shifit?: boolean;
}
export class DeleteRegister {
public isWholeLine: boolean;
public content: string;
constructor(isWholeLine: boolean, content: string) {
this.isWholeLine = isWholeLine;
this.content = content;
}
}
export interface IController {
motionState: MotionState;
setMode(mode: Mode): void;
setVisual(newVisual: boolean): void;
findMotion(input: string): Motion;
isMotionPrefix(input: string): boolean;
setDeleteRegister(register: DeleteRegister): void;
getDeleteRegister(): DeleteRegister;
}
export abstract class AbstractCommandDescriptor {
public abstract createCommand(args?: any): Command;
}
export interface Command {
commandId: string;
args?: any[];
}

View File

@ -1,303 +1,303 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
import { Words } from './words';
import { MotionState, Motion } from './motions';
import { Mode, IController, DeleteRegister, Command, ModifierKeys } 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;
}
if (this._isVisual) {
return;
}
const sel = editor.selection;
const pos = sel.active;
const doc = editor.document;
const lineContent = doc.lineAt(pos.line).text;
if (lineContent.length === 0) {
return;
}
const maxCharacter = lineContent.length - 1;
if (pos.character > maxCharacter) {
setPositionAndReveal(editor, pos.line, maxCharacter);
}
}
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 {
const label = this._getModeLabel();
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
}
private _isInComposition = false;
private _composingText = '';
public compositionStart(editor: TextEditor): void {
this._isInComposition = true;
this._composingText = '';
}
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
this._isInComposition = false;
const text = this._composingText;
this._composingText = '';
if (text.length === 0) {
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
return this.type(editor, text, {});
}
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
return Promise.resolve({
hasConsumedInput: false,
executeEditorCommand: null
});
}
if (this._isInComposition) {
this._composingText += text;
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
if (this._currentMode === Mode.REPLACE) {
const 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);
});
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
this._currentInput += text;
return this._interpretNormalModeInput(editor, modifierKeys);
}
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
return false;
}
if (this._isInComposition) {
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
return true;
}
if (this._currentMode === Mode.REPLACE) {
const pos = editor.selection.active;
editor.edit((builder) => {
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
});
return true;
}
return true;
}
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
if (this._currentInput.startsWith(':')) {
return window.showInputBox({ value: 'tabm' }).then((value) => {
return this._findMapping(value || '', editor, modifierKeys);
});
}
const result = this._findMapping(this._currentInput, editor, modifierKeys);
return Promise.resolve(result);
}
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
const command = Mappings.findCommand(input, modifierKeys);
if (command) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: command
};
}
const operator = Mappings.findOperator(input, modifierKeys);
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
};
}
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
if (motionCommand) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: motionCommand
};
}
const motion = Mappings.findMotion(input);
if (motion) {
const 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
if (this.isMotionPrefix(input)) {
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
// INVALID INPUT - beep!!
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
}
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);
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextEditorCursorStyle, Position, Range, Selection, TextEditor, TextEditorRevealType, window } from 'vscode';
import { Words } from './words';
import { MotionState, Motion } from './motions';
import { Mode, IController, DeleteRegister, Command, ModifierKeys } 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;
}
if (this._isVisual) {
return;
}
const sel = editor.selection;
const pos = sel.active;
const doc = editor.document;
const lineContent = doc.lineAt(pos.line).text;
if (lineContent.length === 0) {
return;
}
const maxCharacter = lineContent.length - 1;
if (pos.character > maxCharacter) {
setPositionAndReveal(editor, pos.line, maxCharacter);
}
}
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 {
const label = this._getModeLabel();
return `VIM:> ${label}` + (this._currentInput ? ` >${this._currentInput}` : ``);
}
private _isInComposition = false;
private _composingText = '';
public compositionStart(editor: TextEditor): void {
this._isInComposition = true;
this._composingText = '';
}
public compositionEnd(editor: TextEditor): Thenable<ITypeResult> {
this._isInComposition = false;
const text = this._composingText;
this._composingText = '';
if (text.length === 0) {
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
return this.type(editor, text, {});
}
public type(editor: TextEditor, text: string, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
return Promise.resolve({
hasConsumedInput: false,
executeEditorCommand: null
});
}
if (this._isInComposition) {
this._composingText += text;
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
if (this._currentMode === Mode.REPLACE) {
const 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);
});
return Promise.resolve({
hasConsumedInput: true,
executeEditorCommand: null
});
}
this._currentInput += text;
return this._interpretNormalModeInput(editor, modifierKeys);
}
public replacePrevChar(editor: TextEditor, text: string, replaceCharCnt: number): boolean {
if (this._currentMode !== Mode.NORMAL && this._currentMode !== Mode.REPLACE) {
return false;
}
if (this._isInComposition) {
this._composingText = this._composingText.substr(0, this._composingText.length - replaceCharCnt) + text;
return true;
}
if (this._currentMode === Mode.REPLACE) {
const pos = editor.selection.active;
editor.edit((builder) => {
builder.replace(new Range(pos.line, pos.character - replaceCharCnt, pos.line, pos.character), text);
});
return true;
}
return true;
}
private _interpretNormalModeInput(editor: TextEditor, modifierKeys: ModifierKeys): Thenable<ITypeResult> {
if (this._currentInput.startsWith(':')) {
return window.showInputBox({ value: 'tabm' }).then((value) => {
return this._findMapping(value || '', editor, modifierKeys);
});
}
const result = this._findMapping(this._currentInput, editor, modifierKeys);
return Promise.resolve(result);
}
private _findMapping(input: string, editor: TextEditor, modifierKeys: ModifierKeys): ITypeResult {
const command = Mappings.findCommand(input, modifierKeys);
if (command) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: command
};
}
const operator = Mappings.findOperator(input, modifierKeys);
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
};
}
const motionCommand = Mappings.findMotionCommand(input, this._isVisual, modifierKeys);
if (motionCommand) {
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: motionCommand
};
}
const motion = Mappings.findMotion(input);
if (motion) {
const 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
if (this.isMotionPrefix(input)) {
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
// INVALID INPUT - beep!!
this._currentInput = '';
return {
hasConsumedInput: true,
executeEditorCommand: null
};
}
}
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);
}

View File

@ -13,7 +13,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand(commandId, run));
}
function registerCtrlKeyBinding(key: string): void {
registerCommandNice(key, function (args) {
registerCommandNice(key, function(args) {
if (!vscode.window.activeTextEditor) {
return;
}
@ -23,34 +23,34 @@ export function activate(context: vscode.ExtensionContext) {
const vimExt = new VimExt();
registerCommandNice('type', function (args) {
registerCommandNice('type', function(args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.type(args.text);
});
registerCommandNice('replacePreviousChar', function (args) {
registerCommandNice('replacePreviousChar', function(args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.replacePrevChar(args.text, args.replaceCharCnt);
});
registerCommandNice('compositionStart', function (args) {
registerCommandNice('compositionStart', function(args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.compositionStart();
});
registerCommandNice('compositionEnd', function (args) {
registerCommandNice('compositionEnd', function(args) {
if (!vscode.window.activeTextEditor) {
return;
}
vimExt.compositionEnd();
});
registerCommandNice('vim.goToNormalMode', function (args) {
registerCommandNice('vim.goToNormalMode', function(args) {
vimExt.goToNormalMode();
});
registerCommandNice('vim.clearInput', function (args) {
registerCommandNice('vim.clearInput', function(args) {
vimExt.clearInput();
});
// registerCommandNice('paste', function(args) {

View File

@ -1,232 +1,232 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextEditor } from 'vscode';
import { Motion, Motions } from './motions';
import { Operator, Operators } from './operators';
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
const CHAR_TO_BINDING: { [char: string]: any; } = {};
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
CHAR_TO_BINDING[key] = value;
}
function getBinding(char: string, modifierKeys: ModifierKeys): any {
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
return CHAR_TO_BINDING[key];
}
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__operator__', operator, modifierKeys);
}
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
return getBinding(char + '__operator__', modifierKeys);
}
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
}
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
return getBinding(char + '__command__', modifierKeys);
}
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__motion__', motion, modifierKeys);
}
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
return getBinding(char + '__motion__', modifierKeys);
}
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
}
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
return getBinding(char + '__motioncommand__', modifierKeys);
}
// Operators
defineOperator('x', Operators.DeleteCharUnderCursor);
defineOperator('i', Operators.Insert);
defineOperator('a', Operators.Append);
defineOperator('A', Operators.AppendEndOfLine);
defineOperator('d', Operators.DeleteTo);
defineOperator('p', Operators.Put);
defineOperator('r', Operators.Replace);
defineOperator('R', Operators.ReplaceMode);
defineOperator('c', Operators.Change);
defineOperator('v', Operators.Visual);
// Commands
defineCommand('u', 'undo');
defineCommand('U', 'undo');
// Left-right motions
defineMotionCommand('h', Motions.Left);
defineMotionCommand('l', Motions.Right);
defineMotion('0', Motions.StartOfLine);
defineMotion('$', Motions.EndOfLine);
defineMotionCommand('g0', Motions.WrappedLineStart);
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
defineMotionCommand('g$', Motions.WrappedLineEnd);
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
// Cursor scroll motions
defineMotionCommand('zh', Motions.CursorScrollLeft);
defineMotionCommand('zl', Motions.CursorScrollRight);
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
// Up-down motions
defineMotionCommand('j', Motions.Down);
defineMotionCommand('k', Motions.Up);
defineMotionCommand('gj', Motions.WrappedLineDown);
defineMotionCommand('gk', Motions.WrappedLineUp);
defineMotion('G', Motions.GoToLine);
defineMotion('gg', Motions.GoToFirstLine);
defineMotionCommand('H', Motions.ViewPortTop);
defineMotionCommand('M', Motions.ViewPortCenter);
defineMotionCommand('L', Motions.ViewPortBottom);
// Word motions
defineMotion('w', Motions.NextWordStart);
defineMotion('e', Motions.NextWordEnd);
// Tab motions
defineMotionCommand('tabm', Motions.MoveActiveEditor);
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
// Scroll motions
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
// Folding
defineMotionCommand('zc', Motions.FoldUnder);
defineMotionCommand('zo', Motions.UnfoldUnder);
export interface IFoundOperator {
runNormal(controller: IController, editor: TextEditor): boolean;
runVisual(controller: IController, editor: TextEditor): boolean;
}
export class Mappings {
public static findMotion(input: string): Motion {
const parsed = _parseNumberAndString(input);
let motion = getMotion(parsed.input.substr(0, 1));
if (!motion) {
motion = getMotion(parsed.input.substr(0, 2));
if (!motion) {
return null;
}
}
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
}
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
let parsed = _parseNumberAndString(input);
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
if (!command) {
parsed = _parseNumberAndString(input, false);
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
}
return command;
}
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
}
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
}
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
const parsed = _parseNumberAndString(input);
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
if (!operator) {
return null;
}
const operatorArgs = parsed.input.substr(1);
return {
runNormal: (controller: IController, editor: TextEditor) => {
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
},
runVisual: (controller: IController, editor: TextEditor) => {
return operator.runVisualMode(controller, editor, operatorArgs);
}
};
}
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
return getCommand(input, modifierKeys) || null;
}
public static isMotionPrefix(input: string): boolean {
if (input.length === 0) {
return true;
}
if (input === 'g' || input === 'v' || input === 'z') {
return true;
}
return /^[1-9]\d*v?g?z?$/.test(input);
}
}
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
if (numberAtBeginning) {
const repeatCountMatch = input.match(/^([1-9]\d*)/);
if (repeatCountMatch) {
return {
hasRepeatCount: true,
repeatCount: parseInt(repeatCountMatch[0], 10),
input: input.substr(repeatCountMatch[0].length)
};
}
} else {
const repeatCountMatch = input.match(/(\d+)$/);
if (repeatCountMatch) {
return {
hasRepeatCount: true,
repeatCount: parseInt(repeatCountMatch[1], 10),
input: input.substr(0, input.length - repeatCountMatch[1].length)
};
}
}
return {
hasRepeatCount: false,
repeatCount: 1,
input: input
};
}
interface INumberAndString {
hasRepeatCount: boolean;
repeatCount: number;
input: string;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextEditor } from 'vscode';
import { Motion, Motions } from './motions';
import { Operator, Operators } from './operators';
import { IController, Command, AbstractCommandDescriptor, ModifierKeys } from './common';
const CHAR_TO_BINDING: { [char: string]: any; } = {};
function defineBinding(char: string, value: any, modifierKeys: ModifierKeys): void {
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
CHAR_TO_BINDING[key] = value;
}
function getBinding(char: string, modifierKeys: ModifierKeys): any {
const key = modifierKeys.ctrl ? 'CTRL + ' + char : char;
return CHAR_TO_BINDING[key];
}
function defineOperator(char: string, operator: Operator, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__operator__', operator, modifierKeys);
}
function getOperator(char: string, modifierKeys: ModifierKeys = {}): Operator {
return getBinding(char + '__operator__', modifierKeys);
}
function defineCommand(char: string, commandId: string, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__command__', { commandId: commandId }, modifierKeys);
}
function getCommand(char: string, modifierKeys: ModifierKeys = {}): Command {
return getBinding(char + '__command__', modifierKeys);
}
function defineMotion(char: string, motion: Motion, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__motion__', motion, modifierKeys);
}
function getMotion(char: string, modifierKeys: ModifierKeys = {}): Motion {
return getBinding(char + '__motion__', modifierKeys);
}
function defineMotionCommand(char: string, motionCommand: AbstractCommandDescriptor, modifierKeys: ModifierKeys = {}): void {
defineBinding(char + '__motioncommand__', motionCommand, modifierKeys);
}
function getMotionCommand(char: string, modifierKeys: ModifierKeys = {}): AbstractCommandDescriptor {
return getBinding(char + '__motioncommand__', modifierKeys);
}
// Operators
defineOperator('x', Operators.DeleteCharUnderCursor);
defineOperator('i', Operators.Insert);
defineOperator('a', Operators.Append);
defineOperator('A', Operators.AppendEndOfLine);
defineOperator('d', Operators.DeleteTo);
defineOperator('p', Operators.Put);
defineOperator('r', Operators.Replace);
defineOperator('R', Operators.ReplaceMode);
defineOperator('c', Operators.Change);
defineOperator('v', Operators.Visual);
// Commands
defineCommand('u', 'undo');
defineCommand('U', 'undo');
// Left-right motions
defineMotionCommand('h', Motions.Left);
defineMotionCommand('l', Motions.Right);
defineMotion('0', Motions.StartOfLine);
defineMotion('$', Motions.EndOfLine);
defineMotionCommand('g0', Motions.WrappedLineStart);
defineMotionCommand('g^', Motions.WrappedLineFirstNonWhiteSpaceCharacter);
defineMotionCommand('gm', Motions.WrappedLineColumnCenter);
defineMotionCommand('g$', Motions.WrappedLineEnd);
defineMotionCommand('g_', Motions.WrappedLineLastNonWhiteSpaceCharacter);
// Cursor scroll motions
defineMotionCommand('zh', Motions.CursorScrollLeft);
defineMotionCommand('zl', Motions.CursorScrollRight);
defineMotionCommand('zH', Motions.CursorScrollLeftByHalfLine);
defineMotionCommand('zL', Motions.CursorScrollRightByHalfLine);
// Up-down motions
defineMotionCommand('j', Motions.Down);
defineMotionCommand('k', Motions.Up);
defineMotionCommand('gj', Motions.WrappedLineDown);
defineMotionCommand('gk', Motions.WrappedLineUp);
defineMotion('G', Motions.GoToLine);
defineMotion('gg', Motions.GoToFirstLine);
defineMotionCommand('H', Motions.ViewPortTop);
defineMotionCommand('M', Motions.ViewPortCenter);
defineMotionCommand('L', Motions.ViewPortBottom);
// Word motions
defineMotion('w', Motions.NextWordStart);
defineMotion('e', Motions.NextWordEnd);
// Tab motions
defineMotionCommand('tabm', Motions.MoveActiveEditor);
defineMotionCommand('tabm<', Motions.MoveActiveEditorLeft);
defineMotionCommand('tabm>', Motions.MoveActiveEditorRight);
defineMotionCommand('tabm<<', Motions.MoveActiveEditorFirst);
defineMotionCommand('tabm>>', Motions.MoveActiveEditorLast);
defineMotionCommand('tabm.', Motions.MoveActiveEditorCenter);
// Scroll motions
defineMotionCommand('e', Motions.ScrollDownByLine, { ctrl: true });
defineMotionCommand('d', Motions.ScrollDownByHalfPage, { ctrl: true });
defineMotionCommand('f', Motions.ScrollDownByPage, { ctrl: true });
defineMotionCommand('y', Motions.ScrollUpByLine, { ctrl: true });
defineMotionCommand('u', Motions.ScrollUpByHalfPage, { ctrl: true });
defineMotionCommand('b', Motions.ScrollUpByPage, { ctrl: true });
defineMotionCommand('zt', Motions.RevealCurrentLineAtTop);
defineMotionCommand('zz', Motions.RevealCurrentLineAtCenter);
defineMotionCommand('zb', Motions.RevealCurrentLineAtBottom);
// Folding
defineMotionCommand('zc', Motions.FoldUnder);
defineMotionCommand('zo', Motions.UnfoldUnder);
export interface IFoundOperator {
runNormal(controller: IController, editor: TextEditor): boolean;
runVisual(controller: IController, editor: TextEditor): boolean;
}
export class Mappings {
public static findMotion(input: string): Motion {
const parsed = _parseNumberAndString(input);
let motion = getMotion(parsed.input.substr(0, 1));
if (!motion) {
motion = getMotion(parsed.input.substr(0, 2));
if (!motion) {
return null;
}
}
return motion.repeat(parsed.hasRepeatCount, parsed.repeatCount);
}
public static findMotionCommand(input: string, isVisual: boolean, modifierKeys: ModifierKeys): Command {
let parsed = _parseNumberAndString(input);
let command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
if (!command) {
parsed = _parseNumberAndString(input, false);
command = Mappings.findMotionCommandFromNumberAndString(parsed, isVisual, modifierKeys);
}
return command;
}
private static findMotionCommandFromNumberAndString(numberAndString: INumberAndString, isVisual: boolean, modifierKeys: ModifierKeys): Command {
let motionCommand = getMotionCommand(numberAndString.input.substr(0, 1), modifierKeys);
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(0, 2), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(1, 2), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input.substr(1, 3), modifierKeys);
}
if (!motionCommand) {
motionCommand = getMotionCommand(numberAndString.input, modifierKeys);
}
return motionCommand ? motionCommand.createCommand({ isVisual: isVisual, repeat: numberAndString.hasRepeatCount ? numberAndString.repeatCount : undefined }) : null;
}
public static findOperator(input: string, modifierKeys: ModifierKeys): IFoundOperator {
const parsed = _parseNumberAndString(input);
const operator = getOperator(parsed.input.substr(0, 1), modifierKeys);
if (!operator) {
return null;
}
const operatorArgs = parsed.input.substr(1);
return {
runNormal: (controller: IController, editor: TextEditor) => {
return operator.runNormalMode(controller, editor, parsed.repeatCount, operatorArgs);
},
runVisual: (controller: IController, editor: TextEditor) => {
return operator.runVisualMode(controller, editor, operatorArgs);
}
};
}
public static findCommand(input: string, modifierKeys: ModifierKeys): Command {
return getCommand(input, modifierKeys) || null;
}
public static isMotionPrefix(input: string): boolean {
if (input.length === 0) {
return true;
}
if (input === 'g' || input === 'v' || input === 'z') {
return true;
}
return /^[1-9]\d*v?g?z?$/.test(input);
}
}
function _parseNumberAndString(input: string, numberAtBeginning = true): INumberAndString {
if (numberAtBeginning) {
const repeatCountMatch = input.match(/^([1-9]\d*)/);
if (repeatCountMatch) {
return {
hasRepeatCount: true,
repeatCount: parseInt(repeatCountMatch[0], 10),
input: input.substr(repeatCountMatch[0].length)
};
}
} else {
const repeatCountMatch = input.match(/(\d+)$/);
if (repeatCountMatch) {
return {
hasRepeatCount: true,
repeatCount: parseInt(repeatCountMatch[1], 10),
input: input.substr(0, input.length - repeatCountMatch[1].length)
};
}
}
return {
hasRepeatCount: false,
repeatCount: 1,
input: input
};
}
interface INumberAndString {
hasRepeatCount: boolean;
repeatCount: number;
input: string;
}

View File

@ -1,432 +1,432 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, TextDocument, window } from 'vscode';
import { Words, WordCharacters } from './words';
import { Command, AbstractCommandDescriptor } from './common';
export class MotionState {
public anchor: Position | null;
public cursorDesiredCharacter: number;
public wordCharacterClass: WordCharacters | null;
constructor() {
this.cursorDesiredCharacter = -1;
this.wordCharacterClass = null;
this.anchor = null;
}
}
export abstract class Motion {
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
public repeat(hasRepeatCount: boolean, count: number): Motion {
if (!hasRepeatCount) {
return this;
}
return new RepeatingMotion(this, count);
}
}
class RepeatingMotion extends Motion {
private _actual: Motion;
private _repeatCount: number;
constructor(actual: Motion, repeatCount: number) {
super();
this._actual = actual;
this._repeatCount = repeatCount;
}
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
pos = this._actual.run(doc, pos, state);
}
return pos;
}
}
class NextCharacterMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
if (pos.character === doc.lineAt(pos.line).text.length) {
// on last character
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
return new Position(pos.line, pos.character + 1);
}
}
class LeftMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = pos.line;
if (pos.character > 0) {
state.cursorDesiredCharacter = pos.character - 1;
return new Position(line, state.cursorDesiredCharacter);
}
return pos;
}
}
class DownMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
let line = pos.line;
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
if (line < doc.lineCount - 1) {
line++;
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
}
return pos;
}
}
class UpMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
let line = pos.line;
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
if (line > 0) {
line--;
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
}
return pos;
}
}
class RightMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = pos.line;
const maxCharacter = doc.lineAt(line).text.length;
if (pos.character < maxCharacter) {
state.cursorDesiredCharacter = pos.character + 1;
return new Position(line, state.cursorDesiredCharacter);
}
return pos;
}
}
class EndOfLineMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(pos.line, doc.lineAt(pos.line).text.length);
}
}
class StartOfLineMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(pos.line, 0);
}
}
class NextWordStartMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lineContent = doc.lineAt(pos.line).text;
if (pos.character >= lineContent.length - 1) {
// cursor at end of line
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
if (!nextWord) {
// return end of the line
return Motions.EndOfLine.run(doc, pos, state);
}
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
// Sitting on a word
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
if (nextNextWord) {
// return start of the next next word
return new Position(pos.line, nextNextWord.start);
} else {
// return end of line
return Motions.EndOfLine.run(doc, pos, state);
}
} else {
// return start of the next word
return new Position(pos.line, nextWord.start);
}
}
}
class NextWordEndMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lineContent = doc.lineAt(pos.line).text;
if (pos.character >= lineContent.length - 1) {
// no content on this line or cursor at end of line
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
if (!nextWord) {
// return end of the line
return Motions.EndOfLine.run(doc, pos, state);
}
// return start of the next word
return new Position(pos.line, nextWord.end);
}
}
class GoToLineUndefinedMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
// does not do anything
return pos;
}
public repeat(hasRepeatCount: boolean, count: number): Motion {
if (!hasRepeatCount) {
return Motions.GoToLastLine;
}
return new GoToLineDefinedMotion(count);
}
}
abstract class GoToLineMotion extends Motion {
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
const lineContent = doc.lineAt(line).text;
let character = 0;
while (character < lineContent.length) {
const ch = lineContent.charAt(character);
if (ch !== ' ' && ch !== '\t') {
break;
}
character++;
}
return character;
}
}
class GoToFirstLineMotion extends GoToLineMotion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
}
}
class GoToLastLineMotion extends GoToLineMotion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lastLine = doc.lineCount - 1;
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
}
}
class GoToLineDefinedMotion extends GoToLineMotion {
private _lineNumber: number;
constructor(lineNumber: number) {
super();
this._lineNumber = lineNumber;
}
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
return new Position(line, this.firstNonWhitespaceChar(doc, line));
}
}
class CursorMoveCommand extends AbstractCommandDescriptor {
constructor(private to: string, private by?: string) {
super();
}
public createCommand(args?: any): Command {
const cursorMoveArgs: any = {
to: this.to,
by: this.by,
value: args.repeat || 1,
select: !!args.isVisual
};
return {
commandId: 'cursorMove',
args: cursorMoveArgs
};
}
}
class EditorScrollCommand extends AbstractCommandDescriptor {
constructor(private to: string, private by?: string) {
super();
}
public createCommand(args?: any): Command {
const editorScrollArgs: any = {
to: this.to,
by: this.by,
value: args.repeat || 1,
revealCursor: true
};
return {
commandId: 'editorScroll',
args: editorScrollArgs
};
}
}
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
constructor(private at: string) {
super();
}
public createCommand(args?: any): Command {
const lineNumber = window.activeTextEditor.selection.start.line;
const revealLineArgs: any = {
lineNumber,
at: this.at
};
return {
commandId: 'revealLine',
args: revealLineArgs
};
}
}
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const moveActiveEditorArgs: any = {
to: args.repeat === void 0 ? 'last' : 'position',
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
};
return {
commandId: 'moveActiveEditor',
args: moveActiveEditorArgs
};
}
}
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
constructor(private to: string) {
super();
}
public createCommand(args?: any): Command {
const moveActiveEditorArgs: any = {
to: this.to,
value: args.repeat ? args.repeat : 1
};
return {
commandId: 'moveActiveEditor',
args: moveActiveEditorArgs
};
}
}
class FoldCommand extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const foldEditorArgs: any = {
levels: args.repeat ? args.repeat : 1,
direction: 'up'
};
return {
commandId: 'editor.fold',
args: foldEditorArgs
};
}
}
class UnfoldCommand extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const foldEditorArgs: any = {
levels: args.repeat ? args.repeat : 1,
direction: 'up'
};
return {
commandId: 'editor.unfold',
args: foldEditorArgs
};
}
}
export const Motions = {
RightMotion: new RightMotion(),
NextCharacter: new NextCharacterMotion(),
Left: new CursorMoveCommand('left'),
Right: new CursorMoveCommand('right'),
Down: new CursorMoveCommand('down'),
Up: new CursorMoveCommand('up'),
EndOfLine: new EndOfLineMotion(),
StartOfLine: new StartOfLineMotion(),
NextWordStart: new NextWordStartMotion(),
NextWordEnd: new NextWordEndMotion(),
GoToLine: new GoToLineUndefinedMotion(),
GoToFirstLine: new GoToFirstLineMotion(),
GoToLastLine: new GoToLastLineMotion(),
CursorScrollLeft: new CursorMoveCommand('left'),
CursorScrollRight: new CursorMoveCommand('right'),
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
ViewPortTop: new CursorMoveCommand('viewPortTop'),
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
FoldUnder: new FoldCommand(),
UnfoldUnder: new UnfoldCommand()
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, TextDocument, window } from 'vscode';
import { Words, WordCharacters } from './words';
import { Command, AbstractCommandDescriptor } from './common';
export class MotionState {
public anchor: Position | null;
public cursorDesiredCharacter: number;
public wordCharacterClass: WordCharacters | null;
constructor() {
this.cursorDesiredCharacter = -1;
this.wordCharacterClass = null;
this.anchor = null;
}
}
export abstract class Motion {
public abstract run(doc: TextDocument, pos: Position, state: MotionState): Position;
public repeat(hasRepeatCount: boolean, count: number): Motion {
if (!hasRepeatCount) {
return this;
}
return new RepeatingMotion(this, count);
}
}
class RepeatingMotion extends Motion {
private _actual: Motion;
private _repeatCount: number;
constructor(actual: Motion, repeatCount: number) {
super();
this._actual = actual;
this._repeatCount = repeatCount;
}
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
for (let cnt = 0; cnt < this._repeatCount; cnt++) {
pos = this._actual.run(doc, pos, state);
}
return pos;
}
}
class NextCharacterMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
if (pos.character === doc.lineAt(pos.line).text.length) {
// on last character
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
return new Position(pos.line, pos.character + 1);
}
}
class LeftMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = pos.line;
if (pos.character > 0) {
state.cursorDesiredCharacter = pos.character - 1;
return new Position(line, state.cursorDesiredCharacter);
}
return pos;
}
}
class DownMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
let line = pos.line;
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
if (line < doc.lineCount - 1) {
line++;
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
}
return pos;
}
}
class UpMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
let line = pos.line;
state.cursorDesiredCharacter = (state.cursorDesiredCharacter === -1 ? pos.character : state.cursorDesiredCharacter);
if (line > 0) {
line--;
return new Position(line, Math.min(state.cursorDesiredCharacter, doc.lineAt(line).text.length));
}
return pos;
}
}
class RightMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = pos.line;
const maxCharacter = doc.lineAt(line).text.length;
if (pos.character < maxCharacter) {
state.cursorDesiredCharacter = pos.character + 1;
return new Position(line, state.cursorDesiredCharacter);
}
return pos;
}
}
class EndOfLineMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(pos.line, doc.lineAt(pos.line).text.length);
}
}
class StartOfLineMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(pos.line, 0);
}
}
class NextWordStartMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lineContent = doc.lineAt(pos.line).text;
if (pos.character >= lineContent.length - 1) {
// cursor at end of line
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
if (!nextWord) {
// return end of the line
return Motions.EndOfLine.run(doc, pos, state);
}
if (nextWord.start <= pos.character && pos.character < nextWord.end) {
// Sitting on a word
const nextNextWord = Words.findNextWord(doc, new Position(pos.line, nextWord.end), state.wordCharacterClass);
if (nextNextWord) {
// return start of the next next word
return new Position(pos.line, nextNextWord.start);
} else {
// return end of line
return Motions.EndOfLine.run(doc, pos, state);
}
} else {
// return start of the next word
return new Position(pos.line, nextWord.start);
}
}
}
class NextWordEndMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lineContent = doc.lineAt(pos.line).text;
if (pos.character >= lineContent.length - 1) {
// no content on this line or cursor at end of line
return ((pos.line + 1 < doc.lineCount) ? new Position(pos.line + 1, 0) : pos);
}
const nextWord = Words.findNextWord(doc, pos, state.wordCharacterClass);
if (!nextWord) {
// return end of the line
return Motions.EndOfLine.run(doc, pos, state);
}
// return start of the next word
return new Position(pos.line, nextWord.end);
}
}
class GoToLineUndefinedMotion extends Motion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
// does not do anything
return pos;
}
public repeat(hasRepeatCount: boolean, count: number): Motion {
if (!hasRepeatCount) {
return Motions.GoToLastLine;
}
return new GoToLineDefinedMotion(count);
}
}
abstract class GoToLineMotion extends Motion {
protected firstNonWhitespaceChar(doc: TextDocument, line: number): number {
const lineContent = doc.lineAt(line).text;
let character = 0;
while (character < lineContent.length) {
const ch = lineContent.charAt(character);
if (ch !== ' ' && ch !== '\t') {
break;
}
character++;
}
return character;
}
}
class GoToFirstLineMotion extends GoToLineMotion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
return new Position(0, this.firstNonWhitespaceChar(doc, 0));
}
}
class GoToLastLineMotion extends GoToLineMotion {
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const lastLine = doc.lineCount - 1;
return new Position(lastLine, this.firstNonWhitespaceChar(doc, lastLine));
}
}
class GoToLineDefinedMotion extends GoToLineMotion {
private _lineNumber: number;
constructor(lineNumber: number) {
super();
this._lineNumber = lineNumber;
}
public run(doc: TextDocument, pos: Position, state: MotionState): Position {
const line = Math.min(doc.lineCount - 1, Math.max(0, this._lineNumber - 1));
return new Position(line, this.firstNonWhitespaceChar(doc, line));
}
}
class CursorMoveCommand extends AbstractCommandDescriptor {
constructor(private to: string, private by?: string) {
super();
}
public createCommand(args?: any): Command {
const cursorMoveArgs: any = {
to: this.to,
by: this.by,
value: args.repeat || 1,
select: !!args.isVisual
};
return {
commandId: 'cursorMove',
args: cursorMoveArgs
};
}
}
class EditorScrollCommand extends AbstractCommandDescriptor {
constructor(private to: string, private by?: string) {
super();
}
public createCommand(args?: any): Command {
const editorScrollArgs: any = {
to: this.to,
by: this.by,
value: args.repeat || 1,
revealCursor: true
};
return {
commandId: 'editorScroll',
args: editorScrollArgs
};
}
}
class RevealCurrentLineCommand extends AbstractCommandDescriptor {
constructor(private at: string) {
super();
}
public createCommand(args?: any): Command {
const lineNumber = window.activeTextEditor.selection.start.line;
const revealLineArgs: any = {
lineNumber,
at: this.at
};
return {
commandId: 'revealLine',
args: revealLineArgs
};
}
}
class MoveActiveEditorCommandByPosition extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const moveActiveEditorArgs: any = {
to: args.repeat === void 0 ? 'last' : 'position',
value: args.repeat !== void 0 ? args.repeat + 1 : undefined
};
return {
commandId: 'moveActiveEditor',
args: moveActiveEditorArgs
};
}
}
class MoveActiveEditorCommand extends AbstractCommandDescriptor {
constructor(private to: string) {
super();
}
public createCommand(args?: any): Command {
const moveActiveEditorArgs: any = {
to: this.to,
value: args.repeat ? args.repeat : 1
};
return {
commandId: 'moveActiveEditor',
args: moveActiveEditorArgs
};
}
}
class FoldCommand extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const foldEditorArgs: any = {
levels: args.repeat ? args.repeat : 1,
direction: 'up'
};
return {
commandId: 'editor.fold',
args: foldEditorArgs
};
}
}
class UnfoldCommand extends AbstractCommandDescriptor {
constructor() {
super();
}
public createCommand(args?: any): Command {
const foldEditorArgs: any = {
levels: args.repeat ? args.repeat : 1,
direction: 'up'
};
return {
commandId: 'editor.unfold',
args: foldEditorArgs
};
}
}
export const Motions = {
RightMotion: new RightMotion(),
NextCharacter: new NextCharacterMotion(),
Left: new CursorMoveCommand('left'),
Right: new CursorMoveCommand('right'),
Down: new CursorMoveCommand('down'),
Up: new CursorMoveCommand('up'),
EndOfLine: new EndOfLineMotion(),
StartOfLine: new StartOfLineMotion(),
NextWordStart: new NextWordStartMotion(),
NextWordEnd: new NextWordEndMotion(),
GoToLine: new GoToLineUndefinedMotion(),
GoToFirstLine: new GoToFirstLineMotion(),
GoToLastLine: new GoToLastLineMotion(),
CursorScrollLeft: new CursorMoveCommand('left'),
CursorScrollRight: new CursorMoveCommand('right'),
CursorScrollLeftByHalfLine: new CursorMoveCommand('left', 'halfLine'),
CursorScrollRightByHalfLine: new CursorMoveCommand('right', 'halfLine'),
WrappedLineUp: new CursorMoveCommand('up', 'wrappedLine'),
WrappedLineDown: new CursorMoveCommand('down', 'wrappedLine'),
WrappedLineStart: new CursorMoveCommand('wrappedLineStart'),
WrappedLineFirstNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineFirstNonWhitespaceCharacter'),
WrappedLineColumnCenter: new CursorMoveCommand('wrappedLineColumnCenter'),
WrappedLineEnd: new CursorMoveCommand('wrappedLineEnd'),
WrappedLineLastNonWhiteSpaceCharacter: new CursorMoveCommand('wrappedLineLastNonWhitespaceCharacter'),
ViewPortTop: new CursorMoveCommand('viewPortTop'),
ViewPortBottom: new CursorMoveCommand('viewPortBottom'),
ViewPortCenter: new CursorMoveCommand('viewPortCenter'),
MoveActiveEditor: new MoveActiveEditorCommandByPosition(),
MoveActiveEditorLeft: new MoveActiveEditorCommand('left'),
MoveActiveEditorRight: new MoveActiveEditorCommand('right'),
MoveActiveEditorFirst: new MoveActiveEditorCommand('first'),
MoveActiveEditorLast: new MoveActiveEditorCommand('last'),
MoveActiveEditorCenter: new MoveActiveEditorCommand('center'),
ScrollDownByLine: new EditorScrollCommand('down', 'line'),
ScrollDownByHalfPage: new EditorScrollCommand('down', 'halfPage'),
ScrollDownByPage: new EditorScrollCommand('down', 'page'),
ScrollUpByLine: new EditorScrollCommand('up', 'line'),
ScrollUpByHalfPage: new EditorScrollCommand('up', 'halfPage'),
ScrollUpByPage: new EditorScrollCommand('up', 'page'),
RevealCurrentLineAtTop: new RevealCurrentLineCommand('top'),
RevealCurrentLineAtCenter: new RevealCurrentLineCommand('center'),
RevealCurrentLineAtBottom: new RevealCurrentLineCommand('bottom'),
FoldUnder: new FoldCommand(),
UnfoldUnder: new UnfoldCommand()
};

View File

@ -1,343 +1,343 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Selection, Range, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
import { Motion, Motions } from './motions';
import { Mode, IController, DeleteRegister } from './common';
export abstract class Operator {
public abstract runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean;
public abstract runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean;
protected doc(ed: TextEditor): TextDocument {
return ed.document;
}
protected pos(ed: TextEditor): Position {
return ed.selection.active;
}
protected sel(ed: TextEditor): Selection {
return ed.selection;
}
protected setPosReveal(ed: TextEditor, line: number, char: number): void {
ed.selection = new Selection(new Position(line, char), new Position(line, char));
ed.revealRange(ed.selection, TextEditorRevealType.Default);
}
protected delete(ctrl: IController, ed: TextEditor, isWholeLine: boolean, range: Range): void {
ctrl.setDeleteRegister(new DeleteRegister(isWholeLine, ed.document.getText(range)));
ed.edit((builder) => {
builder.delete(range);
});
}
}
abstract class OperatorWithNoArgs extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
this._run(ctrl, ed);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this._run(ctrl, ed);
return true;
}
protected abstract _run(ctrl: IController, ed: TextEditor): void;
}
class InsertOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.setMode(Mode.INSERT);
}
}
class AppendOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.RightMotion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class AppendEndOfLineOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.EndOfLine.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class VisualOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.motionState.anchor = this.pos(ed);
ctrl.setVisual(true);
}
}
class DeleteCharUnderCursorOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const to = Motions.NextCharacter.repeat(repeatCount > 1, repeatCount).run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class DeleteLineOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const pos = this.pos(ed);
const doc = this.doc(ed);
let fromLine = pos.line;
let fromCharacter = 0;
let toLine = fromLine + repeatCount;
let toCharacter = 0;
if (toLine >= doc.lineCount - 1) {
// Deleting last line
toLine = doc.lineCount - 1;
toCharacter = doc.lineAt(toLine).text.length;
if (fromLine > 0) {
fromLine = fromLine - 1;
fromCharacter = doc.lineAt(fromLine).text.length;
}
}
this.delete(ctrl, ed, true, new Range(fromLine, fromCharacter, toLine, toCharacter));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
abstract class OperatorWithMotion extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const motion = ctrl.findMotion(args);
if (!motion) {
// is it motion building
if (ctrl.isMotionPrefix(args)) {
return false;
}
// INVALID INPUT - beep!!
return true;
}
return this._runNormalMode(ctrl, ed, motion.repeat(repeatCount > 1, repeatCount));
}
protected abstract _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean;
}
class DeleteToOperator extends OperatorWithMotion {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args === 'd') {
// dd
return Operators.DeleteLine.runNormalMode(ctrl, ed, repeatCount, args);
}
return super.runNormalMode(ctrl, ed, repeatCount, args);
}
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class PutOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return true;
}
let str = repeatString(register.content, repeatCount);
const pos = this.pos(ed);
if (!register.isWholeLine) {
ed.edit((builder) => {
builder.insert(new Position(pos.line, pos.character + 1), str);
});
return true;
}
const doc = this.doc(ed);
let insertLine = pos.line + 1;
let insertCharacter = 0;
if (insertLine >= doc.lineCount) {
// on last line
insertLine = doc.lineCount - 1;
insertCharacter = doc.lineAt(insertLine).text.length;
str = '\n' + str;
}
ed.edit((builder) => {
builder.insert(new Position(insertLine, insertCharacter), str);
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return false;
}
const str = register.content;
const sel = this.sel(ed);
ed.edit((builder) => {
builder.replace(sel, str);
});
return true;
}
}
class ReplaceOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const pos = this.pos(ed);
const toCharacter = pos.character + repeatCount;
if (toCharacter > doc.lineAt(pos).text.length) {
// invalid replace (beep!)
return true;
}
ed.edit((builder) => {
builder.replace(new Range(pos.line, pos.character, pos.line, toCharacter), repeatString(args, repeatCount));
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const sel = this.sel(ed);
const srcString = doc.getText(sel);
let dstString = '';
for (let i = 0; i < srcString.length; i++) {
const ch = srcString.charAt(i);
if (ch === '\r' || ch === '\n') {
dstString += ch;
} else {
dstString += args;
}
}
ed.edit((builder) => {
builder.replace(sel, dstString);
});
return true;
}
}
class ReplaceModeOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
ctrl.setMode(Mode.REPLACE);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this.delete(ctrl, ed, false, this.sel(ed));
ctrl.setMode(Mode.INSERT);
return true;
}
}
class ChangeOperator extends OperatorWithMotion {
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
ctrl.setMode(Mode.INSERT);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
ctrl.setMode(Mode.INSERT);
return true;
}
}
function repeatString(str: string, repeatCount: number): string {
let result = '';
for (let i = 0; i < repeatCount; i++) {
result += str;
}
return result;
}
export const Operators = {
Insert: new InsertOperator(),
Visual: new VisualOperator(),
Append: new AppendOperator(),
AppendEndOfLine: new AppendEndOfLineOperator(),
DeleteCharUnderCursor: new DeleteCharUnderCursorOperator(),
DeleteTo: new DeleteToOperator(),
DeleteLine: new DeleteLineOperator(),
Put: new PutOperator(),
Replace: new ReplaceOperator(),
Change: new ChangeOperator(),
ReplaceMode: new ReplaceModeOperator(),
};
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Selection, Range, TextDocument, TextEditor, TextEditorRevealType } from 'vscode';
import { Motion, Motions } from './motions';
import { Mode, IController, DeleteRegister } from './common';
export abstract class Operator {
public abstract runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean;
public abstract runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean;
protected doc(ed: TextEditor): TextDocument {
return ed.document;
}
protected pos(ed: TextEditor): Position {
return ed.selection.active;
}
protected sel(ed: TextEditor): Selection {
return ed.selection;
}
protected setPosReveal(ed: TextEditor, line: number, char: number): void {
ed.selection = new Selection(new Position(line, char), new Position(line, char));
ed.revealRange(ed.selection, TextEditorRevealType.Default);
}
protected delete(ctrl: IController, ed: TextEditor, isWholeLine: boolean, range: Range): void {
ctrl.setDeleteRegister(new DeleteRegister(isWholeLine, ed.document.getText(range)));
ed.edit((builder) => {
builder.delete(range);
});
}
}
abstract class OperatorWithNoArgs extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
this._run(ctrl, ed);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this._run(ctrl, ed);
return true;
}
protected abstract _run(ctrl: IController, ed: TextEditor): void;
}
class InsertOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.setMode(Mode.INSERT);
}
}
class AppendOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.RightMotion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class AppendEndOfLineOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
const newPos = Motions.EndOfLine.run(this.doc(ed), this.pos(ed), ctrl.motionState);
this.setPosReveal(ed, newPos.line, newPos.character);
ctrl.setMode(Mode.INSERT);
}
}
class VisualOperator extends OperatorWithNoArgs {
protected _run(ctrl: IController, ed: TextEditor): void {
ctrl.motionState.anchor = this.pos(ed);
ctrl.setVisual(true);
}
}
class DeleteCharUnderCursorOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const to = Motions.NextCharacter.repeat(repeatCount > 1, repeatCount).run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class DeleteLineOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const pos = this.pos(ed);
const doc = this.doc(ed);
let fromLine = pos.line;
let fromCharacter = 0;
let toLine = fromLine + repeatCount;
let toCharacter = 0;
if (toLine >= doc.lineCount - 1) {
// Deleting last line
toLine = doc.lineCount - 1;
toCharacter = doc.lineAt(toLine).text.length;
if (fromLine > 0) {
fromLine = fromLine - 1;
fromCharacter = doc.lineAt(fromLine).text.length;
}
}
this.delete(ctrl, ed, true, new Range(fromLine, fromCharacter, toLine, toCharacter));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
abstract class OperatorWithMotion extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const motion = ctrl.findMotion(args);
if (!motion) {
// is it motion building
if (ctrl.isMotionPrefix(args)) {
return false;
}
// INVALID INPUT - beep!!
return true;
}
return this._runNormalMode(ctrl, ed, motion.repeat(repeatCount > 1, repeatCount));
}
protected abstract _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean;
}
class DeleteToOperator extends OperatorWithMotion {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args === 'd') {
// dd
return Operators.DeleteLine.runNormalMode(ctrl, ed, repeatCount, args);
}
return super.runNormalMode(ctrl, ed, repeatCount, args);
}
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
return true;
}
}
class PutOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return true;
}
let str = repeatString(register.content, repeatCount);
const pos = this.pos(ed);
if (!register.isWholeLine) {
ed.edit((builder) => {
builder.insert(new Position(pos.line, pos.character + 1), str);
});
return true;
}
const doc = this.doc(ed);
let insertLine = pos.line + 1;
let insertCharacter = 0;
if (insertLine >= doc.lineCount) {
// on last line
insertLine = doc.lineCount - 1;
insertCharacter = doc.lineAt(insertLine).text.length;
str = '\n' + str;
}
ed.edit((builder) => {
builder.insert(new Position(insertLine, insertCharacter), str);
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const register = ctrl.getDeleteRegister();
if (!register) {
// No delete register - beep!!
return false;
}
const str = register.content;
const sel = this.sel(ed);
ed.edit((builder) => {
builder.replace(sel, str);
});
return true;
}
}
class ReplaceOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const pos = this.pos(ed);
const toCharacter = pos.character + repeatCount;
if (toCharacter > doc.lineAt(pos).text.length) {
// invalid replace (beep!)
return true;
}
ed.edit((builder) => {
builder.replace(new Range(pos.line, pos.character, pos.line, toCharacter), repeatString(args, repeatCount));
});
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
if (args.length === 0) {
// input not ready
return false;
}
const doc = this.doc(ed);
const sel = this.sel(ed);
const srcString = doc.getText(sel);
let dstString = '';
for (let i = 0; i < srcString.length; i++) {
const ch = srcString.charAt(i);
if (ch === '\r' || ch === '\n') {
dstString += ch;
} else {
dstString += args;
}
}
ed.edit((builder) => {
builder.replace(sel, dstString);
});
return true;
}
}
class ReplaceModeOperator extends Operator {
public runNormalMode(ctrl: IController, ed: TextEditor, repeatCount: number, args: string): boolean {
ctrl.setMode(Mode.REPLACE);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
this.delete(ctrl, ed, false, this.sel(ed));
ctrl.setMode(Mode.INSERT);
return true;
}
}
class ChangeOperator extends OperatorWithMotion {
protected _runNormalMode(ctrl: IController, ed: TextEditor, motion: Motion): boolean {
const to = motion.run(this.doc(ed), this.pos(ed), ctrl.motionState);
const from = this.pos(ed);
this.delete(ctrl, ed, false, new Range(from.line, from.character, to.line, to.character));
ctrl.setMode(Mode.INSERT);
return true;
}
public runVisualMode(ctrl: IController, ed: TextEditor, args: string): boolean {
const sel = this.sel(ed);
this.delete(ctrl, ed, false, sel);
ctrl.setMode(Mode.INSERT);
return true;
}
}
function repeatString(str: string, repeatCount: number): string {
let result = '';
for (let i = 0; i < repeatCount; i++) {
result += str;
}
return result;
}
export const Operators = {
Insert: new InsertOperator(),
Visual: new VisualOperator(),
Append: new AppendOperator(),
AppendEndOfLine: new AppendEndOfLineOperator(),
DeleteCharUnderCursor: new DeleteCharUnderCursorOperator(),
DeleteTo: new DeleteToOperator(),
DeleteLine: new DeleteLineOperator(),
Put: new PutOperator(),
Replace: new ReplaceOperator(),
Change: new ChangeOperator(),
ReplaceMode: new ReplaceModeOperator(),
};

View File

@ -1,104 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, TextDocument } from 'vscode';
export enum CharacterClass {
REGULAR = 0,
WORD_SEPARATOR = 1,
WHITESPACE = 2
}
export enum WordType {
NONE = 0,
SEPARATOR = 1,
REGULAR = 2
}
export type WordCharacters = CharacterClass[];
export interface IWord {
start: number;
end: number;
wordType: WordType;
}
export class Words {
public static createWordCharacters(wordSeparators: string): WordCharacters {
const result: CharacterClass[] = [];
// Make array fast for ASCII text
for (let chCode = 0; chCode < 256; chCode++) {
result[chCode] = CharacterClass.REGULAR;
}
for (let i = 0, len = wordSeparators.length; i < len; i++) {
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
}
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
return result;
}
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
const lineContent = doc.lineAt(pos.line).text;
let wordType = WordType.NONE;
const len = lineContent.length;
for (let chIndex = pos.character; chIndex < len; chIndex++) {
const chCode = lineContent.charCodeAt(chIndex);
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
if (chClass === CharacterClass.REGULAR) {
if (wordType === WordType.SEPARATOR) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
wordType = WordType.REGULAR;
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
if (wordType === WordType.REGULAR) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
wordType = WordType.SEPARATOR;
} else if (chClass === CharacterClass.WHITESPACE) {
if (wordType !== WordType.NONE) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
}
}
if (wordType !== WordType.NONE) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
}
return null;
}
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
const chCode = lineContent.charCodeAt(chIndex);
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
if (chClass === CharacterClass.WHITESPACE) {
return chIndex + 1;
}
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
return chIndex + 1;
}
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
return chIndex + 1;
}
}
return 0;
}
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
return { start: start, end: end, wordType: wordType };
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, TextDocument } from 'vscode';
export enum CharacterClass {
REGULAR = 0,
WORD_SEPARATOR = 1,
WHITESPACE = 2
}
export enum WordType {
NONE = 0,
SEPARATOR = 1,
REGULAR = 2
}
export type WordCharacters = CharacterClass[];
export interface IWord {
start: number;
end: number;
wordType: WordType;
}
export class Words {
public static createWordCharacters(wordSeparators: string): WordCharacters {
const result: CharacterClass[] = [];
// Make array fast for ASCII text
for (let chCode = 0; chCode < 256; chCode++) {
result[chCode] = CharacterClass.REGULAR;
}
for (let i = 0, len = wordSeparators.length; i < len; i++) {
result[wordSeparators.charCodeAt(i)] = CharacterClass.WORD_SEPARATOR;
}
result[' '.charCodeAt(0)] = CharacterClass.WHITESPACE;
result['\t'.charCodeAt(0)] = CharacterClass.WHITESPACE;
return result;
}
public static findNextWord(doc: TextDocument, pos: Position, wordCharacterClass: WordCharacters): IWord | null {
const lineContent = doc.lineAt(pos.line).text;
let wordType = WordType.NONE;
const len = lineContent.length;
for (let chIndex = pos.character; chIndex < len; chIndex++) {
const chCode = lineContent.charCodeAt(chIndex);
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
if (chClass === CharacterClass.REGULAR) {
if (wordType === WordType.SEPARATOR) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
wordType = WordType.REGULAR;
} else if (chClass === CharacterClass.WORD_SEPARATOR) {
if (wordType === WordType.REGULAR) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
wordType = WordType.SEPARATOR;
} else if (chClass === CharacterClass.WHITESPACE) {
if (wordType !== WordType.NONE) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, chIndex - 1), chIndex);
}
}
}
if (wordType !== WordType.NONE) {
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordCharacterClass, wordType, len - 1), len);
}
return null;
}
private static _findStartOfWord(lineContent: string, wordCharacterClass: WordCharacters, wordType: WordType, startIndex: number): number {
for (let chIndex = startIndex; chIndex >= 0; chIndex--) {
const chCode = lineContent.charCodeAt(chIndex);
const chClass = (wordCharacterClass[chCode] || CharacterClass.REGULAR);
if (chClass === CharacterClass.WHITESPACE) {
return chIndex + 1;
}
if (wordType === WordType.REGULAR && chClass === CharacterClass.WORD_SEPARATOR) {
return chIndex + 1;
}
if (wordType === WordType.SEPARATOR && chClass === CharacterClass.REGULAR) {
return chIndex + 1;
}
}
return 0;
}
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IWord {
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
return { start: start, end: end, wordType: wordType };
}
}