├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── ROADMAP.md ├── STYLE.md ├── designdoc.md ├── extension.ts ├── gulpfile.js ├── images ├── design │ ├── vscodevim-logo.png │ └── vscodevim-logo.svg └── icon.png ├── package-lock.json ├── package.json ├── scripts ├── create_changelog.py └── keybind_gen.py ├── src ├── common │ └── motion │ │ ├── position.ts │ │ └── range.ts ├── configuration │ └── configuration.ts ├── globals.ts ├── textEditor.ts └── util.ts ├── srcNV ├── nvUtil.ts ├── rpcHandlers.ts ├── screen.ts ├── vimSettings.ts └── vscHandlers.ts ├── tsconfig.json ├── tsd.json ├── tslint.json ├── typings.json └── typings └── custom └── promised-neovim-client.d.ts /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guide 2 | 3 | The following is a set of guidelines for contributing to Vim for VSCode. 4 | These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. 5 | If you need help with Vim for VSCode, drop by on [Slack](https://vscodevim-slackin.azurewebsites.net/). 6 | Thanks for helping us make Vim for VSCode better! 7 | 8 | ## Submitting Issues 9 | 10 | The [GitHub issue tracker](https://github.com/VSCodeVim/Vim/issues) is the preferred channel for tracking bugs and enhancement suggestions. 11 | When creating a new bug report do: 12 | 13 | * Search against existing issues to check if somebody else has already reported your problem or requested your idea 14 | * Include as many details as possible -- include screenshots/gifs and repro steps where applicable. 15 | 16 | ## Submitting Pull Requests 17 | 18 | Pull requests are *awesome*. 19 | If you're looking to raise a PR for something which doesn't have an open issue, consider creating an issue first. 20 | When submitting a PR, ensure: 21 | 22 | 1. All tests pass. 23 | 2. If you added a new feature, add tests to exercise the new code path. 24 | 3. If you fixed a bug, add tests to ensure the bug stays away. 25 | 4. Submit the PR. Pour yourself a glass of champagne and feel good about contributing to open source! 26 | 27 | ## First Time Setup 28 | 29 | 1. Install prerequisites: 30 | * latest [Visual Studio Code](https://code.visualstudio.com/) 31 | * [Node.js](https://nodejs.org/) v4.0.0 or higher 32 | 2. Fork and clone the repository 33 | 3. `cd Vim` 34 | 4. Install the dependencies: 35 | 36 | ```bash 37 | $ npm install -g gulp-cli 38 | $ npm install 39 | ``` 40 | 5. Open the folder in VS Code 41 | 42 | ## Developing 43 | 44 | 1. Watch for changes and recompile Typescript files. Run this in the `Vim` directory: `gulp watch` 45 | 2. Open Visual Studio Code and add the `Vim` directory as a folder. 46 | 3. Click on the debugger. You have two options - Launch Extension (to play around with the extension) and Launch Tests (to run the tests). 47 | 48 | ## Code Architecture 49 | 50 | The code is split into two parts - ModeHandler (which is essentially the Vim state machine), and Actions (which are things that modify the state). 51 | 52 | ### Actions 53 | 54 | Actions are all currently stuffed into actions.ts (sorry!). There are: 55 | * BaseAction - the base Action type that all Actions derive from. 56 | * BaseMovement - A movement, like `w`, `h`, `{`, etc. ONLY updates the cursor position. At worst, might return an IMovement, which indicates a start and stop. This is used for movements like aw which may actually start before the cursor. 57 | * BaseCommand - Anything which is not just a movement is a Command. That includes motions which also update the state of Vim in some way, like `*`. 58 | 59 | At one point, I wanted to have actions.ts be completely pure (no side effects whatsoever), so commands would just return objects indicating what side effects on the editor they would have. This explains the giant switch in handleCommand in ModeHandler. I now believe this to be a dumb idea and someone should get rid of it. 60 | 61 | Probably me. :wink: 62 | 63 | ### The Vim State Machine 64 | 65 | It's contained entirely within modeHandler.ts. It's actually pretty complicated, and I probably won't be able to articulate all of the edge cases it contains. 66 | 67 | It consists of two data structures: 68 | 69 | * VimState - this is the state of Vim. It's what actions update. 70 | * RecordedState - this is temporary state that will reset at the end of a change. (RecordedState is a poor name for this; I've been going back and forth on different names). 71 | 72 | #### How it works 73 | 74 | 1. `handleKeyEventHelper` is called with the most recent keypress. 75 | 2. `Actions.getRelevantAction` determines if all the keys pressed so far uniquely specify any action in actions.ts. If not, we continue waiting for keypresses. 76 | 3. `runAction` runs the action that was matched. Movements, Commands and Operators all have separate functions that dictate how to run them - `executeMovement`, `handleCommand`, and `executeOperator` respectively. 77 | 4. Now that we've updated VimState, we run `updateView` with the new VimState to "redraw" VSCode to the new state. 78 | 79 | #### vscode.window.onDidChangeTextEditorSelection 80 | 81 | This is my hack to simulate a click event based API in an IDE that doesn't have them (yet?). I check the selection that just came in to see if it's the same as what I thought I previously set the selection to the last time the state machine updated. If it's not, the user *probably* clicked. (But she also could have tab completed!) 82 | 83 | ## Release 84 | 85 | To push a release: 86 | 87 | 1. Bump the version number and create a git tag: `gulp patch|minor|major` 88 | 2. Push the changes: `git push origin --tags` 89 | 90 | In addition to building and testing the extension, when a tag is applied to the commit, the CI server will also create a GitHub release and publish the new version to the Visual Studio marketplace. 91 | 92 | ## Troubleshooting 93 | 94 | ### Visual Studio Code Slowdown 95 | 96 | If your autocomplete, your fuzzy file search, or your _everything_ is suddenly running slower, try to recall if you ever ran `npm test` instead of just running tests through Visual Studio Code. This will add a massive folder called `.vscode-test/` to your project, which Visual Studio Code will happily consume all of your CPU cycles indexing. 97 | 98 | Long story short, you can speed up VS Code by: 99 | 100 | `$ rm -rf .vscode-test/` 101 | 102 | ## Styleguide 103 | 104 | Please try your best to adhere our [style guidelines](https://github.com/VSCodeVim/Vim/blob/master/STYLE.md). -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | * Click *thumbs-up* 👍 on this issue if you want it! 7 | * Click *confused* 😕 on this issue if not having it makes VSCodeVim unusable. 8 | 9 | The VSCodeVim team prioritizes issues based on reaction count. 10 | 11 | -------- 12 | 13 | **Is this a BUG REPORT or FEATURE REQUEST?** (choose one): 14 | 15 | 27 | 28 | **Environment**: 29 | 30 | 33 | 34 | - **VSCode Version**: 35 | - **VsCodeVim Version**: 36 | - **OS**: 37 | 38 | **What happened**: 39 | 40 | 43 | 44 | **What did you expect to happen**: 45 | 46 | **How to reproduce it**: 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | testing 3 | node_modules 4 | *.sw? 5 | .vscode-test 6 | .DS_Store 7 | *.log 8 | 9 | typings/* 10 | !typings/custom/ 11 | testing 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | 4 | sudo: false 5 | 6 | os: 7 | - linux 8 | 9 | language: node_js 10 | 11 | node_js: 12 | - 6.3.1 13 | 14 | env: 15 | global: 16 | secure: "tzpL/lQ2L2mVDS0sPY0LnJ3idspKmiOAuKFFfBZbBk4vA6NtJYveXILiskNDWK4p3JIWux/JBxgU3auXU4t7BZy7bMfUpTm0PpsMQfvDV2/rsf3QUQgYvROZkVpeH6b73hxxyTy0wfDHrb0SIUjB9IChUiSDIBjwteVAb/HuIWNKRQ6mbmkZKiQ3xHisFOG9xTkrEO31BfA+nKxUOTtXiZtoHTOWK9+H67QKd19BTRn/vJrwhTUsYqyOBAoFQjDjpAbhRNgs4YxHJz2jn1rmeE8kotf3LsfBrws07Mu9O9CnEcJKsqgJW+ezbHCO/HjLfpul/v0HtF/UQM00v5J2mDFz/ii8OWQ41TjYgkAHhtXIkMDQiM6K1x+gsaq0wm4QDX+Akg3r4sFqzhKsIuSLr1QTAMh9nn9G2asQvVJNNgH1QFvuLMF3bX1xp4l79/xoCEAWqlxx1mScYcmGFtYtQd107U5qPcHrR66vTMV5VqiZ0XapJympE+D5xIiSb4CTFGpl9+PSBk69MjynMEbGsLsudYWp6HZA5CyosxoStXxR1fD+ypqsSyzynSpThZ6IpFJ7Pk95GR3Z78CPhkNZvBvJ62xW/6/hAykWcelRHZy0N5XT2+HP+xcx77Fpqj8ZEAI6ECHNnnZ1KQROodu5LI06oZ2hiM1P1gdBx6xrljg=" 17 | 18 | before_install: 19 | - eval "$(curl -Ss https://raw.githubusercontent.com/neovim/bot-ci/master/scripts/travis-setup.sh) nightly-x64" 20 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 21 | export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; 22 | sh -e /etc/init.d/xvfb start; 23 | sleep 3; 24 | fi 25 | 26 | install: 27 | - npm install -g gulp; 28 | - npm install; 29 | 30 | script: 31 | - gulp 32 | - npm test --silent; 33 | 34 | before_deploy: 35 | - npm install -g vsce; 36 | - vsce package; 37 | 38 | deploy: 39 | - provider: releases 40 | api_key: 41 | secure: YQLI+5JqIZj4XNrVO6EUrahICsfQVZ64wWmu8mMOj2rOaEid3dIL1Q69YbzQWIbcLt3y4oBGeefrakWdr/k/xgrnrkoEj3XnzgIp2E+e7rJvwnH4tM6tV191iq4wXdTo6vf0Ha/Bo6LpibP/LgPOXOY64/qZ0HtS1zqAxQLz+9b1O2knGnAQnaE0sqf0xQwZBl5kd/ChRV/+IIJJb5p3Pok9idbTW54O9BRb2ue720cvOR3afyIau2njVB8INgvNU9b2cYqg6RLAUOj2RaenuaH7GLI2dIYj9a8wgJyxliNTMGNFRhz+KmXie3tUZDuRytqxIq2SD7FHl744+F4L2YQyMiMMs4bF0jLkeDVwHExQsrdXlQ1pW2GLR7YQQpcr202jBkq+CNC7DBNyQlXwe/21M5kxYMTEJMqTMdPDfPgmhshf6VWqTSDczLDxue72m+gWxwv6kseFSQxwvRXly8hW0ev2rddMt9nu73hAZ+Lkmu16qArQ8HxAQUrBASaDYa0RAqJ3bNj6RwKFsa9/HK27XRrUY5GViMSQSeaEv3EO9Nc0ibMbFFL8EEBB9wXDS75jmlAioQcl1B1rzcKbSW37wSOJH1WUJCpM0+xdQVo8Gngld0LAyugOikir+mcnnEkGCeo2ZdeMEy63iqhCKwz2au4+XbEttBgfhd/una4= 42 | file_glob: true 43 | file: "*.vsix" 44 | skip_cleanup: true 45 | on: 46 | repo: VSCodeVim/Vim 47 | tags: true 48 | condition: $TRAVIS_OS_NAME = linux 49 | - provider: script 50 | script: vsce publish -p $VS_TOKEN 51 | skip_cleanup: true 52 | on: 53 | repo: VSCodeVim/Vim 54 | tags: true 55 | condition: $TRAVIS_OS_NAME = linux 56 | -------------------------------------------------------------------------------- /.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": "Build, Run Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/{out, node_modules}/**/*.js" ], 14 | "preLaunchTask": "build", 15 | "internalConsoleOptions": "openOnSessionStart" 16 | }, 17 | { 18 | "name": "Run Extension", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceRoot}" 24 | ], 25 | "stopOnEntry": false, 26 | "sourceMaps": true, 27 | "outFiles": [ "${workspaceRoot}/{out, node_modules}/**/*.js" ] 28 | }, 29 | { 30 | "name": "Run Tests", 31 | "type": "extensionHost", 32 | "request": "launch", 33 | "runtimeExecutable": "${execPath}", 34 | "args": [ 35 | "--extensionDevelopmentPath=${workspaceRoot}", 36 | "--extensionTestsPath=${workspaceRoot}/out/test" 37 | ], 38 | "stopOnEntry": false, 39 | "sourceMaps": true, 40 | "outFiles": [ "${workspaceRoot}/{out, node_modules}/**/*.js" ], 41 | "preLaunchTask": "build", 42 | "internalConsoleOptions": "openOnSessionStart" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 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", // we want to use the TS server from our node_modules folder to control its version 10 | "editor.tabSize": 2, 11 | "editor.insertSpaces": true, 12 | "editor.formatOnSave": true 13 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | { 10 | "version": "0.1.0", 11 | "command": "gulp", 12 | "isShellCommand": true, 13 | "suppressTaskName": true, 14 | "tasks": [ 15 | { 16 | "taskName": "build", 17 | "args": [], 18 | "isBuildCommand": true, 19 | "isWatching": false, 20 | "problemMatcher": "$tsc-watch" 21 | }, 22 | 23 | { 24 | "taskName": "yolo", 25 | "args": ["nothing"], 26 | "isBuildCommand": true, 27 | "isWatching": false, 28 | "problemMatcher": "$tsc-watch" 29 | } 30 | 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github/** 2 | .vscode/** 3 | .vscode-test/** 4 | typings/** 5 | out/test/** 6 | test/** 7 | **/*.ts 8 | **/*.map 9 | .gitignore 10 | tsconfig.json 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Standards 4 | Be nice. Please. Everybody contributing to open source contributes out of good will in their own free time. 5 | 6 | ## Our Responsibilities 7 | 8 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 9 | 10 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 VSCode-Extension 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |


VSCodeNeoVim

2 |

Vim emulation for Visual Studio Code.

3 | 4 | 5 |
6 | 7 | VSCodeNeoVim is a [Visual Studio Code](https://code.visualstudio.com/) rewrite of VSCodeVim, changing everything to be backed by Neovim. 8 | 9 | This is the temporary repo for VSCodeNeovim for development purposes. Please submit PRs/issues here. This extension will not be released on the marketplace, but I will be providing a .vsix for testing purposes. 10 | 11 | Some notes on contributing: 12 | * Much of the VSCodeVim code is still in here. The only places it's used are things like some utility Position functions, etc. 13 | * Most of the "hard" work is in extension.ts. 14 | * The rest of the work is in srcNV. NvUtil contains a lot of utility functions with working with neovim rpc requests. RPCHandlers contain handlers for RPC requests. 15 | * You need to have the newest nvim (installed from nightly/master) installed. At the very least, you need a version of neovim that has this issue fixed: https://github.com/neovim/neovim/issues/6166 16 | * The easiest way to test is to have the neovim instance for VSCode be created by connecting to a pipe. In order to do so, set NVIM_LISTEN_ADDRESS equal to `/tmp/nvim`, open a neovim instance, and then open the extension. This allows you to see what's happening on the neovim side (any errors/prompts/etc.) This is how my typical setup for development looks: ![](https://i.imgur.com/gwck9Do.jpg). There's some code to do this automatically, but it doesn't check if `/tmp/nvim` is a socket, so it may still fail. Also, I think performance is slightly worse when you do this. 17 | 18 | 19 | Other helpful documentation links: 20 | https://vscodevim.slack.com/files/U3EUW86U9/F62R31A5V/Integrating_Neovim_into_VSCode "Design" doc 21 | https://neovim.io/doc/user/api.html#api-global Neovim RPC API documentation 22 | https://neovim.io/doc/user/ui.html Neovim remote UI documentation. We currently aren't using it but will likely use it at some point in the future. 23 | https://github.com/neovim/node-client The node client we're using 24 | https://github.com/lunixbochs/ActualVim Another neovim backed vim plugin (for sublime) 25 | 26 | 27 | Important discussions links: 28 | https://gitter.im/neovim/neovim For talking to the neovim devs 29 | https://gitter.im/neovim/node-client For talking to the neovim node-client devs 30 | https://vscodevim.slack.com/ For talking to the VSCodeVim devs 31 | 32 | Important Neovim PRs to follow: 33 | https://github.com/neovim/neovim/pull/5269 Text Diffs (so we don't have to sync the entire buffer each time) 34 | 35 | Some screenshots (kinda hard to show what's changed without at least a gif...): 36 | Wildmenu working! 37 | ![](https://i.imgur.com/7zMEd1G.jpg) 38 | Autocomplete working! 39 | ![](https://i.imgur.com/aQ7jQyY.jpg) 40 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ## Key 2 | 3 | :white_check_mark: - command done 4 | 5 | :white_check_mark: :star: - command done with VS Code specific customization 6 | 7 | :warning: - some variations of the command are not supported 8 | 9 | :running: - work in progress 10 | 11 | :arrow_down: - command is low priority; open an issue (or thumbs up the relevant issue) if you want to see it sooner 12 | 13 | :x: - command impossible with current VSCode API 14 | 15 | :1234: - command accepts numeric prefix 16 | 17 | ## Roadmap 18 | 19 | These are the big Vim features, put generally in the order in which we plan to implement them. 20 | 21 | Status | Command 22 | ---|-------- 23 | :white_check_mark: | Normal Mode 24 | :white_check_mark: | Insert Mode 25 | :white_check_mark: | Visual Mode 26 | :white_check_mark: | Visual Line Mode 27 | :white_check_mark: | Number Prefixes 28 | :white_check_mark: | . Operator 29 | :white_check_mark: | Searching with / and ? 30 | :white_check_mark: | Correct Undo/Redo 31 | :warning: | Command Remapping 32 | :warning: | Marks 33 | :white_check_mark: | Text Objects 34 | :white_check_mark: | Visual Block Mode 35 | :white_check_mark: | Replace Mode 36 | :white_check_mark: | Multiple Select Mode 37 | :warning: | Macros 38 | :warning: | Buffer/Window/Tab 39 | 40 | 41 | Now follows an exhaustive list of every known Vim command that we could find. 42 | 43 | ## Custom commands 44 | 45 | * `gh` - show the hover tooltip. 46 | * `gb` - add an additional cursor at the next place that matches `*`. 47 | 48 | ## Left-right motions 49 | 50 | Status | Command | Description 51 | ---|--------|------------ 52 | :white_check_mark: |:1234: h | left (also: CTRL-H, BS, or Left key) 53 | :white_check_mark: |:1234: l | right (also: Space or Right key) 54 | :white_check_mark: | 0 | to first character in the line (also: Home key) 55 | :white_check_mark: | ^ | to first non-blank character in the line 56 | :white_check_mark: |:1234: $ | to the last character in the line (N-1 lines lower) (also: End key) 57 | :white_check_mark: | g0 | to first character in screen line (differs from "0" when lines wrap) 58 | :white_check_mark: | g^ | to first non-blank character in screen line (differs from "^" when lines wrap) 59 | :white_check_mark: |:1234: g$ | to last character in screen line (differs from "$" when lines wrap) 60 | :white_check_mark: | gm | to middle of the screen line 61 | :white_check_mark: |:1234: \| | to column N (default: 1) 62 | :white_check_mark: |:1234: f{char} | to the Nth occurrence of {char} to the right 63 | :white_check_mark: |:1234: F{char} | to the Nth occurrence of {char} to the left 64 | :white_check_mark: |:1234: t{char} | till before the Nth occurrence of {char} to the right 65 | :white_check_mark: |:1234: T{char} | till before the Nth occurrence of {char} to the left 66 | :white_check_mark: |:1234: ; | repeat the last "f", "F", "t", or "T" N times 67 | :white_check_mark: |:1234: , | repeat the last "f", "F", "t", or "T" N times in opposite direction 68 | 69 | ## Up-down motions 70 | 71 | Status | Command | Description 72 | ---|--------|------------ 73 | :white_check_mark: | :1234: k | up N lines (also: CTRL-P and Up) 74 | :white_check_mark: | :1234: j | up N lidown N lines (also: CTRL-J, CTRL-N, NL, and Down) 75 | :white_check_mark: | :1234: - | up N lines, on the first non-blank character 76 | :white_check_mark: | :1234: + | down N lines, on the first non-blank character (also: CTRL-M and CR) 77 | :white_check_mark: | :1234: _ | down N-1 lines, on the first non-blank character 78 | :white_check_mark: | :1234: G | goto line N (default: last line), on the first non-blank character 79 | :white_check_mark: | :1234: gg | goto line N (default: first line), on the first non-blank character 80 | :white_check_mark: | :1234: % | goto line N percentage down in the file; N must be given, otherwise it is the |%| command 81 | :white_check_mark: | :1234: gk | up N screen lines (differs from "k" when line wraps) 82 | :white_check_mark: | :1234: gj | own N screen lines (differs from "j" when line wraps) 83 | 84 | ## Text object motions 85 | 86 | Status | Command | Description 87 | ---|--------|------------ 88 | :white_check_mark: | :1234: w | N words forward 89 | :white_check_mark: | :1234: W | N blank-separated |WORD|s forward 90 | :white_check_mark: | :1234: e | N words forward to the end of the Nth word 91 | :white_check_mark: | :1234: E | N words forward to the end of the Nth blank-separated |WORD| 92 | :white_check_mark: | :1234: b | N words backward 93 | :white_check_mark: | :1234: B | N blank-separated |WORD|s backward 94 | :white_check_mark: | :1234: ge | N words backward to the end of the Nth word 95 | :white_check_mark: | :1234: gE | N words backward to the end of the Nth blank-separated |WORD| 96 | :white_check_mark: | :1234: ) | N sentences forward 97 | :white_check_mark: | :1234: ( | N sentences backward 98 | :white_check_mark: | :1234: } | N paragraphs forward 99 | :white_check_mark: | :1234: { | N paragraphs backward 100 | :white_check_mark: | :1234: ]] | N sections forward, at start of section 101 | :white_check_mark: | :1234: [[ | N sections backward, at start of section 102 | :white_check_mark: | :1234: ][ | N sections forward, at end of section 103 | :white_check_mark: | :1234: [] | N sections backward, at end of section 104 | :white_check_mark: | :1234: [( | N times back to unclosed '(' 105 | :white_check_mark: | :1234: [{ | N times back to unclosed '{' 106 | :arrow_down: | :1234: [m | N times back to start of method (for Java) 107 | :arrow_down: | :1234: [M | N times back to end of method (for Java) 108 | :white_check_mark: | :1234: ]) | N times forward to unclosed ')' 109 | :white_check_mark: | :1234: ]} | N times forward to unclosed '}' 110 | :arrow_down: | :1234: ]m | N times forward to start of method (for Java) 111 | :arrow_down: | :1234: ]M | N times forward to end of method (for Java) 112 | :arrow_down: | :1234: [# | N times back to unclosed "#if" or "#else" 113 | :arrow_down: | :1234: ]# | N times forward to unclosed "#else" or "#endif" 114 | :arrow_down: | :1234: [* | N times back to start of a C comment "/*" 115 | :arrow_down: | :1234: ]* | N times forward to end of a C comment "*/" 116 | 117 | ## Pattern searches 118 | 119 | Status | Command | Description | Note 120 | ---|--------|------------|------------------ 121 | :white_check_mark: :star: | :1234: `/{pattern}[/[offset]]` | search forward for the Nth occurrence of {pattern} | Currently we only support JavaScript Regex but not Vim's in-house Regex engine. 122 | :white_check_mark: :star: | :1234: `?{pattern}[?[offset]]` | search backward for the Nth occurrence of {pattern} | Currently we only support JavaScript Regex but not Vim's in-house Regex engine. 123 | :warning: | :1234: `/` | repeat last search, in the forward direction | {count} is not supported. 124 | :warning: | :1234: `?` | repeat last search, in the backward direction | {count} is not supported. 125 | :warning: | :1234: n | repeat last search | {count} is not supported. 126 | :warning: | :1234: N | repeat last search, in opposite direction | {count} is not supported. 127 | :white_check_mark: | :1234: * | search forward for the identifier under the cursor 128 | :white_check_mark: | :1234: # | search backward for the identifier under the cursor 129 | :arrow_down: | :1234: g* | like "*", but also find partial matches 130 | :arrow_down: | :1234: g# | like "#", but also find partial matches 131 | :white_check_mark: | gd | goto local declaration of identifier under the cursor 132 | :arrow_down: | gD | goto global declaration of identifier under the cursor 133 | 134 | ## Marks and motions 135 | 136 | Status | Command | Description 137 | ---|--------|------------------------------ 138 | :white_check_mark: | m{a-zA-Z} | mark current position with mark {a-zA-Z} 139 | :white_check_mark:| `{a-z} | go to mark {a-z} within current file 140 | :white_check_mark:| `{A-Z} | go to mark {A-Z} in any file 141 | :white_check_mark:| `{0-9} | go to the position where Vim was previously exited 142 | :arrow_down: | `` | go to the position before the last jump 143 | :arrow_down: | `" | go to the position when last editing this file 144 | :arrow_down: | `[ | go to the start of the previously operated or put text 145 | :arrow_down: | `] | go to the end of the previously operated or put text 146 | :arrow_down: | `< | go to the start of the (previous) Visual area 147 | :arrow_down: | `> | go to the end of the (previous) Visual area 148 | :arrow_down: | `. | go to the position of the last change in this file 149 | :arrow_down: | '{a-zA-Z0-9[]'"<>.} | same as `, but on the first non-blank in the line 150 | :arrow_down: | :marks | print the active marks 151 | :arrow_down: | :1234: CTRL-O | go to Nth older position in jump list 152 | :arrow_down: | :1234: CTRL-I | go to Nth newer position in jump list 153 | :arrow_down: | :ju[mps] | print the jump list 154 | 155 | ## Various motions 156 | 157 | Status | Command | Description 158 | ---|--------|------------------------------ 159 | :white_check_mark: | % | find the next brace, bracket, comment, or "#if"/ "#else"/"#endif" in this line and go to its match 160 | :white_check_mark: |:1234: H | go to the Nth line in the window, on the first non-blank 161 | :white_check_mark: | M | go to the middle line in the window, on the first non-blank 162 | :white_check_mark: |:1234: L | go to the Nth line from the bottom, on the first non-blank 163 | :arrow_down:|:1234: go | go to Nth byte in the buffer 164 | :arrow_down:|:[range]go[to] [off] | go to [off] byte in the buffer 165 | 166 | ## Using tags 167 | 168 | The following are all marked low priority because VSCode has very good support for tags with Goto Symbol. Try it from the command palette if you haven't yet! 169 | 170 | Status | Command | Description 171 | ---|--------|------------------------------ 172 | :arrow_down:| :ta[g][!] {tag} | jump to tag {tag} 173 | :arrow_down:| :[count]ta[g][!] | jump to [count]'th newer tag in tag list 174 | :arrow_down:| CTRL-] | jump to the tag under cursor, unless changes have been made 175 | :arrow_down:| :ts[elect][!] [tag] | list matching tags and select one to jump to 176 | :arrow_down:| :tj[ump][!] [tag] | jump to tag [tag] or select from list when there are multiple matches 177 | :arrow_down:| :lt[ag][!] [tag] | jump to tag [tag] and add matching tags to the location list 178 | :arrow_down:| :tagsa | print tag list 179 | :arrow_down:| :1234: CTRL-T | jump back from Nth older tag in tag list 180 | :arrow_down:| :[count]po[p][!] | jump back from [count]'th older tag in tag list 181 | :arrow_down:| :[count]tn[ext][!] | jump to [count]'th next matching tag 182 | :arrow_down:| :[count]tp[revious][!] | jump to [count]'th previous matching tag 183 | :arrow_down:| :[count]tr[ewind][!] | jump to [count]'th matching tag 184 | :arrow_down:| :tl[ast][!] | jump to last matching tag 185 | :arrow_down:| :pt[ag] {tag} | open a preview window to show tag {tag} 186 | :arrow_down:| CTRL-W } | like CTRL-] but show tag in preview window 187 | :arrow_down:| :pts[elect] | like ":tselect" but show tag in preview window 188 | :arrow_down:| :ptj[ump] | like ":tjump" but show tag in preview window 189 | :arrow_down:| :pc[lose] | close tag preview window 190 | :arrow_down:| CTRL-W z | close tag preview window` 191 | 192 | ## Scrolling 193 | 194 | Status | Command | Description 195 | ---|--------|------------------------------ 196 | :white_check_mark: | :1234: CTRL-E | window N lines downwards (default: 1) 197 | :white_check_mark: | :1234: CTRL-D | window N lines Downwards (default: 1/2 window) 198 | :white_check_mark: | :1234: CTRL-F | window N pages Forwards (downwards) 199 | :white_check_mark: | :1234: CTRL-Y | window N lines upwards (default: 1) 200 | :white_check_mark: | :1234: CTRL-U | window N lines Upwards (default: 1/2 window) 201 | :white_check_mark: | :1234: CTRL-B | window N pages Backwards (upwards) 202 | :white_check_mark: | z CR or zt | redraw, current line at top of window 203 | :white_check_mark: | z. or zz | redraw, current line at center of window 204 | :white_check_mark: | z- or zb | redraw, current line at bottom of window 205 | 206 | These only work when 'wrap' is off: 207 | 208 | Status | Command | Description | Note 209 | ---|--------|------------------|------------ 210 | :white_check_mark: :star: | :1234: zh | scroll screen N characters to the right | In Code, the cursor wil always move when you run this command, whether the horizontal scrollbar moves or not. 211 | :white_check_mark: :star: | :1234: zl | scroll screen N characters to the left | As above 212 | :white_check_mark: :star: | :1234: zH | scroll screen half a screenwidth to the right | As above 213 | :white_check_mark: :star: | :1234: zL | scroll screen half a screenwidth to the left | As above 214 | 215 | ## Inserting text 216 | 217 | Status | Command | Description 218 | ---|--------|------------------------------ 219 | :white_check_mark: | :1234: a | append text after the cursor (N times) 220 | :white_check_mark: | :1234: A | append text at the end of the line (N times) 221 | :white_check_mark: | :1234: i | insert text before the cursor (N times) (also: Insert) 222 | :white_check_mark: | :1234: I | insert text before the first non-blank in the line (N times) 223 | :white_check_mark: | :1234: gI | insert text in column 1 (N times) 224 | :white_check_mark: | :1234: o | open a new line below the current line, append text (N times) 225 | :white_check_mark: | :1234: O | open a new line above the current line, append text (N times) 226 | 227 | in Visual block mode: 228 | 229 | Status | Command | Description 230 | ---|--------|------------------------------ 231 | :white_check_mark:| I | insert the same text in front of all the selected lines 232 | :white_check_mark:| A | append the same text after all the selected lines 233 | 234 | ## Insert mode keys 235 | 236 | leaving Insert mode: 237 | 238 | Status | Command | Description 239 | ---|--------|------------------------------ 240 | :white_check_mark: | Esc | end Insert mode, back to Normal mode 241 | :white_check_mark: | CTRL-C | like Esc, but do not use an abbreviation 242 | :white_check_mark: | CTRL-O {command} | execute {command} and return to Insert mode 243 | 244 | moving around: 245 | 246 | Status | Command | Description 247 | ---|--------|------------------------------ 248 | :white_check_mark: | cursor keys | move cursor left/right/up/down 249 | :white_check_mark: | shift-left/right | one word left/right 250 | :white_check_mark: | shift-up/down | one screenful backward/forward 251 | :white_check_mark: | End | cursor after last character in the line 252 | :white_check_mark: | Home | cursor to first character in the line 253 | 254 | ## Special keys in Insert mode 255 | 256 | Status | Command | Description | Note 257 | ---|--------|-----------|------------------- 258 | :arrow_down: |CTRL-V {char}.. | insert character literally, or enter decimal byte value 259 | :warning: | NL or CR or CTRL-M or CTRL-J | begin new line | CTRL-M and CTRL-J are not supported 260 | :white_check_mark: | CTRL-E | insert the character from below the cursor 261 | :white_check_mark: | CTRL-Y | insert the character from above the cursor 262 | :white_check_mark: :star: | CTRL-A | insert previously inserted text | We apply previously document change made in previous Insert session and we only apply changes that happen under cursor 263 | :white_check_mark: :star: | CTRL-@ | insert previously inserted text and stop Insert mode | As above 264 | :white_check_mark: | CTRL-R {0-9a-z%#:.-="} | insert the contents of a register 265 | :white_check_mark: | CTRL-N | insert next match of identifier before the cursor 266 | :white_check_mark: | CTRL-P | insert previous match of identifier before the cursor 267 | :arrow_down:| CTRL-X ... | complete the word before the cursor in various ways 268 | :white_check_mark: | BS or CTRL-H | delete the character before the cursor 269 | :white_check_mark: | Del | delete the character under the cursor 270 | :white_check_mark: | CTRL-W | delete word before the cursor 271 | :white_check_mark: | CTRL-U | delete all entered characters in the current line 272 | :white_check_mark: | CTRL-T | insert one shiftwidth of indent in front of the current line 273 | :white_check_mark: | CTRL-D | delete one shiftwidth of indent in front of the current line 274 | :arrow_down: | 0 CTRL-D | delete all indent in the current line 275 | :arrow_down: | ^ CTRL-D | delete all indent in the current line, restore indent in next line 276 | 277 | ## Digraphs 278 | 279 | Status | Command | Description 280 | ---|--------|------------------------------ 281 | :arrow_down: | :dig[raphs] | show current list of digraphs 282 | :arrow_down: | :dig[raphs] {char1}{char2} {number} ... | add digraph(s) to the list 283 | 284 | ## Special inserts 285 | 286 | Status | Command | Description 287 | ---|--------|------------------------------ 288 | :warning: | :r [file] | insert the contents of [file] below the cursor 289 | :warning: | :r! {command} | insert the standard output of {command} below the cursor 290 | 291 | ## Deleting text 292 | 293 | Status | Command | Description 294 | ---|--------|------------------------------ 295 | :white_check_mark: | :1234: x | delete N characters under and after the cursor 296 | :white_check_mark: | :1234: Del | delete N characters under and after the cursor 297 | :white_check_mark: | :1234: X | delete N characters before the cursor 298 | :white_check_mark: | :1234: d{motion} | delete the text that is moved over with {motion} 299 | :white_check_mark: | {visual}d | delete the highlighted text 300 | :white_check_mark: | :1234: dd | delete N lines 301 | :white_check_mark: | :1234: D | delete to the end of the line (and N-1 more lines) 302 | :white_check_mark: | :1234: J | join N-1 lines (delete EOLs) 303 | :white_check_mark: | {visual}J | join the highlighted lines 304 | :white_check_mark: | :1234: gJ | like "J", but without inserting spaces 305 | :white_check_mark:| {visual}gJ | like "{visual}J", but without inserting spaces 306 | :white_check_mark:| :[range]d [x] | delete [range] lines [into register x] 307 | 308 | ## Copying and moving text 309 | 310 | Miscellanea: 311 | 312 | * We don't support read only registers. 313 | 314 | Status | Command | Description | Note 315 | ---|--------|-------------|----------------- 316 | :warning: | "{char} | use register {char} for the next delete, yank, or put | read only registers are not supported 317 | :white_check_mark: | "* | use register `*` to access system clipboard 318 | :white_check_mark: | :reg | show the contents of all registers 319 | :white_check_mark: | :reg {arg} | show the contents of registers mentioned in {arg} 320 | :white_check_mark: | :1234: y{motion} | yank the text moved over with {motion} into a register 321 | :white_check_mark: | {visual}y | yank the highlighted text into a register 322 | :white_check_mark: | :1234: yy | yank N lines into a register 323 | :white_check_mark: | :1234: Y | yank N lines into a register 324 | :white_check_mark: | :1234: p | put a register after the cursor position (N times) 325 | :white_check_mark: | :1234: P | put a register before the cursor position (N times) 326 | :white_check_mark: | :1234: ]p | like p, but adjust indent to current line 327 | :white_check_mark: | :1234: [p | like P, but adjust indent to current line 328 | :white_check_mark: | :1234: gp | like p, but leave cursor after the new text 329 | :white_check_mark: | :1234: gP | like P, but leave cursor after the new text 330 | 331 | ## Changing text 332 | 333 | Status | Command | Description | Note 334 | ---|--------|------------|------------------ 335 | :white_check_mark: | :1234: r{char} | replace N characters with {char} 336 | :arrow_down: | :1234: gr{char} | replace N characters without affecting layout 337 | :white_check_mark: :star: | :1234: R | enter Replace mode (repeat the entered text N times) | {count} is not supported 338 | :arrow_down: | :1234: gR | enter virtual Replace mode: Like Replace mode but without affecting layout 339 | :white_check_mark:| {visual}r{char} | in Visual block, visual, or visual line modes: Replace each char of the selected text with {char} 340 | 341 | (change = delete text and enter Insert mode) 342 | 343 | Status | Command | Description 344 | ---|--------|------------------------------ 345 | :white_check_mark: | :1234: c{motion} | change the text that is moved over with {motion} 346 | :white_check_mark: | {visual}c | change the highlighted text 347 | :white_check_mark: | :1234: cc | change N lines 348 | :white_check_mark: | :1234: S | change N lines 349 | :white_check_mark: | :1234: C | change to the end of the line (and N-1 more lines) 350 | :white_check_mark: | :1234: s | change N characters 351 | :white_check_mark: | {visual}c | in Visual block mode: Change each of the selected lines with the entered text 352 | :white_check_mark: | {visual}C | in Visual block mode: Change each of the selected lines until end-of-line with the entered text 353 | :white_check_mark: | switch case for highlighted text 354 | :white_check_mark: | {visual}u | make highlighted text lowercase 355 | :white_check_mark: | {visual}U | make highlighted text uppercase 356 | :white_check_mark: | g~{motion} | switch case for the text that is moved over with {motion} 357 | :white_check_mark: | gu{motion} | make the text that is moved over with {motion} lowercase 358 | :white_check_mark: | gU{motion} | make the text that is moved over with {motion} uppercase 359 | :arrow_down: | {visual}g? | perform rot13 encoding on highlighted text 360 | :arrow_down: | g?{motion} | perform rot13 encoding on the text that is moved over with {motion} 361 | :white_check_mark: | :1234: CTRL-A | add N to the number at or after the cursor 362 | :white_check_mark: | :1234: CTRL-X | subtract N from the number at or after the cursor 363 | :white_check_mark: | :1234: <{motion} | move the lines that are moved over with {motion} one shiftwidth left 364 | :white_check_mark: | :1234: << | move N lines one shiftwidth left 365 | :white_check_mark: | :1234: >{motion} | move the lines that are moved over with {motion} one shiftwidth right 366 | :white_check_mark: | :1234: >> | move N lines one shiftwidth right 367 | :white_check_mark:| :1234: gq{motion}| format the lines that are moved over with {motion} to 'textwidth' length 368 | :arrow_down: | :[range]ce[nter] [width] | center the lines in [range] 369 | :arrow_down: | :[range]le[ft] [indent] | left-align the lines in [range] (with [indent]) 370 | :arrow_down: | :[ranee]ri[ght] [width] | right-align the lines in [range] 371 | 372 | ## Complex changes 373 | 374 | Status | Command | Description | Note 375 | ---|--------|------------------|------------ 376 | :arrow_down: | :1234: `!{motion}{command}` | filter the lines that are moved over through {command} 377 | :arrow_down: | :1234: `!!{command}` | filter N lines through {command} 378 | :arrow_down: | `{visual}!{command}` | filter the highlighted lines through {command} 379 | :arrow_down: | `:[range]! {command}` | filter [range] lines through {command} 380 | :white_check_mark: | :1234: ={motion} | filter the lines that are moved over through 'equalprg' 381 | :arrow_down:| :1234: == | filter N lines through 'equalprg' 382 | :white_check_mark: | {visual}= | filter the highlighted lines through 'equalprg' 383 | :white_check_mark: :star: :warning: | :[range]s[ubstitute]/{pattern}/{string}/[g][c] | substitute {pattern} by {string} in [range] lines; with [g], replace all occurrences of {pattern}; with [c], confirm each replacement | Currently we only support JavaScript Regex and only options `gi` are implemented 384 | :arrow_down: | :[range]s[ubstitute] [g][c] | repeat previous ":s" with new range and options 385 | :arrow_down: | & | Repeat previous ":s" on current line without options 386 | :arrow_down: | :[range]ret[ab][!] [tabstop] | set 'tabstop' to new value and adjust white space accordingly 387 | 388 | ## Visual mode 389 | 390 | Status | Command | Description 391 | ---|--------|------------------------------ 392 | :white_check_mark: | v | start highlighting characters 393 | :white_check_mark: | V | start highlighting linewise 394 | :white_check_mark:| o | exchange cursor position with start of highlighting 395 | :white_check_mark:| gv | start highlighting on previous visual area 396 | :white_check_mark: | v | highlight characters or stop highlighting 397 | :white_check_mark: | V | highlight linewise or stop highlighting 398 | :white_check_mark: | CTRL-V | highlight blockwise or stop highlighting 399 | 400 | ## Text objects (only in Visual mode or after an operator) 401 | 402 | Status | Command | Description 403 | ---|--------|------------------------------ 404 | :white_check_mark: | :1234: aw | Select "a word" 405 | :white_check_mark: | :1234: iw | Select "inner word" 406 | :white_check_mark: | :1234: aW | Select "a |WORD|" 407 | :white_check_mark: | :1234: iW | Select "inner |WORD|" 408 | :white_check_mark: | :1234: as | Select "a sentence" 409 | :white_check_mark: | :1234: is | Select "inner sentence" 410 | :white_check_mark: | :1234: ap | Select "a paragraph" 411 | :white_check_mark: | :1234: ip | Select "inner paragraph" 412 | :white_check_mark: | :1234: a], a[ | select '[' ']' blocks 413 | :white_check_mark: | :1234: i], i[ | select inner '[' ']' blocks 414 | :white_check_mark: | :1234: ab, a(, a) | Select "a block" (from "[(" to "])") 415 | :white_check_mark: | :1234: ib, i), i( | Select "inner block" (from "[(" to "])") 416 | :white_check_mark: | :1234: a>, a< | Select "a <> block" 417 | :white_check_mark: | :1234: i>, i< | Select "inner <> block" 418 | :white_check_mark: | :1234: aB, a{, a} | Select "a Block" (from "[{" to "]}") 419 | :white_check_mark: | :1234: iB, i{, i} | Select "inner Block" (from "[{" to "]}") 420 | :white_check_mark: | :1234: at | Select "a tag block" (from <aaa> to </aaa>) 421 | :white_check_mark: | :1234: it | Select "inner tag block" (from <aaa> to </aaa>) 422 | :white_check_mark: | :1234: a' | Select "a single quoted string" 423 | :white_check_mark: | :1234: i' | Select "inner single quoted string" 424 | :white_check_mark: | :1234: a" | Select "a double quoted string" 425 | :white_check_mark: | :1234: i" | Select "inner double quoted string" 426 | :white_check_mark: | :1234: a` | Select "a backward quoted string" 427 | :white_check_mark: | :1234: i` | Select "inner backward quoted string" 428 | 429 | ## Repeating commands 430 | 431 | Status | Command | Description | Note 432 | ---|--------|--------------|---------------- 433 | :white_check_mark: :star: | :1234: . | repeat last change (with count replaced with N) | Content changes that don't happen under cursor can not be repeated. 434 | :white_check_mark:| q{a-z} | record typed characters into register {a-z} 435 | :arrow_down: | q{A-Z} | record typed characters, appended to register {a-z} 436 | :white_check_mark:| q | stop recording 437 | :white_check_mark:| :1234: @{a-z} | execute the contents of register {a-z} (N times) 438 | :white_check_mark:| :1234: @@ | repeat previous @{a-z} (N times) 439 | :arrow_down: | :@{a-z} | execute the contents of register {a-z} as an Ex command 440 | :arrow_down: | :@@ | repeat previous :@{a-z} 441 | :arrow_down: | :[range]g[lobal]/{pattern}/[cmd] | execute Ex command [cmd] (default: ":p") on the lines within [range] where {pattern} matches 442 | :arrow_down: | :[range]g[lobal]!/{pattern}/[cmd] | execute Ex command [cmd] (default: ":p") on the lines within [range] where {pattern} does NOT match 443 | :arrow_down: | :so[urce] {file} | read Ex commands from {file} 444 | :arrow_down: | :so[urce]! {file} | read Vim commands from {file} 445 | :arrow_down: | :sl[eep] [sec] | don't do anything for [sec] seconds 446 | :arrow_down: | :1234: gs | goto Sleep for N seconds 447 | 448 | ## options 449 | 450 | Status | Command | Description | Note 451 | ---|--------|---------|--------------------- 452 | :arrow_down: | :se[t] | show all modified options 453 | :arrow_down: | :se[t] all | show all non-termcap options 454 | :arrow_down: | :se[t] termcap | show all termcap options 455 | :white_check_mark: | :se[t] {option} | set boolean option (switch it on), show string or number option 456 | :white_check_mark: | :se[t] no{option} | reset boolean option (switch it off) 457 | :white_check_mark: | :se[t] inv{option} |invert boolean option 458 | :white_check_mark: | :se[t] {option}={value} | set string/number option to {value} 459 | :white_check_mark: | :se[t] {option}+={value} | append {value} to string option, add {value} to number option 460 | :white_check_mark: :star:| :se[t] {option}-={value} | remove {value} to string option, subtract {value} from number option | We don't support string option here. 461 | :white_check_mark: | :se[t] {option}? | show value of {option} 462 | :arrow_down: | :se[t] {option}& | reset {option} to its default value 463 | :arrow_down: | :setl[ocal] | like ":set" but set the local value for options that have one 464 | :arrow_down: | :setg[lobal] | like ":set" but set the global value of a local option 465 | :arrow_down: | :fix[del] | set value of 't_kD' according to value of 't_kb' 466 | :arrow_down: | :opt[ions] | open a new window to view and set options, grouped by functionality, a one line explanation and links to the help 467 | 468 | Since the list is too long, now we just put those already supported options here. 469 | 470 | Status | Command | Default Value | Description 471 | ---|--------|-------|------------------------------ 472 | :white_check_mark:| tabstop (ts) | 4. we use Code's default value `tabSize` instead of Vim | number of spaces that <Tab> in file uses 473 | :white_check_mark:| hlsearch (hls) | false | When there is a previous search pattern, highlight all its matches. 474 | :white_check_mark:| ignorecase (ic) | true | Ignore case in search patterns. 475 | :white_check_mark:| smartcase (scs) | true | Override the 'ignorecase' option if the search pattern contains upper case characters. 476 | :white_check_mark:| iskeyword (isk) | `@,48-57,_,128-167,224-235` | keywords contain alphanumeric characters and '_'. If there is no user setting for `iskeyword`, we use `editor.wordSeparators` properties. 477 | :white_check_mark:| scroll (scr) | 20 | Number of lines to scroll with CTRL-U and CTRL-D commands. 478 | :white_check_mark:| expandtab (et) | True. we use Code's default value `inserSpaces` instead of Vim | use spaces when <Tab> is inserted 479 | :white_check_mark:| autoindent | true | Keep indentation when doing `cc` or `S` in normal mode to replace a line. 480 | 481 | ## Undo/Redo commands 482 | 483 | Status | Command | Description | Note 484 | ---|--------|-------|------------------------------ 485 | :white_check_mark: | :1234: u | undo last N changes | Current implementation may not cover every case perfectly. 486 | :white_check_mark: | :1234: CTRL-R | redo last N undone changes | As above. 487 | :arrow_down: | U | restore last changed line 488 | 489 | ## External commands 490 | 491 | Status | Command | Description 492 | ---|--------|----------------- 493 | :arrow_down: | :sh[ell] | start a shell 494 | :arrow_down: | :!{command} | execute {command} with a shell 495 | :arrow_down: | K | lookup keyword under the cursor with 'keywordprg' program (default: "man") 496 | 497 | ## Ex ranges 498 | 499 | Status | Command | Description | Note 500 | ---|--------|-------|------------------------------ 501 | :white_check_mark: | , | separates two line numbers 502 | :white_check_mark: :star: | ; | idem, set cursor to the first line number before interpreting the second one | The cursor movement is not included. 503 | :white_check_mark: | {number} | an absolute line number 504 | :white_check_mark: | . | the current line 505 | :white_check_mark: | $ | the last line in the file 506 | :white_check_mark: | % | equal to 1,$ (the entire file) 507 | :white_check_mark: | * | equal to '<,'> (visual area) 508 | :white_check_mark: | 't | position of mark t 509 | :arrow_down: | /{pattern}[/] | the next line where {pattern} matches 510 | :arrow_down: | ?{pattern}[?] | the previous line where {pattern} matches 511 | :white_check_mark: | +[num] | add [num] to the preceding line number (default: 1) 512 | :white_check_mark: | -[num] | subtract [num] from the preceding line number (default: 1) 513 | 514 | ## Editing a file 515 | 516 | Status | Command | Description | Note 517 | ---|--------|------------------|----------- 518 | :white_check_mark: :star: | :e[dit] {file} | Edit {file}. | We will open file in a new Tab of current Grouped Editor instead of opening in current tab. 519 | 520 | ## Multi-window commands 521 | 522 | Status | Command | Description | Note 523 | ---|--------|-----------------|------------- 524 | :white_check_mark: :star: | :e[dit] {file} | Edit {file}. | We will open file in a new Tab of current Grouped Editor instead of opening in current tab. 525 | :white_check_mark: :star: | <ctrl-w> hl | Switching between windows. | As we don't have the concept of Window in VS Code, we are mapping these commands to switching between Grouped Editors. 526 | :x: | :sp {file} | Split current window in two. | VS Code doesn't support split Window horizontally. 527 | :white_check_mark: :star: | :vsp {file} | Split vertically current window in two. | VS Code only supports three vertical window at most and that's the limitation of this command. 528 | :x: | :new | Create a new window horizontally and start editing an empty file in it. | VS Code doesn't support split Window horizontally. 529 | :white_check_mark: :star: | :vne[w] | Create a new window vertically and start editing an empty file in it. | VS Code only supports three vertical window at most and that's the limitation of this command. 530 | 531 | 532 | ## Tabs 533 | 534 | Status | Command | Description | Note 535 | ---|--------|------------------|------------ 536 | :white_check_mark: | :tabn[ext] :1234: | Go to next tab page or tab page {count}. The first tab page has number one. 537 | :white_check_mark: | {count}<C-PageDown>, {count}gt | Same as above 538 | :white_check_mark: | :tabp[revious] :1234: | Go to the previous tab page. Wraps around from the first one to the last one. 539 | :white_check_mark: | :tabN[ext] :1234: | Same as above 540 | :white_check_mark: | {count}<C-PageUp>, {count}gT | Same as above 541 | :white_check_mark: | :tabfir[st] | Go to the first tab page. 542 | :white_check_mark: | :tabl[ast] | Go to the last tab page. 543 | :white_check_mark: | :tabe[dit] {file} | Open a new tab page with an empty window, after the current tab page 544 | :arrow_down: | :[count]tabe[dit], :[count]tabnew | Same as above | [count] is not supported. 545 | :white_check_mark: | :tabnew {file} | Open a new tab page with an empty window, after the current tab page 546 | :arrow_down:| :[count]tab {cmd} | Execute {cmd} and when it opens a new window open a new tab page instead. 547 | :white_check_mark: :star: | :tabc[lose][!] :1234: | Close current tab page or close tab page {count}. | Code will close tab directly without saving. 548 | :white_check_mark: :star: | :tabo[nly][!] | Close all other tab pages. | `!` is not supported, Code will close tab directly without saving. 549 | :white_check_mark: | :tabm[ove] [N] | Move the current tab page to after tab page N. 550 | :arrow_down:| :tabs | List the tab pages and the windows they contain. | You can always use Code's built-in shortcut: `cmd/ctrl+p` 551 | :arrow_down:| :tabd[o] {cmd} | Execute {cmd} in each tab page. 552 | 553 | ## Folding 554 | ### Fold methods 555 | The folding method can be set with the 'foldmethod' option. This is currently not possible as we are relying on Code's Fold logic. 556 | 557 | ### Fold commands 558 | 559 | Pretty much everything fold-related is blocked by [this issue](https://github.com/VSCodeVim/Vim/issues/1004). 560 | 561 | Status | Command | Description 562 | ---|--------|------------------------------ 563 | :arrow_down: | zf{motion} or {Visual}zf | Operator to create a fold. 564 | :arrow_down: | zF | Create a fold for [count] lines. Works like "zf". 565 | :arrow_down: | zd | Delete one fold at the cursor. 566 | :arrow_down: | zD | Delete folds recursively at the cursor. 567 | :arrow_down: | zE | Eliminate all folds in the window. 568 | :white_check_mark: | zo | Open one fold under the cursor.When a count is given, that many folds deep will be opened. 569 | :white_check_mark: | zO | Open all folds under the cursor recursively. 570 | :white_check_mark: | zc | Close one fold under the cursor. When a count is given, that many folds deep are closed. 571 | :white_check_mark:| zC | Close all folds under the cursor recursively. 572 | :arrow_down: | za | When on a closed fold: open it. When on an open fold: close it and set 'foldenable'. 573 | :arrow_down: | zA | When on a closed fold: open it recursively. When on an open fold: close it recursively and set 'foldenable'. 574 | :arrow_down: | zv | View cursor line: Open just enough folds to make the line in which the cursor is located not folded. 575 | :arrow_down: | zx | Update folds: Undo manually opened and closed folds: re-apply 'foldlevel', then do "zv": View cursor line. 576 | :arrow_down: | zX | Undo manually opened and closed folds 577 | :arrow_down: | zm | Fold more: Subtract one from 'foldlevel'. 578 | :white_check_mark: | zM | Close all folds: set 'foldlevel' to 0. 'foldenable' will be set. 579 | :arrow_down: | zr | Reduce folding: Add one to 'foldlevel'. 580 | :white_check_mark: | zR | Open all folds. This sets 'foldlevel' to highest fold level. 581 | :arrow_down: | zn | Fold none: reset 'foldenable'. All folds will be open. 582 | :arrow_down: | zN | Fold normal: set 'foldenable'. All folds will be as they were before. 583 | :arrow_down: | zi | Invert 'foldenable'. 584 | :arrow_down: | [z | Move to the start of the current open fold. 585 | :arrow_down: | ]z | Move to the end of the current open fold. 586 | :arrow_down: | zj | Move downwards to the start of the next fold. 587 | :arrow_down: | zk | Move upwards to the end of the previous fold. 588 | 589 | ### Fold options 590 | 591 | Currently we don't support any fold option and we are following Code configurations. 592 | -------------------------------------------------------------------------------- /STYLE.md: -------------------------------------------------------------------------------- 1 | ## Style Guide 2 | 3 | In addition, to VS Code's [coding guidelines](https://github.com/Microsoft/vscode/wiki/Coding-Guidelines), please adhere to the following: 4 | 5 | * Use `for ... of` whenever possible 6 | 7 | **Rationale:** `for ... of` is awesome. It's more readable than any other variant. 8 | 9 | * Don't use `any` as much as possible 10 | 11 | **Rationale:** The language is called *Type*Script, not *Untyped*Script. :wink: Static typing is wonderful. It catches bugs and improves readability. We should strive to use it as much as possible. 12 | 13 | * Use `const` wherever possible. 14 | 15 | **Rationale:** Instead of reading `const` as "constant value," read it as "single assignment." Yes, it means "constant value" in other programming languages, but it's a little different in JavaScript. 16 | 17 | * When we can't use `const`, use `let`; never `var` 18 | 19 | **Rationale:** `var` trips up programmers in a number of cases - hoisting and closure capture are two big ones. Consider the difference between 20 | 21 | ``` 22 | for (var j = 0; j < 5; j++) { setTimeout(() => console.log(j), 5) } 23 | ``` 24 | 25 | and 26 | 27 | ``` 28 | for (let j = 0; j < 5; j++) { setTimeout(() => console.log(j), 5) } 29 | ``` 30 | 31 | Even if you're not capturing the variable, who knows if someone else might later? 32 | 33 | -------------------------------------------------------------------------------- /designdoc.md: -------------------------------------------------------------------------------- 1 | # Integrating Neovim into VSCode 2 | 3 | ## Things we will not port over from Vim (for now). As in, we will have our own implementations that do not try to match vim behavior 4 | 1. Folds 5 | 2. Splits 6 | 3. Intellisense/autocomplete (this includes go to definition, autocomplete, etc.) 7 | 4. Syntax highlighting 8 | These are all things that I see no benefit in including from vim. 9 | 10 | ## Philosophy 11 | As much as possible, we need to pretend like we're in neovim. That means that when we override `gd`, it needs to look exactly like we just did a `gd`. When we're doing autocomplete, it needs to look like the user typed in the text to neovim. 12 | 13 | ## Overarching strategy 14 | 1. Pass all relevant input to neovim. 15 | 2. Get all relevant information back (text, cursor position, mode, tabline) 16 | Simple enough. 17 | 18 | ## Things I need to figure out 19 | 1. Multicursor. 20 | * How do I ensure that some commands are only performed once? 21 | For example, undo. 22 | * How do I handle operations that are local to each cursor. For example, copy and paste. 23 | * Maybe we can use this? https://github.com/terryma/vim-multiple-cursors 24 | * Will be handled by core neovim (eventually, says @justinmk http://i.imgur.com/jPQtHoU.jpg). 25 | 26 | 2. Handling operators properly 27 | * https://github.com/neovim/neovim/issues/6166 28 | * Done through a hack right now 29 | * This is annoying. We don't want to need to maintain state on the vscode side. This is why commands like 2dd or gqq don't work in ActualVim. 30 | 31 | 3. Ensuring that actions coming in are performed in the correct order. 32 | * Transformation queue? 33 | * I don't think this is an issue if we don't do multiple cursors? nvim.input() isn't blocking, but I'm not sure. 34 | 35 | 4. Handling cross file jumping for commands that we're overriding (gd for example) 36 | * Just open files regularly on editor side, feed it back to neovim, and then handle it regularly. 37 | Flow of events: 38 | 1. "gd" (or other) is pressed in neovim. 39 | 2. A RPC request is made to VSCode. 40 | 3. VSCode receives the request, opens up the necessary file on the neovim side. 41 | 4. nvim.eval("m'") and then make the right jumps through setpos() and such. 42 | 43 | 5. Handling splits/folds. 44 | * All of this should be handled through editor commands. 45 | * We can override in vimrc through a map to a rpc call. 46 | * We need some kind of API for this though, to handle keybindings properly. 47 | * Maybe folds are possible to play nice, but they both seem like fundamental ui incompatibilities. 48 | 49 | 6. Handling settings that must be handled on the vscode side. 50 | * I wonder whether there are some things that won't play nice that we might want to lint for. 51 | * Sync them up either with a .vimrc or at startup. I'm leaning towards in the .vimrc (to maintain transparency) 52 | 53 | 7. Snippets? 54 | * no clue how these are implemented. 55 | 56 | 8. Insert mode autocomplete and such. 57 | * This is tricky. There's a lot of things that mess up the autocompletion engine. 58 | 59 | 9. Editor inserted text 60 | * If we can get a diff we can insert it on the vim side as another action? 61 | 62 | 10. Handling opening files and other such things. 63 | * Autocommands that sync up VSCode and neovim state 64 | 65 | 11. Handling commands that we want vscode to handle. 66 | * Don't pass anything that's not a single character//. 67 | * Have an "ignore this" array. 68 | 69 | 12. Mouse -------------------------------------------------------------------------------- /extension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import * as fs from 'fs'; 5 | import * as _ from 'lodash'; 6 | import { attach } from 'neovim'; 7 | import { NeovimClient } from 'neovim/lib/api/client'; 8 | import { TaskQueue } from 'aurelia-task-queue'; 9 | import { Position } from './src/common/motion/position'; 10 | import { Globals } from './src/globals'; 11 | import { Configuration } from './src/configuration/configuration'; 12 | 13 | import { spawn } from 'child_process'; 14 | import { NvUtil } from './srcNV/nvUtil'; 15 | import { RpcRequest } from './srcNV/rpcHandlers'; 16 | import { TextEditor } from './src/textEditor'; 17 | import { Screen, IgnoredKeys } from './srcNV/screen'; 18 | import { VimSettings } from './srcNV/vimSettings'; 19 | import { VscHandlers } from './srcNV/vscHandlers'; 20 | 21 | interface VSCodeKeybinding { 22 | key: string; 23 | command: string; 24 | when: string; 25 | vimKey: string; 26 | } 27 | 28 | const packagejson: { 29 | contributes: { 30 | keybindings: VSCodeKeybinding[]; 31 | }; 32 | } = require('../package.json'); // out/../package.json 33 | 34 | export namespace Vim { 35 | export let nv: NeovimClient; 36 | export let channelId: number; 37 | export let mode: { mode: string; blocking: boolean } = { mode: 'n', blocking: false }; 38 | export let screen: Screen; 39 | export let prevState: { bufferTick: number } = { 40 | bufferTick: -1, 41 | }; 42 | export let numVimChangesToApply = 0; 43 | export let taskQueue = new TaskQueue(); 44 | // We're connecting to an already existing terminal instance, so externalized ui won't work. 45 | export let DEBUG: boolean; 46 | } 47 | 48 | export async function activate(context: vscode.ExtensionContext) { 49 | vscode.workspace.onDidCloseTextDocument(async event => { 50 | const deleted_file = event.fileName; 51 | let buf_id = await nvim.call('bufnr', [`^${deleted_file}$`]); 52 | if (buf_id === -1) { 53 | return; 54 | } 55 | // await nvim.command(`noautocmd ${buf_id}bw!`); 56 | }); 57 | 58 | vscode.window.onDidChangeActiveTextEditor(VscHandlers.handleActiveTextEditorChange, this); 59 | 60 | vscode.window.onDidChangeTextEditorSelection(async e => { 61 | if (e.kind === vscode.TextEditorSelectionChangeKind.Mouse) { 62 | if (e.selections[0]) { 63 | await NvUtil.setSelection(e.selections[0]); 64 | } 65 | } 66 | }); 67 | vscode.workspace.onDidChangeTextDocument(VscHandlers.handleTextDocumentChange); 68 | 69 | // Event to update active configuration items when changed without restarting vscode 70 | vscode.workspace.onDidChangeConfiguration((e: void) => { 71 | Configuration.updateConfiguration(); 72 | }); 73 | 74 | overrideCommand(context, 'type', async args => { 75 | Vim.taskQueue.queueMicroTask(() => { 76 | VscHandlers.handleKeyEventNV(args.text); 77 | }); 78 | }); 79 | 80 | const keysToBind = packagejson.contributes.keybindings; 81 | const ignoreKeys = Configuration.ignoreKeys; 82 | 83 | for (let key of keysToBind) { 84 | if (ignoreKeys.all.indexOf(key.vimKey) !== -1) { 85 | continue; 86 | } 87 | vscode.commands.executeCommand('setContext', `vim.use_${key.vimKey}`, true); 88 | registerCommand(context, key.command, () => { 89 | Vim.taskQueue.queueMicroTask(() => { 90 | VscHandlers.handleKeyEventNV(`${key.vimKey}`); 91 | }); 92 | }); 93 | } 94 | 95 | const proc = spawn( 96 | Configuration.neovimPath, 97 | [ 98 | // '-u', 99 | // 'NONE', 100 | '-N', 101 | '--embed', 102 | vscode.window.activeTextEditor ? vscode.window.activeTextEditor!.document.fileName : '', 103 | ], 104 | { 105 | cwd: __dirname, 106 | } 107 | ); 108 | 109 | proc.on('error', function (err) { 110 | console.log(err); 111 | vscode.window.showErrorMessage('Unable to setup neovim instance! Check your path.'); 112 | }); 113 | let nvim: NeovimClient; 114 | if (fs.existsSync('/tmp/nvim') && fs.lstatSync('/tmp/nvim').isSocket()) { 115 | nvim = attach({ socket: '/tmp/nvim' }); 116 | Vim.DEBUG = true; 117 | } else { 118 | nvim = attach({ proc: proc }); 119 | Vim.DEBUG = false; 120 | } 121 | Vim.nv = nvim; 122 | 123 | Vim.channelId = (await nvim.requestApi())[0] as number; 124 | 125 | const WIDTH = 50; 126 | const HEIGHT = 36; 127 | nvim.uiAttach(WIDTH, HEIGHT, { ext_cmdline: true, ext_wildmenu: true }); 128 | Vim.screen = new Screen({ width: WIDTH, height: HEIGHT }); 129 | 130 | const code = ` 131 | function _vscode_copy_text(text, line, char) 132 | vim.api.nvim_command('undojoin') 133 | vim.api.nvim_buf_set_lines(0, 0, -1, true, text) 134 | vim.api.nvim_call_function('setpos', {'.', {0, line, char, false}}) 135 | end 136 | `; 137 | 138 | await Vim.nv.lua(code, []); 139 | await nvim.command('autocmd!'); 140 | 141 | // todo(chilli): Create this map just from RPCHandlers and a decorator. 142 | const autocmdMap: { [autocmd: string]: string } = { 143 | BufWriteCmd: 'writeBuf', 144 | QuitPre: 'closeBuf', 145 | BufEnter: 'enterBuf', 146 | TabNewEntered: 'newTabEntered', 147 | }; 148 | 149 | for (const autocmd of Object.keys(autocmdMap)) { 150 | await nvim.command( 151 | `autocmd ${autocmd} * :call rpcrequest(${Vim.channelId}, "${autocmdMap[ 152 | autocmd 153 | ]}", expand(""), fnamemodify(expand(''), ':p'), expand(""))` 154 | ); 155 | } 156 | 157 | // Overriding commands to handle them on the vscode side. 158 | // await nvim.command(`nnoremap gd :call rpcrequest(${Vim.channelId},"goToDefinition")`); 159 | 160 | await NvUtil.setSettings(['noswapfile', 'hidden']); 161 | nvim.on('notification', (method: any, args: any) => { 162 | if (vscode.window.activeTextEditor && method === 'redraw') { 163 | Vim.screen.redraw(args); 164 | } 165 | }); 166 | 167 | nvim.on('request', async (method: string, args: Array, resp: any) => { 168 | if (RpcRequest[method] !== undefined) { 169 | const f = RpcRequest[method]; 170 | f(args, resp); 171 | } else { 172 | console.log(`${method} is not defined!`); 173 | } 174 | }); 175 | 176 | if (vscode.window.activeTextEditor) { 177 | await VscHandlers.handleActiveTextEditorChange(); 178 | } 179 | } 180 | 181 | function overrideCommand( 182 | context: vscode.ExtensionContext, 183 | command: string, 184 | callback: (...args: any[]) => any 185 | ) { 186 | let disposable = vscode.commands.registerCommand(command, async args => { 187 | if (!vscode.window.activeTextEditor) { 188 | return; 189 | } 190 | 191 | if ( 192 | vscode.window.activeTextEditor.document && 193 | vscode.window.activeTextEditor.document.uri.toString() === 'debug:input' 194 | ) { 195 | await vscode.commands.executeCommand('default:' + command, args); 196 | return; 197 | } 198 | 199 | callback(args); 200 | }); 201 | context.subscriptions.push(disposable); 202 | } 203 | 204 | function registerCommand( 205 | context: vscode.ExtensionContext, 206 | command: string, 207 | callback: (...args: any[]) => any 208 | ) { 209 | let disposable = vscode.commands.registerCommand(command, async args => { 210 | if (!vscode.window.activeTextEditor) { 211 | return; 212 | } 213 | 214 | callback(args); 215 | }); 216 | context.subscriptions.push(disposable); 217 | } 218 | 219 | process.on('unhandledRejection', function (reason: any, p: any) { 220 | console.log('Unhandled Rejection at: Promise ', p, ' reason: ', reason); 221 | }); 222 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | bump = require('gulp-bump'), 3 | git = require('gulp-git'), 4 | inject = require('gulp-inject-string'), 5 | merge = require('merge-stream'), 6 | tag_version = require('gulp-tag-version'), 7 | tslint = require('gulp-tslint'), 8 | typings = require('gulp-typings'), 9 | shell = require('gulp-shell'); 10 | 11 | var paths = { 12 | src_ts: "src/**/*.ts", 13 | tests_ts: "test/**/*.ts" 14 | }; 15 | 16 | function versionBump(semver) { 17 | return gulp.src(['./package.json']) 18 | .pipe(bump({type: semver})) 19 | .pipe(gulp.dest('./')) 20 | .pipe(git.commit('bump package version')) 21 | .pipe(tag_version()); 22 | } 23 | 24 | gulp.task('typings', function () { 25 | return gulp.src('./typings.json') 26 | .pipe(typings()); 27 | }); 28 | 29 | gulp.task('typings-vscode-definitions', ['typings'], function() { 30 | // add vscode definitions 31 | return gulp.src('./typings/index.d.ts').pipe(gulp.dest('./typings')); 32 | }) 33 | 34 | gulp.task('tslint', function() { 35 | var tslintOptions = { 36 | summarizeFailureOutput: true 37 | }; 38 | 39 | var srcs = gulp.src(paths.src_ts) 40 | .pipe(tslint({ formatter: 'verbose' })) 41 | .pipe(tslint.report(tslintOptions)); 42 | var tests = gulp.src(paths.tests_ts) 43 | .pipe(tslint({ formatter: 'verbose' })) 44 | .pipe(tslint.report(tslintOptions)); 45 | return merge(srcs, tests); 46 | }); 47 | 48 | gulp.task('default', ['tslint', 'compile']); 49 | 50 | gulp.task('compile', shell.task(['npm run vscode:prepublish'])); 51 | gulp.task('watch', shell.task(['npm run compile'])); 52 | gulp.task('init', ['typings', 'typings-vscode-definitions']); 53 | 54 | gulp.task('patch', ['default'], function() { return versionBump('patch'); }) 55 | gulp.task('minor', ['default'], function() { return versionBump('minor'); }) 56 | gulp.task('major', ['default'], function() { return versionBump('major'); }) 57 | -------------------------------------------------------------------------------- /images/design/vscodevim-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chillee/VSCodeNeovim/ec22b1ca3d6bb2899ed74516ba8719b6d4f651b9/images/design/vscodevim-logo.png -------------------------------------------------------------------------------- /images/design/vscodevim-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chillee/VSCodeNeovim/ec22b1ca3d6bb2899ed74516ba8719b6d4f651b9/images/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "activationEvents": [ 3 | "*" 4 | ], 5 | "bugs": { 6 | "url": "https://github.com/VSCodeVim/Vim/issues" 7 | }, 8 | "categories": [ 9 | "Other", 10 | "Keymaps" 11 | ], 12 | "contributes": { 13 | "commands": [], 14 | "configuration": { 15 | "properties": { 16 | "vim.enableHighlights": { 17 | "default": true 18 | }, 19 | "vim.ignoreKeys": { 20 | "default": { 21 | "all": [ 22 | "", 23 | "", 24 | "", 25 | "", 26 | "", 27 | "", 28 | "" 29 | ], 30 | "normal": [], 31 | "insert": [ 32 | "", 33 | "", 34 | "", 35 | "", 36 | "", 37 | "", 38 | "", 39 | "", 40 | "" 41 | ], 42 | "visual": [] 43 | } 44 | }, 45 | "vim.neovimPath": { 46 | "type": "string", 47 | "description": "Path to run neovim executable. For example, /usr/bin/nvim, or C:\\Program Files\\Neovim\\bin\\nvim.exe", 48 | "default": "nvim" 49 | } 50 | }, 51 | "title": "Vim Configuration", 52 | "type": "object" 53 | }, 54 | "keybindings": [ 55 | { 56 | "key": "ctrl+space", 57 | "command": "vim.", 58 | "when": "vim.use_", 59 | "vimKey": "" 60 | }, 61 | { 62 | "key": "space", 63 | "command": "vim.", 64 | "when": "vim.use_", 65 | "vimKey": "" 66 | }, 67 | { 68 | "key": "ctrl+left", 69 | "command": "vim.", 70 | "when": "vim.use_", 71 | "vimKey": "" 72 | }, 73 | { 74 | "key": "left", 75 | "command": "vim.", 76 | "when": "vim.use_", 77 | "vimKey": "" 78 | }, 79 | { 80 | "key": "ctrl+right", 81 | "command": "vim.", 82 | "when": "vim.use_", 83 | "vimKey": "" 84 | }, 85 | { 86 | "key": "right", 87 | "command": "vim.", 88 | "when": "vim.use_", 89 | "vimKey": "" 90 | }, 91 | { 92 | "key": "ctrl+up", 93 | "command": "vim.", 94 | "when": "vim.use_", 95 | "vimKey": "" 96 | }, 97 | { 98 | "key": "up", 99 | "command": "vim.", 100 | "when": "vim.use_", 101 | "vimKey": "" 102 | }, 103 | { 104 | "key": "ctrl+down", 105 | "command": "vim.", 106 | "when": "vim.use_", 107 | "vimKey": "" 108 | }, 109 | { 110 | "key": "down", 111 | "command": "vim.", 112 | "when": "vim.use_", 113 | "vimKey": "" 114 | }, 115 | { 116 | "key": "ctrl+Escape", 117 | "command": "vim.", 118 | "when": "vim.use_", 119 | "vimKey": "" 120 | }, 121 | { 122 | "key": "Escape", 123 | "command": "vim.", 124 | "when": "vim.use_", 125 | "vimKey": "" 126 | }, 127 | { 128 | "key": "ctrl+backspace", 129 | "command": "vim.", 130 | "when": "vim.use_", 131 | "vimKey": "" 132 | }, 133 | { 134 | "key": "backspace", 135 | "command": "vim.", 136 | "when": "vim.use_", 137 | "vimKey": "" 138 | }, 139 | { 140 | "key": "ctrl+tab", 141 | "command": "vim.", 142 | "when": "vim.use_", 143 | "vimKey": "" 144 | }, 145 | { 146 | "key": "tab", 147 | "command": "vim.", 148 | "when": "vim.use_", 149 | "vimKey": "" 150 | }, 151 | { 152 | "key": "ctrl+enter", 153 | "command": "vim.", 154 | "when": "vim.use_", 155 | "vimKey": "" 156 | }, 157 | { 158 | "key": "enter", 159 | "command": "vim.", 160 | "when": "vim.use_", 161 | "vimKey": "" 162 | }, 163 | { 164 | "key": "ctrl+0", 165 | "command": "vim.", 166 | "when": "vim.use_", 167 | "vimKey": "" 168 | }, 169 | { 170 | "key": "ctrl+7", 171 | "command": "vim.", 172 | "when": "vim.use_", 173 | "vimKey": "" 174 | }, 175 | { 176 | "key": "ctrl+n", 177 | "command": "vim.", 178 | "when": "vim.use_", 179 | "vimKey": "" 180 | }, 181 | { 182 | "key": "ctrl+$", 183 | "command": "vim.", 184 | "when": "vim.use_", 185 | "vimKey": "" 186 | }, 187 | { 188 | "key": "ctrl++", 189 | "command": "vim.", 190 | "when": "vim.use_", 191 | "vimKey": "" 192 | }, 193 | { 194 | "key": "ctrl+.", 195 | "command": "vim.", 196 | "when": "vim.use_", 197 | "vimKey": "" 198 | }, 199 | { 200 | "key": "ctrl+_", 201 | "command": "vim.", 202 | "when": "vim.use_", 203 | "vimKey": "" 204 | }, 205 | { 206 | "key": "ctrl+:", 207 | "command": "vim.", 208 | "when": "vim.use_", 209 | "vimKey": "" 210 | }, 211 | { 212 | "key": "ctrl+b", 213 | "command": "vim.", 214 | "when": "vim.use_", 215 | "vimKey": "" 216 | }, 217 | { 218 | "key": "ctrl+e", 219 | "command": "vim.", 220 | "when": "vim.use_", 221 | "vimKey": "" 222 | }, 223 | { 224 | "key": "ctrl+j", 225 | "command": "vim.", 226 | "when": "vim.use_", 227 | "vimKey": "" 228 | }, 229 | { 230 | "key": "ctrl+|", 231 | "command": "vim.", 232 | "when": "vim.use_", 233 | "vimKey": "" 234 | }, 235 | { 236 | "key": "ctrl+^", 237 | "command": "vim.", 238 | "when": "vim.use_", 239 | "vimKey": "" 240 | }, 241 | { 242 | "key": "ctrl+(", 243 | "command": "vim.", 244 | "when": "vim.use_", 245 | "vimKey": "" 246 | }, 247 | { 248 | "key": "ctrl+3", 249 | "command": "vim.", 250 | "when": "vim.use_", 251 | "vimKey": "" 252 | }, 253 | { 254 | "key": "ctrl+1", 255 | "command": "vim.", 256 | "when": "vim.use_", 257 | "vimKey": "" 258 | }, 259 | { 260 | "key": "ctrl+8", 261 | "command": "vim.", 262 | "when": "vim.use_", 263 | "vimKey": "" 264 | }, 265 | { 266 | "key": "ctrl+!", 267 | "command": "vim.", 268 | "when": "vim.use_", 269 | "vimKey": "" 270 | }, 271 | { 272 | "key": "ctrl+~", 273 | "command": "vim.", 274 | "when": "vim.use_", 275 | "vimKey": "" 276 | }, 277 | { 278 | "key": "ctrl+h", 279 | "command": "vim.", 280 | "when": "vim.use_", 281 | "vimKey": "" 282 | }, 283 | { 284 | "key": "ctrl+5", 285 | "command": "vim.", 286 | "when": "vim.use_", 287 | "vimKey": "" 288 | }, 289 | { 290 | "key": "ctrl+*", 291 | "command": "vim.", 292 | "when": "vim.use_", 293 | "vimKey": "" 294 | }, 295 | { 296 | "key": "ctrl+6", 297 | "command": "vim.", 298 | "when": "vim.use_", 299 | "vimKey": "" 300 | }, 301 | { 302 | "key": "ctrl+4", 303 | "command": "vim.", 304 | "when": "vim.use_", 305 | "vimKey": "" 306 | }, 307 | { 308 | "key": "ctrl+q", 309 | "command": "vim.", 310 | "when": "vim.use_", 311 | "vimKey": "" 312 | }, 313 | { 314 | "key": "ctrl+i", 315 | "command": "vim.", 316 | "when": "vim.use_", 317 | "vimKey": "" 318 | }, 319 | { 320 | "key": "ctrl+m", 321 | "command": "vim.", 322 | "when": "vim.use_", 323 | "vimKey": "" 324 | }, 325 | { 326 | "key": "ctrl+g", 327 | "command": "vim.", 328 | "when": "vim.use_", 329 | "vimKey": "" 330 | }, 331 | { 332 | "key": "ctrl+s", 333 | "command": "vim.", 334 | "when": "vim.use_", 335 | "vimKey": "" 336 | }, 337 | { 338 | "key": "ctrl+z", 339 | "command": "vim.", 340 | "when": "vim.use_", 341 | "vimKey": "" 342 | }, 343 | { 344 | "key": "ctrl+t", 345 | "command": "vim.", 346 | "when": "vim.use_", 347 | "vimKey": "" 348 | }, 349 | { 350 | "key": "ctrl+>", 351 | "command": "vim.>", 352 | "when": "vim.use_>", 353 | "vimKey": ">" 354 | }, 355 | { 356 | "key": "ctrl+l", 357 | "command": "vim.", 358 | "when": "vim.use_", 359 | "vimKey": "" 360 | }, 361 | { 362 | "key": "ctrl+\"", 363 | "command": "vim.", 364 | "when": "vim.use_", 365 | "vimKey": "" 366 | }, 367 | { 368 | "key": "ctrl+a", 369 | "command": "vim.", 370 | "when": "vim.use_", 371 | "vimKey": "" 372 | }, 373 | { 374 | "key": "ctrl+u", 375 | "command": "vim.", 376 | "when": "vim.use_", 377 | "vimKey": "" 378 | }, 379 | { 380 | "key": "ctrl+<", 381 | "command": "vim.", 382 | "when": "vim.use_", 383 | "vimKey": "" 384 | }, 385 | { 386 | "key": "ctrl+f", 387 | "command": "vim.", 388 | "when": "vim.use_", 389 | "vimKey": "" 390 | }, 391 | { 392 | "key": "ctrl+`", 393 | "command": "vim.", 394 | "when": "vim.use_", 395 | "vimKey": "" 396 | }, 397 | { 398 | "key": "ctrl+#", 399 | "command": "vim.", 400 | "when": "vim.use_", 401 | "vimKey": "" 402 | }, 403 | { 404 | "key": "ctrl+p", 405 | "command": "vim.", 406 | "when": "vim.use_", 407 | "vimKey": "" 408 | }, 409 | { 410 | "key": "ctrl+2", 411 | "command": "vim.", 412 | "when": "vim.use_", 413 | "vimKey": "" 414 | }, 415 | { 416 | "key": "ctrl+,", 417 | "command": "vim.", 418 | "when": "vim.use_", 419 | "vimKey": "" 420 | }, 421 | { 422 | "key": "ctrl+9", 423 | "command": "vim.", 424 | "when": "vim.use_", 425 | "vimKey": "" 426 | }, 427 | { 428 | "key": "ctrl+'", 429 | "command": "vim.", 430 | "when": "vim.use_", 431 | "vimKey": "" 432 | }, 433 | { 434 | "key": "ctrl+?", 435 | "command": "vim.", 436 | "when": "vim.use_", 437 | "vimKey": "" 438 | }, 439 | { 440 | "key": "ctrl+o", 441 | "command": "vim.", 442 | "when": "vim.use_", 443 | "vimKey": "" 444 | }, 445 | { 446 | "key": "ctrl+}", 447 | "command": "vim.", 448 | "when": "vim.use_", 449 | "vimKey": "" 450 | }, 451 | { 452 | "key": "ctrl+-", 453 | "command": "vim.", 454 | "when": "vim.use_", 455 | "vimKey": "" 456 | }, 457 | { 458 | "key": "ctrl+)", 459 | "command": "vim.", 460 | "when": "vim.use_", 461 | "vimKey": "" 462 | }, 463 | { 464 | "key": "ctrl+]", 465 | "command": "vim.", 466 | "when": "vim.use_", 467 | "vimKey": "" 468 | }, 469 | { 470 | "key": "ctrl+c", 471 | "command": "vim.", 472 | "when": "vim.use_", 473 | "vimKey": "" 474 | }, 475 | { 476 | "key": "ctrl+v", 477 | "command": "vim.", 478 | "when": "vim.use_", 479 | "vimKey": "" 480 | }, 481 | { 482 | "key": "ctrl+x", 483 | "command": "vim.", 484 | "when": "vim.use_", 485 | "vimKey": "" 486 | }, 487 | { 488 | "key": "ctrl+%", 489 | "command": "vim.", 490 | "when": "vim.use_", 491 | "vimKey": "" 492 | }, 493 | { 494 | "key": "ctrl+y", 495 | "command": "vim.", 496 | "when": "vim.use_", 497 | "vimKey": "" 498 | }, 499 | { 500 | "key": "ctrl+;", 501 | "command": "vim.", 502 | "when": "vim.use_", 503 | "vimKey": "" 504 | }, 505 | { 506 | "key": "ctrl+\\", 507 | "command": "vim.", 508 | "when": "vim.use_", 509 | "vimKey": "" 510 | }, 511 | { 512 | "key": "ctrl+d", 513 | "command": "vim.", 514 | "when": "vim.use_", 515 | "vimKey": "" 516 | }, 517 | { 518 | "key": "ctrl+r", 519 | "command": "vim.", 520 | "when": "vim.use_", 521 | "vimKey": "" 522 | }, 523 | { 524 | "key": "ctrl+w", 525 | "command": "vim.", 526 | "when": "vim.use_", 527 | "vimKey": "" 528 | }, 529 | { 530 | "key": "ctrl+[", 531 | "command": "vim.", 532 | "when": "vim.use_", 533 | "vimKey": "" 534 | }, 535 | { 536 | "key": "ctrl+=", 537 | "command": "vim.", 538 | "when": "vim.use_", 539 | "vimKey": "" 540 | }, 541 | { 542 | "key": "ctrl+@", 543 | "command": "vim.", 544 | "when": "vim.use_", 545 | "vimKey": "" 546 | }, 547 | { 548 | "key": "ctrl+{", 549 | "command": "vim.", 550 | "when": "vim.use_", 551 | "vimKey": "" 552 | }, 553 | { 554 | "key": "ctrl+/", 555 | "command": "vim.", 556 | "when": "vim.use_", 557 | "vimKey": "" 558 | }, 559 | { 560 | "key": "ctrl+&", 561 | "command": "vim.", 562 | "when": "vim.use_", 563 | "vimKey": "" 564 | }, 565 | { 566 | "key": "ctrl+k", 567 | "command": "vim.", 568 | "when": "vim.use_", 569 | "vimKey": "" 570 | } 571 | ] 572 | }, 573 | "dependencies": { 574 | "@types/msgpack-lite": "^0.1.5", 575 | "@types/winston": "^2.3.6", 576 | "aurelia-task-queue": "^1.2.1", 577 | "clipboardy": "^1.1.4", 578 | "diff-match-patch": "^1.0.0", 579 | "lodash": "^4.17.4", 580 | "neovim": "3.5.0", 581 | "tmp": "0.0.33", 582 | "untildify": "^3.0.2" 583 | }, 584 | "description": "Vim emulation for Visual Studio Code", 585 | "devDependencies": { 586 | "@types/mocha": "^2.2.41", 587 | "@types/node": "^8.0.47", 588 | "gulp": "^3.9.1", 589 | "gulp-bump": "^2.8.0", 590 | "gulp-git": "^2.5.1", 591 | "gulp-inject-string": "^1.1.0", 592 | "gulp-shell": "^0.6.3", 593 | "gulp-tag-version": "^1.3.0", 594 | "gulp-tslint": "^8.1.1", 595 | "gulp-typings": "^2.0.4", 596 | "merge-stream": "^1.0.1", 597 | "mocha": "^3.4.2", 598 | "tslint": "^5.5.0", 599 | "typescript": "^2.5.3", 600 | "vscode": "^1.1.6" 601 | }, 602 | "displayName": "Vim", 603 | "engines": { 604 | "vscode": "^1.13.0" 605 | }, 606 | "galleryBanner": { 607 | "color": "#e3f4ff", 608 | "theme": "light" 609 | }, 610 | "homepage": "https://github.com/VSCodeVim/Vim", 611 | "icon": "images/icon.png", 612 | "keywords": [ 613 | "vim", 614 | "vi", 615 | "vscodevim" 616 | ], 617 | "license": "MIT", 618 | "main": "./out/extension", 619 | "name": "vim", 620 | "publisher": "vscodevim", 621 | "repository": { 622 | "type": "git", 623 | "url": "https://github.com/VSCodeVim/Vim.git" 624 | }, 625 | "scripts": { 626 | "compile": "tsc -watch -p ./", 627 | "postinstall": "node ./node_modules/vscode/bin/install && gulp init", 628 | "test": "node ./node_modules/vscode/bin/test", 629 | "vscode:prepublish": "tsc -p ./" 630 | }, 631 | "version": "1.0.0" 632 | } 633 | -------------------------------------------------------------------------------- /scripts/create_changelog.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import bs4 3 | import re 4 | 5 | GITHUB_URL = "https://github.com" 6 | f = open("../CHANGELOG.md", "w") 7 | 8 | r = requests.get('https://github.com/VSCodeVim/Vim/releases') 9 | while True: 10 | bs = bs4.BeautifulSoup(r.text, "html5lib") 11 | results = bs.select( 12 | '#js-repo-pjax-container > div.container.new-discussion-timeline.experiment-repo-nav > div.repository-content > div.release-timeline > div' 13 | ) 14 | for i in results: 15 | title = i.select_one("div.release-body.commit.open > div.release-header > h1 > a").text 16 | link = i.select_one("div.release-body.commit.open > div.release-header > h1 > a")["href"] 17 | tag = i.select_one("div.release-meta > ul > li > a > span").text 18 | date = i.select_one("div.release-body.commit.open > div.release-header > p > relative-time") 19 | date = date.text 20 | body = i.select_one("div.release-body.commit.open > div.markdown-body") 21 | if (body is None): 22 | continue 23 | f.write("# [" + tag + " " + title + "](" + GITHUB_URL + link + ")" + " (" + date + ")\n") 24 | # objects are all the paragraphs, headers, tags, etc. 25 | objects = body.select("p,h2,li") 26 | objects = sorted(objects, key=lambda a: str(body).index(str(a))) 27 | for i in objects: 28 | obj_text = i.decode_contents() 29 | links = i.select('a') 30 | for link in links: 31 | obj_text.replace(str(link), link.text) 32 | if re.match('^

', str(i)): 33 | f.write('\n' + obj_text + '\n') 34 | elif re.match('

  • ', str(i)): 35 | f.write('* ' + obj_text + '\n') 36 | elif re.match('

    ', str(i)): 37 | f.write('## ' + obj_text + '\n') 38 | next_page = bs.select( 39 | '#js-repo-pjax-container > div.container.new-discussion-timeline.experiment-repo-nav > div.repository-content > div.paginate-container > div > a' 40 | )[-1] 41 | if (next_page.text == "Previous"): 42 | break 43 | print(next_page['href']) 44 | r = requests.get(next_page['href']) -------------------------------------------------------------------------------- /scripts/keybind_gen.py: -------------------------------------------------------------------------------- 1 | import json 2 | package = open('../package.json').read() 3 | package = json.loads(package) 4 | keysToBind = ['space', 'left', 'right', 'up', 'down', 'esc', 'bs', 'tab', 'cr'] 5 | vimKeyToVS = {'esc': 'Escape', 'bs': 'backspace', 'cr': 'enter'} 6 | keybindings = [] 7 | for key in keysToBind: 8 | vsKey = key 9 | if key in vimKeyToVS: 10 | vsKey = vimKeyToVS[key] 11 | for modifier in ['ctrl']: 12 | modKey = '{0}+{1}'.format(modifier, vsKey) 13 | vimKey = '<{0}-{1}>'.format(modifier[0], key) 14 | keybind = {'key': modKey, 15 | 'command': 'vim.{0}'.format(vimKey), 16 | 'when': 'vim.use_{0}'.format(vimKey), 17 | 'vimKey': vimKey} 18 | keybindings.append(keybind) 19 | if len(key) > 1: 20 | key = '<{0}>'.format(key) 21 | keybind = {'key': vsKey, 'command': 'vim.{0}'.format( 22 | key), 'when': 'vim.use_{0}'.format(key), 'vimKey': key} 23 | keybindings.append(keybind) 24 | 25 | keysToBind = [] 26 | for i in range(ord('!'), ord('~') + 1): 27 | keysToBind.append(chr(i).lower()) 28 | 29 | keysToBind = list(set(keysToBind)) 30 | 31 | for key in keysToBind: 32 | vsKey = key 33 | if key in vimKeyToVS: 34 | vsKey = vimKeyToVS[key] 35 | modifier = 'ctrl' 36 | modKey = '{0}+{1}'.format(modifier, vsKey) 37 | vimKey = '<{0}-{1}>'.format(modifier[0], key) 38 | keybind = {'key': modKey, 39 | 'command': 'vim.{0}'.format(vimKey), 40 | 'when': 'vim.use_{0}'.format(vimKey), 41 | 'vimKey': vimKey} 42 | keybindings.append(keybind) 43 | 44 | 45 | package['contributes']['keybindings'] = keybindings 46 | open('../package.json', 'w').write(json.dumps(package, indent=2, sort_keys=False)) 47 | # keybind[] 48 | # // let vimToVSMap: {[key: string]: string[]} = { 49 | # // }; 50 | # // let vimToVSMap: {[key: string]: string} = { 51 | # // esc: 'escape', 52 | # // }; 53 | # for (let i='!'.charCodeAt(0); i <= '~'.charCodeAt(0); i + +) { 54 | # keysToBind.push(String.fromCharCode(i));} 55 | # for (let key of keysToBind) { 56 | # for (let modifier of['c', 's']) { 57 | # const modKey = `${modifier} -${key}`; 58 | -------------------------------------------------------------------------------- /src/common/motion/position.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as _ from 'lodash'; 4 | import * as vscode from 'vscode'; 5 | import { TextEditor } from './../../textEditor'; 6 | import { betterEscapeRegex } from './../../util'; 7 | 8 | /** 9 | * Represents a difference between two positions. Add it to a position 10 | * to get another position. Create it with the factory methods: 11 | * 12 | * - NewDiff 13 | * - NewBOLDiff 14 | */ 15 | export class PositionDiff { 16 | private _line: number; 17 | private _character: number; 18 | private _isBOLDiff: boolean; 19 | 20 | constructor(line: number, character: number) { 21 | this._line = line; 22 | this._character = character; 23 | } 24 | 25 | /** 26 | * Creates a new PositionDiff that always brings the cursor to the beginning of the line 27 | * when applied to a position. 28 | */ 29 | public static NewBOLDiff(line = 0, character = 0): PositionDiff { 30 | const result = new PositionDiff(line, character); 31 | 32 | result._isBOLDiff = true; 33 | return result; 34 | } 35 | 36 | /** 37 | * Add this PositionDiff to another PositionDiff. 38 | */ 39 | public addDiff(other: PositionDiff) { 40 | if (this._isBOLDiff || other._isBOLDiff) { 41 | throw new Error("johnfn hasn't done this case yet and doesnt want to"); 42 | } 43 | 44 | return new PositionDiff(this._line + other._line, this._character + other._character); 45 | } 46 | 47 | /** 48 | * Adds a Position to this PositionDiff, returning a new PositionDiff. 49 | */ 50 | public addPosition(other: Position, { boundsCheck = true } = {}): Position { 51 | let resultChar = this.isBOLDiff() ? 0 : this.character + other.character; 52 | let resultLine = this.line + other.line; 53 | 54 | if (boundsCheck) { 55 | if (resultChar < 0) { 56 | resultChar = 0; 57 | } 58 | if (resultLine < 0) { 59 | resultLine = 0; 60 | } 61 | } 62 | 63 | return new Position(resultLine, resultChar); 64 | } 65 | 66 | /** 67 | * Difference in lines. 68 | */ 69 | public get line(): number { 70 | return this._line; 71 | } 72 | 73 | /** 74 | * Difference in characters. 75 | */ 76 | public get character(): number { 77 | return this._character; 78 | } 79 | 80 | /** 81 | * Does this diff move the position to the beginning of the line? 82 | */ 83 | public isBOLDiff(): boolean { 84 | return this._isBOLDiff; 85 | } 86 | 87 | public toString(): string { 88 | if (this._isBOLDiff) { 89 | return `[ Diff: BOL ]`; 90 | } 91 | 92 | return `[ Diff: ${this._line} ${this._character} ]`; 93 | } 94 | } 95 | 96 | export class Position extends vscode.Position { 97 | private static NonWordCharacters = '/\\()"\':,.;<>~!@#$%^&*|+=[]{}`?-'; 98 | private static NonBigWordCharacters = ''; 99 | private static NonFileCharacters = '"\'`;<>{}[]()'; 100 | 101 | private _nonWordCharRegex: RegExp; 102 | private _nonBigWordCharRegex: RegExp; 103 | private _sentenceEndRegex: RegExp; 104 | private _nonFileNameRegex: RegExp; 105 | 106 | constructor(line: number, character: number) { 107 | super(line, character); 108 | 109 | this._nonWordCharRegex = this.makeWordRegex(Position.NonWordCharacters); 110 | this._nonBigWordCharRegex = this.makeWordRegex(Position.NonBigWordCharacters); 111 | this._sentenceEndRegex = /[\.!\?]{1}([ \n\t]+|$)/g; 112 | this._nonFileNameRegex = this.makeWordRegex(Position.NonFileCharacters); 113 | } 114 | 115 | public getRightThroughLineBreaks() { 116 | if (this.isAtDocumentEnd()) { 117 | // TODO(bell) 118 | return this; 119 | } 120 | 121 | if (this.isLineEnd()) { 122 | return this.getDown(0); 123 | } 124 | 125 | return this.getRight(); 126 | } 127 | 128 | public toString(): string { 129 | return `[${this.line}, ${this.character}]`; 130 | } 131 | 132 | public static FromVSCodePosition(pos: vscode.Position): Position { 133 | return new Position(pos.line, pos.character); 134 | } 135 | 136 | /** 137 | * Returns which of the 2 provided Positions comes earlier in the document. 138 | */ 139 | public static EarlierOf(p1: Position, p2: Position): Position { 140 | if (p1.line < p2.line) { 141 | return p1; 142 | } 143 | if (p1.line === p2.line && p1.character < p2.character) { 144 | return p1; 145 | } 146 | 147 | return p2; 148 | } 149 | 150 | public isEarlierThan(other: Position): boolean { 151 | if (this.line < other.line) { 152 | return true; 153 | } 154 | if (this.line === other.line && this.character < other.character) { 155 | return true; 156 | } 157 | 158 | return false; 159 | } 160 | 161 | /** 162 | * Iterates over every position in the document starting at start, returning 163 | * at every position the current line text, character text, and a position object. 164 | */ 165 | public static *IterateDocument( 166 | start: Position, 167 | forward = true 168 | ): Iterable<{ line: string; char: string; pos: Position }> { 169 | let lineIndex: number, charIndex: number; 170 | 171 | if (forward) { 172 | for (lineIndex = start.line; lineIndex < TextEditor.getLineCount(); lineIndex++) { 173 | charIndex = lineIndex === start.line ? start.character : 0; 174 | const line = TextEditor.getLineAt(new Position(lineIndex, 0)).text; 175 | 176 | for (; charIndex < line.length; charIndex++) { 177 | yield { 178 | line: line, 179 | char: line[charIndex], 180 | pos: new Position(lineIndex, charIndex), 181 | }; 182 | } 183 | } 184 | } else { 185 | for (lineIndex = start.line; lineIndex >= 0; lineIndex--) { 186 | const line = TextEditor.getLineAt(new Position(lineIndex, 0)).text; 187 | charIndex = lineIndex === start.line ? start.character : line.length - 1; 188 | 189 | for (; charIndex >= 0; charIndex--) { 190 | yield { 191 | line: line, 192 | char: line[charIndex], 193 | pos: new Position(lineIndex, charIndex), 194 | }; 195 | } 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * Iterate over every position in the block defined by the two positions passed in. 202 | */ 203 | public static *IterateBlock( 204 | topLeft: Position, 205 | bottomRight: Position 206 | ): Iterable<{ line: string; char: string; pos: Position }> { 207 | for (let lineIndex = topLeft.line; lineIndex <= bottomRight.line; lineIndex++) { 208 | const line = TextEditor.getLineAt(new Position(lineIndex, 0)).text; 209 | 210 | for (let charIndex = topLeft.character; charIndex < bottomRight.character + 1; charIndex++) { 211 | yield { 212 | line: line, 213 | char: line[charIndex], 214 | pos: new Position(lineIndex, charIndex), 215 | }; 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * Iterate over every position in the selection defined by the two positions passed in. 222 | */ 223 | public static *IterateSelection( 224 | topLeft: Position, 225 | bottomRight: Position 226 | ): Iterable<{ line: string; char: string; pos: Position }> { 227 | for (let lineIndex = topLeft.line; lineIndex <= bottomRight.line; lineIndex++) { 228 | const line = TextEditor.getLineAt(new Position(lineIndex, 0)).text; 229 | 230 | if (lineIndex === topLeft.line) { 231 | for (let charIndex = topLeft.character; charIndex < line.length + 1; charIndex++) { 232 | yield { 233 | line: line, 234 | char: line[charIndex], 235 | pos: new Position(lineIndex, charIndex), 236 | }; 237 | } 238 | } else if (lineIndex === bottomRight.line) { 239 | for (let charIndex = 0; charIndex < bottomRight.character + 1; charIndex++) { 240 | yield { 241 | line: line, 242 | char: line[charIndex], 243 | pos: new Position(lineIndex, charIndex), 244 | }; 245 | } 246 | } else { 247 | for (let charIndex = 0; charIndex < line.length + 1; charIndex++) { 248 | yield { 249 | line: line, 250 | char: line[charIndex], 251 | pos: new Position(lineIndex, charIndex), 252 | }; 253 | } 254 | } 255 | } 256 | } 257 | 258 | // Iterates through words on the same line, starting from the current position. 259 | public static *IterateWords( 260 | start: Position 261 | ): Iterable<{ start: Position; end: Position; word: string }> { 262 | const text = TextEditor.getLineAt(start).text; 263 | let wordEnd = start.getCurrentWordEnd(true); 264 | do { 265 | const word = text.substring(start.character, wordEnd.character + 1); 266 | yield { 267 | start: start, 268 | end: wordEnd, 269 | word: word, 270 | }; 271 | 272 | if (wordEnd.getRight().isLineEnd()) { 273 | return; 274 | } 275 | start = start.getWordRight(); 276 | wordEnd = start.getCurrentWordEnd(true); 277 | } while (true); 278 | } 279 | 280 | /** 281 | * Returns which of the 2 provided Positions comes later in the document. 282 | */ 283 | public static LaterOf(p1: Position, p2: Position): Position { 284 | if (Position.EarlierOf(p1, p2) === p1) { 285 | return p2; 286 | } 287 | 288 | return p1; 289 | } 290 | 291 | /** 292 | * Subtracts another position from this one, returning the 293 | * difference between the two. 294 | */ 295 | public subtract(other: Position): PositionDiff { 296 | return new PositionDiff(this.line - other.line, this.character - other.character); 297 | } 298 | 299 | /** 300 | * Adds a PositionDiff to this position, returning a new 301 | * position. 302 | */ 303 | public add(diff: PositionDiff, { boundsCheck = true } = {}): Position { 304 | let resultChar = this.character + diff.character; 305 | let resultLine = this.line + diff.line; 306 | 307 | if (diff.isBOLDiff()) { 308 | resultChar = diff.character; 309 | } 310 | 311 | if (boundsCheck) { 312 | if (resultChar < 0) { 313 | resultChar = 0; 314 | } 315 | if (resultLine < 0) { 316 | resultLine = 0; 317 | } 318 | if (resultLine >= TextEditor.getLineCount() - 1) { 319 | resultLine = TextEditor.getLineCount() - 1; 320 | } 321 | } 322 | 323 | return new Position(resultLine, resultChar); 324 | } 325 | 326 | public setLocation(line: number, character: number): Position { 327 | let position = new Position(line, character); 328 | return position; 329 | } 330 | 331 | /** 332 | * Gets the position one to the left of this position. Does not go up line 333 | * breaks. 334 | */ 335 | public getLeft(): Position { 336 | if (!this.isLineBeginning()) { 337 | return new Position(this.line, this.character - 1); 338 | } 339 | 340 | return this; 341 | } 342 | 343 | /** 344 | * Same as getLeft, but goes up to the previous line on line 345 | * breaks. 346 | * 347 | * Equivalent to left arrow (in a non-vim editor!) 348 | */ 349 | public getLeftThroughLineBreaks(includeEol = true): Position { 350 | if (!this.isLineBeginning()) { 351 | return this.getLeft(); 352 | } 353 | 354 | // First char on first line, can not go left any more 355 | if (this.line === 0) { 356 | return this; 357 | } 358 | 359 | if (includeEol) { 360 | return this.getUp(0).getLineEnd(); 361 | } else { 362 | return this.getUp(0) 363 | .getLineEnd() 364 | .getLeft(); 365 | } 366 | } 367 | 368 | public getRight(count: number = 1): Position { 369 | if (!this.isLineEnd()) { 370 | return new Position(this.line, this.character + count); 371 | } 372 | 373 | return this; 374 | } 375 | 376 | /** 377 | * Get the position of the line directly below the current line. 378 | */ 379 | public getDown(desiredColumn: number): Position { 380 | if (this.getDocumentEnd().line !== this.line) { 381 | let nextLine = this.line + 1; 382 | let nextLineLength = Position.getLineLength(nextLine); 383 | 384 | return new Position(nextLine, Math.min(nextLineLength, desiredColumn)); 385 | } 386 | 387 | return this; 388 | } 389 | 390 | /** 391 | * Get the position of the line directly above the current line. 392 | */ 393 | public getUp(desiredColumn: number): Position { 394 | if (this.getDocumentBegin().line !== this.line) { 395 | let prevLine = this.line - 1; 396 | let prevLineLength = Position.getLineLength(prevLine); 397 | 398 | return new Position(prevLine, Math.min(prevLineLength, desiredColumn)); 399 | } 400 | 401 | return this; 402 | } 403 | 404 | /** 405 | * Get the position *count* lines down from this position, but not lower 406 | * than the end of the document. 407 | */ 408 | public getDownByCount(count = 0, { boundsCheck = true } = {}): Position { 409 | const line = boundsCheck 410 | ? Math.min(TextEditor.getLineCount() - 1, this.line + count) 411 | : this.line + count; 412 | 413 | return new Position(line, this.character); 414 | } 415 | 416 | /** 417 | * Get the position *count* lines up from this position, but not higher 418 | * than the end of the document. 419 | */ 420 | public getUpByCount(count = 0): Position { 421 | return new Position(Math.max(0, this.line - count), this.character); 422 | } 423 | 424 | /** 425 | * Get the position *count* lines left from this position, but not farther 426 | * than the beginning of the line 427 | */ 428 | public getLeftByCount(count = 0): Position { 429 | return new Position(this.line, Math.max(0, this.character - count)); 430 | } 431 | 432 | /** 433 | * Get the position *count* lines right from this position, but not farther 434 | * than the end of the line 435 | */ 436 | public getRightByCount(count = 0): Position { 437 | return new Position( 438 | this.line, 439 | Math.min(TextEditor.getLineAt(this).text.length - 1, this.character + count) 440 | ); 441 | } 442 | 443 | /** 444 | * Inclusive is true if we consider the current position a valid result, false otherwise. 445 | */ 446 | public getWordLeft(inclusive: boolean = false): Position { 447 | return this.getWordLeftWithRegex(this._nonWordCharRegex, inclusive); 448 | } 449 | 450 | public getBigWordLeft(inclusive: boolean = false): Position { 451 | return this.getWordLeftWithRegex(this._nonBigWordCharRegex, inclusive); 452 | } 453 | 454 | public getFilePathLeft(inclusive: boolean = false): Position { 455 | return this.getWordLeftWithRegex(this._nonFileNameRegex, inclusive); 456 | } 457 | 458 | /** 459 | * Inclusive is true if we consider the current position a valid result, false otherwise. 460 | */ 461 | public getWordRight(inclusive: boolean = false): Position { 462 | return this.getWordRightWithRegex(this._nonWordCharRegex, inclusive); 463 | } 464 | 465 | public getBigWordRight(inclusive: boolean = false): Position { 466 | return this.getWordRightWithRegex(this._nonBigWordCharRegex); 467 | } 468 | 469 | public getFilePathRight(inclusive: boolean = false): Position { 470 | return this.getWordRightWithRegex(this._nonFileNameRegex, inclusive); 471 | } 472 | 473 | public getLastWordEnd(): Position { 474 | return this.getLastWordEndWithRegex(this._nonWordCharRegex); 475 | } 476 | 477 | public getLastBigWordEnd(): Position { 478 | return this.getLastWordEndWithRegex(this._nonBigWordCharRegex); 479 | } 480 | 481 | /** 482 | * Inclusive is true if we consider the current position a valid result, false otherwise. 483 | */ 484 | public getCurrentWordEnd(inclusive: boolean = false): Position { 485 | return this.getCurrentWordEndWithRegex(this._nonWordCharRegex, inclusive); 486 | } 487 | 488 | /** 489 | * Inclusive is true if we consider the current position a valid result, false otherwise. 490 | */ 491 | public getCurrentBigWordEnd(inclusive: boolean = false): Position { 492 | return this.getCurrentWordEndWithRegex(this._nonBigWordCharRegex, inclusive); 493 | } 494 | 495 | /** 496 | * Get the boundary position of the section. 497 | */ 498 | public getSectionBoundary(args: { forward: boolean; boundary: string }): Position { 499 | let pos: Position = this; 500 | 501 | if ( 502 | (args.forward && pos.line === TextEditor.getLineCount() - 1) || 503 | (!args.forward && pos.line === 0) 504 | ) { 505 | return pos.getFirstLineNonBlankChar(); 506 | } 507 | 508 | pos = args.forward ? pos.getDown(0) : pos.getUp(0); 509 | 510 | while (!TextEditor.getLineAt(pos).text.startsWith(args.boundary)) { 511 | if (args.forward) { 512 | if (pos.line === TextEditor.getLineCount() - 1) { 513 | break; 514 | } 515 | 516 | pos = pos.getDown(0); 517 | } else { 518 | if (pos.line === 0) { 519 | break; 520 | } 521 | 522 | pos = pos.getUp(0); 523 | } 524 | } 525 | 526 | return pos.getFirstLineNonBlankChar(); 527 | } 528 | 529 | /** 530 | * Get the end of the current paragraph. 531 | */ 532 | public getCurrentParagraphEnd(): Position { 533 | let pos: Position = this; 534 | 535 | // If we're not in a paragraph yet, go down until we are. 536 | while (TextEditor.getLineAt(pos).text === '' && !TextEditor.isLastLine(pos)) { 537 | pos = pos.getDown(0); 538 | } 539 | 540 | // Go until we're outside of the paragraph, or at the end of the document. 541 | while (TextEditor.getLineAt(pos).text !== '' && pos.line < TextEditor.getLineCount() - 1) { 542 | pos = pos.getDown(0); 543 | } 544 | 545 | return pos.getLineEnd(); 546 | } 547 | 548 | /** 549 | * Get the beginning of the current paragraph. 550 | */ 551 | public getCurrentParagraphBeginning(): Position { 552 | let pos: Position = this; 553 | 554 | // If we're not in a paragraph yet, go up until we are. 555 | while (TextEditor.getLineAt(pos).text === '' && !TextEditor.isFirstLine(pos)) { 556 | pos = pos.getUp(0); 557 | } 558 | 559 | // Go until we're outside of the paragraph, or at the beginning of the document. 560 | while (pos.line > 0 && TextEditor.getLineAt(pos).text !== '') { 561 | pos = pos.getUp(0); 562 | } 563 | 564 | return pos.getLineBegin(); 565 | } 566 | 567 | public getSentenceBegin(args: { forward: boolean }): Position { 568 | if (args.forward) { 569 | return this.getNextSentenceBeginWithRegex(this._sentenceEndRegex, false); 570 | } else { 571 | return this.getPreviousSentenceBeginWithRegex(this._sentenceEndRegex, false); 572 | } 573 | } 574 | 575 | public getCurrentSentenceEnd(): Position { 576 | return this.getCurrentSentenceEndWithRegex(this._sentenceEndRegex, false); 577 | } 578 | 579 | /** 580 | * Get the beginning of the current line. 581 | */ 582 | public getLineBegin(): Position { 583 | return new Position(this.line, 0); 584 | } 585 | 586 | /** 587 | * Get the beginning of the next line. 588 | */ 589 | public getPreviousLineBegin(): Position { 590 | if (this.line === 0) { 591 | return this.getLineBegin(); 592 | } 593 | 594 | return new Position(this.line - 1, 0); 595 | } 596 | 597 | /** 598 | * Get the beginning of the next line. 599 | */ 600 | public getNextLineBegin(): Position { 601 | if (this.line >= TextEditor.getLineCount() - 1) { 602 | return this.getLineEnd(); 603 | } 604 | 605 | return new Position(this.line + 1, 0); 606 | } 607 | 608 | /** 609 | * Returns a new position at the end of this position's line. 610 | */ 611 | public getLineEnd(): Position { 612 | return new Position(this.line, Position.getLineLength(this.line)); 613 | } 614 | 615 | /** 616 | * Returns a new position at the end of this position's line, including the 617 | * invisible newline character. 618 | */ 619 | public getLineEndIncludingEOL(): Position { 620 | return new Position(this.line, Position.getLineLength(this.line) + 1); 621 | } 622 | 623 | public getDocumentBegin(): Position { 624 | return new Position(0, 0); 625 | } 626 | 627 | /** 628 | * Returns a new Position one to the left if this position is on the EOL. Otherwise, 629 | * returns this position. 630 | */ 631 | public getLeftIfEOL(): Position { 632 | if (this.character === Position.getLineLength(this.line)) { 633 | return this.getLeft(); 634 | } else { 635 | return this; 636 | } 637 | } 638 | 639 | /** 640 | * Get the position that the cursor would be at if you 641 | * pasted *text* at the current position. 642 | */ 643 | public advancePositionByText(text: string): Position { 644 | const numberOfLinesSpanned = (text.match(/\n/g) || []).length; 645 | 646 | return new Position( 647 | this.line + numberOfLinesSpanned, 648 | numberOfLinesSpanned === 0 649 | ? this.character + text.length 650 | : text.length - (text.lastIndexOf('\n') + 1) 651 | ); 652 | } 653 | 654 | public getDocumentEnd(): Position { 655 | let lineCount = TextEditor.getLineCount(); 656 | let line = lineCount > 0 ? lineCount - 1 : 0; 657 | let char = Position.getLineLength(line); 658 | 659 | return new Position(line, char); 660 | } 661 | 662 | public isValid(): boolean { 663 | try { 664 | // line 665 | let lineCount = TextEditor.getLineCount() || 1; 666 | if (this.line >= lineCount) { 667 | return false; 668 | } 669 | 670 | // char 671 | let charCount = Position.getLineLength(this.line); 672 | if (this.character > charCount + 1) { 673 | return false; 674 | } 675 | } catch (e) { 676 | return false; 677 | } 678 | 679 | return true; 680 | } 681 | 682 | /** 683 | * Is this position at the beginning of the line? 684 | */ 685 | public isLineBeginning(): boolean { 686 | return this.character === 0; 687 | } 688 | 689 | /** 690 | * Is this position at the end of the line? 691 | */ 692 | public isLineEnd(): boolean { 693 | return this.character >= Position.getLineLength(this.line); 694 | } 695 | 696 | public isFirstWordOfLine(): boolean { 697 | return Position.getFirstNonBlankCharAtLine(this.line) === this.character; 698 | } 699 | 700 | public isAtDocumentEnd(): boolean { 701 | return this.line === TextEditor.getLineCount() - 1 && this.isLineEnd(); 702 | } 703 | 704 | /** 705 | * Returns whether the current position is in the leading whitespace of a line 706 | * @param allowEmpty : Use true if "" is valid 707 | */ 708 | public isInLeadingWhitespace(allowEmpty: boolean = false): boolean { 709 | if (allowEmpty) { 710 | return /^\s*$/.test(TextEditor.getText(new vscode.Range(this.getLineBegin(), this))); 711 | } else { 712 | return /^\s+$/.test(TextEditor.getText(new vscode.Range(this.getLineBegin(), this))); 713 | } 714 | } 715 | 716 | public static getFirstNonBlankCharAtLine(line: number): number { 717 | return TextEditor.readLineAt(line).match(/^\s*/)![0].length; 718 | } 719 | 720 | /** 721 | * The position of the first character on this line which is not whitespace. 722 | */ 723 | public getFirstLineNonBlankChar(): Position { 724 | return new Position(this.line, Position.getFirstNonBlankCharAtLine(this.line)); 725 | } 726 | 727 | public static getLineLength(line: number): number { 728 | return TextEditor.readLineAt(line).length; 729 | } 730 | 731 | private makeWordRegex(characterSet: string): RegExp { 732 | let escaped = characterSet && betterEscapeRegex(characterSet); 733 | let segments: string[] = []; 734 | 735 | segments.push(`([^\\s${escaped}]+)`); 736 | segments.push(`[${escaped}]+`); 737 | segments.push(`$^`); 738 | let result = new RegExp(segments.join('|'), 'g'); 739 | 740 | return result; 741 | } 742 | 743 | private getAllPositions(line: string, regex: RegExp): number[] { 744 | let positions: number[] = []; 745 | let result = regex.exec(line); 746 | 747 | while (result) { 748 | positions.push(result.index); 749 | 750 | // Handles the case where an empty string match causes lastIndex not to advance, 751 | // which gets us in an infinite loop. 752 | if (result.index === regex.lastIndex) { 753 | regex.lastIndex++; 754 | } 755 | result = regex.exec(line); 756 | } 757 | 758 | return positions; 759 | } 760 | 761 | private getAllEndPositions(line: string, regex: RegExp): number[] { 762 | let positions: number[] = []; 763 | let result = regex.exec(line); 764 | 765 | while (result) { 766 | if (result[0].length) { 767 | positions.push(result.index + result[0].length - 1); 768 | } 769 | 770 | // Handles the case where an empty string match causes lastIndex not to advance, 771 | // which gets us in an infinite loop. 772 | if (result.index === regex.lastIndex) { 773 | regex.lastIndex++; 774 | } 775 | result = regex.exec(line); 776 | } 777 | 778 | return positions; 779 | } 780 | 781 | /** 782 | * Inclusive is true if we consider the current position a valid result, false otherwise. 783 | */ 784 | private getWordLeftWithRegex(regex: RegExp, inclusive: boolean = false): Position { 785 | for (let currentLine = this.line; currentLine >= 0; currentLine--) { 786 | let positions = this.getAllPositions( 787 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 788 | regex 789 | ); 790 | let newCharacter = _.find( 791 | positions.reverse(), 792 | index => 793 | (index < this.character && !inclusive) || 794 | (index <= this.character && inclusive) || 795 | currentLine !== this.line 796 | ); 797 | 798 | if (newCharacter !== undefined) { 799 | return new Position(currentLine, newCharacter); 800 | } 801 | } 802 | 803 | return new Position(0, 0).getLineBegin(); 804 | } 805 | 806 | /** 807 | * Inclusive is true if we consider the current position a valid result, false otherwise. 808 | */ 809 | private getWordRightWithRegex(regex: RegExp, inclusive: boolean = false): Position { 810 | for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) { 811 | let positions = this.getAllPositions( 812 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 813 | regex 814 | ); 815 | let newCharacter = _.find( 816 | positions, 817 | index => 818 | (index > this.character && !inclusive) || 819 | (index >= this.character && inclusive) || 820 | currentLine !== this.line 821 | ); 822 | 823 | if (newCharacter !== undefined) { 824 | return new Position(currentLine, newCharacter); 825 | } 826 | } 827 | 828 | return new Position(TextEditor.getLineCount() - 1, 0).getLineEnd(); 829 | } 830 | 831 | private getLastWordEndWithRegex(regex: RegExp): Position { 832 | for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) { 833 | let positions = this.getAllEndPositions( 834 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 835 | regex 836 | ); 837 | let index = _.findIndex(positions, i => i >= this.character || currentLine !== this.line); 838 | let newCharacter = 0; 839 | if (index === -1) { 840 | newCharacter = positions[positions.length - 1]; 841 | } else if (index > 0) { 842 | newCharacter = positions[index - 1]; 843 | } 844 | 845 | if (newCharacter !== undefined) { 846 | return new Position(currentLine, newCharacter); 847 | } 848 | } 849 | 850 | return new Position(TextEditor.getLineCount() - 1, 0).getLineEnd(); 851 | } 852 | 853 | /** 854 | * Inclusive is true if we consider the current position a valid result, false otherwise. 855 | */ 856 | private getCurrentWordEndWithRegex(regex: RegExp, inclusive: boolean): Position { 857 | for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) { 858 | let positions = this.getAllEndPositions( 859 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 860 | regex 861 | ); 862 | let newCharacter = _.find( 863 | positions, 864 | index => 865 | (index > this.character && !inclusive) || 866 | (index >= this.character && inclusive) || 867 | currentLine !== this.line 868 | ); 869 | 870 | if (newCharacter !== undefined) { 871 | return new Position(currentLine, newCharacter); 872 | } 873 | } 874 | 875 | return new Position(TextEditor.getLineCount() - 1, 0).getLineEnd(); 876 | } 877 | 878 | private getPreviousSentenceBeginWithRegex(regex: RegExp, inclusive: boolean): Position { 879 | let paragraphBegin = this.getCurrentParagraphBeginning(); 880 | for (let currentLine = this.line; currentLine >= paragraphBegin.line; currentLine--) { 881 | let endPositions = this.getAllEndPositions( 882 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 883 | regex 884 | ); 885 | let newCharacter = _.find( 886 | endPositions.reverse(), 887 | index => 888 | (index < this.character && 889 | !inclusive && 890 | new Position(currentLine, index).getRightThroughLineBreaks().compareTo(this)) || 891 | (index <= this.character && inclusive) || 892 | currentLine !== this.line 893 | ); 894 | 895 | if (newCharacter !== undefined) { 896 | return new Position(currentLine, newCharacter).getRightThroughLineBreaks(); 897 | } 898 | } 899 | 900 | if (paragraphBegin.line + 1 === this.line || paragraphBegin.line === this.line) { 901 | return paragraphBegin; 902 | } else { 903 | return new Position(paragraphBegin.line + 1, 0); 904 | } 905 | } 906 | 907 | private getNextSentenceBeginWithRegex(regex: RegExp, inclusive: boolean): Position { 908 | // A paragraph and section boundary is also a sentence boundary. 909 | let paragraphEnd = this.getCurrentParagraphEnd(); 910 | for (let currentLine = this.line; currentLine <= paragraphEnd.line; currentLine++) { 911 | let endPositions = this.getAllEndPositions( 912 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 913 | regex 914 | ); 915 | let newCharacter = _.find( 916 | endPositions, 917 | index => 918 | (index > this.character && !inclusive) || 919 | (index >= this.character && inclusive) || 920 | currentLine !== this.line 921 | ); 922 | 923 | if (newCharacter !== undefined) { 924 | return new Position(currentLine, newCharacter).getRightThroughLineBreaks(); 925 | } 926 | } 927 | 928 | return this.getFirstNonWhitespaceInParagraph(paragraphEnd, inclusive); 929 | } 930 | 931 | private getCurrentSentenceEndWithRegex(regex: RegExp, inclusive: boolean): Position { 932 | let paragraphEnd = this.getCurrentParagraphEnd(); 933 | for (let currentLine = this.line; currentLine <= paragraphEnd.line; currentLine++) { 934 | let allPositions = this.getAllPositions( 935 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 936 | regex 937 | ); 938 | let newCharacter = _.find( 939 | allPositions, 940 | index => 941 | (index > this.character && !inclusive) || 942 | (index >= this.character && inclusive) || 943 | currentLine !== this.line 944 | ); 945 | 946 | if (newCharacter !== undefined) { 947 | return new Position(currentLine, newCharacter); 948 | } 949 | } 950 | 951 | return this.getFirstNonWhitespaceInParagraph(paragraphEnd, inclusive); 952 | } 953 | 954 | private getFirstNonWhitespaceInParagraph(paragraphEnd: Position, inclusive: boolean): Position { 955 | // If the cursor is at an empty line, it's the end of a paragraph and the begin of another paragraph 956 | // Find the first non-whitepsace character. 957 | if (TextEditor.getLineAt(new vscode.Position(this.line, 0)).text) { 958 | return paragraphEnd; 959 | } else { 960 | for (let currentLine = this.line; currentLine <= paragraphEnd.line; currentLine++) { 961 | let nonWhitePositions = this.getAllPositions( 962 | TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, 963 | /\S/g 964 | ); 965 | let newCharacter = _.find( 966 | nonWhitePositions, 967 | index => 968 | (index > this.character && !inclusive) || 969 | (index >= this.character && inclusive) || 970 | currentLine !== this.line 971 | ); 972 | 973 | if (newCharacter !== undefined) { 974 | return new Position(currentLine, newCharacter); 975 | } 976 | } 977 | } 978 | 979 | throw new Error('This should never happen...'); 980 | } 981 | 982 | private findHelper(char: string, count: number, direction: number): Position | undefined { 983 | // -1 = backwards, +1 = forwards 984 | const line = TextEditor.getLineAt(this); 985 | let index = this.character; 986 | 987 | while (count && index !== -1) { 988 | if (direction > 0) { 989 | index = line.text.indexOf(char, index + direction); 990 | } else { 991 | index = line.text.lastIndexOf(char, index + direction); 992 | } 993 | count--; 994 | } 995 | 996 | if (index > -1) { 997 | return new Position(this.line, index); 998 | } 999 | 1000 | return undefined; 1001 | } 1002 | 1003 | public tilForwards(char: string, count: number = 1): Position | null { 1004 | const position = this.findHelper(char, count, +1); 1005 | if (!position) { 1006 | return null; 1007 | } 1008 | 1009 | return new Position(this.line, position.character - 1); 1010 | } 1011 | 1012 | public tilBackwards(char: string, count: number = 1): Position | null { 1013 | const position = this.findHelper(char, count, -1); 1014 | if (!position) { 1015 | return null; 1016 | } 1017 | 1018 | return new Position(this.line, position.character + 1); 1019 | } 1020 | 1021 | public findForwards(char: string, count: number = 1): Position | null { 1022 | const position = this.findHelper(char, count, +1); 1023 | if (!position) { 1024 | return null; 1025 | } 1026 | 1027 | return new Position(this.line, position.character); 1028 | } 1029 | 1030 | public findBackwards(char: string, count: number = 1): Position | null { 1031 | const position = this.findHelper(char, count, -1); 1032 | if (!position) { 1033 | return null; 1034 | } 1035 | 1036 | return position; 1037 | } 1038 | } 1039 | -------------------------------------------------------------------------------- /src/common/motion/range.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { Position, PositionDiff } from './position'; 5 | 6 | export class Range { 7 | private _start: Position; 8 | private _stop: Position; 9 | 10 | public get start(): Position { 11 | return this._start; 12 | } 13 | 14 | public get stop(): Position { 15 | return this._stop; 16 | } 17 | 18 | constructor(start: Position, stop: Position) { 19 | this._start = start; 20 | this._stop = stop; 21 | } 22 | 23 | /** 24 | * Create a range from a VSCode selection. 25 | */ 26 | public static FromVSCodeSelection(e: vscode.Selection): Range { 27 | return new Range(Position.FromVSCodePosition(e.start), Position.FromVSCodePosition(e.end)); 28 | } 29 | 30 | public static *IterateRanges( 31 | list: Range[] 32 | ): Iterable<{ start: Position; stop: Position; range: Range; i: number }> { 33 | for (let i = 0; i < list.length; i++) { 34 | yield { 35 | i, 36 | range: list[i], 37 | start: list[i]._start, 38 | stop: list[i]._stop, 39 | }; 40 | } 41 | } 42 | 43 | public getRight(count = 1): Range { 44 | return new Range(this._start.getRight(count), this._stop.getRight(count)); 45 | } 46 | 47 | public getDown(count = 1): Range { 48 | return new Range(this._start.getDownByCount(count), this._stop.getDownByCount(count)); 49 | } 50 | 51 | public equals(other: Range): boolean { 52 | return this._start.isEqual(other._start) && this._stop.isEqual(other._stop); 53 | } 54 | 55 | /** 56 | * Returns a new Range which is the same as this Range, but with the provided 57 | * stop value. 58 | */ 59 | public withNewStop(stop: Position): Range { 60 | return new Range(this._start, stop); 61 | } 62 | 63 | /** 64 | * Returns a new Range which is the same as this Range, but with the provided 65 | * start value. 66 | */ 67 | public withNewStart(start: Position): Range { 68 | return new Range(start, this._stop); 69 | } 70 | 71 | public toString(): string { 72 | return `[ ${this.start.toString()} | ${this.stop.toString()}]`; 73 | } 74 | 75 | public overlaps(other: Range): boolean { 76 | return this.start.isBefore(other.stop) && other.start.isBefore(this.stop); 77 | } 78 | 79 | public add(diff: PositionDiff): Range { 80 | return new Range(this.start.add(diff), this.stop.add(diff)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/configuration/configuration.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { IgnoredKeys } from '../../srcNV/screen'; 5 | import { Globals } from '../../src/globals'; 6 | 7 | export type OptionValue = number | string | boolean; 8 | export type ValueMapping = { 9 | [key: number]: OptionValue; 10 | [key: string]: OptionValue; 11 | }; 12 | 13 | /** 14 | * Every Vim option we support should 15 | * 1. Be added to contribution section of `package.json`. 16 | * 2. Named as `vim.{optionName}`, `optionName` is the name we use in Vim. 17 | * 3. Define a public property in `Configuration `with the same name and a default value. 18 | * Or define a private propery and define customized Getter/Setter accessors for it. 19 | * Always remember to decorate Getter accessor as @enumerable() 20 | * 4. If user doesn't set the option explicitly 21 | * a. we don't have a similar setting in Code, initialize the option as default value. 22 | * b. we have a similar setting in Code, use Code's setting. 23 | * 24 | * Vim option override sequence. 25 | * 1. `:set {option}` on the fly 26 | * 2. TODO .vimrc. 27 | * 2. `vim.{option}` 28 | * 3. VS Code configuration 29 | * 4. VSCodeVim flavored Vim option default values 30 | * 31 | */ 32 | class ConfigurationClass { 33 | private static _instance: ConfigurationClass | null; 34 | 35 | constructor() { 36 | this.updateConfiguration(); 37 | } 38 | 39 | public static getInstance(): ConfigurationClass { 40 | if (ConfigurationClass._instance == null) { 41 | ConfigurationClass._instance = new ConfigurationClass(); 42 | } 43 | 44 | return ConfigurationClass._instance; 45 | } 46 | 47 | updateConfiguration() { 48 | /** 49 | * Load Vim options from User Settings. 50 | */ 51 | let vimOptions = vscode.workspace.getConfiguration('vim'); 52 | /* tslint:disable:forin */ 53 | // Disable forin rule here as we make accessors enumerable.` 54 | for (const option in this) { 55 | const vimOptionValue = vimOptions[option] as any; 56 | if (vimOptionValue !== null && vimOptionValue !== undefined) { 57 | this[option] = vimOptionValue; 58 | } 59 | } 60 | 61 | // Get the cursor type from vscode 62 | const cursorStyleString = vscode.workspace 63 | .getConfiguration() 64 | .get('editor.cursorStyle') as string; 65 | this.userCursor = this.cursorStyleFromString(cursorStyleString); 66 | } 67 | 68 | private cursorStyleFromString(cursorStyle: string): vscode.TextEditorCursorStyle { 69 | const cursorType = { 70 | line: vscode.TextEditorCursorStyle.Line, 71 | block: vscode.TextEditorCursorStyle.Block, 72 | underline: vscode.TextEditorCursorStyle.Underline, 73 | 'line-thin': vscode.TextEditorCursorStyle.LineThin, 74 | 'block-outline': vscode.TextEditorCursorStyle.BlockOutline, 75 | 'underline-thin': vscode.TextEditorCursorStyle.UnderlineThin, 76 | }; 77 | 78 | if (cursorType[cursorStyle] !== undefined) { 79 | return cursorType[cursorStyle]; 80 | } else { 81 | return vscode.TextEditorCursorStyle.Line; 82 | } 83 | } 84 | 85 | /** 86 | * Keys to be ignored and NOT handled by the extensions 87 | */ 88 | ignoreKeys: IgnoredKeys = 89 | { 90 | all: [''], 91 | normal: [''], 92 | insert: [''], 93 | visual: [''] 94 | }; 95 | 96 | 97 | /** 98 | * Type of cursor user is using native to vscode 99 | */ 100 | userCursor: number | undefined; 101 | 102 | neovimPath: string = ''; 103 | } 104 | 105 | export const Configuration = ConfigurationClass.getInstance(); 106 | -------------------------------------------------------------------------------- /src/globals.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * globals.ts hold some globals used throughout the extension 3 | */ 4 | 5 | export class Globals { 6 | // true for running tests, false during regular runtime 7 | public static isTesting = false; 8 | 9 | public static WhitespaceRegExp = new RegExp("^ *$"); 10 | 11 | // false for disabling Vim temporarily 12 | public static active = true; 13 | } -------------------------------------------------------------------------------- /src/textEditor.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { Position, PositionDiff } from './common/motion/position'; 5 | import { Globals } from './globals'; 6 | 7 | export class TextEditor { 8 | // TODO: Refactor args 9 | 10 | /** 11 | * Do not use this method! It has been deprecated. Use InsertTextTransformation 12 | * (or possibly InsertTextVSCodeTransformation) instead. 13 | */ 14 | static async insert( 15 | text: string, 16 | at: Position | undefined = undefined, 17 | letVSCodeHandleKeystrokes: boolean | undefined = undefined 18 | ): Promise { 19 | // If we insert "blah(" with default:type, VSCode will insert the closing ). 20 | // We *probably* don't want that to happen if we're inserting a lot of text. 21 | if (letVSCodeHandleKeystrokes === undefined) { 22 | letVSCodeHandleKeystrokes = text.length === 1; 23 | } 24 | 25 | if (!letVSCodeHandleKeystrokes) { 26 | // const selections = vscode.window.activeTextEditor!.selections.slice(0); 27 | 28 | await vscode.window.activeTextEditor!.edit(editBuilder => { 29 | if (!at) { 30 | at = Position.FromVSCodePosition(vscode.window.activeTextEditor!.selection.active); 31 | } 32 | 33 | editBuilder.insert(at!, text); 34 | }); 35 | 36 | // maintain all selections in multi-cursor mode. 37 | // vscode.window.activeTextEditor!.selections = selections; 38 | } else { 39 | await vscode.commands.executeCommand('default:type', { text }); 40 | } 41 | 42 | return true; 43 | } 44 | 45 | static async insertAt(text: string, position: vscode.Position): Promise { 46 | return vscode.window.activeTextEditor!.edit(editBuilder => { 47 | editBuilder.insert(position, text); 48 | }); 49 | } 50 | 51 | static async delete(range: vscode.Range): Promise { 52 | return vscode.window.activeTextEditor!.edit(editBuilder => { 53 | editBuilder.delete(range); 54 | }); 55 | } 56 | 57 | static getDocumentVersion(): number { 58 | return vscode.window.activeTextEditor!.document.version; 59 | } 60 | 61 | static getDocumentName(): String { 62 | return vscode.window.activeTextEditor!.document.fileName; 63 | } 64 | 65 | /** 66 | * Removes all text in the entire document. 67 | */ 68 | static async deleteDocument(): Promise { 69 | const start = new vscode.Position(0, 0); 70 | const lastLine = vscode.window.activeTextEditor!.document.lineCount - 1; 71 | const end = vscode.window.activeTextEditor!.document.lineAt(lastLine).range.end; 72 | const range = new vscode.Range(start, end); 73 | 74 | return vscode.window.activeTextEditor!.edit(editBuilder => { 75 | editBuilder.delete(range); 76 | }); 77 | } 78 | 79 | /** 80 | * Do not use this method! It has been deprecated. Use ReplaceTextTransformation. 81 | * instead. 82 | */ 83 | static async replace(range: vscode.Range, text: string): Promise { 84 | return vscode.window.activeTextEditor!.edit(editBuilder => { 85 | editBuilder.replace(range, text); 86 | }); 87 | } 88 | 89 | static readLine(): string { 90 | const lineNo = vscode.window.activeTextEditor!.selection.active.line; 91 | 92 | return vscode.window.activeTextEditor!.document.lineAt(lineNo).text; 93 | } 94 | 95 | static readLineAt(lineNo: number): string { 96 | if (lineNo === null) { 97 | lineNo = vscode.window.activeTextEditor!.selection.active.line; 98 | } 99 | 100 | if (lineNo >= vscode.window.activeTextEditor!.document.lineCount) { 101 | throw new RangeError(); 102 | } 103 | 104 | return vscode.window.activeTextEditor!.document.lineAt(lineNo).text; 105 | } 106 | 107 | static getLineCount(): number { 108 | return vscode.window.activeTextEditor!.document.lineCount; 109 | } 110 | 111 | static getLineAt(position: vscode.Position): vscode.TextLine { 112 | return vscode.window.activeTextEditor!.document.lineAt(position); 113 | } 114 | 115 | static getCharAt(position: Position): string { 116 | const line = TextEditor.getLineAt(position); 117 | 118 | return line.text[position.character]; 119 | } 120 | 121 | static getLineMaxColumn(lineNumber: number): number { 122 | if (lineNumber < 0 || lineNumber > TextEditor.getLineCount()) { 123 | throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); 124 | } 125 | 126 | return TextEditor.readLineAt(lineNumber).length; 127 | } 128 | 129 | static getSelection(): vscode.Range { 130 | return vscode.window.activeTextEditor!.selection; 131 | } 132 | 133 | static getText(selection?: vscode.Range): string { 134 | return vscode.window.activeTextEditor!.document.getText(selection); 135 | } 136 | 137 | /** 138 | * Retrieves the current word at position. 139 | * If current position is whitespace, selects the right-closest word 140 | */ 141 | static getWord(position: Position): string | undefined { 142 | let start = position; 143 | let end = position.getRight(); 144 | 145 | const char = TextEditor.getText(new vscode.Range(start, end)); 146 | if (Globals.WhitespaceRegExp.test(char)) { 147 | start = position.getWordRight(); 148 | } else { 149 | start = position.getWordLeft(true); 150 | } 151 | end = start.getCurrentWordEnd(true).getRight(); 152 | 153 | const word = TextEditor.getText(new vscode.Range(start, end)); 154 | 155 | if (Globals.WhitespaceRegExp.test(word)) { 156 | return undefined; 157 | } 158 | 159 | return word; 160 | } 161 | 162 | static isFirstLine(position: vscode.Position): boolean { 163 | return position.line === 0; 164 | } 165 | 166 | static isLastLine(position: vscode.Position): boolean { 167 | return position.line === vscode.window.activeTextEditor!.document.lineCount - 1; 168 | } 169 | 170 | static getPositionAt(offset: number): Position { 171 | const pos = vscode.window.activeTextEditor!.document.positionAt(offset); 172 | 173 | return new Position(pos.line, pos.character); 174 | } 175 | 176 | static getOffsetAt(position: Position): number { 177 | return vscode.window.activeTextEditor!.document.offsetAt(position); 178 | } 179 | } 180 | 181 | /** 182 | * Directions in the view for editor scroll command. 183 | */ 184 | export type EditorScrollDirection = 'up' | 'down'; 185 | 186 | /** 187 | * Units for editor scroll 'by' argument 188 | */ 189 | export type EditorScrollByUnit = 'line' | 'wrappedLine' | 'page' | 'halfPage'; 190 | 191 | /** 192 | * Values for reveal line 'at' argument 193 | */ 194 | export type RevealLineAtArgument = 'top' | 'center' | 'bottom'; 195 | /** 196 | * Positions in the view for cursor move command. 197 | */ 198 | export type CursorMovePosition = 199 | | 'left' 200 | | 'right' 201 | | 'up' 202 | | 'down' 203 | | 'wrappedLineStart' 204 | | 'wrappedLineFirstNonWhitespaceCharacter' 205 | | 'wrappedLineColumnCenter' 206 | | 'wrappedLineEnd' 207 | | 'wrappedLineLastNonWhitespaceCharacter' 208 | | 'viewPortTop' 209 | | 'viewPortCenter' 210 | | 'viewPortBottom' 211 | | 'viewPortIfOutside'; 212 | 213 | /** 214 | * Units for Cursor move 'by' argument 215 | */ 216 | export type CursorMoveByUnit = 'line' | 'wrappedLine' | 'character' | 'halfLine'; 217 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as _ from "lodash"; 4 | import * as vscode from 'vscode'; 5 | import { Range } from './common/motion/range'; 6 | import { Position } from './common/motion/position'; 7 | 8 | export async function showInfo(message: string): Promise<{}> { 9 | return vscode.window.showInformationMessage("Vim: " + message) as {}; 10 | } 11 | 12 | export async function showError(message: string): Promise<{}> { 13 | return vscode.window.showErrorMessage("Vim: " + message) as {}; 14 | } 15 | 16 | const clipboardy = require('clipboardy'); 17 | 18 | export function clipboardCopy(text: string) { 19 | clipboardy.writeSync(text); 20 | } 21 | 22 | export function clipboardPaste(): string { 23 | return clipboardy.readSync(); 24 | } 25 | 26 | /** 27 | * This is certainly quite janky! The problem we're trying to solve 28 | * is that writing editor.selection = new Position() won't immediately 29 | * update the position of the cursor. So we have to wait! 30 | */ 31 | export async function waitForCursorUpdatesToHappen(): Promise { 32 | await new Promise((resolve, reject) => { 33 | setTimeout(resolve, 100); 34 | 35 | const disposer = vscode.window.onDidChangeTextEditorSelection(x => { 36 | disposer.dispose(); 37 | 38 | resolve(); 39 | }); 40 | }); 41 | } 42 | 43 | /** 44 | * Waits for the tabs to change after a command like 'gt' or 'gT' is run. 45 | * Sometimes it is not immediate, so we must busy wait 46 | * On certain versions, the tab changes are synchronous 47 | * For those, a timeout is given 48 | */ 49 | export async function waitForTabChange(): Promise { 50 | await new Promise((resolve, reject) => { 51 | setTimeout(resolve, 500); 52 | 53 | const disposer = vscode.window.onDidChangeActiveTextEditor((textEditor) => { 54 | disposer.dispose(); 55 | 56 | resolve(textEditor); 57 | }); 58 | }); 59 | } 60 | export async function allowVSCodeToPropagateCursorUpdatesAndReturnThem(): Promise { 61 | await waitForCursorUpdatesToHappen(); 62 | 63 | return vscode.window.activeTextEditor!.selections.map(x => 64 | new Range(Position.FromVSCodePosition(x.start), Position.FromVSCodePosition(x.end))); 65 | } 66 | 67 | export async function wait(time: number): Promise { 68 | await new Promise((resolve, reject) => { 69 | setTimeout(resolve, time); 70 | }); 71 | } 72 | 73 | export function betterEscapeRegex(str: string): string { 74 | let result = _.escapeRegExp(str); 75 | 76 | return result.replace(/-/g, "\\-"); 77 | } -------------------------------------------------------------------------------- /srcNV/nvUtil.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as vscode from 'vscode'; 4 | import { Vim } from '../extension'; 5 | import { TextEditor } from '../src/textEditor'; 6 | import { Position } from '../src/common/motion/position'; 7 | import { Configuration } from '../src/configuration/configuration'; 8 | 9 | type UndoTree = { 10 | entries: Array<{ seq: number; time: number }>; 11 | save_cur: number; 12 | save_last: number; 13 | seq_cur: number; 14 | seq_last: number; 15 | synced: number; 16 | time_cur: number; 17 | }; 18 | 19 | export class NvUtil { 20 | private static _caretDecoration = vscode.window.createTextEditorDecorationType({ 21 | backgroundColor: new vscode.ThemeColor('editorCursor.foreground'), 22 | borderColor: new vscode.ThemeColor('editorCursor.foreground'), 23 | dark: { 24 | color: '#515052', 25 | }, 26 | light: { 27 | // used for light colored themes 28 | color: 'rgb(255, 255, 255)', 29 | }, 30 | borderStyle: 'solid', 31 | borderWidth: '1px', 32 | }); 33 | 34 | static async copyTextFromNeovim() { 35 | Vim.numVimChangesToApply++; 36 | let lines = await Vim.nv.buffer.lines; 37 | TextEditor.replace( 38 | new vscode.Range( 39 | 0, 40 | 0, 41 | TextEditor.getLineCount() - 1, 42 | TextEditor.getLineMaxColumn(TextEditor.getLineCount() - 1) 43 | ), 44 | lines.join('\n') 45 | ); 46 | } 47 | 48 | static async setCursorPos(pos: vscode.Position) { 49 | await Vim.nv.call('setpos', ['.', [0, pos.line + 1, pos.character + 1, false]]); 50 | } 51 | 52 | // Must be moving to same line 53 | static async ctrlGMove(start: number, target: number) { 54 | if (start < target) { 55 | // todo(chilli): Causes race condition that seems very tricky to fix :/ 56 | await Vim.nv.input('U'.repeat(target - start)); 57 | } else if (start > target) { 58 | await Vim.nv.input('U'.repeat(start - target)); 59 | } 60 | } 61 | static atomCall(funcName: string, args?: any[]): Array { 62 | if (args) { 63 | return ['nvim_call_function', [funcName, args]]; 64 | } else { 65 | return ['nvim_call_function', [funcName, []]]; 66 | } 67 | } 68 | 69 | static atomCommand(command: string): Array { 70 | return ['nvim_command', [command]]; 71 | } 72 | 73 | static atomBufSetLines( 74 | lines: Array, 75 | buffer = 0, 76 | start = 0, 77 | end = -1, 78 | strictIndexing = 1 79 | ) { 80 | return ['nvim_buf_set_lines', [buffer, start, end, strictIndexing, lines]]; 81 | } 82 | 83 | static atomFeedKeys(keys: string, mode = '', escapeCsi = false) { 84 | return ['nvim_feed_keys', [keys, mode, escapeCsi]]; 85 | } 86 | 87 | // An utility function for joining multiple arrays for use in nvim_atomic_call 88 | static atomJoin(...arrays: Array): Array { 89 | let ret: Array = []; 90 | for (const a of arrays) { 91 | if (a[0] instanceof Array) { 92 | ret.concat(a); 93 | } else { 94 | ret = ret.concat([a]); 95 | } 96 | } 97 | return ret; 98 | } 99 | 100 | static async setSelection(pos: vscode.Range) { 101 | await Vim.nv.callAtomic( 102 | NvUtil.atomJoin( 103 | NvUtil.atomCall('setpos', ['.', [0, pos.start.line + 1, pos.start.character + 1, false]]), 104 | NvUtil.atomFeedKeys('v'), 105 | NvUtil.atomCall('setpos', ['.', [0, pos.end.line + 1, pos.end.character + 1, false]]) 106 | ) 107 | ); 108 | } 109 | 110 | private static async getPos(name: string): Promise { 111 | let [row, character] = ((await Vim.nv.callFunction('getpos', [name])) as Array).slice( 112 | 1, 113 | 3 114 | ); 115 | return new Position(row - 1, character - 1); 116 | } 117 | 118 | static async getCurWant(): Promise { 119 | return (await Vim.nv.call('getcurpos'))[4] - 1; 120 | } 121 | 122 | static async getCursorPos(): Promise { 123 | return this.getPos('.'); 124 | } 125 | 126 | static async getSelectionStartPos(): Promise { 127 | return this.getPos('v'); 128 | } 129 | static async getUndoTree(): Promise { 130 | return (await Vim.nv.call('undotree', [])) as UndoTree; 131 | } 132 | static async changeSelectionFromMode(mode: string) { 133 | return this.changeSelectionFromModeSync( 134 | mode, 135 | await this.getCursorPos(), 136 | await this.getSelectionStartPos(), 137 | await this.getCurWant() 138 | ); 139 | } 140 | 141 | static changeSelectionFromModeSync( 142 | mode: string, 143 | curPos: Position, 144 | startPos: Position, 145 | curWant: number 146 | ) { 147 | const cursorPos = new Position(curPos.line, curPos.character); 148 | let cursorDecorations = []; 149 | switch (mode) { 150 | case 'v': 151 | if (startPos.isBeforeOrEqual(curPos)) { 152 | curPos = curPos.getRightThroughLineBreaks(); 153 | } else { 154 | startPos = startPos.getRightThroughLineBreaks(); 155 | } 156 | vscode.window.activeTextEditor!.options.cursorStyle = vscode.TextEditorCursorStyle.LineThin; 157 | vscode.window.activeTextEditor!.selection = new vscode.Selection(startPos, curPos); 158 | break; 159 | case 'V': 160 | if (startPos.isBeforeOrEqual(curPos)) { 161 | curPos = curPos.getLineEndIncludingEOL(); 162 | startPos = startPos.getLineBegin(); 163 | } else { 164 | curPos = curPos.getLineBegin(); 165 | startPos = startPos.getLineEndIncludingEOL(); 166 | } 167 | vscode.window.activeTextEditor!.options.cursorStyle = vscode.TextEditorCursorStyle.LineThin; 168 | vscode.window.activeTextEditor!.selection = new vscode.Selection(startPos, curPos); 169 | break; 170 | case '\x16': 171 | const top = Position.EarlierOf(curPos, startPos).line; 172 | const bottom = Position.LaterOf(curPos, startPos).line; 173 | const left = Math.min(startPos.character, curWant); 174 | const right = Math.max(startPos.character, curWant) + 1; 175 | let selections = []; 176 | for (let line = top; line <= bottom; line++) { 177 | selections.push( 178 | new vscode.Selection(new Position(line, left), new Position(line, right)) 179 | ); 180 | } 181 | vscode.window.activeTextEditor!.selections = selections; 182 | vscode.window.activeTextEditor!.options.cursorStyle = vscode.TextEditorCursorStyle.LineThin; 183 | 184 | break; 185 | case 'i': 186 | vscode.window.activeTextEditor!.options.cursorStyle = Configuration.userCursor; 187 | vscode.window.activeTextEditor!.selection = new vscode.Selection(curPos, curPos); 188 | break; 189 | case 'R': 190 | vscode.window.activeTextEditor!.options.cursorStyle = 191 | vscode.TextEditorCursorStyle.Underline; 192 | vscode.window.activeTextEditor!.selection = new vscode.Selection(curPos, curPos); 193 | break; 194 | case 'n': 195 | default: 196 | vscode.window.activeTextEditor!.options.cursorStyle = vscode.TextEditorCursorStyle.Block; 197 | vscode.window.activeTextEditor!.selection = new vscode.Selection(curPos, curPos); 198 | break; 199 | } 200 | 201 | switch (mode) { 202 | case 'v': 203 | if (startPos.isEarlierThan(curPos)) { 204 | cursorDecorations.push(new vscode.Range(curPos.getLeft(), curPos)); 205 | } else { 206 | cursorDecorations.push(new vscode.Range(curPos, curPos.getRight())); 207 | } 208 | break; 209 | case 'V': 210 | cursorDecorations.push(new vscode.Range(cursorPos, cursorPos.getRight())); 211 | break; 212 | case '\x16': 213 | cursorDecorations.push(new vscode.Range(curPos, curPos.getRight())); 214 | break; 215 | default: 216 | break; 217 | } 218 | vscode.window.activeTextEditor!.setDecorations(this._caretDecoration, cursorDecorations); 219 | vscode.window.activeTextEditor!.revealRange(new vscode.Range(cursorPos, cursorPos)); 220 | } 221 | 222 | static async updateMode() { 223 | Vim.mode = await Vim.nv.mode; 224 | } 225 | 226 | static async setSettings(arg: Array) { 227 | Vim.nv.command(`set ${arg.join(' ')}`); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /srcNV/rpcHandlers.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Vim } from '../extension'; 3 | import { NvUtil } from './nvUtil'; 4 | import { VimSettings } from './vimSettings'; 5 | import * as fs from 'fs'; 6 | 7 | export class RpcRequest { 8 | static rpcFunctions: { [method: string]: Function } = {}; 9 | 10 | static async enterBuf(args: any, resp: any) { 11 | const filePath = args[1] as string; 12 | const fileURI = vscode.Uri.file(filePath); 13 | console.log(filePath); 14 | if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) { 15 | await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(filePath)); 16 | await NvUtil.changeSelectionFromMode(Vim.mode.mode); 17 | } else { 18 | console.log('Opening non-existing files currently not implemented (and not working well).'); 19 | // await vscode.window.showTextDocument(t); 20 | // console.log(t); 21 | } 22 | resp.send('success'); 23 | } 24 | 25 | static async newTabEntered(_: any, resp: any) { 26 | await Vim.nv.command('tabonly'); 27 | await resp.send('success'); 28 | } 29 | 30 | static async writeBuf(args: Array, resp: any) { 31 | const filePath = vscode.Uri.file(args[1]); 32 | await vscode.commands.executeCommand('workbench.action.files.save', filePath); 33 | // nvim.command('e!'); 34 | await resp.send('success'); 35 | } 36 | 37 | static async closeBuf(args: Array, resp: any) { 38 | const bufName = args[1]; 39 | const filePath = vscode.Uri.file(bufName); 40 | console.log('filepath: ', filePath); 41 | if (args[1] !== vscode.window.activeTextEditor!.document.fileName) { 42 | await vscode.commands.executeCommand('vscode.open', filePath); 43 | } 44 | if (args[1] !== vscode.window.activeTextEditor!.document.fileName) { 45 | resp.send('failure'); 46 | return; 47 | } 48 | await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); 49 | resp.send('success'); 50 | } 51 | 52 | static async goToDefinition(args: Array, resp: any) { 53 | await Vim.nv.command("normal! m'"); 54 | await vscode.commands.executeCommand('editor.action.goToDeclaration'); 55 | await NvUtil.setCursorPos(vscode.window.activeTextEditor!.selection.active); 56 | resp.send('success'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /srcNV/screen.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Position } from '../src/common/motion/position'; 3 | import { TextEditor } from '../src/textEditor'; 4 | import { VimSettings } from './vimSettings'; 5 | import { NvUtil } from './nvUtil'; 6 | import { Vim } from '../extension'; 7 | export class Cell { 8 | v: string; 9 | highlight: any; 10 | constructor(v: string) { 11 | this.v = v; 12 | this.highlight = {}; 13 | } 14 | } 15 | interface ScreenSize { 16 | width: number; 17 | height: number; 18 | } 19 | 20 | export interface IgnoredKeys { 21 | all: string[]; 22 | normal: string[]; 23 | insert: string[]; 24 | visual: string[]; 25 | } 26 | export interface HighlightGroup { 27 | name: string; 28 | decorator?: vscode.TextEditorDecorationType; 29 | } 30 | 31 | export class Screen { 32 | OFFSET_COLOR = 1; 33 | term: Array> = []; 34 | cursX: number; 35 | cursY: number; 36 | size: ScreenSize; 37 | highlighter: any; 38 | cmdline: vscode.StatusBarItem; 39 | wildmenu: vscode.StatusBarItem[]; 40 | wildmenuItems: string[]; 41 | highlightGroups: HighlightGroup[]; 42 | scrollRegion: { 43 | top: number; 44 | bottom: number; 45 | left: number; 46 | right: number; 47 | }; 48 | resize(size: ScreenSize) { 49 | this.size = size; 50 | for (let i = 0; i < this.size.height; i++) { 51 | this.term[i] = []; 52 | for (let j = 0; j < this.size.width; j++) { 53 | this.term[i][j] = new Cell(' '); 54 | } 55 | } 56 | 57 | this.scrollRegion = { 58 | top: 0, 59 | bottom: this.size.height, 60 | left: 0, 61 | right: this.size.width, 62 | }; 63 | } 64 | 65 | clear() { 66 | this.resize(this.size); 67 | } 68 | 69 | scroll(deltaY: number) { 70 | const { top, bottom, left, right } = this.scrollRegion; 71 | 72 | const width = right - left; 73 | const height = bottom - top; 74 | 75 | let yi = [top, bottom]; 76 | if (deltaY < 0) { 77 | yi = [bottom, top - 1]; 78 | } 79 | 80 | for (let y = yi[0]; y !== yi[1]; y = y + Math.sign(deltaY)) { 81 | if (top <= y + deltaY && y + deltaY < bottom) { 82 | for (let x = left; x < right; x++) { 83 | this.term[y][x] = this.term[y + deltaY][x]; 84 | } 85 | } else { 86 | for (let x = left; x < right; x++) { 87 | this.term[y][x] = new Cell(' '); 88 | this.term[y][x].highlight = this.highlighter; 89 | } 90 | } 91 | } 92 | } 93 | 94 | constructor(size: { width: number; height: number }) { 95 | this.size = size; 96 | this.resize(this.size); 97 | 98 | this.cursX = 0; 99 | this.cursY = 0; 100 | this.highlighter = {}; 101 | this.cmdline = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10001); 102 | this.wildmenu = []; 103 | for (let i = 0; i < 10; i++) { 104 | this.wildmenu.push( 105 | vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10000 - i) 106 | ); 107 | // this.wildmenu[i].show(); 108 | } 109 | // todo(chilli): Offer some way of binding these from the client side. 110 | this.highlightGroups = [ 111 | { 112 | name: 'IncSearch', 113 | decorator: vscode.window.createTextEditorDecorationType({ 114 | backgroundColor: new vscode.ThemeColor('editor.findMatchBackground'), 115 | }), 116 | }, 117 | { 118 | name: 'Search', 119 | decorator: vscode.window.createTextEditorDecorationType({ 120 | backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'), 121 | }), 122 | }, 123 | { 124 | name: 'multiple_cursors_visual', 125 | decorator: vscode.window.createTextEditorDecorationType({ 126 | backgroundColor: new vscode.ThemeColor('editor.selectionBackground'), 127 | }), 128 | }, 129 | { 130 | name: 'multiple_cursors_cursor', 131 | decorator: vscode.window.createTextEditorDecorationType({ 132 | backgroundColor: new vscode.ThemeColor('editorCursor.foreground'), 133 | }), 134 | }, 135 | { 136 | name: 'EasyMotionTarget', 137 | decorator: vscode.window.createTextEditorDecorationType({ 138 | backgroundColor: 'black', 139 | textDecoration: 'none;color: red', 140 | }), 141 | }, 142 | { 143 | name: 'EasyMotionShade', 144 | decorator: vscode.window.createTextEditorDecorationType({ 145 | textDecoration: 'none;opacity: 0.3', 146 | }), 147 | }, 148 | ]; 149 | for (let i = 0; i < this.highlightGroups.length; i++) { 150 | Vim.nv.command( 151 | `highlight ${this.highlightGroups[i].name} guibg='#00000${i + this.OFFSET_COLOR}'` 152 | ); 153 | } 154 | } 155 | private async handleModeChange(mode: [string, number]) { 156 | if (mode[0] === 'insert') { 157 | await NvUtil.setSettings(await VimSettings.insertModeSettings()); 158 | } else { 159 | await NvUtil.updateMode(); 160 | await NvUtil.copyTextFromNeovim(); 161 | await NvUtil.changeSelectionFromMode(Vim.mode.mode); 162 | await NvUtil.setSettings(VimSettings.normalModeSettings); 163 | } 164 | // todo(chilli): Do this in a smarter way that generalizes to more categories ... 165 | const ignoreKeys: IgnoredKeys = vscode.workspace 166 | .getConfiguration('vim') 167 | .get('ignoreKeys') as IgnoredKeys; 168 | if (mode[0] === 'insert') { 169 | for (const key of ignoreKeys.visual.concat(ignoreKeys.normal)) { 170 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, true); 171 | } 172 | for (const key of ignoreKeys.insert) { 173 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, false); 174 | } 175 | } else if (mode[0] === 'visual') { 176 | for (const key of ignoreKeys.normal.concat(ignoreKeys.insert)) { 177 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, true); 178 | } 179 | for (const key of ignoreKeys.visual) { 180 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, false); 181 | } 182 | } else { 183 | // I assume normal is just all "other" modes. 184 | for (const key of ignoreKeys.visual.concat(ignoreKeys.insert)) { 185 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, true); 186 | } 187 | for (const key of ignoreKeys.normal) { 188 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, false); 189 | } 190 | } 191 | for (const key of ignoreKeys.all) { 192 | vscode.commands.executeCommand('setContext', `vim.use_${key}`, false); 193 | } 194 | } 195 | 196 | async redraw(changes: Array) { 197 | let highlightsChanged = false; 198 | for (let change of changes) { 199 | change = change as Array; 200 | const name = change[0]; 201 | const args = change.slice(1); 202 | if (name === 'cursor_goto') { 203 | this.cursY = args[0][0]; 204 | this.cursX = args[0][1]; 205 | } else if (name === 'eol_clear') { 206 | for (let i = 0; i < this.size.width - this.cursX; i++) { 207 | this.term[this.cursY][this.cursX + i].v = ' '; 208 | this.term[this.cursY][this.cursX + i].highlight = {}; 209 | } 210 | highlightsChanged = true; 211 | } else if (name === 'put') { 212 | for (const cs of args) { 213 | for (const c of cs) { 214 | this.term[this.cursY][this.cursX].v = c; 215 | this.term[this.cursY][this.cursX].highlight = this.highlighter; 216 | this.cursX += 1; 217 | } 218 | } 219 | highlightsChanged = true; 220 | } else if (name === 'highlight_set') { 221 | this.highlighter = args[args.length - 1][0]; 222 | } else if (name === 'mode_change') { 223 | this.handleModeChange(args[0]); 224 | } else if (name === 'set_scroll_region') { 225 | this.scrollRegion = { 226 | top: args[0][0], 227 | bottom: args[0][1] + 1, 228 | left: args[0][2], 229 | right: args[0][3] + 1, 230 | }; 231 | } else if (name === 'resize') { 232 | this.resize({ width: args[0][0], height: args[0][1] }); 233 | } else if (name === 'scroll') { 234 | this.scroll(args[0][0]); 235 | } else if (name === 'cmdline_show') { 236 | let text = ''; 237 | for (let hlText of args[0][0]) { 238 | text += hlText[1]; 239 | } 240 | this.cmdline.text = 241 | args[0][2] + 242 | args[0][3] + 243 | ' '.repeat(args[0][4]) + 244 | text.slice(0, args[0][1]) + 245 | '|' + 246 | text.slice(args[0][1]); 247 | this.cmdline.text += ' '.repeat(30 - this.cmdline.text.length % 30); 248 | this.cmdline.show(); 249 | } else if (name === 'cmdline_hide') { 250 | this.cmdline.hide(); 251 | } else if ( 252 | [ 253 | 'cmdline_pos', 254 | 'cmdline_special_char', 255 | 'cmdline_block_show', 256 | 'cmdline_block_append', 257 | 'cmdline_block_hide', 258 | ].indexOf(name) !== -1 259 | ) { 260 | // console.log(name); 261 | // console.log(args); 262 | } else if (name === 'wildmenu_show') { 263 | this.wildmenuItems = args[0][0]; 264 | } else if (name === 'wildmenu_hide') { 265 | for (const i of this.wildmenu) { 266 | i.hide(); 267 | } 268 | } else if (name === 'wildmenu_select') { 269 | // There's logic in here to "batch" wildmenu items into groups of 5 each. 270 | const selectIndex = args[0][0]; 271 | const NUM_ITEMS_TO_SHOW = 5; 272 | const startIndex = selectIndex - selectIndex % NUM_ITEMS_TO_SHOW; 273 | const endIndex = selectIndex + 5 - selectIndex % NUM_ITEMS_TO_SHOW; 274 | let offset = startIndex > 0 ? 1 : 0; 275 | if (offset) { 276 | this.wildmenu[0].text = '<'; 277 | } 278 | for (let i = 0; i < NUM_ITEMS_TO_SHOW; i++) { 279 | this.wildmenu[i + offset].text = this.wildmenuItems[startIndex + i]; 280 | if (startIndex + i === selectIndex) { 281 | this.wildmenu[i + offset].color = new vscode.ThemeColor( 282 | 'statusBarItem.prominentBackground' 283 | ); 284 | } else { 285 | this.wildmenu[i + offset].color = undefined; 286 | } 287 | this.wildmenu[i + offset].show(); 288 | } 289 | if (endIndex < this.wildmenuItems.length - 1) { 290 | this.wildmenu[offset + NUM_ITEMS_TO_SHOW].text = '>'; 291 | this.wildmenu[offset + NUM_ITEMS_TO_SHOW].show(); 292 | } 293 | for (let i = offset + NUM_ITEMS_TO_SHOW + 1; i < this.wildmenu.length; i++) { 294 | this.wildmenu[i].hide(); 295 | } 296 | } else { 297 | // console.log(name); 298 | // console.log(args); 299 | } 300 | } 301 | 302 | // If nvim is connected to a TUI, then we can't get external ui for cmdline/wildmenu. 303 | if (Vim.DEBUG) { 304 | this.cmdline.text = this.term[this.size.height - 1].map(x => x.v).join(''); 305 | this.cmdline.show(); 306 | const wildmenuText = this.term[this.size.height - 2] 307 | .map(x => x.v) 308 | .join('') 309 | .replace(/\s+$/, ''); 310 | let wildmenu: string[] = wildmenuText.split(/\s+/); 311 | // Doesn't always work, who cares??? What a pain in the ass. I don't want to not use regex. 312 | let wildmenuIdx = wildmenu.map(x => wildmenuText.indexOf(x)); 313 | if (wildmenu[0] === '<' || wildmenu[wildmenu.length - 1] === '>') { 314 | for (let i = 0; i < wildmenu.length; i++) { 315 | this.wildmenu[i].text = wildmenu[i]; 316 | this.wildmenu[i].show(); 317 | if ( 318 | this.term[this.size.height - 2][wildmenuIdx[i]].highlight.hasOwnProperty('foreground') 319 | ) { 320 | this.wildmenu[i].color = 'red'; 321 | } else { 322 | this.wildmenu[i].color = 'white'; 323 | } 324 | } 325 | for (let i = wildmenu.length; i < this.wildmenu.length; i++) { 326 | this.wildmenu[i].hide(); 327 | } 328 | } else { 329 | for (let i = 0; i < this.wildmenu.length; i++) { 330 | this.wildmenu[i].hide(); 331 | } 332 | } 333 | } 334 | 335 | if (!vscode.workspace.getConfiguration('vim').get('enableHighlights') || !highlightsChanged) { 336 | return; 337 | } 338 | let curPos = await NvUtil.getCursorPos(); 339 | let yOffset = curPos.line - ((await Vim.nv.call('winline')) - 1); 340 | let xOffset = curPos.character - ((await Vim.nv.call('wincol')) - 1); 341 | let hlDecorations: vscode.Range[][] = []; 342 | for (let i = 0; i < this.highlightGroups.length; i++) { 343 | hlDecorations.push([]); 344 | } 345 | let curVimColor = -1; 346 | for (let i = 0; i < this.size.height; i++) { 347 | let isRange = false; 348 | let start = 0; 349 | for (let j = 0; j < this.size.width; j++) { 350 | if (isRange && !(this.term[i][j].highlight.background === curVimColor)) { 351 | isRange = false; 352 | hlDecorations[curVimColor - this.OFFSET_COLOR].push( 353 | new vscode.Range( 354 | new vscode.Position(i + yOffset, start + xOffset), 355 | new vscode.Position(i + yOffset, j + xOffset) 356 | ) 357 | ); 358 | curVimColor = -1; 359 | } 360 | const cellColor = this.term[i][j].highlight.background - this.OFFSET_COLOR; 361 | if (!isRange && cellColor >= 0 && cellColor < hlDecorations.length) { 362 | start = j; 363 | isRange = true; 364 | curVimColor = this.term[i][j].highlight.background; 365 | } 366 | } 367 | } 368 | for (let i = 0; i < hlDecorations.length; i++) { 369 | if (!(this.highlightGroups[i].decorator && vscode.window.activeTextEditor)) { 370 | continue; 371 | } 372 | vscode.window.activeTextEditor!.setDecorations( 373 | this.highlightGroups[i].decorator!, 374 | hlDecorations[i] 375 | ); 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /srcNV/vimSettings.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Vim } from '../extension'; 3 | 4 | export class VimSettings { 5 | static indentexpr: string = ''; 6 | static get normalModeSettings() { 7 | return [ 8 | 'autoindent', 9 | 'cindent', 10 | 'smartindent', 11 | `indentexpr=${this.indentexpr}`, 12 | `shiftwidth=${vscode.window.activeTextEditor!.options.tabSize}`, 13 | ]; 14 | } 15 | static async insertModeSettings() { 16 | return ['noautoindent', 'nocindent', 'nosmartindent', 'indentexpr=', 'shiftwidth=1']; 17 | } 18 | 19 | static async enterFileSettings() { 20 | let result: string[] = []; 21 | const currentFileSettings = vscode.window.activeTextEditor!.options; 22 | if (currentFileSettings.insertSpaces) { 23 | result.push('expandtab'); 24 | } 25 | this.indentexpr = await (await Vim.nv.buffer).getOption('indentexpr'); 26 | 27 | result = result.concat([ 28 | `tabstop=${currentFileSettings.tabSize}`, 29 | `shiftwidth=${currentFileSettings.tabSize}`, 30 | ]); 31 | return result; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /srcNV/vscHandlers.ts: -------------------------------------------------------------------------------- 1 | import { NvUtil } from './nvUtil'; 2 | import { Vim } from '../extension'; 3 | import { Position } from '../src/common/motion/position'; 4 | import * as vscode from 'vscode'; 5 | import { TextEditor } from '../src/textEditor'; 6 | import { VimSettings } from './vimSettings'; 7 | 8 | export class VscHandlers { 9 | // tslint:disable-next-line:no-unused-variable 10 | static async handleSimple(key: string) { 11 | await Vim.nv.input(key); 12 | } 13 | 14 | static async handleKeyEventNV(key: string) { 15 | const prevMode = Vim.mode.mode; 16 | const prevBlocking = Vim.mode.blocking; 17 | async function input(k: string) { 18 | await Vim.nv.input(k === '<' ? '' : k); 19 | await NvUtil.updateMode(); 20 | if (Vim.mode.mode === 'r') { 21 | await Vim.nv.input(''); 22 | } 23 | 24 | const curPos = await NvUtil.getCursorPos(); 25 | const startPos = await NvUtil.getSelectionStartPos(); 26 | const curWant = await NvUtil.getCurWant(); 27 | const winline = (await Vim.nv.call('winline')) - 1; 28 | const curTick = await Vim.nv.buffer.changedtick; 29 | vscode.commands.executeCommand('revealLine', { 30 | lineNumber: Math.min( 31 | vscode.window.activeTextEditor!.selection.active.line, 32 | curPos.line - winline 33 | ), 34 | at: 'top', 35 | }); 36 | // Optimization that makes movement very smooth. However, occasionally 37 | // makes it more difficult to debug so it's turned off for now. 38 | // if (curTick === Vim.prevState.bufferTick) { 39 | // NvUtil.changeSelectionFromModeSync(Vim.mode.mode, curPos, startPos, curWant); 40 | // return; 41 | // } 42 | // Vim.prevState.bufferTick = curTick; 43 | NvUtil.changeSelectionFromModeSync(Vim.mode.mode, curPos, startPos, curWant); 44 | await NvUtil.copyTextFromNeovim(); 45 | NvUtil.changeSelectionFromModeSync(Vim.mode.mode, curPos, startPos, curWant); 46 | } 47 | if (prevMode !== 'i') { 48 | await input(key); 49 | } else { 50 | if (key.length > 1) { 51 | await input(key); 52 | } else { 53 | await vscode.commands.executeCommand('default:type', { text: key }); 54 | } 55 | } 56 | 57 | await vscode.commands.executeCommand('setContext', 'vim.mode', Vim.mode.mode); 58 | } 59 | 60 | static async handleTextDocumentChange(e: vscode.TextDocumentChangeEvent) { 61 | if (e.contentChanges.length === 0) { 62 | return; 63 | } 64 | const change = e.contentChanges[0]; 65 | const changeBegin = Position.FromVSCodePosition(change.range.start); 66 | const changeEnd = Position.FromVSCodePosition(change.range.end); 67 | const curPos = Position.FromVSCodePosition(vscode.window.activeTextEditor!.selection.active); 68 | const curSel = vscode.window.activeTextEditor!.selection; 69 | const docEnd = new Position(0, 0).getDocumentEnd(); 70 | // This ugly if statement is to differentiate the "real" vscode changes that 71 | // should be included in dot repeat(like autocomplete, auto-close parens, 72 | // all regular typing, etc.) from the vscode changes that should not be 73 | // included (the entire buffer syncing, autoformatting, etc.) 74 | 75 | const isInsertModeChange = () => { 76 | if (e.contentChanges.length > 1 || vscode.window.activeTextEditor!.selections.length > 1) { 77 | return false; 78 | } 79 | if (Vim.mode.mode !== 'i') { 80 | return false; 81 | } 82 | // Handles the case where we press backsapce at the beginning of a line. 83 | if (change.text === '' && changeEnd.character === 0 && change.rangeLength === 1) { 84 | return true; 85 | } 86 | // If the change is spanning multiple lines then it's almost definitely 87 | // not an insert mode change (except for a couple of special cases.) 88 | if (!(changeBegin.line === curPos.line && changeBegin.line === changeEnd.line)) { 89 | return false; 90 | } 91 | // Mainly for mouse cursor selection/multicursor stuff. 92 | if ( 93 | curSel.active.line !== curSel.anchor.line || 94 | curSel.active.character !== curSel.anchor.character 95 | ) { 96 | return false; 97 | } 98 | // Tries to handle the case about editing on the first line. 99 | if (changeBegin.line === 0 && changeBegin.character === 0 && change.rangeLength !== 0) { 100 | if (change.text[change.text.length - 1] === '\n') { 101 | return false; 102 | } else if (TextEditor.getLineCount() === 1) { 103 | return false; 104 | } 105 | } 106 | return true; 107 | }; 108 | await NvUtil.updateMode(); 109 | if (isInsertModeChange()) { 110 | if (!Vim.mode.blocking) { 111 | const nvPos = await NvUtil.getCursorPos(); 112 | if (nvPos.line !== curPos.line) { 113 | await NvUtil.setCursorPos(curPos); 114 | } else { 115 | // Is necessary for parentheses autocompletion but causes issues 116 | // when non-atomic with fast text. 117 | await NvUtil.ctrlGMove(nvPos.character, changeEnd.character); 118 | } 119 | } 120 | await Vim.nv.input(''.repeat(change.rangeLength)); 121 | await Vim.nv.input(change.text.replace('<', '')); 122 | } else { 123 | // Should handle race conditions. If we have more than one Vim copy to 124 | // VSCode that we haven't processed, then we don't copy back to neovim. 125 | // NVM. This doesn't actually work as is. 126 | Vim.numVimChangesToApply--; 127 | if (Vim.numVimChangesToApply !== 0) { 128 | return; 129 | } 130 | // todo: Optimize this to only replace relevant lines. Probably not worth 131 | // doing until diffs come in from the neovim side though, since that's the 132 | // real blocking factor. 133 | // todo(chilli): Tests if change is a change that replaces the entire text (ie: the copy 134 | // from neovim buffer to vscode buffer). It's a hack. Won't work if your 135 | // change (refactor) for example, doesn't modify the length of the file 136 | const isRealChange = change.text.length !== change.rangeLength; 137 | if (isRealChange || true) { 138 | // todo(chilli): Doesn't work if there was just an undo command (undojoin 139 | // fails and prevents the following command from executing) 140 | 141 | const startTime = new Date().getTime(); 142 | const newPos = vscode.window.activeTextEditor!.selection.active; 143 | let t = await Vim.nv.lua('return _vscode_copy_text(...)', [ 144 | TextEditor.getText().split('\n'), 145 | newPos.line + 1, 146 | newPos.character + 1, 147 | ]); 148 | console.log(`timeTaken: ${new Date().getTime() - startTime}`); 149 | // const newPos = vscode.window.activeTextEditor!.selection.active; 150 | // await nvim.command('undojoin'); 151 | // await nvim.buffer.setLines(TextEditor.getText().split('\n'), { 152 | // start: 0, 153 | // end: -1, 154 | // strictIndexing: false, 155 | // }); 156 | // await NvUtil.setCursorPos(newPos); 157 | } 158 | } 159 | } 160 | 161 | static async handleActiveTextEditorChange() { 162 | if (vscode.window.activeTextEditor === undefined) { 163 | return; 164 | } 165 | const active_editor_file = vscode.window.activeTextEditor!.document.fileName; 166 | await Vim.nv.command(`edit! ${active_editor_file}`); 167 | await NvUtil.copyTextFromNeovim(); 168 | await NvUtil.setCursorPos(vscode.window.activeTextEditor!.selection.active); 169 | await NvUtil.setSettings(await VimSettings.enterFileSettings()); 170 | await NvUtil.changeSelectionFromMode(Vim.mode.mode); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnusedLabels": false, 4 | "module": "commonjs", 5 | "target": "es6", 6 | "outDir": "out", 7 | "noImplicitAny": true, 8 | "noImplicitReturns": true, 9 | "suppressImplicitAnyIndexErrors": true, 10 | "lib": [ 11 | "es6", 12 | "dom" 13 | ], 14 | "sourceMap": true, 15 | "strictNullChecks": true, 16 | "experimentalDecorators": true 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | ".vscode-test" 21 | ] 22 | } -------------------------------------------------------------------------------- /tsd.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v4", 3 | "repo": "borisyankov/DefinitelyTyped", 4 | "ref": "master", 5 | "path": "typings", 6 | "bundle": "typings/tsd.d.ts", 7 | "installed": { 8 | "diff-match-patch/diff-match-patch.d.ts": { 9 | "commit": "67557f18a898462614ed14063a54ca33a1cfab12" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "align": false, 4 | "ban": false, 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "curly": true, 11 | "eofline": false, 12 | "forin": true, 13 | "indent": [ 14 | true, 15 | "spaces" 16 | ], 17 | "interface-name": false, 18 | "jsdoc-format": true, 19 | "label-position": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": false, 26 | "no-any": false, 27 | "no-arg": true, 28 | "no-bitwise": true, 29 | "no-conditional-assignment": true, 30 | "no-consecutive-blank-lines": false, 31 | "no-console": [ 32 | true, 33 | "debug", 34 | "info", 35 | "time", 36 | "timeEnd", 37 | "trace" 38 | ], 39 | "no-construct": true, 40 | "no-parameter-properties": true, 41 | "no-debugger": true, 42 | "no-duplicate-variable": true, 43 | "no-empty": true, 44 | "no-eval": true, 45 | "no-inferrable-types": false, 46 | "no-internal-module": false, 47 | "no-null-keyword": false, 48 | "no-require-imports": false, 49 | "no-shadowed-variable": true, 50 | "no-string-literal": true, 51 | "no-switch-case-fall-through": true, 52 | "no-trailing-whitespace": true, 53 | "no-unused-expression": true, 54 | "no-unused-variable": true, 55 | "no-use-before-declare": true, 56 | "no-var-keyword": false, 57 | "no-var-requires": false, 58 | "object-literal-sort-keys": false, 59 | "one-line": [ 60 | true, 61 | "check-open-brace", 62 | "check-catch", 63 | "check-else", 64 | "check-finally", 65 | "check-whitespace" 66 | ], 67 | "quotemark": false, 68 | "radix": true, 69 | "semicolon": [true, "always"], 70 | "switch-default": false, 71 | "trailing-comma": false, 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef": false, 77 | "typedef-whitespace": false, 78 | "variable-name": false, 79 | "whitespace": [ 80 | true, 81 | "check-branch", 82 | "check-decl", 83 | "check-operator", 84 | "check-separator", 85 | "check-type" 86 | ] 87 | } 88 | } -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vim", 3 | "dependencies": { 4 | "diff": "registry:npm/diff#2.0.0+20160211003958", 5 | "lodash": "registry:npm/lodash#4.0.0+20160416211519" 6 | }, 7 | "globalDependencies": { 8 | "copy-paste": "registry:dt/copy-paste#1.1.3+20160117130525", 9 | "diff-match-patch": "registry:dt/diff-match-patch#1.0.0+20160821140300" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /typings/custom/promised-neovim-client.d.ts: -------------------------------------------------------------------------------- 1 | declare module "promised-neovim-client" { 2 | 3 | export interface Nvim extends NodeJS.EventEmitter { 4 | quit(): void; 5 | getVersion(): NvimVersion; 6 | bufLineCount(buffer: Buffer, notify?: boolean): Promise; 7 | bufGetLines(buffer: Buffer, start: number, end: number, strict_indexing: boolean, notify?: boolean): Promise>; 8 | bufSetLines(buffer: Buffer, start: number, end: number, strict_indexing: boolean, replacement: Array, notify?: boolean): Promise; 9 | bufGetVar(buffer: Buffer, name: string, notify?: boolean): Promise; 10 | bufSetVar(buffer: Buffer, name: string, value: VimValue, notify?: boolean): Promise; 11 | bufDelVar(buffer: Buffer, name: string, notify?: boolean): Promise; 12 | bufGetOption(buffer: Buffer, name: string, notify?: boolean): Promise; 13 | bufSetOption(buffer: Buffer, name: string, value: VimValue, notify?: boolean): Promise; 14 | bufGetNumber(buffer: Buffer, notify?: boolean): Promise; 15 | bufGetName(buffer: Buffer, notify?: boolean): Promise; 16 | bufSetName(buffer: Buffer, name: string, notify?: boolean): Promise; 17 | bufIsValid(buffer: Buffer, notify?: boolean): Promise; 18 | bufGetMark(buffer: Buffer, name: string, notify?: boolean): Promise>; 19 | bufAddHighlight(buffer: Buffer, src_id: number, hl_group: string, line: number, col_start: number, col_end: number, notify?: boolean): Promise; 20 | bufClearHighlight(buffer: Buffer, src_id: number, line_start: number, line_end: number, notify?: boolean): Promise; 21 | tabpageGetWindows(tabpage: Tabpage, notify?: boolean): Promise>; 22 | tabpageGetVar(tabpage: Tabpage, name: string, notify?: boolean): Promise; 23 | tabpageSetVar(tabpage: Tabpage, name: string, value: VimValue, notify?: boolean): Promise; 24 | tabpageDelVar(tabpage: Tabpage, name: string, notify?: boolean): Promise; 25 | tabpageGetWindow(tabpage: Tabpage, notify?: boolean): Promise; 26 | tabpageIsValid(tabpage: Tabpage, notify?: boolean): Promise; 27 | uiAttach(width: number, height: number, enable_rgb: boolean, notify?: boolean): Promise; 28 | uiDetach(notify?: boolean): Promise; 29 | uiTryResize(width: number, height: number, notify?: boolean): Promise; 30 | uiSetOption(name: string, value: VimValue, notify?: boolean): Promise; 31 | command(str: string, notify?: boolean): Promise; 32 | feedkeys(keys: string, mode: string, escape_csi: boolean, notify?: boolean): Promise; 33 | input(keys: string, notify?: boolean): Promise; 34 | replaceTermcodes(str: string, from_part: boolean, do_lt: boolean, special: boolean, notify?: boolean): Promise; 35 | commandOutput(str: string, notify?: boolean): Promise; 36 | eval(str: string, notify?: boolean): Promise; 37 | callFunction(fname: string, args: Array, notify?: boolean): Promise; 38 | strwidth(str: string, notify?: boolean): Promise; 39 | listRuntimePaths(notify?: boolean): Promise>; 40 | changeDirectory(dir: string, notify?: boolean): Promise; 41 | getCurrentLine(notify?: boolean): Promise; 42 | setCurrentLine(line: string, notify?: boolean): Promise; 43 | delCurrentLine(notify?: boolean): Promise; 44 | getVar(name: string, notify?: boolean): Promise; 45 | setVar(name: string, value: VimValue, notify?: boolean): Promise; 46 | delVar(name: string, notify?: boolean): Promise; 47 | getVvar(name: string, notify?: boolean): Promise; 48 | getOption(name: string, notify?: boolean): Promise; 49 | setOption(name: string, value: VimValue, notify?: boolean): Promise; 50 | outWrite(str: string, notify?: boolean): Promise; 51 | errWrite(str: string, notify?: boolean): Promise; 52 | reportError(str: string, notify?: boolean): Promise; 53 | getBuffers(notify?: boolean): Promise>; 54 | getCurrentBuf(notify?: boolean): Promise; 55 | setCurrentBuffer(buffer: Buffer, notify?: boolean): Promise; 56 | getWindows(notify?: boolean): Promise>; 57 | getCurrentWindow(notify?: boolean): Promise; 58 | setCurrentWindow(window: Window, notify?: boolean): Promise; 59 | getTabpages(notify?: boolean): Promise>; 60 | getCurrentTabpage(notify?: boolean): Promise; 61 | setCurrentTabpage(tabpage: Tabpage, notify?: boolean): Promise; 62 | getMode(notify?: boolean): Promise<{[key: string]: RPCValue}> 63 | subscribe(event: string, notify?: boolean): Promise; 64 | unsubscribe(event: string, notify?: boolean): Promise; 65 | nameToColor(name: string, notify?: boolean): Promise; 66 | getColorMap(notify?: boolean): Promise<{[key: string]: RPCValue}>; 67 | getApiInfo(notify?: boolean): Promise>; 68 | winGetBuffer(window: Window, notify?: boolean): Promise; 69 | winGetHeight(window: Window, notify?: boolean): Promise; 70 | winSetHeight(window: Window, height: number, notify?: boolean): Promise; 71 | winGetWidth(window: Window, notify?: boolean): Promise; 72 | winSetWidth(window: Window, width: number, notify?: boolean): Promise; 73 | winGetVar(window: Window, name: string, notify?: boolean): Promise; 74 | winSetVar(window: Window, name: string, value: VimValue, notify?: boolean): Promise; 75 | winDelVar(window: Window, name: string, notify?: boolean): Promise; 76 | winGetOption(window: Window, name: string, notify?: boolean): Promise; 77 | winSetOption(window: Window, name: string, value: VimValue, notify?: boolean): Promise; 78 | winGetPosition(window: Window, notify?: boolean): Promise>; 79 | winGetTabpage(window: Window, notify?: boolean): Promise; 80 | winIsValid(window: Window, notify?: boolean): Promise; 81 | equals(rhs: Nvim): boolean; 82 | } 83 | export interface Buffer { 84 | getLine(index: number, notify?: boolean): Promise; 85 | setLine(index: number, line: string, notify?: boolean): Promise; 86 | delLine(index: number, notify?: boolean): Promise; 87 | getLineSlice(start: number, end: number, include_start: boolean, include_end: boolean, notify?: boolean): Promise>; 88 | setLineSlice(start: number, end: number, include_start: boolean, include_end: boolean, replacement: Array, notify?: boolean): Promise; 89 | insert(lnum: number, lines: Array, notify?: boolean): Promise; 90 | lineCount(notify?: boolean): Promise; 91 | getLines(start: number, end: number, strict_indexing: boolean, notify?: boolean): Promise>; 92 | setLines(start: number, end: number, strict_indexing: boolean, replacement: Array, notify?: boolean): Promise; 93 | getVar(name: string, notify?: boolean): Promise; 94 | setVar(name: string, value: VimValue, notify?: boolean): Promise; 95 | delVar(name: string, notify?: boolean): Promise; 96 | getOption(name: string, notify?: boolean): Promise; 97 | setOption(name: string, value: VimValue, notify?: boolean): Promise; 98 | getNumber(notify?: boolean): Promise; 99 | getName(notify?: boolean): Promise; 100 | setName(name: string, notify?: boolean): Promise; 101 | isValid(notify?: boolean): Promise; 102 | getMark(name: string, notify?: boolean): Promise>; 103 | addHighlight(src_id: number, hl_group: string, line: number, col_start: number, col_end: number, notify?: boolean): Promise; 104 | clearHighlight(src_id: number, line_start: number, line_end: number, notify?: boolean): Promise; 105 | equals(rhs: Buffer): boolean; 106 | } 107 | export interface Window { 108 | getBuffer(notify?: boolean): Promise; 109 | getCursor(notify?: boolean): Promise>; 110 | setCursor(pos: Array, notify?: boolean): Promise; 111 | getHeight(notify?: boolean): Promise; 112 | setHeight(height: number, notify?: boolean): Promise; 113 | getWidth(notify?: boolean): Promise; 114 | setWidth(width: number, notify?: boolean): Promise; 115 | getVar(name: string, notify?: boolean): Promise; 116 | setVar(name: string, value: VimValue, notify?: boolean): Promise; 117 | delVar(name: string, notify?: boolean): Promise; 118 | getOption(name: string, notify?: boolean): Promise; 119 | setOption(name: string, value: VimValue, notify?: boolean): Promise; 120 | getPosition(notify?: boolean): Promise>; 121 | getTabpage(notify?: boolean): Promise; 122 | isValid(notify?: boolean): Promise; 123 | equals(rhs: Window): boolean; 124 | } 125 | export interface Tabpage { 126 | getWindows(notify?: boolean): Promise>; 127 | getVar(name: string, notify?: boolean): Promise; 128 | setVar(name: string, value: VimValue, notify?: boolean): Promise; 129 | delVar(name: string, notify?: boolean): Promise; 130 | getWindow(notify?: boolean): Promise; 131 | isValid(notify?: boolean): Promise; 132 | equals(rhs: Tabpage): boolean; 133 | } 134 | export function attach(writer: NodeJS.WritableStream, reader: NodeJS.ReadableStream): Promise; 135 | 136 | export interface NvimVersion { major: number; minor: number; patch: number; rest: string; } 137 | export type RPCValue = Buffer | Window | Tabpage | number | boolean | string | any[] | {[key: string]: any}; 138 | export type VimValue = number | boolean | string | any[] | {[key: string]: any} | null 139 | } --------------------------------------------------------------------------------