From f74e887d4d52fb46df46da24d23aef660cdd61e6 Mon Sep 17 00:00:00 2001 From: Jason Poon Date: Fri, 4 Dec 2015 08:53:10 -0800 Subject: [PATCH 1/2] Update `Mode` abstract class to properly return promises when calling `HandleActivation` and `HandleKeyEvent` --- src/cmd_line/main.ts | 8 ++-- src/mode/mode.ts | 4 +- src/mode/modeInsert.ts | 68 +++++++++++++++++----------------- src/mode/modeNormal.ts | 84 ++++++++++++++++++++++-------------------- src/mode/modeVisual.ts | 7 ++-- src/motion/motion.ts | 1 + src/textEditor.ts | 7 ++-- src/util.ts | 8 ++-- 8 files changed, 97 insertions(+), 90 deletions(-) diff --git a/src/cmd_line/main.ts b/src/cmd_line/main.ts index 13969869cd2..4535ad49878 100644 --- a/src/cmd_line/main.ts +++ b/src/cmd_line/main.ts @@ -3,17 +3,17 @@ import * as parser from "./parser"; import * as util from "../util"; // Shows the vim command line. -export function showCmdLine(initialText = "") { +export function showCmdLine(initialText = "") : Thenable<{}> { if (!vscode.window.activeTextEditor) { - util.showInfo("No active document."); - return; + return util.showInfo("No active document."); } const options : vscode.InputBoxOptions = { prompt: "Vim command line", value: initialText }; - vscode.window.showInputBox(options).then( + + return vscode.window.showInputBox(options).then( runCmdLine, vscode.window.showErrorMessage ); diff --git a/src/mode/mode.ts b/src/mode/mode.ts index 4703975352c..c39d75c48cd 100644 --- a/src/mode/mode.ts +++ b/src/mode/mode.ts @@ -33,7 +33,7 @@ export abstract class Mode { abstract ShouldBeActivated(key : string, currentMode : ModeName) : boolean; - abstract HandleActivation(key : string) : Thenable | void; + abstract HandleActivation(key : string) : Thenable<{}>; - abstract HandleKeyEvent(key : string) : void; + abstract HandleKeyEvent(key : string) : Thenable<{}>; } \ No newline at end of file diff --git a/src/mode/modeInsert.ts b/src/mode/modeInsert.ts index 69626ef9901..b0d918ccf79 100644 --- a/src/mode/modeInsert.ts +++ b/src/mode/modeInsert.ts @@ -4,56 +4,56 @@ import TextEditor from './../textEditor'; import {Cursor} from './../motion/motion'; export default class InsertMode extends Mode { - - private activationKeyHandler : { [ key : string] : (cursor : Cursor) => Thenable | void; }; - private cursor : Cursor = new Cursor(); - - constructor() { - super(ModeName.Insert); - - this.activationKeyHandler = { + private cursor : Cursor; + private activationKeyHandler : { [ key : string] : (cursor : Cursor) => Thenable<{}> } = { + "i" : () => { // insert at cursor - "i" : (cursor) => { - // nothing - }, - - // insert at the beginning of the line - "I" : (cursor) => { cursor.lineBegin().move(); }, - + return Promise.resolve({}); + }, + "I" : c => { + // insert at line beginning + return Promise.resolve(c.lineBegin().move()); + }, + "a" : c => { // append after the cursor - "a" : (cursor) => { cursor.right().move(); }, - + return Promise.resolve(c.right().move()); + }, + "A" : c => { // append at the end of the line - "A" : (cursor) => { cursor.lineEnd().move(); }, - + return Promise.resolve(c.lineEnd().move()); + }, + "o" : () => { // open blank line below current line - "o" : () => { - return vscode.commands.executeCommand("editor.action.insertLineAfter"); - }, - + return vscode.commands.executeCommand("editor.action.insertLineAfter"); + }, + "O" : () => { // open blank line above current line - "O" : () => { - return vscode.commands.executeCommand("editor.action.insertLineBefore"); - } - }; + return vscode.commands.executeCommand("editor.action.insertLineBefore"); + } + }; + + constructor() { + super(ModeName.Insert); + this.cursor = new Cursor(); } ShouldBeActivated(key : string, currentMode : ModeName) : boolean { return key in this.activationKeyHandler; } - HandleActivation(key : string) : Thenable | void { + HandleActivation(key : string) : Thenable<{}> { this.cursor.reset(); return this.activationKeyHandler[key](this.cursor); } - HandleKeyEvent(key : string) : Thenable { + HandleKeyEvent(key : string) : Thenable<{}> { this.keyHistory.push(key); - var thenable = TextEditor.insert(this.ResolveKeyValue(key)); - - vscode.commands.executeCommand("editor.action.triggerSuggest"); - return thenable; + return TextEditor + .insert(this.ResolveKeyValue(key)) + .then(() => { + return vscode.commands.executeCommand("editor.action.triggerSuggest"); + }); } // Some keys have names that are different to their value. @@ -66,4 +66,4 @@ export default class InsertMode extends Mode { return key; } } -} \ No newline at end of file +} diff --git a/src/mode/modeNormal.ts b/src/mode/modeNormal.ts index 22c0fdbd23c..4807ca2ece7 100644 --- a/src/mode/modeNormal.ts +++ b/src/mode/modeNormal.ts @@ -4,62 +4,66 @@ import {ModeName, Mode} from './mode'; import {showCmdLine} from './../cmd_line/main'; import {Caret} from './../motion/motion'; -export default class CommandMode extends Mode { - private keyHandler : { [key : string] : () => void; } = {}; - private caret : Caret = new Caret(); +export default class NormalMode extends Mode { + private caret : Caret; + private keyHandler : { [key : string] : (caret : Caret) => Thenable<{}>; } = { + ":" : () => { return showCmdLine(); }, + "u" : () => { return vscode.commands.executeCommand("undo"); }, + "ctrl+r" : () => { return vscode.commands.executeCommand("redo"); }, + "h" : c => { return Promise.resolve(c.left().move()); }, + "j" : c => { return Promise.resolve(c.down().move()); }, + "k" : c => { return Promise.resolve(c.up().move()); }, + "l" : c => { return Promise.resolve(c.right().move()); }, + "$" : c => { return Promise.resolve(c.lineEnd().move()); }, + "^" : c => { return Promise.resolve(c.lineBegin().move()); }, + "gg" : c => {return Promise.resolve(c.firstLineNonBlankChar().move()); }, + "G" : c => { return Promise.resolve(c.lastLineNonBlankChar().move()); }, + "w" : c => { return Promise.resolve(c.wordRight().move()); }, + "b" : c => { return Promise.resolve(c.wordLeft().move()); }, + ">>" : () => { return vscode.commands.executeCommand("editor.action.indentLines"); }, + "<<" : () => { return vscode.commands.executeCommand("editor.action.outdentLines"); }, + "dd" : () => { return vscode.commands.executeCommand("editor.action.deleteLines"); }, + "dw" : () => { return vscode.commands.executeCommand("deleteWordRight"); }, + "db" : () => { return vscode.commands.executeCommand("deleteWordLeft"); }, + // "x" : () => { this.CommandDelete(1); } + "esc": () => { return vscode.commands.executeCommand("workbench.action.closeMessages"); } + }; constructor() { super(ModeName.Normal); - - this.keyHandler = { - ":" : () => { showCmdLine(); }, - "u" : () => { vscode.commands.executeCommand("undo"); }, - "ctrl+r" : () => { vscode.commands.executeCommand("redo"); }, - "h" : () => { this.caret.left().move(); }, - "j" : () => { this.caret.down().move(); }, - "k" : () => { this.caret.up().move(); }, - "l" : () => { this.caret.right().move(); }, - "$" : () => { this.caret.lineEnd().move(); }, - "^" : () => { this.caret.lineBegin().move(); }, - "gg" : () => {this.caret.firstLineNonBlankChar().move(); }, - "G" : () => { this.caret.lastLineNonBlankChar().move(); }, - "w" : () => { this.caret.wordRight().move(); }, - "b" : () => { this.caret.wordLeft().move(); }, - ">>" : () => { vscode.commands.executeCommand("editor.action.indentLines"); }, - "<<" : () => { vscode.commands.executeCommand("editor.action.outdentLines"); }, - "dd" : () => { vscode.commands.executeCommand("editor.action.deleteLines"); }, - "dw" : () => { vscode.commands.executeCommand("deleteWordRight"); }, - "db" : () => { vscode.commands.executeCommand("deleteWordLeft"); }, - // "x" : () => { this.CommandDelete(1); } - "esc": () => { vscode.commands.executeCommand("workbench.action.closeMessages"); } - }; + this.caret = new Caret(); } ShouldBeActivated(key : string, currentMode : ModeName) : boolean { return (key === 'esc' || key === 'ctrl+['); } - HandleActivation(key : string) : void { - this.caret.reset().left().move(); + HandleActivation(key : string) : Thenable<{}> { + return Promise.resolve(this.caret.reset().left().move()); } - HandleKeyEvent(key : string) : void { + HandleKeyEvent(key : string) : Thenable<{}> { this.keyHistory.push(key); - let keyHandled = false; + return new Promise(resolve => { + let keyHandled = false; + let keysPressed : string; + + for (let window = this.keyHistory.length; window > 0; window--) { + keysPressed = _.takeRight(this.keyHistory, window).join(''); + if (this.keyHandler[keysPressed] !== undefined) { + keyHandled = true; + break; + } + } - for (let window = this.keyHistory.length; window > 0; window--) { - let keysPressed = _.takeRight(this.keyHistory, window).join(''); - if (this.keyHandler[keysPressed] !== undefined) { - keyHandled = true; - this.keyHandler[keysPressed](); - break; + if (keyHandled) { + this.keyHistory = []; + return this.keyHandler[keysPressed](this.caret); } - } - if (keyHandled) { - this.keyHistory = []; - } + resolve(); + }); } /* diff --git a/src/mode/modeVisual.ts b/src/mode/modeVisual.ts index 01291e8017a..9647ce2c0cf 100644 --- a/src/mode/modeVisual.ts +++ b/src/mode/modeVisual.ts @@ -10,11 +10,12 @@ export default class VisualMode extends Mode { return (key === "v" || key === "V") && (currentMode === ModeName.Normal); } - HandleActivation(key : string) : void { - // do nothing + HandleActivation(key : string) : Thenable<{}> { + return Promise.resolve({}); } - HandleKeyEvent(key : string) : void { + HandleKeyEvent(key : string) : Thenable<{}> { this.keyHistory.push(key); + return Promise.resolve({}); } } \ No newline at end of file diff --git a/src/motion/motion.ts b/src/motion/motion.ts index d5f85c187af..3b61d405641 100644 --- a/src/motion/motion.ts +++ b/src/motion/motion.ts @@ -19,6 +19,7 @@ abstract class Motion> { line = currentPosition.line; character = currentPosition.character; } + this.prevColumn = character; this.position = new vscode.Position(line, character); } diff --git a/src/textEditor.ts b/src/textEditor.ts index 52c7f4cc224..83a108591ad 100644 --- a/src/textEditor.ts +++ b/src/textEditor.ts @@ -6,27 +6,28 @@ export default class TextEditor { position = vscode.window.activeTextEditor.selection.active; } - return vscode.window.activeTextEditor.edit((editBuilder) => { + return vscode.window.activeTextEditor.edit(editBuilder => { editBuilder.insert(position, text); }); } static delete(range: vscode.Range = null) : Thenable { if (range === null) { + // delete entire document let start = new vscode.Position(0, 0); let lastLine = vscode.window.activeTextEditor.document.lineCount - 1; let end = vscode.window.activeTextEditor.document.lineAt(lastLine).range.end; range = new vscode.Range(start, end); } - return vscode.window.activeTextEditor.edit((editBuilder) => { + return vscode.window.activeTextEditor.edit(editBuilder => { editBuilder.delete(range); }); } static replace(range: vscode.Range, text: string) : Thenable { - return vscode.window.activeTextEditor.edit((editBuilder) => { + return vscode.window.activeTextEditor.edit(editBuilder => { editBuilder.replace(range, text); }); } diff --git a/src/util.ts b/src/util.ts index f5a554a2963..b5c518b7209 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,9 +1,9 @@ import * as vscode from 'vscode'; -export function showInfo(message : string) : void { - vscode.window.showInformationMessage("Vim: " + message); +export function showInfo(message : string) : Thenable<{}> { + return vscode.window.showInformationMessage("Vim: " + message); } -export function showError(message : string) : void { - vscode.window.showErrorMessage("Vim: " + message); +export function showError(message : string) : Thenable<{}> { + return vscode.window.showErrorMessage("Vim: " + message); } From 0ad4f1089b0a953485f11c051f791fad14acbe6d Mon Sep 17 00:00:00 2001 From: Jason Poon Date: Fri, 4 Dec 2015 09:13:50 -0800 Subject: [PATCH 2/2] tests: fixes #88 --- test/mode/modeInsert.test.ts | 66 +++++++++++++++++------------------- test/testHelpers.ts | 14 ++++++++ test/testUtils.ts | 13 ------- 3 files changed, 46 insertions(+), 47 deletions(-) create mode 100644 test/testHelpers.ts delete mode 100644 test/testUtils.ts diff --git a/test/mode/modeInsert.test.ts b/test/mode/modeInsert.test.ts index 01b5591e5a8..1474d9143a2 100644 --- a/test/mode/modeInsert.test.ts +++ b/test/mode/modeInsert.test.ts @@ -4,7 +4,7 @@ import ModeInsert from '../../src/mode/modeInsert'; import {ModeName} from '../../src/mode/mode'; import {Cursor} from '../../src/motion/motion'; import TextEditor from '../../src/textEditor'; -import * as testUtils from '../testUtils'; +import TestHelpers from '../testHelpers'; let modeHandler: ModeInsert = null; @@ -29,69 +29,69 @@ suite("Mode Insert", () => { }); test("can handle key events", (done) => { - let expected = "!"; - modeHandler.HandleKeyEvent("!") .then(() => { - return testUtils.assertTextEditorText(expected); + return TestHelpers.assertEqualLines(["!"]); }).then(done, done); }); test("Can handle 'o'", (done) => { - let expected = "text\n"; - TextEditor.insert("text") .then(() => { return modeHandler.HandleActivation("o"); - }).then(() => { - return testUtils.assertTextEditorText(expected); - }).then(done, done); + }) + .then(() => { + return TestHelpers.assertEqualLines(["text", ""]); + }) + .then(done, done); }); test("Can handle 'O'", (done) => { - let expected = "\ntext"; - TextEditor.insert("text") .then(() => { return modeHandler.HandleActivation("O"); - }).then(() => { - return testUtils.assertTextEditorText(expected); - }).then(done, done); + }) + .then(() => { + return TestHelpers.assertEqualLines(["", "text"]); + }) + .then(done, done); }); test("Can handle 'i'", (done) => { - let expected = "text!text"; - TextEditor.insert("texttext") .then(() => { new Cursor(0, 4).move(); - }).then(() => { + }) + .then(() => { return modeHandler.HandleActivation("i"); - }).then(() => { + }) + .then(() => { return modeHandler.HandleKeyEvent("!"); - }).then(() => { - return testUtils.assertTextEditorText(expected); - }).then(done, done); + }) + .then(() => { + return TestHelpers.assertEqualLines(["text!text"]); + }) + .then(done, done); }); test("Can handle 'I'", (done) => { - let expected = "!text"; - TextEditor.insert("text") .then(() => { new Cursor(0, 4).move(); - }).then(() => { + }) + .then(() => { return modeHandler.HandleActivation("I"); - }).then(() => { + }) + .then(() => { return modeHandler.HandleKeyEvent("!"); - }).then(() => { - return testUtils.assertTextEditorText(expected); - }).then(done, done); + }) + .then(() => { + return TestHelpers.assertEqualLines(["!text"]); + }) + .then(done, done); }); test("Can handle 'a'", (done) => { - let expected = "textt!ext"; - TextEditor.insert("texttext") .then(() => { new Cursor(0, 4).move(); @@ -100,13 +100,11 @@ suite("Mode Insert", () => { }).then(() => { return modeHandler.HandleKeyEvent("!"); }).then(() => { - return testUtils.assertTextEditorText(expected); + return TestHelpers.assertEqualLines(["textt!ext"]); }).then(done, done); }); test("Can handle 'A'", (done) => { - let expected = "text!"; - TextEditor.insert("text") .then(() => { new Cursor(0, 0).move(); @@ -115,7 +113,7 @@ suite("Mode Insert", () => { }).then(() => { return modeHandler.HandleKeyEvent("!"); }).then(() => { - return testUtils.assertTextEditorText(expected); + return TestHelpers.assertEqualLines(["text!"]); }).then(done, done); }); }); diff --git a/test/testHelpers.ts b/test/testHelpers.ts new file mode 100644 index 00000000000..b9a51d614f4 --- /dev/null +++ b/test/testHelpers.ts @@ -0,0 +1,14 @@ +import TextEditor from '../src/textEditor'; +import * as assert from 'assert'; + +export default class TestHelpers { + + public static assertEqualLines(expectedLines : string[]) { + assert.equal(TextEditor.getLineCount(), expectedLines.length); + + for (let i = 0; i < expectedLines.length; i++) { + assert.equal(TextEditor.readLine(i), expectedLines[i]); + } + } + +} \ No newline at end of file diff --git a/test/testUtils.ts b/test/testUtils.ts deleted file mode 100644 index da82d6fe826..00000000000 --- a/test/testUtils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import TextEditor from '../src/textEditor'; -import * as assert from 'assert'; - -export function assertTextEditorText(expected: string, lineNo?: number) { - let actual: string; - if (isNaN(lineNo) || typeof lineNo === 'undefined') { - actual = TextEditor.readFile(); - } else { - actual = TextEditor.readLine(lineNo); - } - - assert.equal(actual, expected); -}