Skip to content

Commit

Permalink
Add visual mode.
Browse files Browse the repository at this point in the history
Convert test to async/await style.
  • Loading branch information
johnfn committed Feb 19, 2016
1 parent 9c9991b commit dbb0e36
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
out
node_modules
typings
*.swp
*.sw?
Binary file removed .swn
Binary file not shown.
Binary file removed .swo
Binary file not shown.
27 changes: 25 additions & 2 deletions src/mode/mode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

import {Motion} from './../motion/motion';
import {Position, PositionOptions} from './../motion/position';

export enum ModeName {
Normal,
Expand Down Expand Up @@ -45,9 +46,31 @@ export abstract class Mode {
this.keyHistory = [];
}

protected keyToNewPosition: { [key: string]: (motion: Position) => Promise<Position>; } = {
"h" : async (c) => { return c.getLeft(); },
"j" : async (c) => { return c.getDown(0); }, // TODO - 0 is incorrect here.
"k" : async (c) => { return c.getUp(0); }, // getDown/Up should, by default, maintain the current column.
"l" : async (c) => { return c.getRight(); },
// "^" : async () => { return vscode.commands.executeCommand("cursorHome"); },
"gg" : async (c) => {
return new Position(0, Position.getFirstNonBlankCharAtLine(0), null); },
"G" : async (c) => {
const lastLine = c.getDocumentEnd().line;

return new Position(lastLine, Position.getFirstNonBlankCharAtLine(lastLine), null);
},
"$" : async (c) => { return c.getLineEnd(); },
"0" : async (c) => { return c.getLineBegin(); },
"w" : async (c) => { return c.getWordRight(); },
"e" : async (c) => { return c.getCurrentWordEnd(); },
"b" : async (c) => { return c.getWordLeft(); },
"}" : async (c) => { return c.getCurrentParagraphEnd(); },
"{" : async (c) => { return c.getCurrentParagraphBeginning(); },
};

abstract shouldBeActivated(key : string, currentMode : ModeName) : boolean;

abstract handleActivation(key : string) : Promise<{}>;
abstract handleActivation(key : string) : Promise<void>;

abstract handleKeyEvent(key : string) : Promise<{}>;
abstract handleKeyEvent(key : string) : Promise<void>;
}
14 changes: 6 additions & 8 deletions src/mode/modeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Mode, ModeName} from './mode';
import {Motion, MotionMode} from './../motion/motion';
import {NormalMode} from './modeNormal';
import {InsertMode} from './modeInsert';
import {VisualMode} from './modeVisual';
import {Configuration} from '../configuration';

