├── .vscodeignore ├── test ├── .gitignore ├── vim │ ├── CopyAndMovingTextTests.ts │ ├── InsertModeTests.ts │ ├── ChangingText.ts │ ├── VimTests.ts │ ├── RepeatTests.ts │ ├── DeletingTextTests.ts │ ├── TextObjects.ts │ ├── UpDownMotions.ts │ └── LeftRightMotions.ts ├── VirtualEditor.test.ts ├── NeoVimTest.test.ts ├── OriginalVimTest.test.ts └── VirtualEditor.ts ├── .gitignore ├── vim.png ├── .travis.yml ├── tutorial ├── tutorial1.gif ├── tutorial2.gif ├── tutorial3.gif ├── tutorial3.txt ├── tutorial2.txt └── tutorial1.txt ├── src ├── mode │ └── InsertMode.ts ├── motion │ ├── wordMorionStateModel │ │ ├── changeWord.png │ │ ├── deleteWord.png │ │ ├── moveWord.png │ │ ├── deleteEndOfWord.png │ │ ├── Makefile │ │ ├── moveWord.plantuml │ │ ├── deleteEndOfWord.plantuml │ │ ├── changeWord.plantuml │ │ └── deleteWord.plantuml │ ├── AbstractMotion.ts │ ├── textObjectSelection │ │ ├── AbstractTextObjectSelection.ts │ │ ├── Quotation.ts │ │ └── Brancket.ts │ ├── FirstCharacterInLineMotion.ts │ ├── LastCharacterInLineMotion.ts │ ├── RightMotion.ts │ ├── DownMotion.ts │ ├── ParagraphMotion.ts │ ├── FirstCharacterMotion.ts │ ├── BrancketMotion.ts │ ├── MoveWordMotion.ts │ ├── FindCharacterMotion.ts │ ├── DeleteWordMotion.ts │ ├── DeleteEndOfWordMotion.ts │ └── ChangeWordMotion.ts ├── action │ ├── StartVisualLineModeAction.ts │ ├── ExpandHighlightedLineAction.ts │ ├── StartVisualModeAction.ts │ ├── GoAction.ts │ ├── RepeatLastChangeAction.ts │ ├── CallEditorCommandAction.ts │ ├── AbstractInsertTextAction.ts │ ├── JoinLinesAction.ts │ ├── MoveLineAction.ts │ ├── ReplaceCharacterOfSelecetdTextAction.ts │ ├── ExpandHighlightedTextAction.ts │ ├── JoinHighlightedLinesAction.ts │ ├── ReplaceCharacterAction.ts │ ├── InsertTextAction.ts │ ├── DeleteYankChangeHighlightedTextAction.ts │ ├── PutRegisterAction.ts │ ├── OpenNewLineAndAppendTextAction.ts │ └── DeleteYankChangeHighlightedLineAction.ts ├── core │ ├── ExMode.ts │ └── Register.ts ├── ex │ └── Map.ts ├── VSCodeEditorKeyBindings.ts ├── Utils.ts ├── extension.ts └── VimStyle.ts ├── testcontainer ├── centos7 │ └── Dockerfile └── ubuntu1604 │ └── Dockerfile ├── tsconfig.json ├── tslint.json ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── Jenkinsfile ├── LICENSE.txt ├── Makefile ├── package.json ├── typings └── vscode-vim.d.ts └── README.md /.vscodeignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | .viminfo 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | *.vsix -------------------------------------------------------------------------------- /vim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/vim.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5.1.0" 4 | before_script: 5 | - tsc -------------------------------------------------------------------------------- /tutorial/tutorial1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/tutorial/tutorial1.gif -------------------------------------------------------------------------------- /tutorial/tutorial2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/tutorial/tutorial2.gif -------------------------------------------------------------------------------- /tutorial/tutorial3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/tutorial/tutorial3.gif -------------------------------------------------------------------------------- /tutorial/tutorial3.txt: -------------------------------------------------------------------------------- 1 | support repeat command : . 2 | 3 | --> var num1 int 4 | --> var num2 int 5 | --> var num3 int -------------------------------------------------------------------------------- /src/mode/InsertMode.ts: -------------------------------------------------------------------------------- 1 | export function InsertModeExecute(key: string, editor: IEditor) { 2 | editor.TypeDirect(key); 3 | } 4 | -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/changeWord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/src/motion/wordMorionStateModel/changeWord.png -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/deleteWord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/src/motion/wordMorionStateModel/deleteWord.png -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/moveWord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/src/motion/wordMorionStateModel/moveWord.png -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/deleteEndOfWord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/74th/vscode-vim/HEAD/src/motion/wordMorionStateModel/deleteEndOfWord.png -------------------------------------------------------------------------------- /testcontainer/centos7/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - 4 | RUN yum -y install nodejs vim -------------------------------------------------------------------------------- /tutorial/tutorial2.txt: -------------------------------------------------------------------------------- 1 | support visual mode : v 2 | 3 | --> aaa bbb eee ccc ddd 4 | 5 | support visual line mode : V 6 | 7 | --> line1 8 | --> line3 9 | --> line4 10 | --> line5 11 | --> line2 -------------------------------------------------------------------------------- /testcontainer/ubuntu1604/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update 4 | RUN apt-get install -y curl vim 5 | RUN curl -sL https://deb.nodesource.com/setup_6.x | bash - 6 | RUN apt-get install -y nodejs -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/Makefile: -------------------------------------------------------------------------------- 1 | .SUFFIXES: .png .plantuml 2 | .PHONY: umlbuild 3 | .plantuml.png: 4 | plantuml -charset UTF-8 $< 5 | 6 | umlbuild: moveWord.png changeWord.png deleteWord.png deleteEndOfWord.png 7 | -------------------------------------------------------------------------------- /tutorial/tutorial1.txt: -------------------------------------------------------------------------------- 1 | insert mode : i A ... 2 | 3 | --> var = function() 4 | 5 | complex command : 3w 2f( ... 6 | 7 | --> var value = func( fuc(arg) ) 8 | 9 | delete and yank : 2dd p yy p 10 | 11 | --> line4 12 | --> line5 13 | --> line1 14 | --> line2 15 | -------------------------------------------------------------------------------- /src/motion/AbstractMotion.ts: -------------------------------------------------------------------------------- 1 | export abstract class AbstractMotion implements IMotion { 2 | 3 | public Count: number; 4 | 5 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 6 | throw new Error("UnImplemented"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/motion/textObjectSelection/AbstractTextObjectSelection.ts: -------------------------------------------------------------------------------- 1 | export abstract class AbstractTextObjectSelection implements ISelectionMotion { 2 | 3 | public Count: number; 4 | 5 | public CalculateRange(editor: IEditor, vim: IVimStyle, start: IPosition): IRange { 6 | throw new Error("UnImplemented"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | ".vscode-test" 14 | ] 15 | } -------------------------------------------------------------------------------- /test/vim/CopyAndMovingTextTests.ts: -------------------------------------------------------------------------------- 1 | export let CopyAndMovingTextTests = {}; 2 | 3 | CopyAndMovingTextTests["Ny{motion}: yank the text moved over with {motion} into a register"] = { 4 | "y2w:yank 2 words:": { 5 | in: [ 6 | "abc |def ghi jkl", 7 | "mnopqr", 8 | ], 9 | key: "y2wjp0", 10 | out: [ 11 | "abc def ghi jkl", 12 | "|mnopqdef ghi r", 13 | ], 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "max-classes-per-file": [ 5 | false 6 | ], 7 | "switch-default": false, 8 | "forin": false, 9 | "object-literal-sort-keys": false, 10 | "whitespace": [ 11 | "check-branch", 12 | "check-decl", 13 | "check-module", 14 | "check-operator", 15 | "check-separator", 16 | "check-type" 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib", 10 | "tslint.configFile": "./tslint.json", 11 | "tslint.autoFixOnSave": true, 12 | "editor.tabSize": 4, 13 | "editor.useTabStops": true 14 | } -------------------------------------------------------------------------------- /src/action/StartVisualLineModeAction.ts: -------------------------------------------------------------------------------- 1 | import { Range } from "../VimStyle"; 2 | 3 | /** 4 | * V 5 | */ 6 | export class StartVisualLineModeAction implements IAction { 7 | 8 | public GetActionType(): ActionType { 9 | return ActionType.Other; 10 | } 11 | 12 | public Execute(editor: IEditor, vim: IVimStyle) { 13 | let cp = editor.GetCurrentPosition(); 14 | editor.ShowVisualLineMode(cp.Line, cp.Line, cp); 15 | vim.ApplyVisualLineMode(); 16 | } 17 | } 18 | 19 | /** 20 | * V 21 | */ 22 | export function StartVisualLineMode(num: number): IAction { 23 | return new StartVisualLineModeAction(); 24 | } 25 | -------------------------------------------------------------------------------- /src/action/ExpandHighlightedLineAction.ts: -------------------------------------------------------------------------------- 1 | import { Range } from "../VimStyle"; 2 | export class ExpandHighlightedLineAction implements IAction { 3 | 4 | public Motion: IMotion; 5 | 6 | constructor() { 7 | this.Motion = null; 8 | } 9 | 10 | public GetActionType(): ActionType { 11 | return ActionType.Other; 12 | } 13 | 14 | public Execute(editor: IEditor, vim: IVimStyle) { 15 | let before = editor.GetCurrentVisualLineModeSelection(); 16 | let cp = before.focusPosition; 17 | let np = this.Motion.CalculateEnd(editor, vim, cp); 18 | editor.ShowVisualLineMode(before.startLine, np.Line, np); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/vim/InsertModeTests.ts: -------------------------------------------------------------------------------- 1 | export let InsertModeTests = {}; 2 | 3 | // ESC Key : _ 4 | 5 | InsertModeTests["Insert Mode"] = { 6 | "i:insert charactors": { 7 | in: ["aa|aa"], 8 | key: "ibc_", 9 | out: ["aab|caa"], 10 | }, 11 | "a:append charctors": { 12 | in: ["aa|aa"], 13 | key: "abc_", 14 | out: ["aaab|ca"], 15 | }, 16 | "I:insert charactors to home of a line": { 17 | in: ["aa|aa"], 18 | key: "Ibc_", 19 | out: ["b|caaaa"], 20 | }, 21 | "A:append charactors to end of a line": { 22 | in: ["aa|aa"], 23 | key: "Abc_", 24 | out: ["aaaab|c"], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/action/StartVisualModeAction.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "../VimStyle"; 2 | 3 | /** 4 | * v 5 | */ 6 | export class StartVisualModeAction implements IAction { 7 | 8 | public GetActionType(): ActionType { 9 | return ActionType.Other; 10 | } 11 | 12 | public Execute(editor: IEditor, vim: IVimStyle) { 13 | let cp = editor.GetCurrentPosition(); 14 | let s = new Range(); 15 | s.start = cp; 16 | s.end = new Position(cp.Line, cp.Char + 1); 17 | editor.ShowVisualMode(s, cp); 18 | vim.ApplyVisualMode(); 19 | } 20 | } 21 | 22 | /** 23 | * v 24 | */ 25 | export function StartVisualMode(num: number): IAction { 26 | return new StartVisualModeAction(); 27 | } 28 | -------------------------------------------------------------------------------- /src/action/GoAction.ts: -------------------------------------------------------------------------------- 1 | export class GoAction implements IAction { 2 | 3 | public Motion: IMotion; 4 | 5 | constructor() { 6 | this.Motion = null; 7 | } 8 | 9 | public GetActionType(): ActionType { 10 | return ActionType.Move; 11 | } 12 | 13 | public Execute(editor: IEditor, vim: IVimStyle) { 14 | let from = editor.GetCurrentPosition(); 15 | let to = this.Motion.CalculateEnd(editor, vim, from); 16 | if (to == null) { 17 | // cancel 18 | return; 19 | } 20 | if (from.Char === to.Char && from.Line === to.Line) { 21 | // not move 22 | return; 23 | } 24 | editor.SetPosition(editor.UpdateValidPosition(to, true)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | stages { 4 | stage('prepare') { 5 | steps { 6 | sh 'npm install' 7 | } 8 | } 9 | stage('build') { 10 | steps { 11 | sh 'make build' 12 | } 13 | } 14 | stage('test') { 15 | steps { 16 | parallel( 17 | "test": { 18 | sh 'make test' 19 | 20 | }, 21 | "OriginalVimTest": { 22 | sh 'make testOriginalVim' 23 | 24 | } 25 | ) 26 | } 27 | } 28 | stage('lint') { 29 | steps { 30 | echo 'OK' 31 | sh 'make tslint' 32 | } 33 | } 34 | stage('release') { 35 | steps { 36 | echo 'OK' 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/action/RepeatLastChangeAction.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "../VimStyle"; 2 | 3 | /** 4 | * . 5 | */ 6 | export class RepeatLastChangeAction implements IAction { 7 | 8 | public GetActionType(): ActionType { 9 | return ActionType.Repeat; 10 | } 11 | 12 | public Execute(editor: IEditor, vim: IVimStyle) { 13 | if (vim.LastEditAction) { 14 | switch (vim.LastEditAction.GetActionType()) { 15 | case ActionType.Insert: 16 | case ActionType.Edit: 17 | let action = vim.LastEditAction; 18 | vim.LastEditAction.Execute(editor, vim); 19 | } 20 | } 21 | } 22 | 23 | } 24 | 25 | /** 26 | * . 27 | */ 28 | export function RepeatLastChange() { 29 | return new RepeatLastChangeAction(); 30 | } 31 | -------------------------------------------------------------------------------- /src/core/ExMode.ts: -------------------------------------------------------------------------------- 1 | import { Nmap, Nnoremap } from "../ex/Map"; 2 | 3 | export function ExecExCommand(line: string, vimStyle: IVimStyle, editor: IEditor) { 4 | let sp = line.indexOf(" "); 5 | let commandName: string; 6 | let commandArg: string; 7 | if (sp >= 0) { 8 | commandName = line.substring(0, sp); 9 | commandArg = line.substring(sp + 1); 10 | } else { 11 | commandName = line; 12 | commandArg = null; 13 | } 14 | let command = selectCommand(commandName); 15 | if (command !== null) { 16 | command.Execute(commandArg, vimStyle, editor); 17 | } 18 | } 19 | 20 | function selectCommand(command: string): IExCommand { 21 | switch (command) { 22 | case "nmap": 23 | return new Nmap(); 24 | case "nnoremap": 25 | return new Nnoremap(); 26 | } 27 | return null; 28 | } 29 | -------------------------------------------------------------------------------- /test/vim/ChangingText.ts: -------------------------------------------------------------------------------- 1 | export let ChangingText = {}; 2 | 3 | ChangingText["replace character"] = { 4 | "r:replace character": { 5 | in: ["a|aa"], 6 | key: "rb", 7 | out: ["a|ba"], 8 | }, 9 | "Nr:replace characters": { 10 | in: ["a|aaaa"], 11 | key: "4rb", 12 | out: ["abbb|b"], 13 | }, 14 | "Nr:fail replacing": { 15 | in: ["a|aaaa"], 16 | key: "5rb", 17 | out: ["a|aaaa"], 18 | }, 19 | "gr:replace character": { 20 | in: ["a|aa"], 21 | key: "grb", 22 | out: ["a|ba"], 23 | }, 24 | "Ngr:replace characters withou affecting layout": { 25 | in: ["a|a"], 26 | key: "5grb", 27 | out: ["abbbb|b"], 28 | }, 29 | "v..r:replace character": { 30 | in: ["a|aaaa"], 31 | key: "v2lrb", 32 | out: ["a|bbba"], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "make", 4 | "isShellCommand": true, 5 | "showOutput": "silent", 6 | "tasks": [ 7 | { 8 | "taskName": "build", 9 | "isBuildCommand": true, 10 | "args": [ 11 | "build" 12 | ] 13 | }, 14 | { 15 | "taskName": "testnow", 16 | "isTestCommand": true, 17 | "args": [ 18 | "testnow" 19 | ] 20 | }, 21 | { 22 | "taskName": "test", 23 | "isTestCommand": true, 24 | "args": [ 25 | "test" 26 | ] 27 | }, 28 | { 29 | "taskName": "clean", 30 | "args": [ 31 | "clean" 32 | ] 33 | }, 34 | { 35 | "taskName": "release", 36 | "args": [ 37 | "release" 38 | ], 39 | "isWatching": true 40 | }, 41 | { 42 | "taskName": "npm", 43 | "args": [ 44 | "debug" 45 | ], 46 | "isWatching": true, 47 | "problemMatcher": "$tsc-watch" 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/moveWord.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title move N words ( Nw ) 3 | 4 | (*) --> first 5 | 6 | note right: do not count a first character 7 | 8 | first -down-> [character] character 9 | first -down-> [space] space 10 | first -down-> [linefeed] "linefeed" 11 | 12 | character --> [same character type] character 13 | character -right-> [space] space 14 | character -right-> [linefeed] linefeed 15 | character -down-> [different character type] "count --" 16 | 17 | if "" then 18 | --> [count=0] (*) 19 | else 20 | -up-> [count > 0]character 21 | end if 22 | 23 | space -down-> [character] "count --" 24 | space --> [space] space 25 | space --> [linefeed] linefeed 26 | 27 | "linefeed" --> [linefeed] "count -- at blnkline" 28 | note right: need count blank line 29 | if "" then 30 | --> [count=0] (*) 31 | else 32 | -up-> [count > 0] space 33 | end if 34 | 35 | "linefeed" --> [character] "count --" 36 | "linefeed" --> [space] space 37 | 38 | @enduml 39 | -------------------------------------------------------------------------------- /test/vim/VimTests.ts: -------------------------------------------------------------------------------- 1 | import { ChangingText } from "./ChangingText"; 2 | import { CopyAndMovingTextTests } from "./CopyAndMovingTextTests"; 3 | import { DeletingText } from "./DeletingTextTests"; 4 | import { InsertModeTests } from "./InsertModeTests"; 5 | import { LeftRightMotions } from "./LeftRightMotions"; 6 | import { RepeatTests } from "./RepeatTests"; 7 | import { TextObjectMotions } from "./TextObjectMotions"; 8 | import { TextObjects } from "./TextObjects"; 9 | import { UpDownMotions } from "./UpDownMotions"; 10 | 11 | export let VimTests = {}; 12 | let testsets = [ 13 | LeftRightMotions, 14 | UpDownMotions, 15 | TextObjectMotions, 16 | InsertModeTests, 17 | ChangingText, 18 | DeletingText, 19 | CopyAndMovingTextTests, 20 | RepeatTests, 21 | TextObjects, 22 | ]; 23 | 24 | for (let testset in testsets) { 25 | for (let test in testsets[testset]) { 26 | VimTests[test] = testsets[testset][test]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/action/CallEditorCommandAction.ts: -------------------------------------------------------------------------------- 1 | import { RegisterItem } from "../core/Register"; 2 | import { Position, Range } from "../VimStyle"; 3 | import { AbstractInsertTextAction } from "./AbstractInsertTextAction"; 4 | 5 | export class CallEditorCommandAction implements IAction { 6 | 7 | public Callback: ICommandCallback; 8 | public Argument: string; 9 | 10 | public GetActionType(): ActionType { 11 | return ActionType.Other; 12 | } 13 | 14 | public Execute(editor: IEditor, vim: IVimStyle) { 15 | if (this.Callback === undefined || this.Callback === null) { 16 | editor.CallEditorCommand(this.Argument); 17 | } else { 18 | this.Callback(editor, vim); 19 | } 20 | } 21 | 22 | } 23 | 24 | export function EditorCommand(command: IVimStyleCommand): IAction { 25 | let a = new CallEditorCommandAction(); 26 | a.Argument = command.argument; 27 | a.Callback = command.callback; 28 | return a; 29 | } 30 | -------------------------------------------------------------------------------- /src/motion/FirstCharacterInLineMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | 5 | /** 6 | * 0 c0 7 | */ 8 | class FirstCharacterInLineMotion extends AbstractMotion { 9 | 10 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 11 | let end = new Position(); 12 | end.Line = start.Line; 13 | end.Char = 0; 14 | return editor.UpdateValidPosition(end); 15 | } 16 | } 17 | 18 | /** 19 | * 0 20 | */ 21 | export function GotoFirstCharacterInLine(num: number): IAction { 22 | 23 | let a = new GoAction(); 24 | a.Motion = new FirstCharacterInLineMotion(); 25 | return a; 26 | } 27 | 28 | /** 29 | * c0 30 | */ 31 | export function AddFirstCharacterInLineMotion(num: number, action: IAction): void { 32 | let a = action; 33 | a.Motion = new FirstCharacterInLineMotion(); 34 | } 35 | -------------------------------------------------------------------------------- /src/motion/LastCharacterInLineMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | 5 | /** 6 | * $ c$ 7 | */ 8 | export class LastCharacterInLineMotion extends AbstractMotion { 9 | 10 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 11 | let end = new Position(); 12 | end.Line = start.Line; 13 | end.Char = Number.MAX_VALUE; 14 | return editor.UpdateValidPosition(end); 15 | } 16 | } 17 | 18 | /** 19 | * $ 20 | */ 21 | export function GotoLastCharacterInLine(num: number): IAction { 22 | let a = new GoAction(); 23 | a.Motion = new LastCharacterInLineMotion(); 24 | return a; 25 | } 26 | 27 | /** 28 | * c$ 29 | */ 30 | export function AddLastCharacterInLineMotion(num: number, action: IAction): void { 31 | let a = action; 32 | a.Motion = new LastCharacterInLineMotion(); 33 | } 34 | -------------------------------------------------------------------------------- /src/ex/Map.ts: -------------------------------------------------------------------------------- 1 | export class Nmap implements IExCommand { 2 | public Execute(arg: string, vimStyle: IVimStyle, editor: IEditor) { 3 | if (arg == null || arg.length === 0) { 4 | showCurrentMap(); 5 | return; 6 | } 7 | 8 | let args = divideArg(arg); 9 | if (args.length === 2) { 10 | vimStyle.CommandFactory.Nmap[args[0]] = args[1]; 11 | } 12 | } 13 | } 14 | 15 | export class Nnoremap implements IExCommand { 16 | public Execute(arg: string, vimStyle: IVimStyle, editor: IEditor) { 17 | if (arg == null || arg.length === 0) { 18 | showCurrentMap(); 19 | return; 20 | } 21 | 22 | let args = divideArg(arg); 23 | if (args.length === 2) { 24 | vimStyle.CommandFactory.Nnoremap[args[0]] = args[1]; 25 | } 26 | } 27 | } 28 | 29 | function divideArg(arg: string): string[] { 30 | // TODO escape string 31 | return arg.split(" "); 32 | } 33 | function showCurrentMap() { 34 | // TODO 35 | } 36 | -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/deleteEndOfWord.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title delete until end of N words ( dNe, cNe, Ne ) 3 | 4 | (*) --> first 5 | 6 | "first" --> [character] "first character" 7 | "first" --> [space,linefeed] "space or linefeed" 8 | "first character" --> [character] "count--" 9 | if "count" then 10 | -down-> [ =0] "final word" 11 | else 12 | -up-> [>0] "character" 13 | end if 14 | "first character" --> [space,linefeed] "space or linefeed" 15 | 16 | "final word" --> [same type character] "final word" 17 | "final word" --> [defferece type character,space,linefeed] "stop" 18 | if then 19 | -down-> [move action] "move to previous character" 20 | else 21 | -down-> [cut or delete action] "delete until just before" 22 | end if 23 | 24 | "character" -> [same type character] "character" 25 | "character" --> [defference type character] "count--" 26 | "character" -> [space,linefeed] "space or linefeed" 27 | 28 | "space or linefeed" --> [character] "count--" 29 | "space or linefeed" -down-> [space,linefeed] "space or linefeed" 30 | "move to previous character" --> (*) 31 | "delete until just before" --> (*) 32 | 33 | @enduml 34 | 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 74th 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/action/AbstractInsertTextAction.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * i I a A cw... 3 | */ 4 | export abstract class AbstractInsertTextAction implements IInsertTextAction { 5 | 6 | protected insertText: string; 7 | protected insertModeInfo: any; 8 | 9 | constructor() { 10 | this.insertModeInfo = null; 11 | this.insertText = null; 12 | } 13 | 14 | public abstract GetActionType(): ActionType; 15 | 16 | public abstract Execute(editor: IEditor, vim: IVimStyle): any; 17 | 18 | public GetInsertModeInfo(): any { 19 | return this.insertModeInfo; 20 | } 21 | public SetInsertText(text: string) { 22 | this.insertText = text; 23 | } 24 | 25 | protected calcPositionAfterInsert(p: IPosition): IPosition { 26 | let splitText = this.insertText.split("\n"); 27 | let np = p.Copy(); 28 | if (splitText.length > 1) { 29 | np.Line += splitText.length - 1; 30 | np.Char = 0; 31 | } 32 | np.Char += splitText[splitText.length - 1].length - 1; 33 | if (np.Char < 0) { 34 | np.Char = 0; 35 | } 36 | return np; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/VirtualEditor.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { Position, VimStyle } from "../src/VimStyle"; 3 | import { VimTests } from "./vim/VimTests"; 4 | import { VirtualEditor } from "./VirtualEditor"; 5 | 6 | let opt: IVimStyleOptions = { 7 | useErgonomicKeyForMotion: false, 8 | vimrc: [], 9 | }; 10 | 11 | let target; 12 | for (target in VimTests) { 13 | describe("VimStyle " + target + ".", () => { 14 | let test = VimTests[target]; 15 | for (let specName in test) { 16 | it(specName, () => { 17 | let spec = test[specName]; 18 | let ed = new VirtualEditor(); 19 | let vim = new VimStyle(ed, opt); 20 | ed.SetContent(spec["in"]); 21 | ed.Type(spec.key); 22 | let out = ed.GetContent(); 23 | let outText = out[0]; 24 | let specText = spec.out[0]; 25 | for (let i = 1; i < out.length; i++) { 26 | outText += "\n" + out[i]; 27 | } 28 | for (let i = 1; i < spec.out.length; i++) { 29 | specText += "\n" + spec.out[i]; 30 | } 31 | assert.equal(outText, specText); 32 | }); 33 | } 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/vim/RepeatTests.ts: -------------------------------------------------------------------------------- 1 | export let RepeatTests = {}; 2 | 3 | RepeatTests["repeat insert"] = { 4 | "a.repeat insert text": { 5 | in: [ 6 | "|abc", 7 | ], 8 | key: "adef_$.", 9 | out: [ 10 | "adefbcde|f", 11 | ], 12 | }, 13 | "A.:repeat insert text containing LF": { 14 | in: [ 15 | "|abc", 16 | "ABC", 17 | ], 18 | key: "Adef\nghe_j.", 19 | out: [ 20 | "abcdef", 21 | "ghe", 22 | "ABCdef", 23 | "gh|e", 24 | ], 25 | }, 26 | "o.:repeat insert text to new line": { 27 | in: [ 28 | "a|bc", 29 | ], 30 | key: "odef_.", 31 | out: [ 32 | "abc", 33 | "def", 34 | "de|f", 35 | ], 36 | }, 37 | "cw.:repeat cut command": { 38 | in: [ 39 | "aaa |bbb", 40 | "ccc ddd", 41 | ], 42 | key: "c$kkk_jb.", 43 | out: [ 44 | "aaa kkk", 45 | "ccc kk|k", 46 | ], 47 | }, 48 | }; 49 | RepeatTests["repeat normal command"] = { 50 | "add text": { 51 | in: [ 52 | "aaa |bbb ccc ddd", 53 | ], 54 | key: "dw.", 55 | out: [ 56 | "aaa |ddd", 57 | ], 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/action/JoinLinesAction.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | import {Position, Range} from "../VimStyle"; 3 | 4 | /** 5 | * J 6 | */ 7 | export class JoinLinesAction implements IAction { 8 | 9 | public Count: number; 10 | 11 | public GetActionType(): ActionType { 12 | return ActionType.Edit; 13 | } 14 | 15 | public Execute(editor: IEditor, vim: IVimStyle) { 16 | 17 | const docLines = editor.GetLastLineNum(); 18 | const startPos = new Position(editor.GetCurrentPosition().Line, 0) ; 19 | let endPos: IPosition; 20 | if (docLines > startPos.Line + this.Count - 1) { 21 | endPos = new Position(startPos.Line + this.Count - 1, 0); 22 | } else { 23 | endPos = new Position(docLines, 0); 24 | } 25 | const range = new Range(); 26 | range.start = startPos; 27 | range.end = endPos; 28 | 29 | let text = editor.ReadRange(range); 30 | text = text.replace(/\r\n/g, " "); 31 | text = text.replace(/\n/g, " "); 32 | 33 | let pos = new Position(startPos.Line, text.length - 1); 34 | 35 | editor.ReplaceRange(range, text); 36 | editor.SetPosition(pos); 37 | } 38 | } 39 | 40 | /** 41 | * J 42 | */ 43 | export function JoinLines(num: number): IAction { 44 | let a = new JoinLinesAction(); 45 | a.Count = num < 2 ? 2 : num; 46 | return a; 47 | } 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHONY:clean test testOriginalVim testNeoVim build tslint release buildcontainer testincontainer 2 | 3 | MOCHA=./node_modules/mocha/bin/mocha --require source-map-support/register 4 | TSC=./node_modules/typescript/bin/tsc 5 | TSLINT=./node_modules/tslint/bin/tslint 6 | 7 | test: build tslint 8 | cd out;../$(MOCHA) -g VimStyle 9 | 10 | testOriginalVim: build 11 | cd out;../$(MOCHA) -g OriginalVim 12 | 13 | testNeoVim: build 14 | cd out;../$(MOCHA) -g NeoVim 15 | 16 | build: node_modules 17 | $(TSC) 18 | 19 | tslint: build 20 | $(TSLINT) src/**/**.ts 21 | 22 | release: test 23 | vsce package 24 | vsce publish 25 | 26 | release-avoid-tests: build 27 | vsce package 28 | vsce publish 29 | 30 | install:node_modules 31 | 32 | node_modules: 33 | npm install 34 | 35 | clean: 36 | rm -rf out 37 | rm -f *.vsix 38 | buildcontainer: 39 | cd testcontainer/centos7;docker build -t vscode-vim-centos7 . 40 | cd testcontainer/ubuntu1604;docker build -t vscode-vim-ubuntu1604 . 41 | testincontainer-originalvim-centos7: 42 | docker run -it -v `pwd`:/root/ -w /root/out/ vscode-vim-centos7 vim --version 43 | docker run -it -v `pwd`:/root/ -w /root/out/ vscode-vim-centos7 ../$(MOCHA) -g OriginalVim 44 | testincontainer-originalvim-ubuntu1604: 45 | docker run -it -v `pwd`:/root/ -w /root/out/ vscode-vim-ubuntu1604 vim --version 46 | docker run -it -v `pwd`:/root/ -w /root/out/ vscode-vim-ubuntu1604 ../$(MOCHA) -g OriginalVim 47 | -------------------------------------------------------------------------------- /src/action/MoveLineAction.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | 3 | /** 4 | * j k 5 | * this refarenced motion/DownMotion.ts 6 | */ 7 | export class GoDownAction implements IAction { 8 | 9 | public Motion: IMotion; 10 | 11 | constructor() { 12 | this.Motion = null; 13 | } 14 | 15 | public GetActionType(): ActionType { 16 | return ActionType.LineMove; 17 | } 18 | 19 | public Execute(editor: IEditor, vim: IVimStyle) { 20 | 21 | let tabSize = editor.GetTabSize(); 22 | 23 | let from = editor.GetCurrentPosition(); 24 | let to = this.Motion.CalculateEnd(editor, vim, from); 25 | if (to == null) { 26 | // cancel 27 | return; 28 | } 29 | 30 | if (vim.LastAction && 31 | ActionType.LineMove === vim.LastAction.GetActionType()) { 32 | // use last move position 33 | let toText = editor.ReadLine(to.Line); 34 | to.Char = Utils.CalcSystemPosition(vim.LastMoveCharPosition, toText, tabSize); 35 | } else { 36 | let fromText = editor.ReadLine(from.Line); 37 | vim.LastMoveCharPosition = Utils.CalcVisialPosition(from.Char, fromText, tabSize); 38 | } 39 | 40 | if (from.Char === to.Char && from.Line === to.Line) { 41 | // not move 42 | return; 43 | } 44 | 45 | editor.SetPosition(editor.UpdateValidPosition(to, true)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/changeWord.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title change N words ( cNw ) 3 | 4 | (*) --> first 5 | if "count" then 6 | --> [ =1] "first count=1" 7 | else 8 | --> [>1] "first count>1" 9 | note right :do not count first character 10 | end if 11 | 12 | "first count=1" --> [character] "character count=1" 13 | "first count=1" --> [space] "space count=1" 14 | "first count=1" --> [linefeed] "delete until just before" 15 | "character count=1" --> [defference type character,linefeed,space] "delete until just before" 16 | "character count=1" -> [same type character] "character count=1" 17 | "space count=1" -> [space] "space count=1" 18 | "space count=1" --> [linefeed, character] "delete until just before" 19 | "delete until just before" --> (*) 20 | 21 | "first count>1" --> [character] "character" 22 | "first count>1" --> [space] "space" 23 | "first count>1" --> [linefeed] "linefeed" 24 | "character" --> [defferece type character] "count--" 25 | if "count" then 26 | -up-> [ =1] "character count=1" 27 | else 28 | -up-> [>1] "character" 29 | end if 30 | "character" -> [same type character] "character" 31 | "character" -> [space] "space" 32 | "character" -> [linefeed] "linefeed" 33 | "space" --> [character] "count--" 34 | "space" -> [space] "space" 35 | "space" -> [linefeed] linefeed 36 | "linefeed" -down-> [linefeed] "count-- at linefeed" 37 | if "count" then 38 | -up-> [ =1] "first count=1" 39 | else 40 | -up-> [>1] "linefeed" 41 | end if 42 | "linefeed" --> [character] "count--" 43 | "linefeed" -> [space] "space" 44 | 45 | @enduml -------------------------------------------------------------------------------- /src/motion/wordMorionStateModel/deleteWord.plantuml: -------------------------------------------------------------------------------- 1 | @startuml 2 | title delete N words ( dNw ) 3 | 4 | (*) --> first 5 | if "count" then 6 | --> [ =1] "first count=1" 7 | else 8 | --> [>1] "first count>1" 9 | note right :do not count first character 10 | end if 11 | 12 | "first count=1" --> [character] "character count=1" 13 | "first count=1" --> [space] "space count=1" 14 | "first count=1" --> [linefeed] "delete until linefeed" 15 | "delete until linefeed" --> (*) 16 | "character count=1" --> [defference type character,linefeed] "delete until just before" 17 | "character count=1" -> [same type character] "character count=1" 18 | "character count=1" -> [space] "space count=1" 19 | "space count=1" -> [space] "space count=1" 20 | "space count=1" --> [linefeed, character] "delete until just before" 21 | "delete until just before" --> (*) 22 | 23 | "first count>1" --> [character] "character" 24 | "first count>1" --> [space] "space" 25 | "first count>1" --> [linefeed] "linefeed" 26 | "character" --> [defferece type character] "count--" 27 | if "count" then 28 | -up-> [ =1] "character count=1" 29 | else 30 | -up-> [>1] "character" 31 | end if 32 | "character" -> [same type character] "character" 33 | "character" -> [space] "space" 34 | "character" -> [linefeed] "linefeed" 35 | "space" --> [character] "count--" 36 | "space" -> [space] "space" 37 | "space" -> [linefeed] linefeed 38 | "linefeed" -down-> [linefeed] "count-- at linefeed" 39 | if "count" then 40 | -up-> [ =1] "first count=1" 41 | else 42 | -up-> [>1] "linefeed" 43 | end if 44 | "linefeed" --> [character] "count--" 45 | "linefeed" -> [space] "space" 46 | 47 | @enduml 48 | 49 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}" 12 | ], 13 | "stopOnEntry": false, 14 | "sourceMaps": true, 15 | "outDir": "${workspaceRoot}/out/src", 16 | "preLaunchTask": "build" 17 | }, 18 | { 19 | "name": "Launch Tests", 20 | "type": "node", 21 | "request": "launch", 22 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 23 | "stopOnEntry": false, 24 | "sourceMaps": true, 25 | "args": [ 26 | "-g", 27 | "VimStyle J:" 28 | ], 29 | "cwd": "${workspaceRoot}/out", 30 | "runtimeExecutable": null, 31 | "preLaunchTask": "build" 32 | }, 33 | { 34 | "name": "Launch current tests", 35 | "type": "node", 36 | "request": "launch", 37 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 38 | "stopOnEntry": false, 39 | "sourceMaps": true, 40 | "args": [ 41 | "-g", 42 | "VimStyle w: N words forward w:word start at 2 spaces" 43 | ], 44 | "outDir": "${workspaceRoot}/out", 45 | "cwd": "${workspaceRoot}/out", 46 | "runtimeExecutable": null, 47 | "preLaunchTask": "build" 48 | }, 49 | { 50 | "type": "node", 51 | "request": "attach", 52 | "name": "Attach by Process ID", 53 | "processId": "${command:PickProcess}", 54 | "outFiles": [ 55 | "${workspaceRoot}/out" 56 | ] 57 | } 58 | 59 | ] 60 | } -------------------------------------------------------------------------------- /src/action/ReplaceCharacterOfSelecetdTextAction.ts: -------------------------------------------------------------------------------- 1 | import { RegisterItem } from "../core/Register"; 2 | import { Position, Range } from "../VimStyle"; 3 | 4 | /** 5 | * {Visual}c{char} 6 | */ 7 | export class ReplaceCharacterOfSelectedTextAction implements IRequireCharAction { 8 | 9 | public CharacterCode: number; 10 | 11 | public GetActionType(): ActionType { 12 | return ActionType.Edit; 13 | } 14 | 15 | public Execute(editor: IEditor, vim: IVimStyle) { 16 | 17 | let s = editor.GetCurrentVisualModeSelection(); 18 | s.Sort(); 19 | 20 | let char = String.fromCharCode(this.CharacterCode); 21 | 22 | let text = ""; 23 | if (s.start.Line === s.end.Line) { 24 | for (let i = s.start.Char; i < s.end.Char; i++) { 25 | text += char; 26 | } 27 | } else { 28 | let line = editor.ReadLine(s.start.Line); 29 | for (let c = s.start.Char; c < line.length; c++) { 30 | text += char; 31 | } 32 | text += "\n"; 33 | for (let l = s.start.Line + 1; l < s.end.Line; l++) { 34 | line = editor.ReadLine(l); 35 | for (let c of line) { 36 | text += char; 37 | } 38 | text += "\n"; 39 | } 40 | line = editor.ReadLine(s.end.Line); 41 | for (let c = 0; c < s.end.Char; c++) { 42 | text += char; 43 | } 44 | } 45 | 46 | editor.ReplaceRange(s, text); 47 | editor.SetPosition(s.start); 48 | } 49 | } 50 | 51 | /** 52 | * {Visual}c{char} 53 | */ 54 | export function ReplaceCharacterOfSelectedText(num: number): IAction { 55 | return new ReplaceCharacterOfSelectedTextAction(); 56 | } 57 | -------------------------------------------------------------------------------- /src/action/ExpandHighlightedTextAction.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range } from "../VimStyle"; 2 | export class ExpandHighlightedTextAction implements IAction { 3 | 4 | public Motion: IMotion; 5 | 6 | constructor() { 7 | this.Motion = null; 8 | } 9 | 10 | public GetActionType(): ActionType { 11 | return ActionType.Other; 12 | } 13 | 14 | public Execute(editor: IEditor, vim: IVimStyle) { 15 | let before = editor.GetCurrentVisualModeSelection(); 16 | let startPosition: Position; 17 | let currentPosition = editor.GetCurrentPosition(); 18 | 19 | if (before.start.IsEqual(currentPosition)) { 20 | startPosition = new Position(before.end.Line, before.end.Char - 1); 21 | } else { 22 | startPosition = before.start; 23 | currentPosition = new Position(currentPosition.Line, currentPosition.Char - 1); 24 | } 25 | 26 | let nextPosition = this.Motion.CalculateEnd(editor, vim, currentPosition); 27 | let after = new Range(); 28 | if (nextPosition.IsAfterOrEqual(startPosition)) { 29 | after.start = startPosition; 30 | let endLine = editor.ReadLine(nextPosition.Line); 31 | if (endLine.length > 0) { 32 | after.end = new Position(nextPosition.Line, nextPosition.Char + 1); 33 | } else { 34 | after.end = nextPosition; 35 | } 36 | } else { 37 | let startLine = editor.ReadLine(nextPosition.Line); 38 | if (startLine.length > 0) { 39 | after.start = new Position(startPosition.Line, startPosition.Char + 1); 40 | } else { 41 | after.start = startPosition; 42 | } 43 | after.end = nextPosition; 44 | } 45 | editor.ShowVisualMode(after, after.end); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/motion/RightMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | 5 | /** 6 | * h l 7 | * ch cl 8 | */ 9 | export class RightMotion extends AbstractMotion { 10 | 11 | public IsLeftDirection: boolean; 12 | 13 | constructor() { 14 | super(); 15 | this.IsLeftDirection = false; 16 | } 17 | 18 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 19 | let line = editor.ReadLineAtCurrentPosition(); 20 | let end = new Position(); 21 | end.Line = start.Line; 22 | if (this.IsLeftDirection) { 23 | end.Char = start.Char - this.Count; 24 | } else { 25 | end.Char = start.Char + this.Count; 26 | } 27 | return editor.UpdateValidPosition(end); 28 | } 29 | } 30 | 31 | // Nh 32 | export function GotoRight(num: number): IAction { 33 | let m = new RightMotion(); 34 | m.Count = num > 0 ? num : 1; 35 | let a = new GoAction(); 36 | a.Motion = new RightMotion(); 37 | a.Motion = m; 38 | return a; 39 | } 40 | 41 | // Nl 42 | export function GotoLeft(num: number): IAction { 43 | let m = new RightMotion(); 44 | m.Count = num > 0 ? num : 1; 45 | m.IsLeftDirection = true; 46 | let a = new GoAction(); 47 | a.Motion = new RightMotion(); 48 | a.Motion = m; 49 | return a; 50 | } 51 | 52 | // ch 53 | export function AddRightMotion(num: number, action: IAction): void { 54 | let m = new RightMotion(); 55 | m.Count = num > 0 ? num : 1; 56 | let a = action; 57 | a.Motion = m; 58 | } 59 | 60 | // cl 61 | export function AddLeftMotion(num: number, action: IAction): void { 62 | let m = new RightMotion(); 63 | m.IsLeftDirection = true; 64 | m.Count = num > 0 ? num : 1; 65 | let a = action; 66 | a.Motion = m; 67 | } 68 | -------------------------------------------------------------------------------- /src/action/JoinHighlightedLinesAction.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | import {Position, Range} from "../VimStyle"; 3 | 4 | /** 5 | * {Visual}J {VisualLine}J 6 | */ 7 | export class JoinHighlightedLinesAction implements IAction { 8 | 9 | public GetActionType(): ActionType { 10 | return ActionType.Edit; 11 | } 12 | 13 | public Execute(editor: IEditor, vim: IVimStyle) { 14 | let startLine: number; 15 | let endLine: number; 16 | if (vim.GetMode() === VimMode.Visual) { 17 | // Visual Mode 18 | let s = editor.GetCurrentVisualModeSelection(); 19 | startLine = s.start.Line; 20 | endLine = s.end.Line; 21 | } else { 22 | // Visual Line Mode 23 | let s = editor.GetCurrentVisualLineModeSelection(); 24 | startLine = s.startLine; 25 | endLine = s.endLine; 26 | } 27 | 28 | const docLines = editor.GetLastLineNum(); 29 | const startPos = new Position(startLine, 0); 30 | let endPos: IPosition; 31 | if (startLine === endLine) { 32 | if (startLine + 1 === docLines) { 33 | return; 34 | } 35 | endPos = new Position(endLine + 1, 0); 36 | } else { 37 | endPos = new Position(endLine, 0); 38 | } 39 | const range = new Range(); 40 | range.start = startPos; 41 | range.end = endPos; 42 | 43 | let text = editor.ReadRange(range); 44 | text = text.replace(/\r\n/g, " "); 45 | text = text.replace(/\n/g, " "); 46 | 47 | let pos = new Position(startPos.Line, text.length - 1); 48 | 49 | editor.ReplaceRange(range, text); 50 | editor.SetPosition(pos); 51 | } 52 | } 53 | 54 | /** 55 | * {Visual}J 56 | */ 57 | export function JoinHighlightedText(num: number): IAction { 58 | let a = new JoinHighlightedLinesAction(); 59 | return a; 60 | } 61 | 62 | /** 63 | * {VisualLine}J 64 | */ 65 | export function JoinHighlightedLines(num: number): IAction { 66 | let a = new JoinHighlightedLinesAction(); 67 | return a; 68 | } 69 | -------------------------------------------------------------------------------- /src/action/ReplaceCharacterAction.ts: -------------------------------------------------------------------------------- 1 | import { RegisterItem } from "../core/Register"; 2 | import { Position, Range } from "../VimStyle"; 3 | import { AbstractInsertTextAction } from "./AbstractInsertTextAction"; 4 | 5 | /** 6 | * Nr{char} Ngr{char} 7 | */ 8 | export class ReplaceCharacterAction implements IRequireCharAction, ICountableAction { 9 | 10 | public Count: number; 11 | public CharacterCode: number; 12 | public IsAffectingLayout: boolean; 13 | 14 | constructor() { 15 | this.Count = null; 16 | this.CharacterCode = null; 17 | this.IsAffectingLayout = true; 18 | } 19 | 20 | public GetActionType(): ActionType { 21 | return ActionType.Edit; 22 | } 23 | 24 | public Execute(editor: IEditor, vim: IVimStyle) { 25 | const cp = editor.GetCurrentPosition(); 26 | const line = editor.ReadLineAtCurrentPosition(); 27 | 28 | let text = ""; 29 | const char = String.fromCharCode(this.CharacterCode); 30 | for (let i = 0; i < this.Count; i++) { 31 | text += char; 32 | } 33 | 34 | let r = new Range(); 35 | r.start = cp; 36 | if (cp.Char + this.Count > line.length) { 37 | if (this.IsAffectingLayout) { 38 | return; 39 | } 40 | r.end = new Position(cp.Line, line.length); 41 | } else { 42 | r.end = new Position(cp.Line, cp.Char + this.Count); 43 | } 44 | let np: IPosition; 45 | np = new Position(cp.Line, cp.Char + this.Count - 1); 46 | 47 | editor.ReplaceRange(r, text); 48 | editor.SetPosition(np); 49 | return; 50 | } 51 | } 52 | 53 | /** 54 | * Nr{char} 55 | */ 56 | export function ReplaceCharacter(num: number): IAction { 57 | const a = new ReplaceCharacterAction(); 58 | a.Count = num === 0 ? 1 : num; 59 | a.IsAffectingLayout = true; 60 | return a; 61 | } 62 | 63 | /** 64 | * Ngr{char} 65 | */ 66 | export function ReplaceCharacterWithoutAffectingLayout(num: number): IAction { 67 | const a = new ReplaceCharacterAction(); 68 | a.Count = num === 0 ? 1 : num; 69 | a.IsAffectingLayout = false; 70 | return a; 71 | } 72 | -------------------------------------------------------------------------------- /src/core/Register.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | 3 | export class RegisterItem { 4 | public Type: RegisterType; 5 | public Body: string; 6 | } 7 | 8 | export class Register implements IRegister { 9 | private char: { [key: string]: IRegisterItem }; 10 | private roll: IRegisterItem[]; 11 | private yank: IRegisterItem; 12 | private unName: IRegisterItem; 13 | constructor() { 14 | this.char = {}; 15 | this.roll = []; 16 | this.yank = null; 17 | this.unName = null; 18 | } 19 | public Set(key: Key, value: IRegisterItem) { 20 | this.char[key] = value; 21 | } 22 | public SetYank(value: IRegisterItem) { 23 | this.unName = value; 24 | this.yank = value; 25 | } 26 | public SetRoll(value: IRegisterItem) { 27 | this.unName = value; 28 | this.roll.unshift(value); 29 | if (this.roll.length > 10) { 30 | this.roll.length = 10; 31 | } 32 | } 33 | public Get(key: Key): IRegisterItem { 34 | switch (key) { 35 | case "0": 36 | return this.yank; 37 | case "\"": 38 | return this.unName; 39 | } 40 | switch (Utils.GetKeyType(key)) { 41 | case VimKeyType.Charactor: 42 | return this.GetCharactorRegister(key); 43 | case VimKeyType.Number: 44 | return this.GetRollRegister(key); 45 | } 46 | return null; 47 | } 48 | public GetUnName(): IRegisterItem { 49 | return this.unName; 50 | } 51 | public GetRollFirst(value: IRegisterItem) { 52 | if (this.roll.length > 0) { 53 | return this.roll[0]; 54 | } 55 | return null; 56 | } 57 | private GetRollRegister(key: Key) { 58 | let n = Utils.KeyToNum(key); 59 | if (this.roll.length > n + 1) { 60 | return this.roll[n]; 61 | } 62 | return null; 63 | } 64 | private GetCharactorRegister(key: Key) { 65 | if (this.char[key] === undefined || this.char[key] == null) { 66 | return null; 67 | } 68 | return this.char[key]; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test/NeoVimTest.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import * as fs from "async-file"; 3 | import { exec } from "child_process"; 4 | import { Position, VimStyle } from "../src/VimStyle"; 5 | import { VimTests } from "./vim/VimTests"; 6 | import { VirtualEditor } from "./VirtualEditor"; 7 | 8 | for (let target in VimTests) { 9 | // tslint:disable-next-line:only-arrow-functions 10 | describe("NeoVim" + target, function () { 11 | this.timeout(500); 12 | let test = VimTests[target]; 13 | for (let specName in test) { 14 | it(specName, async () => { 15 | let spec = test[specName]; 16 | let text = spec["in"][0]; 17 | for (let i = 1; i < spec["in"].length; i++) { 18 | text += "\n" + spec["in"][i]; 19 | } 20 | await fs.writeFile("NeoVimInput", text); 21 | text = "/|\n"; 22 | text += ":normal x\n"; 23 | let list = spec.key.split("_"); 24 | for (let item of list) { 25 | text += ":exe \":normal " + item.replace("\n", "\\") + "\"\n"; 26 | } 27 | text += ":normal i|\n"; 28 | text += ":w! NeoVimOutput\n"; 29 | text += ":q!\n"; 30 | await fs.writeFile("NeoVimKey", text); 31 | await execAsync("nvim -u NONE -s NeoVimKey NeoVimInput"); 32 | text = await fs.readFile("NeoVimOutput"); 33 | let out = text.toString().split("\n"); 34 | let outText = out[0]; 35 | let specText = spec.out[0]; 36 | for (let i = 1; i < spec.out.length; i++) { 37 | outText += "\n" + out[i]; 38 | specText += "\n" + spec.out[i]; 39 | } 40 | assert.equal(outText, specText); 41 | }); 42 | } 43 | }); 44 | } 45 | 46 | function execAsync(cmd: string): Promise { 47 | return new Promise((resolve, reject) => { 48 | exec(cmd).on("exit", (code, signal) => { 49 | resolve(null); 50 | }).on("error", (err) => { 51 | reject(null); 52 | }); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/action/InsertTextAction.ts: -------------------------------------------------------------------------------- 1 | import * as FirstCharacterMotion from "../motion/FirstCharacterMotion"; 2 | import * as LastCharacterInLineMotion from "../motion/LastCharacterInLineMotion"; 3 | import * as RightMotion from "../motion/RightMotion"; 4 | import { AbstractInsertTextAction } from "./AbstractInsertTextAction"; 5 | 6 | /** 7 | * a A i I 8 | */ 9 | export class InsertTextAction extends AbstractInsertTextAction { 10 | public Motion: IMotion; 11 | 12 | constructor(m?: IMotion) { 13 | super(); 14 | if (m === undefined) { 15 | this.Motion = null; 16 | } else { 17 | this.Motion = m; 18 | } 19 | } 20 | 21 | public GetActionType(): ActionType { 22 | return ActionType.Insert; 23 | } 24 | 25 | public Execute(editor: IEditor, vim: IVimStyle) { 26 | let p = editor.GetCurrentPosition(); 27 | if (this.Motion != null) { 28 | p = this.Motion.CalculateEnd(editor, vim, p); 29 | } 30 | if (this.insertText !== null) { 31 | editor.Insert(p, this.insertText); 32 | editor.SetPosition(this.calcPositionAfterInsert(p)); 33 | } else { 34 | vim.ApplyInsertMode(p); 35 | let text = editor.ReadLineAtCurrentPosition(); 36 | this.insertModeInfo = { 37 | DocumentLineCount: editor.GetLastLineNum() + 1, 38 | Position: p.Copy(), 39 | BeforeText: text.substring(0, p.Char), 40 | AfterText: text.substring(p.Char), 41 | }; 42 | } 43 | } 44 | 45 | } 46 | 47 | /** 48 | * a 49 | */ 50 | export function AppendTextAfterCursor(num: number): IAction { 51 | let m = new RightMotion.RightMotion(); 52 | m.Count = 1; 53 | return new InsertTextAction(m); 54 | } 55 | 56 | /** 57 | * A 58 | */ 59 | export function AppendTextAtEndOfLine(num: number): IAction { 60 | let m = new LastCharacterInLineMotion.LastCharacterInLineMotion(); 61 | return new InsertTextAction(m); 62 | } 63 | 64 | /** 65 | * i 66 | */ 67 | export function InsertTextBeforeCursor(num: number): IAction { 68 | return new InsertTextAction(); 69 | } 70 | 71 | /** 72 | * I 73 | */ 74 | export function InsertTextBeforeFirstNonBlankInLine(num: number): IAction { 75 | let m = new FirstCharacterMotion.FirstCharacterMotion(); 76 | m.Target = FirstCharacterMotion.Target.Current; 77 | return new InsertTextAction(m); 78 | } 79 | -------------------------------------------------------------------------------- /test/OriginalVimTest.test.ts: -------------------------------------------------------------------------------- 1 | import { VirtualEditor } from "./VirtualEditor"; 2 | import { Position, VimStyle } from "../src/VimStyle"; 3 | import { VimTests } from "./vim/VimTests"; 4 | import { exec } from "child_process"; 5 | import * as fs from 'async-file'; 6 | import * as assert from "assert"; 7 | 8 | for (let target in VimTests) { 9 | describe("OriginalVim " + target, function () { 10 | this.timeout(500); 11 | let test = VimTests[target]; 12 | for (let specName in test) { 13 | (function (specName) { 14 | it(specName, async function () { 15 | let spec = test[specName]; 16 | let text = spec["in"][0]; 17 | for (let i = 1; i < spec["in"].length; i++) { 18 | text += "\n" + spec["in"][i]; 19 | } 20 | await fs.writeFile("OriginalVimInput", text); 21 | text = "/|\n"; 22 | text += ":normal x\n"; 23 | let list = spec["key"].split("_"); 24 | for (let i = 0; i < list.length; i++) { 25 | text += ":exe \":normal " + list[i].replace("\n", "\\").replace("\"", "\\\"") + "\"\n"; 26 | } 27 | text += ":normal i|\n"; 28 | text += ":w! OriginalVimOutput\n"; 29 | text += ":q!\n"; 30 | await fs.writeFile("OriginalVimKey", text); 31 | await execAsync("HOME=../test vim -s OriginalVimKey OriginalVimInput"); 32 | text = await fs.readFile("OriginalVimOutput"); 33 | let out = text.toString().split("\n"); 34 | let outText = out[0]; 35 | let specText = spec.out[0]; 36 | for (let i = 1; i < spec["out"].length; i++) { 37 | outText += "\n" + out[i]; 38 | specText += "\n" + spec.out[i]; 39 | } 40 | assert.equal(outText, specText); 41 | }); 42 | })(specName); 43 | } 44 | }); 45 | } 46 | 47 | function execAsync(cmd: string): Promise { 48 | return new Promise((resolve, reject) => { 49 | exec(cmd).on("exit", (code, signal) => { 50 | resolve(null); 51 | }).on("error", (err) => { 52 | reject(null); 53 | }); 54 | }); 55 | } -------------------------------------------------------------------------------- /src/motion/DownMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoDownAction } from "../action/MoveLineAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * j k 8 | * cj ck 9 | */ 10 | export class DownMotion extends AbstractMotion { 11 | 12 | public IsUpDirection: boolean; 13 | 14 | constructor() { 15 | super(); 16 | this.IsUpDirection = false; 17 | } 18 | 19 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 20 | 21 | let tabSize = editor.GetTabSize(); 22 | 23 | let end = new Position(); 24 | if (this.IsUpDirection) { 25 | end.Line = start.Line - this.Count; 26 | if (end.Line < 0) { 27 | end.Line = 0; 28 | } 29 | } else { 30 | let lastLine = editor.GetLastLineNum(); 31 | end.Line = start.Line + this.Count; 32 | if (end.Line > lastLine) { 33 | end.Line = lastLine; 34 | } 35 | } 36 | 37 | let startText = editor.ReadLine(start.Line); 38 | let visualChar = Utils.CalcVisialPosition(start.Char, startText, tabSize); 39 | 40 | let endText = editor.ReadLine(end.Line); 41 | end.Char = Utils.CalcSystemPosition(visualChar, endText, tabSize); 42 | 43 | return editor.UpdateValidPosition(end); 44 | } 45 | } 46 | /** 47 | * j 48 | */ 49 | export function GoDown(num: number): IAction { 50 | let m = new DownMotion(); 51 | let a = new GoDownAction(); 52 | m.Count = num > 0 ? num : 1; 53 | a.Motion = m; 54 | return a; 55 | } 56 | 57 | /** 58 | * k 59 | */ 60 | export function GoUp(num: number): IAction { 61 | let m = new DownMotion(); 62 | m.IsUpDirection = true; 63 | m.Count = num > 0 ? num : 1; 64 | let a = new GoDownAction(); 65 | a.Motion = m; 66 | return a; 67 | } 68 | 69 | /** 70 | * cj 71 | */ 72 | export function AddDownMotion(num: number, action: IAction) { 73 | let m = new DownMotion(); 74 | let a = action; 75 | m.Count = num > 0 ? num : 1; 76 | a.Motion = m; 77 | a.IsLine = true; 78 | } 79 | 80 | /** 81 | * ck 82 | */ 83 | export function AddUpMotion(num: number, action: IAction) { 84 | let m = new DownMotion(); 85 | m.IsUpDirection = true; 86 | m.Count = num > 0 ? num : 1; 87 | let a = action; 88 | a.Motion = m; 89 | a.IsLine = true; 90 | } 91 | -------------------------------------------------------------------------------- /src/action/DeleteYankChangeHighlightedTextAction.ts: -------------------------------------------------------------------------------- 1 | import { RegisterItem } from "../core/Register"; 2 | import { Position, Range } from "../VimStyle"; 3 | 4 | /** 5 | * {Visual}d {Visual}y {Visual}c 6 | */ 7 | export class DeleteYankChangeHighlightedTextAction implements IInsertTextAction { 8 | 9 | public isInsert: boolean; 10 | public isOnlyYanc: boolean; 11 | private insertText: string; 12 | private insertModeInfo: any; 13 | 14 | constructor() { 15 | this.isInsert = false; 16 | this.isOnlyYanc = false; 17 | } 18 | 19 | public GetActionType(): ActionType { 20 | if (this.isOnlyYanc) { 21 | return ActionType.Other; 22 | } else if (this.isInsert) { 23 | return ActionType.Insert; 24 | } 25 | return ActionType.Other; 26 | } 27 | 28 | public SetChangeOption() { 29 | this.isInsert = true; 30 | } 31 | 32 | public SetOnlyYancOption() { 33 | this.isOnlyYanc = true; 34 | } 35 | 36 | public GetInsertModeInfo(): any { 37 | return this.insertModeInfo; 38 | } 39 | 40 | public SetInsertText(text: string) { 41 | this.insertText = text; 42 | } 43 | 44 | public Execute(editor: IEditor, vim: IVimStyle) { 45 | 46 | let s = editor.GetCurrentVisualModeSelection(); 47 | s.Sort(); 48 | 49 | if (s.end.Char === 0 && editor.GetLastLineNum() !== s.end.Line) { 50 | let lastLine = editor.ReadLine(s.end.Line); 51 | if (lastLine.length === 0) { 52 | s = s.Copy(); 53 | s.end.Line += 1; 54 | } 55 | } 56 | 57 | let item = new RegisterItem(); 58 | item.Body = editor.ReadRange(s); 59 | item.Type = RegisterType.Text; 60 | vim.Register.SetRoll(item); 61 | 62 | if (this.isInsert) { 63 | vim.ApplyInsertMode(); 64 | } else { 65 | vim.ApplyNormalMode(); 66 | } 67 | 68 | if (!this.isOnlyYanc) { 69 | editor.DeleteRange(s, s.start); 70 | } else { 71 | editor.SetPosition(s.start); 72 | } 73 | 74 | } 75 | } 76 | 77 | /** 78 | * {visual}d 79 | */ 80 | export function DeleteHighlightedText(num: number): IAction { 81 | return new DeleteYankChangeHighlightedTextAction(); 82 | } 83 | /** 84 | * {visual}y 85 | */ 86 | export function YankHighlightedText(num: number): IAction { 87 | let a = new DeleteYankChangeHighlightedTextAction(); 88 | a.SetOnlyYancOption(); 89 | return a; 90 | } 91 | /** 92 | * {visual}c 93 | */ 94 | export function ChangeHighlightedText(num: number): IAction { 95 | let a = new DeleteYankChangeHighlightedTextAction(); 96 | a.SetChangeOption(); 97 | return a; 98 | } 99 | -------------------------------------------------------------------------------- /src/action/PutRegisterAction.ts: -------------------------------------------------------------------------------- 1 | import { Position } from "../VimStyle"; 2 | 3 | /** 4 | * p P 5 | */ 6 | export class PutRegisterAction implements IAction { 7 | 8 | public IsPrev: boolean; 9 | public RegisterKey: Key; 10 | public Count: number; 11 | 12 | constructor() { 13 | this.IsPrev = false; 14 | this.RegisterKey = null; 15 | } 16 | 17 | public GetActionType(): ActionType { 18 | return ActionType.Edit; 19 | } 20 | 21 | public Execute(editor: IEditor, vim: IVimStyle) { 22 | let item: IRegisterItem; 23 | if (this.RegisterKey == null) { 24 | item = vim.Register.GetUnName(); 25 | } else { 26 | item = vim.Register.Get(this.RegisterKey); 27 | } 28 | if (item == null) { 29 | return; 30 | } 31 | let content = item.Body; 32 | let cp = editor.GetCurrentPosition(); 33 | if (item.Type === RegisterType.Text) { 34 | if (this.IsPrev) { 35 | // paste before posision character 36 | editor.InsertTextAtCurrentPosition(item.Body); 37 | editor.SetPosition(cp); 38 | return; 39 | } 40 | // paste after position charactor 41 | let np = new Position(); 42 | np.Char = cp.Char + 1; 43 | np.Line = cp.Line; 44 | editor.Insert(np, content); 45 | editor.SetPosition(np); 46 | return; 47 | } else { 48 | // line Paste 49 | let np = new Position(); 50 | np.Char = 0; 51 | if (this.IsPrev) { 52 | // paste at home of current position¥ 53 | np.Line = cp.Line; 54 | } else { 55 | // paste at next line 56 | np.Line = cp.Line + 1; 57 | if (cp.Line === editor.GetLastLineNum()) { 58 | // next line is last 59 | content = "\n" + content.substring(0, content.length - 1); 60 | let lp = editor.GetLastPosition(); 61 | editor.Insert(lp, content); 62 | editor.SetPosition(np); 63 | return; 64 | } 65 | } 66 | editor.Insert(np, content); 67 | editor.SetPosition(np); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * p 74 | */ 75 | export function PutRegisterAfterCursorPosition(num: number): IAction { 76 | let a = new PutRegisterAction(); 77 | a.Count = num === 0 ? 1 : num; 78 | return a; 79 | } 80 | 81 | /** 82 | * P 83 | */ 84 | export function PutRegisterBeforeCursorPosition(num: number): IAction { 85 | let a = new PutRegisterAction(); 86 | a.IsPrev = true; 87 | a.Count = num === 0 ? 1 : num; 88 | return a; 89 | } 90 | -------------------------------------------------------------------------------- /src/motion/ParagraphMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * { } 8 | */ 9 | export class ParagraphMotion extends AbstractMotion { 10 | 11 | public IsUpDirection: boolean; 12 | 13 | constructor() { 14 | super(); 15 | this.IsUpDirection = false; 16 | } 17 | 18 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 19 | 20 | let tabSize = editor.GetTabSize(); 21 | 22 | let line = start.Line; 23 | let lineText = editor.ReadLine(start.Line); 24 | let previousLineIsBlank: boolean = lineText.length === 0; 25 | let lastLine = editor.GetLastLineNum(); 26 | let count = this.Count; 27 | 28 | while (count > 0) { 29 | 30 | if (this.IsUpDirection) { 31 | line--; 32 | if (line === 0) { 33 | break; 34 | } 35 | } else { 36 | line++; 37 | if (line > lastLine) { 38 | return editor.GetLastPosition(); 39 | } 40 | } 41 | 42 | lineText = editor.ReadLine(line); 43 | if (lineText.length === 0 && !previousLineIsBlank) { 44 | count--; 45 | previousLineIsBlank = true; 46 | } else if (lineText.length > 0) { 47 | previousLineIsBlank = false; 48 | } 49 | 50 | } 51 | 52 | let end = new Position(line, 0); 53 | return editor.UpdateValidPosition(end); 54 | } 55 | } 56 | 57 | // N{ 58 | export function GotoParagraphBackword(num: number): IAction { 59 | let a = new GoAction(); 60 | let m: ParagraphMotion = new ParagraphMotion(); 61 | m.IsUpDirection = true; 62 | m.Count = num > 0 ? num : 1; 63 | a.Motion = m; 64 | return a; 65 | } 66 | 67 | // cN{ 68 | export function AddParagraphBackwordMotion(num: number, action: IAction) { 69 | let m: ParagraphMotion = new ParagraphMotion(); 70 | m.IsUpDirection = true; 71 | m.Count = num > 0 ? num : 1; 72 | let a = action; 73 | a.Motion = m; 74 | } 75 | 76 | // N} 77 | export function GotoParagraphFoword(num: number): IAction { 78 | let a = new GoAction(); 79 | let m: ParagraphMotion = new ParagraphMotion(); 80 | m.IsUpDirection = false; 81 | m.Count = num > 0 ? num : 1; 82 | a.Motion = m; 83 | return a; 84 | } 85 | 86 | // cN} 87 | export function AddParagraphFowordMotion(num: number, action: IAction) { 88 | let m: ParagraphMotion = new ParagraphMotion(); 89 | m.IsUpDirection = false; 90 | m.Count = num > 0 ? num : 1; 91 | let a = action; 92 | a.Motion = m; 93 | } 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vimStyle", 3 | "displayname": "Vim Style", 4 | "description": "Vim emulator for VSCode", 5 | "icon": "vim.png", 6 | "galleryBanner": { 7 | "color": "#229932", 8 | "theme": "dark" 9 | }, 10 | "license": "MIT License", 11 | "bugs": { 12 | "url": "https://github.com/74th/vscode-vim/issues" 13 | }, 14 | "homepage": "https://github.com/74th/vscode-vim", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/74th/vscode-vim.git" 18 | }, 19 | "version": "0.5.7", 20 | "publisher": "74th", 21 | "engines": { 22 | "vscode": "^1.31.0" 23 | }, 24 | "keywords": [ 25 | "vim" 26 | ], 27 | "categories": [ 28 | "Other", 29 | "Keymaps" 30 | ], 31 | "activationEvents": [ 32 | "*" 33 | ], 34 | "main": "./out/src/extension", 35 | "contributes": { 36 | "configuration": { 37 | "type": "object", 38 | "title": "vim style configuration", 39 | "properties": { 40 | "vimStyle.showMode": { 41 | "type": "boolean", 42 | "default": true, 43 | "description": "Show mode in status bar" 44 | }, 45 | "vimStyle.useErgonomicKeyForMotion": { 46 | "type": "boolean", 47 | "default": false, 48 | "description": "move a cursor by `jkl;` keys" 49 | }, 50 | "vimStyle.changeCursorStyle": { 51 | "type": "boolean", 52 | "default": true, 53 | "description": "change cursor by mode" 54 | }, 55 | "vimStyle.defaultMode": { 56 | "type": "string", 57 | "default": "normal", 58 | "description": "default mode", 59 | "enum": [ 60 | "normal", 61 | "insert" 62 | ] 63 | }, 64 | "vimStyle.enabled": { 65 | "type": "boolean", 66 | "default": true 67 | }, 68 | "vimStyle.imapEsc": { 69 | "type": "string", 70 | "default": "", 71 | "description": "keys go to normal mode from insert mode" 72 | }, 73 | "vimStyle.vimrc": { 74 | "type": "array", 75 | "default": [ 76 | "" 77 | ], 78 | "description": "some vim style settings" 79 | } 80 | } 81 | }, 82 | "keybindings": [ 83 | { 84 | "command": "vim.Esc", 85 | "key": "escape", 86 | "when": "editorTextFocus && vim.enabled" 87 | } 88 | ] 89 | }, 90 | "scripts": { 91 | "vscode:prepublish": "tsc -p ./", 92 | "compile": "tsc -watch -p ./", 93 | "postinstall": "node ./node_modules/vscode/bin/install" 94 | }, 95 | "devDependencies": { 96 | "@types/mocha": "^2.2.42", 97 | "@types/node": "^10.12.21", 98 | "async-file": "^2.0.2", 99 | "mocha": "^5.2.0", 100 | "source-map-support": "^0.4.7", 101 | "tslint": "^5.12.1", 102 | "typescript": "^3.2.4", 103 | "vscode": "^1.1.29" 104 | }, 105 | "__metadata": { 106 | "id": "c7009534-d65e-4619-bd71-27742a7965c5", 107 | "publisherDisplayName": "74th", 108 | "publisherId": "3cef02ed-b83e-4b08-b16f-d427ad5fa333" 109 | }, 110 | "dependencies": {} 111 | } 112 | -------------------------------------------------------------------------------- /src/action/OpenNewLineAndAppendTextAction.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractInsertTextAction } from "./AbstractInsertTextAction"; 4 | 5 | export class OpenNewLineAndAppendTextAction extends AbstractInsertTextAction { 6 | 7 | public IsAbove: boolean; 8 | 9 | constructor() { 10 | super(); 11 | this.IsAbove = false; 12 | } 13 | 14 | public GetActionType(): ActionType { 15 | return ActionType.Insert; 16 | } 17 | 18 | public Execute(editor: IEditor, vim: IVimStyle) { 19 | let currentPosition = editor.GetCurrentPosition(); 20 | let selecterPosition = new Position(); 21 | selecterPosition.Char = 0; 22 | let insertPosition = new Position(); 23 | let currentLine = editor.ReadLineAtCurrentPosition(); 24 | if (this.IsAbove) { 25 | selecterPosition.Line = currentPosition.Line; 26 | insertPosition.Line = currentPosition.Line; 27 | insertPosition.Char = 0; 28 | } else { 29 | selecterPosition.Line = currentPosition.Line + 1; 30 | insertPosition.Line = currentPosition.Line; 31 | insertPosition.Char = currentLine.length; 32 | } 33 | 34 | let insertSpace = appendWhiteSpace(currentLine); 35 | selecterPosition.Char = insertSpace.length; 36 | 37 | let insertText = insertSpace; 38 | if (this.insertText !== null) { 39 | insertText += this.insertText; 40 | } 41 | if (this.IsAbove) { 42 | insertText = insertText + "\n"; 43 | } else { 44 | insertText = "\n" + insertText; 45 | } 46 | editor.Insert(insertPosition, insertText); 47 | 48 | if (this.insertText === null) { 49 | 50 | this.insertModeInfo = { 51 | DocumentLineCunt: editor.GetLastLineNum() + 1, 52 | Position: selecterPosition.Copy(), 53 | BeforeText: insertSpace, 54 | AfterText: "", 55 | }; 56 | 57 | vim.ApplyInsertMode(selecterPosition); 58 | 59 | } else { 60 | editor.SetPosition(this.calcPositionAfterInsert(selecterPosition)); 61 | } 62 | } 63 | } 64 | 65 | function appendWhiteSpace(prevLine: string) { 66 | if (prevLine.length === 0) { 67 | return ""; 68 | } 69 | 70 | let firstChar = prevLine.charCodeAt(0); 71 | if (Utils.GetCharClass(firstChar) !== CharGroup.Spaces) { 72 | return ""; 73 | } 74 | 75 | let i = 0; 76 | for (i = 0; i < prevLine.length; i++) { 77 | if (prevLine.charCodeAt(i) !== firstChar) { 78 | break; 79 | } 80 | } 81 | return prevLine.substr(0, i); 82 | } 83 | // o 84 | export function OpenNewLineBelowCurrentLineAndAppendText(num: number): IAction { 85 | return new OpenNewLineAndAppendTextAction(); 86 | 87 | } 88 | 89 | // O 90 | export function OpenNewLineAboveCurrentLineAndAppendText(num: number): IAction { 91 | let a = new OpenNewLineAndAppendTextAction(); 92 | a.IsAbove = true; 93 | return a; 94 | } 95 | -------------------------------------------------------------------------------- /test/vim/DeletingTextTests.ts: -------------------------------------------------------------------------------- 1 | export let DeletingText = {}; 2 | 3 | DeletingText["Nx:delete N characters under and after the cursor"] = { 4 | "x:delete charactor": { 5 | in: ["a|bc"], 6 | key: "x", 7 | out: ["a|c"], 8 | }, 9 | "3x:delete 3 charactors": { 10 | in: ["a|bcdef"], 11 | key: "3x", 12 | out: ["a|ef"], 13 | }, 14 | }; 15 | 16 | DeletingText["NX:delete N characters before the cursor"] = { 17 | "X:delete previous charactor": { 18 | in: ["ab|c"], 19 | key: "X", 20 | out: ["a|c"], 21 | }, 22 | "3X:delete 3 previous charactors": { 23 | in: ["abcd|ef"], 24 | key: "3X", 25 | out: ["a|ef"], 26 | }, 27 | }; 28 | 29 | // Nd{motion} dw cw and others are in TextObjectMotion.js 30 | 31 | // {visual}d 32 | DeletingText["{visual}d:delete the highlighted text"] = { 33 | "vwld:delete visual text": { 34 | in: [ 35 | "a|bc def ghe", 36 | ], 37 | key: "vwld", 38 | out: [ 39 | "a|f ghe", 40 | ], 41 | }, 42 | }; 43 | 44 | DeletingText["Ndd:delete N lines"] = { 45 | "dd:delete line": { 46 | in: [ 47 | "abc", 48 | "d|ef", 49 | "ghe", 50 | ], 51 | key: "dd", 52 | out: [ 53 | "abc", 54 | "|ghe", 55 | ], 56 | }, 57 | "3dd:delete 3 lines": { 58 | in: ["a", "|b", "c", "d", "e", "f"], 59 | key: "3dd", 60 | out: ["a", "|e", "f"], 61 | }, 62 | }; 63 | 64 | // ND 65 | // N3D not support 66 | DeletingText["J:join N-1 lines (delete s)"] = { 67 | "J:join 1 line": { 68 | in: ["a", "|b", "c", "d"], 69 | key: "J", 70 | out: ["a", "b| c", "d"], 71 | }, 72 | "3J:join 3 lines": { 73 | in: ["a", "|b", "c", "d", "e"], 74 | key: "3J", 75 | out: ["a", "b c| d", "e"], 76 | }, 77 | "3J:join 3 lines including last line": { 78 | in: ["a", "b", "c", "|d", "e"], 79 | key: "3J", 80 | out: ["a", "b", "c", "d| e"], 81 | }, 82 | "vJ:join 1 lines": { 83 | in: ["a", "b", "|ccc", "d", "e"], 84 | key: "vlJ", 85 | out: ["a", "b", "ccc| d", "e"], 86 | }, 87 | "vJ:join 3 lines": { 88 | in: ["a", "|b", "c", "d", "e"], 89 | key: "vjjJ", 90 | out: ["a", "b c| d", "e"], 91 | }, 92 | "vJ:join 3 lines include last line": { 93 | in: ["a", "b", "|c", "d", "e"], 94 | key: "vjjJ", 95 | out: ["a", "b", "c d| e"], 96 | }, 97 | "VJ:join 1 lines": { 98 | in: ["a", "b", "|ccc", "d", "e"], 99 | key: "VlJ", 100 | out: ["a", "b", "ccc| d", "e"], 101 | }, 102 | "VJ:join 3 lines": { 103 | in: ["a", "|b", "c", "d", "e"], 104 | key: "VjjJ", 105 | out: ["a", "b c| d", "e"], 106 | }, 107 | "VJ:join 3 lines include last line": { 108 | in: ["a", "b", "|c", "d", "e"], 109 | key: "VjjJ", 110 | out: ["a", "b", "c d| e"], 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /test/vim/TextObjects.ts: -------------------------------------------------------------------------------- 1 | export let TextObjects = {}; 2 | 3 | TextObjects["di("] = { 4 | "di(": { 5 | in: ["( (| ) )"], 6 | key: "di(", 7 | out: ["( (|) )"], 8 | }, 9 | "d2i)": { 10 | in: ["( ( ) (| ) ( ) )"], 11 | key: "d2i)", 12 | out: ["(|)"], 13 | }, 14 | "di{": { 15 | in: ["{ {| } }"], 16 | key: "di{", 17 | out: ["{ {|} }"], 18 | }, 19 | "d2i}": { 20 | in: ["{ { } {| } { } }"], 21 | key: "d2i}", 22 | out: ["{|}"], 23 | }, 24 | "di<": { 25 | in: ["< <| > >"], 26 | key: "di<", 27 | out: ["< <|> >"], 28 | }, 29 | "d2i>": { 30 | in: ["< < > <| > < > >"], 31 | key: "d2i>", 32 | out: ["<|>"], 33 | }, 34 | "di[": { 35 | in: ["[ [| ] ]"], 36 | key: "di[", 37 | out: ["[ [|] ]"], 38 | }, 39 | "d2i]": { 40 | in: ["[ [ ] [| ] [ ] ]"], 41 | key: "d2i]", 42 | out: ["[|]"], 43 | }, 44 | "d3i[": { 45 | in: ["[ [ ] [| ] [ ] ]"], 46 | key: "d3i]", 47 | out: ["[ [ ] [| ] [ ] ]"], 48 | }, 49 | }; 50 | 51 | TextObjects["da("] = { 52 | "da(": { 53 | in: ["( (| ) )"], 54 | key: "da(", 55 | out: ["( | )"], 56 | }, 57 | "d2a)": { 58 | in: [" ( ( ) (| ) ( ) ) "], 59 | key: "d2a)", 60 | out: [" | "], 61 | }, 62 | "da{": { 63 | in: ["{ {| } }"], 64 | key: "da{", 65 | out: ["{ | }"], 66 | }, 67 | "d2a}": { 68 | in: [" { { } {| } { } } "], 69 | key: "d2a}", 70 | out: [" | "], 71 | }, 72 | "da<": { 73 | in: ["< <| > >"], 74 | key: "da<", 75 | out: ["< | >"], 76 | }, 77 | "d2a>": { 78 | in: [" < < > <| > < > > "], 79 | key: "d2a>", 80 | out: [" | "], 81 | }, 82 | "da[": { 83 | in: ["[ [| ] ]"], 84 | key: "da[", 85 | out: ["[ | ]"], 86 | }, 87 | "d2a]": { 88 | in: [" [ [ ] [| ] [ ] ] "], 89 | key: "d2a]", 90 | out: [" | "], 91 | }, 92 | "d3a[": { 93 | in: ["[ [ ] [| ] [ ] ]"], 94 | key: "d3a]", 95 | out: ["[ [ ] [| ] [ ] ]"], 96 | }, 97 | }; 98 | 99 | TextObjects["di'"] = { 100 | "di'": { 101 | in: ["' '| ' '"], 102 | key: "di'", 103 | out: ["' '|' '"], 104 | }, 105 | "di\"": { 106 | in: ["\" \"| \" \""], 107 | key: "di\"", 108 | out: ["\" \"|\" \""], 109 | }, 110 | "di`": { 111 | in: ["` `| ` `"], 112 | key: "di`", 113 | out: ["` `|` `"], 114 | }, 115 | } 116 | 117 | TextObjects["da'"] = { 118 | "da'": { 119 | in: ["' '| ' '"], 120 | key: "da'", 121 | out: ["' |'"], 122 | }, 123 | "da\"": { 124 | in: ["\" \"| \" \""], 125 | key: "da\"", 126 | out: ["\" |\""], 127 | }, 128 | "da`": { 129 | in: ["` `| ` `"], 130 | key: "da`", 131 | out: ["` |`"], 132 | }, 133 | } -------------------------------------------------------------------------------- /src/action/DeleteYankChangeHighlightedLineAction.ts: -------------------------------------------------------------------------------- 1 | import { RegisterItem } from "../core/Register"; 2 | import { Position, Range } from "../VimStyle"; 3 | 4 | /** 5 | * {VisualLine}d {VisualLine} y {VisualLine}c 6 | */ 7 | export class DeleteYankChangeHighlightedLineAction implements IInsertTextAction { 8 | 9 | public isInsert: boolean; 10 | public isOnlyYanc: boolean; 11 | private insertText: string; 12 | private insertModeInfo: any; 13 | 14 | constructor() { 15 | this.isInsert = false; 16 | this.isOnlyYanc = false; 17 | } 18 | 19 | public GetActionType(): ActionType { 20 | if (this.isOnlyYanc) { 21 | return ActionType.Other; 22 | } else if (this.isInsert) { 23 | return ActionType.Insert; 24 | } 25 | return ActionType.Other; 26 | } 27 | 28 | public SetChangeOption() { 29 | this.isInsert = true; 30 | } 31 | 32 | public SetOnlyYancOption() { 33 | this.isOnlyYanc = true; 34 | } 35 | 36 | public GetInsertModeInfo(): any { 37 | return this.insertModeInfo; 38 | } 39 | 40 | public SetInsertText(text: string) { 41 | this.insertText = text; 42 | } 43 | 44 | public Execute(editor: IEditor, vim: IVimStyle) { 45 | 46 | let s = editor.GetCurrentVisualLineModeSelection(); 47 | let n1line: number; 48 | let n2line: number; 49 | if (s.startLine < s.endLine) { 50 | n1line = s.startLine; 51 | n2line = s.endLine; 52 | } else { 53 | n1line = s.endLine; 54 | n2line = s.startLine; 55 | } 56 | let line2 = editor.ReadLine(n2line); 57 | let n1 = new Position(n1line, 0); 58 | let n2 = new Position(n2line, line2.length); 59 | let nrange = new Range(); 60 | nrange.start = n1; 61 | nrange.end = n2; 62 | 63 | let item = new RegisterItem(); 64 | item.Body = editor.ReadRange(nrange) + "\n"; 65 | item.Type = RegisterType.LineText; 66 | vim.Register.SetRoll(item); 67 | 68 | if (this.isOnlyYanc) { 69 | // y 70 | vim.ApplyNormalMode(); 71 | editor.SetPosition(s.focusPosition); 72 | } else if (this.isInsert) { 73 | // c 74 | vim.ApplyInsertMode(); 75 | editor.DeleteRange(nrange, nrange.start); 76 | } else { 77 | // d 78 | vim.ApplyNormalMode(); 79 | 80 | if (n2line === editor.GetLastLineNum()) { 81 | // delete last line 82 | if (nrange.start.Line !== 0) { 83 | // delete previous line Ln 84 | nrange.start.Line -= 1; 85 | let prevLine = editor.ReadLine(nrange.start.Line); 86 | nrange.start.Char = prevLine.length; 87 | } 88 | } else { 89 | // include next line Ln 90 | nrange.end.Line += 1; 91 | nrange.end.Char = 0; 92 | } 93 | editor.DeleteRange(nrange, nrange.start); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * {visualLine}d 100 | */ 101 | export function DeleteHighlightedLine(num: number): IAction { 102 | return new DeleteYankChangeHighlightedLineAction(); 103 | } 104 | 105 | /** 106 | * {visualLine}y 107 | */ 108 | export function YankHighlightedLine(num: number): IAction { 109 | let a = new DeleteYankChangeHighlightedLineAction(); 110 | a.SetOnlyYancOption(); 111 | return a; 112 | } 113 | 114 | /** 115 | * {visualLine}c 116 | */ 117 | export function ChangeHighligtedLine(num: number): IAction { 118 | let a = new DeleteYankChangeHighlightedLineAction(); 119 | a.SetChangeOption(); 120 | return a; 121 | } 122 | -------------------------------------------------------------------------------- /src/VSCodeEditorKeyBindings.ts: -------------------------------------------------------------------------------- 1 | import { EditorCommand } from "./action/CallEditorCommandAction"; 2 | export const VSCodeEditorKeyBindngs: IKeyBindings = { 3 | AtStart: { 4 | "u": { 5 | CreateActionWithArguments: EditorCommand, 6 | argument: "undo", 7 | }, 8 | ":": { 9 | CreateActionWithArguments: EditorCommand, 10 | argument: "workbench.action.showCommands", 11 | }, 12 | "/": { 13 | CreateActionWithArguments: EditorCommand, 14 | argument: "actions.find", 15 | }, 16 | "n": { 17 | CreateActionWithArguments: EditorCommand, 18 | argument: "editor.action.nextMatchFindAction", 19 | }, 20 | "N": { 21 | CreateActionWithArguments: EditorCommand, 22 | argument: "editor.action.previousMatchFindAction", 23 | }, 24 | ">": { 25 | CreateActionWithArguments: EditorCommand, 26 | argument: "editor.action.indentLines", 27 | }, 28 | "<": { 29 | CreateActionWithArguments: EditorCommand, 30 | argument: "editor.action.outdentLines", 31 | }, 32 | "%": { 33 | CreateActionWithArguments: EditorCommand, 34 | argument: "editor.action.jumpToBracket", 35 | }, 36 | }, 37 | FirstNum: { 38 | }, 39 | RequireMotion: { 40 | }, 41 | RequireMotionNum: { 42 | }, 43 | RequireBrancketForLeftBrancket: { 44 | }, 45 | RequireBrancketForLeftBrancketMotion: { 46 | }, 47 | RequireBrancketForRightBrancket: { 48 | }, 49 | RequireBrancketForRightBrancketMotion: { 50 | }, 51 | SmallG: { 52 | }, 53 | SmallGForMotion: { 54 | }, 55 | VisualMode: { 56 | "u": { 57 | CreateActionWithArguments: EditorCommand, 58 | argument: "undo", 59 | }, 60 | ":": { 61 | CreateActionWithArguments: EditorCommand, 62 | argument: "workbench.action.showCommands", 63 | }, 64 | "/": { 65 | CreateActionWithArguments: EditorCommand, 66 | argument: "actions.find", 67 | }, 68 | "n": { 69 | CreateActionWithArguments: EditorCommand, 70 | argument: "editor.action.nextMatchFindAction", 71 | }, 72 | "N": { 73 | CreateActionWithArguments: EditorCommand, 74 | argument: "editor.action.previousMatchFindAction", 75 | }, 76 | ">": { 77 | CreateActionWithArguments: EditorCommand, 78 | argument: "editor.action.indentLines", 79 | }, 80 | "<": { 81 | CreateActionWithArguments: EditorCommand, 82 | argument: "editor.action.outdentLines", 83 | }, 84 | "%": { 85 | CreateActionWithArguments: EditorCommand, 86 | argument: "editor.action.jumpToBracket", 87 | }, 88 | }, 89 | VisualModeNum: {}, 90 | VisualLineMode: { 91 | "u": { 92 | CreateActionWithArguments: EditorCommand, 93 | argument: "undo", 94 | }, 95 | ":": { 96 | CreateActionWithArguments: EditorCommand, 97 | argument: "workbench.action.showCommands", 98 | }, 99 | "/": { 100 | CreateActionWithArguments: EditorCommand, 101 | argument: "actions.find", 102 | }, 103 | "n": { 104 | CreateActionWithArguments: EditorCommand, 105 | argument: "editor.action.nextMatchFindAction", 106 | }, 107 | "N": { 108 | CreateActionWithArguments: EditorCommand, 109 | argument: "editor.action.previousMatchFindAction", 110 | }, 111 | ">": { 112 | CreateActionWithArguments: EditorCommand, 113 | argument: "editor.action.indentLines", 114 | }, 115 | "<": { 116 | CreateActionWithArguments: EditorCommand, 117 | argument: "editor.action.outdentLines", 118 | }, 119 | "%": { 120 | CreateActionWithArguments: EditorCommand, 121 | argument: "editor.action.jumpToBracket", 122 | }, 123 | }, 124 | }; 125 | -------------------------------------------------------------------------------- /src/Utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export function ModeToString(mode: VimMode): string { 3 | switch (mode) { 4 | case VimMode.Insert: 5 | return "INSERT"; 6 | case VimMode.Normal: 7 | return "NORMAL"; 8 | case VimMode.Visual: 9 | return "VISUAL"; 10 | case VimMode.VisualLine: 11 | return "VISUAL LINE"; 12 | default: 13 | throw new Error("Panic!"); 14 | } 15 | } 16 | 17 | /** 18 | * isNumber: Check if value is a number, including 19 | * the number 0. 20 | */ 21 | export function isNumber(val) { 22 | let res = parseInt(val, 10); 23 | return isNaN ? null : res; 24 | } 25 | 26 | export function KeyToNum(key: Key): number { 27 | let num = key.charCodeAt(0) - 0x30; 28 | if (num < 0 || 9 < num) { 29 | return NaN; 30 | } 31 | return num; 32 | } 33 | 34 | export function GetKeyType(key: Key): VimKeyType { 35 | let charCode = key.charCodeAt(0); 36 | if (charCode <= 0x2F) { 37 | // ! - / 38 | return VimKeyType.Mark; 39 | } 40 | if (charCode <= 0x39) { 41 | // 0 - 9 42 | return VimKeyType.Number; 43 | } 44 | if (charCode <= 0x40) { 45 | // : - @ 46 | return VimKeyType.Number; 47 | } 48 | if (charCode <= 0x5A) { 49 | // A - Z 50 | return VimKeyType.Charactor; 51 | } 52 | if (charCode <= 0x60) { 53 | // [ - ` 54 | return VimKeyType.Mark; 55 | } 56 | if (charCode <= 0x7A) { 57 | // a - z 58 | return VimKeyType.Charactor; 59 | } 60 | return VimKeyType.Mark; 61 | } 62 | 63 | export function GetCharClass(charCode: number): CharGroup { 64 | if (charCode <= 0x20) { 65 | // Space,Tab,LF... 66 | return CharGroup.Spaces; 67 | } 68 | if (charCode <= 0x2F) { 69 | // ! - / 70 | return CharGroup.Marks; 71 | } 72 | if (charCode <= 0x39) { 73 | // 0 - 9 74 | return CharGroup.AlphabetAndNumber; 75 | } 76 | if (charCode <= 0x40) { 77 | // : - @ 78 | return CharGroup.Marks; 79 | } 80 | if (charCode <= 0x5A) { 81 | // A - Z 82 | return CharGroup.AlphabetAndNumber; 83 | } 84 | if (charCode <= 0x5E) { 85 | // [ - ^ 86 | return CharGroup.Marks; 87 | } 88 | if (charCode === 0x5F) { 89 | // _ 90 | return CharGroup.AlphabetAndNumber; 91 | } 92 | if (charCode === 0x60) { 93 | // ` 94 | return CharGroup.Marks; 95 | } 96 | if (charCode <= 0x7A) { 97 | // a - z 98 | return CharGroup.AlphabetAndNumber; 99 | } 100 | if (charCode <= 0x7E) { 101 | // { - ~ 102 | return CharGroup.Marks; 103 | } 104 | if (charCode === 0x7F) { 105 | // DEL 106 | return CharGroup.Spaces; 107 | } 108 | if (charCode < 0x3000) { 109 | // 漢字とか 110 | return CharGroup.Other; 111 | } 112 | if (charCode === 0x3000) { 113 | // 全角スペース 114 | return CharGroup.Spaces; 115 | } 116 | if (charCode <= 0x303F) { 117 | // 全角記号 118 | return CharGroup.Marks; 119 | } 120 | if (charCode <= 0x309F) { 121 | // 平仮名 122 | return CharGroup.Hiragana; 123 | } 124 | if (charCode <= 0x30FF) { 125 | // 片仮名 126 | return CharGroup.Katakana; 127 | } 128 | return CharGroup.Other; 129 | } 130 | 131 | export function CalcVisialPosition(systemPosition: number, text: string, tabSize: number): number { 132 | let tabCount = 0; 133 | for (let char of text) { 134 | if (char === "\t") { 135 | tabCount++; 136 | } else { 137 | break; 138 | } 139 | } 140 | return (tabSize - 1) * tabCount + systemPosition; 141 | } 142 | 143 | export function CalcSystemPosition(visualPosition: number, text: string, tabSize: number): number { 144 | let tabCount = 0; 145 | for (let char of text) { 146 | if (char === "\t") { 147 | tabCount++; 148 | } else { 149 | break; 150 | } 151 | } 152 | if (visualPosition <= tabSize * tabCount) { 153 | return Math.floor(visualPosition / tabSize); 154 | } 155 | return visualPosition - (tabSize - 1) * tabCount; 156 | } 157 | -------------------------------------------------------------------------------- /src/motion/FirstCharacterMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * ^ G gg 8 | * c^ cG cgg 9 | */ 10 | export class FirstCharacterMotion extends AbstractMotion { 11 | 12 | public Target: Target; 13 | public IsSkipSpaces: boolean; 14 | 15 | constructor() { 16 | super(); 17 | this.Target = Target.Number; 18 | this.IsSkipSpaces = false; 19 | } 20 | 21 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 22 | 23 | let lineDocument: string; 24 | let lineNumber: number; 25 | switch (this.Target) { 26 | case Target.Current: 27 | lineDocument = editor.ReadLineAtCurrentPosition(); 28 | lineNumber = start.Line; 29 | break; 30 | case Target.First: 31 | lineNumber = 0; 32 | lineDocument = editor.ReadLine(lineNumber); 33 | break; 34 | case Target.Last: 35 | lineNumber = editor.GetLastLineNum(); 36 | lineDocument = editor.ReadLine(lineNumber); 37 | break; 38 | case Target.Number: 39 | lineNumber = this.Count - 1; 40 | let lastLineNum = editor.GetLastLineNum(); 41 | if (lineNumber > lastLineNum) { 42 | lineNumber = lastLineNum; 43 | } 44 | lineDocument = editor.ReadLine(lineNumber); 45 | break; 46 | } 47 | 48 | let l = lineDocument.length; 49 | let charNumber: number; 50 | for (charNumber = 0; charNumber < l; charNumber++) { 51 | let c = Utils.GetCharClass(lineDocument.charCodeAt(charNumber)); 52 | if (c !== CharGroup.Spaces) { 53 | break; 54 | } 55 | } 56 | let p = new Position(); 57 | p.Line = lineNumber; 58 | p.Char = charNumber; 59 | return p; 60 | } 61 | } 62 | 63 | export enum Target { 64 | Current, 65 | First, 66 | Last, 67 | Number, 68 | } 69 | 70 | /** 71 | * ^ 72 | */ 73 | export function GotoFirstNonBlankCharacterInLine(num: number): IAction { 74 | let a = new GoAction(); 75 | let m = new FirstCharacterMotion(); 76 | a.Motion = m; 77 | m.Target = Target.Current; 78 | return a; 79 | } 80 | 81 | /** 82 | * c^ 83 | */ 84 | export function AddFirstNonBlankCharacterInLineMotion(num: number, action: IAction): void { 85 | let a = action; 86 | let m = new FirstCharacterMotion(); 87 | m.Target = Target.Current; 88 | a.Motion = m; 89 | } 90 | 91 | /** 92 | * G 93 | */ 94 | export function GotoLastLine(num: number): IAction { 95 | let a = new GoAction(); 96 | let m = new FirstCharacterMotion(); 97 | if (num === 0) { 98 | m.Target = Target.Last; 99 | } else { 100 | m.Target = Target.Number; 101 | m.Count = num; 102 | } 103 | a.Motion = m; 104 | return a; 105 | } 106 | 107 | /** 108 | * cG 109 | */ 110 | export function AddLastLineMotion(num: number, action: IAction): void { 111 | let m = new FirstCharacterMotion(); 112 | if (num === 0) { 113 | m.Target = Target.Last; 114 | } else { 115 | m.Target = Target.Number; 116 | m.Count = num; 117 | } 118 | let a = action; 119 | a.Motion = m; 120 | a.IsLine = true; 121 | } 122 | 123 | /** 124 | * gg 125 | */ 126 | export function GotoFirstLineOnFirstNonBlankCharacter(num: number): IAction { 127 | let a = new GoAction(); 128 | let m = new FirstCharacterMotion(); 129 | if (num === 0) { 130 | m.Target = Target.First; 131 | } else { 132 | m.Target = Target.Number; 133 | m.Count = num; 134 | } 135 | a.Motion = m; 136 | this.action = a; 137 | return a; 138 | } 139 | 140 | /** 141 | * cgg 142 | */ 143 | export function AddFirstLineMotion(num: number, action: IAction) { 144 | let m = new FirstCharacterMotion(); 145 | if (num === 0) { 146 | m.Target = Target.First; 147 | } else { 148 | m.Target = Target.Number; 149 | m.Count = num; 150 | } 151 | let a = action; 152 | a.Motion = m; 153 | a.IsLine = true; 154 | } 155 | -------------------------------------------------------------------------------- /src/motion/textObjectSelection/Quotation.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../../Utils"; 2 | import { Range } from "../../VimStyle"; 3 | import { AbstractTextObjectSelection } from "./AbstractTextObjectSelection"; 4 | 5 | const INNER = 0; 6 | const OUTER = 1; 7 | 8 | /** 9 | * ci" ci' ci` 10 | * ca" ca' ca` 11 | */ 12 | export class QuotationSelection extends AbstractTextObjectSelection { 13 | 14 | public Quote: string; 15 | public InnerOuter: number; 16 | 17 | constructor() { 18 | super(); 19 | } 20 | 21 | public CalculateRange(editor: IEditor, vim: IVimStyle, start: IPosition): IRange { 22 | 23 | let left = this.SearchLeftQuote(editor, vim, start); 24 | let right = this.SearchRightQuote(editor, vim, start); 25 | if (left == null || right == null) { 26 | return null; 27 | } 28 | 29 | if (this.InnerOuter === INNER) { 30 | left.Char++; 31 | } 32 | 33 | let result = new Range(); 34 | result.start = left; 35 | result.end = right; 36 | return result; 37 | } 38 | 39 | public SearchRightQuote(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 40 | 41 | let p = start.Copy(); 42 | let line = editor.ReadLineAtCurrentPosition(); 43 | let lastLine = editor.GetLastLineNum(); 44 | while (true) { 45 | 46 | // read 1 char 47 | p.Char++; 48 | if (p.Char >= line.length) { 49 | p.Line++; 50 | if (p.Line > lastLine) { 51 | return null; 52 | } 53 | line = editor.ReadLine(p.Line); 54 | if (line.length === 0) { 55 | // skip blank line 56 | continue; 57 | } 58 | p.Char = 0; 59 | } 60 | 61 | let c: string = line[p.Char]; 62 | 63 | if (c === this.Quote) { 64 | break; 65 | } 66 | } 67 | if (this.InnerOuter === OUTER) { 68 | while (true) { 69 | // read following space 70 | p.Char++; 71 | if (p.Char >= line.length) { 72 | p.Char = line.length; 73 | break; 74 | } 75 | if (Utils.GetCharClass(line.charCodeAt(p.Char)) !== CharGroup.Spaces) { 76 | break; 77 | } 78 | } 79 | } 80 | return p; 81 | } 82 | 83 | private SearchLeftQuote(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 84 | let p = start.Copy(); 85 | let line = editor.ReadLineAtCurrentPosition(); 86 | let lastLine = editor.GetLastLineNum(); 87 | while (true) { 88 | 89 | // read 1 char 90 | p.Char--; 91 | if (p.Char < 0) { 92 | p.Line--; 93 | if (p.Line < 0) { 94 | return null; 95 | } 96 | line = editor.ReadLine(p.Line); 97 | if (line.length === 0) { 98 | // skip blank line 99 | continue; 100 | } 101 | p.Char = line.length - 1; 102 | } 103 | 104 | let c: string = line[p.Char]; 105 | 106 | if (c === this.Quote) { 107 | break; 108 | } 109 | } 110 | 111 | return p; 112 | } 113 | } 114 | 115 | // ci' 116 | export function AddInnerApostropheSelection(num: number, action: IAction) { 117 | let m = new QuotationSelection(); 118 | m.Quote = "'"; 119 | m.InnerOuter = INNER; 120 | m.Count = num > 0 ? num : 1; 121 | let a = action; 122 | a.Selection = m; 123 | } 124 | 125 | // ca' 126 | export function AddOuterApostropheSelection(num: number, action: IAction) { 127 | let m = new QuotationSelection(); 128 | m.Quote = "'"; 129 | m.InnerOuter = OUTER; 130 | m.Count = num > 0 ? num : 1; 131 | let a = action; 132 | a.Selection = m; 133 | } 134 | 135 | // ci" 136 | export function AddInnerQuotationSelection(num: number, action: IAction) { 137 | let m = new QuotationSelection(); 138 | m.Quote = "\""; 139 | m.InnerOuter = INNER; 140 | m.Count = num > 0 ? num : 1; 141 | let a = action; 142 | a.Selection = m; 143 | } 144 | 145 | // ca" 146 | export function AddOuterQuotationSelection(num: number, action: IAction) { 147 | let m = new QuotationSelection(); 148 | m.Quote = "\""; 149 | m.InnerOuter = OUTER; 150 | m.Count = num > 0 ? num : 1; 151 | let a = action; 152 | a.Selection = m; 153 | } 154 | 155 | // ci` 156 | export function AddInnerGraveAccentSelection(num: number, action: IAction) { 157 | let m = new QuotationSelection(); 158 | m.Quote = "`"; 159 | m.InnerOuter = INNER; 160 | m.Count = num > 0 ? num : 1; 161 | let a = action; 162 | a.Selection = m; 163 | } 164 | 165 | // ca` 166 | export function AddOuterGraveAccentSelection(num: number, action: IAction) { 167 | let m = new QuotationSelection(); 168 | m.Quote = "`"; 169 | m.InnerOuter = OUTER; 170 | m.Count = num > 0 ? num : 1; 171 | let a = action; 172 | a.Selection = m; 173 | } 174 | -------------------------------------------------------------------------------- /test/vim/UpDownMotions.ts: -------------------------------------------------------------------------------- 1 | export let UpDownMotions = {}; 2 | 3 | UpDownMotions["line motion"] = { 4 | "j:move down": { 5 | in: [ 6 | "abc d|ef", 7 | "abc", 8 | "abc def", 9 | ], 10 | key: "j", 11 | out: [ 12 | "abc def", 13 | "ab|c", 14 | "abc def", 15 | ], 16 | }, 17 | "2j:move down over a short line": { 18 | in: [ 19 | "abcd|efg", 20 | "b", 21 | "abcdefg", 22 | ], 23 | key: "2j", 24 | out: [ 25 | "abcdefg", 26 | "b", 27 | "abcd|efg", 28 | ], 29 | }, 30 | "3j:move to last line": { 31 | in: [ 32 | "abcd|efg", 33 | "b", 34 | "abcdefg", 35 | ], 36 | key: "3j", 37 | out: [ 38 | "abcdefg", 39 | "b", 40 | "abcd|efg", 41 | ], 42 | }, 43 | "v2j:move down over a short line in visual mode": { 44 | in: [ 45 | "abcd|efg", 46 | "b", 47 | "abcdefg", 48 | ], 49 | key: "v2jd", 50 | out: [ 51 | "abcd|fg", 52 | ], 53 | }, 54 | "k:move up": { 55 | in: [ 56 | "abc def", 57 | "abc", 58 | "abc d|ef", 59 | ], 60 | key: "k", 61 | out: [ 62 | "abc def", 63 | "ab|c", 64 | "abc def", 65 | ], 66 | }, 67 | "kk:move up over a short line": { 68 | in: [ 69 | "abcdef g", 70 | "a", 71 | "abcdef| g", 72 | ], 73 | key: "kk", 74 | out: [ 75 | "abcdef| g", 76 | "a", 77 | "abcdef g", 78 | ], 79 | }, 80 | "3k:move to first line": { 81 | in: [ 82 | "abcdef g", 83 | "a", 84 | "abcdef| g", 85 | ], 86 | key: "3k", 87 | out: [ 88 | "abcdef| g", 89 | "a", 90 | "abcdef g", 91 | ], 92 | }, 93 | "vk:move up in visual mode": { 94 | in: [ 95 | "abc def", 96 | "abc", 97 | "abc d|ef", 98 | ], 99 | key: "vkd", 100 | out: [ 101 | "abc def", 102 | "abc|f", 103 | ], 104 | }, 105 | "gg:go to line 1": { 106 | in: [ 107 | " a", 108 | " b", 109 | "| c", 110 | " d", 111 | " e", 112 | ], 113 | key: "gg", 114 | out: [ 115 | " |a", 116 | " b", 117 | " c", 118 | " d", 119 | " e", 120 | ], 121 | }, 122 | "4gg:go to line 4": { 123 | in: [ 124 | " a", 125 | " b", 126 | "| c", 127 | " d", 128 | " e", 129 | ], 130 | key: "4gg", 131 | out: [ 132 | " a", 133 | " b", 134 | " c", 135 | " |d", 136 | " e", 137 | ], 138 | }, 139 | // #56 https://github.com/74th/vscode-vim/issues/56 140 | // "dgg:delete line between first and current line": { 141 | // "in": [ 142 | // " a", 143 | // " b", 144 | // "| c", 145 | // " d", 146 | // " e" 147 | // ], 148 | // "key": "dgg", 149 | // "out": [ 150 | // " |d", 151 | // " e" 152 | // ], 153 | // }, 154 | // "cgg:cut line between first and current line": { 155 | // "in": [ 156 | // " a", 157 | // " b", 158 | // "| c", 159 | // " d", 160 | // " e" 161 | // ], 162 | // "key": "cgg", 163 | // "out": [ 164 | // "|", 165 | // " d", 166 | // " e" 167 | // ], 168 | // }, 169 | // "d4gg:delete lines between current adn 4": { 170 | // "in": [ 171 | // " a", 172 | // " b", 173 | // "| c", 174 | // " d", 175 | // " e" 176 | // ], 177 | // "key": "d4gg", 178 | // "out": [ 179 | // " a", 180 | // " b", 181 | // " |e" 182 | // ], 183 | // }, 184 | "G:go to final line": { 185 | in: [" a", " b", "| c", " d", " e"], 186 | key: "G", 187 | out: [" a", " b", " c", " d", " |e"], 188 | }, 189 | "4G:go to line 4": { 190 | in: [" a", " b", "| c", " d", " e"], 191 | key: "4G", 192 | out: [" a", " b", " c", " |d", " e"], 193 | }, 194 | // #56 https://github.com/74th/vscode-vim/issues/56 195 | // "dG:delete lines between first and final line": { 196 | // "in": [" a", " b", "| c", " d", " e"], 197 | // "key": "dG", 198 | // "out": [" a", " |b"], 199 | // }, 200 | // "d4G:delete lines between current adn 4": { 201 | // "in": [ 202 | // " a", 203 | // " b", 204 | // "| c", 205 | // " d", 206 | // " e" 207 | // ], 208 | // "key": "d4G", 209 | // "out": [ 210 | // " a", 211 | // " b", 212 | // " |e" 213 | // ], 214 | // }, 215 | }; 216 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import * as utils from "./Utils"; 3 | import { VimStyle } from "./VimStyle"; 4 | import { IVSCodeEditorOptions, VSCodeEditor } from "./VSCodeEditor"; 5 | import { VSCodeEditorKeyBindngs } from "./VSCodeEditorKeyBindings"; 6 | 7 | function checkImapAction(bindkey: string): boolean { 8 | let p = vscode.window.activeTextEditor.selection.active; 9 | let line = vscode.window.activeTextEditor.document.lineAt(p.line).text; 10 | if (p.character < bindkey.length - 1) { 11 | return false; 12 | } 13 | for (let i = 0; i < bindkey.length - 1; i++) { 14 | if (line[p.character - (bindkey.length - 1) + i] !== bindkey[i]) { 15 | return false; 16 | } 17 | } 18 | // delete bindKey[0:-1] character 19 | if (bindkey.length > 1) { 20 | let deleteRange = new vscode.Range(new vscode.Position(p.line, p.character - (bindkey.length - 1)), p); 21 | vscode.window.activeTextEditor.edit((editBuilder) => { 22 | editBuilder.delete(deleteRange); 23 | }); 24 | } 25 | // set position 26 | let np = new vscode.Position(p.line, p.character - (bindkey.length - 1)); 27 | vscode.window.activeTextEditor.selection = new vscode.Selection(np, np); 28 | return true; 29 | } 30 | 31 | export function activate(context: vscode.ExtensionContext) { 32 | if (vscode.workspace.getConfiguration("vimStyle").get("enabled", true)) { 33 | activateVimStyle(context); 34 | } 35 | } 36 | 37 | function activateVimStyle(context: vscode.ExtensionContext) { 38 | let editorOpt: IVSCodeEditorOptions; 39 | let vimOpt: IVimStyleOptions; 40 | function loadConfiguration() { 41 | let conf = vscode.workspace.getConfiguration("vimStyle"); 42 | editorOpt = { 43 | showMode: conf.get("showMode", true), 44 | changeCursorStyle: conf.get("changeCursorStyle", true), 45 | defaultMode: conf.get("defaultMode", "normal"), 46 | imapEsc: conf.get("imapEsc", ""), 47 | }; 48 | vimOpt = { 49 | useErgonomicKeyForMotion: conf.get("useErgonomicKeyForMotion", false), 50 | editorKeyBindings: VSCodeEditorKeyBindngs, 51 | vimrc: conf.get("vimrc", null), 52 | }; 53 | } 54 | loadConfiguration(); 55 | 56 | let editor = new VSCodeEditor(editorOpt); 57 | context.subscriptions.push(editor); 58 | let vim = new VimStyle(editor, vimOpt); 59 | 60 | let disposable = vscode.workspace.onDidChangeConfiguration(() => { 61 | loadConfiguration(); 62 | vim.ApplyOptions(vimOpt); 63 | editor.ApplyOptions(editorOpt); 64 | }); 65 | context.subscriptions.push(disposable); 66 | disposable = vscode.window.onDidChangeActiveTextEditor((textEditor) => { 67 | editor.ChangePositionByUser(); 68 | }); 69 | context.subscriptions.push(disposable); 70 | disposable = vscode.window.onDidChangeTextEditorSelection((textEditor) => { 71 | editor.ChangePositionByUser(); 72 | }); 73 | context.subscriptions.push(disposable); 74 | 75 | let imapEscPointer = 0; 76 | context.subscriptions.push(vscode.commands.registerCommand("type", async (args) => { 77 | let text: string; 78 | if (args.text && args.text.length > 0) { 79 | text = args.text; 80 | let charClass = utils.GetCharClass(text.charCodeAt(0)); 81 | if (charClass !== CharGroup.AlphabetAndNumber && 82 | charClass !== CharGroup.Marks && 83 | charClass !== CharGroup.Spaces) { 84 | vim.PushKey(args.text); 85 | return; 86 | } 87 | } 88 | if (!vscode.window.activeTextEditor) { 89 | return; 90 | } 91 | if (vim.GetMode() === VimMode.Insert) { 92 | if (editorOpt.imapEsc.length === 0) { 93 | vim.PushKey(args.text); 94 | return; 95 | } 96 | if (editorOpt.imapEsc[imapEscPointer] === args.text) { 97 | imapEscPointer++; 98 | if (editorOpt.imapEsc.length === imapEscPointer) { 99 | if (checkImapAction(editorOpt.imapEsc)) { 100 | imapEscPointer = 0; 101 | vim.PushEscKey(); 102 | return; 103 | } 104 | } else { 105 | vim.PushKey(args.text); 106 | return; 107 | } 108 | } else if (editorOpt.imapEsc[0] === args.text) { 109 | imapEscPointer = 1; 110 | vim.PushKey(args.text); 111 | return; 112 | } 113 | imapEscPointer = 0; 114 | vim.PushKey(args.text); 115 | return; 116 | } 117 | vim.PushKey(args.text); 118 | return; 119 | })); 120 | context.subscriptions.push(vscode.commands.registerCommand("vim.Esc", () => { 121 | vim.PushEscKey(); 122 | })); 123 | 124 | let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-=_+[]\\{}|,./<>?"; 125 | let otherKeys = ["", "", "", ""]; 126 | let addTypeVim = (c: string) => { 127 | context.subscriptions.push(vscode.commands.registerCommand("vim.type-" + c, () => { 128 | vim.PushKey(c); 129 | })); 130 | }; 131 | for (let char of chars) { 132 | addTypeVim(char); 133 | } 134 | for (let key of otherKeys) { 135 | addTypeVim(key); 136 | } 137 | 138 | vscode.commands.executeCommand("setContext", "vim.enabled", true); 139 | 140 | setTimeout(() => { 141 | if (editorOpt.defaultMode === "insert") { 142 | vim.PushKey("i"); 143 | } else { 144 | vim.PushEscKey(); 145 | } 146 | }, 1000); 147 | } 148 | -------------------------------------------------------------------------------- /src/motion/textObjectSelection/Brancket.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../../Utils"; 2 | import { Range } from "../../VimStyle"; 3 | import { AbstractTextObjectSelection } from "./AbstractTextObjectSelection"; 4 | 5 | const INNER = 0; 6 | const OUTER = 1; 7 | 8 | /** 9 | * ci[ ci( ci{ ci< 10 | * ca[ ca( ca{ ca< 11 | */ 12 | export class BrancketSelection extends AbstractTextObjectSelection { 13 | 14 | public LeftBrancket: string; 15 | public RightBrancket: string; 16 | public InnerOuter: number; 17 | 18 | constructor() { 19 | super(); 20 | } 21 | 22 | public CalculateRange(editor: IEditor, vim: IVimStyle, start: IPosition): IRange { 23 | 24 | let left = this.SearchLeftBrancket(editor, vim, start); 25 | let right = this.SearchRightBrancket(editor, vim, start); 26 | if (left == null || right == null) { 27 | return null; 28 | } 29 | 30 | if (this.InnerOuter === INNER) { 31 | left.Char++; 32 | } else { 33 | right.Char++; 34 | } 35 | 36 | let result = new Range(); 37 | result.start = left; 38 | result.end = right; 39 | return result; 40 | } 41 | 42 | public SearchRightBrancket(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 43 | 44 | let p = start.Copy(); 45 | let line = editor.ReadLineAtCurrentPosition(); 46 | let lastLine = editor.GetLastLineNum(); 47 | let count = this.Count; 48 | while (count > 0) { 49 | 50 | // read 1 char 51 | p.Char++; 52 | if (p.Char >= line.length) { 53 | p.Line++; 54 | if (p.Line > lastLine) { 55 | return null; 56 | } 57 | line = editor.ReadLine(p.Line); 58 | if (line.length === 0) { 59 | // skip blank line 60 | continue; 61 | } 62 | p.Char = 0; 63 | } 64 | 65 | let c: string = line[p.Char]; 66 | 67 | if (c === this.LeftBrancket) { 68 | count++; 69 | } 70 | if (c === this.RightBrancket) { 71 | count--; 72 | } 73 | 74 | if (count === 0) { 75 | break; 76 | } 77 | 78 | } 79 | return p; 80 | } 81 | 82 | private SearchLeftBrancket(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 83 | let p = start.Copy(); 84 | let line = editor.ReadLineAtCurrentPosition(); 85 | let lastLine = editor.GetLastLineNum(); 86 | let count = this.Count; 87 | while (this.Count > 0) { 88 | 89 | // read 1 char 90 | p.Char--; 91 | if (p.Char < 0) { 92 | p.Line--; 93 | if (p.Line < 0) { 94 | return null; 95 | } 96 | line = editor.ReadLine(p.Line); 97 | if (line.length === 0) { 98 | // skip blank line 99 | continue; 100 | } 101 | p.Char = line.length - 1; 102 | } 103 | 104 | let c: string = line[p.Char]; 105 | 106 | if (c === this.LeftBrancket) { 107 | count--; 108 | } 109 | if (c === this.RightBrancket) { 110 | count++; 111 | } 112 | 113 | if (count === 0) { 114 | break; 115 | } 116 | } 117 | 118 | return p; 119 | } 120 | 121 | } 122 | 123 | // ci( ci) 124 | export function AddInnerUnclosedParenthesisSelection(num: number, action: IAction) { 125 | let m = new BrancketSelection(); 126 | m.LeftBrancket = "("; 127 | m.RightBrancket = ")"; 128 | m.InnerOuter = INNER; 129 | m.Count = num > 0 ? num : 1; 130 | let a = action; 131 | a.Selection = m; 132 | } 133 | 134 | // ca( ca) 135 | export function AddOuterUnclosedParenthesisSelection(num: number, action: IAction) { 136 | let m = new BrancketSelection(); 137 | m.LeftBrancket = "("; 138 | m.RightBrancket = ")"; 139 | m.InnerOuter = OUTER; 140 | m.Count = num > 0 ? num : 1; 141 | let a = action; 142 | a.Selection = m; 143 | } 144 | 145 | // ci< ci> 146 | export function AddInnerLessThanSignSelection(num: number, action: IAction) { 147 | let m = new BrancketSelection(); 148 | m.LeftBrancket = "<"; 149 | m.RightBrancket = ">"; 150 | m.InnerOuter = INNER; 151 | m.Count = num > 0 ? num : 1; 152 | let a = action; 153 | a.Selection = m; 154 | } 155 | 156 | // ca< ca> 157 | export function AddOuterLessThanSignSelection(num: number, action: IAction) { 158 | let m = new BrancketSelection(); 159 | m.LeftBrancket = "<"; 160 | m.RightBrancket = ">"; 161 | m.InnerOuter = OUTER; 162 | m.Count = num > 0 ? num : 1; 163 | let a = action; 164 | a.Selection = m; 165 | } 166 | 167 | // ci[ ci] 168 | export function AddInnerSquareBlancketSelection(num: number, action: IAction) { 169 | let m = new BrancketSelection(); 170 | m.LeftBrancket = "["; 171 | m.RightBrancket = "]"; 172 | m.InnerOuter = INNER; 173 | m.Count = num > 0 ? num : 1; 174 | let a = action; 175 | a.Selection = m; 176 | } 177 | 178 | // ca[ ca] 179 | export function AddOuterSquareBlancketSelection(num: number, action: IAction) { 180 | let m = new BrancketSelection(); 181 | m.LeftBrancket = "["; 182 | m.RightBrancket = "]"; 183 | m.InnerOuter = OUTER; 184 | m.Count = num > 0 ? num : 1; 185 | let a = action; 186 | a.Selection = m; 187 | } 188 | 189 | // ci{ ci} 190 | export function AddInnerCurlyBrancketSelection(num: number, action: IAction) { 191 | let m = new BrancketSelection(); 192 | m.LeftBrancket = "{"; 193 | m.RightBrancket = "}"; 194 | m.InnerOuter = INNER; 195 | m.Count = num > 0 ? num : 1; 196 | let a = action; 197 | a.Selection = m; 198 | } 199 | 200 | // ca{ ca} 201 | export function AddOuterCurlyBrancketSelection(num: number, action: IAction) { 202 | let m = new BrancketSelection(); 203 | m.LeftBrancket = "{"; 204 | m.RightBrancket = "}"; 205 | m.InnerOuter = OUTER; 206 | m.Count = num > 0 ? num : 1; 207 | let a = action; 208 | a.Selection = m; 209 | } 210 | -------------------------------------------------------------------------------- /src/motion/BrancketMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * [{ [( ]} ]) 8 | */ 9 | export class BackToBrancketMotion extends AbstractMotion { 10 | 11 | public LeftBrancket: string; 12 | public RightBrancket: string; 13 | public TargetBrancket: string; 14 | 15 | constructor() { 16 | super(); 17 | } 18 | 19 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 20 | 21 | let p: IPosition = start.Copy(); 22 | let line: string = editor.ReadLineAtCurrentPosition(); 23 | let lastLine: number = editor.GetLastLineNum(); 24 | let lastBrancket: IPosition = null; 25 | while (this.Count > 0) { 26 | 27 | // read 1 char 28 | p.Char--; 29 | if (p.Char < 0) { 30 | p.Line--; 31 | if (p.Line < 0) { 32 | p = new Position(0, 0); 33 | break; 34 | } 35 | line = editor.ReadLine(p.Line); 36 | if (line.length === 0) { 37 | // skip blank line 38 | continue; 39 | } 40 | p.Char = line.length - 1; 41 | } 42 | 43 | let c: string = line[p.Char]; 44 | 45 | if (c === this.LeftBrancket) { 46 | lastBrancket = p.Copy(); 47 | this.Count--; 48 | } 49 | if (c === this.RightBrancket) { 50 | this.Count++; 51 | } 52 | 53 | if (this.Count === 0 && c === this.TargetBrancket) { 54 | break; 55 | } 56 | } 57 | 58 | if (lastBrancket == null) { 59 | return start.Copy(); 60 | } 61 | return lastBrancket; 62 | } 63 | } 64 | 65 | export class ToBrancketMotion extends AbstractMotion { 66 | 67 | public LeftBrancket: string; 68 | public RightBrancket: string; 69 | public TargetBrancket: string; 70 | 71 | constructor() { 72 | super(); 73 | } 74 | 75 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 76 | 77 | let p: IPosition = start.Copy(); 78 | let line: string = editor.ReadLineAtCurrentPosition(); 79 | let lastLine: number = editor.GetLastLineNum(); 80 | let lastBrancket: IPosition = null; 81 | while (this.Count > 0) { 82 | 83 | // read 1 char 84 | p.Char++; 85 | if (p.Char >= line.length) { 86 | p.Line++; 87 | if (p.Line > lastLine) { 88 | break; 89 | } 90 | line = editor.ReadLine(p.Line); 91 | if (line.length === 0) { 92 | // skip blank line 93 | continue; 94 | } 95 | p.Char = 0; 96 | } 97 | 98 | let c: string = line[p.Char]; 99 | 100 | if (c === this.LeftBrancket) { 101 | this.Count++; 102 | } 103 | if (c === this.RightBrancket) { 104 | lastBrancket = p.Copy(); 105 | this.Count--; 106 | } 107 | 108 | if (this.Count === 0 && c === this.TargetBrancket) { 109 | break; 110 | } 111 | 112 | } 113 | if (lastBrancket == null) { 114 | return start.Copy(); 115 | } 116 | return lastBrancket; 117 | } 118 | } 119 | 120 | // N[( 121 | export function GoBackToUnclosedLeftParenthesis(num: number): IAction { 122 | let a = new GoAction(); 123 | let m = new BackToBrancketMotion(); 124 | m.LeftBrancket = "("; 125 | m.RightBrancket = ")"; 126 | m.TargetBrancket = "("; 127 | m.Count = num > 0 ? num : 1; 128 | a.Motion = m; 129 | return a; 130 | } 131 | 132 | // cN[( 133 | export function AddBackToUnclosedLeftParenthesisMotion(num: number, action: IAction) { 134 | let m = new BackToBrancketMotion(); 135 | m.LeftBrancket = "("; 136 | m.RightBrancket = ")"; 137 | m.TargetBrancket = "("; 138 | m.Count = num > 0 ? num : 1; 139 | let a = action; 140 | a.Motion = m; 141 | } 142 | 143 | // N[{ 144 | export function GoBackToUnclosedLeftCurlyBracket(num: number): IAction { 145 | let a = new GoAction(); 146 | let m = new BackToBrancketMotion(); 147 | m.LeftBrancket = "{"; 148 | m.RightBrancket = "}"; 149 | m.TargetBrancket = "{"; 150 | m.Count = num > 0 ? num : 1; 151 | a.Motion = m; 152 | return a; 153 | } 154 | 155 | // cN[{ 156 | export function AddBackToUnclosedLeftCurlyBracketMotion(num: number, action: IAction) { 157 | let m = new BackToBrancketMotion(); 158 | m.LeftBrancket = "{"; 159 | m.RightBrancket = "}"; 160 | m.TargetBrancket = "{"; 161 | m.Count = num > 0 ? num : 1; 162 | let a = this.action; 163 | a.Motion = m; 164 | } 165 | 166 | // N]) 167 | export function GoToUnclosedRightParenthesis(num: number): IAction { 168 | let a = new GoAction(); 169 | let m = new ToBrancketMotion(); 170 | m.LeftBrancket = "("; 171 | m.RightBrancket = ")"; 172 | m.TargetBrancket = ")"; 173 | m.Count = num > 0 ? num : 1; 174 | a.Motion = m; 175 | return a; 176 | } 177 | 178 | // cN]) 179 | export function AddToUnclosedRightParenthesisMotion(num: number, action: IAction) { 180 | let m = new ToBrancketMotion(); 181 | m.LeftBrancket = "("; 182 | m.RightBrancket = ")"; 183 | m.TargetBrancket = ")"; 184 | m.Count = num > 0 ? num : 1; 185 | let a = action; 186 | a.Motion = m; 187 | } 188 | 189 | // N]} 190 | export function GoToUnclosedRightCurlyBracket(num: number): IAction { 191 | let a = new GoAction(); 192 | let m = new ToBrancketMotion(); 193 | m.LeftBrancket = "{"; 194 | m.RightBrancket = "}"; 195 | m.TargetBrancket = "}"; 196 | m.Count = num > 0 ? num : 1; 197 | a.Motion = m; 198 | return a; 199 | } 200 | 201 | // cN]} 202 | export function AddToUnclosedRightCurlyBracketMotion(num: number, action: IAction) { 203 | let m = new ToBrancketMotion(); 204 | m.LeftBrancket = "{"; 205 | m.RightBrancket = "}"; 206 | m.TargetBrancket = "}"; 207 | m.Count = num > 0 ? num : 1; 208 | let a = action; 209 | a.Motion = m; 210 | } 211 | -------------------------------------------------------------------------------- /typings/vscode-vim.d.ts: -------------------------------------------------------------------------------- 1 | interface IEditor { 2 | 3 | // Status 4 | CloseCommandStatus(); 5 | ShowCommandStatus(text: string); 6 | ShowModeStatus(mode: VimMode); 7 | 8 | // Edit 9 | InsertTextAtCurrentPosition(text: string); 10 | InsertCharactorAtCurrentPosition(char: string); 11 | Insert(position: IPosition, text: string); 12 | DeleteRange(range: IRange, position?: IPosition); 13 | ReplaceRange(range: IRange, text: string); 14 | TypeDirect(char: string); 15 | 16 | // Read Line 17 | ReadLineAtCurrentPosition(): string; 18 | ReadLine(line: number): string; 19 | 20 | // Read Range 21 | ReadRange(range: IRange): string; 22 | 23 | // Position 24 | GetCurrentPosition(): IPosition; 25 | SetPosition(position: IPosition); 26 | GetLastPosition(): IPosition; 27 | 28 | // Document Info 29 | GetLastLineNum(): number; 30 | 31 | // Set VimStyle 32 | SetVimStyle(vim: IVimStyle); 33 | 34 | // set modes 35 | ApplyNormalMode(cursor?: IPosition, isLineHasNoChar?: boolean, isLastLine?: boolean); 36 | ApplyInsertMode(p: IPosition); 37 | 38 | // Visual mode 39 | ShowVisualMode(range: IRange, focusPosition?: IPosition); 40 | GetCurrentVisualModeSelection(): IRange; 41 | ShowVisualLineMode(startLine: number, endLine: number, focusPosition?: IPosition); 42 | GetCurrentVisualLineModeSelection(): IVisualLineModeSelectionInfo; 43 | 44 | // check invalid position 45 | UpdateValidPosition(p: IPosition, isBlock?: boolean): IPosition; 46 | 47 | GetTabSize(): number; 48 | 49 | CallEditorCommand(argument: string): void; 50 | 51 | dispose(): void; 52 | } 53 | 54 | interface ICommandFactory { 55 | KeyBindings: IKeyBindings; 56 | Nmap: { [key: string]: string }; 57 | Nnoremap: { [key: string]: string }; 58 | PushKey(key: string, mode: VimMode, remap: boolean): IAction[]; 59 | Clear(): void; 60 | GetCommandString(): string; 61 | } 62 | 63 | 64 | interface IRegisterItem { 65 | Type: RegisterType; 66 | Body: string; 67 | } 68 | 69 | interface IRegister { 70 | Set(key: Key, value: IRegisterItem): void; 71 | SetYank(value: IRegisterItem): void; 72 | SetRoll(value: IRegisterItem): void; 73 | Get(key: Key): IRegisterItem; 74 | GetUnName(): IRegisterItem; 75 | GetRollFirst(value: IRegisterItem): IRegisterItem; 76 | } 77 | 78 | interface IPosition { 79 | Line: number; 80 | Char: number; 81 | 82 | IsEqual(p: IPosition): boolean; 83 | IsBefore(p: IPosition): boolean; 84 | IsBeforeOrEqual(p: IPosition): boolean; 85 | IsAfter(p: IPosition): boolean; 86 | IsAfterOrEqual(p: IPosition): boolean; 87 | Copy(): IPosition; 88 | } 89 | 90 | interface IRange { 91 | start: IPosition; 92 | end: IPosition; 93 | 94 | Sort(): void; 95 | IsContain(p: IPosition): boolean; 96 | Copy(): IRange; 97 | } 98 | 99 | interface IAction { 100 | Execute(editor: IEditor, vim: IVimStyle); 101 | GetActionType(): ActionType; 102 | } 103 | 104 | interface IRequireCharAction extends IAction { 105 | CharacterCode: number; 106 | } 107 | 108 | interface ICountableAction extends IAction { 109 | Count: number; 110 | } 111 | 112 | interface IInsertTextAction extends IAction { 113 | SetInsertText(text: string); 114 | GetInsertModeInfo(): any; 115 | } 116 | 117 | interface IRequireMotionAction extends IAction { 118 | Motion: IMotion; 119 | Selection: ISelectionMotion; 120 | IsLine: boolean; 121 | IsChange: boolean; 122 | IsLarge: boolean; 123 | } 124 | 125 | interface IMotion { 126 | Count: number; 127 | CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition; 128 | } 129 | 130 | interface ISelectionMotion { 131 | Count: number; 132 | CalculateRange(editor: IEditor, vim: IVimStyle, start: IPosition): IRange; 133 | } 134 | 135 | interface IRequireCharacterMotion extends IMotion { 136 | CharacterCode: number; 137 | } 138 | 139 | interface IVimStyle { 140 | Register: IRegister; 141 | Options: IVimStyleOptions; 142 | CommandFactory: ICommandFactory; 143 | LastAction: IAction; 144 | LastEditAction: IAction; 145 | LastMoveCharPosition: number; 146 | LastFindCharacterMotion: any; 147 | 148 | PushKey(key: string): void; 149 | PushEscKey(): void; 150 | ApplyNormalMode(); 151 | ApplyInsertMode(p?: IPosition): void; 152 | ApplyVisualMode(): void; 153 | ApplyVisualLineMode(): void; 154 | GetMode(): VimMode; 155 | } 156 | 157 | interface IVimStyleCommand { 158 | state?: StateName; 159 | cmd?: VimCommand; 160 | argument?: string; 161 | callback?: ICommandCallback; 162 | CreateAction?: (num: number) => IAction; 163 | CreateActionWithArguments?: (command: IVimStyleCommand) => IAction; 164 | AddMotion?: (num: number, action: IAction) => void; 165 | } 166 | 167 | interface ICommandCallback { (editor: IEditor, vimStyle: IVimStyle): void } 168 | 169 | interface IKeyBindings { 170 | AtStart?: { [key: string]: IVimStyleCommand }; 171 | FirstNum?: { [key: string]: IVimStyleCommand }; 172 | RequireMotion?: { [key: string]: IVimStyleCommand }; 173 | RequireMotionNum?: { [key: string]: IVimStyleCommand }; 174 | RequireBrancketForLeftBrancket?: { [key: string]: IVimStyleCommand }; 175 | RequireBrancketForRightBrancket?: { [key: string]: IVimStyleCommand }; 176 | RequireBrancketForLeftBrancketMotion?: { [key: string]: IVimStyleCommand }; 177 | RequireBrancketForRightBrancketMotion?: { [key: string]: IVimStyleCommand }; 178 | RequireInnerTextObject?: { [key: string]: IVimStyleCommand }; 179 | RequireOuterTextObject?: { [key: string]: IVimStyleCommand }; 180 | SmallG?: { [key: string]: IVimStyleCommand }; 181 | SmallGForMotion?: { [key: string]: IVimStyleCommand }; 182 | VisualMode?: { [key: string]: IVimStyleCommand }; 183 | VisualModeNum?: { [key: string]: IVimStyleCommand }; 184 | VisualLineMode?: { [key: string]: IVimStyleCommand }; 185 | } 186 | 187 | interface IVimStyleOptions { 188 | useErgonomicKeyForMotion: boolean; 189 | editorKeyBindings?: IKeyBindings; 190 | vimrc: string[]; 191 | } 192 | 193 | interface IVisualLineModeSelectionInfo { 194 | startLine: number; 195 | endLine: number; 196 | focusPosition: IPosition; 197 | } 198 | 199 | interface IExCommand { 200 | Execute(line: string, vimStyle: IVimStyle, editor: IEditor); 201 | } 202 | 203 | type Key = string; 204 | 205 | declare const enum VimKeyType { 206 | Number, 207 | Charactor, 208 | Mark 209 | } 210 | declare const enum ActionType { 211 | Insert, 212 | LineMove, 213 | Move, 214 | Edit, 215 | Other, 216 | Repeat 217 | } 218 | 219 | declare const enum RegisterType { 220 | Text, 221 | LineText 222 | } 223 | 224 | declare const enum Direction { 225 | Right, 226 | Left 227 | } 228 | 229 | declare const enum CharGroup { 230 | AlphabetAndNumber, 231 | Marks, 232 | Spaces, 233 | Hiragana, 234 | Katakana, 235 | Other, 236 | } 237 | 238 | declare const enum VimMode { 239 | Normal, 240 | Insert, 241 | Visual, 242 | VisualLine, 243 | } 244 | 245 | declare const enum VimCommand { 246 | // other 247 | stackNumber, 248 | nothing, 249 | editorCommand, 250 | } 251 | 252 | declare const enum StateName { 253 | AtStart, 254 | FirstNum, 255 | RequireMotion, 256 | RequireMotionNum, 257 | RequireCharForMotion, 258 | RequireCharForAction, 259 | RequireCharForRegister, 260 | RequireBrancketForLeftBrancket, 261 | RequireBrancketForRightBrancket, 262 | RequireBrancketForLeftBrancketMotion, 263 | RequireBrancketForRightBrancketMotion, 264 | RequireInnerTextObject, 265 | RequireOuterTextObject, 266 | SmallG, 267 | SmallGForMotion, 268 | VisualMode, 269 | VisualModeNum, 270 | Panic 271 | } -------------------------------------------------------------------------------- /src/motion/MoveWordMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * Nw 8 | * please see wordMotionStateModel/moveWord.png 9 | */ 10 | export class MoveWordMotion extends AbstractMotion { 11 | 12 | public IsWORD: boolean; 13 | 14 | constructor() { 15 | super(); 16 | }; 17 | 18 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 19 | let cal = new Calculater(start, this.Count, this.IsWORD, editor); 20 | return cal.CalculateEnd(); 21 | } 22 | } 23 | 24 | enum State { 25 | first = 1, 26 | character, 27 | space, 28 | linefeed, 29 | decreaseCount, 30 | decreaseCountAtLinefeed, 31 | moveTo, 32 | reachDocumentEnd, 33 | } 34 | 35 | enum NextCharacter { 36 | character, 37 | sameTypeCharacter, 38 | differenceTypeCharacter, 39 | lineFeed, 40 | space, 41 | } 42 | 43 | class Calculater { 44 | public pos: IPosition; 45 | public line: string; 46 | public documentLines: number; 47 | public count: number; 48 | public IsWORD: boolean; 49 | 50 | public beforeCharacterGroup: CharGroup; 51 | 52 | public editor: IEditor; 53 | 54 | constructor(start: IPosition, count: number, isWord: boolean, editor: IEditor) { 55 | this.pos = start.Copy(); 56 | this.pos.Char--; 57 | this.line = editor.ReadLine(start.Line); 58 | this.editor = editor; 59 | this.documentLines = editor.GetLastLineNum(); 60 | this.count = count; 61 | this.beforeCharacterGroup = null; 62 | this.IsWORD = isWord; 63 | }; 64 | 65 | public getNextCharacter(): NextCharacter { 66 | this.pos.Char++; 67 | if (this.pos.Char > this.line.length) { 68 | this.pos.Line++; 69 | this.pos.Char = 0; 70 | this.line = this.editor.ReadLine(this.pos.Line); 71 | } 72 | if (this.pos.Char === this.line.length) { 73 | if (this.pos.Line === this.documentLines) { 74 | return null; 75 | } 76 | this.beforeCharacterGroup = null; 77 | return NextCharacter.lineFeed; 78 | } 79 | let characterGroup: CharGroup = Utils.GetCharClass(this.line.charCodeAt(this.pos.Char)); 80 | let result: NextCharacter; 81 | if (characterGroup === CharGroup.Spaces) { 82 | this.beforeCharacterGroup = null; 83 | return NextCharacter.space; 84 | } 85 | if (this.beforeCharacterGroup === null) { 86 | this.beforeCharacterGroup = characterGroup; 87 | return NextCharacter.character; 88 | } 89 | if (this.IsWORD) { 90 | this.beforeCharacterGroup = characterGroup; 91 | return NextCharacter.sameTypeCharacter; 92 | } 93 | if (this.beforeCharacterGroup === characterGroup) { 94 | return NextCharacter.sameTypeCharacter; 95 | } 96 | this.beforeCharacterGroup = characterGroup; 97 | return NextCharacter.differenceTypeCharacter; 98 | } 99 | 100 | public doAtFirst(): State { 101 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 102 | if (nextCharacterGroup === null) { 103 | return State.reachDocumentEnd; 104 | } 105 | switch (nextCharacterGroup) { 106 | case NextCharacter.character: 107 | return State.character; 108 | case NextCharacter.lineFeed: 109 | return State.linefeed; 110 | case NextCharacter.space: 111 | return State.space; 112 | } 113 | }; 114 | 115 | public doAtCharacter(): State { 116 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 117 | if (nextCharacterGroup === null) { 118 | return State.reachDocumentEnd; 119 | } 120 | switch (nextCharacterGroup) { 121 | case NextCharacter.differenceTypeCharacter: 122 | return State.decreaseCount; 123 | case NextCharacter.sameTypeCharacter: 124 | return State.character; 125 | case NextCharacter.lineFeed: 126 | return State.linefeed; 127 | case NextCharacter.space: 128 | return State.space; 129 | } 130 | } 131 | 132 | public doAtLinefeed(): State { 133 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 134 | if (nextCharacterGroup === null) { 135 | return State.reachDocumentEnd; 136 | } 137 | switch (nextCharacterGroup) { 138 | case NextCharacter.character: 139 | return State.decreaseCount; 140 | case NextCharacter.lineFeed: 141 | return State.decreaseCountAtLinefeed; 142 | case NextCharacter.space: 143 | return State.space; 144 | } 145 | } 146 | 147 | public doAtSpace(): State { 148 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 149 | if (nextCharacterGroup === null) { 150 | return State.reachDocumentEnd; 151 | } 152 | switch (nextCharacterGroup) { 153 | case NextCharacter.character: 154 | return State.decreaseCount; 155 | case NextCharacter.lineFeed: 156 | return State.linefeed; 157 | case NextCharacter.space: 158 | return State.space; 159 | } 160 | } 161 | 162 | public decreaseCount(): State { 163 | this.count--; 164 | if (this.count === 0) { 165 | return State.moveTo; 166 | } 167 | return State.character; 168 | } 169 | 170 | public decreaseCountAtLinefeed(): State { 171 | this.count--; 172 | if (this.count === 0) { 173 | return State.moveTo; 174 | } 175 | return State.linefeed; 176 | } 177 | 178 | public CalculateEnd(): IPosition { 179 | let state: State = State.first; 180 | let whileContinue = true; 181 | while (whileContinue) { 182 | switch (state) { 183 | case State.reachDocumentEnd: 184 | whileContinue = false; 185 | break; 186 | case State.first: 187 | state = this.doAtFirst(); 188 | break; 189 | case State.character: 190 | state = this.doAtCharacter(); 191 | break; 192 | case State.linefeed: 193 | state = this.doAtLinefeed(); 194 | break; 195 | case State.space: 196 | state = this.doAtSpace(); 197 | break; 198 | case State.decreaseCount: 199 | state = this.decreaseCount(); 200 | break; 201 | case State.decreaseCountAtLinefeed: 202 | state = this.decreaseCountAtLinefeed(); 203 | break; 204 | case State.moveTo: 205 | whileContinue = false; 206 | break; 207 | } 208 | } 209 | return this.pos; 210 | } 211 | } 212 | 213 | /** 214 | * Nw 215 | */ 216 | export function GotoWordFoword(num: number): IAction { 217 | let a = new GoAction(); 218 | let m = new MoveWordMotion(); 219 | m.Count = num > 0 ? num : 1; 220 | a.Motion = m; 221 | return a; 222 | } 223 | 224 | /** 225 | * NW 226 | */ 227 | export function GotoBlankSeparated(num: number): IAction { 228 | let a = new GoAction(); 229 | let m = new MoveWordMotion(); 230 | m.IsWORD = true; 231 | m.Count = num > 0 ? num : 1; 232 | a.Motion = m; 233 | return a; 234 | } 235 | 236 | /** 237 | * vNw 238 | */ 239 | export function AddToWordFowordMotion(num: number, action: IAction) { 240 | let a = action; 241 | let m = new MoveWordMotion(); 242 | m.Count = num > 0 ? num : 1; 243 | a.Motion = m; 244 | } 245 | 246 | /** 247 | * NW 248 | */ 249 | export function AddToBlankSeparatedMotion(num: number, action: IAction) { 250 | let a = action; 251 | let m = new MoveWordMotion(); 252 | m.IsWORD = true; 253 | m.Count = num > 0 ? num : 1; 254 | a.Motion = m; 255 | } 256 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **There are no plans to update this project.** 2 | 3 | **I recommend using [Neo Vim Extension](https://marketplace.visualstudio.com/items?itemName=asvetliakov.vscode-neovim).** 4 | 5 | 6 | Vim emulator for VSCode 7 | 8 | ![vimanimetion](https://raw.githubusercontent.com/74th/vscode-vim/master/tutorial/tutorial1.gif) 9 | 10 | * GitHub https://github.com/74th/vscode-vim 11 | * Visual Studio Marketplace https://marketplace.visualstudio.com/items/74th.vimStyle 12 | 13 | [![Build Status](https://travis-ci.org/74th/vscode-vim.svg?branch=master)](https://travis-ci.org/74th/vscode-vim) 14 | [![Version](https://vsmarketplacebadge.apphb.com/version/74th.vimStyle.svg)](https://marketplace.visualstudio.com/items?itemName=74th.vimStyle) 15 | [![Installs](https://vsmarketplacebadge.apphb.com/installs/74th.vimStyle.svg)](https://marketplace.visualstudio.com/items?itemName=74th.vimStyle) 16 | [![Rating](https://vsmarketplacebadge.apphb.com/rating/74th.vimStyle.svg)](https://marketplace.visualstudio.com/items/74th.vimStyle) 17 | 18 | ## Require 19 | 20 | * v0.3 : Visual Studio Code 1.0.0 21 | 22 | ## Features 23 | 24 | * pure TypeScript vim engine 25 | * [have tests comparing with original vim](https://github.com/74th/vscode-vim/tree/master/test/vim) 26 | * support complex command 27 | * support visual mode 28 | 29 | ![vimanimetion](https://raw.githubusercontent.com/74th/vscode-vim/master/tutorial/tutorial2.gif) 30 | 31 | * support repeat command 32 | 33 | ![vimanimetion](https://raw.githubusercontent.com/74th/vscode-vim/master/tutorial/tutorial3.gif) 34 | 35 | ## Installation 36 | 37 | ``` 38 | ext install vimStyle 39 | ``` 40 | 41 | ## Supported vim commands 42 | 43 | * h j k l 0 $ ^ w W b B e E { } [{ ]} tx fx Tx Fx ; , gg G 44 | * Nh Nj Nk Nl Nw NW Nb NB Ne NE N{ N} N[{ N]} Ntx Nfx NTx NFx N; N, NG 45 | * i a s o x r I A S O X gr J 46 | * Nx Nr Ngr 47 | * d y c dd yy cc D C p P d$ y$ c$ ... 48 | * Ndd Nyy Ncc 49 | * ci[ ca[ ci" ca" 50 | * Nci[ Nca[ 51 | * v V v..d v..c v..y v..r v..j 52 | * . 53 | 54 | * [quickref function list is here.](https://github.com/74th/vscode-vim/blob/master/quickref.md) 55 | * If you need Ctrl-D and Ctrl-U for scrolling, you can use [additional scroll key entention](https://marketplace.visualstudio.com/items?itemName=74th.scrollkey). 56 | 57 | ## Support to call VSCode commands from vim keybind 58 | 59 | * u : / n N > < % 60 | 61 | But they do not behave exactly as vim command, and their supports may be changed. 62 | 63 | ## Settings 64 | 65 | If you needs optional settings, please copy belong json and paste to your `settings.json`. 66 | 67 | ``` 68 | // Show mode in status bar (default: true) 69 | "vimStyle.showMode": false, 70 | // changing cursor by mode (default: true) 71 | "vimStyle.changeCursorStyle": false, 72 | // motion with jkl;(default: false) 73 | "vimStyle.useErgonomicKeyForMotion": true, 74 | // default mode (default: "normal") 75 | "vimStyle.defaultMode": "insert", 76 | // disable without to uninstall (default: "true") 77 | "vimStyle.enabled": "false", 78 | // keys go to normal mode from insert mode (default: "" disable) 79 | "vimStyle.imapEsc": "jj", 80 | // some vimrc like settings 81 | "vimStyle.vimrc": [ 82 | "nmap Y y$", // 83 | "nnoremapmap Y y$", // 84 | ] 85 | ``` 86 | 87 | If you would like to use `ctrl+[` instead of `escape`, please add blong setting to your `keybindings.json`. 88 | 89 | ``` 90 | { 91 | "key": "ctrl+[", 92 | "command": "vim.Esc", 93 | "when": "editorTextFocus" 94 | } 95 | ``` 96 | 97 | If you would like more vim flavor, you can use `vim.inNormalMode`, `vim.inInsertMode` and `vim.inVisualMode` to your keyboard shortcuts. For example, 98 | 99 | ``` 100 | // in insert mode 101 | { 102 | // Ctrl-h : BackSpace 103 | "key": "ctrl+h", "command": "deleteLeft", 104 | "when": "editorTextFocus && vim.inInsertMode" 105 | }, 106 | { 107 | // Ctrl-w : delete word 108 | "key": "ctrl+w", "command": "deleteWordLeft", 109 | "when": "editorTextFocus && vim.inInsertMode" 110 | }, 111 | // in visual mode 112 | { 113 | // arrow up 114 | "key": "up", "command": "vim.type-", 115 | "when": "editorTextFocus && vim.inVisualMode" 116 | }, 117 | { 118 | // arrow down 119 | "key": "down", "command": "vim.type-", 120 | "when": "editorTextFocus && vim.inVisualMode" 121 | }, 122 | { 123 | // arrow right 124 | "key": "right", "command": "vim.type-", 125 | "when": "editorTextFocus && vim.inVisualMode" 126 | }, 127 | { 128 | // arrow left 129 | "key": "left", "command": "vim.type-", 130 | "when": "editorTextFocus && vim.inVisualMode" 131 | }, 132 | ``` 133 | 134 | ## Support vimrc settings 135 | 136 | * `nmap Y y$` 137 | * `nnoremap Y y$` 138 | 139 | ## Will never be supported 140 | 141 | * Ctrl-x Cmd-x Alt-x Meta-x 142 | 143 | ## VimStyle Loadmap 144 | 145 | the vim emulator written type script 146 | 147 | * [x] basic vim keybinds 148 | * [x] fix word motion bugs 149 | * [x] user keybindings ( nmap ) 150 | * [ ] user keybindings ( call vscode function ) 151 | * [ ] text object ( diw, di{ ... ) 152 | * [ ] resister 153 | * [ ] vim style plugin for chrome browser 154 | * [ ] complete quickref list 155 | 156 | ## License 157 | 158 | MIT License 159 | 160 | ## Contributing 161 | 162 | [function list](https://github.com/74th/vscode-vim/blob/master/quickref.md) 163 | 164 | ``` 165 | git clone https://github.com/74th/vscode-vim.git ~/.vscode/extensions/vscode-vim 166 | cd ~/.vscode/extensions/vscode-vim 167 | npm install 168 | npm run-script build 169 | ``` 170 | 171 | ## Updates 172 | 173 | ### 0.5.7 174 | 175 | * fix insert mode 176 | 177 | ### 0.5.5 178 | 179 | * fix https://github.com/74th/vscode-vim/issues/73 180 | 181 | ### 0.5.4 182 | 183 | * fix multi select problem [#64](https://github.com/74th/vscode-vim/issues/64) [#69](https://github.com/74th/vscode-vim/issues/69) 184 | 185 | ### 0.5.3 186 | 187 | * #57 support arrow keys in visual mode(need add keybindings) 188 | 189 | ### 0.5.2 190 | 191 | * fix #65 192 | 193 | ### 0.5.1 194 | 195 | * support J v..J V..J 196 | 197 | ### 0.5.0 198 | 199 | * support ci[ ci{ ci( ci< ca[ ca{ ca( ca< 200 | * support ci' ci" ci` ca' ca" ca` 201 | * treat `_` as alphabet 202 | 203 | ### 0.4.7 204 | 205 | * fix visual mode moves 206 | 207 | ### 0.4.6 208 | 209 | * revert 0.4.2 210 | 211 | ### 0.4.5 212 | 213 | * some bugfix 214 | 215 | ### 0.4.2 216 | 217 | * support `[(`,`[{`,`])`,`]}` 218 | 219 | ### 0.4.1 220 | 221 | * support `{`,`}` 222 | 223 | ### 0.4.0 224 | 225 | * support nmap nnoremap (use like vimrc setting) 226 | 227 | ### 0.3.13 228 | 229 | * support vim mode contexts for keyboard shortcuts #45 230 | * fix word motion bugs #38 231 | 232 | ### 0.3.12 233 | 234 | * support imap ESC keybinding 235 | * support enable setting 236 | 237 | ### 0.3.11 238 | 239 | * [fix #38](https://github.com/74th/vscode-vim/issues/38) 240 | * [fix #39](https://github.com/74th/vscode-vim/issues/39) 241 | 242 | ### 0.3.10 243 | 244 | * [default mode stting #34](https://github.com/74th/vscode-vim/issues/34) 245 | * [fix word motion bug](https://github.com/74th/vscode-vim/issues/35) 246 | 247 | ### 0.3.9 248 | 249 | * support ; , 250 | * [fix #38](https://github.com/74th/vscode-vim/issues/38) 251 | * [support #31](https://github.com/74th/vscode-vim/issues/31) 252 | * [test to fix #32](https://github.com/74th/vscode-vim/issues/32) 253 | 254 | ### 0.3.6 255 | 256 | * support r gr v..r 257 | * fix visual mode move bug 258 | 259 | ### 0.3.5 260 | 261 | * support VSCode commands from vim keybindings % 262 | * fix some bug 263 | 264 | ### 0.3.4 265 | 266 | * support VSCode commands from vim keybindings : / n N > < 267 | 268 | ### 0.3.3 269 | 270 | * support ^ 271 | * change icon 272 | 273 | ![icon](https://raw.githubusercontent.com/74th/vscode-vim/master/vim.png) 274 | 275 | ### 0.3.2 276 | 277 | * update for vscode 1.0.0 278 | * support repeat command 279 | * support visual line mode 280 | * support tab size 281 | 282 | ### 0.2.4 283 | 284 | * support W B e E 285 | 286 | ### 0.2.3 287 | 288 | * support `useErgonomicKeyForMotion` option : move a cursur by `jkl;` 289 | * support visual mode 290 | 291 | ### 0.2.2 292 | 293 | * fix #21 CR+LF bug 294 | 295 | ### 0.2.1 296 | 297 | * update README 298 | 299 | ### 0.2.0 300 | 301 | * more friendly with VSCode functions 302 | * show block sursor 303 | * bug fix : dfx dFx dtx dTx 304 | * append indents by o O 305 | 306 | ### 0.1.8 307 | 308 | * JISキーボード向けオプション(support Win and Mac Jis keyboard option) 309 | * show suggestion by only alpabet and . 310 | * fix some bugs 311 | 312 | ### 0.1.7 313 | 314 | * gg G 315 | 316 | ### 0.1.1 317 | 318 | pre release! 319 | -------------------------------------------------------------------------------- /src/VimStyle.ts: -------------------------------------------------------------------------------- 1 | import { InsertTextAction } from "./action/InsertTextAction"; 2 | import { CommandFactory } from "./core/CommandFactory"; 3 | import { ExecExCommand } from "./core/ExMode"; 4 | import { ApplyKeyBindings, LoadKeyBindings } from "./core/KeyBindings"; 5 | import { Register } from "./core/Register"; 6 | import { InsertModeExecute } from "./mode/InsertMode"; 7 | import { FindCharacterMotion } from "./motion/FindCharacterMotion"; 8 | import * as Utils from "./Utils"; 9 | 10 | export class VimStyle implements IVimStyle { 11 | 12 | public CommandFactory: ICommandFactory; 13 | public Options: IVimStyleOptions; 14 | public Register: IRegister; 15 | 16 | public LastAction: IAction; 17 | public LastEditAction: IAction; 18 | public LastMoveCharPosition: number; 19 | public LastFindCharacterMotion: FindCharacterMotion; 20 | 21 | private mode: VimMode; 22 | private editor: IEditor; 23 | 24 | constructor(editor: IEditor, conf: IVimStyleOptions) { 25 | this.editor = editor; 26 | editor.SetVimStyle(this); 27 | this.setMode(VimMode.Normal); 28 | this.CommandFactory = new CommandFactory(); 29 | this.Register = new Register(); 30 | 31 | this.LastAction = null; 32 | this.LastEditAction = null; 33 | this.LastMoveCharPosition = null; 34 | 35 | this.ApplyOptions(conf); 36 | 37 | this.ExecuteVimrc(conf); 38 | } 39 | 40 | public PushKey(key: string) { 41 | // tslint:disable-next-line:switch-default 42 | switch (this.mode) { 43 | case VimMode.Normal: 44 | case VimMode.Visual: 45 | case VimMode.VisualLine: 46 | this.readCommand(key); 47 | return; 48 | case VimMode.Insert: 49 | InsertModeExecute(key, this.editor); 50 | } 51 | } 52 | 53 | public PushEscKey() { 54 | 55 | // if this mode insert 56 | // save inserted text infomation 57 | if (this.mode === VimMode.Insert) { 58 | this.setInsertText(); 59 | } 60 | 61 | let p = this.editor.GetCurrentPosition(); 62 | if (p != null) { 63 | if (this.mode === VimMode.Insert && p.Char > 0) { 64 | p.Char -= 1; 65 | } 66 | if (this.mode === VimMode.Visual && p.Char > 0) { 67 | p.Char -= 1; 68 | } 69 | } 70 | 71 | this.setMode(VimMode.Normal); 72 | 73 | this.CommandFactory.Clear(); 74 | this.editor.CloseCommandStatus(); 75 | this.editor.ApplyNormalMode(p); 76 | } 77 | 78 | public ApplyInsertMode(p?: Position) { 79 | this.setMode(VimMode.Insert); 80 | this.editor.ApplyInsertMode(p); 81 | } 82 | 83 | public ApplyVisualMode() { 84 | this.setMode(VimMode.Visual); 85 | 86 | } 87 | 88 | public ApplyVisualLineMode() { 89 | this.setMode(VimMode.VisualLine); 90 | } 91 | 92 | public GetMode(): VimMode { 93 | return this.mode; 94 | } 95 | public ApplyNormalMode() { 96 | this.setMode(VimMode.Normal); 97 | } 98 | 99 | public ApplyOptions(conf: IVimStyleOptions) { 100 | this.Options = conf; 101 | this.LoadKeyBinding(); 102 | } 103 | 104 | public ExecuteVimrc(conf: IVimStyleOptions) { 105 | if (conf.vimrc == null || conf.vimrc.length === 0) { 106 | return; 107 | } 108 | for (let line of conf.vimrc) { 109 | ExecExCommand(line, this, this.editor); 110 | } 111 | } 112 | 113 | public SetAdditionalKeyBinds(keyBindings: IKeyBindings) { 114 | ApplyKeyBindings(this.CommandFactory.KeyBindings, keyBindings); 115 | } 116 | 117 | private readCommand(key: string) { 118 | 119 | let actionList = this.CommandFactory.PushKey(key, this.mode, true); 120 | 121 | if (actionList.length === 0) { 122 | this.showCommand(); 123 | return; 124 | } 125 | 126 | this.editor.CloseCommandStatus(); 127 | 128 | for (let action of actionList) { 129 | action.Execute(this.editor, this); 130 | 131 | let type = action.GetActionType(); 132 | switch (type) { 133 | case ActionType.Edit: 134 | case ActionType.Insert: 135 | this.LastEditAction = action; 136 | break; 137 | } 138 | this.LastAction = action; 139 | } 140 | 141 | this.CommandFactory.Clear(); 142 | } 143 | 144 | private showCommand() { 145 | this.editor.ShowCommandStatus(this.CommandFactory.GetCommandString()); 146 | } 147 | 148 | private setMode(mode: VimMode) { 149 | this.mode = mode; 150 | this.editor.ShowModeStatus(this.mode); 151 | } 152 | 153 | private LoadKeyBinding() { 154 | this.CommandFactory.KeyBindings = LoadKeyBindings(this.Options); 155 | } 156 | 157 | private setInsertText() { 158 | 159 | if (this.LastAction.GetActionType() !== ActionType.Insert) { 160 | return; 161 | } 162 | let action = this.LastAction as IInsertTextAction; 163 | let info = action.GetInsertModeInfo(); 164 | if (!info) { 165 | this.LastAction = null; 166 | return; 167 | } 168 | 169 | let lineCount = this.editor.GetLastLineNum() + 1; 170 | if (info.DocumentLineCount > lineCount) { 171 | // reduced document? 172 | return; 173 | } 174 | 175 | let cp = this.editor.GetCurrentPosition(); 176 | if (cp.Line < info.Position.Line) { 177 | // move to back? 178 | return; 179 | } 180 | let startLine = this.editor.ReadLine(info.Position.Line); 181 | let endLine = this.editor.ReadLine(cp.Line); 182 | if (startLine.substring(0, info.Position.Char) !== info.BeforeText) { 183 | // use backspace? 184 | return; 185 | } 186 | if (endLine.substring(cp.Char) !== info.AfterText) { 187 | // use delete key? 188 | return; 189 | } 190 | let range = new Range(); 191 | range.start = info.Position; 192 | range.end = cp; 193 | action.SetInsertText(this.editor.ReadRange(range)); 194 | } 195 | 196 | } 197 | 198 | export class Position implements IPosition { 199 | public Line: number; 200 | public Char: number; 201 | constructor(line?: number, char?: number) { 202 | this.Line = line === undefined ? 0 : line; 203 | this.Char = char === undefined ? 0 : char; 204 | } 205 | public Copy(): IPosition { 206 | return new Position(this.Line, this.Char); 207 | } 208 | 209 | public IsEqual(other: IPosition) { 210 | return other.Line === this.Line && other.Char === this.Char; 211 | } 212 | 213 | public IsBefore(other: IPosition) { 214 | if (this.Line === other.Line) { 215 | return this.Char < other.Char; 216 | } 217 | return this.Line < other.Line; 218 | } 219 | 220 | public IsBeforeOrEqual(other: IPosition) { 221 | if (this.Line === other.Line) { 222 | return this.Char <= other.Char; 223 | } 224 | return this.Line < other.Line; 225 | } 226 | 227 | public IsAfter(other: IPosition) { 228 | if (this.Line === other.Line) { 229 | return this.Char > other.Char; 230 | } 231 | return this.Line > other.Line; 232 | } 233 | 234 | public IsAfterOrEqual(other: IPosition) { 235 | if (this.Line === other.Line) { 236 | return this.Char >= other.Char; 237 | } 238 | return this.Line > other.Line; 239 | } 240 | } 241 | 242 | export class Range implements IRange { 243 | public start: IPosition; 244 | public end: IPosition; 245 | 246 | constructor() { 247 | this.start = new Position(); 248 | this.end = new Position(); 249 | } 250 | 251 | public Sort() { 252 | let isReverse = false; 253 | if (this.end.Line < this.start.Line) { 254 | isReverse = true; 255 | } else if (this.end.Line === this.start.Line) { 256 | if (this.end.Char < this.start.Char) { 257 | isReverse = true; 258 | } 259 | } 260 | if (isReverse) { 261 | let b = this.start; 262 | this.start = this.end; 263 | this.end = b; 264 | } 265 | } 266 | 267 | public IsContain(p: IPosition): boolean { 268 | let r = this.Copy(); 269 | r.Sort(); 270 | if (p.Line < r.start.Line) { 271 | return false; 272 | } 273 | if (r.end.Line < p.Line) { 274 | return false; 275 | } 276 | if (p.Line === r.start.Line && p.Char < r.start.Char) { 277 | return false; 278 | } 279 | if (p.Line === r.end.Line && r.end.Char < p.Char) { 280 | return false; 281 | } 282 | return true; 283 | } 284 | 285 | public Copy(): IRange { 286 | let r = new Range(); 287 | r.start.Char = this.start.Char; 288 | r.start.Line = this.start.Line; 289 | r.end.Char = this.end.Char; 290 | r.end.Line = this.end.Line; 291 | return r; 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/motion/FindCharacterMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | 5 | /** 6 | * fx Fx tx Tx ; , 7 | * cfx cFx ctx cTx c; c, 8 | */ 9 | export class FindCharacterMotion extends AbstractMotion implements IRequireCharacterMotion { 10 | 11 | public CharacterCode: number; 12 | public Direction: Direction; 13 | public OppositeDirection: boolean; 14 | public IsTill: boolean; 15 | public IsContainTargetChar: boolean; 16 | 17 | constructor(direction: Direction) { 18 | super(); 19 | this.Direction = direction; 20 | this.OppositeDirection = false; 21 | this.IsTill = false; 22 | this.IsContainTargetChar = false; 23 | this.CharacterCode = null; 24 | } 25 | 26 | public SetChar(c: string) { 27 | this.CharacterCode = c.charCodeAt(0); 28 | } 29 | 30 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 31 | let line = editor.ReadLineAtCurrentPosition(); 32 | let end = new Position(); 33 | end.Line = start.Line; 34 | let i; 35 | let count = this.Count; 36 | if (this.CharacterCode === null) { 37 | 38 | if (vim.LastFindCharacterMotion === null) { 39 | return null; 40 | } 41 | 42 | let last: any; 43 | last = vim.LastFindCharacterMotion; 44 | 45 | if (this.OppositeDirection) { 46 | if (last.Direction === Direction.Left) { 47 | this.Direction = Direction.Right; 48 | } else { 49 | this.Direction = Direction.Left; 50 | } 51 | } else { 52 | this.Direction = last.Direction; 53 | } 54 | this.IsTill = last.IsTill; 55 | this.CharacterCode = last.CharacterCode; 56 | 57 | } else { 58 | 59 | // save direction for ; , 60 | vim.LastFindCharacterMotion = this; 61 | 62 | } 63 | if (this.CharacterCode === null) { 64 | return null; 65 | } 66 | 67 | if (this.Direction === Direction.Right) { 68 | for (i = start.Char + 1; i < line.length; i++) { 69 | if (this.IsTill && i === start.Char + 1) { 70 | continue; 71 | } 72 | if (this.CharacterCode === line.charCodeAt(i)) { 73 | count--; 74 | if (count === 0) { 75 | end.Char = i; 76 | break; 77 | } 78 | } 79 | } 80 | } else { 81 | for (i = start.Char - 1; i >= 0; i--) { 82 | if (this.IsTill && i === start.Char - 1) { 83 | continue; 84 | } 85 | if (this.CharacterCode === line.charCodeAt(i)) { 86 | count--; 87 | if (count === 0) { 88 | end.Char = i; 89 | break; 90 | } 91 | } 92 | } 93 | } 94 | if (count > 0) { 95 | return null; 96 | } 97 | if (this.IsTill) { 98 | if (this.Direction === Direction.Right) { 99 | end.Char -= 1; 100 | } else { 101 | end.Char += 1; 102 | } 103 | } 104 | if (this.IsContainTargetChar) { 105 | // use dfx dtx 106 | end.Char += 1; 107 | } 108 | return end; 109 | } 110 | } 111 | 112 | /** 113 | * fx 114 | */ 115 | export function GotoCharacterToRight(num: number): IAction { 116 | let a = new GoAction(); 117 | let m: FindCharacterMotion; 118 | m = new FindCharacterMotion(Direction.Right); 119 | m.Count = num > 0 ? num : 1; 120 | a.Motion = m; 121 | return a; 122 | } 123 | 124 | /** 125 | * Fx 126 | */ 127 | export function GotoCharacterToLeft(num: number): IAction { 128 | let a = new GoAction(); 129 | let m: FindCharacterMotion; 130 | m = new FindCharacterMotion(Direction.Left); 131 | m.Count = num > 0 ? num : 1; 132 | a.Motion = m; 133 | return a; 134 | } 135 | 136 | /** 137 | * tx 138 | */ 139 | export function GoTillBeforeCharacterToRight(num: number): IAction { 140 | let a = new GoAction(); 141 | let m: FindCharacterMotion; 142 | m = new FindCharacterMotion(Direction.Right); 143 | m.Count = num > 0 ? num : 1; 144 | m.IsTill = true; 145 | a.Motion = m; 146 | return a; 147 | } 148 | 149 | /** 150 | * Tx 151 | */ 152 | export function GoTillBeforeCharacterToLeft(num: number): IAction { 153 | let a = new GoAction(); 154 | let m: FindCharacterMotion; 155 | m = new FindCharacterMotion(Direction.Left); 156 | m.Count = num > 0 ? num : 1; 157 | m.IsTill = true; 158 | a.Motion = m; 159 | return a; 160 | } 161 | 162 | /** 163 | * cfx 164 | */ 165 | export function AddCharacterToRightMotion(num: number, action: IAction): void { 166 | let m: FindCharacterMotion; 167 | m = new FindCharacterMotion(Direction.Right); 168 | m.IsContainTargetChar = true; 169 | m.Count = num > 0 ? num : 1; 170 | let a = action; 171 | a.Motion = m; 172 | } 173 | 174 | /** 175 | * cFx 176 | */ 177 | export function AddCharacterToLeftMotion(num: number, action: IAction): void { 178 | let m: FindCharacterMotion; 179 | m = new FindCharacterMotion(Direction.Left); 180 | m.Count = num > 0 ? num : 1; 181 | let a = action; 182 | a.Motion = m; 183 | } 184 | 185 | /** 186 | * ctx 187 | */ 188 | export function AddTillCharacterToRightMotion(num: number, action: IAction): void { 189 | let m: FindCharacterMotion; 190 | m = new FindCharacterMotion(Direction.Right); 191 | m.IsContainTargetChar = true; 192 | m.Count = num > 0 ? num : 1; 193 | m.IsTill = true; 194 | let a = action; 195 | a.Motion = m; 196 | } 197 | 198 | /** 199 | * cTx 200 | */ 201 | export function AddTillCharacterToLeftMotion(num: number, action: IAction): void { 202 | let m: FindCharacterMotion; 203 | m = new FindCharacterMotion(Direction.Left); 204 | m.Count = num > 0 ? num : 1; 205 | m.IsTill = true; 206 | let a = action; 207 | a.Motion = m; 208 | } 209 | 210 | /** 211 | * vfx 212 | */ 213 | export function AddVisualGotoCharacterToRightMotion(num: number, action: IAction): IAction { 214 | let a = action; 215 | let m: FindCharacterMotion; 216 | m = new FindCharacterMotion(Direction.Right); 217 | m.IsContainTargetChar = false; 218 | m.Count = num > 0 ? num : 1; 219 | a.Motion = m; 220 | return a; 221 | } 222 | 223 | /** 224 | * vFx 225 | */ 226 | export let AddVisualGotoCharacterToLeftMotion: (num: number, action: IAction) => void = AddCharacterToLeftMotion; 227 | 228 | /** 229 | * vtx 230 | */ 231 | export function AddVisualGoTillCharacterToRightMotion(num: number, action: IAction): IAction { 232 | let a = action; 233 | let m: FindCharacterMotion; 234 | m = new FindCharacterMotion(Direction.Right); 235 | m.Count = num > 0 ? num : 1; 236 | m.IsContainTargetChar = false; 237 | m.IsTill = true; 238 | a.Motion = m; 239 | return a; 240 | } 241 | 242 | /** 243 | * vTx 244 | */ 245 | export let AddVisualGoTillCharacterToLeftMotion: (num: number, action: IAction) => void = AddTillCharacterToLeftMotion; 246 | 247 | /** 248 | * N; 249 | */ 250 | export function GotoRepeatCharacter(num: number): IAction { 251 | let a = new GoAction(); 252 | let m: FindCharacterMotion; 253 | m = new FindCharacterMotion(Direction.Right); 254 | m.Count = num > 0 ? num : 1; 255 | a.Motion = m; 256 | return a; 257 | } 258 | 259 | /** 260 | * c; 261 | */ 262 | export function AddRepeartCharacterMotion(num: number, action: IAction) { 263 | let m: FindCharacterMotion; 264 | m = new FindCharacterMotion(null); 265 | m.IsContainTargetChar = true; 266 | m.Count = num > 0 ? num : 1; 267 | let a = action; 268 | a.Motion = m; 269 | } 270 | 271 | /** 272 | * vc; 273 | */ 274 | export function AddVisualGotoRepeartCharacterMotion(num: number, action: IAction) { 275 | let m: FindCharacterMotion; 276 | m = new FindCharacterMotion(null); 277 | m.IsContainTargetChar = false; 278 | m.Count = num > 0 ? num : 1; 279 | let a = action; 280 | a.Motion = m; 281 | } 282 | 283 | /** 284 | * N, 285 | */ 286 | export function GotoRepeatCharacterOppositeDirection(num: number): IAction { 287 | let a = new GoAction(); 288 | let m: FindCharacterMotion; 289 | m = new FindCharacterMotion(null); 290 | m.OppositeDirection = true; 291 | m.Count = num > 0 ? num : 1; 292 | a.Motion = m; 293 | return a; 294 | } 295 | 296 | /** 297 | * c, 298 | */ 299 | export function AddRepeartCharacterMotionOppositeDirection(num: number, action: IAction) { 300 | let m: FindCharacterMotion; 301 | m = new FindCharacterMotion(null); 302 | m.OppositeDirection = true; 303 | m.Count = num > 0 ? num : 1; 304 | let a = action; 305 | a.Motion = m; 306 | } 307 | 308 | /** 309 | * vcN, 310 | */ 311 | export let AddVisualGotoRepeartCharacterMotionOppositeDirection = AddRepeartCharacterMotionOppositeDirection; 312 | -------------------------------------------------------------------------------- /test/VirtualEditor.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../src/Utils"; 2 | import { Position, Range, VimStyle } from "../src/VimStyle"; 3 | 4 | export class VirtualEditor implements IEditor { 5 | 6 | public CommandStatus: string; 7 | public ModeStatus: string; 8 | public Position: Position; 9 | public VimStyle: IVimStyle; 10 | 11 | private contents: string[]; 12 | private currentVisualLineModeInfo: IVisualLineModeSelectionInfo; 13 | private currentVisualModeSelection: IRange; 14 | 15 | constructor() { 16 | this.contents = []; 17 | this.CommandStatus = ""; 18 | this.ModeStatus = ""; 19 | this.Position = new Position(); 20 | this.Position.Char = 0; 21 | this.Position.Line = 0; 22 | } 23 | 24 | public SetContent(textList: string[]) { 25 | this.contents = textList; 26 | let len = textList.length; 27 | for (let i = 0; i < len; i++) { 28 | let line = textList[i]; 29 | let c = line.indexOf("|"); 30 | if (c !== -1) { 31 | this.Position.Line = i; 32 | this.Position.Char = c; 33 | this.contents[i] = this.contents[i].replace("|", ""); 34 | return; 35 | } 36 | } 37 | } 38 | 39 | public GetContent(): string[] { 40 | let output = []; 41 | let len = this.contents.length; 42 | for (let i = 0; i < len; i++) { 43 | output.push(this.contents[i]); 44 | } 45 | let line = output[this.Position.Line]; 46 | output[this.Position.Line] = 47 | line.substr(0, this.Position.Char) + "|" + line.substr(this.Position.Char, line.length); 48 | return output; 49 | } 50 | 51 | public CloseCommandStatus() { 52 | this.CommandStatus = ""; 53 | } 54 | public ShowCommandStatus(text: string) { 55 | this.CommandStatus = text; 56 | } 57 | public ShowModeStatus(mode: VimMode) { 58 | this.ModeStatus = Utils.ModeToString(mode); 59 | } 60 | public InsertTextAtCurrentPosition(text: string) { 61 | let cLine = this.contents[this.Position.Line]; 62 | let pre = cLine.substr(0, this.Position.Char); 63 | let su = cLine.substr(this.Position.Char, cLine.length - this.Position.Char); 64 | let lineList = text.split("\n"); 65 | if (lineList.length === 1) { 66 | this.contents[this.Position.Line] = pre + text + su; 67 | this.Position.Char += text.length; 68 | return; 69 | } 70 | this.contents[this.Position.Line] = pre + lineList[0]; 71 | for (let i = 1; i < lineList.length - 1; i++) { 72 | this.contents.splice(this.Position.Line + i, 0, lineList[i]); 73 | } 74 | this.contents.splice(this.Position.Line + lineList.length - 1, 0, lineList[lineList.length - 1] + su); 75 | this.Position.Line = this.Position.Line + lineList.length - 1; 76 | this.Position.Char = lineList[lineList.length].length; 77 | } 78 | public SetModeStatusVisibility(visible: boolean) { 79 | return; 80 | } 81 | 82 | public InsertCharactorAtCurrentPosition(char: string) { 83 | let cLine = this.contents[this.Position.Line]; 84 | let pre = cLine.substr(0, this.Position.Char); 85 | let su = cLine.substr(this.Position.Char, cLine.length - this.Position.Char); 86 | this.contents[this.Position.Line] = pre + char + su; 87 | this.Position.Char += 1; 88 | } 89 | public TypeDirect(char: string) { 90 | this.InsertCharactorAtCurrentPosition(char); 91 | } 92 | public Insert(position: IPosition, text: string) { 93 | let cLine = this.contents[position.Line]; 94 | let pre = cLine.substr(0, position.Char); 95 | let su = cLine.substr(position.Char, cLine.length - position.Char); 96 | let lineList = text.split("\n"); 97 | if (lineList.length === 1) { 98 | this.contents[position.Line] = pre + text + su; 99 | return; 100 | } 101 | this.contents[position.Line] = pre + lineList[0]; 102 | for (let i = 1; i < lineList.length - 1; i++) { 103 | this.contents.splice(position.Line + i, 0, lineList[i]); 104 | } 105 | this.contents.splice(position.Line + lineList.length - 1, 0, lineList[lineList.length - 1] + su); 106 | } 107 | public DeleteRange(range: IRange, position?: IPosition) { 108 | if (range.start.Line === range.end.Line) { 109 | let line = this.contents[range.start.Line]; 110 | let pre = line.substr(0, range.start.Char); 111 | let su = line.substr(range.end.Char, line.length - range.end.Char); 112 | this.contents[range.start.Line] = pre + su; 113 | } else { 114 | let line = this.contents[range.start.Line].substr(0, range.start.Char); 115 | line += this.contents[range.end.Line].substr( 116 | range.end.Char, this.contents[range.end.Line].length - range.end.Char); 117 | this.contents.splice(range.start.Line, range.end.Line - range.start.Line + 1, line); 118 | } 119 | if (position !== undefined) { 120 | this.Position = position; 121 | } 122 | } 123 | public ReplaceRange(range: IRange, text: string) { 124 | this.DeleteRange(range); 125 | this.Insert(range.start, text); 126 | } 127 | 128 | // Read Line 129 | public ReadLineAtCurrentPosition(): string { 130 | return this.ReadLine(this.Position.Line); 131 | } 132 | public ReadLine(line: number): string { 133 | return this.contents[line]; 134 | } 135 | 136 | // Read Range 137 | public ReadRange(range: IRange): string { 138 | if (range.start.Line === range.end.Line) { 139 | return this.contents[range.start.Line].substr(range.start.Char, range.end.Char - range.start.Char); 140 | } 141 | let line = this.contents[range.start.Line]; 142 | let result = line.substr(range.start.Char, line.length - range.start.Char); 143 | for (let i = range.start.Line + 1; i < range.end.Line; i++) { 144 | result += "\n" + this.contents[i]; 145 | } 146 | line = this.contents[range.end.Line]; 147 | result += "\n" + line.substr(0, range.end.Char); 148 | return result; 149 | } 150 | 151 | // Position 152 | public GetCurrentPosition(): IPosition { 153 | return this.Position; 154 | } 155 | public SetPosition(position: IPosition) { 156 | this.Position = position; 157 | } 158 | public GetLastPosition(): IPosition { 159 | let p = new Position(); 160 | p.Line = this.GetLastLineNum(); 161 | p.Char = this.contents[this.GetLastLineNum()].length; 162 | return p; 163 | } 164 | 165 | // Document Info 166 | public GetLastLineNum(): number { 167 | return this.contents.length - 1; 168 | } 169 | 170 | // Set VimStyle 171 | public SetVimStyle(vim: IVimStyle) { 172 | this.VimStyle = vim; 173 | } 174 | 175 | // set modes 176 | public ApplyNormalMode(cursor?: IPosition, isLineHasNoChar?: boolean, isLastLine?: boolean) { 177 | return; 178 | } 179 | public ApplyInsertMode(p: IPosition) { 180 | this.Position = p; 181 | } 182 | public ShowVisualMode(range: IRange, focusPosition?: IPosition) { 183 | this.Position = focusPosition; 184 | this.currentVisualModeSelection = range; 185 | } 186 | 187 | public GetCurrentVisualModeSelection(): IRange { 188 | return this.currentVisualModeSelection; 189 | } 190 | 191 | public ShowVisualLineMode(startLine: number, endLine: number, focusPosition?: IPosition) { 192 | this.currentVisualLineModeInfo = { 193 | startLine, 194 | endLine, 195 | focusPosition, 196 | }; 197 | } 198 | 199 | public GetCurrentVisualLineModeSelection(): IVisualLineModeSelectionInfo { 200 | return this.currentVisualLineModeInfo; 201 | } 202 | // check invalid position 203 | public UpdateValidPosition(p: IPosition, isBlock?: boolean): IPosition { 204 | if (p.Line > this.GetLastLineNum()) { 205 | return this.GetLastPosition(); 206 | } 207 | if (isBlock) { 208 | if (p.Char > this.contents[p.Line].length - 1) { 209 | let np = new Position(); 210 | np.Line = p.Line; 211 | np.Char = this.contents[p.Line].length - 1; 212 | return np; 213 | } 214 | } else { 215 | if (p.Char > this.contents[p.Line].length) { 216 | let np = new Position(); 217 | np.Line = p.Line; 218 | np.Char = this.contents[p.Line].length; 219 | return np; 220 | } 221 | } 222 | 223 | return p; 224 | } 225 | 226 | public Type(keystroke: string) { 227 | let len = keystroke.length; 228 | for (let i = 0; i < len; i++) { 229 | let k = keystroke.charAt(i); 230 | if (k === "_") { 231 | this.VimStyle.PushEscKey(); 232 | } else { 233 | this.VimStyle.PushKey(k); 234 | } 235 | } 236 | } 237 | 238 | public GetTabSize() { 239 | return 4; 240 | } 241 | 242 | public dispose() { 243 | return; 244 | } 245 | 246 | // tslint:disable-next-line:no-empty 247 | public CallEditorCommand(argument: string) { 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/motion/DeleteWordMotion.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | 5 | /** 6 | * dw 7 | * please see wordMotionStateModel/deleteWord.png 8 | */ 9 | export class DeleteWordMotion extends AbstractMotion { 10 | 11 | public IsWORD: boolean; 12 | 13 | constructor() { 14 | super(); 15 | }; 16 | 17 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 18 | let cal = new Calculater(start, this.Count, this.IsWORD, editor); 19 | return cal.CalculateEnd(); 20 | } 21 | } 22 | 23 | enum State { 24 | first = 1, 25 | firstWhenCountGreaterThan1, 26 | character, 27 | space, 28 | linefeed, 29 | decreaseCount, 30 | decreaseCountAtLinefeed, 31 | characterWhenCountEq1, 32 | firstWhenCountEq1, 33 | spaceWhenCountEq1, 34 | deleteUntilLinefeed, 35 | deleteUntilJustBefore, 36 | reachDocumentEnd, 37 | } 38 | 39 | enum NextCharacter { 40 | character, 41 | sameTypeCharacter, 42 | differenceTypeCharacter, 43 | lineFeed, 44 | space, 45 | } 46 | 47 | class Calculater { 48 | public pos: IPosition; 49 | public line: string; 50 | public documentLines: number; 51 | public count: number; 52 | public IsWORD: boolean; 53 | 54 | public beforeCharacterGroup: CharGroup; 55 | 56 | public editor: IEditor; 57 | 58 | constructor(start: IPosition, count: number, isWord: boolean, editor: IEditor) { 59 | this.pos = start.Copy(); 60 | this.pos.Char--; 61 | this.line = editor.ReadLine(start.Line); 62 | this.editor = editor; 63 | this.documentLines = editor.GetLastLineNum(); 64 | this.count = count; 65 | this.beforeCharacterGroup = null; 66 | this.IsWORD = isWord; 67 | }; 68 | 69 | public getNextCharacter(): NextCharacter { 70 | this.pos.Char++; 71 | if (this.pos.Char > this.line.length) { 72 | this.pos.Line++; 73 | this.pos.Char = 0; 74 | this.line = this.editor.ReadLine(this.pos.Line); 75 | } 76 | if (this.pos.Char === this.line.length) { 77 | if (this.pos.Line === this.documentLines) { 78 | return null; 79 | } 80 | this.beforeCharacterGroup = null; 81 | return NextCharacter.lineFeed; 82 | } 83 | let characterGroup: CharGroup = Utils.GetCharClass(this.line.charCodeAt(this.pos.Char)); 84 | let result: NextCharacter; 85 | if (characterGroup === CharGroup.Spaces) { 86 | this.beforeCharacterGroup = null; 87 | return NextCharacter.space; 88 | } 89 | if (this.beforeCharacterGroup === null) { 90 | this.beforeCharacterGroup = characterGroup; 91 | return NextCharacter.character; 92 | } 93 | if (this.IsWORD) { 94 | this.beforeCharacterGroup = characterGroup; 95 | return NextCharacter.sameTypeCharacter; 96 | } 97 | if (this.beforeCharacterGroup === characterGroup) { 98 | return NextCharacter.sameTypeCharacter; 99 | } 100 | this.beforeCharacterGroup = characterGroup; 101 | return NextCharacter.differenceTypeCharacter; 102 | } 103 | 104 | public doAtFirst(): State { 105 | if (this.count === 1) { 106 | return State.firstWhenCountEq1; 107 | } 108 | return State.firstWhenCountGreaterThan1; 109 | }; 110 | 111 | public doAtFirstWhenCountGreaterThan1(): State { 112 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 113 | if (nextCharacterGroup === null) { 114 | return State.reachDocumentEnd; 115 | } 116 | switch (nextCharacterGroup) { 117 | case NextCharacter.character: 118 | return State.character; 119 | case NextCharacter.lineFeed: 120 | return State.linefeed; 121 | case NextCharacter.space: 122 | return State.space; 123 | } 124 | } 125 | 126 | public doAtCharacter(): State { 127 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 128 | if (nextCharacterGroup === null) { 129 | return State.reachDocumentEnd; 130 | } 131 | switch (nextCharacterGroup) { 132 | case NextCharacter.differenceTypeCharacter: 133 | return State.decreaseCount; 134 | case NextCharacter.sameTypeCharacter: 135 | return State.character; 136 | case NextCharacter.lineFeed: 137 | return State.linefeed; 138 | case NextCharacter.space: 139 | return State.space; 140 | } 141 | } 142 | 143 | public doAtLinefeed(): State { 144 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 145 | if (nextCharacterGroup === null) { 146 | return State.reachDocumentEnd; 147 | } 148 | switch (nextCharacterGroup) { 149 | case NextCharacter.character: 150 | return State.decreaseCountAtLinefeed; 151 | case NextCharacter.lineFeed: 152 | return State.linefeed; 153 | case NextCharacter.space: 154 | return State.space; 155 | } 156 | } 157 | 158 | public doAtSpace(): State { 159 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 160 | if (nextCharacterGroup === null) { 161 | return State.reachDocumentEnd; 162 | } 163 | switch (nextCharacterGroup) { 164 | case NextCharacter.character: 165 | return State.decreaseCount; 166 | case NextCharacter.lineFeed: 167 | return State.linefeed; 168 | case NextCharacter.space: 169 | return State.space; 170 | } 171 | } 172 | 173 | public decreaseCount(): State { 174 | this.count--; 175 | if (this.count === 1) { 176 | return State.characterWhenCountEq1; 177 | } 178 | return State.character; 179 | } 180 | 181 | public decreaseCountAtLinefeed(): State { 182 | this.count--; 183 | if (this.count === 1) { 184 | return State.firstWhenCountEq1; 185 | } 186 | return State.linefeed; 187 | } 188 | 189 | public doAtFirstWhenCountEq1(): State { 190 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 191 | if (nextCharacterGroup === null) { 192 | return State.reachDocumentEnd; 193 | } 194 | switch (nextCharacterGroup) { 195 | case NextCharacter.character: 196 | return State.characterWhenCountEq1; 197 | case NextCharacter.lineFeed: 198 | return State.deleteUntilLinefeed; 199 | case NextCharacter.space: 200 | return State.spaceWhenCountEq1; 201 | } 202 | } 203 | 204 | public doAtCharacterWhenCountEq1(): State { 205 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 206 | if (nextCharacterGroup === null) { 207 | return State.reachDocumentEnd; 208 | } 209 | switch (nextCharacterGroup) { 210 | case NextCharacter.differenceTypeCharacter: 211 | case NextCharacter.lineFeed: 212 | return State.deleteUntilJustBefore; 213 | case NextCharacter.sameTypeCharacter: 214 | return State.characterWhenCountEq1; 215 | case NextCharacter.space: 216 | return State.spaceWhenCountEq1; 217 | } 218 | } 219 | 220 | public doAtSpaceWhenCountEq1(): State { 221 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 222 | if (nextCharacterGroup === null) { 223 | return State.reachDocumentEnd; 224 | } 225 | switch (nextCharacterGroup) { 226 | case NextCharacter.character: 227 | case NextCharacter.lineFeed: 228 | return State.deleteUntilJustBefore; 229 | case NextCharacter.space: 230 | return State.spaceWhenCountEq1; 231 | } 232 | } 233 | 234 | public CalculateEnd(): IPosition { 235 | let state: State = State.first; 236 | let whileContinue = true; 237 | while (whileContinue) { 238 | switch (state) { 239 | case State.reachDocumentEnd: 240 | whileContinue = false; 241 | break; 242 | case State.first: 243 | state = this.doAtFirst(); 244 | break; 245 | case State.firstWhenCountGreaterThan1: 246 | state = this.doAtFirstWhenCountGreaterThan1(); 247 | break; 248 | case State.character: 249 | state = this.doAtCharacter(); 250 | break; 251 | case State.linefeed: 252 | state = this.doAtLinefeed(); 253 | break; 254 | case State.space: 255 | state = this.doAtSpace(); 256 | break; 257 | case State.decreaseCount: 258 | state = this.decreaseCount(); 259 | break; 260 | case State.decreaseCountAtLinefeed: 261 | state = this.decreaseCountAtLinefeed(); 262 | break; 263 | case State.firstWhenCountEq1: 264 | state = this.doAtFirstWhenCountEq1(); 265 | break; 266 | case State.characterWhenCountEq1: 267 | state = this.doAtCharacterWhenCountEq1(); 268 | break; 269 | case State.spaceWhenCountEq1: 270 | state = this.doAtSpaceWhenCountEq1(); 271 | break; 272 | case State.deleteUntilJustBefore: 273 | case State.deleteUntilLinefeed: 274 | whileContinue = false; 275 | break; 276 | } 277 | } 278 | return this.pos; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /test/vim/LeftRightMotions.ts: -------------------------------------------------------------------------------- 1 | export let LeftRightMotions = {}; 2 | 3 | LeftRightMotions["h: left"] = { 4 | "h": { 5 | in: ["abc| def"], 6 | key: "h", 7 | out: ["ab|c def"], 8 | }, 9 | "3h": { 10 | in: ["abcdef|g"], 11 | key: "3h", 12 | out: ["abc|defg"], 13 | }, 14 | "ch": { 15 | in: ["abc| def"], 16 | key: "dh", 17 | out: ["ab| def"], 18 | }, 19 | "c3h": { 20 | in: ["abcdef|g"], 21 | key: "d3h", 22 | out: ["abc|g"], 23 | }, 24 | "vh": { 25 | in: ["abc| def"], 26 | key: "vhd", 27 | out: ["ab|def"], 28 | }, 29 | "v3h": { 30 | in: ["abcdef|gh"], 31 | key: "v3hd", 32 | out: ["abc|h"], 33 | }, 34 | }; 35 | LeftRightMotions["l: right"] = { 36 | "l": { 37 | in: ["abc| def"], 38 | key: "l", 39 | out: ["abc |def"], 40 | }, 41 | "3l": { 42 | in: ["a|bcdefg"], 43 | key: "3l", 44 | out: ["abcd|efg"], 45 | }, 46 | "cl": { 47 | in: ["abc| def"], 48 | key: "dl", 49 | out: ["abc|def"], 50 | }, 51 | "c3l": { 52 | in: ["a|bcdefg"], 53 | key: "d3l", 54 | out: ["a|efg"], 55 | }, 56 | "vl": { 57 | in: ["abc| def"], 58 | key: "vld", 59 | out: ["abc|ef"], 60 | }, 61 | "v3l": { 62 | in: ["a|bcdefg"], 63 | key: "v3ld", 64 | out: ["a|fg"], 65 | }, 66 | }; 67 | 68 | LeftRightMotions["f: to the Nth occurrence of {char} to the right"] = { 69 | "fx": { 70 | in: [ 71 | "ab|cdefghi", 72 | ], 73 | key: "ff", 74 | out: [ 75 | "abcde|fghi", 76 | ], 77 | }, 78 | "2fx": { 79 | in: [ 80 | "ab|cdefghifghi", 81 | ], 82 | key: "2ff", 83 | out: [ 84 | "abcdefghi|fghi", 85 | ], 86 | }, 87 | "dfx": { 88 | in: [ 89 | "ab|cdefghiabcdefghi", 90 | ], 91 | key: "dff", 92 | out: [ 93 | "ab|ghiabcdefghi", 94 | ], 95 | }, 96 | "d2fx": { 97 | in: [ 98 | "ab|cdefghiabcdefghi", 99 | ], 100 | key: "d2ff", 101 | out: [ 102 | "ab|ghi", 103 | ], 104 | }, 105 | "vfxd": { 106 | in: [ 107 | "ab|cdefghi", 108 | ], 109 | key: "vffd", 110 | out: [ 111 | "ab|ghi", 112 | ], 113 | }, 114 | "v2fxd": { 115 | in: [ 116 | "ab|cdefghiabcdefghi", 117 | ], 118 | key: "v2ffd", 119 | out: [ 120 | "ab|ghi", 121 | ], 122 | }, 123 | }; 124 | LeftRightMotions["F: to the Nth occurrence of {char} to the left"] = { 125 | "Fx": { 126 | in: [ 127 | "abcdefg|hi", 128 | ], 129 | key: "Fc", 130 | out: [ 131 | "ab|cdefghi", 132 | ], 133 | }, 134 | "2Fx": { 135 | in: [ 136 | "abcdefabcdefg|hi", 137 | ], 138 | key: "2Fc", 139 | out: [ 140 | "ab|cdefabcdefghi", 141 | ], 142 | }, 143 | "dFx": { 144 | in: [ 145 | "abcdefg|hi", 146 | ], 147 | key: "dFc", 148 | out: [ 149 | "ab|hi", 150 | ], 151 | }, 152 | "d2Fx": { 153 | in: [ 154 | "abcdefabcdefg|hi", 155 | ], 156 | key: "d2Fc", 157 | out: [ 158 | "ab|hi", 159 | ], 160 | }, 161 | "vFxd": { 162 | in: [ 163 | "abcdefg|hi", 164 | ], 165 | key: "vFcd", 166 | out: [ 167 | "ab|i", 168 | ], 169 | }, 170 | "v2Fxd": { 171 | in: [ 172 | "abcdefabcdefg|hi", 173 | ], 174 | key: "v2Fcd", 175 | out: [ 176 | "ab|i", 177 | ], 178 | }, 179 | }; 180 | LeftRightMotions["t: till before the Nth occurrence of {char} to the right"] = { 181 | "tx": { 182 | in: [ 183 | "ab|cdefghi", 184 | ], 185 | key: "tf", 186 | out: [ 187 | "abcd|efghi", 188 | ], 189 | }, 190 | "2tf": { 191 | in: [ 192 | "ab|cdefghiabcdefghi", 193 | ], 194 | key: "2tf", 195 | out: [ 196 | "abcdefghiabcd|efghi", 197 | ], 198 | }, 199 | "dtf": { 200 | in: [ 201 | "ab|cdefghiabcdefghi", 202 | ], 203 | key: "dtf", 204 | out: [ 205 | "ab|fghiabcdefghi", 206 | ], 207 | }, 208 | "d2tf": { 209 | in: [ 210 | "ab|cdefghiabcdefghi", 211 | ], 212 | key: "d2tf", 213 | out: [ 214 | "ab|fghi", 215 | ], 216 | }, 217 | "vtxd": { 218 | in: [ 219 | "ab|cdefghi", 220 | ], 221 | key: "vtfd", 222 | out: [ 223 | "ab|fghi", 224 | ], 225 | }, 226 | "v2tfd": { 227 | in: [ 228 | "ab|cdefghiabcdefghi", 229 | ], 230 | key: "v2tfd", 231 | out: [ 232 | "ab|fghi", 233 | ], 234 | }, 235 | }; 236 | LeftRightMotions["T: till before the Nth occurrence of {char} to the left"] = { 237 | "Tx": { 238 | in: [ 239 | "abcdefg|hi", 240 | ], 241 | key: "Tc", 242 | out: [ 243 | "abc|defghi", 244 | ], 245 | }, 246 | "2Tx": { 247 | in: [ 248 | "abcdefgabcdefg|hi", 249 | ], 250 | key: "2Tc", 251 | out: [ 252 | "abc|defgabcdefghi", 253 | ], 254 | }, 255 | "dTx": { 256 | in: [ 257 | "abcdefg|hi", 258 | ], 259 | key: "dTc", 260 | out: [ 261 | "abc|hi", 262 | ], 263 | }, 264 | "d2Tx": { 265 | in: [ 266 | "abcdefgabcdefg|hi", 267 | ], 268 | key: "d2Tc", 269 | out: [ 270 | "abc|hi", 271 | ], 272 | }, 273 | "vTxd": { 274 | in: [ 275 | "abcdefg|hi", 276 | ], 277 | key: "vTcd", 278 | out: [ 279 | "abc|i", 280 | ], 281 | }, 282 | "v2Txd": { 283 | in: [ 284 | "abcdefgabcdefg|hi", 285 | ], 286 | key: "v2Tcd", 287 | out: [ 288 | "abc|i", 289 | ], 290 | }, 291 | }; 292 | LeftRightMotions[";: repeat the last \"f\", \"F\", \"t\", or \"T\" N times"] = { 293 | "fx;": { 294 | in: [ 295 | "ab|cdefghiadbcdefghi", 296 | ], 297 | key: "ff;", 298 | out: [ 299 | "abcdefghiadbcde|fghi", 300 | ], 301 | }, 302 | "Fx;": { 303 | in: [ 304 | "abcdefghiabcdefg|hi", 305 | ], 306 | key: "Fc;", 307 | out: [ 308 | "ab|cdefghiabcdefghi", 309 | ], 310 | }, 311 | "tx;": { 312 | in: [ 313 | "ab|cdefghiabcdefghi", 314 | ], 315 | key: "tf;", 316 | out: [ 317 | "abcdefghiabcd|efghi", 318 | ], 319 | }, 320 | "Tx;": { 321 | in: [ 322 | "abcdefghiabcdefg|hi", 323 | ], 324 | key: "Tc;", 325 | out: [ 326 | "abc|defghiabcdefghi", 327 | ], 328 | }, 329 | "fx2;": { 330 | in: [ 331 | "ab|cdefghiadbcdefghiadbcdefghi", 332 | ], 333 | key: "ff2;", 334 | out: [ 335 | "abcdefghiadbcdefghiadbcde|fghi", 336 | ], 337 | }, 338 | "fxd;": { 339 | in: [ 340 | "ab|cdefghiadbcdefghi", 341 | ], 342 | key: "ffd;", 343 | out: [ 344 | "abcde|ghi", 345 | ], 346 | }, 347 | "fxd2;": { 348 | in: [ 349 | "ab|cdefghiadbcdefghiadbcdefghi", 350 | ], 351 | key: "ffd2;", 352 | out: [ 353 | "abcde|ghi", 354 | ], 355 | }, 356 | "fxv;d": { 357 | in: [ 358 | "ab|cdefghiadbcdefghi", 359 | ], 360 | key: "ffv;d", 361 | out: [ 362 | "abcde|ghi", 363 | ], 364 | }, 365 | "fxv2;d": { 366 | in: [ 367 | "ab|cdefghiadbcdefghiadbcdefghi", 368 | ], 369 | key: "ffv2;d", 370 | out: [ 371 | "abcde|ghi", 372 | ], 373 | }, 374 | }; 375 | LeftRightMotions[",: repeat the last \"f\", \"F\", \"t\", or \"T\" N times in opposite direction"] = { 376 | "fx,": { 377 | in: [ 378 | "abcdef|ghiabcdefghi", 379 | ], 380 | key: "fc,", 381 | out: [ 382 | "ab|cdefghiabcdefghi", 383 | ], 384 | }, 385 | "Fx,": { 386 | in: [ 387 | "abcdefghia|dbcdefghi", 388 | ], 389 | key: "Ff,", 390 | out: [ 391 | "abcdefghiadbcde|fghi", 392 | ], 393 | }, 394 | "tx,": { 395 | in: [ 396 | "abcdef|ghiabcdefghi", 397 | ], 398 | key: "tc,", 399 | out: [ 400 | "abc|defghiabcdefghi", 401 | ], 402 | }, 403 | "Tx,": { 404 | in: [ 405 | "abcdefghi|abcdefghi", 406 | ], 407 | key: "Tf,", 408 | out: [ 409 | "abcdefghiabcd|efghi", 410 | ], 411 | }, 412 | "fx2,": { 413 | in: [ 414 | "abcdefghabcdef|ghiabcdefghi", 415 | ], 416 | key: "fc2,", 417 | out: [ 418 | "ab|cdefghabcdefghiabcdefghi", 419 | ], 420 | }, 421 | "fxd,": { 422 | in: [ 423 | "abcdefghabcdef|ghiabcdefghi", 424 | ], 425 | key: "fcd,", 426 | out: [ 427 | "abcdefghab|cdefghi", 428 | ], 429 | }, 430 | "fxd2,": { 431 | in: [ 432 | "abcdefghabcdef|ghiabcdefghi", 433 | ], 434 | key: "fcd2,", 435 | out: [ 436 | "ab|cdefghi", 437 | ], 438 | }, 439 | "vfx,d": { 440 | in: [ 441 | "abcdefghabcdef|ghiabcdefghi", 442 | ], 443 | key: "vfc,d", 444 | out: [ 445 | "abcdefghab|hiabcdefghi", 446 | ], 447 | }, 448 | "vfx2,d": { 449 | in: [ 450 | "abcdefghabcdef|ghiabcdefghi", 451 | ], 452 | key: "vfc2,d", 453 | out: [ 454 | "ab|hiabcdefghi", 455 | ], 456 | }, 457 | }; 458 | -------------------------------------------------------------------------------- /src/motion/DeleteEndOfWordMotion.ts: -------------------------------------------------------------------------------- 1 | import { GoAction } from "../action/GoAction"; 2 | import * as Utils from "../Utils"; 3 | import { Position } from "../VimStyle"; 4 | import { AbstractMotion } from "./AbstractMotion"; 5 | 6 | /** 7 | * e ce de E cE dE 8 | * please see wordMotionStateModel/deleteEndOfWord.png 9 | */ 10 | export class DeleteEndOfWordMotion extends AbstractMotion { 11 | 12 | public IsWORD: boolean; 13 | public IsMove: boolean; 14 | 15 | constructor() { 16 | super(); 17 | }; 18 | 19 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 20 | let cal = new Calculater(start, this.Count, this.IsWORD, this.IsMove, editor); 21 | return cal.CalculateEnd(); 22 | } 23 | } 24 | 25 | enum State { 26 | first = 1, 27 | firstCharacter, 28 | character, 29 | spaceOrLinefeed, 30 | decreaseCount, 31 | finalWord, 32 | stop, 33 | deleteUntilJustBefore, 34 | moveToPreviousCharacter, 35 | reachDocumentEnd, 36 | } 37 | 38 | enum NextCharacter { 39 | character, 40 | sameTypeCharacter, 41 | differenceTypeCharacter, 42 | lineFeed, 43 | space, 44 | } 45 | 46 | class Calculater { 47 | public pos: IPosition; 48 | public line: string; 49 | public documentLines: number; 50 | public count: number; 51 | public IsWORD: boolean; 52 | public IsMove: boolean; 53 | 54 | public beforeCharacterGroup: CharGroup; 55 | 56 | public editor: IEditor; 57 | 58 | constructor(start: IPosition, count: number, isWord: boolean, isMove: boolean, editor: IEditor) { 59 | this.pos = start.Copy(); 60 | this.pos.Char--; 61 | this.line = editor.ReadLine(start.Line); 62 | this.editor = editor; 63 | this.documentLines = editor.GetLastLineNum(); 64 | this.count = count; 65 | this.beforeCharacterGroup = null; 66 | this.IsWORD = isWord; 67 | this.IsMove = isMove; 68 | }; 69 | 70 | public getNextCharacter(): NextCharacter { 71 | this.pos.Char++; 72 | if (this.pos.Char > this.line.length) { 73 | this.pos.Line++; 74 | this.pos.Char = 0; 75 | this.line = this.editor.ReadLine(this.pos.Line); 76 | } 77 | if (this.pos.Char === this.line.length) { 78 | if (this.pos.Line === this.documentLines) { 79 | return null; 80 | } 81 | this.beforeCharacterGroup = null; 82 | return NextCharacter.lineFeed; 83 | } 84 | let characterGroup: CharGroup = Utils.GetCharClass(this.line.charCodeAt(this.pos.Char)); 85 | let result: NextCharacter; 86 | if (characterGroup === CharGroup.Spaces) { 87 | this.beforeCharacterGroup = null; 88 | return NextCharacter.space; 89 | } 90 | if (this.beforeCharacterGroup === null) { 91 | this.beforeCharacterGroup = characterGroup; 92 | return NextCharacter.character; 93 | } 94 | if (this.IsWORD) { 95 | this.beforeCharacterGroup = characterGroup; 96 | return NextCharacter.sameTypeCharacter; 97 | } 98 | if (this.beforeCharacterGroup === characterGroup) { 99 | return NextCharacter.sameTypeCharacter; 100 | } 101 | this.beforeCharacterGroup = characterGroup; 102 | return NextCharacter.differenceTypeCharacter; 103 | } 104 | 105 | public doAtFirst(): State { 106 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 107 | if (nextCharacterGroup === null) { 108 | return State.reachDocumentEnd; 109 | } 110 | switch (nextCharacterGroup) { 111 | case NextCharacter.character: 112 | return State.firstCharacter; 113 | case NextCharacter.lineFeed: 114 | case NextCharacter.space: 115 | return State.spaceOrLinefeed; 116 | } 117 | }; 118 | 119 | public doAtFirstCharacter(): State { 120 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 121 | if (nextCharacterGroup === null) { 122 | return State.reachDocumentEnd; 123 | } 124 | switch (nextCharacterGroup) { 125 | case NextCharacter.sameTypeCharacter: 126 | case NextCharacter.differenceTypeCharacter: 127 | return State.decreaseCount; 128 | case NextCharacter.lineFeed: 129 | case NextCharacter.space: 130 | return State.spaceOrLinefeed; 131 | } 132 | } 133 | 134 | public doAtCharacter(): State { 135 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 136 | if (nextCharacterGroup === null) { 137 | return State.reachDocumentEnd; 138 | } 139 | switch (nextCharacterGroup) { 140 | case NextCharacter.differenceTypeCharacter: 141 | return State.decreaseCount; 142 | case NextCharacter.sameTypeCharacter: 143 | return State.character; 144 | case NextCharacter.lineFeed: 145 | case NextCharacter.space: 146 | return State.spaceOrLinefeed; 147 | } 148 | } 149 | 150 | public doAtSpaceOrLinefeed(): State { 151 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 152 | if (nextCharacterGroup === null) { 153 | return State.reachDocumentEnd; 154 | } 155 | switch (nextCharacterGroup) { 156 | case NextCharacter.character: 157 | return State.decreaseCount; 158 | case NextCharacter.lineFeed: 159 | case NextCharacter.space: 160 | return State.spaceOrLinefeed; 161 | } 162 | } 163 | 164 | public decreaseCount(): State { 165 | this.count--; 166 | if (this.count === 0) { 167 | return State.finalWord; 168 | } 169 | return State.character; 170 | } 171 | 172 | public doAtFinalWord(): State { 173 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 174 | if (nextCharacterGroup === null) { 175 | return State.reachDocumentEnd; 176 | } 177 | switch (nextCharacterGroup) { 178 | case NextCharacter.sameTypeCharacter: 179 | return State.finalWord; 180 | case NextCharacter.differenceTypeCharacter: 181 | case NextCharacter.lineFeed: 182 | case NextCharacter.space: 183 | return State.stop; 184 | } 185 | } 186 | 187 | public doAtStoppedPos(): State { 188 | if (this.IsMove) { 189 | return State.moveToPreviousCharacter; 190 | } 191 | return State.deleteUntilJustBefore; 192 | } 193 | 194 | public moveToPreviousCharacter() { 195 | if (this.pos.Char > 0) { 196 | this.pos.Char--; 197 | } 198 | } 199 | 200 | public CalculateEnd(): IPosition { 201 | let state: State = State.first; 202 | let whileContinue = true; 203 | while (whileContinue) { 204 | switch (state) { 205 | case State.reachDocumentEnd: 206 | whileContinue = false; 207 | break; 208 | case State.first: 209 | state = this.doAtFirst(); 210 | break; 211 | case State.firstCharacter: 212 | state = this.doAtFirstCharacter(); 213 | break; 214 | case State.character: 215 | state = this.doAtCharacter(); 216 | break; 217 | case State.spaceOrLinefeed: 218 | state = this.doAtSpaceOrLinefeed(); 219 | break; 220 | case State.decreaseCount: 221 | state = this.decreaseCount(); 222 | break; 223 | case State.finalWord: 224 | state = this.doAtFinalWord(); 225 | break; 226 | case State.stop: 227 | state = this.doAtStoppedPos(); 228 | break; 229 | case State.deleteUntilJustBefore: 230 | whileContinue = false; 231 | break; 232 | case State.moveToPreviousCharacter: 233 | this.moveToPreviousCharacter(); 234 | whileContinue = false; 235 | break; 236 | } 237 | } 238 | return this.pos; 239 | } 240 | } 241 | 242 | /** 243 | * Ne 244 | */ 245 | export function GotoForwardToEndOfWold(num: number): IAction { 246 | let a = new GoAction(); 247 | let m = new DeleteEndOfWordMotion(); 248 | m.IsWORD = false; 249 | m.IsMove = true; 250 | m.Count = num > 0 ? num : 1; 251 | a.Motion = m; 252 | return a; 253 | } 254 | 255 | /** 256 | * cNe 257 | */ 258 | export function AddEndOfWordMotion(num: number, action: IAction) { 259 | let m = new DeleteEndOfWordMotion(); 260 | m.IsWORD = false; 261 | m.IsMove = false; 262 | m.Count = num > 0 ? num : 1; 263 | let a = action; 264 | a.Motion = m; 265 | } 266 | 267 | /** 268 | * vNe 269 | */ 270 | export function AddMoveToForwardToEndOfWoldMotion(num: number, action: IAction) { 271 | let a = action; 272 | let m = new DeleteEndOfWordMotion(); 273 | m.IsWORD = false; 274 | m.IsMove = true; 275 | m.Count = num > 0 ? num : 1; 276 | a.Motion = m; 277 | } 278 | 279 | /** 280 | * NE 281 | */ 282 | export function GotoForwardToEndOfBlankSeparated(num: number): IAction { 283 | let a = new GoAction(); 284 | let m = new DeleteEndOfWordMotion(); 285 | m.IsWORD = true; 286 | m.IsMove = true; 287 | m.Count = num > 0 ? num : 1; 288 | a.Motion = m; 289 | return a; 290 | } 291 | 292 | /** 293 | * cWE 294 | */ 295 | export function AddEndOfBlankSeparatedMotion(num: number, action: IAction) { 296 | let m = new DeleteEndOfWordMotion(); 297 | m.IsWORD = true; 298 | m.IsMove = false; 299 | m.Count = num > 0 ? num : 1; 300 | let a = action; 301 | a.Motion = m; 302 | } 303 | 304 | /** 305 | * vNE 306 | */ 307 | export function AddMoveToForwardToEndOfBlankSeparatedMotion(num: number, action: IAction) { 308 | let a = action; 309 | let m = new DeleteEndOfWordMotion(); 310 | m.IsWORD = true; 311 | m.IsMove = true; 312 | m.Count = num > 0 ? num : 1; 313 | a.Motion = m; 314 | } 315 | -------------------------------------------------------------------------------- /src/motion/ChangeWordMotion.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "../Utils"; 2 | import { Position } from "../VimStyle"; 3 | import { AbstractMotion } from "./AbstractMotion"; 4 | import { DeleteWordMotion } from "./DeleteWordMotion"; 5 | 6 | /** 7 | * cw 8 | * please see wordMotionStateModel/changeWord.png 9 | */ 10 | export class ChangeWordMotion extends AbstractMotion { 11 | 12 | public IsWORD: boolean; 13 | 14 | constructor() { 15 | super(); 16 | }; 17 | 18 | public CalculateEnd(editor: IEditor, vim: IVimStyle, start: IPosition): IPosition { 19 | let cal = new Calculater(start, this.Count, this.IsWORD, editor); 20 | return cal.CalculateEnd(); 21 | } 22 | } 23 | 24 | enum State { 25 | first = 1, 26 | firstWhenCountGreaterThan1, 27 | character, 28 | space, 29 | linefeed, 30 | decreaseCount, 31 | decreaseCountAtLinefeed, 32 | characterWhenCountEq1, 33 | firstWhenCountEq1, 34 | spaceWhenCountEq1, 35 | deleteUntilJustBefore, 36 | reachDocumentEnd, 37 | } 38 | 39 | enum NextCharacter { 40 | character, 41 | sameTypeCharacter, 42 | differenceTypeCharacter, 43 | lineFeed, 44 | space, 45 | } 46 | 47 | class Calculater { 48 | public pos: IPosition; 49 | public line: string; 50 | public documentLines: number; 51 | public count: number; 52 | public IsWORD: boolean; 53 | 54 | public beforeCharacterGroup: CharGroup; 55 | 56 | public editor: IEditor; 57 | 58 | constructor(start: IPosition, count: number, isWord: boolean, editor: IEditor) { 59 | this.pos = start.Copy(); 60 | this.pos.Char--; 61 | this.line = editor.ReadLine(start.Line); 62 | this.editor = editor; 63 | this.documentLines = editor.GetLastLineNum(); 64 | this.count = count; 65 | this.beforeCharacterGroup = null; 66 | this.IsWORD = isWord; 67 | }; 68 | 69 | public getNextCharacter(): NextCharacter { 70 | this.pos.Char++; 71 | if (this.pos.Char > this.line.length) { 72 | this.pos.Line++; 73 | this.pos.Char = 0; 74 | this.line = this.editor.ReadLine(this.pos.Line); 75 | } 76 | if (this.pos.Char === this.line.length) { 77 | if (this.pos.Line === this.documentLines) { 78 | return null; 79 | } 80 | this.beforeCharacterGroup = null; 81 | return NextCharacter.lineFeed; 82 | } 83 | let characterGroup: CharGroup = Utils.GetCharClass(this.line.charCodeAt(this.pos.Char)); 84 | let result: NextCharacter; 85 | if (characterGroup === CharGroup.Spaces) { 86 | this.beforeCharacterGroup = null; 87 | return NextCharacter.space; 88 | } 89 | if (this.beforeCharacterGroup === null) { 90 | this.beforeCharacterGroup = characterGroup; 91 | return NextCharacter.character; 92 | } 93 | if (this.IsWORD) { 94 | this.beforeCharacterGroup = characterGroup; 95 | return NextCharacter.sameTypeCharacter; 96 | } 97 | if (this.beforeCharacterGroup === characterGroup) { 98 | return NextCharacter.sameTypeCharacter; 99 | } 100 | this.beforeCharacterGroup = characterGroup; 101 | return NextCharacter.differenceTypeCharacter; 102 | } 103 | 104 | public doAtFirst(): State { 105 | if (this.count === 1) { 106 | return State.firstWhenCountEq1; 107 | } 108 | return State.firstWhenCountGreaterThan1; 109 | }; 110 | 111 | public doAtFirstWhenCountGreaterThan1(): State { 112 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 113 | if (nextCharacterGroup === null) { 114 | return State.reachDocumentEnd; 115 | } 116 | switch (nextCharacterGroup) { 117 | case NextCharacter.character: 118 | return State.character; 119 | case NextCharacter.lineFeed: 120 | return State.linefeed; 121 | case NextCharacter.space: 122 | return State.space; 123 | } 124 | } 125 | 126 | public doAtCharacter(): State { 127 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 128 | if (nextCharacterGroup === null) { 129 | return State.reachDocumentEnd; 130 | } 131 | switch (nextCharacterGroup) { 132 | case NextCharacter.differenceTypeCharacter: 133 | return State.decreaseCount; 134 | case NextCharacter.sameTypeCharacter: 135 | return State.character; 136 | case NextCharacter.lineFeed: 137 | return State.linefeed; 138 | case NextCharacter.space: 139 | return State.space; 140 | } 141 | } 142 | 143 | public doAtLinefeed(): State { 144 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 145 | if (nextCharacterGroup === null) { 146 | return State.reachDocumentEnd; 147 | } 148 | switch (nextCharacterGroup) { 149 | case NextCharacter.character: 150 | return State.decreaseCount; 151 | case NextCharacter.lineFeed: 152 | return State.decreaseCountAtLinefeed; 153 | case NextCharacter.space: 154 | return State.space; 155 | } 156 | } 157 | 158 | public doAtSpace(): State { 159 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 160 | if (nextCharacterGroup === null) { 161 | return State.reachDocumentEnd; 162 | } 163 | switch (nextCharacterGroup) { 164 | case NextCharacter.character: 165 | return State.decreaseCount; 166 | case NextCharacter.lineFeed: 167 | return State.linefeed; 168 | case NextCharacter.space: 169 | return State.space; 170 | } 171 | } 172 | 173 | public decreaseCount(): State { 174 | this.count--; 175 | if (this.count === 1) { 176 | return State.characterWhenCountEq1; 177 | } 178 | return State.character; 179 | } 180 | 181 | public decreaseCountAtLinefeed(): State { 182 | this.count--; 183 | if (this.count === 1) { 184 | return State.firstWhenCountEq1; 185 | } 186 | return State.firstWhenCountEq1; 187 | } 188 | 189 | public doAtFirstWhenCountEq1(): State { 190 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 191 | if (nextCharacterGroup === null) { 192 | return State.reachDocumentEnd; 193 | } 194 | switch (nextCharacterGroup) { 195 | case NextCharacter.character: 196 | return State.characterWhenCountEq1; 197 | case NextCharacter.lineFeed: 198 | return State.deleteUntilJustBefore; 199 | case NextCharacter.space: 200 | return State.spaceWhenCountEq1; 201 | } 202 | } 203 | 204 | public doAtCharacterWhenCountEq1(): State { 205 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 206 | if (nextCharacterGroup === null) { 207 | return State.reachDocumentEnd; 208 | } 209 | switch (nextCharacterGroup) { 210 | case NextCharacter.differenceTypeCharacter: 211 | case NextCharacter.lineFeed: 212 | case NextCharacter.space: 213 | return State.deleteUntilJustBefore; 214 | case NextCharacter.sameTypeCharacter: 215 | return State.characterWhenCountEq1; 216 | } 217 | } 218 | 219 | public doAtSpaceWhenCountEq1(): State { 220 | let nextCharacterGroup: NextCharacter = this.getNextCharacter(); 221 | if (nextCharacterGroup === null) { 222 | return State.reachDocumentEnd; 223 | } 224 | switch (nextCharacterGroup) { 225 | case NextCharacter.character: 226 | case NextCharacter.lineFeed: 227 | return State.deleteUntilJustBefore; 228 | case NextCharacter.space: 229 | return State.spaceWhenCountEq1; 230 | } 231 | } 232 | 233 | public CalculateEnd(): IPosition { 234 | let state: State = State.first; 235 | let whileContinue = true; 236 | while (whileContinue) { 237 | switch (state) { 238 | case State.reachDocumentEnd: 239 | whileContinue = false; 240 | break; 241 | case State.first: 242 | state = this.doAtFirst(); 243 | break; 244 | case State.firstWhenCountGreaterThan1: 245 | state = this.doAtFirstWhenCountGreaterThan1(); 246 | break; 247 | case State.character: 248 | state = this.doAtCharacter(); 249 | break; 250 | case State.linefeed: 251 | state = this.doAtLinefeed(); 252 | break; 253 | case State.space: 254 | state = this.doAtSpace(); 255 | break; 256 | case State.decreaseCount: 257 | state = this.decreaseCount(); 258 | break; 259 | case State.decreaseCountAtLinefeed: 260 | state = this.decreaseCountAtLinefeed(); 261 | break; 262 | case State.firstWhenCountEq1: 263 | state = this.doAtFirstWhenCountEq1(); 264 | break; 265 | case State.characterWhenCountEq1: 266 | state = this.doAtCharacterWhenCountEq1(); 267 | break; 268 | case State.spaceWhenCountEq1: 269 | state = this.doAtSpaceWhenCountEq1(); 270 | break; 271 | case State.deleteUntilJustBefore: 272 | whileContinue = false; 273 | break; 274 | } 275 | } 276 | return this.pos; 277 | } 278 | } 279 | 280 | /** 281 | * cNw dNw 282 | */ 283 | export function AddWordForwordMotion(num: number, action: IAction) { 284 | let a = action; 285 | if (a.IsChange) { 286 | let cm = new ChangeWordMotion(); 287 | cm.IsWORD = false; 288 | cm.Count = num > 0 ? num : 1; 289 | a.Motion = cm; 290 | } else { 291 | let dm = new DeleteWordMotion(); 292 | dm.Count = num > 0 ? num : 1; 293 | dm.IsWORD = false; 294 | a.Motion = dm; 295 | } 296 | } 297 | 298 | /** 299 | * cNW dNW 300 | */ 301 | export function AddBlankSparatedMotion(num: number, action: IAction) { 302 | let a = action; 303 | if (a.IsChange) { 304 | let cm = new ChangeWordMotion(); 305 | cm.IsWORD = true; 306 | cm.Count = num > 0 ? num : 1; 307 | a.Motion = cm; 308 | } else { 309 | let dm = new DeleteWordMotion(); 310 | dm.Count = num > 0 ? num : 1; 311 | dm.IsWORD = true; 312 | a.Motion = dm; 313 | } 314 | } 315 | --------------------------------------------------------------------------------