Make all state transitions into Actions.

This commit is contained in:
johnfn 2016-05-29 21:59:07 -07:00
parent c6db2d5e49
commit 0b5296958e
10 changed files with 163 additions and 97 deletions

View File

@ -21,8 +21,6 @@ export function activate(context: vscode.ExtensionContext) {
return;
}
console.log(args.text);
var isHandled = await handleKeyEvent(args.text);
if (!isHandled) {

View File

@ -1,6 +1,7 @@
import { ModeHandler } from './../mode/modeHandler';
import { ModeName } from './../mode/mode';
import { Position } from './../motion/position';
import * as vscode from 'vscode';
export abstract class BaseAction {
/**
@ -45,11 +46,8 @@ export abstract class BaseMovement extends BaseAction {
export abstract class BaseCommand extends BaseAction {
/**
* Run the command.
*
* TODO: The dream is to not pass in modeHandler, only motion.
* This is quite a far off dream, though.
*/
public abstract async exec(modeHandler: ModeHandler): Promise<void>;
public abstract async exec(modeHandler: ModeHandler, position: Position): Promise<Position>;
}
export class Actions {
@ -66,9 +64,9 @@ export class Actions {
* TODO - this is a great place for optional types, once
* Typescript 2.0 lands!
*/
public static getRelevantAction(keysPressed: string): BaseAction {
public static getRelevantAction(keysPressed: string, mode: ModeName): BaseAction {
for (const action of Actions.allActions) {
if (action.key === keysPressed) {
if (action.key === keysPressed && action.modes.indexOf(mode) !== -1) {
return action;
}
}
@ -184,8 +182,34 @@ class CommandEsc extends BaseCommand {
modes = [ModeName.Insert, ModeName.Visual, ModeName.VisualLine];
key = "<esc>";
public async exec(modeHandler: ModeHandler): Promise<void> {
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Normal);
return position;
}
}
@RegisterAction
class CommandVInVisualMode extends BaseCommand {
modes = [ModeName.Visual];
key = "v";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Normal);
return position;
}
}
@RegisterAction
class CommandVInNormalMode extends BaseCommand {
modes = [ModeName.Normal];
key = "v";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Visual);
return position;
}
}
@ -194,8 +218,90 @@ class CommandOpenSquareBracket extends BaseCommand {
modes = [ModeName.Insert, ModeName.Visual, ModeName.VisualLine];
key = "<c-[>";
public async exec(modeHandler: ModeHandler): Promise<void> {
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Normal);
return position;
}
}
// begin insert commands
@RegisterAction
class CommandInsertAtCursor extends BaseCommand {
modes = [ModeName.Normal];
key = "i";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Insert);
return position;
}
}
@RegisterAction
class CommandInsertAtLineBegin extends BaseCommand {
modes = [ModeName.Normal];
key = "I";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Insert);
return position.getLineBegin();
}
}
@RegisterAction
class CommandInsertAfterCursor extends BaseCommand {
modes = [ModeName.Normal];
key = "a";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Insert);
return position.getRight();
}
}
@RegisterAction
class CommandInsertAtLineEnd extends BaseCommand {
modes = [ModeName.Normal];
key = "A";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
modeHandler.setCurrentModeByName(ModeName.Insert);
return position.getLineEnd();
}
}
@RegisterAction
class CommandInsertNewLineAbove extends BaseCommand {
modes = [ModeName.Normal];
key = "O";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
// TODO: This code no good.
await vscode.commands.executeCommand("editor.action.insertLineBefore");
modeHandler.setCurrentModeByName(ModeName.Insert);
return position;
}
}
@RegisterAction
class CommandInsertNewLineBefore extends BaseCommand {
modes = [ModeName.Normal];
key = "o";
public async exec(modeHandler: ModeHandler, position: Position): Promise<Position> {
// TODO: This code no good.
await vscode.commands.executeCommand("editor.action.insertLineAfter");
modeHandler.setCurrentModeByName(ModeName.Insert);
return new Position(position.line + 1, 0, position.positionOptions);
}
}

View File