export class ModeHandler implements vscode.Disposable {
Expand All @@ -22,23 +23,20 @@ export class ModeHandler implements vscode.Disposable {
this._modes = [
new NormalMode(this._motion),
new InsertMode(this._motion),
new VisualMode(this._motion, this),
];

this.setCurrentModeByName(ModeName.Normal);
}

get currentMode() : Mode {
let currentMode = this._modes.find((mode, index) => {
return mode.isActive;
});

return currentMode;
return this._modes.find(mode => mode.isActive);
}

setCurrentModeByName(modeName : ModeName) {
this._modes.forEach(mode => {
for (let mode of this._modes) {
mode.isActive = (mode.name === modeName);
});
}

switch (modeName) {
case ModeName.Insert:
Expand Down Expand Up @@ -88,7 +86,7 @@ export class ModeHandler implements vscode.Disposable {
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
}

this._statusBarItem.text = (text) ? '-- ' + text + ' --' : '';
this._statusBarItem.text = text ? `--${text}--` : '';
this._statusBarItem.show();
}

Expand Down
9 changes: 4 additions & 5 deletions src/mode/modeInsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,15 @@ export class InsertMode extends Mode {
return key in this.activationKeyHandler;
}

handleActivation(key : string) : Promise<{}> {
return this.activationKeyHandler[key](this.motion);
async handleActivation(key : string): Promise<void> {
await this.activationKeyHandler[key](this.motion);
}

async handleKeyEvent(key : string) : Promise<{}> {
async handleKeyEvent(key : string) : Promise<void> {
this.keyHistory.push(key);

await TextEditor.insert(this.resolveKeyValue(key));

return vscode.commands.executeCommand("editor.action.triggerSuggest");
await vscode.commands.executeCommand("editor.action.triggerSuggest");
}

// Some keys have names that are different to their value.
Expand Down
12 changes: 6 additions & 6 deletions src/mode/modeNormal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {Motion} from './../motion/motion';
import {DeleteAction} from './../action/deleteAction';

export class NormalMode extends Mode {
private keyHandler : { [key : string] : (motion : Motion) => Promise<{}>; } = {
protected keyHandler : { [key : string] : (motion : Motion) => Promise<{}>; } = {
":" : async () => { return showCmdLine(""); },
"u" : async () => { return vscode.commands.executeCommand("undo"); },
"ctrl+r" : async () => { return vscode.commands.executeCommand("redo"); },
Expand Down Expand Up @@ -48,17 +48,17 @@ export class NormalMode extends Mode {
return (key === 'esc' || key === 'ctrl+[' || key === "ctrl+c");
}

async handleActivation(key : string): Promise<{}> {
async handleActivation(key : string): Promise<void> {
this.motion.left().move();

return this.motion;
await this.motion;
}

async handleKeyEvent(key : string): Promise<{}> {
async handleKeyEvent(key : string): Promise<void> {
this.keyHistory.push(key);

let keyHandled = false;
let keysPressed : string;
let keysPressed: string;

for (let window = this.keyHistory.length; window > 0; window--) {
keysPressed = _.takeRight(this.keyHistory, window).join('');
Expand All @@ -70,7 +70,7 @@ export class NormalMode extends Mode {

if (keyHandled) {
this.keyHistory = [];
return this.keyHandler[keysPressed](this.motion);
await this.keyHandler[keysPressed](this.motion);
}
}
}
117 changes: 117 additions & 0 deletions src/mode/modeVisual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
"use strict";

import * as vscode from 'vscode';
import * as _ from 'lodash'

import {ModeName, Mode} from './mode';
import {TextEditor} from './../textEditor';
import {Motion} from './../motion/motion';
import {Position, PositionOptions} from './../motion/position';
import { Operator } from './../operator/operator'
import { DeleteOperator } from './../operator/delete'
import { ModeHandler } from './modeHandler.ts'
import { ChangeOperator } from './../operator/change'

export class VisualMode extends Mode {
private _selectionStart: Position;
private _selectionStop : Position;
private _modeHandler : ModeHandler;

private _keysToOperators: { [key: string]: Operator };

constructor(motion: Motion, modeHandler: ModeHandler) {
super(ModeName.Visual, motion);

this._keysToOperators = {
// TODO: use DeleteOperator.key()

// TODO: Don't pass in mode handler to DeleteOperators,
// simply allow the operators to say what mode they transition into.
'd': new DeleteOperator(modeHandler),
'x': new DeleteOperator(modeHandler),
'c': new ChangeOperator(modeHandler),
}
}

shouldBeActivated(key: string, currentMode: ModeName): boolean {
return key === "v";
}

async handleActivation(key: string): Promise<void> {
this._selectionStart = this.motion.position;
this._selectionStop = this._selectionStart.getRight();

this.motion.selectTo(this._selectionStop);
}

handleDeactivation(): void {
super.handleDeactivation();

this.motion.moveTo(this._selectionStop.line, this._selectionStop.character);
}

/**
* TODO:
*
* Eventually, the following functions should be moved into a unified
* key handler and dispatcher thing.
*/

private async _handleMotion(): Promise<boolean> {
let keyHandled = false;
let keysPressed: string;

for (let window = this.keyHistory.length; window > 0; window--) {
keysPressed = _.takeRight(this.keyHistory, window).join('');
if (this.keyToNewPosition[keysPressed] !== undefined) {
keyHandled = true;
break;
}
}

if (keyHandled) {
this._selectionStop = await this.keyToNewPosition[keysPressed](this._selectionStop);

this.motion.moveTo(this._selectionStart.line, this._selectionStart.character);
this.motion.selectTo(this._selectionStop.getRight());

this.keyHistory = [];
}

return keyHandled;
}

private async _handleOperator(): Promise<boolean> {
let keyHandled = false;
let keysPressed: string;
let operator: Operator;

for (let window = this.keyHistory.length; window > 0; window--) {
keysPressed = _.takeRight(this.keyHistory, window).join('');
if (this._keysToOperators[keysPressed] !== undefined) {
operator = this._keysToOperators[keysPressed];
break;
}
}

if (operator) {
if (this._selectionStart.compareTo(this._selectionStop)) {
operator.run(this._selectionStart, this._selectionStop.getRight());
} else {
operator.run(this._selectionStop, this._selectionStart.getRight());
}
}

return !!operator;
}

async handleKeyEvent(key: string): Promise<void> {
this.keyHistory.push(key);

const wasMotion = await this._handleMotion();

if (!wasMotion) {
await this._handleOperator();
}
}
}
37 changes: 33 additions & 4 deletions src/motion/motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export class Motion implements vscode.Disposable {
return this._position;
}

public set position(val: Position) {
this._position = val;
}

public constructor(mode: MotionMode) {
// initialize to current position
let currentPosition = vscode.window.activeTextEditor.selection.active;
Expand All @@ -52,8 +56,10 @@ export class Motion implements vscode.Disposable {
let selection = e.selections[0];

if (selection) {
let line = selection.active.line;
let char = selection.active.character;
const whosFirst = selection.anchor.compareTo(selection.active);

let line = whosFirst > 0 ? selection.active.line : selection.anchor.line;
let char = whosFirst > 0 ? selection.active.character : selection.anchor.character;

if (this.position.line !== line ||
this.position.character !== char) {
Expand Down Expand Up @@ -100,7 +106,24 @@ export class Motion implements vscode.Disposable {
let selection = new vscode.Selection(this.position, this.position);
vscode.window.activeTextEditor.selection = selection;

let range = new vscode.Range(this.position, this.position.translate(0, 1));
this.highlightBlock(this.position);

return this;
}

/**
* Allows us to simulate a block cursor by highlighting a 1 character
* space at the provided position in a lighter color.
*/
private highlightBlock(start: Position): void {
this.highlightRange(start, start.getRight());
}

/**
* Highlights the range from start to end in the color of a block cursor.
*/
private highlightRange(start: Position, end: Position): void {
let range = new vscode.Range(start, end);
vscode.window.activeTextEditor.revealRange(range, vscode.TextEditorRevealType.InCenterIfOutsideViewport);

switch (this._motionMode) {
Expand All @@ -114,8 +137,14 @@ export class Motion implements vscode.Disposable {
vscode.window.activeTextEditor.setDecorations(this._caretDecoration, []);
break;
}
}

return this;
public selectTo(other: Position): void {
let selection = new vscode.Selection(this.position, other);

vscode.window.activeTextEditor.selection = selection;

this.highlightBlock(other.getLeft());
}

public left() : Motion {
Expand Down
28 changes: 28 additions & 0 deletions src/operator/change.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use strict";

import {Position, PositionOptions} from './../motion/position';
import { TextEditor } from './../textEditor'
import { Operator } from './Operator'
import { ModeHandler } from './../mode/modeHandler.ts'
import { ModeName } from './../mode/mode'

import * as vscode from 'vscode'

export class ChangeOperator {
private _modeHandler: ModeHandler;

constructor(modeHandler: ModeHandler) {
this._modeHandler = modeHandler;
}

public key(): string { return "d"; }

/**
* Run this operator on a range.
*/
public async run(start: Position, end: Position): Promise<void> {
await TextEditor.delete(new vscode.Range(start, end));

this._modeHandler.setCurrentModeByName(ModeName.Insert);
}
}
28 changes: 28 additions & 0 deletions src/operator/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use strict";

import {Position, PositionOptions} from './../motion/position';
import { TextEditor } from './../textEditor'
import { Operator } from './Operator'
import { ModeHandler } from './../mode/modeHandler.ts'
import { ModeName } from './../mode/mode'

import * as vscode from 'vscode'

export class DeleteOperator {
private _modeHandler: ModeHandler;

constructor(modeHandler: ModeHandler) {
this._modeHandler = modeHandler;
}

public key(): string { return "d"; }

/**
* Run this operator on a range.
*/
public async run(start: Position, end: Position): Promise<void> {
await TextEditor.delete(new vscode.Range(start, end));

this._modeHandler.setCurrentModeByName(ModeName.Normal);
}
}
Loading

0 comments on commit dbb0e36

Please sign in to comment.