diff --git a/extension.ts b/extension.ts index 6846de0467b2..7ef0f4113a3d 100644 --- a/extension.ts +++ b/extension.ts @@ -179,7 +179,7 @@ export async function activate(context: vscode.ExtensionContext) { showCmdLine("", modeHandlerToEditorIdentity[new EditorIdentity(vscode.window.activeTextEditor).toString()]); }); - 'rfbducw['.split('').forEach(key => { + 'rfbducw[ax'.split('').forEach(key => { registerCommand(context, `extension.vim_ctrl+${key}`, () => handleKeyEvent(`ctrl+${key}`)); }); diff --git a/package.json b/package.json index 2f9caea29e75..ef860073d89e 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,16 @@ "command": "extension.vim_ctrl+c", "when": "editorTextFocus && vim.useCtrlKeys" }, + { + "key": "ctrl+a", + "command": "extension.vim_ctrl+a", + "when": "editorTextFocus" + }, + { + "key": "ctrl+x", + "command": "extension.vim_ctrl+x", + "when": "editorTextFocus" + }, { "key": "left", "command": "extension.vim_left", diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 09eaf2c5271e..60ee433d21e2 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -2694,3 +2694,76 @@ class ToggleCaseAndMoveForward extends BaseMovement { return position.getRight(); } } + +abstract class IncrementDecrementNumberAction extends BaseMovement { + modes = [ModeName.Normal]; + canBeRepeatedWithDot = true; + canBePrefixedWithCount = true; + + offset: number; + + public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise { + count = count || 1; + const text = TextEditor.getLineAt(position).text; + + // They may start on a number, so check the current word boundaries first. + let wordEnd = position.getCurrentWordEnd(true); + let wordStart = position.getWordLeft(true); + do { + // '-' doesn't count as a word, but is important to include in parsing the number + if (text[wordStart.character - 1] === '-') { + wordStart = wordStart.getLeft(); + } + const word = text.substring(wordStart.character, wordEnd.character + 1); + // Strict number parsing so "1a" doesn't silently get converted to "1" + const num = Number(word); + + if (!isNaN(num)) { + return this.replaceNum(num, this.offset * count, wordStart, wordEnd); + } + + if (wordEnd.isLineEnd()) { + // We got to the end of the line and didn't find anything that looks like a number + break; + } + + // Move forward to the next word + wordStart = wordStart.getWordRight(); + wordEnd = wordStart.getCurrentWordEnd(); + } while (true); + + // No usable numbers, return the original position + return position; + } + + public async replaceNum(start: number, offset: number, startPos: Position, endPos: Position): Promise { + const oldWidth = start.toString().length; + start += offset; + const newNum = start.toString(); + + const range = new vscode.Range(startPos, endPos.getRight()); + + if (oldWidth !== newNum.length) { + // Can't use replace, since new number may be different width than old + await TextEditor.delete(range); + await TextEditor.insertAt(newNum, startPos); + } else { + await TextEditor.replace(range, newNum); + } + + // Advance to the end of the word in case it's longer + return Position.FromVSCodePosition(endPos.translate(0, newNum.length - oldWidth)); + } +} + +@RegisterAction +class IncrementNumberAction extends IncrementDecrementNumberAction { + keys = ["ctrl+a"]; + offset = +1; +} + +@RegisterAction +class DecrementNumberAction extends IncrementDecrementNumberAction { + keys = ["ctrl+x"]; + offset = -1; +} \ No newline at end of file diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 24b4fec7cd0e..9947a4444e4b 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -503,8 +503,6 @@ export class ModeHandler implements vscode.Disposable { } async handleKeyEvent(key: string): Promise { - if (key === "") { key = "ctrl+r"; } // TODO - temporary hack for tests only! - // 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 diff --git a/test/mode/modeNormal.test.ts b/test/mode/modeNormal.test.ts index cd1271be731b..b2ede15ed317 100644 --- a/test/mode/modeNormal.test.ts +++ b/test/mode/modeNormal.test.ts @@ -963,4 +963,46 @@ suite("Mode Normal", () => { keysPressed: "dE", end: ["one two| "] }); + + newTest({ + title: "can ctrl-a correctly behind a word", + start: ["|one 9"], + keysPressed: "", + end: ["one 1|0"] + }); + + newTest({ + title: "can ctrl-a on word", + start: ["one -|11"], + keysPressed: "", + end: ["one -1|0"] + }); + + newTest({ + title: "can ctrl-a on decimal", + start: ["1|1.123"], + keysPressed: "", + end: ["1|2.123"] + }); + + newTest({ + title: "can ctrl-a with numeric prefix", + start: ["|-10"], + keysPressed: "15", + end: ["|5"] + }); + + newTest({ + title: "can ctrl-a on a decimal", + start: ["-10.|1"], + keysPressed: "10", + end: ["-10.1|1"] + }); + + newTest({ + title: "can ctrl-x correctly behind a word", + start: ["|one 10"], + keysPressed: "", + end: ["one |9"] + }); }); \ No newline at end of file diff --git a/test/testSimplifier.ts b/test/testSimplifier.ts index 12fb1e8f4256..af1c74770b38 100644 --- a/test/testSimplifier.ts +++ b/test/testSimplifier.ts @@ -146,7 +146,7 @@ class TestObjectHelper { } /** - * Tokenize a string like "abcd" into ["a", "b", "c", "", "d", ""] + * Tokenize a string like "abcd" into ["a", "b", "c", "", "d", "ctrl+c"] */ function tokenizeKeySequence(sequence: string): string[] { let isBracketedKey = false; @@ -162,6 +162,9 @@ function tokenizeKeySequence(sequence: string): string[] { if (char === '>') { isBracketedKey = false; + if (key.startsWith("