@ -54,8 +54,4 @@ export abstract class Mode {
public handleDeactivation() : void {
this._keyHistory = [];
}
abstract shouldBeActivated(key: string, currentMode: ModeName): boolean;
abstract handleActivation(key: string): Promise<void>;
}

View File

@ -66,9 +66,9 @@ export class ActionState {
public getAction(mode: ModeName): BaseAction {
for (let window = this.keysPressed.length; window > 0; window--) {
let keysPressed = _.takeRight(this.keysPressed, window).join('');
let action = Actions.getRelevantAction(keysPressed);
let action = Actions.getRelevantAction(keysPressed, mode);
if (action && action.modes.indexOf(mode) !== -1) {
if (action) {
this.keysPressed = [];
return action;
}
@ -86,6 +86,7 @@ export class ModeHandler implements vscode.Disposable {
constructor() {
this._configuration = Configuration.fromUserFile();
this._actionState = new ActionState();
this._motion = new Motion(null);
this._modes = [
new NormalMode(this._motion, this, this._configuration.commandKeyMap.normalModeKeyMap),
@ -99,17 +100,26 @@ export class ModeHandler implements vscode.Disposable {
/**
* The active mode.
*/
get currentMode() : Mode {
get currentMode(): Mode {
return this._modes.find(mode => mode.isActive);
}
setNormal() {
this.setCurrentModeByName(ModeName.Normal);
}
setCurrentModeByName(modeName: ModeName) {
let activeMode: Mode;
if (this.currentMode) {
this.currentMode.handleDeactivation();
}
// TODO actually making these into functions on modes
// like we used to have is a good idea.
setCurrentModeByName(modeName : ModeName) {
for (let mode of this._modes) {
mode.isActive = (mode.name === modeName);
if (mode.name === modeName) {
activeMode = mode;
}
mode.isActive = (mode.name === modeName)
}
switch (modeName) {
@ -120,43 +130,24 @@ export class ModeHandler implements vscode.Disposable {
case ModeName.Normal:
this._motion = this._motion.changeMode(MotionMode.Caret);
break;
case ModeName.Visual:
(activeMode as VisualMode).start();
break;
}
const statusBarText = (this.currentMode.name === ModeName.Normal) ? '' : ModeName[modeName];
this.setupStatusBarItem(statusBarText ? `-- ${statusBarText.toUpperCase()} --` : '');
}
async handleKeyEvent(key : string) : Promise<Boolean> {
async handleKeyEvent(key: string): Promise<Boolean> {
// Due to a limitation in Electron, en-US QWERTY char codes are used in international keyboards.
// We'll try to mitigate this problem until it's fixed upstream.
// https://github.com/Microsoft/vscode/issues/713
key = this._configuration.keyboardLayout.translate(key);
let currentModeName = this.currentMode.name;
let nextMode: Mode;
let inactiveModes = _.filter(this._modes, (m) => !m.isActive);
for (let mode of inactiveModes) {
if (mode.shouldBeActivated(key, currentModeName)) {
if (nextMode) {
console.error("More that one mode matched in handleKeyEvent!");
}
nextMode = mode;
}
}
if (nextMode) {
this.currentMode.handleDeactivation();
this.setCurrentModeByName(nextMode.name);
await nextMode.handleActivation(key);
this._actionState = new ActionState();
return true;
}
this._actionState.keysPressed.push(key);
@ -166,7 +157,7 @@ export class ModeHandler implements vscode.Disposable {
if (!action && currentModeName === ModeName.Insert) {
// TODO: Slightly janky, for reasons that are hard to describe.
await this.currentMode.handleAction(this._actionState);
await (this.currentMode as any).handleAction(this._actionState);
this._actionState = new ActionState();
return true;
@ -206,7 +197,9 @@ export class ModeHandler implements vscode.Disposable {
if (readyToExecute) {
if (this._actionState.command) {
await this._actionState.command.exec(this);
let newPosition = await this._actionState.command.exec(this, this._motion.position);
this._motion.moveTo(newPosition.line, newPosition.character);
} else if (this._actionState.operator) {
await this._actionState.operator.run(this, this._actionState.motionStart, this._actionState.motionStop);
} else {

View File

@ -9,42 +9,10 @@ import { Motion } from './../motion/motion';
import { ActionState } from './modeHandler';
export class InsertMode extends Mode {
protected handleActivationKey(command : Command) : (motion: Motion) => Promise<{}> {
switch ( command ) {
case Command.InsertAtCursor:
return async (c) => { return c.move(); };
case Command.InsertAtLineBegin:
return async (c) => { return c.lineBegin().move(); };
case Command.InsertAfterCursor:
return async (c) => { return c.right().move(); };
case Command.InsertAtLineEnd:
return async (c) => { return c.lineEnd().move(); };
case Command.InsertNewLineBelow:
return async () => {
return await vscode.commands.executeCommand("editor.action.insertLineAfter");
};
case Command.InsertNewLineAbove:
return async () => {
return await vscode.commands.executeCommand("editor.action.insertLineBefore");
};
default:
return async () => { return {}; };
}
}
constructor(motion: Motion, keymap : CommandKeyHandler) {
constructor(motion: Motion, keymap : CommandKeyHandler) {
super(ModeName.Insert, motion, keymap);
}
shouldBeActivated(key: string, currentMode : ModeName) : boolean {
return key in this._keymap && currentMode === ModeName.Normal;
}
async handleActivation(key: string): Promise<void> {
let command : Command = this._keymap[key];
await this.handleActivationKey(command)(this.motion);
}
async handleAction(action: ActionState): Promise<void> {
// TODO: REALLY dumb, especially since there are actually actions
// that work in insert mode.

View File

@ -15,10 +15,4 @@ export class NormalMode extends Mode {
this._modeHandler = modeHandler;
}
shouldBeActivated(key: string, currentMode: ModeName) : boolean {
return ((key === "v" && currentMode === ModeName.Visual));
}
async handleActivation(key: string): Promise<void> { ; }
}

View File

@ -40,12 +40,7 @@ export class VisualMode extends Mode {
this._modeHandler = modeHandler;
}
shouldBeActivated(key: string, currentMode: ModeName): boolean {
let command : Command = this._keymap[key];
return command === Command.EnterVisualMode && currentMode === ModeName.Normal;
}
async handleActivation(key: string): Promise<void> {
public start(): void {
this._selectionStart = this.motion.position;
this._selectionStop = this._selectionStart;

View File

@ -20,6 +20,6 @@ export class YankOperator extends BaseOperator {
await TextEditor.copy(new vscode.Range(start, end))
modeHandler.currentMode.motion.select(end, end);
modeHandler.setNormal();
modeHandler.setCurrentModeByName(ModeName.Normal);
}
}

View File

@ -24,6 +24,13 @@ suite("Mode Visual", () => {
teardown(cleanUpWorkspace);
test("I broke visual mode tests", async () => {
assert(false, "I BROKE THEM!");
});
/*
TODO
test("can be activated", () => {
assert.equal(visualMode.shouldBeActivated("v", ModeName.Normal), true, "v didn't trigger visual mode...");
assert.equal(visualMode.shouldBeActivated("v", ModeName.Insert), false, "activated from insert mode");
@ -116,4 +123,7 @@ suite("Mode Visual", () => {
assert.equal(((visualMode as any)._modeHandler as ModeHandler).currentMode.name, ModeName.Insert);
});
*/
});

View File

@ -14,11 +14,16 @@ suite("put operator", () => {
suiteTeardown(cleanUpWorkspace);
test ("stuff doesnt work", () => {
assert(false);
});
/*
test("put 'the dog' into empty file", async () => {
const expectedText = "the dog";
const position = new Position(0, 0, PositionOptions.CharacterWiseExclusive);
const mode = new ModeHandler();
const put = new PutOperator(mode);
const put = new PutOperator();
Register.put(expectedText);
@ -39,7 +44,7 @@ suite("put operator", () => {
const expectedText = `the ${phrase}dog`;
const position = new Position(0, 3, PositionOptions.CharacterWiseExclusive);
const mode = new ModeHandler();
const put = new PutOperator(mode);
const put = new PutOperator();
Register.put(phrase);
@ -60,4 +65,5 @@ suite("put operator", () => {
assert.equal(cursorPosition.character, position.getRight().character,
"cursor should be on start of put content");
});
*/
});