[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  env: {\n    es6: true,\n    node: true,\n  },\n  extends: [\n    'plugin:@typescript-eslint/recommended',\n    'plugin:@typescript-eslint/recommended-requiring-type-checking',\n    'prettier',\n  ],\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: 'tsconfig.json',\n    sourceType: 'module',\n  },\n  plugins: ['eslint-plugin-jsdoc', 'eslint-plugin-prefer-arrow', '@typescript-eslint'],\n  root: true,\n  ignorePatterns: ['*.js'],\n  rules: {\n    '@typescript-eslint/adjacent-overload-signatures': 'error',\n    '@typescript-eslint/array-type': [\n      'error',\n      {\n        default: 'array-simple',\n      },\n    ],\n    '@typescript-eslint/await-thenable': 'error',\n    '@typescript-eslint/ban-ts-comment': 'error',\n    '@typescript-eslint/consistent-type-assertions': 'error',\n    '@typescript-eslint/dot-notation': 'error',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/naming-convention': [\n      'error',\n      {\n        selector: 'variable',\n        format: ['camelCase', 'UPPER_CASE', 'PascalCase'],\n        leadingUnderscore: 'allow',\n        trailingUnderscore: 'forbid',\n      },\n    ],\n    '@typescript-eslint/no-array-constructor': 'error',\n    // ignore the rule to conform to current code\n    '@typescript-eslint/no-base-to-string': 'off',\n    '@typescript-eslint/no-duplicate-enum-values': 'error',\n    '@typescript-eslint/no-duplicate-type-constituents': 'error',\n    '@typescript-eslint/no-empty-function': 'error',\n    '@typescript-eslint/no-empty-interface': 'error',\n    // ignore the rule to conform to current code\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-extra-non-null-assertion': 'error',\n    '@typescript-eslint/no-floating-promises': 'error',\n    '@typescript-eslint/no-for-in-array': 'error',\n    '@typescript-eslint/no-implied-eval': 'error',\n    '@typescript-eslint/no-loss-of-precision': 'error',\n    '@typescript-eslint/no-misused-new': 'error',\n    '@typescript-eslint/no-misused-promises': [\n      'error',\n      {\n        checksVoidReturn: false, // TODO\n      },\n    ],\n    '@typescript-eslint/no-namespace': 'error',\n    '@typescript-eslint/no-non-null-asserted-optional-chain': 'error',\n    '@typescript-eslint/parameter-properties': 'error',\n    '@typescript-eslint/no-redundant-type-constituents': 'error',\n    '@typescript-eslint/no-shadow': [\n      'error',\n      {\n        hoist: 'all',\n      },\n    ],\n    '@typescript-eslint/no-this-alias': 'error',\n    '@typescript-eslint/no-unnecessary-type-assertion': 'error',\n    '@typescript-eslint/no-unnecessary-type-constraint': 'error',\n    '@typescript-eslint/no-unsafe-argument': 'error',\n    '@typescript-eslint/no-unsafe-assignment': 'error',\n    '@typescript-eslint/no-unsafe-call': 'error',\n    '@typescript-eslint/no-unsafe-declaration-merging': 'error',\n    '@typescript-eslint/no-unsafe-member-access': 'error',\n    '@typescript-eslint/no-unsafe-return': 'error',\n    '@typescript-eslint/no-unused-expressions': 'error',\n    '@typescript-eslint/no-unused-vars': 'off',\n    '@typescript-eslint/no-use-before-define': 'off',\n    '@typescript-eslint/no-var-requires': 'error',\n    '@typescript-eslint/prefer-as-const': 'error',\n    '@typescript-eslint/prefer-for-of': 'error',\n    '@typescript-eslint/prefer-function-type': 'error',\n    '@typescript-eslint/prefer-namespace-keyword': 'error',\n    // ignore the rule to conform to current code\n    '@typescript-eslint/require-await': 'off',\n    '@typescript-eslint/restrict-plus-operands': 'error',\n    // ignore the rule to conform to current code\n    '@typescript-eslint/restrict-template-expressions': 'off',\n    '@typescript-eslint/triple-slash-reference': [\n      'error',\n      {\n        path: 'always',\n        types: 'prefer-import',\n        lib: 'always',\n      },\n    ],\n    '@typescript-eslint/typedef': 'off',\n    '@typescript-eslint/unbound-method': 'error',\n    '@typescript-eslint/unified-signatures': 'error',\n    complexity: 'off',\n    'constructor-super': 'error',\n    'dot-notation': 'off',\n    eqeqeq: ['error', 'smart'],\n    'guard-for-in': 'error',\n    'id-denylist': [\n      'error',\n      'any',\n      'Number',\n      'number',\n      'String',\n      'string',\n      'Boolean',\n      'boolean',\n      'Undefined',\n      'undefined',\n    ],\n    'id-match': 'error',\n    'jsdoc/check-alignment': 'error',\n    // ignore the rule to conform to current code\n    'jsdoc/check-indentation': 'off',\n    'max-classes-per-file': 'off',\n    'new-parens': 'error',\n    'no-array-constructor': 'off',\n    'no-bitwise': 'error',\n    'no-caller': 'error',\n    'no-cond-assign': 'error',\n    'no-console': [\n      'error',\n      {\n        allow: [\n          'log',\n          'warn',\n          'dir',\n          'timeLog',\n          'assert',\n          'clear',\n          'count',\n          'countReset',\n          'group',\n          'groupEnd',\n          'table',\n          'dirxml',\n          'error',\n          'groupCollapsed',\n          'Console',\n          'profile',\n          'profileEnd',\n          'timeStamp',\n          'context',\n        ],\n      },\n    ],\n    'no-debugger': 'error',\n    'no-empty': 'error',\n    'no-empty-function': 'off',\n    'no-eval': 'error',\n    'no-fallthrough': 'error',\n    'no-implied-eval': 'off',\n    'no-invalid-this': 'off',\n    'no-loss-of-precision': 'off',\n    'no-new-wrappers': 'error',\n    'no-return-await': 'error',\n    'no-shadow': 'off',\n    'no-throw-literal': 'error',\n    'no-trailing-spaces': 'error',\n    'no-undef-init': 'error',\n    'no-underscore-dangle': 'off',\n    'no-unsafe-finally': 'error',\n    'no-unused-expressions': 'off',\n    'no-unused-labels': 'error',\n    'no-unused-vars': 'off',\n    'no-use-before-define': 'off',\n    'no-var': 'error',\n    'object-shorthand': 'error',\n    'one-var': ['error', 'never'],\n    'prefer-arrow/prefer-arrow-functions': [\n      'error',\n      {\n        allowStandaloneDeclarations: true,\n      },\n    ],\n    'prefer-const': [\n      'error',\n      {\n        destructuring: 'all',\n      },\n    ],\n    radix: 'error',\n    'require-await': 'off',\n    'spaced-comment': [\n      'error',\n      'always',\n      {\n        markers: ['/'],\n      },\n    ],\n    'use-isnan': 'error',\n    'valid-typeof': 'off',\n  },\n};\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contribution Guide\n\nThis document offers a set of guidelines for contributing to VSCodeVim.\nThese are just guidelines, not rules; use your best judgment and feel free to propose changes to this document.\nIf you need help, drop by on [GitHub Discussions](https://github.com/VSCodeVim/Vim/discussions) or [Slack](https://vscodevim.slack.com/).\n\nThanks for helping us in making VSCodeVim better! :clap:\n\n## Submitting Issues\n\nThe [GitHub issue tracker](https://github.com/VSCodeVim/Vim/issues) is the preferred channel for tracking bugs and enhancement suggestions.\nWhen creating a new bug report do:\n\n- Search against existing issues to check if somebody else has already reported your problem or requested your idea\n- Fill out the issue template.\n\n### Improve Existing Issues\n\n- Try to replicate bugs and describe the method if you're able to.\n- Search for [duplicate issues](https://github.com/VSCodeVim/Vim/issues?q=is%3Aissue+is%3Aopen+cursor). See which thread(s) are more mature, and recommend the duplicate be closed, or just provide links to related issues.\n- Find [old issues](https://github.com/VSCodeVim/Vim/issues?page=25&q=is%3Aissue+is%3Aopen) and test them in the latest version of VSCodeVim. If the issue has been resolved, comment & recommend OP to close (or provide more information if not resolved).\n- Give thumbs up / thumbs down to existing issues, to indicate your support (or not)\n\n## Submitting Pull Requests\n\nPull requests are _awesome_.\nIf you're looking to raise a PR for something which doesn't have an open issue, consider creating an issue first.\n\nWhen submitting a PR, please fill out the template that is presented by GitHub when a PR is opened.\n\n## First Time Setup\n\n1. Install prerequisites:\n   - [Visual Studio Code](https://code.visualstudio.com/), latest stable or insiders\n   - [Node.js](https://nodejs.org/) v22.x or higher\n   - [Yarn](https://classic.yarnpkg.com/) v1.x\n   - _Optional_: [Docker Community Edition](https://store.docker.com/search?type=edition&offering=community) 🐋\n\n2. Fork and clone repository:\n\n   ```bash\n   git clone git@github.com:<YOUR-FORK>/Vim.git\n   cd Vim\n   ```\n\n3. Build extension:\n\n   ```bash\n   # Install the dependencies\n   yarn install\n\n   # Open in VS Code\n   code .\n\n   # Build with one of these...\n   yarn build-dev # Fast build for development\n   yarn build     # Slow build for release\n   yarn watch     # Fast build whenever a file changes\n   ```\n\n4. Run extension using VS Code's \"Run and Debug\" menu\n\n5. Run tests:\n\n   ```bash\n   # If Docker is installed and running:\n   npx gulp test                 # Run tests inside Docker container\n   npx gulp test --grep <REGEX>  # Run only tests/suites matching <REGEX> inside Docker container\n\n   # Otherwise, build and run the tests locally:\n   yarn build                    # Build\n   yarn build-test               # Build tests\n   yarn test                     # Test (must close all instances of VS Code)\n   ```\n\n6. Package and install extension:\n\n   ```bash\n   # Package extension into `vim-<MAJOR>.<MINOR>.<PATCH>.vsix`\n   # (This can be opened and inspected like a .zip file)\n   yarn package\n\n   # Install packaged extension to your local VS Code installation\n   code --install-extension vim-<MAJOR>.<MINOR>.<PATCH>.vsix --force\n   ```\n\n## Code Architecture\n\nThe code is split into two parts:\n\n- ModeHandler - Vim state machine\n- Actions - 'actions' which modify the state\n\n### Actions\n\nActions are all currently stuffed into actions.ts (sorry!). There are:\n\n- `BaseAction` - the base Action type that all Actions derive from.\n- `BaseMovement` - A movement (e.g.`w`, `h`, `{`, etc.) _ONLY_ updates the cursor position or returns an `IMovement`, which indicates a start and stop. This is used for movements like `aw` which may actually start before the cursor.\n- `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 `*`.\n\nActions should, when possible, avoid side effects other than modifying `vimState`.\n\n### The Vim State Machine\n\nConsists of two data structures:\n\n- `VimState` - this is the state of Vim, scoped to a `TextEditor`. It's what actions update.\n- `RecordedState` - this is temporary state that will reset at the end of a change.\n\n#### How it works\n\n1. `handleKeyEventHelper` is called with the most recent keypress.\n2. `Actions.getRelevantAction` determines if all the keys pressed so far uniquely specify any action in actions.ts. If not, we continue waiting for keypresses.\n3. `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.\n4. Now that we've updated `VimState`, we run `updateView` with the new VimState to \"redraw\" VS Code to the new state.\n\n#### `vscode.window.onDidChangeTextEditorSelection`\n\nThis 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!)\n\n## Release\n\nBefore you push a release, be sure to make sure the changelog is updated!\n\nTo push a release:\n\n```bash\nnpx gulp release --semver [SEMVER]\ngit push --follow-tags\n```\n\nThe above Gulp command will:\n\n1. Bump the package version based off the semver supplied. Supported values: patch, minor, major.\n2. Create a Git commit with the above changes.\n3. Create a Git tag using the new package version.\n\nIn 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.\n\n## Troubleshooting\n\n### VS Code Slowdown\n\nIf you notice a slowdown and have ever run `yarn test` in the past instead of running tests through VSCode, you might find a `.vscode-test/` folder, which VSCode is continually consuming CPU cycles to index. Long story short, you can speed up VSCode by:\n\n```bash\nrm -rf .vscode-test/\n```\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [J-Fields]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n1.  Go to '...'\n2.  Click on '....'\n3.  Scroll down to '....'\n4.  See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\nIf remapping-related, please attach log output: https://github.com/VSCodeVim/Vim#debugging-remappings.\n\n**Environment (please complete the following information):**\n\n<!--\nEnsure you are on the latest VSCode + VSCodeVim\nYou can use \"Report Issue\" by running \"Developer: Show Running Extensions\" from the Command Palette to prefill these.\n-->\n\n- Extension (VsCodeVim) version:\n- VSCode version:\n- OS:\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nYay! Thanks for sending us a PR! 🎊\n\nPlease ensure your PR adheres to:\n\n- [ ] Commit messages has a short & issue references when necessary\n- [ ] Each commit does a logical chunk of work.\n- [ ] It builds and tests pass (e.g `gulp`)\n-->\n\n**What this PR does / why we need it**:\n\n**Which issue(s) this PR fixes**\n\n<!--\nCommits in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)\n-->\n\n**Special notes for your reviewer**:\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# Copilot Instructions for VSCodeVim\n\n## Project Overview\n\n- **VSCodeVim** is a complex VS Code extension that emulates Vim behavior within VS Code. It is written in TypeScript and targets both desktop and web environments.\n- The codebase is organized by Vim concepts: actions, motions, operators, modes, state, configuration, and plugin emulation. See `src/` for main logic, with subfolders for each concept.\n- The extension supports advanced features like `.vimrc` parsing, Neovim integration, multi-cursor, and emulated Vim plugins (see `README.md` for the full list).\n\n## Key Architectural Patterns\n\n- **Mode Handler:** Each open document is managed by a `ModeHandler` (`src/mode/`), which can be compared to an instance of Vim. State transitions and command dispatch are centralized here.\n- **State Management:** Each `ModeHandler` has a `VimState` (`src/state/vimState.ts`) which tracks the current mode, cursor, registers, and more. State is passed through most command flows.\n- **Action/Motion/Operator Classes:** User input is parsed into actions, motions, and operators (see `src/actions/`). These are composed to implement Vim commands.\n- **Configuration:** Settings are loaded from VS Code config, `.vimrc`, and defaults, in that order. See `src/configuration/` and `README.md` for details.\n- **Plugin Emulation:** Many Vim plugins are emulated natively (see `src/actions/plugins/` and `README.md` > Emulated Plugins).\n- **Neovim Integration:** If enabled, some Ex-commands are delegated to a Neovim process (`src/neovim/`).\n\n## Developer Workflows\n\n- **Build:** Use `yarn build-dev` (or the VS Code task) to build the extension. See `package.json` and `gulpfile.js` for all tasks.\n- **Test:** Run tests with `yarn build-test` then `yarn test`. Tests are in `test/` and mirror the structure of `src/`.\n- **Debug:** Launch the extension in the Extension Development Host via VS Code's debugger. Use breakpoints in TypeScript files.\n- **Release:** Versioning and release tasks are managed via Gulp (`gulpfile.js`).\n\n## Project-Specific Conventions\n\n- **Remapping:** Key remapping is handled via configuration and `.vimrc`. Only remaps are supported in `.vimrc` (see `README.md`).\n- **Settings Precedence:** Settings are loaded in this order: Ex-commands, user/workspace settings, VS Code settings, then defaults.\n- **Plugin Emulation:** Emulated plugins are implemented as native TypeScript, not as Vimscript or external scripts.\n- **Testing:** Tests are colocated with the feature under test (e.g., `test/actions/`, `test/mode/`).\n- **Cross-Platform:** Some features (e.g., input method switching) require platform-specific configuration (see `README.md`).\n\n## Integration Points\n\n- **VS Code API:** Extension entry point is `extension.ts`. All VS Code API usage is centralized here and in `extensionBase.ts`/`extensionWeb.ts`.\n- **Neovim:** Integration is optional and controlled by settings. See `src/neovim/`.\n- **Status Bar:** Status bar updates are handled in `src/statusBar.ts`.\n\n## References\n\n- See `README.md` for user-facing documentation, settings, and plugin support.\n- See `gulpfile.js` for build/test/release automation.\n- See `src/` for main extension logic, organized by Vim concept.\n- See `test/` for tests, which mirror the structure of `src/`.\n\n---\n\nIf you are unsure about a pattern or workflow, check the `README.md` or the relevant subdirectory in `src/` or `test/`. For new features, follow the structure and conventions of existing code.\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  push:\n    branches:\n      - 'master'\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout VSCodeVim\n        uses: actions/checkout@v6\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 22\n          cache: yarn\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Prettier\n        if: matrix.os != 'windows-latest'\n        run: yarn prettier:check\n\n      - name: Lint\n        if: matrix.os != 'windows-latest'\n        run: yarn lint\n\n      - name: Build\n        run: yarn build\n\n      - name: Test on ubuntu-latest\n        if: matrix.os != 'windows-latest'\n        run: |\n          yarn build-test\n          xvfb-run -a yarn test\n\n      - name: Test on windows-latest\n        if: matrix.os == 'windows-latest'\n        run: |\n          yarn build-test\n          yarn test\n"
  },
  {
    "path": ".github/workflows/pull_request.yml",
    "content": "name: Pull Request\n\non:\n  pull_request:\n    branches:\n      - '**'\n\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - name: Checkout VSCodeVim\n        uses: actions/checkout@v6\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 22\n          cache: yarn\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Prettier\n        run: yarn prettier:check\n\n      - name: Lint\n        run: yarn lint\n\n      - name: Build\n        run: yarn build\n\n      - name: Test\n        run: |\n          yarn build-test\n          xvfb-run -a yarn test\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - v1.[0-9]+.[0-9]+\n\njobs:\n  release:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout VSCodeVim\n        uses: actions/checkout@v6\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 22\n          cache: yarn\n\n      - name: Install dependencies\n        run: yarn install --frozen-lockfile\n\n      - name: Lint\n        run: yarn lint\n\n      - name: Build\n        run: yarn build\n\n      - name: Test\n        run: |\n          yarn build-test\n          xvfb-run -a yarn test\n\n      - name: Build extension package\n        id: build_vsix\n        run: |\n          yarn package;\n          echo ::set-output name=vsix_path::$(ls *.vsix);\n\n      - name: Create release on GitHub\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: ${{ github.ref }}\n          draft: false\n          prerelease: false\n\n      - name: Upload .vsix as release asset to GitHub\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ${{ steps.build_vsix.outputs.vsix_path }}\n          asset_name: ${{ steps.build_vsix.outputs.vsix_path }}\n          asset_content_type: application/zip\n\n      - name: Publish to VSCode Extension Marketplace\n        run: yarn run vsce publish --yarn\n        env:\n          VSCE_PAT: ${{ secrets.VSCE_PAT }}\n\n      - name: Publish to Open VSX Registry\n        uses: HaaLeo/publish-vscode-extension@v1\n        id: publishToOpenVSX\n        with:\n          pat: ${{ secrets.OPEN_VSX_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "out\ntesting\nnode_modules\n*.sw?\n.vscode-test\n.DS_Store\n*.vsix\n*.log\n.cache\n\ntypings/*\n!typings/custom/\ntesting\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "npx lint-staged\n"
  },
  {
    "path": ".prettierignore",
    "content": ".vscode-test\r\nout"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"plugins\": [\"prettier-plugin-organize-imports\"]\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.\n  // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp\n\n  // List of extensions which should be recommended for users of this workspace.\n  \"recommendations\": [\"esbenp.prettier-vscode\", \"dbaeumer.vscode-eslint\"],\n  // List of extensions recommended by VS Code that should not be recommended for users of this workspace.\n  \"unwantedRecommendations\": []\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "content": "// A launch configuration that compiles the extension and then opens it inside a new window\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Build, Run Extension\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"runtimeExecutable\": \"${execPath}\",\n      \"args\": [\"--extensionDevelopmentPath=${workspaceRoot}\"],\n      \"stopOnEntry\": false,\n      \"sourceMaps\": true,\n      \"outFiles\": [\"${workspaceRoot}/{out, node_modules}/**/*.js\"],\n      \"preLaunchTask\": \"gulp: build-dev\",\n      \"internalConsoleOptions\": \"openOnSessionStart\"\n    },\n    {\n      \"name\": \"Run Extension\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"runtimeExecutable\": \"${execPath}\",\n      \"args\": [\"--extensionDevelopmentPath=${workspaceRoot}\"],\n      \"stopOnEntry\": false,\n      \"sourceMaps\": true,\n      \"outFiles\": [\"${workspaceRoot}/{out, node_modules}/**/*.js\"]\n    },\n    {\n      \"name\": \"Run Tests\",\n      \"type\": \"extensionHost\",\n      \"request\": \"launch\",\n      \"runtimeExecutable\": \"${execPath}\",\n      \"args\": [\n        \"--extensionDevelopmentPath=${workspaceRoot}\",\n        \"--extensionTestsPath=${workspaceRoot}/out/test\"\n      ],\n      \"stopOnEntry\": false,\n      \"sourceMaps\": true,\n      \"outFiles\": [\"${workspaceRoot}/{out, node_modules}/**/*.js\"],\n      \"preLaunchTask\": \"gulp: prepare-test\",\n      \"internalConsoleOptions\": \"openOnSessionStart\"\n    },\n    {\n      \"name\": \"Run Web Extension\",\n      \"type\": \"pwa-extensionHost\",\n      \"debugWebWorkerHost\": true,\n      \"request\": \"launch\",\n      \"args\": [\"--extensionDevelopmentPath=${workspaceRoot}\", \"--extensionDevelopmentKind=web\"],\n      \"outFiles\": [\"${workspaceRoot}/{out, node_modules}/**/*.js\"]\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"files.trimTrailingWhitespace\": true,\n  \"files.exclude\": {\n    \"out\": false // set this to true to hide the \"out\" folder with the compiled JS files\n  },\n  \"search.exclude\": {\n    \"out\": true // set this to false to include \"out\" folder in search results\n  },\n  \"typescript.tsdk\": \"./node_modules/typescript/lib\", // we want to use the TS server from our node_modules folder to control its version\n  \"editor.tabSize\": 2,\n  \"editor.insertSpaces\": true\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "// Available variables which can be used inside of strings.\n// ${workspaceRoot}: the root folder of the team\n// ${file}: the current opened file\n// ${fileBasename}: the current opened file's basename\n// ${fileDirname}: the current opened file's dirname\n// ${fileExtname}: the current opened file's extension\n// ${cwd}: the current working directory of the spawned process\n{\n  \"version\": \"2.0.0\",\n  \"tasks\": [\n    {\n      \"type\": \"gulp\",\n      \"task\": \"default\",\n      \"problemMatcher\": \"$tsc-watch\"\n    },\n    {\n      \"type\": \"gulp\",\n      \"task\": \"build-dev\",\n      \"problemMatcher\": []\n    },\n    {\n      \"type\": \"gulp\",\n      \"task\": \"prepare-test\",\n      \"problemMatcher\": []\n    }\n  ]\n}\n"
  },
  {
    "path": ".vscodeignore",
    "content": ".github/**\n.husky/**\n.yarn/**\n.vscode/**\n.vscode-test/**\n\n**/*.ts\n*.yml\n\nsrc/**\nbuild/**\ntest/**\ntypings/**\nout/src/**\nout/test/**\nout/*.map\nnode_modules/**\nimages/design/**\n\n.gitignore\n.prettierignore\n\ntsconfig.json\nwebpack.*.js\ngulpfile.js\nrenovate.json\n"
  },
  {
    "path": ".yarnrc",
    "content": "ignore-engines true"
  },
  {
    "path": "CHANGELOG.OLD.md",
    "content": "# Change Log\n\n## **_NOTE: For versions 1.23.0 and newer, include the lastest changes; please see [CHANGELOG.md](CHANGELOG.md)._**\n\n## [v1.22.2](https://github.com/vscodevim/vim/tree/v1.22.2) (2022-02-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.22.1...v1.22.2)\n\n**Fixed Bugs:**\n\n- Failed to handle key `j`: Cannot read property 'substring' of undefined [\\#7512](https://github.com/VSCodeVim/Vim/issues/7512)\n- 1.22 broken for browser [\\#7469](https://github.com/VSCodeVim/Vim/issues/7469)\n- Tab completion of file names should be case insensitive on Windows [\\#7160](https://github.com/VSCodeVim/Vim/issues/7160)\n\n**Merged pull requests:**\n\n- Fix extension for web [\\#7520](https://github.com/VSCodeVim/Vim/pull/7520) ([jeanp413](https://github.com/jeanp413))\n- fix bugs with: Failed to handle key ... Cannot read property 'substring' of undefined [\\#7513](https://github.com/VSCodeVim/Vim/pull/7513) ([elazarcoh](https://github.com/elazarcoh))\n- Tab completion of file names is case insensitive on Windows [\\#7471](https://github.com/VSCodeVim/Vim/pull/7471) ([elazarcoh](https://github.com/elazarcoh))\n\n## [v1.22.1](https://github.com/vscodevim/vim/tree/v1.22.1) (2022-02-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.22.0...v1.22.1)\n\n**Fixed Bugs:**\n\n- `\\#` does not work with `visualstar` enabled [\\#7463](https://github.com/VSCodeVim/Vim/issues/7463)\n- `\\*` does not reliably update search highlights [\\#7462](https://github.com/VSCodeVim/Vim/issues/7462)\n\n**Merged pull requests:**\n\n- Added documentation for complex keyboard shortcuts [\\#6944](https://github.com/VSCodeVim/Vim/pull/6944) ([w-cantin](https://github.com/w-cantin))\n\n## [v1.22.0](https://github.com/vscodevim/vim/tree/v1.22.0) (2022-02-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.10...v1.22.0)\n\n**Enhancements:**\n\n- Vim filter with WSL2 is unavailable [\\#7100](https://github.com/VSCodeVim/Vim/issues/7100)\n\n**Fixed Bugs:**\n\n- `O` does not properly preserve indentation [\\#7423](https://github.com/VSCodeVim/Vim/issues/7423)\n- `:marks` to list marks doesn't work [\\#7367](https://github.com/VSCodeVim/Vim/issues/7367)\n- `cgn` fails when the match is one character long [\\#7291](https://github.com/VSCodeVim/Vim/issues/7291)\n- Notebook-cells duplicate marks [\\#7280](https://github.com/VSCodeVim/Vim/issues/7280)\n- Command line `move` command throws `E488: Trailing characters` [\\#7207](https://github.com/VSCodeVim/Vim/issues/7207)\n- Opening search in multiple editors leads to invalid state [\\#7038](https://github.com/VSCodeVim/Vim/issues/7038)\n- `:s` does not properly handle capture groups \\(`\\0`, `\\1`, ...\\) [\\#6963](https://github.com/VSCodeVim/Vim/issues/6963)\n- Undo doesn't work in Notebook Cell [\\#6960](https://github.com/VSCodeVim/Vim/issues/6960)\n- Cannot paste with `\\<D-v\\>` in command-line mode on OSX. Works with `\\<C-v\\>` [\\#6922](https://github.com/VSCodeVim/Vim/issues/6922)\n- Exclude `:w`, `:q`, and `:wq` from dot command [\\#6829](https://github.com/VSCodeVim/Vim/issues/6829)\n\n**Closed issues:**\n\n- why sneak doesn't highlight matching results using different characters as expected? [\\#7429](https://github.com/VSCodeVim/Vim/issues/7429)\n- gh to show variable value in debug mode [\\#7409](https://github.com/VSCodeVim/Vim/issues/7409)\n- Vimrc file won't reload after saving file with vimrcPath's home environment variable [\\#7359](https://github.com/VSCodeVim/Vim/issues/7359)\n- vim.editVimrc command failed with vimrcPath's home environment variable [\\#7358](https://github.com/VSCodeVim/Vim/issues/7358)\n- Keep selection when copying text [\\#7352](https://github.com/VSCodeVim/Vim/issues/7352)\n- Type lower case \"j\" in insert mode has strange cursor behavior. [\\#7351](https://github.com/VSCodeVim/Vim/issues/7351)\n- `vim.mode == 'Normal'` in key binding is triggering in replace mode [\\#7256](https://github.com/VSCodeVim/Vim/issues/7256)\n- Use a separate color for the current match while searching [\\#7212](https://github.com/VSCodeVim/Vim/issues/7212)\n- Can't install in Azure Data Studio from VSIX package [\\#7079](https://github.com/VSCodeVim/Vim/issues/7079)\n\n**Merged pull requests:**\n\n- Fixed a bug where insertLineAbove would leave extra whitespace. [\\#7450](https://github.com/VSCodeVim/Vim/pull/7450) ([half-potato](https://github.com/half-potato))\n- add sentence when currentChar is undefined [\\#7439](https://github.com/VSCodeVim/Vim/pull/7439) ([monjara](https://github.com/monjara))\n- Exclude :w, :q, and :wq from dot command [\\#7428](https://github.com/VSCodeVim/Vim/pull/7428) ([justalmill](https://github.com/justalmill))\n- Fixed insertLineBefore indent behavior [\\#7424](https://github.com/VSCodeVim/Vim/pull/7424) ([half-potato](https://github.com/half-potato))\n- Implement `inccommand` [\\#7416](https://github.com/VSCodeVim/Vim/pull/7416) ([adrsm108](https://github.com/adrsm108))\n- added enable key-repeating doc for Codium Exploration Users [\\#7408](https://github.com/VSCodeVim/Vim/pull/7408) ([AMMAR-62](https://github.com/AMMAR-62))\n- Disable other extensions while running tests for avoiding unexpected side effect [\\#7376](https://github.com/VSCodeVim/Vim/pull/7376) ([waynewaynetsai](https://github.com/waynewaynetsai))\n- Fix \\<D-c\\> override system-clipboard issue for macOS users [\\#7375](https://github.com/VSCodeVim/Vim/pull/7375) ([waynewaynetsai](https://github.com/waynewaynetsai))\n- fix typo in README [\\#7365](https://github.com/VSCodeVim/Vim/pull/7365) ([ambiguous48](https://github.com/ambiguous48))\n- Fix .vimrc file's issues with vimrcPath's home environment variable [\\#7360](https://github.com/VSCodeVim/Vim/pull/7360) ([waynewaynetsai](https://github.com/waynewaynetsai))\n- Update README.md [\\#7311](https://github.com/VSCodeVim/Vim/pull/7311) ([xerosanyam](https://github.com/xerosanyam))\n- Silence failing tests on Windows and add Windows build step [\\#7293](https://github.com/VSCodeVim/Vim/pull/7293) ([tagniam](https://github.com/tagniam))\n- Add `vim.shell` setting for custom `!` shell [\\#7255](https://github.com/VSCodeVim/Vim/pull/7255) ([tagniam](https://github.com/tagniam))\n- Add silent option to key remappings [\\#7253](https://github.com/VSCodeVim/Vim/pull/7253) ([mly32](https://github.com/mly32))\n- Refactor `externalCommand.ts` to not use temporary files [\\#7252](https://github.com/VSCodeVim/Vim/pull/7252) ([tagniam](https://github.com/tagniam))\n- fix \\#6922: paste with \\<D-v\\> in command-line mode [\\#7227](https://github.com/VSCodeVim/Vim/pull/7227) ([Injae-Lee](https://github.com/Injae-Lee))\n- Improve incremental search [\\#7224](https://github.com/VSCodeVim/Vim/pull/7224) ([adrsm108](https://github.com/adrsm108))\n- search operator `\\%V` [\\#7215](https://github.com/VSCodeVim/Vim/pull/7215) ([elazarcoh](https://github.com/elazarcoh))\n- Fix a typo in the bug report template [\\#7205](https://github.com/VSCodeVim/Vim/pull/7205) ([brettcannon](https://github.com/brettcannon))\n- Fix release date error [\\#7178](https://github.com/VSCodeVim/Vim/pull/7178) ([oo6](https://github.com/oo6))\n- Added documentation for argument text objects [\\#6942](https://github.com/VSCodeVim/Vim/pull/6942) ([w-cantin](https://github.com/w-cantin))\n\n## [v1.21.10](https://github.com/vscodevim/vim/tree/v1.21.10) (2021-10-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.9...v1.21.10)\n\n**Fixed Bugs:**\n\n- `:tabo\\[nly\\]` and `:tabc\\[lose\\]` throw `E488` in version 1.21.9 [\\#7171](https://github.com/VSCodeVim/Vim/issues/7171)\n\n## [v1.21.9](https://github.com/vscodevim/vim/tree/v1.21.9) (2021-10-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.8...v1.21.9)\n\n**Fixed Bugs:**\n\n- /pattern/s/.../.../ doesn't work [\\#7151](https://github.com/VSCodeVim/Vim/issues/7151)\n- 1.21.8 does not work in web worker anymore [\\#7150](https://github.com/VSCodeVim/Vim/issues/7150)\n- `\\*` throws an error when `wordSeparators` doesn't have `/` [\\#7135](https://github.com/VSCodeVim/Vim/issues/7135)\n- `iskeyword` doesn't work for multiple languages [\\#7123](https://github.com/VSCodeVim/Vim/issues/7123)\n- Ex \"copy\" command with `.`, `-`, or `+` \\(current, previous, or next line\\) at end of command stopped working [\\#7058](https://github.com/VSCodeVim/Vim/issues/7058)\n\n**Closed issues:**\n\n- README.md missing installation item: linux setup [\\#7080](https://github.com/VSCodeVim/Vim/issues/7080)\n\n**Merged pull requests:**\n\n- Load process polyfill automatically, required by util [\\#7156](https://github.com/VSCodeVim/Vim/pull/7156) ([jeanp413](https://github.com/jeanp413))\n- Add pane resize keybindings [\\#7138](https://github.com/VSCodeVim/Vim/pull/7138) ([tagniam](https://github.com/tagniam))\n- Update ROADMAP.ZH.md [\\#7137](https://github.com/VSCodeVim/Vim/pull/7137) ([hellorayza](https://github.com/hellorayza))\n- iskeyword is evaluated when a command is called \\(\\#7123\\) [\\#7126](https://github.com/VSCodeVim/Vim/pull/7126) ([shinichy](https://github.com/shinichy))\n- Fix bang command with ranges [\\#7122](https://github.com/VSCodeVim/Vim/pull/7122) ([tagniam](https://github.com/tagniam))\n- \\#6553: capture file mode and restore it after force write [\\#7092](https://github.com/VSCodeVim/Vim/pull/7092) ([joecrop](https://github.com/joecrop))\n\n## [v1.21.8](https://github.com/vscodevim/vim/tree/v1.21.8) (2021-09-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.7...v1.21.8)\n\n**Enhancements:**\n\n- Support `:substitute`'s `n` flag \\(count matches without substituting\\) [\\#7081](https://github.com/VSCodeVim/Vim/issues/7081)\n- Support `\\['` and `\\]'` \\(move to nearby lowercase mark\\) commands [\\#7041](https://github.com/VSCodeVim/Vim/issues/7041)\n\n**Closed issues:**\n\n- Inconsistent indentation? [\\#7107](https://github.com/VSCodeVim/Vim/issues/7107)\n- Cannot change to normal mode. [\\#7106](https://github.com/VSCodeVim/Vim/issues/7106)\n- Simple movement like HJKL should not be recorded in jump history for Ctrl-O and Ctrl-I [\\#7102](https://github.com/VSCodeVim/Vim/issues/7102)\n\n**Merged pull requests:**\n\n- fix ROADMAP.md typo [\\#7066](https://github.com/VSCodeVim/Vim/pull/7066) ([mly32](https://github.com/mly32))\n- make vim strict ui extension [\\#7049](https://github.com/VSCodeVim/Vim/pull/7049) ([sandy081](https://github.com/sandy081))\n- Added documentation for all Vim Modes [\\#6945](https://github.com/VSCodeVim/Vim/pull/6945) ([w-cantin](https://github.com/w-cantin))\n\n## [v1.21.7](https://github.com/vscodevim/vim/tree/v1.21.7) (2021-08-31)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.6...v1.21.7)\n\n**Enhancements:**\n\n- `:delete` and `:yank` should support `{count}` argument [\\#6995](https://github.com/VSCodeVim/Vim/issues/6995)\n\n**Fixed Bugs:**\n\n- Failed to handle key=\\<C-e\\>. Cannot read property 'end' of undefined [\\#7027](https://github.com/VSCodeVim/Vim/issues/7027)\n- Failed to handle key=\\<Esc\\>. e.getTransformation is not a function [\\#7009](https://github.com/VSCodeVim/Vim/issues/7009)\n\n**Closed issues:**\n\n- Why vim-surround command csw\" \\(word surround\\) is not working now? [\\#7003](https://github.com/VSCodeVim/Vim/issues/7003)\n- Allow for appending to \\[a-z\\] registers [\\#6965](https://github.com/VSCodeVim/Vim/issues/6965)\n\n**Merged pull requests:**\n\n- Show command and search when showmodename is disabled [\\#7021](https://github.com/VSCodeVim/Vim/pull/7021) ([BlakeWilliams](https://github.com/BlakeWilliams))\n- Adds count argument to `:yank` and `:delete` commands [\\#7007](https://github.com/VSCodeVim/Vim/pull/7007) ([DevinLeamy](https://github.com/DevinLeamy))\n- fix: \\<tab\\> behavior in replace mode [\\#6997](https://github.com/VSCodeVim/Vim/pull/6997) ([Komar0ff](https://github.com/Komar0ff))\n- Append to \\[a-z\\] registers [\\#6971](https://github.com/VSCodeVim/Vim/pull/6971) ([DevinLeamy](https://github.com/DevinLeamy))\n\n## [v1.21.6](https://github.com/vscodevim/vim/tree/v1.21.6) (2021-08-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.5...v1.21.6)\n\n**Fixed Bugs:**\n\n- Backslashes must be duplicated in :s substitution [\\#6890](https://github.com/VSCodeVim/Vim/issues/6890)\n- Failed to handle key=\\<Esc\\>. Overlapping ranges are not allowed! [\\#6888](https://github.com/VSCodeVim/Vim/issues/6888)\n- Failed to handle key=:. No cursor index - this should never ever happen! [\\#6887](https://github.com/VSCodeVim/Vim/issues/6887)\n- `:marks` show error position when focusing on another file [\\#6886](https://github.com/VSCodeVim/Vim/issues/6886)\n- Failed to handle key=.. Illegal argument: line must be non-negative [\\#6870](https://github.com/VSCodeVim/Vim/issues/6870)\n- Repeating with `.` does not play nice with auto-matching quotes [\\#6819](https://github.com/VSCodeVim/Vim/issues/6819)\n\n**Closed issues:**\n\n- s [\\#6959](https://github.com/VSCodeVim/Vim/issues/6959)\n- Make \"gd\" Open definition to the side in Search Editor [\\#6921](https://github.com/VSCodeVim/Vim/issues/6921)\n- Failed to handle key=\\<C-o\\>. Could NOT open editor for \"file:///home/fabrice/CRIStAL/Speed/examples/train_example.py\". [\\#6868](https://github.com/VSCodeVim/Vim/issues/6868)\n- Failed to handle key=2. Cannot read property 'length' of undefined [\\#6861](https://github.com/VSCodeVim/Vim/issues/6861)\n- Failed to handle key=.. Overlapping ranges are not allowed! [\\#6840](https://github.com/VSCodeVim/Vim/issues/6840)\n\n**Merged pull requests:**\n\n- Fix history navigation in VS Code interactive window [\\#6980](https://github.com/VSCodeVim/Vim/pull/6980) ([rebornix](https://github.com/rebornix))\n- Remove look behind for Safari [\\#6937](https://github.com/VSCodeVim/Vim/pull/6937) ([rebornix](https://github.com/rebornix))\n- Fix /\\\\c by requiring odd number of \\'s before c for case \\(in\\)sensitivity [\\#6900](https://github.com/VSCodeVim/Vim/pull/6900) ([edemaine](https://github.com/edemaine))\n- Fix escaping in :s substitutions [\\#6891](https://github.com/VSCodeVim/Vim/pull/6891) ([edemaine](https://github.com/edemaine))\n- Argument text object documentation [\\#6857](https://github.com/VSCodeVim/Vim/pull/6857) ([w-cantin](https://github.com/w-cantin))\n- Add debuggingForeground to colorCustomizations [\\#6852](https://github.com/VSCodeVim/Vim/pull/6852) ([lmlorca](https://github.com/lmlorca))\n\n## [v1.21.5](https://github.com/vscodevim/vim/tree/v1.21.5) (2021-07-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.4...v1.21.5)\n\n**Fixed Bugs:**\n\n- :sort u merges two duplicates [\\#6825](https://github.com/VSCodeVim/Vim/issues/6825)\n- setting space as leader key does not work anymore [\\#6824](https://github.com/VSCodeVim/Vim/issues/6824)\n- Problems with \\<leader\\> key and remapping in the latest version [\\#6821](https://github.com/VSCodeVim/Vim/issues/6821)\n\n**Merged pull requests:**\n\n- Fix sort unique bug [\\#6835](https://github.com/VSCodeVim/Vim/pull/6835) ([sixskys](https://github.com/sixskys))\n\n## [v1.21.4](https://github.com/vscodevim/vim/tree/v1.21.4) (2021-07-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.3...v1.21.4)\n\n**Fixed Bugs:**\n\n- `2i\"` should act like `a\"`, but exclude whitespace before/after the quotes [\\#6806](https://github.com/VSCodeVim/Vim/issues/6806)\n\n## [v1.21.3](https://github.com/vscodevim/vim/tree/v1.21.3) (2021-06-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.2...v1.21.3)\n\n## [v1.21.2](https://github.com/vscodevim/vim/tree/v1.21.2) (2021-06-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.1...v1.21.2)\n\n## [v1.21.1](https://github.com/vscodevim/vim/tree/v1.21.1) (2021-06-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.21.0...v1.21.1)\n\n## [v1.21.0](https://github.com/vscodevim/vim/tree/v1.21.0) (2021-06-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.20.3...v1.21.0)\n\n## [v1.20.3](https://github.com/vscodevim/vim/tree/v1.20.3) (2021-05-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.20.2...v1.20.3)\n\n## [v1.20.2](https://github.com/vscodevim/vim/tree/v1.20.2) (2021-04-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.20.1...v1.20.2)\n\n## [v1.20.1](https://github.com/vscodevim/vim/tree/v1.20.1) (2021-04-25)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.20.0...v1.20.1)\n\n## [v1.20.0](https://github.com/vscodevim/vim/tree/v1.20.0) (2021-04-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.19.3...v1.20.0)\n\n## [v1.19.3](https://github.com/vscodevim/vim/tree/v1.19.3) (2021-03-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.19.2...v1.19.3)\n\n## [v1.19.2](https://github.com/vscodevim/vim/tree/v1.19.2) (2021-03-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.19.1...v1.19.2)\n\n## [v1.19.1](https://github.com/vscodevim/vim/tree/v1.19.1) (2021-03-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.19.0...v1.19.1)\n\n## [v1.19.0](https://github.com/vscodevim/vim/tree/v1.19.0) (2021-03-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.9...v1.19.0)\n\n## [v1.18.9](https://github.com/vscodevim/vim/tree/v1.18.9) (2021-02-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.8...v1.18.9)\n\n## [v1.18.8](https://github.com/vscodevim/vim/tree/v1.18.8) (2021-02-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.7...v1.18.8)\n\n## [v1.18.7](https://github.com/vscodevim/vim/tree/v1.18.7) (2021-02-01)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.5...v1.18.7)\n\n## [v1.18.5](https://github.com/vscodevim/vim/tree/v1.18.5) (2020-12-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.4...v1.18.5)\n\n## [v1.18.4](https://github.com/vscodevim/vim/tree/v1.18.4) (2020-12-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.3...v1.18.4)\n\n## [v1.18.3](https://github.com/vscodevim/vim/tree/v1.18.3) (2020-12-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.2...v1.18.3)\n\n## [v1.18.2](https://github.com/vscodevim/vim/tree/v1.18.2) (2020-12-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.18.0...v1.18.2)\n\n## [v1.18.0](https://github.com/vscodevim/vim/tree/v1.18.0) (2020-12-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.17.1...v1.18.0)\n\n## [v1.17.1](https://github.com/vscodevim/vim/tree/v1.17.1) (2020-09-25)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.17.0...v1.17.1)\n\n## [v1.17.0](https://github.com/vscodevim/vim/tree/v1.17.0) (2020-09-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/beta...v1.17.0)\n\n## [beta](https://github.com/vscodevim/vim/tree/beta) (2020-09-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.16.0...beta)\n\n## [v1.11.0](https://github.com/vscodevim/vim/tree/v1.11.0) (2019-09-28)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.10.2...v1.11.0)\n\n**Enhancements:**\n\n- Support VSCode's View: Toggle Panel in vim mode. [\\#4103](https://github.com/VSCodeVim/Vim/issues/4103)\n- Store subparsers in terms of abbreviation and full command [\\#4094](https://github.com/VSCodeVim/Vim/issues/4094)\n- directories are un-completable with tab-completion [\\#4085](https://github.com/VSCodeVim/Vim/issues/4085)\n- Command mode status bar is too small [\\#4077](https://github.com/VSCodeVim/Vim/issues/4077)\n- set cursorcolumn [\\#4076](https://github.com/VSCodeVim/Vim/issues/4076)\n- Support for whichwarp [\\#4068](https://github.com/VSCodeVim/Vim/issues/4068)\n- Command line does not support Ctrl-W [\\#4027](https://github.com/VSCodeVim/Vim/issues/4027)\n- Add setting to swap ; with : in Easymotion [\\#4020](https://github.com/VSCodeVim/Vim/issues/4020)\n- Allow for placeholders in rebindings [\\#4012](https://github.com/VSCodeVim/Vim/issues/4012)\n- Support :his\\[tory\\] [\\#3949](https://github.com/VSCodeVim/Vim/issues/3949)\n- Support gdefault option [\\#3594](https://github.com/VSCodeVim/Vim/issues/3594)\n\n**Fixed Bugs:**\n\n- Find and replace all occurances in current line does not work [\\#4067](https://github.com/VSCodeVim/Vim/issues/4067)\n- Commentary does not work in visual block mode [\\#4036](https://github.com/VSCodeVim/Vim/issues/4036)\n- Change operator doesn't behave linewise when appropriate [\\#4024](https://github.com/VSCodeVim/Vim/issues/4024)\n- \\$ command takes newline in visual mode [\\#3970](https://github.com/VSCodeVim/Vim/issues/3970)\n- Text reflow doesn't respect tabs [\\#3929](https://github.com/VSCodeVim/Vim/issues/3929)\n- commands \\(d, y, c...\\) don't work with the smart selection [\\#3850](https://github.com/VSCodeVim/Vim/issues/3850)\n- :split Can't Open Files With Names That Include Spaces [\\#3824](https://github.com/VSCodeVim/Vim/issues/3824)\n- Unexpected jumping after deleting a line with 'd-d' [\\#3804](https://github.com/VSCodeVim/Vim/issues/3804)\n- jk doesn't respect tab size [\\#3796](https://github.com/VSCodeVim/Vim/issues/3796)\n- 'dd' followed by any character jumps cursor to end of file. [\\#3713](https://github.com/VSCodeVim/Vim/issues/3713)\n- In ctrl v mode, c doesn't change all instances [\\#3601](https://github.com/VSCodeVim/Vim/issues/3601)\n\n**Closed issues:**\n\n- gf doesn't work for files not from current directory [\\#4099](https://github.com/VSCodeVim/Vim/issues/4099)\n- ViM extension makes VSCode super slow, typing is almost impossible. [\\#4088](https://github.com/VSCodeVim/Vim/issues/4088)\n- mapping control-something to escape in insert doesn't work [\\#4062](https://github.com/VSCodeVim/Vim/issues/4062)\n- When Overtype extension presents, VSCodeVim stops working. [\\#4046](https://github.com/VSCodeVim/Vim/issues/4046)\n- \\<C-v\\> in search mode doesn't respect cursor position [\\#4044](https://github.com/VSCodeVim/Vim/issues/4044)\n- Tests for special keys on command line [\\#4040](https://github.com/VSCodeVim/Vim/issues/4040)\n- Cannot find module 'winston-transport' [\\#4029](https://github.com/VSCodeVim/Vim/issues/4029)\n- How to re-map \":e\" to \":w\"? [\\#4026](https://github.com/VSCodeVim/Vim/issues/4026)\n- Ctrl+h ignores useCtrlKeys and handleKeys binds [\\#4019](https://github.com/VSCodeVim/Vim/issues/4019)\n- It is possible to scroll the cursor out of screen [\\#3846](https://github.com/VSCodeVim/Vim/issues/3846)\n- ModeHandler messages not coming through debug console [\\#3828](https://github.com/VSCodeVim/Vim/issues/3828)\n- :o fails in remote SSH [\\#3815](https://github.com/VSCodeVim/Vim/issues/3815)\n- Being able to disable VIM on startup [\\#3783](https://github.com/VSCodeVim/Vim/issues/3783)\n- Autocomplete feature [\\#3570](https://github.com/VSCodeVim/Vim/issues/3570)\n\n**Merged pull requests:**\n\n- Use command abbreviations [\\#4106](https://github.com/VSCodeVim/Vim/pull/4106) ([J-Fields](https://github.com/J-Fields))\n- Update dependency @types/node to v12.7.8 [\\#4100](https://github.com/VSCodeVim/Vim/pull/4100) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/node to v12.7.7 [\\#4097](https://github.com/VSCodeVim/Vim/pull/4097) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency sinon to v7.5.0 [\\#4095](https://github.com/VSCodeVim/Vim/pull/4095) ([renovate[bot]](https://github.com/apps/renovate))\n- Tests for special keys on the command line [\\#4090](https://github.com/VSCodeVim/Vim/pull/4090) ([J-Fields](https://github.com/J-Fields))\n- Add shift+tab support for cmd line [\\#4089](https://github.com/VSCodeVim/Vim/pull/4089) ([stevenguh](https://github.com/stevenguh))\n- Update dependency ts-loader to v6.1.2 [\\#4087](https://github.com/VSCodeVim/Vim/pull/4087) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ts-loader to v6.1.1 [\\#4084](https://github.com/VSCodeVim/Vim/pull/4084) ([renovate[bot]](https://github.com/apps/renovate))\n- Add missing `to` in CONTRIBUTING.md [\\#4080](https://github.com/VSCodeVim/Vim/pull/4080) ([caleywoods](https://github.com/caleywoods))\n- Fix incorrect position when editing the same file in 2 splits [\\#4074](https://github.com/VSCodeVim/Vim/pull/4074) ([uHOOCCOOHu](https://github.com/uHOOCCOOHu))\n- Smile command [\\#4070](https://github.com/VSCodeVim/Vim/pull/4070) ([caleywoods](https://github.com/caleywoods))\n- Update dependency @types/node to v12.7.5 [\\#4066](https://github.com/VSCodeVim/Vim/pull/4066) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency ts-loader to v6.1.0 [\\#4065](https://github.com/VSCodeVim/Vim/pull/4065) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency typescript to v3.6.3 [\\#4064](https://github.com/VSCodeVim/Vim/pull/4064) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tslint to v5.20.0 [\\#4060](https://github.com/VSCodeVim/Vim/pull/4060) ([renovate[bot]](https://github.com/apps/renovate))\n- Don't use lodash for things ES6 supports natively [\\#4056](https://github.com/VSCodeVim/Vim/pull/4056) ([J-Fields](https://github.com/J-Fields))\n- Pin dependencies [\\#4051](https://github.com/VSCodeVim/Vim/pull/4051) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix gq to handle tab indentation [\\#4050](https://github.com/VSCodeVim/Vim/pull/4050) ([orn688](https://github.com/orn688))\n- Add flag to replace `f` with a single-character sneak [\\#4048](https://github.com/VSCodeVim/Vim/pull/4048) ([J-Fields](https://github.com/J-Fields))\n- \\<C-v\\> doesn't respect the cursor in search mode [\\#4045](https://github.com/VSCodeVim/Vim/pull/4045) ([stevenguh](https://github.com/stevenguh))\n- Fix dependencies [\\#4037](https://github.com/VSCodeVim/Vim/pull/4037) ([J-Fields](https://github.com/J-Fields))\n- Update dependency @types/node to v12.7.4 [\\#4033](https://github.com/VSCodeVim/Vim/pull/4033) ([renovate[bot]](https://github.com/apps/renovate))\n- Refactor the existing file opening and auto completion [\\#4032](https://github.com/VSCodeVim/Vim/pull/4032) ([stevenguh](https://github.com/stevenguh))\n- Remove word in command line with \\<C-w\\> [\\#4031](https://github.com/VSCodeVim/Vim/pull/4031) ([stevenguh](https://github.com/stevenguh))\n- Update dependency sinon to v7.4.2 [\\#4030](https://github.com/VSCodeVim/Vim/pull/4030) ([renovate[bot]](https://github.com/apps/renovate))\n- Implement `nowrapscan` [\\#4028](https://github.com/VSCodeVim/Vim/pull/4028) ([contrib15](https://github.com/contrib15))\n- linewise change operator [\\#4025](https://github.com/VSCodeVim/Vim/pull/4025) ([JoshuaRichards](https://github.com/JoshuaRichards))\n- Fix gj/gk so it maintains cursor position [\\#3890](https://github.com/VSCodeVim/Vim/pull/3890) ([hetmankp](https://github.com/hetmankp))\n- WebPack builds for improved loading times [\\#3889](https://github.com/VSCodeVim/Vim/pull/3889) ([ianjfrosst](https://github.com/ianjfrosst))\n\n## [v1.10.2](https://github.com/vscodevim/vim/tree/v1.10.2) (2019-09-01)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.10.1...v1.10.2)\n\n**Closed issues:**\n\n- Cut release 1.10.1 [\\#4022](https://github.com/VSCodeVim/Vim/issues/4022)\n\n**Merged pull requests:**\n\n- Fix case sensitive sorting [\\#4023](https://github.com/VSCodeVim/Vim/pull/4023) ([noslaver](https://github.com/noslaver))\n\n## [v1.10.1](https://github.com/vscodevim/vim/tree/v1.10.1) (2019-08-31)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.10.0...v1.10.1)\n\n**Fixed Bugs:**\n\n- ReplaceWithRegister doesn't work in visual mode [\\#4015](https://github.com/VSCodeVim/Vim/issues/4015)\n- \\<C-n\\> not working in 1.10.0 [\\#4011](https://github.com/VSCodeVim/Vim/issues/4011)\n- zh/zl/zH/zL not working properly [\\#4008](https://github.com/VSCodeVim/Vim/issues/4008)\n\n**Closed issues:**\n\n- Ctrl-P and Ctrl-N can‘t work in the latest version [\\#4017](https://github.com/VSCodeVim/Vim/issues/4017)\n- d Command Removes Mode Text [\\#3781](https://github.com/VSCodeVim/Vim/issues/3781)\n- Yanking \"clears\" mode \\(or makes it disappear\\) from status bar until INSERT mode [\\#3488](https://github.com/VSCodeVim/Vim/issues/3488)\n\n**Merged pull requests:**\n\n- Update dependency @types/node to v12.7.3 [\\#4021](https://github.com/VSCodeVim/Vim/pull/4021) ([renovate[bot]](https://github.com/apps/renovate))\n- Make ReplaceWithRegister work in visual mode [\\#4016](https://github.com/VSCodeVim/Vim/pull/4016) ([stevenguh](https://github.com/stevenguh))\n- :w write in background [\\#4013](https://github.com/VSCodeVim/Vim/pull/4013) ([stevenguh](https://github.com/stevenguh))\n- Update dependency typescript to v3.6.2 [\\#4010](https://github.com/VSCodeVim/Vim/pull/4010) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v1.10.0](https://github.com/vscodevim/vim/tree/v1.10.0) (2019-08-28)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.9.0...v1.10.0)\n\n**Enhancements:**\n\n- \\<C-b\\> and \\<C-e\\> should be equivalent to \\<Home\\> and \\<End\\> on command line / search bar [\\#3995](https://github.com/VSCodeVim/Vim/issues/3995)\n- Support `when` for contextual keybindings [\\#3994](https://github.com/VSCodeVim/Vim/issues/3994)\n- Del should work on command/search line [\\#3992](https://github.com/VSCodeVim/Vim/issues/3992)\n- Home/End should work on command/search line [\\#3991](https://github.com/VSCodeVim/Vim/issues/3991)\n- `Ctrl-R` should allow pasting from a register when typing a command, as in insert mode [\\#3950](https://github.com/VSCodeVim/Vim/issues/3950)\n- Ctrl-P and Ctrl-N should be equivalent to Up / Down when entering a command or search [\\#3942](https://github.com/VSCodeVim/Vim/issues/3942)\n- Support ignorecase for sort command [\\#3939](https://github.com/VSCodeVim/Vim/issues/3939)\n- Support search offsets [\\#3917](https://github.com/VSCodeVim/Vim/issues/3917)\n- Enhancement: sneak one char jump. [\\#3907](https://github.com/VSCodeVim/Vim/issues/3907)\n- Simple undo command behaviour from vi/vim not implemented [\\#3649](https://github.com/VSCodeVim/Vim/issues/3649)\n\n**Fixed Bugs:**\n\n- Variable highlighting not working [\\#3982](https://github.com/VSCodeVim/Vim/issues/3982)\n- Change side in diff mode [\\#3979](https://github.com/VSCodeVim/Vim/issues/3979)\n- Annoying brackets autoremoving [\\#3936](https://github.com/VSCodeVim/Vim/issues/3936)\n- \"Search forward\" functionality is not case sensitive [\\#3764](https://github.com/VSCodeVim/Vim/issues/3764)\n- Does not start up with VSCode and no vim commands work [\\#3753](https://github.com/VSCodeVim/Vim/issues/3753)\n\n**Closed issues:**\n\n- `/` is not case sensitive [\\#3980](https://github.com/VSCodeVim/Vim/issues/3980)\n- Will VIM extension be compatible with python interactive window in the next update? [\\#3973](https://github.com/VSCodeVim/Vim/issues/3973)\n- visual mode block copy/past [\\#3971](https://github.com/VSCodeVim/Vim/issues/3971)\n- range yank does not work [\\#3931](https://github.com/VSCodeVim/Vim/issues/3931)\n- Console warning [\\#3926](https://github.com/VSCodeVim/Vim/issues/3926)\n- :wq does not close window if there are unsaved changes [\\#3922](https://github.com/VSCodeVim/Vim/issues/3922)\n- make easymotion looks exactly the vim-easymotion way [\\#3901](https://github.com/VSCodeVim/Vim/issues/3901)\n- bug to record macro [\\#3898](https://github.com/VSCodeVim/Vim/issues/3898)\n- Faulty link in readme [\\#3827](https://github.com/VSCodeVim/Vim/issues/3827)\n- Navigation in the explorer pane vim way \\(j , k\\) doesn't work after window reload [\\#3760](https://github.com/VSCodeVim/Vim/issues/3760)\n- Easy motion shows error when jumping to brackets and backslash [\\#3685](https://github.com/VSCodeVim/Vim/issues/3685)\n- I can't continuous movement the cursor ,and copy or delete more line. [\\#3634](https://github.com/VSCodeVim/Vim/issues/3634)\n- Why don't work command mode? [\\#3500](https://github.com/VSCodeVim/Vim/issues/3500)\n- Tab completion for `:vnew` and `:tabnew` [\\#3479](https://github.com/VSCodeVim/Vim/issues/3479)\n- Yank lines in 1 window should be available for pasting in another window [\\#3401](https://github.com/VSCodeVim/Vim/issues/3401)\n\n**Merged pull requests:**\n\n- Update dependency @types/lodash to v4.14.138 [\\#4003](https://github.com/VSCodeVim/Vim/pull/4003) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix typo in README.md [\\#4002](https://github.com/VSCodeVim/Vim/pull/4002) ([jedevc](https://github.com/jedevc))\n- Implement single char sneak [\\#3999](https://github.com/VSCodeVim/Vim/pull/3999) ([JohnnyUrosevic](https://github.com/JohnnyUrosevic))\n- fix :wq in remote [\\#3998](https://github.com/VSCodeVim/Vim/pull/3998) ([stevenguh](https://github.com/stevenguh))\n- Update dependency tslint to v5.19.0 [\\#3987](https://github.com/VSCodeVim/Vim/pull/3987) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix console warning [\\#3985](https://github.com/VSCodeVim/Vim/pull/3985) ([huww98](https://github.com/huww98))\n- Fix duplicated command added in c542b42 [\\#3984](https://github.com/VSCodeVim/Vim/pull/3984) ([huww98](https://github.com/huww98))\n- Update dependency @types/lodash to v4.14.137 [\\#3983](https://github.com/VSCodeVim/Vim/pull/3983) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/node to v12.7.2 [\\#3981](https://github.com/VSCodeVim/Vim/pull/3981) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/node to v12.7.1 [\\#3967](https://github.com/VSCodeVim/Vim/pull/3967) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/node to v12.7.0 [\\#3964](https://github.com/VSCodeVim/Vim/pull/3964) ([renovate[bot]](https://github.com/apps/renovate))\n- Disallow all forms of :help [\\#3962](https://github.com/VSCodeVim/Vim/pull/3962) ([J-Fields](https://github.com/J-Fields))\n- Be clear in package.json that vim.statusBarColorControl reduces performance [\\#3961](https://github.com/VSCodeVim/Vim/pull/3961) ([J-Fields](https://github.com/J-Fields))\n- Update dependency sinon to v7.4.1 [\\#3958](https://github.com/VSCodeVim/Vim/pull/3958) ([renovate[bot]](https://github.com/apps/renovate))\n- Implement `q/` and `q?` [\\#3956](https://github.com/VSCodeVim/Vim/pull/3956) ([J-Fields](https://github.com/J-Fields))\n- When the `c` \\(confirm\\) flag is used in a `:s` command, don't use neovim [\\#3955](https://github.com/VSCodeVim/Vim/pull/3955) ([J-Fields](https://github.com/J-Fields))\n- `\\<C-f\\>` shows command history when pressed on command line [\\#3954](https://github.com/VSCodeVim/Vim/pull/3954) ([J-Fields](https://github.com/J-Fields))\n- Fix `gC` in visual mode [\\#3948](https://github.com/VSCodeVim/Vim/pull/3948) ([J-Fields](https://github.com/J-Fields))\n- Roll back dependency sinon to 7.3.2 [\\#3947](https://github.com/VSCodeVim/Vim/pull/3947) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency sinon to v7.4.0 [\\#3944](https://github.com/VSCodeVim/Vim/pull/3944) ([renovate[bot]](https://github.com/apps/renovate))\n- Allow \\<C-p\\> and \\<C-n\\> to be used as prev/next when entering a command or search [\\#3943](https://github.com/VSCodeVim/Vim/pull/3943) ([J-Fields](https://github.com/J-Fields))\n- Respect `editor.autoClosingBrackets` and `editor.autoClosingQuotes` when deleting a bracket/quote [\\#3941](https://github.com/VSCodeVim/Vim/pull/3941) ([J-Fields](https://github.com/J-Fields))\n- added option to ignore case when sorting [\\#3938](https://github.com/VSCodeVim/Vim/pull/3938) ([noslaver](https://github.com/noslaver))\n- Update dependency @types/node to v12.6.9 [\\#3937](https://github.com/VSCodeVim/Vim/pull/3937) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency vscode to v1.1.36 [\\#3933](https://github.com/VSCodeVim/Vim/pull/3933) ([renovate[bot]](https://github.com/apps/renovate))\n- Implement search offsets [\\#3918](https://github.com/VSCodeVim/Vim/pull/3918) ([J-Fields](https://github.com/J-Fields))\n\n## [v1.9.0](https://github.com/vscodevim/vim/tree/v1.9.0) (2019-07-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.8.2...v1.9.0)\n\n**Enhancements:**\n\n- Support ampersand \\(\"&\"\\) action in normal mode [\\#3808](https://github.com/VSCodeVim/Vim/issues/3808)\n\n**Fixed Bugs:**\n\n- At beginning of line with all spaces, backspace causes error [\\#3915](https://github.com/VSCodeVim/Vim/issues/3915)\n- Go to Line Using \\[line\\]+gg Throws Exception [\\#3845](https://github.com/VSCodeVim/Vim/issues/3845)\n- Easymotion uses RegExp [\\#3844](https://github.com/VSCodeVim/Vim/issues/3844)\n- `%` doesn't ignore unmatched `\\>` [\\#3807](https://github.com/VSCodeVim/Vim/issues/3807)\n- Regression: full path to nvim is now required [\\#3754](https://github.com/VSCodeVim/Vim/issues/3754)\n\n**Closed issues:**\n\n- Mapping s in Visual Mode causes strange mistake [\\#3788](https://github.com/VSCodeVim/Vim/issues/3788)\n\n**Merged pull requests:**\n\n- Make `C` work with registers [\\#3927](https://github.com/VSCodeVim/Vim/pull/3927) ([J-Fields](https://github.com/J-Fields))\n- Implement ampersand \\(&\\) action [\\#3925](https://github.com/VSCodeVim/Vim/pull/3925) ([J-Fields](https://github.com/J-Fields))\n- Move prettier configuration to .prettierrc [\\#3921](https://github.com/VSCodeVim/Vim/pull/3921) ([kizza](https://github.com/kizza))\n- Handle backspace on first character of all-space line correctly [\\#3916](https://github.com/VSCodeVim/Vim/pull/3916) ([J-Fields](https://github.com/J-Fields))\n- Fix f/F/t/T with \\<tab\\> [\\#3914](https://github.com/VSCodeVim/Vim/pull/3914) ([J-Fields](https://github.com/J-Fields))\n- Make `%` skip over characters such as '\\>' [\\#3913](https://github.com/VSCodeVim/Vim/pull/3913) ([J-Fields](https://github.com/J-Fields))\n- Do not treat easymotion input as regex unless it's a letter [\\#3911](https://github.com/VSCodeVim/Vim/pull/3911) ([J-Fields](https://github.com/J-Fields))\n- fix\\(deps\\): update dependency lodash to v4.17.15 [\\#3906](https://github.com/VSCodeVim/Vim/pull/3906) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency mocha to v6.2.0 [\\#3905](https://github.com/VSCodeVim/Vim/pull/3905) ([renovate[bot]](https://github.com/apps/renovate))\n- Fixes \\#3754. Don't require full path to neovim [\\#3903](https://github.com/VSCodeVim/Vim/pull/3903) ([notskm](https://github.com/notskm))\n- chore\\(deps\\): update dependency @types/node to v12.6.8 [\\#3902](https://github.com/VSCodeVim/Vim/pull/3902) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v12.6.6 [\\#3897](https://github.com/VSCodeVim/Vim/pull/3897) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v12.6.4 [\\#3896](https://github.com/VSCodeVim/Vim/pull/3896) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v12.6.3 [\\#3893](https://github.com/VSCodeVim/Vim/pull/3893) ([renovate[bot]](https://github.com/apps/renovate))\n- Add ReplaceWithRegister plugin [\\#3887](https://github.com/VSCodeVim/Vim/pull/3887) ([kizza](https://github.com/kizza))\n\n## [v1.8.2](https://github.com/vscodevim/vim/tree/v1.8.2) (2019-07-15)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.8.1...v1.8.2)\n\n**Fixed Bugs:**\n\n- GoToDefinition make invalid history when use C\\# extension [\\#3865](https://github.com/VSCodeVim/Vim/issues/3865)\n- Invisible \"WORD\" in roadmap [\\#3823](https://github.com/VSCodeVim/Vim/issues/3823)\n\n**Closed issues:**\n\n- Identifier highlights do not appear with keyboard movement [\\#3885](https://github.com/VSCodeVim/Vim/issues/3885)\n- Cursor width when indent using tabs [\\#3856](https://github.com/VSCodeVim/Vim/issues/3856)\n- cw without yank [\\#3836](https://github.com/VSCodeVim/Vim/issues/3836)\n- Frozen in 'Activating Extensions' [\\#3826](https://github.com/VSCodeVim/Vim/issues/3826)\n- How can we make a normal-mode shift-enter mapping? [\\#3814](https://github.com/VSCodeVim/Vim/issues/3814)\n- Input response is too slow after updating vsc to the latest version\\(1.34.0\\) [\\#3810](https://github.com/VSCodeVim/Vim/issues/3810)\n- Yank + motion only working partially [\\#3794](https://github.com/VSCodeVim/Vim/issues/3794)\n- vim mode does not work after upgrading to 1.8.1 [\\#3791](https://github.com/VSCodeVim/Vim/issues/3791)\n- Save File Using leader leader [\\#3790](https://github.com/VSCodeVim/Vim/issues/3790)\n- space + tab transforme to solo tab [\\#3789](https://github.com/VSCodeVim/Vim/issues/3789)\n- Unable to replace single quotes surrounding string with double quotes like I can in Vim [\\#3657](https://github.com/VSCodeVim/Vim/issues/3657)\n- cannot bind \",\" [\\#3565](https://github.com/VSCodeVim/Vim/issues/3565)\n\n**Merged pull requests:**\n\n- fix\\(deps\\): update dependency lodash to v4.17.14 [\\#3884](https://github.com/VSCodeVim/Vim/pull/3884) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v12.6.2 [\\#3882](https://github.com/VSCodeVim/Vim/pull/3882) ([renovate[bot]](https://github.com/apps/renovate))\n- fix\\(deps\\): update dependency lodash to v4.17.13 [\\#3881](https://github.com/VSCodeVim/Vim/pull/3881) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency typescript to v3.5.3 [\\#3878](https://github.com/VSCodeVim/Vim/pull/3878) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.136 [\\#3877](https://github.com/VSCodeVim/Vim/pull/3877) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v12.6.1 [\\#3876](https://github.com/VSCodeVim/Vim/pull/3876) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency tslint to v5.18.0 [\\#3874](https://github.com/VSCodeVim/Vim/pull/3874) ([renovate[bot]](https://github.com/apps/renovate))\n- fix: fix build break [\\#3873](https://github.com/VSCodeVim/Vim/pull/3873) ([jpoon](https://github.com/jpoon))\n- chore: fix URL for input method setting [\\#3870](https://github.com/VSCodeVim/Vim/pull/3870) ([AndersDJohnson](https://github.com/AndersDJohnson))\n- Assign activeTextEditor to local variable first. [\\#3866](https://github.com/VSCodeVim/Vim/pull/3866) ([yaegaki](https://github.com/yaegaki))\n- Update dependency @types/node to v12.0.12 [\\#3862](https://github.com/VSCodeVim/Vim/pull/3862) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/node to v12.0.11 [\\#3861](https://github.com/VSCodeVim/Vim/pull/3861) ([renovate[bot]](https://github.com/apps/renovate))\n- fix log message for 'vim.debug.silent' [\\#3859](https://github.com/VSCodeVim/Vim/pull/3859) ([stfnwp](https://github.com/stfnwp))\n- Update dependency @types/node to v12.0.10 [\\#3858](https://github.com/VSCodeVim/Vim/pull/3858) ([renovate-bot](https://github.com/renovate-bot))\n- Fix build per microsoft/vscode\\#75873 [\\#3857](https://github.com/VSCodeVim/Vim/pull/3857) ([octref](https://github.com/octref))\n- Update dependency vscode to v1.1.35 [\\#3855](https://github.com/VSCodeVim/Vim/pull/3855) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.135 [\\#3854](https://github.com/VSCodeVim/Vim/pull/3854) ([renovate[bot]](https://github.com/apps/renovate))\n- pull request to fix the issue \\#3845 [\\#3853](https://github.com/VSCodeVim/Vim/pull/3853) ([zhuzisheng](https://github.com/zhuzisheng))\n- upgrade pkgs [\\#3843](https://github.com/VSCodeVim/Vim/pull/3843) ([jpoon](https://github.com/jpoon))\n- Fix broken links in README.md [\\#3842](https://github.com/VSCodeVim/Vim/pull/3842) ([aquova](https://github.com/aquova))\n- Update dependency typescript to v3.5.2 [\\#3834](https://github.com/VSCodeVim/Vim/pull/3834) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix WORD wrapped in pipes [\\#3829](https://github.com/VSCodeVim/Vim/pull/3829) ([scebotari66](https://github.com/scebotari66))\n- Update dependency prettier to v1.18.2 [\\#3819](https://github.com/VSCodeVim/Vim/pull/3819) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency prettier to v1.18.0 [\\#3818](https://github.com/VSCodeVim/Vim/pull/3818) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.134 [\\#3817](https://github.com/VSCodeVim/Vim/pull/3817) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.133 [\\#3802](https://github.com/VSCodeVim/Vim/pull/3802) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency tslint to v5.17.0 [\\#3801](https://github.com/VSCodeVim/Vim/pull/3801) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/mocha to v5.2.7 [\\#3800](https://github.com/VSCodeVim/Vim/pull/3800) ([renovate[bot]](https://github.com/apps/renovate))\n- Consolidate documentation for visual modes [\\#3799](https://github.com/VSCodeVim/Vim/pull/3799) ([max-sixty](https://github.com/max-sixty))\n- Update dependency typescript to v3.5.1 [\\#3798](https://github.com/VSCodeVim/Vim/pull/3798) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/sinon to v7.0.12 [\\#3795](https://github.com/VSCodeVim/Vim/pull/3795) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.132 [\\#3792](https://github.com/VSCodeVim/Vim/pull/3792) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v1.8.1](https://github.com/vscodevim/vim/tree/v1.8.1) (2019-05-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.8.0...v1.8.1)\n\n**Fixed Bugs:**\n\n- Vim extension UI \"blocks\" on remote development save [\\#3777](https://github.com/VSCodeVim/Vim/issues/3777)\n- Cancelling a search should not undo :noh [\\#3748](https://github.com/VSCodeVim/Vim/issues/3748)\n- \\<C-c\\> and \\<C-\\[\\> don't cancel search [\\#3668](https://github.com/VSCodeVim/Vim/issues/3668)\n- \\<C-u\\>/\\<C-b\\> don't move cursor if the first line is visible [\\#3648](https://github.com/VSCodeVim/Vim/issues/3648)\n- vim.statusBarColors.normal reports type error [\\#3607](https://github.com/VSCodeVim/Vim/issues/3607)\n\n**Closed issues:**\n\n- Copy inside of words after typing ci\" [\\#3758](https://github.com/VSCodeVim/Vim/issues/3758)\n\n**Merged pull requests:**\n\n- Update dependency @types/lodash to v4.14.130 [\\#3784](https://github.com/VSCodeVim/Vim/pull/3784) ([renovate[bot]](https://github.com/apps/renovate))\n- Update ROADMAP.ZH.md [\\#3782](https://github.com/VSCodeVim/Vim/pull/3782) ([sxlwar](https://github.com/sxlwar))\n- Make the write command non-blocking on remote files [\\#3778](https://github.com/VSCodeVim/Vim/pull/3778) ([suo](https://github.com/suo))\n- Fix MoveHalfPageUp \\(\\<C-u\\>\\) when first line is visible. [\\#3776](https://github.com/VSCodeVim/Vim/pull/3776) ([faldah](https://github.com/faldah))\n- Update dependency @types/lodash to v4.14.129 [\\#3771](https://github.com/VSCodeVim/Vim/pull/3771) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.127 [\\#3770](https://github.com/VSCodeVim/Vim/pull/3770) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix statusBarColors linting in vscode user settings. [\\#3767](https://github.com/VSCodeVim/Vim/pull/3767) ([faldah](https://github.com/faldah))\n- Update dependency prettier to v1.17.1 [\\#3765](https://github.com/VSCodeVim/Vim/pull/3765) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.126 [\\#3755](https://github.com/VSCodeVim/Vim/pull/3755) ([renovate[bot]](https://github.com/apps/renovate))\n- Make sure :noh disables hlsearch until the next search is done [\\#3749](https://github.com/VSCodeVim/Vim/pull/3749) ([J-Fields](https://github.com/J-Fields))\n\n## [v1.8.0](https://github.com/vscodevim/vim/tree/v1.8.0) (2019-05-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.7.1...v1.8.0)\n\n**Enhancements:**\n\n- :reg should show multiple registers if given multiple arguments [\\#3610](https://github.com/VSCodeVim/Vim/issues/3610)\n- :reg should not show the \\_ \\(black hole\\) register [\\#3606](https://github.com/VSCodeVim/Vim/issues/3606)\n- Implement the % \\(file name\\) and : \\(last executed command\\) registers [\\#3605](https://github.com/VSCodeVim/Vim/issues/3605)\n- The . \\(last inserted text\\) register should be read-only [\\#3604](https://github.com/VSCodeVim/Vim/issues/3604)\n\n**Fixed Bugs:**\n\n- Backspace in command line mode should return to normal mode if the command is empty [\\#3729](https://github.com/VSCodeVim/Vim/issues/3729)\n\n**Closed issues:**\n\n- Tab to spaces setting in vscode not applying when extension is enabled [\\#3732](https://github.com/VSCodeVim/Vim/issues/3732)\n- %d/string/d\" does not work [\\#3709](https://github.com/VSCodeVim/Vim/issues/3709)\n- Extension issue [\\#3615](https://github.com/VSCodeVim/Vim/issues/3615)\n- Support the / register [\\#3542](https://github.com/VSCodeVim/Vim/issues/3542)\n\n**Merged pull requests:**\n\n- Show search results in the overview ruler [\\#3750](https://github.com/VSCodeVim/Vim/pull/3750) ([J-Fields](https://github.com/J-Fields))\n- Update dependency @types/lodash to v4.14.125 [\\#3747](https://github.com/VSCodeVim/Vim/pull/3747) ([renovate[bot]](https://github.com/apps/renovate))\n- \\<C-\\[\\> and \\<C-c\\> should terminate search mode [\\#3746](https://github.com/VSCodeVim/Vim/pull/3746) ([hkleynhans](https://github.com/hkleynhans))\n- Update dependency vscode to v1.1.34 [\\#3739](https://github.com/VSCodeVim/Vim/pull/3739) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency gulp to v4.0.2 [\\#3738](https://github.com/VSCodeVim/Vim/pull/3738) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/lodash to v4.14.124 [\\#3737](https://github.com/VSCodeVim/Vim/pull/3737) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix replace character \\(`r`\\) behavior with newline [\\#3735](https://github.com/VSCodeVim/Vim/pull/3735) ([J-Fields](https://github.com/J-Fields))\n- Show `match {x} of {y}` in the status bar when searching [\\#3734](https://github.com/VSCodeVim/Vim/pull/3734) ([J-Fields](https://github.com/J-Fields))\n- Keymapping bindings inconsistently cased \\#3012 [\\#3731](https://github.com/VSCodeVim/Vim/pull/3731) ([ObliviousJamie](https://github.com/ObliviousJamie))\n- Return to normal mode after hitting \\<BS\\> on empty command line [\\#3730](https://github.com/VSCodeVim/Vim/pull/3730) ([J-Fields](https://github.com/J-Fields))\n- Various improvements to registers [\\#3728](https://github.com/VSCodeVim/Vim/pull/3728) ([J-Fields](https://github.com/J-Fields))\n- Add tab completion on vim command line [\\#3639](https://github.com/VSCodeVim/Vim/pull/3639) ([keith-ferney](https://github.com/keith-ferney))\n\n## [v1.7.1](https://github.com/vscodevim/vim/tree/v1.7.1) (2019-05-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.7.0...v1.7.1)\n\n**Enhancements:**\n\n- Set extensionKind in package.json to support Remote Development [\\#3720](https://github.com/VSCodeVim/Vim/issues/3720)\n- gf doesn't work with filepath:linenumber format [\\#3710](https://github.com/VSCodeVim/Vim/issues/3710)\n- Hive ctrl+G show which file is editing been supported? [\\#3700](https://github.com/VSCodeVim/Vim/issues/3700)\n\n**Fixed Bugs:**\n\n- Replace \\(:%s\\) confirm text is wrong [\\#3715](https://github.com/VSCodeVim/Vim/issues/3715)\n\n**Merged pull requests:**\n\n- Update dependency untildify to v4 [\\#3725](https://github.com/VSCodeVim/Vim/pull/3725) ([renovate[bot]](https://github.com/apps/renovate))\n- Add searches from \\* and \\# to the search history [\\#3724](https://github.com/VSCodeVim/Vim/pull/3724) ([J-Fields](https://github.com/J-Fields))\n- Implement Ctrl+G and :file [\\#3723](https://github.com/VSCodeVim/Vim/pull/3723) ([J-Fields](https://github.com/J-Fields))\n- Correct replacement confirmation text [\\#3722](https://github.com/VSCodeVim/Vim/pull/3722) ([J-Fields](https://github.com/J-Fields))\n- Set \"extensionKind\": \"ui\" to support remote development [\\#3721](https://github.com/VSCodeVim/Vim/pull/3721) ([mjbvz](https://github.com/mjbvz))\n\n## [v1.7.0](https://github.com/vscodevim/vim/tree/v1.7.0) (2019-04-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.4.0...v1.7.0)\n\n**Fixed Bugs:**\n\n- vim.debug.suppress invalid [\\#3703](https://github.com/VSCodeVim/Vim/issues/3703)\n- cw, dw, vw doesn't work with non-ascii char earlier in line [\\#3680](https://github.com/VSCodeVim/Vim/issues/3680)\n- Word seperate doesn't works well [\\#3665](https://github.com/VSCodeVim/Vim/issues/3665)\n- catastrophic performance [\\#3654](https://github.com/VSCodeVim/Vim/issues/3654)\n\n**Closed issues:**\n\n- Ctrl keys can not be remapped in insert mode [\\#3697](https://github.com/VSCodeVim/Vim/issues/3697)\n- Surround: Implement whitespace configuration [\\#3681](https://github.com/VSCodeVim/Vim/issues/3681)\n- :\\[line number\\]d causes type error [\\#3678](https://github.com/VSCodeVim/Vim/issues/3678)\n- How to fit VIM search on IDE footer with long git branch name? [\\#3652](https://github.com/VSCodeVim/Vim/issues/3652)\n- cannot open or close directories with L key in file navigation [\\#3576](https://github.com/VSCodeVim/Vim/issues/3576)\n- VsCodeVim makes workbench.tree.indent not effective [\\#3561](https://github.com/VSCodeVim/Vim/issues/3561)\n- Ex command 'copy' throws \"failed to handle key=.undefined\" error [\\#3505](https://github.com/VSCodeVim/Vim/issues/3505)\n- All mappings in Visual mode do not work when you just enter Visual mod by pressing v [\\#3503](https://github.com/VSCodeVim/Vim/issues/3503)\n\n**Merged pull requests:**\n\n- Fix reverse selecting in normal mode. [\\#3712](https://github.com/VSCodeVim/Vim/pull/3712) ([kroton](https://github.com/kroton))\n- chore\\(deps\\): update dependency typescript to v3.4.5 [\\#3701](https://github.com/VSCodeVim/Vim/pull/3701) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp to v4.0.1 [\\#3698](https://github.com/VSCodeVim/Vim/pull/3698) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency typescript to v3.4.4 [\\#3690](https://github.com/VSCodeVim/Vim/pull/3690) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency mocha to v6.1.4 [\\#3689](https://github.com/VSCodeVim/Vim/pull/3689) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency sinon to v7.3.2 [\\#3686](https://github.com/VSCodeVim/Vim/pull/3686) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency tslint to v5.16.0 [\\#3683](https://github.com/VSCodeVim/Vim/pull/3683) ([renovate[bot]](https://github.com/apps/renovate))\n- docs: update slackin link [\\#3679](https://github.com/VSCodeVim/Vim/pull/3679) ([khoitd1997](https://github.com/khoitd1997))\n- Update dependency typescript to v3.4.3 [\\#3677](https://github.com/VSCodeVim/Vim/pull/3677) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency prettier to v1.17.0 [\\#3676](https://github.com/VSCodeVim/Vim/pull/3676) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency mocha to v6.1.3 [\\#3675](https://github.com/VSCodeVim/Vim/pull/3675) ([renovate[bot]](https://github.com/apps/renovate))\n- Add note about unsupported motions [\\#3670](https://github.com/VSCodeVim/Vim/pull/3670) ([karlhorky](https://github.com/karlhorky))\n- Fix word separation [\\#3667](https://github.com/VSCodeVim/Vim/pull/3667) ([ajalab](https://github.com/ajalab))\n- Update dependency typescript to v3.4.2 [\\#3664](https://github.com/VSCodeVim/Vim/pull/3664) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency mocha to v6.1.2 [\\#3663](https://github.com/VSCodeVim/Vim/pull/3663) ([renovate[bot]](https://github.com/apps/renovate))\n- Fixes \\#2754. Ctrl+d/u pull cursor along when screen moves past cursor [\\#3658](https://github.com/VSCodeVim/Vim/pull/3658) ([mayhewluke](https://github.com/mayhewluke))\n- Implement \\<C-w\\> s [\\#3563](https://github.com/VSCodeVim/Vim/pull/3563) ([aminroosta](https://github.com/aminroosta))\n\n## [v1.4.0](https://github.com/vscodevim/vim/tree/v1.4.0) (2019-04-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.3.0...v1.4.0)\n\n**Fixed Bugs:**\n\n- Performance degradation of word motions in v1.3.0 [\\#3660](https://github.com/VSCodeVim/Vim/issues/3660)\n\n**Closed issues:**\n\n- Adding vim style 'Go to Symbol in Workspace' shortcut [\\#3624](https://github.com/VSCodeVim/Vim/issues/3624)\n\n**Merged pull requests:**\n\n- Improve performance of word motions [\\#3662](https://github.com/VSCodeVim/Vim/pull/3662) ([ajalab](https://github.com/ajalab))\n- Update dependency tslint to v5.15.0 [\\#3647](https://github.com/VSCodeVim/Vim/pull/3647) ([renovate[bot]](https://github.com/apps/renovate))\n- Update dependency @types/mocha to v5.2.6 [\\#3646](https://github.com/VSCodeVim/Vim/pull/3646) ([renovate[bot]](https://github.com/apps/renovate))\n- Document display line movement best practices [\\#3623](https://github.com/VSCodeVim/Vim/pull/3623) ([karlhorky](https://github.com/karlhorky))\n- Only use regex lookbehind where supported [\\#3525](https://github.com/VSCodeVim/Vim/pull/3525) ([JKillian](https://github.com/JKillian))\n\n## [v1.3.0](https://github.com/vscodevim/vim/tree/v1.3.0) (2019-04-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.2.0...v1.3.0)\n\n**Enhancements:**\n\n- Better non-ASCII character support in word motions [\\#3612](https://github.com/VSCodeVim/Vim/issues/3612)\n\n**Fixed Bugs:**\n\n- Preview file from explorer is not tracked as jump [\\#3507](https://github.com/VSCodeVim/Vim/issues/3507)\n- ‘W’ and 'w' shortcut keys do not support Chinese characters！ [\\#3439](https://github.com/VSCodeVim/Vim/issues/3439)\n\n**Closed issues:**\n\n- emmet with vscode vim [\\#3644](https://github.com/VSCodeVim/Vim/issues/3644)\n- How do I insert a linebreak where the cursor is without entering into insert mode in VSCodeVim? [\\#3636](https://github.com/VSCodeVim/Vim/issues/3636)\n- Hitting backspace with an empty search should return to normal mode [\\#3619](https://github.com/VSCodeVim/Vim/issues/3619)\n- Search state should not change until a new search command is completed [\\#3616](https://github.com/VSCodeVim/Vim/issues/3616)\n- Jumping to a mark that is off-screen should center the view around the mark [\\#3609](https://github.com/VSCodeVim/Vim/issues/3609)\n- The original vim's redo command \\(Ctrl+Shift+R\\) doesn't work [\\#3608](https://github.com/VSCodeVim/Vim/issues/3608)\n- vim-surround does not work with multiple cursors [\\#3600](https://github.com/VSCodeVim/Vim/issues/3600)\n- digraphs cannot be inputted in different order [\\#3599](https://github.com/VSCodeVim/Vim/issues/3599)\n- gU/gu does not work in visual mode [\\#3491](https://github.com/VSCodeVim/Vim/issues/3491)\n- Error when executing 'View Latex PDF'-command from latex-workshop-plugin [\\#3484](https://github.com/VSCodeVim/Vim/issues/3484)\n\n**Merged pull requests:**\n\n- chore\\(deps\\): update dependency vscode to v1.1.33 [\\#3643](https://github.com/VSCodeVim/Vim/pull/3643) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency typescript to v3.4.1 [\\#3642](https://github.com/VSCodeVim/Vim/pull/3642) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.11 [\\#3641](https://github.com/VSCodeVim/Vim/pull/3641) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/diff to v4.0.2 [\\#3640](https://github.com/VSCodeVim/Vim/pull/3640) ([renovate[bot]](https://github.com/apps/renovate))\n- Digraphs: Allow input in reverse order \\(fixes \\#3599\\) [\\#3635](https://github.com/VSCodeVim/Vim/pull/3635) ([jbaiter](https://github.com/jbaiter))\n- Assign lastClosedModeHandler when onDidCloseTextDocument. [\\#3630](https://github.com/VSCodeVim/Vim/pull/3630) ([yaegaki](https://github.com/yaegaki))\n- When backspace is hit on an empty search, cancel the search [\\#3626](https://github.com/VSCodeVim/Vim/pull/3626) ([J-Fields](https://github.com/J-Fields))\n- Mark several features that have been implemented as complete in ROADMAP.md [\\#3620](https://github.com/VSCodeVim/Vim/pull/3620) ([J-Fields](https://github.com/J-Fields))\n- When a search is cancelled, revert to previous search state [\\#3617](https://github.com/VSCodeVim/Vim/pull/3617) ([J-Fields](https://github.com/J-Fields))\n- Support word motions for non-ASCII characters [\\#3614](https://github.com/VSCodeVim/Vim/pull/3614) ([ajalab](https://github.com/ajalab))\n- Support for gU and gu in visual mode [\\#3603](https://github.com/VSCodeVim/Vim/pull/3603) ([J-Fields](https://github.com/J-Fields))\n- Chinese translation of ROADMAP.MD [\\#3597](https://github.com/VSCodeVim/Vim/pull/3597) ([sxlwar](https://github.com/sxlwar))\n- fix\\(deps\\): update dependency neovim to v4.5.0 [\\#3555](https://github.com/VSCodeVim/Vim/pull/3555) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v1.2.0](https://github.com/vscodevim/vim/tree/v1.2.0) (2019-03-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.1.0...v1.2.0)\n\n**Enhancements:**\n\n- The small delete register \"- doesn't work [\\#3492](https://github.com/VSCodeVim/Vim/issues/3492)\n\n**Closed issues:**\n\n- Extension causes high cpu load [\\#3587](https://github.com/VSCodeVim/Vim/issues/3587)\n- Custom keybind breaks search [\\#3558](https://github.com/VSCodeVim/Vim/issues/3558)\n- vim-auto-save [\\#3550](https://github.com/VSCodeVim/Vim/issues/3550)\n- Extension causes high cpu load [\\#3546](https://github.com/VSCodeVim/Vim/issues/3546)\n- Extension causes high cpu load [\\#3533](https://github.com/VSCodeVim/Vim/issues/3533)\n- The extension don't work with Java Extension Pack [\\#3526](https://github.com/VSCodeVim/Vim/issues/3526)\n- command 'toggleVim' not found. [\\#3524](https://github.com/VSCodeVim/Vim/issues/3524)\n- Error when upgraded to 1.1.0 [\\#3521](https://github.com/VSCodeVim/Vim/issues/3521)\n- TaskQueue: Error running task. Invalid regular expression: [\\#3519](https://github.com/VSCodeVim/Vim/issues/3519)\n- Chinese i18n support? [\\#3497](https://github.com/VSCodeVim/Vim/issues/3497)\n\n**Merged pull requests:**\n\n- Add yank highlighting \\(REBASED\\) [\\#3593](https://github.com/VSCodeVim/Vim/pull/3593) ([epeli](https://github.com/epeli))\n- chore\\(deps\\): update dependency tslint to v5.14.0 [\\#3586](https://github.com/VSCodeVim/Vim/pull/3586) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp-typescript to v5.0.1 [\\#3585](https://github.com/VSCodeVim/Vim/pull/3585) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.10 [\\#3583](https://github.com/VSCodeVim/Vim/pull/3583) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.123 [\\#3582](https://github.com/VSCodeVim/Vim/pull/3582) ([renovate[bot]](https://github.com/apps/renovate))\n- Fix TOC [\\#3574](https://github.com/VSCodeVim/Vim/pull/3574) ([mtsmfm](https://github.com/mtsmfm))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.9 [\\#3568](https://github.com/VSCodeVim/Vim/pull/3568) ([renovate[bot]](https://github.com/apps/renovate))\n- Bump minimum VSCode version to 1.31.0 [\\#3567](https://github.com/VSCodeVim/Vim/pull/3567) ([JKillian](https://github.com/JKillian))\n- docs: remove outdated notes on splits from roadmap [\\#3564](https://github.com/VSCodeVim/Vim/pull/3564) ([JKillian](https://github.com/JKillian))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.122 [\\#3557](https://github.com/VSCodeVim/Vim/pull/3557) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency sinon to v7.2.7 [\\#3554](https://github.com/VSCodeVim/Vim/pull/3554) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency sinon to v7.2.6 [\\#3552](https://github.com/VSCodeVim/Vim/pull/3552) ([renovate[bot]](https://github.com/apps/renovate))\n- Add small deletions to small delete register [\\#3544](https://github.com/VSCodeVim/Vim/pull/3544) ([rickythefox](https://github.com/rickythefox))\n- chore\\(deps\\): update dependency tslint to v5.13.1 [\\#3541](https://github.com/VSCodeVim/Vim/pull/3541) ([renovate[bot]](https://github.com/apps/renovate))\n- Mod:change sneak sneakUseIgnorecaseAndSmartcase default value explana… [\\#3540](https://github.com/VSCodeVim/Vim/pull/3540) ([duguanyue](https://github.com/duguanyue))\n- Fix links in README [\\#3534](https://github.com/VSCodeVim/Vim/pull/3534) ([yorinasub17](https://github.com/yorinasub17))\n- chore\\(deps\\): update dependency mocha to v6.0.2 [\\#3529](https://github.com/VSCodeVim/Vim/pull/3529) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.8 [\\#3528](https://github.com/VSCodeVim/Vim/pull/3528) ([renovate[bot]](https://github.com/apps/renovate))\n\n## [v1.1.0](https://github.com/vscodevim/vim/tree/v1.1.0) (2019-02-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.8...v1.1.0)\n\n**Fixed Bugs:**\n\n- vim.searchHighlightColor does not work [\\#3489](https://github.com/VSCodeVim/Vim/issues/3489)\n- Error when jumping to undefined mark [\\#3468](https://github.com/VSCodeVim/Vim/issues/3468)\n\n**Closed issues:**\n\n- \\[Feature request\\]: Add the ability to copy the current query into clipboard. [\\#3493](https://github.com/VSCodeVim/Vim/issues/3493)\n- Not working on vscode 1.31.0 [\\#3473](https://github.com/VSCodeVim/Vim/issues/3473)\n- Extension causes high cpu load [\\#3471](https://github.com/VSCodeVim/Vim/issues/3471)\n- Error when using the `\\> motion [\\#3452](https://github.com/VSCodeVim/Vim/issues/3452)\n- Show mark label like VIM in visual studio [\\#3406](https://github.com/VSCodeVim/Vim/issues/3406)\n\n**Merged pull requests:**\n\n- Fixes vim.searchHighlightColor [\\#3517](https://github.com/VSCodeVim/Vim/pull/3517) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency tslint to v5.13.0 [\\#3516](https://github.com/VSCodeVim/Vim/pull/3516) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency vscode to v1.1.30 [\\#3513](https://github.com/VSCodeVim/Vim/pull/3513) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency typescript to v3.3.3333 [\\#3512](https://github.com/VSCodeVim/Vim/pull/3512) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency mocha to v6.0.1 [\\#3511](https://github.com/VSCodeVim/Vim/pull/3511) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp-tslint to v8.1.4 [\\#3510](https://github.com/VSCodeVim/Vim/pull/3510) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency mocha to v6 [\\#3499](https://github.com/VSCodeVim/Vim/pull/3499) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp-sourcemaps to v2.6.5 [\\#3498](https://github.com/VSCodeVim/Vim/pull/3498) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/node to v10.12.27 [\\#3496](https://github.com/VSCodeVim/Vim/pull/3496) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.121 [\\#3487](https://github.com/VSCodeVim/Vim/pull/3487) ([renovate[bot]](https://github.com/apps/renovate))\n- Add CamelCaseMotion plugin [\\#3483](https://github.com/VSCodeVim/Vim/pull/3483) ([JKillian](https://github.com/JKillian))\n- chore\\(deps\\): update dependency @types/node to v9.6.42 [\\#3478](https://github.com/VSCodeVim/Vim/pull/3478) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency vscode to v1.1.29 [\\#3476](https://github.com/VSCodeVim/Vim/pull/3476) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency typescript to v3.3.3 [\\#3475](https://github.com/VSCodeVim/Vim/pull/3475) ([renovate[bot]](https://github.com/apps/renovate))\n- Set \\< and \\> marks when yanking in visual mode. [\\#3472](https://github.com/VSCodeVim/Vim/pull/3472) ([rickythefox](https://github.com/rickythefox))\n- Fixes \\#3468 [\\#3469](https://github.com/VSCodeVim/Vim/pull/3469) ([hnefatl](https://github.com/hnefatl))\n- chore\\(deps\\): update dependency prettier to v1.16.4 [\\#3465](https://github.com/VSCodeVim/Vim/pull/3465) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp-git to v2.9.0 [\\#3464](https://github.com/VSCodeVim/Vim/pull/3464) ([renovate[bot]](https://github.com/apps/renovate))\n- Digraph support [\\#3407](https://github.com/VSCodeVim/Vim/pull/3407) ([jbaiter](https://github.com/jbaiter))\n\n## [v1.0.8](https://github.com/vscodevim/vim/tree/v1.0.8) (2019-02-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.7...v1.0.8)\n\n**Fixed Bugs:**\n\n- Cursor jumps after building with CMake [\\#3462](https://github.com/VSCodeVim/Vim/issues/3462)\n- Illegal Value for Line using any input mode while WallabyJs || Quokka is running [\\#3459](https://github.com/VSCodeVim/Vim/issues/3459)\n- Cursor jumps up to the beginning of a file after saving. [\\#3444](https://github.com/VSCodeVim/Vim/issues/3444)\n\n**Merged pull requests:**\n\n- fix: cursor jumps when selection changes to output window [\\#3463](https://github.com/VSCodeVim/Vim/pull/3463) ([jpoon](https://github.com/jpoon))\n- feat: configuration validators [\\#3451](https://github.com/VSCodeVim/Vim/pull/3451) ([jpoon](https://github.com/jpoon))\n- fix: de-dupe cursors [\\#3449](https://github.com/VSCodeVim/Vim/pull/3449) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/diff to v4.0.1 [\\#3448](https://github.com/VSCodeVim/Vim/pull/3448) ([renovate[bot]](https://github.com/apps/renovate))\n- v1.0.7 [\\#3447](https://github.com/VSCodeVim/Vim/pull/3447) ([jpoon](https://github.com/jpoon))\n- refactor: no need for so many different ways to create a position object [\\#3446](https://github.com/VSCodeVim/Vim/pull/3446) ([jpoon](https://github.com/jpoon))\n\n## [v1.0.7](https://github.com/vscodevim/vim/tree/v1.0.7) (2019-02-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.6...v1.0.7)\n\n**Fixed Bugs:**\n\n- Illegal value for line error using command-mode range deletion [\\#3441](https://github.com/VSCodeVim/Vim/issues/3441)\n- Extension crash or hangs when failing to call nvim [\\#3433](https://github.com/VSCodeVim/Vim/issues/3433)\n\n**Merged pull requests:**\n\n- \\[Bugfix\\] - sentences backward [\\#3445](https://github.com/VSCodeVim/Vim/pull/3445) ([esetnik](https://github.com/esetnik))\n- refactor: rename cursorPositionJustBeforeAnythingHappened to cursorsInitialState [\\#3443](https://github.com/VSCodeVim/Vim/pull/3443) ([jpoon](https://github.com/jpoon))\n- fix: ensure cursor is in bounds. closes \\#3441 [\\#3442](https://github.com/VSCodeVim/Vim/pull/3442) ([jpoon](https://github.com/jpoon))\n- fix: validate that remappings are string arrays [\\#3440](https://github.com/VSCodeVim/Vim/pull/3440) ([jpoon](https://github.com/jpoon))\n- v1.0.6 [\\#3438](https://github.com/VSCodeVim/Vim/pull/3438) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency typescript to v3.3.1 [\\#3436](https://github.com/VSCodeVim/Vim/pull/3436) ([renovate[bot]](https://github.com/apps/renovate))\n- Adopt latest list navigation support [\\#3432](https://github.com/VSCodeVim/Vim/pull/3432) ([joaomoreno](https://github.com/joaomoreno))\n\n## [v1.0.6](https://github.com/vscodevim/vim/tree/v1.0.6) (2019-02-01)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.5...v1.0.6)\n\n**Fixed Bugs:**\n\n- Bad interaction between 1.0.5 and jscode-java-pack [\\#3431](https://github.com/VSCodeVim/Vim/issues/3431)\n- Release 1.0.4 doesn't contain listed changes [\\#3429](https://github.com/VSCodeVim/Vim/issues/3429)\n\n**Merged pull requests:**\n\n- fix: check neovim configurations and timeout on nvim attach [\\#3437](https://github.com/VSCodeVim/Vim/pull/3437) ([jpoon](https://github.com/jpoon))\n- fix: revert back to previous non-async code when syncing cursor [\\#3435](https://github.com/VSCodeVim/Vim/pull/3435) ([jpoon](https://github.com/jpoon))\n- feat: output commit hash. closes \\#3429 [\\#3430](https://github.com/VSCodeVim/Vim/pull/3430) ([jpoon](https://github.com/jpoon))\n\n## [v1.0.5](https://github.com/vscodevim/vim/tree/v1.0.5) (2019-01-31)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.4...v1.0.5)\n\n**Merged pull requests:**\n\n- chore\\(deps\\): update dependency prettier to v1.16.3 [\\#3428](https://github.com/VSCodeVim/Vim/pull/3428) ([renovate[bot]](https://github.com/apps/renovate))\n- v1.0.4 [\\#3427](https://github.com/VSCodeVim/Vim/pull/3427) ([jpoon](https://github.com/jpoon))\n\n## [v1.0.4](https://github.com/vscodevim/vim/tree/v1.0.4) (2019-01-31)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.3...v1.0.4)\n\n**Fixed Bugs:**\n\n- \"Delete surrounding quotes\" doesn't work in certain cases [\\#3415](https://github.com/VSCodeVim/Vim/issues/3415)\n- 'gd' is working correctly, but an error occurs. [\\#3387](https://github.com/VSCodeVim/Vim/issues/3387)\n\n**Closed issues:**\n\n- Extension causes high cpu load [\\#3400](https://github.com/VSCodeVim/Vim/issues/3400)\n\n**Merged pull requests:**\n\n- fix ds\" with nested quotes and add some tests - fixes \\#3415 [\\#3426](https://github.com/VSCodeVim/Vim/pull/3426) ([esetnik](https://github.com/esetnik))\n- chore\\(deps\\): update dependency @types/diff to v4 [\\#3425](https://github.com/VSCodeVim/Vim/pull/3425) ([renovate[bot]](https://github.com/apps/renovate))\n- fix: single-key remappings were being ignored [\\#3424](https://github.com/VSCodeVim/Vim/pull/3424) ([jpoon](https://github.com/jpoon))\n- fix\\(deps\\): update dependency winston to v3.2.1 [\\#3423](https://github.com/VSCodeVim/Vim/pull/3423) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency prettier to v1.16.2 [\\#3422](https://github.com/VSCodeVim/Vim/pull/3422) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.5 [\\#3421](https://github.com/VSCodeVim/Vim/pull/3421) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/diff to v3.5.3 [\\#3420](https://github.com/VSCodeVim/Vim/pull/3420) ([renovate[bot]](https://github.com/apps/renovate))\n- fix: validate configurations once, instead of every key press [\\#3418](https://github.com/VSCodeVim/Vim/pull/3418) ([jpoon](https://github.com/jpoon))\n- Run `closeMarkersNavigation` on ESC. Fix \\#3367 [\\#3416](https://github.com/VSCodeVim/Vim/pull/3416) ([octref](https://github.com/octref))\n- chore\\(deps\\): update dependency vscode to v1.1.28 [\\#3412](https://github.com/VSCodeVim/Vim/pull/3412) ([renovate-bot](https://github.com/renovate-bot))\n- refactor: make globalstate singleton class [\\#3411](https://github.com/VSCodeVim/Vim/pull/3411) ([jpoon](https://github.com/jpoon))\n- Misc async fixes - new revision [\\#3410](https://github.com/VSCodeVim/Vim/pull/3410) ([xconverge](https://github.com/xconverge))\n- fix: closes \\#3157 [\\#3409](https://github.com/VSCodeVim/Vim/pull/3409) ([jpoon](https://github.com/jpoon))\n- fix \\#3157: register single onDidChangeTextDocument handler and delegate to appropriate mode handler [\\#3408](https://github.com/VSCodeVim/Vim/pull/3408) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency prettier to v1.16.1 [\\#3405](https://github.com/VSCodeVim/Vim/pull/3405) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency vscode to v1.1.27 [\\#3403](https://github.com/VSCodeVim/Vim/pull/3403) ([renovate-bot](https://github.com/renovate-bot))\n- fix address 'gf' bug. `replace file://` method [\\#3402](https://github.com/VSCodeVim/Vim/pull/3402) ([pikulev](https://github.com/pikulev))\n- bump version [\\#3399](https://github.com/VSCodeVim/Vim/pull/3399) ([jpoon](https://github.com/jpoon))\n\n## [v1.0.3](https://github.com/vscodevim/vim/tree/v1.0.3) (2019-01-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.2...v1.0.3)\n\n**Merged pull requests:**\n\n- fix rangeerror. action buttons on log messages. [\\#3398](https://github.com/VSCodeVim/Vim/pull/3398) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency prettier to v1.16.0 [\\#3397](https://github.com/VSCodeVim/Vim/pull/3397) ([renovate-bot](https://github.com/renovate-bot))\n- fix: gf over a 'file://...' path and \\#3310 issue \\(v2\\) [\\#3396](https://github.com/VSCodeVim/Vim/pull/3396) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency sinon to v7.2.3 [\\#3394](https://github.com/VSCodeVim/Vim/pull/3394) ([renovate-bot](https://github.com/renovate-bot))\n- fix: 3350 [\\#3393](https://github.com/VSCodeVim/Vim/pull/3393) ([jpoon](https://github.com/jpoon))\n- docs: change slackin host [\\#3392](https://github.com/VSCodeVim/Vim/pull/3392) ([jpoon](https://github.com/jpoon))\n- Update dependency @types/lodash to v4.14.120 [\\#3385](https://github.com/VSCodeVim/Vim/pull/3385) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency typescript to v3.2.4 [\\#3384](https://github.com/VSCodeVim/Vim/pull/3384) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/sinon to v7.0.4 [\\#3383](https://github.com/VSCodeVim/Vim/pull/3383) ([renovate-bot](https://github.com/renovate-bot))\n- Fixes \\#3378 [\\#3381](https://github.com/VSCodeVim/Vim/pull/3381) ([xconverge](https://github.com/xconverge))\n- fixes \\#3374 [\\#3380](https://github.com/VSCodeVim/Vim/pull/3380) ([xconverge](https://github.com/xconverge))\n\n## [v1.0.2](https://github.com/vscodevim/vim/tree/v1.0.2) (2019-01-16)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.1...v1.0.2)\n\n## [v1.0.1](https://github.com/vscodevim/vim/tree/v1.0.1) (2019-01-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v1.0.0...v1.0.1)\n\n## [v1.0.0](https://github.com/vscodevim/vim/tree/v1.0.0) (2019-01-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.17.3...v1.0.0)\n\nThe first commit to this project was a little over 3 years ago, and what a journey it's been. To celebrate the new year, we are pushing out v1.0.0 of VSCodeVim! In addition to this project reaching such an amazing milestone, but in my personal life, I'll soon be celebrating the birth of my first-born. With that in mind, over the last few weeks I've tried to close out as many issues as I could before all my spare time is filled with diapers and bottles. Thanks to amazing team of maintainers, contributors, and users that have brought us to where we are today and where we'll go tomorrow.\n\n**Breaking Change:**\n\n- `vim.debug.loggingLevel` has been removed. In it's place we now have `vim.debug.loggingLevelForConsole`. For full details, see the [settings section of our README](https://github.com/VSCodeVim/Vim#vscodevim-settings).\n\n**Enhancements:**\n\n- feat: change debug configurations to loggingLevelForConsole, loggingLevelForAlert [\\#3325](https://github.com/VSCodeVim/Vim/pull/3325) ([jpoon](https://github.com/jpoon))\n\n**Fixed Bugs:**\n\n- Status Bar Color did not changed with the mode [\\#3316](https://github.com/VSCodeVim/Vim/issues/3316)\n- Error when remapping to commands with name starting with \"extension.\" [\\#3307](https://github.com/VSCodeVim/Vim/issues/3307)\n\n**Closed issues:**\n\n- gf: 'try to find it with the same extension'-code doesn't work [\\#3309](https://github.com/VSCodeVim/Vim/issues/3309)\n- Extension causes high cpu load [\\#3289](https://github.com/VSCodeVim/Vim/issues/3289)\n- The Vim plugin can not edit except i/a/s [\\#3270](https://github.com/VSCodeVim/Vim/issues/3270)\n- Keyboard stops working with VSCode when indenting multiline \\[MacOS Mojave\\] [\\#3206](https://github.com/VSCodeVim/Vim/issues/3206)\n- ctrl o shortcut not work sometimes [\\#3074](https://github.com/VSCodeVim/Vim/issues/3074)\n\n**Merged pull requests:**\n\n- fix: closes \\#3316 [\\#3321](https://github.com/VSCodeVim/Vim/pull/3321) ([jpoon](https://github.com/jpoon))\n- fix: Actually fix \\#3295. [\\#3320](https://github.com/VSCodeVim/Vim/pull/3320) ([jpoon](https://github.com/jpoon))\n- refactor: disableExtension configuration should follow pattern of rest of configs [\\#3318](https://github.com/VSCodeVim/Vim/pull/3318) ([jpoon](https://github.com/jpoon))\n- feat: show vim errors in vscode informational window [\\#3315](https://github.com/VSCodeVim/Vim/pull/3315) ([jpoon](https://github.com/jpoon))\n- fix: log warning if remapped command does not exist. closes \\#3307 [\\#3314](https://github.com/VSCodeVim/Vim/pull/3314) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.3 [\\#3313](https://github.com/VSCodeVim/Vim/pull/3313) ([renovate-bot](https://github.com/renovate-bot))\n- v0.17.3 [\\#3306](https://github.com/VSCodeVim/Vim/pull/3306) ([jpoon](https://github.com/jpoon))\n\n## [v0.17.3](https://github.com/vscodevim/vim/tree/v0.17.3) (2018-12-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.17.2...v0.17.3)\n\n**Enhancements:**\n\n- :on is not an editor command [\\#3286](https://github.com/VSCodeVim/Vim/issues/3286)\n- editor.wordSeparators setting is ignored [\\#3166](https://github.com/VSCodeVim/Vim/issues/3166)\n- save \\(:w or :wq\\) with SSHFS and LiveShare guest don't work properly [\\#2956](https://github.com/VSCodeVim/Vim/issues/2956)\n\n**Fixed Bugs:**\n\n- \\<c-o\\> jumps back to wrong location after 'gd' [\\#3277](https://github.com/VSCodeVim/Vim/issues/3277)\n\n**Closed issues:**\n\n- Either slash or colon not working [\\#3291](https://github.com/VSCodeVim/Vim/issues/3291)\n- s and S Key Commands Not Working [\\#3274](https://github.com/VSCodeVim/Vim/issues/3274)\n- Extension Host is unresponsive [\\#3056](https://github.com/VSCodeVim/Vim/issues/3056)\n- Vim mode randomly not functional - show warning [\\#2725](https://github.com/VSCodeVim/Vim/issues/2725)\n- Is hanging. [\\#2629](https://github.com/VSCodeVim/Vim/issues/2629)\n\n**Merged pull requests:**\n\n- fix: sync editor.wordSeparators and vim.iskeyword. closes \\#3166 [\\#3305](https://github.com/VSCodeVim/Vim/pull/3305) ([jpoon](https://github.com/jpoon))\n- feat: add on as alias for only [\\#3303](https://github.com/VSCodeVim/Vim/pull/3303) ([jpoon](https://github.com/jpoon))\n- fix: \\#3277 [\\#3302](https://github.com/VSCodeVim/Vim/pull/3302) ([jpoon](https://github.com/jpoon))\n- fix saving remote file error [\\#3281](https://github.com/VSCodeVim/Vim/pull/3281) ([zhuzisheng](https://github.com/zhuzisheng))\n\n## [v0.17.2](https://github.com/vscodevim/vim/tree/v0.17.2) (2018-12-28)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.17.1...v0.17.2)\n\n**Fixed Bugs:**\n\n- v0.17.1 prints `\\<tab\\>` string for every tab keystroke [\\#3298](https://github.com/VSCodeVim/Vim/issues/3298)\n\n**Merged pull requests:**\n\n- fix: v0.17.1 regression [\\#3299](https://github.com/VSCodeVim/Vim/pull/3299) ([jpoon](https://github.com/jpoon))\n- v0.17.0-\\>v0.17.1 [\\#3297](https://github.com/VSCodeVim/Vim/pull/3297) ([jpoon](https://github.com/jpoon))\n\n## [v0.17.1](https://github.com/vscodevim/vim/tree/v0.17.1) (2018-12-28)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.17.0...v0.17.1)\n\n**Fixed Bugs:**\n\n- Keybindings reset on invalid command [\\#3295](https://github.com/VSCodeVim/Vim/issues/3295)\n\n**Closed issues:**\n\n- For easy motion plugin, allow user to remap leader key. [\\#3244](https://github.com/VSCodeVim/Vim/issues/3244)\n- after opening user settings, all Vim keybindings are disabled [\\#3029](https://github.com/VSCodeVim/Vim/issues/3029)\n\n**Merged pull requests:**\n\n- fix: ignore remappings with non-existent commands. fixes \\#3295 [\\#3296](https://github.com/VSCodeVim/Vim/pull/3296) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update node.js to v8.15 [\\#3294](https://github.com/VSCodeVim/Vim/pull/3294) ([renovate-bot](https://github.com/renovate-bot))\n- fix: slightly improve perf by caching vscode context [\\#3293](https://github.com/VSCodeVim/Vim/pull/3293) ([jpoon](https://github.com/jpoon))\n- fix: disable nvim shada [\\#3288](https://github.com/VSCodeVim/Vim/pull/3288) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/sinon to v7.0.2 [\\#3279](https://github.com/VSCodeVim/Vim/pull/3279) ([renovate-bot](https://github.com/renovate-bot))\n- refactor: status bar [\\#3276](https://github.com/VSCodeVim/Vim/pull/3276) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.41 [\\#3275](https://github.com/VSCodeVim/Vim/pull/3275) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency tslint to v5.12.0 [\\#3272](https://github.com/VSCodeVim/Vim/pull/3272) ([renovate-bot](https://github.com/renovate-bot))\n- Release [\\#3271](https://github.com/VSCodeVim/Vim/pull/3271) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency typescript to v3.2.2 [\\#3234](https://github.com/VSCodeVim/Vim/pull/3234) ([renovate-bot](https://github.com/renovate-bot))\n\n## [v0.17.0](https://github.com/vscodevim/vim/tree/v0.17.0) (2018-12-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.14...v0.17.0)\n\n**Fixed Bugs:**\n\n- Running :reg when clipboard is empty causes an error [\\#2898](https://github.com/VSCodeVim/Vim/issues/2898)\n\n**Merged pull requests:**\n\n- Change to use native vscode clipboard [\\#3261](https://github.com/VSCodeVim/Vim/pull/3261) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/sinon to v7 [\\#3259](https://github.com/VSCodeVim/Vim/pull/3259) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency sinon to v7.2.1 [\\#3258](https://github.com/VSCodeVim/Vim/pull/3258) ([renovate-bot](https://github.com/renovate-bot))\n- v0.16.13 -\\> v0.16.14 [\\#3257](https://github.com/VSCodeVim/Vim/pull/3257) ([jpoon](https://github.com/jpoon))\n\n## [v0.16.14](https://github.com/vscodevim/vim/tree/v0.16.14) (2018-12-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.13...v0.16.14)\n\n**Enhancements:**\n\n- Add support for new grid layout with splits [\\#2696](https://github.com/VSCodeVim/Vim/issues/2696)\n\n**Fixed Bugs:**\n\n- It seems % command is not treated like a motion [\\#3138](https://github.com/VSCodeVim/Vim/issues/3138)\n\n**Closed issues:**\n\n- vim.normalModeKeyBindingsNonRecursive do not work [\\#3247](https://github.com/VSCodeVim/Vim/issues/3247)\n- Status bar in zen mode [\\#3245](https://github.com/VSCodeVim/Vim/issues/3245)\n- When closing a window with `:q` VS Code now selects the tab \"before\" the one you were previously on [\\#2984](https://github.com/VSCodeVim/Vim/issues/2984)\n\n**Merged pull requests:**\n\n- chore\\(deps\\): update dependency vscode to v1.1.26 [\\#3256](https://github.com/VSCodeVim/Vim/pull/3256) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency sinon to v7.2.0 [\\#3255](https://github.com/VSCodeVim/Vim/pull/3255) ([renovate-bot](https://github.com/renovate-bot))\n- Format operator fixes and tests [\\#3254](https://github.com/VSCodeVim/Vim/pull/3254) ([watsoncj](https://github.com/watsoncj))\n- Added common example for key remapping for £ [\\#3250](https://github.com/VSCodeVim/Vim/pull/3250) ([ycmjason](https://github.com/ycmjason))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.119 [\\#3246](https://github.com/VSCodeVim/Vim/pull/3246) ([renovate-bot](https://github.com/renovate-bot))\n- Re-implement `` and '' with jumpTracker [\\#3242](https://github.com/VSCodeVim/Vim/pull/3242) ([dsschnau](https://github.com/dsschnau))\n- chore\\(deps\\): update dependency gulp-typescript to v5 [\\#3240](https://github.com/VSCodeVim/Vim/pull/3240) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency prettier to v1.15.3 [\\#3236](https://github.com/VSCodeVim/Vim/pull/3236) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.40 [\\#3235](https://github.com/VSCodeVim/Vim/pull/3235) ([renovate-bot](https://github.com/renovate-bot))\n- fix typo [\\#3230](https://github.com/VSCodeVim/Vim/pull/3230) ([fourcels](https://github.com/fourcels))\n- chore\\(deps\\): update node.js to v8.14 [\\#3228](https://github.com/VSCodeVim/Vim/pull/3228) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency vscode to v1.1.24 [\\#3224](https://github.com/VSCodeVim/Vim/pull/3224) ([renovate-bot](https://github.com/renovate-bot))\n- Fix \\#2984: wrong tab selected after :quit [\\#3170](https://github.com/VSCodeVim/Vim/pull/3170) ([ohjames](https://github.com/ohjames))\n\n## [v0.16.13](https://github.com/vscodevim/vim/tree/v0.16.13) (2018-11-27)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.12...v0.16.13)\n\n**Fixed Bugs:**\n\n- Finding with `?` renders `/` in the status bar instead of `?` [\\#3211](https://github.com/VSCodeVim/Vim/issues/3211)\n- Test docker - debconf enforces interactive during build [\\#3168](https://github.com/VSCodeVim/Vim/issues/3168)\n\n**Closed issues:**\n\n- Problem with insert mode after highlighting in visual mode [\\#3174](https://github.com/VSCodeVim/Vim/issues/3174)\n- Recursive mapping V key [\\#3173](https://github.com/VSCodeVim/Vim/issues/3173)\n- Code Action not working when using Vim mappings [\\#3160](https://github.com/VSCodeVim/Vim/issues/3160)\n\n**Merged pull requests:**\n\n- v0.16.13 [\\#3223](https://github.com/VSCodeVim/Vim/pull/3223) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update node.js to v8.13 [\\#3222](https://github.com/VSCodeVim/Vim/pull/3222) ([renovate-bot](https://github.com/renovate-bot))\n- display '?' or '/' in status bar in search mode [\\#3218](https://github.com/VSCodeVim/Vim/pull/3218) ([dsschnau](https://github.com/dsschnau))\n- fix: upgrade sinon 5.0.5-\\>5.0.7. prettier 1.14.3-\\>1.15.2 [\\#3217](https://github.com/VSCodeVim/Vim/pull/3217) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.39 [\\#3215](https://github.com/VSCodeVim/Vim/pull/3215) ([renovate-bot](https://github.com/renovate-bot))\n- Fix \\#1287: CJK characters\\(korean\\) overlap each other in insert mode [\\#3214](https://github.com/VSCodeVim/Vim/pull/3214) ([Injae-Lee](https://github.com/Injae-Lee))\n- chore\\(deps\\): update dependency @types/node to v9.6.37 [\\#3204](https://github.com/VSCodeVim/Vim/pull/3204) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.118 [\\#3196](https://github.com/VSCodeVim/Vim/pull/3196) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update node.js to v8.12 [\\#3194](https://github.com/VSCodeVim/Vim/pull/3194) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/diff to v3.5.2 [\\#3193](https://github.com/VSCodeVim/Vim/pull/3193) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency typescript to v3.1.6 [\\#3188](https://github.com/VSCodeVim/Vim/pull/3188) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.36 [\\#3187](https://github.com/VSCodeVim/Vim/pull/3187) ([renovate-bot](https://github.com/renovate-bot))\n- docs: update roadmap for split and new [\\#3184](https://github.com/VSCodeVim/Vim/pull/3184) ([jpoon](https://github.com/jpoon))\n- fix: automerge renovate minor/patch [\\#3183](https://github.com/VSCodeVim/Vim/pull/3183) ([jpoon](https://github.com/jpoon))\n- Update dependency typescript to v3.1.5 [\\#3182](https://github.com/VSCodeVim/Vim/pull/3182) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency typescript to v3.1.4 [\\#3175](https://github.com/VSCodeVim/Vim/pull/3175) ([renovate-bot](https://github.com/renovate-bot))\n- Issue \\#3168 - Ubuntu tests [\\#3169](https://github.com/VSCodeVim/Vim/pull/3169) ([pschoffer](https://github.com/pschoffer))\n- v0.16.12 [\\#3165](https://github.com/VSCodeVim/Vim/pull/3165) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency sinon to v7.1.1 [\\#3162](https://github.com/VSCodeVim/Vim/pull/3162) ([renovate-bot](https://github.com/renovate-bot))\n- Convert synchronous funcs to async [\\#3123](https://github.com/VSCodeVim/Vim/pull/3123) ([kylecarbs](https://github.com/kylecarbs))\n\n## [v0.16.12](https://github.com/vscodevim/vim/tree/v0.16.12) (2018-10-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.11...v0.16.12)\n\n**Fixed Bugs:**\n\n- Gulp test with Docker fails to launch [\\#3152](https://github.com/VSCodeVim/Vim/issues/3152)\n- The link to \\*Multi-Cursor\\* mode in \\_\\_Table of content\\_\\_ doesn't work \\(in repo\\) [\\#3149](https://github.com/VSCodeVim/Vim/issues/3149)\n- Multi-Cursor + insertModeKeyBinding jk -\\> \\<Esc\\> [\\#2752](https://github.com/VSCodeVim/Vim/issues/2752)\n\n**Merged pull requests:**\n\n- Add more Docker documentation [\\#3156](https://github.com/VSCodeVim/Vim/pull/3156) ([westim](https://github.com/westim))\n- Fix 3152: Upgrade Docker prerequisite libgtk from 2.0 to 3.0 [\\#3153](https://github.com/VSCodeVim/Vim/pull/3153) ([westim](https://github.com/westim))\n- Fix \\#3149: broken table of contents links [\\#3151](https://github.com/VSCodeVim/Vim/pull/3151) ([westim](https://github.com/westim))\n- Fix for \\#2752 [\\#3131](https://github.com/VSCodeVim/Vim/pull/3131) ([donald93](https://github.com/donald93))\n\n## [v0.16.11](https://github.com/vscodevim/vim/tree/v0.16.11) (2018-10-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.10...v0.16.11)\n\n**Closed issues:**\n\n- Version 0.16.10 stuck in insert mode [\\#3143](https://github.com/VSCodeVim/Vim/issues/3143)\n- fold code block bug [\\#3140](https://github.com/VSCodeVim/Vim/issues/3140)\n- Escape key stopped being registered so can't exit insert mode [\\#3139](https://github.com/VSCodeVim/Vim/issues/3139)\n\n**Merged pull requests:**\n\n- Prevent error on loading search history if no active editor on startup [\\#3146](https://github.com/VSCodeVim/Vim/pull/3146) ([shawnaxsom](https://github.com/shawnaxsom))\n- v0.16.10 [\\#3137](https://github.com/VSCodeVim/Vim/pull/3137) ([jpoon](https://github.com/jpoon))\n\n## [v0.16.10](https://github.com/vscodevim/vim/tree/v0.16.10) (2018-10-14)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.9...v0.16.10)\n\n**Enhancements:**\n\n- Previous searches are not saved across sessions [\\#3098](https://github.com/VSCodeVim/Vim/issues/3098)\n- substitution statefulness [\\#3067](https://github.com/VSCodeVim/Vim/issues/3067)\n- feat: implement 'changeWordIncludesWhitespace' option [\\#2964](https://github.com/VSCodeVim/Vim/pull/2964) ([darfink](https://github.com/darfink))\n\n**Fixed Bugs:**\n\n- Wrong cursor position after using same file in two panels [\\#2688](https://github.com/VSCodeVim/Vim/issues/2688)\n- Search and replace doesn't work with current line \\(.\\) and relative lines [\\#2384](https://github.com/VSCodeVim/Vim/issues/2384)\n\n**Closed issues:**\n\n- Broken on Insiders build [\\#3119](https://github.com/VSCodeVim/Vim/issues/3119)\n- Cannot bind \\<C-h\\> [\\#3072](https://github.com/VSCodeVim/Vim/issues/3072)\n- CTRL-\\[ does not quit the command-line editing mode [\\#3019](https://github.com/VSCodeVim/Vim/issues/3019)\n\n**Merged pull requests:**\n\n- chore\\(deps\\): update dependency sinon to v7 [\\#3135](https://github.com/VSCodeVim/Vim/pull/3135) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency typescript to v3.1.3 [\\#3130](https://github.com/VSCodeVim/Vim/pull/3130) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency typescript to v3.1.2 [\\#3122](https://github.com/VSCodeVim/Vim/pull/3122) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.35 [\\#3121](https://github.com/VSCodeVim/Vim/pull/3121) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/lodash to v4.14.117 [\\#3120](https://github.com/VSCodeVim/Vim/pull/3120) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/sinon to v5.0.5 [\\#3118](https://github.com/VSCodeVim/Vim/pull/3118) ([renovate-bot](https://github.com/renovate-bot))\n- Save search history to a file like commandline history [\\#3116](https://github.com/VSCodeVim/Vim/pull/3116) ([xconverge](https://github.com/xconverge))\n- fix \\(simpler\\) - cursor whenever changing editors - closes \\#2688 [\\#3103](https://github.com/VSCodeVim/Vim/pull/3103) ([captaincaius](https://github.com/captaincaius))\n- feature: relative, plus/minus ranges. closes \\#2384 [\\#3071](https://github.com/VSCodeVim/Vim/pull/3071) ([captaincaius](https://github.com/captaincaius))\n- Adding state to substitution command [\\#3068](https://github.com/VSCodeVim/Vim/pull/3068) ([captaincaius](https://github.com/captaincaius))\n\n## [v0.16.9](https://github.com/vscodevim/vim/tree/v0.16.9) (2018-10-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.8...v0.16.9)\n\n**Fixed Bugs:**\n\n- Repeating command \\(`.`\\) after doing vim-easymotion find character command doesn't work. [\\#3111](https://github.com/VSCodeVim/Vim/issues/3111)\n- Incrementing / Decrementing numbers doesn't work when it's after a minus sign and a word [\\#3057](https://github.com/VSCodeVim/Vim/issues/3057)\n- Unexpected behavior with easymotion and `.` as repeat command [\\#2310](https://github.com/VSCodeVim/Vim/issues/2310)\n\n**Merged pull requests:**\n\n- support \"edit\" command [\\#3114](https://github.com/VSCodeVim/Vim/pull/3114) ([m59peacemaker](https://github.com/m59peacemaker))\n- Minor C-a C-x fix [\\#3113](https://github.com/VSCodeVim/Vim/pull/3113) ([xconverge](https://github.com/xconverge))\n- Allow dot to repeat after doing any EasyMotion move [\\#3112](https://github.com/VSCodeVim/Vim/pull/3112) ([xconverge](https://github.com/xconverge))\n\n## [v0.16.8](https://github.com/vscodevim/vim/tree/v0.16.8) (2018-10-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.7...v0.16.8)\n\n**Closed issues:**\n\n- \\<C -c\\> stopped working this morning [\\#3110](https://github.com/VSCodeVim/Vim/issues/3110)\n- version 0.16.6 cause \\<tab\\> key insert string for unknown reason [\\#3096](https://github.com/VSCodeVim/Vim/issues/3096)\n- yank in visual mode doesn't update register 0 [\\#3065](https://github.com/VSCodeVim/Vim/issues/3065)\n- Paste the yanked text with \"0p does no work [\\#2554](https://github.com/VSCodeVim/Vim/issues/2554)\n- Surround: Keep HTML attributes when changing tags [\\#1938](https://github.com/VSCodeVim/Vim/issues/1938)\n\n**Merged pull requests:**\n\n- Fix issues with keybindings when changing to an editor in different mode [\\#3108](https://github.com/VSCodeVim/Vim/pull/3108) ([shawnaxsom](https://github.com/shawnaxsom))\n- README cleanup [\\#3107](https://github.com/VSCodeVim/Vim/pull/3107) ([xconverge](https://github.com/xconverge))\n- Update readme based on new feature for surround with attributes [\\#3106](https://github.com/VSCodeVim/Vim/pull/3106) ([xconverge](https://github.com/xconverge))\n- fixes \\#1938 Allow to retain attributes when using surround [\\#3105](https://github.com/VSCodeVim/Vim/pull/3105) ([xconverge](https://github.com/xconverge))\n- Multiline yank writes to 0 register; fixes \\#1214 [\\#3087](https://github.com/VSCodeVim/Vim/pull/3087) ([JKillian](https://github.com/JKillian))\n\n## [v0.16.7](https://github.com/vscodevim/vim/tree/v0.16.7) (2018-10-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.6...v0.16.7)\n\n**Merged pull requests:**\n\n- Update dependency @types/sinon to v5.0.4 [\\#3104](https://github.com/VSCodeVim/Vim/pull/3104) ([renovate-bot](https://github.com/renovate-bot))\n- Cleanup gt count command [\\#3097](https://github.com/VSCodeVim/Vim/pull/3097) ([xconverge](https://github.com/xconverge))\n- Update dependency @types/sinon to v5.0.3 [\\#3093](https://github.com/VSCodeVim/Vim/pull/3093) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.34 [\\#3092](https://github.com/VSCodeVim/Vim/pull/3092) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency sinon to v6.3.5 [\\#3091](https://github.com/VSCodeVim/Vim/pull/3091) ([renovate-bot](https://github.com/renovate-bot))\n- Remappings not applying with operators that enter insert mode [\\#3090](https://github.com/VSCodeVim/Vim/pull/3090) ([shawnaxsom](https://github.com/shawnaxsom))\n- v0.16.6 [\\#3085](https://github.com/VSCodeVim/Vim/pull/3085) ([jpoon](https://github.com/jpoon))\n- Add support for grid layout [\\#2697](https://github.com/VSCodeVim/Vim/pull/2697) ([rodcloutier](https://github.com/rodcloutier))\n\n## [v0.16.6](https://github.com/vscodevim/vim/tree/v0.16.6) (2018-10-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.5...v0.16.6)\n\n**Fixed Bugs:**\n\n- Confirm-Replace works incorrectly with global substitute for certain types of replace patterns [\\#2950](https://github.com/VSCodeVim/Vim/issues/2950)\n- Remapping `d` to always delete to black-hole [\\#2672](https://github.com/VSCodeVim/Vim/issues/2672)\n\n**Closed issues:**\n\n- Visual Block Mode when not using Ctrl keys [\\#3042](https://github.com/VSCodeVim/Vim/issues/3042)\n- Investigate reducing startup activation time [\\#2947](https://github.com/VSCodeVim/Vim/issues/2947)\n\n**Merged pull requests:**\n\n- Feature/fix black hole operator mappings [\\#3081](https://github.com/VSCodeVim/Vim/pull/3081) ([shawnaxsom](https://github.com/shawnaxsom))\n- Feature/insert mode optimizations [\\#3078](https://github.com/VSCodeVim/Vim/pull/3078) ([shawnaxsom](https://github.com/shawnaxsom))\n- Update dependency typescript to v3.1.1 [\\#3077](https://github.com/VSCodeVim/Vim/pull/3077) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.32 [\\#3066](https://github.com/VSCodeVim/Vim/pull/3066) ([renovate-bot](https://github.com/renovate-bot))\n- Fix substitute with gc flag [\\#3055](https://github.com/VSCodeVim/Vim/pull/3055) ([tomotg](https://github.com/tomotg))\n\n## [v0.16.5](https://github.com/vscodevim/vim/tree/v0.16.5) (2018-09-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.4...v0.16.5)\n\n**Fixed Bugs:**\n\n- keybinding \\<c-f\\> overwrite vscode's default behavior [\\#3050](https://github.com/VSCodeVim/Vim/issues/3050)\n- New Jump Tracker doesn't always handle that isn't left open in a tab [\\#3039](https://github.com/VSCodeVim/Vim/issues/3039)\n- Exiting CommandMode should mimic Vim behavior [\\#3035](https://github.com/VSCodeVim/Vim/issues/3035)\n\n**Closed issues:**\n\n- C-o, C-i strange jumping behavior. [\\#3047](https://github.com/VSCodeVim/Vim/issues/3047)\n- Support vscode's color copy [\\#3038](https://github.com/VSCodeVim/Vim/issues/3038)\n- Possible for `:new` to a open a new editor in the current group without splitting? [\\#2911](https://github.com/VSCodeVim/Vim/issues/2911)\n- Support for ' ' \\(Jump to previous cursor position\\) [\\#2031](https://github.com/VSCodeVim/Vim/issues/2031)\n\n**Merged pull requests:**\n\n- Update dependency prettier to v1.14.3 [\\#3060](https://github.com/VSCodeVim/Vim/pull/3060) ([renovate-bot](https://github.com/renovate-bot))\n- fix `\\<C-f\\>` in 「Insert」mode [\\#3051](https://github.com/VSCodeVim/Vim/pull/3051) ([myhere](https://github.com/myhere))\n- Support for line completion \\(\\<C-x\\>\\<C-l\\>\\) [\\#3048](https://github.com/VSCodeVim/Vim/pull/3048) ([shawnaxsom](https://github.com/shawnaxsom))\n- Update dependency lodash to v4.17.11 [\\#3045](https://github.com/VSCodeVim/Vim/pull/3045) ([renovate-bot](https://github.com/renovate-bot))\n- Fixed Jump Tracker jumps when jumping from a file that auto closes [\\#3041](https://github.com/VSCodeVim/Vim/pull/3041) ([shawnaxsom](https://github.com/shawnaxsom))\n- Fix: Missing bindings to exit CommandMode. closes \\#3035 [\\#3036](https://github.com/VSCodeVim/Vim/pull/3036) ([mxlian](https://github.com/mxlian))\n\n## [v0.16.4](https://github.com/vscodevim/vim/tree/v0.16.4) (2018-09-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.3...v0.16.4)\n\n**Enhancements:**\n\n- \\[FEATURE REQUEST\\]visual line mode support A or I [\\#2167](https://github.com/VSCodeVim/Vim/issues/2167)\n\n**Closed issues:**\n\n- Moving out of viewport centers the viewport when it shouldn't [\\#2998](https://github.com/VSCodeVim/Vim/issues/2998)\n- docs: all-contributors [\\#2645](https://github.com/VSCodeVim/Vim/issues/2645)\n- Make small movement command not registered to Ctrl+o [\\#1933](https://github.com/VSCodeVim/Vim/issues/1933)\n\n**Merged pull requests:**\n\n- Feature/improved jump list [\\#3028](https://github.com/VSCodeVim/Vim/pull/3028) ([shawnaxsom](https://github.com/shawnaxsom))\n- I or A in visual/visual line mode creates multiple cursors \\#2167 [\\#2993](https://github.com/VSCodeVim/Vim/pull/2993) ([shawnaxsom](https://github.com/shawnaxsom))\n\n## [v0.16.3](https://github.com/vscodevim/vim/tree/v0.16.3) (2018-09-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.2...v0.16.3)\n\n**Enhancements:**\n\n- Add activationEvent 'onCommand:type' to avoid missing keystrokes [\\#3016](https://github.com/VSCodeVim/Vim/issues/3016)\n- va{a{ doesn't work [\\#2506](https://github.com/VSCodeVim/Vim/issues/2506)\n\n**Closed issues:**\n\n- Expand selection with inner tag selection command [\\#2907](https://github.com/VSCodeVim/Vim/issues/2907)\n\n**Merged pull requests:**\n\n- fix: re-enable relativelinenumbers. closes \\#3020 [\\#3025](https://github.com/VSCodeVim/Vim/pull/3025) ([jpoon](https://github.com/jpoon))\n- fix: add activationevent onCommand type. closes \\#3016 [\\#3023](https://github.com/VSCodeVim/Vim/pull/3023) ([jpoon](https://github.com/jpoon))\n- Update dependency winston to v3.1.0 [\\#3021](https://github.com/VSCodeVim/Vim/pull/3021) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency diff-match-patch to v1.0.4 [\\#3018](https://github.com/VSCodeVim/Vim/pull/3018) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.31 [\\#3011](https://github.com/VSCodeVim/Vim/pull/3011) ([renovate-bot](https://github.com/renovate-bot))\n- Fix multiple issues with expand selection commands and pair/block movement [\\#2921](https://github.com/VSCodeVim/Vim/pull/2921) ([xmbhasin](https://github.com/xmbhasin))\n\n## [v0.16.2](https://github.com/vscodevim/vim/tree/v0.16.2) (2018-08-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.1...v0.16.2)\n\n**Closed issues:**\n\n- Intermediate cursor shape to show that a command is being entered [\\#2999](https://github.com/VSCodeVim/Vim/issues/2999)\n\n**Merged pull requests:**\n\n- Revert \"Center cursor vertically on movement out of viewport\" [\\#3009](https://github.com/VSCodeVim/Vim/pull/3009) ([hhu94](https://github.com/hhu94))\n- Update dependency typescript to v3.0.3 [\\#3008](https://github.com/VSCodeVim/Vim/pull/3008) ([renovate-bot](https://github.com/renovate-bot))\n- Update vim.searchHighlightColor in README.md [\\#3007](https://github.com/VSCodeVim/Vim/pull/3007) ([ytang](https://github.com/ytang))\n- v0.16.1 [\\#2997](https://github.com/VSCodeVim/Vim/pull/2997) ([jpoon](https://github.com/jpoon))\n\n## [v0.16.1](https://github.com/vscodevim/vim/tree/v0.16.1) (2018-08-27)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.16.0...v0.16.1)\n\n**Fixed Bugs:**\n\n- `:vsp file\\_name` cannot open file_name, although this file does exist [\\#2983](https://github.com/VSCodeVim/Vim/issues/2983)\n- `gf` \\(go to file under cursor\\) produces the \"Vim: The file ... does not exist.\" error, even though file clearly exists [\\#2966](https://github.com/VSCodeVim/Vim/issues/2966)\n- Open File with :e deletes file content [\\#2963](https://github.com/VSCodeVim/Vim/issues/2963)\n\n**Closed issues:**\n\n- \"before\": \\[\"\\<C-x\\>\", \"C-s\\>\"\\] not work. [\\#2949](https://github.com/VSCodeVim/Vim/issues/2949)\n- VSCodeVim airline affecting color scheme [\\#2948](https://github.com/VSCodeVim/Vim/issues/2948)\n- \\[Feature Request\\] : ReplaceWithRegister [\\#2937](https://github.com/VSCodeVim/Vim/issues/2937)\n- % should match on strings & chars [\\#2935](https://github.com/VSCodeVim/Vim/issues/2935)\n- Throw away the mouse [\\#2922](https://github.com/VSCodeVim/Vim/issues/2922)\n- Wried cursor behavior with INSERT MULTI CURSOR mode [\\#2910](https://github.com/VSCodeVim/Vim/issues/2910)\n\n**Merged pull requests:**\n\n- Lazy Load Neovim [\\#2992](https://github.com/VSCodeVim/Vim/pull/2992) ([jpoon](https://github.com/jpoon))\n- Update dependency @types/node to v9.6.30 [\\#2987](https://github.com/VSCodeVim/Vim/pull/2987) ([renovate-bot](https://github.com/renovate-bot))\n- Fix type in ROADMAP.md [\\#2980](https://github.com/VSCodeVim/Vim/pull/2980) ([nickebbitt](https://github.com/nickebbitt))\n- Fix emulated plugins link in README [\\#2977](https://github.com/VSCodeVim/Vim/pull/2977) ([jjt](https://github.com/jjt))\n- Fix `gf` showing error for files which exist [\\#2969](https://github.com/VSCodeVim/Vim/pull/2969) ([arussellk](https://github.com/arussellk))\n- Fix Typo in ROADMAP [\\#2967](https://github.com/VSCodeVim/Vim/pull/2967) ([AdrieanKhisbe](https://github.com/AdrieanKhisbe))\n- Center cursor vertically on movement out of viewport [\\#2962](https://github.com/VSCodeVim/Vim/pull/2962) ([hhu94](https://github.com/hhu94))\n- chore\\(deps\\): update dependency vscode to v1.1.21 [\\#2958](https://github.com/VSCodeVim/Vim/pull/2958) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.28 [\\#2952](https://github.com/VSCodeVim/Vim/pull/2952) ([renovate-bot](https://github.com/renovate-bot))\n\n## [v0.16.0](https://github.com/vscodevim/vim/tree/v0.16.0) (2018-08-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.7...v0.16.0)\n\n**Enhancements:**\n\n- Reenable change that minimized the calls to setContext [\\#2900](https://github.com/VSCodeVim/Vim/pull/2900) ([xconverge](https://github.com/xconverge))\n\n**Fixed Bugs:**\n\n- Cannot create files with extensions using :e\\[dit\\] {file} [\\#2923](https://github.com/VSCodeVim/Vim/issues/2923)\n- :tablast broken with vscode 1.25.0 [\\#2813](https://github.com/VSCodeVim/Vim/issues/2813)\n- 2gt not goes to the right tab [\\#2789](https://github.com/VSCodeVim/Vim/issues/2789)\n\n**Closed issues:**\n\n- \"commandlineinprogress\": \"underline\" causes issues [\\#2896](https://github.com/VSCodeVim/Vim/issues/2896)\n- Quote macro sometimes doubling in Python [\\#2662](https://github.com/VSCodeVim/Vim/issues/2662)\n- easy motion mapping key problem [\\#1894](https://github.com/VSCodeVim/Vim/issues/1894)\n\n**Merged pull requests:**\n\n- bump version [\\#2946](https://github.com/VSCodeVim/Vim/pull/2946) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency prettier to v1.14.2 [\\#2943](https://github.com/VSCodeVim/Vim/pull/2943) ([renovate-bot](https://github.com/renovate-bot))\n- docs: move configs to tables for readability [\\#2941](https://github.com/VSCodeVim/Vim/pull/2941) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.26 [\\#2940](https://github.com/VSCodeVim/Vim/pull/2940) ([renovate-bot](https://github.com/renovate-bot))\n- docs: clean-up readme [\\#2931](https://github.com/VSCodeVim/Vim/pull/2931) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.116 [\\#2930](https://github.com/VSCodeVim/Vim/pull/2930) ([renovate-bot](https://github.com/renovate-bot))\n- fix: files with extensions not being auto-created. closes \\#2923. [\\#2928](https://github.com/VSCodeVim/Vim/pull/2928) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.25 [\\#2927](https://github.com/VSCodeVim/Vim/pull/2927) ([renovate-bot](https://github.com/renovate-bot))\n- Fix :tablast breaking in vscode 1.25 \\#2813 [\\#2926](https://github.com/VSCodeVim/Vim/pull/2926) ([Roshanjossey](https://github.com/Roshanjossey))\n- chore\\(deps\\): update dependency typescript to v3 [\\#2920](https://github.com/VSCodeVim/Vim/pull/2920) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency gulp-git to v2.8.0 [\\#2919](https://github.com/VSCodeVim/Vim/pull/2919) ([renovate-bot](https://github.com/renovate-bot))\n- Fix Emulated Plugins TOC link in README [\\#2918](https://github.com/VSCodeVim/Vim/pull/2918) ([jjt](https://github.com/jjt))\n- fix: use full path for configs [\\#2915](https://github.com/VSCodeVim/Vim/pull/2915) ([jpoon](https://github.com/jpoon))\n- fix: enable prettier for md [\\#2909](https://github.com/VSCodeVim/Vim/pull/2909) ([jpoon](https://github.com/jpoon))\n- Update dependency prettier to v1.14.0 [\\#2908](https://github.com/VSCodeVim/Vim/pull/2908) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.24 [\\#2906](https://github.com/VSCodeVim/Vim/pull/2906) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/lodash to v4.14.115 [\\#2905](https://github.com/VSCodeVim/Vim/pull/2905) ([renovate-bot](https://github.com/renovate-bot))\n- Add --grep flag to gulp test [\\#2904](https://github.com/VSCodeVim/Vim/pull/2904) ([xmbhasin](https://github.com/xmbhasin))\n- Update dependency @types/lodash to v4.14.114 [\\#2901](https://github.com/VSCodeVim/Vim/pull/2901) ([renovate-bot](https://github.com/renovate-bot))\n- Fix gt tab navigation with count prefix [\\#2899](https://github.com/VSCodeVim/Vim/pull/2899) ([xconverge](https://github.com/xconverge))\n- Updating README FAQ [\\#2894](https://github.com/VSCodeVim/Vim/pull/2894) ([augustnmonteiro](https://github.com/augustnmonteiro))\n- refactor baseaction [\\#2892](https://github.com/VSCodeVim/Vim/pull/2892) ([jpoon](https://github.com/jpoon))\n- Revert \"fix: use ferrarimarco's image instead of my fork to generate changelog\" [\\#2891](https://github.com/VSCodeVim/Vim/pull/2891) ([jpoon](https://github.com/jpoon))\n- Integrate SmartIM to VSCodeVim [\\#2643](https://github.com/VSCodeVim/Vim/pull/2643) ([daipeihust](https://github.com/daipeihust))\n\n## [v0.15.7](https://github.com/vscodevim/vim/tree/v0.15.7) (2018-07-25)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.6...v0.15.7)\n\n**Enhancements:**\n\n- Please use vscode's config folder for .cmdline_history [\\#2799](https://github.com/VSCodeVim/Vim/issues/2799)\n- Improve neovim command execution status reporting in status bar [\\#2878](https://github.com/VSCodeVim/Vim/pull/2878) ([xconverge](https://github.com/xconverge))\n\n**Fixed Bugs:**\n\n- 'r' in insert mode not entered when typed quickly [\\#2888](https://github.com/VSCodeVim/Vim/issues/2888)\n- Vim extension stops working [\\#2873](https://github.com/VSCodeVim/Vim/issues/2873)\n\n**Closed issues:**\n\n- hjkl keys as arrow keys in intellisense contextual menu do not work [\\#2885](https://github.com/VSCodeVim/Vim/issues/2885)\n\n**Merged pull requests:**\n\n- Fix issue with incorrectly finding and triggering certain remappings [\\#2890](https://github.com/VSCodeVim/Vim/pull/2890) ([xconverge](https://github.com/xconverge))\n- Move commandline history to XDG_CACHE_HOME or %APPDATA% [\\#2889](https://github.com/VSCodeVim/Vim/pull/2889) ([xconverge](https://github.com/xconverge))\n- fix: use ferrarimarco's image instead of my fork to generate changelog [\\#2884](https://github.com/VSCodeVim/Vim/pull/2884) ([jpoon](https://github.com/jpoon))\n- fix: use map to search for relevant actions. \\#2021 [\\#2883](https://github.com/VSCodeVim/Vim/pull/2883) ([jpoon](https://github.com/jpoon))\n- fix: handle non-string remapped key. closes \\#2873 [\\#2881](https://github.com/VSCodeVim/Vim/pull/2881) ([jpoon](https://github.com/jpoon))\n\n## [v0.15.6](https://github.com/vscodevim/vim/tree/v0.15.6) (2018-07-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.5...v0.15.6)\n\n**Merged pull requests:**\n\n- Fix regression with setContext in modeHandler [\\#2880](https://github.com/VSCodeVim/Vim/pull/2880) ([xconverge](https://github.com/xconverge))\n\n## [v0.15.5](https://github.com/vscodevim/vim/tree/v0.15.5) (2018-07-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.4...v0.15.5)\n\n**Merged pull requests:**\n\n- Neovim integration show errors when using commandline at correct times [\\#2877](https://github.com/VSCodeVim/Vim/pull/2877) ([xconverge](https://github.com/xconverge))\n- Improve error reporting with neovim commandline [\\#2876](https://github.com/VSCodeVim/Vim/pull/2876) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.113 [\\#2875](https://github.com/VSCodeVim/Vim/pull/2875) ([renovate-bot](https://github.com/renovate-bot))\n\n## [v0.15.4](https://github.com/vscodevim/vim/tree/v0.15.4) (2018-07-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.3...v0.15.4)\n\n**Enhancements:**\n\n- Moving down at a fold that's at the end of the file causes an infinite loop [\\#1855](https://github.com/VSCodeVim/Vim/issues/1855)\n\n**Fixed Bugs:**\n\n- Long key chords does not trigger configured action. [\\#2735](https://github.com/VSCodeVim/Vim/issues/2735)\n- Cursor jumps erratically before moving vertically [\\#2163](https://github.com/VSCodeVim/Vim/issues/2163)\n\n**Closed issues:**\n\n- ^f stopped working after 1.25.1 update [\\#2865](https://github.com/VSCodeVim/Vim/issues/2865)\n- Switching escape and capslock [\\#2859](https://github.com/VSCodeVim/Vim/issues/2859)\n\n**Merged pull requests:**\n\n- fix: add missing wrapkeys to test configuration [\\#2871](https://github.com/VSCodeVim/Vim/pull/2871) ([jpoon](https://github.com/jpoon))\n- Improve foldfix performance and potentially fix some bugs\\(\\#1855 \\#2163\\) [\\#2867](https://github.com/VSCodeVim/Vim/pull/2867) ([xmbhasin](https://github.com/xmbhasin))\n- Roadmap doc fix for visual mode case switching [\\#2866](https://github.com/VSCodeVim/Vim/pull/2866) ([pjlangley](https://github.com/pjlangley))\n- Add whichwrap [\\#2864](https://github.com/VSCodeVim/Vim/pull/2864) ([davidmfoley](https://github.com/davidmfoley))\n- docs: add section on debugging remappings [\\#2862](https://github.com/VSCodeVim/Vim/pull/2862) ([jpoon](https://github.com/jpoon))\n- Cache mode so that calls to setContext is minimized [\\#2861](https://github.com/VSCodeVim/Vim/pull/2861) ([xconverge](https://github.com/xconverge))\n- Workaround surround bug [\\#2830](https://github.com/VSCodeVim/Vim/pull/2830) ([reujab](https://github.com/reujab))\n- Add unit test for long user configured chords. [\\#2736](https://github.com/VSCodeVim/Vim/pull/2736) ([regiontog](https://github.com/regiontog))\n\n## [v0.15.3](https://github.com/vscodevim/vim/tree/v0.15.3) (2018-07-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.2...v0.15.3)\n\n**Fixed Bugs:**\n\n- :\\$ requires additional enter to go to end of buffer [\\#2858](https://github.com/VSCodeVim/Vim/issues/2858)\n\n**Merged pull requests:**\n\n- Fixes \\$ and % commands [\\#2860](https://github.com/VSCodeVim/Vim/pull/2860) ([xconverge](https://github.com/xconverge))\n- fixed buggy interactive substitute replacements [\\#2857](https://github.com/VSCodeVim/Vim/pull/2857) ([kevintighe](https://github.com/kevintighe))\n\n## [v0.15.2](https://github.com/vscodevim/vim/tree/v0.15.2) (2018-07-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.1...v0.15.2)\n\n**Fixed Bugs:**\n\n- Change surround tag with tag including a dot [\\#2850](https://github.com/VSCodeVim/Vim/issues/2850)\n- Delete using \\('d' + 'number' + '+/-'\\) \\(e.g. d5+\\) doesn't work like expected. [\\#2846](https://github.com/VSCodeVim/Vim/issues/2846)\n\n**Merged pull requests:**\n\n- fixes \\#2850 [\\#2856](https://github.com/VSCodeVim/Vim/pull/2856) ([xconverge](https://github.com/xconverge))\n- fix: don't run test when launching through vscode [\\#2854](https://github.com/VSCodeVim/Vim/pull/2854) ([jpoon](https://github.com/jpoon))\n- v0.15.1 [\\#2853](https://github.com/VSCodeVim/Vim/pull/2853) ([jpoon](https://github.com/jpoon))\n- Interactive Substitute [\\#2851](https://github.com/VSCodeVim/Vim/pull/2851) ([kevintighe](https://github.com/kevintighe))\n\n## [v0.15.1](https://github.com/vscodevim/vim/tree/v0.15.1) (2018-07-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.15.0...v0.15.1)\n\n**Enhancements:**\n\n- Option case-insensitive for vim-sneak [\\#2829](https://github.com/VSCodeVim/Vim/issues/2829)\n- \"x\" operation far too cpu-hungry [\\#1581](https://github.com/VSCodeVim/Vim/issues/1581)\n\n**Fixed Bugs:**\n\n- ctrl+v no longer pastes in insert mode [\\#2646](https://github.com/VSCodeVim/Vim/issues/2646)\n\n**Merged pull requests:**\n\n- fix: upgrade winston to 3.0 [\\#2852](https://github.com/VSCodeVim/Vim/pull/2852) ([jpoon](https://github.com/jpoon))\n- update tslint and fix radix linting [\\#2849](https://github.com/VSCodeVim/Vim/pull/2849) ([xconverge](https://github.com/xconverge))\n- Update dependency @types/mocha to v5.2.5 [\\#2847](https://github.com/VSCodeVim/Vim/pull/2847) ([renovate-bot](https://github.com/renovate-bot))\n- gulp release [\\#2841](https://github.com/VSCodeVim/Vim/pull/2841) ([jpoon](https://github.com/jpoon))\n- Update dependency @types/lodash to v4.14.112 [\\#2839](https://github.com/VSCodeVim/Vim/pull/2839) ([renovate-bot](https://github.com/renovate-bot))\n- Add config option for sneak to use smartcase and ignorecase [\\#2837](https://github.com/VSCodeVim/Vim/pull/2837) ([xconverge](https://github.com/xconverge))\n\n## [v0.15.0](https://github.com/vscodevim/vim/tree/v0.15.0) (2018-07-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.14.2...v0.15.0)\n\n**Enhancements:**\n\n- TypeError shown on invalid search command. [\\#2823](https://github.com/VSCodeVim/Vim/issues/2823)\n- Allow registering keybindings commands using strings [\\#2806](https://github.com/VSCodeVim/Vim/issues/2806)\n\n**Fixed Bugs:**\n\n- Keybindings not triggering [\\#2833](https://github.com/VSCodeVim/Vim/issues/2833)\n- Macro doesn't memoryize `delete` key. [\\#2702](https://github.com/VSCodeVim/Vim/issues/2702)\n- VimError's does not show up on the status bar [\\#2525](https://github.com/VSCodeVim/Vim/issues/2525)\n\n**Merged pull requests:**\n\n- Add \"cursor\" to commandline entry [\\#2836](https://github.com/VSCodeVim/Vim/pull/2836) ([xconverge](https://github.com/xconverge))\n- Update issue templates [\\#2825](https://github.com/VSCodeVim/Vim/pull/2825) ([jpoon](https://github.com/jpoon))\n- Cache the mode for updating status bar colors [\\#2822](https://github.com/VSCodeVim/Vim/pull/2822) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.111 [\\#2821](https://github.com/VSCodeVim/Vim/pull/2821) ([renovate-bot](https://github.com/renovate-bot))\n- Fix quickpick commandline [\\#2816](https://github.com/VSCodeVim/Vim/pull/2816) ([xconverge](https://github.com/xconverge))\n- Added ability to register commands using simple strings \\(fixes \\#2806\\) [\\#2807](https://github.com/VSCodeVim/Vim/pull/2807) ([6A](https://github.com/6A))\n\n## [v0.14.2](https://github.com/vscodevim/vim/tree/v0.14.2) (2018-07-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.14.1...v0.14.2)\n\n**Enhancements:**\n\n- \\<C-u\\> doesn't behave as expected in insert mode [\\#2804](https://github.com/VSCodeVim/Vim/issues/2804)\n- \\(feature\\) Add an option to bring commandline back to old place [\\#2773](https://github.com/VSCodeVim/Vim/issues/2773)\n\n**Fixed Bugs:**\n\n- 2gt not goes to the right tab [\\#2789](https://github.com/VSCodeVim/Vim/issues/2789)\n- Repeating a VISUAL LINE indentation is inconsistent with native vim behaviour [\\#2606](https://github.com/VSCodeVim/Vim/issues/2606)\n- ngt/ngT for tab switching is broken [\\#2580](https://github.com/VSCodeVim/Vim/issues/2580)\n\n**Closed issues:**\n\n- editor.cursorStyle not being respected [\\#2809](https://github.com/VSCodeVim/Vim/issues/2809)\n\n**Merged pull requests:**\n\n- Make gt work correctly like gT [\\#2812](https://github.com/VSCodeVim/Vim/pull/2812) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/node to v9.6.23 [\\#2811](https://github.com/VSCodeVim/Vim/pull/2811) ([renovate-bot](https://github.com/renovate-bot))\n- feat: Update \\<C-u\\> insert mode behavior [\\#2805](https://github.com/VSCodeVim/Vim/pull/2805) ([mrwest808](https://github.com/mrwest808))\n- bump version [\\#2797](https://github.com/VSCodeVim/Vim/pull/2797) ([jpoon](https://github.com/jpoon))\n- fixes \\#2606 [\\#2790](https://github.com/VSCodeVim/Vim/pull/2790) ([xconverge](https://github.com/xconverge))\n- Allow for quickpick commandline usage [\\#2781](https://github.com/VSCodeVim/Vim/pull/2781) ([xconverge](https://github.com/xconverge))\n\n## [v0.14.1](https://github.com/vscodevim/vim/tree/v0.14.1) (2018-06-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.14.0...v0.14.1)\n\n**Fixed Bugs:**\n\n- Remapping \\> to editor.fold [\\#2774](https://github.com/VSCodeVim/Vim/issues/2774)\n- Bug: Remapping Numbers \\(0-9\\) [\\#2759](https://github.com/VSCodeVim/Vim/issues/2759)\n- At a certain point VSCodeVim \"forgets\" all remappings for every new tab opened [\\#2271](https://github.com/VSCodeVim/Vim/issues/2271)\n\n**Closed issues:**\n\n- 0.14.0 doesn't work on Fedora 28, but 0.13.1 works. [\\#2780](https://github.com/VSCodeVim/Vim/issues/2780)\n- \\[neovim\\] Inconsistent behaviour when clicking files in the file tree [\\#2770](https://github.com/VSCodeVim/Vim/issues/2770)\n\n**Merged pull requests:**\n\n- doc: emojify readme [\\#2796](https://github.com/VSCodeVim/Vim/pull/2796) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/mocha to v5.2.4 [\\#2795](https://github.com/VSCodeVim/Vim/pull/2795) ([renovate-bot](https://github.com/renovate-bot))\n- fix: enable remapping of numbers [\\#2793](https://github.com/VSCodeVim/Vim/pull/2793) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency prettier to v1.13.7 [\\#2786](https://github.com/VSCodeVim/Vim/pull/2786) ([renovate-bot](https://github.com/renovate-bot))\n- refactor: simplify normalizekey\\(\\) by using existing map [\\#2782](https://github.com/VSCodeVim/Vim/pull/2782) ([jpoon](https://github.com/jpoon))\n- fix: fixes bug where null arguments to vscode executecommand would fail [\\#2776](https://github.com/VSCodeVim/Vim/pull/2776) ([jpoon](https://github.com/jpoon))\n\n## [v0.14.0](https://github.com/vscodevim/vim/tree/v0.14.0) (2018-06-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.13.1...v0.14.0)\n\n**Fixed Bugs:**\n\n- Surround aliases not working as targets [\\#2769](https://github.com/VSCodeVim/Vim/issues/2769)\n- Ctrl+D stuck on top of the window on visual mode [\\#2766](https://github.com/VSCodeVim/Vim/issues/2766)\n- Cut two characters but only paste one. [\\#2760](https://github.com/VSCodeVim/Vim/issues/2760)\n- Paste with CTRL+V while in edit mode does not work [\\#2706](https://github.com/VSCodeVim/Vim/issues/2706)\n- Can't bind leader key shortcuts to some vscode methods [\\#2674](https://github.com/VSCodeVim/Vim/issues/2674)\n- Searching forward / backward ignores count [\\#2664](https://github.com/VSCodeVim/Vim/issues/2664)\n\n**Closed issues:**\n\n- Yanking/deleting multiline into default register then pasting over other multiline text copies that overwritten multiline text, instead of retaining original yanked text. [\\#2717](https://github.com/VSCodeVim/Vim/issues/2717)\n- \"S\" \\(capital s\\) does not behave properly when on prefixing whitespace [\\#2240](https://github.com/VSCodeVim/Vim/issues/2240)\n- Bug: Can't navigate in autocompletion with \"Ctrl+j\" and \"Ctrl+k\". [\\#1980](https://github.com/VSCodeVim/Vim/issues/1980)\n- Backwards delete using \"X\" doesn't allow count prefixes [\\#1780](https://github.com/VSCodeVim/Vim/issues/1780)\n\n**Merged pull requests:**\n\n- fixes \\#2769 [\\#2772](https://github.com/VSCodeVim/Vim/pull/2772) ([xconverge](https://github.com/xconverge))\n- Fix \\#2766. [\\#2771](https://github.com/VSCodeVim/Vim/pull/2771) ([rebornix](https://github.com/rebornix))\n- Update dependency prettier to v1.13.6 [\\#2768](https://github.com/VSCodeVim/Vim/pull/2768) ([renovate-bot](https://github.com/renovate-bot))\n- fixes \\#2766 [\\#2767](https://github.com/VSCodeVim/Vim/pull/2767) ([xconverge](https://github.com/xconverge))\n- fixes \\#1980 [\\#2765](https://github.com/VSCodeVim/Vim/pull/2765) ([xconverge](https://github.com/xconverge))\n- Fixes \\#1780 [\\#2764](https://github.com/VSCodeVim/Vim/pull/2764) ([xconverge](https://github.com/xconverge))\n- fixes \\#2664 and removes unused variable [\\#2763](https://github.com/VSCodeVim/Vim/pull/2763) ([xconverge](https://github.com/xconverge))\n- fixes \\#2706 [\\#2762](https://github.com/VSCodeVim/Vim/pull/2762) ([xconverge](https://github.com/xconverge))\n- fixes \\#2760 [\\#2761](https://github.com/VSCodeVim/Vim/pull/2761) ([xconverge](https://github.com/xconverge))\n- Move commandline to status bar to allow history navigation [\\#2758](https://github.com/VSCodeVim/Vim/pull/2758) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/mocha to v5.2.3 [\\#2757](https://github.com/VSCodeVim/Vim/pull/2757) ([renovate-bot](https://github.com/renovate-bot))\n- v0.13.1 [\\#2753](https://github.com/VSCodeVim/Vim/pull/2753) ([jpoon](https://github.com/jpoon))\n\n## [v0.13.1](https://github.com/vscodevim/vim/tree/v0.13.1) (2018-06-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.13.0...v0.13.1)\n\n**Closed issues:**\n\n- Remapping ESC in insert mode with CR or Space does work via settings [\\#2584](https://github.com/VSCodeVim/Vim/issues/2584)\n\n**Merged pull requests:**\n\n- fix: closes \\#1472. insertModeKeyBindings apply to insert and replace modes [\\#2749](https://github.com/VSCodeVim/Vim/pull/2749) ([jpoon](https://github.com/jpoon))\n- fix: closes \\#2390. enables remapping using '\\<enter\\>' [\\#2748](https://github.com/VSCodeVim/Vim/pull/2748) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.110 [\\#2745](https://github.com/VSCodeVim/Vim/pull/2745) ([renovate-bot](https://github.com/renovate-bot))\n- Update visualModeKeyBindingsNonRecursive example [\\#2744](https://github.com/VSCodeVim/Vim/pull/2744) ([chibicode](https://github.com/chibicode))\n- Fix \\#1348. ctrl+D/U correct position [\\#2723](https://github.com/VSCodeVim/Vim/pull/2723) ([rebornix](https://github.com/rebornix))\n\n## [v0.13.0](https://github.com/vscodevim/vim/tree/v0.13.0) (2018-06-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.12.0...v0.13.0)\n\n**Breaking changes:**\n\n- Add normalModeKeyBindings and visualModeKeyBindings, remove otherModesKeyBindings [\\#2726](https://github.com/VSCodeVim/Vim/pull/2726) ([chibicode](https://github.com/chibicode))\n\n**Enhancements:**\n\n- Allow remappings from mocked configurations during testing. [\\#2732](https://github.com/VSCodeVim/Vim/issues/2732)\n- use vscode task api [\\#2731](https://github.com/VSCodeVim/Vim/issues/2731)\n- Add visualModeKeyBindings, in addition to otherModesKeyBindings [\\#2705](https://github.com/VSCodeVim/Vim/issues/2705)\n- \\[FEATURE REQUEST\\] \"q:\" command [\\#2617](https://github.com/VSCodeVim/Vim/issues/2617)\n- How to make a keybinding only work in visual mode? [\\#1805](https://github.com/VSCodeVim/Vim/issues/1805)\n- Allow simplified keybinding syntax in settings.json [\\#1667](https://github.com/VSCodeVim/Vim/issues/1667)\n\n**Fixed Bugs:**\n\n- gf creates files when the given file does not exist [\\#2683](https://github.com/VSCodeVim/Vim/issues/2683)\n- Change/Delete/Yank combined with next unmatched bracket/parenthesis not behaving correctly [\\#2670](https://github.com/VSCodeVim/Vim/issues/2670)\n- \\[Bug report\\]: 'c' key in multi-cursor mode removes additional cursors [\\#2668](https://github.com/VSCodeVim/Vim/issues/2668)\n\n**Closed issues:**\n\n- Keybindings with Alt modifier. [\\#2713](https://github.com/VSCodeVim/Vim/issues/2713)\n- Commands cc and S do not respect indent level if executed before the first character [\\#2497](https://github.com/VSCodeVim/Vim/issues/2497)\n- Toggling Vim Mode using keybindings is broken [\\#2381](https://github.com/VSCodeVim/Vim/issues/2381)\n- Searching finds nothing when pasting from cmd [\\#2362](https://github.com/VSCodeVim/Vim/issues/2362)\n- Evil mode [\\#2328](https://github.com/VSCodeVim/Vim/issues/2328)\n- different key bindings for normal and visual mode [\\#2205](https://github.com/VSCodeVim/Vim/issues/2205)\n- need support for alt+x key mapping [\\#2061](https://github.com/VSCodeVim/Vim/issues/2061)\n- Keybindings with space don't seem to work [\\#2039](https://github.com/VSCodeVim/Vim/issues/2039)\n- \\[Not Sure\\] Copy using Windows Clipboard looses CR/LF [\\#2022](https://github.com/VSCodeVim/Vim/issues/2022)\n- \"TypeError: Cannot read property 'isEqual' of undefined\" while debugging an extension with vim enabled [\\#2019](https://github.com/VSCodeVim/Vim/issues/2019)\n- :m command doesn't work [\\#2010](https://github.com/VSCodeVim/Vim/issues/2010)\n- pane switching is broken in newest vscode-insiders [\\#1973](https://github.com/VSCodeVim/Vim/issues/1973)\n- \\[Bug\\] Copy text destroys special characters [\\#1825](https://github.com/VSCodeVim/Vim/issues/1825)\n\n**Merged pull requests:**\n\n- fix: handle when commandLineHistory is empty [\\#2741](https://github.com/VSCodeVim/Vim/pull/2741) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.22 [\\#2739](https://github.com/VSCodeVim/Vim/pull/2739) ([renovate-bot](https://github.com/renovate-bot))\n- fix: use explicit configuration for logginglevel [\\#2738](https://github.com/VSCodeVim/Vim/pull/2738) ([jpoon](https://github.com/jpoon))\n- fix: remove duplicate UT [\\#2734](https://github.com/VSCodeVim/Vim/pull/2734) ([jpoon](https://github.com/jpoon))\n- Don't ignore mocked configurations' remaps during testing. [\\#2733](https://github.com/VSCodeVim/Vim/pull/2733) ([regiontog](https://github.com/regiontog))\n- chore\\(deps\\): update dependency typescript to v2.9.2 [\\#2730](https://github.com/VSCodeVim/Vim/pull/2730) ([renovate-bot](https://github.com/renovate-bot))\n- Fix autoindent on cc/S \\#2497 [\\#2729](https://github.com/VSCodeVim/Vim/pull/2729) ([dqsully](https://github.com/dqsully))\n- chore\\(deps\\): update dependency @types/mocha to v5.2.2 [\\#2724](https://github.com/VSCodeVim/Vim/pull/2724) ([renovate-bot](https://github.com/renovate-bot))\n- fix: revert our workaround cursor toggle as this has been fixed in vscode [\\#2720](https://github.com/VSCodeVim/Vim/pull/2720) ([jpoon](https://github.com/jpoon))\n- feat: use winston for logging [\\#2719](https://github.com/VSCodeVim/Vim/pull/2719) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency prettier to v1.13.5 [\\#2718](https://github.com/VSCodeVim/Vim/pull/2718) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.21 [\\#2715](https://github.com/VSCodeVim/Vim/pull/2715) ([renovate-bot](https://github.com/renovate-bot))\n- Update prettier dependency [\\#2712](https://github.com/VSCodeVim/Vim/pull/2712) ([xconverge](https://github.com/xconverge))\n- chore\\(deps\\): update dependency @types/mocha to v5.2.1 [\\#2704](https://github.com/VSCodeVim/Vim/pull/2704) ([renovate-bot](https://github.com/renovate-bot))\n- fix gf to be like issue \\#2683 [\\#2701](https://github.com/VSCodeVim/Vim/pull/2701) ([SuyogSoti](https://github.com/SuyogSoti))\n- chore\\(deps\\): update dependency typescript to v2.9.1 [\\#2698](https://github.com/VSCodeVim/Vim/pull/2698) ([renovate-bot](https://github.com/renovate-bot))\n- Fix vim-commentary description in README [\\#2694](https://github.com/VSCodeVim/Vim/pull/2694) ([Ran4](https://github.com/Ran4))\n- chore\\(deps\\): update dependency @types/node to v9.6.20 [\\#2691](https://github.com/VSCodeVim/Vim/pull/2691) ([renovate-bot](https://github.com/renovate-bot))\n- fix: fix 'no-use-before-declare' requires type information lint warning [\\#2679](https://github.com/VSCodeVim/Vim/pull/2679) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency gulp-git to v2.7.0 [\\#2678](https://github.com/VSCodeVim/Vim/pull/2678) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency vscode to v1.1.18 [\\#2676](https://github.com/VSCodeVim/Vim/pull/2676) ([renovate-bot](https://github.com/renovate-bot))\n- Fixed difference in behavior for \\]\\) and \\]} when combined with certain operators [\\#2671](https://github.com/VSCodeVim/Vim/pull/2671) ([willcassella](https://github.com/willcassella))\n- fix\\(deps\\): update dependency untildify to v3.0.3 [\\#2669](https://github.com/VSCodeVim/Vim/pull/2669) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency mocha to v5.2.0 [\\#2666](https://github.com/VSCodeVim/Vim/pull/2666) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.16 [\\#2644](https://github.com/VSCodeVim/Vim/pull/2644) ([renovate-bot](https://github.com/renovate-bot))\n\n## [v0.12.0](https://github.com/vscodevim/vim/tree/v0.12.0) (2018-05-16)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.6...v0.12.0)\n\n- Fix development problems on win [\\#2651](https://github.com/VSCodeVim/Vim/pull/2651) ([KamikazeZirou](https://github.com/KamikazeZirou))\n- Fixes \\#2632 [\\#2641](https://github.com/VSCodeVim/Vim/pull/2641) ([xconverge](https://github.com/xconverge))\n- Revert \"\\[Fix\\] Restore 'when' conditions in \\<C-v\\>, \\<C-j\\>, \\<C-k\\>\" [\\#2640](https://github.com/VSCodeVim/Vim/pull/2640) ([jpoon](https://github.com/jpoon))\n- fix\\(deps\\): update dependency diff-match-patch to v1.0.1 [\\#2631](https://github.com/VSCodeVim/Vim/pull/2631) ([renovate-bot](https://github.com/renovate-bot))\n- Update dependency @types/node to v9.6.14 [\\#2630](https://github.com/VSCodeVim/Vim/pull/2630) ([renovate-bot](https://github.com/renovate-bot))\n- \\[Fix\\] Restore 'when' conditions in \\<C-v\\>, \\<C-j\\>, \\<C-k\\> [\\#2628](https://github.com/VSCodeVim/Vim/pull/2628) ([tyru](https://github.com/tyru))\n- Link to Linux setup [\\#2627](https://github.com/VSCodeVim/Vim/pull/2627) ([gggauravgandhi](https://github.com/gggauravgandhi))\n- fix: immediately exit travis on build error [\\#2626](https://github.com/VSCodeVim/Vim/pull/2626) ([jpoon](https://github.com/jpoon))\n- fix: immediately exit if there is an error on ts [\\#2625](https://github.com/VSCodeVim/Vim/pull/2625) ([jpoon](https://github.com/jpoon))\n- feat: log to outputChannel [\\#2623](https://github.com/VSCodeVim/Vim/pull/2623) ([jpoon](https://github.com/jpoon))\n- Implement \"q:\" command [\\#2618](https://github.com/VSCodeVim/Vim/pull/2618) ([KamikazeZirou](https://github.com/KamikazeZirou))\n\n## [v0.11.6](https://github.com/vscodevim/vim/tree/v0.11.6) (2018-05-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.5...v0.11.6)\n\n- chore\\(deps\\): update dependency @types/node to v9.6.12 [\\#2615](https://github.com/VSCodeVim/Vim/pull/2615) ([renovate-bot](https://github.com/renovate-bot))\n- \\[Fix\\] \\* command highlights extra content [\\#2611](https://github.com/VSCodeVim/Vim/pull/2611) ([tyru](https://github.com/tyru))\n- \\[Fix\\] p in visual line appends unnecessary newline [\\#2609](https://github.com/VSCodeVim/Vim/pull/2609) ([tyru](https://github.com/tyru))\n- chore\\(deps\\): update dependency tslint to v5.10.0 [\\#2605](https://github.com/VSCodeVim/Vim/pull/2605) ([renovate-bot](https://github.com/renovate-bot))\n- Add o command in visual block mode [\\#2604](https://github.com/VSCodeVim/Vim/pull/2604) ([tyru](https://github.com/tyru))\n- \\[Fix\\] p in visual-mode should update register content [\\#2602](https://github.com/VSCodeVim/Vim/pull/2602) ([tyru](https://github.com/tyru))\n- \\[Fix\\] p won't work in linewise visual-mode at the end of document [\\#2601](https://github.com/VSCodeVim/Vim/pull/2601) ([tyru](https://github.com/tyru))\n- Add missing window keys \\(\\<C-w\\>\\<C-\\[hjklovq\\]\\>\\) [\\#2600](https://github.com/VSCodeVim/Vim/pull/2600) ([tyru](https://github.com/tyru))\n- fix: fail on ts transpile errors by setting noEmitOnErrors [\\#2599](https://github.com/VSCodeVim/Vim/pull/2599) ([jpoon](https://github.com/jpoon))\n- add easymotion-lineforward and easymotion-linebackward [\\#2596](https://github.com/VSCodeVim/Vim/pull/2596) ([hy950831](https://github.com/hy950831))\n- Fix description in 🔢 % command [\\#2595](https://github.com/VSCodeVim/Vim/pull/2595) ([Ding-Fan](https://github.com/Ding-Fan))\n- \\[Fix\\] \\<C-h\\> should work as same as \\<BS\\> in search mode [\\#2593](https://github.com/VSCodeVim/Vim/pull/2593) ([tyru](https://github.com/tyru))\n- \\[Fix\\] aW doesn't work at the end of lines [\\#2591](https://github.com/VSCodeVim/Vim/pull/2591) ([tyru](https://github.com/tyru))\n- Implement gn,gN command [\\#2589](https://github.com/VSCodeVim/Vim/pull/2589) ([tyru](https://github.com/tyru))\n- \\[Fix\\] p in visual-mode should save last selection [\\#2588](https://github.com/VSCodeVim/Vim/pull/2588) ([tyru](https://github.com/tyru))\n- \\[Fix\\] Transition between v,V,\\<C-v\\> is different with original Vim behavior [\\#2581](https://github.com/VSCodeVim/Vim/pull/2581) ([tyru](https://github.com/tyru))\n- \\[Fix\\] Don't add beginning newline of linewise put in visual-mode [\\#2579](https://github.com/VSCodeVim/Vim/pull/2579) ([tyru](https://github.com/tyru))\n- fix: Manually dispose ModeHandler when no longer needed [\\#2577](https://github.com/VSCodeVim/Vim/pull/2577) ([BinaryKhaos](https://github.com/BinaryKhaos))\n- chore\\(deps\\): update dependency vscode to v1.1.16 [\\#2575](https://github.com/VSCodeVim/Vim/pull/2575) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.7 [\\#2573](https://github.com/VSCodeVim/Vim/pull/2573) ([renovate-bot](https://github.com/renovate-bot))\n- Fixes \\#2569. Fix vi{ for nested braces. [\\#2572](https://github.com/VSCodeVim/Vim/pull/2572) ([Shadaraman](https://github.com/Shadaraman))\n- Fixed neovim spawning in invalid directories [\\#2570](https://github.com/VSCodeVim/Vim/pull/2570) ([Chillee](https://github.com/Chillee))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.108 [\\#2565](https://github.com/VSCodeVim/Vim/pull/2565) ([renovate-bot](https://github.com/renovate-bot))\n- Hopefully fixing the rest of our undo issues [\\#2559](https://github.com/VSCodeVim/Vim/pull/2559) ([Chillee](https://github.com/Chillee))\n\n## [v0.11.5](https://github.com/vscodevim/vim/tree/v0.11.5) (2018-04-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.4...v0.11.5)\n\n- chore\\(deps\\): update dependency gulp-bump to v3.1.1 [\\#2556](https://github.com/VSCodeVim/Vim/pull/2556) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency typescript to v2.8.3 [\\#2553](https://github.com/VSCodeVim/Vim/pull/2553) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/node to v9.6.6 [\\#2551](https://github.com/VSCodeVim/Vim/pull/2551) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/mocha to v5.2.0 [\\#2550](https://github.com/VSCodeVim/Vim/pull/2550) ([renovate-bot](https://github.com/renovate-bot))\n- Fixed undo issue given in \\#2545 [\\#2547](https://github.com/VSCodeVim/Vim/pull/2547) ([Chillee](https://github.com/Chillee))\n- chore\\(deps\\): update dependency mocha to v5.1.1 [\\#2546](https://github.com/VSCodeVim/Vim/pull/2546) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency prettier to v1.12.1 [\\#2543](https://github.com/VSCodeVim/Vim/pull/2543) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.107 [\\#2540](https://github.com/VSCodeVim/Vim/pull/2540) ([renovate-bot](https://github.com/renovate-bot))\n\n## [v0.11.4](https://github.com/vscodevim/vim/tree/v0.11.4) (2018-04-14)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.3...v0.11.4)\n\n- fix: don't call prettier when no files updated [\\#2539](https://github.com/VSCodeVim/Vim/pull/2539) ([jpoon](https://github.com/jpoon))\n- chore\\(dep\\): upgrade gulp-bump, gulp-git, gulp-typescript, prettier, typescript, vscode [\\#2538](https://github.com/VSCodeVim/Vim/pull/2538) ([jpoon](https://github.com/jpoon))\n- chore\\(deps\\): update dependency @types/node to v9.6.5 [\\#2535](https://github.com/VSCodeVim/Vim/pull/2535) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency mocha to v5.1.0 [\\#2534](https://github.com/VSCodeVim/Vim/pull/2534) ([renovate-bot](https://github.com/renovate-bot))\n- docs: update readme to indicate restart of vscode needed [\\#2530](https://github.com/VSCodeVim/Vim/pull/2530) ([jdhines](https://github.com/jdhines))\n- chore\\(deps\\): update dependency @types/node to v9.6.4 [\\#2528](https://github.com/VSCodeVim/Vim/pull/2528) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/diff to v3.5.1 [\\#2527](https://github.com/VSCodeVim/Vim/pull/2527) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency @types/diff to v3.5.0 [\\#2523](https://github.com/VSCodeVim/Vim/pull/2523) ([renovate-bot](https://github.com/renovate-bot))\n- bug: Neovim not spawned in appropriate directory \\(fixes \\#2482\\) [\\#2522](https://github.com/VSCodeVim/Vim/pull/2522) ([Chillee](https://github.com/Chillee))\n- bug: fixes behaviour of search when using \\* and \\# \\(fixes \\#2517\\) [\\#2518](https://github.com/VSCodeVim/Vim/pull/2518) ([clamb](https://github.com/clamb))\n- chore\\(deps\\): update dependency @types/node to v9.6.2 [\\#2509](https://github.com/VSCodeVim/Vim/pull/2509) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update node docker tag to v8.11 [\\#2496](https://github.com/VSCodeVim/Vim/pull/2496) ([renovate-bot](https://github.com/renovate-bot))\n- chore\\(deps\\): update dependency mocha to v5.0.5 [\\#2490](https://github.com/VSCodeVim/Vim/pull/2490) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency gulp-tslint to v8.1.3 [\\#2489](https://github.com/VSCodeVim/Vim/pull/2489) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): update dependency @types/lodash to v4.14.106 [\\#2485](https://github.com/VSCodeVim/Vim/pull/2485) ([renovate[bot]](https://github.com/apps/renovate))\n- chore\\(deps\\): pin dependencies [\\#2483](https://github.com/VSCodeVim/Vim/pull/2483) ([renovate[bot]](https://github.com/apps/renovate))\n- Configure Renovate [\\#2480](https://github.com/VSCodeVim/Vim/pull/2480) ([renovate[bot]](https://github.com/apps/renovate))\n- Add jumptoanywhere command for easymotion [\\#2454](https://github.com/VSCodeVim/Vim/pull/2454) ([jsonMartin](https://github.com/jsonMartin))\n\n## [v0.11.3](https://github.com/vscodevim/vim/tree/v0.11.3) (2018-03-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.2...v0.11.3)\n\n- docs: add documentation for installing xsel. fixes \\#2071 [\\#2476](https://github.com/VSCodeVim/Vim/pull/2476) ([jpoon](https://github.com/jpoon))\n- Respect vim.visualstar configuration \\(fixes \\#2469\\) [\\#2470](https://github.com/VSCodeVim/Vim/pull/2470) ([ytang](https://github.com/ytang))\n- feat: Added \\<C-w\\>= keybind [\\#2453](https://github.com/VSCodeVim/Vim/pull/2453) ([844196](https://github.com/844196))\n- neovim.ts: typo in log [\\#2451](https://github.com/VSCodeVim/Vim/pull/2451) ([prakashdanish](https://github.com/prakashdanish))\n- await openEditorAtIndex1 command [\\#2442](https://github.com/VSCodeVim/Vim/pull/2442) ([arussellk](https://github.com/arussellk))\n\n## [v0.11.2](https://github.com/vscodevim/vim/tree/v0.11.2) (2018-03-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.1...v0.11.2)\n\n- Readds vimState.lastClickWasPastEOL. Fixes \\#2404 [\\#2433](https://github.com/VSCodeVim/Vim/pull/2433) ([Chillee](https://github.com/Chillee))\n- fix: selection in search in visual mode \\#2406 [\\#2418](https://github.com/VSCodeVim/Vim/pull/2418) ([shortheron](https://github.com/shortheron))\n\n## [v0.11.1](https://github.com/vscodevim/vim/tree/v0.11.1) (2018-03-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.11.0...v0.11.1)\n\n- Set the timeout to 0 for waitforcursorupdatestopropagate [\\#2428](https://github.com/VSCodeVim/Vim/pull/2428) ([Chillee](https://github.com/Chillee))\n- fix: use 'fsPath'. closes \\#2422 [\\#2426](https://github.com/VSCodeVim/Vim/pull/2426) ([jpoon](https://github.com/jpoon))\n- fix: don't overwrite file if file exists. fixes \\#2408 [\\#2409](https://github.com/VSCodeVim/Vim/pull/2409) ([jpoon](https://github.com/jpoon))\n- Fix :tabm to use moveActiveEditor command [\\#2405](https://github.com/VSCodeVim/Vim/pull/2405) ([arussellk](https://github.com/arussellk))\n\n## [v0.11.0](https://github.com/vscodevim/vim/tree/v0.11.0) (2018-02-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.13...v0.11.0)\n\n- Fix :tabe {file} only relative to current file \\(\\#1162\\) [\\#2400](https://github.com/VSCodeVim/Vim/pull/2400) ([arussellk](https://github.com/arussellk))\n- fix: clean-up neovim processes. closes \\#2038 [\\#2395](https://github.com/VSCodeVim/Vim/pull/2395) ([jpoon](https://github.com/jpoon))\n- refactor: no need to set current mode twice [\\#2394](https://github.com/VSCodeVim/Vim/pull/2394) ([jpoon](https://github.com/jpoon))\n- feat: create file if file does not exist. closes \\#2274 [\\#2392](https://github.com/VSCodeVim/Vim/pull/2392) ([jpoon](https://github.com/jpoon))\n- fix: status bar when configuration.showcmd is set \\(fixes \\#2365\\) [\\#2386](https://github.com/VSCodeVim/Vim/pull/2386) ([jpoon](https://github.com/jpoon))\n- `jj` cursor position fix for \\#1418 [\\#2366](https://github.com/VSCodeVim/Vim/pull/2366) ([prog666](https://github.com/prog666))\n- fix: actually run prettier [\\#2359](https://github.com/VSCodeVim/Vim/pull/2359) ([jpoon](https://github.com/jpoon))\n- feat: implements usage of `insert` to toggle between modes \\(as per \\#1787\\) [\\#2356](https://github.com/VSCodeVim/Vim/pull/2356) ([jpoon](https://github.com/jpoon))\n- Build Improvements [\\#2351](https://github.com/VSCodeVim/Vim/pull/2351) ([jpoon](https://github.com/jpoon))\n- Possibility to set statusBar foreground color [\\#2350](https://github.com/VSCodeVim/Vim/pull/2350) ([mgor](https://github.com/mgor))\n- Fixes \\#2346 [\\#2347](https://github.com/VSCodeVim/Vim/pull/2347) ([Chillee](https://github.com/Chillee))\n- Improve Test Infrastructure [\\#2335](https://github.com/VSCodeVim/Vim/pull/2335) ([jpoon](https://github.com/jpoon))\n- fix typo in README [\\#2327](https://github.com/VSCodeVim/Vim/pull/2327) ([hayley](https://github.com/hayley))\n- Sneak plugin [\\#2307](https://github.com/VSCodeVim/Vim/pull/2307) ([jpotterm](https://github.com/jpotterm))\n\n## [v0.10.13](https://github.com/vscodevim/vim/tree/v0.10.13) (2018-01-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.12...v0.10.13)\n\n- fix: bad jason. fix bad release. [\\#2324](https://github.com/VSCodeVim/Vim/pull/2324) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.12](https://github.com/vscodevim/vim/tree/v0.10.12) (2018-01-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.11...v0.10.12)\n\n- fix: closes \\#730. setcontext when switching active text editors [\\#2320](https://github.com/VSCodeVim/Vim/pull/2320) ([jpoon](https://github.com/jpoon))\n- Update README for Mac key repeat [\\#2316](https://github.com/VSCodeVim/Vim/pull/2316) ([puradox](https://github.com/puradox))\n- Default to vim behaviour for Ctrl+D [\\#2314](https://github.com/VSCodeVim/Vim/pull/2314) ([Graham42](https://github.com/Graham42))\n- Left shift fix 2299 [\\#2300](https://github.com/VSCodeVim/Vim/pull/2300) ([jessewmc](https://github.com/jessewmc))\n\n## [v0.10.11](https://github.com/vscodevim/vim/tree/v0.10.11) (2018-01-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.10...v0.10.11)\n\n- fix: status bar not updating properly when recording macros. fixes \\#2296. [\\#2304](https://github.com/VSCodeVim/Vim/pull/2304) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.10](https://github.com/vscodevim/vim/tree/v0.10.10) (2018-01-16)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.9...v0.10.10)\n\n- fix: add tests for compareKeyPressSequence [\\#2289](https://github.com/VSCodeVim/Vim/pull/2289) ([jpoon](https://github.com/jpoon))\n- Fix BaseAction.couldActionApply to work with two-dimensional keys array [\\#2288](https://github.com/VSCodeVim/Vim/pull/2288) ([jpotterm](https://github.com/jpotterm))\n- refactor: move modehandlermap to own class [\\#2285](https://github.com/VSCodeVim/Vim/pull/2285) ([jpoon](https://github.com/jpoon))\n- fix: status bar not updating following toggle [\\#2283](https://github.com/VSCodeVim/Vim/pull/2283) ([jpoon](https://github.com/jpoon))\n- Fix: Warnings when retrieving configurations w/o resource [\\#2282](https://github.com/VSCodeVim/Vim/pull/2282) ([jpoon](https://github.com/jpoon))\n- fix: \\<C-d\\> remapping disabled by default. functionality controlled by \"handleKeys\" [\\#2269](https://github.com/VSCodeVim/Vim/pull/2269) ([Arxzin](https://github.com/Arxzin))\n\n## [v0.10.9](https://github.com/vscodevim/vim/tree/v0.10.9) (2018-01-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.8...v0.10.9)\n\n- feature: \"h\", \"l\" keybindings for sidebar [\\#2290](https://github.com/VSCodeVim/Vim/pull/2290) ([Nodman](https://github.com/Nodman))\n- fix: no need to change cursor if there is no active editor. closes \\#2273 [\\#2278](https://github.com/VSCodeVim/Vim/pull/2278) ([jpoon](https://github.com/jpoon))\n- fix: fixes circular dependency between notation and configuration [\\#2277](https://github.com/VSCodeVim/Vim/pull/2277) ([jpoon](https://github.com/jpoon))\n- fix: show cmd-line errors in status bar. add new E492 error [\\#2272](https://github.com/VSCodeVim/Vim/pull/2272) ([jpoon](https://github.com/jpoon))\n- refactor: normalize keys when loading configuration [\\#2268](https://github.com/VSCodeVim/Vim/pull/2268) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.8](https://github.com/vscodevim/vim/tree/v0.10.8) (2018-01-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.7...v0.10.8)\n\n- fix\\(2162\\): handleKeys was previously only handling negation [\\#2267](https://github.com/VSCodeVim/Vim/pull/2267) ([jpoon](https://github.com/jpoon))\n- fix\\(2264\\): go-to-line [\\#2266](https://github.com/VSCodeVim/Vim/pull/2266) ([jpoon](https://github.com/jpoon))\n- fix\\(2261\\): change status bar text for search-in-progress to be more l… [\\#2263](https://github.com/VSCodeVim/Vim/pull/2263) ([jpoon](https://github.com/jpoon))\n- fix\\(2261\\): fix regression. show search string in status bar [\\#2262](https://github.com/VSCodeVim/Vim/pull/2262) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.7](https://github.com/vscodevim/vim/tree/v0.10.7) (2018-01-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.6...v0.10.7)\n\n- Stop Silently Failing [\\#2250](https://github.com/VSCodeVim/Vim/pull/2250) ([jpoon](https://github.com/jpoon))\n- Misc Bug Fixes and Refactoring [\\#2243](https://github.com/VSCodeVim/Vim/pull/2243) ([jpoon](https://github.com/jpoon))\n- fix\\(2184\\): handle situation when no document is opened [\\#2237](https://github.com/VSCodeVim/Vim/pull/2237) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.6](https://github.com/vscodevim/vim/tree/v0.10.6) (2017-12-15)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.5...v0.10.6)\n\n- update\\(package.json\\) [\\#2225](https://github.com/VSCodeVim/Vim/pull/2225) ([jpoon](https://github.com/jpoon))\n- Add C-\\[ to Replace Mode escape [\\#2223](https://github.com/VSCodeVim/Vim/pull/2223) ([deybhayden](https://github.com/deybhayden))\n- Do not open open file dialog when calling `:e!` [\\#2215](https://github.com/VSCodeVim/Vim/pull/2215) ([squgeim](https://github.com/squgeim))\n- Update `list.\\*` command keybindings [\\#2213](https://github.com/VSCodeVim/Vim/pull/2213) ([joaomoreno](https://github.com/joaomoreno))\n- moar clean-up [\\#2208](https://github.com/VSCodeVim/Vim/pull/2208) ([jpoon](https://github.com/jpoon))\n- Fix cursor position of \\<C-o\\> command in insertmode [\\#2206](https://github.com/VSCodeVim/Vim/pull/2206) ([hy950831](https://github.com/hy950831))\n- refactor\\(modehandler-updateview\\): use map and remove unused context [\\#2197](https://github.com/VSCodeVim/Vim/pull/2197) ([jpoon](https://github.com/jpoon))\n- Integrate TravisBuddy [\\#2191](https://github.com/VSCodeVim/Vim/pull/2191) ([bluzi](https://github.com/bluzi))\n- Fix \\#2168: Surround offset [\\#2171](https://github.com/VSCodeVim/Vim/pull/2171) ([westim](https://github.com/westim))\n- Fix \\#1945 \\$ in VisualBlock works on ragged lines [\\#2096](https://github.com/VSCodeVim/Vim/pull/2096) ([Strafos](https://github.com/Strafos))\n\n## [v0.10.5](https://github.com/vscodevim/vim/tree/v0.10.5) (2017-11-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.4...v0.10.5)\n\n- Fixed incorrect styling of 'fake' cursors [\\#2161](https://github.com/VSCodeVim/Vim/pull/2161) ([Chillee](https://github.com/Chillee))\n- Fix \\#2155, Fix \\#2133: escape delimiter substitute [\\#2159](https://github.com/VSCodeVim/Vim/pull/2159) ([westim](https://github.com/westim))\n- Fix \\#2148: vertical split command [\\#2158](https://github.com/VSCodeVim/Vim/pull/2158) ([westim](https://github.com/westim))\n- fix\\(1673\\): re-enable some tests [\\#2152](https://github.com/VSCodeVim/Vim/pull/2152) ([jpoon](https://github.com/jpoon))\n- keep workbench color customizations when using status bar color [\\#2122](https://github.com/VSCodeVim/Vim/pull/2122) ([rodrigo-garcia-leon](https://github.com/rodrigo-garcia-leon))\n\n## [v0.10.4](https://github.com/vscodevim/vim/tree/v0.10.4) (2017-11-14)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.3...v0.10.4)\n\n- fix\\(2145\\): reverse logic [\\#2147](https://github.com/VSCodeVim/Vim/pull/2147) ([jpoon](https://github.com/jpoon))\n\n## [v0.10.3](https://github.com/vscodevim/vim/tree/v0.10.3) (2017-11-13)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.2...v0.10.3)\n\n- Fix release [\\#2142](https://github.com/VSCodeVim/Vim/pull/2142) ([jpoon](https://github.com/jpoon))\n- Code Cleanup [\\#2138](https://github.com/VSCodeVim/Vim/pull/2138) ([jpoon](https://github.com/jpoon))\n- Fixed typo in README [\\#2137](https://github.com/VSCodeVim/Vim/pull/2137) ([Nonoctis](https://github.com/Nonoctis))\n- fix\\(travis\\): use lts/carbon \\(v8.9.1\\) for travis [\\#2129](https://github.com/VSCodeVim/Vim/pull/2129) ([jpoon](https://github.com/jpoon))\n- Fix ^, \\$, add case sensitivity override in search [\\#2123](https://github.com/VSCodeVim/Vim/pull/2123) ([parkovski](https://github.com/parkovski))\n- fix vscode launch/tasks [\\#2121](https://github.com/VSCodeVim/Vim/pull/2121) ([jpoon](https://github.com/jpoon))\n- Fix remapping keys to actions with \"mustBeFirstKey\", fixes \\#2216 [\\#2117](https://github.com/VSCodeVim/Vim/pull/2117) ([ohjames](https://github.com/ohjames))\n- Fixes \\#2113: Start in Disabled mode configuration. [\\#2115](https://github.com/VSCodeVim/Vim/pull/2115) ([westim](https://github.com/westim))\n- fix\\(line-endings\\): change all files to lf [\\#2111](https://github.com/VSCodeVim/Vim/pull/2111) ([jpoon](https://github.com/jpoon))\n- fix\\(build\\): position does not exist for replacetexttransformation [\\#2105](https://github.com/VSCodeVim/Vim/pull/2105) ([jpoon](https://github.com/jpoon))\n- Use 'editor.unfold' with direction: 'down' [\\#2104](https://github.com/VSCodeVim/Vim/pull/2104) ([aeschli](https://github.com/aeschli))\n- Pesky penguin CHANGELOG.md update. [\\#2091](https://github.com/VSCodeVim/Vim/pull/2091) ([westim](https://github.com/westim))\n- Added unit tests for movement commands. [\\#2088](https://github.com/VSCodeVim/Vim/pull/2088) ([westim](https://github.com/westim))\n- Fix \\#2080 [\\#2087](https://github.com/VSCodeVim/Vim/pull/2087) ([Strafos](https://github.com/Strafos))\n- Update Contributors [\\#2083](https://github.com/VSCodeVim/Vim/pull/2083) ([mcsosa121](https://github.com/mcsosa121))\n- Fixes \\#1974: U command [\\#2081](https://github.com/VSCodeVim/Vim/pull/2081) ([westim](https://github.com/westim))\n- Fix \\#2063 [\\#2079](https://github.com/VSCodeVim/Vim/pull/2079) ([Strafos](https://github.com/Strafos))\n- Fix \\#1852 surround issue at end of line [\\#2077](https://github.com/VSCodeVim/Vim/pull/2077) ([Strafos](https://github.com/Strafos))\n- added `showOpenDialog` when typing emtpy e [\\#2067](https://github.com/VSCodeVim/Vim/pull/2067) ([DanEEStar](https://github.com/DanEEStar))\n- Fix gj/gk in visual block mode [\\#2046](https://github.com/VSCodeVim/Vim/pull/2046) ([orn688](https://github.com/orn688))\n\n## [v0.10.2](https://github.com/vscodevim/vim/tree/v0.10.2) (2017-10-14)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.1...v0.10.2)\n\n- Update ROADMAP.md [\\#2073](https://github.com/VSCodeVim/Vim/pull/2073) ([xconverge](https://github.com/xconverge))\n- Change ignoreFocusOut to false for the command line [\\#2072](https://github.com/VSCodeVim/Vim/pull/2072) ([gadkadosh](https://github.com/gadkadosh))\n- Upgrade packages [\\#2070](https://github.com/VSCodeVim/Vim/pull/2070) ([jpoon](https://github.com/jpoon))\n- fixes \\#1576 and showcmd configuration option [\\#2069](https://github.com/VSCodeVim/Vim/pull/2069) ([xconverge](https://github.com/xconverge))\n- removed code which is not needed anymore due to \\#2062 [\\#2065](https://github.com/VSCodeVim/Vim/pull/2065) ([DanEEStar](https://github.com/DanEEStar))\n- An option to show the colon at the start of the command line box [\\#2064](https://github.com/VSCodeVim/Vim/pull/2064) ([gadkadosh](https://github.com/gadkadosh))\n- Bugfix \\#1951: text selection in insert mode [\\#2062](https://github.com/VSCodeVim/Vim/pull/2062) ([DanEEStar](https://github.com/DanEEStar))\n- Dispose modehandler if NO documents match the modehandler document anymore [\\#2058](https://github.com/VSCodeVim/Vim/pull/2058) ([xconverge](https://github.com/xconverge))\n- Fixes \\#2050 Allow custom cursor styles per mode [\\#2054](https://github.com/VSCodeVim/Vim/pull/2054) ([xconverge](https://github.com/xconverge))\n- Fixes \\#1824: g; and g, commands. [\\#2040](https://github.com/VSCodeVim/Vim/pull/2040) ([westim](https://github.com/westim))\n- Fixes \\#1248: support for '., `., and gi commands. [\\#2037](https://github.com/VSCodeVim/Vim/pull/2037) ([westim](https://github.com/westim))\n- Fix for issue \\#1860, visual multicursor movement. [\\#2036](https://github.com/VSCodeVim/Vim/pull/2036) ([westim](https://github.com/westim))\n- Fix a typo [\\#2028](https://github.com/VSCodeVim/Vim/pull/2028) ([joonro](https://github.com/joonro))\n\n## [v0.10.1](https://github.com/vscodevim/vim/tree/v0.10.1) (2017-09-16)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.10.0...v0.10.1)\n\n- Fixing travis issues [\\#2024](https://github.com/VSCodeVim/Vim/pull/2024) ([Chillee](https://github.com/Chillee))\n- Correct behavior of mouseSelectionGoesIntoVisualMode [\\#2020](https://github.com/VSCodeVim/Vim/pull/2020) ([nguymin4](https://github.com/nguymin4))\n- Easymotion improvements [\\#2017](https://github.com/VSCodeVim/Vim/pull/2017) ([MaxfieldWalker](https://github.com/MaxfieldWalker))\n- fix \\#2009 [\\#2012](https://github.com/VSCodeVim/Vim/pull/2012) ([MaxfieldWalker](https://github.com/MaxfieldWalker))\n- Fix deref of undefined race on startup. [\\#2002](https://github.com/VSCodeVim/Vim/pull/2002) ([brandonbloom](https://github.com/brandonbloom))\n- Use Go To Def & history absent a tag stack. [\\#2001](https://github.com/VSCodeVim/Vim/pull/2001) ([brandonbloom](https://github.com/brandonbloom))\n- Fix\\#1981 [\\#1997](https://github.com/VSCodeVim/Vim/pull/1997) ([MaxfieldWalker](https://github.com/MaxfieldWalker))\n- Improvements to paragraph text objects. [\\#1996](https://github.com/VSCodeVim/Vim/pull/1996) ([brandonbloom](https://github.com/brandonbloom))\n- Implement '' and ``. [\\#1993](https://github.com/VSCodeVim/Vim/pull/1993) ([brandonbloom](https://github.com/brandonbloom))\n\n## [v0.10.0](https://github.com/vscodevim/vim/tree/v0.10.0) (2017-08-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.9.0...v0.10.0)\n\n- Make prettier work on Windows [\\#1987](https://github.com/VSCodeVim/Vim/pull/1987) ([MaxfieldWalker](https://github.com/MaxfieldWalker))\n- Remove flaky tests [\\#1982](https://github.com/VSCodeVim/Vim/pull/1982) ([Chillee](https://github.com/Chillee))\n- Fixed iW on beginning of word \\(\\#1935\\) [\\#1977](https://github.com/VSCodeVim/Vim/pull/1977) ([Ghust1995](https://github.com/Ghust1995))\n- Easymotion new features [\\#1967](https://github.com/VSCodeVim/Vim/pull/1967) ([MaxfieldWalker](https://github.com/MaxfieldWalker))\n- Trying to fix the travis issues with neovim [\\#1958](https://github.com/VSCodeVim/Vim/pull/1958) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1941: Action repetition with Ctrl-\\[ [\\#1953](https://github.com/VSCodeVim/Vim/pull/1953) ([tagniam](https://github.com/tagniam))\n- Fixes \\#1950: counter for \\$ [\\#1952](https://github.com/VSCodeVim/Vim/pull/1952) ([tagniam](https://github.com/tagniam))\n- Makes all tests pass on Windows [\\#1939](https://github.com/VSCodeVim/Vim/pull/1939) ([philipmat](https://github.com/philipmat))\n- Update tests due to VSCode PR 28238 [\\#1926](https://github.com/VSCodeVim/Vim/pull/1926) ([philipmat](https://github.com/philipmat))\n- fix `z O` unfoldRecursively [\\#1924](https://github.com/VSCodeVim/Vim/pull/1924) ([VincentBel](https://github.com/VincentBel))\n- Renamed test to reflect purpose [\\#1913](https://github.com/VSCodeVim/Vim/pull/1913) ([philipmat](https://github.com/philipmat))\n- Ctrl-C should copy to clipboard in visual mode - fix for \\#1896 [\\#1912](https://github.com/VSCodeVim/Vim/pull/1912) ([philipmat](https://github.com/philipmat))\n- Substitute global flag \\(like Vim's `gdefault`\\) [\\#1909](https://github.com/VSCodeVim/Vim/pull/1909) ([philipmat](https://github.com/philipmat))\n- Fixes \\#1871: Adds configuration option to go into visual mode upon clicking in insert mode [\\#1898](https://github.com/VSCodeVim/Vim/pull/1898) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1886: indent repeat doesn't work in visual mode [\\#1890](https://github.com/VSCodeVim/Vim/pull/1890) ([Chillee](https://github.com/Chillee))\n- Formattted everything with prettier [\\#1879](https://github.com/VSCodeVim/Vim/pull/1879) ([Chillee](https://github.com/Chillee))\n\n## [v0.9.0](https://github.com/vscodevim/vim/tree/v0.9.0) (2017-06-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.7...v0.9.0)\n\n- fixes \\#1861 [\\#1868](https://github.com/VSCodeVim/Vim/pull/1868) ([xconverge](https://github.com/xconverge))\n- Fix off by one error in visual mode [\\#1862](https://github.com/VSCodeVim/Vim/pull/1862) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.7](https://github.com/vscodevim/vim/tree/v0.8.7) (2017-06-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.6...v0.8.7)\n\n- Added :only command and corresponding shortcuts [\\#1882](https://github.com/VSCodeVim/Vim/pull/1882) ([LeonB](https://github.com/LeonB))\n- Select in visual mode when scrolling [\\#1859](https://github.com/VSCodeVim/Vim/pull/1859) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1857: P not creating an undo stop [\\#1858](https://github.com/VSCodeVim/Vim/pull/1858) ([Chillee](https://github.com/Chillee))\n- Fixes \\#979: Adds q! to close without saving [\\#1854](https://github.com/VSCodeVim/Vim/pull/1854) ([Chillee](https://github.com/Chillee))\n- Update README.md \\(minor\\) [\\#1851](https://github.com/VSCodeVim/Vim/pull/1851) ([BlueDrink9](https://github.com/BlueDrink9))\n- fixes \\#1843 A and I preceded by count [\\#1846](https://github.com/VSCodeVim/Vim/pull/1846) ([xconverge](https://github.com/xconverge))\n- WIP Fixes \\#754: Adds j,k,o,\\<Enter\\>, gg, G, ctrl+d, and ctrl+u commands for navigating inside the file explorer [\\#1718](https://github.com/VSCodeVim/Vim/pull/1718) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.6](https://github.com/vscodevim/vim/tree/v0.8.6) (2017-06-15)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.5...v0.8.6)\n\n- Removed solid block cursor [\\#1842](https://github.com/VSCodeVim/Vim/pull/1842) ([Chillee](https://github.com/Chillee))\n- Fix yiw cursor pos [\\#1837](https://github.com/VSCodeVim/Vim/pull/1837) ([xconverge](https://github.com/xconverge))\n- Fixes \\#1794: Undo not undoing all changes [\\#1833](https://github.com/VSCodeVim/Vim/pull/1833) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1827: Autocomplete fails when any lines are wrapped/folded [\\#1832](https://github.com/VSCodeVim/Vim/pull/1832) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1826: Jump to line with neovim disabled doesn't work [\\#1831](https://github.com/VSCodeVim/Vim/pull/1831) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.5](https://github.com/vscodevim/vim/tree/v0.8.5) (2017-06-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.4...v0.8.5)\n\n- Fixes \\#1814: Undo history getting deleted when file changes [\\#1820](https://github.com/VSCodeVim/Vim/pull/1820) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1200: :e doesn't expand tildes [\\#1819](https://github.com/VSCodeVim/Vim/pull/1819) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1786: Adds relative line ranges [\\#1810](https://github.com/VSCodeVim/Vim/pull/1810) ([Chillee](https://github.com/Chillee))\n- Fixed \\#1803: zc automatically reopens folds if the fold is performed in the middle. [\\#1809](https://github.com/VSCodeVim/Vim/pull/1809) ([Chillee](https://github.com/Chillee))\n- Vertical split shortcut keys [\\#1795](https://github.com/VSCodeVim/Vim/pull/1795) ([beefsack](https://github.com/beefsack))\n\n## [v0.8.4](https://github.com/vscodevim/vim/tree/v0.8.4) (2017-05-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.3...v0.8.4)\n\n- Fixes \\#1743: Fixed pasting over visual mode with named register overwriting the named register [\\#1777](https://github.com/VSCodeVim/Vim/pull/1777) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1760: Deindenting not working properly with neovim ex-commands [\\#1770](https://github.com/VSCodeVim/Vim/pull/1770) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1768: Backspace deletes more than one tab when tabs are mandated by language specific settings [\\#1769](https://github.com/VSCodeVim/Vim/pull/1769) ([Chillee](https://github.com/Chillee))\n- More v8 patches [\\#1766](https://github.com/VSCodeVim/Vim/pull/1766) ([Chillee](https://github.com/Chillee))\n- fixed \\#1027 maybe? [\\#1740](https://github.com/VSCodeVim/Vim/pull/1740) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.3](https://github.com/vscodevim/vim/tree/v0.8.3) (2017-05-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.2...v0.8.3)\n\n## [v0.8.2](https://github.com/vscodevim/vim/tree/v0.8.2) (2017-05-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.1...v0.8.2)\n\n- Fixes \\#1750: gq doesn't work for JSDoc type comments [\\#1759](https://github.com/VSCodeVim/Vim/pull/1759) ([Chillee](https://github.com/Chillee))\n- Some patches for v0.8.0 [\\#1757](https://github.com/VSCodeVim/Vim/pull/1757) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.1](https://github.com/vscodevim/vim/tree/v0.8.1) (2017-05-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.8.0...v0.8.1)\n\n- Fixes \\#1752: Tab Completion [\\#1753](https://github.com/VSCodeVim/Vim/pull/1753) ([Chillee](https://github.com/Chillee))\n\n## [v0.8.0](https://github.com/vscodevim/vim/tree/v0.8.0) (2017-05-25)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.7.1...v0.8.0)\n\n- Fixes \\#1749: \\<D-d\\> in insert mode doesn't work when the word isn't by itself [\\#1748](https://github.com/VSCodeVim/Vim/pull/1748) ([Chillee](https://github.com/Chillee))\n- Added automatic changelog generator [\\#1747](https://github.com/VSCodeVim/Vim/pull/1747) ([Chillee](https://github.com/Chillee))\n- Actually readded \\<c-j\\> and \\<c-k\\> [\\#1730](https://github.com/VSCodeVim/Vim/pull/1730) ([Chillee](https://github.com/Chillee))\n- Revert \"Unfixes \\#1720\" [\\#1729](https://github.com/VSCodeVim/Vim/pull/1729) ([Chillee](https://github.com/Chillee))\n- Unfixes \\#1720 [\\#1728](https://github.com/VSCodeVim/Vim/pull/1728) ([Chillee](https://github.com/Chillee))\n- Embedding Neovim for Ex commands [\\#1725](https://github.com/VSCodeVim/Vim/pull/1725) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1720: Removed unused \\<c- \\> bindings from package.json [\\#1722](https://github.com/VSCodeVim/Vim/pull/1722) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1376: \\<C-a\\> doesn't work correctly when a word has more than 1 number [\\#1721](https://github.com/VSCodeVim/Vim/pull/1721) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1715: Adds multicursor paste [\\#1717](https://github.com/VSCodeVim/Vim/pull/1717) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1534, \\#1518, \\#1716, \\#1618, \\#1450: Refactored repeating motions [\\#1712](https://github.com/VSCodeVim/Vim/pull/1712) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1520: search in visual/visualLine/visualBlock mode [\\#1710](https://github.com/VSCodeVim/Vim/pull/1710) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1403: VisualBlock doesn't respect keybindings. [\\#1709](https://github.com/VSCodeVim/Vim/pull/1709) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1655: Extends gf to line numbers [\\#1708](https://github.com/VSCodeVim/Vim/pull/1708) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1436: extension prevents 'find all references' pop-up from closing through \\<esc\\> if it's empty. [\\#1707](https://github.com/VSCodeVim/Vim/pull/1707) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1668: Self closing tags not properly handled. [\\#1702](https://github.com/VSCodeVim/Vim/pull/1702) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1674: repeating . with characters like \" or \\) leaves cursor in wrong place [\\#1700](https://github.com/VSCodeVim/Vim/pull/1700) ([Chillee](https://github.com/Chillee))\n- remove system clipboard hack for UTF-8 [\\#1695](https://github.com/VSCodeVim/Vim/pull/1695) ([xconverge](https://github.com/xconverge))\n- Fixes \\#1684: Fixed gq spacing issues [\\#1686](https://github.com/VSCodeVim/Vim/pull/1686) ([Chillee](https://github.com/Chillee))\n- Fixed some regressions I introduced [\\#1681](https://github.com/VSCodeVim/Vim/pull/1681) ([Chillee](https://github.com/Chillee))\n- feat\\(surround\\): support complex tags surround [\\#1680](https://github.com/VSCodeVim/Vim/pull/1680) ([admosity](https://github.com/admosity))\n- Fixes \\#1400, \\#612, \\#1632, \\#1634, \\#1531, \\#1458: Tab isn't handled properly for insert and visualblockinsert modes [\\#1663](https://github.com/VSCodeVim/Vim/pull/1663) ([Chillee](https://github.com/Chillee))\n- Fixes \\#792: Selecting range before Ex-commands highlights initial text [\\#1659](https://github.com/VSCodeVim/Vim/pull/1659) ([Chillee](https://github.com/Chillee))\n- Cobbweb/more readme fixes [\\#1656](https://github.com/VSCodeVim/Vim/pull/1656) ([cobbweb](https://github.com/cobbweb))\n- Fixes \\#1256 and \\#394: Fixes delete key and adds functionality [\\#1644](https://github.com/VSCodeVim/Vim/pull/1644) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1196, \\#1197: d}/y} not working correctly [\\#1621](https://github.com/VSCodeVim/Vim/pull/1621) ([Chillee](https://github.com/Chillee))\n- Fixing the automatic fold expansion \\(\\#1004\\) [\\#1552](https://github.com/VSCodeVim/Vim/pull/1552) ([Chillee](https://github.com/Chillee))\n- Fix visual mode bugs\\#1304to\\#1308 [\\#1322](https://github.com/VSCodeVim/Vim/pull/1322) ([xlaech](https://github.com/xlaech))\n\n## [v0.7.1](https://github.com/vscodevim/vim/tree/v0.7.1) (2017-05-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.7.0...v0.7.1)\n\n- Changes tabs to navigate inside the same split [\\#1677](https://github.com/VSCodeVim/Vim/pull/1677) ([vinicio](https://github.com/vinicio))\n- clean up tests. increase timeout [\\#1672](https://github.com/VSCodeVim/Vim/pull/1672) ([jpoon](https://github.com/jpoon))\n- Fixes \\#1585: Added \\<C-w\\> j and \\<C-w\\> k [\\#1666](https://github.com/VSCodeVim/Vim/pull/1666) ([Chillee](https://github.com/Chillee))\n- Add :close support based on :quit [\\#1665](https://github.com/VSCodeVim/Vim/pull/1665) ([mspaulding06](https://github.com/mspaulding06))\n- Fixes \\#1280: Pasting over selection doesn't yank deleted section [\\#1651](https://github.com/VSCodeVim/Vim/pull/1651) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1535, \\#1467, \\#1311: D-d doesn't work in insert mode [\\#1631](https://github.com/VSCodeVim/Vim/pull/1631) ([Chillee](https://github.com/Chillee))\n\n## [v0.7.0](https://github.com/vscodevim/vim/tree/v0.7.0) (2017-05-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.20...v0.7.0)\n\n- Join HTML on single line to prevent extraneous \\<br\\>s [\\#1643](https://github.com/VSCodeVim/Vim/pull/1643) ([cobbweb](https://github.com/cobbweb))\n- Refactor [\\#1642](https://github.com/VSCodeVim/Vim/pull/1642) ([rebornix](https://github.com/rebornix))\n- Fixes \\#1637, \\#1638: z- and z\\<CR\\> movements [\\#1640](https://github.com/VSCodeVim/Vim/pull/1640) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1503: Undo history isn't kept when switching tabs [\\#1629](https://github.com/VSCodeVim/Vim/pull/1629) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1441: Ctrl-c dropping a character when selecting from right to left in insert mode [\\#1628](https://github.com/VSCodeVim/Vim/pull/1628) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1300: Fixed bug with recently submitted tag PR [\\#1625](https://github.com/VSCodeVim/Vim/pull/1625) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1137: i\\_\\<C-w\\> deletes through whitespace at beginning of line [\\#1624](https://github.com/VSCodeVim/Vim/pull/1624) ([Chillee](https://github.com/Chillee))\n- Further work on tag matching \\(based off of \\#1454\\) [\\#1620](https://github.com/VSCodeVim/Vim/pull/1620) ([Chillee](https://github.com/Chillee))\n- Toggle vim [\\#1619](https://github.com/VSCodeVim/Vim/pull/1619) ([rebornix](https://github.com/rebornix))\n- Fixes \\#1588: \\<C-a\\> does wrong things if cursor is to the right of a number \\(and there's a number on the next line\\) [\\#1617](https://github.com/VSCodeVim/Vim/pull/1617) ([Chillee](https://github.com/Chillee))\n- Visualstar [\\#1616](https://github.com/VSCodeVim/Vim/pull/1616) ([mikew](https://github.com/mikew))\n- outfiles needs to be globbed [\\#1615](https://github.com/VSCodeVim/Vim/pull/1615) ([jpoon](https://github.com/jpoon))\n- Upgrade typescript 2.2.1-\\>2.3.2. tslint 3.10.2-\\>2.3.2. Fix errors [\\#1614](https://github.com/VSCodeVim/Vim/pull/1614) ([jpoon](https://github.com/jpoon))\n- Fix warning [\\#1613](https://github.com/VSCodeVim/Vim/pull/1613) ([jpoon](https://github.com/jpoon))\n- Stopped getLineMaxColumn from erroring on line 0 [\\#1610](https://github.com/VSCodeVim/Vim/pull/1610) ([Chillee](https://github.com/Chillee))\n- use editor from event fixes \\#1607 [\\#1608](https://github.com/VSCodeVim/Vim/pull/1608) ([brandoncc](https://github.com/brandoncc))\n- Fixes \\#1532: gd doesn't set desiredColumn properly [\\#1605](https://github.com/VSCodeVim/Vim/pull/1605) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1594: \\<Copy\\> drops the first and last line when selecting in visual line mode from the bottom up [\\#1604](https://github.com/VSCodeVim/Vim/pull/1604) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1575: Adds support for searching for strings with newlines [\\#1603](https://github.com/VSCodeVim/Vim/pull/1603) ([Chillee](https://github.com/Chillee))\n- Fix status bar color when change mode [\\#1602](https://github.com/VSCodeVim/Vim/pull/1602) ([zelphir](https://github.com/zelphir))\n- Made command line persistent when switching windows [\\#1601](https://github.com/VSCodeVim/Vim/pull/1601) ([Chillee](https://github.com/Chillee))\n- Fixes \\#890, \\#1377: Selection \\(both visual/visualline\\) is very wonky with gj and gk [\\#1600](https://github.com/VSCodeVim/Vim/pull/1600) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1251: gq always adds an extra space to beginning of block. [\\#1596](https://github.com/VSCodeVim/Vim/pull/1596) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1599: dot command doesn't work in macros [\\#1595](https://github.com/VSCodeVim/Vim/pull/1595) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1369: Change on a selection where endpoint was at beginning of line misses last character [\\#1560](https://github.com/VSCodeVim/Vim/pull/1560) ([Chillee](https://github.com/Chillee))\n- Add support for indent objects [\\#1550](https://github.com/VSCodeVim/Vim/pull/1550) ([mikew](https://github.com/mikew))\n- Navigate between view [\\#1504](https://github.com/VSCodeVim/Vim/pull/1504) ([lyup](https://github.com/lyup))\n\n## [v0.6.20](https://github.com/vscodevim/vim/tree/v0.6.20) (2017-04-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.19...v0.6.20)\n\n## [v0.6.19](https://github.com/vscodevim/vim/tree/v0.6.19) (2017-04-26)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.18...v0.6.19)\n\n- Fixes \\#1573: Backspace at beginning of file causes subsequent operation to nop [\\#1577](https://github.com/VSCodeVim/Vim/pull/1577) ([Chillee](https://github.com/Chillee))\n- Fix logo src so logo displays inside VSCode [\\#1572](https://github.com/VSCodeVim/Vim/pull/1572) ([cobbweb](https://github.com/cobbweb))\n- fixes \\#1449 [\\#1571](https://github.com/VSCodeVim/Vim/pull/1571) ([squedd](https://github.com/squedd))\n- fixes \\#1252 [\\#1569](https://github.com/VSCodeVim/Vim/pull/1569) ([xconverge](https://github.com/xconverge))\n- fixes \\#1486 :wqa command [\\#1568](https://github.com/VSCodeVim/Vim/pull/1568) ([xconverge](https://github.com/xconverge))\n- fixes \\#1357 [\\#1567](https://github.com/VSCodeVim/Vim/pull/1567) ([xconverge](https://github.com/xconverge))\n- Fix surround aliases [\\#1564](https://github.com/VSCodeVim/Vim/pull/1564) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.18](https://github.com/vscodevim/vim/tree/v0.6.18) (2017-04-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.17...v0.6.18)\n\n- update clipboardy library with windows utf-8 fix [\\#1559](https://github.com/VSCodeVim/Vim/pull/1559) ([xconverge](https://github.com/xconverge))\n- Fixes \\#1539: Displaying values in register stops displaying anything after the newline [\\#1558](https://github.com/VSCodeVim/Vim/pull/1558) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1539: Viewing register value displays incorrectly for macros [\\#1557](https://github.com/VSCodeVim/Vim/pull/1557) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1554, \\#1553: Fixed daW bugs [\\#1555](https://github.com/VSCodeVim/Vim/pull/1555) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1193, \\#1350, \\#967: Fixes daw bugs [\\#1549](https://github.com/VSCodeVim/Vim/pull/1549) ([Chillee](https://github.com/Chillee))\n- Allow users to use VSCode keybinding for remapping [\\#1548](https://github.com/VSCodeVim/Vim/pull/1548) ([rebornix](https://github.com/rebornix))\n- README enhancements [\\#1547](https://github.com/VSCodeVim/Vim/pull/1547) ([cobbweb](https://github.com/cobbweb))\n- Fixes \\#1533: \\<Copy\\> not activating when \\<C-c\\> is pressed [\\#1542](https://github.com/VSCodeVim/Vim/pull/1542) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1528: daw on end of word doesn't delete properly [\\#1536](https://github.com/VSCodeVim/Vim/pull/1536) ([Chillee](https://github.com/Chillee))\n- Fixes \\#1513: Backspace on middle of whitespace only line fails [\\#1514](https://github.com/VSCodeVim/Vim/pull/1514) ([Chillee](https://github.com/Chillee))\n\n## [v0.6.17](https://github.com/vscodevim/vim/tree/v0.6.17) (2017-04-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.16...v0.6.17)\n\n- Allow user to change status bar color based on mode [\\#1529](https://github.com/VSCodeVim/Vim/pull/1529) ([xconverge](https://github.com/xconverge))\n- Fix README description for `af` [\\#1522](https://github.com/VSCodeVim/Vim/pull/1522) ([esturcke](https://github.com/esturcke))\n- fixes \\#1519 [\\#1521](https://github.com/VSCodeVim/Vim/pull/1521) ([xconverge](https://github.com/xconverge))\n- make surround repeatable with dot [\\#1515](https://github.com/VSCodeVim/Vim/pull/1515) ([xconverge](https://github.com/xconverge))\n- \\[WIP\\] change system clipboard library to a newer more maintained library [\\#1487](https://github.com/VSCodeVim/Vim/pull/1487) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.16](https://github.com/vscodevim/vim/tree/v0.6.16) (2017-04-16)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.6.15...v0.6.16)\n\n- added cmd_line commands to remapper [\\#1516](https://github.com/VSCodeVim/Vim/pull/1516) ([xconverge](https://github.com/xconverge))\n- fixes \\#1507 and removes workspace settings that should not be there [\\#1509](https://github.com/VSCodeVim/Vim/pull/1509) ([xconverge](https://github.com/xconverge))\n- Add line comment operator [\\#1506](https://github.com/VSCodeVim/Vim/pull/1506) ([fiedler](https://github.com/fiedler))\n- Add 5i= or 4a- so that the previously inserted text is repeated upon exiting to normal mode [\\#1495](https://github.com/VSCodeVim/Vim/pull/1495) ([xconverge](https://github.com/xconverge))\n- Add ability to turn surround plugin off [\\#1494](https://github.com/VSCodeVim/Vim/pull/1494) ([xconverge](https://github.com/xconverge))\n- Added new style settings \\(color, size, etc.\\) for easymotion markers [\\#1493](https://github.com/VSCodeVim/Vim/pull/1493) ([edasaki](https://github.com/edasaki))\n- fixes \\#1475 [\\#1485](https://github.com/VSCodeVim/Vim/pull/1485) ([xconverge](https://github.com/xconverge))\n- fix for double clicking a word with mouse not showing selection properly [\\#1484](https://github.com/VSCodeVim/Vim/pull/1484) ([xconverge](https://github.com/xconverge))\n- fix easymotion j and k [\\#1474](https://github.com/VSCodeVim/Vim/pull/1474) ([xconverge](https://github.com/xconverge))\n\n## [0.6.15](https://github.com/vscodevim/vim/tree/0.6.15) (2017-04-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.6.14...0.6.15)\n\n## [0.6.14](https://github.com/vscodevim/vim/tree/0.6.14) (2017-04-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.13...0.6.14)\n\n- Fix tables in roadmap [\\#1469](https://github.com/VSCodeVim/Vim/pull/1469) ([xconverge](https://github.com/xconverge))\n- Fix visual block mode not updating multicursor selection [\\#1468](https://github.com/VSCodeVim/Vim/pull/1468) ([xconverge](https://github.com/xconverge))\n- Fix type suggestion for handleKeys object [\\#1465](https://github.com/VSCodeVim/Vim/pull/1465) ([abhiranjankumar00](https://github.com/abhiranjankumar00))\n\n## [v0.6.13](https://github.com/vscodevim/vim/tree/v0.6.13) (2017-04-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.6.12...v0.6.13)\n\n- fixes \\#1448 [\\#1462](https://github.com/VSCodeVim/Vim/pull/1462) ([xconverge](https://github.com/xconverge))\n- fix multi line in 'at' and 'it' commands [\\#1454](https://github.com/VSCodeVim/Vim/pull/1454) ([jrenton](https://github.com/jrenton))\n\n## [0.6.12](https://github.com/vscodevim/vim/tree/0.6.12) (2017-04-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.11...0.6.12)\n\n- fixes \\#1432 [\\#1434](https://github.com/VSCodeVim/Vim/pull/1434) ([xconverge](https://github.com/xconverge))\n- fixes \\#1312 [\\#1433](https://github.com/VSCodeVim/Vim/pull/1433) ([xconverge](https://github.com/xconverge))\n- Change easymotion decoration colors to use searchHighlight colors [\\#1431](https://github.com/VSCodeVim/Vim/pull/1431) ([xconverge](https://github.com/xconverge))\n- minor cleanup to improve leader usage with \\<space\\> [\\#1429](https://github.com/VSCodeVim/Vim/pull/1429) ([xconverge](https://github.com/xconverge))\n- gUU and guu [\\#1428](https://github.com/VSCodeVim/Vim/pull/1428) ([xconverge](https://github.com/xconverge))\n- Allowing user to selectively disable some key combos [\\#1425](https://github.com/VSCodeVim/Vim/pull/1425) ([xconverge](https://github.com/xconverge))\n- Remapper cleanup key history [\\#1416](https://github.com/VSCodeVim/Vim/pull/1416) ([xconverge](https://github.com/xconverge))\n- fix undo points when moving around in insert with mouse or arrow keys [\\#1413](https://github.com/VSCodeVim/Vim/pull/1413) ([xconverge](https://github.com/xconverge))\n- update readme for plugins [\\#1411](https://github.com/VSCodeVim/Vim/pull/1411) ([xconverge](https://github.com/xconverge))\n- Allow users to use their own cursor style for insert from editor.cursorStyle [\\#1399](https://github.com/VSCodeVim/Vim/pull/1399) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.11](https://github.com/vscodevim/vim/tree/v0.6.11) (2017-03-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.10...v0.6.11)\n\n- Fix comment syntax for shell commands. [\\#1408](https://github.com/VSCodeVim/Vim/pull/1408) ([frewsxcv](https://github.com/frewsxcv))\n- Increase timeout for some test cases in mocha [\\#1379](https://github.com/VSCodeVim/Vim/pull/1379) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.10](https://github.com/vscodevim/vim/tree/v0.6.10) (2017-03-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.9...v0.6.10)\n\n## [v0.6.9](https://github.com/vscodevim/vim/tree/v0.6.9) (2017-03-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.8...v0.6.9)\n\n## [v0.6.8](https://github.com/vscodevim/vim/tree/v0.6.8) (2017-03-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.7...v0.6.8)\n\n## [v0.6.7](https://github.com/vscodevim/vim/tree/v0.6.7) (2017-03-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.6...v0.6.7)\n\n- fix bracket motion behavior for use with % and a count, or \\[\\( and a c… [\\#1406](https://github.com/VSCodeVim/Vim/pull/1406) ([xconverge](https://github.com/xconverge))\n- fix for cursor not changing correctly, workaround for vscode issue [\\#1402](https://github.com/VSCodeVim/Vim/pull/1402) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.6](https://github.com/vscodevim/vim/tree/v0.6.6) (2017-03-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.5...v0.6.6)\n\n- Use block cursor in visual & underline in replace [\\#1394](https://github.com/VSCodeVim/Vim/pull/1394) ([net](https://github.com/net))\n- Perform remapped commands when prefix by a number [\\#1359](https://github.com/VSCodeVim/Vim/pull/1359) ([bdauria](https://github.com/bdauria))\n\n## [v0.6.5](https://github.com/vscodevim/vim/tree/v0.6.5) (2017-03-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.4...v0.6.5)\n\n## [v0.6.4](https://github.com/vscodevim/vim/tree/v0.6.4) (2017-03-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.3...v0.6.4)\n\n- Update README.md [\\#1390](https://github.com/VSCodeVim/Vim/pull/1390) ([xconverge](https://github.com/xconverge))\n- fixes \\#1385 % motion with a count [\\#1387](https://github.com/VSCodeVim/Vim/pull/1387) ([xconverge](https://github.com/xconverge))\n- fixes \\#1382 [\\#1386](https://github.com/VSCodeVim/Vim/pull/1386) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.3](https://github.com/vscodevim/vim/tree/v0.6.3) (2017-03-11)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.6.0...v0.6.3)\n\n- fixes \\#1373 [\\#1374](https://github.com/VSCodeVim/Vim/pull/1374) ([xconverge](https://github.com/xconverge))\n- Remove log file. [\\#1368](https://github.com/VSCodeVim/Vim/pull/1368) ([frewsxcv](https://github.com/frewsxcv))\n- Remove our modified older typings [\\#1367](https://github.com/VSCodeVim/Vim/pull/1367) ([xconverge](https://github.com/xconverge))\n- \\[WIP\\] fix travis due to double digit version numbers [\\#1366](https://github.com/VSCodeVim/Vim/pull/1366) ([xconverge](https://github.com/xconverge))\n- Fixed numbered registered macros from overwriting themselves [\\#1362](https://github.com/VSCodeVim/Vim/pull/1362) ([xconverge](https://github.com/xconverge))\n- Update config options without restarting [\\#1361](https://github.com/VSCodeVim/Vim/pull/1361) ([xconverge](https://github.com/xconverge))\n- Index fixes [\\#1190](https://github.com/VSCodeVim/Vim/pull/1190) ([xconverge](https://github.com/xconverge))\n\n## [v0.6.0](https://github.com/vscodevim/vim/tree/v0.6.0) (2017-03-03)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.5.3...v0.6.0)\n\n- Fix clipboard copy [\\#1349](https://github.com/VSCodeVim/Vim/pull/1349) ([johnfn](https://github.com/johnfn))\n- regex match [\\#1346](https://github.com/VSCodeVim/Vim/pull/1346) ([rebornix](https://github.com/rebornix))\n- Add limited support for :sort [\\#1342](https://github.com/VSCodeVim/Vim/pull/1342) ([jordan-heemskerk](https://github.com/jordan-heemskerk))\n- Override VSCode copy command. \\#1337, \\#616. [\\#1339](https://github.com/VSCodeVim/Vim/pull/1339) ([johnfn](https://github.com/johnfn))\n- Fix \\#1318 [\\#1338](https://github.com/VSCodeVim/Vim/pull/1338) ([rebornix](https://github.com/rebornix))\n- Fix \\#1329 failing build by removing undefined in configuration.ts [\\#1332](https://github.com/VSCodeVim/Vim/pull/1332) ([misoguy](https://github.com/misoguy))\n- fixes \\#1327 [\\#1331](https://github.com/VSCodeVim/Vim/pull/1331) ([xconverge](https://github.com/xconverge))\n- fixes \\#1320 [\\#1325](https://github.com/VSCodeVim/Vim/pull/1325) ([xconverge](https://github.com/xconverge))\n- fixes \\#1313 [\\#1324](https://github.com/VSCodeVim/Vim/pull/1324) ([xconverge](https://github.com/xconverge))\n- Add ctrl-w q action to quit current window. [\\#1317](https://github.com/VSCodeVim/Vim/pull/1317) ([tail](https://github.com/tail))\n- Fix lint issue. [\\#1316](https://github.com/VSCodeVim/Vim/pull/1316) ([tail](https://github.com/tail))\n- Fix c on line beginning\\#1302 [\\#1303](https://github.com/VSCodeVim/Vim/pull/1303) ([xlaech](https://github.com/xlaech))\n- fixes travis with minor hack used in tests [\\#1301](https://github.com/VSCodeVim/Vim/pull/1301) ([xconverge](https://github.com/xconverge))\n- D in visual mode behaves like d [\\#1297](https://github.com/VSCodeVim/Vim/pull/1297) ([xlaech](https://github.com/xlaech))\n- Fix for \\#1293 [\\#1296](https://github.com/VSCodeVim/Vim/pull/1296) ([xlaech](https://github.com/xlaech))\n- Update readme for some clarity on using settings [\\#1295](https://github.com/VSCodeVim/Vim/pull/1295) ([xconverge](https://github.com/xconverge))\n- fixes \\#1290, visual block still has the same issue though [\\#1291](https://github.com/VSCodeVim/Vim/pull/1291) ([xconverge](https://github.com/xconverge))\n- More surround fixes [\\#1289](https://github.com/VSCodeVim/Vim/pull/1289) ([xconverge](https://github.com/xconverge))\n\n## [v0.5.3](https://github.com/vscodevim/vim/tree/v0.5.3) (2017-02-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.5.0...v0.5.3)\n\n- fixes \\#1258 [\\#1286](https://github.com/VSCodeVim/Vim/pull/1286) ([xconverge](https://github.com/xconverge))\n- avoid using user remapping in test mode [\\#1278](https://github.com/VSCodeVim/Vim/pull/1278) ([rufusroflpunch](https://github.com/rufusroflpunch))\n- Support exact and inexact current word search [\\#1277](https://github.com/VSCodeVim/Vim/pull/1277) ([rhys-vdw](https://github.com/rhys-vdw))\n- fixes \\#1271 [\\#1274](https://github.com/VSCodeVim/Vim/pull/1274) ([xconverge](https://github.com/xconverge))\n- fixes \\#1199 easymotion in visual mode [\\#1273](https://github.com/VSCodeVim/Vim/pull/1273) ([xconverge](https://github.com/xconverge))\n- fixes \\#1145 [\\#1272](https://github.com/VSCodeVim/Vim/pull/1272) ([xconverge](https://github.com/xconverge))\n- Delete matching bracket upon backspace [\\#1267](https://github.com/VSCodeVim/Vim/pull/1267) ([rufusroflpunch](https://github.com/rufusroflpunch))\n- Clearing commandList for remapped commands [\\#1263](https://github.com/VSCodeVim/Vim/pull/1263) ([rufusroflpunch](https://github.com/rufusroflpunch))\n- Added tag text to status bar in surround mode [\\#1254](https://github.com/VSCodeVim/Vim/pull/1254) ([xconverge](https://github.com/xconverge))\n- Fix autoindent when opening a line above [\\#1249](https://github.com/VSCodeVim/Vim/pull/1249) ([inejge](https://github.com/inejge))\n- Fixes README spelling mistake [\\#1246](https://github.com/VSCodeVim/Vim/pull/1246) ([eastwood](https://github.com/eastwood))\n\n## [v0.5.0](https://github.com/vscodevim/vim/tree/v0.5.0) (2017-01-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.5.1...v0.5.0)\n\n## [v0.5.1](https://github.com/vscodevim/vim/tree/v0.5.1) (2017-01-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.10...v0.5.1)\n\n- Surround [\\#1238](https://github.com/VSCodeVim/Vim/pull/1238) ([johnfn](https://github.com/johnfn))\n- Support \"gf\" in es6 import statements by adding the file extension [\\#1227](https://github.com/VSCodeVim/Vim/pull/1227) ([aminroosta](https://github.com/aminroosta))\n- fixes \\#1214 [\\#1217](https://github.com/VSCodeVim/Vim/pull/1217) ([Platzer](https://github.com/Platzer))\n\n## [v0.4.10](https://github.com/vscodevim/vim/tree/v0.4.10) (2016-12-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.9...v0.4.10)\n\n- fixes \\#1132 [\\#1187](https://github.com/VSCodeVim/Vim/pull/1187) ([xconverge](https://github.com/xconverge))\n- fixes \\#1173 [\\#1186](https://github.com/VSCodeVim/Vim/pull/1186) ([xconverge](https://github.com/xconverge))\n- Fixed register tests breaking due to \\#1183 [\\#1185](https://github.com/VSCodeVim/Vim/pull/1185) ([vikramthyagarajan](https://github.com/vikramthyagarajan))\n- fixes \\#1180 [\\#1183](https://github.com/VSCodeVim/Vim/pull/1183) ([xconverge](https://github.com/xconverge))\n- Adds documentation for adding leader bindings [\\#1182](https://github.com/VSCodeVim/Vim/pull/1182) ([eastwood](https://github.com/eastwood))\n- Implements Global state [\\#1179](https://github.com/VSCodeVim/Vim/pull/1179) ([vikramthyagarajan](https://github.com/vikramthyagarajan))\n- fixes \\#1176 [\\#1177](https://github.com/VSCodeVim/Vim/pull/1177) ([xconverge](https://github.com/xconverge))\n- Select inner vi\\( fix [\\#1175](https://github.com/VSCodeVim/Vim/pull/1175) ([xconverge](https://github.com/xconverge))\n- fixes \\#1170 [\\#1174](https://github.com/VSCodeVim/Vim/pull/1174) ([xconverge](https://github.com/xconverge))\n- Fixes travis [\\#1169](https://github.com/VSCodeVim/Vim/pull/1169) ([xconverge](https://github.com/xconverge))\n- control key bindings respect the useCtrlKey setting [\\#1151](https://github.com/VSCodeVim/Vim/pull/1151) ([xwvvvvwx](https://github.com/xwvvvvwx))\n- fixes \\#657 implements search history [\\#1147](https://github.com/VSCodeVim/Vim/pull/1147) ([xconverge](https://github.com/xconverge))\n- More click past eol o no [\\#1146](https://github.com/VSCodeVim/Vim/pull/1146) ([xconverge](https://github.com/xconverge))\n- Reselect visual implemented \\(gv\\) [\\#1141](https://github.com/VSCodeVim/Vim/pull/1141) ([xconverge](https://github.com/xconverge))\n- fixes \\#1136 [\\#1139](https://github.com/VSCodeVim/Vim/pull/1139) ([xconverge](https://github.com/xconverge))\n- minor fixes for \\# and \\* after using :nohl [\\#1134](https://github.com/VSCodeVim/Vim/pull/1134) ([xconverge](https://github.com/xconverge))\n- Updated useCtrlKeys default value [\\#1126](https://github.com/VSCodeVim/Vim/pull/1126) ([Mxbonn](https://github.com/Mxbonn))\n- fixes \\#1063 [\\#1124](https://github.com/VSCodeVim/Vim/pull/1124) ([xconverge](https://github.com/xconverge))\n- Fixed \"d\" and \"D\" in multicursor mode [\\#1029](https://github.com/VSCodeVim/Vim/pull/1029) ([Platzer](https://github.com/Platzer))\n\n## [v0.4.9](https://github.com/vscodevim/vim/tree/v0.4.9) (2016-12-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.8...v0.4.9)\n\n## [v0.4.8](https://github.com/vscodevim/vim/tree/v0.4.8) (2016-12-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.7...v0.4.8)\n\n- Update readme for easymotion [\\#1114](https://github.com/VSCodeVim/Vim/pull/1114) ([xconverge](https://github.com/xconverge))\n\n## [v0.4.7](https://github.com/vscodevim/vim/tree/v0.4.7) (2016-12-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.6...v0.4.7)\n\n- Fix minor typo [\\#1113](https://github.com/VSCodeVim/Vim/pull/1113) ([xconverge](https://github.com/xconverge))\n- \\[WIP\\] initial leader fixes [\\#1112](https://github.com/VSCodeVim/Vim/pull/1112) ([xconverge](https://github.com/xconverge))\n- Added more aliases for nohl [\\#1111](https://github.com/VSCodeVim/Vim/pull/1111) ([xconverge](https://github.com/xconverge))\n- Turns highlighting back on after nohl if you try to go to a new searc… [\\#1110](https://github.com/VSCodeVim/Vim/pull/1110) ([xconverge](https://github.com/xconverge))\n\n## [v0.4.6](https://github.com/vscodevim/vim/tree/v0.4.6) (2016-12-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.4.5...v0.4.6)\n\n## [0.4.5](https://github.com/vscodevim/vim/tree/0.4.5) (2016-12-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.5...0.4.5)\n\n- \\[WIP\\] gq [\\#1106](https://github.com/VSCodeVim/Vim/pull/1106) ([johnfn](https://github.com/johnfn))\n\n## [v0.4.5](https://github.com/vscodevim/vim/tree/v0.4.5) (2016-12-02)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.4...v0.4.5)\n\n- Override home key \\(for pressing home in visual for example\\) [\\#1100](https://github.com/VSCodeVim/Vim/pull/1100) ([xconverge](https://github.com/xconverge))\n- avoid syncing style back to config [\\#1099](https://github.com/VSCodeVim/Vim/pull/1099) ([rebornix](https://github.com/rebornix))\n- Implement open file command - Issue \\#801 [\\#1098](https://github.com/VSCodeVim/Vim/pull/1098) ([jamirvin](https://github.com/jamirvin))\n\n## [v0.4.4](https://github.com/vscodevim/vim/tree/v0.4.4) (2016-11-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.3...v0.4.4)\n\n- Removed debug print [\\#1083](https://github.com/VSCodeVim/Vim/pull/1083) ([xconverge](https://github.com/xconverge))\n- Update roadmap for ctrl-o [\\#1082](https://github.com/VSCodeVim/Vim/pull/1082) ([xconverge](https://github.com/xconverge))\n- fixes \\#1076 [\\#1077](https://github.com/VSCodeVim/Vim/pull/1077) ([xconverge](https://github.com/xconverge))\n- fixes \\#1073 [\\#1074](https://github.com/VSCodeVim/Vim/pull/1074) ([xconverge](https://github.com/xconverge))\n- fixes \\#1065 [\\#1071](https://github.com/VSCodeVim/Vim/pull/1071) ([xconverge](https://github.com/xconverge))\n- fixes \\#1023 [\\#1069](https://github.com/VSCodeVim/Vim/pull/1069) ([xconverge](https://github.com/xconverge))\n\n## [v0.4.3](https://github.com/vscodevim/vim/tree/v0.4.3) (2016-11-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.2...v0.4.3)\n\n- fixes \\#1034 [\\#1068](https://github.com/VSCodeVim/Vim/pull/1068) ([xconverge](https://github.com/xconverge))\n- fixes \\#1035 [\\#1067](https://github.com/VSCodeVim/Vim/pull/1067) ([xconverge](https://github.com/xconverge))\n- fixes \\#1064 [\\#1066](https://github.com/VSCodeVim/Vim/pull/1066) ([xconverge](https://github.com/xconverge))\n- How can I fix travis failure [\\#1062](https://github.com/VSCodeVim/Vim/pull/1062) ([rebornix](https://github.com/rebornix))\n\n## [v0.4.2](https://github.com/vscodevim/vim/tree/v0.4.2) (2016-11-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.1...v0.4.2)\n\n- Visual block fixes to cursor position and tests [\\#1044](https://github.com/VSCodeVim/Vim/pull/1044) ([xconverge](https://github.com/xconverge))\n- Hide the info line in issue template [\\#1037](https://github.com/VSCodeVim/Vim/pull/1037) ([octref](https://github.com/octref))\n- Implemented EasyMotion plugin functionality [\\#993](https://github.com/VSCodeVim/Vim/pull/993) ([Metamist](https://github.com/Metamist))\n\n## [v0.4.1](https://github.com/vscodevim/vim/tree/v0.4.1) (2016-10-31)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.4.0...v0.4.1)\n\n- fixes \\#1013 [\\#1014](https://github.com/VSCodeVim/Vim/pull/1014) ([xconverge](https://github.com/xconverge))\n- Update Readme [\\#1012](https://github.com/VSCodeVim/Vim/pull/1012) ([jpoon](https://github.com/jpoon))\n- fixes \\#983 [\\#1008](https://github.com/VSCodeVim/Vim/pull/1008) ([xconverge](https://github.com/xconverge))\n- Make create-multicursor commands repeatable [\\#1007](https://github.com/VSCodeVim/Vim/pull/1007) ([Platzer](https://github.com/Platzer))\n- fix mouse clicking past EOL [\\#1006](https://github.com/VSCodeVim/Vim/pull/1006) ([xconverge](https://github.com/xconverge))\n- fixes \\#1000 and a minor replace issue [\\#1005](https://github.com/VSCodeVim/Vim/pull/1005) ([xconverge](https://github.com/xconverge))\n- Update \"r\" for visual modes on roadmap [\\#1002](https://github.com/VSCodeVim/Vim/pull/1002) ([xconverge](https://github.com/xconverge))\n- fixes \\#998 [\\#1001](https://github.com/VSCodeVim/Vim/pull/1001) ([xconverge](https://github.com/xconverge))\n- Remove fix-whitespace gulp command. [\\#999](https://github.com/VSCodeVim/Vim/pull/999) ([jpoon](https://github.com/jpoon))\n- Improved performance of visual block replace by a lot [\\#997](https://github.com/VSCodeVim/Vim/pull/997) ([xconverge](https://github.com/xconverge))\n- fixes \\#663 [\\#996](https://github.com/VSCodeVim/Vim/pull/996) ([xconverge](https://github.com/xconverge))\n- No need for \"i\" flag on a numerical only regex [\\#995](https://github.com/VSCodeVim/Vim/pull/995) ([stefanoio](https://github.com/stefanoio))\n- SearchState - Fixed isRegex not set on search [\\#994](https://github.com/VSCodeVim/Vim/pull/994) ([Metamist](https://github.com/Metamist))\n- fix \\#985 [\\#992](https://github.com/VSCodeVim/Vim/pull/992) ([rebornix](https://github.com/rebornix))\n- Run all tests [\\#990](https://github.com/VSCodeVim/Vim/pull/990) ([xconverge](https://github.com/xconverge))\n- Fix for visual line behaving funky when going from bottom up [\\#989](https://github.com/VSCodeVim/Vim/pull/989) ([xconverge](https://github.com/xconverge))\n- Add Keymaps category [\\#987](https://github.com/VSCodeVim/Vim/pull/987) ([waderyan](https://github.com/waderyan))\n- fix \\#982 [\\#984](https://github.com/VSCodeVim/Vim/pull/984) ([rebornix](https://github.com/rebornix))\n- fix \\#977 [\\#981](https://github.com/VSCodeVim/Vim/pull/981) ([rebornix](https://github.com/rebornix))\n- fix \\#689 [\\#980](https://github.com/VSCodeVim/Vim/pull/980) ([rebornix](https://github.com/rebornix))\n- Numbered, upper case and multicursor register [\\#974](https://github.com/VSCodeVim/Vim/pull/974) ([Platzer](https://github.com/Platzer))\n- remove leading spaces when \\<Esc\\>... is pressed \\#685 [\\#962](https://github.com/VSCodeVim/Vim/pull/962) ([Zzzen](https://github.com/Zzzen))\n- Fix replace in visual, visual line, and visual block mode [\\#953](https://github.com/VSCodeVim/Vim/pull/953) ([xconverge](https://github.com/xconverge))\n- Add some tests and fix some exceptions during the tests [\\#914](https://github.com/VSCodeVim/Vim/pull/914) ([xconverge](https://github.com/xconverge))\n\n## [v0.4.0](https://github.com/vscodevim/vim/tree/v0.4.0) (2016-10-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.3.8...v0.4.0)\n\n- fix \\#528 [\\#966](https://github.com/VSCodeVim/Vim/pull/966) ([rebornix](https://github.com/rebornix))\n- fix \\#693 [\\#964](https://github.com/VSCodeVim/Vim/pull/964) ([rebornix](https://github.com/rebornix))\n- fix \\#922 [\\#960](https://github.com/VSCodeVim/Vim/pull/960) ([rebornix](https://github.com/rebornix))\n- fix \\#939 [\\#958](https://github.com/VSCodeVim/Vim/pull/958) ([rebornix](https://github.com/rebornix))\n- Add a command is `D` in visual block mode. [\\#957](https://github.com/VSCodeVim/Vim/pull/957) ([Kooooya](https://github.com/Kooooya))\n- Add commands is `s` and `S` in visual block mode. [\\#954](https://github.com/VSCodeVim/Vim/pull/954) ([Kooooya](https://github.com/Kooooya))\n- fix \\#808 [\\#952](https://github.com/VSCodeVim/Vim/pull/952) ([rebornix](https://github.com/rebornix))\n- fix \\#484 [\\#951](https://github.com/VSCodeVim/Vim/pull/951) ([rebornix](https://github.com/rebornix))\n- fix \\#921 [\\#950](https://github.com/VSCodeVim/Vim/pull/950) ([rebornix](https://github.com/rebornix))\n- make tab sequence feel right [\\#949](https://github.com/VSCodeVim/Vim/pull/949) ([rebornix](https://github.com/rebornix))\n- stop revealing cursor when not necessary [\\#948](https://github.com/VSCodeVim/Vim/pull/948) ([rebornix](https://github.com/rebornix))\n- add gh hover command [\\#945](https://github.com/VSCodeVim/Vim/pull/945) ([will-wow](https://github.com/will-wow))\n- New increment without separators [\\#944](https://github.com/VSCodeVim/Vim/pull/944) ([xconverge](https://github.com/xconverge))\n- fix \\#937 [\\#943](https://github.com/VSCodeVim/Vim/pull/943) ([rebornix](https://github.com/rebornix))\n- fixes \\#878 [\\#942](https://github.com/VSCodeVim/Vim/pull/942) ([xconverge](https://github.com/xconverge))\n- Support num registers macros [\\#941](https://github.com/VSCodeVim/Vim/pull/941) ([xconverge](https://github.com/xconverge))\n- Test enhancement [\\#938](https://github.com/VSCodeVim/Vim/pull/938) ([rebornix](https://github.com/rebornix))\n- fix \\#845 [\\#911](https://github.com/VSCodeVim/Vim/pull/911) ([rebornix](https://github.com/rebornix))\n\n## [v0.3.8](https://github.com/vscodevim/vim/tree/v0.3.8) (2016-10-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.7...v0.3.8)\n\n- fixes \\#879 [\\#933](https://github.com/VSCodeVim/Vim/pull/933) ([xconverge](https://github.com/xconverge))\n- fixes \\#905 [\\#932](https://github.com/VSCodeVim/Vim/pull/932) ([xconverge](https://github.com/xconverge))\n- fixes \\#652 [\\#931](https://github.com/VSCodeVim/Vim/pull/931) ([xconverge](https://github.com/xconverge))\n- Update internal cursor position when necessary [\\#927](https://github.com/VSCodeVim/Vim/pull/927) ([rebornix](https://github.com/rebornix))\n- Draw multicursor correctly in Visual Mode [\\#920](https://github.com/VSCodeVim/Vim/pull/920) ([Platzer](https://github.com/Platzer))\n- update internal cursor position per Code selection change [\\#919](https://github.com/VSCodeVim/Vim/pull/919) ([rebornix](https://github.com/rebornix))\n- display register value in reg-cmd, fix \\#830 [\\#915](https://github.com/VSCodeVim/Vim/pull/915) ([Platzer](https://github.com/Platzer))\n- \\[Post 1.0\\] Two way syncing of Vim and Code's configuration [\\#913](https://github.com/VSCodeVim/Vim/pull/913) ([rebornix](https://github.com/rebornix))\n- Macro [\\#894](https://github.com/VSCodeVim/Vim/pull/894) ([rebornix](https://github.com/rebornix))\n\n## [0.3.7](https://github.com/vscodevim/vim/tree/0.3.7) (2016-10-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.6...0.3.7)\n\n- fixes \\#888 [\\#902](https://github.com/VSCodeVim/Vim/pull/902) ([xconverge](https://github.com/xconverge))\n- fixes \\#882 [\\#900](https://github.com/VSCodeVim/Vim/pull/900) ([xconverge](https://github.com/xconverge))\n\n## [0.3.6](https://github.com/vscodevim/vim/tree/0.3.6) (2016-10-12)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.5...0.3.6)\n\n- allow remapping of ctrl-j and ctrl-k in settings.json [\\#891](https://github.com/VSCodeVim/Vim/pull/891) ([xwvvvvwx](https://github.com/xwvvvvwx))\n- Fix visual block x [\\#861](https://github.com/VSCodeVim/Vim/pull/861) ([xconverge](https://github.com/xconverge))\n\n## [0.3.5](https://github.com/vscodevim/vim/tree/0.3.5) (2016-10-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.4...0.3.5)\n\n## [0.3.4](https://github.com/vscodevim/vim/tree/0.3.4) (2016-10-10)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.3...0.3.4)\n\n- Remove unused modehandlers when tabs are closed [\\#865](https://github.com/VSCodeVim/Vim/pull/865) ([xconverge](https://github.com/xconverge))\n- Insert Previous text [\\#768](https://github.com/VSCodeVim/Vim/pull/768) ([rebornix](https://github.com/rebornix))\n\n## [0.3.3](https://github.com/vscodevim/vim/tree/0.3.3) (2016-10-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.3.2...0.3.3)\n\n## [0.3.2](https://github.com/vscodevim/vim/tree/0.3.2) (2016-10-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.3.1...0.3.2)\n\n## [v0.3.1](https://github.com/vscodevim/vim/tree/v0.3.1) (2016-10-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.3.0...v0.3.1)\n\n- Unnecessary quit check on untitled files [\\#855](https://github.com/VSCodeVim/Vim/pull/855) ([xconverge](https://github.com/xconverge))\n- Add new logo icon [\\#852](https://github.com/VSCodeVim/Vim/pull/852) ([kevincoleman](https://github.com/kevincoleman))\n- Fixes arrow navigation to EOL while in insert [\\#838](https://github.com/VSCodeVim/Vim/pull/838) ([xconverge](https://github.com/xconverge))\n- fixes \\#832 [\\#837](https://github.com/VSCodeVim/Vim/pull/837) ([xconverge](https://github.com/xconverge))\n- \\[WIP\\] Use new transformation style in delete and paste [\\#835](https://github.com/VSCodeVim/Vim/pull/835) ([johnfn](https://github.com/johnfn))\n- X eats eol [\\#827](https://github.com/VSCodeVim/Vim/pull/827) ([xconverge](https://github.com/xconverge))\n- Fix to allow A while in visual mode [\\#816](https://github.com/VSCodeVim/Vim/pull/816) ([xconverge](https://github.com/xconverge))\n- Fix issue where could not use I while in visual mode [\\#815](https://github.com/VSCodeVim/Vim/pull/815) ([xconverge](https://github.com/xconverge))\n- fixes \\#784 [\\#814](https://github.com/VSCodeVim/Vim/pull/814) ([xconverge](https://github.com/xconverge))\n\n## [v0.3.0](https://github.com/vscodevim/vim/tree/v0.3.0) (2016-10-03)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.2.0...v0.3.0)\n\n- Show debug console when session launches [\\#821](https://github.com/VSCodeVim/Vim/pull/821) ([xconverge](https://github.com/xconverge))\n- zz in visual, visualline, and visual block mode [\\#820](https://github.com/VSCodeVim/Vim/pull/820) ([xconverge](https://github.com/xconverge))\n- Fixes \\#817 [\\#819](https://github.com/VSCodeVim/Vim/pull/819) ([xconverge](https://github.com/xconverge))\n- Clean up typings [\\#818](https://github.com/VSCodeVim/Vim/pull/818) ([jpoon](https://github.com/jpoon))\n- Updated documentation for linux system clipboard use [\\#813](https://github.com/VSCodeVim/Vim/pull/813) ([xconverge](https://github.com/xconverge))\n- Multi-Cursor Mode v 2.0 [\\#811](https://github.com/VSCodeVim/Vim/pull/811) ([johnfn](https://github.com/johnfn))\n- Fix docs [\\#807](https://github.com/VSCodeVim/Vim/pull/807) ([jpoon](https://github.com/jpoon))\n- Fix bug joining lines with whitespace only next line [\\#799](https://github.com/VSCodeVim/Vim/pull/799) ([mleech](https://github.com/mleech))\n- Add autoindent to README, fix hlsearch default [\\#796](https://github.com/VSCodeVim/Vim/pull/796) ([srenatus](https://github.com/srenatus))\n- Support \"+ system clipboard register \\(\\#780\\) [\\#782](https://github.com/VSCodeVim/Vim/pull/782) ([bdchauvette](https://github.com/bdchauvette))\n- fixes \\#739 [\\#767](https://github.com/VSCodeVim/Vim/pull/767) ([xconverge](https://github.com/xconverge))\n\n## [v0.2.0](https://github.com/vscodevim/vim/tree/v0.2.0) (2016-09-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.11...v0.2.0)\n\n## [v0.1.11](https://github.com/vscodevim/vim/tree/v0.1.11) (2016-09-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.10...v0.1.11)\n\n- Release Pipeline [\\#788](https://github.com/VSCodeVim/Vim/pull/788) ([jpoon](https://github.com/jpoon))\n- Fix delete line with CRLF \\(\\#743\\) [\\#770](https://github.com/VSCodeVim/Vim/pull/770) ([jgoz](https://github.com/jgoz))\n- fixes \\#740 [\\#766](https://github.com/VSCodeVim/Vim/pull/766) ([xconverge](https://github.com/xconverge))\n- fixes \\#764 [\\#765](https://github.com/VSCodeVim/Vim/pull/765) ([xconverge](https://github.com/xconverge))\n- fixes \\#759 [\\#760](https://github.com/VSCodeVim/Vim/pull/760) ([xconverge](https://github.com/xconverge))\n- Register info [\\#756](https://github.com/VSCodeVim/Vim/pull/756) ([rebornix](https://github.com/rebornix))\n- build on extension/test launch [\\#755](https://github.com/VSCodeVim/Vim/pull/755) ([jpoon](https://github.com/jpoon))\n- fixes \\#750 [\\#752](https://github.com/VSCodeVim/Vim/pull/752) ([xconverge](https://github.com/xconverge))\n- clean gulpfile [\\#748](https://github.com/VSCodeVim/Vim/pull/748) ([jpoon](https://github.com/jpoon))\n- Substitute marks [\\#744](https://github.com/VSCodeVim/Vim/pull/744) ([rebornix](https://github.com/rebornix))\n- Read command [\\#736](https://github.com/VSCodeVim/Vim/pull/736) ([domgee](https://github.com/domgee))\n- Doc for enabling repeating j/k for Insider build [\\#733](https://github.com/VSCodeVim/Vim/pull/733) ([octref](https://github.com/octref))\n- Add autoindent setting [\\#726](https://github.com/VSCodeVim/Vim/pull/726) ([octref](https://github.com/octref))\n- Disable Vim Mode in Debug Repl [\\#723](https://github.com/VSCodeVim/Vim/pull/723) ([rebornix](https://github.com/rebornix))\n- \\[WIP\\] Roadmap update [\\#717](https://github.com/VSCodeVim/Vim/pull/717) ([rebornix](https://github.com/rebornix))\n- Editor Scroll [\\#681](https://github.com/VSCodeVim/Vim/pull/681) ([rebornix](https://github.com/rebornix))\n- Implement :wa\\[ll\\] command \\(write all\\) [\\#671](https://github.com/VSCodeVim/Vim/pull/671) ([mleech](https://github.com/mleech))\n- Special keys in Insert Mode [\\#615](https://github.com/VSCodeVim/Vim/pull/615) ([rebornix](https://github.com/rebornix))\n\n## [v0.1.10](https://github.com/vscodevim/vim/tree/v0.1.10) (2016-09-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.9...v0.1.10)\n\n- Align Screen Line commands with latest Code API [\\#724](https://github.com/VSCodeVim/Vim/pull/724) ([rebornix](https://github.com/rebornix))\n- Visual block tests [\\#722](https://github.com/VSCodeVim/Vim/pull/722) ([xconverge](https://github.com/xconverge))\n- Remapper fixes [\\#721](https://github.com/VSCodeVim/Vim/pull/721) ([jpoon](https://github.com/jpoon))\n- fixes \\#718 A and I have cursor in right position now [\\#720](https://github.com/VSCodeVim/Vim/pull/720) ([xconverge](https://github.com/xconverge))\n- fixes \\#696 [\\#715](https://github.com/VSCodeVim/Vim/pull/715) ([xconverge](https://github.com/xconverge))\n- fix \\#690 and other toggle case issues [\\#698](https://github.com/VSCodeVim/Vim/pull/698) ([xconverge](https://github.com/xconverge))\n\n## [v0.1.9](https://github.com/vscodevim/vim/tree/v0.1.9) (2016-09-05)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.8...v0.1.9)\n\n- Update README.md [\\#714](https://github.com/VSCodeVim/Vim/pull/714) ([jpoon](https://github.com/jpoon))\n- Add vim.\\* settings to readme. Fixes \\#503 [\\#713](https://github.com/VSCodeVim/Vim/pull/713) ([jpoon](https://github.com/jpoon))\n- Set diff timeout to 1 second. [\\#712](https://github.com/VSCodeVim/Vim/pull/712) ([johnfn](https://github.com/johnfn))\n- Inserts repeated with . would add many undo points; fix this. [\\#711](https://github.com/VSCodeVim/Vim/pull/711) ([johnfn](https://github.com/johnfn))\n- Hotfix remapping [\\#710](https://github.com/VSCodeVim/Vim/pull/710) ([johnfn](https://github.com/johnfn))\n- Tiny change to issue template. [\\#709](https://github.com/VSCodeVim/Vim/pull/709) ([johnfn](https://github.com/johnfn))\n\n## [v0.1.8](https://github.com/vscodevim/vim/tree/v0.1.8) (2016-09-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.7...v0.1.8)\n\n- Fix race condition with switching active text editor. [\\#705](https://github.com/VSCodeVim/Vim/pull/705) ([johnfn](https://github.com/johnfn))\n- Fix bug with undo on untitled files. [\\#704](https://github.com/VSCodeVim/Vim/pull/704) ([johnfn](https://github.com/johnfn))\n- clear history when content from disk is changed [\\#703](https://github.com/VSCodeVim/Vim/pull/703) ([aminroosta](https://github.com/aminroosta))\n- Fix `\\#` and `\\*` Behaviour [\\#702](https://github.com/VSCodeVim/Vim/pull/702) ([jpoon](https://github.com/jpoon))\n- Fix error when \\<BS\\> at beginning of document [\\#691](https://github.com/VSCodeVim/Vim/pull/691) ([jpoon](https://github.com/jpoon))\n- Handle Ns and fix \\#684 [\\#688](https://github.com/VSCodeVim/Vim/pull/688) ([octref](https://github.com/octref))\n- Use Angle Bracket Notation \\(Fixes \\#64\\) [\\#683](https://github.com/VSCodeVim/Vim/pull/683) ([jpoon](https://github.com/jpoon))\n- Implement ; and , [\\#674](https://github.com/VSCodeVim/Vim/pull/674) ([aminroosta](https://github.com/aminroosta))\n- Some visual block fixes [\\#667](https://github.com/VSCodeVim/Vim/pull/667) ([xconverge](https://github.com/xconverge))\n- implement useSystemClipboard command [\\#665](https://github.com/VSCodeVim/Vim/pull/665) ([aminroosta](https://github.com/aminroosta))\n- Document autoindent option [\\#664](https://github.com/VSCodeVim/Vim/pull/664) ([sectioneight](https://github.com/sectioneight))\n- fix \\#510 [\\#659](https://github.com/VSCodeVim/Vim/pull/659) ([xconverge](https://github.com/xconverge))\n- fix \\#654 [\\#656](https://github.com/VSCodeVim/Vim/pull/656) ([xconverge](https://github.com/xconverge))\n- fix \\#652 [\\#655](https://github.com/VSCodeVim/Vim/pull/655) ([xconverge](https://github.com/xconverge))\n- improves bracket undo behavior when vscode autocloses brackets [\\#649](https://github.com/VSCodeVim/Vim/pull/649) ([xconverge](https://github.com/xconverge))\n- Fix missleading readme instruction [\\#647](https://github.com/VSCodeVim/Vim/pull/647) ([AntonAderum](https://github.com/AntonAderum))\n- Undo behavior when position changes using arrows or mouse [\\#646](https://github.com/VSCodeVim/Vim/pull/646) ([xconverge](https://github.com/xconverge))\n- fix for extra character when double click mouse selection [\\#645](https://github.com/VSCodeVim/Vim/pull/645) ([xconverge](https://github.com/xconverge))\n- fix \\#639 visual block mode minor issues [\\#640](https://github.com/VSCodeVim/Vim/pull/640) ([xconverge](https://github.com/xconverge))\n- Ctrl+a and Ctrl+x now create undo points correctly and can be repeate… [\\#636](https://github.com/VSCodeVim/Vim/pull/636) ([xconverge](https://github.com/xconverge))\n- fix \\#501 some more to include 'k' [\\#635](https://github.com/VSCodeVim/Vim/pull/635) ([xconverge](https://github.com/xconverge))\n- updating the undo tree when using bracket operators slightly [\\#634](https://github.com/VSCodeVim/Vim/pull/634) ([xconverge](https://github.com/xconverge))\n- fix \\#501 [\\#632](https://github.com/VSCodeVim/Vim/pull/632) ([xconverge](https://github.com/xconverge))\n- Fix bug \\#613 \":wq command dows not work\" [\\#630](https://github.com/VSCodeVim/Vim/pull/630) ([Platzer](https://github.com/Platzer))\n- Respect indentation on cc and S [\\#629](https://github.com/VSCodeVim/Vim/pull/629) ([sectioneight](https://github.com/sectioneight))\n- Fix tag markup in roadmap [\\#628](https://github.com/VSCodeVim/Vim/pull/628) ([sectioneight](https://github.com/sectioneight))\n- Allow regex in / search [\\#627](https://github.com/VSCodeVim/Vim/pull/627) ([sectioneight](https://github.com/sectioneight))\n- Synonyms [\\#621](https://github.com/VSCodeVim/Vim/pull/621) ([rebornix](https://github.com/rebornix))\n- Implement tag movements [\\#619](https://github.com/VSCodeVim/Vim/pull/619) ([sectioneight](https://github.com/sectioneight))\n\n## [v0.1.7](https://github.com/vscodevim/vim/tree/v0.1.7) (2016-08-14)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.6...v0.1.7)\n\n- Add support Y in visual mode [\\#597](https://github.com/VSCodeVim/Vim/pull/597) ([shotaAkasaka](https://github.com/shotaAkasaka))\n- Sentence selection [\\#592](https://github.com/VSCodeVim/Vim/pull/592) ([rebornix](https://github.com/rebornix))\n- fix C or cc kill the empty line [\\#591](https://github.com/VSCodeVim/Vim/pull/591) ([shotaAkasaka](https://github.com/shotaAkasaka))\n- Added Non-Recursive mapping capability. Fixes issue \\#408 [\\#589](https://github.com/VSCodeVim/Vim/pull/589) ([somkun](https://github.com/somkun))\n- Vim Settings [\\#508](https://github.com/VSCodeVim/Vim/pull/508) ([rebornix](https://github.com/rebornix))\n\n## [v0.1.6](https://github.com/vscodevim/vim/tree/v0.1.6) (2016-08-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.5...v0.1.6)\n\n- \\[WIP\\] Visual block mode [\\#469](https://github.com/VSCodeVim/Vim/pull/469) ([johnfn](https://github.com/johnfn))\n\n## [v0.1.5](https://github.com/vscodevim/vim/tree/v0.1.5) (2016-08-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.1.5...v0.1.5)\n\n## [0.1.5](https://github.com/vscodevim/vim/tree/0.1.5) (2016-08-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.4...0.1.5)\n\n- Replace mode [\\#580](https://github.com/VSCodeVim/Vim/pull/580) ([rebornix](https://github.com/rebornix))\n- Fix for issue \\#571 [\\#579](https://github.com/VSCodeVim/Vim/pull/579) ([xconverge](https://github.com/xconverge))\n- OS X non-global key repeat fix [\\#577](https://github.com/VSCodeVim/Vim/pull/577) ([jimray](https://github.com/jimray))\n- Hack to mitigate \\#569 and prevent extension from locking up [\\#576](https://github.com/VSCodeVim/Vim/pull/576) ([jpoon](https://github.com/jpoon))\n- Fix binding of control-keys [\\#575](https://github.com/VSCodeVim/Vim/pull/575) ([sectioneight](https://github.com/sectioneight))\n- Fix test regression [\\#560](https://github.com/VSCodeVim/Vim/pull/560) ([rebornix](https://github.com/rebornix))\n- Fix gt,gT numeric prefix [\\#559](https://github.com/VSCodeVim/Vim/pull/559) ([rebornix](https://github.com/rebornix))\n- Fix incorrect cursor location after deleting linebreak \\(fixes \\#550\\) [\\#551](https://github.com/VSCodeVim/Vim/pull/551) ([thomasboyt](https://github.com/thomasboyt))\n- Support gd [\\#547](https://github.com/VSCodeVim/Vim/pull/547) ([johnfn](https://github.com/johnfn))\n- Add support for S [\\#546](https://github.com/VSCodeVim/Vim/pull/546) ([glibsm](https://github.com/glibsm))\n- update roadmap [\\#545](https://github.com/VSCodeVim/Vim/pull/545) ([rebornix](https://github.com/rebornix))\n- Support \"{char} registers and clipboard access via \"\\* register. [\\#543](https://github.com/VSCodeVim/Vim/pull/543) ([aminroosta](https://github.com/aminroosta))\n- Added CommandGoToOtherEndOfHiglightedText - \\#526 [\\#539](https://github.com/VSCodeVim/Vim/pull/539) ([Platzer](https://github.com/Platzer))\n- Move sections [\\#533](https://github.com/VSCodeVim/Vim/pull/533) ([rebornix](https://github.com/rebornix))\n- Substitute with no range or marks [\\#525](https://github.com/VSCodeVim/Vim/pull/525) ([rebornix](https://github.com/rebornix))\n- Correct Fold behavior and update roadmap [\\#524](https://github.com/VSCodeVim/Vim/pull/524) ([rebornix](https://github.com/rebornix))\n- Make \\<delete\\> repeatable in Normal Mode. Fix \\#394 [\\#514](https://github.com/VSCodeVim/Vim/pull/514) ([octref](https://github.com/octref))\n- Screen lines and characters. [\\#486](https://github.com/VSCodeVim/Vim/pull/486) ([rebornix](https://github.com/rebornix))\n\n## [v0.1.4](https://github.com/vscodevim/vim/tree/v0.1.4) (2016-07-28)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.3...v0.1.4)\n\n- Implement increment and decrement operators [\\#515](https://github.com/VSCodeVim/Vim/pull/515) ([sectioneight](https://github.com/sectioneight))\n- Fix \\#502 [\\#509](https://github.com/VSCodeVim/Vim/pull/509) ([rebornix](https://github.com/rebornix))\n- Add tabs movement and fix tab command with correct counting [\\#507](https://github.com/VSCodeVim/Vim/pull/507) ([rebornix](https://github.com/rebornix))\n- Omit first word in hash backwards search [\\#506](https://github.com/VSCodeVim/Vim/pull/506) ([sectioneight](https://github.com/sectioneight))\n- Turn around for cursor problem [\\#505](https://github.com/VSCodeVim/Vim/pull/505) ([rebornix](https://github.com/rebornix))\n- Fix instructions for setting key bindings [\\#499](https://github.com/VSCodeVim/Vim/pull/499) ([positron](https://github.com/positron))\n- Code clean-up. Remove dead code. [\\#497](https://github.com/VSCodeVim/Vim/pull/497) ([jpoon](https://github.com/jpoon))\n- Merge history changes into a single operation. Fixes \\#427 [\\#496](https://github.com/VSCodeVim/Vim/pull/496) ([infogulch](https://github.com/infogulch))\n- Fix \\#438 - Limit the number of matches, and try to only recalculate when the searchString changes, or the document changes [\\#494](https://github.com/VSCodeVim/Vim/pull/494) ([roblourens](https://github.com/roblourens))\n- CommandFold should be available in Normal mode [\\#493](https://github.com/VSCodeVim/Vim/pull/493) ([aminroosta](https://github.com/aminroosta))\n- Fix % movement when not on opening character [\\#490](https://github.com/VSCodeVim/Vim/pull/490) ([sectioneight](https://github.com/sectioneight))\n- Suggest npm run compile in CONTRIBUTING page [\\#488](https://github.com/VSCodeVim/Vim/pull/488) ([aminroosta](https://github.com/aminroosta))\n- Implement quoted text objects [\\#483](https://github.com/VSCodeVim/Vim/pull/483) ([sectioneight](https://github.com/sectioneight))\n- Fix \\#338 - add gt, gT support [\\#482](https://github.com/VSCodeVim/Vim/pull/482) ([arussellk](https://github.com/arussellk))\n- Set correct cursor and selection after code format. [\\#478](https://github.com/VSCodeVim/Vim/pull/478) ([rebornix](https://github.com/rebornix))\n- CJK in all modes [\\#475](https://github.com/VSCodeVim/Vim/pull/475) ([rebornix](https://github.com/rebornix))\n- Fix \\#358. [\\#399](https://github.com/VSCodeVim/Vim/pull/399) ([rebornix](https://github.com/rebornix))\n- Word in visual mode [\\#385](https://github.com/VSCodeVim/Vim/pull/385) ([rebornix](https://github.com/rebornix))\n\n## [v0.1.3](https://github.com/vscodevim/vim/tree/v0.1.3) (2016-07-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.2...v0.1.3)\n\n- Fix wrong command for ctrl+f [\\#476](https://github.com/VSCodeVim/Vim/pull/476) ([rebornix](https://github.com/rebornix))\n- Fix regressions in text objects [\\#473](https://github.com/VSCodeVim/Vim/pull/473) ([sectioneight](https://github.com/sectioneight))\n- Fix handling of opener for nested text objects [\\#472](https://github.com/VSCodeVim/Vim/pull/472) ([sectioneight](https://github.com/sectioneight))\n- Implement square-bracket text object [\\#467](https://github.com/VSCodeVim/Vim/pull/467) ([sectioneight](https://github.com/sectioneight))\n- Add support for failed motions [\\#466](https://github.com/VSCodeVim/Vim/pull/466) ([johnfn](https://github.com/johnfn))\n- Add test-specific tslint [\\#464](https://github.com/VSCodeVim/Vim/pull/464) ([sectioneight](https://github.com/sectioneight))\n- Initialize mode and cursor after startup [\\#462](https://github.com/VSCodeVim/Vim/pull/462) ([rebornix](https://github.com/rebornix))\n- FixTabStops [\\#461](https://github.com/VSCodeVim/Vim/pull/461) ([rebornix](https://github.com/rebornix))\n- Convert 4 space tab to 2 space tab. [\\#460](https://github.com/VSCodeVim/Vim/pull/460) ([rebornix](https://github.com/rebornix))\n- Enforce TSLint. Closes \\#456 [\\#459](https://github.com/VSCodeVim/Vim/pull/459) ([jpoon](https://github.com/jpoon))\n- Add back missing control-c registration [\\#455](https://github.com/VSCodeVim/Vim/pull/455) ([sectioneight](https://github.com/sectioneight))\n- Fix checkmark syntax on roadmap [\\#454](https://github.com/VSCodeVim/Vim/pull/454) ([sectioneight](https://github.com/sectioneight))\n- Add support for ctrl+w in insert mode [\\#453](https://github.com/VSCodeVim/Vim/pull/453) ([sectioneight](https://github.com/sectioneight))\n- Implement additional text object commands [\\#450](https://github.com/VSCodeVim/Vim/pull/450) ([sectioneight](https://github.com/sectioneight))\n- Remove custom keyboard mapping \\(fixes \\#432\\). Fix duplicate definition… [\\#447](https://github.com/VSCodeVim/Vim/pull/447) ([jpoon](https://github.com/jpoon))\n- Fix \\#341 CJK Problem. [\\#446](https://github.com/VSCodeVim/Vim/pull/446) ([rebornix](https://github.com/rebornix))\n- Fix \\#426 [\\#445](https://github.com/VSCodeVim/Vim/pull/445) ([arussellk](https://github.com/arussellk))\n- Read TextEditor options from active editor [\\#444](https://github.com/VSCodeVim/Vim/pull/444) ([rebornix](https://github.com/rebornix))\n- \\[p, \\[p, gp and gP [\\#412](https://github.com/VSCodeVim/Vim/pull/412) ([rebornix](https://github.com/rebornix))\n- Open file in new window. [\\#404](https://github.com/VSCodeVim/Vim/pull/404) ([rebornix](https://github.com/rebornix))\n\n## [v0.1.2](https://github.com/vscodevim/vim/tree/v0.1.2) (2016-07-13)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1.1...v0.1.2)\n\n- Fix spec for otherModesKeyBindings to match insert [\\#434](https://github.com/VSCodeVim/Vim/pull/434) ([sectioneight](https://github.com/sectioneight))\n- Use TypeScript 2.0 and use strictNullChecks. [\\#431](https://github.com/VSCodeVim/Vim/pull/431) ([johnfn](https://github.com/johnfn))\n- Ctrl+U and Ctrl+D [\\#430](https://github.com/VSCodeVim/Vim/pull/430) ([rebornix](https://github.com/rebornix))\n- Fix\\#369. `dw` eats EOF. [\\#428](https://github.com/VSCodeVim/Vim/pull/428) ([rebornix](https://github.com/rebornix))\n- Include vscode typings [\\#419](https://github.com/VSCodeVim/Vim/pull/419) ([jpoon](https://github.com/jpoon))\n- Fix ctrl+b, ctrl+f [\\#418](https://github.com/VSCodeVim/Vim/pull/418) ([jpoon](https://github.com/jpoon))\n- Fix \\#397. [\\#413](https://github.com/VSCodeVim/Vim/pull/413) ([rebornix](https://github.com/rebornix))\n- Fix layout mistake in Contributing and gulp typo [\\#411](https://github.com/VSCodeVim/Vim/pull/411) ([frederickfogerty](https://github.com/frederickfogerty))\n\n## [v0.1.1](https://github.com/vscodevim/vim/tree/v0.1.1) (2016-07-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.1...v0.1.1)\n\n- Fix \\#414. [\\#415](https://github.com/VSCodeVim/Vim/pull/415) ([rebornix](https://github.com/rebornix))\n- Substitute [\\#376](https://github.com/VSCodeVim/Vim/pull/376) ([rebornix](https://github.com/rebornix))\n\n## [v0.1](https://github.com/vscodevim/vim/tree/v0.1) (2016-07-08)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.28...v0.1)\n\n- Fix Roadmap link in Readme [\\#405](https://github.com/VSCodeVim/Vim/pull/405) ([frederickfogerty](https://github.com/frederickfogerty))\n- Fix TS2318 and ignore .vscode-test folder [\\#400](https://github.com/VSCodeVim/Vim/pull/400) ([rebornix](https://github.com/rebornix))\n- Update window command status [\\#398](https://github.com/VSCodeVim/Vim/pull/398) ([rebornix](https://github.com/rebornix))\n- `workbench.files.action.closeAllFiles` is deprecated. [\\#395](https://github.com/VSCodeVim/Vim/pull/395) ([rebornix](https://github.com/rebornix))\n- Basic Key Remapping [\\#390](https://github.com/VSCodeVim/Vim/pull/390) ([johnfn](https://github.com/johnfn))\n- Use correct API for file open. [\\#388](https://github.com/VSCodeVim/Vim/pull/388) ([rebornix](https://github.com/rebornix))\n- Use Arrows in Insert Mode. [\\#387](https://github.com/VSCodeVim/Vim/pull/387) ([rebornix](https://github.com/rebornix))\n- Marks [\\#386](https://github.com/VSCodeVim/Vim/pull/386) ([johnfn](https://github.com/johnfn))\n- Arrows [\\#383](https://github.com/VSCodeVim/Vim/pull/383) ([rebornix](https://github.com/rebornix))\n- Edit File [\\#372](https://github.com/VSCodeVim/Vim/pull/372) ([rebornix](https://github.com/rebornix))\n- Unclosed brackets [\\#371](https://github.com/VSCodeVim/Vim/pull/371) ([rebornix](https://github.com/rebornix))\n- Manual history tracking [\\#370](https://github.com/VSCodeVim/Vim/pull/370) ([johnfn](https://github.com/johnfn))\n- Tabs [\\#368](https://github.com/VSCodeVim/Vim/pull/368) ([rebornix](https://github.com/rebornix))\n- Rebornix switch pane [\\#367](https://github.com/VSCodeVim/Vim/pull/367) ([johnfn](https://github.com/johnfn))\n- Support `C` [\\#366](https://github.com/VSCodeVim/Vim/pull/366) ([rebornix](https://github.com/rebornix))\n- Add Ncc support and revise cc behavior [\\#365](https://github.com/VSCodeVim/Vim/pull/365) ([rebornix](https://github.com/rebornix))\n- Bring Ctrl keys back [\\#364](https://github.com/VSCodeVim/Vim/pull/364) ([rebornix](https://github.com/rebornix))\n- \\[WIP\\]: Switch Window [\\#363](https://github.com/VSCodeVim/Vim/pull/363) ([rebornix](https://github.com/rebornix))\n- Sentence [\\#362](https://github.com/VSCodeVim/Vim/pull/362) ([rebornix](https://github.com/rebornix))\n- Add config option for nonblinking block cursor. [\\#361](https://github.com/VSCodeVim/Vim/pull/361) ([johnfn](https://github.com/johnfn))\n- Refactor search [\\#357](https://github.com/VSCodeVim/Vim/pull/357) ([johnfn](https://github.com/johnfn))\n- WriteQuit [\\#354](https://github.com/VSCodeVim/Vim/pull/354) ([srepollock](https://github.com/srepollock))\n\n## [v0.0.28](https://github.com/vscodevim/vim/tree/v0.0.28) (2016-06-24)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.27...v0.0.28)\n\n- Implement \\<count\\>yy [\\#351](https://github.com/VSCodeVim/Vim/pull/351) ([rebornix](https://github.com/rebornix))\n- Align TextEditorOptions between test code and workspace [\\#350](https://github.com/VSCodeVim/Vim/pull/350) ([rebornix](https://github.com/rebornix))\n- Uppercase support [\\#349](https://github.com/VSCodeVim/Vim/pull/349) ([johnfn](https://github.com/johnfn))\n- Add format code support. Fix \\#308. [\\#348](https://github.com/VSCodeVim/Vim/pull/348) ([rebornix](https://github.com/rebornix))\n\n## [v0.0.27](https://github.com/vscodevim/vim/tree/v0.0.27) (2016-06-23)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.26...v0.0.27)\n\n## [v0.0.26](https://github.com/vscodevim/vim/tree/v0.0.26) (2016-06-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.0.26...v0.0.26)\n\n## [0.0.26](https://github.com/vscodevim/vim/tree/0.0.26) (2016-06-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.0.25...0.0.26)\n\n- Star and hash [\\#335](https://github.com/VSCodeVim/Vim/pull/335) ([johnfn](https://github.com/johnfn))\n- Tilde key toggles case and moves forwards [\\#325](https://github.com/VSCodeVim/Vim/pull/325) ([markrendle](https://github.com/markrendle))\n- Pressing Enter moves cursor to start of next line [\\#324](https://github.com/VSCodeVim/Vim/pull/324) ([markrendle](https://github.com/markrendle))\n- Add infrastructure for repeatable commands. [\\#322](https://github.com/VSCodeVim/Vim/pull/322) ([johnfn](https://github.com/johnfn))\n- Add support for 'U' uppercase [\\#312](https://github.com/VSCodeVim/Vim/pull/312) ([rebornix](https://github.com/rebornix))\n\n## [0.0.25](https://github.com/vscodevim/vim/tree/0.0.25) (2016-06-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.25...0.0.25)\n\n## [v0.0.25](https://github.com/vscodevim/vim/tree/v0.0.25) (2016-06-20)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.0.24...v0.0.25)\n\n- Repeated motions [\\#321](https://github.com/VSCodeVim/Vim/pull/321) ([johnfn](https://github.com/johnfn))\n\n## [0.0.24](https://github.com/vscodevim/vim/tree/0.0.24) (2016-06-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.24...0.0.24)\n\n## [v0.0.24](https://github.com/vscodevim/vim/tree/v0.0.24) (2016-06-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.23...v0.0.24)\n\n## [v0.0.23](https://github.com/vscodevim/vim/tree/v0.0.23) (2016-06-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.0.23...v0.0.23)\n\n## [0.0.23](https://github.com/vscodevim/vim/tree/0.0.23) (2016-06-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.22...0.0.23)\n\n- Add %. [\\#319](https://github.com/VSCodeVim/Vim/pull/319) ([johnfn](https://github.com/johnfn))\n- @darrenweston's test improvements + more work [\\#316](https://github.com/VSCodeVim/Vim/pull/316) ([johnfn](https://github.com/johnfn))\n\n## [v0.0.22](https://github.com/vscodevim/vim/tree/v0.0.22) (2016-06-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.21...v0.0.22)\n\n## [v0.0.21](https://github.com/vscodevim/vim/tree/v0.0.21) (2016-06-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.20...v0.0.21)\n\n- Fix visual line selection from bottom to top. [\\#307](https://github.com/VSCodeVim/Vim/pull/307) ([johnfn](https://github.com/johnfn))\n- Fix autocomplete [\\#304](https://github.com/VSCodeVim/Vim/pull/304) ([johnfn](https://github.com/johnfn))\n- Select into visual mode [\\#302](https://github.com/VSCodeVim/Vim/pull/302) ([johnfn](https://github.com/johnfn))\n- Refactor dot [\\#294](https://github.com/VSCodeVim/Vim/pull/294) ([johnfn](https://github.com/johnfn))\n\n## [v0.0.20](https://github.com/vscodevim/vim/tree/v0.0.20) (2016-06-13)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.19...v0.0.20)\n\n- Add simpler test mechanism and convert some tests [\\#292](https://github.com/VSCodeVim/Vim/pull/292) ([darrenweston](https://github.com/darrenweston))\n- Refactor motions [\\#288](https://github.com/VSCodeVim/Vim/pull/288) ([johnfn](https://github.com/johnfn))\n- Search [\\#277](https://github.com/VSCodeVim/Vim/pull/277) ([johnfn](https://github.com/johnfn))\n- Tests [\\#275](https://github.com/VSCodeVim/Vim/pull/275) ([johnfn](https://github.com/johnfn))\n- Add P. [\\#262](https://github.com/VSCodeVim/Vim/pull/262) ([johnfn](https://github.com/johnfn))\n- Add zz. [\\#261](https://github.com/VSCodeVim/Vim/pull/261) ([johnfn](https://github.com/johnfn))\n- Added some 'r' tests [\\#260](https://github.com/VSCodeVim/Vim/pull/260) ([darrenweston](https://github.com/darrenweston))\n- Add r. [\\#252](https://github.com/VSCodeVim/Vim/pull/252) ([johnfn](https://github.com/johnfn))\n- J [\\#251](https://github.com/VSCodeVim/Vim/pull/251) ([johnfn](https://github.com/johnfn))\n- Dot key. [\\#249](https://github.com/VSCodeVim/Vim/pull/249) ([johnfn](https://github.com/johnfn))\n- No longer special case insert mode keys. [\\#246](https://github.com/VSCodeVim/Vim/pull/246) ([johnfn](https://github.com/johnfn))\n- Use vscode built in support for block cursors [\\#245](https://github.com/VSCodeVim/Vim/pull/245) ([Paxxi](https://github.com/Paxxi))\n\n## [v0.0.19](https://github.com/vscodevim/vim/tree/v0.0.19) (2016-06-07)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.18...v0.0.19)\n\n- Add f, F, t and T motions [\\#244](https://github.com/VSCodeVim/Vim/pull/244) ([johnfn](https://github.com/johnfn))\n- Add visual line mode tests. [\\#243](https://github.com/VSCodeVim/Vim/pull/243) ([johnfn](https://github.com/johnfn))\n- List keys individually rather than as a string. [\\#242](https://github.com/VSCodeVim/Vim/pull/242) ([johnfn](https://github.com/johnfn))\n- Fix vims wonky visual eol behavior [\\#241](https://github.com/VSCodeVim/Vim/pull/241) ([johnfn](https://github.com/johnfn))\n- Add Visual Line mode [\\#240](https://github.com/VSCodeVim/Vim/pull/240) ([johnfn](https://github.com/johnfn))\n- Move word special case to appropriate place. [\\#239](https://github.com/VSCodeVim/Vim/pull/239) ([johnfn](https://github.com/johnfn))\n- Cleanup cursor drawing and remove Motion class [\\#238](https://github.com/VSCodeVim/Vim/pull/238) ([johnfn](https://github.com/johnfn))\n- dd, cc & yy tests [\\#237](https://github.com/VSCodeVim/Vim/pull/237) ([johnfn](https://github.com/johnfn))\n- Add cc/yy/dd. [\\#236](https://github.com/VSCodeVim/Vim/pull/236) ([johnfn](https://github.com/johnfn))\n- Add s keybinding [\\#235](https://github.com/VSCodeVim/Vim/pull/235) ([Paxxi](https://github.com/Paxxi))\n- Refactor commands \\[WIP\\] [\\#234](https://github.com/VSCodeVim/Vim/pull/234) ([johnfn](https://github.com/johnfn))\n- Don't use ctrl-c to leave insert mode by default. [\\#233](https://github.com/VSCodeVim/Vim/pull/233) ([johnfn](https://github.com/johnfn))\n- Add rudimentary register implementation. [\\#232](https://github.com/VSCodeVim/Vim/pull/232) ([johnfn](https://github.com/johnfn))\n- Rewrite normal mode tests. [\\#231](https://github.com/VSCodeVim/Vim/pull/231) ([johnfn](https://github.com/johnfn))\n- Rewrite Normal Mode tests to use the ModeHandler interface. [\\#230](https://github.com/VSCodeVim/Vim/pull/230) ([johnfn](https://github.com/johnfn))\n- Refactor CommandKeyMap [\\#228](https://github.com/VSCodeVim/Vim/pull/228) ([jpoon](https://github.com/jpoon))\n- Add yank support for Visual mode [\\#217](https://github.com/VSCodeVim/Vim/pull/217) ([pjvds](https://github.com/pjvds))\n\n## [v0.0.18](https://github.com/vscodevim/vim/tree/v0.0.18) (2016-05-19)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.17...v0.0.18)\n\n- Install Gulp for Travis [\\#225](https://github.com/VSCodeVim/Vim/pull/225) ([jpoon](https://github.com/jpoon))\n- Update to vscode 0.10.12 APIs [\\#224](https://github.com/VSCodeVim/Vim/pull/224) ([jpoon](https://github.com/jpoon))\n\n## [v0.0.17](https://github.com/vscodevim/vim/tree/v0.0.17) (2016-05-17)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.16...v0.0.17)\n\n- Added basic fold commands zc, zo, zC, zO. [\\#222](https://github.com/VSCodeVim/Vim/pull/222) ([geksilla](https://github.com/geksilla))\n- keymap configurations only override defaults that are changed [\\#221](https://github.com/VSCodeVim/Vim/pull/221) ([adiviness](https://github.com/adiviness))\n- Added basic support for rebinding keys. [\\#219](https://github.com/VSCodeVim/Vim/pull/219) ([Lindenk](https://github.com/Lindenk))\n- waffle.io Badge [\\#216](https://github.com/VSCodeVim/Vim/pull/216) ([waffle-iron](https://github.com/waffle-iron))\n- Add check mark to D key in README [\\#215](https://github.com/VSCodeVim/Vim/pull/215) ([pjvds](https://github.com/pjvds))\n\n## [v0.0.16](https://github.com/vscodevim/vim/tree/v0.0.16) (2016-05-03)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.15...v0.0.16)\n\n- I think this may fix the build failure. [\\#209](https://github.com/VSCodeVim/Vim/pull/209) ([edthedev](https://github.com/edthedev))\n- Support for copy and p command [\\#208](https://github.com/VSCodeVim/Vim/pull/208) ([petegleeson](https://github.com/petegleeson))\n- Fix issue / key doesn't search current file [\\#205](https://github.com/VSCodeVim/Vim/pull/205) ([tnngo2](https://github.com/tnngo2))\n- Fixes Incorrect Cursor Position after Transition into Normal Mode [\\#202](https://github.com/VSCodeVim/Vim/pull/202) ([dpbackes](https://github.com/dpbackes))\n- Fixes Issue with Cursor Position After 'dw' [\\#200](https://github.com/VSCodeVim/Vim/pull/200) ([dpbackes](https://github.com/dpbackes))\n\n## [v0.0.15](https://github.com/vscodevim/vim/tree/v0.0.15) (2016-03-22)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.14...v0.0.15)\n\n- Bug fixes [\\#192](https://github.com/VSCodeVim/Vim/pull/192) ([jpoon](https://github.com/jpoon))\n\n## [v0.0.14](https://github.com/vscodevim/vim/tree/v0.0.14) (2016-03-21)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.13...v0.0.14)\n\n- Bug fixes [\\#191](https://github.com/VSCodeVim/Vim/pull/191) ([jpoon](https://github.com/jpoon))\n- Search '/' in Command Mode [\\#190](https://github.com/VSCodeVim/Vim/pull/190) ([jpoon](https://github.com/jpoon))\n\n## [v0.0.13](https://github.com/vscodevim/vim/tree/v0.0.13) (2016-03-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.12...v0.0.13)\n\n- fix appveyor build [\\#189](https://github.com/VSCodeVim/Vim/pull/189) ([jpoon](https://github.com/jpoon))\n- Fixup/highlight eol char [\\#182](https://github.com/VSCodeVim/Vim/pull/182) ([khisakuni](https://github.com/khisakuni))\n- c commands and ge motions [\\#180](https://github.com/VSCodeVim/Vim/pull/180) ([frarees](https://github.com/frarees))\n- add github_token to appveyor/travis [\\#178](https://github.com/VSCodeVim/Vim/pull/178) ([jpoon](https://github.com/jpoon))\n- Commands can write to status bar [\\#177](https://github.com/VSCodeVim/Vim/pull/177) ([frarees](https://github.com/frarees))\n- Wait for test files to get written [\\#175](https://github.com/VSCodeVim/Vim/pull/175) ([frarees](https://github.com/frarees))\n- d{motion} support [\\#174](https://github.com/VSCodeVim/Vim/pull/174) ([frarees](https://github.com/frarees))\n\n## [v0.0.12](https://github.com/vscodevim/vim/tree/v0.0.12) (2016-03-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.11...v0.0.12)\n\n- Spanish keyboard mappings [\\#169](https://github.com/VSCodeVim/Vim/pull/169) ([frarees](https://github.com/frarees))\n- Fix visual mode activated on insert mode [\\#168](https://github.com/VSCodeVim/Vim/pull/168) ([frarees](https://github.com/frarees))\n- Fix lexer unreachable code causing build error [\\#165](https://github.com/VSCodeVim/Vim/pull/165) ([frarees](https://github.com/frarees))\n- Update Package Dependencies. Remove Ctrl+C [\\#163](https://github.com/VSCodeVim/Vim/pull/163) ([jpoon](https://github.com/jpoon))\n- Add E \\(end of WORD\\), and fix up e \\(end of word\\). [\\#160](https://github.com/VSCodeVim/Vim/pull/160) ([tma-isbx](https://github.com/tma-isbx))\n- Fix for block cursor in insert mode [\\#154](https://github.com/VSCodeVim/Vim/pull/154) ([sWW26](https://github.com/sWW26))\n- Move private methods and update readme [\\#153](https://github.com/VSCodeVim/Vim/pull/153) ([tma-isbx](https://github.com/tma-isbx))\n- Visual Mode + Rudimentary Operators [\\#144](https://github.com/VSCodeVim/Vim/pull/144) ([johnfn](https://github.com/johnfn))\n\n## [v0.0.11](https://github.com/vscodevim/vim/tree/v0.0.11) (2016-02-18)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.10...v0.0.11)\n\n- Upgrade to Typings as TSD has been deprecated [\\#152](https://github.com/VSCodeVim/Vim/pull/152) ([jpoon](https://github.com/jpoon))\n- Convert test to async/await style. [\\#150](https://github.com/VSCodeVim/Vim/pull/150) ([johnfn](https://github.com/johnfn))\n- Capital W/B word movement [\\#147](https://github.com/VSCodeVim/Vim/pull/147) ([tma-isbx](https://github.com/tma-isbx))\n- Implement 'X' in normal mode \\(backspace\\) [\\#145](https://github.com/VSCodeVim/Vim/pull/145) ([tma-isbx](https://github.com/tma-isbx))\n- Fix b motion. [\\#143](https://github.com/VSCodeVim/Vim/pull/143) ([johnfn](https://github.com/johnfn))\n- Implement ctrl+f/ctrl+b \\(PageDown/PageUp\\) [\\#142](https://github.com/VSCodeVim/Vim/pull/142) ([tma-isbx](https://github.com/tma-isbx))\n- \\[\\#127\\] Fix 'x' behavior at EOL [\\#141](https://github.com/VSCodeVim/Vim/pull/141) ([tma-isbx](https://github.com/tma-isbx))\n- Implement % to jump to matching brace [\\#140](https://github.com/VSCodeVim/Vim/pull/140) ([tma-isbx](https://github.com/tma-isbx))\n- Add ctrl-c. [\\#139](https://github.com/VSCodeVim/Vim/pull/139) ([johnfn](https://github.com/johnfn))\n- Fix word and back-word motions, and fix tests. [\\#138](https://github.com/VSCodeVim/Vim/pull/138) ([johnfn](https://github.com/johnfn))\n- Convert to ES6, Promises, async and await. [\\#137](https://github.com/VSCodeVim/Vim/pull/137) ([johnfn](https://github.com/johnfn))\n\n## [v0.0.10](https://github.com/vscodevim/vim/tree/v0.0.10) (2016-02-01)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.9...v0.0.10)\n\n- Implement % to jump to matching brace [\\#134](https://github.com/VSCodeVim/Vim/pull/134) ([tma-isbx](https://github.com/tma-isbx))\n- Add paragraph motions [\\#133](https://github.com/VSCodeVim/Vim/pull/133) ([johnfn](https://github.com/johnfn))\n- Add Swedish keyboard layout [\\#130](https://github.com/VSCodeVim/Vim/pull/130) ([AntonAderum](https://github.com/AntonAderum))\n\n## [v0.0.9](https://github.com/vscodevim/vim/tree/v0.0.9) (2016-01-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/0.0.9...v0.0.9)\n\n## [0.0.9](https://github.com/vscodevim/vim/tree/0.0.9) (2016-01-06)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.8...0.0.9)\n\n- added danish keyboard layout - fix issue \\#124 [\\#125](https://github.com/VSCodeVim/Vim/pull/125) ([kedde](https://github.com/kedde))\n- Delete Right when user presses x [\\#122](https://github.com/VSCodeVim/Vim/pull/122) ([sharpoverride](https://github.com/sharpoverride))\n\n## [v0.0.8](https://github.com/vscodevim/vim/tree/v0.0.8) (2016-01-03)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.7...v0.0.8)\n\n## [v0.0.7](https://github.com/vscodevim/vim/tree/v0.0.7) (2016-01-03)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.6...v0.0.7)\n\n- Block Cursor [\\#120](https://github.com/VSCodeVim/Vim/pull/120) ([jpoon](https://github.com/jpoon))\n- BugFix: swapped cursor and caret. desired column not updated properly [\\#119](https://github.com/VSCodeVim/Vim/pull/119) ([jpoon](https://github.com/jpoon))\n- Readme: update with keyboard configuration [\\#116](https://github.com/VSCodeVim/Vim/pull/116) ([jpoon](https://github.com/jpoon))\n- Tests: Enable all tests to be run in Travis CI [\\#115](https://github.com/VSCodeVim/Vim/pull/115) ([jpoon](https://github.com/jpoon))\n- Cleanup [\\#114](https://github.com/VSCodeVim/Vim/pull/114) ([jpoon](https://github.com/jpoon))\n\n## [v0.0.6](https://github.com/vscodevim/vim/tree/v0.0.6) (2015-12-30)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.5...v0.0.6)\n\n- Cleanup [\\#113](https://github.com/VSCodeVim/Vim/pull/113) ([jpoon](https://github.com/jpoon))\n- Motion Fixes [\\#112](https://github.com/VSCodeVim/Vim/pull/112) ([jpoon](https://github.com/jpoon))\n- Fix character position persistence on up/down commands, add : \"e\", \"0\", and fix \"^\" [\\#109](https://github.com/VSCodeVim/Vim/pull/109) ([corymickelson](https://github.com/corymickelson))\n\n## [v0.0.5](https://github.com/vscodevim/vim/tree/v0.0.5) (2015-12-09)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.3...v0.0.5)\n\n## [v0.0.3](https://github.com/vscodevim/vim/tree/v0.0.3) (2015-12-04)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.2...v0.0.3)\n\n- Promisify [\\#92](https://github.com/VSCodeVim/Vim/pull/92) ([jpoon](https://github.com/jpoon))\n- fix cursor position after entering command mode \\(again\\) [\\#91](https://github.com/VSCodeVim/Vim/pull/91) ([kimitake](https://github.com/kimitake))\n- Refactor motion. [\\#87](https://github.com/VSCodeVim/Vim/pull/87) ([jpoon](https://github.com/jpoon))\n- Added CONTRIBUTING doc [\\#83](https://github.com/VSCodeVim/Vim/pull/83) ([markrendle](https://github.com/markrendle))\n- readme: update more detailed contributing info [\\#80](https://github.com/VSCodeVim/Vim/pull/80) ([jpoon](https://github.com/jpoon))\n- Created tests for modeInsert [\\#79](https://github.com/VSCodeVim/Vim/pull/79) ([benjaminRomano](https://github.com/benjaminRomano))\n- gulp: add trim-whitespace task [\\#78](https://github.com/VSCodeVim/Vim/pull/78) ([jpoon](https://github.com/jpoon))\n- implement correct w,b motion behaviour [\\#76](https://github.com/VSCodeVim/Vim/pull/76) ([adriaanp](https://github.com/adriaanp))\n- Fix PR builds [\\#75](https://github.com/VSCodeVim/Vim/pull/75) ([jpoon](https://github.com/jpoon))\n- Tests [\\#74](https://github.com/VSCodeVim/Vim/pull/74) ([jpoon](https://github.com/jpoon))\n- Add commands support for 'gg' and 'G' [\\#71](https://github.com/VSCodeVim/Vim/pull/71) ([liushuping](https://github.com/liushuping))\n- fix line end determination for a, A, \\$ [\\#68](https://github.com/VSCodeVim/Vim/pull/68) ([kimitake](https://github.com/kimitake))\n- '\\$' and '^' for Moving to beginning and end of line [\\#66](https://github.com/VSCodeVim/Vim/pull/66) ([josephliccini](https://github.com/josephliccini))\n- support x command [\\#65](https://github.com/VSCodeVim/Vim/pull/65) ([kimitake](https://github.com/kimitake))\n- Update README.md [\\#63](https://github.com/VSCodeVim/Vim/pull/63) ([markrendle](https://github.com/markrendle))\n- map keys from US keyboard to other layouts [\\#61](https://github.com/VSCodeVim/Vim/pull/61) ([guillermooo](https://github.com/guillermooo))\n- fix bug for Cursor class [\\#58](https://github.com/VSCodeVim/Vim/pull/58) ([kimitake](https://github.com/kimitake))\n- Cursor Motions [\\#56](https://github.com/VSCodeVim/Vim/pull/56) ([jpoon](https://github.com/jpoon))\n- Add word motion and db [\\#53](https://github.com/VSCodeVim/Vim/pull/53) ([adriaanp](https://github.com/adriaanp))\n\n## [v0.0.2](https://github.com/vscodevim/vim/tree/v0.0.2) (2015-11-29)\n\n[Full Changelog](https://github.com/vscodevim/vim/compare/v0.0.1...v0.0.2)\n\n- move cursor position after getting normal mode [\\#50](https://github.com/VSCodeVim/Vim/pull/50) ([kimitake](https://github.com/kimitake))\n\n## [v0.0.1](https://github.com/vscodevim/vim/tree/v0.0.1) (2015-11-29)\n\n- Implement Redo, Refactor Keybindings [\\#46](https://github.com/VSCodeVim/Vim/pull/46) ([jpoon](https://github.com/jpoon))\n- reorganize tests; add tests [\\#45](https://github.com/VSCodeVim/Vim/pull/45) ([guillermooo](https://github.com/guillermooo))\n- fixes; add VimError class [\\#43](https://github.com/VSCodeVim/Vim/pull/43) ([guillermooo](https://github.com/guillermooo))\n- Refactor cmdline [\\#42](https://github.com/VSCodeVim/Vim/pull/42) ([guillermooo](https://github.com/guillermooo))\n- ensure user can dismiss global messages with esc [\\#41](https://github.com/VSCodeVim/Vim/pull/41) ([guillermooo](https://github.com/guillermooo))\n- implement :quit [\\#40](https://github.com/VSCodeVim/Vim/pull/40) ([guillermooo](https://github.com/guillermooo))\n- Commands: `u` and `dw` [\\#38](https://github.com/VSCodeVim/Vim/pull/38) ([jpoon](https://github.com/jpoon))\n- Update metadata getting ready for a release [\\#37](https://github.com/VSCodeVim/Vim/pull/37) ([jpoon](https://github.com/jpoon))\n- rename command mode to normal mode [\\#34](https://github.com/VSCodeVim/Vim/pull/34) ([jpoon](https://github.com/jpoon))\n- Support `\\<\\<` and `\\>\\>` [\\#32](https://github.com/VSCodeVim/Vim/pull/32) ([jpoon](https://github.com/jpoon))\n- Add Slackin to Readme [\\#31](https://github.com/VSCodeVim/Vim/pull/31) ([jpoon](https://github.com/jpoon))\n- start code in CI server [\\#28](https://github.com/VSCodeVim/Vim/pull/28) ([guillermooo](https://github.com/guillermooo))\n- assorted fixes [\\#27](https://github.com/VSCodeVim/Vim/pull/27) ([guillermooo](https://github.com/guillermooo))\n- travis: turn off email notifications [\\#25](https://github.com/VSCodeVim/Vim/pull/25) ([jpoon](https://github.com/jpoon))\n- add keys [\\#22](https://github.com/VSCodeVim/Vim/pull/22) ([guillermooo](https://github.com/guillermooo))\n- Ex mode [\\#20](https://github.com/VSCodeVim/Vim/pull/20) ([guillermooo](https://github.com/guillermooo))\n- Command/Insert Modes [\\#16](https://github.com/VSCodeVim/Vim/pull/16) ([jpoon](https://github.com/jpoon))\n- Update tslint to vscode official style guidelines [\\#14](https://github.com/VSCodeVim/Vim/pull/14) ([jpoon](https://github.com/jpoon))\n- Run Tests a la Gulp [\\#11](https://github.com/VSCodeVim/Vim/pull/11) ([jpoon](https://github.com/jpoon))\n- assorted fixes [\\#10](https://github.com/VSCodeVim/Vim/pull/10) ([guillermooo](https://github.com/guillermooo))\n- Assorted fixes [\\#7](https://github.com/VSCodeVim/Vim/pull/7) ([guillermooo](https://github.com/guillermooo))\n- add gulp + tslint [\\#6](https://github.com/VSCodeVim/Vim/pull/6) ([jpoon](https://github.com/jpoon))\n- command line mode refactoring [\\#5](https://github.com/VSCodeVim/Vim/pull/5) ([guillermooo](https://github.com/guillermooo))\n- Navigation mode [\\#4](https://github.com/VSCodeVim/Vim/pull/4) ([jpoon](https://github.com/jpoon))\n- Add ex mode [\\#3](https://github.com/VSCodeVim/Vim/pull/3) ([guillermooo](https://github.com/guillermooo))\n\n\\* _This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)_\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\n## [v1.32.4](https://github.com/vscodevim/vim/tree/v1.32.4) (2025-12-14)\n\n### Fixed\n\n- Improved undo behavior after document is changed by an external process ([@J-Fields](https://github.com/J-Fields)).\n- Fixed spurious `Already at oldest change` message ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.32.3](https://github.com/vscodevim/vim/tree/v1.32.3) (2025-12-10)\n\n### Fixed\n\n- Fixed jump tracking when document is changed by an external process ([@J-Fields](https://github.com/J-Fields)).\n- Fixed `<` and `>` marks after backward selection in Visual mode ([@paakmau](https://github.com/paakmau)).\n\n## [v1.32.2](https://github.com/vscodevim/vim/tree/v1.32.2) (2025-11-30)\n\n### Fixed\n\n- Improved multi-cursor support ([@J-Fields](https://github.com/J-Fields)).\n  - `j` and `k` no longer force cursors to same column.\n  - Fix recording and executing macros with multi-cursor.\n  - `u` no longer cancels multi-cursor.\n  - Fixed several actions in VisualBlock mode.\n  - Fixed several actions incorrectly being executed once per cursor.\n\n- Fix several bugs in `:bd[elete]` ([@J-Fields](https://github.com/J-Fields)).\n  - `bd!` suppresses unsaved changes warning.\n  - `bd {bufname}` closes the specified buffer.\n  - Fixed `bd {N}` with high index.\n  - Better error handling.\n\n- Fix cursor rendering when `cursorStylePerMode.visual` is set ([@jheroy](https://github.com/jheroy)).\n\n- Fix an infinite loop ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.32.1](https://github.com/vscodevim/vim/tree/v1.32.1) (2025-11-9)\n\n### Fixed\n\n- Fixed clipboard register, which was broken in `v1.32.0` ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.32.0](https://github.com/vscodevim/vim/tree/v1.32.0) (2025-11-8)\n\n### Added\n\n- Improved expression support ([@J-Fields](https://github.com/J-Fields)).\n- `:let` can now set registers and environment variables ([@J-Fields](https://github.com/J-Fields)).\n- `:s[ubstitute]` can now replace instances with the value of an expression ([@J-Fields](https://github.com/J-Fields)).\n\n### Fixed\n\n- Fixed `:qa!` prompting about unsaved changes ([@hirokiokada77](https://github.com/hirokiokada77)).\n- Fixed Ex commands being repeatable with `.` ([@coxxny](https://github.com/coxxny)).\n- Fixed bug that would freeze editor if `.` was pressed twice after a non-repeatable action at startup ([@coxxny](https://github.com/coxxny)).\n- Fixed some motions like `[{` when executed with a high count ([@brasswood](https://github.com/brasswood)).\n- Fixed `j` and `k` with multiple cursors and `vim.foldfix` enabled ([@dandn9](https://github.com/dandn9)).\n- Fixed `showTextDocument` being executed for non-preview tabs on `:w[rite]` ([@mccheesy](https://github.com/mccheesy)).\n- Fixed various Ex command error messages ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.31.0](https://github.com/vscodevim/vim/tree/v1.31.0) (2025-10-5)\n\n### Added\n\n- Added `:gr[ep]` and `:vim[grep]` ([@AzimovParviz](https://github.com/AzimovParviz)).\n- Added `:c[hange]` ([@kiing-dom](https://github.com/kiing-dom)).\n- Added better expression support ([@J-Fields](https://github.com/J-Fields)).\n- Added unpacking, indexing, and slicing with `:let` ([@J-Fields](https://github.com/J-Fields)).\n- Added `:unl[et]` ([@J-Fields](https://github.com/J-Fields)).\n\n### Changed\n\n- `:w` now disables preview for the saved editor, like VS Code's native `workbench.action.files.save` ([@mangas](https://github.com/mangas)).\n\n### Fixed\n\n- Fixed significant delay after confirming IME input ([@s-kai273](https://github.com/s-kai273)).\n- Fixed `<Esc>` not closing quick diff ([@jacklee1792](https://github.com/jacklee1792)).\n- Fixed `Vi{` incorrectly including the ending braces ([@Whiskas101](https://github.com/Whiskas101)).\n- Fixed small delete register (`\"-`) not being updated while recording a macro ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.30.1](https://github.com/vscodevim/vim/tree/v1.30.1) (2025-05-28)\n\n### Added\n\n- Added a few character classes to Vim regexes ([@J-Fields](https://github.com/@J-Fields)).\n\n### Fixed\n\n- Fixed an issue where the cursor would jump away after some VS Code navigation commands ([@alythobani](https://github.com/@alythobani)).\n- Fixed surround with tags containing non-word characters ([@robertmoura](https://github.com/@robertmoura)).\n- Fixed a performance issue when typing `(` or similar in large files ([@J-Fields](https://github.com/@J-Fields)).\n\n## [v1.30.0](https://github.com/vscodevim/vim/tree/v1.30.0) (2025-05-22)\n\n### Added\n\n- Added `:pw[d]` ([@zeevoffen](https://github.com/zeevoffen)).\n- Added `:ma[rk]` ([@arunchaganty](https://github.com/arunchaganty)).\n\n### Changed\n\n- Enabled emulated Vim plugins in web extension ([@joshuali925](https://github.com/joshuali925)).\n- `gf` now interprets relative paths as relative to current file. If that fails, it tries relative to workspace root ([@J-Fields](https://github.com/J-Fields)).\n\n### Fixed\n\n- Fixed Python function motions `[m`/`]m` with `async def` ([@nathan-gage](https://github.com/nathan-gage)).\n- Fixed `o` in Visual mode when selection starts at line end ([@kajikentaro](https://github.com/kajikentaro)).\n- Fixed `i(` when cursor is between two pairs of parentheses ([@prakhargupta-jan](https://github.com/prakhargupta-jan)).\n- Fixed global marks jumping to the wrong position ([@NeedsSoySauce](https://github.com/NeedsSoySauce)).\n- Fixed global marks messing up your position in the file you came from ([@J-Fields](https://github.com/J-Fields)).\n- Fixed `gf` with paths containing `..` ([@ekinakkaya](https://github.com/ekinakkaya)).\n- Fixed cursor position after `:ju[mps]` and `breakl[ist]` ([@J-Fields](https://github.com/J-Fields))\n- Fixed jumps going to right file, but wrong line ([@J-Fields](https://github.com/J-Fields)).\n- Fixed `:norm[al]` with a double quote (`\"`) in the argument ([@s-kai273](https://github.com/s-kai273)).\n\n## [v1.29.2](https://github.com/vscodevim/vim/tree/v1.29.2) (2025-05-16)\n\n### Fixed\n\n- Revert make tab and escape fix for native vscode keybindings ([@ulugbekna](https://github.com/ulugbekna)).\n- Dismiss inline suggestion/NES on Escape without interfering with Vim modes ([@ulugbekna](https://github.com/ulugbekna)).\n\n## [v1.29.1](https://github.com/vscodevim/vim/tree/v1.29.1) (2025-05-15)\n\n### Fixed\n\n- fix(keybindings): make tab and escape play nicer with native vscode keybindings ([@ulugbekna](https://github.com/ulugbekna)).\n\n## [v1.29.0](https://github.com/vscodevim/vim/tree/v1.29.0) (2024-12-04)\n\n### Added\n\n- Support `langmap` ([@Opisek](https://github.com/Opisek)).\n- Partial support for expressions, `:let`, and `:ec[ho]` ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.28.1](https://github.com/vscodevim/vim/tree/v1.28.1) (2024-09-07)\n\n### Fixed\n\n- Fixed `h` with unicode surrogate pairs ([@semicube](https://github.com/semicube)).\n\n## [v1.28.0](https://github.com/vscodevim/vim/tree/v1.28.0) (2024-08-25)\n\n### Added\n\n- Support `:norm[al]` ([@s-kai273](https://github.com/s-kai273)).\n- Status item click mapped to `toggleVim` command ([@JoeyShapiro](https://github.com/JoeyShapiro)).\n- Set `vim.command` in `VSCodeContext` on key press ([@raineorshine](https://github.com/raineorshine)).\n- Support `:b[uffer] {bufname}` ([@ccassise](https://github.com/ccassise)).\n- Support `<Del>` in Replace mode ([@s-kai273](https://github.com/s-kai273)).\n\n### Changed\n\n- `:on[ly]` joins groups but does not close editors ([@kopiczko](https://github.com/kopiczko)).\n\n### Fixed\n\n- Fixed split pane (`<C-w>v` and `<C-w>s`) on VS Code 1.90 ([@HenryTSZ](https://github.com/HenryTSZ)).\n- Fixed `:tabe[dit]` with relative path ([@iblislin](https://github.com/iblislin)).\n- Fixed `easymotionDimColor` ([@HenryTSZ](https://github.com/HenryTSZ)).\n- Fixed escaping multiple forward slashes in `visualstar` search ([@zaneduffield](https://github.com/zaneduffield)).\n- Fixed `.` with numbered registers ([@SirTomme](https://github.com/SirTomme)).\n- Fixed `H` and `L` not respecting `scrolloff` ([@rpuhalovich](https://github.com/rpuhalovich)).\n- Fixed surround with HTML tag attributes ([@Nestastnikos](https://github.com/Nestastnikos)).\n- Fixed deleting unicode surrogate pairs ([@semicube](https://github.com/semicube)).\n- Fixed repeating Ex commands with `<Leader>` in them ([@HenryTSZ](https://github.com/HenryTSZ)).\n\n## [v1.27.3](https://github.com/vscodevim/vim/tree/v1.27.3) (2024-05-20)\n\n### Added\n\n- Custom digraphs can be added via `:dig[raphs]` ([@J-Fields](https://github.com/J-Fields)).\n- `:Ex[plore]` is mapped to `workbench.view.explorer` ([@JaiminBrahmbhatt](https://github.com/JaiminBrahmbhatt)).\n\n### Changed\n\n- When used with a count, `<C-d>` and `<C-u>` set the `scroll` option to the count ([@ontanj](https://github.com/ontanj)).\n\n### Fixed\n\n- Fix `:s[ubstitute]` with the `n` flag moving cursor ([@J-Fields](https://github.com/J-Fields)).\n- Fix special marks displaying in gutter ([@shinohara-rin](https://github.com/shinohara-rin)).\n- Fix several edge cases of `<C-o>` ([@harunou](https://github.com/harunou)).\n- Fix incorrect digraph mappings ([@mlbykn](https://github.com/mlbykn)).\n- Fix `gv` after visual selection with mouse or command ([@zyd2001](https://github.com/zyd2001)).\n- Fix `gv` being unaffected by `m<` and `m>` ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.27.2](https://github.com/vscodevim/vim/tree/v1.27.2) (2023-12-22)\n\n### Added\n\n- Map `:ls` to `workbench.action.quickOpenLeastRecentlyUsedEditorInGroup` ([@riyuejiuzhao](https://github.com/riyuejiuzhao)).\n\n### Fixed\n\n- Fix scrolling when `editor.smoothScrolling` is enabled ([@zhuowei](https://github.com/zhuowei)).\n- Fix cursor movement when `vim.foldfix` is enabled ([@HenryTSZ](https://github.com/HenryTSZ)).\n- Fix `editor.action.smartSelection.expand` command in VisualLine mode ([@rogeryk](https://github.com/rogeryk)).\n\n## [v1.27.1](https://github.com/vscodevim/vim/tree/v1.27.1) (2023-11-22)\n\n### Fixed\n\n- Fix `:sp[lit]` and `:vs[plit]` with no file name ([@bcho](https://github.com/bcho)).\n\n## [v1.27.0](https://github.com/vscodevim/vim/tree/v1.27.0) (2023-11-17)\n\n### Added\n\n- Allow `:sp[lit]` and `:vs[plit]` to open non-existing files ([@JLMSC](https://github.com/JLMSC)).\n- Support changing case via `:s[ubstitute]` with `\\L`, `\\U`, `\\E`, `\\u`, and `\\l` ([@J-Fields](https://github.com/J-Fields)).\n- Add border to search and `:s[ubstitute]` decorations, based on the `editor.findMatchBorder` and `editor.findMatchHighlightBorder` ThemeColors ([@bryclee](https://github.com/bryclee)).\n\n### Fixed\n\n- Make `gf` interpret non-absolute paths as relative to project root ([@Foo-x](https://github.com/Foo-x)).\n- Fix `gf` with a line number after the path ([@Foo-x](https://github.com/Foo-x)).\n- Fix status bar color in VisualLine mode ([@chandradeepdey](https://github.com/chandradeepdey)).\n\n## [v1.26.2](https://github.com/vscodevim/vim/tree/v1.26.2) (2023-10-21)\n\n### Fixed\n\n- Fixed illegible text with certain color schemes when `vim.statusBarColorControl` is enabled ([@chandradeepdey](https://github.com/chandradeepdey)).\n\n### Changed\n\n- Changed extension's `activationEvents` to include `onStartupFinished` rather than `*`, which may improve startup performance ([@whitphx](https://github.com/whitphx)).\n\n## [v1.26.1](https://github.com/vscodevim/vim/tree/v1.26.1) (2023-10-09)\n\n### Fixed\n\n- Fixed several Insert mode bugs caused by a regression in `v1.26.0` ([@nullbus](https://github.com/nullbus)).\n- Fixed dot repeat (`.`) after `:reg[isters]` ([@dannoe](https://github.com/dannoe)).\n- Fixed overlapping text in Quick Pick caused by `:reg[isters]` ([@dannoe](https://github.com/dannoe)).\n- Fixed some uses of `vim.remap` ([@jdanbrown](https://github.com/jdanbrown)).\n\n## [v1.26.0](https://github.com/vscodevim/vim/tree/v1.26.0) (2023-09-09)\n\n### Added\n\n- Implemented `:m[ove]` ([@zhanyi22333](https://github.com/zhanyi22333)).\n- Implemented `:red[o]` ([@hamza-tam](https://github.com/hamza-tam)).\n- Implemented `:pu[t] =` ([@elazarcoh](https://github.com/elazarcoh)).\n\n### Fixed\n\n- Fixed misbehavior when selecting from bottom to top with shift+click ([@lqqyt2423](https://github.com/lqqyt2423)).\n- Fixed `@@` when used in a different editor ([@J-Fields](https://github.com/J-Fields)).\n- Fixed race condition in the `c` operator and a few other actions when `vim.autoSwitchInputMethod` is enabled ([@listenerri](https://github.com/listenerri)).\n- Fixed `when` clause for provided `<C-p>` key bind to enable it being remapped ([@grosssoftware](https://github.com/grosssoftware)).\n- Fixed `:sp[lit] [file]` ([@fernando-gap](https://github.com/fernando-gap)).\n- Fixed `<Tab>` key bind which was blocking menu navigation in VS Code's find dialog ([@devrelm](https://github.com/devrelm)).\n- Fixed VSCode's auto-surround functionality in Insert mode ([@Elliot-Roberts](https://github.com/Elliot-Roberts)).\n- Fixed `<C-d>` and `<C-u>` not respecting `[count]` ([@rpuhalovich](https://github.com/rpuhalovich)).\n\n## [v1.25.2](https://github.com/vscodevim/vim/tree/v1.25.2) (2023-03-01)\n\n### Added\n\n- Support for `:w <FILENAME>` ([@JLMSC](https://github.com/JLMSC)).\n\n### Changed\n\n- Reduced extension bundle size by removing source maps ([@kidonng](https://github.com/kidonng)).\n- Replaced \"Report bug\" popup on exceptions with an error log message ([@J-Fields](https://github.com/J-Fields)).\n\n### Fixed\n\n- Fixed remaps which pass multiple positional arguments to a command ([@elmar-peise](https://github.com/elmar-peise)).\n- Fixed cursor position after certain surround actions ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.25.0](https://github.com/vscodevim/vim/tree/v1.25.0) (2023-02-28)\n\n### Added\n\n- Support for `:cw[indow]`, `:lw[indow]`, and related commands ([@mogelbrod](https://github.com/mogelbrod)).\n\n### Changed\n\n- Logging is now done to a `LogOutputChannel`. It can be accessed in the `Output` panel and configured using `workbench.action.setLogLevel` ([@J-Fields](https://github.com/J-Fields)).\n- Scope for settings under `vim.autoSwitchInputMethod.*` is now `machine` ([@Quanuanc](https://github.com/Quanuanc)).\n\n### Fixed\n\n- Fixed undo/redo after recent VS Code update ([@J-Fields](https://github.com/J-Fields)).\n- Fixed `.` after exiting Visual mode or command line with `<Esc>` ([@wgr45097](https://github.com/wgr45097)).\n- Fixed ex command line ranges with no explicit start, such as `,5` ([@lazygyu](https://github.com/lazygyu)).\n\n## [v1.24.3](https://github.com/vscodevim/vim/tree/v1.24.3) (2022-11-06)\n\n### Added\n\n- Text registers can now be executed as macros with `@` ([@elazarcoh](https://github.com/elazarcoh)).\n\n### Fixed\n\n- Fixed some ex commands when repeated with `@:` ([@J-Fields](https://github.com/J-Fields)).\n- Fixed cursor position after `gp` or `gP` in VisualBlock mode ([@burnsdy](https://github.com/burnsdy)).\n- Fixed edge case of `i{` and `a{` ([@elazarcoh](https://github.com/elazarcoh)).\n\n## [v1.24.2](https://github.com/vscodevim/vim/tree/v1.24.2) (2022-10-29)\n\n### Added\n\n- Support for the `'scrolloff'` option, which is mapped to VS Code's `editor.cursorSurroundingLines` setting ([@LinHeLurking](https://github.com/LinHeLurking)).\n\n### Fixed\n\n- Fixed indent (`>`) and outdent (`<`) in VisualBlock mode ([@burnsdy](https://github.com/burnsdy)).\n- Fixed `cW` when the cursor is on the last character of a word ([@wgr45097](https://github.com/wgr45097)).\n- Fixed indent textobjects (`ii`, `ai`, and `aI`) in VisualLine mode ([@mogelbrod](https://github.com/mogelbrod)).\n\n## [v1.24.1](https://github.com/vscodevim/vim/tree/v1.24.1) (2022-09-26)\n\n### Fixed\n\n- Fixed `gt` and `gT` ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.24.0](https://github.com/vscodevim/vim/tree/v1.24.0) (2022-09-26)\n\n### Added\n\n- Support for `zf`/`zd` commands, which fold/unfold arbitrary ranges ([@elazarcoh](https://github.com/elazarcoh)).\n- Support for surrounding with function call ([@riccardofano](https://github.com/riccardofano)).\n- Support for `:sor[t] n`, which sorts lines numerically, rather than lexicographically ([@jan25](https://github.com/jan25)).\n\n### Changed\n\n- `P` in Visual modes no longer overwrites the default register with the selection's contents ([@J-Fields](https://github.com/J-Fields)).\n- Yanking block-wise now pads shorter lines with spaces ([@burnsdy](https://github.com/burnsdy)).\n- `<C-]>` now goes to definition, not declaration ([@J-Fields](https://github.com/J-Fields)).\n- `:tabn[ext] {N}` now goes to the Nth tab, not N tabs forward [@elazarcoh](https://github.com/elazarcoh).\n\n### Fixed\n\n- Fixed insertion of surrogate pairs, like emoji 🙂 ([@garzj](https://github.com/garzj)).\n- Fixed `<BS>` and `<Del>` when cursor is at start of command line ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.23.2](https://github.com/vscodevim/vim/tree/v1.23.2) (2022-08-01)\n\n### Fixed\n\n- Fix the jump list ([@pitkali](https://github.com/pitkali)).\n- Make increment/decrement (`<C-a>` and `<C-x>`) preserve case of hex numbers ([@smallkirby](https://github.com/smallkirby)).\n- Fix search highlights on inactive but visible editors ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.23.1](https://github.com/vscodevim/vim/tree/v1.23.1) (2022-06-28)\n\n### Fixed\n\n- Fold commands such as `zo` and `zc` no longer throw error ([@J-Fields](https://github.com/J-Fields)).\n- Fix regression in VisualBlock `c` ([@J-Fields](https://github.com/J-Fields)).\n\n## [v1.23.0](https://github.com/vscodevim/vim/tree/v1.23.0) (2022-06-27)\n\n### Added\n\n- Partial implementation of the [targets.vim](https://github.com/wellle/targets.vim#quote-text-objects) plugin ([@elazarcoh](https://github.com/elazarcoh)).\n  - See the configuration available under `vim.targets`.\n- Support for `:breaka[dd]`, `:breakd[el]`, and `:breakl[ist]` ([@elazarcoh](https://github.com/elazarcoh)).\n- Support for `:ret[ab]` ([@ivanmaeder](https://github.com/ivanmaeder)).\n- Support for `:<` and `:>` ([@J-Fields](https://github.com/J-Fields)).\n- In the replacement string of `:s[ubstitute]`, `~` stands for previous replace string ([@J-Fields](https://github.com/J-Fields)).\n\n### Changed\n\n- Searches now abort after ~1 second, rather than after finding 1000 matches ([@elazarcoh](https://github.com/elazarcoh)).\n- In the replacement string of `:s[ubstitute]`, `&` (rather than `\\&`) stands for entire matched text ([@J-Fields](https://github.com/J-Fields)).\n- The `vim.textwidth` configuration option can now be set per-language ([@BlakeWilliams](https://github.com/BlakeWilliams)).\n\n### Fixed\n\n- Allow space in ex command file names if preceded by a backslash ([@J-Fields](https://github.com/J-Fields)).\n- Fix Replace mode with multiple cursors ([@J-Fields](https://github.com/J-Fields)).\n- Fix `<C-e>` and `<C-y>` (scroll view) with multiple cursors ([@J-Fields](https://github.com/J-Fields)).\n- Fix `<C-^>` (go to alternate file) ([@J-Fields](https://github.com/J-Fields)).\n- Fix the `CamelCaseMotion` plugin ([@rcywongaa](https://github.com/rcywongaa))\n- Fix behavior around surrogate pairs ([@smallkirby](https://github.com/smallkirby)).\n- Fix `:delm[arks]` ([@chewcw](https://github.com/chewcw)).\n- Fix `<C-d>` in Insert mode when tabs are used ([@J-Fields](https://github.com/J-Fields)).\n- Fix `c` in VisualBlock mode when block extends over short lines ([@J-Fields](https://github.com/J-Fields)).\n- Make `c`, `s`, and `D` in VisualBlock mode copy to register ([@monjara](https://github.com/monjara)).\n- Fix several edge cases of `gv` ([@J-Fields](https://github.com/J-Fields)).\n- Fix `p` in Visual modes with multiple cursors ([@joel1st](https://github.com/joel1st)).\n- Improve search performance ([@J-Fields](https://github.com/J-Fields)).\n\n### Removed\n\n- Remove the following deprecated and unused configuration ([@J-Fields](https://github.com/J-Fields)):\n  - `vim.easymotionMarkerForegroundColorTwoChar`\n  - `vim.easymotionMarkerWidthPerChar`\n  - `vim.easymotionMarkerFontFamily`\n  - `vim.easymotionMarkerFontSize`\n  - `vim.easymotionMarkerMargin`\n\n## **_For versions older than 1.23.0, please see [CHANGELOG.OLD.md](CHANGELOG.OLD.md)_**\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Our Standards\n\nBe nice. Please. Everybody contributing to open source contributes out of good will in their own free time.\n\n## Our Responsibilities\n\nProject 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.\n\nProject 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.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 VSCodeVim\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "<h2 align=\"center\"><img src=\"https://raw.githubusercontent.com/VSCodeVim/Vim/master/images/icon.png\" height=\"128\"><br>VSCodeVim</h2>\n<p align=\"center\"><strong>Vim emulation for Visual Studio Code</strong></p>\n\n<!-- TODO: use pgns or something; otherwise vsce won't package it\n[![](https://vsmarketplacebadge.apphb.com/version/vscodevim.vim.svg)](http://aka.ms/vscodevim)\n[![](https://vsmarketplacebadge.apphb.com/installs-short/vscodevim.vim.svg)](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim)\n[![](https://github.com/VSCodeVim/Vim/workflows/build/badge.svg?branch=master)](https://github.com/VSCodeVim/Vim/actions?query=workflow%3Abuild+branch%3Amaster)\n-->\n\nVSCodeVim is a Vim emulator for [Visual Studio Code](https://code.visualstudio.com/).\n\n- 📃 Our [change log](CHANGELOG.md) outlines the breaking/major/minor updates between releases.\n- Report missing features/bugs on [GitHub](https://github.com/VSCodeVim/Vim/issues).\n\n<details>\n <summary><strong>Table of Contents</strong> (click to expand)</summary>\n\n- [💾 Installation](#-installation)\n  - [Mac](#mac)\n  - [Windows](#windows)\n- [⚙️ Settings](#️-settings)\n  - [Quick Example](#quick-example)\n  - [VSCodeVim settings](#vscodevim-settings)\n  - [Neovim Integration](#neovim-integration)\n  - [Key Remapping](#key-remapping)\n    - [`\"vim.insertModeKeyBindings\"`/`\"vim.normalModeKeyBindings\"`/`\"vim.visualModeKeyBindings\"`/`\"vim.operatorPendingModeKeyBindings\"`](#viminsertmodekeybindingsvimnormalmodekeybindingsvimvisualmodekeybindingsvimoperatorpendingmodekeybindings)\n    - [`\"vim.insertModeKeyBindingsNonRecursive\"`/`\"normalModeKeyBindingsNonRecursive\"`/`\"visualModeKeyBindingsNonRecursive\"`/`\"operatorPendingModeKeyBindingsNonRecursive\"`](#viminsertmodekeybindingsnonrecursivenormalmodekeybindingsnonrecursivevisualmodekeybindingsnonrecursiveoperatorpendingmodekeybindingsnonrecursive)\n    - [Debugging Remappings](#debugging-remappings)\n    - [Remapping more complex key combinations](#remapping-more-complex-key-combinations)\n  - [Vim modes](#vim-modes)\n  - [Vim settings](#vim-settings)\n- [.vimrc support](#vimrc-support)\n- [🖱️ Multi-Cursor Mode](#️-multi-cursor-mode)\n- [🔌 Emulated Plugins](#-emulated-plugins)\n  - [vim-airline](#vim-airline)\n  - [vim-easymotion](#vim-easymotion)\n  - [vim-surround](#vim-surround)\n  - [vim-commentary](#vim-commentary)\n  - [vim-indent-object](#vim-indent-object)\n  - [vim-sneak](#vim-sneak)\n  - [CamelCaseMotion](#camelcasemotion)\n  - [Input Method](#input-method)\n  - [ReplaceWithRegister](#replacewithregister)\n  - [vim-textobj-entire](#vim-textobj-entire)\n  - [vim-textobj-arguments](#vim-textobj-arguments)\n- [🎩 VSCodeVim tricks!](#-vscodevim-tricks)\n- [📚 F.A.Q.](#-faq)\n- [❤️ Contributing](#️-contributing)\n  - [Special shoutouts to:](#special-shoutouts-to)\n\n</details>\n\n## 💾 Installation\n\nVSCodeVim can be installed via the VS Code [Marketplace](https://marketplace.visualstudio.com/items?itemName=vscodevim.vim) or the OpenVSX [Marketplace](https://open-vsx.org/extension/vscodevim/vim).\n\n### Mac\n\nTo enable key-repeating, execute the following in your Terminal, log out and back in, and then restart VS Code:\n\n```sh\ndefaults write com.microsoft.VSCode ApplePressAndHoldEnabled -bool false              # For VS Code\ndefaults write com.microsoft.VSCodeInsiders ApplePressAndHoldEnabled -bool false      # For VS Code Insider\ndefaults write com.vscodium ApplePressAndHoldEnabled -bool false                      # For VS Codium\ndefaults write com.microsoft.VSCodeExploration ApplePressAndHoldEnabled -bool false   # For VS Codium Exploration users\ndefaults write com.exafunction.windsurf ApplePressAndHoldEnabled -bool false          # For Windsurf\ndefaults delete -g ApplePressAndHoldEnabled                                           # If necessary, reset global default\n```\n\nWe also recommend increasing Key Repeat and Delay Until Repeat settings in _System Settings/Preferences -> Keyboard_.\n\n### Windows\n\nLike real vim, VSCodeVim will take over your control keys. This behavior can be adjusted with the [`useCtrlKeys`](#vscodevim-settings) and [`handleKeys`](#vscodevim-settings) settings.\n\n## ⚙️ Settings\n\nThe settings documented here are a subset of the supported settings; the full list is described in the `FEATURES` -> `Settings` tab of VSCodeVim's [extension details page](https://code.visualstudio.com/docs/editor/extension-marketplace#_extension-details), which can be found in the [extensions view](https://code.visualstudio.com/docs/editor/extension-marketplace) of VS Code.\n\n### Quick Example\n\nBelow is an example of a [settings.json](https://code.visualstudio.com/Docs/customization/userandworkspace) file with settings relevant to VSCodeVim:\n\n```json\n{\n  \"vim.easymotion\": true,\n  \"vim.incsearch\": true,\n  \"vim.useSystemClipboard\": true,\n  \"vim.useCtrlKeys\": true,\n  \"vim.hlsearch\": true,\n  \"vim.insertModeKeyBindings\": [\n    {\n      \"before\": [\"j\", \"j\"],\n      \"after\": [\"<Esc>\"]\n    }\n  ],\n  \"vim.normalModeKeyBindingsNonRecursive\": [\n    {\n      \"before\": [\"<leader>\", \"d\"],\n      \"after\": [\"d\", \"d\"]\n    },\n    {\n      \"before\": [\"<C-n>\"],\n      \"commands\": [\":nohl\"]\n    },\n    {\n      \"before\": [\"K\"],\n      \"commands\": [\"lineBreakInsert\"],\n      \"silent\": true\n    }\n  ],\n  \"vim.leader\": \"<space>\",\n  \"vim.handleKeys\": {\n    \"<C-a>\": false,\n    \"<C-f>\": false\n  },\n  // To improve performance\n  \"extensions.experimental.affinity\": {\n    \"vscodevim.vim\": 1\n  }\n}\n```\n\n### VSCodeVim settings\n\nThese settings are specific to VSCodeVim.\n\n| Setting                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                        | Type    | Default Value                                                 |\n| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------- |\n| vim.changeWordIncludesWhitespace | Include trailing whitespace when changing word. This configures the <kbd>cw</kbd> action to act consistently as its siblings (<kbd>yw</kbd> and <kbd>dw</kbd>) instead of acting as <kbd>ce</kbd>.                                                                                                                                                                                                                                 | Boolean | false                                                         |\n| vim.cursorStylePerMode._{Mode}_  | Configure a specific cursor style for _{Mode}_. Omitted modes will use [default cursor type](https://github.com/VSCodeVim/Vim/blob/4a6fde6dbd4d1fac1f204c0dc27c32883651ef1a/src/mode/mode.ts#L34) Supported cursors: line, block, underline, line-thin, block-outline, and underline-thin.                                                                                                                                         | String  | None                                                          |\n| vim.digraphs._{shorthand}_       | Set custom digraph shorthands that can override the default ones. Entries should map a two-character shorthand to a descriptive string and one or more UTF16 code points. Example: `\"R!\": [\"🚀\", [55357, 56960]]`                                                                                                                                                                                                                  | Object  | `{\"R!\": [\"🚀\", [0xD83D, 0xDE80]]`                             |\n| vim.disableExtension             | Disable VSCodeVim extension. This setting can also be toggled using `toggleVim` command in the Command Palette                                                                                                                                                                                                                                                                                                                     | Boolean | false                                                         |\n| vim.handleKeys                   | Delegate configured keys to be handled by VS Code instead of by the VSCodeVim extension. Any key in `keybindings` section of the [package.json](https://github.com/VSCodeVim/Vim/blob/master/package.json) that has a `vim.use<C-...>` in the `when` argument can be delegated back to VS Code by setting `\"<C-...>\": false`. Example: to use `ctrl+f` for find (native VS Code behavior): `\"vim.handleKeys\": { \"<C-f>\": false }`. | String  | `\"<C-d>\": true`<br /> `\"<C-s>\": false`<br /> `\"<C-z>\": false` |\n| vim.overrideCopy                 | Override VS Code's copy command with our own, which works correctly with VSCodeVim. If cmd-c/ctrl-c is giving you issues, set this to false and complain [here](https://github.com/Microsoft/vscode/issues/217).                                                                                                                                                                                                                   | Boolean | false                                                         |\n| vim.useSystemClipboard           | Use the system clipboard register (`*`) as the default register                                                                                                                                                                                                                                                                                                                                                                    | Boolean | false                                                         |\n| vim.searchHighlightColor         | Background color of non-current search matches                                                                                                                                                                                                                                                                                                                                                                                     | String  | `findMatchHighlightBackground` ThemeColor                     |\n| vim.searchHighlightTextColor     | Foreground color of non-current search matches                                                                                                                                                                                                                                                                                                                                                                                     | String  | None                                                          |\n| vim.searchMatchColor             | Background color of current search match                                                                                                                                                                                                                                                                                                                                                                                           | String  | `findMatchBackground` ThemeColor                              |\n| vim.searchMatchTextColor         | Foreground color of current search match                                                                                                                                                                                                                                                                                                                                                                                           | String  | None                                                          |\n| vim.substitutionColor            | Background color of substitution text when `vim.inccommand` is enabled                                                                                                                                                                                                                                                                                                                                                             | String  | \"#50f01080\"                                                   |\n| vim.substitutionTextColor        | Foreground color of substitution text when `vim.inccommand` is enabled                                                                                                                                                                                                                                                                                                                                                             | String  | None                                                          |\n| vim.startInInsertMode            | Start in Insert mode instead of Normal Mode                                                                                                                                                                                                                                                                                                                                                                                        | Boolean | false                                                         |\n| vim.useCtrlKeys                  | Enable Vim ctrl keys overriding common VS Code operations such as copy, paste, find, etc.                                                                                                                                                                                                                                                                                                                                          | Boolean | true                                                          |\n| vim.visualstar                   | In visual mode, start a search with `*` or `#` using the current selection                                                                                                                                                                                                                                                                                                                                                         | Boolean | false                                                         |\n| vim.highlightedyank.enable       | Enable highlighting when yanking                                                                                                                                                                                                                                                                                                                                                                                                   | Boolean | false                                                         |\n| vim.highlightedyank.color        | Set the color of yank highlights                                                                                                                                                                                                                                                                                                                                                                                                   | String  | rgba(250, 240, 170, 0.5)                                      |\n| vim.highlightedyank.duration     | Set the duration of yank highlights                                                                                                                                                                                                                                                                                                                                                                                                | Number  | 200                                                           |\n\n### Neovim Integration\n\n> :warning: Experimental feature. Please leave feedback on neovim integration [here](https://github.com/VSCodeVim/Vim/issues/1735).\n\nTo leverage neovim for Ex-commands,\n\n1.  Install [neovim](https://github.com/neovim/neovim/wiki/Installing-Neovim)\n2.  Modify the following configurations:\n\n| Setting                 | Description                                                                                                                                            | Type    | Default Value |\n| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ------------- |\n| vim.enableNeovim        | Enable Neovim                                                                                                                                          | Boolean | false         |\n| vim.neovimPath          | Full path to neovim executable. If left empty, PATH environment variable will be automatically checked for neovim path.                                | String  |               |\n| vim.neovimUseConfigFile | If `true`, Neovim will load a config file specified by `vim.neovimConfigPath`. This is necessary if you want Neovim to be able to use its own plugins. | Boolean | false         |\n| vim.neovimConfigPath    | Path that Neovim will load as config file. If left blank, Neovim will search in its default location.                                                  | String  |               |\n\nHere's some ideas on what you can do with neovim integration:\n\n- [The power of g](http://vim.wikia.com/wiki/Power_of_g)\n- [The :normal command](https://vi.stackexchange.com/questions/4418/execute-normal-command-over-range)\n- Faster search and replace!\n\n### Key Remapping\n\nCustom remappings are defined on a per-mode basis.\n\n#### `\"vim.insertModeKeyBindings\"`/`\"vim.normalModeKeyBindings\"`/`\"vim.visualModeKeyBindings\"`/`\"vim.operatorPendingModeKeyBindings\"`\n\n- Keybinding overrides to use for insert, normal, operatorPending and visual modes.\n- Keybinding overrides can include `\"before\"`, `\"after\"`, `\"commands\"`, and `\"silent\"`.\n- Bind `jj` to `<Esc>` in insert mode:\n\n```json\n    \"vim.insertModeKeyBindings\": [\n        {\n            \"before\": [\"j\", \"j\"],\n            \"after\": [\"<Esc>\"]\n        }\n    ]\n```\n\n- Bind `£` to goto previous whole word under cursor:\n\n```json\n    \"vim.normalModeKeyBindings\": [\n        {\n            \"before\": [\"£\"],\n            \"after\": [\"#\"]\n        }\n    ]\n```\n\n- Bind `:` to show the command palette, and don't show the message on the status bar:\n\n```json\n    \"vim.normalModeKeyBindings\": [\n        {\n            \"before\": [\":\"],\n            \"commands\": [\n                \"workbench.action.showCommands\",\n            ],\n            \"silent\": true\n        }\n    ]\n```\n\n- Bind `<leader>m` to add a bookmark and `<leader>b` to open the list of all bookmarks (using the [Bookmarks](https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks) extension):\n\n```json\n    \"vim.normalModeKeyBindings\": [\n        {\n            \"before\": [\"<leader>\", \"m\"],\n            \"commands\": [\n                \"bookmarks.toggle\"\n            ]\n        },\n        {\n            \"before\": [\"<leader>\", \"b\"],\n            \"commands\": [\n                \"bookmarks.list\"\n            ]\n        }\n    ]\n```\n\n- Bind `ctrl+n` to turn off search highlighting and `<leader>w` to save the current file:\n\n```json\n    \"vim.normalModeKeyBindings\": [\n        {\n            \"before\":[\"<C-n>\"],\n            \"commands\": [\n                \":nohl\",\n            ]\n        },\n        {\n            \"before\": [\"leader\", \"w\"],\n            \"commands\": [\n                \"workbench.action.files.save\",\n            ]\n        }\n    ]\n```\n\n- Bind `{` to `w` in operator pending mode makes `y{` and `d{` work like `yw` and `dw` respectively:\n\n```json\n    \"vim.operatorPendingModeKeyBindings\": [\n        {\n            \"before\": [\"{\"],\n            \"after\": [\"w\"]\n        }\n    ]\n```\n\n- Bind `L` to `$` and `H` to `^` in operator pending mode makes `yL` and `dH` work like `y$` and `d^` respectively:\n\n```json\n    \"vim.operatorPendingModeKeyBindings\": [\n        {\n            \"before\": [\"L\"],\n            \"after\": [\"$\"]\n        },\n        {\n            \"before\": [\"H\"],\n            \"after\": [\"^\"]\n        }\n    ]\n```\n\n- Bind `>` and `<` in visual mode to indent/outdent lines (repeatable):\n\n```json\n    \"vim.visualModeKeyBindings\": [\n        {\n            \"before\": [\n                \">\"\n            ],\n            \"commands\": [\n                \"editor.action.indentLines\"\n            ]\n        },\n        {\n            \"before\": [\n                \"<\"\n            ],\n            \"commands\": [\n                \"editor.action.outdentLines\"\n            ]\n        },\n    ]\n```\n\n- Bind `<leader>vim` to clone this repository to the selected location:\n\n```json\n    \"vim.visualModeKeyBindings\": [\n        {\n            \"before\": [\n                \"<leader>\", \"v\", \"i\", \"m\"\n            ],\n            \"commands\": [\n                {\n                    \"command\": \"git.clone\",\n                    \"args\": [ \"https://github.com/VSCodeVim/Vim.git\" ]\n                }\n            ]\n        }\n    ]\n```\n\n#### `\"vim.insertModeKeyBindingsNonRecursive\"`/`\"normalModeKeyBindingsNonRecursive\"`/`\"visualModeKeyBindingsNonRecursive\"`/`\"operatorPendingModeKeyBindingsNonRecursive\"`\n\n- Non-recursive keybinding overrides to use for insert, normal, and visual modes\n- _Example:_ Exchange the meaning of two keys like `j` to `k` and `k` to `j` to exchange the cursor up and down commands. Notice that if you attempted this binding normally, the `j` would be replaced with `k` and the `k` would be replaced with `j`, on and on forever. When this happens 'maxmapdepth' times (default 1000) the error message 'E223 Recursive Mapping' will be thrown. Stop this recursive expansion using the NonRecursive variation of the keybindings:\n\n```json\n    \"vim.normalModeKeyBindingsNonRecursive\": [\n        {\n            \"before\": [\"j\"],\n            \"after\": [\"k\"]\n        },\n        {\n            \"before\": [\"k\"],\n            \"after\": [\"j\"]\n        }\n    ]\n```\n\n- Bind `(` to 'i(' in operator pending mode makes 'y(' and 'c(' work like 'yi(' and 'ci(' respectively:\n\n```json\n    \"vim.operatorPendingModeKeyBindingsNonRecursive\": [\n        {\n            \"before\": [\"(\"],\n            \"after\": [\"i(\"]\n        }\n    ]\n```\n\n- Bind `p` in visual mode to paste without overriding the current register:\n\n```json\n    \"vim.visualModeKeyBindingsNonRecursive\": [\n        {\n            \"before\": [\n                \"p\",\n            ],\n            \"after\": [\n                \"p\",\n                \"g\",\n                \"v\",\n                \"y\"\n            ]\n        }\n    ],\n```\n\n#### Debugging Remappings\n\n1.  Adjust the extension's logging level to 'debug' and open the Output window:\n    1.  Run `Developer: Set Log Level` from the command palette.\n    2.  Select `Vim`, then `Debug`\n    3.  Run `Developer: Reload window`\n    4.  In the bottom panel, open the `Output` tab and select `Vim` from the dropdown selection.\n2.  Are your configurations correct?\n\n    As each remapped configuration is loaded, it is logged to the Vim Output panel. Do you see any errors?\n\n    ```console\n    debug: Remapper: normalModeKeyBindingsNonRecursive. before=0. after=^.\n    debug: Remapper: insertModeKeyBindings. before=j,j. after=<Esc>.\n    error: Remapper: insertModeKeyBindings. Invalid configuration. Missing 'after' key or 'commands'. before=j,k.\n    ```\n\n    Misconfigured configurations are ignored.\n\n3.  Does the extension handle the keys you are trying to remap?\n\n    VSCodeVim explicitly instructs VS Code which key events we care about through the [package.json](https://github.com/VSCodeVim/Vim/blob/9bab33c75d0a53873880a79c5d2de41c8be1bef9/package.json#L62). If the key you are trying to remap is a key in which vim/vscodevim generally does not handle, then it's most likely that this extension does not receive those key events from VS Code. In the Vim Output panel, you should see:\n\n    ```console\n    debug: ModeHandler: handling key=A.\n    debug: ModeHandler: handling key=l.\n    debug: ModeHandler: handling key=<BS>.\n    debug: ModeHandler: handling key=<C-a>.\n    ```\n\n    As you press the key that you are trying to remap, do you see it outputted here? If not, it means we don't subscribe to those key events. It is still possible to remap those keys by using VSCode's [keybindings.json](https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-reference) (see next section: [Remapping more complex key combinations](#remapping-more-complex-key-combinations)).\n\n#### Remapping more complex key combinations\n\nIt is highly recommended to remap keys using vim commands like `\"vim.normalModeKeyBindings\"` ([see here](#key-remapping)). But sometimes the usual remapping commands are not enough as they do not support every key combinations possible (for example `Alt+key` or `Ctrl+Shift+key`). In this case it is possible to create new keybindings inside [keybindings.json](https://code.visualstudio.com/docs/getstarted/keybindings#_keyboard-shortcuts-reference). To do so: open up keybindings.json in VSCode using `CTRL+SHIFT+P` and select `Open keyboard shortcuts (JSON)`.\n\nYou can then add a new entry to the keybindings like so:\n\n```json\n{\n  \"key\": \"YOUR_KEY_COMBINATION\",\n  \"command\": \"vim.remap\",\n  \"when\": \"inputFocus && vim.mode == 'VIM_MODE_YOU_WANT_TO_REBIND'\",\n  \"args\": {\n    \"after\": [\"YOUR_VIM_ACTION\"]\n  }\n}\n```\n\nFor example, to rebind `ctrl+shift+y` to VSCodeVim's `yy` (yank line) in normal mode, add this to your keybindings.json:\n\n```json\n{\n  \"key\": \"ctrl+shift+y\",\n  \"command\": \"vim.remap\",\n  \"when\": \"inputFocus && vim.mode == 'Normal'\",\n  \"args\": {\n    \"after\": [\"y\", \"y\"]\n  }\n}\n```\n\nIf keybindings.json is empty the first time you open it, make sure to add opening `[` and closing `]` square brackets to the file as the keybindings should be inside a JSON Array.\n\n### Vim modes\n\nHere are all the modes used by VSCodeVim:\n\n| Mode                  |\n| --------------------- |\n| Normal                |\n| Insert                |\n| Visual                |\n| VisualBlock           |\n| VisualLine            |\n| SearchInProgressMode  |\n| CommandlineInProgress |\n| Replace               |\n| EasyMotionMode        |\n| EasyMotionInputMode   |\n| SurroundInputMode     |\n| OperatorPendingMode   |\n| Disabled              |\n\nWhen rebinding keys in [keybindings.json](https://code.visualstudio.com/docs/getstarted/keybindings) using [\"when clause context\"](https://code.visualstudio.com/api/references/when-clause-contexts), it can be useful to know in which mode vim currently is. For example to write a \"when clause\" that checks if vim is currently in normal mode or visual mode it is possible to write the following:\n\n```json\n\"when\": \"vim.mode == 'Normal' || vim.mode == 'Visual'\",\n```\n\n### Vim settings\n\nConfiguration settings that have been copied from vim. Vim settings are loaded in the following sequence:\n\n1.  `:set {setting}`\n2.  `vim.{setting}` from user/workspace settings.\n3.  VS Code settings\n4.  VSCodeVim default values\n\n| Setting          | Description                                                                                                                                                                                                                                                   | Type    | Default Value                                                  |\n| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------- |\n| vim.autoindent   | Copy indent from current line when starting a new line                                                                                                                                                                                                        | Boolean | true                                                           |\n| vim.gdefault     | When on, the `:substitute` flag `g` is default on. This means that all matches in a line are substituted instead of one. When a `g` flag is given to a `:substitute` command, this will toggle the substitution of all or one match.                          | Boolean | false                                                          |\n| vim.hlsearch     | Highlights all text matching current search                                                                                                                                                                                                                   | Boolean | false                                                          |\n| vim.ignorecase   | Ignore case in search patterns                                                                                                                                                                                                                                | Boolean | true                                                           |\n| vim.incsearch    | Show the next match while entering a search                                                                                                                                                                                                                   | Boolean | true                                                           |\n| vim.inccommand   | Show the effects of the `:substitute` command while typing                                                                                                                                                                                                    | String  | `replace`                                                      |\n| vim.joinspaces   | Add two spaces after '.', '?', and '!' when joining or reformatting                                                                                                                                                                                           | Boolean | true                                                           |\n| vim.leader       | Defines key for `<leader>` to be used in key remappings                                                                                                                                                                                                       | String  | `\\`                                                            |\n| vim.maxmapdepth  | Maximum number of times a mapping is done without resulting in a character to be used. This normally catches endless mappings, like \":map x y\" with \":map y x\". It still does not catch \":map g wg\", because the 'w' is used before the next mapping is done. | Number  | 1000                                                           |\n| vim.report       | Threshold for reporting number of lines changed.                                                                                                                                                                                                              | Number  | 2                                                              |\n| vim.shell        | Path to the shell to use for `!` and `:!` commands.                                                                                                                                                                                                           | String  | `/bin/sh` on Unix, `%COMSPEC%` environment variable on Windows |\n| vim.showcmd      | Show (partial) command in status bar                                                                                                                                                                                                                          | Boolean | true                                                           |\n| vim.showmodename | Show name of current mode in status bar                                                                                                                                                                                                                       | Boolean | true                                                           |\n| vim.smartcase    | Override the 'ignorecase' setting if search pattern contains uppercase characters                                                                                                                                                                             | Boolean | true                                                           |\n| vim.textwidth    | Width to word-wrap when using `gq`                                                                                                                                                                                                                            | Number  | 80                                                             |\n| vim.timeout      | Timeout in milliseconds for remapped commands                                                                                                                                                                                                                 | Number  | 1000                                                           |\n| vim.whichwrap    | Allow specified keys that move the cursor left/right to move to the previous/next line when the cursor is on the first/last character in the line. See [:help whichwrap](https://vimhelp.org/options.txt.html#%27whichwrap%27).                               | String  | `b,s`                                                          |\n\n## .vimrc support\n\n> :warning: .vimrc support is currently experimental. Only remaps are supported, and you may experience bugs. Please [report them](https://github.com/VSCodeVim/Vim/issues/new?template=bug_report.md)!\n\nSet `vim.vimrc.enable` to `true` and set `vim.vimrc.path` appropriately.\n\n## 🖱️ Multi-Cursor Mode\n\n> :warning: Multi-Cursor mode is experimental. Please report issues in our [feedback thread.](https://github.com/VSCodeVim/Vim/issues/824)\n\nEnter multi-cursor mode by:\n\n- On OSX, `cmd-d`. On Windows, `ctrl-d`.\n- `gb`, a new shortcut we added which is equivalent to `cmd-d` (OSX) or `ctrl-d` (Windows). It adds another cursor at the next word that matches the word the cursor is currently on.\n- Running \"Add Cursor Above/Below\" or the shortcut on any platform.\n\nOnce you have multiple cursors, you should be able to use Vim commands as you see fit. Most should work; some are unsupported (ref [PR#587](https://github.com/VSCodeVim/Vim/pull/587)).\n\n- Each cursor has its own clipboard.\n- Pressing Escape in Multi-Cursor Visual Mode will bring you to Multi-Cursor Normal mode. Pressing it again will return you to Normal mode.\n\n## 🔌 Emulated Plugins\n\n### vim-airline\n\n> :warning: There are performance implications to using this plugin. In order to change the status bar, we override the configurations in your workspace settings.json which results in increased latency and a constant changing diff in your working directory (see [issue#2124](https://github.com/VSCodeVim/Vim/issues/2124)).\n\nChange the color of the status bar based on the current mode. Once enabled, configure `\"vim.statusBarColors\"`. Colors can be defined for each mode either as `string` (background only), or `string[]` (background, foreground).\n\n```json\n    \"vim.statusBarColorControl\": true,\n    \"vim.statusBarColors.normal\": [\"#8FBCBB\", \"#434C5E\"],\n    \"vim.statusBarColors.insert\": \"#BF616A\",\n    \"vim.statusBarColors.visual\": \"#B48EAD\",\n    \"vim.statusBarColors.visualline\": \"#B48EAD\",\n    \"vim.statusBarColors.visualblock\": \"#A3BE8C\",\n    \"vim.statusBarColors.replace\": \"#D08770\",\n    \"vim.statusBarColors.commandlineinprogress\": \"#007ACC\",\n    \"vim.statusBarColors.searchinprogressmode\": \"#007ACC\",\n    \"vim.statusBarColors.easymotionmode\": \"#007ACC\",\n    \"vim.statusBarColors.easymotioninputmode\": \"#007ACC\",\n    \"vim.statusBarColors.surroundinputmode\": \"#007ACC\",\n```\n\n### vim-easymotion\n\nBased on [vim-easymotion](https://github.com/easymotion/vim-easymotion) and configured through the following settings:\n\n| Setting                                          | Description                                                                                              | Type    | Default Value                                      |\n| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------- |\n| vim.easymotion                                   | Enable/disable easymotion plugin                                                                         | Boolean | false                                              |\n| vim.easymotionMarkerBackgroundColor              | The background color of the marker box.                                                                  | String  | '#0000'                                            |\n| vim.easymotionMarkerForegroundColorOneChar       | The font color for one-character markers.                                                                | String  | '#ff0000'                                          |\n| vim.easymotionMarkerForegroundColorTwoCharFirst  | The font color for the first of two-character markers, used to differentiate from one-character markers. | String  | '#ffb400'                                          |\n| vim.easymotionMarkerForegroundColorTwoCharSecond | The font color for the second of two-character markers, used to differentiate consecutive markers.       | String  | '#b98300'                                          |\n| vim.easymotionIncSearchForegroundColor           | The font color for the search n-character command, used to highlight the matches.                        | String  | '#7fbf00'                                          |\n| vim.easymotionDimColor                           | The font color for the dimmed characters, used when `#vim.easymotionDimBackground#` is set to true.      | String  | '#777777'                                          |\n| vim.easymotionDimBackground                      | Whether to dim other text while markers are visible.                                                     | Boolean | true                                               |\n| vim.easymotionMarkerFontWeight                   | The font weight used for the marker text.                                                                | String  | 'bold'                                             |\n| vim.easymotionKeys                               | The characters used for jump marker name                                                                 | String  | 'hklyuiopnm,qwertzxcvbasdgjf;'                     |\n| vim.easymotionJumpToAnywhereRegex                | Custom regex to match for JumpToAnywhere motion (analogous to `Easymotion_re_anywhere`)                  | String  | `\\b[A-Za-z0-9]\\|[A-Za-z0-9]\\b\\|_.\\|#.\\|[a-z][A-Z]` |\n\nOnce easymotion is active, initiate motions using the following commands. After you initiate the motion, text decorators/markers will be displayed and you can press the keys displayed to jump to that position. `leader` is configurable and is `\\` by default.\n\n| Motion Command                      | Description                                                                                                    |\n| ----------------------------------- | -------------------------------------------------------------------------------------------------------------- |\n| `<leader><leader> s <char>`         | Search character                                                                                               |\n| `<leader><leader> f <char>`         | Find character forwards                                                                                        |\n| `<leader><leader> F <char>`         | Find character backwards                                                                                       |\n| `<leader><leader> t <char>`         | Til character forwards                                                                                         |\n| `<leader><leader> T <char>`         | Til character backwards                                                                                        |\n| `<leader><leader> w`                | Start of word forwards                                                                                         |\n| `<leader><leader> b`                | Start of word backwards                                                                                        |\n| `<leader><leader> l`                | Matches beginning & ending of word, camelCase, after `_`, and after `#` forwards                               |\n| `<leader><leader> h`                | Matches beginning & ending of word, camelCase, after `_`, and after `#` backwards                              |\n| `<leader><leader> e`                | End of word forwards                                                                                           |\n| `<leader><leader> ge`               | End of word backwards                                                                                          |\n| `<leader><leader> j`                | Start of line forwards                                                                                         |\n| `<leader><leader> k`                | Start of line backwards                                                                                        |\n| `<leader><leader> / <char>... <CR>` | Search n-character                                                                                             |\n| `<leader><leader><leader> bdt`      | Til character                                                                                                  |\n| `<leader><leader><leader> bdw`      | Start of word                                                                                                  |\n| `<leader><leader><leader> bde`      | End of word                                                                                                    |\n| `<leader><leader><leader> bdjk`     | Start of line                                                                                                  |\n| `<leader><leader><leader> j`        | JumpToAnywhere motion; default behavior matches beginning & ending of word, camelCase, after `_` and after `#` |\n\n`<leader><leader> (2s|2f|2F|2t|2T) <char><char>` and `<leader><leader><leader> bd2t <char>char>` are also available.\nThe difference is character count required for search.\nFor example, `<leader><leader> 2s <char><char>` requires two characters, and search by two characters.\nThis mapping is not a standard mapping, so it is recommended to use your custom mapping.\n\n### vim-surround\n\nBased on [surround.vim](https://github.com/tpope/vim-surround), the plugin is used to work with surrounding characters like parentheses, brackets, quotes, and XML tags.\n\n| Setting      | Description                 | Type    | Default Value |\n| ------------ | --------------------------- | ------- | ------------- |\n| vim.surround | Enable/disable vim-surround | Boolean | true          |\n\n`t` or `<` as `<desired>` or `<existing>` will enter tag entry mode. Using `<CR>` instead of `>` to finish changing a tag will preserve any existing attributes.\n\n| Surround Command           | Description                                              |\n| -------------------------- | -------------------------------------------------------- |\n| `y s <motion> <desired>`   | Add `desired` surround around text defined by `<motion>` |\n| `d s <existing>`           | Delete `existing` surround                               |\n| `c s <existing> <desired>` | Change `existing` surround to `desired`                  |\n| `S <desired>`              | Surround when in visual modes (surrounds full selection) |\n\nSome examples:\n\n- `\"test\"` with cursor inside quotes type `cs\"'` to end up with `'test'`\n- `\"test\"` with cursor inside quotes type `ds\"` to end up with `test`\n- `\"test\"` with cursor inside quotes type `cs\"t` and enter `123>` to end up with `<123>test</123>`\n\n### vim-commentary\n\nSimilar to [vim-commentary](https://github.com/tpope/vim-commentary), but uses the VS Code native _Toggle Line Comment_ and _Toggle Block Comment_ features.\n\nUsage examples:\n\n- `gc` - toggles line comment. For example `gcc` to toggle line comment for current line and `gc2j` to toggle line comments for the current line and the next two lines.\n- `gC` - toggles block comment. For example `gCi)` to comment out everything within parentheses.\n\n### vim-indent-object\n\nBased on [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object), it allows for treating blocks of code at the current indentation level as text objects. Useful in languages that don't use braces around statements (e.g. Python).\n\nProvided there is a new line between the opening and closing braces / tag, it can be considered an agnostic `cib`/`ci{`/`ci[`/`cit`.\n\n| Command        | Description                                                                                          |\n| -------------- | ---------------------------------------------------------------------------------------------------- |\n| `<operator>ii` | This indentation level                                                                               |\n| `<operator>ai` | This indentation level and the line above (think `if` statements in Python)                          |\n| `<operator>aI` | This indentation level, the line above, and the line after (think `if` statements in C/C++/Java/etc) |\n\n### vim-sneak\n\nBased on [vim-sneak](https://github.com/justinmk/vim-sneak), it allows for jumping to any location specified by two characters.\n\n| Setting                            | Description                                                 | Type    | Default Value |\n| ---------------------------------- | ----------------------------------------------------------- | ------- | ------------- |\n| vim.sneak                          | Enable/disable vim-sneak                                    | Boolean | false         |\n| vim.sneakUseIgnorecaseAndSmartcase | Respect `vim.ignorecase` and `vim.smartcase` while sneaking | Boolean | false         |\n\nOnce sneak is active, initiate motions using the following commands. For operators sneak uses `z` instead of `s` because `s` is already taken by the surround plugin.\n\n| Motion Command            | Description                                                             |\n| ------------------------- | ----------------------------------------------------------------------- |\n| `s<char><char>`           | Move forward to the first occurrence of `<char><char>`                  |\n| `S<char><char>`           | Move backward to the first occurrence of `<char><char>`                 |\n| `<operator>z<char><char>` | Perform `<operator>` forward to the first occurrence of `<char><char>`  |\n| `<operator>Z<char><char>` | Perform `<operator>` backward to the first occurrence of `<char><char>` |\n\n### CamelCaseMotion\n\nBased on [CamelCaseMotion](https://github.com/bkad/CamelCaseMotion), though not an exact emulation. This plugin provides an easier way to move through camelCase and snake_case words.\n\n| Setting                    | Description                    | Type    | Default Value |\n| -------------------------- | ------------------------------ | ------- | ------------- |\n| vim.camelCaseMotion.enable | Enable/disable CamelCaseMotion | Boolean | false         |\n\nOnce CamelCaseMotion is enabled, the following motions are available:\n\n| Motion Command         | Description                                                                |\n| ---------------------- | -------------------------------------------------------------------------- |\n| `<leader>w`            | Move forward to the start of the next camelCase or snake_case word segment |\n| `<leader>e`            | Move forward to the next end of a camelCase or snake_case word segment     |\n| `<leader>b`            | Move back to the prior beginning of a camelCase or snake_case word segment |\n| `<operator>i<leader>w` | Select/change/delete/etc. the current camelCase or snake_case word segment |\n\nBy default, `<leader>` is mapped to `\\`, so for example, `d2i\\w` would delete the current and next camelCase word segment.\n\n### Input Method\n\nDisable input method when exiting Insert Mode.\n\n| Setting                                 | Description                                                                                      |\n| --------------------------------------- | ------------------------------------------------------------------------------------------------ |\n| `vim.autoSwitchInputMethod.enable`      | Boolean denoting whether autoSwitchInputMethod is on/off.                                        |\n| `vim.autoSwitchInputMethod.defaultIM`   | Default input method.                                                                            |\n| `vim.autoSwitchInputMethod.obtainIMCmd` | The full path to command to retrieve the current input method key.                               |\n| `vim.autoSwitchInputMethod.switchIMCmd` | The full path to command to switch input method, with `{im}` a placeholder for input method key. |\n\nAny third-party program can be used to switch input methods. The following will walkthrough the configuration using [im-select](https://github.com/daipeihust/im-select).\n\n1.  Install im-select (see [installation guide](https://github.com/daipeihust/im-select#installation))\n1.  Find your default input method key\n    - Mac:\n\n      Switch your input method to English, and run the following in your terminal: `/<path-to-im-select-installation>/im-select` to output your default input method. The table below lists the common English key layouts for MacOS.\n\n      | Key                            | Description |\n      | ------------------------------ | ----------- |\n      | com.apple.keylayout.US         | U.S.        |\n      | com.apple.keylayout.ABC        | ABC         |\n      | com.apple.keylayout.British    | British     |\n      | com.apple.keylayout.Irish      | Irish       |\n      | com.apple.keylayout.Australian | Australian  |\n      | com.apple.keylayout.Dvorak     | Dvorak      |\n      | com.apple.keylayout.Colemak    | Colemak     |\n\n    - Windows:\n\n      Refer to the [im-select guide](https://github.com/daipeihust/im-select#to-get-current-keyboard-locale) on how to discover your input method key. Generally, if your keyboard layout is en_US the input method key is 1033 (the locale ID of en_US). You can also find your locale ID from [this page](https://www.science.co.il/language/Locale-codes.php), where the `LCID Decimal` column is the locale ID.\n\n1.  Configure `vim.autoSwitchInputMethod`.\n    - MacOS:\n\n      Given the input method key of `com.apple.keylayout.US` and `im-select` located at `/usr/local/bin`. The configuration is:\n\n      ```json\n      \"vim.autoSwitchInputMethod.enable\": true,\n      \"vim.autoSwitchInputMethod.defaultIM\": \"com.apple.keylayout.US\",\n      \"vim.autoSwitchInputMethod.obtainIMCmd\": \"/usr/local/bin/im-select\",\n      \"vim.autoSwitchInputMethod.switchIMCmd\": \"/usr/local/bin/im-select {im}\"\n      ```\n\n    - Windows:\n\n      Given the input method key of `1033` (en_US) and `im-select.exe` located at `D:/bin`. The configuration is:\n\n      ```json\n      \"vim.autoSwitchInputMethod.enable\": true,\n      \"vim.autoSwitchInputMethod.defaultIM\": \"1033\",\n      \"vim.autoSwitchInputMethod.obtainIMCmd\": \"D:\\\\bin\\\\im-select.exe\",\n      \"vim.autoSwitchInputMethod.switchIMCmd\": \"D:\\\\bin\\\\im-select.exe {im}\"\n      ```\n\nThe `{im}` argument above is a command-line option that will be passed to `im-select` denoting the input method to switch to. If using an alternative program to switch input methods, you should add a similar option to the configuration. For example, if the program's usage is `my-program -s imKey` to switch input method, the `vim.autoSwitchInputMethod.switchIMCmd` should be `/path/to/my-program -s {im}`.\n\n### ReplaceWithRegister\n\nBased on [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister), an easy way to replace existing text with the contents of a register.\n\n| Setting                 | Description                        | Type    | Default Value |\n| ----------------------- | ---------------------------------- | ------- | ------------- |\n| vim.replaceWithRegister | Enable/disable ReplaceWithRegister | Boolean | false         |\n\nOnce active, type `gr` (say \"go replace\") followed by a motion to describe the text you want replaced by the contents of the register.\n\n| Motion Command          | Description                                                                             |\n| ----------------------- | --------------------------------------------------------------------------------------- |\n| `[count][\"a]gr<motion>` | Replace the text described by the motion with the contents of the specified register    |\n| `[count][\"a]grr`        | Replace the \\[count\\] lines or current line with the contents of the specified register |\n| `{Visual}[\"a]gr`        | Replace the selection with the contents of the specified register                       |\n\n### vim-textobj-entire\n\nSimilar to [vim-textobj-entire](https://github.com/kana/vim-textobj-entire).\n\nAdds two useful text-objects:\n\n- `ae` which represents the entire content of a buffer.\n- `ie` which represents the entire content of a buffer without the leading and trailing spaces.\n\nUsage examples:\n\n- `dae` - delete the whole buffer content.\n- `yie` - will yank the buffer content except leading and trailing blank lines.\n- `gUae` - transform the whole buffer to uppercase.\n\n### vim-textobj-arguments\n\nSimilar to the argument text object in [targets.vim](https://github.com/wellle/targets.vim). It is an easy way to deal with arguments inside functions in most programming languages.\n\n| Motion Command | Description                        |\n| -------------- | ---------------------------------- |\n| `<operator>ia` | The argument excluding separators. |\n| `<operator>aa` | The argument including separators. |\n\nUsage examples:\n\n- `cia` - change the argument under the cursor while preserving separators like comma `,`.\n- `daa` - will delete the whole argument under the cursor and the separators if applicable.\n\n| Setting                             | Description                  | Type        | Default Value |\n| ----------------------------------- | ---------------------------- | ----------- | ------------- |\n| vim.argumentObjectOpeningDelimiters | A list of opening delimiters | String list | [\"(\", \"[\"]    |\n| vim.argumentObjectClosingDelimiters | A list of closing delimiters | String list | [\")\", \"]\"]    |\n| vim.argumentObjectSeparators        | A list of object separators  | String list | [\",\"]         |\n\n## 🎩 VSCodeVim tricks!\n\nVS Code has a lot of nifty tricks and we try to preserve some of them:\n\n- `gd` - jump to definition.\n- `gq` - on a visual selection reflow and wordwrap blocks of text, preserving commenting style. Great for formatting documentation comments.\n- `gb` - adds another cursor on the next word it finds which is the same as the word under the cursor.\n- `af` - visual mode command which selects increasingly large blocks of text. For example, if you had \"blah (foo [bar 'ba|z'])\" then it would select 'baz' first. If you pressed `af` again, it'd then select [bar 'baz'], and if you did it a third time it would select \"(foo [bar 'baz'])\".\n- `gh` - equivalent to hovering your mouse over wherever the cursor is. Handy for seeing types and error messages without reaching for the mouse!\n\n## 📚 F.A.Q.\n\n- None of the native Visual Studio Code `ctrl` (e.g. `ctrl+f`, `ctrl+v`) commands work\n\n  Set the [`useCtrlKeys` setting](#vscodevim-settings) to `false`.\n\n- Moving `j`/`k` over folds opens up the folds\n\n  Try setting `vim.foldfix` to `true`. This is a hack; it works fine, but there are side effects (see [issue#22276](https://github.com/Microsoft/vscode/issues/22276)).\n\n- Key repeat doesn't work\n\n  Are you on a Mac? Did you go through our [mac-setup](#mac) instructions?\n\n- There are annoying intellisense/notifications/popups that I can't close with `<esc>`! Or I'm in a snippet and I want to close intellisense\n\n  Press `shift+<esc>` to close all of those boxes.\n\n- How can I use the commandline when in Zen mode or when the status bar is disabled?\n\n  This extension exposes a remappable command to show a VS Code style quick-pick version of the commandline, with more limited functionality. This can be remapped as follows in VS Code's keybindings.json settings file.\n\n  ```json\n  {\n    \"key\": \"shift+;\",\n    \"command\": \"vim.showQuickpickCmdLine\",\n    \"when\": \"editorTextFocus && vim.mode != 'Insert'\"\n  }\n  ```\n\n  Or for Zen mode only:\n\n  ```json\n  {\n    \"key\": \"shift+;\",\n    \"command\": \"vim.showQuickpickCmdLine\",\n    \"when\": \"inZenMode && vim.mode != 'Insert'\"\n  }\n  ```\n\n- How can I move the cursor by each display line with word wrapping?\n\n  If you have word wrap on and would like the cursor to enter each wrapped line when using <kbd>j</kbd>, <kbd>k</kbd>, <kbd>↓</kbd> or <kbd>↑</kbd>, set the following in VS Code's keybindings.json settings file.\n\n  <!-- prettier-ignore -->\n  ```json\n  {\n    \"key\": \"up\",\n    \"command\": \"cursorUp\",\n    \"when\": \"editorTextFocus && vim.active && !inDebugRepl && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible\"\n  },\n  {\n    \"key\": \"down\",\n    \"command\": \"cursorDown\",\n    \"when\": \"editorTextFocus && vim.active && !inDebugRepl && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible\"\n  },\n  {\n    \"key\": \"k\",\n    \"command\": \"cursorUp\",\n    \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode == 'Normal' && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible\"\n  },\n  {\n    \"key\": \"j\",\n    \"command\": \"cursorDown\",\n    \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode == 'Normal' && !suggestWidgetMultipleSuggestions && !suggestWidgetVisible\"\n  }\n  ```\n\n  **Caveats:** This solution restores the default VS Code behavior for the <kbd>j</kbd> and <kbd>k</kbd> keys, so motions like `10j` [will not work](https://github.com/VSCodeVim/Vim/pull/3623#issuecomment-481473981). If you need these motions to work, [other, less performant options exist](https://github.com/VSCodeVim/Vim/issues/2924#issuecomment-476121848).\n\n- I've swapped Escape and Caps Lock with setxkbmap and VSCodeVim isn't respecting the swap\n\n  This is a [known issue in VS Code](https://github.com/microsoft/vscode/issues/23991), as a workaround you can set `\"keyboard.dispatch\": \"keyCode\"` and restart VS Code.\n\n- VSCodeVim is too slow!\n\n  You can try adding the following [setting](https://github.com/microsoft/vscode/issues/75627#issuecomment-1078827311), and reload/restart VSCode:\n\n  ```json\n  \"extensions.experimental.affinity\": {\n    \"vscodevim.vim\": 1\n  }\n  ```\n\n  **Caveats:** One issue with using the affinity setting is that each time you update your settings file, the Vim plugin will reload, which can take a few seconds.\n\n## ❤️ Contributing\n\nThis project is maintained by a group of awesome [people](https://github.com/VSCodeVim/Vim/graphs/contributors) and contributions are extremely welcome :heart:. For a quick tutorial on how you can help, see our [contributing guide](/.github/CONTRIBUTING.md).\n\n<a href=\"https://www.buymeacoffee.com/jasonpoon\" target=\"_blank\"><img src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Us A Coffee\" style=\"height: auto !important;width: auto !important;\" ></a>\n\n### Special shoutouts to:\n\n- Thanks to @xconverge for making over 100 commits to the repo. If you're wondering why your least favorite bug packed up and left, it was probably him.\n- Thanks to @Metamist for implementing EasyMotion!\n- Thanks to @sectioneight for implementing text objects!\n- Special props to [Kevin Coleman](http://kevincoleman.io), who created our awesome logo!\n- Shoutout to @chillee aka Horace He for his contributions and hard work.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nPlease _do not_ open an issue for a security vulnerability.\n\nInstead, please send an email to jasonfields4@gmail.com with \"SECURITY\" in the subject. I should get back to you within 48 hours; please follow up with another email if I do not.\n"
  },
  {
    "path": "build/Dockerfile",
    "content": "FROM node:22\n\nARG DEBIAN_FRONTEND=noninteractive\n\nRUN apt-get update && \\\n    apt-get install -y xorg xvfb libxss-dev libgtk-3-0 gconf2 libnss3 libasound2 libsecret-1-0\n\nENV CXX=\"g++-4.9\"\nENV CC=\"gcc-4.9\"\nENV DISPLAY=:99.0\n\nWORKDIR /app\n\nENTRYPOINT [\"sh\", \"-c\", \"(Xvfb $DISPLAY -screen 0 1024x768x16 &) && npm run test\"]\n"
  },
  {
    "path": "extension.ts",
    "content": "/**\n * Extension.ts is a lightweight wrapper around ModeHandler. It converts key\n * events to their string names and passes them on to ModeHandler via\n * handleKeyEvent().\n */\nimport './src/actions/include-main';\nimport './src/actions/include-plugins';\n\n/**\n * Load configuration validator\n */\n\nimport './src/configuration/validators/inputMethodSwitcherValidator';\nimport './src/configuration/validators/neovimValidator';\nimport './src/configuration/validators/remappingValidator';\nimport './src/configuration/validators/vimrcValidator';\n\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport {\n  activate as activateFunc,\n  loadConfiguration,\n  registerCommand,\n  registerEventListener,\n} from './extensionBase';\nimport { vimrc } from './src/configuration/vimrc';\nimport { Globals } from './src/globals';\nimport { Register } from './src/register/register';\nimport { Logger } from './src/util/logger';\n\nexport { getAndUpdateModeHandler } from './extensionBase';\n\nexport async function activate(context: vscode.ExtensionContext) {\n  // Set the storage path to be used by history files\n  Globals.extensionStoragePath = context.globalStorageUri.fsPath;\n\n  await activateFunc(context);\n\n  registerEventListener(context, vscode.workspace.onDidSaveTextDocument, async (document) => {\n    if (vimrc.vimrcPath && path.relative(document.fileName, vimrc.vimrcPath) === '') {\n      await loadConfiguration();\n      Logger.info('Sourced new .vimrc');\n    }\n  });\n\n  registerCommand(\n    context,\n    'vim.editVimrc',\n    async () => {\n      if (vimrc.vimrcPath) {\n        const document = await vscode.workspace.openTextDocument(vimrc.vimrcPath);\n        await vscode.window.showTextDocument(document);\n      } else {\n        await vscode.window.showWarningMessage('No .vimrc found. Please set `vim.vimrc.path`.');\n      }\n    },\n    false,\n  );\n}\n\nexport async function deactivate() {\n  await Register.saveToDisk(true);\n}\n"
  },
  {
    "path": "extensionBase.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { ExCommandLine, SearchCommandLine } from './src/cmd_line/commandLine';\nimport { configuration } from './src/configuration/configuration';\nimport { Notation } from './src/configuration/notation';\nimport { Globals } from './src/globals';\nimport { Jump } from './src/jumps/jump';\nimport { Mode } from './src/mode/mode';\nimport { ModeHandler } from './src/mode/modeHandler';\nimport { ModeHandlerMap } from './src/mode/modeHandlerMap';\nimport { Register } from './src/register/register';\nimport { CompositionState } from './src/state/compositionState';\nimport { globalState } from './src/state/globalState';\nimport { StatusBar } from './src/statusBar';\nimport { taskQueue } from './src/taskQueue';\nimport { Logger } from './src/util/logger';\nimport { SpecialKeys } from './src/util/specialKeys';\nimport { VSCodeContext } from './src/util/vscodeContext';\nimport { exCommandParser } from './src/vimscript/exCommandParser';\n\nlet extensionContext: vscode.ExtensionContext;\nlet previousActiveEditorUri: vscode.Uri | undefined;\nlet lastClosedModeHandler: ModeHandler | null = null;\n\ninterface ICodeKeybinding {\n  after?: string[];\n  commands?: Array<{ command: string; args: any[] }>;\n}\n\nexport async function getAndUpdateModeHandler(\n  forceSyncAndUpdate = false,\n): Promise<ModeHandler | undefined> {\n  const activeTextEditor = vscode.window.activeTextEditor;\n  if (activeTextEditor === undefined || activeTextEditor.document.isClosed) {\n    return undefined;\n  }\n\n  const [curHandler, isNew] = await ModeHandlerMap.getOrCreate(activeTextEditor);\n  if (isNew) {\n    extensionContext.subscriptions.push(curHandler);\n  }\n\n  curHandler.vimState.editor = activeTextEditor;\n\n  if (\n    forceSyncAndUpdate ||\n    !previousActiveEditorUri ||\n    previousActiveEditorUri !== activeTextEditor.document.uri\n  ) {\n    // We sync the cursors here because ModeHandler is specific to a document, not an editor, so we\n    // need to update our representation of the cursors when switching between editors for the same document.\n    // This will be unnecessary once #4889 is fixed.\n    curHandler.syncCursors();\n    curHandler.updateView({ drawSelection: false, revealRange: false });\n  }\n\n  previousActiveEditorUri = activeTextEditor.document.uri;\n\n  if (curHandler.focusChanged) {\n    curHandler.focusChanged = false;\n\n    if (previousActiveEditorUri) {\n      const prevHandler = ModeHandlerMap.get(previousActiveEditorUri);\n      prevHandler!.focusChanged = true;\n    }\n  }\n\n  return curHandler;\n}\n\n/**\n * Loads and validates the user's configuration\n */\nexport async function loadConfiguration() {\n  const validatorResults = await configuration.load();\n\n  Logger.debug(`${validatorResults.numErrors} errors found with vim configuration`);\n\n  if (validatorResults.numErrors > 0) {\n    for (const validatorResult of validatorResults.get()) {\n      switch (validatorResult.level) {\n        case 'error':\n          Logger.error(validatorResult.message);\n          break;\n        case 'warning':\n          Logger.warn(validatorResult.message);\n          break;\n      }\n    }\n  }\n}\n\n/**\n * The extension's entry point\n */\nexport async function activate(context: vscode.ExtensionContext, handleLocal: boolean = true) {\n  ExCommandLine.parser = exCommandParser;\n\n  Logger.init();\n\n  // before we do anything else, we need to load the configuration\n  await loadConfiguration();\n\n  Logger.debug('Start');\n\n  extensionContext = context;\n  extensionContext.subscriptions.push(StatusBar);\n\n  // Load state\n  Register.loadFromDisk(handleLocal);\n  await Promise.all([ExCommandLine.loadHistory(context), SearchCommandLine.loadHistory(context)]);\n\n  const document = vscode.window.activeTextEditor?.document;\n  if (document) {\n    const filepathComponents = document.fileName.split(/\\\\|\\//);\n    Register.setReadonlyRegister('%', filepathComponents.at(-1)!);\n  }\n\n  // workspace events\n  registerEventListener(\n    context,\n    vscode.workspace.onDidChangeConfiguration,\n    async () => {\n      Logger.info('Configuration changed');\n      await loadConfiguration();\n    },\n    false,\n  );\n\n  registerEventListener(context, vscode.workspace.onDidChangeTextDocument, async (event) => {\n    if (event.document.uri.scheme === 'output') {\n      // Without this, we'll get an infinite logging loop\n      return;\n    }\n    if (event.contentChanges.length === 0) {\n      // This happens when the document is saved\n      return;\n    }\n\n    Logger.debug(\n      `${event.contentChanges.length} change(s) to ${event.document.fileName} because ${event.reason}`,\n    );\n    for (const x of event.contentChanges) {\n      Logger.trace(`\\t-${x.rangeLength}, +'${x.text}'`);\n    }\n\n    for (const change of event.contentChanges) {\n      if (change.rangeLength > 0) {\n        globalState.jumpTracker.handleTextDeleted(event.document, change.range);\n      }\n      if (change.text.length > 0) {\n        globalState.jumpTracker.handleTextAdded(event.document, change.range.start, change.text);\n      }\n    }\n\n    const mh = ModeHandlerMap.get(event.document.uri);\n    if (mh) {\n      // Change from VSCode editor should set document.isDirty to true but they initially don't!\n      // There is a timing issue in VSCode codebase between when the isDirty flag is set and\n      // when registered callbacks are fired. https://github.com/Microsoft/vscode/issues/11339\n      if (mh.vimState.currentMode === Mode.Insert) {\n        mh.vimState.historyTracker.currentContentChanges.push(...event.contentChanges);\n      }\n    }\n  });\n\n  registerEventListener(\n    context,\n    vscode.workspace.onDidCloseTextDocument,\n    async (closedDocument) => {\n      Logger.info(`${closedDocument.fileName} closed`);\n\n      // Delete modehandler once all tabs of this document have been closed\n      for (const [uri, modeHandler] of ModeHandlerMap.entries()) {\n        let shouldDelete = false;\n        if (modeHandler == null) {\n          shouldDelete = true;\n        } else {\n          if (!vscode.workspace.textDocuments.includes(modeHandler.vimState.document)) {\n            shouldDelete = true;\n            if (closedDocument === modeHandler.vimState.document) {\n              lastClosedModeHandler = modeHandler;\n            }\n          }\n        }\n\n        if (shouldDelete) {\n          ModeHandlerMap.delete(uri);\n        }\n      }\n    },\n    false,\n  );\n\n  // window events\n  registerEventListener(\n    context,\n    vscode.window.onDidChangeActiveTextEditor,\n    async (activeTextEditor: vscode.TextEditor | undefined) => {\n      if (activeTextEditor) {\n        Logger.info(`Active editor: ${activeTextEditor.document.uri}`);\n      } else {\n        Logger.debug(`No active editor`);\n      }\n\n      const mhPrevious: ModeHandler | undefined = previousActiveEditorUri\n        ? ModeHandlerMap.get(previousActiveEditorUri)\n        : undefined;\n      // Track the closed editor so we can use it the next time an open event occurs.\n      // When vscode changes away from a temporary file, onDidChangeActiveTextEditor first twice.\n      // First it fires when leaving the closed editor. Then onDidCloseTextDocument first, and we delete\n      // the old ModeHandler. Then a new editor opens.\n      //\n      // This also applies to files that are merely closed, which allows you to jump back to that file similarly\n      // once a new file is opened.\n      lastClosedModeHandler = mhPrevious || lastClosedModeHandler;\n\n      const oldFileRegister = (await Register.get('%'))?.text;\n      const relativePath = activeTextEditor\n        ? vscode.workspace.asRelativePath(activeTextEditor.document.uri, false)\n        : '';\n\n      if (relativePath !== oldFileRegister) {\n        if (oldFileRegister && oldFileRegister !== '') {\n          Register.setReadonlyRegister('#', oldFileRegister as string);\n        }\n        Register.setReadonlyRegister('%', relativePath);\n      }\n\n      if (activeTextEditor === undefined) {\n        return;\n      }\n\n      taskQueue.enqueueTask(async () => {\n        const mh = await getAndUpdateModeHandler(true);\n        if (mh) {\n          globalState.jumpTracker.handleFileJump(\n            lastClosedModeHandler ? Jump.fromStateNow(lastClosedModeHandler.vimState) : undefined,\n            Jump.fromStateNow(mh.vimState),\n          );\n        }\n      });\n    },\n    true,\n    true,\n  );\n\n  registerEventListener(\n    context,\n    vscode.window.onDidChangeTextEditorSelection,\n    async (e: vscode.TextEditorSelectionChangeEvent) => {\n      if (e.textEditor.document.uri.scheme === 'output') {\n        // Without this, we can an infinite logging loop\n        return;\n      }\n      if (\n        vscode.window.activeTextEditor === undefined ||\n        e.textEditor.document !== vscode.window.activeTextEditor.document\n      ) {\n        // We don't care if user selection changed in a paneled window (e.g debug console/terminal)\n        return;\n      }\n\n      const mh = ModeHandlerMap.get(vscode.window.activeTextEditor.document.uri);\n      if (mh === undefined) {\n        // We don't care if there is no active editor\n        return;\n      }\n\n      if (\n        e.kind !== vscode.TextEditorSelectionChangeKind.Mouse &&\n        mh.internalSelectionsTracker.shouldIgnoreAsInternalSelectionChangeEvent(e)\n      ) {\n        return;\n      }\n\n      // We may receive changes from other panels when, having selections in them containing the same file\n      // and changing text before the selection in current panel.\n      if (e.textEditor !== mh.vimState.editor) {\n        return;\n      }\n\n      if (mh.focusChanged) {\n        mh.focusChanged = false;\n        return;\n      }\n\n      if (mh.vimState.currentMode === Mode.EasyMotionMode) {\n        return;\n      }\n\n      taskQueue.enqueueTask(() => mh.handleSelectionChange(e));\n    },\n    true,\n    false,\n  );\n\n  registerEventListener(\n    context,\n    vscode.window.onDidChangeTextEditorVisibleRanges,\n    async (e: vscode.TextEditorVisibleRangesChangeEvent) => {\n      if (e.textEditor !== vscode.window.activeTextEditor) {\n        return;\n      }\n      taskQueue.enqueueTask(async () => {\n        // Scrolling the viewport clears any status bar message, even errors.\n        const mh = await getAndUpdateModeHandler();\n        if (mh && StatusBar.lastMessageTime) {\n          // TODO: Using the time elapsed works most of the time, but is a bit of a hack\n          const timeElapsed = Date.now() - Number(StatusBar.lastMessageTime);\n          if (timeElapsed > 100) {\n            StatusBar.clear(mh.vimState, true);\n          }\n        }\n      });\n    },\n  );\n\n  const compositionState = new CompositionState();\n\n  // Override VSCode commands\n  overrideCommand(context, 'type', async (args: { text: string }) => {\n    taskQueue.enqueueTask(async () => {\n      const mh = await getAndUpdateModeHandler();\n      if (mh) {\n        if (compositionState.isInComposition) {\n          compositionState.composingText += args.text;\n          if (mh.vimState.currentMode === Mode.Insert) {\n            compositionState.insertedText = true;\n            void vscode.commands.executeCommand('default:type', { text: args.text });\n          }\n        } else {\n          await mh.handleKeyEvent(args.text);\n        }\n      }\n    });\n  });\n\n  overrideCommand(\n    context,\n    'replacePreviousChar',\n    async (args: { replaceCharCnt: number; text: string }) => {\n      taskQueue.enqueueTask(async () => {\n        const mh = await getAndUpdateModeHandler();\n        if (mh) {\n          if (compositionState.isInComposition) {\n            compositionState.composingText =\n              compositionState.composingText.substr(\n                0,\n                compositionState.composingText.length - args.replaceCharCnt,\n              ) + args.text;\n          }\n          if (compositionState.insertedText) {\n            await vscode.commands.executeCommand('default:replacePreviousChar', {\n              text: args.text,\n              replaceCharCnt: args.replaceCharCnt,\n            });\n            mh.vimState.cursorStopPosition = mh.vimState.editor.selection.start;\n            mh.vimState.cursorStartPosition = mh.vimState.editor.selection.start;\n          }\n        } else {\n          await vscode.commands.executeCommand('default:replacePreviousChar', {\n            text: args.text,\n            replaceCharCnt: args.replaceCharCnt,\n          });\n        }\n      });\n    },\n  );\n\n  overrideCommand(context, 'compositionStart', async () => {\n    taskQueue.enqueueTask(async () => {\n      compositionState.isInComposition = true;\n    });\n  });\n\n  overrideCommand(context, 'compositionEnd', async () => {\n    taskQueue.enqueueTask(async () => {\n      const mh = await getAndUpdateModeHandler();\n      if (mh) {\n        if (compositionState.insertedText) {\n          mh.internalSelectionsTracker.startIgnoringIntermediateSelections();\n          await vscode.commands.executeCommand('default:replacePreviousChar', {\n            text: '',\n            replaceCharCnt: compositionState.composingText.length,\n          });\n          mh.vimState.cursorStopPosition = mh.vimState.editor.selection.active;\n          mh.vimState.cursorStartPosition = mh.vimState.editor.selection.active;\n          mh.internalSelectionsTracker.stopIgnoringIntermediateSelections();\n        }\n        const text = compositionState.composingText;\n        if (compositionState.insertedText) {\n          await mh.handleMultipleKeyEvents([text]);\n        } else {\n          await mh.handleMultipleKeyEvents(text.split(''));\n        }\n      }\n      compositionState.reset();\n    });\n  });\n\n  // Register extension commands\n  registerCommand(context, 'vim.showQuickpickCmdLine', async () => {\n    const mh = await getAndUpdateModeHandler();\n    if (mh) {\n      const cmd = await vscode.window.showInputBox({\n        prompt: 'Vim command line',\n        value: '',\n        ignoreFocusOut: false,\n        valueSelection: [0, 0],\n      });\n      if (cmd) {\n        await new ExCommandLine(cmd, mh.vimState.currentMode).run(mh.vimState);\n      }\n      mh.updateView();\n    }\n  });\n\n  registerCommand(context, 'vim.remap', async (args: ICodeKeybinding) => {\n    taskQueue.enqueueTask(async () => {\n      const mh = await getAndUpdateModeHandler();\n      if (mh === undefined) {\n        return;\n      }\n\n      if (!args) {\n        throw new Error(\n          \"'args' is undefined. For this remap to work it needs to have 'args' with an '\\\"after\\\": string[]' and/or a '\\\"commands\\\": { command: string; args: any[] }[]'\",\n        );\n      }\n\n      if (args.after) {\n        for (const key of args.after) {\n          await mh.handleKeyEvent(Notation.NormalizeKey(key, configuration.leader));\n        }\n      }\n\n      if (args.commands) {\n        for (const command of args.commands) {\n          // Check if this is a vim command by looking for :\n          if (command.command.startsWith(':')) {\n            await new ExCommandLine(\n              command.command.slice(1, command.command.length),\n              mh.vimState.currentMode,\n            ).run(mh.vimState);\n            mh.updateView();\n          } else {\n            await vscode.commands.executeCommand(command.command, command.args);\n          }\n        }\n      }\n    });\n  });\n\n  registerCommand(context, 'toggleVim', async () => {\n    configuration.disableExtension = !configuration.disableExtension;\n    void toggleExtension(configuration.disableExtension, compositionState);\n  });\n\n  for (const boundKey of configuration.boundKeyCombinations) {\n    const command = ['<Esc>', '<C-c>'].includes(boundKey.key)\n      ? async () => {\n          const mh = await getAndUpdateModeHandler();\n          if (mh && !(await forceStopRecursiveRemap(mh))) {\n            await mh.handleKeyEvent(`${boundKey.key}`);\n          }\n        }\n      : async () => {\n          const mh = await getAndUpdateModeHandler();\n          if (mh) {\n            await mh.handleKeyEvent(`${boundKey.key}`);\n          }\n        };\n    registerCommand(context, boundKey.command, async () => {\n      taskQueue.enqueueTask(command);\n    });\n  }\n\n  {\n    // Initialize mode handler for current active Text Editor at startup.\n    const modeHandler = await getAndUpdateModeHandler();\n    if (modeHandler) {\n      if (!configuration.startInInsertMode) {\n        const vimState = modeHandler.vimState;\n\n        // Make sure no cursors start on the EOL character (which is invalid in normal mode)\n        // This can happen if we quit last session in insert mode at the end of the line\n        vimState.cursors = vimState.cursors.map((cursor) => {\n          const eolColumn = vimState.document.lineAt(cursor.stop).text.length;\n          if (cursor.stop.character >= eolColumn) {\n            const character = Math.max(eolColumn - 1, 0);\n            return cursor.withNewStop(cursor.stop.with({ character }));\n          } else {\n            return cursor;\n          }\n        });\n      }\n\n      // This is called last because getAndUpdateModeHandler() will change cursor\n      void modeHandler.updateView({ drawSelection: true, revealRange: false });\n    }\n  }\n\n  // Disable automatic keyboard navigation in lists, so it doesn't interfere\n  // with our list navigation keybindings\n  await VSCodeContext.set('listAutomaticKeyboardNavigation', false);\n\n  await toggleExtension(configuration.disableExtension, compositionState);\n\n  Logger.debug('Finish.');\n}\n\n/**\n * Toggles the VSCodeVim extension between Enabled mode and Disabled mode. This\n * function is activated by calling the 'toggleVim' command from the Command Palette.\n *\n * @param isDisabled if true, sets VSCodeVim to Disabled mode; else sets to enabled mode\n */\nasync function toggleExtension(isDisabled: boolean, compositionState: CompositionState) {\n  await VSCodeContext.set('vim.active', !isDisabled);\n  const mh = await getAndUpdateModeHandler();\n  if (mh) {\n    if (isDisabled) {\n      await mh.handleKeyEvent(SpecialKeys.ExtensionDisable);\n      compositionState.reset();\n      ModeHandlerMap.clear();\n    } else {\n      await mh.handleKeyEvent(SpecialKeys.ExtensionEnable);\n    }\n  }\n}\n\nfunction overrideCommand(\n  context: vscode.ExtensionContext,\n  command: string,\n  callback: (...args: any[]) => any,\n) {\n  const disposable = vscode.commands.registerCommand(command, async (args) => {\n    if (configuration.disableExtension) {\n      return vscode.commands.executeCommand('default:' + command, args);\n    }\n\n    if (!vscode.window.activeTextEditor) {\n      return;\n    }\n\n    if (\n      vscode.window.activeTextEditor.document &&\n      vscode.window.activeTextEditor.document.uri.toString() === 'debug:input'\n    ) {\n      return vscode.commands.executeCommand('default:' + command, args);\n    }\n\n    return callback(args) as vscode.Disposable;\n  });\n  context.subscriptions.push(disposable);\n}\n\nexport function registerCommand(\n  context: vscode.ExtensionContext,\n  command: string,\n  callback: (...args: any[]) => any,\n  requiresActiveEditor: boolean = true,\n) {\n  const disposable = vscode.commands.registerCommand(command, async (args) => {\n    if (requiresActiveEditor && !vscode.window.activeTextEditor) {\n      return;\n    }\n\n    callback(args);\n  });\n  context.subscriptions.push(disposable);\n}\n\nexport function registerEventListener<T>(\n  context: vscode.ExtensionContext,\n  event: vscode.Event<T>,\n  listener: (e: T) => Promise<void>,\n  exitOnExtensionDisable = true,\n  exitOnTests = false,\n) {\n  const disposable = event(async (e) => {\n    if (exitOnExtensionDisable && configuration.disableExtension) {\n      return;\n    }\n\n    if (exitOnTests && Globals.isTesting) {\n      return;\n    }\n\n    await listener(e);\n  });\n  context.subscriptions.push(disposable);\n}\n\n/**\n * @returns true if there was a remap being executed to stop\n */\nasync function forceStopRecursiveRemap(mh: ModeHandler): Promise<boolean> {\n  if (mh.remapState.isCurrentlyPerformingRecursiveRemapping) {\n    mh.remapState.forceStopRecursiveRemapping = true;\n    return true;\n  }\n\n  return false;\n}\n"
  },
  {
    "path": "extensionWeb.ts",
    "content": "/**\n * Extension.ts is a lightweight wrapper around ModeHandler. It converts key\n * events to their string names and passes them on to ModeHandler via\n * handleKeyEvent().\n */\nimport './src/actions/include-main';\nimport './src/actions/include-plugins';\n\n/**\n * Load configuration validator\n */\n\nimport './src/configuration/validators/inputMethodSwitcherValidator';\nimport './src/configuration/validators/remappingValidator';\n\nimport * as vscode from 'vscode';\nimport { activate as activateFunc } from './extensionBase';\n\nexport async function activate(context: vscode.ExtensionContext) {\n  void activateFunc(context, false);\n}\n"
  },
  {
    "path": "gulpfile.js",
    "content": "const gulp = require('gulp');\nconst bump = require('gulp-bump');\nconst tag_version = require('gulp-tag-version');\nconst ts = require('gulp-typescript');\nconst PluginError = require('plugin-error');\nconst minimist = require('minimist');\nconst path = require('path');\nconst es = require('event-stream');\nconst shell = require('gulp-shell');\n\nconst releaseOptions = {\n  semver: '',\n};\n\nfunction validateArgs(done) {\n  const options = minimist(process.argv.slice(2), releaseOptions);\n  if (!options.semver) {\n    return done(\n      new PluginError('updateVersion', {\n        message: 'Missing `--semver` option. Possible values: patch, minor, major',\n      }),\n    );\n  }\n  if (!['patch', 'minor', 'major'].includes(options.semver)) {\n    return done(\n      new PluginError('updateVersion', {\n        message: 'Invalid `--semver` option. Possible values: patch, minor, major',\n      }),\n    );\n  }\n\n  done();\n}\n\nfunction createGitTag() {\n  return gulp.src(['./package.json']).pipe(tag_version());\n}\n\nfunction updateVersion(done) {\n  var options = minimist(process.argv.slice(2), releaseOptions);\n\n  return gulp\n    .src(['./package.json', './yarn.lock'])\n    .pipe(bump({ type: options.semver }))\n    .pipe(gulp.dest('./'))\n    .on('end', () => {\n      done();\n    });\n}\n\nfunction updatePath() {\n  const input = es.through();\n  const output = input.pipe(\n    es.mapSync((f) => {\n      const contents = f.contents.toString('utf8');\n      const filePath = f.path;\n      let platformRelativepath = path.relative(\n        path.dirname(filePath),\n        path.resolve(process.cwd(), 'out/src/platform/node'),\n      );\n      platformRelativepath = platformRelativepath.replace(/\\\\/g, '/');\n      f.contents = Buffer.from(\n        contents.replace(\n          /\\(\\\"platform\\/([^\"]*)\\\"\\)/g,\n          '(\"' + (platformRelativepath === '' ? './' : platformRelativepath + '/') + '$1\")',\n        ),\n        'utf8',\n      );\n      return f;\n    }),\n  );\n  return es.duplex(input, output);\n}\n\nfunction copyPackageJson() {\n  return gulp.src('./package.json').pipe(gulp.dest('out'));\n}\n\ngulp.task('tsc', function () {\n  var isError = false;\n\n  var tsProject = ts.createProject('tsconfig.json', { noEmitOnError: true });\n  var tsResult = tsProject\n    .src()\n    .pipe(tsProject())\n    .on('error', () => {\n      isError = true;\n    })\n    .on('finish', () => {\n      isError && process.exit(1);\n    });\n\n  return tsResult.js.pipe(updatePath()).pipe(gulp.dest('out'));\n});\n\n// test\ngulp.task('run-test', function (done) {\n  // the flag --grep takes js regex as a string and filters by test and test suite names\n  var knownOptions = {\n    string: 'grep',\n    default: { grep: '' },\n  };\n  var options = minimist(process.argv.slice(2), knownOptions);\n\n  var spawn = require('child_process').spawn;\n  const dockerTag = 'vscodevim';\n\n  console.log('Building container...');\n  var dockerBuildCmd = spawn(\n    'docker',\n    ['build', '-f', './build/Dockerfile', './build/', '-t', dockerTag],\n    {\n      cwd: process.cwd(),\n      stdio: 'inherit',\n    },\n  );\n\n  dockerBuildCmd.on('exit', function (exitCode) {\n    if (exitCode !== 0) {\n      return done(\n        new PluginError('test', {\n          message: 'Docker build failed.',\n        }),\n      );\n    }\n\n    const dockerRunArgs = [\n      'run',\n      '-it',\n      '--rm',\n      '--env',\n      `MOCHA_GREP=${options.grep}`,\n      '-v',\n      process.cwd() + ':/app',\n      dockerTag,\n    ];\n    console.log('Running tests inside container...');\n    var dockerRunCmd = spawn('docker', dockerRunArgs, {\n      cwd: process.cwd(),\n      stdio: 'inherit',\n    });\n\n    dockerRunCmd.on('exit', function (exitCode) {\n      done(exitCode);\n    });\n  });\n});\n\ngulp.task('prepare-test', gulp.parallel('tsc', copyPackageJson));\ngulp.task('test', gulp.series('prepare-test', 'run-test'));\ngulp.task(\n  'release',\n  gulp.series(\n    validateArgs,\n    updateVersion,\n    shell.task('git commit -am \"bump version\"'),\n    createGitTag,\n  ),\n);\ngulp.task('default', shell.task('yarn build-dev'));\n"
  },
  {
    "path": "language-configuration.json",
    "content": "{\n  \"comments\": {\n    \"lineComment\": \"\\\"\"\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vim\",\n  \"displayName\": \"Vim\",\n  \"description\": \"Vim emulation for Visual Studio Code\",\n  \"icon\": \"images/icon.png\",\n  \"version\": \"1.32.4\",\n  \"publisher\": \"vscodevim\",\n  \"sponsor\": {\n    \"url\": \"https://github.com/sponsors/J-Fields\"\n  },\n  \"galleryBanner\": {\n    \"color\": \"#e3f4ff\",\n    \"theme\": \"light\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"vim\",\n    \"vi\",\n    \"vscodevim\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/VSCodeVim/Vim.git\"\n  },\n  \"homepage\": \"https://github.com/VSCodeVim/Vim\",\n  \"bugs\": {\n    \"url\": \"https://github.com/VSCodeVim/Vim/issues\"\n  },\n  \"engines\": {\n    \"vscode\": \"^1.74.0\"\n  },\n  \"categories\": [\n    \"Other\",\n    \"Keymaps\"\n  ],\n  \"extensionKind\": [\n    \"ui\"\n  ],\n  \"sideEffects\": false,\n  \"activationEvents\": [\n    \"onStartupFinished\",\n    \"onCommand:type\"\n  ],\n  \"qna\": \"https://github.com/VSCodeVim/Vim/discussions\",\n  \"main\": \"./out/extension\",\n  \"browser\": \"./out/extensionWeb\",\n  \"capabilities\": {\n    \"untrustedWorkspaces\": {\n      \"supported\": true\n    },\n    \"virtualWorkspaces\": true\n  },\n  \"contributes\": {\n    \"commands\": [\n      {\n        \"command\": \"toggleVim\",\n        \"title\": \"Vim: Toggle Vim Mode\"\n      },\n      {\n        \"command\": \"vim.showQuickpickCmdLine\",\n        \"title\": \"Vim: Show Command Line\"\n      },\n      {\n        \"command\": \"vim.editVimrc\",\n        \"enablement\": \"!isWeb\",\n        \"title\": \"Vim: Edit .vimrc\"\n      }\n    ],\n    \"keybindings\": [\n      {\n        \"key\": \"Escape\",\n        \"command\": \"extension.vim_escape\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"Escape\",\n        \"command\": \"notebook.cell.quitEdit\",\n        \"when\": \"inputFocus && notebookEditorFocused && !editorHasSelection && !editorHoverVisible && vim.active && vim.mode == 'Normal'\"\n      },\n      {\n        \"key\": \"Home\",\n        \"command\": \"extension.vim_home\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"ctrl+home\",\n        \"command\": \"extension.vim_ctrl+home\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"End\",\n        \"command\": \"extension.vim_end\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"ctrl+end\",\n        \"command\": \"extension.vim_ctrl+end\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"Insert\",\n        \"command\": \"extension.vim_insert\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"Backspace\",\n        \"command\": \"extension.vim_backspace\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"Delete\",\n        \"command\": \"extension.vim_delete\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"tab\",\n        \"command\": \"extension.vim_tab\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl && !inlineEditIsVisible\"\n      },\n      {\n        \"key\": \"shift+tab\",\n        \"command\": \"extension.vim_shift+tab\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"left\",\n        \"command\": \"extension.vim_left\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"right\",\n        \"command\": \"extension.vim_right\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"up\",\n        \"command\": \"extension.vim_up\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && !suggestWidgetVisible && !parameterHintsVisible\"\n      },\n      {\n        \"key\": \"down\",\n        \"command\": \"extension.vim_down\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl && !suggestWidgetVisible && !parameterHintsVisible\"\n      },\n      {\n        \"key\": \"g g\",\n        \"command\": \"list.focusFirst\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"h\",\n        \"command\": \"list.collapse\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"j\",\n        \"command\": \"list.focusDown\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"k\",\n        \"command\": \"list.focusUp\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"l\",\n        \"command\": \"list.select\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"o\",\n        \"command\": \"list.toggleExpand\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"/\",\n        \"command\": \"list.toggleKeyboardNavigation\",\n        \"when\": \"listFocus && !inputFocus && listSupportsKeyboardNavigation\"\n      },\n      {\n        \"key\": \"ctrl+a\",\n        \"command\": \"extension.vim_ctrl+a\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-a> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+b\",\n        \"command\": \"extension.vim_ctrl+b\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-b> && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+c\",\n        \"command\": \"extension.vim_ctrl+c\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-c> && !inDebugRepl && vim.overrideCtrlC\"\n      },\n      {\n        \"key\": \"ctrl+d\",\n        \"command\": \"extension.vim_ctrl+d\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-d> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+d\",\n        \"command\": \"list.focusPageDown\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"ctrl+e\",\n        \"command\": \"extension.vim_ctrl+e\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-e> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+f\",\n        \"command\": \"extension.vim_ctrl+f\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-f> && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+g\",\n        \"command\": \"extension.vim_ctrl+g\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-g> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+h\",\n        \"command\": \"extension.vim_ctrl+h\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-h> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+i\",\n        \"command\": \"extension.vim_ctrl+i\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-i> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+j\",\n        \"command\": \"extension.vim_ctrl+j\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-j> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+k\",\n        \"command\": \"extension.vim_ctrl+k\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-k> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+l\",\n        \"command\": \"extension.vim_navigateCtrlL\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-l> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+m\",\n        \"command\": \"extension.vim_ctrl+m\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-m> && !inDebugRepl || vim.mode == 'CommandlineInProgress' && vim.active && vim.use<C-m> && !inDebugRepl || vim.mode == 'SearchInProgressMode' && vim.active && vim.use<C-m> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+n\",\n        \"command\": \"extension.vim_ctrl+n\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-n> && !inDebugRepl || vim.mode == 'CommandlineInProgress' && vim.active && vim.use<C-n> && !inDebugRepl || vim.mode == 'SearchInProgressMode' && vim.active && vim.use<C-n> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+o\",\n        \"command\": \"extension.vim_ctrl+o\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-o> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+p\",\n        \"command\": \"extension.vim_ctrl+p\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-p> && !inDebugRepl || vim.mode == 'CommandlineInProgress' && vim.active && vim.use<C-p> && !inDebugRepl || vim.mode == 'SearchInProgressMode' && vim.active && vim.use<C-p> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+q\",\n        \"command\": \"extension.vim_winCtrlQ\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-q> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+r\",\n        \"command\": \"extension.vim_ctrl+r\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-r> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+s\",\n        \"command\": \"extension.vim_ctrl+s\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-s> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+t\",\n        \"command\": \"extension.vim_ctrl+t\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-t> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+u\",\n        \"command\": \"extension.vim_ctrl+u\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-u> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+u\",\n        \"command\": \"list.focusPageUp\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"ctrl+v\",\n        \"command\": \"extension.vim_ctrl+v\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-v> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+w\",\n        \"command\": \"extension.vim_ctrl+w\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-w> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+x\",\n        \"command\": \"extension.vim_ctrl+x\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-x> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+y\",\n        \"command\": \"extension.vim_ctrl+y\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-y> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+z\",\n        \"command\": \"extension.vim_ctrl+z\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-z> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+6\",\n        \"command\": \"extension.vim_ctrl+6\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-6> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+^\",\n        \"command\": \"extension.vim_ctrl+^\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-^> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+[\",\n        \"command\": \"extension.vim_ctrl+[\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-[> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+]\",\n        \"command\": \"extension.vim_ctrl+]\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-]> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+shift+2\",\n        \"command\": \"extension.vim_ctrl+shift+2\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-shift+2>\"\n      },\n      {\n        \"key\": \"ctrl+up\",\n        \"command\": \"extension.vim_ctrl+up\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+down\",\n        \"command\": \"extension.vim_ctrl+down\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+left\",\n        \"command\": \"extension.vim_ctrl+left\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+right\",\n        \"command\": \"extension.vim_ctrl+right\",\n        \"when\": \"editorTextFocus && vim.active && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+pagedown\",\n        \"command\": \"extension.vim_ctrl+pagedown\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-pagedown> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+pageup\",\n        \"command\": \"extension.vim_ctrl+pageup\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-pageup> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+space\",\n        \"command\": \"extension.vim_ctrl+space\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-space> && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"shift+G\",\n        \"command\": \"list.focusLast\",\n        \"when\": \"listFocus && !inputFocus\"\n      },\n      {\n        \"key\": \"ctrl+backspace\",\n        \"command\": \"extension.vim_ctrl+backspace\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<C-BS> && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"shift+backspace\",\n        \"command\": \"extension.vim_shift+backspace\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<S-BS> && vim.mode != 'Insert' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"cmd+left\",\n        \"command\": \"extension.vim_cmd+left\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-left> && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"cmd+right\",\n        \"command\": \"extension.vim_cmd+right\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-right> && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"cmd+a\",\n        \"command\": \"extension.vim_cmd+a\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-a> && !inDebugRepl && vim.mode != 'Insert'\"\n      },\n      {\n        \"key\": \"cmd+c\",\n        \"command\": \"extension.vim_cmd+c\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-c> && vim.overrideCopy && !inDebugRepl\"\n      },\n      {\n        \"key\": \"cmd+d\",\n        \"command\": \"extension.vim_cmd+d\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-d> && !inDebugRepl\"\n      },\n      {\n        \"key\": \"cmd+v\",\n        \"command\": \"extension.vim_cmd+v\",\n        \"when\": \"editorTextFocus && vim.active && vim.use<D-v> && vim.mode == 'CommandlineInProgress' && !inDebugRepl || editorTextFocus && vim.active && vim.use<D-v> && vim.mode == 'SearchInProgressMode' && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+alt+down\",\n        \"linux\": \"shift+alt+down\",\n        \"mac\": \"cmd+alt+down\",\n        \"command\": \"extension.vim_cmd+alt+down\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"ctrl+alt+up\",\n        \"linux\": \"shift+alt+up\",\n        \"mac\": \"cmd+alt+up\",\n        \"command\": \"extension.vim_cmd+alt+up\",\n        \"when\": \"editorTextFocus && vim.active && !inDebugRepl\"\n      },\n      {\n        \"key\": \"j\",\n        \"command\": \"notebook.focusNextEditor\",\n        \"when\": \"vim.mode == 'Normal' && editorTextFocus && inputFocus && notebookEditorFocused && notebookEditorCursorAtBoundary != 'none' && notebookEditorCursorAtBoundary != 'top'\"\n      },\n      {\n        \"key\": \"k\",\n        \"command\": \"notebook.focusPreviousEditor\",\n        \"when\": \"vim.mode == 'Normal' && editorTextFocus && inputFocus && notebookEditorFocused && notebookEditorCursorAtBoundary != 'bottom' && notebookEditorCursorAtBoundary != 'none'\"\n      }\n    ],\n    \"configuration\": {\n      \"title\": \"Vim\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"vim.normalModeKeyBindings\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Remapped keys in Normal mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.normalModeKeyBindingsNonRecursive\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Non-recursive remapped keys in Normal mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.operatorPendingModeKeyBindings\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Remapped keys in OperatorPending mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.operatorPendingModeKeyBindingsNonRecursive\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Non-recursive remapped keys in OperatorPending mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.useCtrlKeys\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable some Vim Ctrl key commands that override otherwise common operations, like `Ctrl+C`.\",\n          \"default\": true\n        },\n        \"vim.leader\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"What key should `<leader>` map to in remappings?\",\n          \"default\": \"\\\\\"\n        },\n        \"vim.searchHighlightColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Background color of non-current search matches. The color must not be opaque so as not to hide underlying decorations. Uses `#editor.findMatchHighlightColor#` from current theme if undefined.\"\n        },\n        \"vim.searchHighlightTextColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Foreground color of non-current search matches.\"\n        },\n        \"vim.searchMatchColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Background color of the current match. Uses `#editor.findMatchColor#` from current theme if undefined.\"\n        },\n        \"vim.searchMatchTextColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Foreground color of the current match.\"\n        },\n        \"vim.substitutionColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Background color of substituted text when `#editor.inccommand#` is enabled. Uses `#editor.findMatchColor#` from current theme if undefined.\"\n        },\n        \"vim.substitutionTextColor\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Foreground color of substituted text when `#editor.inccommand#` is enabled.\"\n        },\n        \"vim.highlightedyank.enable\": {\n          \"type\": \"boolean\",\n          \"description\": \"Enable highlighting when yanking.\",\n          \"default\": false\n        },\n        \"vim.highlightedyank.color\": {\n          \"type\": \"string\",\n          \"description\": \"Background color of yanked text. The color must not be opaque so as not to hide underlying decorations.\",\n          \"default\": \"rgba(250, 240, 170, 0.5)\"\n        },\n        \"vim.highlightedyank.textColor\": {\n          \"type\": \"string\",\n          \"description\": \"Foreground color of yanked text.\"\n        },\n        \"vim.highlightedyank.duration\": {\n          \"type\": \"number\",\n          \"description\": \"Duration in milliseconds of the yank highlight.\",\n          \"default\": 200,\n          \"minimum\": 1\n        },\n        \"vim.useSystemClipboard\": {\n          \"type\": \"boolean\",\n          \"description\": \"Use system clipboard for unnamed register.\",\n          \"default\": false\n        },\n        \"vim.overrideCopy\": {\n          \"type\": \"boolean\",\n          \"description\": \"Override VS Code's copy command with our own copy command, which works better with VSCodeVim. Turn this off if copying is not working.\",\n          \"default\": true\n        },\n        \"vim.insertModeKeyBindings\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Remapped keys in Insert mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.insertModeKeyBindingsNonRecursive\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Non-recursive keybinding overrides to use for Insert mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.visualModeKeyBindings\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Remapped keys in Visual mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.visualModeKeyBindingsNonRecursive\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Non-recursive keybinding overrides to use for Visual mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.commandLineModeKeyBindings\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Remapped keys in command line mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.commandLineModeKeyBindingsNonRecursive\": {\n          \"type\": \"array\",\n          \"markdownDescription\": \"Non-recursive keybinding overrides to use for command line mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details.\",\n          \"scope\": \"application\"\n        },\n        \"vim.textwidth\": {\n          \"type\": \"number\",\n          \"markdownDescription\": \"Width to word-wrap to when using `gq`.\",\n          \"default\": 80,\n          \"scope\": \"language-overridable\",\n          \"minimum\": 1\n        },\n        \"vim.timeout\": {\n          \"type\": \"number\",\n          \"description\": \"Timeout in milliseconds for remapped commands.\",\n          \"default\": 1000,\n          \"minimum\": 0\n        },\n        \"vim.maxmapdepth\": {\n          \"type\": \"number\",\n          \"description\": \"Maximum number of times a mapping is done without resulting in a character to be used.\",\n          \"default\": 1000,\n          \"minimum\": 0\n        },\n        \"vim.scroll\": {\n          \"type\": \"number\",\n          \"markdownDescription\": \"Number of lines to scroll with `Ctrl-U` and `Ctrl-D` commands. Set to 0 to use a half page scroll.\",\n          \"default\": 0,\n          \"minimum\": 0\n        },\n        \"vim.showcmd\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show the text of any command you are in the middle of writing.\",\n          \"default\": true\n        },\n        \"vim.showmodename\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show the name of the current mode in the statusbar.\",\n          \"default\": true\n        },\n        \"vim.iskeyword\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Keywords contain alphanumeric characters and '_'. If not configured, `#editor.wordSeparators#` is used.\"\n        },\n        \"vim.ignorecase\": {\n          \"type\": \"boolean\",\n          \"description\": \"Ignore case in search patterns.\",\n          \"default\": true\n        },\n        \"vim.smartcase\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Override the `ignorecase` option if the search pattern contains upper case characters.\",\n          \"default\": true\n        },\n        \"vim.matchpairs\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Characters that form pairs. The % command jumps from one to the other. Only character pairs are allowed that are different, thus you cannot jump between two double quotes. The characters must be separated by a colon. The pairs must be separated by a comma.\",\n          \"default\": \"(:),{:},[:]\",\n          \"pattern\": \"^(.:.)?(,.:.)*$\"\n        },\n        \"vim.camelCaseMotion.enable\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the [CamelCaseMotion](https://github.com/bkad/CamelCaseMotion) plugin for Vim.\",\n          \"default\": false\n        },\n        \"vim.easymotion\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the [EasyMotion](https://github.com/easymotion/vim-easymotion) plugin for Vim.\",\n          \"default\": false\n        },\n        \"vim.easymotionMarkerBackgroundColor\": {\n          \"type\": \"string\",\n          \"default\": \"#0000\",\n          \"description\": \"Set a custom background color for EasyMotion markers.\"\n        },\n        \"vim.easymotionMarkerForegroundColorOneChar\": {\n          \"type\": \"string\",\n          \"default\": \"#ff0000\",\n          \"description\": \"Set a custom color for the text on one character long markers.\"\n        },\n        \"vim.easymotionMarkerForegroundColorTwoCharFirst\": {\n          \"type\": \"string\",\n          \"default\": \"#ffb400\",\n          \"description\": \"Set a custom color for the first character on two character long markers.\"\n        },\n        \"vim.easymotionMarkerForegroundColorTwoCharSecond\": {\n          \"type\": \"string\",\n          \"default\": \"#b98300\",\n          \"description\": \"Set a custom color for the second character on two character long markers.\"\n        },\n        \"vim.easymotionIncSearchForegroundColor\": {\n          \"type\": \"string\",\n          \"default\": \"#7fbf00\",\n          \"markdownDescription\": \"Set a custom color for the easymotion search n-character (default `<leader><leader>/`).\"\n        },\n        \"vim.easymotionDimColor\": {\n          \"type\": \"string\",\n          \"default\": \"#777777\",\n          \"markdownDescription\": \"Set a custom color for the easymotion dimmed characters when `#vim.easymotionDimBackground#` is set to true.\"\n        },\n        \"vim.easymotionDimBackground\": {\n          \"type\": \"boolean\",\n          \"description\": \"Whether to dim other text while markers are visible.\",\n          \"default\": true\n        },\n        \"vim.easymotionMarkerFontWeight\": {\n          \"type\": \"string\",\n          \"description\": \"Set the font weight of the marker text.\",\n          \"default\": \"bold\"\n        },\n        \"vim.easymotionKeys\": {\n          \"type\": \"string\",\n          \"description\": \"Set the characters used for jump marker name.\",\n          \"default\": \"hklyuiopnm,qwertzxcvbasdgjf;\"\n        },\n        \"vim.easymotionJumpToAnywhereRegex\": {\n          \"type\": \"string\",\n          \"description\": \"Regex matches for JumpToAnywhere motion.\",\n          \"default\": \"\\\\b[A-Za-z0-9]|[A-Za-z0-9]\\\\b|_.|#.|[a-z][A-Z]\"\n        },\n        \"vim.replaceWithRegister\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister) plugin for Vim.\",\n          \"default\": false\n        },\n        \"vim.smartRelativeLine\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"`#editor.lineNumbers#` is determined by the active Vim mode, absolute when in insert or disabled mode, relative otherwise.\",\n          \"default\": false\n        },\n        \"vim.targets.enable\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable [targets.vim](https://github.com/wellle/targets.vim#quote-text-objects) plugin (not fully implemented yet).\",\n          \"default\": false\n        },\n        \"vim.targets.bracketObjects.enable\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable last/next movements for bracket objects.\",\n          \"default\": true\n        },\n        \"vim.targets.smartQuotes.enable\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the smart quotes movements from [targets.vim](https://github.com/wellle/targets.vim#quote-text-objects).\",\n          \"default\": true\n        },\n        \"vim.targets.smartQuotes.breakThroughLines\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Whether to break through lines when using [n]ext/[l]ast motion, see [targets.vim#next-and-last-quote](https://github.com/wellle/targets.vim#next-and-last-quote).\",\n          \"default\": true\n        },\n        \"vim.targets.smartQuotes.aIncludesSurroundingSpaces\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Whether to use default Vim behavior when using `a` (e.g. `da'`) which include surrounding spaces, or not, as for other text objects.\",\n          \"default\": true\n        },\n        \"vim.sneak\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the [Sneak](https://github.com/justinmk/vim-sneak) plugin for Vim.\",\n          \"default\": false\n        },\n        \"vim.sneakUseIgnorecaseAndSmartcase\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Case sensitivity is determined by `#vim.ignorecase#` and `#vim.smartcase#`.\",\n          \"default\": false\n        },\n        \"vim.sneakReplacesF\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Use single-character [Sneak](https://github.com/justinmk/vim-sneak) instead of Vim's native `f`.\",\n          \"default\": false\n        },\n        \"vim.surround\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Enable the [Surround](https://github.com/tpope/vim-surround) plugin for Vim.\",\n          \"default\": true\n        },\n        \"vim.argumentObjectSeparators\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"markdownDescription\": \"Set separators for the argument text object.\",\n          \"default\": [\n            \",\"\n          ]\n        },\n        \"vim.argumentObjectOpeningDelimiters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"markdownDescription\": \"Set opening delimiters for the argument text object.\",\n          \"default\": [\n            \"(\",\n            \"[\"\n          ]\n        },\n        \"vim.argumentObjectClosingDelimiters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"markdownDescription\": \"Set closing delimiters for the argument text object.\",\n          \"default\": [\n            \")\",\n            \"]\"\n          ]\n        },\n        \"vim.hlsearch\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show all matches of the most recent search pattern.\",\n          \"default\": false\n        },\n        \"vim.inccommand\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Show the effects of the `:substitute` command as you type.\",\n          \"default\": \"replace\",\n          \"enum\": [\n            \"\",\n            \"append\",\n            \"replace\"\n          ],\n          \"enumDescriptions\": [\n            \"Don't show substitutions\",\n            \"Show substitutions after matched text\",\n            \"Replace matched text with substitutions\"\n          ]\n        },\n        \"vim.incsearch\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Show where a `/` or `?` search matches as you type it.\",\n          \"default\": true\n        },\n        \"vim.history\": {\n          \"type\": \"number\",\n          \"description\": \"How much search or command history should be remembered.\",\n          \"default\": 50,\n          \"minimum\": 1,\n          \"maximum\": 10000\n        },\n        \"vim.autoindent\": {\n          \"type\": \"boolean\",\n          \"description\": \"Indent code automatically.\",\n          \"default\": true\n        },\n        \"vim.joinspaces\": {\n          \"type\": \"boolean\",\n          \"description\": \"Add two spaces after '.', '?', and '!' when joining or reformatting.\",\n          \"default\": true\n        },\n        \"vim.startInInsertMode\": {\n          \"type\": \"boolean\",\n          \"description\": \"Start in Insert mode.\"\n        },\n        \"vim.startInInsertModeSchemes\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"default\": [\n            \"comment\"\n          ],\n          \"markdownDescription\": \"List of document URI schemes that should automatically start in Insert mode. For example, use `[\\\"comment\\\"]` for GitHub PR comment editors, or `[\\\"comment\\\", \\\"gitlens\\\"]` for multiple schemes.\"\n        },\n        \"vim.handleKeys\": {\n          \"type\": \"object\",\n          \"description\": \"Delegate certain key combinations back to VS Code to be handled natively.\",\n          \"default\": {\n            \"<C-d>\": true,\n            \"<C-s>\": false,\n            \"<C-z>\": false\n          }\n        },\n        \"vim.statusBarColorControl\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Allow VSCodeVim to change status bar color based on mode. **NOTE:** Using this feature will have a negative impact on performance.\",\n          \"default\": false\n        },\n        \"vim.statusBarColors.normal\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in Normal mode.\",\n          \"default\": [\n            \"#005f5f\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.insert\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in Insert mode.\",\n          \"default\": [\n            \"#5f0000\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.visual\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in Visual mode.\",\n          \"default\": [\n            \"#5f00af\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.visualline\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in VisualLine mode.\",\n          \"default\": [\n            \"#005f87\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.visualblock\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in VisualBlock mode.\",\n          \"default\": [\n            \"#86592d\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.replace\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in Replace mode.\",\n          \"default\": [\n            \"#00000\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.commandlineinprogress\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in CommandLineInProgress mode.\",\n          \"default\": [\n            \"#007acc\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.searchinprogressmode\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in SearchInProgress mode.\",\n          \"default\": [\n            \"#007acc\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.easymotionmode\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in EasyMotion mode.\",\n          \"default\": [\n            \"#007acc\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.easymotioninputmode\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in EasyMotionInput mode.\",\n          \"default\": [\n            \"#007acc\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.statusBarColors.surroundinputmode\": {\n          \"type\": [\n            \"string\",\n            \"array\"\n          ],\n          \"description\": \"Status bar color when in SurroundInput mode.\",\n          \"default\": [\n            \"#007acc\",\n            \"#ffffff\"\n          ]\n        },\n        \"vim.visualstar\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"In Visual mode, start a search with `*` or `#` using the current selection.\",\n          \"default\": false\n        },\n        \"vim.changeWordIncludesWhitespace\": {\n          \"type\": \"boolean\",\n          \"description\": \"Includes trailing whitespace when changing word.\",\n          \"default\": false\n        },\n        \"vim.foldfix\": {\n          \"type\": \"boolean\",\n          \"description\": \"Uses a hack to move around folds properly.\",\n          \"default\": false\n        },\n        \"vim.mouseSelectionGoesIntoVisualMode\": {\n          \"type\": \"boolean\",\n          \"description\": \"If enabled, dragging with the mouse activates Visual mode.\",\n          \"default\": true\n        },\n        \"vim.disableExtension\": {\n          \"type\": \"boolean\",\n          \"description\": \"Disables the VSCodeVim extension. The extension will continue to be loaded and activated, but Vim functionality will be disabled.\",\n          \"default\": false\n        },\n        \"vim.enableNeovim\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Use Neovim to execute Ex commands. You should restart VS Code after enabling/disabling this for the changes to take effect. **NOTE:** Neovim version 0.2.0 or greater must be installed, and if the executable is not on your PATH, `#vim.neovimPath#` must be set.\",\n          \"default\": false\n        },\n        \"vim.neovimPath\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Path to Neovim executable. For example, `/usr/bin/nvim`, or  `C:\\\\Program Files\\\\Neovim\\\\bin\\\\nvim.exe`.\",\n          \"default\": \"\",\n          \"scope\": \"machine\"\n        },\n        \"vim.neovimUseConfigFile\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Use a config file for Neovim, specified by `#vim.neovimConfigPath#`.\",\n          \"default\": false\n        },\n        \"vim.neovimConfigPath\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Path to Neovim configuration file. `#vim.neovimUseConfigFile#` must be enabled. If path is left blank, Neovim will use its default config path, i.e. `~/.config/nvim/init.vim` or 'C:\\\\Users\\\\USERNAME\\\\AppData\\\\Local\\\\nvim\\\\init.vim'.\",\n          \"default\": \"\",\n          \"scope\": \"machine\"\n        },\n        \"vim.vimrc.enable\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Use key mappings from a `.vimrc` file.\",\n          \"default\": false\n        },\n        \"vim.vimrc.path\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"Path to a Vim configuration file. If unset, it will check for `$HOME/.vscodevimrc`, `$HOME/.vimrc`, `$HOME/_vimrc`, and `$HOME/.config/nvim/init.vim`, in that order.\"\n        },\n        \"vim.substituteGlobalFlag\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Automatically apply the global flag, `/g`, to substitute commands. When set to true, use `/g` to mean only first match should be replaced.\",\n          \"default\": false,\n          \"markdownDeprecationMessage\": \"**Deprecated**: Please use `#vim.gdefault#` instead.\",\n          \"deprecationMessage\": \"Deprecated: Please use vim.gdefault instead.\"\n        },\n        \"vim.gdefault\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"Automatically apply the global flag, `/g`, to substitute commands. When set to true, use `/g` to mean only first match should be replaced.\",\n          \"default\": false\n        },\n        \"vim.cursorStylePerMode.normal\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for Normal mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.cursorStylePerMode.insert\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for Insert mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.cursorStylePerMode.replace\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for Replace mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.cursorStylePerMode.visual\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for Visual mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.cursorStylePerMode.visualline\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for VisualLine mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.cursorStylePerMode.visualblock\": {\n          \"type\": \"string\",\n          \"description\": \"Cursor style for VisualBlock mode.\",\n          \"enum\": [\n            \"\",\n            \"block\",\n            \"block-outline\",\n            \"line\",\n            \"line-thin\",\n            \"underline\",\n            \"underline-thin\"\n          ]\n        },\n        \"vim.autoSwitchInputMethod.enable\": {\n          \"type\": \"boolean\",\n          \"description\": \"If enabled, the input method switches automatically when the mode changes.\",\n          \"default\": false\n        },\n        \"vim.autoSwitchInputMethod.defaultIM\": {\n          \"type\": \"string\",\n          \"markdownDescription\": \"The input method for your normal mode, find more information [here](https://github.com/VSCodeVim/Vim#input-method).\",\n          \"default\": \"\",\n          \"scope\": \"machine\"\n        },\n        \"vim.autoSwitchInputMethod.switchIMCmd\": {\n          \"type\": \"string\",\n          \"description\": \"The shell command to switch input method.\",\n          \"default\": \"/path/to/im-select {im}\",\n          \"scope\": \"machine\"\n        },\n        \"vim.autoSwitchInputMethod.obtainIMCmd\": {\n          \"type\": \"string\",\n          \"description\": \"The shell command to get current input method.\",\n          \"default\": \"/path/to/im-select\",\n          \"scope\": \"machine\"\n        },\n        \"vim.whichwrap\": {\n          \"type\": \"string\",\n          \"description\": \"Comma-separated list of motion keys that should wrap to next/previous line.\",\n          \"default\": \"b,s\"\n        },\n        \"vim.report\": {\n          \"type\": \"number\",\n          \"description\": \"Threshold for reporting number of lines changed.\",\n          \"default\": 2,\n          \"minimum\": 1\n        },\n        \"vim.digraphs\": {\n          \"type\": \"object\",\n          \"description\": \"Custom digraph shortcuts for inserting special characters, expressed as UTF16 code points.\",\n          \"default\": {}\n        },\n        \"vim.wrapscan\": {\n          \"type\": \"boolean\",\n          \"description\": \"Searches wrap around the end of the file.\",\n          \"default\": true\n        },\n        \"vim.startofline\": {\n          \"type\": \"boolean\",\n          \"markdownDescription\": \"When `true` the commands listed below move the cursor to the first non-blank of the line.  When `false` the cursor is kept in the same column (if possible).  This applies to the commands: `<C-d>`, `<C-u>`, `<C-b>`, `<C-f>`, `G`, `H`, `M`, `L`, `gg`, and to the commands `d`, `<<` and `>>` with a linewise operator.\",\n          \"default\": true\n        },\n        \"vim.showMarksInGutter\": {\n          \"type\": \"boolean\",\n          \"description\": \"Show the currently set mark(s) in the gutter.\",\n          \"default\": false\n        },\n        \"vim.shell\": {\n          \"type\": \"string\",\n          \"description\": \"Path to the shell to use for `!` and `:!` commands.\",\n          \"default\": \"\"\n        },\n        \"vim.langmap\": {\n          \"type\": \"string\",\n          \"description\": \"Language map for alternate keyboard layouts. When you are typing text in Insert (or Replace, etc.) mode, the characters are inserted derectly. Otherwise, they are translated based on the provided map.\"\n        }\n      }\n    },\n    \"languages\": [\n      {\n        \"id\": \"Vimscript\",\n        \"extensions\": [\n          \".vim\",\n          \".vimrc\"\n        ],\n        \"configuration\": \"./language-configuration.json\"\n      }\n    ],\n    \"grammars\": [\n      {\n        \"language\": \"Vimscript\",\n        \"scopeName\": \"source.vimscript\",\n        \"path\": \"./syntaxes/vimscript.tmLanguage.json\"\n      }\n    ]\n  },\n  \"scripts\": {\n    \"vscode:prepublish\": \"yarn build\",\n    \"build\": \"webpack -c webpack.config.js && npm run commit-hash\",\n    \"build-dev\": \"webpack -c webpack.dev.js && npm run commit-hash\",\n    \"build-test\": \"gulp prepare-test\",\n    \"commit-hash\": \"git rev-parse HEAD > out/version.txt\",\n    \"test\": \"node ./out/test/runTest.js\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"prettier\": \"npx prettier --write .\",\n    \"prettier:check\": \"npx prettier --check .\",\n    \"watch\": \"webpack -c webpack.dev.js --watch\",\n    \"package\": \"yarn run vsce package --yarn --allow-star-activation\",\n    \"prepare\": \"husky\"\n  },\n  \"dependencies\": {\n    \"diff-match-patch\": \"1.0.5\",\n    \"lodash\": \"^4.17.21\",\n    \"neovim\": \"5.4.0\",\n    \"os-browserify\": \"0.3.0\",\n    \"parsimmon\": \"^1.18.0\",\n    \"path-browserify\": \"1.0.1\",\n    \"process\": \"0.11.10\",\n    \"queue\": \"^6.0.2\",\n    \"untildify\": \"4.0.0\",\n    \"util\": \"0.12.5\"\n  },\n  \"devDependencies\": {\n    \"@types/diff-match-patch\": \"1.0.36\",\n    \"@types/lodash\": \"4.17.23\",\n    \"@types/minimatch\": \"5.1.2\",\n    \"@types/mocha\": \"10.0.10\",\n    \"@types/node\": \"22.19.1\",\n    \"@types/parsimmon\": \"1.10.9\",\n    \"@types/sinon\": \"17.0.4\",\n    \"@types/vscode\": \"1.74.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.0.0\",\n    \"@typescript-eslint/parser\": \"^8.0.0\",\n    \"@vscode/test-electron\": \"2.5.2\",\n    \"@vscode/vsce\": \"^3.6.2\",\n    \"circular-dependency-plugin\": \"^5.2.2\",\n    \"clean-webpack-plugin\": \"4.0.0\",\n    \"eslint\": \"^8.52.0\",\n    \"eslint-config-prettier\": \"^10.0.0\",\n    \"eslint-plugin-jsdoc\": \"^62.0.0\",\n    \"eslint-plugin-prefer-arrow\": \"^1.2.3\",\n    \"event-stream\": \"4.0.1\",\n    \"fork-ts-checker-webpack-plugin\": \"9.1.0\",\n    \"gulp\": \"5.0.1\",\n    \"gulp-bump\": \"3.2.0\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"gulp-tag-version\": \"1.3.1\",\n    \"gulp-typescript\": \"5.0.1\",\n    \"husky\": \"^9.0.0\",\n    \"lint-staged\": \"^16.0.0\",\n    \"minimist\": \"1.2.8\",\n    \"mocha\": \"11.7.5\",\n    \"plugin-error\": \"2.0.1\",\n    \"prettier\": \"3.8.1\",\n    \"prettier-plugin-organize-imports\": \"^4.3.0\",\n    \"sinon\": \"20.0.0\",\n    \"ts-loader\": \"9.5.4\",\n    \"typescript\": \"5.9.3\",\n    \"webpack\": \"5.105.4\",\n    \"webpack-cli\": \"7.0.2\",\n    \"webpack-merge\": \"6.0.1\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,js,json,md,yml}\": \"prettier --write\",\n    \"*.ts\": \"eslint --fix\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:base\", \"default:pinDigestsDisabled\"],\n  \"automerge\": false,\n  \"ignoreDeps\": [\"@types/vscode\"],\n  \"labels\": [\"pr/dependency\"]\n}\n"
  },
  {
    "path": "src/actions/base.ts",
    "content": "import { Position } from 'vscode';\nimport { Cursor } from '../common/motion/cursor';\nimport { isLiteralMode, unmapLiteral } from '../configuration/langmap';\nimport { Notation } from '../configuration/notation';\nimport { isTextTransformation } from '../transformations/transformations';\nimport { configuration } from './../configuration/configuration';\nimport { Mode } from './../mode/mode';\nimport { VimState } from './../state/vimState';\nimport { ActionType, IBaseAction } from './types';\n\nexport abstract class BaseAction implements IBaseAction {\n  abstract readonly actionType: ActionType;\n\n  public readonly name: string | undefined;\n\n  /**\n   * If true, the cursor position will be added to the jump list on completion.\n   */\n  public readonly isJump: boolean = false;\n\n  /**\n   * If true, the action will create an undo point.\n   */\n  public readonly createsUndoPoint: boolean = false;\n\n  /**\n   * If this is being run in multi cursor mode, the index of the cursor\n   * this action is being applied to.\n   */\n  public multicursorIndex: number | undefined;\n\n  /**\n   * Whether we should change `vimState.desiredColumn`\n   */\n  public readonly preservesDesiredColumn: boolean = false;\n\n  /**\n   * Modes that this action can be run in.\n   */\n  public abstract readonly modes: readonly Mode[];\n\n  /**\n   * The sequence of keys you use to trigger the action, or a list of such sequences.\n   */\n  public abstract keys: readonly string[] | readonly string[][];\n\n  /**\n   * The keys pressed at the time that this action was triggered.\n   */\n  // TODO: make readonly\n  public keysPressed: string[] = [];\n\n  private static readonly isSingleNumber: RegExp = /^[0-9]$/;\n  private static readonly isSingleAlpha: RegExp = /^[a-zA-Z]$/;\n  private static readonly isMacroRegister: RegExp = /^[0-9a-zA-Z]$/;\n\n  /**\n   * Is this action valid in the current Vim state?\n   */\n  public doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (\n      vimState.currentModeIncludingPseudoModes === Mode.OperatorPendingMode &&\n      this.actionType === 'command'\n    ) {\n      return false;\n    }\n\n    return (\n      this.modes.includes(vimState.currentMode) &&\n      BaseAction.CompareKeypressSequence(this.keys, keysPressed)\n    );\n  }\n\n  /**\n   * Could the user be in the process of doing this action.\n   */\n  public couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (\n      vimState.currentModeIncludingPseudoModes === Mode.OperatorPendingMode &&\n      this.actionType === 'command'\n    ) {\n      return false;\n    }\n\n    if (!this.modes.includes(vimState.currentMode)) {\n      return false;\n    }\n\n    const keys2D = BaseAction.is2DArray(this.keys) ? this.keys : [this.keys];\n    const keysSlice = keys2D.map((x) => x.slice(0, keysPressed.length));\n    if (!BaseAction.CompareKeypressSequence(keysSlice, keysPressed)) {\n      return false;\n    }\n\n    return true;\n  }\n\n  public static CompareKeypressSequence(\n    one: readonly string[] | readonly string[][],\n    two: readonly string[],\n  ): boolean {\n    if (BaseAction.is2DArray(one)) {\n      for (const sequence of one) {\n        if (BaseAction.CompareKeypressSequence(sequence, two)) {\n          return true;\n        }\n      }\n\n      return false;\n    }\n\n    if (one.length !== two.length) {\n      return false;\n    }\n\n    for (let i = 0, j = 0; i < one.length; i++, j++) {\n      const left = one[i];\n      const right = two[j];\n\n      if (left === right && right !== configuration.leader) {\n        continue;\n      } else if (left === '<any>') {\n        continue;\n      } else if (left === '<leader>' && right === configuration.leader) {\n        continue;\n      } else if (left === '<number>' && this.isSingleNumber.test(right)) {\n        continue;\n      } else if (left === '<alpha>' && this.isSingleAlpha.test(right)) {\n        continue;\n      } else if (left === '<macro>' && this.isMacroRegister.test(right)) {\n        continue;\n      } else if (['<character>', '<register>'].includes(left) && !Notation.IsControlKey(right)) {\n        continue;\n      } else {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  public toString(): string {\n    return this.keys.join('');\n  }\n\n  private static is2DArray<T>(x: readonly T[] | readonly T[][]): x is readonly T[][] {\n    return Array.isArray(x[0]);\n  }\n}\n\n/**\n * A command is something like <Esc>, :, v, i, etc.\n */\nexport abstract class BaseCommand extends BaseAction {\n  override actionType: ActionType = 'command' as const;\n\n  /**\n   * If isCompleteAction is true, then triggering this command is a complete action -\n   * that means that we'll go and try to run it.\n   */\n  public isCompleteAction = true;\n\n  /**\n   * In multi-cursor mode, do we run this command for every cursor, or just once?\n   */\n  public runsOnceForEveryCursor(): boolean {\n    return true;\n  }\n\n  /**\n   * If true, exec() will get called N times where N is the count.\n   *\n   * If false, exec() will only be called once, and you are expected to\n   * handle count prefixes (e.g. the 3 in 3w) yourself.\n   */\n  public readonly runsOnceForEachCountPrefix: boolean = false;\n\n  /**\n   * Run the command a single time.\n   */\n  public async exec(position: Position, vimState: VimState): Promise<void> {\n    throw new Error('Not implemented!');\n  }\n\n  /**\n   * Run the command the number of times VimState wants us to.\n   */\n  public async execCount(position: Position, vimState: VimState): Promise<void> {\n    const timesToRepeat = this.runsOnceForEachCountPrefix ? vimState.recordedState.count || 1 : 1;\n\n    if (!this.runsOnceForEveryCursor()) {\n      for (let i = 0; i < timesToRepeat; i++) {\n        await this.exec(position, vimState);\n      }\n\n      for (const transformation of vimState.recordedState.transformer.transformations) {\n        if (isTextTransformation(transformation) && transformation.cursorIndex === undefined) {\n          transformation.cursorIndex = 0;\n        }\n      }\n\n      return;\n    }\n\n    const cursorsToIterateOver = [...vimState.cursors].sort((a, b) =>\n      a.start.line > b.start.line ||\n      (a.start.line === b.start.line && a.start.character > b.start.character)\n        ? 1\n        : -1,\n    );\n\n    const resultingCursors: Cursor[] = [];\n\n    for (const [index, { start, stop }] of cursorsToIterateOver.entries()) {\n      this.multicursorIndex = index;\n\n      vimState.cursorStopPosition = stop;\n      vimState.cursorStartPosition = start;\n\n      for (let j = 0; j < timesToRepeat; j++) {\n        await this.exec(stop, vimState);\n      }\n\n      resultingCursors.push(new Cursor(vimState.cursorStartPosition, vimState.cursorStopPosition));\n\n      for (const transformation of vimState.recordedState.transformer.transformations) {\n        if (isTextTransformation(transformation) && transformation.cursorIndex === undefined) {\n          transformation.cursorIndex = this.multicursorIndex;\n        }\n      }\n    }\n\n    vimState.cursors = resultingCursors;\n  }\n}\n\nexport enum KeypressState {\n  WaitingOnKeys,\n  NoPossibleMatch,\n}\n\n/**\n * Every Vim action will be added here with the @RegisterAction decorator.\n */\nconst actionMap = new Map<Mode, Array<new () => BaseAction>>();\n\n/**\n * Gets the action that should be triggered given a key sequence.\n *\n * If there is a definitive action that matched, returns that action.\n *\n * If an action could potentially match if more keys were to be pressed, returns `KeyPressState.WaitingOnKeys`\n * (e.g. you pressed \"g\" and are about to press \"g\" action to make the full action \"gg\")\n *\n * If no action could ever match, returns `KeypressState.NoPossibleMatch`.\n */\nexport function getRelevantAction(\n  keysPressed: string[],\n  vimState: VimState,\n): BaseAction | KeypressState {\n  const possibleActionsForMode = actionMap.get(vimState.currentMode) ?? [];\n\n  let hasPotentialMatch = false;\n  for (const actionType of possibleActionsForMode) {\n    // TODO: Constructing up to several hundred Actions every time we hit a key is moronic.\n    //       I think we can make `doesActionApply` and `couldActionApply` static...\n    const action = new actionType();\n    if (action.doesActionApply(vimState, keysPressed)) {\n      action.keysPressed = isLiteralMode(vimState.currentMode)\n        ? [...vimState.recordedState.actionKeys]\n        : unmapLiteral(action.keys, vimState.recordedState.actionKeys);\n      return action;\n    }\n\n    hasPotentialMatch ||= action.couldActionApply(vimState, keysPressed);\n  }\n\n  return hasPotentialMatch ? KeypressState.WaitingOnKeys : KeypressState.NoPossibleMatch;\n}\n\nexport function RegisterAction(action: new () => BaseAction): void {\n  const actionInstance = new action();\n  for (const modeName of actionInstance.modes) {\n    let actions = actionMap.get(modeName);\n    if (!actions) {\n      actions = [];\n      actionMap.set(modeName, actions);\n    }\n\n    if (actionInstance.keys === undefined) {\n      // action that can't be called directly\n      continue;\n    }\n\n    actions.push(action);\n  }\n}\n"
  },
  {
    "path": "src/actions/baseMotion.ts",
    "content": "import { Position } from 'vscode';\nimport { Mode } from '../mode/mode';\nimport { VimState } from '../state/vimState';\nimport { clamp } from '../util/util';\nimport { BaseAction } from './base';\n\nexport function isIMovement(o: IMovement | Position): o is IMovement {\n  return (o as IMovement).start !== undefined && (o as IMovement).stop !== undefined;\n}\n\nexport enum SelectionType {\n  Concatenating, // Selections that concatenate repeated movements\n  Expanding, // Selections that expand the start and end of the previous selection\n}\n\n/**\n * The result of a (more sophisticated) Movement.\n */\nexport interface IMovement {\n  start: Position;\n  stop: Position;\n\n  /**\n   * Whether this motion succeeded. Some commands, like fx when 'x' can't be found,\n   * will not move the cursor. Furthermore, dfx won't delete *anything*, even though\n   * deleting to the current character would generally delete 1 character.\n   */\n  failed?: boolean;\n\n  /**\n   * Whether this motion resulted in the current multicursor index being removed.\n   * This happens when multiple selections combine into one.\n   */\n  removed?: boolean;\n}\n\nexport function failedMovement(vimState: VimState): IMovement {\n  return {\n    start: vimState.cursorStartPosition,\n    stop: vimState.cursorStopPosition,\n    failed: true,\n  };\n}\n\nexport abstract class BaseMovement extends BaseAction {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  override actionType = 'motion' as const;\n\n  /**\n   * If movement can be repeated with semicolon or comma this will be true when\n   * running the repetition.\n   */\n  isRepeat = false;\n\n  protected selectionType = SelectionType.Concatenating;\n\n  constructor(keysPressed?: string[], isRepeat?: boolean) {\n    super();\n\n    if (keysPressed) {\n      this.keysPressed = keysPressed;\n    }\n\n    if (isRepeat) {\n      this.isRepeat = isRepeat;\n    }\n  }\n\n  /**\n   * Run the movement a single time.\n   *\n   * Generally returns a new Position. If necessary, it can return an IMovement instead.\n   * Note: If returning an IMovement, make sure that repeated actions on a\n   * visual selection work. For example, V}}\n   */\n  public async execAction(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<Position | IMovement> {\n    throw new Error('Not implemented!');\n  }\n\n  /**\n   * Run the movement in an operator context a single time.\n   *\n   * Some movements operate over different ranges when used for operators.\n   */\n  protected async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<Position | IMovement> {\n    return this.execAction(position, vimState, firstIteration, lastIteration);\n  }\n\n  /**\n   * Run a movement count times.\n   *\n   * count: the number prefix the user entered, or 0 if they didn't enter one.\n   */\n  public async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    let result!: Position | IMovement;\n    let prevResult: Position | IMovement = failedMovement(vimState);\n    let firstMovementStart = position;\n\n    count = clamp(count, 1, 99999);\n\n    for (let i = 0; i < count; i++) {\n      const firstIteration = i === 0;\n      const lastIteration = i === count - 1;\n      result =\n        vimState.recordedState.operator && lastIteration\n          ? await this.execActionForOperator(position, vimState, firstIteration, lastIteration)\n          : await this.execAction(position, vimState, firstIteration, lastIteration);\n\n      if (result instanceof Position) {\n        /**\n         * This position will be passed to the `motion` on the next iteration,\n         * it may cause some issues when count > 1.\n         */\n        position = result;\n      } else {\n        if (result.failed) {\n          return prevResult;\n        }\n\n        if (firstIteration) {\n          firstMovementStart = result.start;\n        }\n\n        position = this.adjustPosition(position, result, lastIteration);\n      }\n      prevResult = result;\n    }\n\n    if (this.selectionType === SelectionType.Concatenating && isIMovement(result)) {\n      result.start = firstMovementStart;\n    }\n\n    return result;\n  }\n\n  protected adjustPosition(position: Position, result: IMovement, lastIteration: boolean) {\n    if (!lastIteration) {\n      position = result.stop.getRightThroughLineBreaks();\n    }\n    return position;\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/actions.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { VimError } from '../../error';\nimport { globalState } from '../../state/globalState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { Clipboard } from '../../util/clipboard';\nimport { SpecialKeys } from '../../util/specialKeys';\nimport { reportSearch } from '../../util/statusBarTextUtils';\nimport { getCursorsAfterSync } from '../../util/util';\nimport { SearchDirection } from '../../vimscript/pattern';\nimport { shouldWrapKey } from '../wrapping';\nimport { ExCommandLine, SearchCommandLine } from './../../cmd_line/commandLine';\nimport { PositionDiff, earlierOf, laterOf, sorted } from './../../common/motion/position';\nimport { configuration } from './../../configuration/configuration';\nimport {\n  Mode,\n  visualBlockGetBottomRightPosition,\n  visualBlockGetTopLeftPosition,\n} from './../../mode/mode';\nimport { Register, RegisterMode } from './../../register/register';\nimport { TextEditor } from './../../textEditor';\nimport { BaseCommand, RegisterAction } from './../base';\nimport * as operator from './../operator';\n\n@RegisterAction\nclass DisableExtension extends BaseCommand {\n  modes = [\n    Mode.Normal,\n    Mode.Insert,\n    Mode.Visual,\n    Mode.VisualBlock,\n    Mode.VisualLine,\n    Mode.SearchInProgressMode,\n    Mode.CommandlineInProgress,\n    Mode.Replace,\n    Mode.EasyMotionMode,\n    Mode.EasyMotionInputMode,\n    Mode.SurroundInputMode,\n  ];\n  keys = [SpecialKeys.ExtensionDisable];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Disabled);\n  }\n}\n\n@RegisterAction\nclass EnableExtension extends BaseCommand {\n  modes = [Mode.Disabled];\n  keys = [SpecialKeys.ExtensionEnable];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nexport class CommandNumber extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['<number>'];\n  override name = 'cmd_num';\n  override isCompleteAction = false;\n  override actionType = 'number' as const;\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const num = parseInt(this.keysPressed[0], 10);\n    const operatorCount = vimState.recordedState.operatorCount;\n\n    if (operatorCount > 0) {\n      const lastAction =\n        vimState.recordedState.actionsRun[vimState.recordedState.actionsRun.length - 2];\n      if (!(lastAction instanceof CommandNumber)) {\n        // We have set an operatorCount !== 0 after an operator, but now we got another count\n        // number so we need to multiply them.\n        vimState.recordedState.count = operatorCount * num;\n      } else {\n        // We are now getting another digit which means we need to multiply by 10 and add\n        // the new digit multiplied by operatorCount.\n        //\n        // Example: user presses '2d31w':\n        // - After '2' the number 2 is stored in 'count'\n        // - After 'd' the count (2) is stored in 'operatorCount'\n        // - After '3' the number 3 multiplied by 'operatorCount' (3 x 2 = 6) is stored in 'count'\n        // - After '1' the count is multiplied by 10 and added by number 1 multiplied by 'operatorCount'\n        //   (6 * 10 + 1 * 2 = 62)\n        // The final result will be the deletion of 62 words.\n        vimState.recordedState.count = vimState.recordedState.count * 10 + num * operatorCount;\n      }\n    } else {\n      vimState.recordedState.count = vimState.recordedState.count * 10 + num;\n    }\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const isZero = keysPressed[0] === '0';\n\n    return (\n      super.doesActionApply(vimState, keysPressed) &&\n      ((isZero && vimState.recordedState.count > 0) || !isZero)\n    );\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const isZero = keysPressed[0] === '0';\n\n    return (\n      super.couldActionApply(vimState, keysPressed) &&\n      ((isZero && vimState.recordedState.count > 0) || !isZero)\n    );\n  }\n}\n\n@RegisterAction\nexport class CommandRegister extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['\"', '<register>'];\n  override name = 'cmd_register';\n  override isCompleteAction = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const register = this.keysPressed[1];\n\n    if (Register.isValidRegister(register)) {\n      vimState.recordedState.registerName = register;\n    } else {\n      // TODO: Changing isCompleteAction here is maybe a bit janky - should it be a function?\n      this.isCompleteAction = true;\n    }\n  }\n}\n\n@RegisterAction\nclass CommandEsc extends BaseCommand {\n  modes = [\n    Mode.Visual,\n    Mode.VisualLine,\n    Mode.VisualBlock,\n    Mode.Normal,\n    Mode.SurroundInputMode,\n    Mode.EasyMotionMode,\n    Mode.EasyMotionInputMode,\n  ];\n  keys = [['<Esc>'], ['<C-c>'], ['<C-[>']];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  override preservesDesiredColumn = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.currentMode === Mode.Normal) {\n      vimState.surround = undefined;\n\n      if (vimState.isMultiCursor) {\n        vimState.cursors = [vimState.cursor];\n      } else {\n        // If there's nothing to do on the vim side, we might as well call some\n        // of vscode's default \"close notification\" actions. I think we should\n        // just add to this list as needed.\n        await Promise.allSettled([\n          vscode.commands.executeCommand('closeReferenceSearchEditor'),\n          vscode.commands.executeCommand('closeMarkersNavigation'),\n          // TODO: closeDirtyDiff renamed to closeQuickDiff (see microsoft/vscode#235601)\n          vscode.commands.executeCommand('closeDirtyDiff'),\n          vscode.commands.executeCommand('closeQuickDiff'),\n          vscode.commands.executeCommand('editor.action.inlineSuggest.hide'),\n        ]);\n      }\n    } else {\n      if (vimState.currentMode === Mode.EasyMotionMode) {\n        vimState.easyMotion.clearDecorations(vimState.editor);\n      } else if (vimState.currentMode === Mode.SurroundInputMode) {\n        vimState.surround = undefined;\n      }\n\n      await vimState.setCurrentMode(Mode.Normal);\n    }\n  }\n}\n\n/**\n * Our Vim implementation selects up to but not including the final character\n * of a visual selection, instead opting to render a block cursor on the final\n * character. This works for everything except VSCode's native copy command,\n * which loses the final character because it's not selected. We override that\n * copy command here by default to include the final character.\n */\n@RegisterAction\nclass CommandOverrideCopy extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock, Mode.Insert, Mode.Normal];\n  keys = ['<copy>']; // A special key - see ModeHandler\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let text = '';\n\n    if (vimState.currentMode === Mode.Visual) {\n      text = vimState.cursors\n        .map((range) => {\n          const [start, stop] = sorted(range.start, range.stop);\n          return vimState.document.getText(new vscode.Range(start, stop.getRight()));\n        })\n        .join('\\n');\n    } else if (vimState.currentMode === Mode.VisualLine) {\n      text = vimState.cursors\n        .map((range) => {\n          return vimState.document.getText(\n            new vscode.Range(\n              earlierOf(range.start.getLineBegin(), range.stop.getLineBegin()),\n              laterOf(range.start.getLineEnd(), range.stop.getLineEnd()),\n            ),\n          );\n        })\n        .join('\\n');\n    } else if (vimState.currentMode === Mode.VisualBlock) {\n      for (const { line } of TextEditor.iterateLinesInBlock(vimState)) {\n        text += line + '\\n';\n      }\n    } else if (vimState.currentMode === Mode.Insert || vimState.currentMode === Mode.Normal) {\n      text = vimState.editor.selections\n        .map((selection) => {\n          return vimState.document.getText(new vscode.Range(selection.start, selection.end));\n        })\n        .join('\\n');\n    }\n\n    const editorSelection = vimState.editor.selection;\n    const hasSelectedText = !editorSelection.active.isEqual(editorSelection.anchor);\n\n    if (hasSelectedText) {\n      await Clipboard.Copy(text);\n    }\n\n    // all vim yank operations return to normal mode.\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass CommandCmdA extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['<D-a>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.cursorStartPosition = new Position(0, vimState.desiredColumn);\n    vimState.cursorStopPosition = new Position(\n      vimState.document.lineCount - 1,\n      vimState.desiredColumn,\n    );\n    await vimState.setCurrentMode(Mode.VisualLine);\n  }\n}\n\n@RegisterAction\nclass MarkCommand extends BaseCommand {\n  keys = ['m', '<register>'];\n  modes = [Mode.Normal];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const markName = this.keysPressed[1];\n\n    vimState.historyTracker.addMark(vimState.document, position, markName);\n  }\n}\n\n@RegisterAction\nclass ShowCommandLine extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = [':'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let commandLineText: string;\n    if (vimState.currentMode === Mode.Normal) {\n      if (vimState.recordedState.count) {\n        commandLineText = `.,.+${vimState.recordedState.count - 1}`;\n      } else {\n        commandLineText = '';\n      }\n    } else {\n      commandLineText = \"'<,'>\";\n    }\n\n    const previousMode = vimState.currentMode;\n    await vimState.setCurrentMode(Mode.CommandlineInProgress);\n    // TODO: Change or supplement `setCurrentMode` API so this isn't necessary\n    if (vimState.modeData.mode === Mode.CommandlineInProgress) {\n      vimState.modeData.commandLine = new ExCommandLine(commandLineText, previousMode);\n    }\n  }\n}\n\n@RegisterAction\nexport class ShowCommandHistory extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['q', ':'];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const cmd = await vscode.window.showQuickPick(ExCommandLine.history.get().slice().reverse(), {\n      placeHolder: 'Vim command history',\n      ignoreFocusOut: false,\n    });\n    if (cmd && cmd.length !== 0) {\n      await new ExCommandLine(cmd, vimState.currentMode).run(vimState);\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\nExCommandLine.onSearch = async (vimState: VimState) => {\n  void new ShowCommandHistory().exec(vimState.cursorStopPosition, vimState);\n};\n\n@RegisterAction\nexport class ShowSearchHistory extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = [\n    ['q', '/'],\n    ['q', '?'],\n  ];\n\n  private direction = SearchDirection.Forward;\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public constructor(direction = SearchDirection.Forward) {\n    super();\n    this.direction = direction;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (this.keysPressed.includes('?')) {\n      this.direction = SearchDirection.Backward;\n    }\n\n    const searchState = await SearchCommandLine.showSearchHistory();\n    if (searchState) {\n      globalState.searchState = searchState;\n      globalState.hl = true;\n\n      const nextMatch = searchState.getNextSearchMatchPosition(\n        vimState,\n        vimState.cursorStartPosition,\n        this.direction,\n      );\n\n      if (!nextMatch) {\n        throw this.direction === SearchDirection.Forward\n          ? VimError.SearchHitBottom(searchState.searchString)\n          : VimError.SearchHitTop(searchState.searchString);\n      }\n\n      vimState.cursorStopPosition = nextMatch.pos;\n      reportSearch(nextMatch.index, searchState.getMatchRanges(vimState).length, vimState);\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n// Register the command to execute on CtrlF.\nSearchCommandLine.onSearch = async (vimState: VimState, direction: SearchDirection) => {\n  return new ShowSearchHistory(direction).exec(vimState.cursorStopPosition, vimState);\n};\n\n@RegisterAction\nclass DotRepeat extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['.'];\n  override createsUndoPoint = true;\n\n  public override async execCount(position: Position, vimState: VimState): Promise<void> {\n    if (globalState.previousFullAction) {\n      const count = vimState.recordedState.count || 1;\n\n      vimState.recordedState.transformer.addTransformation({\n        type: 'replayRecordedState',\n        count,\n        recordedState: globalState.previousFullAction,\n      });\n    } else {\n      // No previous action to repeat, so mark this as non-repeatable\n      vimState.lastCommandDotRepeatable = false;\n    }\n  }\n}\n\n@RegisterAction\nclass RepeatSubstitution extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['&'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // Parsing the command from a string, while not ideal, is currently\n    // necessary to make this work with and without neovim integration\n    await ExCommandLine.parser.tryParse('s').command.execute(vimState);\n  }\n}\n\n@RegisterAction\nclass GoToOtherEndOfHighlightedText extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['o'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    [vimState.cursorStartPosition, vimState.cursorStopPosition] = [\n      vimState.cursorStopPosition,\n      vimState.cursorStartPosition,\n    ];\n  }\n}\n\n@RegisterAction\nclass GoToOtherSideOfHighlightedText extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['O'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.currentMode === Mode.VisualBlock) {\n      [vimState.cursorStartPosition, vimState.cursorStopPosition] = [\n        new Position(vimState.cursorStartPosition.line, vimState.cursorStopPosition.character),\n        new Position(vimState.cursorStopPosition.line, vimState.cursorStartPosition.character),\n      ];\n    } else {\n      return new GoToOtherEndOfHighlightedText().exec(position, vimState);\n    }\n  }\n}\n\n@RegisterAction\nclass DeleteToLineEnd extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['D'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.isLineEnd(vimState.document)) {\n      return;\n    }\n\n    const linesDown = (vimState.recordedState.count || 1) - 1;\n    const start = position;\n    const end = position.getDown(linesDown).getLineEnd().getLeftThroughLineBreaks();\n\n    await new operator.DeleteOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nclass YankLine extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['Y'];\n  override name = 'yank_full_line';\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const linesDown = (vimState.recordedState.count || 1) - 1;\n    const start = position.getLineBegin();\n    const end = position.getDown(linesDown).getLeft();\n\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    await new operator.YankOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nclass ChangeToLineEnd extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['C'];\n  override runsOnceForEachCountPrefix = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const count = vimState.recordedState.count || 1;\n\n    await new operator.ChangeOperator(this.multicursorIndex).run(\n      vimState,\n      position,\n      position\n        .getDown(Math.max(0, count - 1))\n        .getLineEnd()\n        .getLeft(),\n    );\n  }\n}\n\n@RegisterAction\nclass ChangeLine extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['S'];\n  override runsOnceForEachCountPrefix = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new operator.ChangeOperator(this.multicursorIndex).runRepeat(\n      vimState,\n      position,\n      vimState.recordedState.count || 1,\n    );\n  }\n\n  // Don't clash with sneak\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.doesActionApply(vimState, keysPressed) && !configuration.sneak;\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.couldActionApply(vimState, keysPressed) && !configuration.sneak;\n  }\n}\n\n@RegisterAction\nclass ActionDeleteChar extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['x'];\n  override name = 'delete_char';\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // If line is empty, do nothing\n    if (vimState.document.lineAt(position).text.length === 0) {\n      return;\n    }\n\n    const timesToRepeat = vimState.recordedState.count || 1;\n\n    await new operator.DeleteOperator(this.multicursorIndex).run(\n      vimState,\n      position,\n      position.getRight(timesToRepeat - 1).getLeftIfEOL(),\n    );\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass ActionDeleteCharWithDeleteKey extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['<Del>'];\n  override name = 'delete_char_with_del';\n  override runsOnceForEachCountPrefix = true;\n  override createsUndoPoint = true;\n\n  public override async execCount(position: Position, vimState: VimState): Promise<void> {\n    // If <del> has a count in front of it, then <del> deletes a character\n    // off the count. Therefore, 100<del>x, would apply 'x' 10 times.\n    // http://vimdoc.sourceforge.net/htmldoc/change.html#<Del>\n    if (vimState.recordedState.count !== 0) {\n      vimState.recordedState.count = Math.floor(vimState.recordedState.count / 10);\n\n      // Change actionsRunPressedKeys so that showCmd updates correctly\n      vimState.recordedState.actionsRunPressedKeys =\n        vimState.recordedState.count > 0 ? vimState.recordedState.count.toString().split('') : [];\n      this.isCompleteAction = false;\n    } else {\n      await new ActionDeleteChar().execCount(position, vimState);\n    }\n  }\n}\n\n@RegisterAction\nclass ActionDeleteLastChar extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['X'];\n  override name = 'delete_last_char';\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.character === 0) {\n      return;\n    }\n\n    const timesToRepeat = vimState.recordedState.count || 1;\n\n    await new operator.DeleteOperator(this.multicursorIndex).run(\n      vimState,\n      position.getLeft(timesToRepeat),\n      position.getLeft(),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualBlockDelete extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = [['d'], ['x'], ['X']];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const lines: string[] = [];\n\n    for (const { line, start, end } of TextEditor.iterateLinesInBlock(vimState)) {\n      lines.push(line);\n      vimState.recordedState.transformer.addTransformation({\n        type: 'deleteRange',\n        range: new vscode.Range(start, end),\n        manuallySetCursorPositions: true,\n      });\n    }\n\n    const text = lines.length === 1 ? lines[0] : lines.join('\\n');\n    vimState.currentRegisterMode = RegisterMode.BlockWise;\n    Register.put(vimState, text, this.multicursorIndex, true);\n\n    vimState.cursors = [\n      Cursor.atPosition(\n        visualBlockGetTopLeftPosition(vimState.cursorStopPosition, vimState.cursorStartPosition),\n      ),\n    ];\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass VisualBlockDeleteToLineEnd extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['D'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const lines: string[] = [];\n    for (const { start } of TextEditor.iterateLinesInBlock(vimState)) {\n      const range = new vscode.Range(start, start.getLineEnd());\n      lines.push(vimState.editor.document.getText(range));\n      vimState.recordedState.transformer.addTransformation({\n        type: 'deleteRange',\n        range,\n        manuallySetCursorPositions: true,\n      });\n    }\n\n    const topLeft = visualBlockGetTopLeftPosition(\n      vimState.cursorStopPosition,\n      vimState.cursorStartPosition,\n    );\n\n    const text = lines.length === 1 ? lines[0] : lines.join('\\n');\n    Register.put(vimState, text, this.multicursorIndex, true);\n\n    vimState.cursors = [Cursor.atPosition(topLeft)];\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass VisualBlockInsert extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['I'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const cursors: Cursor[] = [];\n    for (const cursor of vimState.cursors) {\n      for (const { line, start } of TextEditor.iterateLinesInBlock(vimState, cursor)) {\n        if (line === '' && start.character !== 0) {\n          continue;\n        }\n        cursors.push(Cursor.atPosition(start));\n      }\n    }\n    vimState.cursors = cursors;\n\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\n@RegisterAction\nclass VisualBlockChange extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = [['c'], ['s']];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const cursors: Cursor[] = [];\n    const lines: string[] = [];\n    for (const cursor of vimState.cursors) {\n      const width =\n        1 +\n        visualBlockGetBottomRightPosition(cursor.start, cursor.stop).character -\n        visualBlockGetTopLeftPosition(cursor.start, cursor.stop).character;\n      for (const { line, start, end } of TextEditor.iterateLinesInBlock(vimState, cursor)) {\n        // TODO: is this behavior consistent with similar actions like VisualBlock `d`?\n        lines.push(line.padEnd(width, ' '));\n        if (line) {\n          vimState.recordedState.transformer.addTransformation({\n            type: 'deleteRange',\n            range: new vscode.Range(start, end),\n            manuallySetCursorPositions: true,\n          });\n          cursors.push(Cursor.atPosition(start));\n        }\n      }\n    }\n    vimState.cursors = cursors;\n\n    const text = lines.length === 1 ? lines[0] : lines.join('\\n');\n    Register.put(vimState, text, this.multicursorIndex, true);\n\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\n@RegisterAction\nclass ActionChangeToEOLInVisualBlockMode extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['C'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const cursors: Cursor[] = [];\n    for (const cursor of vimState.cursors) {\n      for (const { start, end } of TextEditor.iterateLinesInBlock(vimState, cursor)) {\n        vimState.recordedState.transformer.delete(new vscode.Range(start, start.getLineEnd()));\n        cursors.push(Cursor.atPosition(end));\n      }\n    }\n    vimState.cursors = cursors;\n\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\nabstract class ActionGoToInsertVisualLineModeCommand extends BaseCommand {\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  abstract getCursorRangeForLine(\n    line: vscode.TextLine,\n    selectionStart: Position,\n    selectionEnd: Position,\n  ): Cursor;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.isFakeMultiCursor = true;\n\n    const resultingCursors: Cursor[] = [];\n    const cursorsOnBlankLines: Cursor[] = [];\n    for (const selection of vimState.editor.selections) {\n      const { start, end } = selection;\n\n      for (let i = start.line; i <= end.line; i++) {\n        const line = vimState.document.lineAt(i);\n\n        const cursorRange = this.getCursorRangeForLine(line, start, end);\n        if (!line.isEmptyOrWhitespace) {\n          resultingCursors.push(cursorRange);\n        } else {\n          cursorsOnBlankLines.push(cursorRange);\n        }\n      }\n    }\n\n    if (resultingCursors.length > 0) {\n      vimState.cursors = resultingCursors;\n    } else {\n      vimState.cursors = cursorsOnBlankLines;\n    }\n  }\n}\n\n@RegisterAction\nclass VisualLineInsert extends ActionGoToInsertVisualLineModeCommand {\n  modes = [Mode.VisualLine];\n  keys = ['I'];\n\n  getCursorRangeForLine(line: vscode.TextLine): Cursor {\n    return Cursor.atPosition(new Position(line.lineNumber, line.firstNonWhitespaceCharacterIndex));\n  }\n}\n\n@RegisterAction\nclass VisualLineAppend extends ActionGoToInsertVisualLineModeCommand {\n  modes = [Mode.VisualLine];\n  keys = ['A'];\n\n  getCursorRangeForLine(line: vscode.TextLine): Cursor {\n    return Cursor.atPosition(new Position(line.lineNumber, line.range.end.character));\n  }\n}\n\n@RegisterAction\nclass VisualInsert extends ActionGoToInsertVisualLineModeCommand {\n  modes = [Mode.Visual];\n  keys = ['I'];\n\n  getCursorRangeForLine(\n    line: vscode.TextLine,\n    selectionStart: Position,\n    selectionEnd: Position,\n  ): Cursor {\n    return Cursor.atPosition(\n      line.lineNumber === selectionStart.line\n        ? selectionStart\n        : new Position(line.lineNumber, line.firstNonWhitespaceCharacterIndex),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualAppend extends ActionGoToInsertVisualLineModeCommand {\n  modes = [Mode.Visual];\n  keys = ['A'];\n\n  getCursorRangeForLine(\n    line: vscode.TextLine,\n    selectionStart: Position,\n    selectionEnd: Position,\n  ): Cursor {\n    return Cursor.atPosition(\n      line.lineNumber === selectionEnd.line\n        ? selectionEnd\n        : new Position(line.lineNumber, line.range.end.character),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualBlockAppend extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['A'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const newCursors: Cursor[] = [];\n    for (const cursor of vimState.cursors) {\n      const [start, end] = sorted(cursor.start, cursor.stop);\n      for (let lineNum = start.line; lineNum <= end.line; lineNum++) {\n        const line = vimState.document.lineAt(lineNum);\n        const insertionColumn =\n          vimState.desiredColumn === Number.POSITIVE_INFINITY\n            ? line.text.length\n            : Math.max(cursor.start.character, cursor.stop.character) + 1;\n        if (line.text.length < insertionColumn) {\n          await TextEditor.insert(\n            vimState.editor,\n            ' '.repeat(insertionColumn - line.text.length),\n            line.range.end,\n            false,\n          );\n        }\n        newCursors.push(Cursor.atPosition(new Position(lineNum, insertionColumn)));\n      }\n    }\n\n    vimState.cursors = newCursors;\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\n@RegisterAction\nclass VisualLineDeleteChar extends BaseCommand {\n  modes = [Mode.VisualLine];\n  keys = ['x'];\n  override name = 'delete_char_visual_line_mode';\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    await new operator.DeleteOperator(this.multicursorIndex).run(\n      vimState,\n      start.getLineBegin(),\n      end.getLineEnd(),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualDeleteLine extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['X'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    await new operator.DeleteOperator(this.multicursorIndex).run(\n      vimState,\n      start.getLineBegin(),\n      end.getLineEnd(),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualChangeLine extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = [['C'], ['R']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    await new operator.ChangeOperator(this.multicursorIndex).run(\n      vimState,\n      start.getLineBegin(),\n      end.getLineEnd().getLeftIfEOL(),\n    );\n  }\n}\n\n@RegisterAction\nclass VisualChangeLine_2 extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['S'];\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return !configuration.surround && super.doesActionApply(vimState, keysPressed);\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new VisualChangeLine().exec(position, vimState);\n  }\n}\n\n@RegisterAction\nclass VisualBlockChangeLine extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = [['R'], ['S']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    return new VisualChangeLine().exec(position, vimState);\n  }\n}\n\n@RegisterAction\nclass ChangeChar extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['s'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new operator.ChangeOperator(this.multicursorIndex).run(\n      vimState,\n      position,\n      position.getRight((vimState.recordedState.count || 1) - 1),\n    );\n  }\n\n  // Don't clash with surround or sneak modes!\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return (\n      super.doesActionApply(vimState, keysPressed) &&\n      !configuration.sneak &&\n      !vimState.recordedState.operator\n    );\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return (\n      super.couldActionApply(vimState, keysPressed) &&\n      !configuration.sneak &&\n      !vimState.recordedState.operator\n    );\n  }\n}\n\n@RegisterAction\nclass ToggleCaseAndMoveForward extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['~'];\n  override createsUndoPoint = true;\n\n  private toggleCase(text: string): string {\n    let newText = '';\n    for (const char of text) {\n      let toggled = char.toLocaleLowerCase();\n      if (toggled === char) {\n        toggled = char.toLocaleUpperCase();\n      }\n      newText += toggled;\n    }\n    return newText;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const count = vimState.recordedState.count || 1;\n    const range = new vscode.Range(\n      position,\n      shouldWrapKey(vimState.currentMode, '~')\n        ? position.getOffsetThroughLineBreaks(count)\n        : position.getRight(count),\n    );\n\n    vimState.recordedState.transformer.replace(\n      range,\n      this.toggleCase(vimState.document.getText(range)),\n      PositionDiff.exactPosition(range.end),\n    );\n  }\n}\n\n@RegisterAction\nexport class CommandUnicodeName extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'a'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const char = vimState.document.getText(new vscode.Range(position, position.getRight()));\n    const charCode = char.charCodeAt(0);\n    // TODO: Handle charCode > 127 by also including <M-x>\n    StatusBar.setText(\n      vimState,\n      `<${char}>  ${charCode},  Hex ${charCode.toString(16)},  Octal ${charCode.toString(8)}`,\n    );\n  }\n}\n\n@RegisterAction\nclass ShowHover extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'h'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('editor.action.showHover');\n  }\n}\n\n/**\n * Multi-Cursor Command Overrides\n *\n * We currently have to override the VSCode key commands that get us into multi-cursor mode.\n *\n * Normally, we'd just listen for another cursor to be added in order to go into multi-cursor\n * mode rather than rewriting each keybinding one-by-one. We can't currently do that because\n * Visual Block Mode also creates additional cursors, but will get confused if you're in\n * multi-cursor mode.\n */\n\n@RegisterAction\nexport class ActionOverrideCmdD extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual];\n  keys = [['<D-d>'], ['g', 'b']];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  override runsOnceForEachCountPrefix = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('editor.action.addSelectionToNextFindMatch');\n    vimState.cursors = getCursorsAfterSync(vimState.editor);\n\n    // If this is the first cursor, select 1 character less\n    // so that only the word is selected, no extra character\n    vimState.cursors = vimState.cursors.map((x) => x.withNewStop(x.stop.getLeft()));\n\n    await vimState.setCurrentMode(Mode.Visual);\n  }\n}\n\n@RegisterAction\nclass ActionOverrideCmdDInsert extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<D-d>'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  override runsOnceForEachCountPrefix = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // Since editor.action.addSelectionToNextFindMatch uses the selection to\n    // determine where to add a word, we need to do a hack and manually set the\n    // selections to the word boundaries before we make the api call.\n    vimState.editor.selections = vimState.editor.selections.map((x, idx) => {\n      const curPos = x.active;\n      if (idx === 0) {\n        return new vscode.Selection(\n          curPos.prevWordStart(vimState.document),\n          curPos.getLeft().nextWordEnd(vimState.document, { inclusive: true }).getRight(),\n        );\n      } else {\n        // Since we're adding the selections ourselves, we need to make sure\n        // that our selection is actually over what our original word is\n        const matchWordPos = vimState.editor.selections[0].active;\n        const matchWordLength =\n          matchWordPos.getLeft().nextWordEnd(vimState.document, { inclusive: true }).getRight()\n            .character - matchWordPos.prevWordStart(vimState.document).character;\n        const wordBegin = curPos.getLeft(matchWordLength);\n        return new vscode.Selection(wordBegin, curPos);\n      }\n    });\n    vimState.recordedState.transformer.vscodeCommand('editor.action.addSelectionToNextFindMatch');\n  }\n}\n\n@RegisterAction\nclass InsertCursorBelow extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual];\n  keys = [\n    ['<D-alt+down>'], // OSX\n    ['<C-alt+down>'], // Windows\n  ];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  override runsOnceForEachCountPrefix = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.vscodeCommand('editor.action.insertCursorBelow');\n  }\n}\n\n@RegisterAction\nclass InsertCursorAbove extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual];\n  keys = [\n    ['<D-alt+up>'], // OSX\n    ['<C-alt+up>'], // Windows\n  ];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  override runsOnceForEachCountPrefix = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.vscodeCommand('editor.action.insertCursorAbove');\n  }\n}\n\n@RegisterAction\nclass ShowFileOutline extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'O'];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('outline.focus');\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/commandLine.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { CommandLine, ExCommandLine, SearchCommandLine } from '../../cmd_line/commandLine';\nimport { ChangeCommand } from '../../cmd_line/commands/change';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { Register, RegisterMode } from '../../register/register';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { TextEditor } from '../../textEditor';\nimport { Clipboard } from '../../util/clipboard';\nimport { getPathDetails, readDirectory } from '../../util/path';\nimport { builtinExCommands } from '../../vimscript/exCommandParser';\nimport { SearchDirection } from '../../vimscript/pattern';\nimport { BaseCommand, RegisterAction } from '../base';\n\nabstract class CommandLineAction extends BaseCommand {\n  modes = [Mode.CommandlineInProgress, Mode.SearchInProgressMode];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  protected abstract run(vimState: VimState, commandLine: CommandLine): Promise<void>;\n\n  public override async exec(position: vscode.Position, vimState: VimState): Promise<void> {\n    if (\n      !(\n        vimState.modeData.mode === Mode.CommandlineInProgress ||\n        vimState.modeData.mode === Mode.SearchInProgressMode\n      )\n    ) {\n      throw new Error(`Unexpected mode ${vimState.modeData.mode} in CommandLineAction`);\n    }\n\n    await this.run(vimState, vimState.modeData.commandLine);\n  }\n}\n\n@RegisterAction\nclass CommandLineTab extends CommandLineAction {\n  override modes = [Mode.CommandlineInProgress];\n  keys = [['<tab>'], ['<S-tab>']];\n\n  private cycleCompletion(isTabForward: boolean, commandLine: ExCommandLine) {\n    const autoCompleteItems = commandLine.autoCompleteItems;\n    if (autoCompleteItems.length === 0) {\n      return;\n    }\n\n    commandLine.autoCompleteIndex = isTabForward\n      ? (commandLine.autoCompleteIndex + 1) % autoCompleteItems.length\n      : (commandLine.autoCompleteIndex - 1 + autoCompleteItems.length) % autoCompleteItems.length;\n\n    const lastPos = commandLine.preCompleteCharacterPos;\n    const lastCmd = commandLine.preCompleteCommand;\n    const evalCmd = lastCmd.slice(0, lastPos);\n    const restCmd = lastCmd.slice(lastPos);\n\n    commandLine.text = evalCmd + autoCompleteItems[commandLine.autoCompleteIndex] + restCmd;\n    commandLine.cursorIndex = commandLine.text.length - restCmd.length;\n  }\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    if (!(commandLine instanceof ExCommandLine)) {\n      throw new Error('Expected ExCommandLine in CommandLineTab::run()');\n    }\n\n    const key = this.keysPressed[0];\n    const isTabForward = key === '<tab>';\n\n    // If we hit <Tab> twice in a row, definitely cycle\n    if (\n      commandLine.autoCompleteItems.length !== 0 &&\n      vimState.recordedState.actionsRun[vimState.recordedState.actionsRun.length - 2] instanceof\n        CommandLineTab\n    ) {\n      this.cycleCompletion(isTabForward, commandLine);\n      return;\n    }\n\n    let newCompletionItems: string[] = [];\n\n    // Sub string since vim does completion before the cursor\n    let evalCmd = commandLine.text.slice(0, commandLine.cursorIndex);\n    const restCmd = commandLine.text.slice(commandLine.cursorIndex);\n\n    // \\s* is the match the extra space before any character like ':  edit'\n    const cmdRegex = /^\\s*\\w+$/;\n    const fileRegex = /^\\s*\\w+\\s+/g;\n    if (cmdRegex.test(evalCmd)) {\n      // Command completion\n      newCompletionItems = builtinExCommands\n        .map((pair) => pair[0][0] + pair[0][1])\n        .filter((cmd) => cmd.startsWith(evalCmd))\n        // Remove the already typed portion in the array\n        .map((cmd) => cmd.slice(cmd.search(evalCmd) + evalCmd.length))\n        .sort();\n    } else if (fileRegex.exec(evalCmd)) {\n      // File completion by searching if there is a space after the first word/command\n      // ideally it should be a process of white-listing to selected commands like :e and :vsp\n      const filePathInCmd = evalCmd.substring(fileRegex.lastIndex);\n      const currentUri = vimState.document.uri;\n      const isRemote = !!vscode.env.remoteName;\n\n      const {\n        fullDirPath,\n        baseName,\n        partialPath,\n        path: p,\n      } = getPathDetails(filePathInCmd, currentUri, isRemote);\n      // Update the evalCmd in case of windows, where we change / to \\\n      evalCmd = evalCmd.slice(0, fileRegex.lastIndex) + partialPath;\n\n      // test if the baseName is . or ..\n      const shouldAddDotItems = /^\\.\\.?$/g.test(baseName);\n      const dirItems = await readDirectory(\n        fullDirPath,\n        p.sep,\n        currentUri,\n        isRemote,\n        shouldAddDotItems,\n      );\n      const startWithBaseNameRegex = new RegExp(\n        `^${baseName}`,\n        process.platform === 'win32' ? 'i' : '',\n      );\n      newCompletionItems = dirItems\n        .map((name): [RegExpExecArray | null, string] => [startWithBaseNameRegex.exec(name), name])\n        .filter(([isMatch]) => isMatch !== null)\n        .map(([match, name]) => name.slice(match![0].length))\n        .sort();\n    }\n\n    const newIndex = isTabForward ? 0 : newCompletionItems.length - 1;\n    commandLine.autoCompleteIndex = newIndex;\n    // If here only one items we fill cmd direct, so the next tab will not cycle the one item array\n    commandLine.autoCompleteItems = newCompletionItems.length <= 1 ? [] : newCompletionItems;\n    commandLine.preCompleteCharacterPos = commandLine.cursorIndex;\n    commandLine.preCompleteCommand = evalCmd + restCmd;\n\n    const completion = newCompletionItems.length === 0 ? '' : newCompletionItems[newIndex];\n    commandLine.text = evalCmd + completion + restCmd;\n    commandLine.cursorIndex = commandLine.text.length - restCmd.length;\n  }\n}\n\n@RegisterAction\nclass ExCommandLineEnter extends CommandLineAction {\n  override modes = [Mode.CommandlineInProgress];\n  keys = [['\\n'], ['<C-m>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.run(vimState);\n\n    if (commandLine instanceof ExCommandLine && commandLine.getCommand() instanceof ChangeCommand) {\n      return;\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass SearchCommandLineEnter extends CommandLineAction {\n  override modes = [Mode.SearchInProgressMode];\n  keys = [['\\n'], ['<C-m>']];\n\n  override runsOnceForEveryCursor() {\n    return true;\n  }\n  override isJump = true;\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.run(vimState);\n    if (this.multicursorIndex === vimState.cursors.length - 1) {\n      // TODO: gah, this is stupid\n      await vimState.setCurrentMode(commandLine.previousMode);\n    }\n  }\n}\n\n@RegisterAction\nclass CommandLineEscape extends CommandLineAction {\n  keys = [['<Esc>'], ['<C-c>'], ['<C-[>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.escape(vimState);\n  }\n}\n\n@RegisterAction\nclass CommandLineCtrlF extends CommandLineAction {\n  keys = ['<C-f>'];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.ctrlF(vimState);\n  }\n}\n\n@RegisterAction\nclass CommandLineBackspace extends CommandLineAction {\n  keys = [['<BS>'], ['<S-BS>'], ['<C-h>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.backspace(vimState);\n  }\n}\n\n@RegisterAction\nclass CommandLineDelete extends CommandLineAction {\n  keys = ['<Del>'];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    await commandLine.delete(vimState);\n  }\n}\n\n@RegisterAction\nclass CommandlineHome extends CommandLineAction {\n  keys = [['<Home>'], ['<C-b>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.home();\n  }\n}\n\n@RegisterAction\nclass CommandLineEnd extends CommandLineAction {\n  keys = [['<End>'], ['<C-e>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.end();\n  }\n}\n\n@RegisterAction\nclass CommandLineDeleteWord extends CommandLineAction {\n  keys = [['<C-w>'], ['<C-BS>']];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.deleteWord();\n  }\n}\n\n@RegisterAction\nclass CommandLineDeleteToBeginning extends CommandLineAction {\n  keys = ['<C-u>'];\n\n  protected override async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.deleteToBeginning();\n  }\n}\n\n@RegisterAction\nclass CommandLineWordLeft extends CommandLineAction {\n  keys = ['<C-left>'];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.wordLeft();\n  }\n}\n\n@RegisterAction\nclass CommandLineWordRight extends CommandLineAction {\n  keys = ['<C-right>'];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.wordRight();\n  }\n}\n\n@RegisterAction\nclass CommandLineHistoryBack extends CommandLineAction {\n  keys = [['<up>'], ['<C-p>']];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.historyBack();\n  }\n}\n\n@RegisterAction\nclass CommandLineHistoryForward extends CommandLineAction {\n  keys = [['<down>'], ['<C-n>']];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.historyForward();\n  }\n}\n\n@RegisterAction\nclass CommandInsertRegisterContentInCommandLine extends CommandLineAction {\n  keys = ['<C-r>', '<character>'];\n  override isCompleteAction = false;\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    const registerKey = this.keysPressed[1];\n    if (!Register.isValidRegister(registerKey)) {\n      return;\n    }\n\n    vimState.recordedState.registerName = registerKey;\n    const register = await Register.get(vimState.recordedState.registerName, this.multicursorIndex);\n    if (register === undefined) {\n      StatusBar.displayError(\n        vimState,\n        VimError.NothingInRegister(vimState.recordedState.registerName),\n      );\n      return;\n    }\n\n    let text: string;\n    if (register.text instanceof Array) {\n      text = register.text.join('\\n');\n    } else if (register.text instanceof RecordedState) {\n      let keyStrokes: string[] = [];\n\n      for (const action of register.text.actionsRun) {\n        keyStrokes = keyStrokes.concat(action.keysPressed);\n      }\n\n      text = keyStrokes.join('\\n');\n    } else {\n      text = register.text;\n    }\n\n    if (register.registerMode === RegisterMode.LineWise) {\n      text += '\\n';\n    }\n\n    commandLine.text += text;\n    commandLine.cursorIndex += text.length;\n  }\n}\n\n@RegisterAction\nclass CommandInsertWord extends CommandLineAction {\n  keys = ['<C-r>', '<C-w>'];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    const word = TextEditor.getWord(vimState.document, vimState.cursorStopPosition.getLeftIfEOL());\n\n    if (word !== undefined) {\n      commandLine.text += word;\n      commandLine.cursorIndex += word.length;\n    }\n  }\n}\n\n@RegisterAction\nclass CommandLineLeftRight extends CommandLineAction {\n  keys = [['<left>'], ['<right>']];\n\n  private getTrimmedStatusBarText() {\n    // first regex removes the : / and | from the string\n    // second regex removes a single space from the end of the string\n    const trimmedStatusBarText = StatusBar.getText()\n      .replace(/^(?:\\/|\\:)(.*)(?:\\|)(.*)/, '$1$2')\n      .replace(/(.*) $/, '$1');\n    return trimmedStatusBarText;\n  }\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    const key = this.keysPressed[0];\n    const statusBarText = this.getTrimmedStatusBarText();\n    if (key === '<right>') {\n      commandLine.cursorIndex = Math.min(commandLine.cursorIndex + 1, statusBarText.length);\n    } else if (key === '<left>') {\n      commandLine.cursorIndex = Math.max(commandLine.cursorIndex - 1, 0);\n    }\n  }\n}\n\n@RegisterAction\nclass CommandLinePaste extends CommandLineAction {\n  keys = [['<C-v>'], ['<D-v>']];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    const textFromClipboard = await Clipboard.Paste();\n\n    commandLine.text = commandLine.text\n      .substring(0, commandLine.cursorIndex)\n      .concat(textFromClipboard)\n      .concat(commandLine.text.slice(commandLine.cursorIndex));\n    commandLine.cursorIndex += textFromClipboard.length;\n  }\n}\n\n@RegisterAction\nclass CommandCtrlLInSearchMode extends CommandLineAction {\n  override modes = [Mode.SearchInProgressMode];\n  keys = ['<C-l>'];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    if (commandLine instanceof SearchCommandLine) {\n      const currentMatch = commandLine.getCurrentMatchRange(vimState);\n      if (currentMatch) {\n        const line = vimState.document.lineAt(currentMatch.range.end).text;\n        if (currentMatch.range.end.character < line.length) {\n          commandLine.getSearchState().searchString += line[currentMatch.range.end.character];\n          commandLine.cursorIndex++;\n        }\n      }\n    }\n  }\n}\n\n@RegisterAction\nclass CommandAdvanceCurrentMatch extends CommandLineAction {\n  override modes = [Mode.SearchInProgressMode];\n  keys = [['<C-g>'], ['<C-t>']];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    const key = this.keysPressed[0];\n    const direction =\n      key === '<C-g>'\n        ? SearchDirection.Forward\n        : key === '<C-t>'\n          ? SearchDirection.Backward\n          : undefined;\n    if (commandLine instanceof SearchCommandLine && direction !== undefined) {\n      commandLine.advanceCurrentMatch(vimState, direction);\n    }\n  }\n}\n\n@RegisterAction\nclass CommandLineType extends CommandLineAction {\n  keys = [['<character>']];\n\n  protected async run(vimState: VimState, commandLine: CommandLine): Promise<void> {\n    commandLine.typeCharacter(this.keysPressed[0]);\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/digraphs.ts",
    "content": "// prettier-ignore\nexport const DefaultDigraphs = new Map<string, [string, number | number[]]>([\n  [\"NU\", [\"^@\", 10]],\n  [\"SH\", [\"^A\", 1]],\n  [\"SX\", [\"^B\", 2]],\n  [\"EX\", [\"^C\", 3]],\n  [\"ET\", [\"^D\", 4]],\n  [\"EQ\", [\"^E\", 5]],\n  [\"AK\", [\"^F\", 6]],\n  [\"BL\", [\"^G\", 7]],\n  [\"BS\", [\"^H\", 8]],\n  [\"HT\", [\"^I\", 9]],\n  [\"LF\", [\"^@\", 10]],\n  [\"VT\", [\"^K\", 11]],\n  [\"FF\", [\"^L\", 12]],\n  [\"CR\", [\"^M\", 13]],\n  [\"SO\", [\"^N\", 14]],\n  [\"SI\", [\"^O\", 15]],\n  [\"DL\", [\"^P\", 16]],\n  [\"D1\", [\"^Q\", 17]],\n  [\"D2\", [\"^R\", 18]],\n  [\"D3\", [\"^S\", 19]],\n  [\"D4\", [\"^T\", 20]],\n  [\"NK\", [\"^U\", 21]],\n  [\"SY\", [\"^V\", 22]],\n  [\"EB\", [\"^W\", 23]],\n  [\"CN\", [\"^X\", 24]],\n  [\"EM\", [\"^Y\", 25]],\n  [\"SB\", [\"^Z\", 26]],\n  [\"EC\", [\"^[\", 27]],\n  [\"FS\", [\"^\\\\\", 28]],\n  [\"GS\", [\"^]\", 29]],\n  [\"RS\", [\"^^\", 30]],\n  [\"US\", [\"^_\", 31]],\n  [\"SP\", [\" \", 32]],\n  [\"Nb\", [\"#\", 35]],\n  [\"DO\", [\"$\", 36]],\n  [\"At\", [\"@\", 64]],\n  [\"<(\", [\"[\", 91]],\n  [\"//\", [\"\\\\\", 92]],\n  [\")>\", [\"]\", 93]],\n  [\"'>\", [\"^\", 94]],\n  [\"'!\", [\"`\", 96]],\n  [\"(!\", [\"{\", 123]],\n  [\"!!\", [\"|\", 124]],\n  [\"!)\", [\"}\", 125]],\n  [\"'?\", [\"~\", 126]],\n  [\"DT\", [\"^?\", 127]],\n  [\"PA\", [\"<80>\", 128]],\n  [\"HO\", [\"<81>\", 129]],\n  [\"BH\", [\"<82>\", 130]],\n  [\"NH\", [\"<83>\", 131]],\n  [\"IN\", [\"<84>\", 132]],\n  [\"NL\", [\"<85>\", 133]],\n  [\"SA\", [\"<86>\", 134]],\n  [\"ES\", [\"<87>\", 135]],\n  [\"HS\", [\"<88>\", 136]],\n  [\"HJ\", [\"<89>\", 137]],\n  [\"VS\", [\"<8a>\", 138]],\n  [\"PD\", [\"<8b>\", 139]],\n  [\"PU\", [\"<8c>\", 140]],\n  [\"RI\", [\"<8d>\", 141]],\n  [\"S2\", [\"<8e>\", 142]],\n  [\"S3\", [\"<8f>\", 143]],\n  [\"DC\", [\"<90>\", 144]],\n  [\"P1\", [\"<91>\", 145]],\n  [\"P2\", [\"<92>\", 146]],\n  [\"TS\", [\"<93>\", 147]],\n  [\"CC\", [\"<94>\", 148]],\n  [\"MW\", [\"<95>\", 149]],\n  [\"SG\", [\"<96>\", 150]],\n  [\"EG\", [\"<97>\", 151]],\n  [\"SS\", [\"<98>\", 152]],\n  [\"GC\", [\"<99>\", 153]],\n  [\"SC\", [\"<9a>\", 154]],\n  [\"CI\", [\"<9b>\", 155]],\n  [\"ST\", [\"<9c>\", 156]],\n  [\"OC\", [\"<9d>\", 157]],\n  [\"PM\", [\"<9e>\", 158]],\n  [\"AC\", [\"<9f>\", 159]],\n  [\"NS\", [\" \", 160]],\n  [\"!I\", [\"¡\", 161]],\n  [\"~!\", [\"¡\", 161]],\n  [\"Ct\", [\"¢\", 162]],\n  [\"c|\", [\"¢\", 162]],\n  [\"Pd\", [\"£\", 163]],\n  [\"$$\", [\"£\", 163]],\n  [\"Cu\", [\"¤\", 164]],\n  [\"ox\", [\"¤\", 164]],\n  [\"Ye\", [\"¥\", 165]],\n  [\"Y-\", [\"¥\", 165]],\n  [\"BB\", [\"¦\", 166]],\n  [\"||\", [\"¦\", 166]],\n  [\"SE\", [\"§\", 167]],\n  [\"':\", [\"¨\", 168]],\n  [\"Co\", [\"©\", 169]],\n  [\"cO\", [\"©\", 169]],\n  [\"-a\", [\"ª\", 170]],\n  [\"<<\", [\"«\", 171]],\n  [\"NO\", [\"¬\", 172]],\n  [\"-,\", [\"¬\", 172]],\n  [\"--\", [\"­\", 173]],\n  [\"Rg\", [\"®\", 174]],\n  [\"'m\", [\"¯\", 175]],\n  [\"-=\", [\"¯\", 175]],\n  [\"DG\", [\"°\", 176]],\n  [\"~o\", [\"°\", 176]],\n  [\"+-\", [\"±\", 177]],\n  [\"2S\", [\"²\", 178]],\n  [\"22\", [\"²\", 178]],\n  [\"3S\", [\"³\", 179]],\n  [\"33\", [\"³\", 179]],\n  [\"''\", [\"´\", 180]],\n  [\"My\", [\"µ\", 181]],\n  [\"PI\", [\"¶\", 182]],\n  [\"pp\", [\"¶\", 182]],\n  [\".M\", [\"·\", 183]],\n  [\"~.\", [\"·\", 183]],\n  [\"',\", [\"¸\", 184]],\n  [\"1S\", [\"¹\", 185]],\n  [\"11\", [\"¹\", 185]],\n  [\"-o\", [\"º\", 186]],\n  [\">>\", [\"»\", 187]],\n  [\"14\", [\"¼\", 188]],\n  [\"12\", [\"½\", 189]],\n  [\"34\", [\"¾\", 190]],\n  [\"?I\", [\"¿\", 191]],\n  [\"~?\", [\"¿\", 191]],\n  [\"A!\", [\"À\", 192]],\n  [\"A`\", [\"À\", 192]],\n  [\"A'\", [\"Á\", 193]],\n  [\"A>\", [\"Â\", 194]],\n  [\"A^\", [\"Â\", 194]],\n  [\"A?\", [\"Ã\", 195]],\n  [\"A~\", [\"Ã\", 195]],\n  [\"A:\", [\"Ä\", 196]],\n  [\"A\\\"\", [\"Ä\", 196]],\n  [\"AA\", [\"Å\", 197]],\n  [\"A@\", [\"Å\", 197]],\n  [\"AE\", [\"Æ\", 198]],\n  [\"C,\", [\"Ç\", 199]],\n  [\"E!\", [\"È\", 200]],\n  [\"E`\", [\"È\", 200]],\n  [\"E'\", [\"É\", 201]],\n  [\"E>\", [\"Ê\", 202]],\n  [\"E^\", [\"Ê\", 202]],\n  [\"E:\", [\"Ë\", 203]],\n  [\"E\\\"\", [\"Ë\", 203]],\n  [\"I!\", [\"Ì\", 204]],\n  [\"I`\", [\"Ì\", 204]],\n  [\"I'\", [\"Í\", 205]],\n  [\"I>\", [\"Î\", 206]],\n  [\"I^\", [\"Î\", 206]],\n  [\"I:\", [\"Ï\", 207]],\n  [\"I\\\"\", [\"Ï\", 207]],\n  [\"D-\", [\"Ð\", 208]],\n  [\"N?\", [\"Ñ\", 209]],\n  [\"N~\", [\"Ñ\", 209]],\n  [\"O!\", [\"Ò\", 210]],\n  [\"O`\", [\"Ò\", 210]],\n  [\"O'\", [\"Ó\", 211]],\n  [\"O>\", [\"Ô\", 212]],\n  [\"O^\", [\"Ô\", 212]],\n  [\"O?\", [\"Õ\", 213]],\n  [\"O~\", [\"Õ\", 213]],\n  [\"O:\", [\"Ö\", 214]],\n  [\"*X\", [\"×\", 215]],\n  [\"/\\\\\", [\"×\", 215]],\n  [\"O/\", [\"Ø\", 216]],\n  [\"U!\", [\"Ù\", 217]],\n  [\"U`\", [\"Ù\", 217]],\n  [\"U'\", [\"Ú\", 218]],\n  [\"U>\", [\"Û\", 219]],\n  [\"U^\", [\"Û\", 219]],\n  [\"U:\", [\"Ü\", 220]],\n  [\"Y'\", [\"Ý\", 221]],\n  [\"TH\", [\"Þ\", 222]],\n  [\"Ip\", [\"Þ\", 222]],\n  [\"ss\", [\"ß\", 223]],\n  [\"a!\", [\"à\", 224]],\n  [\"a`\", [\"à\", 224]],\n  [\"a'\", [\"á\", 225]],\n  [\"a>\", [\"â\", 226]],\n  [\"a^\", [\"â\", 226]],\n  [\"a?\", [\"ã\", 227]],\n  [\"a~\", [\"ã\", 227]],\n  [\"a:\", [\"ä\", 228]],\n  [\"a\\\"\", [\"ä\", 228]],\n  [\"aa\", [\"å\", 229]],\n  [\"a@\", [\"å\", 229]],\n  [\"ae\", [\"æ\", 230]],\n  [\"c,\", [\"ç\", 231]],\n  [\"e!\", [\"è\", 232]],\n  [\"e`\", [\"è\", 232]],\n  [\"e'\", [\"é\", 233]],\n  [\"e>\", [\"ê\", 234]],\n  [\"e^\", [\"ê\", 234]],\n  [\"e:\", [\"ë\", 235]],\n  [\"e\\\"\", [\"ë\", 235]],\n  [\"i!\", [\"ì\", 236]],\n  [\"i`\", [\"ì\", 236]],\n  [\"i'\", [\"í\", 237]],\n  [\"i>\", [\"î\", 238]],\n  [\"i^\", [\"î\", 238]],\n  [\"i:\", [\"ï\", 239]],\n  [\"d-\", [\"ð\", 240]],\n  [\"n?\", [\"ñ\", 241]],\n  [\"n~\", [\"ñ\", 241]],\n  [\"o!\", [\"ò\", 242]],\n  [\"o`\", [\"ò\", 242]],\n  [\"o'\", [\"ó\", 243]],\n  [\"o>\", [\"ô\", 244]],\n  [\"o^\", [\"ô\", 244]],\n  [\"o?\", [\"õ\", 245]],\n  [\"o~\", [\"õ\", 245]],\n  [\"o:\", [\"ö\", 246]],\n  [\"-:\", [\"÷\", 247]],\n  [\"o/\", [\"ø\", 248]],\n  [\"u!\", [\"ù\", 249]],\n  [\"u`\", [\"ù\", 249]],\n  [\"u'\", [\"ú\", 250]],\n  [\"u>\", [\"û\", 251]],\n  [\"u^\", [\"û\", 251]],\n  [\"u:\", [\"ü\", 252]],\n  [\"y'\", [\"ý\", 253]],\n  [\"th\", [\"þ\", 254]],\n  [\"y:\", [\"ÿ\", 255]],\n  [\"y\\\"\", [\"ÿ\", 255]],\n  [\"A-\", [\"Ā\", 256]],\n  [\"a-\", [\"ā\", 257]],\n  [\"A(\", [\"Ă\", 258]],\n  [\"a(\", [\"ă\", 259]],\n  [\"A;\", [\"Ą\", 260]],\n  [\"a;\", [\"ą\", 261]],\n  [\"C'\", [\"Ć\", 262]],\n  [\"c'\", [\"ć\", 263]],\n  [\"C>\", [\"Ĉ\", 264]],\n  [\"c>\", [\"ĉ\", 265]],\n  [\"C.\", [\"Ċ\", 266]],\n  [\"c.\", [\"ċ\", 267]],\n  [\"C<\", [\"Č\", 268]],\n  [\"c<\", [\"č\", 269]],\n  [\"D<\", [\"Ď\", 270]],\n  [\"d<\", [\"ď\", 271]],\n  [\"D/\", [\"Đ\", 272]],\n  [\"d/\", [\"đ\", 273]],\n  [\"E-\", [\"Ē\", 274]],\n  [\"e-\", [\"ē\", 275]],\n  [\"E(\", [\"Ĕ\", 276]],\n  [\"e(\", [\"ĕ\", 277]],\n  [\"E.\", [\"Ė\", 278]],\n  [\"e.\", [\"ė\", 279]],\n  [\"E;\", [\"Ę\", 280]],\n  [\"e;\", [\"ę\", 281]],\n  [\"E<\", [\"Ě\", 282]],\n  [\"e<\", [\"ě\", 283]],\n  [\"G>\", [\"Ĝ\", 284]],\n  [\"g>\", [\"ĝ\", 285]],\n  [\"G(\", [\"Ğ\", 286]],\n  [\"g(\", [\"ğ\", 287]],\n  [\"G.\", [\"Ġ\", 288]],\n  [\"g.\", [\"ġ\", 289]],\n  [\"G,\", [\"Ģ\", 290]],\n  [\"g,\", [\"ģ\", 291]],\n  [\"H>\", [\"Ĥ\", 292]],\n  [\"h>\", [\"ĥ\", 293]],\n  [\"H/\", [\"Ħ\", 294]],\n  [\"h/\", [\"ħ\", 295]],\n  [\"I?\", [\"Ĩ\", 296]],\n  [\"i?\", [\"ĩ\", 297]],\n  [\"I-\", [\"Ī\", 298]],\n  [\"i-\", [\"ī\", 299]],\n  [\"I(\", [\"Ĭ\", 300]],\n  [\"i(\", [\"ĭ\", 301]],\n  [\"I;\", [\"Į\", 302]],\n  [\"i;\", [\"į\", 303]],\n  [\"I.\", [\"İ\", 304]],\n  [\"i.\", [\"ı\", 305]],\n  [\"IJ\", [\"Ĳ\", 306]],\n  [\"ij\", [\"ĳ\", 307]],\n  [\"J>\", [\"Ĵ\", 308]],\n  [\"j>\", [\"ĵ\", 309]],\n  [\"K,\", [\"Ķ\", 310]],\n  [\"k,\", [\"ķ\", 311]],\n  [\"kk\", [\"ĸ\", 312]],\n  [\"L'\", [\"Ĺ\", 313]],\n  [\"l'\", [\"ĺ\", 314]],\n  [\"L,\", [\"Ļ\", 315]],\n  [\"l,\", [\"ļ\", 316]],\n  [\"L<\", [\"Ľ\", 317]],\n  [\"l<\", [\"ľ\", 318]],\n  [\"L.\", [\"Ŀ\", 319]],\n  [\"l.\", [\"ŀ\", 320]],\n  [\"L/\", [\"Ł\", 321]],\n  [\"l/\", [\"ł\", 322]],\n  [\"N'\", [\"Ń\", 323]],\n  [\"n'\", [\"ń\", 324]],\n  [\"N,\", [\"Ņ\", 325]],\n  [\"n,\", [\"ņ\", 326]],\n  [\"N<\", [\"Ň\", 327]],\n  [\"n<\", [\"ň\", 328]],\n  [\"'n\", [\"ŉ\", 329]],\n  [\"NG\", [\"Ŋ\", 330]],\n  [\"ng\", [\"ŋ\", 331]],\n  [\"O-\", [\"Ō\", 332]],\n  [\"o-\", [\"ō\", 333]],\n  [\"O(\", [\"Ŏ\", 334]],\n  [\"o(\", [\"ŏ\", 335]],\n  [\"O\\\"\", [\"Ő\", 336]],\n  [\"o\\\"\", [\"ő\", 337]],\n  [\"OE\", [\"Œ\", 338]],\n  [\"oe\", [\"œ\", 339]],\n  [\"R'\", [\"Ŕ\", 340]],\n  [\"r'\", [\"ŕ\", 341]],\n  [\"R,\", [\"Ŗ\", 342]],\n  [\"r,\", [\"ŗ\", 343]],\n  [\"R<\", [\"Ř\", 344]],\n  [\"r<\", [\"ř\", 345]],\n  [\"S'\", [\"Ś\", 346]],\n  [\"s'\", [\"ś\", 347]],\n  [\"S>\", [\"Ŝ\", 348]],\n  [\"s>\", [\"ŝ\", 349]],\n  [\"S,\", [\"Ş\", 350]],\n  [\"s,\", [\"ş\", 351]],\n  [\"S<\", [\"Š\", 352]],\n  [\"s<\", [\"š\", 353]],\n  [\"T,\", [\"Ţ\", 354]],\n  [\"t,\", [\"ţ\", 355]],\n  [\"T<\", [\"Ť\", 356]],\n  [\"t<\", [\"ť\", 357]],\n  [\"T/\", [\"Ŧ\", 358]],\n  [\"t/\", [\"ŧ\", 359]],\n  [\"U?\", [\"Ũ\", 360]],\n  [\"u?\", [\"ũ\", 361]],\n  [\"U-\", [\"Ū\", 362]],\n  [\"u-\", [\"ū\", 363]],\n  [\"U(\", [\"Ŭ\", 364]],\n  [\"u(\", [\"ŭ\", 365]],\n  [\"U0\", [\"Ů\", 366]],\n  [\"u0\", [\"ů\", 367]],\n  [\"U\\\"\", [\"Ű\", 368]],\n  [\"u\\\"\", [\"ű\", 369]],\n  [\"U;\", [\"Ų\", 370]],\n  [\"u;\", [\"ų\", 371]],\n  [\"W>\", [\"Ŵ\", 372]],\n  [\"w>\", [\"ŵ\", 373]],\n  [\"Y>\", [\"Ŷ\", 374]],\n  [\"y>\", [\"ŷ\", 375]],\n  [\"Y:\", [\"Ÿ\", 376]],\n  [\"Z'\", [\"Ź\", 377]],\n  [\"z'\", [\"ź\", 378]],\n  [\"Z.\", [\"Ż\", 379]],\n  [\"z.\", [\"ż\", 380]],\n  [\"Z<\", [\"Ž\", 381]],\n  [\"z<\", [\"ž\", 382]],\n  [\"O9\", [\"Ơ\", 416]],\n  [\"o9\", [\"ơ\", 417]],\n  [\"OI\", [\"Ƣ\", 418]],\n  [\"oi\", [\"ƣ\", 419]],\n  [\"yr\", [\"Ʀ\", 422]],\n  [\"U9\", [\"Ư\", 431]],\n  [\"u9\", [\"ư\", 432]],\n  [\"Z/\", [\"Ƶ\", 437]],\n  [\"z/\", [\"ƶ\", 438]],\n  [\"ED\", [\"Ʒ\", 439]],\n  [\"A<\", [\"Ǎ\", 461]],\n  [\"a<\", [\"ǎ\", 462]],\n  [\"I<\", [\"Ǐ\", 463]],\n  [\"i<\", [\"ǐ\", 464]],\n  [\"O<\", [\"Ǒ\", 465]],\n  [\"o<\", [\"ǒ\", 466]],\n  [\"U<\", [\"Ǔ\", 467]],\n  [\"u<\", [\"ǔ\", 468]],\n  [\"A1\", [\"Ǟ\", 478]],\n  [\"a1\", [\"ǟ\", 479]],\n  [\"A7\", [\"Ǡ\", 480]],\n  [\"a7\", [\"ǡ\", 481]],\n  [\"A3\", [\"Ǣ\", 482]],\n  [\"a3\", [\"ǣ\", 483]],\n  [\"G/\", [\"Ǥ\", 484]],\n  [\"g/\", [\"ǥ\", 485]],\n  [\"G<\", [\"Ǧ\", 486]],\n  [\"g<\", [\"ǧ\", 487]],\n  [\"K<\", [\"Ǩ\", 488]],\n  [\"k<\", [\"ǩ\", 489]],\n  [\"O;\", [\"Ǫ\", 490]],\n  [\"o;\", [\"ǫ\", 491]],\n  [\"O1\", [\"Ǭ\", 492]],\n  [\"o1\", [\"ǭ\", 493]],\n  [\"EZ\", [\"Ǯ\", 494]],\n  [\"ez\", [\"ǯ\", 495]],\n  [\"j<\", [\"ǰ\", 496]],\n  [\"G'\", [\"Ǵ\", 500]],\n  [\"g'\", [\"ǵ\", 501]],\n  [\";S\", [\"ʿ\", 703]],\n  [\"'<\", [\"ˇ\", 711]],\n  [\"'(\", [\"˘\", 728]],\n  [\"'.\", [\"˙\", 729]],\n  [\"'0\", [\"˚\", 730]],\n  [\"';\", [\"˛\", 731]],\n  [\"'\\\"\", [\"˝\", 733]],\n  [\"A%\", [\"Ά\", 902]],\n  [\"E%\", [\"Έ\", 904]],\n  [\"Y%\", [\"Ή\", 905]],\n  [\"I%\", [\"Ί\", 906]],\n  [\"O%\", [\"Ό\", 908]],\n  [\"U%\", [\"Ύ\", 910]],\n  [\"W%\", [\"Ώ\", 911]],\n  [\"i3\", [\"ΐ\", 912]],\n  [\"A*\", [\"Α\", 913]],\n  [\"B*\", [\"Β\", 914]],\n  [\"G*\", [\"Γ\", 915]],\n  [\"D*\", [\"Δ\", 916]],\n  [\"E*\", [\"Ε\", 917]],\n  [\"Z*\", [\"Ζ\", 918]],\n  [\"Y*\", [\"Η\", 919]],\n  [\"H*\", [\"Θ\", 920]],\n  [\"I*\", [\"Ι\", 921]],\n  [\"K*\", [\"Κ\", 922]],\n  [\"L*\", [\"Λ\", 923]],\n  [\"M*\", [\"Μ\", 924]],\n  [\"N*\", [\"Ν\", 925]],\n  [\"C*\", [\"Ξ\", 926]],\n  [\"O*\", [\"Ο\", 927]],\n  [\"P*\", [\"Π\", 928]],\n  [\"R*\", [\"Ρ\", 929]],\n  [\"S*\", [\"Σ\", 931]],\n  [\"T*\", [\"Τ\", 932]],\n  [\"U*\", [\"Υ\", 933]],\n  [\"F*\", [\"Φ\", 934]],\n  [\"X*\", [\"Χ\", 935]],\n  [\"Q*\", [\"Ψ\", 936]],\n  [\"W*\", [\"Ω\", 937]],\n  [\"J*\", [\"Ϊ\", 938]],\n  [\"V*\", [\"Ϋ\", 939]],\n  [\"a%\", [\"ά\", 940]],\n  [\"e%\", [\"έ\", 941]],\n  [\"y%\", [\"ή\", 942]],\n  [\"i%\", [\"ί\", 943]],\n  [\"u3\", [\"ΰ\", 944]],\n  [\"a*\", [\"α\", 945]],\n  [\"b*\", [\"β\", 946]],\n  [\"g*\", [\"γ\", 947]],\n  [\"d*\", [\"δ\", 948]],\n  [\"e*\", [\"ε\", 949]],\n  [\"z*\", [\"ζ\", 950]],\n  [\"y*\", [\"η\", 951]],\n  [\"h*\", [\"θ\", 952]],\n  [\"i*\", [\"ι\", 953]],\n  [\"k*\", [\"κ\", 954]],\n  [\"l*\", [\"λ\", 955]],\n  [\"m*\", [\"μ\", 956]],\n  [\"n*\", [\"ν\", 957]],\n  [\"c*\", [\"ξ\", 958]],\n  [\"o*\", [\"ο\", 959]],\n  [\"p*\", [\"π\", 960]],\n  [\"r*\", [\"ρ\", 961]],\n  [\"*s\", [\"ς\", 962]],\n  [\"s*\", [\"σ\", 963]],\n  [\"t*\", [\"τ\", 964]],\n  [\"u*\", [\"υ\", 965]],\n  [\"f*\", [\"φ\", 966]],\n  [\"x*\", [\"χ\", 967]],\n  [\"q*\", [\"ψ\", 968]],\n  [\"w*\", [\"ω\", 969]],\n  [\"j*\", [\"ϊ\", 970]],\n  [\"v*\", [\"ϋ\", 971]],\n  [\"o%\", [\"ό\", 972]],\n  [\"u%\", [\"ύ\", 973]],\n  [\"w%\", [\"ώ\", 974]],\n  [\"'G\", [\"Ϙ\", 984]],\n  [\",G\", [\"ϙ\", 985]],\n  [\"T3\", [\"Ϛ\", 986]],\n  [\"t3\", [\"ϛ\", 987]],\n  [\"M3\", [\"Ϝ\", 988]],\n  [\"m3\", [\"ϝ\", 989]],\n  [\"K3\", [\"Ϟ\", 990]],\n  [\"k3\", [\"ϟ\", 991]],\n  [\"P3\", [\"Ϡ\", 992]],\n  [\"p3\", [\"ϡ\", 993]],\n  [\"'%\", [\"ϴ\", 1012]],\n  [\"j3\", [\"ϵ\", 1013]],\n  [\"IO\", [\"Ё\", 1025]],\n  [\"D%\", [\"Ђ\", 1026]],\n  [\"G%\", [\"Ѓ\", 1027]],\n  [\"IE\", [\"Є\", 1028]],\n  [\"DS\", [\"Ѕ\", 1029]],\n  [\"II\", [\"І\", 1030]],\n  [\"YI\", [\"Ї\", 1031]],\n  [\"J%\", [\"Ј\", 1032]],\n  [\"LJ\", [\"Љ\", 1033]],\n  [\"NJ\", [\"Њ\", 1034]],\n  [\"Ts\", [\"Ћ\", 1035]],\n  [\"KJ\", [\"Ќ\", 1036]],\n  [\"V%\", [\"Ў\", 1038]],\n  [\"DZ\", [\"Џ\", 1039]],\n  [\"A=\", [\"А\", 1040]],\n  [\"B=\", [\"Б\", 1041]],\n  [\"V=\", [\"В\", 1042]],\n  [\"G=\", [\"Г\", 1043]],\n  [\"D=\", [\"Д\", 1044]],\n  [\"E=\", [\"Е\", 1045]],\n  [\"Z%\", [\"Ж\", 1046]],\n  [\"Z=\", [\"З\", 1047]],\n  [\"I=\", [\"И\", 1048]],\n  [\"J=\", [\"Й\", 1049]],\n  [\"K=\", [\"К\", 1050]],\n  [\"L=\", [\"Л\", 1051]],\n  [\"M=\", [\"М\", 1052]],\n  [\"N=\", [\"Н\", 1053]],\n  [\"O=\", [\"О\", 1054]],\n  [\"P=\", [\"П\", 1055]],\n  [\"R=\", [\"Р\", 1056]],\n  [\"S=\", [\"С\", 1057]],\n  [\"T=\", [\"Т\", 1058]],\n  [\"U=\", [\"У\", 1059]],\n  [\"F=\", [\"Ф\", 1060]],\n  [\"H=\", [\"Х\", 1061]],\n  [\"C=\", [\"Ц\", 1062]],\n  [\"C%\", [\"Ч\", 1063]],\n  [\"S%\", [\"Ш\", 1064]],\n  [\"Sc\", [\"Щ\", 1065]],\n  [\"=\\\"\", [\"Ъ\", 1066]],\n  [\"Y=\", [\"Ы\", 1067]],\n  [\"%\\\"\", [\"Ь\", 1068]],\n  [\"JE\", [\"Э\", 1069]],\n  [\"JU\", [\"Ю\", 1070]],\n  [\"JA\", [\"Я\", 1071]],\n  [\"a=\", [\"а\", 1072]],\n  [\"b=\", [\"б\", 1073]],\n  [\"v=\", [\"в\", 1074]],\n  [\"g=\", [\"г\", 1075]],\n  [\"d=\", [\"д\", 1076]],\n  [\"e=\", [\"е\", 1077]],\n  [\"z%\", [\"ж\", 1078]],\n  [\"z=\", [\"з\", 1079]],\n  [\"i=\", [\"и\", 1080]],\n  [\"j=\", [\"й\", 1081]],\n  [\"k=\", [\"к\", 1082]],\n  [\"l=\", [\"л\", 1083]],\n  [\"m=\", [\"м\", 1084]],\n  [\"n=\", [\"н\", 1085]],\n  [\"o=\", [\"о\", 1086]],\n  [\"p=\", [\"п\", 1087]],\n  [\"r=\", [\"р\", 1088]],\n  [\"s=\", [\"с\", 1089]],\n  [\"t=\", [\"т\", 1090]],\n  [\"u=\", [\"у\", 1091]],\n  [\"f=\", [\"ф\", 1092]],\n  [\"h=\", [\"х\", 1093]],\n  [\"c=\", [\"ц\", 1094]],\n  [\"c%\", [\"ч\", 1095]],\n  [\"s%\", [\"ш\", 1096]],\n  [\"sc\", [\"щ\", 1097]],\n  [\"='\", [\"ъ\", 1098]],\n  [\"y=\", [\"ы\", 1099]],\n  [\"%'\", [\"ь\", 1100]],\n  [\"je\", [\"э\", 1101]],\n  [\"ju\", [\"ю\", 1102]],\n  [\"ja\", [\"я\", 1103]],\n  [\"io\", [\"ё\", 1105]],\n  [\"d%\", [\"ђ\", 1106]],\n  [\"g%\", [\"ѓ\", 1107]],\n  [\"ie\", [\"є\", 1108]],\n  [\"ds\", [\"ѕ\", 1109]],\n  [\"ii\", [\"і\", 1110]],\n  [\"yi\", [\"ї\", 1111]],\n  [\"j%\", [\"ј\", 1112]],\n  [\"lj\", [\"љ\", 1113]],\n  [\"nj\", [\"њ\", 1114]],\n  [\"ts\", [\"ћ\", 1115]],\n  [\"kj\", [\"ќ\", 1116]],\n  [\"v%\", [\"ў\", 1118]],\n  [\"dz\", [\"џ\", 1119]],\n  [\"Y3\", [\"Ѣ\", 1122]],\n  [\"y3\", [\"ѣ\", 1123]],\n  [\"O3\", [\"Ѫ\", 1130]],\n  [\"o3\", [\"ѫ\", 1131]],\n  [\"F3\", [\"Ѳ\", 1138]],\n  [\"f3\", [\"ѳ\", 1139]],\n  [\"V3\", [\"Ѵ\", 1140]],\n  [\"v3\", [\"ѵ\", 1141]],\n  [\"C3\", [\"Ҁ\", 1152]],\n  [\"c3\", [\"ҁ\", 1153]],\n  [\"G3\", [\"Ґ\", 1168]],\n  [\"g3\", [\"ґ\", 1169]],\n  [\"A+\", [\"א\", 1488]],\n  [\"B+\", [\"ב\", 1489]],\n  [\"G+\", [\"ג\", 1490]],\n  [\"D+\", [\"ד\", 1491]],\n  [\"H+\", [\"ה\", 1492]],\n  [\"W+\", [\"ו\", 1493]],\n  [\"Z+\", [\"ז\", 1494]],\n  [\"X+\", [\"ח\", 1495]],\n  [\"Tj\", [\"ט\", 1496]],\n  [\"J+\", [\"י\", 1497]],\n  [\"K%\", [\"ך\", 1498]],\n  [\"K+\", [\"כ\", 1499]],\n  [\"L+\", [\"ל\", 1500]],\n  [\"M%\", [\"ם\", 1501]],\n  [\"M+\", [\"מ\", 1502]],\n  [\"N%\", [\"ן\", 1503]],\n  [\"N+\", [\"נ\", 1504]],\n  [\"S+\", [\"ס\", 1505]],\n  [\"E+\", [\"ע\", 1506]],\n  [\"P%\", [\"ף\", 1507]],\n  [\"P+\", [\"פ\", 1508]],\n  [\"Zj\", [\"ץ\", 1509]],\n  [\"ZJ\", [\"צ\", 1510]],\n  [\"Q+\", [\"ק\", 1511]],\n  [\"R+\", [\"ר\", 1512]],\n  [\"Sh\", [\"ש\", 1513]],\n  [\"T+\", [\"ת\", 1514]],\n  [\",+\", [\"،\", 1548]],\n  [\";+\", [\"؛\", 1563]],\n  [\"?+\", [\"؟\", 1567]],\n  [\"H'\", [\"ء\", 1569]],\n  [\"aM\", [\"آ\", 1570]],\n  [\"aH\", [\"أ\", 1571]],\n  [\"wH\", [\"ؤ\", 1572]],\n  [\"ah\", [\"إ\", 1573]],\n  [\"yH\", [\"ئ\", 1574]],\n  [\"a+\", [\"ا\", 1575]],\n  [\"b+\", [\"ب\", 1576]],\n  [\"tm\", [\"ة\", 1577]],\n  [\"t+\", [\"ت\", 1578]],\n  [\"tk\", [\"ث\", 1579]],\n  [\"g+\", [\"ج\", 1580]],\n  [\"hk\", [\"ح\", 1581]],\n  [\"x+\", [\"خ\", 1582]],\n  [\"d+\", [\"د\", 1583]],\n  [\"dk\", [\"ذ\", 1584]],\n  [\"r+\", [\"ر\", 1585]],\n  [\"z+\", [\"ز\", 1586]],\n  [\"s+\", [\"س\", 1587]],\n  [\"sn\", [\"ش\", 1588]],\n  [\"c+\", [\"ص\", 1589]],\n  [\"dd\", [\"ض\", 1590]],\n  [\"tj\", [\"ط\", 1591]],\n  [\"zH\", [\"ظ\", 1592]],\n  [\"e+\", [\"ع\", 1593]],\n  [\"i+\", [\"غ\", 1594]],\n  [\"++\", [\"ـ\", 1600]],\n  [\"f+\", [\"ف\", 1601]],\n  [\"q+\", [\"ق\", 1602]],\n  [\"k+\", [\"ك\", 1603]],\n  [\"l+\", [\"ل\", 1604]],\n  [\"m+\", [\"م\", 1605]],\n  [\"n+\", [\"ن\", 1606]],\n  [\"h+\", [\"ه\", 1607]],\n  [\"w+\", [\"و\", 1608]],\n  [\"j+\", [\"ى\", 1609]],\n  [\"y+\", [\"ي\", 1610]],\n  [\":+\", [\"ً\", 1611]],\n  [\"\\\"+\", [\"ٌ\", 1612]],\n  [\"=+\", [\"ٍ\", 1613]],\n  [\"/+\", [\"َ\", 1614]],\n  [\"'+\", [\"ُ\", 1615]],\n  [\"1+\", [\"ِ\", 1616]],\n  [\"3+\", [\"ّ\", 1617]],\n  [\"0+\", [\"ْ\", 1618]],\n  [\"aS\", [\"ٰ\", 1648]],\n  [\"p+\", [\"پ\", 1662]],\n  [\"v+\", [\"ڤ\", 1700]],\n  [\"gf\", [\"گ\", 1711]],\n  [\"0a\", [\"۰\", 1776]],\n  [\"1a\", [\"۱\", 1777]],\n  [\"2a\", [\"۲\", 1778]],\n  [\"3a\", [\"۳\", 1779]],\n  [\"4a\", [\"۴\", 1780]],\n  [\"5a\", [\"۵\", 1781]],\n  [\"6a\", [\"۶\", 1782]],\n  [\"7a\", [\"۷\", 1783]],\n  [\"8a\", [\"۸\", 1784]],\n  [\"9a\", [\"۹\", 1785]],\n  [\"B.\", [\"Ḃ\", 7682]],\n  [\"b.\", [\"ḃ\", 7683]],\n  [\"B_\", [\"Ḇ\", 7686]],\n  [\"b_\", [\"ḇ\", 7687]],\n  [\"D.\", [\"Ḋ\", 7690]],\n  [\"d.\", [\"ḋ\", 7691]],\n  [\"D_\", [\"Ḏ\", 7694]],\n  [\"d_\", [\"ḏ\", 7695]],\n  [\"D,\", [\"Ḑ\", 7696]],\n  [\"d,\", [\"ḑ\", 7697]],\n  [\"F.\", [\"Ḟ\", 7710]],\n  [\"f.\", [\"ḟ\", 7711]],\n  [\"G-\", [\"Ḡ\", 7712]],\n  [\"g-\", [\"ḡ\", 7713]],\n  [\"H.\", [\"Ḣ\", 7714]],\n  [\"h.\", [\"ḣ\", 7715]],\n  [\"H:\", [\"Ḧ\", 7718]],\n  [\"h:\", [\"ḧ\", 7719]],\n  [\"H,\", [\"Ḩ\", 7720]],\n  [\"h,\", [\"ḩ\", 7721]],\n  [\"K'\", [\"Ḱ\", 7728]],\n  [\"k'\", [\"ḱ\", 7729]],\n  [\"K_\", [\"Ḵ\", 7732]],\n  [\"k_\", [\"ḵ\", 7733]],\n  [\"L_\", [\"Ḻ\", 7738]],\n  [\"l_\", [\"ḻ\", 7739]],\n  [\"M'\", [\"Ḿ\", 7742]],\n  [\"m'\", [\"ḿ\", 7743]],\n  [\"M.\", [\"Ṁ\", 7744]],\n  [\"m.\", [\"ṁ\", 7745]],\n  [\"N.\", [\"Ṅ\", 7748]],\n  [\"n.\", [\"ṅ\", 7749]],\n  [\"N_\", [\"Ṉ\", 7752]],\n  [\"n_\", [\"ṉ\", 7753]],\n  [\"P'\", [\"Ṕ\", 7764]],\n  [\"p'\", [\"ṕ\", 7765]],\n  [\"P.\", [\"Ṗ\", 7766]],\n  [\"p.\", [\"ṗ\", 7767]],\n  [\"R.\", [\"Ṙ\", 7768]],\n  [\"r.\", [\"ṙ\", 7769]],\n  [\"R_\", [\"Ṟ\", 7774]],\n  [\"r_\", [\"ṟ\", 7775]],\n  [\"S.\", [\"Ṡ\", 7776]],\n  [\"s.\", [\"ṡ\", 7777]],\n  [\"T.\", [\"Ṫ\", 7786]],\n  [\"t.\", [\"ṫ\", 7787]],\n  [\"T_\", [\"Ṯ\", 7790]],\n  [\"t_\", [\"ṯ\", 7791]],\n  [\"V?\", [\"Ṽ\", 7804]],\n  [\"v?\", [\"ṽ\", 7805]],\n  [\"W!\", [\"Ẁ\", 7808]],\n  [\"W`\", [\"Ẁ\", 7808]],\n  [\"w!\", [\"ẁ\", 7809]],\n  [\"w`\", [\"ẁ\", 7809]],\n  [\"W'\", [\"Ẃ\", 7810]],\n  [\"w'\", [\"ẃ\", 7811]],\n  [\"W:\", [\"Ẅ\", 7812]],\n  [\"w:\", [\"ẅ\", 7813]],\n  [\"W.\", [\"Ẇ\", 7814]],\n  [\"w.\", [\"ẇ\", 7815]],\n  [\"X.\", [\"Ẋ\", 7818]],\n  [\"x.\", [\"ẋ\", 7819]],\n  [\"X:\", [\"Ẍ\", 7820]],\n  [\"x:\", [\"ẍ\", 7821]],\n  [\"Y.\", [\"Ẏ\", 7822]],\n  [\"y.\", [\"ẏ\", 7823]],\n  [\"Z>\", [\"Ẑ\", 7824]],\n  [\"z>\", [\"ẑ\", 7825]],\n  [\"Z_\", [\"Ẕ\", 7828]],\n  [\"z_\", [\"ẕ\", 7829]],\n  [\"h_\", [\"ẖ\", 7830]],\n  [\"t:\", [\"ẗ\", 7831]],\n  [\"w0\", [\"ẘ\", 7832]],\n  [\"y0\", [\"ẙ\", 7833]],\n  [\"A2\", [\"Ả\", 7842]],\n  [\"a2\", [\"ả\", 7843]],\n  [\"E2\", [\"Ẻ\", 7866]],\n  [\"e2\", [\"ẻ\", 7867]],\n  [\"E?\", [\"Ẽ\", 7868]],\n  [\"e?\", [\"ẽ\", 7869]],\n  [\"I2\", [\"Ỉ\", 7880]],\n  [\"i2\", [\"ỉ\", 7881]],\n  [\"O2\", [\"Ỏ\", 7886]],\n  [\"o2\", [\"ỏ\", 7887]],\n  [\"U2\", [\"Ủ\", 7910]],\n  [\"u2\", [\"ủ\", 7911]],\n  [\"Y!\", [\"Ỳ\", 7922]],\n  [\"Y`\", [\"Ỳ\", 7922]],\n  [\"y!\", [\"ỳ\", 7923]],\n  [\"y`\", [\"ỳ\", 7923]],\n  [\"Y2\", [\"Ỷ\", 7926]],\n  [\"y2\", [\"ỷ\", 7927]],\n  [\"Y?\", [\"Ỹ\", 7928]],\n  [\"y?\", [\"ỹ\", 7929]],\n  [\";'\", [\"ἀ\", 7936]],\n  [\",'\", [\"ἁ\", 7937]],\n  [\";!\", [\"ἂ\", 7938]],\n  [\",!\", [\"ἃ\", 7939]],\n  [\"?;\", [\"ἄ\", 7940]],\n  [\"?,\", [\"ἅ\", 7941]],\n  [\"!:\", [\"ἆ\", 7942]],\n  [\"?:\", [\"ἇ\", 7943]],\n  [\"1N\", [\" \", 8194]],\n  [\"1M\", [\" \", 8195]],\n  [\"3M\", [\" \", 8196]],\n  [\"4M\", [\" \", 8197]],\n  [\"6M\", [\" \", 8198]],\n  [\"1T\", [\" \", 8201]],\n  [\"1H\", [\" \", 8202]],\n  [\"-1\", [\"‐\", 8208]],\n  [\"-N\", [\"–\", 8211]],\n  [\"-M\", [\"—\", 8212]],\n  [\"-3\", [\"―\", 8213]],\n  [\"!2\", [\"‖\", 8214]],\n  [\"=2\", [\"‗\", 8215]],\n  [\"'6\", [\"‘\", 8216]],\n  [\"'9\", [\"’\", 8217]],\n  [\".9\", [\"‚\", 8218]],\n  [\"9'\", [\"‛\", 8219]],\n  [\"\\\"6\", [\"“\", 8220]],\n  [\"\\\"9\", [\"”\", 8221]],\n  [\":9\", [\"„\", 8222]],\n  [\"9\\\"\", [\"‟\", 8223]],\n  [\"/-\", [\"†\", 8224]],\n  [\"/=\", [\"‡\", 8225]],\n  [\"oo\", [\"•\", 8226]],\n  [\"..\", [\"‥\", 8229]],\n  [\",.\", [\"…\", 8230]],\n  [\"%0\", [\"‰\", 8240]],\n  [\"1'\", [\"′\", 8242]],\n  [\"2'\", [\"″\", 8243]],\n  [\"3'\", [\"‴\", 8244]],\n  [\"4'\", [\"⁗\", 8279]],\n  [\"1\\\"\", [\"‵\", 8245]],\n  [\"2\\\"\", [\"‶\", 8246]],\n  [\"3\\\"\", [\"‷\", 8247]],\n  [\"Ca\", [\"‸\", 8248]],\n  [\"<1\", [\"‹\", 8249]],\n  [\">1\", [\"›\", 8250]],\n  [\":X\", [\"※\", 8251]],\n  [\"'-\", [\"‾\", 8254]],\n  [\"/f\", [\"⁄\", 8260]],\n  [\"0S\", [\"⁰\", 8304]],\n  [\"4S\", [\"⁴\", 8308]],\n  [\"5S\", [\"⁵\", 8309]],\n  [\"6S\", [\"⁶\", 8310]],\n  [\"7S\", [\"⁷\", 8311]],\n  [\"8S\", [\"⁸\", 8312]],\n  [\"9S\", [\"⁹\", 8313]],\n  [\"+S\", [\"⁺\", 8314]],\n  [\"-S\", [\"⁻\", 8315]],\n  [\"=S\", [\"⁼\", 8316]],\n  [\"(S\", [\"⁽\", 8317]],\n  [\")S\", [\"⁾\", 8318]],\n  [\"nS\", [\"ⁿ\", 8319]],\n  [\"0s\", [\"₀\", 8320]],\n  [\"1s\", [\"₁\", 8321]],\n  [\"2s\", [\"₂\", 8322]],\n  [\"3s\", [\"₃\", 8323]],\n  [\"4s\", [\"₄\", 8324]],\n  [\"5s\", [\"₅\", 8325]],\n  [\"6s\", [\"₆\", 8326]],\n  [\"7s\", [\"₇\", 8327]],\n  [\"8s\", [\"₈\", 8328]],\n  [\"9s\", [\"₉\", 8329]],\n  [\"+s\", [\"₊\", 8330]],\n  [\"-s\", [\"₋\", 8331]],\n  [\"=s\", [\"₌\", 8332]],\n  [\"(s\", [\"₍\", 8333]],\n  [\")s\", [\"₎\", 8334]],\n  [\"Li\", [\"₤\", 8356]],\n  [\"Pt\", [\"₧\", 8359]],\n  [\"W=\", [\"₩\", 8361]],\n  [\"=e\", [\"€\", 8364]],\n  [\"Eu\", [\"€\", 8364]],\n  [\"=R\", [\"₽\", 8381]],\n  [\"=P\", [\"₽\", 8381]],\n  [\"oC\", [\"℃\", 8451]],\n  [\"co\", [\"℅\", 8453]],\n  [\"oF\", [\"℉\", 8457]],\n  [\"N0\", [\"№\", 8470]],\n  [\"PO\", [\"℗\", 8471]],\n  [\"Rx\", [\"℞\", 8478]],\n  [\"SM\", [\"℠\", 8480]],\n  [\"TM\", [\"™\", 8482]],\n  [\"Om\", [\"Ω\", 8486]],\n  [\"AO\", [\"Å\", 8491]],\n  [\"13\", [\"⅓\", 8531]],\n  [\"23\", [\"⅔\", 8532]],\n  [\"15\", [\"⅕\", 8533]],\n  [\"25\", [\"⅖\", 8534]],\n  [\"35\", [\"⅗\", 8535]],\n  [\"45\", [\"⅘\", 8536]],\n  [\"16\", [\"⅙\", 8537]],\n  [\"56\", [\"⅚\", 8538]],\n  [\"18\", [\"⅛\", 8539]],\n  [\"38\", [\"⅜\", 8540]],\n  [\"58\", [\"⅝\", 8541]],\n  [\"78\", [\"⅞\", 8542]],\n  [\"1R\", [\"Ⅰ\", 8544]],\n  [\"2R\", [\"Ⅱ\", 8545]],\n  [\"3R\", [\"Ⅲ\", 8546]],\n  [\"4R\", [\"Ⅳ\", 8547]],\n  [\"5R\", [\"Ⅴ\", 8548]],\n  [\"6R\", [\"Ⅵ\", 8549]],\n  [\"7R\", [\"Ⅶ\", 8550]],\n  [\"8R\", [\"Ⅷ\", 8551]],\n  [\"9R\", [\"Ⅸ\", 8552]],\n  [\"aR\", [\"Ⅹ\", 8553]],\n  [\"bR\", [\"Ⅺ\", 8554]],\n  [\"cR\", [\"Ⅻ\", 8555]],\n  [\"1r\", [\"ⅰ\", 8560]],\n  [\"2r\", [\"ⅱ\", 8561]],\n  [\"3r\", [\"ⅲ\", 8562]],\n  [\"4r\", [\"ⅳ\", 8563]],\n  [\"5r\", [\"ⅴ\", 8564]],\n  [\"6r\", [\"ⅵ\", 8565]],\n  [\"7r\", [\"ⅶ\", 8566]],\n  [\"8r\", [\"ⅷ\", 8567]],\n  [\"9r\", [\"ⅸ\", 8568]],\n  [\"ar\", [\"ⅹ\", 8569]],\n  [\"br\", [\"ⅺ\", 8570]],\n  [\"cr\", [\"ⅻ\", 8571]],\n  [\"<-\", [\"←\", 8592]],\n  [\"-!\", [\"↑\", 8593]],\n  [\"->\", [\"→\", 8594]],\n  [\"-v\", [\"↓\", 8595]],\n  [\"<>\", [\"↔\", 8596]],\n  [\"UD\", [\"↕\", 8597]],\n  [\"<=\", [\"⇐\", 8656]],\n  [\"=>\", [\"⇒\", 8658]],\n  [\"==\", [\"⇔\", 8660]],\n  [\"FA\", [\"∀\", 8704]],\n  [\"dP\", [\"∂\", 8706]],\n  [\"TE\", [\"∃\", 8707]],\n  [\"/0\", [\"∅\", 8709]],\n  [\"DE\", [\"∆\", 8710]],\n  [\"NB\", [\"∇\", 8711]],\n  [\"(-\", [\"∈\", 8712]],\n  [\"-)\", [\"∋\", 8715]],\n  [\"*P\", [\"∏\", 8719]],\n  [\"+Z\", [\"∑\", 8721]],\n  [\"-2\", [\"−\", 8722]],\n  [\"-+\", [\"∓\", 8723]],\n  [\"*-\", [\"∗\", 8727]],\n  [\"Ob\", [\"∘\", 8728]],\n  [\"Sb\", [\"∙\", 8729]],\n  [\"RT\", [\"√\", 8730]],\n  [\"0(\", [\"∝\", 8733]],\n  [\"00\", [\"∞\", 8734]],\n  [\"-L\", [\"∟\", 8735]],\n  [\"-V\", [\"∠\", 8736]],\n  [\"PP\", [\"∥\", 8741]],\n  [\"AN\", [\"∧\", 8743]],\n  [\"OR\", [\"∨\", 8744]],\n  [\"(U\", [\"∩\", 8745]],\n  [\")U\", [\"∪\", 8746]],\n  [\"In\", [\"∫\", 8747]],\n  [\"DI\", [\"∬\", 8748]],\n  [\"Io\", [\"∮\", 8750]],\n  [\".:\", [\"∴\", 8756]],\n  [\":.\", [\"∵\", 8757]],\n  [\":R\", [\"∶\", 8758]],\n  [\"::\", [\"∷\", 8759]],\n  [\"?1\", [\"∼\", 8764]],\n  [\"CG\", [\"∾\", 8766]],\n  [\"?-\", [\"≃\", 8771]],\n  [\"?=\", [\"≅\", 8773]],\n  [\"?2\", [\"≈\", 8776]],\n  [\"=?\", [\"≌\", 8780]],\n  [\"HI\", [\"≓\", 8787]],\n  [\"!=\", [\"≠\", 8800]],\n  [\"=3\", [\"≡\", 8801]],\n  [\"=<\", [\"≤\", 8804]],\n  [\">=\", [\"≥\", 8805]],\n  [\"<*\", [\"≪\", 8810]],\n  [\"*>\", [\"≫\", 8811]],\n  [\"!<\", [\"≮\", 8814]],\n  [\"!>\", [\"≯\", 8815]],\n  [\"(C\", [\"⊂\", 8834]],\n  [\")C\", [\"⊃\", 8835]],\n  [\"(_\", [\"⊆\", 8838]],\n  [\")_\", [\"⊇\", 8839]],\n  [\"0.\", [\"⊙\", 8857]],\n  [\"02\", [\"⊚\", 8858]],\n  [\"-T\", [\"⊥\", 8869]],\n  [\".P\", [\"⋅\", 8901]],\n  [\":3\", [\"⋮\", 8942]],\n  [\".3\", [\"⋯\", 8943]],\n  [\"Eh\", [\"⌂\", 8962]],\n  [\"<7\", [\"⌈\", 8968]],\n  [\">7\", [\"⌉\", 8969]],\n  [\"7<\", [\"⌊\", 8970]],\n  [\"7>\", [\"⌋\", 8971]],\n  [\"NI\", [\"⌐\", 8976]],\n  [\"(A\", [\"⌒\", 8978]],\n  [\"TR\", [\"⌕\", 8981]],\n  [\"Iu\", [\"⌠\", 8992]],\n  [\"Il\", [\"⌡\", 8993]],\n  [\"</\", [\"〈\", 9001]],\n  [\"/>\", [\"〉\", 9002]],\n  [\"Vs\", [\"␣\", 9251]],\n  [\"1h\", [\"⑀\", 9280]],\n  [\"3h\", [\"⑁\", 9281]],\n  [\"2h\", [\"⑂\", 9282]],\n  [\"4h\", [\"⑃\", 9283]],\n  [\"1j\", [\"⑆\", 9286]],\n  [\"2j\", [\"⑇\", 9287]],\n  [\"3j\", [\"⑈\", 9288]],\n  [\"4j\", [\"⑉\", 9289]],\n  [\"1.\", [\"⒈\", 9352]],\n  [\"2.\", [\"⒉\", 9353]],\n  [\"3.\", [\"⒊\", 9354]],\n  [\"4.\", [\"⒋\", 9355]],\n  [\"5.\", [\"⒌\", 9356]],\n  [\"6.\", [\"⒍\", 9357]],\n  [\"7.\", [\"⒎\", 9358]],\n  [\"8.\", [\"⒏\", 9359]],\n  [\"9.\", [\"⒐\", 9360]],\n  [\"hh\", [\"─\", 9472]],\n  [\"HH\", [\"━\", 9473]],\n  [\"vv\", [\"│\", 9474]],\n  [\"VV\", [\"┃\", 9475]],\n  [\"3-\", [\"┄\", 9476]],\n  [\"3_\", [\"┅\", 9477]],\n  [\"3!\", [\"┆\", 9478]],\n  [\"3/\", [\"┇\", 9479]],\n  [\"4-\", [\"┈\", 9480]],\n  [\"4_\", [\"┉\", 9481]],\n  [\"4!\", [\"┊\", 9482]],\n  [\"4/\", [\"┋\", 9483]],\n  [\"dr\", [\"┌\", 9484]],\n  [\"dR\", [\"┍\", 9485]],\n  [\"Dr\", [\"┎\", 9486]],\n  [\"DR\", [\"┏\", 9487]],\n  [\"dl\", [\"┐\", 9488]],\n  [\"dL\", [\"┑\", 9489]],\n  [\"Dl\", [\"┒\", 9490]],\n  [\"LD\", [\"┓\", 9491]],\n  [\"ur\", [\"└\", 9492]],\n  [\"uR\", [\"┕\", 9493]],\n  [\"Ur\", [\"┖\", 9494]],\n  [\"UR\", [\"┗\", 9495]],\n  [\"ul\", [\"┘\", 9496]],\n  [\"uL\", [\"┙\", 9497]],\n  [\"Ul\", [\"┚\", 9498]],\n  [\"UL\", [\"┛\", 9499]],\n  [\"vr\", [\"├\", 9500]],\n  [\"vR\", [\"┝\", 9501]],\n  [\"Vr\", [\"┠\", 9504]],\n  [\"VR\", [\"┣\", 9507]],\n  [\"vl\", [\"┤\", 9508]],\n  [\"vL\", [\"┥\", 9509]],\n  [\"Vl\", [\"┨\", 9512]],\n  [\"VL\", [\"┫\", 9515]],\n  [\"dh\", [\"┬\", 9516]],\n  [\"dH\", [\"┯\", 9519]],\n  [\"Dh\", [\"┰\", 9520]],\n  [\"DH\", [\"┳\", 9523]],\n  [\"uh\", [\"┴\", 9524]],\n  [\"uH\", [\"┷\", 9527]],\n  [\"Uh\", [\"┸\", 9528]],\n  [\"UH\", [\"┻\", 9531]],\n  [\"vh\", [\"┼\", 9532]],\n  [\"vH\", [\"┿\", 9535]],\n  [\"Vh\", [\"╂\", 9538]],\n  [\"VH\", [\"╋\", 9547]],\n  [\"FD\", [\"╱\", 9585]],\n  [\"BD\", [\"╲\", 9586]],\n  [\"TB\", [\"▀\", 9600]],\n  [\"LB\", [\"▄\", 9604]],\n  [\"FB\", [\"█\", 9608]],\n  [\"lB\", [\"▌\", 9612]],\n  [\"RB\", [\"▐\", 9616]],\n  [\".S\", [\"░\", 9617]],\n  [\":S\", [\"▒\", 9618]],\n  [\"?S\", [\"▓\", 9619]],\n  [\"fS\", [\"■\", 9632]],\n  [\"OS\", [\"□\", 9633]],\n  [\"RO\", [\"▢\", 9634]],\n  [\"Rr\", [\"▣\", 9635]],\n  [\"RF\", [\"▤\", 9636]],\n  [\"RY\", [\"▥\", 9637]],\n  [\"RH\", [\"▦\", 9638]],\n  [\"RZ\", [\"▧\", 9639]],\n  [\"RK\", [\"▨\", 9640]],\n  [\"RX\", [\"▩\", 9641]],\n  [\"sB\", [\"▪\", 9642]],\n  [\"SR\", [\"▬\", 9644]],\n  [\"Or\", [\"▭\", 9645]],\n  [\"UT\", [\"▲\", 9650]],\n  [\"uT\", [\"△\", 9651]],\n  [\"PR\", [\"▶\", 9654]],\n  [\"Tr\", [\"▷\", 9655]],\n  [\"Dt\", [\"▼\", 9660]],\n  [\"dT\", [\"▽\", 9661]],\n  [\"PL\", [\"◀\", 9664]],\n  [\"Tl\", [\"◁\", 9665]],\n  [\"Db\", [\"◆\", 9670]],\n  [\"Dw\", [\"◇\", 9671]],\n  [\"LZ\", [\"◊\", 9674]],\n  [\"0m\", [\"○\", 9675]],\n  [\"0o\", [\"◎\", 9678]],\n  [\"0M\", [\"●\", 9679]],\n  [\"0L\", [\"◐\", 9680]],\n  [\"0R\", [\"◑\", 9681]],\n  [\"Sn\", [\"◘\", 9688]],\n  [\"Ic\", [\"◙\", 9689]],\n  [\"Fd\", [\"◢\", 9698]],\n  [\"Bd\", [\"◣\", 9699]],\n  [\"*2\", [\"★\", 9733]],\n  [\"*1\", [\"☆\", 9734]],\n  [\"<H\", [\"☜\", 9756]],\n  [\">H\", [\"☞\", 9758]],\n  [\"0u\", [\"☺\", 9786]],\n  [\"0U\", [\"☻\", 9787]],\n  [\"SU\", [\"☼\", 9788]],\n  [\"Fm\", [\"♀\", 9792]],\n  [\"Ml\", [\"♂\", 9794]],\n  [\"cS\", [\"♠\", 9824]],\n  [\"cH\", [\"♡\", 9825]],\n  [\"cD\", [\"♢\", 9826]],\n  [\"cC\", [\"♣\", 9827]],\n  [\"Md\", [\"♩\", 9833]],\n  [\"M8\", [\"♪\", 9834]],\n  [\"M2\", [\"♫\", 9835]],\n  [\"Mb\", [\"♭\", 9837]],\n  [\"Mx\", [\"♮\", 9838]],\n  [\"MX\", [\"♯\", 9839]],\n  [\"OK\", [\"✓\", 10003]],\n  [\"XX\", [\"✗\", 10007]],\n  [\"-X\", [\"✠\", 10016]],\n  [\"IS\", [\"　\", 12288]],\n  [\",_\", [\"、\", 12289]],\n  [\"._\", [\"。\", 12290]],\n  [\"+\\\"\", [\"〃\", 12291]],\n  [\"+_\", [\"〄\", 12292]],\n  [\"*_\", [\"々\", 12293]],\n  [\";_\", [\"〆\", 12294]],\n  [\"0_\", [\"〇\", 12295]],\n  [\"<+\", [\"《\", 12298]],\n  [\">+\", [\"》\", 12299]],\n  [\"<'\", [\"「\", 12300]],\n  [\">'\", [\"」\", 12301]],\n  [\"<\\\"\", [\"『\", 12302]],\n  [\">\\\"\", [\"』\", 12303]],\n  [\"(\\\"\", [\"【\", 12304]],\n  [\")\\\"\", [\"】\", 12305]],\n  [\"=T\", [\"〒\", 12306]],\n  [\"=_\", [\"〓\", 12307]],\n  [\"('\", [\"〔\", 12308]],\n  [\")'\", [\"〕\", 12309]],\n  [\"(I\", [\"〖\", 12310]],\n  [\")I\", [\"〗\", 12311]],\n  [\"-?\", [\"〜\", 12316]],\n  [\"A5\", [\"ぁ\", 12353]],\n  [\"a5\", [\"あ\", 12354]],\n  [\"I5\", [\"ぃ\", 12355]],\n  [\"i5\", [\"い\", 12356]],\n  [\"U5\", [\"ぅ\", 12357]],\n  [\"u5\", [\"う\", 12358]],\n  [\"E5\", [\"ぇ\", 12359]],\n  [\"e5\", [\"え\", 12360]],\n  [\"O5\", [\"ぉ\", 12361]],\n  [\"o5\", [\"お\", 12362]],\n  [\"ka\", [\"か\", 12363]],\n  [\"ga\", [\"が\", 12364]],\n  [\"ki\", [\"き\", 12365]],\n  [\"gi\", [\"ぎ\", 12366]],\n  [\"ku\", [\"く\", 12367]],\n  [\"gu\", [\"ぐ\", 12368]],\n  [\"ke\", [\"け\", 12369]],\n  [\"ge\", [\"げ\", 12370]],\n  [\"ko\", [\"こ\", 12371]],\n  [\"go\", [\"ご\", 12372]],\n  [\"sa\", [\"さ\", 12373]],\n  [\"za\", [\"ざ\", 12374]],\n  [\"si\", [\"し\", 12375]],\n  [\"zi\", [\"じ\", 12376]],\n  [\"su\", [\"す\", 12377]],\n  [\"zu\", [\"ず\", 12378]],\n  [\"se\", [\"せ\", 12379]],\n  [\"ze\", [\"ぜ\", 12380]],\n  [\"so\", [\"そ\", 12381]],\n  [\"zo\", [\"ぞ\", 12382]],\n  [\"ta\", [\"た\", 12383]],\n  [\"da\", [\"だ\", 12384]],\n  [\"ti\", [\"ち\", 12385]],\n  [\"di\", [\"ぢ\", 12386]],\n  [\"tU\", [\"っ\", 12387]],\n  [\"tu\", [\"つ\", 12388]],\n  [\"du\", [\"づ\", 12389]],\n  [\"te\", [\"て\", 12390]],\n  [\"de\", [\"で\", 12391]],\n  [\"to\", [\"と\", 12392]],\n  [\"do\", [\"ど\", 12393]],\n  [\"na\", [\"な\", 12394]],\n  [\"ni\", [\"に\", 12395]],\n  [\"nu\", [\"ぬ\", 12396]],\n  [\"ne\", [\"ね\", 12397]],\n  [\"no\", [\"の\", 12398]],\n  [\"ha\", [\"は\", 12399]],\n  [\"ba\", [\"ば\", 12400]],\n  [\"pa\", [\"ぱ\", 12401]],\n  [\"hi\", [\"ひ\", 12402]],\n  [\"bi\", [\"び\", 12403]],\n  [\"pi\", [\"ぴ\", 12404]],\n  [\"hu\", [\"ふ\", 12405]],\n  [\"bu\", [\"ぶ\", 12406]],\n  [\"pu\", [\"ぷ\", 12407]],\n  [\"he\", [\"へ\", 12408]],\n  [\"be\", [\"べ\", 12409]],\n  [\"pe\", [\"ぺ\", 12410]],\n  [\"ho\", [\"ほ\", 12411]],\n  [\"bo\", [\"ぼ\", 12412]],\n  [\"po\", [\"ぽ\", 12413]],\n  [\"ma\", [\"ま\", 12414]],\n  [\"mi\", [\"み\", 12415]],\n  [\"mu\", [\"む\", 12416]],\n  [\"me\", [\"め\", 12417]],\n  [\"mo\", [\"も\", 12418]],\n  [\"yA\", [\"ゃ\", 12419]],\n  [\"ya\", [\"や\", 12420]],\n  [\"yU\", [\"ゅ\", 12421]],\n  [\"yu\", [\"ゆ\", 12422]],\n  [\"yO\", [\"ょ\", 12423]],\n  [\"yo\", [\"よ\", 12424]],\n  [\"ra\", [\"ら\", 12425]],\n  [\"ri\", [\"り\", 12426]],\n  [\"ru\", [\"る\", 12427]],\n  [\"re\", [\"れ\", 12428]],\n  [\"ro\", [\"ろ\", 12429]],\n  [\"wA\", [\"ゎ\", 12430]],\n  [\"wa\", [\"わ\", 12431]],\n  [\"wi\", [\"ゐ\", 12432]],\n  [\"we\", [\"ゑ\", 12433]],\n  [\"wo\", [\"を\", 12434]],\n  [\"n5\", [\"ん\", 12435]],\n  [\"vu\", [\"ゔ\", 12436]],\n  [\"\\\"5\", [\"゛\", 12443]],\n  [\"05\", [\"゜\", 12444]],\n  [\"*5\", [\"ゝ\", 12445]],\n  [\"+5\", [\"ゞ\", 12446]],\n  [\"a6\", [\"ァ\", 12449]],\n  [\"A6\", [\"ア\", 12450]],\n  [\"i6\", [\"ィ\", 12451]],\n  [\"I6\", [\"イ\", 12452]],\n  [\"u6\", [\"ゥ\", 12453]],\n  [\"U6\", [\"ウ\", 12454]],\n  [\"e6\", [\"ェ\", 12455]],\n  [\"E6\", [\"エ\", 12456]],\n  [\"o6\", [\"ォ\", 12457]],\n  [\"O6\", [\"オ\", 12458]],\n  [\"Ka\", [\"カ\", 12459]],\n  [\"Ga\", [\"ガ\", 12460]],\n  [\"Ki\", [\"キ\", 12461]],\n  [\"Gi\", [\"ギ\", 12462]],\n  [\"Ku\", [\"ク\", 12463]],\n  [\"Gu\", [\"グ\", 12464]],\n  [\"Ke\", [\"ケ\", 12465]],\n  [\"Ge\", [\"ゲ\", 12466]],\n  [\"Ko\", [\"コ\", 12467]],\n  [\"Go\", [\"ゴ\", 12468]],\n  [\"Sa\", [\"サ\", 12469]],\n  [\"Za\", [\"ザ\", 12470]],\n  [\"Si\", [\"シ\", 12471]],\n  [\"Zi\", [\"ジ\", 12472]],\n  [\"Su\", [\"ス\", 12473]],\n  [\"Zu\", [\"ズ\", 12474]],\n  [\"Se\", [\"セ\", 12475]],\n  [\"Ze\", [\"ゼ\", 12476]],\n  [\"So\", [\"ソ\", 12477]],\n  [\"Zo\", [\"ゾ\", 12478]],\n  [\"Ta\", [\"タ\", 12479]],\n  [\"Da\", [\"ダ\", 12480]],\n  [\"Ti\", [\"チ\", 12481]],\n  [\"Di\", [\"ヂ\", 12482]],\n  [\"TU\", [\"ッ\", 12483]],\n  [\"Tu\", [\"ツ\", 12484]],\n  [\"Du\", [\"ヅ\", 12485]],\n  [\"Te\", [\"テ\", 12486]],\n  [\"De\", [\"デ\", 12487]],\n  [\"To\", [\"ト\", 12488]],\n  [\"Do\", [\"ド\", 12489]],\n  [\"Na\", [\"ナ\", 12490]],\n  [\"Ni\", [\"ニ\", 12491]],\n  [\"Nu\", [\"ヌ\", 12492]],\n  [\"Ne\", [\"ネ\", 12493]],\n  [\"No\", [\"ノ\", 12494]],\n  [\"Ha\", [\"ハ\", 12495]],\n  [\"Ba\", [\"バ\", 12496]],\n  [\"Pa\", [\"パ\", 12497]],\n  [\"Hi\", [\"ヒ\", 12498]],\n  [\"Bi\", [\"ビ\", 12499]],\n  [\"Pi\", [\"ピ\", 12500]],\n  [\"Hu\", [\"フ\", 12501]],\n  [\"Bu\", [\"ブ\", 12502]],\n  [\"Pu\", [\"プ\", 12503]],\n  [\"He\", [\"ヘ\", 12504]],\n  [\"Be\", [\"ベ\", 12505]],\n  [\"Pe\", [\"ペ\", 12506]],\n  [\"Ho\", [\"ホ\", 12507]],\n  [\"Bo\", [\"ボ\", 12508]],\n  [\"Po\", [\"ポ\", 12509]],\n  [\"Ma\", [\"マ\", 12510]],\n  [\"Mi\", [\"ミ\", 12511]],\n  [\"Mu\", [\"ム\", 12512]],\n  [\"Me\", [\"メ\", 12513]],\n  [\"Mo\", [\"モ\", 12514]],\n  [\"YA\", [\"ャ\", 12515]],\n  [\"Ya\", [\"ヤ\", 12516]],\n  [\"YU\", [\"ュ\", 12517]],\n  [\"Yu\", [\"ユ\", 12518]],\n  [\"YO\", [\"ョ\", 12519]],\n  [\"Yo\", [\"ヨ\", 12520]],\n  [\"Ra\", [\"ラ\", 12521]],\n  [\"Ri\", [\"リ\", 12522]],\n  [\"Ru\", [\"ル\", 12523]],\n  [\"Re\", [\"レ\", 12524]],\n  [\"Ro\", [\"ロ\", 12525]],\n  [\"WA\", [\"ヮ\", 12526]],\n  [\"Wa\", [\"ワ\", 12527]],\n  [\"Wi\", [\"ヰ\", 12528]],\n  [\"We\", [\"ヱ\", 12529]],\n  [\"Wo\", [\"ヲ\", 12530]],\n  [\"N6\", [\"ン\", 12531]],\n  [\"Vu\", [\"ヴ\", 12532]],\n  [\"KA\", [\"ヵ\", 12533]],\n  [\"KE\", [\"ヶ\", 12534]],\n  [\"Va\", [\"ヷ\", 12535]],\n  [\"Vi\", [\"ヸ\", 12536]],\n  [\"Ve\", [\"ヹ\", 12537]],\n  [\"Vo\", [\"ヺ\", 12538]],\n  [\".6\", [\"・\", 12539]],\n  [\"-6\", [\"ー\", 12540]],\n  [\"*6\", [\"ヽ\", 12541]],\n  [\"+6\", [\"ヾ\", 12542]],\n  [\"b4\", [\"ㄅ\", 12549]],\n  [\"p4\", [\"ㄆ\", 12550]],\n  [\"m4\", [\"ㄇ\", 12551]],\n  [\"f4\", [\"ㄈ\", 12552]],\n  [\"d4\", [\"ㄉ\", 12553]],\n  [\"t4\", [\"ㄊ\", 12554]],\n  [\"n4\", [\"ㄋ\", 12555]],\n  [\"l4\", [\"ㄌ\", 12556]],\n  [\"g4\", [\"ㄍ\", 12557]],\n  [\"k4\", [\"ㄎ\", 12558]],\n  [\"h4\", [\"ㄏ\", 12559]],\n  [\"j4\", [\"ㄐ\", 12560]],\n  [\"q4\", [\"ㄑ\", 12561]],\n  [\"x4\", [\"ㄒ\", 12562]],\n  [\"zh\", [\"ㄓ\", 12563]],\n  [\"ch\", [\"ㄔ\", 12564]],\n  [\"sh\", [\"ㄕ\", 12565]],\n  [\"r4\", [\"ㄖ\", 12566]],\n  [\"z4\", [\"ㄗ\", 12567]],\n  [\"c4\", [\"ㄘ\", 12568]],\n  [\"s4\", [\"ㄙ\", 12569]],\n  [\"a4\", [\"ㄚ\", 12570]],\n  [\"o4\", [\"ㄛ\", 12571]],\n  [\"e4\", [\"ㄜ\", 12572]],\n  [\"ai\", [\"ㄞ\", 12574]],\n  [\"ei\", [\"ㄟ\", 12575]],\n  [\"au\", [\"ㄠ\", 12576]],\n  [\"ou\", [\"ㄡ\", 12577]],\n  [\"an\", [\"ㄢ\", 12578]],\n  [\"en\", [\"ㄣ\", 12579]],\n  [\"aN\", [\"ㄤ\", 12580]],\n  [\"eN\", [\"ㄥ\", 12581]],\n  [\"er\", [\"ㄦ\", 12582]],\n  [\"i4\", [\"ㄧ\", 12583]],\n  [\"u4\", [\"ㄨ\", 12584]],\n  [\"iu\", [\"ㄩ\", 12585]],\n  [\"v4\", [\"ㄪ\", 12586]],\n  [\"nG\", [\"ㄫ\", 12587]],\n  [\"gn\", [\"ㄬ\", 12588]],\n  [\"1c\", [\"㈠\", 12832]],\n  [\"2c\", [\"㈡\", 12833]],\n  [\"3c\", [\"㈢\", 12834]],\n  [\"4c\", [\"㈣\", 12835]],\n  [\"5c\", [\"㈤\", 12836]],\n  [\"6c\", [\"㈥\", 12837]],\n  [\"7c\", [\"㈦\", 12838]],\n  [\"8c\", [\"㈧\", 12839]],\n  [\"9c\", [\"㈨\", 12840]],\n  [\"ff\", [\"ﬀ\", 64256]],\n  [\"fi\", [\"ﬁ\", 64257]],\n  [\"fl\", [\"ﬂ\", 64258]],\n  [\"ft\", [\"ﬅ\", 64261]],\n  [\"st\", [\"ﬆ\", 64262]],\n]);\n"
  },
  {
    "path": "src/actions/commands/documentChange.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { PositionDiff, laterOf } from '../../common/motion/position';\nimport { VimState } from '../../state/vimState';\nimport { Transformation } from '../../transformations/transformations';\nimport { BaseCommand } from '../base';\n\ntype Change = vscode.TextDocumentContentChangeEvent;\n\n/**\n * A very special snowflake.\n *\n * Each keystroke when typing in Insert mode is its own Action, which means naively replaying a\n * realistic insertion (via `.` or a macro) does many small insertions, which is very slow.\n * So instead, we fold all those actions after the fact into a single DocumentContentChangeAction,\n * which compresses the changes, generally into a single document edit per cursor.\n */\nexport class DocumentContentChangeAction extends BaseCommand {\n  modes = [];\n  keys = [];\n  private readonly cursorStart: Position;\n  private cursorEnd: Position;\n\n  constructor(cursorStart: Position) {\n    super();\n    this.cursorStart = cursorStart;\n    this.cursorEnd = cursorStart;\n  }\n\n  private contentChanges: Change[] = [];\n\n  public addChanges(changes: Change[], cursorPosition: Position) {\n    this.contentChanges = [...this.contentChanges, ...changes];\n    this.compressChanges();\n    this.cursorEnd = cursorPosition;\n  }\n\n  public getTransformation(positionDiff: PositionDiff): Transformation {\n    return {\n      type: 'contentChange',\n      changes: this.contentChanges,\n      diff: positionDiff,\n    };\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (this.contentChanges.length === 0) {\n      return;\n    }\n\n    let originalLeftBoundary = this.cursorStart;\n\n    let rightBoundary: Position = position;\n    for (const change of this.contentChanges) {\n      if (change.range.start.line < originalLeftBoundary.line) {\n        // This change should be ignored\n        const linesAffected = change.range.end.line - change.range.start.line + 1;\n        const resultLines = change.text.split('\\n').length;\n        originalLeftBoundary = originalLeftBoundary.with(\n          Math.max(0, originalLeftBoundary.line + resultLines - linesAffected),\n        );\n        continue;\n      }\n\n      // Translates diffPos from a position relative to originalLeftBoundary to one relative to position\n      const translate = (diffPos: Position): Position => {\n        const lineOffset = diffPos.line - originalLeftBoundary.line;\n        const char =\n          lineOffset === 0\n            ? position.character + diffPos.character - originalLeftBoundary.character\n            : diffPos.character;\n        // TODO: Should we document.validate() this position?\n        return new Position(Math.max(position.line + lineOffset, 0), Math.max(char, 0));\n      };\n\n      const replaceRange = new vscode.Range(\n        translate(change.range.start),\n        translate(change.range.end),\n      );\n\n      if (replaceRange.start.isAfter(rightBoundary)) {\n        // This change should be ignored as it's out of boundary\n        continue;\n      }\n\n      // Calculate new right boundary\n      const textDiffLines = change.text.split('\\n');\n      const numLinesAdded = textDiffLines.length - 1;\n      const newRightBoundary =\n        numLinesAdded === 0\n          ? new Position(replaceRange.start.line, replaceRange.start.character + change.text.length)\n          : new Position(replaceRange.start.line + numLinesAdded, textDiffLines.pop()!.length);\n\n      rightBoundary = laterOf(rightBoundary, newRightBoundary);\n\n      if (replaceRange.start.isEqual(replaceRange.end)) {\n        vimState.recordedState.transformer.insert(\n          replaceRange.start,\n          change.text,\n          PositionDiff.exactPosition(translate(this.cursorEnd)),\n        );\n      } else {\n        vimState.recordedState.transformer.replace(\n          replaceRange,\n          change.text,\n          PositionDiff.exactPosition(translate(this.cursorEnd)),\n        );\n      }\n    }\n  }\n\n  private compressChanges(): void {\n    const merge = (first: Change, second: Change): Change | undefined => {\n      if (first.rangeOffset + first.text.length === second.rangeOffset) {\n        // Simple concatenation\n        return {\n          text: first.text + second.text,\n          range: first.range,\n          rangeOffset: first.rangeOffset,\n          rangeLength: first.rangeLength,\n        };\n      } else if (\n        first.rangeOffset <= second.rangeOffset &&\n        first.text.length >= second.rangeLength\n      ) {\n        const start = second.rangeOffset - first.rangeOffset;\n        const end = start + second.rangeLength;\n        const text = first.text.slice(0, start) + second.text + first.text.slice(end);\n        // `second` replaces part of `first`\n        // Most often, this is the result of confirming an auto-completion\n        return {\n          text,\n          range: first.range,\n          rangeOffset: first.rangeOffset,\n          rangeLength: first.rangeLength,\n        };\n      } else {\n        // TODO: Do any of the cases falling into this `else` matter?\n        // TODO: YES - make an insertion and then autocomplete to something totally different (replace subsumes insert)\n        return undefined;\n      }\n    };\n\n    const compressed: Change[] = [];\n    let prev: Change | undefined;\n    for (const change of this.contentChanges) {\n      if (prev === undefined) {\n        prev = change;\n      } else {\n        const merged = merge(prev, change);\n        if (merged) {\n          prev = merged;\n        } else {\n          compressed.push(prev);\n          prev = change;\n        }\n      }\n    }\n    if (prev !== undefined) {\n      compressed.push(prev);\n    }\n    this.contentChanges = compressed;\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/file.ts",
    "content": "import path from 'path';\nimport { doesFileExist } from 'platform/fs';\nimport { Position, Range, Uri, window, workspace } from 'vscode';\nimport { FileCommand } from '../../cmd_line/commands/file';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { Register } from '../../register/register';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { WordType } from '../../textobject/word';\nimport { reportFileInfo } from '../../util/statusBarTextUtils';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass ShowFileInfo extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['<C-g>'];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    reportFileInfo(position, vimState);\n  }\n}\n\n@RegisterAction\nclass GoToAlternateFile extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [['<C-6>'], ['<C-^>']];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const altFile = await Register.get('#');\n    if (altFile?.text instanceof RecordedState) {\n      throw new Error(`# register unexpectedly contained a RecordedState`);\n    } else if (altFile === undefined || altFile.text === '') {\n      StatusBar.displayError(vimState, VimError.NoAlternateFile());\n    } else {\n      let files: Uri[];\n      if (await doesFileExist(Uri.file(altFile.text))) {\n        files = [Uri.file(altFile.text)];\n      } else {\n        files = await workspace.findFiles(altFile.text);\n      }\n\n      // TODO: if the path matches a file from multiple workspace roots, we may not choose the right one\n      if (files.length > 0) {\n        const document = await workspace.openTextDocument(files[0]);\n        await window.showTextDocument(document);\n      }\n    }\n  }\n}\n\n@RegisterAction\nclass OpenFile extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual];\n  keys = ['g', 'f'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let fullFilePath: string;\n    if (vimState.currentMode === Mode.Visual) {\n      fullFilePath = vimState.document.getText(vimState.editor.selection);\n    } else {\n      const range = new Range(\n        position.prevWordStart(vimState.document, { wordType: WordType.FileName, inclusive: true }),\n        position.nextWordStart(vimState.document, { wordType: WordType.FileName }),\n      );\n\n      fullFilePath = vimState.document.getText(range).trim();\n    }\n\n    const fileInfo = fullFilePath.match(/(.*?(?=:[0-9]+)|.*):?([0-9]*)$/);\n    if (fileInfo) {\n      const fileUri: Uri = await (async () => {\n        const pathStr = fileInfo[1];\n        if (path.isAbsolute(pathStr)) {\n          return Uri.file(pathStr);\n        } else {\n          let uri = Uri.file(path.resolve(path.dirname(vimState.document.uri.fsPath), pathStr));\n          if (!(await doesFileExist(uri))) {\n            const workspaceRoot = workspace.getWorkspaceFolder(vimState.document.uri)?.uri;\n            if (workspaceRoot) {\n              uri = Uri.file(path.join(workspaceRoot.fsPath, pathStr));\n              if (!(await doesFileExist(uri))) {\n                throw VimError.CantFindFileInPath(pathStr);\n              }\n            }\n          }\n          return uri;\n        }\n      })();\n\n      const line = parseInt(fileInfo[2], 10);\n      const fileCommand = new FileCommand({\n        name: 'edit',\n        bang: false,\n        opt: [],\n        file: fileUri.fsPath,\n        cmd: isNaN(line) ? undefined : { type: 'line_number', line: line - 1 },\n        createFileIfNotExists: false,\n      });\n      void fileCommand.execute(vimState);\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/fold.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { Mode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { BaseCommand, RegisterAction } from '../base';\nimport { BaseOperator } from '../operator';\n\ntype FoldDirection = 'up' | 'down';\nabstract class CommandFold extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  abstract commandName: string;\n  direction: FoldDirection | undefined;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const timesToRepeat = vimState.recordedState.count || 1;\n    const args =\n      this.direction !== undefined\n        ? { levels: timesToRepeat, direction: this.direction }\n        : undefined;\n    vimState.recordedState.transformer.vscodeCommand(this.commandName, args);\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass ToggleFold extends CommandFold {\n  keys = ['z', 'a'];\n  commandName = 'editor.toggleFold';\n}\n\n@RegisterAction\nclass CloseFold extends CommandFold {\n  keys = ['z', 'c'];\n  commandName = 'editor.fold';\n  override direction: FoldDirection = 'up';\n}\n\n@RegisterAction\nclass CloseAllFolds extends CommandFold {\n  keys = ['z', 'M'];\n  commandName = 'editor.foldAll';\n}\n\n@RegisterAction\nclass OpenFold extends CommandFold {\n  keys = ['z', 'o'];\n  commandName = 'editor.unfold';\n  override direction: FoldDirection = 'down';\n}\n\n@RegisterAction\nclass OpenAllFolds extends CommandFold {\n  keys = ['z', 'R'];\n  commandName = 'editor.unfoldAll';\n}\n\n@RegisterAction\nclass CloseAllFoldsRecursively extends CommandFold {\n  override modes = [Mode.Normal];\n  keys = ['z', 'C'];\n  commandName = 'editor.foldRecursively';\n}\n\n@RegisterAction\nclass OpenAllFoldsRecursively extends CommandFold {\n  override modes = [Mode.Normal];\n  keys = ['z', 'O'];\n  commandName = 'editor.unfoldRecursively';\n}\n\n@RegisterAction\nclass AddFold extends BaseOperator {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['z', 'f'];\n\n  readonly commandName = 'editor.createFoldingRangeFromSelection';\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    const previousSelections = vimState.lastVisualSelection; // keep in case of Normal mode\n    vimState.editor.selection = new vscode.Selection(start, end);\n    await vscode.commands.executeCommand(this.commandName);\n    vimState.lastVisualSelection = previousSelections;\n    vimState.cursors = [Cursor.atPosition(start)];\n    await vimState.setCurrentMode(Mode.Normal); // Vim behavior\n  }\n}\n\n@RegisterAction\nclass RemoveFold extends BaseCommand {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['z', 'd'];\n  readonly commandName = 'editor.removeManualFoldingRanges';\n\n  override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand(this.commandName);\n\n    vimState.cursors = [\n      Cursor.atPosition(\n        vimState.currentMode === Mode.Visual ? vimState.editor.selection.start : position,\n      ),\n    ];\n    await vimState.setCurrentMode(Mode.Normal); // Vim behavior\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/incrementDecrement.ts",
    "content": "import { Position, Range } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { PositionDiff, sorted } from '../../common/motion/position';\nimport { NumericString } from '../../common/number/numericString';\nimport {\n  Mode,\n  isVisualMode,\n  visualBlockGetBottomRightPosition,\n  visualBlockGetTopLeftPosition,\n} from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\nimport { BaseCommand, RegisterAction } from '../base';\n\nabstract class IncrementDecrementNumberAction extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override createsUndoPoint = true;\n  abstract offset: number;\n  abstract staircase: boolean;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const ranges = this.getSearchRanges(vimState);\n\n    let stepNum = 1;\n\n    for (const [idx, range] of ranges.entries()) {\n      position = range.start;\n\n      const text = vimState.document.lineAt(position).text;\n\n      // Make sure position within the text is possible and return if not\n      if (text.length <= position.character) {\n        continue;\n      }\n\n      // Start looking to the right for the next word to increment, unless we're\n      // already on a word to increment, in which case start at the beginning of\n      // that word.\n      const whereToStart = text[position.character].match(/\\s/)\n        ? position\n        : position.prevWordStart(vimState.document, { inclusive: true });\n\n      wordLoop: for (let { start, end, word } of TextEditor.iterateWords(\n        vimState.document,\n        whereToStart,\n      )) {\n        if (start.isAfter(range.stop)) {\n          break;\n        }\n\n        // '-' doesn't count as a word, but is important to include in parsing\n        // the number, as long as it is not just part of the word (-foo2 for example)\n        if (text[start.character - 1] === '-' && /\\d/.test(text[start.character])) {\n          start = start.getLeft();\n          word = text[start.character] + word;\n        }\n        // Strict number parsing so \"1a\" doesn't silently get converted to \"1\"\n        do {\n          const result = NumericString.parse(word);\n          if (result === undefined) {\n            break;\n          }\n          const { num, suffixOffset } = result;\n\n          // Use suffix offset to check if current cursor is in or before detected number.\n          if (position.character < start.character + suffixOffset) {\n            const pos = await this.replaceNum(\n              vimState,\n              num,\n              this.offset * stepNum * (vimState.recordedState.count || 1),\n              start,\n              end,\n            );\n\n            if (this.staircase) {\n              stepNum++;\n            }\n\n            if (vimState.currentMode === Mode.Normal) {\n              vimState.recordedState.transformer.moveCursor(\n                PositionDiff.exactPosition(pos.getLeft(num.suffix.length)),\n              );\n            }\n            break wordLoop;\n          } else {\n            // For situation like this: xyz1999em199[cursor]9m\n            word = word.slice(suffixOffset);\n            start = new Position(start.line, start.character + suffixOffset);\n          }\n        } while (true);\n      }\n    }\n\n    if (isVisualMode(vimState.currentMode)) {\n      vimState.recordedState.transformer.moveCursor(PositionDiff.exactPosition(ranges[0].start));\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n\n  private async replaceNum(\n    vimState: VimState,\n    start: NumericString,\n    offset: number,\n    startPos: Position,\n    endPos: Position,\n  ): Promise<Position> {\n    const oldLength = endPos.character + 1 - startPos.character;\n    start.value += offset;\n    const newNum = start.toString();\n\n    const range = new Range(startPos, endPos.getRight());\n\n    vimState.recordedState.transformer.replace(range, newNum);\n    if (oldLength !== newNum.length) {\n      // Adjust end position according to difference in width of number-string\n      endPos = new Position(endPos.line, startPos.character + newNum.length - 1);\n    }\n\n    return endPos;\n  }\n\n  /**\n   * @returns a list of Ranges in which to search for numbers\n   */\n  private getSearchRanges(vimState: VimState): Cursor[] {\n    const ranges: Cursor[] = [];\n    const [start, stop] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    switch (vimState.currentMode) {\n      case Mode.Normal: {\n        ranges.push(\n          new Cursor(vimState.cursorStopPosition, vimState.cursorStopPosition.getLineEnd()),\n        );\n        break;\n      }\n\n      case Mode.Visual: {\n        ranges.push(new Cursor(start, start.getLineEnd()));\n        for (let line = start.line + 1; line < stop.line; line++) {\n          const lineStart = new Position(line, 0);\n          ranges.push(new Cursor(lineStart, lineStart.getLineEnd()));\n        }\n        ranges.push(new Cursor(stop.getLineBegin(), stop));\n        break;\n      }\n\n      case Mode.VisualLine: {\n        for (let line = start.line; line <= stop.line; line++) {\n          const lineStart = new Position(line, 0);\n          ranges.push(new Cursor(lineStart, lineStart.getLineEnd()));\n        }\n        break;\n      }\n\n      case Mode.VisualBlock: {\n        const topLeft = visualBlockGetTopLeftPosition(start, stop);\n        const bottomRight = visualBlockGetBottomRightPosition(start, stop);\n        for (let line = topLeft.line; line <= bottomRight.line; line++) {\n          ranges.push(\n            new Cursor(\n              new Position(line, topLeft.character),\n              new Position(line, bottomRight.character),\n            ),\n          );\n        }\n        break;\n      }\n\n      default:\n        throw new Error(\n          `Unexpected mode ${vimState.currentMode} in IncrementDecrementNumberAction.getPositions()`,\n        );\n    }\n    return ranges;\n  }\n}\n\n@RegisterAction\nclass IncrementNumber extends IncrementDecrementNumberAction {\n  keys = ['<C-a>'];\n  offset = +1;\n  staircase = false;\n}\n\n@RegisterAction\nclass DecrementNumber extends IncrementDecrementNumberAction {\n  keys = ['<C-x>'];\n  offset = -1;\n  staircase = false;\n}\n\n@RegisterAction\nclass IncrementNumberStaircase extends IncrementDecrementNumberAction {\n  keys = ['g', '<C-a>'];\n  offset = +1;\n  staircase = true;\n}\n\n@RegisterAction\nclass DecrementNumberStaircase extends IncrementDecrementNumberAction {\n  keys = ['g', '<C-x>'];\n  offset = -1;\n  staircase = true;\n}\n"
  },
  {
    "path": "src/actions/commands/insert.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position, Range } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { lineCompletionProvider } from '../../completion/lineCompletionProvider';\nimport { VimError } from '../../error';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { getCursorsAfterSync, isHighSurrogate, isLowSurrogate } from '../../util/util';\nimport { BaseMovement } from '../baseMotion';\nimport { MoveDown, MoveLeft, MoveRight, MoveUp } from '../motion';\nimport { PositionDiff } from './../../common/motion/position';\nimport { configuration } from './../../configuration/configuration';\nimport { Mode } from './../../mode/mode';\nimport { Register, RegisterMode } from './../../register/register';\nimport { TextEditor } from './../../textEditor';\nimport { BaseCommand, RegisterAction } from './../base';\nimport { CommandNumber } from './actions';\nimport { DefaultDigraphs } from './digraphs';\nimport { DocumentContentChangeAction } from './documentChange';\nimport { EnterReplaceMode } from './replace';\n\n@RegisterAction\nexport class Insert extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [['i'], ['<Insert>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Only allow this command to be prefixed with a count or nothing, no other\n    // actions or operators before\n    let previousActionsNumbers = true;\n    for (const prevAction of vimState.recordedState.actionsRun) {\n      if (!(prevAction instanceof CommandNumber)) {\n        previousActionsNumbers = false;\n        break;\n      }\n    }\n\n    if (vimState.recordedState.actionsRun.length === 0 || previousActionsNumbers) {\n      return super.couldActionApply(vimState, keysPressed);\n    }\n    return false;\n  }\n}\n\n@RegisterAction\nexport class Append extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['a'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.cursorStopPosition = vimState.cursorStartPosition = position.getRight();\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Only allow this command to be prefixed with a count or nothing, no other actions or operators before\n    if (!vimState.recordedState.actionsRun.every((action) => action instanceof CommandNumber)) {\n      return false;\n    }\n\n    return super.couldActionApply(vimState, keysPressed);\n  }\n}\n\n@RegisterAction\nclass InsertAtLastChange extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'i'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.cursorStopPosition = vimState.cursorStartPosition =\n      vimState.historyTracker.getLastChangeEndPosition() ?? new Position(0, 0);\n\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n}\n\n@RegisterAction\nclass InsertAfterFirstWhitespaceOnLine extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['I'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.cursorStopPosition = vimState.cursorStartPosition =\n      TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, position.line);\n  }\n}\n\n@RegisterAction\nclass InsertAtLineBegin extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'I'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.cursorStopPosition = vimState.cursorStartPosition = position.getLineBegin();\n  }\n}\n\n@RegisterAction\nclass InsertAtLineEnd extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['A'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    vimState.cursorStopPosition = vimState.cursorStartPosition = position.getLineEnd();\n  }\n}\n\n@RegisterAction\nclass InsertAbove extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['O'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async execCount(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    const count = vimState.recordedState.count || 1;\n\n    const charPos = position.getLineBeginRespectingIndent(vimState.document).character;\n\n    for (let i = 0; i < count; i++) {\n      await vscode.commands.executeCommand('editor.action.insertLineBefore');\n    }\n\n    vimState.cursors = getCursorsAfterSync(vimState.editor);\n    const endPos = vimState.cursor.start.character;\n    const indentAmt = charPos - endPos;\n\n    for (let i = 0; i < count; i++) {\n      const newPos = new Position(vimState.cursor.start.line + i, charPos);\n      if (i === 0) {\n        vimState.cursor = Cursor.atPosition(newPos);\n      } else {\n        vimState.cursors.push(Cursor.atPosition(newPos));\n      }\n      if (indentAmt >= 0) {\n        vimState.recordedState.transformer.addTransformation({\n          type: 'insertText',\n          // TODO: Use `editor.options.insertSpaces`, I think\n          text: TextEditor.setIndentationLevel('', indentAmt, configuration.expandtab),\n          position: newPos,\n          cursorIndex: i,\n          manuallySetCursorPositions: true,\n        });\n      } else {\n        vimState.recordedState.transformer.addTransformation({\n          type: 'deleteRange',\n          cursorIndex: i,\n          range: new Range(newPos, new Position(newPos.line, endPos)),\n          manuallySetCursorPositions: true,\n        });\n      }\n    }\n    vimState.cursors = vimState.cursors.reverse();\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\n@RegisterAction\nclass InsertBelow extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['o'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async execCount(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n    const count = vimState.recordedState.count || 1;\n\n    for (let i = 0; i < count; i++) {\n      await vscode.commands.executeCommand('editor.action.insertLineAfter');\n    }\n    vimState.cursors = getCursorsAfterSync(vimState.editor);\n    for (let i = 1; i < count; i++) {\n      const newPos = new Position(\n        vimState.cursorStartPosition.line - i,\n        vimState.cursorStartPosition.character,\n      );\n      vimState.cursors.push(Cursor.atPosition(newPos));\n\n      // Ahhhhhh. We have to manually set cursor position here as we need text\n      // transformations AND to set multiple cursors.\n      vimState.recordedState.transformer.addTransformation({\n        type: 'insertText',\n        // TODO: Use `editor.options.insertSpaces`, I think\n        text: TextEditor.setIndentationLevel('', newPos.character, configuration.expandtab),\n        position: newPos,\n        cursorIndex: i,\n        manuallySetCursorPositions: true,\n      });\n    }\n    vimState.cursors = vimState.cursors.reverse();\n    vimState.isFakeMultiCursor = true;\n  }\n}\n\n@RegisterAction\nexport class ExitInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = [['<Esc>'], ['<C-c>'], ['<C-[>']];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    void vscode.commands.executeCommand('closeParameterHints');\n    void vscode.commands.executeCommand('editor.action.inlineSuggest.hide');\n\n    vimState.cursors = vimState.cursors.map((x) => x.withNewStop(x.stop.getLeft()));\n    if (vimState.returnToInsertAfterCommand && position.character !== 0) {\n      vimState.cursors = vimState.cursors.map((x) => x.withNewStop(x.stop.getRight()));\n    }\n\n    // only remove leading spaces inserted by vscode.\n    // vscode only inserts them when user enter a new line,\n    // ie, o/O in Normal mode or \\n in Insert mode.\n    const lastActionBeforeEsc =\n      vimState.recordedState.actionsRun[vimState.recordedState.actionsRun.length - 2];\n    if (\n      vimState.document.languageId !== 'plaintext' &&\n      (lastActionBeforeEsc instanceof InsertBelow ||\n        lastActionBeforeEsc instanceof InsertAbove ||\n        (lastActionBeforeEsc instanceof DocumentContentChangeAction &&\n          lastActionBeforeEsc.keysPressed[lastActionBeforeEsc.keysPressed.length - 1] === '\\n'))\n    ) {\n      for (const cursor of vimState.cursors) {\n        const line = vimState.document.lineAt(cursor.stop);\n        if (line.text.length > 0 && line.isEmptyOrWhitespace) {\n          vimState.recordedState.transformer.delete(line.range);\n        }\n      }\n    }\n    await vimState.setCurrentMode(Mode.Normal);\n\n    // If we wanted to repeat this insert (only for i and a), now is the time to do it. Insert\n    // count amount of these strings before returning back to normal mode\n    const shouldRepeatInsert =\n      vimState.recordedState.count > 1 &&\n      vimState.recordedState.actionsRun.find(\n        (a) =>\n          a instanceof Insert ||\n          a instanceof Append ||\n          a instanceof InsertAtLineBegin ||\n          a instanceof InsertAtLineEnd ||\n          a instanceof InsertAfterFirstWhitespaceOnLine ||\n          a instanceof InsertAtLastChange,\n      ) !== undefined;\n\n    // If this is the type to repeat insert, do this now\n    if (shouldRepeatInsert) {\n      const changeAction = vimState.recordedState.actionsRun\n        .slice()\n        .reverse()\n        .find((a) => a instanceof DocumentContentChangeAction);\n      if (changeAction instanceof DocumentContentChangeAction) {\n        // Add count amount of inserts in the case of 4i=<esc>\n        // TODO: A few actions such as <C-t> should be repeated, but are not\n        for (let i = 0; i < vimState.recordedState.count - 1; i++) {\n          // If this is the last transform, move cursor back one character\n          const positionDiff =\n            i === vimState.recordedState.count - 2\n              ? PositionDiff.offset({ character: -1 })\n              : PositionDiff.identity();\n\n          // Add a transform containing the change\n          vimState.recordedState.transformer.addTransformation(\n            changeAction.getTransformation(positionDiff),\n          );\n        }\n      }\n    }\n\n    vimState.historyTracker.currentContentChanges = [];\n\n    if (vimState.isFakeMultiCursor) {\n      vimState.cursors = [vimState.cursor];\n      vimState.isFakeMultiCursor = false;\n    }\n  }\n}\n\n@RegisterAction\nexport class InsertPreviousText extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-a>'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const register = await Register.get('.');\n    if (\n      register === undefined ||\n      !(register.text instanceof RecordedState) ||\n      !register.text.actionsRun\n    ) {\n      throw VimError.NoInsertedTextYet();\n    }\n\n    const recordedState = register.text.clone();\n\n    // The first action is entering Insert Mode, which is not necessary in this case\n    recordedState.actionsRun.shift();\n\n    // The last action is leaving Insert Mode, which is not necessary in this case\n    recordedState.actionsRun.pop();\n\n    if (recordedState.actionsRun?.[0] instanceof ArrowsInInsertMode) {\n      // Note, arrow keys are the only Insert action command that can't be repeated here as far as @rebornix knows.\n      recordedState.actionsRun.shift();\n    }\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'replayRecordedState',\n      count: 1,\n      recordedState,\n    });\n  }\n}\n\n@RegisterAction\nclass InsertPreviousTextAndQuit extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-shift+2>']; // <C-@>\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new InsertPreviousText().exec(position, vimState);\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\nabstract class IndentCommand extends BaseCommand {\n  modes = [Mode.Insert];\n  abstract readonly delta: number;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const line = vimState.document.lineAt(position);\n    const tabSize = Number(vimState.editor.options.tabSize);\n    const indentationWidth = TextEditor.getIndentationLevel(line.text, tabSize);\n    const newIndentationWidth = (Math.floor(indentationWidth / tabSize) + this.delta) * tabSize;\n\n    vimState.recordedState.transformer.replace(\n      new Range(\n        position.getLineBegin(),\n        position.with({ character: line.firstNonWhitespaceCharacterIndex }),\n      ),\n      TextEditor.setIndentationLevel(\n        line.text,\n        newIndentationWidth,\n        vimState.editor.options.insertSpaces as boolean,\n      ).match(/^(\\s*)/)![1],\n    );\n  }\n}\n\n@RegisterAction\nclass IncreaseIndent extends IndentCommand {\n  keys = ['<C-t>'];\n  override readonly delta = 1;\n}\n@RegisterAction\nclass DecreaseIndent extends IndentCommand {\n  keys = ['<C-d>'];\n  override readonly delta = -1;\n}\n\n@RegisterAction\nexport class BackspaceInInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = [['<BS>'], ['<C-h>']];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.vscodeCommand('deleteLeft');\n  }\n}\n\n@RegisterAction\nclass DeleteInInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<Del>'];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.vscodeCommand('deleteRight');\n  }\n}\n\n@RegisterAction\nexport class TypeInInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<character>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const char = this.keysPressed.at(-1)!;\n\n    let text = char;\n\n    if (char.length === 1) {\n      const prevHighSurrogate =\n        vimState.modeData.mode === Mode.Insert ? vimState.modeData.highSurrogate : undefined;\n\n      if (isHighSurrogate(char.charCodeAt(0))) {\n        await vimState.setModeData({\n          mode: Mode.Insert,\n          highSurrogate: char,\n        });\n\n        if (prevHighSurrogate === undefined) return;\n        text = prevHighSurrogate;\n      } else {\n        if (isLowSurrogate(char.charCodeAt(0)) && prevHighSurrogate !== undefined) {\n          text = prevHighSurrogate + char;\n        }\n\n        await vimState.setModeData({\n          mode: Mode.Insert,\n          highSurrogate: undefined,\n        });\n      }\n    }\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'insertTextVSCode',\n      text,\n      isMultiCursor: vimState.isMultiCursor,\n    });\n  }\n\n  public override toString(): string {\n    return this.keysPressed.at(-1)!;\n  }\n}\n\n@RegisterAction\nclass InsertDigraph extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-k>', '<any>', '<any>'];\n  override isCompleteAction = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const digraph = this.keysPressed.slice(1, 3).join('');\n    const reverseDigraph = digraph.split('').reverse().join('');\n    let charCodes = (DefaultDigraphs.get(digraph) ||\n      DefaultDigraphs.get(reverseDigraph) ||\n      configuration.digraphs[digraph] ||\n      configuration.digraphs[reverseDigraph])[1];\n    if (!(charCodes instanceof Array)) {\n      charCodes = [charCodes];\n    }\n    const char = String.fromCharCode(...charCodes);\n    vimState.recordedState.transformer.insert(position, char);\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (!super.doesActionApply(vimState, keysPressed)) {\n      return false;\n    }\n    const chars = keysPressed.slice(1, 3).join('');\n    const reverseChars = chars.split('').reverse().join('');\n    return (\n      chars in configuration.digraphs ||\n      reverseChars in configuration.digraphs ||\n      DefaultDigraphs.has(chars) ||\n      DefaultDigraphs.has(reverseChars)\n    );\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (!super.couldActionApply(vimState, keysPressed)) {\n      return false;\n    }\n    const chars = keysPressed.slice(1, keysPressed.length).join('');\n    const reverseChars = chars.split('').reverse().join('');\n    if (chars.length > 0) {\n      const predicate = (digraph: string) => {\n        const digraphChars = digraph.substring(0, chars.length);\n        return chars === digraphChars || reverseChars === digraphChars;\n      };\n      const match =\n        Object.keys(configuration.digraphs).find(predicate) ||\n        [...DefaultDigraphs.keys()].find(predicate);\n      return match !== undefined;\n    }\n    return true;\n  }\n}\n\n@RegisterAction\nclass InsertRegisterContent extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-r>', '<character>'];\n  override isCompleteAction = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const registerKey = this.keysPressed[1];\n    if (!Register.isValidRegister(registerKey)) {\n      return;\n    }\n\n    const register = await Register.get(registerKey, this.multicursorIndex);\n    if (register === undefined) {\n      StatusBar.displayError(vimState, VimError.NothingInRegister(registerKey));\n      return;\n    }\n\n    if (register.text instanceof RecordedState) {\n      vimState.recordedState.transformer.addTransformation({\n        type: 'macro',\n        register: vimState.recordedState.registerName,\n        replay: 'keystrokes',\n      });\n\n      return;\n    }\n\n    let text = register.text;\n    if (register.registerMode === RegisterMode.LineWise && !vimState.isMultiCursor) {\n      text += '\\n';\n    }\n\n    vimState.recordedState.transformer.insert(position, text);\n  }\n}\n\n@RegisterAction\nclass ExecuteOneNormalCommandInInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-o>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.returnToInsertAfterCommand = true;\n    vimState.actionCount = 0;\n    await new ExitInsertMode().exec(position, vimState);\n  }\n}\n\n@RegisterAction\nexport class InsertCharAbove extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-y>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.line === 0) {\n      return;\n    }\n\n    const charPos = position.getUp();\n    if (charPos.isLineEnd(vimState.document)) {\n      return;\n    }\n\n    const char = vimState.document.getText(new Range(charPos, charPos.getRight()));\n\n    vimState.recordedState.transformer.insert(position, char);\n  }\n}\n\n@RegisterAction\nexport class InsertCharBelow extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-e>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.line >= vimState.document.lineCount - 1) {\n      return;\n    }\n\n    const charPos = position.getDown();\n    if (charPos.isLineEnd(vimState.document)) {\n      return;\n    }\n\n    const char = vimState.document.getText(new Range(charPos, charPos.getRight()));\n\n    vimState.recordedState.transformer.insert(position, char);\n  }\n}\n\n@RegisterAction\nclass DeleteWord extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-w>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.isAtDocumentBegin()) {\n      return;\n    }\n\n    let wordBegin: Position;\n    if (position.isInLeadingWhitespace(vimState.document)) {\n      wordBegin = position.getLineBegin();\n    } else if (position.isLineBeginning()) {\n      wordBegin = position.getUp().getLineEnd();\n    } else {\n      wordBegin = position.prevWordStart(vimState.document);\n    }\n\n    vimState.recordedState.transformer.delete(new Range(wordBegin, position));\n\n    vimState.cursorStopPosition = wordBegin;\n  }\n}\n\n@RegisterAction\nclass DeleteAllBeforeCursor extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-u>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let start: Position;\n    if (position.character === 0) {\n      start = position.getLeftThroughLineBreaks(true);\n    } else if (position.isInLeadingWhitespace(vimState.document)) {\n      start = position.getLineBegin();\n    } else {\n      start = position.getLineBeginRespectingIndent(vimState.document);\n    }\n\n    vimState.recordedState.transformer.delete(new Range(start, position));\n\n    vimState.cursorStopPosition = start;\n    vimState.cursorStartPosition = start;\n  }\n}\n\n@RegisterAction\nclass SelectNextSuggestion extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = [['<C-n>'], ['<C-j>']];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('selectNextSuggestion');\n  }\n}\n\n@RegisterAction\nclass SelectPrevSuggestion extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-p>'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('selectPrevSuggestion');\n  }\n}\n\n@RegisterAction\nclass CtrlVInInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-v>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const clipboard = await Register.get('*', this.multicursorIndex);\n    const text = clipboard?.text instanceof RecordedState ? undefined : clipboard?.text;\n\n    if (text) {\n      vimState.recordedState.transformer.insert(vimState.cursorStopPosition, text);\n    }\n  }\n}\n\n@RegisterAction\nclass ShowLineAutocomplete extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-x>', '<C-l>'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await lineCompletionProvider.showLineCompletionsQuickPick(position, vimState);\n  }\n}\n\n@RegisterAction\nclass NewLineInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = [['<C-j>'], ['<C-m>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.insert(\n      position,\n      '\\n',\n      PositionDiff.offset({ character: -1 }),\n    );\n  }\n}\n\n@RegisterAction\nclass ReplaceAtCursorFromInsertMode extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<Insert>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new EnterReplaceMode().exec(position, vimState);\n  }\n}\n\n@RegisterAction\nclass CreateUndoPoint extends BaseCommand {\n  modes = [Mode.Insert];\n  keys = ['<C-g>', 'u'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.historyTracker.addChange(true);\n    vimState.historyTracker.finishCurrentStep();\n  }\n}\n\n@RegisterAction\nexport class ArrowsInInsertMode extends BaseMovement {\n  override modes = [Mode.Insert];\n  keys = [['<up>'], ['<down>'], ['<left>'], ['<right>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    // Moving with the arrow keys in Insert mode \"resets\" our insertion for the purpose of repeating with dot or `<C-a>`.\n    // No matter how we got into Insert mode, repeating will now be done as if we started with `i`.\n    // Note that this does not affect macros, which re-construct a list of actions based on keypresses.\n    // TODO: ACTUALLY, we should reset this only after something is typed (`Axyz<Left><Esc>.` does repeat the insertion)\n    // TODO: This also should mark an \"insertion end\" for the purpose of `<C-a>` (try `ixyz<Right><C-a>`)\n    vimState.recordedState.actionsRun = [new Insert()];\n\n    // Force an undo point to be created\n    vimState.historyTracker.addChange(true);\n    vimState.historyTracker.finishCurrentStep();\n\n    let newPosition: Position;\n    switch (this.keysPressed[0]) {\n      case '<up>':\n        newPosition = await new MoveUp(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<down>':\n        newPosition = await new MoveDown(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<left>':\n        newPosition = await new MoveLeft(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<right>':\n        newPosition = await new MoveRight(this.keysPressed).execAction(position, vimState);\n        break;\n      default:\n        throw new Error(`Unexpected 'arrow' key: ${this.keys[0]}`);\n    }\n    return newPosition;\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/join.ts",
    "content": "import { Position, Range } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { PositionDiff, sorted } from '../../common/motion/position';\nimport { configuration } from '../../configuration/configuration';\nimport { Mode } from '../../mode/mode';\nimport { RegisterMode } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\nimport { isTextTransformation } from '../../transformations/transformations';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass ActionJoin extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['J'];\n  override createsUndoPoint = true;\n  override runsOnceForEachCountPrefix = false;\n\n  public async execJoinLines(\n    startPosition: Position,\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<void> {\n    count = count - 1 || 1;\n\n    let startLineNumber: number;\n    let endLineNumber: number;\n\n    if (startPosition.isEqual(position) || startPosition.line === position.line) {\n      if (position.line + 1 < vimState.document.lineCount) {\n        startLineNumber = position.line;\n        endLineNumber = position.getDown(count).line;\n      } else {\n        startLineNumber = position.line;\n        endLineNumber = position.line;\n      }\n    } else {\n      startLineNumber = startPosition.line;\n      endLineNumber = position.line;\n    }\n\n    let trimmedLinesContent = vimState.document.lineAt(startPosition).text;\n    let columnDeltaOffset: number = 0;\n\n    for (let i = startLineNumber + 1; i <= endLineNumber; i++) {\n      const line = vimState.document.lineAt(i);\n\n      if (line.firstNonWhitespaceCharacterIndex < line.text.length) {\n        // Compute number of spaces to separate the lines\n        let insertSpace = ' ';\n\n        if (trimmedLinesContent === '' || trimmedLinesContent.endsWith('\\t')) {\n          insertSpace = '';\n        } else if (\n          configuration.joinspaces &&\n          (trimmedLinesContent.endsWith('.') ||\n            trimmedLinesContent.endsWith('!') ||\n            trimmedLinesContent.endsWith('?'))\n        ) {\n          insertSpace = '  ';\n        } else if (\n          configuration.joinspaces &&\n          (trimmedLinesContent.endsWith('. ') ||\n            trimmedLinesContent.endsWith('! ') ||\n            trimmedLinesContent.endsWith('? '))\n        ) {\n          insertSpace = ' ';\n        } else if (trimmedLinesContent.endsWith(' ')) {\n          insertSpace = '';\n        }\n\n        const lineTextWithoutIndent = line.text.substring(line.firstNonWhitespaceCharacterIndex);\n\n        if (lineTextWithoutIndent.charAt(0) === ')') {\n          insertSpace = '';\n        }\n\n        trimmedLinesContent += insertSpace + lineTextWithoutIndent;\n        columnDeltaOffset = lineTextWithoutIndent.length + insertSpace.length;\n      }\n    }\n\n    const deleteRange = new Range(\n      new Position(startLineNumber, 0),\n      new Position(endLineNumber, TextEditor.getLineLength(endLineNumber)),\n    );\n\n    if (!deleteRange.start.isEqual(deleteRange.end)) {\n      if (startPosition.isEqual(position)) {\n        vimState.recordedState.transformer.replace(\n          new Range(deleteRange.start, deleteRange.end),\n          trimmedLinesContent,\n          PositionDiff.offset({\n            character: trimmedLinesContent.length - columnDeltaOffset - position.character,\n          }),\n        );\n      } else {\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: trimmedLinesContent,\n          range: new Range(deleteRange.start, deleteRange.end),\n          manuallySetCursorPositions: true,\n        });\n\n        vimState.cursorStartPosition = vimState.cursorStopPosition = new Position(\n          startPosition.line,\n          trimmedLinesContent.length - columnDeltaOffset,\n        );\n        await vimState.setCurrentMode(Mode.Normal);\n      }\n    }\n  }\n\n  public override async execCount(position: Position, vimState: VimState): Promise<void> {\n    const cursorsToIterateOver = [...vimState.cursors].sort((a, b) =>\n      a.start.line > b.start.line ||\n      (a.start.line === b.start.line && a.start.character > b.start.character)\n        ? 1\n        : -1,\n    );\n\n    const resultingCursors: Cursor[] = [];\n    for (const [idx, { start, stop }] of cursorsToIterateOver.entries()) {\n      this.multicursorIndex = idx;\n\n      vimState.cursorStopPosition = stop;\n      vimState.cursorStartPosition = start;\n\n      await this.execJoinLines(start, stop, vimState, vimState.recordedState.count || 1);\n\n      resultingCursors.push(new Cursor(vimState.cursorStartPosition, vimState.cursorStopPosition));\n\n      for (const transformation of vimState.recordedState.transformer.transformations) {\n        if (isTextTransformation(transformation) && transformation.cursorIndex === undefined) {\n          transformation.cursorIndex = this.multicursorIndex;\n        }\n      }\n    }\n\n    vimState.cursors = resultingCursors;\n  }\n}\n\n@RegisterAction\nclass ActionJoinVisualMode extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['J'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.editor.selection.start, vimState.editor.selection.end);\n\n    /**\n     * For joining lines, Visual Line behaves the same as Visual so we align the register mode here.\n     */\n    vimState.currentRegisterMode = RegisterMode.CharacterWise;\n    await new ActionJoin().execJoinLines(start, end, vimState, 1);\n  }\n}\n\n@RegisterAction\nclass ActionJoinVisualBlockMode extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['J'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n\n    vimState.currentRegisterMode = RegisterMode.CharacterWise;\n    await new ActionJoin().execJoinLines(start, end, vimState, 1);\n  }\n}\n\n@RegisterAction\nclass ActionJoinNoWhitespace extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'J'];\n  override createsUndoPoint = true;\n\n  // gJ is essentially J without the edge cases. ;-)\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (position.line === vimState.document.lineCount - 1) {\n      return; // TODO: bell\n    }\n\n    const count = vimState.recordedState.count > 2 ? vimState.recordedState.count - 1 : 1;\n    await this.execJoin(count, position, vimState);\n  }\n\n  public async execJoin(count: number, position: Position, vimState: VimState): Promise<void> {\n    const replaceRange = new Range(\n      new Position(position.line, 0),\n      new Position(\n        Math.min(position.line + count, vimState.document.lineCount - 1),\n        0,\n      ).getLineEnd(),\n    );\n\n    const joinedText = vimState.document.getText(replaceRange).replace(/\\r?\\n/g, '');\n\n    // Put the cursor at the start of the last joined line's text\n    const newCursorColumn =\n      joinedText.length - vimState.document.lineAt(replaceRange.end).text.length;\n\n    vimState.recordedState.transformer.replace(\n      replaceRange,\n      joinedText,\n      PositionDiff.exactCharacter({\n        character: newCursorColumn,\n      }),\n    );\n  }\n}\n\n@RegisterAction\nclass ActionJoinNoWhitespaceVisualMode extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['g', 'J'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    const count = start.line === end.line ? 1 : end.line - start.line;\n    await new ActionJoinNoWhitespace().execJoin(count, start, vimState);\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/macro.ts",
    "content": "import { Position } from 'vscode';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { Register } from '../../register/register';\nimport { globalState } from '../../state/globalState';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass RecordMacro extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['q', '<macro>'],\n    ['q', '\"'],\n  ];\n\n  public override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const registerKey = this.keysPressed[1];\n    const register = registerKey.toLocaleLowerCase();\n    vimState.macro = new RecordedState();\n    vimState.macro.registerKey = registerKey;\n    vimState.macro.registerName = register;\n\n    if (!Register.isValidUppercaseRegister(registerKey) || !Register.has(register)) {\n      // TODO: this seems suspect - why are we not putting `vimState.macro` in the register? Why are we setting `registerName`?\n      const newRegister = new RecordedState();\n      newRegister.registerName = register;\n\n      vimState.recordedState.registerName = register;\n      Register.put(vimState, newRegister);\n    }\n  }\n}\n\n@RegisterAction\nexport class QuitRecordMacro extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['q'];\n\n  public override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const macro = vimState.macro;\n    if (macro === undefined) {\n      return;\n    }\n\n    const existingMacro = (await Register.get(macro.registerName))?.text;\n    if (existingMacro instanceof RecordedState) {\n      if (Register.isValidUppercaseRegister(macro.registerKey)) {\n        existingMacro.actionsRun = existingMacro.actionsRun.concat(macro.actionsRun);\n      } else {\n        existingMacro.actionsRun = macro.actionsRun;\n      }\n    }\n\n    vimState.macro = undefined;\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.doesActionApply(vimState, keysPressed) && vimState.macro !== undefined;\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.couldActionApply(vimState, keysPressed) && vimState.macro !== undefined;\n  }\n}\n\n@RegisterAction\nclass ExecuteLastMacro extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['@', '@'];\n  override runsOnceForEachCountPrefix = true;\n  override createsUndoPoint = true;\n  override isJump = true;\n\n  public override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const { lastInvokedMacro } = globalState;\n\n    if (lastInvokedMacro) {\n      vimState.recordedState.transformer.addTransformation({\n        type: 'macro',\n        register: lastInvokedMacro.registerName,\n        replay: 'contentChange',\n      });\n    } else {\n      StatusBar.displayError(vimState, VimError.NoPreviouslyUsedRegister());\n    }\n  }\n}\n\n@RegisterAction\nclass ExecuteMacro extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['@', '<register>'];\n  override runsOnceForEachCountPrefix = true;\n  override createsUndoPoint = true;\n\n  public override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const register = this.keysPressed[1].toLocaleLowerCase();\n\n    const isFilenameRegister = register === '%' || register === '#';\n    if (!Register.isValidRegister(register) || isFilenameRegister) {\n      StatusBar.displayError(vimState, VimError.InvalidRegisterName(register));\n    }\n\n    if (Register.has(register)) {\n      vimState.recordedState.transformer.addTransformation({\n        type: 'macro',\n        register,\n        replay: 'contentChange',\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/navigate.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { globalState } from '../../state/globalState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass GoToDeclaration extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [\n    ['g', 'd'],\n    ['g', 'D'],\n  ];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('editor.action.goToDeclaration');\n\n    if (vimState.editor === vscode.window.activeTextEditor) {\n      // We didn't switch to a different editor\n      vimState.cursorStartPosition = vimState.editor.selection.start;\n      vimState.cursorStopPosition = vimState.editor.selection.end;\n    }\n  }\n}\n\n@RegisterAction\nclass GoToDefinition extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['<C-]>'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('editor.action.revealDefinition');\n\n    if (vimState.editor === vscode.window.activeTextEditor) {\n      // We didn't switch to a different editor\n      vimState.cursorStopPosition = vimState.editor.selection.start;\n    }\n  }\n}\n\n@RegisterAction\nclass OpenLink extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['g', 'x'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    void vscode.commands.executeCommand('editor.action.openLink');\n  }\n}\n\n@RegisterAction\nclass GoBackInChangelist extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', ';'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const prevPos = vimState.historyTracker.prevChangeInChangeList();\n\n    if (prevPos instanceof VimError) {\n      StatusBar.displayError(vimState, prevPos);\n    } else {\n      vimState.cursorStopPosition = prevPos;\n    }\n  }\n}\n\n@RegisterAction\nclass GoForwardInChangelist extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', ','];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const nextPos = vimState.historyTracker.nextChangeInChangeList();\n\n    if (nextPos instanceof VimError) {\n      StatusBar.displayError(vimState, nextPos);\n    } else {\n      vimState.cursorStopPosition = nextPos;\n    }\n  }\n}\n\n@RegisterAction\nclass NavigateBack extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [['<C-o>'], ['<C-t>']];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await globalState.jumpTracker.jumpBack(position, vimState);\n  }\n}\n\n@RegisterAction\nclass NavigateForward extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['<C-i>'];\n\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await globalState.jumpTracker.jumpForward(position, vimState);\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/put.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position, TextDocument } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { laterOf, PositionDiff, sorted } from '../../common/motion/position';\nimport { configuration } from '../../configuration/configuration';\nimport { VimError } from '../../error';\nimport { isVisualMode, Mode } from '../../mode/mode';\nimport { IRegisterContent, Register, RegisterMode } from '../../register/register';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { TextEditor } from '../../textEditor';\nimport { Transformation } from '../../transformations/transformations';\nimport { reportLinesChanged } from '../../util/statusBarTextUtils';\nimport { BaseCommand, RegisterAction } from '../base';\n\nfunction firstNonBlankChar(text: string): number {\n  return text.match(/\\S/)?.index ?? 0;\n}\n\ntype GetCursorPositionParams = {\n  document: TextDocument;\n  mode: Mode;\n  replaceRange: vscode.Range;\n  registerMode: RegisterMode;\n  count: number;\n  text: string;\n  returnToInsertAfterCommand: boolean;\n};\n\nabstract class BasePutCommand extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override createsUndoPoint = true;\n\n  protected overwritesRegisterWithSelection = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const register = await Register.get(vimState.recordedState.registerName, this.multicursorIndex);\n    if (register === undefined) {\n      StatusBar.displayError(\n        vimState,\n        VimError.NothingInRegister(vimState.recordedState.registerName),\n      );\n      return;\n    }\n\n    const count = vimState.recordedState.count || 1;\n\n    const mode =\n      vimState.currentMode === Mode.CommandlineInProgress ? Mode.Normal : vimState.currentMode;\n    const registerMode = this.getRegisterMode(register);\n\n    const replaceRange = this.getReplaceRange(mode, vimState.cursor, registerMode);\n\n    let text = this.getRegisterText(mode, register, count);\n    if (this.shouldAdjustIndent(mode, registerMode)) {\n      let lineToMatch: number | undefined;\n      if (mode === Mode.VisualLine) {\n        const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n        if (end.line < vimState.document.lineCount - 1) {\n          lineToMatch = end.line + 1;\n        } else if (start.line > 0) {\n          lineToMatch = start.line - 1;\n        }\n      } else {\n        lineToMatch = position.line;\n      }\n      text = this.adjustIndent(\n        lineToMatch !== undefined ? vimState.document.lineAt(lineToMatch).text : '',\n        text,\n      );\n    }\n\n    const newCursorPosition = this.getCursorPosition({\n      document: vimState.document,\n      returnToInsertAfterCommand: vimState.returnToInsertAfterCommand,\n      mode,\n      replaceRange,\n      registerMode,\n      count,\n      text,\n    });\n\n    vimState.recordedState.transformer.moveCursor(\n      PositionDiff.exactPosition(newCursorPosition),\n      this.multicursorIndex ?? 0,\n    );\n\n    if (registerMode === RegisterMode.LineWise) {\n      text = this.adjustLinewiseRegisterText(mode, text);\n    }\n\n    for (const transformation of this.getTransformations(\n      vimState.document,\n      mode,\n      replaceRange,\n      registerMode,\n      text,\n    )) {\n      vimState.recordedState.transformer.addTransformation(transformation);\n    }\n\n    // We do not run this in multi-cursor mode as it will overwrite the register for upcoming put iterations\n    if (isVisualMode(mode) && !vimState.isMultiCursor) {\n      // After using \"p\" or \"P\" in Visual mode the text that was put will be selected (from Vim's \":help gv\").\n      vimState.lastVisualSelection = {\n        mode,\n        start: replaceRange.start,\n        end: replaceRange.start.advancePositionByText(text),\n      };\n\n      if (this.overwritesRegisterWithSelection) {\n        vimState.recordedState.registerName = configuration.useSystemClipboard ? '*' : '\"';\n        Register.put(\n          vimState,\n          vimState.document.getText(replaceRange),\n          this.multicursorIndex,\n          true,\n        );\n      }\n    }\n\n    // Report lines changed\n    let numNewlinesAfterPut = text.split('\\n').length;\n    if (registerMode === RegisterMode.LineWise) {\n      numNewlinesAfterPut--;\n    }\n    reportLinesChanged(numNewlinesAfterPut, vimState);\n\n    const isLastCursor =\n      !vimState.isMultiCursor || vimState.cursors.length - 1 === this.multicursorIndex;\n    // Place the cursor back into normal mode after all puts are completed\n    if (isLastCursor) {\n      await vimState.setCurrentMode(Mode.Normal);\n    }\n  }\n\n  private getRegisterText(mode: Mode, register: IRegisterContent, count: number): string {\n    if (register.text instanceof RecordedState) {\n      return register.text.actionsRun\n        .map((action) => action.keysPressed.join(''))\n        .join('')\n        .repeat(count);\n    }\n\n    if (register.registerMode === RegisterMode.CharacterWise) {\n      return mode === Mode.VisualLine\n        ? Array(count).fill(register.text).join('\\n')\n        : register.text.repeat(count);\n    } else if (register.registerMode === RegisterMode.LineWise || mode === Mode.VisualLine) {\n      return Array(count).fill(register.text).join('\\n');\n    } else if (register.registerMode === RegisterMode.BlockWise) {\n      const lines = register.text.split('\\n');\n      const longestLength = Math.max(...lines.map((line) => line.length));\n      return lines\n        .map((line) => {\n          const space = longestLength - line.length;\n          const lineWithSpace = line + ' '.repeat(space);\n          return lineWithSpace.repeat(count - 1) + line;\n        })\n        .join('\\n');\n    } else {\n      throw new Error(`Unexpected RegisterMode ${register.registerMode}`);\n    }\n  }\n\n  private adjustIndent(lineToMatch: string, text: string): string {\n    const lines = text.split('\\n');\n\n    // Adjust indent to current line\n    const tabSize = configuration.tabstop; // TODO: Use `editor.options.tabSize`, I think\n    const indentationWidth = TextEditor.getIndentationLevel(lineToMatch, tabSize);\n    const firstLineIdentationWidth = TextEditor.getIndentationLevel(lines[0], tabSize);\n\n    return lines\n      .map((line) => {\n        const currentIdentationWidth = TextEditor.getIndentationLevel(line, tabSize);\n        const newIndentationWidth =\n          currentIdentationWidth - firstLineIdentationWidth + indentationWidth;\n\n        // TODO: Use `editor.options.insertSpaces`, I think\n        return TextEditor.setIndentationLevel(line, newIndentationWidth, configuration.expandtab);\n      })\n      .join('\\n');\n  }\n\n  private getTransformations(\n    document: TextDocument,\n    mode: Mode,\n    replaceRange: vscode.Range,\n    registerMode: RegisterMode,\n    text: string,\n  ): Transformation[] {\n    // Pasting block-wise content is very different, except in VisualLine mode, where it works exactly like line-wise\n    if (registerMode === RegisterMode.BlockWise && mode !== Mode.VisualLine) {\n      const transformations: Transformation[] = [];\n      const lines = text.split('\\n');\n      const lineCount = Math.max(lines.length, replaceRange.end.line - replaceRange.start.line + 1);\n      const longestLength = Math.max(...lines.map((line) => line.length));\n\n      // Only relevant for Visual mode\n      // If we replace 2 newlines, subsequent transformations need to take that into account (otherwise we get overlaps)\n      let deletedNewlines = 0;\n\n      for (let idx = 0; idx < lineCount; idx++) {\n        const lineText = lines[idx] ?? '';\n\n        let range: vscode.Range;\n        if (mode === Mode.VisualBlock) {\n          if (replaceRange.start.line + idx > replaceRange.end.line) {\n            const pos = replaceRange.start.with({ line: replaceRange.start.line + idx });\n            range = new vscode.Range(pos, pos);\n          } else {\n            range = new vscode.Range(\n              replaceRange.start.with({ line: replaceRange.start.line + idx }),\n              replaceRange.end.with({ line: replaceRange.start.line + idx }),\n            );\n          }\n        } else {\n          if (idx > 0) {\n            const pos = replaceRange.start.with({\n              line: replaceRange.start.line + idx + deletedNewlines,\n            });\n            range = new vscode.Range(pos, pos);\n          } else {\n            range = new vscode.Range(replaceRange.start, replaceRange.end);\n            deletedNewlines = document.getText(range).split('\\n').length - 1;\n          }\n        }\n\n        const lineNumber = replaceRange.start.line + idx;\n        if (lineNumber > document.lineCount - 1) {\n          transformations.push({\n            type: 'replaceText',\n            range,\n            text: '\\n' + ' '.repeat(replaceRange.start.character) + lineText,\n          });\n        } else {\n          const lineLength = document.lineAt(lineNumber).text.length;\n          const leftPadding = Math.max(replaceRange.start.character - lineLength, 0);\n          let rightPadding = 0;\n          if (\n            mode !== Mode.VisualBlock &&\n            ((lineNumber <= replaceRange.end.line && replaceRange.end.character < lineLength) ||\n              (lineNumber > replaceRange.end.line && replaceRange.start.character < lineLength))\n          ) {\n            rightPadding = longestLength - lineText.length;\n          }\n          transformations.push({\n            type: 'replaceText',\n            range,\n            text: ' '.repeat(leftPadding) + lineText + ' '.repeat(rightPadding),\n          });\n        }\n      }\n      return transformations;\n    }\n\n    if (mode === Mode.Normal || mode === Mode.Visual || mode === Mode.VisualLine) {\n      return [\n        {\n          type: 'replaceText',\n          range: replaceRange,\n          text,\n        },\n      ];\n    } else if (mode === Mode.VisualBlock) {\n      const transformations: Transformation[] = [];\n      if (registerMode === RegisterMode.CharacterWise) {\n        for (let line = replaceRange.start.line; line <= replaceRange.end.line; line++) {\n          const range = new vscode.Range(\n            new Position(line, replaceRange.start.character),\n            new Position(line, replaceRange.end.character),\n          );\n          const lineText = !text.includes('\\n') || line === replaceRange.start.line ? text : '';\n          transformations.push({\n            type: 'replaceText',\n            range,\n            text: lineText,\n          });\n        }\n      } else if (registerMode === RegisterMode.LineWise) {\n        // Weird case: first delete the block...\n        for (let line = replaceRange.start.line; line <= replaceRange.end.line; line++) {\n          const range = new vscode.Range(\n            new Position(line, replaceRange.start.character),\n            new Position(line, replaceRange.end.character),\n          );\n          transformations.push({\n            type: 'replaceText',\n            range,\n            text: '',\n          });\n        }\n\n        // ...then paste the lines before/after the block\n        const insertPos = this.putBefore()\n          ? new Position(replaceRange.start.line, 0)\n          : new Position(replaceRange.end.line, 0).getLineEnd();\n        transformations.push({\n          type: 'replaceText',\n          range: new vscode.Range(insertPos, insertPos),\n          text,\n        });\n      } else {\n        throw new Error(`Unexpected RegisterMode ${registerMode}`);\n      }\n      return transformations;\n    } else {\n      throw new Error(`Unexpected Mode ${mode}`);\n    }\n  }\n\n  protected abstract putBefore(): boolean;\n\n  protected abstract getRegisterMode(register: IRegisterContent): RegisterMode;\n\n  protected abstract getReplaceRange(\n    mode: Mode,\n    cursor: Cursor,\n    registerMode: RegisterMode,\n  ): vscode.Range;\n\n  protected abstract adjustLinewiseRegisterText(mode: Mode, text: string): string;\n\n  protected abstract shouldAdjustIndent(mode: Mode, registerMode: RegisterMode): boolean;\n\n  protected abstract getCursorPosition(params: GetCursorPositionParams): Position;\n}\n\n@RegisterAction\nclass PutCommand extends BasePutCommand {\n  keys: string[] | string[][] = ['p'];\n\n  protected putBefore(): boolean {\n    return false;\n  }\n\n  protected getRegisterMode(register: IRegisterContent): RegisterMode {\n    return register.registerMode;\n  }\n\n  protected getReplaceRange(mode: Mode, cursor: Cursor, registerMode: RegisterMode): vscode.Range {\n    if (mode === Mode.Normal) {\n      let pos: Position;\n      if (registerMode === RegisterMode.CharacterWise || registerMode === RegisterMode.BlockWise) {\n        pos = cursor.stop.getRight();\n      } else if (registerMode === RegisterMode.LineWise) {\n        pos = cursor.stop.getLineEnd();\n      } else {\n        throw new Error(`Unexpected RegisterMode ${registerMode}`);\n      }\n      return new vscode.Range(pos, pos);\n    } else if (mode === Mode.Visual) {\n      const [start, end] = sorted(cursor.start, cursor.stop);\n      return new vscode.Range(start, end.getRight());\n    } else if (mode === Mode.VisualLine) {\n      const [start, end] = sorted(cursor.start, cursor.stop);\n      return new vscode.Range(start.getLineBegin(), end.getLineEnd());\n    } else {\n      const [start, end] = sorted(cursor.start, cursor.stop);\n      return new vscode.Range(start, end.getRight());\n    }\n  }\n\n  protected adjustLinewiseRegisterText(mode: Mode, text: string): string {\n    if (mode === Mode.Normal || mode === Mode.VisualBlock) {\n      return '\\n' + text;\n    } else if (mode === Mode.Visual) {\n      return '\\n' + text + '\\n';\n    } else {\n      return text;\n    }\n  }\n\n  protected shouldAdjustIndent(mode: Mode, registerMode: RegisterMode): boolean {\n    return false;\n  }\n\n  protected getCursorPosition({\n    mode,\n    replaceRange,\n    registerMode,\n    text,\n    returnToInsertAfterCommand,\n  }: GetCursorPositionParams): Position {\n    const rangeStart = replaceRange.start;\n    if (mode === Mode.Normal || mode === Mode.Visual) {\n      if (registerMode === RegisterMode.CharacterWise) {\n        if (text.includes('\\n')) {\n          return rangeStart;\n        } else if (returnToInsertAfterCommand) {\n          return rangeStart.advancePositionByText(text);\n        } else {\n          return rangeStart.advancePositionByText(text).getLeft();\n        }\n      } else if (registerMode === RegisterMode.LineWise) {\n        return new Position(rangeStart.line + 1, firstNonBlankChar(text));\n      } else if (registerMode === RegisterMode.BlockWise) {\n        return rangeStart;\n      } else {\n        throw new Error(`Unexpected RegisterMode ${registerMode}`);\n      }\n    } else if (mode === Mode.VisualLine) {\n      return rangeStart.with({ character: firstNonBlankChar(text) });\n    } else if (mode === Mode.VisualBlock) {\n      if (registerMode === RegisterMode.LineWise) {\n        return new Position(replaceRange.end.line + 1, firstNonBlankChar(text));\n      } else if (registerMode === RegisterMode.BlockWise) {\n        return rangeStart;\n      } else {\n        return rangeStart.with({ character: rangeStart.character + text.length - 1 });\n      }\n    } else {\n      throw new Error(`Unexpected Mode ${mode}`);\n    }\n  }\n}\n\n@RegisterAction\nclass PutBeforeCommand extends PutCommand {\n  override keys: string[] | string[][] = ['P'];\n\n  // Since Vim 9.0, Visual `P` does not overwrite the unnamed register with selection's contents\n  override overwritesRegisterWithSelection = false;\n\n  protected override putBefore(): boolean {\n    return true;\n  }\n\n  protected override adjustLinewiseRegisterText(mode: Mode, text: string): string {\n    if (mode === Mode.Normal || mode === Mode.VisualBlock) {\n      return text + '\\n';\n    }\n\n    return super.adjustLinewiseRegisterText(mode, text);\n  }\n\n  protected override getReplaceRange(\n    mode: Mode,\n    cursor: Cursor,\n    registerMode: RegisterMode,\n  ): vscode.Range {\n    if (mode === Mode.Normal) {\n      if (registerMode === RegisterMode.CharacterWise || registerMode === RegisterMode.BlockWise) {\n        const pos = cursor.stop;\n        return new vscode.Range(pos, pos);\n      } else if (registerMode === RegisterMode.LineWise) {\n        const pos = cursor.stop.getLineBegin();\n        return new vscode.Range(pos, pos);\n      }\n    }\n\n    return super.getReplaceRange(mode, cursor, registerMode);\n  }\n\n  protected override getCursorPosition({\n    mode,\n    replaceRange,\n    text,\n    registerMode,\n    ...params\n  }: GetCursorPositionParams): Position {\n    const rangeStart = replaceRange.start;\n    if (mode === Mode.Normal || mode === Mode.VisualBlock) {\n      if (registerMode === RegisterMode.LineWise) {\n        return rangeStart.with({ character: firstNonBlankChar(text) });\n      }\n    }\n\n    return super.getCursorPosition({ mode, replaceRange, text, registerMode, ...params });\n  }\n}\n\nfunction PlaceCursorAfterText<TBase extends new (...args: any[]) => PutCommand>(Base: TBase) {\n  return class CursorAfterText extends Base {\n    protected override getCursorPosition({\n      document,\n      mode,\n      replaceRange,\n      registerMode,\n      count,\n      text,\n      ...params\n    }: GetCursorPositionParams): Position {\n      const rangeStart = replaceRange.start;\n      if (mode === Mode.Normal || mode === Mode.Visual) {\n        if (registerMode === RegisterMode.CharacterWise) {\n          if (text.includes('\\n')) {\n            // Weird case: if there's a newline, the cursor goes to the same place, regardless of [count]\n            // HACK: We're undoing the repeat() here - definitely a bit janky\n            text = text.slice(0, text.length / count);\n          }\n          return rangeStart.advancePositionByText(text);\n        } else if (registerMode === RegisterMode.LineWise) {\n          let line = rangeStart.line + text.split('\\n').length;\n          if (\n            mode === Mode.Visual ||\n            (!this.putBefore() && rangeStart.line < document.lineCount - 1)\n          ) {\n            line++;\n          }\n          return new Position(line, 0);\n        } else if (registerMode === RegisterMode.BlockWise) {\n          const lines = text.split('\\n');\n          const lastLine = rangeStart.line + lines.length - 1;\n          const longestLineLength = Math.max(...lines.map((line) => line.length));\n          return new Position(lastLine, rangeStart.character + longestLineLength);\n        }\n      } else if (mode === Mode.VisualLine) {\n        return new Position(rangeStart.line + text.split('\\n').length, 0);\n      } else if (mode === Mode.VisualBlock) {\n        const lines = text.split('\\n');\n        if (registerMode === RegisterMode.LineWise) {\n          if (this.putBefore()) {\n            return new Position(rangeStart.line + lines.length, 0);\n          } else {\n            return new Position(replaceRange.end.line + lines.length + 1, 0);\n          }\n        } else if (registerMode === RegisterMode.BlockWise) {\n          return new Position(\n            replaceRange.start.line + lines.length - 1,\n            replaceRange.start.character + lines[lines.length - 1].length,\n          );\n        } else {\n          return rangeStart.with({ character: rangeStart.character + text.length });\n        }\n      }\n\n      return super.getCursorPosition({\n        document,\n        mode,\n        replaceRange,\n        registerMode,\n        count,\n        text,\n        ...params,\n      });\n    }\n  };\n}\n\n@RegisterAction\n@PlaceCursorAfterText\nclass GPutCommand extends PutCommand {\n  override keys = ['g', 'p'];\n}\n\n@RegisterAction\n@PlaceCursorAfterText\nclass GPutBeforeCommand extends PutBeforeCommand {\n  override keys = ['g', 'P'];\n  override overwritesRegisterWithSelection = true;\n}\n\nfunction AdjustIndent<TBase extends new (...args: any[]) => PutCommand>(Base: TBase) {\n  return class AdjustedIndent extends Base {\n    protected override shouldAdjustIndent(mode: Mode, registerMode: RegisterMode): boolean {\n      return (\n        (mode === Mode.Normal || mode === Mode.VisualLine) && registerMode === RegisterMode.LineWise\n      );\n    }\n  };\n}\n\n@RegisterAction\n@AdjustIndent\nclass PutWithIndentCommand extends PutCommand {\n  override keys = [']', 'p'];\n}\n\n@RegisterAction\n@AdjustIndent\nclass PutBeforeWithIndentCommand extends PutBeforeCommand {\n  override keys = [\n    ['[', 'P'],\n    [']', 'P'],\n    ['[', 'p'],\n  ];\n}\n\nfunction ExCommand<TBase extends new (...args: any[]) => PutCommand>(Base: TBase) {\n  return class Ex extends Base {\n    private insertLine?: number;\n\n    public setInsertionLine(insertLine: number) {\n      this.insertLine = insertLine;\n    }\n\n    protected override getRegisterMode(register: IRegisterContent): RegisterMode {\n      return RegisterMode.LineWise;\n    }\n\n    protected override getReplaceRange(\n      mode: Mode,\n      cursor: Cursor,\n      registerMode: RegisterMode,\n    ): vscode.Range {\n      const line = this.insertLine ?? laterOf(cursor.start, cursor.stop).line;\n      const pos = this.putBefore() ? new Position(line, 0) : new Position(line, 0).getLineEnd();\n      return new vscode.Range(pos, pos);\n    }\n\n    protected override getCursorPosition({\n      replaceRange,\n      text,\n    }: GetCursorPositionParams): Position {\n      const lines = text.split('\\n');\n      return new Position(\n        replaceRange.start.line + lines.length - (this.putBefore() ? 1 : 0),\n        firstNonBlankChar(lines[lines.length - 1]),\n      );\n    }\n  };\n}\n\nexport const PutFromCmdLine = ExCommand(PutCommand);\nexport const PutBeforeFromCmdLine = ExCommand(PutBeforeCommand);\n"
  },
  {
    "path": "src/actions/commands/replace.ts",
    "content": "import { Position, Range } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { PositionDiff, sorted } from '../../common/motion/position';\nimport { DotCommandStatus, Mode, visualBlockGetTopLeftPosition } from '../../mode/mode';\nimport { ModeDataFor } from '../../mode/modeData';\nimport { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\nimport { BaseCommand, RegisterAction } from '../base';\nimport { BaseMovement } from '../baseMotion';\nimport { MoveDown, MoveLeft, MoveRight, MoveUp } from '../motion';\n\n@RegisterAction\nexport class ReplaceCharacter extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['r', '<character>'];\n  override createsUndoPoint = true;\n  override runsOnceForEachCountPrefix = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const timesToRepeat = vimState.recordedState.count || 1;\n    const toReplace = this.keysPressed[1];\n\n    /**\n     * <character> includes <BS>, <S-BS> and <TAB> but not any control keys,\n     * so we ignore the former two keys and have a special handle for <tab>.\n     */\n\n    if (['<BS>', '<S-BS>'].includes(toReplace.toUpperCase())) {\n      return;\n    }\n\n    if (position.character + timesToRepeat > position.getLineEnd().character) {\n      return;\n    }\n\n    let endPos = new Position(position.line, position.character + timesToRepeat);\n\n    // Return if tried to repeat longer than linelength\n    if (endPos.character > vimState.document.lineAt(endPos).text.length) {\n      return;\n    }\n\n    // If last char (not EOL char), add 1 so that replace selection is complete\n    if (endPos.character > vimState.document.lineAt(endPos).text.length) {\n      endPos = new Position(endPos.line, endPos.character + 1);\n    }\n\n    if (toReplace === '<tab>') {\n      vimState.recordedState.transformer.delete(new Range(position, endPos));\n      vimState.recordedState.transformer.vscodeCommand('tab');\n      vimState.recordedState.transformer.moveCursor(\n        PositionDiff.offset({ character: -1 }),\n        this.multicursorIndex,\n      );\n    } else if (toReplace === '\\n') {\n      // A newline replacement always inserts exactly one newline (regardless\n      // of count prefix) and puts the cursor on the next line.\n      // We use `insertTextVSCode` so we get the right indentation\n      vimState.recordedState.transformer.delete(new Range(position, endPos));\n      vimState.recordedState.transformer.addTransformation({\n        type: 'insertTextVSCode',\n        text: '\\n',\n      });\n    } else {\n      vimState.recordedState.transformer.addTransformation({\n        type: 'replaceText',\n        text: toReplace.repeat(timesToRepeat),\n        range: new Range(position, endPos),\n        diff: PositionDiff.offset({ character: timesToRepeat - 1 }),\n        manuallySetCursorPositions:\n          vimState.dotCommandStatus === DotCommandStatus.Executing ? true : undefined,\n      });\n    }\n  }\n}\n\n@RegisterAction\nclass ReplaceCharacterVisual extends BaseCommand {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['r', '<character>'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let toInsert = this.keysPressed[1];\n\n    if (toInsert === '<tab>') {\n      toInsert = TextEditor.getTabCharacter(vimState.editor);\n    }\n\n    let visualSelectionOffset = 1;\n\n    // If selection is reversed, reorganize it so that the text replace logic always works\n    let [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n    if (vimState.currentMode === Mode.VisualLine) {\n      [start, end] = [start.getLineBegin(), end.getLineEnd()];\n    }\n\n    // Limit to not replace EOL\n    const textLength = vimState.document.lineAt(end).text.length;\n    if (textLength <= 0) {\n      visualSelectionOffset = 0;\n    }\n    end = new Position(end.line, Math.min(end.character, textLength > 0 ? textLength - 1 : 0));\n\n    // Iterate over every line in the current selection\n    for (let lineNum = start.line; lineNum <= end.line; lineNum++) {\n      // Get line of text\n      const lineText = vimState.document.lineAt(lineNum).text;\n\n      if (start.line === end.line) {\n        // This is a visual section all on one line, only replace the part within the selection\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: Array(end.character - start.character + 2).join(toInsert),\n          range: new Range(start, new Position(end.line, end.character + 1)),\n          manuallySetCursorPositions: true,\n        });\n      } else if (lineNum === start.line) {\n        // This is the first line of the selection so only replace after the cursor\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: Array(lineText.length - start.character + 1).join(toInsert),\n          range: new Range(start, new Position(start.line, lineText.length)),\n          manuallySetCursorPositions: true,\n        });\n      } else if (lineNum === end.line) {\n        // This is the last line of the selection so only replace before the cursor\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: Array(end.character + 1 + visualSelectionOffset).join(toInsert),\n          range: new Range(\n            new Position(end.line, 0),\n            new Position(end.line, end.character + visualSelectionOffset),\n          ),\n          manuallySetCursorPositions: true,\n        });\n      } else {\n        // Replace the entire line length since it is in the middle of the selection\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: Array(lineText.length + 1).join(toInsert),\n          range: new Range(new Position(lineNum, 0), new Position(lineNum, lineText.length)),\n          manuallySetCursorPositions: true,\n        });\n      }\n    }\n\n    vimState.cursorStopPosition = start;\n    vimState.cursorStartPosition = start;\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass ReplaceCharacterVisualBlock extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = ['r', '<character>'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    let toInsert = this.keysPressed[1];\n\n    if (toInsert === '<tab>') {\n      toInsert = TextEditor.getTabCharacter(vimState.editor);\n    }\n\n    for (const { start, end } of TextEditor.iterateLinesInBlock(vimState)) {\n      if (end.isBeforeOrEqual(start)) {\n        continue;\n      }\n\n      vimState.recordedState.transformer.addTransformation({\n        type: 'replaceText',\n        text: Array(end.character - start.character + 1).join(toInsert),\n        range: new Range(start, end),\n        manuallySetCursorPositions: true,\n      });\n    }\n\n    vimState.cursors = [\n      Cursor.atPosition(\n        visualBlockGetTopLeftPosition(vimState.cursorStopPosition, vimState.cursorStartPosition),\n      ),\n    ];\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nexport class EnterReplaceMode extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['R'];\n\n  public override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Replace);\n  }\n}\n\n@RegisterAction\nclass ExitReplaceMode extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = [['<Esc>'], ['<C-c>'], ['<C-[>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.modeData.mode !== Mode.Replace) {\n      throw new Error(`Unexpected mode ${vimState.modeData.mode} in ExitReplaceMode`);\n    }\n\n    const timesToRepeat = vimState.modeData.replaceState.timesToRepeat;\n\n    const cursorIdx = this.multicursorIndex ?? 0;\n    const changes = vimState.modeData.replaceState.getChanges(cursorIdx);\n\n    // `3Rabc` results in 'abc' replacing the next characters 2 more times\n    if (changes && timesToRepeat > 1) {\n      const newText = changes\n        .map((change) => change.after)\n        .join('')\n        .repeat(timesToRepeat - 1);\n      vimState.recordedState.transformer.replace(\n        new Range(position, position.getRight(newText.length)),\n        newText,\n      );\n    } else {\n      vimState.cursorStopPosition = vimState.cursorStopPosition.getLeft();\n    }\n\n    if (this.multicursorIndex === vimState.cursors.length - 1) {\n      await vimState.setCurrentMode(Mode.Normal);\n    }\n  }\n}\n\n@RegisterAction\nclass ReplaceModeToInsertMode extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = ['<Insert>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n}\n\n@RegisterAction\nclass BackspaceInReplaceMode extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = [['<BS>'], ['<S-BS>'], ['<C-BS>'], ['<C-h>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.modeData.mode !== Mode.Replace) {\n      throw new Error(`Unexpected mode ${vimState.modeData.mode} in BackspaceInReplaceMode`);\n    }\n\n    const cursorIdx = this.multicursorIndex ?? 0;\n    const changes = vimState.modeData.replaceState.getChanges(cursorIdx);\n\n    if (changes.length === 0) {\n      // If you backspace before the beginning of where you started to replace, just move the cursor back.\n      const newPosition = position.getLeftThroughLineBreaks();\n\n      vimState.modeData.replaceState.resetChanges(cursorIdx);\n\n      vimState.cursorStopPosition = newPosition;\n      vimState.cursorStartPosition = newPosition;\n    } else {\n      const { before } = changes.pop()!;\n      if (before === '') {\n        // We've gone beyond the originally existing text; just backspace.\n        // TODO: should this use a 'deleteLeft' transformation?\n        vimState.recordedState.transformer.delete(\n          new Range(position.getLeftThroughLineBreaks(), position),\n        );\n      } else {\n        vimState.recordedState.transformer.replace(\n          new Range(position.getLeft(), position),\n          before,\n          PositionDiff.offset({ character: -1 }),\n        );\n      }\n    }\n  }\n}\n\n@RegisterAction\nclass DeleteInReplaceMode extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = ['<Del>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.vscodeCommand('deleteRight');\n  }\n}\n\n@RegisterAction\nclass ReplaceInReplaceMode extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = ['<character>'];\n  override createsUndoPoint = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.modeData.mode !== Mode.Replace) {\n      throw new Error(`Unexpected mode ${vimState.modeData.mode} in ReplaceInReplaceMode`);\n    }\n\n    const char = this.keysPressed[0];\n    const isNewLineOrTab = char === '\\n' || char === '<tab>';\n\n    const replaceRange = new Range(position, position.getRight());\n\n    let before = vimState.document.getText(replaceRange);\n    if (!position.isLineEnd(vimState.document) && !isNewLineOrTab) {\n      vimState.recordedState.transformer.replace(\n        replaceRange,\n        char,\n        PositionDiff.offset({ character: 1 }),\n      );\n    } else if (char === '<tab>') {\n      vimState.recordedState.transformer.delete(replaceRange);\n      vimState.recordedState.transformer.vscodeCommand('tab');\n    } else {\n      vimState.recordedState.transformer.insert(position, char);\n      before = '';\n    }\n\n    vimState.modeData.replaceState.getChanges(this.multicursorIndex ?? 0).push({\n      before,\n      after: char,\n    });\n  }\n}\n\n@RegisterAction\nclass CreateUndoPoint extends BaseCommand {\n  modes = [Mode.Replace];\n  keys = ['<C-g>', 'u'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.historyTracker.addChange(true);\n    vimState.historyTracker.finishCurrentStep();\n  }\n}\n\n@RegisterAction\nclass ArrowsInReplaceMode extends BaseMovement {\n  override modes = [Mode.Replace];\n  keys = [['<up>'], ['<down>'], ['<left>'], ['<right>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    // Force an undo point to be created\n    vimState.historyTracker.addChange(true);\n    vimState.historyTracker.finishCurrentStep();\n\n    let newPosition: Position = position;\n    switch (this.keysPressed[0]) {\n      case '<up>':\n        newPosition = await new MoveUp(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<down>':\n        newPosition = await new MoveDown(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<left>':\n        newPosition = await new MoveLeft(this.keysPressed).execAction(position, vimState);\n        break;\n      case '<right>':\n        newPosition = await new MoveRight(this.keysPressed).execAction(position, vimState);\n        break;\n      default:\n        throw new Error(`Unexpected 'arrow' key: ${this.keys[0]}`);\n    }\n    (vimState.modeData as ModeDataFor<Mode.Replace>).replaceState.resetChanges(\n      this.multicursorIndex ?? 0,\n    );\n    return newPosition;\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/scroll.ts",
    "content": "import { clamp } from 'lodash';\nimport * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { Mode, isVisualMode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { EditorScrollByUnit, EditorScrollDirection, TextEditor } from '../../textEditor';\nimport { BaseCommand, RegisterAction } from '../base';\n\nabstract class CommandEditorScroll extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override runsOnceForEachCountPrefix = false;\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  abstract to: EditorScrollDirection;\n  abstract by: EditorScrollByUnit;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const timesToRepeat = vimState.recordedState.count || 1;\n    const scrolloff = configuration\n      .getConfiguration('editor')\n      .get<number>('cursorSurroundingLines', 0);\n\n    const visibleRange = vimState.editor.visibleRanges[0];\n    if (visibleRange === undefined) {\n      return;\n    }\n\n    const linesAboveCursor =\n      visibleRange.end.line - vimState.cursorStopPosition.line - timesToRepeat;\n    const linesBelowCursor =\n      vimState.cursorStopPosition.line - visibleRange.start.line - timesToRepeat;\n    if (this.to === 'up' && scrolloff > linesAboveCursor) {\n      vimState.cursorStopPosition = vimState.cursorStopPosition\n        .getUp(scrolloff - linesAboveCursor)\n        .withColumn(vimState.desiredColumn);\n    } else if (this.to === 'down' && scrolloff > linesBelowCursor) {\n      vimState.cursorStopPosition = vimState.cursorStopPosition\n        .getDown(scrolloff - linesBelowCursor)\n        .withColumn(vimState.desiredColumn);\n    }\n\n    vimState.postponedCodeViewChanges.push({\n      command: 'editorScroll',\n      args: {\n        to: this.to,\n        by: this.by,\n        value: timesToRepeat,\n        select: isVisualMode(vimState.currentMode),\n      },\n    });\n  }\n}\n\n@RegisterAction\nclass CommandCtrlE extends CommandEditorScroll {\n  keys = ['<C-e>'];\n  override preservesDesiredColumn = true;\n  to: EditorScrollDirection = 'down';\n  by: EditorScrollByUnit = 'line';\n}\n\n@RegisterAction\nclass CommandCtrlY extends CommandEditorScroll {\n  keys = ['<C-y>'];\n  override preservesDesiredColumn = true;\n  to: EditorScrollDirection = 'up';\n  by: EditorScrollByUnit = 'line';\n}\n\n/**\n * Commands like `<C-d>` and `<C-f>` act *sort* of like `<count><C-e>`, but they move\n * your cursor down and put it on the first non-whitespace character of the line.\n */\nabstract class CommandScrollAndMoveCursor extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override runsOnceForEachCountPrefix = false;\n  abstract to: EditorScrollDirection;\n  /** if true, set scroll option instead of repeating command */\n  setScroll = false;\n\n  /**\n   * @returns the number of lines this command should move the cursor\n   */\n  protected abstract getNumLines(visibleRanges: readonly vscode.Range[]): number;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const { visibleRanges } = vimState.editor;\n    if (visibleRanges.length === 0) {\n      return;\n    }\n\n    const smoothScrolling = configuration\n      .getConfiguration('editor')\n      .get<boolean>('smoothScrolling', false);\n\n    if (this.setScroll && vimState.recordedState.count)\n      configuration.scroll = vimState.recordedState.count;\n    const timesToRepeat = (!this.setScroll && vimState.recordedState.count) || 1;\n    const moveLines = timesToRepeat * this.getNumLines(visibleRanges);\n\n    let scrollLines = moveLines;\n    if (this.to === 'down') {\n      // This makes <C-d> less wonky when `editor.scrollBeyondLastLine` is enabled\n      scrollLines = Math.min(\n        moveLines,\n        vimState.document.lineCount - 1 - visibleRanges[visibleRanges.length - 1].end.line,\n      );\n    }\n\n    if (scrollLines > 0) {\n      const args = {\n        to: this.to,\n        by: 'line',\n        value: scrollLines,\n        revealCursor: smoothScrolling,\n        select: isVisualMode(vimState.currentMode),\n      };\n      if (smoothScrolling) {\n        await vscode.commands.executeCommand('editorScroll', args);\n      } else {\n        vimState.postponedCodeViewChanges.push({\n          command: 'editorScroll',\n          args,\n        });\n      }\n    }\n\n    const newPositionLine = clamp(\n      position.line + (this.to === 'down' ? moveLines : -moveLines),\n      0,\n      vimState.document.lineCount - 1,\n    );\n    vimState.cursorStopPosition = new Position(\n      newPositionLine,\n      vimState.desiredColumn,\n    ).obeyStartOfLine(vimState.document);\n  }\n}\n\n@RegisterAction\nclass CommandMoveFullPageUp extends CommandScrollAndMoveCursor {\n  keys = ['<C-b>'];\n  to: EditorScrollDirection = 'up';\n\n  protected getNumLines(visibleRanges: vscode.Range[]) {\n    return visibleRanges[0].end.line - visibleRanges[0].start.line;\n  }\n}\n\n@RegisterAction\nclass CommandMoveFullPageDown extends CommandScrollAndMoveCursor {\n  keys = ['<C-f>'];\n  to: EditorScrollDirection = 'down';\n\n  protected getNumLines(visibleRanges: vscode.Range[]) {\n    return visibleRanges[0].end.line - visibleRanges[0].start.line;\n  }\n}\n\n@RegisterAction\nclass CommandCtrlD extends CommandScrollAndMoveCursor {\n  keys = ['<C-d>'];\n  to: EditorScrollDirection = 'down';\n  override setScroll = true;\n\n  protected getNumLines(visibleRanges: vscode.Range[]) {\n    return configuration.getScrollLines(visibleRanges);\n  }\n}\n\n@RegisterAction\nclass CommandCtrlU extends CommandScrollAndMoveCursor {\n  keys = ['<C-u>'];\n  to: EditorScrollDirection = 'up';\n  override setScroll = true;\n\n  protected getNumLines(visibleRanges: vscode.Range[]) {\n    return configuration.getScrollLines(visibleRanges);\n  }\n}\n\n@RegisterAction\nclass CommandCenterScroll extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'z'];\n\n  override preservesDesiredColumn = true;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // In these modes you want to center on the cursor position\n    vimState.editor.revealRange(\n      new vscode.Range(vimState.cursorStopPosition, vimState.cursorStopPosition),\n      vscode.TextEditorRevealType.InCenter,\n    );\n  }\n}\n\n@RegisterAction\nclass CommandCenterScrollFirstChar extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', '.'];\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // In these modes you want to center on the cursor position\n    // This particular one moves cursor to first non blank char though\n    vimState.editor.revealRange(\n      new vscode.Range(vimState.cursorStopPosition, vimState.cursorStopPosition),\n      vscode.TextEditorRevealType.InCenter,\n    );\n\n    // Move cursor to first char of line\n    vimState.cursorStopPosition = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      vimState.cursorStopPosition.line,\n    );\n  }\n}\n\n@RegisterAction\nclass CommandTopScroll extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 't'];\n\n  override preservesDesiredColumn = true;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'revealLine',\n      args: {\n        lineNumber: position.line,\n        at: 'top',\n      },\n    });\n  }\n}\n\n@RegisterAction\nclass CommandTopScrollFirstChar extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', '\\n'];\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // In these modes you want to center on the cursor position\n    // This particular one moves cursor to first non blank char though\n    vimState.postponedCodeViewChanges.push({\n      command: 'revealLine',\n      args: {\n        lineNumber: position.line,\n        at: 'top',\n      },\n    });\n\n    // Move cursor to first char of line\n    vimState.cursorStopPosition = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      vimState.cursorStopPosition.line,\n    );\n  }\n}\n\n@RegisterAction\nclass CommandBottomScroll extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'b'];\n\n  override preservesDesiredColumn = true;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'revealLine',\n      args: {\n        lineNumber: position.line,\n        at: 'bottom',\n      },\n    });\n  }\n}\n\n@RegisterAction\nclass CommandBottomScrollFirstChar extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', '-'];\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // In these modes you want to center on the cursor position\n    // This particular one moves cursor to first non blank char though\n    vimState.postponedCodeViewChanges.push({\n      command: 'revealLine',\n      args: {\n        lineNumber: position.line,\n        at: 'bottom',\n      },\n    });\n\n    // Move cursor to first char of line\n    vimState.cursorStopPosition = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      vimState.cursorStopPosition.line,\n    );\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/search.ts",
    "content": "import * as _ from 'lodash';\nimport { escapeRegExp } from 'lodash';\nimport { Position, Selection } from 'vscode';\nimport { SearchCommandLine } from '../../cmd_line/commandLine';\nimport { sorted } from '../../common/motion/position';\nimport { configuration } from '../../configuration/configuration';\nimport { VimError } from '../../error';\nimport { Mode, isVisualMode } from '../../mode/mode';\nimport { Register } from '../../register/register';\nimport { globalState } from '../../state/globalState';\nimport { SearchState } from '../../state/searchState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { TextEditor } from '../../textEditor';\nimport { TextObject } from '../../textobject/textobject';\nimport { reportSearch } from '../../util/statusBarTextUtils';\nimport { SearchDirection } from '../../vimscript/pattern';\nimport { BaseCommand, RegisterAction } from '../base';\nimport { IMovement, failedMovement } from '../baseMotion';\n\n/**\n * Search for the word under the cursor; used by [g]* and [g]#\n */\nasync function searchCurrentWord(\n  position: Position,\n  vimState: VimState,\n  direction: SearchDirection,\n  isExact: boolean,\n): Promise<void> {\n  let currentWord = TextEditor.getWord(vimState.document, position);\n\n  if (currentWord) {\n    if (/\\W/.test(currentWord[0]) || /\\W/.test(currentWord[currentWord.length - 1])) {\n      // TODO: this kind of sucks. JS regex does not consider the boundary between a special\n      // character and whitespace to be a \"word boundary\", so we can't easily do an exact search.\n      isExact = false;\n    }\n\n    if (isExact) {\n      currentWord = _.escapeRegExp(currentWord);\n    }\n    // If the search is going left then use `getWordLeft()` on position to start\n    // at the beginning of the word. This ensures that any matches happen\n    // outside of the currently selected word.\n    const searchStartCursorPosition =\n      direction === SearchDirection.Backward\n        ? vimState.cursorStopPosition.prevWordStart(vimState.document, { inclusive: true })\n        : vimState.cursorStopPosition;\n\n    await createSearchStateAndMoveToMatch({\n      needle: currentWord,\n      vimState,\n      direction,\n      isExact,\n      searchStartCursorPosition,\n    });\n  } else {\n    StatusBar.displayError(vimState, VimError.NoStringUnderCursor());\n  }\n}\n\n/**\n * Search for the word under the cursor; used by [g]* and [g]# in visual mode when `visualstar` is enabled\n */\nasync function searchCurrentSelection(vimState: VimState, direction: SearchDirection) {\n  const currentSelection = vimState.document.getText(vimState.editor.selection);\n\n  // Go back to Normal mode, otherwise the selection grows to the next match.\n  await vimState.setCurrentMode(Mode.Normal);\n\n  const [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition);\n\n  // Ensure that any matches happen outside of the currently selected word.\n  const searchStartCursorPosition =\n    direction === SearchDirection.Backward ? start.getLeft() : end.getRight();\n\n  await createSearchStateAndMoveToMatch({\n    needle: currentSelection,\n    vimState,\n    direction,\n    isExact: false,\n    searchStartCursorPosition,\n  });\n}\n\n/**\n * Used by [g]* and [g]#\n */\nasync function createSearchStateAndMoveToMatch(args: {\n  needle: string;\n  vimState: VimState;\n  direction: SearchDirection;\n  isExact: boolean;\n  searchStartCursorPosition: Position;\n}): Promise<void> {\n  const { needle, vimState, isExact } = args;\n\n  if (needle.length === 0) {\n    return;\n  }\n\n  const escapedNeedle = escapeRegExp(needle).replaceAll('/', '\\\\/');\n  const searchString = isExact ? `\\\\<${escapedNeedle}\\\\>` : escapedNeedle;\n\n  // Start a search for the given term.\n  globalState.searchState = new SearchState(\n    args.direction,\n    vimState.cursorStopPosition,\n    searchString,\n    { ignoreSmartcase: true },\n  );\n  Register.setReadonlyRegister('/', globalState.searchState.searchString);\n  void SearchCommandLine.addSearchStateToHistory(globalState.searchState);\n\n  // Turn one of the highlighting flags back on (turned off with :nohl)\n  globalState.hl = true;\n\n  const nextMatch = globalState.searchState.getNextSearchMatchPosition(\n    vimState,\n    args.searchStartCursorPosition,\n  );\n  if (nextMatch) {\n    vimState.cursorStopPosition = nextMatch.pos;\n\n    reportSearch(\n      nextMatch.index,\n      globalState.searchState.getMatchRanges(vimState).length,\n      vimState,\n    );\n  } else {\n    StatusBar.displayError(\n      vimState,\n      args.direction === SearchDirection.Forward\n        ? VimError.SearchHitBottom(globalState.searchState.searchString)\n        : VimError.SearchHitTop(globalState.searchState.searchString),\n    );\n  }\n}\n\n@RegisterAction\nclass SearchCurrentWordExactForward extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['*'];\n  override actionType = 'motion' as const;\n  override runsOnceForEachCountPrefix = true;\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (isVisualMode(vimState.currentMode) && configuration.visualstar) {\n      await searchCurrentSelection(vimState, SearchDirection.Forward);\n    } else {\n      await searchCurrentWord(position, vimState, SearchDirection.Forward, true);\n    }\n  }\n}\n\n@RegisterAction\nclass SearchCurrentWordForward extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['g', '*'];\n  override actionType = 'motion' as const;\n  override runsOnceForEachCountPrefix = true;\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await searchCurrentWord(position, vimState, SearchDirection.Forward, false);\n  }\n}\n\n@RegisterAction\nclass SearchCurrentWordExactBackward extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['#'];\n  override actionType = 'motion' as const;\n  override runsOnceForEachCountPrefix = true;\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (isVisualMode(vimState.currentMode) && configuration.visualstar) {\n      await searchCurrentSelection(vimState, SearchDirection.Backward);\n    } else {\n      await searchCurrentWord(position, vimState, SearchDirection.Backward, true);\n    }\n  }\n}\n\n@RegisterAction\nclass SearchCurrentWordBackward extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['g', '#'];\n  override actionType = 'motion' as const;\n  override runsOnceForEachCountPrefix = true;\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await searchCurrentWord(position, vimState, SearchDirection.Backward, false);\n  }\n}\n\n@RegisterAction\nclass SearchForwards extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['/'];\n  override actionType = 'motion' as const;\n  override isJump = true;\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.SearchInProgressMode);\n  }\n}\n\n@RegisterAction\nclass SearchBackwards extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['?'];\n  override actionType = 'motion' as const;\n  override isJump = true;\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // TODO: Better VimState API than this...\n    await vimState.setModeData({\n      mode: Mode.SearchInProgressMode,\n      commandLine: new SearchCommandLine(vimState, '', SearchDirection.Backward),\n      firstVisibleLineBeforeSearch: vimState.editor.visibleRanges[0].start.line,\n    });\n  }\n}\n\nabstract class SearchObject extends TextObject {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n  protected abstract readonly direction: SearchDirection;\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    const searchState = globalState.searchState;\n    if (!searchState || searchState.searchString === '') {\n      return failedMovement(vimState);\n    }\n\n    const newSearchState = new SearchState(\n      this.direction,\n      vimState.cursorStopPosition,\n      searchState.searchString,\n      {},\n    );\n\n    // At first, try to search for current word, and stop searching if matched.\n    // Try to search for the next word if not matched or\n    // if the cursor is at the end of a match string in visual-mode.\n    let result = newSearchState.findContainingMatchRange(vimState, vimState.cursorStopPosition);\n    if (\n      result &&\n      vimState.currentMode === Mode.Visual &&\n      vimState.cursorStopPosition.isEqual(result.range.end.getLeftThroughLineBreaks())\n    ) {\n      result = undefined;\n    }\n\n    if (result === undefined) {\n      // Try to search for the next word\n      result = newSearchState.getNextSearchMatchRange(vimState, vimState.cursorStopPosition);\n      if (result === undefined) {\n        return failedMovement(vimState);\n      }\n    }\n\n    reportSearch(result.index, searchState.getMatchRanges(vimState).length, vimState);\n\n    const [start, stop] = [\n      vimState.currentMode === Mode.Normal ? result.range.start : vimState.cursorStopPosition,\n      result.range.end.getLeftThroughLineBreaks(),\n    ];\n\n    // Move the cursor, this is a bit hacky...\n    vimState.cursorStartPosition = start;\n    vimState.cursorStopPosition = stop;\n    vimState.editor.selection = new Selection(start, stop);\n\n    await vimState.setCurrentMode(Mode.Visual);\n\n    return {\n      start,\n      stop,\n    };\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<IMovement> {\n    return this.execAction(position, vimState);\n  }\n}\n\n@RegisterAction\nclass SearchObjectForward extends SearchObject {\n  keys = ['g', 'n'];\n  direction = SearchDirection.Forward;\n}\n\n@RegisterAction\nclass SearchObjectBackward extends SearchObject {\n  keys = ['g', 'N'];\n  direction = SearchDirection.Backward;\n}\n"
  },
  {
    "path": "src/actions/commands/undo.ts",
    "content": "import { Position } from 'vscode';\nimport { Mode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nexport class Undo extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['u'];\n  // we support a count to undo by this setting\n  override runsOnceForEachCountPrefix = true;\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.historyTracker.goBackHistoryStep();\n  }\n}\n\n@RegisterAction\nclass UndoOnLine extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['U'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.historyTracker.goBackHistoryStepsOnLine();\n  }\n}\n\n@RegisterAction\nexport class Redo extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['<C-r>'];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.historyTracker.goForwardHistoryStep();\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/visual.ts",
    "content": "import { Position } from 'vscode';\nimport { Mode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass EnterVisualMode extends BaseCommand {\n  modes = [Mode.Normal, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['v'];\n  override isCompleteAction = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.currentMode === Mode.Normal && vimState.recordedState.count > 1) {\n      vimState.cursorStopPosition = position.getRight(vimState.recordedState.count - 1);\n    }\n    await vimState.setCurrentMode(Mode.Visual);\n  }\n}\n\n@RegisterAction\nclass ExitVisualMode extends BaseCommand {\n  modes = [Mode.Visual];\n  keys = ['v'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass EnterVisualLineMode extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n  keys = ['V'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.currentMode === Mode.Normal && vimState.recordedState.count > 1) {\n      vimState.cursorStopPosition = position.getDown(vimState.recordedState.count - 1);\n    }\n    await vimState.setCurrentMode(Mode.VisualLine);\n  }\n}\n\n@RegisterAction\nclass ExitVisualLineMode extends BaseCommand {\n  modes = [Mode.VisualLine];\n  keys = ['V'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass EnterVisualBlockMode extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [['<C-v>'], ['<C-q>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.currentMode === Mode.Normal && vimState.recordedState.count > 1) {\n      vimState.cursorStopPosition = position.getRight(vimState.recordedState.count - 1);\n    }\n    await vimState.setCurrentMode(Mode.VisualBlock);\n  }\n}\n\n@RegisterAction\nclass ExitVisualBlockMode extends BaseCommand {\n  modes = [Mode.VisualBlock];\n  keys = [['<C-v>'], ['<C-q>']];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass RestoreVisualSelection extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = ['g', 'v'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (vimState.lastVisualSelection === undefined) {\n      return;\n    }\n\n    let { start, end, mode } = vimState.lastVisualSelection;\n    if (mode !== Mode.Visual || !start.isEqual(end)) {\n      if (end.line <= vimState.document.lineCount - 1) {\n        if (mode === Mode.Visual && start.isBefore(end)) {\n          end = end.getLeftThroughLineBreaks(true);\n        }\n\n        await vimState.setCurrentMode(mode);\n        vimState.cursorStartPosition = start;\n        vimState.cursorStopPosition = end;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/commands/window.ts",
    "content": "import { Position } from 'vscode';\nimport { OnlyCommand } from '../../cmd_line/commands/only';\nimport { QuitCommand } from '../../cmd_line/commands/quit';\nimport { TabCommand, TabCommandType } from '../../cmd_line/commands/tab';\nimport { WriteQuitCommand } from '../../cmd_line/commands/writequit';\nimport { Mode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { BaseCommand, RegisterAction } from '../base';\n\n@RegisterAction\nclass Quit extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [\n    ['<C-w>', 'q'],\n    ['<C-w>', '<C-q>'],\n    ['<C-w>', 'c'],\n    ['<C-w>', '<C-c>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    void new QuitCommand({}).execute(vimState);\n  }\n}\n\n@RegisterAction\nclass WriteQuit extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [['Z', 'Z']];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new WriteQuitCommand({ bang: false, opt: [] }).execute(vimState);\n  }\n}\n\n@RegisterAction\nclass ForceQuit extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [['Z', 'Q']];\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await new QuitCommand({ bang: true }).execute(vimState);\n  }\n}\n\n@RegisterAction\nclass Only extends BaseCommand {\n  modes = [Mode.Normal];\n  keys = [\n    ['<C-w>', 'o'],\n    ['<C-w>', '<C-o>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    void new OnlyCommand().execute(vimState);\n  }\n}\n\n@RegisterAction\nclass MoveToLeftPane extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 'h'],\n    ['<C-w>', '<left>'],\n    ['<C-w>', '<C-h>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.navigateLeft',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass MoveToRightPane extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 'l'],\n    ['<C-w>', '<right>'],\n    ['<C-w>', '<C-l>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.navigateRight',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass MoveToLowerPane extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 'j'],\n    ['<C-w>', '<down>'],\n    ['<C-w>', '<C-j>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.navigateDown',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass MoveToUpperPane extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 'k'],\n    ['<C-w>', '<up>'],\n    ['<C-w>', '<C-k>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.navigateUp',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass CycleThroughPanes extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', '<C-w>'],\n    ['<C-w>', 'w'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.navigateEditorGroups',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass VerticalSplit extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 'v'],\n    ['<C-w>', '<C-v>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.splitEditor',\n      args: undefined,\n    });\n  }\n}\n\n@RegisterAction\nclass OrthogonalSplit extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [\n    ['<C-w>', 's'],\n    ['<C-w>', '<C-s>'],\n  ];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.splitEditorOrthogonal',\n      args: undefined,\n    });\n  }\n}\n\n@RegisterAction\nclass EvenPaneWidths extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['<C-w>', '='];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.evenEditorWidths',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass IncreasePaneWidth extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['<C-w>', '>'];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.increaseViewWidth',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass DecreasePaneWidth extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['<C-w>', '<'];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.decreaseViewWidth',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass IncreasePaneHeight extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['<C-w>', '+'];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.increaseViewHeight',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass DecreasePaneHeight extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['<C-w>', '-'];\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    vimState.postponedCodeViewChanges.push({\n      command: 'workbench.action.decreaseViewHeight',\n      args: {},\n    });\n  }\n}\n\n@RegisterAction\nclass NextTab extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [['g', 't'], ['<C-pagedown>']];\n  override runsOnceForEachCountPrefix = false;\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // gt behaves differently than gT and goes to an absolute index tab\n    // (1-based), it does NOT iterate over next tabs\n    if (vimState.recordedState.count > 0) {\n      void new TabCommand({\n        type: TabCommandType.Edit,\n        buf: vimState.recordedState.count,\n      }).execute(vimState);\n    } else {\n      void new TabCommand({\n        type: TabCommandType.Next,\n        bang: false,\n      }).execute(vimState);\n    }\n  }\n}\n\n@RegisterAction\nclass PreviousTab extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = [['g', 'T'], ['<C-pageup>']];\n  override runsOnceForEachCountPrefix = true; // Yes, this is different from `{count}gt`\n  override runsOnceForEveryCursor(): boolean {\n    return false;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    void new TabCommand({\n      type: TabCommandType.Previous,\n      bang: false,\n    }).execute(vimState);\n  }\n}\n"
  },
  {
    "path": "src/actions/include-main.ts",
    "content": "import '../textobject/textobject';\nimport './base';\nimport './motion';\nimport './operator';\n\n// commands\nimport './commands/actions';\nimport './commands/commandLine';\nimport './commands/digraphs';\nimport './commands/documentChange';\nimport './commands/file';\nimport './commands/fold';\nimport './commands/incrementDecrement';\nimport './commands/insert';\nimport './commands/join';\nimport './commands/macro';\nimport './commands/navigate';\nimport './commands/put';\nimport './commands/replace';\nimport './commands/scroll';\nimport './commands/search';\nimport './commands/undo';\nimport './commands/visual';\nimport './commands/window';\n"
  },
  {
    "path": "src/actions/include-plugins.ts",
    "content": "// plugin\nimport './plugins/camelCaseMotion';\nimport './plugins/easymotion/easymotion.cmd';\nimport './plugins/easymotion/registerMoveActions';\nimport './plugins/replaceWithRegister';\nimport './plugins/sneak';\nimport './plugins/surround';\nimport './plugins/targets/targets';\n"
  },
  {
    "path": "src/actions/languages/python/motion.ts",
    "content": "import { Position, TextDocument } from 'vscode';\nimport { VimState } from '../../../state/vimState';\nimport { RegisterAction } from '../../base';\nimport { BaseMovement, failedMovement, IMovement } from '../../baseMotion';\n\ntype Type = 'function' | 'class';\ntype Edge = 'start' | 'end';\ntype Direction = 'next' | 'prev';\n\ninterface LineInfo {\n  line: number;\n  indentation: number;\n  text: string;\n}\n\ninterface StructureElement {\n  type: Type;\n  start: Position;\n  end: Position;\n}\n\n// Older browsers don't support lookbehind - in this case, use an inferior regex rather than crashing\nlet supportsLookbehind = true;\ntry {\n  new RegExp('(?<=x)');\n} catch {\n  supportsLookbehind = false;\n}\n\n/*\n * Utility class used to parse the lines in the document and\n * determine class and function boundaries\n *\n * The class keeps track of two positions: the ORIGINAL and the CURRENT\n * using their relative locations to make decisions.\n */\nexport class PythonDocument {\n  _document: TextDocument;\n  structure: StructureElement[];\n\n  static readonly reOnlyWhitespace = /\\S/;\n  static readonly reLastNonWhiteSpaceCharacter = supportsLookbehind\n    ? new RegExp('(?<=\\\\S)\\\\s*$')\n    : /(\\S)\\s*$/;\n  static readonly reDefOrClass = /^\\s*(?:async\\s+)?(def|class) /;\n\n  constructor(document: TextDocument) {\n    this._document = document;\n    const parsed = PythonDocument._parseLines(document);\n    this.structure = PythonDocument._parseStructure(parsed);\n  }\n\n  /*\n   * Generator of the lines of text in the document\n   */\n  static *lines(document: TextDocument): Generator<string> {\n    for (let index = 0; index < document.lineCount; index++) {\n      yield document.lineAt(index).text;\n    }\n  }\n\n  /*\n   * Calculate the indentation of a line of text.\n   * Lines consisting entirely of whitespace of \"starting\" with a comment are defined\n   * to have an indentation of \"undefined\".\n   */\n  static _indentation(line: string): number | undefined {\n    const index: number = line.search(PythonDocument.reOnlyWhitespace);\n\n    // Return undefined if line is empty, just whitespace, or starts with a comment\n    if (index === -1 || line[index] === '#') {\n      return undefined;\n    }\n\n    return index;\n  }\n\n  /*\n   * Parse a line of text to extract LineInfo\n   * Return undefined if the line is empty or starts with a comment\n   */\n  static _parseLine(index: number, text: string): LineInfo | undefined {\n    const indentation = this._indentation(text);\n\n    // Since indentation === 0 is a valid result we need to check for undefined explicitly\n    return indentation !== undefined ? { line: index, indentation, text } : undefined;\n  }\n\n  static _parseLines(document: TextDocument): LineInfo[] {\n    const lines = [...this.lines(document)]; // convert generator to Array\n    const infos = lines.map((text, index) => this._parseLine(index, text));\n\n    return infos.filter((x) => x) as LineInfo[]; // filter out empty/comment lines (undefined info)\n  }\n\n  static _parseStructure(lines: LineInfo[]): StructureElement[] {\n    const last = lines.length;\n    const structure: StructureElement[] = [];\n\n    for (let index = 0; index < last; index++) {\n      const info = lines[index];\n      const text = info.text;\n      const match = text.match(PythonDocument.reDefOrClass);\n\n      if (match) {\n        const type = match[1] === 'def' ? 'function' : 'class';\n\n        // Find the end of the current function/class\n        let idx = index + 1;\n\n        for (; idx < last; idx++) {\n          if (lines[idx].indentation <= info.indentation) {\n            break;\n          }\n        }\n\n        // Since we stop when we find the first line with a less indentation\n        // we pull back one line to get to the end of the function/class\n        idx--;\n\n        const endLine = lines[idx];\n\n        structure.push({\n          type,\n          start: new Position(info.line, info.indentation),\n          // Calculate position of last non-white character)\n          end: new Position(\n            endLine.line,\n            endLine.text.search(PythonDocument.reLastNonWhiteSpaceCharacter) - 1,\n          ),\n        });\n      }\n    }\n\n    return structure;\n  }\n\n  /*\n   * Find the position of the specified:\n   *    type: function or class\n   *    direction: next or prev\n   *    edge: start or end\n   *\n   * With this information one can determine all of the required motions\n   */\n  find(type: Type, direction: Direction, edge: Edge, position: Position): Position | undefined {\n    // Choose the ordering method name based on direction\n    const isDirection = direction === 'next' ? 'isAfter' : 'isBefore';\n\n    // Filter function for all elements whose \"edge\" is in the correct \"direction\"\n    // relative to the cursor's position, excluding the current function for prev direction\n    const dir = (element: StructureElement) => {\n      const pos = element[edge];\n      return direction === 'next' ? pos.isAfter(position) : pos.line < position.line; // For prev, we want strictly before\n    };\n\n    // Filter out elements from structure based on type and direction\n    const elements = this.structure.filter((elem) => elem.type === type).filter(dir);\n\n    if (edge === 'end') {\n      // When moving to an 'end' the elements should be started by the end position\n      elements.sort((a, b) => a.end.line - b.end.line);\n    }\n\n    // Return the first match if any exist\n    if (elements.length) {\n      // If direction === 'next' return the first element\n      // otherwise return the last element\n      const index = direction === 'next' ? 0 : elements.length - 1;\n      const element = elements[index];\n      const pos = element[edge];\n\n      // execAction MUST return a fully realized Position object created using new\n      return pos;\n    }\n\n    return undefined;\n  }\n\n  // Use PythonDocument instance to move to specified class boundary\n  static moveClassBoundary(\n    document: TextDocument,\n    position: Position,\n    vimState: VimState,\n    forward: boolean,\n    start: boolean,\n  ): Position | IMovement {\n    const direction = forward ? 'next' : 'prev';\n    const edge = start ? 'start' : 'end';\n\n    return (\n      new PythonDocument(document).find('class', direction, edge, position) ??\n      failedMovement(vimState)\n    );\n  }\n}\n\n// Uses the specified findFunction to execute the motion coupled to the shortcut (keys)\nabstract class BasePythonMovement extends BaseMovement {\n  abstract type: Type;\n  abstract direction: Direction;\n  abstract edge: Edge;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.document.languageId === 'python'\n    );\n  }\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const document = vimState.document;\n    return (\n      new PythonDocument(document).find(this.type, this.direction, this.edge, position) ??\n      failedMovement(vimState)\n    );\n  }\n}\n\n@RegisterAction\nclass MovePythonNextFunctionStart extends BasePythonMovement {\n  keys = [']', 'm'];\n  type: Type = 'function';\n  direction: Direction = 'next';\n  edge: Edge = 'start';\n}\n\n@RegisterAction\nclass MovePythonPrevFunctionStart extends BasePythonMovement {\n  keys = ['[', 'm'];\n  type: Type = 'function';\n  direction: Direction = 'prev';\n  edge: Edge = 'start';\n}\n\n@RegisterAction\nclass MovePythonNextFunctionEnd extends BasePythonMovement {\n  keys = [']', 'M'];\n  type: Type = 'function';\n  direction: Direction = 'next';\n  edge: Edge = 'end';\n}\n\n@RegisterAction\nclass MovePythonPrevFunctionEnd extends BasePythonMovement {\n  keys = ['[', 'M'];\n  type: Type = 'function';\n  direction: Direction = 'prev';\n  edge: Edge = 'end';\n}\n"
  },
  {
    "path": "src/actions/motion.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { sorted } from '../common/motion/position';\nimport { Notation } from '../configuration/notation';\nimport { VimError } from '../error';\nimport { globalState } from '../state/globalState';\nimport { StatusBar } from '../statusBar';\nimport { getCurrentParagraphBeginning, getCurrentParagraphEnd } from '../textobject/paragraph';\nimport { WordType } from '../textobject/word';\nimport { reportSearch } from '../util/statusBarTextUtils';\nimport { clamp, isHighSurrogate, isLowSurrogate } from '../util/util';\nimport { SearchDirection } from '../vimscript/pattern';\nimport { PairMatcher } from './../common/matching/matcher';\nimport { QuoteMatcher } from './../common/matching/quoteMatcher';\nimport { TagMatcher } from './../common/matching/tagMatcher';\nimport { configuration } from './../configuration/configuration';\nimport { isVisualMode, Mode } from './../mode/mode';\nimport { RegisterMode } from './../register/register';\nimport { VimState } from './../state/vimState';\nimport { CursorMoveByUnit, CursorMovePosition, TextEditor } from './../textEditor';\nimport { RegisterAction } from './base';\nimport { BaseMovement, failedMovement, IMovement, isIMovement, SelectionType } from './baseMotion';\nimport { PythonDocument } from './languages/python/motion';\nimport { ChangeOperator, DeleteOperator, YankOperator } from './operator';\nimport { SneakBackward, SneakForward } from './plugins/sneak';\nimport { SmartQuoteMatcher, WhichQuotes } from './plugins/targets/smartQuotesMatcher';\nimport { useSmartQuotes } from './plugins/targets/targetsConfig';\nimport { shouldWrapKey } from './wrapping';\n\nfunction adjustForDesiredColumn(args: {\n  position: Position;\n  desiredColumn: number;\n  multicursorIndex: number | undefined;\n}): Position {\n  const { position, desiredColumn, multicursorIndex } = args;\n  // HACK: until we put `desiredColumn` on `Cursor`, only the first cursor will respect it (except after `$`)\n  if (multicursorIndex && multicursorIndex > 0 && desiredColumn !== Number.POSITIVE_INFINITY) {\n    return position;\n  }\n  return position.with({ character: desiredColumn });\n}\n\n/**\n * A movement is something like 'h', 'k', 'w', 'b', 'gg', etc.\n */\n\nexport abstract class ExpandingSelection extends BaseMovement {\n  protected override selectionType = SelectionType.Expanding;\n\n  protected override adjustPosition(position: Position, result: IMovement, lastIteration: boolean) {\n    if (!lastIteration) {\n      position = result.stop;\n    }\n    return position;\n  }\n}\n\nabstract class MoveByScreenLine extends BaseMovement {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  abstract movementType: CursorMovePosition;\n  by?: CursorMoveByUnit;\n  value: number = 1;\n\n  public override async execAction(position: Position, vimState: VimState) {\n    return this.execActionWithCount(position, vimState, 1);\n  }\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    const multicursorIndex = this.multicursorIndex ?? 0;\n\n    if (multicursorIndex === 0) {\n      if (vimState.currentMode === Mode.Visual) {\n        // If we change the `vimState.editor.selections` directly with the forEach\n        // for some reason vscode doesn't update them. But doing it this way does\n        // update vscode's selections.\n        vimState.editor.selections = vimState.editor.selections.map((s, i) => {\n          if (s.active.isAfter(s.anchor)) {\n            // The selection is on the right side of the cursor, while our representation\n            // considers the cursor to be the left edge, so we need to move the selection\n            // to the right place before executing the 'cursorMove' command.\n            const active = s.active.getLeftThroughLineBreaks();\n            return new vscode.Selection(s.anchor, active);\n          } else {\n            return s;\n          }\n        });\n      }\n\n      // When we have multicursors and run a 'cursorMove' command, vscode applies that command\n      // to all cursors at the same time. So we should only run it once.\n      await vscode.commands.executeCommand('cursorMove', {\n        to: this.movementType,\n        select: vimState.currentMode !== Mode.Normal,\n        // select: ![Mode.Normal, Mode.Insert].includes(vimState.currentMode),\n        by: this.by,\n        value: this.value * count,\n      });\n    }\n\n    /**\n     * HACK:\n     * The `cursorMove` command is handling the selection for us.\n     * So we are not following our design principal (do no real movement inside an action) here\n     */\n    if (!vimState.editor.selections[multicursorIndex]) {\n      // VS Code selections no longer have the same amount of cursors as we do. This means that\n      // two or more selections combined into one. In this case we return these cursors as they\n      // were with the removed flag so that they can be removed.\n      // TODO: does this work in VisualBlock (where cursors are not 1 to 1 with selections)?\n      return {\n        start: vimState.cursorStartPosition,\n        stop: vimState.cursorStopPosition,\n        removed: true,\n      };\n    }\n\n    if (vimState.currentMode === Mode.Normal) {\n      return vimState.editor.selections[multicursorIndex].active;\n    } else {\n      let start = vimState.editor.selections[multicursorIndex].anchor;\n      const stop = vimState.editor.selections[multicursorIndex].active;\n\n      // If we are moving up we need to keep getting the left of anchor/start because vscode is\n      // to the right of the character in order to include it but our positions are always on the\n      // left side of the character.\n      // Also when we switch from being before anchor to being after anchor we need to move\n      // the anchor/start to the left as well in order to include the character.\n      if (\n        (start.isAfter(stop) &&\n          vimState.cursorStartPosition.isAfter(vimState.cursorStopPosition)) ||\n        (vimState.cursorStartPosition.isAfter(vimState.cursorStopPosition) &&\n          start.isBeforeOrEqual(stop))\n      ) {\n        start = start.getLeft();\n      }\n\n      return { start, stop };\n    }\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<IMovement> {\n    const multicursorIndex = this.multicursorIndex ?? 0;\n    if (multicursorIndex === 0) {\n      // When we have multicursors and run a 'cursorMove' command, vscode applies that command\n      // to all cursors at the same time. So we should only run it once.\n      await vscode.commands.executeCommand('cursorMove', {\n        to: this.movementType,\n        select: true,\n        by: this.by,\n        value: this.value,\n      });\n    }\n\n    if (!vimState.editor.selections[multicursorIndex]) {\n      // Vscode selections no longer have the same amount of cursors as we do. This means that\n      // two or more selections combined into one. In this case we return these cursors as they\n      // were with the removed flag so that they can be removed.\n      return {\n        start: vimState.cursorStartPosition,\n        stop: vimState.cursorStopPosition,\n        removed: true,\n      };\n    }\n\n    return {\n      start: vimState.editor.selections[multicursorIndex].start,\n      stop: vimState.editor.selections[multicursorIndex].end,\n    };\n  }\n}\n\nclass MoveUpByScreenLine extends MoveByScreenLine {\n  keys = [];\n  movementType: CursorMovePosition = 'up';\n  override by: CursorMoveByUnit = 'wrappedLine';\n  override value = 1;\n\n  constructor(multicursorIndex: number) {\n    super();\n    this.multicursorIndex = multicursorIndex;\n  }\n}\n\nclass MoveDownByScreenLine extends MoveByScreenLine {\n  keys = [];\n  movementType: CursorMovePosition = 'down';\n  override by: CursorMoveByUnit = 'wrappedLine';\n  override value = 1;\n\n  constructor(multicursorIndex: number) {\n    super();\n    this.multicursorIndex = multicursorIndex;\n  }\n}\n\nabstract class MoveByScreenLineMaintainDesiredColumn extends MoveByScreenLine {\n  override preservesDesiredColumn = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const prevDesiredColumn = vimState.desiredColumn;\n    const prevLine = vimState.editor.selection.active.line;\n\n    if (vimState.currentMode !== Mode.Normal) {\n      /**\n       * As VIM and VSCode handle the end of selection index a little\n       * differently we need to sometimes move the cursor at the end\n       * of the selection back by a character.\n       */\n      const start = vimState.editor.selection.start;\n      if (\n        (this.movementType === 'down' && position.line > start.line) ||\n        (this.movementType === 'up' && position.line < prevLine)\n      ) {\n        await vscode.commands.executeCommand('cursorMove', {\n          to: 'left',\n          select: true,\n          by: 'character',\n          value: 1,\n        });\n      }\n    }\n\n    await vscode.commands.executeCommand('cursorMove', {\n      to: this.movementType,\n      select: vimState.currentMode !== Mode.Normal,\n      by: this.by,\n      value: this.value,\n    });\n\n    if (vimState.currentMode === Mode.Normal) {\n      let returnedPos = vimState.editor.selection.active;\n      if (prevLine !== returnedPos.line) {\n        returnedPos = returnedPos.withColumn(prevDesiredColumn);\n      }\n      return returnedPos;\n    } else {\n      /**\n       * cursorMove command is handling the selection for us.\n       * So we are not following our design principal (do no real movement inside an action) here.\n       */\n      let start = vimState.editor.selection.start;\n      let stop = vimState.editor.selection.end;\n      const curPos = vimState.editor.selection.active;\n\n      // We want to swap the cursor start stop positions based on which direction we are moving, up or down\n      if (start.isEqual(curPos) && !start.isEqual(stop)) {\n        [start, stop] = [stop, start];\n        if (prevLine !== start.line) {\n          start = start.getLeft();\n        }\n      }\n\n      if (position.line !== stop.line) {\n        stop = stop.withColumn(prevDesiredColumn);\n      }\n\n      return { start, stop };\n    }\n  }\n}\n\nclass MoveDownFoldFix extends MoveByScreenLineMaintainDesiredColumn {\n  keys = [];\n  movementType: CursorMovePosition = 'down';\n  override by: CursorMoveByUnit = 'line';\n  override value = 1;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (position.line >= vimState.document.lineCount - 1) {\n      return position;\n    }\n    let t: Position | IMovement = position;\n    let prev: Position = position;\n    const moveDownByScreenLine = new MoveDownByScreenLine(this.multicursorIndex ?? 0);\n    while (true) {\n      t = await moveDownByScreenLine.execAction(t, vimState);\n      t = t instanceof Position ? t : t.stop;\n      const lineChanged = prev.line !== t.line;\n      // wrappedLine movement goes to eol character only when at the last line\n      // thus a column change on wrappedLine movement represents a visual last line\n      const colChanged = prev.character !== t.character;\n      if (lineChanged || !colChanged) {\n        break;\n      }\n      prev = t;\n    }\n    return adjustForDesiredColumn({\n      position: t,\n      desiredColumn: vimState.desiredColumn,\n      multicursorIndex: this.multicursorIndex,\n    });\n  }\n}\n\n@RegisterAction\nexport class MoveDown extends BaseMovement {\n  keys = [['j'], ['<down>'], ['<C-j>'], ['<C-n>']];\n  override preservesDesiredColumn = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (\n      vimState.currentMode === Mode.Insert &&\n      this.keysPressed[0] === '<down>' &&\n      vimState.editor.document.uri.scheme === 'vscode-interactive-input' &&\n      position.line === vimState.document.lineCount - 1 &&\n      vimState.editor.selection.isEmpty\n    ) {\n      // navigate history in interactive window\n      await vscode.commands.executeCommand('interactive.history.next');\n      return vimState.editor.selection.active;\n    }\n\n    if (configuration.foldfix && vimState.currentMode !== Mode.VisualBlock) {\n      const moveDownFoldFix = new MoveDownFoldFix();\n      moveDownFoldFix.multicursorIndex = this.multicursorIndex;\n      return moveDownFoldFix.execAction(position, vimState);\n    }\n\n    if (position.line < vimState.document.lineCount - 1) {\n      return adjustForDesiredColumn({\n        position,\n        desiredColumn: vimState.desiredColumn,\n        multicursorIndex: this.multicursorIndex,\n      }).getDown();\n    }\n    return position;\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return position.getDown();\n  }\n}\n\n@RegisterAction\nexport class MoveUp extends BaseMovement {\n  keys = [['k'], ['<up>'], ['<C-p>']];\n  override preservesDesiredColumn = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (\n      vimState.currentMode === Mode.Insert &&\n      this.keysPressed[0] === '<up>' &&\n      vimState.editor.document.uri.scheme === 'vscode-interactive-input' &&\n      position.line === 0 &&\n      vimState.editor.selection.isEmpty\n    ) {\n      // navigate history in interactive window\n      await vscode.commands.executeCommand('interactive.history.previous');\n      return vimState.editor.selection.active;\n    }\n\n    if (configuration.foldfix && vimState.currentMode !== Mode.VisualBlock) {\n      const moveUpFoldFix = new MoveUpFoldFix();\n      moveUpFoldFix.multicursorIndex = this.multicursorIndex;\n      return moveUpFoldFix.execAction(position, vimState);\n    }\n\n    if (position.line > 0) {\n      return adjustForDesiredColumn({\n        position,\n        desiredColumn: vimState.desiredColumn,\n        multicursorIndex: this.multicursorIndex,\n      }).getUp();\n    }\n    return position;\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return position.getUp();\n  }\n}\n\n@RegisterAction\nclass MoveUpFoldFix extends MoveByScreenLineMaintainDesiredColumn {\n  keys = [];\n  movementType: CursorMovePosition = 'up';\n  override by: CursorMoveByUnit = 'line';\n  override value = 1;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (position.line === 0) {\n      return position;\n    }\n    let t: Position | IMovement;\n    let prev: Position = position;\n    const moveUpByScreenLine = new MoveUpByScreenLine(this.multicursorIndex ?? 0);\n    while (true) {\n      t = await moveUpByScreenLine.execAction(position, vimState);\n      t = t instanceof Position ? t : t.stop;\n      const lineChanged = prev.line !== t.line;\n      const colChanged = prev.character !== t.character;\n      if (lineChanged || !colChanged) {\n        break;\n      }\n      prev = t;\n    }\n    return adjustForDesiredColumn({\n      position: t,\n      desiredColumn: vimState.desiredColumn,\n      multicursorIndex: this.multicursorIndex,\n    });\n  }\n}\n\n@RegisterAction\nclass CommandNextSearchMatch extends BaseMovement {\n  keys = ['n'];\n  override isJump = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const searchState = globalState.searchState;\n\n    if (!searchState || searchState.searchString === '') {\n      return position;\n    }\n\n    // Turn one of the highlighting flags back on (turned off with :nohl)\n    globalState.hl = true;\n\n    if (searchState.getMatchRanges(vimState).length === 0) {\n      StatusBar.displayError(vimState, VimError.PatternNotFound(searchState.searchString));\n      return failedMovement(vimState);\n    }\n\n    // we have to handle a special case here: searching for $ or \\n,\n    // which we approximate by positionIsEOL. In that case (but only when searching forward)\n    // we need to \"offset\" by getRight for searching the next match, otherwise we get stuck.\n    const searchForward = searchState.direction === SearchDirection.Forward;\n    const positionIsEOL = position.getRight().isEqual(position.getLineEnd());\n    const nextMatch =\n      positionIsEOL && searchForward\n        ? searchState.getNextSearchMatchPosition(vimState, position.getRight())\n        : searchState.getNextSearchMatchPosition(vimState, position);\n\n    if (!nextMatch) {\n      StatusBar.displayError(\n        vimState,\n        searchState.direction === SearchDirection.Forward\n          ? VimError.SearchHitBottom(searchState.searchString)\n          : VimError.SearchHitTop(searchState.searchString),\n      );\n      return failedMovement(vimState);\n    }\n\n    reportSearch(nextMatch.index, searchState.getMatchRanges(vimState).length, vimState);\n\n    return nextMatch.pos;\n  }\n}\n\n@RegisterAction\nclass CommandPreviousSearchMatch extends BaseMovement {\n  keys = ['N'];\n  override isJump = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const searchState = globalState.searchState;\n\n    if (!searchState || searchState.searchString === '') {\n      return position;\n    }\n\n    // Turn one of the highlighting flags back on (turned off with :nohl)\n    globalState.hl = true;\n\n    if (searchState.getMatchRanges(vimState).length === 0) {\n      StatusBar.displayError(vimState, VimError.PatternNotFound(searchState.searchString));\n      return failedMovement(vimState);\n    }\n\n    const searchForward = searchState.direction === SearchDirection.Forward;\n    const positionIsEOL = position.getRight().isEqual(position.getLineEnd());\n\n    // see implementation of n, above.\n    const prevMatch =\n      positionIsEOL && !searchForward\n        ? searchState.getNextSearchMatchPosition(\n            vimState,\n            position.getRight(),\n            SearchDirection.Backward,\n          )\n        : searchState.getNextSearchMatchPosition(vimState, position, SearchDirection.Backward);\n\n    if (!prevMatch) {\n      StatusBar.displayError(\n        vimState,\n        searchState.direction === SearchDirection.Forward\n          ? VimError.SearchHitTop(searchState.searchString)\n          : VimError.SearchHitBottom(searchState.searchString),\n      );\n      return failedMovement(vimState);\n    }\n\n    reportSearch(prevMatch.index, searchState.getMatchRanges(vimState).length, vimState);\n\n    return prevMatch.pos;\n  }\n}\n\nexport abstract class BaseMarkMovement extends BaseMovement {\n  override isJump = true;\n  protected registerMode?: RegisterMode;\n\n  protected abstract getNewPosition(document: vscode.TextDocument, position: Position): Position;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const markName = this.keysPressed[1];\n    const mark = vimState.historyTracker.getMark(markName);\n\n    if (mark === undefined) {\n      throw VimError.MarkNotSet();\n    }\n\n    if (\n      mark.isUppercaseMark &&\n      vimState.recordedState.operator &&\n      mark.document !== vimState.document\n    ) {\n      throw VimError.MarkNotSet();\n    }\n\n    if (this.registerMode) {\n      vimState.currentRegisterMode = this.registerMode;\n    }\n\n    const document = mark.isUppercaseMark ? mark.document : vimState.document;\n    const newPosition = this.getNewPosition(document, mark.position);\n\n    // Navigate to mark in another document\n    if (mark.isUppercaseMark && mark.document !== vimState.document) {\n      const options: vscode.TextDocumentShowOptions = {\n        selection: new vscode.Range(newPosition, newPosition),\n      };\n      await vscode.window.showTextDocument(mark.document, options);\n      return failedMovement(vimState); // Don't move cursor in current document\n    }\n\n    // Navigate to mark in the current document\n    return newPosition;\n  }\n}\n\n@RegisterAction\nexport class MarkMovementBOL extends BaseMarkMovement {\n  keys = [\"'\", '<register>'];\n  protected override registerMode = RegisterMode.LineWise;\n\n  protected override getNewPosition(document: vscode.TextDocument, position: Position): Position {\n    return TextEditor.getFirstNonWhitespaceCharOnLine(document, position.line);\n  }\n}\n\n@RegisterAction\nexport class MarkMovement extends BaseMarkMovement {\n  keys = ['`', '<register>'];\n\n  /**\n   * If the exact position exists, returns that position.\n   * If the character position is beyond the end of line, returns the end of line.\n   * Otherwise returns the position at the end of the document.\n   */\n  protected override getNewPosition(document: vscode.TextDocument, position: Position): Position {\n    const lastLine = document.lineCount - 1;\n    if (position.line > lastLine) {\n      const lastLineLength = document.lineAt(lastLine).text.length;\n      return new Position(lastLine, lastLineLength);\n    }\n\n    const { text } = document.lineAt(position.line);\n    const character = Math.min(position.character, text.length);\n    return new Position(position.line, character);\n  }\n}\n\n@RegisterAction\nclass NextMark extends BaseMovement {\n  keys = [']', '`'];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    const positions = vimState.historyTracker\n      .getLocalMarks()\n      .filter((mark) => mark.position.isAfter(position))\n      .map((mark) => mark.position)\n      .sort((x, y) => x.compareTo(y));\n    return positions.length === 0 ? position : positions[0];\n  }\n}\n\n@RegisterAction\nclass PrevMark extends BaseMovement {\n  keys = ['[', '`'];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    const positions = vimState.historyTracker\n      .getLocalMarks()\n      .filter((mark) => mark.position.isBefore(position))\n      .map((mark) => mark.position)\n      .sort((x, y) => y.compareTo(x));\n    return positions.length === 0 ? position : positions[0];\n  }\n}\n\n@RegisterAction\nclass NextMarkLinewise extends BaseMovement {\n  keys = [']', \"'\"];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    const lines = vimState.historyTracker\n      .getLocalMarks()\n      .filter((mark) => mark.position.line > position.line)\n      .map((mark) => mark.position.line);\n    const line = lines.length === 0 ? position.line : Math.min(...lines);\n    return new Position(line, 0).getLineBeginRespectingIndent(vimState.document);\n  }\n}\n\n@RegisterAction\nclass PrevMarkLinewise extends BaseMovement {\n  keys = ['[', \"'\"];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    const lines = vimState.historyTracker\n      .getLocalMarks()\n      .filter((mark) => mark.position.line < position.line)\n      .map((mark) => mark.position.line);\n    const line = lines.length === 0 ? position.line : Math.max(...lines);\n    return new Position(line, 0).getLineBeginRespectingIndent(vimState.document);\n  }\n}\n\n@RegisterAction\nexport class MoveLeft extends BaseMovement {\n  keys = [['h'], ['<left>'], ['<BS>'], ['<C-BS>'], ['<S-BS>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    const getLeftWhile = (p: Position): Position => {\n      const line = vimState.document.lineAt(p.line).text;\n\n      if (p.character === 0) {\n        return p;\n      }\n      if (\n        isLowSurrogate(line.charCodeAt(p.character)) &&\n        isHighSurrogate(line.charCodeAt(p.character - 1))\n      ) {\n        p = p.getLeft();\n      }\n\n      const newPosition = p.getLeft();\n      if (\n        newPosition.character > 0 &&\n        isLowSurrogate(line.charCodeAt(newPosition.character)) &&\n        isHighSurrogate(line.charCodeAt(newPosition.character - 1))\n      ) {\n        return newPosition.getLeft();\n      } else {\n        return newPosition;\n      }\n    };\n    return shouldWrapKey(vimState.currentMode, this.keysPressed[0])\n      ? position.getLeftThroughLineBreaks(\n          [Mode.Insert, Mode.Replace].includes(vimState.currentMode),\n        )\n      : getLeftWhile(position);\n  }\n}\n\n@RegisterAction\nexport class MoveRight extends BaseMovement {\n  keys = [['l'], ['<right>'], [' ']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    const getRightWhile = (p: Position): Position => {\n      const line = vimState.document.lineAt(p.line).text;\n      const newPosition = p.getRight();\n      if (newPosition.character >= vimState.document.lineAt(newPosition.line).text.length) {\n        return newPosition;\n      }\n      if (\n        isLowSurrogate(line.charCodeAt(newPosition.character)) &&\n        isHighSurrogate(line.charCodeAt(p.character))\n      ) {\n        return newPosition.getRight();\n      } else {\n        return newPosition;\n      }\n    };\n    return shouldWrapKey(vimState.currentMode, this.keysPressed[0])\n      ? position.getRightThroughLineBreaks(\n          [Mode.Insert, Mode.Replace].includes(vimState.currentMode),\n        )\n      : getRightWhile(position);\n  }\n}\n\n@RegisterAction\nclass MoveDownNonBlank extends BaseMovement {\n  keys = [['+'], ['\\n'], ['<C-m>']];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      position.getDown(Math.max(count, 1)).line,\n    );\n  }\n}\n\n@RegisterAction\nclass MoveUpNonBlank extends BaseMovement {\n  keys = ['-'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      position.getUp(Math.max(count, 1)).line,\n    );\n  }\n}\n\n@RegisterAction\nclass MoveDownUnderscore extends BaseMovement {\n  keys = ['_'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    const pos = position.getDown(Math.max(count - 1, 0));\n    return vimState.recordedState.operator\n      ? pos\n      : TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, pos.line);\n  }\n}\n\n@RegisterAction\nclass MoveToColumn extends BaseMovement {\n  keys = ['|'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    return new Position(position.line, Math.max(0, count - 1));\n  }\n}\n\n/**\n * Returns the Postion of the next instance of `char` on the line\n * @param char character to look for\n * @param count number of times to look\n * @param direction direction to look in\n */\nfunction findHelper(\n  vimState: VimState,\n  start: Position,\n  char: string,\n  count: number,\n  direction: 'forward' | 'backward',\n): Position | undefined {\n  const line = vimState.document.lineAt(start);\n\n  let index = start.character;\n  while (count > 0 && index >= 0) {\n    if (direction === 'forward') {\n      index = line.text.indexOf(char, index + 1);\n    } else {\n      index = line.text.lastIndexOf(char, index - 1);\n    }\n    count--;\n  }\n\n  if (index >= 0) {\n    return new Position(start.line, index);\n  }\n\n  return undefined;\n}\n\n@RegisterAction\nclass MoveFindForward extends BaseMovement {\n  keys = ['f', '<character>'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    if (configuration.sneakReplacesF) {\n      const pos = await new SneakForward(\n        this.keysPressed.concat('\\n'),\n        this.isRepeat,\n      ).execActionWithCount(position, vimState, count);\n      if (vimState.recordedState.operator && !isIMovement(pos)) {\n        return pos.getRight();\n      }\n\n      return pos;\n    }\n\n    count ||= 1;\n    const toFind = Notation.ToControlCharacter(this.keysPressed[1]);\n    let result = findHelper(vimState, position, toFind, count, 'forward');\n\n    vimState.lastSemicolonRepeatableMovement = new MoveFindForward(this.keysPressed, true);\n    vimState.lastCommaRepeatableMovement = new MoveFindBackward(this.keysPressed, true);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    if (vimState.recordedState.operator) {\n      result = result.getRight();\n    }\n\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveFindBackward extends BaseMovement {\n  keys = ['F', '<character>'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    if (configuration.sneakReplacesF) {\n      return new SneakBackward(this.keysPressed.concat('\\n'), this.isRepeat).execActionWithCount(\n        position,\n        vimState,\n        count,\n      );\n    }\n\n    count ||= 1;\n    const toFind = Notation.ToControlCharacter(this.keysPressed[1]);\n    const result = findHelper(vimState, position, toFind, count, 'backward');\n\n    vimState.lastSemicolonRepeatableMovement = new MoveFindBackward(this.keysPressed, true);\n    vimState.lastCommaRepeatableMovement = new MoveFindForward(this.keysPressed, true);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    return result;\n  }\n}\n\nfunction tilHelper(\n  vimState: VimState,\n  start: Position,\n  char: string,\n  count: number,\n  direction: 'forward' | 'backward',\n) {\n  const result = findHelper(vimState, start, char, count, direction);\n  return direction === 'forward' ? result?.getLeft() : result?.getRight();\n}\n\n@RegisterAction\nclass MoveTilForward extends BaseMovement {\n  keys = ['t', '<character>'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    count ||= 1;\n    const toFind = Notation.ToControlCharacter(this.keysPressed[1]);\n    let result = tilHelper(vimState, position, toFind, count, 'forward');\n\n    // For t<character> vim executes ; as 2; and , as 2,\n    if (result && this.isRepeat && position.isEqual(result) && count === 1) {\n      result = tilHelper(vimState, position, toFind, 2, 'forward');\n    }\n\n    vimState.lastSemicolonRepeatableMovement = new MoveTilForward(this.keysPressed, true);\n    vimState.lastCommaRepeatableMovement = new MoveTilBackward(this.keysPressed, true);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    if (vimState.recordedState.operator) {\n      result = result.getRight();\n    }\n\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveTilBackward extends BaseMovement {\n  keys = ['T', '<character>'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    count ||= 1;\n    const toFind = Notation.ToControlCharacter(this.keysPressed[1]);\n    let result = tilHelper(vimState, position, toFind, count, 'backward');\n\n    // For T<character> vim executes ; as 2; and , as 2,\n    if (result && this.isRepeat && position.isEqual(result) && count === 1) {\n      result = tilHelper(vimState, position, toFind, 2, 'backward');\n    }\n\n    vimState.lastSemicolonRepeatableMovement = new MoveTilBackward(this.keysPressed, true);\n    vimState.lastCommaRepeatableMovement = new MoveTilForward(this.keysPressed, true);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveRepeat extends BaseMovement {\n  keys = [';'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    const movement = vimState.lastSemicolonRepeatableMovement;\n    if (movement) {\n      return movement.execActionWithCount(position, vimState, count);\n    }\n    return position;\n  }\n}\n\n@RegisterAction\nclass MoveRepeatReversed extends BaseMovement {\n  keys = [','];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    const semiColonMovement = vimState.lastSemicolonRepeatableMovement;\n    const commaMovement = vimState.lastCommaRepeatableMovement;\n    if (commaMovement) {\n      const result = commaMovement.execActionWithCount(position, vimState, count);\n\n      // Make sure these don't change. Otherwise, comma's direction flips back\n      // and forth when done repeatedly. This is a bit hacky, so feel free to refactor.\n      vimState.lastSemicolonRepeatableMovement = semiColonMovement;\n      vimState.lastCommaRepeatableMovement = commaMovement;\n\n      return result;\n    }\n    return position;\n  }\n}\n\n@RegisterAction\nexport class MoveLineEnd extends BaseMovement {\n  keys = [['$'], ['<End>'], ['<D-right>']];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    return position.getDown(Math.max(count - 1, 0)).getLineEnd();\n  }\n}\n\n@RegisterAction\nclass MoveLineBegin extends BaseMovement {\n  keys = [['0'], ['<Home>'], ['<D-left>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.getLineBegin();\n  }\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.doesActionApply(vimState, keysPressed) && vimState.recordedState.count === 0;\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.couldActionApply(vimState, keysPressed) && vimState.recordedState.count === 0;\n  }\n}\n\n@RegisterAction\nclass MoveScreenLineBegin extends MoveByScreenLine {\n  keys = ['g', '0'];\n  movementType: CursorMovePosition = 'wrappedLineStart';\n}\n\n@RegisterAction\nclass MoveScreenNonBlank extends MoveByScreenLine {\n  keys = ['g', '^'];\n  movementType: CursorMovePosition = 'wrappedLineFirstNonWhitespaceCharacter';\n}\n\n@RegisterAction\nclass MoveScreenLineEnd extends MoveByScreenLine {\n  keys = ['g', '$'];\n  movementType: CursorMovePosition = 'wrappedLineEnd';\n}\n\n@RegisterAction\nclass MoveScreenLineEndNonBlank extends MoveByScreenLine {\n  keys = ['g', '_'];\n  movementType: CursorMovePosition = 'wrappedLineLastNonWhitespaceCharacter';\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    count ||= 1;\n    const pos = await super.execActionWithCount(position, vimState, count);\n\n    // If in visual, return a selection\n    if (pos instanceof Position) {\n      return pos.getDown(count - 1);\n    } else {\n      return { start: pos.start, stop: pos.stop.getDown(count - 1).getLeftThroughLineBreaks() };\n    }\n  }\n}\n\n@RegisterAction\nclass MoveScreenLineCenter extends MoveByScreenLine {\n  keys = ['g', 'm'];\n  movementType: CursorMovePosition = 'wrappedLineColumnCenter';\n}\n\n@RegisterAction\nclass MoveUpByDisplayLine extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = [\n    ['g', 'k'],\n    ['g', '<up>'],\n  ];\n  movementType: CursorMovePosition = 'up';\n  override by: CursorMoveByUnit = 'wrappedLine';\n  override value = 1;\n}\n\n@RegisterAction\nclass MoveDownByDisplayLine extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = [\n    ['g', 'j'],\n    ['g', '<down>'],\n  ];\n  movementType: CursorMovePosition = 'down';\n  override by: CursorMoveByUnit = 'wrappedLine';\n  override value = 1;\n}\n\n// Because we can't support moving by screen line when in visualLine mode,\n// we change to moving by regular line in visualLine mode. We can't move by\n// screen line is that our ranges only support a start and stop attribute,\n// and moving by screen line just snaps us back to the original position.\n// Check PR #1600 for discussion.\n@RegisterAction\nclass MoveUpByScreenLineVisualLine extends MoveByScreenLine {\n  override modes = [Mode.VisualLine];\n  keys = [\n    ['g', 'k'],\n    ['g', '<up>'],\n  ];\n  movementType: CursorMovePosition = 'up';\n  override by: CursorMoveByUnit = 'line';\n  override value = 1;\n}\n\n@RegisterAction\nclass MoveDownByScreenLineVisualLine extends MoveByScreenLine {\n  override modes = [Mode.VisualLine];\n  keys = [\n    ['g', 'j'],\n    ['g', '<down>'],\n  ];\n  movementType: CursorMovePosition = 'down';\n  override by: CursorMoveByUnit = 'line';\n  override value = 1;\n}\n\n@RegisterAction\nclass MoveUpByScreenLineVisualBlock extends BaseMovement {\n  override modes = [Mode.VisualBlock];\n  keys = [\n    ['g', 'k'],\n    ['g', '<up>'],\n  ];\n  override preservesDesiredColumn = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    if (position.line > 0) {\n      return adjustForDesiredColumn({\n        position,\n        desiredColumn: vimState.desiredColumn,\n        multicursorIndex: this.multicursorIndex,\n      }).getUp();\n    }\n    return position;\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return position.getUp();\n  }\n}\n\n@RegisterAction\nclass MoveDownByScreenLineVisualBlock extends BaseMovement {\n  override modes = [Mode.VisualBlock];\n  keys = [\n    ['g', 'j'],\n    ['g', '<down>'],\n  ];\n  override preservesDesiredColumn = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    if (position.line < vimState.document.lineCount - 1) {\n      return adjustForDesiredColumn({\n        position,\n        desiredColumn: vimState.desiredColumn,\n        multicursorIndex: this.multicursorIndex,\n      }).getDown();\n    }\n    return position;\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return position.getDown();\n  }\n}\n\n@RegisterAction\nclass MoveScreenToRight extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'h'];\n  movementType: CursorMovePosition = 'right';\n  override by: CursorMoveByUnit = 'character';\n  override value = 1;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n}\n\n@RegisterAction\nclass MoveScreenToLeft extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'l'];\n  movementType: CursorMovePosition = 'left';\n  override by: CursorMoveByUnit = 'character';\n  override value = 1;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n}\n\n@RegisterAction\nclass MoveScreenToRightHalf extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'H'];\n  movementType: CursorMovePosition = 'right';\n  override by: CursorMoveByUnit = 'halfLine';\n  override value = 1;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n}\n\n@RegisterAction\nclass MoveScreenToLeftHalf extends MoveByScreenLine {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  keys = ['z', 'L'];\n  movementType: CursorMovePosition = 'left';\n  override by: CursorMoveByUnit = 'halfLine';\n  override value = 1;\n  override isJump = true;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    // Don't run if there's an operator because the Sneak plugin uses <operator>z\n    return (\n      super.doesActionApply(vimState, keysPressed) && vimState.recordedState.operator === undefined\n    );\n  }\n}\n\n@RegisterAction\nclass MoveToLineFromViewPortTop extends BaseMovement {\n  keys = ['H'];\n  override isJump = true;\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    const topLine = vimState.editor.visibleRanges[0].start.line ?? 0;\n    if (topLine === 0) {\n      return {\n        start: vimState.cursorStartPosition,\n        stop: position.with({ line: topLine }).obeyStartOfLine(vimState.document),\n      };\n    }\n\n    const scrolloff = configuration\n      .getConfiguration('editor')\n      .get<number>('cursorSurroundingLines', 0);\n    const line = topLine + scrolloff;\n\n    return {\n      start: vimState.cursorStartPosition,\n      stop: position.with({ line }).obeyStartOfLine(vimState.document),\n    };\n  }\n}\n\n@RegisterAction\nclass MoveToLineFromViewPortBottom extends BaseMovement {\n  keys = ['L'];\n  override isJump = true;\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    const bottomLine = vimState.editor.visibleRanges[0].end.line ?? 0;\n    const numLines = vimState.editor.document.lineCount;\n    if (bottomLine === numLines - 1) {\n      // NOTE: editor will scroll to accommodate editor.cursorSurroundingLines in this scenario\n      return {\n        start: vimState.cursorStartPosition,\n        stop: position.with({ line: bottomLine }).obeyStartOfLine(vimState.document),\n      };\n    }\n\n    const scrolloff = configuration\n      .getConfiguration('editor')\n      .get<number>('cursorSurroundingLines', 0);\n    const line = bottomLine - scrolloff;\n\n    return {\n      start: vimState.cursorStartPosition,\n      stop: position.with({ line }).obeyStartOfLine(vimState.document),\n    };\n  }\n}\n\n@RegisterAction\nclass MoveToMiddleLineInViewPort extends MoveByScreenLine {\n  keys = ['M'];\n  movementType: CursorMovePosition = 'viewPortCenter';\n  override by: CursorMoveByUnit = 'line';\n  override isJump = true;\n}\n\n@RegisterAction\nclass MoveNonBlank extends BaseMovement {\n  keys = ['^'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, position.line);\n  }\n}\n\n@RegisterAction\nclass MoveNonBlankFirst extends BaseMovement {\n  keys = [['g', 'g'], ['<C-Home>']];\n  override isJump = true;\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    const line = clamp(count, 1, vimState.document.lineCount) - 1;\n\n    return {\n      start: vimState.cursorStartPosition,\n      stop: position.with({ line }).obeyStartOfLine(vimState.document),\n    };\n  }\n}\n\n@RegisterAction\nclass MoveNonBlankLast extends BaseMovement {\n  keys = ['G'];\n  override isJump = true;\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    let stop: Position;\n    if (count === 0) {\n      stop = new Position(vimState.document.lineCount - 1, position.character).obeyStartOfLine(\n        vimState.document,\n      );\n    } else {\n      stop = new Position(\n        Math.min(count, vimState.document.lineCount) - 1,\n        position.character,\n      ).obeyStartOfLine(vimState.document);\n    }\n\n    return {\n      start: vimState.cursorStartPosition,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nclass EndOfSpecificLine extends BaseMovement {\n  keys = ['<C-End>'];\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position> {\n    const line = count\n      ? clamp(count - 1, 0, vimState.document.lineCount - 1)\n      : vimState.document.lineCount - 1;\n\n    return new Position(line, 0).getLineEnd();\n  }\n}\n\n@RegisterAction\nexport class MoveWordBegin extends BaseMovement {\n  keys = ['w'];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<Position> {\n    if (\n      lastIteration &&\n      !configuration.changeWordIncludesWhitespace &&\n      vimState.recordedState.operator instanceof ChangeOperator\n    ) {\n      const line = vimState.document.lineAt(position);\n      if (line.text.length === 0) {\n        return position;\n      }\n\n      const char = line.text[position.character];\n\n      /*\n      From the Vim manual:\n\n      Special case: \"cw\" and \"cW\" are treated like \"ce\" and \"cE\" if the cursor is\n      on a non-blank.  This is because \"cw\" is interpreted as change-word, and a\n      word does not include the following white space.\n      */\n\n      if (' \\t'.includes(char)) {\n        return position.nextWordStart(vimState.document);\n      } else {\n        return position.nextWordEnd(vimState.document, { inclusive: true }).getRight();\n      }\n    } else {\n      return position.nextWordStart(vimState.document);\n    }\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<Position> {\n    const result = await this.execAction(position, vimState, firstIteration, lastIteration);\n\n    /*\n    From the Vim documentation:\n\n    Another special case: When using the \"w\" motion in combination with an\n    operator and the last word moved over is at the end of a line, the end of\n    that word becomes the end of the operated text, not the first word in the\n    next line.\n    */\n\n    if (\n      result.line > position.line + 1 ||\n      (result.line === position.line + 1 && result.isFirstWordOfLine(vimState.document))\n    ) {\n      return position.getLineEnd();\n    }\n\n    if (result.isLineEnd(vimState.document)) {\n      return new Position(result.line, result.character + 1);\n    }\n\n    return result;\n  }\n}\n\n@RegisterAction\nexport class MoveFullWordBegin extends BaseMovement {\n  keys = [['W'], ['<C-right>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (\n      !configuration.changeWordIncludesWhitespace &&\n      vimState.recordedState.operator instanceof ChangeOperator\n    ) {\n      // TODO use execForOperator? Or maybe dont?\n\n      // See note for w\n      return position\n        .nextWordEnd(vimState.document, { wordType: WordType.Big, inclusive: true })\n        .getRight();\n    } else {\n      return position.nextWordStart(vimState.document, { wordType: WordType.Big });\n    }\n  }\n}\n\n@RegisterAction\nclass MoveWordEnd extends BaseMovement {\n  keys = ['e'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.nextWordEnd(vimState.document);\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    const end = position.nextWordEnd(vimState.document);\n\n    return new Position(end.line, end.character + 1);\n  }\n}\n\n@RegisterAction\nclass MoveFullWordEnd extends BaseMovement {\n  keys = ['E'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.nextWordEnd(vimState.document, { wordType: WordType.Big });\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    return position.nextWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n  }\n}\n\n@RegisterAction\nclass MoveLastWordEnd extends BaseMovement {\n  keys = ['g', 'e'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.prevWordEnd(vimState.document);\n  }\n}\n\n@RegisterAction\nclass MoveLastFullWordEnd extends BaseMovement {\n  keys = ['g', 'E'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.prevWordEnd(vimState.document, { wordType: WordType.Big });\n  }\n}\n\n@RegisterAction\nclass MoveBeginningWord extends BaseMovement {\n  keys = [['b'], ['<C-left>']];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.prevWordStart(vimState.document);\n  }\n}\n\n@RegisterAction\nclass MoveBeginningFullWord extends BaseMovement {\n  keys = ['B'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.prevWordStart(vimState.document, { wordType: WordType.Big });\n  }\n}\n\n@RegisterAction\nclass MovePreviousSentenceBegin extends BaseMovement {\n  keys = ['('];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.getSentenceBegin({ forward: false });\n  }\n}\n\n@RegisterAction\nclass GoToOffset extends BaseMovement {\n  keys = ['g', 'o'];\n  override isJump = true;\n\n  public override async execActionWithCount(position: Position, vimState: VimState, count: number) {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    return vimState.document.positionAt((count || 1) - 1);\n  }\n}\n\n@RegisterAction\nclass MoveNextSentenceBegin extends BaseMovement {\n  keys = [')'];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.getSentenceBegin({ forward: true });\n  }\n}\n\n@RegisterAction\nclass MoveParagraphEnd extends BaseMovement {\n  keys = ['}'];\n  override isJump = true;\n  iteration = 0;\n  isFirstLineWise = false;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    const hasOperator = vimState.recordedState.operator;\n    const paragraphEnd = getCurrentParagraphEnd(position);\n\n    if (hasOperator) {\n      /**\n       * When paired with an `operator` and a `count` this move will be executed\n       * multiple times which could cause issues like https://github.com/VSCodeVim/Vim/issues/4488\n       * because subsequent runs will receive back whatever position we return\n       * (See comment in `BaseMotion.execActionWithCount()`).\n       *\n       * We keep track of the iteration we are in, this way we can\n       * return the correct position when on the last iteration, and we don't\n       * accidentally set the `registerMode` incorrectly.\n       */\n      this.iteration++;\n\n      const isLineWise = position.isLineBeginning() && vimState.currentMode === Mode.Normal;\n\n      // TODO: `execAction` receives `firstIteration` and `lastIteration` - don't reinvent the wheel\n      const isLastIteration = vimState.recordedState.count\n        ? vimState.recordedState.count === this.iteration\n        : true;\n\n      /**\n       * `position` may not represent the position of the cursor from which the command was initiated.\n       * In the case that we will be repeating this move more than once\n       * we want to respect whether the starting position was at the beginning of line or not.\n       */\n      this.isFirstLineWise = this.iteration === 1 ? isLineWise : this.isFirstLineWise;\n\n      vimState.currentRegisterMode = this.isFirstLineWise ? RegisterMode.LineWise : undefined;\n\n      /**\n       * `paragraphEnd` is the first blank line after the last word in the\n       * current paragraph, we want the position just before that one to\n       * accurately emulate Vim's behaviour, unless we are at EOF.\n       */\n      return isLastIteration && !paragraphEnd.isAtDocumentEnd(vimState.document)\n        ? paragraphEnd.getLeftThroughLineBreaks(true)\n        : paragraphEnd;\n    }\n\n    return paragraphEnd;\n  }\n}\n\n@RegisterAction\nclass MoveParagraphBegin extends BaseMovement {\n  keys = ['{'];\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return getCurrentParagraphBeginning(position);\n  }\n}\n\nabstract class MoveSectionBoundary extends BaseMovement {\n  abstract begin: boolean;\n  abstract forward: boolean;\n  override isJump = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const document = vimState.document;\n\n    switch (document.languageId) {\n      case 'python':\n        return PythonDocument.moveClassBoundary(\n          document,\n          position,\n          vimState,\n          this.forward,\n          this.begin,\n        );\n    }\n\n    const boundary = this.begin ? '{' : '}';\n    let line = position.line;\n\n    if (\n      (this.forward && line === vimState.document.lineCount - 1) ||\n      (!this.forward && line === 0)\n    ) {\n      return TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, line);\n    }\n\n    line = this.forward ? line + 1 : line - 1;\n\n    while (!vimState.document.lineAt(line).text.startsWith(boundary)) {\n      if (this.forward) {\n        if (line === vimState.document.lineCount - 1) {\n          break;\n        }\n\n        line++;\n      } else {\n        if (line === 0) {\n          break;\n        }\n\n        line--;\n      }\n    }\n\n    return TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, line);\n  }\n}\n\n@RegisterAction\nclass MoveNextSectionBegin extends MoveSectionBoundary {\n  keys = [']', ']'];\n  begin = true;\n  forward = true;\n}\n\n@RegisterAction\nclass MoveNextSectionEnd extends MoveSectionBoundary {\n  keys = [']', '['];\n  begin = false;\n  forward = true;\n}\n\n@RegisterAction\nclass MovePreviousSectionBegin extends MoveSectionBoundary {\n  keys = ['[', '['];\n  begin = true;\n  forward = false;\n}\n\n@RegisterAction\nclass MovePreviousSectionEnd extends MoveSectionBoundary {\n  keys = ['[', ']'];\n  begin = false;\n  forward = false;\n}\n\n@RegisterAction\nclass MoveToMatchingBracket extends BaseMovement {\n  keys = ['%'];\n  override isJump = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    position = position.getLeftIfEOL();\n\n    const lineText = vimState.document.lineAt(position).text;\n    const failure = failedMovement(vimState);\n\n    for (let col = position.character; col < lineText.length; col++) {\n      const currentChar = lineText[col];\n      const pairing = PairMatcher.getPercentPairing(currentChar);\n\n      // we need to check pairing, because with text: bla |bla < blub > blub\n      // this for loop will walk over bla and check for a pairing till it finds <\n      if (pairing) {\n        // We found an opening char, now move to the matching closing char\n        return (\n          PairMatcher.nextPairedChar(\n            new Position(position.line, col),\n            lineText[col],\n            vimState,\n            false,\n          ) || failure\n        );\n      }\n    }\n\n    // No matchable character on the line; admit defeat\n    return failure;\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const result = await this.execAction(position, vimState);\n\n    if (isIMovement(result)) {\n      if (result.failed) {\n        return result;\n      } else {\n        throw new Error('Did not ever handle this case!');\n      }\n    }\n\n    if (position.isAfter(result)) {\n      return {\n        start: result,\n        stop: position.getRight(),\n      };\n    } else {\n      return result.getRight();\n    }\n  }\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement> {\n    // % has a special mode that lets you use it to jump to a percentage of the file\n    // However, some other bracket motions inherit from this so only do this behavior for % explicitly\n    if (Object.getPrototypeOf(this) === MoveToMatchingBracket.prototype) {\n      if (count === 0) {\n        if (vimState.recordedState.operator) {\n          return this.execActionForOperator(position, vimState);\n        } else {\n          return this.execAction(position, vimState);\n        }\n      }\n\n      // Check to make sure this is a valid percentage\n      if (count < 0 || count > 100) {\n        return failedMovement(vimState);\n      }\n\n      // See `:help N%`\n      const targetLine = Math.trunc((count * vimState.document.lineCount + 99) / 100) - 1;\n\n      return position.with({ line: targetLine }).obeyStartOfLine(vimState.document);\n    } else {\n      return super.execActionWithCount(position, vimState, count);\n    }\n  }\n}\n\nexport abstract class MoveInsideCharacter extends ExpandingSelection {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  protected abstract charToMatch: string;\n\n  /** True for \"around\" actions, such as `a(`, and false for \"inside\" actions, such as `i(`  */\n  protected includeSurrounding = false;\n  override isJump = true;\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<IMovement> {\n    const closingChar = PairMatcher.pairings[this.charToMatch].match;\n    const [selStart, selEnd] = sorted(vimState.cursorStartPosition, position);\n\n    // First, search backwards for the opening character of the sequence\n    let openPos = PairMatcher.nextPairedChar(selStart, closingChar, vimState, true);\n    if (openPos === undefined) {\n      // If opening character not found, search forwards\n      let lineNum = selStart.line;\n      while (true) {\n        if (lineNum >= vimState.document.lineCount) {\n          break;\n        }\n        const lineText = vimState.document.lineAt(lineNum).text;\n        const matchIndex = lineText.indexOf(this.charToMatch, selStart.character);\n        if (matchIndex !== -1) {\n          openPos = new Position(lineNum, matchIndex);\n          break;\n        }\n        ++lineNum;\n      }\n      if (openPos === undefined) return failedMovement(vimState);\n    }\n\n    // Next, search forwards for the closing character which matches\n    let closePos = PairMatcher.nextPairedChar(openPos, this.charToMatch, vimState, true);\n    if (closePos === undefined) {\n      return failedMovement(vimState);\n    }\n\n    if (\n      !this.includeSurrounding &&\n      (isVisualMode(vimState.currentMode) || !firstIteration) &&\n      selStart.getLeftThroughLineBreaks(false).isBeforeOrEqual(openPos) &&\n      selEnd.getRightThroughLineBreaks(false).isAfterOrEqual(closePos)\n    ) {\n      // Special case: inner, with all inner content already selected\n      const outerOpenPos = PairMatcher.nextPairedChar(openPos, closingChar, vimState, false);\n      const outerClosePos = outerOpenPos\n        ? PairMatcher.nextPairedChar(outerOpenPos, this.charToMatch, vimState, false)\n        : undefined;\n\n      if (outerOpenPos && outerClosePos) {\n        openPos = outerOpenPos;\n        closePos = outerClosePos;\n      }\n    }\n\n    if (this.includeSurrounding) {\n      if (vimState.currentMode !== Mode.Visual) {\n        closePos = new Position(closePos.line, closePos.character + 1);\n      }\n    } else {\n      openPos = openPos.getRightThroughLineBreaks();\n      // If the closing character is the first on the line, don't swallow it.\n      if (closePos.isInLeadingWhitespace(vimState.document)) {\n        closePos = closePos.getLineBegin();\n      }\n\n      if (vimState.currentMode === Mode.Visual) {\n        closePos = closePos.getLeftThroughLineBreaks();\n      }\n    }\n\n    if (lastIteration && !isVisualMode(vimState.currentMode) && selStart.isBefore(openPos)) {\n      vimState.recordedState.operatorPositionDiff = openPos.subtract(selStart);\n    }\n\n    // Adjust for VisualLine mode: exclude the line containing the closing brace\n    // moves the cursor back to just within the brackets, accurately mirroring what\n    // Vim does for Vi{ Vi( Vi[ etc.\n    if (\n      !this.includeSurrounding &&\n      vimState.currentMode === Mode.VisualLine &&\n      closePos.line > openPos.line\n    ) {\n      const adjustedLine = closePos.line - 1;\n      if (adjustedLine >= 0) {\n        const lineText = vimState.document.lineAt(adjustedLine).text;\n        closePos = new Position(adjustedLine, lineText.length);\n      }\n    }\n\n    // TODO: setting the cursor manually like this shouldn't be necessary (probably a Cursor, not Position, should be passed to `exec`)\n    vimState.cursorStartPosition = openPos;\n    return {\n      start: openPos,\n      stop: closePos,\n    };\n  }\n}\n\n@RegisterAction\nexport class MoveInsideParentheses extends MoveInsideCharacter {\n  keys = [\n    ['i', '('],\n    ['i', ')'],\n    ['i', 'b'],\n  ];\n  charToMatch = '(';\n}\n\n@RegisterAction\nexport class MoveAroundParentheses extends MoveInsideCharacter {\n  keys = [\n    ['a', '('],\n    ['a', ')'],\n    ['a', 'b'],\n  ];\n  charToMatch = '(';\n  override includeSurrounding = true;\n}\n\n// special treatment for curly braces\nexport abstract class MoveCurlyBrace extends MoveInsideCharacter {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  protected charToMatch: string = '{';\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<IMovement> {\n    // curly braces has a special treatment. In case the cursor is before an opening curly brace,\n    // and there are no characters before the opening curly brace in the same line, it should jump\n    // to the next opening curly brace, even if it already inside a pair of curly braces.\n    const text = vimState.document.lineAt(position).text;\n    const openCurlyBraceIndexFromCursor = text.substring(position.character).indexOf('{');\n    const startSameAsEnd = vimState.cursorStartPosition.isEqual(position);\n    if (\n      openCurlyBraceIndexFromCursor !== -1 &&\n      text.substring(0, position.character + openCurlyBraceIndexFromCursor).trim().length === 0 &&\n      startSameAsEnd\n    ) {\n      const curlyPos = position.with(\n        position.line,\n        position.character + openCurlyBraceIndexFromCursor,\n      );\n      vimState.cursorStartPosition = vimState.cursorStopPosition = curlyPos;\n      const movement = await super.execAction(curlyPos, vimState, firstIteration, lastIteration);\n      if (movement.failed) {\n        return movement;\n      }\n      const { start, stop } = movement;\n      if (!isVisualMode(vimState.currentMode) && position.isBefore(start)) {\n        vimState.recordedState.operatorPositionDiff = start.subtract(position);\n      } else if (!isVisualMode(vimState.currentMode) && position.isAfter(stop)) {\n        if (position.line === stop.line) {\n          vimState.recordedState.operatorPositionDiff = stop.subtract(position);\n        } else {\n          vimState.recordedState.operatorPositionDiff = start.subtract(position);\n        }\n      }\n\n      vimState.cursorStartPosition = start;\n      vimState.cursorStopPosition = stop;\n      return movement;\n    } else {\n      return super.execAction(position, vimState, firstIteration, lastIteration);\n    }\n  }\n}\n\n@RegisterAction\nexport class MoveInsideCurlyBrace extends MoveCurlyBrace {\n  keys = [\n    ['i', '{'],\n    ['i', '}'],\n    ['i', 'B'],\n  ];\n}\n\n@RegisterAction\nexport class MoveAroundCurlyBrace extends MoveCurlyBrace {\n  keys = [\n    ['a', '{'],\n    ['a', '}'],\n    ['a', 'B'],\n  ];\n  override includeSurrounding = true;\n}\n\n@RegisterAction\nexport class MoveInsideCaret extends MoveInsideCharacter {\n  keys = [\n    ['i', '<'],\n    ['i', '>'],\n  ];\n  charToMatch = '<';\n}\n\n@RegisterAction\nexport class MoveAroundCaret extends MoveInsideCharacter {\n  keys = [\n    ['a', '<'],\n    ['a', '>'],\n  ];\n  charToMatch = '<';\n  override includeSurrounding = true;\n}\n\n@RegisterAction\nexport class MoveInsideSquareBracket extends MoveInsideCharacter {\n  keys = [\n    ['i', '['],\n    ['i', ']'],\n  ];\n  charToMatch = '[';\n}\n\n@RegisterAction\nexport class MoveAroundSquareBracket extends MoveInsideCharacter {\n  keys = [\n    ['a', '['],\n    ['a', ']'],\n  ];\n  charToMatch = '[';\n  override includeSurrounding = true;\n}\n\n// TODO: Shouldn't this be a TextObject? A clearer delineation between motions and objects should be made.\nexport abstract class MoveQuoteMatch extends BaseMovement {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n  protected readonly anyQuote: boolean = false;\n  protected abstract readonly charToMatch: '\"' | \"'\" | '`';\n  protected includeQuotes = false;\n  override isJump = true;\n  readonly which: WhichQuotes = 'current';\n\n  // HACK: surround uses these classes, but does not want trailing whitespace to be included\n  private adjustForTrailingWhitespace: boolean = true;\n\n  constructor(adjustForTrailingWhitespace: boolean = true) {\n    super();\n    this.adjustForTrailingWhitespace = adjustForTrailingWhitespace;\n  }\n\n  public override async execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<IMovement> {\n    // TODO: this is super janky\n    return (await super.execActionWithCount(position, vimState, 1)) as IMovement;\n  }\n\n  public override async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    if (\n      !this.includeQuotes &&\n      (vimState.recordedState.count > 1 || vimState.recordedState.operatorCount > 1)\n    ) {\n      // i\" special case: With a count of 2 the quotes are included, but no extra white space as with a\"/a'/a`.\n      // (a\" does not make use of count)\n      this.includeQuotes = true;\n      this.adjustForTrailingWhitespace = false;\n    }\n\n    if (useSmartQuotes()) {\n      const quoteMatcher = new SmartQuoteMatcher(\n        this.anyQuote ? 'any' : this.charToMatch,\n        vimState.document,\n      );\n      const res = quoteMatcher.smartSurroundingQuotes(position, this.which);\n\n      if (res === undefined) {\n        return failedMovement(vimState);\n      }\n      let { start, stop, lineText } = res;\n\n      if (!this.includeQuotes) {\n        // Don't include the quotes\n        start = start.translate({ characterDelta: 1 });\n        stop = stop.translate({ characterDelta: -1 });\n      } else if (\n        this.adjustForTrailingWhitespace &&\n        configuration.targets.smartQuotes.aIncludesSurroundingSpaces\n      ) {\n        // Include trailing whitespace if there is any...\n        const trailingWhitespace = lineText.substring(stop.character + 1).search(/\\S|$/);\n        if (trailingWhitespace > 0) {\n          stop = stop.translate({ characterDelta: trailingWhitespace });\n        } else {\n          // ...otherwise include leading whitespace\n          start = start.with({ character: lineText.substring(0, start.character).search(/\\s*$/) });\n        }\n      }\n\n      if (!isVisualMode(vimState.currentMode) && position.isBefore(start)) {\n        vimState.recordedState.operatorPositionDiff = start.subtract(position);\n      } else if (!isVisualMode(vimState.currentMode) && position.isAfter(stop)) {\n        if (position.line === stop.line) {\n          vimState.recordedState.operatorPositionDiff = stop.getRight().subtract(position);\n        } else {\n          vimState.recordedState.operatorPositionDiff = start.subtract(position);\n        }\n      }\n\n      vimState.cursorStartPosition = start;\n      return {\n        start,\n        stop,\n      };\n    } else {\n      const text = vimState.document.lineAt(position).text;\n      const quoteMatcher = new QuoteMatcher(this.charToMatch, text);\n      const quoteIndices = quoteMatcher.surroundingQuotes(position.character);\n\n      if (quoteIndices === undefined) {\n        return failedMovement(vimState);\n      }\n\n      let [start, end] = quoteIndices;\n\n      if (!this.includeQuotes) {\n        // Don't include the quotes\n        start++;\n        end--;\n      } else if (this.adjustForTrailingWhitespace) {\n        // Include trailing whitespace if there is any...\n        const trailingWhitespace = text.substring(end + 1).search(/\\S|$/);\n        if (trailingWhitespace > 0) {\n          end += trailingWhitespace;\n        } else {\n          // ...otherwise include leading whitespace\n          start = text.substring(0, start).search(/\\s*$/);\n        }\n      }\n\n      const startPos = new Position(position.line, start);\n      const endPos = new Position(position.line, end);\n\n      if (!isVisualMode(vimState.currentMode) && position.isBefore(startPos)) {\n        vimState.recordedState.operatorPositionDiff = startPos.subtract(position);\n      }\n\n      return {\n        start: startPos,\n        stop: endPos,\n      };\n    }\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const result = await this.execAction(position, vimState);\n    if (isIMovement(result)) {\n      if (result.failed) {\n        vimState.recordedState.hasRunOperator = false;\n        vimState.recordedState.actionsRun = [];\n      } else {\n        result.stop = result.stop.getRight();\n      }\n    }\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveInsideSingleQuotes extends MoveQuoteMatch {\n  keys = ['i', \"'\"];\n  readonly charToMatch = \"'\";\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundSingleQuotes extends MoveQuoteMatch {\n  keys = ['a', \"'\"];\n  readonly charToMatch = \"'\";\n  override includeQuotes = true;\n}\n\n@RegisterAction\nclass MoveInsideDoubleQuotes extends MoveQuoteMatch {\n  keys = ['i', '\"'];\n  readonly charToMatch = '\"';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundDoubleQuotes extends MoveQuoteMatch {\n  keys = ['a', '\"'];\n  readonly charToMatch = '\"';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nclass MoveInsideBacktick extends MoveQuoteMatch {\n  keys = ['i', '`'];\n  readonly charToMatch = '`';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundBacktick extends MoveQuoteMatch {\n  keys = ['a', '`'];\n  readonly charToMatch = '`';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nclass MoveToUnclosedRoundBracketBackward extends MoveToMatchingBracket {\n  override keys = ['[', '('];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const charToMatch = ')';\n    const result = PairMatcher.nextPairedChar(position, charToMatch, vimState, false);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveToUnclosedRoundBracketForward extends MoveToMatchingBracket {\n  override keys = [']', ')'];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const charToMatch = '(';\n    const result = PairMatcher.nextPairedChar(position, charToMatch, vimState, false);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    if (\n      vimState.recordedState.operator instanceof ChangeOperator ||\n      vimState.recordedState.operator instanceof DeleteOperator ||\n      vimState.recordedState.operator instanceof YankOperator\n    ) {\n      return result.getLeftThroughLineBreaks();\n    }\n\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveToUnclosedCurlyBracketBackward extends MoveToMatchingBracket {\n  override keys = ['[', '{'];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const charToMatch = '}';\n    const result = PairMatcher.nextPairedChar(position, charToMatch, vimState, false);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n    return result;\n  }\n}\n\n@RegisterAction\nclass MoveToUnclosedCurlyBracketForward extends MoveToMatchingBracket {\n  override keys = [']', '}'];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    const charToMatch = '{';\n    const result = PairMatcher.nextPairedChar(position, charToMatch, vimState, false);\n\n    if (!result) {\n      return failedMovement(vimState);\n    }\n\n    if (\n      vimState.recordedState.operator instanceof ChangeOperator ||\n      vimState.recordedState.operator instanceof DeleteOperator ||\n      vimState.recordedState.operator instanceof YankOperator\n    ) {\n      return result.getLeftThroughLineBreaks();\n    }\n\n    return result;\n  }\n}\n\nabstract class MoveTagMatch extends ExpandingSelection {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n  protected includeTag = false;\n  override isJump = true;\n\n  public override async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    const editorText = vimState.document.getText();\n    const offset = vimState.document.offsetAt(position);\n    const tagMatcher = new TagMatcher(editorText, offset, vimState);\n    const start = tagMatcher.findOpening(this.includeTag);\n    const end = tagMatcher.findClosing(this.includeTag);\n\n    if (start === undefined || end === undefined) {\n      return failedMovement(vimState);\n    }\n\n    const startPosition =\n      start >= 0 ? vimState.document.positionAt(start) : vimState.cursorStartPosition;\n    let endPosition = end >= 0 ? vimState.document.positionAt(end) : position;\n    if (vimState.currentMode === Mode.Visual || vimState.currentMode === Mode.SurroundInputMode) {\n      endPosition = endPosition.getLeftThroughLineBreaks();\n    }\n\n    if (position.isAfter(endPosition)) {\n      vimState.recordedState.transformer.moveCursor(endPosition.subtract(position));\n    } else if (position.isBefore(startPosition)) {\n      vimState.recordedState.transformer.moveCursor(startPosition.subtract(position));\n    }\n    // if (start === end) {\n    //   if (vimState.recordedState.operator instanceof ChangeOperator) {\n    //     await vimState.setCurrentMode(ModeName.Insert);\n    //   }\n    //   return failedMovement(vimState);\n    // }\n    vimState.cursorStartPosition = startPosition;\n    return {\n      start: startPosition,\n      stop: endPosition,\n    };\n  }\n}\n\n@RegisterAction\nexport class MoveInsideTag extends MoveTagMatch {\n  keys = ['i', 't'];\n  override includeTag = false;\n}\n\n@RegisterAction\nexport class MoveAroundTag extends MoveTagMatch {\n  keys = ['a', 't'];\n  override includeTag = true;\n}\n"
  },
  {
    "path": "src/actions/operator.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { reportLinesChanged, reportLinesYanked } from '../util/statusBarTextUtils';\nimport { isHighSurrogate, isLowSurrogate } from '../util/util';\nimport { ExCommandLine } from './../cmd_line/commandLine';\nimport { Cursor } from './../common/motion/cursor';\nimport { PositionDiff, earlierOf, sorted } from './../common/motion/position';\nimport { configuration } from './../configuration/configuration';\nimport { DotCommandStatus, Mode, isVisualMode } from './../mode/mode';\nimport { Register, RegisterMode } from './../register/register';\nimport { VimState } from './../state/vimState';\nimport { TextEditor } from './../textEditor';\nimport { BaseAction, RegisterAction } from './base';\n\nexport abstract class BaseOperator extends BaseAction {\n  override actionType = 'operator' as const;\n\n  constructor(multicursorIndex?: number) {\n    super();\n    this.multicursorIndex = multicursorIndex;\n  }\n  override createsUndoPoint = true;\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (this.doesRepeatedOperatorApply(vimState, keysPressed)) {\n      return true;\n    }\n    if (!this.modes.includes(vimState.currentMode)) {\n      return false;\n    }\n    if (!BaseAction.CompareKeypressSequence(this.keys, keysPressed)) {\n      return false;\n    }\n    if (this instanceof BaseOperator && vimState.recordedState.operator) {\n      return false;\n    }\n\n    return true;\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    if (!this.modes.includes(vimState.currentMode)) {\n      return false;\n    }\n    if (!BaseAction.CompareKeypressSequence(this.keys.slice(0, keysPressed.length), keysPressed)) {\n      return false;\n    }\n    if (this instanceof BaseOperator && vimState.recordedState.operator) {\n      return false;\n    }\n\n    return true;\n  }\n\n  public doesRepeatedOperatorApply(vimState: VimState, keysPressed: string[]) {\n    const nonCountActions = vimState.recordedState.actionsRun.filter((x) => x.name !== 'cmd_num');\n    const prevAction = nonCountActions.at(-1);\n    return (\n      keysPressed.length === 1 &&\n      prevAction &&\n      this.modes.includes(vimState.currentMode) &&\n      // The previous action is the same as the one we're testing\n      prevAction.constructor === this.constructor &&\n      // The key pressed is the same as the previous action's last key.\n      BaseAction.CompareKeypressSequence(prevAction.keysPressed.slice(-1), keysPressed)\n    );\n  }\n\n  /**\n   * Run this operator on a range, returning the new location of the cursor.\n   */\n  public abstract run(vimState: VimState, start: Position, stop: Position): Promise<void>;\n\n  public async runRepeat(vimState: VimState, position: Position, count: number): Promise<void> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    await this.run(\n      vimState,\n      position.getLineBegin(),\n      position.getDown(Math.max(0, count - 1)).getLineEnd(),\n    );\n  }\n\n  public highlightYankedRanges(vimState: VimState, ranges: vscode.Range[]) {\n    if (!configuration.highlightedyank.enable) {\n      return;\n    }\n\n    const yankDecoration = vscode.window.createTextEditorDecorationType({\n      backgroundColor: configuration.highlightedyank.color,\n      color: configuration.highlightedyank.textColor,\n    });\n\n    vimState.editor.setDecorations(yankDecoration, ranges);\n    setTimeout(() => yankDecoration.dispose(), configuration.highlightedyank.duration);\n  }\n}\n\n@RegisterAction\nexport class DeleteOperator extends BaseOperator {\n  public override name = 'delete_op';\n  public keys = ['d'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // TODO: this is off by one when character-wise and not including last EOL\n    const numLinesDeleted = Math.abs(start.line - end.line) + 1;\n\n    if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      start = start.getLineBegin();\n      end = end.getLineEnd();\n    }\n\n    end = new Position(end.line, end.character + 1);\n\n    const isOnLastLine = end.line === vimState.document.lineCount - 1;\n\n    // Vim does this weird thing where it allows you to select and delete\n    // the newline character, which it places 1 past the last character\n    // in the line. Here we interpret a character position 1 past the end\n    // as selecting the newline character. Don't allow this in visual block mode\n    if (\n      vimState.currentMode !== Mode.VisualBlock &&\n      !isOnLastLine &&\n      end.character === vimState.document.lineAt(end).text.length + 1\n    ) {\n      end = new Position(end.line + 1, 0);\n    }\n\n    const sLine = vimState.document.lineAt(start.line).text;\n    const eLine = vimState.document.lineAt(end.line).text;\n    if (\n      start.character !== 0 &&\n      isLowSurrogate(sLine.charCodeAt(start.character)) &&\n      isHighSurrogate(sLine.charCodeAt(start.character - 1))\n    ) {\n      start = start.getLeft();\n    }\n    if (\n      end.character !== 0 &&\n      isLowSurrogate(eLine.charCodeAt(end.character)) &&\n      isHighSurrogate(eLine.charCodeAt(end.character - 1))\n    ) {\n      end = end.getRight();\n    }\n\n    // Yank the text\n    let text = vimState.document.getText(new vscode.Range(start, end));\n    if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      // When deleting linewise, exclude final newline\n      text = text.endsWith('\\r\\n')\n        ? text.slice(0, -2)\n        : text.endsWith('\\n')\n          ? text.slice(0, -1)\n          : text;\n    }\n    Register.put(vimState, text, this.multicursorIndex, true);\n\n    // When deleting the last line linewise, we need to delete the newline\n    // character BEFORE the range because there isn't one after the range.\n    if (\n      isOnLastLine &&\n      start.line !== 0 &&\n      vimState.currentRegisterMode === RegisterMode.LineWise\n    ) {\n      start = start.getUp().getLineEnd();\n    }\n\n    let diff: PositionDiff | undefined;\n    if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      diff = PositionDiff.startOfLine();\n    } else if (start.character > vimState.document.lineAt(start).text.length) {\n      diff = PositionDiff.offset({ character: -1 });\n    }\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'deleteRange',\n      range: new vscode.Range(start, end),\n      diff,\n    });\n\n    await vimState.setCurrentMode(Mode.Normal);\n\n    reportLinesChanged(-numLinesDeleted, vimState);\n  }\n}\n\n@RegisterAction\nclass DeleteOperatorVisual extends BaseOperator {\n  public keys = ['D'];\n  public modes = [Mode.Visual, Mode.VisualLine];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // ensures linewise deletion when in visual mode\n    // see special case in DeleteOperator.delete()\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    await new DeleteOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nexport class YankOperator extends BaseOperator {\n  public keys = ['y'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  override name = 'yank_op';\n  override createsUndoPoint = false;\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    [start, end] = sorted(start, end);\n    let extendedEnd = new Position(end.line, end.character + 1);\n\n    if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      start = start.getLineBegin();\n      extendedEnd = extendedEnd.getLineEnd();\n    }\n\n    const sLine = vimState.document.lineAt(start.line).text;\n    const eLine = vimState.document.lineAt(extendedEnd.line).text;\n    if (\n      start.character !== 0 &&\n      isLowSurrogate(sLine.charCodeAt(start.character)) &&\n      isHighSurrogate(sLine.charCodeAt(start.character - 1))\n    ) {\n      start = start.getLeft();\n    }\n    if (\n      extendedEnd.character !== 0 &&\n      isLowSurrogate(eLine.charCodeAt(extendedEnd.character)) &&\n      isHighSurrogate(eLine.charCodeAt(extendedEnd.character - 1))\n    ) {\n      extendedEnd = extendedEnd.getRight();\n    }\n    const range = new vscode.Range(start, extendedEnd);\n    let text = vimState.document.getText(range);\n\n    // If we selected the newline character, add it as well.\n    if (\n      vimState.currentMode === Mode.Visual &&\n      extendedEnd.character === vimState.document.lineAt(extendedEnd).text.length + 1\n    ) {\n      text = text + '\\n';\n    }\n\n    this.highlightYankedRanges(vimState, [range]);\n\n    Register.put(vimState, text, this.multicursorIndex, true);\n\n    vimState.cursorStopPosition =\n      vimState.currentMode === Mode.Normal && vimState.currentRegisterMode === RegisterMode.LineWise\n        ? start.with({ character: vimState.cursorStopPosition.character })\n        : start;\n\n    await vimState.setCurrentMode(Mode.Normal);\n\n    const numLinesYanked = text.split('\\n').length;\n    reportLinesYanked(numLinesYanked, vimState);\n  }\n}\n\n@RegisterAction\nclass FilterOperator extends BaseOperator {\n  public keys = ['!'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    [start, end] = sorted(start, end);\n\n    let commandLineText: string;\n    if (vimState.currentMode === Mode.Normal && start.line === end.line) {\n      commandLineText = '.!';\n    } else if (vimState.currentMode === Mode.Normal && start.line !== end.line) {\n      commandLineText = `.,.+${end.line - start.line}!`;\n    } else {\n      commandLineText = \"'<,'>!\";\n    }\n\n    vimState.cursorStartPosition = start;\n    if (vimState.currentMode === Mode.Normal) {\n      vimState.cursorStopPosition = start;\n    } else {\n      vimState.cursors = [...vimState.cursorsInitialState];\n    }\n\n    const previousMode = vimState.currentMode;\n    await vimState.setCurrentMode(Mode.CommandlineInProgress);\n    // TODO: Change or supplement `setCurrentMode` API so this isn't necessary\n    if (vimState.modeData.mode === Mode.CommandlineInProgress) {\n      vimState.modeData.commandLine = new ExCommandLine(commandLineText, previousMode);\n    }\n  }\n}\n\n@RegisterAction\nclass ShiftYankOperatorVisual extends BaseOperator {\n  public keys = ['Y'];\n  public modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    await new YankOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nclass DeleteOperatorXVisual extends BaseOperator {\n  public keys = [['x'], ['<Del>']];\n  public modes = [Mode.Visual, Mode.VisualLine];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    await new DeleteOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nclass ChangeOperatorSVisual extends BaseOperator {\n  public keys = ['s'];\n  public modes = [Mode.Visual, Mode.VisualLine];\n\n  // Don't clash with Sneak plugin\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return super.doesActionApply(vimState, keysPressed) && !configuration.sneak;\n  }\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    await new ChangeOperator(this.multicursorIndex).run(vimState, start, end);\n  }\n}\n\n@RegisterAction\nclass FormatOperator extends BaseOperator {\n  public keys = ['='];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // = operates on complete lines\n    vimState.editor.selection = new vscode.Selection(start.getLineBegin(), end.getLineEnd());\n    await vscode.commands.executeCommand('editor.action.formatSelection');\n    let line = vimState.cursorStartPosition.line;\n\n    if (vimState.cursorStartPosition.isAfter(vimState.cursorStopPosition)) {\n      line = vimState.cursorStopPosition.line;\n    }\n\n    const newCursorPosition = TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, line);\n    vimState.cursorStopPosition = newCursorPosition;\n    vimState.cursorStartPosition = newCursorPosition;\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\nabstract class ChangeCaseOperator extends BaseOperator {\n  public modes = [Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  abstract transformText(text: string): string;\n\n  public async run(vimState: VimState, startPos: Position, endPos: Position): Promise<void> {\n    if (vimState.currentMode === Mode.VisualBlock) {\n      for (const { start, end } of TextEditor.iterateLinesInBlock(vimState)) {\n        const range = new vscode.Range(start, end);\n        vimState.recordedState.transformer.replace(\n          range,\n          this.transformText(vimState.document.getText(range)),\n        );\n      }\n\n      // HACK: currently must do this nonsense to collapse all cursors into one\n      for (let i = 0; i < vimState.editor.selections.length; i++) {\n        vimState.recordedState.transformer.moveCursor(\n          PositionDiff.exactPosition(earlierOf(startPos, endPos)),\n          i,\n        );\n      }\n    } else {\n      if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n        startPos = startPos.getLineBegin();\n        endPos = endPos.getLineEnd();\n      }\n\n      const range = new vscode.Range(startPos, new Position(endPos.line, endPos.character + 1));\n\n      vimState.recordedState.transformer.replace(\n        range,\n        this.transformText(vimState.document.getText(range)),\n        PositionDiff.exactPosition(startPos),\n      );\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass UpperCaseOperator extends ChangeCaseOperator {\n  public keys = [['g', 'U'], ['U']];\n\n  public transformText(text: string): string {\n    return text.toUpperCase();\n  }\n}\n\n@RegisterAction\nclass UpperCaseWithMotion extends UpperCaseOperator {\n  public override keys = [['g', 'U']];\n  public override modes = [Mode.Normal];\n}\n\n@RegisterAction\nclass LowerCaseOperator extends ChangeCaseOperator {\n  public keys = [['g', 'u'], ['u']];\n\n  public transformText(text: string): string {\n    return text.toLowerCase();\n  }\n}\n\n@RegisterAction\nclass LowerCaseWithMotion extends LowerCaseOperator {\n  public override keys = [['g', 'u']];\n  public override modes = [Mode.Normal];\n}\n\n@RegisterAction\nclass ToggleCaseOperator extends ChangeCaseOperator {\n  public keys = [['g', '~'], ['~']];\n\n  public transformText(text: string): string {\n    let newText = '';\n    for (const char of text) {\n      let toggled = char.toLocaleLowerCase();\n      if (toggled === char) {\n        toggled = char.toLocaleUpperCase();\n      }\n      newText += toggled;\n    }\n    return newText;\n  }\n}\n\n@RegisterAction\nclass ToggleCaseWithMotion extends ToggleCaseOperator {\n  public override keys = [['g', '~']];\n  public override modes = [Mode.Normal];\n}\n\n@RegisterAction\nclass IndentOperator extends BaseOperator {\n  modes = [Mode.Normal];\n  keys = ['>'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    vimState.editor.selection = new vscode.Selection(start.getLineBegin(), end.getLineEnd());\n\n    await vscode.commands.executeCommand('editor.action.indentLines');\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursorStopPosition = start.obeyStartOfLine(vimState.document);\n  }\n}\n\n/**\n * `3>` to indent a line 3 times in visual mode is actually a bit of a special case.\n *\n * > is an operator, and generally speaking, you don't run operators multiple times, you run motions multiple times.\n * e.g. `d3w` runs `w` 3 times, then runs d once.\n *\n * Same with literally every other operator motion combination... until `3>`in visual mode\n * walked into my life.\n */\n@RegisterAction\nclass IndentOperatorVisualAndVisualLine extends BaseOperator {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['>'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // Repeating this command with dot should apply the indent to the previous selection\n    if (\n      vimState.dotCommandStatus === DotCommandStatus.Executing &&\n      vimState.dotCommandPreviousVisualSelection\n    ) {\n      if (vimState.cursorStartPosition.isAfter(vimState.cursorStopPosition)) {\n        const shiftSelectionByNum =\n          vimState.dotCommandPreviousVisualSelection.end.line -\n          vimState.dotCommandPreviousVisualSelection.start.line;\n\n        start = vimState.cursorStartPosition;\n        const newEnd = vimState.cursorStartPosition.getDown(shiftSelectionByNum);\n\n        vimState.editor.selection = new vscode.Selection(start, newEnd);\n      }\n    }\n\n    for (let i = 0; i < (vimState.recordedState.count || 1); i++) {\n      await vscode.commands.executeCommand('editor.action.indentLines');\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursorStopPosition = start.obeyStartOfLine(vimState.document);\n  }\n}\n\n@RegisterAction\nclass IndentOperatorVisualBlock extends BaseOperator {\n  modes = [Mode.VisualBlock];\n  keys = ['>'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    /**\n     * Repeating this command with dot should apply the indent to the left edge of the\n     * block formed by extending the cursor start position downward by the number of lines\n     * in the previous visual block selection.\n     */\n    if (\n      vimState.dotCommandStatus === DotCommandStatus.Executing &&\n      vimState.dotCommandPreviousVisualSelection\n    ) {\n      const shiftSelectionByNum = Math.abs(\n        vimState.dotCommandPreviousVisualSelection.end.line -\n          vimState.dotCommandPreviousVisualSelection.start.line,\n      );\n\n      start = vimState.cursorStartPosition;\n      end = vimState.cursorStartPosition.getDown(shiftSelectionByNum);\n\n      vimState.editor.selection = new vscode.Selection(start, end);\n    }\n\n    for (let lineIdx = 0; lineIdx < end.line - start.line + 1; lineIdx++) {\n      const tabSize = Number(vimState.editor.options.tabSize);\n      const currentLineEnd = vimState.document.lineAt(start.line + lineIdx).range.end.character;\n\n      if (currentLineEnd > start.character) {\n        vimState.recordedState.transformer.addTransformation({\n          type: 'insertText',\n          text: ' '.repeat(tabSize).repeat(vimState.recordedState.count || 1),\n          position: start.getDown(lineIdx),\n          manuallySetCursorPositions: true,\n        });\n      }\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursors = [Cursor.atPosition(start)];\n  }\n}\n@RegisterAction\nclass OutdentOperator extends BaseOperator {\n  modes = [Mode.Normal];\n  keys = ['<'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    vimState.editor.selection = new vscode.Selection(start, end.getLineEnd());\n\n    await vscode.commands.executeCommand('editor.action.outdentLines');\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursorStopPosition = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      start.line,\n    );\n  }\n}\n\n/**\n * See comment for IndentOperatorVisualAndVisualLine\n */\n@RegisterAction\nclass OutdentOperatorVisualAndVisualLine extends BaseOperator {\n  modes = [Mode.Visual, Mode.VisualLine];\n  keys = ['<'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // Repeating this command with dot should apply the indent to the previous selection\n    if (\n      vimState.dotCommandStatus === DotCommandStatus.Executing &&\n      vimState.dotCommandPreviousVisualSelection\n    ) {\n      if (vimState.cursorStartPosition.isAfter(vimState.cursorStopPosition)) {\n        const shiftSelectionByNum =\n          vimState.dotCommandPreviousVisualSelection.end.line -\n          vimState.dotCommandPreviousVisualSelection.start.line;\n\n        start = vimState.cursorStartPosition;\n        const newEnd = vimState.cursorStartPosition.getDown(shiftSelectionByNum);\n\n        vimState.editor.selection = new vscode.Selection(start, newEnd);\n      }\n    }\n\n    for (let i = 0; i < (vimState.recordedState.count || 1); i++) {\n      await vscode.commands.executeCommand('editor.action.outdentLines');\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursorStopPosition = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      start.line,\n    );\n  }\n}\n\n@RegisterAction\nclass OutdentOperatorVisualBlock extends BaseOperator {\n  modes = [Mode.VisualBlock];\n  keys = ['<'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    /**\n     * Repeating this command with dot should apply the outdent to the left edge of the\n     * block formed by extending the cursor start position downward by the number of lines\n     * in the previous visual block selection.\n     */\n    if (\n      vimState.dotCommandStatus === DotCommandStatus.Executing &&\n      vimState.dotCommandPreviousVisualSelection\n    ) {\n      const shiftSelectionByNum = Math.abs(\n        vimState.dotCommandPreviousVisualSelection.end.line -\n          vimState.dotCommandPreviousVisualSelection.start.line,\n      );\n\n      start = vimState.cursorStartPosition;\n      end = vimState.cursorStartPosition.getDown(shiftSelectionByNum);\n\n      vimState.editor.selection = new vscode.Selection(start, end);\n    }\n\n    for (let lineIdx = 0; lineIdx < end.line - start.line + 1; lineIdx++) {\n      const tabSize = Number(vimState.editor.options.tabSize);\n      const currentLine = vimState.document.lineAt(start.line + lineIdx);\n      const currentLineEnd = currentLine.range.end.character;\n\n      if (currentLineEnd > start.character) {\n        const currentLineFromStart = currentLine.text.slice(start.character);\n        const isFirstCharBlank = /\\s/.test(currentLineFromStart.charAt(0));\n\n        if (isFirstCharBlank) {\n          const currentLinePosition = start.getDown(lineIdx);\n          const distToNonBlankChar = currentLineFromStart.match(/\\S/)?.index ?? 0;\n          const outdentDist = Math.min(\n            distToNonBlankChar,\n            tabSize * (vimState.recordedState.count || 1),\n          );\n\n          vimState.recordedState.transformer.addTransformation({\n            type: 'deleteRange',\n            range: new vscode.Range(currentLinePosition, currentLinePosition.getRight(outdentDist)),\n            manuallySetCursorPositions: true,\n          });\n        }\n      }\n    }\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursors = [Cursor.atPosition(start)];\n  }\n}\n\n@RegisterAction\nexport class ChangeOperator extends BaseOperator {\n  public keys = ['c'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      start = start.getLineBegin();\n      end = end.getLineEnd();\n    } else if (vimState.currentMode === Mode.Visual && end.isLineEnd(vimState.document)) {\n      end = end.getRightThroughLineBreaks();\n    } else {\n      end = end.getRight();\n    }\n\n    const deleteRange = new vscode.Range(start, end);\n\n    Register.put(vimState, vimState.document.getText(deleteRange), this.multicursorIndex, true);\n\n    if (vimState.currentRegisterMode === RegisterMode.LineWise && configuration.autoindent) {\n      // Linewise is a bit of a special case - we want to preserve the first line's indentation,\n      // then let the language server adjust that indentation if it can.\n\n      const firstLineIndent = vimState.document.getText(\n        new vscode.Range(\n          deleteRange.start.getLineBegin(),\n          deleteRange.start.getLineBeginRespectingIndent(vimState.document),\n        ),\n      );\n\n      vimState.recordedState.transformer.replace(\n        deleteRange,\n        firstLineIndent,\n        PositionDiff.exactPosition(new Position(deleteRange.start.line, firstLineIndent.length)),\n      );\n\n      if (vimState.document.languageId !== 'plaintext') {\n        vimState.recordedState.transformer.vscodeCommand('editor.action.reindentselectedlines');\n        vimState.recordedState.transformer.moveCursor(\n          PositionDiff.endOfLine(),\n          this.multicursorIndex,\n        );\n      }\n    } else {\n      vimState.recordedState.transformer.delete(deleteRange);\n    }\n\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n}\n\n@RegisterAction\nclass YankVisualBlockMode extends BaseOperator {\n  public keys = ['y'];\n  public modes = [Mode.VisualBlock];\n  override createsUndoPoint = false;\n  runsOnceForEveryCursor() {\n    return false;\n  }\n\n  public async run(vimState: VimState, startPos: Position, endPos: Position): Promise<void> {\n    const ranges: vscode.Range[] = [];\n    const lines: string[] = [];\n    for (const { line, start, end } of TextEditor.iterateLinesInBlock(vimState)) {\n      lines.push(line);\n      ranges.push(new vscode.Range(start, end));\n    }\n\n    vimState.currentRegisterMode = RegisterMode.BlockWise;\n\n    this.highlightYankedRanges(vimState, ranges);\n\n    Register.put(vimState, lines.join('\\n'), this.multicursorIndex, true);\n\n    vimState.historyTracker.addMark(vimState.document, startPos, '<');\n    vimState.historyTracker.addMark(vimState.document, endPos, '>');\n\n    const numLinesYanked = lines.length;\n    reportLinesYanked(numLinesYanked, vimState);\n\n    await vimState.setCurrentMode(Mode.Normal);\n    vimState.cursorStopPosition = startPos;\n  }\n}\n\n@RegisterAction\nclass CommentOperator extends BaseOperator {\n  public keys = ['g', 'c'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    vimState.editor.selection = new vscode.Selection(start.getLineBegin(), end.getLineEnd());\n    await vscode.commands.executeCommand('editor.action.commentLine');\n\n    vimState.cursorStopPosition = new Position(start.line, 0);\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nexport class ROT13Operator extends BaseOperator {\n  public keys = ['g', '?'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    let selections: readonly vscode.Selection[];\n    if (isVisualMode(vimState.currentMode)) {\n      selections = vimState.editor.selections;\n    } else if (vimState.currentRegisterMode === RegisterMode.LineWise) {\n      selections = [new vscode.Selection(start.getLineBegin(), end.getLineEnd())];\n    } else {\n      selections = [new vscode.Selection(start, end.getRight())];\n    }\n\n    for (const range of selections) {\n      const original = vimState.document.getText(range);\n      vimState.recordedState.transformer.replace(range, ROT13Operator.rot13(original));\n    }\n  }\n\n  /**\n   * https://en.wikipedia.org/wiki/ROT13\n   */\n  public static rot13(str: string) {\n    return str\n      .split('')\n      .map((char: string) => {\n        let charCode = char.charCodeAt(0);\n\n        if (char >= 'a' && char <= 'z') {\n          const a = 'a'.charCodeAt(0);\n          charCode = ((charCode - a + 13) % 26) + a;\n        }\n\n        if (char >= 'A' && char <= 'Z') {\n          const A = 'A'.charCodeAt(0);\n          charCode = ((charCode - A + 13) % 26) + A;\n        }\n\n        return String.fromCharCode(charCode);\n      })\n      .join('');\n  }\n}\n\n@RegisterAction\nclass CommentBlockOperator extends BaseOperator {\n  public keys = ['g', 'C'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    if (vimState.currentMode === Mode.Normal) {\n      // If we're in normal mode, we need to construct a selection for the\n      // command to operate on. If we're not, we've already got it.\n      const endPosition = end.getRight();\n      vimState.editor.selection = new vscode.Selection(start, endPosition);\n    }\n    await vscode.commands.executeCommand('editor.action.blockComment');\n\n    vimState.cursorStopPosition = start;\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\ninterface CommentTypeSingle {\n  singleLine: true;\n\n  start: string;\n}\n\ninterface CommentTypeMultiLine {\n  singleLine: false;\n\n  start: string;\n  inner: string;\n  final: string;\n}\n\ntype CommentType = CommentTypeSingle | CommentTypeMultiLine;\n\n@RegisterAction\nclass ActionVisualReflowParagraph extends BaseOperator {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n  keys = ['g', 'q'];\n\n  public static CommentTypes: CommentType[] = [\n    { singleLine: false, start: '/**', inner: '*', final: '*/' },\n    { singleLine: false, start: '/*', inner: '*', final: '*/' },\n    { singleLine: false, start: '{-', inner: '-', final: '-}' },\n    { singleLine: true, start: '///' },\n    { singleLine: true, start: '//!' },\n    { singleLine: true, start: '//' },\n    { singleLine: true, start: '--' },\n    { singleLine: true, start: '#' },\n    { singleLine: true, start: ';' },\n    { singleLine: true, start: '*' },\n    { singleLine: true, start: '%' },\n\n    // Needs to come last, since everything starts with the empty string!\n    { singleLine: true, start: '' },\n  ];\n\n  public getIndentation(s: string): string {\n    // Use the indentation of the first non-whitespace line, if any such line is\n    // selected.\n    for (const line of s.split('\\n')) {\n      const result = line.match(/^\\s+/g);\n      const indent = result ? result[0] : '';\n\n      if (indent !== line) {\n        return indent;\n      }\n    }\n\n    return '';\n  }\n\n  public reflowParagraph(s: string): string {\n    const indent = this.getIndentation(s);\n\n    let indentLevel = 0;\n    for (const char of indent) {\n      indentLevel += char === '\\t' ? configuration.tabstop : 1;\n    }\n    const maximumLineLength = configuration.textwidth - indentLevel;\n\n    // Chunk the lines by commenting style.\n\n    interface Chunk {\n      commentType: CommentType;\n      content: string;\n      indentLevelAfterComment: number;\n      final: boolean;\n    }\n    const chunksToReflow: Chunk[] = [];\n\n    for (const line of s.split('\\n')) {\n      let lastChunk: Chunk | undefined = chunksToReflow.at(-1);\n      const trimmedLine = line.trimStart();\n\n      // See what comment type they are using.\n\n      let commentType: CommentType | undefined;\n\n      for (const type of ActionVisualReflowParagraph.CommentTypes) {\n        if (trimmedLine.startsWith(type.start)) {\n          commentType = type;\n\n          break;\n        }\n\n        // If they're currently in a multiline comment, see if they continued it.\n        if (\n          lastChunk &&\n          !lastChunk.final &&\n          type.start === lastChunk.commentType.start &&\n          !type.singleLine\n        ) {\n          if (trimmedLine.startsWith(type.inner)) {\n            commentType = type;\n\n            break;\n          }\n\n          if (trimmedLine.endsWith(type.final)) {\n            commentType = type;\n\n            break;\n          }\n        }\n      }\n\n      if (!commentType) {\n        break;\n      } // will never happen, just to satisfy typechecker.\n\n      // Did they start a new comment type?\n      if (!lastChunk || lastChunk.final || commentType.start !== lastChunk.commentType.start) {\n        const chunk = {\n          commentType,\n          content: `${trimmedLine.substr(commentType.start.length).trimStart()}`,\n          indentLevelAfterComment: 0,\n          final: false,\n        };\n        if (commentType.singleLine) {\n          chunk.indentLevelAfterComment =\n            trimmedLine.substr(commentType.start.length).length - chunk.content.length;\n        } else if (chunk.content.endsWith(commentType.final)) {\n          // Multiline comment started and ended on one line\n          chunk.content = chunk.content\n            .substr(0, chunk.content.length - commentType.final.length)\n            .trim();\n          chunk.final = true;\n        }\n        chunksToReflow.push(chunk);\n\n        continue;\n      }\n\n      // Parse out commenting style, gather words.\n\n      lastChunk = chunksToReflow.at(-1)!;\n\n      if (lastChunk.commentType.singleLine) {\n        // is it a continuation of a comment like \"//\"\n        lastChunk.content += `\\n${trimmedLine\n          .substr(lastChunk.commentType.start.length)\n          .trimStart()}`;\n      } else if (!lastChunk.final) {\n        // are we in the middle of a multiline comment like \"/*\"\n        if (trimmedLine.endsWith(lastChunk.commentType.final)) {\n          lastChunk.final = true;\n          const prefix = trimmedLine.startsWith(lastChunk.commentType.inner)\n            ? lastChunk.commentType.inner.length\n            : 0;\n          lastChunk.content += `\\n${trimmedLine\n            .substr(prefix, trimmedLine.length - lastChunk.commentType.final.length - prefix)\n            .trim()}`;\n        } else if (trimmedLine.startsWith(lastChunk.commentType.inner)) {\n          lastChunk.content += `\\n${trimmedLine\n            .substr(lastChunk.commentType.inner.length)\n            .trimStart()}`;\n        } else if (trimmedLine.startsWith(lastChunk.commentType.start)) {\n          lastChunk.content += `\\n${trimmedLine\n            .substr(lastChunk.commentType.start.length)\n            .trimStart()}`;\n        }\n      }\n    }\n\n    // Reflow each chunk.\n    const result: string[] = [];\n\n    for (const { commentType, content, indentLevelAfterComment } of chunksToReflow) {\n      const indentAfterComment = Array(indentLevelAfterComment + 1).join(' ');\n      const commentLength = commentType.start.length + indentAfterComment.length;\n\n      // Start with a single empty content line.\n      const lines: string[] = [``];\n\n      for (let line of content.split('\\n')) {\n        // Preserve blank lines in output.\n        if (line.trim() === '') {\n          // Replace empty content line with blank line.\n          if (lines.at(-1) === '') {\n            lines.pop();\n          }\n\n          lines.push(line);\n\n          // Add new empty content line for remaining content.\n          lines.push(``);\n\n          continue;\n        }\n\n        // Repeatedly partition line into pieces that fit in maximumLineLength\n        while (line) {\n          const lastLine = lines.at(-1)!;\n\n          // Determine the separator that we'd need to add to the last line\n          // in order to join onto this line.\n          let separator;\n          if (!lastLine) {\n            separator = '';\n          } else if (\n            configuration.joinspaces &&\n            (lastLine.endsWith('.') || lastLine.endsWith('?') || lastLine.endsWith('!'))\n          ) {\n            separator = '  ';\n          } else if (lastLine.endsWith(' ')) {\n            if (\n              configuration.joinspaces &&\n              (lastLine.endsWith('. ') || lastLine.endsWith('? ') || lastLine.endsWith('! '))\n            ) {\n              separator = ' ';\n            } else {\n              separator = '';\n            }\n          } else {\n            separator = ' ';\n          }\n\n          // Consider appending separator and part of line to last line\n          const remaining = maximumLineLength - separator.length - lastLine.length - commentLength;\n          const trimmedLine = line.trimStart();\n          if (trimmedLine.length <= remaining) {\n            // Entire line fits on last line\n            lines[lines.length - 1] += `${separator}${trimmedLine}`;\n            break;\n          } else {\n            // Find largest portion of line that fits on last line,\n            // by searching backward for a whitespace character (space or tab).\n            let breakpoint = Math.max(\n              trimmedLine.lastIndexOf(' ', remaining),\n              trimmedLine.lastIndexOf('\\t', remaining),\n            );\n            if (breakpoint < 0) {\n              // Next word is too long to fit on the current line.\n              if (lastLine) {\n                // Start a new line and try again next round.\n                lines.push('');\n                continue;\n              } else {\n                // Next word is too long to fit on a line by itself.\n                // Break it at the next word boundary, if there is one.\n                breakpoint = trimmedLine.search(/[ \\t]/);\n                if (breakpoint < 0) breakpoint = line.length;\n              }\n            }\n\n            // Split the line into the part that fits on the last line\n            // and the remainder.  Start a new line for the remainder.\n            lines[lines.length - 1] += `${separator}${trimmedLine.slice(0, breakpoint).trimEnd()}`;\n            line = line.slice(breakpoint + 1);\n            lines.push('');\n          }\n        }\n      }\n\n      // Drop final empty content line.\n      if (lines.at(-1) === '') {\n        lines.pop();\n      }\n\n      for (let i = 0; i < lines.length; i++) {\n        if (commentType.singleLine) {\n          lines[i] = `${indent}${commentType.start}${indentAfterComment}${lines[i]}`;\n        } else {\n          if (i === 0) {\n            if (lines[i] === '') {\n              lines[i] = `${indent}${commentType.start}`;\n            } else {\n              lines[i] = `${indent}${commentType.start} ${lines[i]}`;\n            }\n            if (i === lines.length - 1) {\n              lines[i] += ` ${commentType.final}`;\n            }\n          } else if (i === lines.length - 1) {\n            if (lines[i] === '') {\n              lines[i] = `${indent} ${commentType.final}`;\n            } else {\n              lines[i] = `${indent} ${commentType.inner} ${lines[i]} ${commentType.final}`;\n            }\n          } else {\n            if (lines[i] === '') {\n              lines[i] = `${indent} ${commentType.inner}`;\n            } else {\n              lines[i] = `${indent} ${commentType.inner} ${lines[i]}`;\n            }\n          }\n        }\n      }\n\n      result.push(...lines);\n    }\n\n    return result.join('\\n');\n  }\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    [start, end] = sorted(start, end);\n\n    start = start.getLineBegin();\n    end = end.getLineEnd();\n\n    let textToReflow = vimState.document.getText(new vscode.Range(start, end));\n    textToReflow = this.reflowParagraph(textToReflow);\n\n    vimState.recordedState.transformer.replace(\n      new vscode.Range(start, end),\n      textToReflow,\n      // Move cursor to front of line to realign the view\n      PositionDiff.exactCharacter({ character: 0 }),\n    );\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/camelCaseMotion.ts",
    "content": "import { Position } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { Mode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { TextObject } from '../../textobject/textobject';\nimport { WordType } from '../../textobject/word';\nimport { RegisterAction } from '../base';\nimport { BaseMovement, IMovement } from '../baseMotion';\nimport { ChangeOperator } from '../operator';\n\nabstract class CamelCaseBaseMovement extends BaseMovement {\n  public override doesActionApply(vimState: VimState, keysPressed: string[]) {\n    return configuration.camelCaseMotion.enable && super.doesActionApply(vimState, keysPressed);\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]) {\n    return configuration.camelCaseMotion.enable && super.couldActionApply(vimState, keysPressed);\n  }\n}\n\nabstract class CamelCaseTextObjectMovement extends TextObject {\n  public override doesActionApply(vimState: VimState, keysPressed: string[]) {\n    return configuration.camelCaseMotion.enable && super.doesActionApply(vimState, keysPressed);\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]) {\n    return configuration.camelCaseMotion.enable && super.couldActionApply(vimState, keysPressed);\n  }\n}\n\n// based off of `MoveWordBegin`\n@RegisterAction\nclass MoveCamelCaseWordBegin extends CamelCaseBaseMovement {\n  keys = ['<leader>', 'w'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    if (\n      !configuration.changeWordIncludesWhitespace &&\n      vimState.recordedState.operator instanceof ChangeOperator\n    ) {\n      // TODO use execForOperator? Or maybe dont?\n\n      // See note for w\n      return position.nextWordEnd(vimState.document, { wordType: WordType.CamelCase }).getRight();\n    } else {\n      return position.nextWordStart(vimState.document, { wordType: WordType.CamelCase });\n    }\n  }\n}\n\n// based off of `MoveWordEnd`\n@RegisterAction\nclass MoveCamelCaseWordEnd extends CamelCaseBaseMovement {\n  keys = ['<leader>', 'e'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.nextWordEnd(vimState.document, { wordType: WordType.CamelCase });\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position> {\n    const end = position.nextWordEnd(vimState.document, { wordType: WordType.CamelCase });\n\n    return new Position(end.line, end.character + 1);\n  }\n}\n\n// based off of `MoveBeginningWord`\n@RegisterAction\nclass MoveBeginningCamelCaseWord extends CamelCaseBaseMovement {\n  keys = ['<leader>', 'b'];\n\n  public override async execAction(position: Position, vimState: VimState): Promise<Position> {\n    return position.prevWordStart(vimState.document, { wordType: WordType.CamelCase });\n  }\n}\n\n// based off of `SelectInnerWord`\n@RegisterAction\nclass SelectInnerCamelCaseWord extends CamelCaseTextObjectMovement {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['i', '<leader>', 'w'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n    const currentChar = vimState.document.lineAt(position).text[position.character];\n\n    if (/\\s/.test(currentChar)) {\n      start = position.prevWordEnd(vimState.document, { wordType: WordType.CamelCase }).getRight();\n      stop = position\n        .nextWordStart(vimState.document, { wordType: WordType.CamelCase })\n        .getLeftThroughLineBreaks();\n    } else {\n      start = position.prevWordStart(vimState.document, {\n        wordType: WordType.CamelCase,\n        inclusive: true,\n      });\n      stop = position.nextWordEnd(vimState.document, {\n        wordType: WordType.CamelCase,\n        inclusive: true,\n      });\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting words in reverser order.\n        if (/\\s/.test(currentChar)) {\n          stop = position\n            .prevWordEnd(vimState.document, { wordType: WordType.CamelCase })\n            .getRight();\n        } else {\n          stop = position.prevWordStart(vimState.document, {\n            wordType: WordType.CamelCase,\n            inclusive: true,\n          });\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/easymotion/easymotion.cmd.ts",
    "content": "import { Position } from 'vscode';\nimport { globalState } from '../../../state/globalState';\nimport { VimState } from '../../../state/vimState';\nimport { TextEditor } from '../../../textEditor';\nimport { configuration } from './../../../configuration/configuration';\nimport { Mode } from './../../../mode/mode';\nimport { BaseCommand, RegisterAction } from './../../base';\nimport { EasyMotion } from './easymotion';\nimport { MarkerGenerator } from './markerGenerator';\nimport {\n  EasyMotionCharMoveOpions,\n  EasyMotionMoveOptionsBase,\n  EasyMotionSearchAction,\n  EasyMotionWordMoveOpions,\n  Match,\n  SearchOptions,\n} from './types';\n\nexport interface EasymotionTrigger {\n  key: string;\n  leaderCount?: number;\n}\n\nexport function buildTriggerKeys(trigger: EasymotionTrigger) {\n  return [\n    ...Array.from({ length: trigger.leaderCount || 2 }, () => '<leader>'),\n    ...trigger.key.split(''),\n  ];\n}\n\nabstract class BaseEasyMotionCommand extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n\n  private _baseOptions: EasyMotionMoveOptionsBase;\n\n  public abstract getMatches(position: Position, vimState: VimState): Match[];\n\n  constructor(baseOptions: EasyMotionMoveOptionsBase) {\n    super();\n    this._baseOptions = baseOptions;\n  }\n\n  public abstract resolveMatchPosition(match: Match): Position;\n\n  public processMarkers(matches: Match[], cursorPosition: Position, vimState: VimState) {\n    // Clear existing markers, just in case\n    vimState.easyMotion.clearMarkers();\n\n    let index = 0;\n    const markerGenerator = new MarkerGenerator(matches.length);\n    for (const match of matches) {\n      const matchPosition = this.resolveMatchPosition(match);\n      // Skip if the match position equals to cursor position\n      if (!matchPosition.isEqual(cursorPosition)) {\n        const marker = markerGenerator.generateMarker(index++, matchPosition);\n        if (marker) {\n          vimState.easyMotion.addMarker(marker);\n        }\n      }\n    }\n  }\n\n  protected searchOptions(position: Position): SearchOptions {\n    switch (this._baseOptions.searchOptions) {\n      case 'min':\n        return { min: position };\n      case 'max':\n        return { max: position };\n      default:\n        return {};\n    }\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // Only execute the action if the configuration is set\n    if (configuration.easymotion) {\n      // Search all occurences of the character pressed\n      const matches = this.getMatches(position, vimState);\n\n      // Stop if there are no matches\n      if (matches.length > 0) {\n        vimState.easyMotion = new EasyMotion();\n        this.processMarkers(matches, position, vimState);\n\n        if (matches.length === 1) {\n          // Only one found, navigate to it\n          const marker = vimState.easyMotion.markers[0];\n          // Set cursor position based on marker entered\n          vimState.cursorStopPosition = marker.position;\n          vimState.easyMotion.clearDecorations(vimState.editor);\n        } else {\n          // Store mode to return to after performing easy motion\n          vimState.easyMotion.previousMode = vimState.currentMode;\n          // Enter the EasyMotion mode and await further keys\n          await vimState.setCurrentMode(Mode.EasyMotionMode);\n        }\n      }\n    }\n  }\n}\n\nfunction getMatchesForString(\n  position: Position,\n  vimState: VimState,\n  searchString: string,\n  options?: SearchOptions,\n): Match[] {\n  switch (searchString) {\n    case '':\n      return [];\n    case ' ':\n      // Searching for space should only find the first space\n      return vimState.easyMotion.sortedSearch(\n        vimState.document,\n        position,\n        new RegExp(' {1,}', 'g'),\n        options,\n      );\n    default:\n      // Search all occurences of the character pressed\n\n      // If the input is not a letter, treating it as regex can cause issues\n      if (!/[a-zA-Z]/.test(searchString)) {\n        return vimState.easyMotion.sortedSearch(vimState.document, position, searchString, options);\n      }\n\n      const ignorecase =\n        configuration.ignorecase && !(configuration.smartcase && /[A-Z]/.test(searchString));\n      const regexFlags = ignorecase ? 'gi' : 'g';\n      return vimState.easyMotion.sortedSearch(\n        vimState.document,\n        position,\n        new RegExp(searchString, regexFlags),\n        options,\n      );\n  }\n}\n\nexport class SearchByCharCommand extends BaseEasyMotionCommand implements EasyMotionSearchAction {\n  keys = [];\n  public searchString: string = '';\n  private _options: EasyMotionCharMoveOpions;\n\n  get searchCharCount() {\n    return this._options.charCount;\n  }\n\n  constructor(options: EasyMotionCharMoveOpions) {\n    super(options);\n    this._options = options;\n  }\n\n  public getMatches(position: Position, vimState: VimState): Match[] {\n    return getMatchesForString(position, vimState, this.searchString, this.searchOptions(position));\n  }\n\n  public shouldFire() {\n    const charCount = this._options.charCount;\n    return charCount ? this.searchString.length >= charCount : true;\n  }\n\n  public async fire(position: Position, vimState: VimState): Promise<void> {\n    await this.exec(position, vimState);\n  }\n\n  public resolveMatchPosition(match: Match): Position {\n    const { line, character } = match.position;\n    switch (this._options.labelPosition) {\n      case 'after':\n        return new Position(line, character + this._options.charCount);\n      case 'before':\n        return new Position(line, Math.max(0, character - 1));\n      default:\n        return match.position;\n    }\n  }\n}\n\nexport class SearchByNCharCommand extends BaseEasyMotionCommand implements EasyMotionSearchAction {\n  keys = [];\n  public searchString: string = '';\n\n  get searchCharCount() {\n    return -1;\n  }\n\n  constructor() {\n    super({});\n  }\n\n  public resolveMatchPosition(match: Match): Position {\n    return match.position;\n  }\n\n  public getMatches(position: Position, vimState: VimState): Match[] {\n    return getMatchesForString(\n      position,\n      vimState,\n      this.removeTrailingLineBreak(this.searchString),\n      {},\n    );\n  }\n\n  private removeTrailingLineBreak(s: string) {\n    return s.replace(new RegExp('\\n+$', 'g'), '');\n  }\n\n  public shouldFire() {\n    // Fire when <CR> typed\n    return this.searchString.endsWith('\\n');\n  }\n\n  public async fire(position: Position, vimState: VimState): Promise<void> {\n    if (this.removeTrailingLineBreak(this.searchString) !== '') {\n      await this.exec(position, vimState);\n    }\n  }\n}\n\nexport abstract class EasyMotionCharMoveCommandBase extends BaseCommand {\n  modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  private _action: EasyMotionSearchAction;\n\n  constructor(action: EasyMotionSearchAction) {\n    super();\n    this._action = action;\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    // Only execute the action if easymotion is enabled\n    if (configuration.easymotion) {\n      vimState.easyMotion = new EasyMotion();\n      vimState.easyMotion.previousMode = vimState.currentMode;\n      vimState.easyMotion.searchAction = this._action;\n      globalState.hl = true;\n\n      await vimState.setCurrentMode(Mode.EasyMotionInputMode);\n    }\n  }\n}\n\nexport abstract class EasyMotionWordMoveCommandBase extends BaseEasyMotionCommand {\n  private _options: EasyMotionWordMoveOpions;\n\n  constructor(options: EasyMotionWordMoveOpions = {}) {\n    super(options);\n    this._options = options;\n  }\n\n  public getMatches(position: Position, vimState: VimState): Match[] {\n    return this.getMatchesForWord(position, vimState, this.searchOptions(position));\n  }\n\n  public resolveMatchPosition(match: Match): Position {\n    const { line, character } = match.position;\n    switch (this._options.labelPosition) {\n      case 'after':\n        return new Position(line, character + match.text.length - 1);\n      default:\n        return match.position;\n    }\n  }\n\n  private getMatchesForWord(\n    position: Position,\n    vimState: VimState,\n    options?: SearchOptions,\n  ): Match[] {\n    const regex = this._options.jumpToAnywhere\n      ? new RegExp(configuration.easymotionJumpToAnywhereRegex, 'g')\n      : new RegExp('\\\\w{1,}', 'g');\n    return vimState.easyMotion.sortedSearch(vimState.document, position, regex, options);\n  }\n}\n\nexport abstract class EasyMotionLineMoveCommandBase extends BaseEasyMotionCommand {\n  private _options: EasyMotionMoveOptionsBase;\n\n  constructor(options: EasyMotionMoveOptionsBase = {}) {\n    super(options);\n    this._options = options;\n  }\n\n  public resolveMatchPosition(match: Match): Position {\n    return match.position;\n  }\n\n  public getMatches(position: Position, vimState: VimState): Match[] {\n    return this.getMatchesForLineStart(position, vimState, this.searchOptions(position));\n  }\n\n  private getMatchesForLineStart(\n    position: Position,\n    vimState: VimState,\n    options?: SearchOptions,\n  ): Match[] {\n    // Search for the beginning of all non whitespace chars on each line before the cursor\n    const matches = vimState.easyMotion.sortedSearch(\n      vimState.document,\n      position,\n      new RegExp('^.', 'gm'),\n      options,\n    );\n    for (const match of matches) {\n      match.position = TextEditor.getFirstNonWhitespaceCharOnLine(\n        vimState.document,\n        match.position.line,\n      );\n    }\n    return matches;\n  }\n}\n\n@RegisterAction\nclass EasyMotionCharInputMode extends BaseCommand {\n  modes = [Mode.EasyMotionInputMode];\n  keys = ['<character>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const key = this.keysPressed[0];\n    const action = vimState.easyMotion.searchAction;\n    action.searchString =\n      key === '<BS>' || key === '<S-BS>'\n        ? action.searchString.slice(0, -1)\n        : action.searchString + key;\n    if (action.shouldFire()) {\n      // Skip Easymotion input mode to make sure not to back to it\n      await vimState.setCurrentMode(vimState.easyMotion.previousMode);\n      await action.fire(vimState.cursorStopPosition, vimState);\n    }\n  }\n}\n\n@RegisterAction\nclass CommandEscEasyMotionCharInputMode extends BaseCommand {\n  modes = [Mode.EasyMotionInputMode];\n  keys = ['<Esc>'];\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\n@RegisterAction\nclass MoveEasyMotion extends BaseCommand {\n  modes = [Mode.EasyMotionMode];\n  keys = ['<character>'];\n  override isJump = true;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const key = this.keysPressed[0];\n    if (key) {\n      // \"nail\" refers to the accumulated depth keys\n      const nail = vimState.easyMotion.accumulation + key;\n      vimState.easyMotion.accumulation = nail;\n\n      // Find markers starting with \"nail\"\n      const markers = vimState.easyMotion.findMarkers(nail, true);\n\n      if (markers.length === 1) {\n        // Only one found, navigate to it\n        const marker = markers[0];\n\n        vimState.easyMotion.clearDecorations(vimState.editor);\n        // Restore the mode from before easy motion\n        await vimState.setCurrentMode(vimState.easyMotion.previousMode);\n\n        // Set cursor position based on marker entered\n        vimState.cursorStopPosition = marker.position;\n      } else if (markers.length === 0) {\n        // None found, exit mode\n        vimState.easyMotion.clearDecorations(vimState.editor);\n        await vimState.setCurrentMode(vimState.easyMotion.previousMode);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/easymotion/easymotion.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { Mode } from '../../../mode/mode';\nimport { configuration } from './../../../configuration/configuration';\nimport { EasyMotionSearchAction, IEasyMotion, Marker, Match, SearchOptions } from './types';\n\nexport class EasyMotion implements IEasyMotion {\n  /**\n   * Refers to the accumulated keys for depth navigation\n   */\n  public accumulation = '';\n\n  // TODO: is this actually always set?\n  public searchAction!: EasyMotionSearchAction;\n\n  /**\n   * Array of all markers and decorations\n   */\n  public readonly markers: Marker[];\n\n  private visibleMarkers: Marker[]; // Array of currently showing markers\n  private decorations: vscode.DecorationOptions[][];\n\n  private static fade: vscode.TextEditorDecorationType | null = null;\n  private static getFadeDecorationType(): vscode.TextEditorDecorationType {\n    if (this.fade === null) {\n      this.fade = vscode.window.createTextEditorDecorationType({\n        color: configuration.easymotionDimColor,\n      });\n    }\n    return this.fade;\n  }\n\n  private static readonly hide = vscode.window.createTextEditorDecorationType({\n    color: 'transparent',\n  });\n\n  /**\n   * TODO: For future motions\n   */\n  private static specialCharactersRegex: RegExp = /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g;\n\n  /**\n   * Caches for decorations\n   */\n  private static decorationTypeCache: vscode.TextEditorDecorationType[] = [];\n\n  /**\n   * Mode to return to after attempting easymotion\n   */\n  // TODO: make this optional (in some circumstances it isn't actually set)\n  public previousMode!: Mode;\n\n  constructor() {\n    this.markers = [];\n    this.visibleMarkers = [];\n    this.decorations = [];\n  }\n\n  /**\n   * Create and cache decoration types for different marker lengths\n   */\n  public static getDecorationType(\n    length: number,\n    decorations?: vscode.DecorationRenderOptions,\n  ): vscode.TextEditorDecorationType {\n    const cache = this.decorationTypeCache[length];\n    if (cache) {\n      return cache;\n    } else {\n      const type = vscode.window.createTextEditorDecorationType(decorations || {});\n\n      this.decorationTypeCache[length] = type;\n\n      return type;\n    }\n  }\n\n  /**\n   * Clear all decorations\n   */\n  public clearDecorations(editor: vscode.TextEditor) {\n    for (let i = 1; i <= this.decorations.length; i++) {\n      editor.setDecorations(EasyMotion.getDecorationType(i), []);\n    }\n\n    editor.setDecorations(EasyMotion.getFadeDecorationType(), []);\n    editor.setDecorations(EasyMotion.hide, []);\n  }\n\n  /**\n   * Clear all markers\n   */\n  public clearMarkers() {\n    while (this.markers.length) {\n      this.markers.pop();\n    }\n    this.visibleMarkers = [];\n  }\n\n  public addMarker(marker: Marker) {\n    this.markers.push(marker);\n  }\n\n  /**\n   * Find markers beginning with a string\n   */\n  public findMarkers(nail: string, onlyVisible: boolean): Marker[] {\n    const markers = onlyVisible ? this.visibleMarkers : this.markers;\n    return markers.filter((marker) => marker.name.startsWith(nail));\n  }\n\n  /**\n   * Search and sort using the index of a match compared to the index of position (usually the cursor)\n   */\n  public sortedSearch(\n    document: vscode.TextDocument,\n    position: Position,\n    search: string | RegExp = '',\n    options: SearchOptions = {},\n  ): Match[] {\n    const regex =\n      typeof search === 'string'\n        ? new RegExp(search.replace(EasyMotion.specialCharactersRegex, '\\\\$&'), 'g')\n        : search;\n\n    const matches: Match[] = [];\n\n    // Cursor index refers to the index of the marker that is on or to the right of the cursor\n    let cursorIndex = position.character;\n    let prevMatch: Match | undefined;\n\n    // Calculate the min/max bounds for the search\n    const lineCount = document.lineCount;\n    const lineMin = options.min ? Math.max(options.min.line, 0) : 0;\n    const lineMax = options.max ? Math.min(options.max.line + 1, lineCount) : lineCount;\n\n    outer: for (let lineIdx = lineMin; lineIdx < lineMax; lineIdx++) {\n      const line = document.lineAt(lineIdx).text;\n      let result = regex.exec(line);\n\n      while (result) {\n        if (matches.length >= 1000) {\n          break outer;\n        } else {\n          const pos = new Position(lineIdx, result.index);\n\n          // Check if match is within bounds\n          if (\n            (options.min && pos.isBefore(options.min)) ||\n            (options.max && pos.isAfter(options.max)) ||\n            Math.abs(pos.line - position.line) > 100\n          ) {\n            // Stop searching after 100 lines in both directions\n            result = regex.exec(line);\n          } else {\n            // Update cursor index to the marker on the right side of the cursor\n            if (!prevMatch || prevMatch.position.isBefore(position)) {\n              cursorIndex = matches.length;\n            }\n            // Matches on the cursor position should be ignored\n            if (pos.isEqual(position)) {\n              result = regex.exec(line);\n            } else {\n              prevMatch = new Match(pos, result[0], matches.length);\n              matches.push(prevMatch);\n              result = regex.exec(line);\n            }\n          }\n        }\n      }\n    }\n\n    // Sort by the index distance from the cursor index\n    matches.sort((a: Match, b: Match): number => {\n      const computeAboluteDiff = (matchIndex: number) => {\n        const absDiff = Math.abs(cursorIndex - matchIndex);\n        // Prioritize the matches on the right side of the cursor index\n        return matchIndex < cursorIndex ? absDiff - 0.5 : absDiff;\n      };\n\n      const absDiffA = computeAboluteDiff(a.index);\n      const absDiffB = computeAboluteDiff(b.index);\n      return absDiffA - absDiffB;\n    });\n\n    return matches;\n  }\n\n  private getMarkerColor(\n    customizedValue: string,\n    themeColorId: string,\n  ): string | vscode.ThemeColor {\n    if (customizedValue) {\n      return customizedValue;\n    } else if (!themeColorId.startsWith('#')) {\n      return new vscode.ThemeColor(themeColorId);\n    } else {\n      return themeColorId;\n    }\n  }\n\n  private getEasymotionMarkerBackgroundColor() {\n    return this.getMarkerColor(configuration.easymotionMarkerBackgroundColor, '#0000');\n  }\n\n  private getEasymotionMarkerForegroundColorOneChar() {\n    return this.getMarkerColor(configuration.easymotionMarkerForegroundColorOneChar, '#ff0000');\n  }\n\n  private getEasymotionMarkerForegroundColorTwoCharFirst() {\n    return this.getMarkerColor(\n      configuration.easymotionMarkerForegroundColorTwoCharFirst,\n      '#ffb400',\n    );\n  }\n\n  private getEasymotionMarkerForegroundColorTwoCharSecond() {\n    return this.getMarkerColor(\n      configuration.easymotionMarkerForegroundColorTwoCharSecond,\n      '#b98300',\n    );\n  }\n\n  private getEasymotionDimColor() {\n    return this.getMarkerColor(configuration.easymotionDimColor, '#777777');\n  }\n\n  public updateDecorations(editor: vscode.TextEditor) {\n    this.clearDecorations(editor);\n\n    this.visibleMarkers = [];\n    this.decorations = [];\n\n    // Set the decorations for all the different marker lengths\n    const dimmingZones: vscode.DecorationOptions[] = [];\n    const dimmingRenderOptions: vscode.ThemableDecorationRenderOptions = {\n      // we update the color here again in case the configuration has changed\n      color: this.getEasymotionDimColor(),\n    };\n    // Why this instead of `background-color` on the marker?\n    // The easy fix would've been to let the user set the marker background to the same\n    // color as the editor so it would hide the character behind, However this would require\n    // the user to do more work, with this solution we temporarily hide the marked character\n    // so no user specific setting is needed\n    const hiddenChars: vscode.Range[] = [];\n    const markers = this.markers\n      .filter((m) => m.name.startsWith(this.accumulation))\n      .sort((a, b) => (a.position.isBefore(b.position) ? -1 : 1));\n\n    // Ignore markers that do not start with the accumulated depth level\n    for (const marker of markers) {\n      const pos = marker.position;\n      // Get keys after the depth we're at\n      const keystroke = marker.name.substr(this.accumulation.length);\n\n      if (!this.decorations[keystroke.length]) {\n        this.decorations[keystroke.length] = [];\n      }\n\n      // #region Hack (remove once backend handles this)\n\n      /*\n        This hack is here because the backend for easy motion reports two adjacent\n        2 char markers resulting in a 4 char wide markers, this isn't what happens in\n        original easymotion for instance: for doom\n            - original reports d[m][m2]m where [m] is a marker and [m2] is secondary\n            - here it reports d[m][m][m][m]m\n        The reason this won't work with current impl is that it overflows resulting in\n        one extra hidden character, hence the check below (until backend truely mimics original)\n\n        if two consecutive 2 char markers, we only use the first char from the current marker\n        and reduce the char substitution by 1. Once backend properly reports adjacent markers\n        all instances of `trim` can be removed\n      */\n      let trim = 0;\n      const next = markers[markers.indexOf(marker) + 1];\n\n      if (\n        next &&\n        next.position.character - pos.character === 1 &&\n        next.position.line === pos.line\n      ) {\n        const nextKeystroke = next.name.substr(this.accumulation.length);\n\n        if (keystroke.length > 1 && nextKeystroke.length > 1) {\n          trim = -1;\n        }\n      }\n\n      // #endregion\n\n      // First Char/One Char decoration\n      const firstCharFontColor =\n        keystroke.length > 1\n          ? this.getEasymotionMarkerForegroundColorTwoCharFirst()\n          : this.getEasymotionMarkerForegroundColorOneChar();\n      const backgroundColor = this.getEasymotionMarkerBackgroundColor();\n      const firstCharRange = new vscode.Range(pos.line, pos.character, pos.line, pos.character);\n      const firstCharRenderOptions: vscode.ThemableDecorationInstanceRenderOptions = {\n        before: {\n          contentText: keystroke.substring(0, 1),\n          backgroundColor,\n          color: firstCharFontColor,\n          margin: `0 -1ch 0 0;\n          position: absolute;\n          font-weight: ${configuration.easymotionMarkerFontWeight};`,\n          height: '100%',\n        },\n      };\n\n      this.decorations[keystroke.length].push({\n        range: firstCharRange,\n        renderOptions: {\n          dark: firstCharRenderOptions,\n          light: firstCharRenderOptions,\n        },\n      });\n\n      // Second Char decoration\n      if (keystroke.length + trim > 1) {\n        const secondCharFontColor = this.getEasymotionMarkerForegroundColorTwoCharSecond();\n        const secondCharRange = new vscode.Range(\n          pos.line,\n          pos.character + 1,\n          pos.line,\n          pos.character + 1,\n        );\n\n        const secondCharRenderOptions: vscode.ThemableDecorationInstanceRenderOptions = {\n          before: {\n            contentText: keystroke.slice(1),\n            backgroundColor,\n            color: secondCharFontColor,\n            margin: `0 -1ch 0 0;\n            position: absolute;\n            font-weight: ${configuration.easymotionMarkerFontWeight};`,\n            height: '100%',\n          },\n        };\n        this.decorations[keystroke.length].push({\n          range: secondCharRange,\n          renderOptions: {\n            dark: secondCharRenderOptions,\n            light: secondCharRenderOptions,\n          },\n        });\n      }\n\n      hiddenChars.push(\n        new vscode.Range(\n          pos.line,\n          pos.character,\n          pos.line,\n          pos.character + keystroke.length + trim,\n        ),\n      );\n\n      if (configuration.easymotionDimBackground) {\n        // This excludes markers from the dimming ranges by using them as anchors\n        // each marker adds the range between it and previous marker to the dimming zone\n        // except last marker after which the rest of document is dimmed\n        //\n        // example [m1] text that has multiple [m2] marks\n        // |<------    |<----------------------     ---->|\n        if (dimmingZones.length === 0) {\n          dimmingZones.push({\n            range: new vscode.Range(0, 0, pos.line, pos.character),\n            renderOptions: dimmingRenderOptions,\n          });\n        } else {\n          const prevMarker = markers[markers.indexOf(marker) - 1];\n          const prevKeystroke = prevMarker.name.substring(this.accumulation.length);\n          const prevDimPos = prevMarker.position;\n          const offsetPrevDimPos = prevDimPos.withColumn(\n            prevDimPos.character + prevKeystroke.length,\n          );\n\n          // Don't create dimming ranges in between consecutive markers (the 'after' is in the cases\n          // where you have 2 char consecutive markers where the first one only shows the first char.\n          // since we don't take that into account when creating 'offsetPrevDimPos' it will be after\n          // the current marker position which means we are in the middle of two consecutive markers.\n          // See the hack region above.)\n          if (!offsetPrevDimPos.isAfterOrEqual(pos)) {\n            dimmingZones.push({\n              range: new vscode.Range(\n                offsetPrevDimPos.line,\n                offsetPrevDimPos.character,\n                pos.line,\n                pos.character,\n              ),\n              renderOptions: dimmingRenderOptions,\n            });\n          }\n        }\n      }\n\n      this.visibleMarkers.push(marker);\n    }\n\n    // for the last marker dim till document end\n    if (configuration.easymotionDimBackground && markers.length > 0) {\n      const prevMarker = markers[markers.length - 1];\n      const prevKeystroke = prevMarker.name.substring(this.accumulation.length);\n      const prevDimPos = dimmingZones[dimmingZones.length - 1].range.end;\n      const offsetPrevDimPos = prevDimPos.withColumn(prevDimPos.character + prevKeystroke.length);\n\n      // Don't create any more dimming ranges when the last marker is at document end\n      if (!offsetPrevDimPos.isAtDocumentEnd(editor.document)) {\n        dimmingZones.push({\n          range: new vscode.Range(\n            offsetPrevDimPos,\n            new Position(editor.document.lineCount, Number.MAX_VALUE),\n          ),\n          renderOptions: dimmingRenderOptions,\n        });\n      }\n    }\n\n    for (let j = 1; j < this.decorations.length; j++) {\n      if (this.decorations[j]) {\n        editor.setDecorations(EasyMotion.getDecorationType(j), this.decorations[j]);\n      }\n    }\n\n    editor.setDecorations(EasyMotion.hide, hiddenChars);\n\n    if (configuration.easymotionDimBackground) {\n      editor.setDecorations(EasyMotion.getFadeDecorationType(), dimmingZones);\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/easymotion/markerGenerator.ts",
    "content": "import { Position } from 'vscode';\nimport { configuration } from './../../../configuration/configuration';\nimport { Marker } from './types';\n\nexport class MarkerGenerator {\n  private matchesCount: number;\n  private keyTable: string[];\n  private prefixKeyTable: string[];\n\n  constructor(matchesCount: number) {\n    this.matchesCount = matchesCount;\n    this.keyTable = this.getKeyTable();\n    this.prefixKeyTable = this.createPrefixKeyTable();\n  }\n\n  public generateMarker(index: number, markerPosition: Position): Marker | null {\n    const { keyTable, prefixKeyTable } = this;\n\n    if (index >= keyTable.length - prefixKeyTable.length) {\n      const remainder = index - (keyTable.length - prefixKeyTable.length);\n      const currentStep = Math.floor(remainder / keyTable.length) + 1;\n      if (currentStep > prefixKeyTable.length) {\n        return null;\n      } else {\n        const prefix = prefixKeyTable[currentStep - 1];\n        const label = keyTable[remainder % keyTable.length];\n        return {\n          name: prefix + label,\n          position: markerPosition,\n        };\n      }\n    } else {\n      return {\n        name: keyTable[index],\n        position: markerPosition,\n      };\n    }\n  }\n\n  private createPrefixKeyTable(): string[] {\n    const totalRemainder = Math.max(this.matchesCount - this.keyTable.length, 0);\n    const totalSteps = Math.ceil(totalRemainder / this.keyTable.length);\n    const reversed = this.keyTable.slice().reverse();\n    const count = Math.min(totalSteps, reversed.length);\n    return reversed.slice(0, count);\n  }\n\n  /**\n   * The key sequence for marker name generation\n   */\n  private getKeyTable(): string[] {\n    if (configuration.easymotionKeys) {\n      return configuration.easymotionKeys.split('');\n    } else {\n      return 'hklyuiopnm,qwertzxcvbasdgjf;'.split('');\n    }\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/easymotion/registerMoveActions.ts",
    "content": "import { RegisterAction } from './../../base';\nimport {\n  buildTriggerKeys,\n  EasyMotionCharMoveCommandBase,\n  EasyMotionLineMoveCommandBase,\n  EasyMotionWordMoveCommandBase,\n  SearchByCharCommand,\n  SearchByNCharCommand,\n} from './easymotion.cmd';\n\n// EasyMotion n-char-move command\n\n@RegisterAction\nclass EasyMotionNCharSearchCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '/' });\n\n  constructor() {\n    super(new SearchByNCharCommand());\n  }\n}\n\n// EasyMotion char-move commands\n\n@RegisterAction\nclass EasyMotionTwoCharSearchCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '2s' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2 }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTwoCharFindForwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '2f' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2, searchOptions: 'min' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTwoCharFindBackwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '2F' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2, searchOptions: 'max' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTwoCharTilCharacterForwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '2t' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2, searchOptions: 'min', labelPosition: 'before' }));\n  }\n}\n\n// easymotion-bd-t2\n\n@RegisterAction\nclass EasyMotionTwoCharTilCharacterBidirectionalCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'bd2t', leaderCount: 3 });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2, labelPosition: 'before' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTwoCharTilBackwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: '2T' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 2, searchOptions: 'max', labelPosition: 'after' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionSearchCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 's' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1 }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionFindForwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'f' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1, searchOptions: 'min' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionFindBackwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'F' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1, searchOptions: 'max' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTilCharacterForwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 't' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1, searchOptions: 'min', labelPosition: 'before' }));\n  }\n}\n\n// easymotion-bd-t\n\n@RegisterAction\nclass EasyMotionTilCharacterBidirectionalCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'bdt', leaderCount: 3 });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1, labelPosition: 'before' }));\n  }\n}\n\n@RegisterAction\nclass EasyMotionTilBackwardCommand extends EasyMotionCharMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'T' });\n\n  constructor() {\n    super(new SearchByCharCommand({ charCount: 1, searchOptions: 'max', labelPosition: 'after' }));\n  }\n}\n\n// EasyMotion word-move commands\n\n@RegisterAction\nclass EasyMotionStartOfWordForwardsCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'w' });\n\n  constructor() {\n    super({ searchOptions: 'min' });\n  }\n}\n\n// easymotion-bd-w\n\n@RegisterAction\nclass EasyMotionStartOfWordBidirectionalCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'bdw', leaderCount: 3 });\n}\n\n@RegisterAction\nclass EasyMotionLineForward extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'l' });\n\n  constructor() {\n    super({ jumpToAnywhere: true, searchOptions: 'min', labelPosition: 'after' });\n  }\n}\n\n@RegisterAction\nclass EasyMotionLineBackward extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'h' });\n\n  constructor() {\n    super({ jumpToAnywhere: true, searchOptions: 'max', labelPosition: 'after' });\n  }\n}\n\n// easymotion \"JumpToAnywhere\" motion\n\n@RegisterAction\nclass EasyMotionJumpToAnywhereCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'j', leaderCount: 3 });\n\n  constructor() {\n    super({ jumpToAnywhere: true, labelPosition: 'after' });\n  }\n}\n\n@RegisterAction\nclass EasyMotionEndOfWordForwardsCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'e' });\n\n  constructor() {\n    super({ searchOptions: 'min', labelPosition: 'after' });\n  }\n}\n\n// easymotion-bd-e\n\n@RegisterAction\nclass EasyMotionEndOfWordBidirectionalCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'bde', leaderCount: 3 });\n\n  constructor() {\n    super({ labelPosition: 'after' });\n  }\n}\n\n@RegisterAction\nclass EasyMotionBeginningWordCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'b' });\n\n  constructor() {\n    super({ searchOptions: 'max' });\n  }\n}\n\n@RegisterAction\nclass EasyMotionEndBackwardCommand extends EasyMotionWordMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'ge' });\n\n  constructor() {\n    super({ searchOptions: 'max', labelPosition: 'after' });\n  }\n}\n\n// EasyMotion line-move commands\n\n@RegisterAction\nclass EasyMotionStartOfLineForwardsCommand extends EasyMotionLineMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'j' });\n\n  constructor() {\n    super({ searchOptions: 'min' });\n  }\n}\n\n@RegisterAction\nclass EasyMotionStartOfLineBackwordsCommand extends EasyMotionLineMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'k' });\n\n  constructor() {\n    super({ searchOptions: 'max' });\n  }\n}\n\n// easymotion-bd-jk\n\n@RegisterAction\nclass EasyMotionStartOfLineBidirectionalCommand extends EasyMotionLineMoveCommandBase {\n  keys = buildTriggerKeys({ key: 'bdjk', leaderCount: 3 });\n}\n"
  },
  {
    "path": "src/actions/plugins/easymotion/types.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { Mode } from '../../../mode/mode';\nimport type { VimState } from '../../../state/vimState';\n\nexport type LabelPosition = 'after' | 'before';\nexport type JumpToAnywhere = true | false;\n\nexport interface EasyMotionMoveOptionsBase {\n  searchOptions?: 'min' | 'max';\n}\n\nexport interface EasyMotionCharMoveOpions extends EasyMotionMoveOptionsBase {\n  charCount: number;\n  labelPosition?: LabelPosition;\n}\n\nexport interface EasyMotionWordMoveOpions extends EasyMotionMoveOptionsBase {\n  labelPosition?: LabelPosition;\n  jumpToAnywhere?: JumpToAnywhere;\n}\n\nexport interface Marker {\n  name: string;\n  position: Position;\n}\n\nexport class Match {\n  public position: Position;\n  public readonly text: string;\n  public readonly index: number;\n\n  constructor(position: Position, text: string, index: number) {\n    this.position = position;\n    this.text = text;\n    this.index = index;\n  }\n\n  public toRange(): vscode.Range {\n    return new vscode.Range(this.position, this.position.translate(0, this.text.length));\n  }\n}\n\nexport interface SearchOptions {\n  /**\n   * The minimum bound of the search\n   */\n  min?: Position;\n\n  /**\n   * The maximum bound of the search\n   */\n  max?: Position;\n}\n\nexport interface EasyMotionSearchAction {\n  searchString: string;\n\n  /**\n   * True if it should go to Easymotion mode\n   */\n  shouldFire(): boolean;\n\n  /**\n   * Command to execute when it should fire\n   */\n  fire(position: Position, vimState: VimState): Promise<void>;\n  getMatches(position: Position, vimState: VimState): Match[];\n  readonly searchCharCount: number;\n}\n\nexport interface IEasyMotion {\n  accumulation: string;\n  previousMode: Mode;\n  markers: Marker[];\n  searchAction: EasyMotionSearchAction;\n\n  addMarker(marker: Marker): void;\n  findMarkers(nail: string, onlyVisible: boolean): Marker[];\n  sortedSearch(\n    document: vscode.TextDocument,\n    position: Position,\n    search?: string | RegExp,\n    options?: SearchOptions,\n  ): Match[];\n  updateDecorations(editor: vscode.TextEditor): void;\n  clearMarkers(): void;\n  clearDecorations(editor: vscode.TextEditor): void;\n}\n"
  },
  {
    "path": "src/actions/plugins/imswitcher.ts",
    "content": "import { exec } from 'child_process';\nimport { configuration } from '../../configuration/configuration';\nimport { Mode } from '../../mode/mode';\nimport { Logger } from '../../util/logger';\n\n/**\n * This function executes a shell command and returns the standard output as a string.\n */\nfunction executeShell(cmd: string): Promise<string> {\n  return new Promise<string>((resolve, reject) => {\n    try {\n      exec(cmd, (err, stdout, stderr) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(stdout);\n        }\n      });\n    } catch (error) {\n      reject(error as Error);\n    }\n  });\n}\n\n/**\n * InputMethodSwitcher changes input method when mode changed\n */\nexport class InputMethodSwitcher {\n  private execute: (cmd: string) => Promise<string>;\n  private savedIMKey = '';\n\n  constructor(execute: (cmd: string) => Promise<string> = executeShell) {\n    this.execute = execute;\n  }\n\n  public async switchInputMethod(prevMode: Mode, newMode: Mode) {\n    if (configuration.autoSwitchInputMethod.enable !== true) {\n      return;\n    }\n    // when you exit from insert-like mode, save origin input method and set it to default\n    const isPrevModeInsertLike = this.isInsertLikeMode(prevMode);\n    const isNewModeInsertLike = this.isInsertLikeMode(newMode);\n    if (isPrevModeInsertLike !== isNewModeInsertLike) {\n      if (isNewModeInsertLike) {\n        await this.resumeIM();\n      } else {\n        await this.switchToDefaultIM();\n      }\n    }\n  }\n\n  // save origin input method and set input method to default\n  private async switchToDefaultIM() {\n    const obtainIMCmd = configuration.autoSwitchInputMethod.obtainIMCmd;\n    try {\n      const insertIMKey = await this.execute(obtainIMCmd);\n      if (insertIMKey !== undefined) {\n        this.savedIMKey = insertIMKey.trim();\n      }\n    } catch (e) {\n      Logger.error(`Error switching to default IM. err=${e}`);\n    }\n\n    const defaultIMKey = configuration.autoSwitchInputMethod.defaultIM;\n    if (defaultIMKey !== this.savedIMKey) {\n      await this.switchToIM(defaultIMKey);\n    }\n  }\n\n  // resume origin inputmethod\n  private async resumeIM() {\n    if (this.savedIMKey !== configuration.autoSwitchInputMethod.defaultIM) {\n      await this.switchToIM(this.savedIMKey);\n    }\n  }\n\n  private async switchToIM(imKey: string) {\n    let switchIMCmd = configuration.autoSwitchInputMethod.switchIMCmd;\n    if (imKey !== '' && imKey !== undefined) {\n      switchIMCmd = switchIMCmd.replace('{im}', imKey);\n      try {\n        await this.execute(switchIMCmd);\n      } catch (e) {\n        Logger.error(`Error switching to IM. err=${e}`);\n      }\n    }\n  }\n\n  private isInsertLikeMode(mode: Mode): boolean {\n    return [Mode.Insert, Mode.Replace, Mode.SurroundInputMode].includes(mode);\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/pluginDefaultMappings.ts",
    "content": "import { IConfiguration, IKeyRemapping } from '../../configuration/iconfiguration';\n\nexport class PluginDefaultMappings {\n  // plugin authers may add entries here\n  private static defaultMappings: Array<{\n    mode: string;\n    configSwitch: string;\n    mapping: IKeyRemapping;\n  }> = [\n    // default maps for surround\n    {\n      mode: 'normalModeKeyBindingsNonRecursive',\n      configSwitch: 'surround',\n      mapping: { before: ['y', 's'], after: ['<plugys>'] },\n    },\n    {\n      mode: 'normalModeKeyBindingsNonRecursive',\n      configSwitch: 'surround',\n      mapping: { before: ['y', 's', 's'], after: ['<plugys>', '<plugys>'] },\n    },\n    {\n      mode: 'normalModeKeyBindingsNonRecursive',\n      configSwitch: 'surround',\n      mapping: { before: ['c', 's'], after: ['<plugcs>'] },\n    },\n    {\n      mode: 'normalModeKeyBindingsNonRecursive',\n      configSwitch: 'surround',\n      mapping: { before: ['d', 's'], after: ['<plugds>'] },\n    },\n  ];\n\n  public static getPluginDefaultMappings(mode: string, config: IConfiguration): IKeyRemapping[] {\n    return this.defaultMappings\n      .filter((m) => m.mode === mode && config[m.configSwitch])\n      .map((m) => m.mapping);\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/replaceWithRegister.ts",
    "content": "import { Position, Range } from 'vscode';\nimport { PositionDiff } from '../../common/motion/position';\nimport { configuration } from '../../configuration/configuration';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { Register, RegisterMode } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { BaseOperator } from '../operator';\nimport { RegisterAction } from './../base';\n\n@RegisterAction\nclass ReplaceOperator extends BaseOperator {\n  public keys = ['g', 'r'];\n  public modes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return configuration.replaceWithRegister && super.doesActionApply(vimState, keysPressed);\n  }\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return configuration.replaceWithRegister && super.doesActionApply(vimState, keysPressed);\n  }\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    const range =\n      vimState.currentRegisterMode === RegisterMode.LineWise\n        ? new Range(start.getLineBegin(), end.getLineEnd())\n        : new Range(start, end.getRight());\n\n    const register = await Register.get(vimState.recordedState.registerName, this.multicursorIndex);\n    if (register === undefined) {\n      StatusBar.displayError(\n        vimState,\n        VimError.NothingInRegister(vimState.recordedState.registerName),\n      );\n      return;\n    }\n\n    const replaceWith = register.text as string;\n\n    vimState.recordedState.transformer.replace(\n      range,\n      replaceWith,\n      PositionDiff.exactPosition(getCursorPosition(vimState, range, replaceWith)),\n    );\n\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n}\n\nconst getCursorPosition = (vimState: VimState, range: Range, replaceWith: string): Position => {\n  const {\n    recordedState: { actionKeys },\n  } = vimState;\n  const lines = replaceWith.split('\\n');\n  const wasRunAsLineAction = actionKeys.indexOf('r') === 0 && actionKeys.length === 1; // ie. grr\n  const registerAndRangeAreSingleLines = lines.length === 1 && range.isSingleLine;\n  const singleLineAction = registerAndRangeAreSingleLines && !wasRunAsLineAction;\n\n  return singleLineAction\n    ? cursorAtEndOfReplacement(range, replaceWith)\n    : cursorAtFirstNonBlankCharOfLine(range.start.line, lines[0]);\n};\n\nconst cursorAtEndOfReplacement = (range: Range, replacement: string) =>\n  new Position(range.start.line, Math.max(0, range.start.character + replacement.length - 1));\n\nconst cursorAtFirstNonBlankCharOfLine = (line: number, text: string) =>\n  new Position(line, text.match(/\\S/)?.index ?? 0);\n"
  },
  {
    "path": "src/actions/plugins/sneak.ts",
    "content": "import { Position } from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { BaseMovement, IMovement } from '../baseMotion';\nimport { configuration } from './../../configuration/configuration';\nimport { RegisterAction } from './../base';\n\n@RegisterAction\nexport class SneakForward extends BaseMovement {\n  keys = [\n    ['s', '<character>', '<character>'],\n    ['z', '<character>', '<character>'],\n  ];\n  override isJump = true;\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const startingLetter = vimState.recordedState.operator === undefined ? 's' : 'z';\n\n    return (\n      configuration.sneak &&\n      super.couldActionApply(vimState, keysPressed) &&\n      keysPressed[0] === startingLetter\n    );\n  }\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    if (!this.isRepeat) {\n      vimState.lastSemicolonRepeatableMovement = new SneakForward(this.keysPressed, true);\n      vimState.lastCommaRepeatableMovement = new SneakBackward(this.keysPressed, true);\n    }\n\n    if (this.keysPressed[2] === '\\n') {\n      // Single key sneak\n      this.keysPressed[2] = '';\n    }\n\n    const searchString = this.keysPressed[1] + this.keysPressed[2];\n\n    const document = vimState.document;\n    const lineCount = document.lineCount;\n    for (let i = position.line; i < lineCount; ++i) {\n      const lineText = document.lineAt(i).text;\n\n      // Start searching after the current character so we don't find the same match twice\n      const fromIndex = i === position.line ? position.character + 1 : 0;\n\n      let matchIndex = -1;\n\n      const ignorecase =\n        configuration.sneakUseIgnorecaseAndSmartcase &&\n        configuration.ignorecase &&\n        !(configuration.smartcase && /[A-Z]/.test(searchString));\n\n      // Check for matches\n      if (ignorecase) {\n        matchIndex = lineText\n          .toLocaleLowerCase()\n          .indexOf(searchString.toLocaleLowerCase(), fromIndex);\n      } else {\n        matchIndex = lineText.indexOf(searchString, fromIndex);\n      }\n\n      if (matchIndex >= 0) {\n        return new Position(i, matchIndex);\n      }\n    }\n\n    return position;\n  }\n}\n\n@RegisterAction\nexport class SneakBackward extends BaseMovement {\n  keys = [\n    ['S', '<character>', '<character>'],\n    ['Z', '<character>', '<character>'],\n  ];\n  override isJump = true;\n\n  public override couldActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const startingLetter = vimState.recordedState.operator === undefined ? 'S' : 'Z';\n\n    return (\n      configuration.sneak &&\n      super.couldActionApply(vimState, keysPressed) &&\n      keysPressed[0] === startingLetter\n    );\n  }\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n  ): Promise<Position | IMovement> {\n    if (!this.isRepeat) {\n      vimState.lastSemicolonRepeatableMovement = new SneakBackward(this.keysPressed, true);\n      vimState.lastCommaRepeatableMovement = new SneakForward(this.keysPressed, true);\n    }\n\n    if (this.keysPressed[2] === '\\n') {\n      // Single key sneak\n      this.keysPressed[2] = '';\n    }\n\n    const searchString = this.keysPressed[1] + this.keysPressed[2];\n\n    const document = vimState.document;\n    for (let i = position.line; i >= 0; --i) {\n      const lineText = document.lineAt(i).text;\n\n      // Start searching before the current character so we don't find the same match twice\n      const fromIndex = i === position.line ? position.character - 1 : +Infinity;\n\n      let matchIndex = -1;\n\n      const ignorecase =\n        configuration.sneakUseIgnorecaseAndSmartcase &&\n        configuration.ignorecase &&\n        !(configuration.smartcase && /[A-Z]/.test(searchString));\n\n      // Check for matches\n      if (ignorecase) {\n        matchIndex = lineText\n          .toLocaleLowerCase()\n          .lastIndexOf(searchString.toLocaleLowerCase(), fromIndex);\n      } else {\n        matchIndex = lineText.lastIndexOf(searchString, fromIndex);\n      }\n\n      if (matchIndex >= 0) {\n        return new Position(i, matchIndex);\n      }\n    }\n\n    return position;\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/surround.ts",
    "content": "import { Position, Range, window } from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport {\n  SelectABigWord,\n  SelectInnerWord,\n  SelectWord,\n  TextObject,\n} from '../../textobject/textobject';\nimport { WordType } from '../../textobject/word';\nimport { isIMovement } from '../baseMotion';\nimport {\n  MoveAroundBacktick,\n  MoveAroundCaret,\n  MoveAroundCurlyBrace,\n  MoveAroundDoubleQuotes,\n  MoveAroundParentheses,\n  MoveAroundSingleQuotes,\n  MoveAroundSquareBracket,\n  MoveAroundTag,\n  MoveFullWordBegin,\n  MoveInsideCharacter,\n  MoveInsideTag,\n  MoveQuoteMatch,\n  MoveWordBegin,\n} from '../motion';\nimport { PositionDiff, sorted } from './../../common/motion/position';\nimport { configuration } from './../../configuration/configuration';\nimport { DotCommandStatus, Mode } from './../../mode/mode';\nimport { BaseCommand, RegisterAction } from './../base';\nimport { BaseOperator } from './../operator';\n\ntype SurroundEdge = {\n  leftEdge: Range;\n  rightEdge: Range;\n  /** we need to pass this with transformations */\n  cursorIndex: number;\n  /** to support changing a tag, cstt */\n  leftTagName?: Range;\n  rightTagName?: Range;\n};\n\ntype TagReplacement = {\n  tag: string;\n  /** when  changing tag to tag, do we keep attributes? default: yes */\n  keepAttributes: boolean;\n};\n\nexport interface SurroundState {\n  /** The operator paired with the surround action. \"yank\" is really \"add\", but it uses 'y' */\n  operator: 'change' | 'delete' | 'yank';\n\n  /** target of surround op: X in csXy and dsX */\n  target: string | undefined;\n\n  /** the added surrounding, like \",',(). t = tag */\n  replacement: string;\n\n  /** name of tag */\n  tag?: TagReplacement;\n\n  /** name of function */\n  function?: string;\n\n  /** for visual line mode */\n  addNewline?: boolean;\n\n  edges: SurroundEdge[];\n\n  /** The mode before surround was triggered */\n  previousMode: Mode;\n}\n\nabstract class SurroundOperator extends BaseOperator {\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    return configuration.surround && super.doesActionApply(vimState, keysPressed);\n  }\n}\n\n@RegisterAction\nclass YankSurroundOperator extends SurroundOperator {\n  // needs: nnoremap ys <plugys>. we leave it to Remapper to figure out y vs ys.\n  public keys = ['<plugys>'];\n  public modes = [Mode.Normal];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    // reset surround state when run for first cursor\n    if (!this.multicursorIndex) {\n      vimState.surround = {\n        operator: 'yank',\n        target: undefined,\n        replacement: '',\n        edges: [],\n        previousMode: vimState.currentMode,\n      };\n    }\n    const getYankRanges = (): SurroundEdge => {\n      // for special handling for w motion.\n      // with \"|surroundme ZONK\" it will jump to Z, but we just want surroundme\n      const endPlus1 = new Range(end.getRight(), end.getRight());\n      const prevWordEnd = end.getRight().prevWordEnd(vimState.document);\n      const endW = new Range(prevWordEnd.getRight(), prevWordEnd.getRight());\n      const lastMotion =\n        vimState.recordedState.actionsRun[vimState.recordedState.actionsRun.length - 1];\n      const ranWwMotion =\n        lastMotion instanceof MoveWordBegin ||\n        lastMotion instanceof MoveFullWordBegin ||\n        lastMotion instanceof SelectABigWord ||\n        lastMotion instanceof SelectWord;\n      const rightEdge = ranWwMotion ? endW : endPlus1;\n      return {\n        leftEdge: new Range(start, start),\n        rightEdge,\n        cursorIndex: multicursorIndex,\n      };\n    };\n    // then collect ranges for all cursors\n    const multicursorIndex = this.multicursorIndex ?? 0;\n    vimState.surround!.edges.push(getYankRanges());\n    vimState.cursorStartPosition = start;\n    // when called from visual operator, use end for stop to keep visual selection\n    vimState.cursorStopPosition = vimState.currentMode === Mode.Visual ? end : start;\n    await vimState.setCurrentMode(Mode.SurroundInputMode);\n  }\n\n  public override async runRepeat(\n    vimState: VimState,\n    position: Position,\n    count: number,\n  ): Promise<void> {\n    // we want to act on range: first non whitespace to last non whitespace\n    await this.run(\n      vimState,\n      position.getLineBeginRespectingIndent(vimState.document),\n      position\n        .getDown(Math.max(0, count - 1))\n        .getLineEnd()\n        .prevWordEnd(vimState.document),\n    );\n  }\n}\n\n@RegisterAction\nclass CommandSurroundModeStartVisual extends SurroundOperator {\n  modes = [Mode.Visual];\n  keys = ['S'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    [start, end] = sorted(start, end);\n    await new YankSurroundOperator(this.multicursorIndex).run(vimState, start, end);\n    return;\n  }\n}\n\n@RegisterAction\nclass CommandSurroundModeStartVisualLine extends SurroundOperator {\n  modes = [Mode.VisualLine];\n  keys = ['S'];\n\n  public async run(vimState: VimState, start: Position, end: Position): Promise<void> {\n    [start, end] = sorted(start.getLineBegin(), end.getLineEnd());\n\n    // reset surround state when run for first cursor\n    if (!this.multicursorIndex) {\n      vimState.surround = {\n        target: undefined,\n        operator: 'yank',\n        replacement: '',\n        addNewline: true,\n        edges: [],\n        previousMode: vimState.currentMode,\n      };\n    }\n\n    // collect ranges for all cursors\n    vimState.surround?.edges.push({\n      leftEdge: new Range(start, start),\n      rightEdge: new Range(end, end),\n      cursorIndex: this.multicursorIndex ?? 0,\n    });\n\n    vimState.cursorStartPosition = start;\n    vimState.cursorStopPosition = end;\n    await vimState.setCurrentMode(Mode.SurroundInputMode);\n    return;\n  }\n}\n\nabstract class CommandSurround extends BaseCommand {\n  modes = [Mode.Normal];\n  override createsUndoPoint = true;\n  override runsOnceForEveryCursor() {\n    return true;\n  }\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const target = keysPressed[keysPressed.length - 1];\n    return (\n      configuration.surround &&\n      super.doesActionApply(vimState, keysPressed) &&\n      SurroundHelper.edgePairings[target] !== undefined\n    );\n  }\n}\n\n@RegisterAction\nclass CommandSurroundDeleteSurround extends CommandSurround {\n  keys = ['<plugds>', '<any>'];\n  keysHasCnt = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const target = this.keysPressed[this.keysPressed.length - 1];\n    // for derived class, support ds2X\n    if (this.keysHasCnt) {\n      const cntKey = this.keysPressed[this.keysPressed.length - 2];\n      // eslint-disable-next-line radix\n      vimState.recordedState.count = parseInt(cntKey, undefined);\n    }\n\n    // for this operator, we set surround state and execute for each cursor one at a time\n    vimState.surround = {\n      operator: 'delete',\n      target,\n      replacement: '',\n      edges: [],\n      previousMode: Mode.Normal,\n    };\n\n    // we need surround state initiated for this call\n    const replaceRanges = await SurroundHelper.getReplaceRanges(\n      vimState,\n      position,\n      this.multicursorIndex ?? 0,\n    );\n\n    if (replaceRanges) {\n      vimState.surround.edges = [replaceRanges];\n      await SurroundHelper.ExecuteSurround(vimState);\n    }\n  }\n}\n\n@RegisterAction\nclass CommandSurroundDeleteSurroundCnt extends CommandSurroundDeleteSurround {\n  // supports cnt up to 9, should be enough\n  override keys = ['<plugds>', '<number>', '<any>'];\n  override keysHasCnt = true;\n}\n\n@RegisterAction\nclass CommandSurroundChangeSurround extends CommandSurround {\n  keys = ['<plugcs>', '<any>'];\n  override isCompleteAction = false;\n  keysHasCnt = false;\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const target = this.keysPressed[this.keysPressed.length - 1];\n    // for derived class, support ds2X\n    if (this.keysHasCnt) {\n      const cntKey = this.keysPressed[this.keysPressed.length - 2];\n      // eslint-disable-next-line radix\n      vimState.recordedState.count = parseInt(cntKey, undefined);\n    }\n\n    // reset surround state when run for first cursor\n    if (!this.multicursorIndex) {\n      vimState.surround = {\n        operator: 'change',\n        target,\n        replacement: '',\n        edges: [],\n        previousMode: Mode.Normal,\n      };\n    }\n\n    // we need state surround initiated for this call\n    const replaceRanges = await SurroundHelper.getReplaceRanges(\n      vimState,\n      position,\n      this.multicursorIndex ?? 0,\n    );\n\n    // collect ranges for all cursors\n    if (replaceRanges) {\n      vimState.surround!.edges.push(replaceRanges);\n    }\n    await vimState.setCurrentMode(Mode.SurroundInputMode);\n  }\n}\n\n@RegisterAction\nclass CommandSurroundChangeSurroundCnt extends CommandSurroundChangeSurround {\n  // supports cnt up to 9, should be enough\n  override keys = ['<plugcs>', '<number>', '<any>'];\n  override keysHasCnt = true;\n}\n\n@RegisterAction\nclass CommandSurroundAddSurrounding extends BaseCommand {\n  modes = [Mode.SurroundInputMode];\n  // add surrounding / read X when: ys + motion + X. or csYX\n  keys = ['<any>'];\n  override isCompleteAction = true;\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n    const replacement = keysPressed[keysPressed.length - 1];\n    return (\n      configuration.surround &&\n      super.doesActionApply(vimState, keysPressed) &&\n      replacement !== 't' && // do not run this for surrounding with a tag\n      replacement !== '<' &&\n      replacement !== 'f' && // or for surrounding with a function\n      replacement !== 'F' &&\n      replacement !== '<C-f>'\n    );\n  }\n\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    const replacement = this.keysPressed[this.keysPressed.length - 1];\n\n    if (!vimState.surround || !SurroundHelper.edgePairings[replacement]) {\n      // cant surround, abort.\n      // this typically handles, when last keypress was wrong and not a valid surrounding\n      vimState.surround = undefined;\n      await vimState.setCurrentMode(Mode.Normal);\n      return;\n    }\n\n    vimState.surround.replacement = replacement;\n\n    await SurroundHelper.ExecuteSurround(vimState);\n  }\n}\n\n@RegisterAction\nexport class CommandSurroundAddSurroundingTag extends BaseCommand {\n  modes = [Mode.SurroundInputMode];\n  // add surrounding / read X when: ys + motion + X\n  keys = [['<'], ['t']];\n  override isCompleteAction = true;\n  recordedTag = ''; // to save for repeat\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (!vimState.surround) {\n      return;\n    }\n\n    vimState.surround.replacement = 't';\n    const tagInput =\n      vimState.dotCommandStatus === DotCommandStatus.Executing || vimState.isReplayingMacro\n        ? this.recordedTag\n        : await this.readTag();\n\n    if (!tagInput) {\n      vimState.surround = undefined;\n      await vimState.setCurrentMode(Mode.Normal);\n      return;\n    }\n\n    // record tag for repeat. this works because recordedState will store the actual objects\n    this.recordedTag = tagInput;\n\n    // local helper\n    const checkReplaceAttributes = (tag: string) => {\n      return tag.substring(tag.length - 1) === '>'\n        ? { tag: tag.substring(0, tag.length - 1), keepAttributes: false }\n        : { tag, keepAttributes: true };\n    };\n\n    // check as special case (set by >) if we want to replace the attributes on tag or keep them (default)\n    vimState.surround.tag = checkReplaceAttributes(tagInput);\n\n    // finally, we can exec surround\n    await SurroundHelper.ExecuteSurround(vimState);\n  }\n\n  private async readTag(): Promise<string | undefined> {\n    return window.showInputBox({\n      prompt: 'Enter tag',\n      ignoreFocusOut: true,\n    });\n  }\n}\n\n@RegisterAction\nexport class CommandSurroundAddSurroundingFunction extends BaseCommand {\n  modes = [Mode.SurroundInputMode];\n  // add surrounding / read X when: ys + motion + X\n  keys = [['f'], ['F'], ['<C-f>']];\n  override isCompleteAction = true;\n  recordedFunction = ''; // to save for repeat\n  override runsOnceForEveryCursor() {\n    return false;\n  }\n  public override async exec(position: Position, vimState: VimState): Promise<void> {\n    if (!vimState.surround) {\n      return;\n    }\n\n    // reuse the spacing logic from the parentheses\n    // for the right side of the replacement\n    vimState.surround.replacement =\n      this.keysPressed[this.keysPressed.length - 1] === 'F' ? '(' : ')';\n\n    const functionInput =\n      vimState.dotCommandStatus === DotCommandStatus.Executing || vimState.isReplayingMacro\n        ? this.recordedFunction\n        : await this.readFunction();\n\n    if (!functionInput) {\n      vimState.surround = undefined;\n      await vimState.setCurrentMode(Mode.Normal);\n      return;\n    }\n\n    // record function for repeat.\n    this.recordedFunction = functionInput;\n\n    // format the left side of the replacement based on the key pressed\n    vimState.surround.function = this.formatFunction(functionInput);\n\n    await SurroundHelper.ExecuteSurround(vimState);\n  }\n\n  private async readFunction(): Promise<string | undefined> {\n    return window.showInputBox({\n      prompt: 'Enter function',\n      ignoreFocusOut: true,\n    });\n  }\n\n  private formatFunction(fn: string): string {\n    switch (this.keysPressed[this.keysPressed.length - 1]) {\n      case 'f':\n        return fn + '(';\n      case 'F':\n        return fn + '( ';\n      case '<C-f>':\n      default:\n        return '(' + fn + ' ';\n    }\n  }\n}\n\n// following are static internal helper functions\n// top level helper is ExecuteSurround, which is called from exec and does the actual text transformations\nclass SurroundHelper {\n  /** a map which holds for each target key: inserted text + implementation helper */\n  static edgePairings: {\n    [key: string]: {\n      left: string;\n      right: string;\n      /** do we consume space on the edges? \"(\" vs \")\" */\n      removeSpace: boolean;\n      movement: () => MoveInsideCharacter | MoveQuoteMatch | MoveAroundTag | TextObject;\n      /** typically to extend an inner  word. with *foo*, from \"foo\" to \"*foo*\" */\n      extraChars?: number;\n    };\n  } = {\n    // helpful linter is helpful :-D\n    '(': {\n      left: '( ',\n      right: ' )',\n      removeSpace: true,\n      movement: () => new MoveAroundParentheses(),\n    },\n    ')': { left: '(', right: ')', removeSpace: false, movement: () => new MoveAroundParentheses() },\n    '[': {\n      left: '[ ',\n      right: ' ]',\n      removeSpace: true,\n      movement: () => new MoveAroundSquareBracket(),\n    },\n    ']': {\n      left: '[',\n      right: ']',\n      removeSpace: false,\n      movement: () => new MoveAroundSquareBracket(),\n    },\n    '{': { left: '{ ', right: ' }', removeSpace: true, movement: () => new MoveAroundCurlyBrace() },\n    '}': { left: '{', right: '}', removeSpace: false, movement: () => new MoveAroundCurlyBrace() },\n    '>': { left: '<', right: '>', removeSpace: false, movement: () => new MoveAroundCaret() },\n    '\"': {\n      left: '\"',\n      right: '\"',\n      removeSpace: false,\n      movement: () => new MoveAroundDoubleQuotes(false),\n    },\n    \"'\": {\n      left: \"'\",\n      right: \"'\",\n      removeSpace: false,\n      movement: () => new MoveAroundSingleQuotes(false),\n    },\n    '`': {\n      left: '`',\n      right: '`',\n      removeSpace: false,\n      movement: () => new MoveAroundBacktick(false),\n    },\n    '<': { left: '', right: '', removeSpace: false, movement: () => new MoveAroundTag() },\n    '*': {\n      left: '*',\n      right: '*',\n      removeSpace: false,\n      movement: () => new SelectInnerWord(),\n      extraChars: 1,\n    },\n    // aliases\n    b: { left: '(', right: ')', removeSpace: false, movement: () => new MoveAroundParentheses() },\n    r: { left: '[', right: ']', removeSpace: false, movement: () => new MoveAroundSquareBracket() },\n    B: { left: '{', right: '}', removeSpace: false, movement: () => new MoveAroundCurlyBrace() },\n    a: { left: '<', right: '>', removeSpace: false, movement: () => new MoveAroundCaret() },\n    t: { left: '', right: '', removeSpace: false, movement: () => new MoveAroundTag() },\n    _: { left: '_', right: '_', removeSpace: false, movement: () => new SelectInnerWord() },\n  };\n\n  /** returns two ranges (for left and right replacement) for our surround target (X in dsX, csXy) relative to position */\n  public static async getReplaceRanges(\n    vimState: VimState,\n    position: Position,\n    multicursorIndex: number,\n  ): Promise<SurroundEdge | undefined> {\n    /* so this method is a bit of a dumpster for edge cases and ugly details\n    the main idea is this:\n    1. from position, we execute a textobject movement to get the total range of our surround target\n    2. from there, we derive two ranges (left and right), where to apply delete/change\n    3. that our result to return\n    */\n\n    // input verification\n    if (!vimState.surround || !vimState.surround.target) {\n      return undefined;\n    }\n    const target = this.edgePairings[vimState.surround.target];\n    if (!target) {\n      return undefined;\n    }\n\n    // we want start, end of executing movement for surround target count times from position\n    const { removeSpace, movement } = target;\n    vimState.cursorStartPosition = position; // some textobj (MoveInsideCharacter) expect this\n    const count = vimState.recordedState.count || 1;\n    const targetMovement = await movement().execActionWithCount(position, vimState, count);\n    if (!isIMovement(targetMovement) || !!targetMovement.failed) {\n      // we want as result an IMovement, that did not fail.\n      return undefined;\n    }\n    let rangeStart = targetMovement.start;\n    let rangeEnd = targetMovement.stop;\n\n    // some local helpers\n    const getAdjustedRanges = (): SurroundEdge => {\n      if (movement() instanceof MoveInsideCharacter) {\n        // for parens, brackets, curly ... we have to adjust the right range\n        // there seems to be inconsistency between MoveInsideCharacter and MoveQuoteMatch\n        rangeEnd = rangeEnd.getLeft();\n      }\n      if (target.extraChars) {\n        rangeStart = rangeStart.getLeft(target.extraChars);\n        rangeEnd = rangeEnd.getRight(target.extraChars);\n      }\n      // now start and end are on ()\n      // next, check if there is space to remove (foo) vs ( bar )\n      const delSpace = checkRemoveSpace(); // 0 or 1\n\n      return {\n        leftEdge: new Range(rangeStart, rangeStart.getRight(1 + delSpace)),\n        rightEdge: new Range(rangeEnd.getLeft(delSpace), rangeEnd.getRight()),\n        cursorIndex: multicursorIndex,\n      };\n    };\n    const checkRemoveSpace = (): number => {\n      // capiche?\n      const leftSpace = vimState.editor.document.getText(\n        new Range(rangeStart.getRight(), rangeStart.getRight(2)),\n      );\n      const rightSpace = vimState.editor.document.getText(new Range(rangeEnd.getLeft(), rangeEnd));\n      return removeSpace && leftSpace === ' ' && rightSpace === ' ' ? 1 : 0;\n    };\n    const getAdjustedRangesForTag = async (): Promise<SurroundEdge | undefined> => {\n      // we are on start of opening tag and end of closing tag\n      // return ranges from there to the other side\n      // start -> <foo>bar</foo> <-- stop\n      const openTagNameStart = rangeStart.getRight();\n      const openTagNameEnd = openTagNameStart\n        .nextWordEnd(vimState.document, { wordType: WordType.TagName, inclusive: true })\n        .getRight();\n      const closeTagNameStart = rangeEnd\n        .getLeft(2)\n        .prevWordStart(vimState.document, { wordType: WordType.TagName, inclusive: true });\n      const closeTagNameEnd = rangeEnd.getLeft();\n      vimState.cursorStartPosition = position; // some textobj (MoveInsideCharacter) expect this\n      vimState.cursorStopPosition = position;\n      const innerTag =\n        count === 1\n          ? await new MoveInsideTag().execActionWithCount(position, vimState, 1)\n          : await new MoveAroundTag().execActionWithCount(position, vimState, count - 1);\n      if (!isIMovement(innerTag) || !!innerTag.failed) {\n        return undefined;\n      } else {\n        return {\n          leftEdge: new Range(rangeStart, innerTag.start),\n          // maybe there is a small bug with cstt for multicursor, 2nd+ cursors\n          rightEdge: new Range(innerTag.stop, rangeEnd),\n          leftTagName: new Range(openTagNameStart, openTagNameEnd),\n          rightTagName: new Range(closeTagNameStart, closeTagNameEnd),\n          cursorIndex: multicursorIndex,\n        };\n      }\n    };\n\n    // good to go, now we can calculate our ranges based on rangeStart and rangeEnd\n    return vimState.surround.target === 't' ? getAdjustedRangesForTag() : getAdjustedRanges();\n  }\n\n  /** executes our prepared surround changes */\n  public static async ExecuteSurround(vimState: VimState): Promise<void> {\n    const surroundState = vimState.surround;\n    if (!surroundState || !surroundState.edges) {\n      return;\n    }\n\n    const replacement = this.edgePairings[surroundState.replacement];\n    // undefined allowed only for delete operator\n    if (!replacement && surroundState.operator !== 'delete') {\n      throw new Error('replacement missing in pairs');\n    }\n    // handle special case: cstt, replace only tag name\n    if (surroundState.target === 't' && surroundState.tag && surroundState.tag.keepAttributes) {\n      for (const { leftTagName, rightTagName } of surroundState.edges) {\n        if (!surroundState.tag || !leftTagName || !rightTagName) {\n          // throw ?\n          continue;\n        }\n        vimState.recordedState.transformer.replace(leftTagName, surroundState.tag.tag);\n        vimState.recordedState.transformer.replace(rightTagName, surroundState.tag.tag);\n      }\n    }\n    // all other cases: ys, ds, cs\n    else {\n      const optNewline = surroundState.addNewline ? '\\n' : '';\n      const leftFixed =\n        surroundState.operator === 'delete'\n          ? ''\n          : surroundState.tag\n            ? '<' + surroundState.tag.tag + '>' + optNewline\n            : surroundState.function\n              ? surroundState.function + optNewline\n              : replacement.left + optNewline;\n\n      const rightFixed =\n        surroundState.operator === 'delete'\n          ? ''\n          : surroundState.tag\n            ? optNewline + '</' + SurroundHelper.trimAttributes(surroundState.tag.tag) + '>'\n            : optNewline + replacement.right;\n\n      for (const { leftEdge, rightEdge, cursorIndex } of surroundState.edges) {\n        vimState.recordedState.transformer.addTransformation({\n          type: 'replaceText',\n          text: leftFixed,\n          range: leftEdge,\n          cursorIndex,\n          // keep cursor on left edge / start. todo: not completly correct vor visual S\n          diff:\n            surroundState.operator === 'yank'\n              ? PositionDiff.offset({ character: -leftFixed.length })\n              : undefined,\n        });\n        vimState.recordedState.transformer.replace(rightEdge, rightFixed);\n      }\n    }\n\n    // finish / cleanup. sql-koala was here :D\n    await vimState.setCurrentMode(Mode.Normal);\n  }\n\n  private static trimAttributes(wholeTag: string) {\n    const endTagIndex = wholeTag.indexOf(' ');\n    const isAnyAttributeAfterTag = endTagIndex !== -1;\n    return isAnyAttributeAfterTag ? wholeTag.substring(0, endTagIndex) : wholeTag;\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/lastNextObjectHelper.ts",
    "content": "import { Position } from 'vscode';\nimport { isVisualMode } from '../../../mode/mode';\nimport { VimState } from '../../../state/vimState';\nimport { Logger } from '../../../util/logger';\nimport { BaseMovement, failedMovement, IMovement } from '../../baseMotion';\nimport { MoveInsideCharacter } from '../../motion';\nimport { searchPosition } from './searchUtils';\nimport { bracketObjectsEnabled } from './targetsConfig';\n\n/*\n * This function creates a last/next movement based on an existing one.\n * It works by searching for a next/last character, and then applying the given action in its position.\n * For examples of how to use it, see src/actions/plugins/targets/lastNextObjects.ts.\n */\nfunction LastNextObject<T extends MoveInsideCharacter>(type: new () => T, which: 'l' | 'n') {\n  abstract class NextHandlerClass extends BaseMovement {\n    public override readonly keys: readonly string[] | readonly string[][];\n    override isJump = true;\n\n    // actual action (e.g. `i(` )\n    private readonly actual: T;\n    readonly secondKey: 'l' | 'n' = which;\n    // character to search forward/backward for next/last (e.g. `(` for next parenthesis)\n    abstract readonly charToFind: string;\n    // this is just to make sure we won't register anything that we can't handle. see constructor.\n    readonly valid: boolean;\n\n    public override doesActionApply(vimState: VimState, keysPressed: string[]): boolean {\n      return this.valid && bracketObjectsEnabled() && super.doesActionApply(vimState, keysPressed);\n    }\n\n    constructor() {\n      super();\n      this.actual = new type();\n\n      const secondKey = this.secondKey;\n      const withWhichKey = (keys: string[]): string[] | undefined => {\n        if (keys.length === 2) {\n          return [keys[0], secondKey, keys[1]];\n        } else {\n          return undefined;\n        }\n      };\n\n      // we want fail without throwing an exception, but with log, to not break the Vim\n      const errMsg = `failed to register ${which === 'l' ? 'last' : 'next'} for ${type.name}`;\n      // failed, but it should never happen\n      if (this.actual.keys.length < 1) {\n        this.valid = false;\n        this.keys = [];\n        Logger.error(errMsg);\n        return;\n      }\n      if (typeof this.actual.keys[0] === 'string') {\n        const keys = withWhichKey(this.actual.keys as string[]);\n        // failed\n        if (keys === undefined) {\n          this.valid = false;\n          this.keys = [];\n          Logger.error(errMsg);\n          return;\n        } else {\n          this.keys = keys;\n        }\n      } else {\n        const keys = this.actual.keys.map((k) => withWhichKey(k as string[]));\n        // failed\n        if (!keys.every((p) => p !== undefined)) {\n          this.valid = false;\n          this.keys = [];\n          Logger.error(errMsg);\n          return;\n        } else {\n          this.keys = keys;\n        }\n      }\n      this.valid = true;\n    }\n\n    public override async execAction(\n      position: Position,\n      vimState: VimState,\n      firstIteration: boolean,\n      lastIteration: boolean,\n    ): Promise<IMovement> {\n      const maybePosition = searchPosition(this.charToFind, vimState.document, position, {\n        direction: which === 'l' ? '<' : '>',\n        includeCursor: false,\n        throughLineBreaks: true,\n      });\n      if (maybePosition === undefined) {\n        return failedMovement(vimState);\n      }\n      vimState.cursorStartPosition = maybePosition;\n      vimState.cursorStopPosition = maybePosition;\n      const movement = await this.actual.execAction(\n        maybePosition,\n        vimState,\n        firstIteration,\n        lastIteration,\n      );\n      if (movement.failed) {\n        return movement;\n      }\n      const { start, stop } = movement;\n      if (!isVisualMode(vimState.currentMode) && position.isBefore(start)) {\n        vimState.recordedState.operatorPositionDiff = start.subtract(position);\n      } else if (!isVisualMode(vimState.currentMode) && position.isAfter(stop)) {\n        if (position.line === stop.line) {\n          vimState.recordedState.operatorPositionDiff = stop.subtract(position);\n        } else {\n          vimState.recordedState.operatorPositionDiff = start.subtract(position);\n        }\n      }\n\n      vimState.cursorStartPosition = start;\n      vimState.cursorStopPosition = stop;\n      return movement;\n    }\n  }\n  return NextHandlerClass;\n}\n\nexport function LastObject<T extends MoveInsideCharacter>(type: new () => T) {\n  return LastNextObject(type, 'l');\n}\nexport function NextObject<T extends MoveInsideCharacter>(type: new () => T) {\n  return LastNextObject(type, 'n');\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/lastNextObjects.ts",
    "content": "import { RegisterAction } from '../../base';\nimport {\n  MoveAroundCaret,\n  MoveAroundCurlyBrace,\n  MoveAroundParentheses,\n  MoveAroundSquareBracket,\n  MoveInsideCaret,\n  MoveInsideCurlyBrace,\n  MoveInsideParentheses,\n  MoveInsideSquareBracket,\n} from '../../motion';\nimport { LastObject, NextObject } from './lastNextObjectHelper';\n\n@RegisterAction\nclass MoveInsideNextParentheses extends NextObject(MoveInsideParentheses) {\n  override readonly charToFind: string = '(';\n}\n\n@RegisterAction\nclass MoveInsideLastParentheses extends LastObject(MoveInsideParentheses) {\n  override readonly charToFind: string = ')';\n}\n\n@RegisterAction\nclass MoveAroundNextParentheses extends NextObject(MoveAroundParentheses) {\n  override readonly charToFind: string = '(';\n}\n\n@RegisterAction\nclass MoveAroundLastParentheses extends LastObject(MoveAroundParentheses) {\n  override readonly charToFind: string = ')';\n}\n\n@RegisterAction\nclass MoveInsideNextCurlyBrace extends NextObject(MoveInsideCurlyBrace) {\n  override readonly charToFind: string = '{';\n}\n\n@RegisterAction\nclass MoveInsideLastCurlyBrace extends LastObject(MoveInsideCurlyBrace) {\n  override readonly charToFind: string = '}';\n}\n\n@RegisterAction\nclass MoveAroundNextCurlyBrace extends NextObject(MoveAroundCurlyBrace) {\n  override readonly charToFind: string = '{';\n}\n\n@RegisterAction\nclass MoveAroundLastCurlyBrace extends LastObject(MoveAroundCurlyBrace) {\n  override readonly charToFind: string = '}';\n}\n\n@RegisterAction\nclass MoveInsideNextSquareBracket extends NextObject(MoveInsideSquareBracket) {\n  override readonly charToFind: string = '[';\n}\n\n@RegisterAction\nclass MoveInsideLastSquareBracket extends LastObject(MoveInsideSquareBracket) {\n  override readonly charToFind: string = ']';\n}\n\n@RegisterAction\nclass MoveAroundNextSquareBracket extends NextObject(MoveAroundSquareBracket) {\n  override readonly charToFind: string = '[';\n}\n\n@RegisterAction\nclass MoveAroundLastSquareBracket extends LastObject(MoveAroundSquareBracket) {\n  override readonly charToFind: string = ']';\n}\n\n@RegisterAction\nclass MoveInsideNextCaret extends NextObject(MoveInsideCaret) {\n  override readonly charToFind: string = '<';\n}\n\n@RegisterAction\nclass MoveInsideLastCaret extends LastObject(MoveInsideCaret) {\n  override readonly charToFind: string = '>';\n}\n\n@RegisterAction\nclass MoveAroundNextCaret extends NextObject(MoveAroundCaret) {\n  override readonly charToFind: string = '<';\n}\n\n@RegisterAction\nclass MoveAroundLastCaret extends LastObject(MoveAroundCaret) {\n  override readonly charToFind: string = '>';\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/searchUtils.ts",
    "content": "import { Position, TextDocument } from 'vscode';\n\nexport interface SearchFlags {\n  direction?: '<' | '>';\n  includeCursor?: boolean;\n  throughLineBreaks?: boolean;\n}\n\nfunction searchForward(\n  str: string,\n  document: TextDocument,\n  start: Position,\n  flags: {\n    throughLineBreaks?: boolean;\n  } = {\n    throughLineBreaks: false,\n  },\n): Position | undefined {\n  let position = start;\n  for (\n    let line = position.line;\n    line < document.lineCount && (flags.throughLineBreaks || line === start.line);\n    line++\n  ) {\n    position = document.validatePosition(position.with({ line }));\n    const text = document.lineAt(position).text;\n    const index = text.indexOf(str, position.character);\n    if (index >= 0) {\n      return position.with({ character: index });\n    }\n    position = position.with({ character: 0 }); // set at line begin for next iteration\n  }\n  return undefined;\n}\n\nfunction searchBackward(\n  str: string,\n  document: TextDocument,\n  start: Position,\n  flags: {\n    throughLineBreaks?: boolean;\n  } = {\n    throughLineBreaks: false,\n  },\n): Position | undefined {\n  let position = start;\n  for (\n    let line = position.line;\n    line >= 0 && (flags.throughLineBreaks || line === start.line);\n    line--\n  ) {\n    position = document.validatePosition(position.with({ line }));\n    const text = document.lineAt(position).text;\n    const index = text.lastIndexOf(str, position.character);\n    if (index >= 0) {\n      return position.with({ character: index });\n    }\n    position = position.with({ character: +Infinity }); // set at line end for next iteration\n  }\n  return undefined;\n}\n\nexport function maybeGetLeft(\n  position: Position,\n  {\n    count = 1,\n    throughLineBreaks,\n    dontMove,\n  }: { count?: number; throughLineBreaks?: boolean; dontMove?: boolean },\n) {\n  return dontMove\n    ? position\n    : throughLineBreaks\n      ? position.getOffsetThroughLineBreaks(-count)\n      : position.getLeft(count);\n}\nexport function maybeGetRight(\n  position: Position,\n  {\n    count = 1,\n    throughLineBreaks,\n    dontMove,\n  }: { count?: number; throughLineBreaks?: boolean; dontMove?: boolean },\n) {\n  return dontMove\n    ? position\n    : throughLineBreaks\n      ? position.getOffsetThroughLineBreaks(count)\n      : position.getRight(count);\n}\n\nexport function searchPosition(\n  str: string,\n  document: TextDocument,\n  start: Position,\n  flags: SearchFlags = {\n    direction: '>',\n    includeCursor: true,\n    throughLineBreaks: false,\n  },\n): Position | undefined {\n  if (flags.direction === '<') {\n    start = maybeGetLeft(start, {\n      dontMove: flags.includeCursor,\n      throughLineBreaks: flags.throughLineBreaks,\n    });\n    return searchBackward(str, document, start, flags);\n  } else {\n    start = maybeGetRight(start, {\n      dontMove: flags.includeCursor,\n      throughLineBreaks: flags.throughLineBreaks,\n    });\n    return searchForward(str, document, start, flags);\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/smartQuotes.ts",
    "content": "import { Mode } from '../../../mode/mode';\nimport { RegisterAction } from '../../base';\nimport { MoveQuoteMatch } from '../../motion';\n\nabstract class SmartQuotes extends MoveQuoteMatch {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n}\n\n@RegisterAction\nexport class MoveAroundNextSingleQuotes extends SmartQuotes {\n  keys = ['a', 'n', \"'\"];\n  readonly charToMatch = \"'\";\n  override readonly which = 'next';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideNextSingleQuotes extends SmartQuotes {\n  keys = ['i', 'n', \"'\"];\n  readonly charToMatch = \"'\";\n  override readonly which = 'next';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundLastSingleQuotes extends SmartQuotes {\n  keys = ['a', 'l', \"'\"];\n  readonly charToMatch = \"'\";\n  override readonly which = 'last';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideLastSingleQuotes extends SmartQuotes {\n  keys = ['i', 'l', \"'\"];\n  readonly charToMatch = \"'\";\n  override readonly which = 'last';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundNextDoubleQuotes extends SmartQuotes {\n  keys = ['a', 'n', '\"'];\n  readonly charToMatch = '\"';\n  override readonly which = 'next';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideNextDoubleQuotes extends SmartQuotes {\n  keys = ['i', 'n', '\"'];\n  readonly charToMatch = '\"';\n  override readonly which = 'next';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundLastDoubleQuotes extends SmartQuotes {\n  keys = ['a', 'l', '\"'];\n  readonly charToMatch = '\"';\n  override readonly which = 'last';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideLastDoubleQuotes extends SmartQuotes {\n  keys = ['i', 'l', '\"'];\n  readonly charToMatch = '\"';\n  override readonly which = 'last';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundNextBacktick extends SmartQuotes {\n  keys = ['a', 'n', '`'];\n  readonly charToMatch = '`';\n  override readonly which = 'next';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideNextBacktick extends SmartQuotes {\n  keys = ['i', 'n', '`'];\n  readonly charToMatch = '`';\n  override readonly which = 'next';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundLastBacktick extends SmartQuotes {\n  keys = ['a', 'l', '`'];\n  readonly charToMatch = '`';\n  override readonly which = 'last';\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideLastBacktick extends SmartQuotes {\n  keys = ['i', 'l', '`'];\n  readonly charToMatch = '`';\n  override readonly which = 'last';\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundQuote extends SmartQuotes {\n  keys = ['a', 'q'];\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideQuote extends SmartQuotes {\n  keys = ['i', 'q'];\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundNextQuote extends SmartQuotes {\n  keys = ['a', 'n', 'q'];\n  override readonly which = 'next';\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideNextQuote extends SmartQuotes {\n  keys = ['i', 'n', 'q'];\n  override readonly which = 'next';\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = false;\n}\n\n@RegisterAction\nexport class MoveAroundLastQuote extends SmartQuotes {\n  keys = ['a', 'l', 'q'];\n  override readonly which = 'last';\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = true;\n}\n\n@RegisterAction\nexport class MoveInsideLastQuote extends SmartQuotes {\n  keys = ['i', 'l', 'q'];\n  override readonly which = 'last';\n  override readonly anyQuote = true;\n  readonly charToMatch = '\"'; // it is not in use, because anyQuote is true.\n  override includeQuotes = false;\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/smartQuotesMatcher.ts",
    "content": "import { Position, TextDocument } from 'vscode';\nimport { configuration } from '../../../configuration/configuration';\n\ntype Quote = '\"' | \"'\" | '`';\nenum QuoteMatch {\n  Opening,\n  Closing,\n}\nexport type WhichQuotes = 'current' | 'next' | 'last';\ntype Dir = '>' | '<';\ntype SearchAction = {\n  first: Dir;\n  second: Dir;\n  includeCurrent: boolean;\n};\ntype QuotesAction = {\n  search: SearchAction | undefined;\n  skipToLeft: number; // for last quotes, how many quotes need to skip while searching\n  skipToRight: number; // for next quotes, how many quotes need to skip while searching\n};\n\n/**\n * This mapping is used to give a way to identify which action we need to take when operating on a line.\n * The keys here are, in some sense, the number of quotes in the line, in the format of `lcr`, where:\n * `l` means left of the cursor, `c` whether the cursor is on a quote, and `r` is right of the cursor.\n *\n * It is based on the ideas used in `targets.vim`. For each line & cursor position, we count the number of quotes\n * left (#L) and right (#R) of the cursor. Using those numbers and whether the cursor it on a quote, we know\n * what action to make.\n *\n * For each entry we have an example of a line & position.\n */\nconst quoteDirs: Record<string, QuotesAction> = {\n  '002': {\n    // | \"a\" \"b\" \"c\"\n    search: { first: '>', second: '>', includeCurrent: false },\n    skipToLeft: 0,\n    skipToRight: 1,\n  },\n  '012': {\n    // |\"a\" \"b\" \"c\" \"\n    search: { first: '>', second: '>', includeCurrent: true },\n    skipToLeft: 0,\n    skipToRight: 2,\n  },\n  '102': {\n    // \"a\" \"|b\" \"c\" \"\n    search: { first: '<', second: '>', includeCurrent: false },\n    skipToLeft: 2,\n    skipToRight: 2,\n  },\n  '112': {\n    //  \"a\" \"b|\" \"c\"\n    search: { first: '<', second: '<', includeCurrent: true },\n    skipToLeft: 2,\n    skipToRight: 1,\n  },\n  '202': {\n    //  \"a\"| \"b\" \"c\"\n    search: { first: '>', second: '>', includeCurrent: false },\n    skipToLeft: 1,\n    skipToRight: 1,\n  },\n  '211': {\n    //  \"a\" |\"b\" \"c\"\n    search: { first: '>', second: '>', includeCurrent: true },\n    skipToLeft: 1,\n    skipToRight: 2,\n  },\n  '101': {\n    //  \"a\" \"|b\" \"c\"\n    search: { first: '<', second: '>', includeCurrent: false },\n    skipToLeft: 2,\n    skipToRight: 2,\n  },\n  '011': {\n    //  |\"a\" \"b\" \"c\"\n    search: { first: '>', second: '>', includeCurrent: true },\n    skipToLeft: 0,\n    skipToRight: 2,\n  },\n  '110': {\n    //  \"a\" \"b\" \"c|\"\n    search: { first: '<', second: '<', includeCurrent: true },\n    skipToLeft: 2,\n    skipToRight: 0,\n  },\n  '212': {\n    //  \"a\" |\"b\" \"c\" \"\n    search: { first: '>', second: '>', includeCurrent: true },\n    skipToLeft: 1,\n    skipToRight: 2,\n  },\n  '111': {\n    //  \"a\" \"b|\" \"c\" \"\n    search: { first: '<', second: '<', includeCurrent: true },\n    skipToLeft: 2,\n    skipToRight: 1,\n  },\n  '200': {\n    //  \"a\" \"b\" \"c\"|\n    search: { first: '<', second: '<', includeCurrent: false },\n    skipToLeft: 1,\n    skipToRight: 0,\n  },\n  '201': {\n    //  \"a\" \"b\" \"c\"| \"\n    //  \"a\"| \"b\" \"c\" \"\n    search: { first: '>', second: '>', includeCurrent: false },\n    skipToLeft: 1,\n    skipToRight: 1,\n  },\n  '210': {\n    //  \"a\" \"b\" \"c\" |\"\n    search: undefined,\n    skipToLeft: 1,\n    skipToRight: 0,\n  },\n  '001': {\n    // | \"a\" \"b\" \"c\" \"\n    search: undefined,\n    skipToLeft: 0,\n    skipToRight: 1,\n  },\n  '010': {\n    //  a|\"b\n    search: undefined,\n    skipToLeft: 0,\n    skipToRight: 0,\n  },\n  '100': {\n    //  \"a\" \"b\" \"c\" \"|\n    search: undefined,\n    skipToLeft: 2,\n    skipToRight: 0,\n  },\n  '000': {\n    //  |ab\n    search: undefined,\n    skipToLeft: 0,\n    skipToRight: 0,\n  },\n};\n\nexport class SmartQuoteMatcher {\n  static readonly escapeChar = '\\\\';\n\n  private document: TextDocument;\n  private quote: Quote | 'any';\n\n  constructor(quote: Quote | 'any', document: TextDocument) {\n    this.quote = quote;\n    this.document = document;\n  }\n\n  private buildQuoteMap(text: string) {\n    const quoteMap: QuoteMatch[] = [];\n    let openingQuote = true;\n    // Loop over text, marking quotes and respecting escape characters.\n    for (let i = 0; i < text.length; i++) {\n      if (text[i] === SmartQuoteMatcher.escapeChar) {\n        i += 1;\n        continue;\n      }\n\n      if (\n        (this.quote === 'any' && (text[i] === '\"' || text[i] === \"'\" || text[i] === '`')) ||\n        text[i] === this.quote\n      ) {\n        quoteMap[i] = openingQuote ? QuoteMatch.Opening : QuoteMatch.Closing;\n        openingQuote = !openingQuote;\n      }\n    }\n    return quoteMap;\n  }\n\n  private static lineSearchAction(cursorIndex: number, quoteMap: QuoteMatch[]) {\n    // base on ideas from targets.vim\n\n    // cut line in left of, on and right of cursor\n    const left = Array.from(quoteMap.entries()).slice(undefined, cursorIndex);\n    const cursor = quoteMap[cursorIndex];\n    const right = Array.from(quoteMap.entries()).slice(cursorIndex + 1, undefined);\n\n    // how many delimiters left, on and right of cursor\n    const lc = left.filter(([_, v]) => v !== undefined).length;\n    const cc = cursor !== undefined ? 1 : 0;\n    const rc = right.filter(([_, v]) => v !== undefined).length;\n\n    // truncate counts\n    const lct = lc === 0 ? 0 : lc % 2 === 0 ? 2 : 1;\n    const rct = rc === 0 ? 0 : rc >= 2 ? 2 : 1;\n\n    const key = `${lct}${cc}${rct}`;\n    const act = quoteDirs[key];\n\n    return act;\n  }\n\n  public smartSurroundingQuotes(\n    position: Position,\n    which: WhichQuotes,\n  ): { start: Position; stop: Position; lineText: string } | undefined {\n    position = this.document.validatePosition(position);\n    const cursorIndex = position.character;\n    const lineText = this.document.lineAt(position).text;\n    const quoteMap = this.buildQuoteMap(lineText);\n\n    const act = SmartQuoteMatcher.lineSearchAction(cursorIndex, quoteMap);\n\n    if (which === 'current') {\n      if (act.search) {\n        const searchRes = this.smartSearch(cursorIndex, act.search, quoteMap);\n        return searchRes\n          ? {\n              start: position.with({ character: searchRes[0] }),\n              stop: position.with({ character: searchRes[1] }),\n              lineText,\n            }\n          : undefined;\n      } else {\n        return undefined;\n      }\n    } else if (which === 'next') {\n      // search quote in current line\n      const right = Array.from(quoteMap.entries()).slice(cursorIndex + 1, undefined);\n      const [index, found] = right.filter(([i, v]) => v !== undefined)[act.skipToRight] ?? [\n        +Infinity,\n        undefined,\n      ];\n      // find next position for surrounding quotes, possibly breaking through lines\n      let nextPos;\n      position = position.with({ character: index });\n      if (found === undefined && configuration.targets.smartQuotes.breakThroughLines) {\n        // nextPos = State.evalGenerator(this.getNextQuoteThroughLineBreaks(), position);\n        nextPos = this.getNextQuoteThroughLineBreaks(position);\n      } else {\n        nextPos = found !== undefined ? position : undefined;\n      }\n\n      // find surrounding with new position\n      if (nextPos) {\n        return this.smartSurroundingQuotes(nextPos, 'current');\n      } else {\n        return undefined;\n      }\n    } else if (which === 'last') {\n      // search quote in current line\n      const left = Array.from(quoteMap.entries()).slice(undefined, cursorIndex);\n      const [index, found] = left.reverse().filter(([i, v]) => v !== undefined)[act.skipToLeft] ?? [\n        0,\n        undefined,\n      ];\n      // find last position for surrounding quotes, possibly breaking through lines\n      let lastPos;\n      position = position.with({ character: index });\n      if (found === undefined && configuration.targets.smartQuotes.breakThroughLines) {\n        position = position.getLeftThroughLineBreaks();\n        lastPos = this.getLastQuoteThroughLineBreaks(position);\n      } else {\n        lastPos = found !== undefined ? position : undefined;\n      }\n\n      // find surrounding with new position\n      if (lastPos) {\n        return this.smartSurroundingQuotes(lastPos, 'current');\n      } else {\n        return undefined;\n      }\n    } else {\n      return undefined;\n    }\n  }\n\n  private smartSearch(\n    start: number,\n    action: SearchAction,\n    quoteMap: QuoteMatch[],\n  ): [number, number] | undefined {\n    const offset = action.includeCurrent ? 1 : 0;\n    let cursorPos: number | undefined = start;\n    let fst: number | undefined;\n    let snd: number | undefined;\n\n    if (action.first === '>') {\n      cursorPos = fst = this.getNextQuote(cursorPos - offset, quoteMap);\n    } else {\n      // dir === '<'\n      cursorPos = fst = this.getPrevQuote(cursorPos + offset, quoteMap);\n    }\n    if (cursorPos === undefined) return undefined;\n\n    if (action.second === '>') {\n      snd = this.getNextQuote(cursorPos, quoteMap);\n    } else {\n      // dir === '<'\n      snd = this.getPrevQuote(cursorPos, quoteMap);\n    }\n\n    if (fst === undefined || snd === undefined) return undefined;\n\n    if (fst < snd) return [fst, snd];\n    else return [snd, fst];\n  }\n\n  private getNextQuoteThroughLineBreaks(position: Position): Position | undefined {\n    for (let line = position.line; line < this.document.lineCount; line++) {\n      position = this.document.validatePosition(position.with({ line }));\n      const text = this.document.lineAt(position).text;\n      if (this.quote === 'any') {\n        for (let i = position.character; i < text.length; i++) {\n          if (text[i] === '\"' || text[i] === \"'\" || text[i] === '`') {\n            return position.with({ character: i });\n          }\n        }\n      } else {\n        const index = text.indexOf(this.quote, position.character);\n        if (index >= 0) {\n          return position.with({ character: index });\n        }\n      }\n      position = position.with({ character: 0 }); // set at line begin for next iteration\n    }\n    return undefined;\n  }\n  private getLastQuoteThroughLineBreaks(position: Position): Position | undefined {\n    for (let line = position.line; line >= 0; line--) {\n      position = this.document.validatePosition(position.with({ line }));\n      const text = this.document.lineAt(position).text;\n      if (this.quote === 'any') {\n        for (let i = position.character; i >= 0; i--) {\n          if (text[i] === '\"' || text[i] === \"'\" || text[i] === '`') {\n            return position.with({ character: i });\n          }\n        }\n      } else {\n        const index = text.lastIndexOf(this.quote, position.character);\n        if (index >= 0) {\n          return position.with({ character: index });\n        }\n      }\n      position = position.with({ character: +Infinity }); // set at line end for next iteration\n    }\n    return undefined;\n  }\n\n  private getNextQuote(start: number, quoteMap: QuoteMatch[]): number | undefined {\n    for (let i = start + 1; i < quoteMap.length; i++) {\n      if (quoteMap[i] !== undefined) {\n        return i;\n      }\n    }\n\n    return undefined;\n  }\n\n  private getPrevQuote(start: number, quoteMap: QuoteMatch[]): number | undefined {\n    for (let i = start - 1; i >= 0; i--) {\n      if (quoteMap[i] !== undefined) {\n        return i;\n      }\n    }\n\n    return undefined;\n  }\n}\n"
  },
  {
    "path": "src/actions/plugins/targets/targets.ts",
    "content": "// targets sub-plugins\nimport './lastNextObjects';\nimport './smartQuotes';\n"
  },
  {
    "path": "src/actions/plugins/targets/targetsConfig.ts",
    "content": "import { configuration } from '../../../configuration/configuration';\n\nexport function useSmartQuotes(): boolean {\n  return (\n    (configuration.targets.enable === true && configuration.targets.smartQuotes.enable !== false) ||\n    (configuration.targets.enable === undefined &&\n      configuration.targets.smartQuotes.enable === true)\n  );\n}\n\nexport function bracketObjectsEnabled(): boolean {\n  return (\n    (configuration.targets.enable === true &&\n      configuration.targets.bracketObjects.enable !== false) ||\n    (configuration.targets.enable === undefined &&\n      configuration.targets.bracketObjects.enable === true)\n  );\n}\n"
  },
  {
    "path": "src/actions/types.d.ts",
    "content": "import type { Position } from 'vscode';\nimport type { VimState } from '../state/vimState';\n\nexport type ActionType = 'command' | 'motion' | 'operator' | 'number';\n\nexport interface IBaseAction {\n  readonly name: string | undefined;\n  readonly actionType: ActionType;\n  readonly isJump: boolean;\n  readonly createsUndoPoint: boolean;\n\n  keysPressed: string[];\n  multicursorIndex: number | undefined;\n\n  readonly preservesDesiredColumn: boolean;\n}\n\nexport interface IBaseCommand extends IBaseAction {\n  exec(position: Position, vimState: VimState): Promise<void>;\n}\n\nexport interface IBaseOperator extends IBaseAction {\n  run(vimState: VimState, start: Position, stop: Position): Promise<void>;\n  runRepeat(vimState: VimState, position: Position, count: number): Promise<void>;\n}\n"
  },
  {
    "path": "src/actions/wrapping.ts",
    "content": "import { configuration } from './../configuration/configuration';\nimport { Mode } from './../mode/mode';\n\n/**\n * See https://vimhelp.org/options.txt.html#%27whichwrap%27\n *\n * @returns true if the given key should cause the cursor to wrap around line boundary\n */\nexport const shouldWrapKey = (mode: Mode, key: string): boolean => {\n  let k: string;\n  if (key === '<left>') {\n    k = [Mode.Insert, Mode.Replace].includes(mode) ? '[' : '<';\n  } else if (key === '<right>') {\n    k = [Mode.Insert, Mode.Replace].includes(mode) ? ']' : '>';\n  } else if (['<BS>', '<C-BS>', '<S-BS>'].includes(key)) {\n    k = 'b';\n  } else if (key === ' ') {\n    k = 's';\n  } else if (['h', 'l', '~'].includes(key)) {\n    k = key;\n  } else {\n    throw new Error(`shouldWrapKey called with unexpected key='${key}'`);\n  }\n  return configuration.whichwrap.split(',').includes(k);\n};\n"
  },
  {
    "path": "src/cmd_line/commandLine.ts",
    "content": "import { Parser } from 'parsimmon';\nimport { ExtensionContext, Position, window } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { ErrorCode, VimError } from '../error';\nimport { CommandLineHistory, HistoryFile, SearchHistory } from '../history/historyFile';\nimport { Register } from '../register/register';\nimport { globalState } from '../state/globalState';\nimport { RecordedState } from '../state/recordedState';\nimport { IndexedPosition, IndexedRange, SearchState } from '../state/searchState';\nimport { VimState } from '../state/vimState';\nimport { StatusBar } from '../statusBar';\nimport { WordType, getWordLeftInText, getWordRightInText } from '../textobject/word';\nimport { SearchDecorations, getDecorationsForSearchMatchRanges } from '../util/decorationUtils';\nimport { Logger } from '../util/logger';\nimport { escapeCSSIcons, reportSearch } from '../util/statusBarTextUtils';\nimport { scrollView } from '../util/util';\nimport { ExCommand } from '../vimscript/exCommand';\nimport { LineRange } from '../vimscript/lineRange';\nimport { SearchDirection } from '../vimscript/pattern';\nimport { Mode } from './../mode/mode';\nimport { RegisterCommand } from './commands/register';\nimport { SubstituteCommand } from './commands/substitute';\n\nexport abstract class CommandLine {\n  public cursorIndex: number;\n  public previousMode: Mode;\n  protected historyIndex: number | undefined;\n  private savedText: string;\n\n  constructor(text: string, previousMode: Mode) {\n    this.cursorIndex = text.length;\n    this.historyIndex = this.getHistory().get().length;\n    this.previousMode = previousMode;\n    this.savedText = text;\n  }\n\n  /**\n   * @returns the text to be displayed in the status bar\n   */\n  public abstract display(cursorChar: string): string;\n\n  /**\n   * What the user has typed, minus any prefix, etc.\n   */\n  public abstract get text(): string;\n  public abstract set text(text: string);\n\n  /**\n   * @returns the SearchState associated with this CommandLine, if one exists\n   *\n   * This applies to `/`, `:s`, `:g`, `:v`, etc.\n   */\n  public abstract getSearchState(): SearchState | undefined;\n\n  public abstract getHistory(): HistoryFile;\n\n  public abstract getDecorations(vimState: VimState): SearchDecorations | undefined;\n\n  /**\n   * Called when `<Enter>` is pressed\n   */\n  public abstract run(vimState: VimState): Promise<void>;\n\n  /**\n   * Called when `<Esc>` is pressed\n   */\n  public abstract escape(vimState: VimState): Promise<void>;\n\n  /**\n   * Called when `<C-f>` is pressed\n   */\n  public abstract ctrlF(vimState: VimState): Promise<void>;\n\n  public historyBack(): void {\n    if (this.historyIndex === 0) {\n      return;\n    }\n\n    const historyEntries = this.getHistory().get();\n    if (this.historyIndex === undefined) {\n      this.historyIndex = historyEntries.length - 1;\n      this.savedText = this.text;\n    } else if (this.historyIndex > 0) {\n      this.historyIndex--;\n    }\n\n    this.text = historyEntries[this.historyIndex];\n    this.cursorIndex = this.text.length;\n  }\n\n  public historyForward(): void {\n    if (this.historyIndex === undefined) {\n      return;\n    }\n\n    const historyEntries = this.getHistory().get();\n    if (this.historyIndex === historyEntries.length - 1) {\n      this.historyIndex = undefined;\n      this.text = this.savedText;\n    } else if (this.historyIndex < historyEntries.length - 1) {\n      this.historyIndex++;\n      this.text = historyEntries[this.historyIndex];\n    }\n\n    this.cursorIndex = this.text.length;\n  }\n\n  /**\n   * Called when `<BS>` is pressed\n   */\n  public async backspace(vimState: VimState): Promise<void> {\n    if (this.cursorIndex === 0) {\n      if (this.text.length === 0) {\n        await this.escape(vimState);\n      }\n      return;\n    }\n\n    this.text = this.text.slice(0, this.cursorIndex - 1) + this.text.slice(this.cursorIndex);\n    this.cursorIndex = Math.max(this.cursorIndex - 1, 0);\n  }\n\n  /**\n   * Called when `<Del>` is pressed\n   */\n  public async delete(vimState: VimState): Promise<void> {\n    if (this.cursorIndex === this.text.length) {\n      return this.backspace(vimState);\n    }\n\n    this.text = this.text.slice(0, this.cursorIndex) + this.text.slice(this.cursorIndex + 1);\n  }\n\n  /**\n   * Called when `<Home>` is pressed\n   */\n  public home(): void {\n    this.cursorIndex = 0;\n  }\n\n  /**\n   * Called when `<End>` is pressed\n   */\n  public end(): void {\n    this.cursorIndex = this.text.length;\n  }\n\n  /**\n   * Called when `<C-Left>` is pressed\n   */\n  public wordLeft(): void {\n    this.cursorIndex = getWordLeftInText(this.text, this.cursorIndex, WordType.Big) ?? 0;\n  }\n\n  /**\n   * Called when `<C-Right>` is pressed\n   */\n  public wordRight(): void {\n    this.cursorIndex =\n      getWordRightInText(this.text, this.cursorIndex, WordType.Big) ?? this.text.length;\n  }\n\n  /**\n   * Called when `<C-BS>` is pressed\n   */\n  public deleteWord(): void {\n    const wordStart = getWordLeftInText(this.text, this.cursorIndex, WordType.Normal);\n    if (wordStart !== undefined) {\n      this.text = this.text.substring(0, wordStart).concat(this.text.slice(this.cursorIndex));\n      this.cursorIndex = this.cursorIndex - (this.cursorIndex - wordStart);\n    }\n  }\n\n  /**\n   * Called when `<C-BS>` is pressed\n   */\n  public deleteToBeginning(): void {\n    this.text = this.text.slice(this.cursorIndex);\n    this.cursorIndex = 0;\n  }\n\n  public typeCharacter(char: string): void {\n    const modifiedString = this.text.split('');\n    modifiedString.splice(this.cursorIndex, 0, char);\n    this.text = modifiedString.join('');\n    this.cursorIndex += char.length;\n  }\n}\n\nexport class ExCommandLine extends CommandLine {\n  static history: CommandLineHistory;\n  static parser: Parser<{ lineRange: LineRange | undefined; command: ExCommand }>;\n  static onSearch: (vimState: VimState) => Promise<void>;\n\n  public static async loadHistory(context: ExtensionContext): Promise<void> {\n    ExCommandLine.history = new CommandLineHistory(context);\n    await ExCommandLine.history.load();\n  }\n\n  // TODO: Make this stuff private?\n  public autoCompleteIndex = 0;\n  public autoCompleteItems: string[] = [];\n  public preCompleteCharacterPos = 0;\n  public preCompleteCommand = '';\n\n  private commandText: string;\n  private lineRange: LineRange | undefined;\n  private command: ExCommand | undefined;\n\n  constructor(commandText: string, previousMode: Mode) {\n    super(commandText, previousMode);\n    this.commandText = commandText;\n    this.text = commandText;\n    this.previousMode = previousMode;\n  }\n\n  public display(cursorChar: string): string {\n    return escapeCSSIcons(\n      `:${this.text.substring(0, this.cursorIndex)}${cursorChar}${this.text.substring(\n        this.cursorIndex,\n      )}`,\n    );\n  }\n\n  public get text(): string {\n    return this.commandText;\n  }\n  public set text(text: string) {\n    this.commandText = text;\n\n    try {\n      // TODO: This eager parsing is costly, and if it's not `:s` or similar, don't need to parse the args at all\n      const { lineRange, command } = ExCommandLine.parser.tryParse(this.commandText);\n      this.lineRange = lineRange;\n      this.command = command;\n    } catch (err) {\n      this.lineRange = undefined;\n      this.command = undefined;\n    }\n  }\n\n  public getSearchState(): SearchState | undefined {\n    return undefined;\n  }\n\n  public getCommand(): ExCommand | undefined {\n    return this.command;\n  }\n\n  public getDecorations(vimState: VimState): SearchDecorations | undefined {\n    return this.command instanceof SubstituteCommand &&\n      vimState.currentMode === Mode.CommandlineInProgress\n      ? this.command.getSubstitutionDecorations(vimState, this.lineRange)\n      : undefined;\n  }\n\n  public getHistory(): HistoryFile {\n    return ExCommandLine.history;\n  }\n\n  public async run(vimState: VimState): Promise<void> {\n    Logger.info(`Executing :${this.text}`);\n    void ExCommandLine.history.add(this.text);\n    this.historyIndex = ExCommandLine.history.get().length;\n\n    if (!(this.command instanceof RegisterCommand)) {\n      // TODO(jfields): Wait...why are we saving the `:` register as a RecordedState?\n      const recState = new RecordedState();\n      recState.registerName = ':';\n      recState.commandList = this.text.split('');\n      Register.setReadonlyRegister(':', recState);\n    }\n\n    try {\n      if (this.command === undefined) {\n        // TODO: A bit gross:\n        ExCommandLine.parser.tryParse(this.text);\n        throw new Error(`Expected parsing ExCommand '${this.text}' to fail`);\n      }\n\n      const useNeovim = configuration.enableNeovim && this.command.neovimCapable();\n      if (useNeovim && vimState.nvim) {\n        const { statusBarText, error } = await vimState.nvim.run(vimState, this.text);\n        StatusBar.setText(vimState, statusBarText, error);\n      } else {\n        if (this.lineRange) {\n          await this.command.executeWithRange(vimState, this.lineRange);\n        } else {\n          await this.command.execute(vimState);\n        }\n      }\n    } catch (e) {\n      if (e instanceof VimError) {\n        if (\n          e.code === ErrorCode.NotAnEditorCommand &&\n          configuration.enableNeovim &&\n          vimState.nvim\n        ) {\n          const { statusBarText } = await vimState.nvim.run(vimState, this.text);\n          StatusBar.setText(vimState, statusBarText, true);\n        } else {\n          StatusBar.setText(vimState, e.toString(), true);\n        }\n      } else {\n        Logger.error(`Error executing cmd=${this.text}. err=${e}.`);\n      }\n    }\n\n    // Update state if this command is repeatable via dot command.\n    vimState.lastCommandDotRepeatable = this.command?.isRepeatableWithDot ?? false;\n  }\n\n  public async escape(vimState: VimState): Promise<void> {\n    await vimState.setCurrentMode(Mode.Normal);\n    if (this.text.length > 0) {\n      void ExCommandLine.history.add(this.text);\n    }\n  }\n\n  public async ctrlF(vimState: VimState): Promise<void> {\n    void ExCommandLine.onSearch(vimState);\n  }\n}\n\nexport class SearchCommandLine extends CommandLine {\n  public static history: SearchHistory;\n  public static readonly previousSearchStates: SearchState[] = [];\n  public static onSearch: (vimState: VimState, direction: SearchDirection) => Promise<void>;\n\n  /**\n   * Shows the search history as a QuickPick (popup list)\n   *\n   * @returns The SearchState that was selected by the user, if there was one.\n   */\n  public static async showSearchHistory(): Promise<SearchState | undefined> {\n    const items = SearchCommandLine.previousSearchStates\n      .slice()\n      .reverse()\n      .map((searchState) => {\n        return {\n          label: searchState.searchString,\n          searchState,\n        };\n      });\n\n    const item = await window.showQuickPick(items, {\n      placeHolder: 'Vim search history',\n      ignoreFocusOut: false,\n    });\n\n    return item?.searchState;\n  }\n\n  public static async loadHistory(context: ExtensionContext): Promise<void> {\n    SearchCommandLine.history = new SearchHistory(context);\n    SearchCommandLine.history\n      .get()\n      .forEach((val) =>\n        SearchCommandLine.previousSearchStates.push(\n          new SearchState(SearchDirection.Forward, new Position(0, 0), val, undefined),\n        ),\n      );\n  }\n\n  public static async addSearchStateToHistory(searchState: SearchState) {\n    const prevSearchString = SearchCommandLine.previousSearchStates.at(-1)?.searchString;\n    // Store this search if different than previous\n    if (searchState.searchString !== prevSearchString) {\n      SearchCommandLine.previousSearchStates.push(searchState);\n      if (SearchCommandLine.history !== undefined) {\n        await SearchCommandLine.history.add(searchState.searchString);\n      }\n    }\n\n    // Make sure search history does not exceed configuration option\n    if (SearchCommandLine.previousSearchStates.length > configuration.history) {\n      SearchCommandLine.previousSearchStates.splice(0, 1);\n    }\n  }\n\n  /**\n   * Keeps the state of the current match, i.e. the match to which the cursor moves when the search is executed.\n   * Incremented / decremented by \\<C-g> or \\<C-t> in SearchInProgress mode.\n   * Resets to 0 if the search string becomes empty.\n   *\n   * @see {@link getCurrentMatchRelativeIndex}\n   */\n  private currentMatchDisplacement: number = 0;\n  private searchState: SearchState;\n\n  constructor(vimState: VimState, searchString: string, direction: SearchDirection) {\n    super(searchString, vimState.currentMode);\n    this.searchState = new SearchState(direction, vimState.cursorStopPosition, searchString);\n  }\n\n  public display(cursorChar: string): string {\n    return escapeCSSIcons(\n      `${this.searchState.direction === SearchDirection.Forward ? '/' : '?'}${this.text.substring(\n        0,\n        this.cursorIndex,\n      )}${cursorChar}${this.text.substring(this.cursorIndex)}`,\n    );\n  }\n\n  public get text(): string {\n    return this.searchState.searchString;\n  }\n  public set text(text: string) {\n    this.searchState.searchString = text;\n    if (text === '') {\n      this.currentMatchDisplacement = 0;\n    }\n  }\n\n  public getSearchState(): SearchState {\n    return this.searchState;\n  }\n\n  public getHistory(): HistoryFile {\n    return SearchCommandLine.history;\n  }\n\n  /**\n   * @returns the index of the current match, relative to the next match.\n   */\n  private getCurrentMatchRelativeIndex(vimState: VimState): number {\n    const count = vimState.recordedState.count || 1;\n    return count - 1 + this.currentMatchDisplacement * count;\n  }\n\n  /**\n   * @returns The start of the current match range (after applying the search offset) and its rank in the document's matches\n   */\n  public getCurrentMatchPosition(vimState: VimState): IndexedPosition | undefined {\n    return this.searchState.getNextSearchMatchPosition(\n      vimState,\n      vimState.cursorStopPosition,\n      SearchDirection.Forward,\n      this.getCurrentMatchRelativeIndex(vimState),\n    );\n  }\n\n  /**\n   * @returns The current match range and its rank in the document's matches\n   *\n   * NOTE: This method does not take the search offset into account\n   */\n  public getCurrentMatchRange(vimState: VimState): IndexedRange | undefined {\n    return this.searchState.getNextSearchMatchRange(\n      vimState,\n      vimState.cursorStopPosition,\n      SearchDirection.Forward,\n      this.getCurrentMatchRelativeIndex(vimState),\n    );\n  }\n\n  public getDecorations(vimState: VimState): SearchDecorations | undefined {\n    return getDecorationsForSearchMatchRanges(\n      this.searchState.getMatchRanges(vimState),\n      vimState.document,\n      configuration.incsearch && vimState.currentMode === Mode.SearchInProgressMode\n        ? this.getCurrentMatchRange(vimState)?.index\n        : undefined,\n    );\n  }\n\n  public async run(vimState: VimState): Promise<void> {\n    // Repeat the previous search if no new string is entered\n    if (this.text === '') {\n      const prevSearchString = SearchCommandLine.previousSearchStates.at(-1)?.searchString;\n      if (prevSearchString !== undefined) {\n        this.text = prevSearchString;\n      }\n    }\n    Logger.info(`Searching for ${this.text}`);\n\n    this.cursorIndex = 0;\n    Register.setReadonlyRegister('/', this.text);\n    void SearchCommandLine.addSearchStateToHistory(this.searchState);\n    globalState.hl = true;\n\n    if (this.searchState.getMatchRanges(vimState).length === 0) {\n      StatusBar.displayError(vimState, VimError.PatternNotFound(this.text));\n      return;\n    }\n\n    const currentMatch = this.getCurrentMatchPosition(vimState);\n\n    if (currentMatch === undefined) {\n      StatusBar.displayError(\n        vimState,\n        this.searchState.direction === SearchDirection.Backward\n          ? VimError.SearchHitTop(this.text)\n          : VimError.SearchHitBottom(this.text),\n      );\n      return;\n    }\n\n    vimState.cursorStopPosition = currentMatch.pos;\n\n    reportSearch(currentMatch.index, this.searchState.getMatchRanges(vimState).length, vimState);\n  }\n\n  public async escape(vimState: VimState): Promise<void> {\n    vimState.cursorStopPosition = this.searchState.cursorStartPosition;\n\n    globalState.searchState = SearchCommandLine.previousSearchStates.at(-1);\n\n    if (vimState.modeData.mode === Mode.SearchInProgressMode) {\n      const offset =\n        vimState.editor.visibleRanges[0].start.line -\n        vimState.modeData.firstVisibleLineBeforeSearch;\n      scrollView(vimState, offset);\n    }\n\n    await vimState.setCurrentMode(this.previousMode);\n    if (this.text.length > 0) {\n      void SearchCommandLine.addSearchStateToHistory(this.searchState);\n    }\n  }\n\n  public async ctrlF(vimState: VimState): Promise<void> {\n    await SearchCommandLine.onSearch(vimState, this.searchState.direction);\n  }\n\n  /**\n   * Called when <C-g> or <C-t> is pressed during SearchInProgress mode\n   */\n  public advanceCurrentMatch(vimState: VimState, direction: SearchDirection): void {\n    // <C-g> always moves forward in the document, and <C-t> always moves back, regardless of search direction.\n    // To compensate, multiply the desired direction by the searchState's direction, so that\n    // effectiveDirection == direction * (searchState.direction)^2 == direction.\n    this.currentMatchDisplacement += this.searchState.direction * direction;\n\n    // With nowrapscan, <C-g>/<C-t> shouldn't do anything if it would mean advancing past the last reachable match in the buffer.\n    // We account for this by checking whether getCurrentMatchRange returns undefined once this.currentMatchDisplacement is advanced.\n    // If it does, we undo the change to this.currentMatchDisplacement before exiting, making this command a noop.\n    if (!configuration.wrapscan && !this.getCurrentMatchRange(vimState)) {\n      this.currentMatchDisplacement -= this.searchState.direction * direction;\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/ascii.ts",
    "content": "import { CommandUnicodeName } from '../../actions/commands/actions';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class AsciiCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    await new CommandUnicodeName().exec(vimState.cursorStopPosition, vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/bang.ts",
    "content": "import { all, Parser } from 'parsimmon';\nimport { PositionDiff } from '../../common/motion/position';\nimport { VimState } from '../../state/vimState';\nimport { externalCommand } from '../../util/externalCommand';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\n\nexport interface IBangCommandArguments {\n  command: string;\n}\n\nexport class BangCommand extends ExCommand {\n  public static readonly argParser: Parser<BangCommand> = all.map(\n    (command) =>\n      new BangCommand({\n        command,\n      }),\n  );\n\n  protected _arguments: IBangCommandArguments;\n  constructor(args: IBangCommandArguments) {\n    super();\n    this._arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  private getReplaceDiff(text: string): PositionDiff {\n    const lines = text.split('\\n');\n    const numNewlines = lines.length - 1;\n    const check = lines[0].match(/^\\s*/);\n    const numWhitespace = check ? check[0].length : 0;\n\n    return PositionDiff.exactCharacter({\n      lineOffset: -numNewlines,\n      character: numWhitespace,\n    });\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await externalCommand.run(this._arguments.command);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const resolvedRange = range.resolveToRange(vimState);\n\n    // pipe in stdin from lines in range\n    const input = vimState.document.getText(resolvedRange);\n    const output = await externalCommand.run(this._arguments.command, input);\n\n    // place cursor at the start of the replaced text and first non-whitespace character\n    const diff = this.getReplaceDiff(output);\n\n    vimState.recordedState.transformer.replace(resolvedRange, output, diff);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/breakpoints.ts",
    "content": "import {\n  all,\n  alt,\n  eof,\n  optWhitespace,\n  regexp,\n  seqObj,\n  // eslint-disable-next-line id-denylist\n  string,\n  succeed,\n  whitespace,\n} from 'parsimmon';\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { fileNameParser, numberParser } from '../../vimscript/parserUtils';\n\nfunction isSourceBreakpoint(b: vscode.Breakpoint): b is vscode.SourceBreakpoint {\n  return (b as vscode.SourceBreakpoint).location !== undefined;\n}\nfunction isFunctionBreakpoint(b: vscode.Breakpoint): b is vscode.FunctionBreakpoint {\n  return (b as vscode.FunctionBreakpoint).functionName !== undefined;\n}\n\n/**\n * Add Breakpoint Command\n */\ntype AddBreakpointHere = { type: 'here' };\ntype AddBreakpointFile = { type: 'file'; line: number; file: string };\ntype AddBreakpointFunction = { type: 'func'; function: string };\ntype AddBreakpointExpr = { type: 'expr'; expr: string };\ntype AddBreakpoint =\n  | AddBreakpointHere\n  | AddBreakpointFile\n  | AddBreakpointFunction\n  | AddBreakpointExpr;\n\nclass AddBreakpointCommand extends ExCommand {\n  private readonly addBreakpoint: AddBreakpoint;\n\n  constructor(addBreakpoint: AddBreakpoint) {\n    super();\n    this.addBreakpoint = addBreakpoint;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (this.addBreakpoint.type === 'here') {\n      const location = new vscode.Location(vimState.document.uri, vimState.cursorStartPosition);\n      return vscode.debug.addBreakpoints([new vscode.SourceBreakpoint(location)]);\n    } else if (this.addBreakpoint.type === 'file') {\n      let file: vscode.Uri;\n      if (this.addBreakpoint.file === '') {\n        file = vimState.document.uri;\n      } else {\n        const workspaceFolder =\n          vscode.workspace.getWorkspaceFolder(vimState.document.uri)?.uri ?? vscode.Uri.file('./');\n        file = vscode.Uri.joinPath(workspaceFolder, this.addBreakpoint.file);\n      }\n      const location = new vscode.Location(\n        file,\n        new vscode.Position(this.addBreakpoint.line - 1, 0),\n      );\n      return vscode.debug.addBreakpoints([new vscode.SourceBreakpoint(location)]);\n    } else if (this.addBreakpoint.type === 'func') {\n      return vscode.debug.addBreakpoints([\n        new vscode.FunctionBreakpoint(this.addBreakpoint.function),\n      ]);\n    } else if (this.addBreakpoint.type === 'expr') {\n      const location = new vscode.Location(vimState.document.uri, vimState.cursorStartPosition);\n      return vscode.debug.addBreakpoints([\n        new vscode.SourceBreakpoint(location, undefined, this.addBreakpoint.expr),\n      ]);\n    }\n  }\n}\n\n/**\n * Delete Breakpoint Command\n */\ntype DelBreakpointById = { type: 'byId'; id: number };\ntype DelAllBreakpoints = { type: 'all' };\ntype DelBreakpointFunction = { type: 'func'; function: string };\ntype DelBreakpointFile = { type: 'file'; line: number; file: string };\ntype DelBreakpointHere = { type: 'here' };\ntype DelBreakpoint =\n  | DelBreakpointById\n  | DelAllBreakpoints\n  | DelBreakpointFunction\n  | DelBreakpointFile\n  | DelBreakpointHere;\n\nclass DeleteBreakpointCommand extends ExCommand {\n  private readonly delBreakpoint: DelBreakpoint;\n\n  constructor(delBreakpoint: DelBreakpoint) {\n    super();\n    this.delBreakpoint = delBreakpoint;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (this.delBreakpoint.type === 'byId') {\n      return vscode.debug.removeBreakpoints(\n        vscode.debug.breakpoints.slice(this.delBreakpoint.id - 1, 1),\n      );\n    } else if (this.delBreakpoint.type === 'all') {\n      return vscode.debug.removeBreakpoints(vscode.debug.breakpoints);\n    } else if (this.delBreakpoint.type === 'file') {\n      let reqUri: vscode.Uri;\n      if (this.delBreakpoint.file === '') {\n        reqUri = vimState.document.uri;\n      } else {\n        const workspaceFolder =\n          vscode.workspace.getWorkspaceFolder(vimState.document.uri)?.uri ?? vscode.Uri.file('./');\n        reqUri = vscode.Uri.joinPath(workspaceFolder, this.delBreakpoint.file);\n      }\n      const reqLine = this.delBreakpoint.line - 1;\n      const breakpoint = vscode.debug.breakpoints\n        .filter(isSourceBreakpoint)\n        .find(\n          (b) =>\n            b.location.uri.toString() === reqUri.toString() &&\n            b.location.range.start.line === reqLine,\n        );\n      if (breakpoint) return vscode.debug.removeBreakpoints([breakpoint]);\n    } else if (this.delBreakpoint.type === 'func') {\n      const functionName = this.delBreakpoint.function;\n      const breakpoint = vscode.debug.breakpoints\n        .filter(isFunctionBreakpoint)\n        .filter((b) => b.functionName === functionName);\n      if (breakpoint) return vscode.debug.removeBreakpoints(breakpoint);\n    } else if (this.delBreakpoint.type === 'here') {\n      const location = new vscode.Location(vimState.document.uri, vimState.cursorStartPosition);\n      const distFromLocationCharacter = (b: vscode.SourceBreakpoint) =>\n        Math.abs(b.location.range.start.character - location.range.start.character);\n\n      const breakpoint = vscode.debug.breakpoints\n        .filter(isSourceBreakpoint)\n        .filter(\n          (b) =>\n            b.location.uri.toString() === location.uri.toString() &&\n            b.location.range.start.line === location.range.start.line,\n        )\n        .sort((a, b) => distFromLocationCharacter(a) - distFromLocationCharacter(b))[0];\n      if (breakpoint) return vscode.debug.removeBreakpoints([breakpoint]);\n    }\n  }\n}\n\n/**\n * List Breakpoints Command\n */\nclass ListBreakpointsCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    const breakpoints = vscode.debug.breakpoints;\n    type BreakpointQuickPick = { breakpointId: string } & vscode.QuickPickItem;\n    const lines = breakpoints.map((b, i): BreakpointQuickPick => {\n      const { id, enabled, condition } = b;\n      let label = '';\n      label += `#${i + 1}\\t`;\n      label += enabled ? '$(circle-filled)\\t' : '$(circle-outline)\\t';\n      label += condition ? '$(debug-breakpoint-conditional)\\t' : '\\t';\n      if (isSourceBreakpoint(b))\n        label += `${path.basename(b.location.uri.fsPath)}:${b.location.range.start.line + 1}`;\n      if (isFunctionBreakpoint(b)) label += `$(debug-breakpoint-function)${b.functionName}`;\n      return {\n        label,\n        breakpointId: id,\n      };\n    });\n    await vscode.window.showQuickPick(lines).then(async (selected) => {\n      if (selected) {\n        const id = selected.breakpointId;\n        const breakpoint = breakpoints.find((b) => b.id === id);\n        if (breakpoint && isSourceBreakpoint(breakpoint)) {\n          const pos = breakpoint.location.range.start;\n          await vscode.window.showTextDocument(breakpoint.location.uri, {\n            selection: new vscode.Range(pos, pos),\n          });\n        }\n      }\n    });\n  }\n}\n\nexport class Breakpoints {\n  public static readonly argParsers = {\n    add: whitespace\n      .then(\n        alt(\n          // here\n          seqObj<AddBreakpointHere>(['type', string('here')], optWhitespace),\n          // file\n          seqObj<AddBreakpointFile>(\n            ['type', string('file')],\n            ['line', optWhitespace.then(numberParser).fallback(1)],\n            ['file', optWhitespace.then(fileNameParser).fallback('')],\n          ),\n          // func\n          seqObj<AddBreakpointFunction>(\n            ['type', string('func')],\n            optWhitespace.then(numberParser).fallback(1), // we don't support line numbers in function names, but Vim does, so we'll allow it.\n            ['function', optWhitespace.then(regexp(/\\S+/))],\n          ),\n          // expr\n          seqObj<AddBreakpointExpr>(['type', string('expr')], ['expr', optWhitespace.then(all)]),\n        ),\n      )\n      .or(\n        // without arg\n        eof.result<DelBreakpointHere>({ type: 'here' }),\n      )\n      .map((a: AddBreakpoint) => new AddBreakpointCommand(a)),\n\n    del: whitespace\n      .then(\n        alt(\n          // here\n          seqObj<DelBreakpointHere>(['type', string('here')], optWhitespace),\n          // file\n          seqObj<DelBreakpointFile>(\n            ['type', string('file')],\n            ['line', optWhitespace.then(numberParser).fallback(1)],\n            ['file', optWhitespace.then(fileNameParser).fallback('')],\n          ),\n          // func\n          seqObj<DelBreakpointFunction>(\n            ['type', string('func')],\n            optWhitespace.then(numberParser).fallback(1), // we don't support line numbers in function names, but Vim does, so we'll allow it.\n            ['function', optWhitespace.then(regexp(/\\S+/))],\n          ),\n          // all\n          string('*').then(optWhitespace).result<DelAllBreakpoints>({ type: 'all' }),\n          // by number\n          numberParser.map((n) => ({ type: 'byId', id: n })),\n        ),\n      )\n      .or(\n        // without arg\n        eof.result<DelBreakpointHere>({ type: 'here' }),\n      )\n      .map((a: DelBreakpoint) => new DeleteBreakpointCommand(a)),\n\n    list: succeed(new ListBreakpointsCommand()),\n  };\n}\n"
  },
  {
    "path": "src/cmd_line/commands/bufferDelete.ts",
    "content": "import { alt, optWhitespace, Parser, seq, whitespace } from 'parsimmon';\nimport * as vscode from 'vscode';\n\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { findTabInActiveTabGroup } from '../../util/util';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser, fileNameParser, numberParser } from '../../vimscript/parserUtils';\n\ninterface IBufferDeleteCommandArguments {\n  bang: boolean;\n  buffers: Array<string | number>;\n}\n\n//\n//  Implements :bd\n// http://vimdoc.sourceforge.net/htmldoc/windows.html#buffers\n//\nexport class BufferDeleteCommand extends ExCommand {\n  public static readonly argParser: Parser<BufferDeleteCommand> = seq(\n    bangParser.skip(optWhitespace),\n    alt<string | number>(numberParser, fileNameParser).sepBy(whitespace),\n  ).map(([bang, buffers]) => new BufferDeleteCommand({ bang, buffers }));\n\n  public readonly arguments: IBufferDeleteCommandArguments;\n  constructor(args: IBufferDeleteCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (vimState.document.isDirty && !this.arguments.bang) {\n      throw VimError.NoWriteSinceLastChange();\n    }\n\n    const activeBuffer =\n      vscode.window.tabGroups.activeTabGroup.tabs.findIndex((t) => t.isActive) + 1;\n    if (this.arguments.buffers.length === 0) {\n      this.arguments.buffers = [activeBuffer];\n    }\n\n    let deletedBuffers = 0;\n\n    for (let buffer of this.arguments.buffers) {\n      if (typeof buffer === 'string') {\n        const [idx, tab] = findTabInActiveTabGroup(buffer);\n        buffer = idx + 1;\n      }\n\n      if (buffer < 1) {\n        throw VimError.PositiveCountRequired();\n      }\n      if (buffer > vscode.window.tabGroups.activeTabGroup.tabs.length) {\n        continue;\n      }\n\n      if (buffer !== activeBuffer) {\n        try {\n          await vscode.commands.executeCommand('workbench.action.openEditorAtIndex', buffer - 1);\n        } catch (e) {\n          continue;\n        }\n      }\n\n      if (this.arguments.bang) {\n        await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');\n      } else {\n        await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n      }\n      ++deletedBuffers;\n    }\n\n    if (deletedBuffers === 0) {\n      throw VimError.NoBuffersDeleted();\n    }\n  }\n\n  // TODO: executeWithRange\n}\n"
  },
  {
    "path": "src/cmd_line/commands/change.ts",
    "content": "import * as vscode from 'vscode';\n// eslint-disable-next-line id-denylist\nimport { Parser, alt, any, optWhitespace, seq, whitespace } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { Mode } from '../../mode/mode';\nimport { Register, RegisterMode } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\nexport interface IChangeCommandArguments {\n  register?: string;\n  count?: number;\n}\n\nexport class ChangeCommand extends ExCommand {\n  public static readonly argParser: Parser<ChangeCommand> = optWhitespace.then(\n    alt(\n      numberParser.map((count) => {\n        return { register: undefined, count };\n      }),\n      // eslint-disable-next-line id-denylist\n      seq(any.fallback(undefined), whitespace.then(numberParser).fallback(undefined)).map(\n        ([register, count]) => {\n          return { register, count };\n        },\n      ),\n    ).map(\n      ({ register, count }) =>\n        new ChangeCommand({\n          register,\n          count,\n        }),\n    ),\n  );\n\n  private readonly arguments: IChangeCommandArguments;\n  constructor(args: IChangeCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  /**\n   * Deletes text between `startLine` and `endLine`, inclusive.\n   * Puts the cursor at the start of the line where the deleted range was\n   * Then enters insert mode\n   */\n  private changeRange(startLine: number, endLine: number, vimState: VimState): void {\n    const start = new Position(startLine, 0);\n    const end = new Position(endLine + 1, 0);\n\n    const range = new vscode.Range(start, end);\n    const text = vimState.document.getText(range);\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'replaceText',\n      text: '\\n',\n      range,\n      manuallySetCursorPositions: true,\n    });\n    vimState.cursorStopPosition = start;\n\n    if (this.arguments.register) {\n      vimState.recordedState.registerName = this.arguments.register;\n    }\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    Register.put(vimState, text, 0, true);\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const linesToRemove = this.arguments.count ?? 1;\n    // :c[hange][cnt] changes [cnt] lines\n    const startLine = vimState.cursorStartPosition.line;\n    const endLine = startLine + (linesToRemove - 1);\n    this.changeRange(startLine, endLine, vimState);\n\n    // Enter insert mode\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    /**\n     * If a [cnt] and [range] is specified (e.g. :.+2c3), :change\n     * the end of the [range].\n     * Ex. if two lines are VisualLine hightlighted, :<,>c3 will :c3\n     * from the end of the selected lines\n     */\n    const { start, end } = range.resolve(vimState);\n    if (this.arguments.count) {\n      vimState.cursorStartPosition = new Position(end, 0);\n      await this.execute(vimState);\n      return;\n    }\n    this.changeRange(start, end, vimState);\n    // Enter insert mode\n    await vimState.setCurrentMode(Mode.Insert);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/close.ts",
    "content": "import { Parser } from 'parsimmon';\nimport * as vscode from 'vscode';\n\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser } from '../../vimscript/parserUtils';\n\n//\n//  Implements :close\n//  http://vimdoc.sourceforge.net/htmldoc/windows.html#:close\n//\nexport class CloseCommand extends ExCommand {\n  public static readonly argParser: Parser<CloseCommand> = bangParser.map(\n    (bang) => new CloseCommand(bang),\n  );\n\n  public readonly bang: boolean;\n  constructor(bang: boolean) {\n    super();\n    this.bang = bang;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (vimState.document.isDirty && !this.bang) {\n      throw VimError.NoWriteSinceLastChange();\n    }\n\n    if (vscode.window.visibleTextEditors.length === 1) {\n      throw VimError.CannotCloseLastWindow();\n    }\n\n    const oldViewColumn = vimState.editor.viewColumn;\n    await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n\n    if (\n      vscode.window.activeTextEditor !== undefined &&\n      vscode.window.activeTextEditor.viewColumn === oldViewColumn\n    ) {\n      await vscode.commands.executeCommand('workbench.action.previousEditor');\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/copy.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { Position, Range } from 'vscode';\nimport { PositionDiff } from '../../common/motion/position';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { Address, LineRange } from '../../vimscript/lineRange';\n\nexport class CopyCommand extends ExCommand {\n  public static readonly argParser: Parser<CopyCommand> = optWhitespace\n    .then(Address.parser.fallback(undefined))\n    .map((address) => new CopyCommand(address));\n\n  private address?: Address;\n  constructor(address?: Address) {\n    super();\n    this.address = address;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  private copyLines(vimState: VimState, sourceStart: number, sourceEnd: number) {\n    const dest = this.address?.resolve(vimState, 'left', false);\n    if (dest === undefined || dest < -1 || dest > vimState.document.lineCount) {\n      StatusBar.displayError(vimState, VimError.InvalidAddress());\n      return;\n    }\n\n    if (sourceEnd < sourceStart) {\n      [sourceStart, sourceEnd] = [sourceEnd, sourceStart];\n    }\n\n    const copiedText = vimState.document.getText(\n      new Range(new Position(sourceStart, 0), new Position(sourceEnd, 0).getLineEnd()),\n    );\n\n    let text: string;\n    let position: Position;\n    if (dest === -1) {\n      text = copiedText + '\\n';\n      position = new Position(0, 0);\n    } else {\n      text = '\\n' + copiedText;\n      position = new Position(dest, 0).getLineEnd();\n    }\n\n    const lines = copiedText.split('\\n');\n    const cursorPosition = new Position(\n      Math.max(dest + lines.length, 0),\n      lines[lines.length - 1].match(/\\S/)?.index ?? 0,\n    );\n\n    vimState.recordedState.transformer.insert(\n      position,\n      text,\n      PositionDiff.exactPosition(cursorPosition),\n    );\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    const line = vimState.cursor.stop.line;\n    this.copyLines(vimState, line, line);\n  }\n\n  public override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n    this.copyLines(vimState, start, end);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/delete.ts",
    "content": "import * as vscode from 'vscode';\n\n// eslint-disable-next-line id-denylist\nimport { Parser, alt, any, optWhitespace, seq, whitespace } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { Register, RegisterMode } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\nexport interface IDeleteCommandArguments {\n  register?: string;\n  count?: number;\n}\n\nexport class DeleteCommand extends ExCommand {\n  // TODO: this is copy-pasted from `:y[ank]`\n  public static readonly argParser: Parser<DeleteCommand> = optWhitespace.then(\n    alt(\n      numberParser.map((count) => {\n        return { register: undefined, count };\n      }),\n      // eslint-disable-next-line id-denylist\n      seq(any.fallback(undefined), whitespace.then(numberParser).fallback(undefined)).map(\n        ([register, count]) => {\n          return { register, count };\n        },\n      ),\n    ).map(\n      ({ register, count }) =>\n        new DeleteCommand({\n          register,\n          count,\n        }),\n    ),\n  );\n\n  private readonly arguments: IDeleteCommandArguments;\n  constructor(args: IDeleteCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  /**\n   * Deletes text between `startLine` and `endLine`, inclusive.\n   * Puts the cursor at the start of the line where the deleted range was.\n   */\n  private deleteRange(startLine: number, endLine: number, vimState: VimState): void {\n    let start = new Position(startLine, 0);\n    let end = new Position(endLine, 0).getLineEnd();\n\n    if (endLine < vimState.document.lineCount - 1) {\n      end = end.getRightThroughLineBreaks();\n    } else if (startLine > 0) {\n      start = start.getLeftThroughLineBreaks();\n    }\n\n    const range = new vscode.Range(start, end);\n    const text = vimState.document\n      .getText(range)\n      // Remove leading or trailing newline\n      .replace(/^\\r?\\n/, '')\n      .replace(/\\r?\\n$/, '');\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'deleteRange',\n      range,\n      manuallySetCursorPositions: true,\n    });\n    vimState.cursorStopPosition = start.getLineBegin();\n\n    if (this.arguments.register) {\n      vimState.recordedState.registerName = this.arguments.register;\n    }\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    Register.put(vimState, text, 0, true);\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const linesToRemove = this.arguments.count ?? 1;\n    // :d[elete][cnt] removes [cnt] lines\n    const startLine = vimState.cursorStartPosition.line;\n    const endLine = startLine + (linesToRemove - 1);\n    this.deleteRange(startLine, endLine, vimState);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    /**\n     * If a [cnt] and [range] is specified (e.g. :.+2d3), :delete [cnt] is called from\n     * the end of the [range].\n     * Ex. if two lines are VisualLine highlighted, :<,>d3 will :d3\n     * from the end of the selected lines.\n     */\n    const { start, end } = range.resolve(vimState);\n    if (this.arguments.count) {\n      vimState.cursorStartPosition = new Position(end, 0);\n      await this.execute(vimState);\n      return;\n    }\n    this.deleteRange(start, end, vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/digraph.ts",
    "content": "import * as vscode from 'vscode';\n\n// eslint-disable-next-line id-denylist\nimport { any, Parser, seq, whitespace } from 'parsimmon';\nimport { DefaultDigraphs } from '../../actions/commands/digraphs';\nimport { Digraph } from '../../configuration/iconfiguration';\nimport { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser, numberParser } from '../../vimscript/parserUtils';\nimport { configuration } from './../../configuration/configuration';\n\nexport interface IDigraphsCommandArguments {\n  bang: boolean;\n  newDigraph: [string, string, number[]] | undefined;\n}\n\ninterface DigraphQuickPickItem extends vscode.QuickPickItem {\n  charCodes: number[];\n}\n\nexport class DigraphsCommand extends ExCommand {\n  public static readonly argParser: Parser<DigraphsCommand> = seq(\n    bangParser,\n    whitespace.then(seq(any, any, whitespace.then(numberParser).atLeast(1))).fallback(undefined),\n  ).map(([bang, newDigraph]) => new DigraphsCommand({ bang, newDigraph }));\n\n  private readonly arguments: IDigraphsCommandArguments;\n  constructor(args: IDigraphsCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  private makeQuickPicks(digraphs: Array<[string, Digraph]>): DigraphQuickPickItem[] {\n    return digraphs.map(([shortcut, [charDesc, charCodes]]) => {\n      if (!Array.isArray(charCodes)) {\n        charCodes = [charCodes];\n      }\n      return {\n        label: shortcut,\n        description: `${charDesc} (user)`,\n        charCodes,\n      };\n    });\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (this.arguments.newDigraph) {\n      const digraph = this.arguments.newDigraph[0] + this.arguments.newDigraph[1];\n      const charCodes = this.arguments.newDigraph[2];\n      DefaultDigraphs.set(digraph, [String.fromCharCode(...charCodes), charCodes]);\n    } else {\n      const digraphKeyAndContent = this.makeQuickPicks(\n        Object.entries(configuration.digraphs),\n      ).concat(this.makeQuickPicks([...DefaultDigraphs.entries()]));\n\n      void vscode.window.showQuickPick(digraphKeyAndContent).then(async (val) => {\n        if (val) {\n          const char = String.fromCharCode(...val.charCodes);\n          await TextEditor.insert(vimState.editor, char);\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/echo.ts",
    "content": "import { all, optWhitespace, Parser, seq } from 'parsimmon';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { displayValue } from '../../vimscript/expression/displayValue';\nimport { EvaluationContext } from '../../vimscript/expression/evaluate';\nimport { expressionParser } from '../../vimscript/expression/parser';\nimport { Expression } from '../../vimscript/expression/types';\n\nexport class EchoCommand extends ExCommand {\n  public static argParser(echoArgs: { sep: string; error: boolean }): Parser<EchoCommand> {\n    return optWhitespace\n      .then(seq(expressionParser.sepBy(optWhitespace), all))\n      .map(([expressions, trailing]) => {\n        trailing = trailing.trimStart();\n        if (trailing) {\n          throw VimError.InvalidExpression(trailing);\n        }\n        return new EchoCommand(echoArgs, expressions);\n      });\n  }\n\n  private sep: string;\n  private error: boolean;\n  private expressions: Expression[];\n  constructor(args: { sep: string; error: boolean }, expressions: Expression[]) {\n    super();\n    this.sep = args.sep;\n    this.error = args.error;\n    this.expressions = expressions;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    const ctx = new EvaluationContext(vimState);\n    const values = this.expressions.map((x) => ctx.evaluate(x));\n    const message = values.map((v) => displayValue(v)).join(this.sep);\n    StatusBar.setText(vimState, message, this.error);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/eval.ts",
    "content": "import { all, optWhitespace, Parser, seq } from 'parsimmon';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { EvaluationContext } from '../../vimscript/expression/evaluate';\nimport { expressionParser, functionCallParser } from '../../vimscript/expression/parser';\nimport { Expression } from '../../vimscript/expression/types';\n\nexport class EvalCommand extends ExCommand {\n  public static argParser: Parser<EvalCommand> = optWhitespace\n    .then(seq(expressionParser.fallback(undefined), all))\n    .map(([expression, trailing]) => {\n      trailing = trailing.trim();\n      if (expression === undefined) {\n        throw VimError.InvalidExpression(trailing);\n      }\n      if (trailing) {\n        throw VimError.TrailingCharacters(trailing);\n      }\n      return new EvalCommand(expression);\n    });\n\n  private expression: Expression;\n  constructor(expression: Expression) {\n    super();\n    this.expression = expression;\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    const ctx = new EvaluationContext(vimState);\n    ctx.evaluate(this.expression);\n  }\n}\n\nexport class CallCommand extends ExCommand {\n  public static argParser: Parser<CallCommand> = optWhitespace\n    .then(functionCallParser)\n    .map((call) => new CallCommand(call));\n\n  private expression: Expression;\n  private constructor(funcCall: Expression) {\n    super();\n    this.expression = funcCall;\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    const ctx = new EvaluationContext(vimState);\n    ctx.evaluate(this.expression);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/explore.ts",
    "content": "import { commands } from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class ExploreCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    await commands.executeCommand('workbench.view.explorer');\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/file.ts",
    "content": "import { optWhitespace, seq } from 'parsimmon';\nimport { doesFileExist } from 'platform/fs';\nimport * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { Logger } from '../../util/logger';\nimport { getPathDetails, resolveUri } from '../../util/path';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport {\n  bangParser,\n  FileCmd,\n  fileCmdParser,\n  fileNameParser,\n  FileOpt,\n  fileOptParser,\n} from '../../vimscript/parserUtils';\n// TODO:\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nimport untildify = require('untildify');\n\nexport enum FilePosition {\n  NewWindowVerticalSplit,\n  NewWindowHorizontalSplit,\n}\n\nexport type IFileCommandArguments =\n  | {\n      name: 'edit';\n      bang: boolean;\n      opt: FileOpt;\n      cmd?: FileCmd;\n      file?: string;\n      createFileIfNotExists?: boolean;\n    }\n  | {\n      name: 'enew';\n      bang: boolean;\n      createFileIfNotExists?: boolean;\n    }\n  | {\n      name: 'new' | 'vnew' | 'split' | 'vsplit';\n      opt: FileOpt;\n      cmd?: FileCmd;\n      file?: string;\n      createFileIfNotExists?: boolean;\n    };\n\n// TODO: This is a hack to get this to work in the short term with new arg parsing logic.\ntype LegacyArgs = {\n  file?: string;\n  bang?: boolean;\n  position?: FilePosition;\n  cmd?: FileCmd;\n  createFileIfNotExists?: boolean;\n};\nfunction getLegacyArgs(args: IFileCommandArguments): LegacyArgs {\n  if (args.name === 'edit') {\n    return { file: args.file, bang: args.bang, cmd: args.cmd, createFileIfNotExists: true };\n  } else if (args.name === 'enew') {\n    return { bang: args.bang, createFileIfNotExists: true };\n  } else if (args.name === 'new') {\n    return {\n      file: args.file,\n      position: FilePosition.NewWindowHorizontalSplit,\n      createFileIfNotExists: true,\n    };\n  } else if (args.name === 'vnew') {\n    return {\n      file: args.file,\n      position: FilePosition.NewWindowVerticalSplit,\n      createFileIfNotExists: true,\n    };\n  } else if (args.name === 'split') {\n    return {\n      file: args.file,\n      position: FilePosition.NewWindowHorizontalSplit,\n      // only to create if file arg is specified\n      createFileIfNotExists: args.file !== undefined,\n    };\n  } else if (args.name === 'vsplit') {\n    return {\n      file: args.file,\n      position: FilePosition.NewWindowVerticalSplit,\n      // only to create if file arg is specified\n      createFileIfNotExists: args.file !== undefined,\n    };\n  } else {\n    throw new Error(`Unexpected FileCommand.arguments.name: ${args.name}`);\n  }\n}\n\nexport class FileCommand extends ExCommand {\n  // TODO: There's a lot of duplication here\n  // TODO: These `optWhitespace` calls should be `whitespace`\n  public static readonly argParsers = {\n    edit: seq(\n      bangParser,\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([bang, opt, cmd, file]) => new FileCommand({ name: 'edit', bang, opt, cmd, file })),\n    enew: bangParser.map((bang) => new FileCommand({ name: 'enew', bang })),\n    new: seq(\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([opt, cmd, file]) => new FileCommand({ name: 'new', opt, cmd, file })),\n    split: seq(\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([opt, cmd, file]) => new FileCommand({ name: 'split', opt, cmd, file })),\n    vnew: seq(\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([opt, cmd, file]) => new FileCommand({ name: 'vnew', opt, cmd, file })),\n    vsplit: seq(\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([opt, cmd, file]) => new FileCommand({ name: 'vsplit', opt, cmd, file })),\n  };\n\n  private readonly arguments: IFileCommandArguments;\n\n  constructor(args: IFileCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const args = getLegacyArgs(this.arguments);\n\n    if (args.bang) {\n      await vscode.commands.executeCommand('workbench.action.files.revert');\n      return;\n    }\n\n    // Need to do this before the split since it loses the activeTextEditor\n    const editorFileUri = vscode.window.activeTextEditor!.document.uri;\n    const editorFilePath = editorFileUri.fsPath;\n\n    // Do the split if requested\n    let split = false;\n    if (args.position === FilePosition.NewWindowVerticalSplit) {\n      await vscode.commands.executeCommand('workbench.action.splitEditorRight');\n      split = true;\n    }\n    if (args.position === FilePosition.NewWindowHorizontalSplit) {\n      await vscode.commands.executeCommand('workbench.action.splitEditorDown');\n      split = true;\n    }\n\n    const hidePreviousEditor = async () => {\n      if (split === true) {\n        await vscode.commands.executeCommand('workbench.action.previousEditor');\n        await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n      }\n    };\n\n    // No file was specified\n    if (args.file === undefined) {\n      if (args.createFileIfNotExists === true) {\n        await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');\n        await hidePreviousEditor();\n      }\n      return;\n    }\n\n    // Only untidify when the currently open page and file completion is local\n    if (args.file && editorFileUri.scheme === 'file') {\n      args.file = untildify(args.file);\n    }\n\n    let fileUri = editorFileUri;\n    // Using the empty string will request to open a file\n    if (args.file === '') {\n      // No file on split is fine and just return\n      if (split === true) {\n        return;\n      }\n\n      const fileList = await vscode.window.showOpenDialog({});\n      if (fileList && fileList.length > 0) {\n        fileUri = fileList[0];\n      }\n    } else {\n      // remove file://\n      args.file = args.file.replace(/^file:\\/\\//, '');\n\n      // Using a filename, open or create the file\n      const isRemote = !!vscode.env.remoteName;\n      const { fullPath, path: p } = getPathDetails(args.file, editorFileUri, isRemote);\n      // Only if the expanded path of the full path is different than\n      // the currently opened window path\n      if (fullPath !== editorFilePath) {\n        const uriPath = resolveUri(fullPath, p.sep, editorFileUri, isRemote);\n        if (uriPath === null) {\n          // return if the path is invalid\n          return;\n        }\n\n        let fileExists = await doesFileExist(uriPath);\n        if (fileExists) {\n          // If the file without the added ext exists\n          fileUri = uriPath;\n        } else {\n          // if file does not exist\n          // try to find it with the same extension as the current file\n          const pathWithExt = fullPath + p.extname(editorFilePath);\n          const uriPathWithExt = resolveUri(pathWithExt, p.sep, editorFileUri, isRemote);\n          if (uriPathWithExt !== null) {\n            fileExists = await doesFileExist(uriPathWithExt);\n            if (fileExists) {\n              // if the file with the added ext exists\n              fileUri = uriPathWithExt;\n            }\n          }\n        }\n\n        // If both with and without ext path do not exist\n        if (!fileExists) {\n          if (args.createFileIfNotExists) {\n            // Change the scheme to untitled to open an\n            // untitled tab\n            fileUri = uriPath.with({ scheme: 'untitled' });\n          } else {\n            Logger.error(`${args.file} does not exist.`);\n            return;\n          }\n        }\n      }\n    }\n\n    const doc = await vscode.workspace.openTextDocument(fileUri);\n\n    const lineNumber =\n      args.cmd?.type === 'line_number'\n        ? args.cmd.line\n        : args.cmd?.type === 'last_line'\n          ? doc.lineCount - 1\n          : undefined;\n    const options: vscode.TextDocumentShowOptions = {};\n    if (lineNumber !== undefined && lineNumber >= 0) {\n      const pos = new Position(lineNumber, 0);\n      options.selection = new vscode.Range(pos, pos);\n    }\n    await vscode.window.showTextDocument(doc, options);\n\n    await hidePreviousEditor();\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/fileInfo.ts",
    "content": "import { all, optWhitespace, Parser, seq } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { reportFileInfo } from '../../util/statusBarTextUtils';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser } from '../../vimscript/parserUtils';\n\nexport class FileInfoCommand extends ExCommand {\n  public static readonly argParser: Parser<FileInfoCommand> = seq(\n    bangParser,\n    optWhitespace.then(all),\n  ).map(([bang, fileName]) => new FileInfoCommand({ bang, fileName }));\n\n  private args: {\n    bang: boolean;\n    fileName?: string;\n  };\n  private constructor(args: { bang: boolean; fileName?: string }) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO: Use `this.args`\n    reportFileInfo(vimState.cursor.start, vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/goto.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\nexport class GotoCommand extends ExCommand {\n  public static readonly argParser: Parser<GotoCommand> = optWhitespace\n    .then(numberParser.fallback(undefined))\n    .map((count) => new GotoCommand(count));\n\n  private offset?: number;\n  constructor(offset?: number) {\n    super();\n    this.offset = offset;\n  }\n\n  private gotoOffset(vimState: VimState, offset: number) {\n    vimState.cursorStopPosition = vimState.document.positionAt(offset);\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    this.gotoOffset(vimState, this.offset ?? 0);\n  }\n\n  public override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    if (this.offset === undefined) {\n      this.offset = range.resolve(vimState)?.end ?? 0;\n    }\n    this.gotoOffset(vimState, this.offset);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/gotoLine.ts",
    "content": "import { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\n\nexport class GotoLineCommand extends ExCommand {\n  public async execute(vimState: VimState): Promise<void> {\n    return;\n  }\n\n  public override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    vimState.cursorStartPosition = vimState.cursorStopPosition = vimState.cursorStopPosition\n      .with({ line: range.resolve(vimState).end })\n      .obeyStartOfLine(vimState.document);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/grep.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { optWhitespace, Parser, seq, whitespace } from 'parsimmon';\nimport { VimError } from '../../error';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { fileNameParser } from '../../vimscript/parserUtils';\nimport { Pattern, SearchDirection } from '../../vimscript/pattern';\n\n// Still missing:\n// When a number is put before the command this is used\n// as the maximum number of matches to find.  Use\n// \":1vimgrep pattern file\" to find only the first.\n// Useful if you only want to check if there is a match\n// and quit quickly when it's found.\n\n// Without the 'j' flag Vim jumps to the first match.\n// With 'j' only the quickfix list is updated.\n// With the [!] any changes in the current buffer are\n// abandoned.\ninterface IGrepCommandArguments {\n  pattern: Pattern;\n  files: string[];\n}\n\n// Implements :grep\n// https://vimdoc.sourceforge.net/htmldoc/quickfix.html#:vimgrep\nexport class GrepCommand extends ExCommand {\n  // TODO: parse the pattern for flags to notify the user that they are not supported yet\n  public static readonly argParser: Parser<GrepCommand> = optWhitespace.then(\n    seq(\n      Pattern.parser({ direction: SearchDirection.Backward, delimiter: ' ' }),\n      fileNameParser.sepBy(whitespace),\n    ).map(([pattern, files]) => new GrepCommand({ pattern, files })),\n  );\n\n  public readonly arguments: IGrepCommandArguments;\n  constructor(args: IGrepCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(): Promise<void> {\n    const { pattern, files } = this.arguments;\n    if (files.length === 0) {\n      throw VimError.NoFileName();\n    }\n    // There are other arguments that can be passed, but probably need to dig into the VSCode source code, since they are not listed in the API reference\n    // https://code.visualstudio.com/api/references/commands\n    // This link on the other hand has the commands and I used this as a reference\n    // https://stackoverflow.com/questions/62251045/search-find-in-files-keybinding-can-take-arguments-workbench-view-search-can\n    await vscode.commands.executeCommand('workbench.action.findInFiles', {\n      query: pattern.patternString,\n      filesToInclude: files.join(','),\n      triggerSearch: true,\n      isRegex: true,\n    });\n    await vscode.commands.executeCommand('search.action.focusSearchList');\n    // TODO: Only if there's no [j] flag\n    await vscode.commands.executeCommand('search.action.focusNextSearchResult');\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/history.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { Parser, alt, optWhitespace, string } from 'parsimmon';\nimport { ShowCommandHistory, ShowSearchHistory } from '../../actions/commands/actions';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { nameAbbrevParser } from '../../vimscript/parserUtils';\nimport { SearchDirection } from '../../vimscript/pattern';\n\nexport enum HistoryCommandType {\n  Cmd,\n  Search,\n  Expr,\n  Input,\n  Debug,\n  All,\n}\n\nconst historyTypeParser: Parser<HistoryCommandType> = alt(\n  alt(nameAbbrevParser('c', 'md'), string(':')).result(HistoryCommandType.Cmd),\n  alt(nameAbbrevParser('s', 'earch'), string('/')).result(HistoryCommandType.Search),\n  alt(nameAbbrevParser('e', 'xpr'), string('=')).result(HistoryCommandType.Expr),\n  alt(nameAbbrevParser('i', 'nput'), string('@')).result(HistoryCommandType.Input),\n  alt(nameAbbrevParser('d', 'ebug'), string('>')).result(HistoryCommandType.Debug),\n  nameAbbrevParser('a', 'll').result(HistoryCommandType.All),\n);\n\nexport interface IHistoryCommandArguments {\n  type: HistoryCommandType;\n  // TODO: :history can also accept a range\n}\n\n// http://vimdoc.sourceforge.net/htmldoc/cmdline.html#:history\nexport class HistoryCommand extends ExCommand {\n  public static readonly argParser: Parser<HistoryCommand> = optWhitespace\n    .then(historyTypeParser.fallback(HistoryCommandType.Cmd))\n    .map((type) => new HistoryCommand({ type }));\n\n  private readonly arguments: IHistoryCommandArguments;\n  constructor(args: IHistoryCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    switch (this.arguments.type) {\n      case HistoryCommandType.Cmd:\n        await new ShowCommandHistory().exec(vimState.cursorStopPosition, vimState);\n        break;\n      case HistoryCommandType.Search:\n        await new ShowSearchHistory(SearchDirection.Forward).exec(\n          vimState.cursorStopPosition,\n          vimState,\n        );\n        break;\n      // TODO: Implement these\n      case HistoryCommandType.Expr:\n        throw new Error('Not implemented');\n      case HistoryCommandType.Input:\n        throw new Error('Not implemented');\n      case HistoryCommandType.Debug:\n        throw new Error('Not implemented');\n      case HistoryCommandType.All:\n        throw new Error('Not implemented');\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/jumps.ts",
    "content": "import { QuickPickItem, Range, window } from 'vscode';\n\nimport { Jump } from '../../jumps/jump';\nimport { globalState } from '../../state/globalState';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nclass JumpPickItem implements QuickPickItem {\n  jump: Jump;\n\n  label: string;\n  description?: string;\n  detail?: string;\n  picked?: boolean;\n  alwaysShow?: boolean;\n\n  constructor(jump: Jump, idx: number) {\n    this.jump = jump;\n    this.label = jump.fileName;\n    this.detail = `jump ${idx} line ${jump.position.line + 1} col ${jump.position.character}`;\n    try {\n      this.description = jump.document.lineAt(jump.position).text;\n    } catch (e) {\n      this.description = undefined;\n    }\n  }\n}\n\nexport class JumpsCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    const jumpTracker = globalState.jumpTracker;\n    if (jumpTracker.hasJumps) {\n      const quickPickItems = jumpTracker.jumps.map((jump, idx) => new JumpPickItem(jump, idx));\n      const item = await window.showQuickPick(quickPickItems, {\n        canPickMany: false,\n      });\n      if (item && item.jump.document !== undefined) {\n        void window.showTextDocument(item.jump.document, {\n          selection: new Range(item.jump.position, item.jump.position),\n        });\n      }\n    } else {\n      void window.showInformationMessage('No jumps available');\n    }\n  }\n}\n\nexport class ClearJumpsCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    const jumpTracker = globalState.jumpTracker;\n    jumpTracker.clearJumps();\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/leftRightCenter.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { Range, TextLine } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { Address, LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\ntype LeftArgs = {\n  indent: number;\n};\n\nexport class LeftCommand extends ExCommand {\n  public static readonly argParser: Parser<LeftCommand> = optWhitespace\n    .then(numberParser.fallback(0))\n    .map((indent) => new LeftCommand({ indent }));\n\n  private args: LeftArgs;\n  constructor(args: LeftArgs) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n\n    const lines: TextLine[] = [];\n    for (let line = start; line <= end; line++) {\n      lines.push(vimState.document.lineAt(line));\n    }\n\n    vimState.recordedState.transformer.replace(\n      new Range(lines[0].range.start, lines[lines.length - 1].range.end),\n      lines\n        .map(\n          (line) =>\n            ' '.repeat(this.args.indent) + line.text.slice(line.firstNonWhitespaceCharacterIndex),\n        )\n        .join('\\n'),\n    );\n  }\n}\n\ntype RightArgs = {\n  width: number;\n};\n\nexport class RightCommand extends ExCommand {\n  public static readonly argParser: Parser<RightCommand> = optWhitespace\n    .then(numberParser.fallback(undefined))\n    .map((width) => new RightCommand({ width: width ?? configuration.textwidth }));\n\n  private args: RightArgs;\n  constructor(args: RightArgs) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n\n    const lines: TextLine[] = [];\n    for (let line = start; line <= end; line++) {\n      lines.push(vimState.document.lineAt(line));\n    }\n\n    vimState.recordedState.transformer.replace(\n      new Range(lines[0].range.start, lines[lines.length - 1].range.end),\n      lines\n        .map((line) => {\n          const indent = ' '.repeat(\n            Math.max(\n              0,\n              this.args.width - (line.text.length - line.firstNonWhitespaceCharacterIndex),\n            ),\n          );\n          return indent + line.text.slice(line.firstNonWhitespaceCharacterIndex);\n        })\n        .join('\\n'),\n    );\n  }\n}\n\ntype CenterArgs = {\n  width: number;\n};\n\nexport class CenterCommand extends ExCommand {\n  public static readonly argParser: Parser<CenterCommand> = optWhitespace\n    .then(numberParser.fallback(undefined))\n    .map((width) => new CenterCommand({ width: width ?? configuration.textwidth }));\n\n  private args: CenterArgs;\n  constructor(args: CenterArgs) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n\n    const lines: TextLine[] = [];\n    for (let line = start; line <= end; line++) {\n      lines.push(vimState.document.lineAt(line));\n    }\n\n    vimState.recordedState.transformer.replace(\n      new Range(lines[0].range.start, lines[lines.length - 1].range.end),\n      lines\n        .map((line) => {\n          const indent = ' '.repeat(\n            Math.max(\n              0,\n              this.args.width - (line.text.length - line.firstNonWhitespaceCharacterIndex),\n            ) / 2,\n          );\n          return indent + line.text.slice(line.firstNonWhitespaceCharacterIndex);\n        })\n        .join('\\n'),\n    );\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/let.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { all, alt, optWhitespace, Parser, sepBy, seq, seqMap, string, whitespace } from 'parsimmon';\nimport { VimError } from '../../error';\nimport { Register } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport {\n  add,\n  concat,\n  divide,\n  int,\n  modulo,\n  multiply,\n  subtract,\n  toExpr,\n} from '../../vimscript/expression/build';\nimport { displayValue } from '../../vimscript/expression/displayValue';\nimport { EvaluationContext, toInt, toString } from '../../vimscript/expression/evaluate';\nimport {\n  envVariableParser,\n  expressionParser,\n  optionParser,\n  registerParser,\n  variableParser,\n  varNameParser,\n} from '../../vimscript/expression/parser';\nimport {\n  EnvVariableExpression,\n  Expression,\n  OptionExpression,\n  RegisterExpression,\n  Value,\n  VariableExpression,\n} from '../../vimscript/expression/types';\nimport { bangParser } from '../../vimscript/parserUtils';\n\ntype Unpack = {\n  type: 'unpack';\n  names: string[];\n};\ntype Index = {\n  type: 'index';\n  variable: VariableExpression;\n  index: Expression;\n};\ntype Slice = {\n  type: 'slice';\n  variable: VariableExpression;\n  start: Expression | undefined;\n  end: Expression | undefined;\n};\n\nexport type LetCommandOperation = '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '.=' | '..=';\nexport type LetCommandVariable =\n  | VariableExpression\n  | OptionExpression\n  | RegisterExpression\n  | EnvVariableExpression;\nexport type LetCommandArgs =\n  | {\n      operation: LetCommandOperation;\n      variable: LetCommandVariable | Unpack | Index | Slice;\n      expression: Expression;\n      lock: boolean;\n    }\n  | {\n      operation: 'print';\n      variables: LetCommandVariable[];\n    };\n\nconst operationParser: Parser<LetCommandOperation> = alt(\n  string('='),\n  string('+='),\n  string('-='),\n  string('*='),\n  string('/='),\n  string('%='),\n  string('.='),\n  string('..='),\n);\n\nconst letVarParser = alt<LetCommandVariable>(\n  variableParser,\n  optionParser,\n  envVariableParser,\n  registerParser,\n);\n\nconst unpackParser: Parser<Unpack> = sepBy(varNameParser, string(',').trim(optWhitespace))\n  .wrap(string('[').then(optWhitespace), optWhitespace.then(string(']')))\n  .map((names) => ({\n    type: 'unpack',\n    names,\n  }));\n\nconst indexParser: Parser<Index> = seq(\n  variableParser,\n  expressionParser.wrap(string('[').then(optWhitespace), optWhitespace.then(string(']'))),\n).map(([variable, index]) => ({\n  type: 'index',\n  variable,\n  index,\n}));\n\nconst sliceParser: Parser<Slice> = seq(\n  variableParser,\n  string('[').then(optWhitespace).then(expressionParser).fallback(undefined),\n  string(':').trim(optWhitespace),\n  expressionParser.fallback(undefined).skip(optWhitespace.then(string(']'))),\n).map(([variable, start, _, end]) => ({\n  type: 'slice',\n  variable,\n  start,\n  end,\n}));\n\nexport class LetCommand extends ExCommand {\n  public static readonly argParser = (lock: boolean) =>\n    alt<LetCommand>(\n      // `:let {var} = {expr}`\n      // `:let {var} += {expr}`\n      // `:let {var} -= {expr}`\n      // `:let {var} .= {expr}`\n      whitespace.then(\n        seq(\n          alt<LetCommandVariable | Unpack | Index | Slice>(\n            unpackParser,\n            sliceParser,\n            indexParser,\n            letVarParser,\n          ),\n          operationParser.trim(optWhitespace),\n          expressionParser.fallback(undefined),\n          all,\n        ).map(([variable, operation, expression, trailing]) => {\n          trailing = trailing.trim();\n          if (expression === undefined) {\n            throw VimError.InvalidExpression(trailing);\n          }\n          if (trailing) {\n            throw VimError.TrailingCharacters(trailing);\n          }\n          return new LetCommand({\n            operation,\n            variable,\n            expression,\n            lock,\n          });\n        }),\n      ),\n      // `:let`\n      // `:let {var-name} ...`\n      optWhitespace\n        .then(letVarParser.sepBy(whitespace))\n        .map((variables) => new LetCommand({ operation: 'print', variables })),\n    );\n\n  private args: LetCommandArgs;\n  constructor(args: LetCommandArgs) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const context = new EvaluationContext(vimState);\n    if (this.args.operation === 'print') {\n      if (this.args.variables.length === 0) {\n        // TODO\n      } else {\n        const variable = this.args.variables.at(-1)!;\n        const value = context.evaluate(variable);\n        const prefix = value.type === 'number' ? '#' : value.type === 'funcref' ? '*' : '';\n        StatusBar.setText(vimState, `${variable.name}    ${prefix}${displayValue(value)}`);\n      }\n    } else {\n      const variable = this.args.variable;\n\n      if (this.args.lock) {\n        if (this.args.operation !== '=') {\n          throw VimError.CannotModifyExistingVariable();\n        }\n        if (variable.type !== 'variable') {\n          if (variable.type === 'register') {\n            throw VimError.CannotLock('a register');\n          }\n          if (variable.type === 'option') {\n            throw VimError.CannotLock('an option');\n          }\n          if (variable.type === 'env_variable') {\n            throw VimError.CannotLock('an environment variable');\n          }\n          if (variable.type === 'slice') {\n            throw VimError.CannotLock('a range');\n          }\n          throw VimError.CannotLock('a list or dict');\n        }\n      }\n\n      const value = context.evaluate(this.args.expression);\n      const newValue = (_var: Expression, _value: Value) => {\n        if (this.args.operation === '+=') {\n          return context.evaluate(add(_var, toExpr(_value)));\n        } else if (this.args.operation === '-=') {\n          return context.evaluate(subtract(_var, toExpr(_value)));\n        } else if (this.args.operation === '*=') {\n          return context.evaluate(multiply(_var, toExpr(_value)));\n        } else if (this.args.operation === '/=') {\n          return context.evaluate(divide(_var, toExpr(_value)));\n        } else if (this.args.operation === '%=') {\n          return context.evaluate(modulo(_var, toExpr(_value)));\n        } else if (this.args.operation === '.=') {\n          return context.evaluate(concat(_var, toExpr(_value)));\n        } else if (this.args.operation === '..=') {\n          return context.evaluate(concat(_var, toExpr(_value)));\n        }\n        return _value;\n      };\n\n      if (variable.type === 'variable') {\n        if (\n          variable.namespace === 'v' &&\n          variable.name in\n            [\n              'count',\n              'false',\n              'key',\n              'null',\n              'operator',\n              'prevcount',\n              'progname',\n              'progpath',\n              'servername',\n              'shell_error',\n              'swapname',\n              't_bool',\n              't_dict',\n              't_float',\n              't_func',\n              't_list',\n              't_number',\n              't_string',\n              't_blob',\n              'true',\n              'val',\n              'version',\n              'vim_did_enter',\n            ]\n        ) {\n          throw VimError.CannotChangeReadOnlyVariable(`v:${variable.name}`);\n        }\n        context.setVariable(variable, newValue(variable, value), this.args.lock);\n      } else if (variable.type === 'register') {\n        if (this.args.operation === '=') {\n          vimState.recordedState.registerName = variable.name;\n          Register.put(vimState, toString(value));\n        } else if (this.args.operation === '.=' || this.args.operation === '..=') {\n          throw VimError.WrongVariableType(this.args.operation); // TODO\n        } else {\n          throw VimError.WrongVariableType(this.args.operation);\n        }\n      } else if (variable.type === 'option') {\n        // TODO\n      } else if (variable.type === 'env_variable') {\n        if (this.args.operation === '=') {\n          process.env[variable.name] = toString(value);\n        } else if (this.args.operation === '.=' || this.args.operation === '..=') {\n          process.env[variable.name] = (process.env[variable.name] ?? '') + toString(value);\n        } else {\n          throw VimError.WrongVariableType(this.args.operation);\n        }\n      } else if (variable.type === 'unpack') {\n        // TODO: Support :let [a, b; rest] = [\"aval\", \"bval\", 3, 4]\n        if (value.type !== 'list') {\n          throw VimError.ListRequired();\n        }\n        if (variable.names.length < value.items.length) {\n          throw VimError.LessTargetsThanListItems();\n        }\n        if (variable.names.length > value.items.length) {\n          throw VimError.MoreTargetsThanListItems();\n        }\n        for (const [i, name] of variable.names.entries()) {\n          const item: VariableExpression = { type: 'variable', namespace: undefined, name };\n          context.setVariable(item, newValue(item, value.items[i]), this.args.lock);\n        }\n      } else if (variable.type === 'index') {\n        const varValue = context.evaluate(variable.variable);\n        if (varValue.type === 'list') {\n          const idx = toInt(context.evaluate(variable.index));\n          const newItem = newValue(\n            {\n              type: 'index',\n              expression: variable.variable,\n              index: int(idx),\n            },\n            value,\n          );\n          varValue.items[idx] = newItem;\n          context.setVariable(variable.variable, varValue, this.args.lock);\n        } else if (varValue.type === 'dictionary') {\n          const key = toString(context.evaluate(variable.index));\n          const newItem = newValue(\n            {\n              type: 'entry',\n              expression: variable.variable,\n              entryName: key,\n            },\n            value,\n          );\n          varValue.items.set(key, newItem);\n          context.setVariable(variable.variable, varValue, this.args.lock);\n        } else {\n          // TODO: Support blobs\n          throw VimError.CanOnlyIndexAListDictionaryOrBlob();\n        }\n      } else if (variable.type === 'slice') {\n        // TODO: Operations other than `=`?\n        // TODO: Support blobs\n        const varValue = context.evaluate(variable.variable);\n        if (varValue.type !== 'list' || value.type !== 'list') {\n          throw VimError.CanOnlyIndexAListDictionaryOrBlob();\n        }\n        if (value.type !== 'list') {\n          throw VimError.SliceRequiresAListOrBlobValue();\n        }\n        const start = variable.start ? toInt(context.evaluate(variable.start)) : 0;\n        if (start > varValue.items.length - 1) {\n          throw VimError.ListIndexOutOfRange(start);\n        }\n        // NOTE: end is inclusive, unlike in JS\n        const end = variable.end\n          ? toInt(context.evaluate(variable.end))\n          : varValue.items.length - 1;\n        const slots = end - start + 1;\n        if (slots > value.items.length) {\n          throw VimError.ListValueHasNotEnoughItems();\n        } else if (slots < value.items.length) {\n          // TODO: Allow this when going past end of list and end === undefined\n          throw VimError.ListValueHasMoreItemsThanTarget();\n        }\n        let i = start;\n        for (const item of value.items) {\n          varValue.items[i] = item;\n          ++i;\n        }\n        context.setVariable(variable.variable, varValue, this.args.lock);\n      }\n    }\n  }\n}\n\nexport class UnletCommand extends ExCommand {\n  public static readonly argParser = seqMap(\n    bangParser,\n    whitespace.then(variableParser.sepBy(whitespace)),\n    (bang, variables) => {\n      if (variables.length === 0) {\n        throw VimError.ArgumentRequired();\n      }\n      return new UnletCommand(variables, bang);\n    },\n  );\n\n  private variables: VariableExpression[];\n  private bang: boolean;\n  public constructor(variables: VariableExpression[], bang: boolean) {\n    super();\n    this.variables = variables;\n    this.bang = bang;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const ctx = new EvaluationContext(vimState);\n    for (const variable of this.variables) {\n      const store = ctx.getVariableStore(variable.namespace);\n      const existed = store?.delete(variable.name);\n      if (!existed && !this.bang) {\n        throw VimError.NoSuchVariable(\n          variable.namespace ? `${variable.namespace}:${variable.name}` : variable.name,\n        );\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/marks.ts",
    "content": "import { QuickPickItem, window } from 'vscode';\n\n// eslint-disable-next-line id-denylist\nimport { Parser, alt, noneOf, optWhitespace, regexp, seq, string, whitespace } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { Cursor } from '../../common/motion/cursor';\nimport { VimError } from '../../error';\nimport { IMark } from '../../history/historyTracker';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\n\nclass MarkQuickPickItem implements QuickPickItem {\n  mark: IMark;\n\n  label: string;\n  description: string;\n  detail: string;\n  picked = false;\n  alwaysShow = false;\n\n  constructor(vimState: VimState, mark: IMark) {\n    this.mark = mark;\n    this.label = mark.name;\n    if (mark.isUppercaseMark && mark.document !== vimState.document) {\n      this.description = mark.document.fileName;\n    } else {\n      this.description = vimState.document.lineAt(mark.position).text.trim();\n    }\n    this.detail = `line ${mark.position.line} col ${mark.position.character}`;\n  }\n}\n\nexport class MarksCommand extends ExCommand {\n  public static readonly argParser: Parser<MarksCommand> = optWhitespace\n    .then(noneOf('|'))\n    .many()\n    .map((marks) => new MarksCommand(marks));\n\n  private marksFilter: string[];\n  constructor(marksFilter: string[]) {\n    super();\n    this.marksFilter = marksFilter;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const quickPickItems: MarkQuickPickItem[] = vimState.historyTracker\n      .getMarks()\n      .filter((mark) => {\n        return this.marksFilter.length === 0 || this.marksFilter.includes(mark.name);\n      })\n      .map((mark) => new MarkQuickPickItem(vimState, mark));\n\n    if (quickPickItems.length > 0) {\n      const item = await window.showQuickPick(quickPickItems, {\n        canPickMany: false,\n      });\n      if (item) {\n        vimState.cursors = [Cursor.atPosition(item.mark.position)];\n      }\n    } else {\n      void window.showInformationMessage('No marks set');\n    }\n  }\n}\n\ntype DeleteMarksArgs = Array<{ start: string; end: string } | string> | '!';\n\nexport class DeleteMarksCommand extends ExCommand {\n  public static readonly argParser: Parser<DeleteMarksCommand> = alt<DeleteMarksArgs>(\n    string('!'),\n    whitespace.then(\n      optWhitespace\n        .then(\n          alt<{ start: string; end: string } | string>(\n            seq(regexp(/[a-z]/).skip(string('-')), regexp(/[a-z]/)).map(([start, end]) => {\n              return { start, end };\n            }),\n            seq(regexp(/[A-Z]/).skip(string('-')), regexp(/[A-Z]/)).map(([start, end]) => {\n              return { start, end };\n            }),\n            seq(regexp(/[0-9]/).skip(string('-')), regexp(/[0-9]/)).map(([start, end]) => {\n              return { start, end };\n            }),\n            noneOf('-'),\n          ),\n        )\n        .many(),\n    ),\n  ).map((marks) => new DeleteMarksCommand(marks));\n\n  private args: DeleteMarksArgs;\n  constructor(args: DeleteMarksArgs) {\n    super();\n    this.args = args;\n  }\n\n  private static resolveMarkList(vimState: VimState, args: DeleteMarksArgs) {\n    const asciiRange = (start: string, end: string) => {\n      if (start > end) {\n        throw VimError.InvalidArgument474();\n      }\n\n      const [asciiStart, asciiEnd] = [start.charCodeAt(0), end.charCodeAt(0)];\n\n      const chars: string[] = [];\n      for (let ascii = asciiStart; ascii <= asciiEnd; ascii++) {\n        chars.push(String.fromCharCode(ascii));\n      }\n      return chars;\n    };\n\n    if (args === '!') {\n      // TODO: clear change list\n      return asciiRange('a', 'z');\n    }\n\n    const marks: string[] = [];\n    for (const x of args) {\n      if (typeof x === 'string') {\n        marks.push(x);\n      } else {\n        const range = asciiRange(x.start, x.end);\n        if (range === undefined) {\n          throw VimError.InvalidArgument474();\n        }\n        marks.push(...range.concat());\n      }\n    }\n    return marks;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const marks = DeleteMarksCommand.resolveMarkList(vimState, this.args);\n    vimState.historyTracker.removeMarks(marks);\n  }\n}\n\nexport class MarkCommand extends ExCommand {\n  public static readonly argParser: Parser<MarkCommand> = seq(\n    optWhitespace,\n    regexp(/[a-zA-Z'`<>[\\].]/).desc('mark name'),\n    optWhitespace,\n  ).map(([, markName]) => new MarkCommand(markName));\n\n  private markName: string;\n  constructor(markName: string) {\n    super();\n    this.markName = markName;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const position = vimState.cursorStopPosition;\n    vimState.historyTracker.addMark(vimState.document, position, this.markName);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    /**\n     * When a range is specified, the mark is set at the last line of the range.\n     * For example, :1,5mark a will set mark 'a' at line 5.\n     */\n    const { end } = range.resolve(vimState);\n    const position = new Position(end, 0);\n    vimState.historyTracker.addMark(vimState.document, position, this.markName);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/move.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { Position, Range } from 'vscode';\nimport { PositionDiff } from '../../common/motion/position';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { Address, LineRange } from '../../vimscript/lineRange';\n\nexport class MoveCommand extends ExCommand {\n  public static readonly argParser: Parser<MoveCommand> = optWhitespace\n    .then(Address.parser.fallback(undefined))\n    .map((address) => new MoveCommand(address));\n\n  private address?: Address;\n  constructor(address?: Address) {\n    super();\n    this.address = address;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  private moveLines(vimState: VimState, sourceStart: number, sourceEnd: number) {\n    const dest = this.address?.resolve(vimState, 'left', false);\n    if (dest === undefined || dest < -1 || dest > vimState.document.lineCount) {\n      StatusBar.displayError(vimState, VimError.InvalidAddress());\n      return;\n    }\n\n    if (sourceEnd < sourceStart) {\n      [sourceStart, sourceEnd] = [sourceEnd, sourceStart];\n    }\n    /* make sure\n    1. not move a range to the place inside itself.\n    2. not move a range to the place right below or above itself, which leads to no change.\n    */\n    if (dest >= sourceStart && dest <= sourceEnd) {\n      StatusBar.displayError(vimState, VimError.InvalidAddress());\n      return;\n    }\n\n    // copy\n    const copiedText = vimState.document.getText(\n      new Range(new Position(sourceStart, 0), new Position(sourceEnd, 0).getLineEnd()),\n    );\n\n    let text: string;\n    let position: Position;\n    if (dest === -1) {\n      text = copiedText + '\\n';\n      position = new Position(0, 0);\n    } else {\n      text = '\\n' + copiedText;\n      position = new Position(dest, 0).getLineEnd();\n    }\n\n    const lines = copiedText.split('\\n');\n    let cursorPosition: Position;\n    if (dest > sourceEnd) {\n      // make the cursor position at the beginning of the endline.\n      cursorPosition = new Position(Math.max(dest, 0), lines.at(-1)!.match(/\\S/)?.index ?? 0);\n    } else {\n      cursorPosition = new Position(\n        Math.max(dest + lines.length, 0),\n        lines.at(-1)!.match(/\\S/)?.index ?? 0,\n      );\n    }\n    // delete\n    let start = new Position(sourceStart, 0);\n    let end = new Position(sourceEnd, 0).getLineEnd();\n\n    if (sourceEnd < vimState.document.lineCount - 1) {\n      end = end.getRightThroughLineBreaks();\n    } else if (sourceStart > 0) {\n      start = start.getLeftThroughLineBreaks();\n    }\n    vimState.recordedState.transformer.addTransformation({\n      type: 'deleteRange',\n      range: new Range(start, end),\n      manuallySetCursorPositions: true,\n    });\n\n    // insert\n    vimState.recordedState.transformer.insert(\n      position,\n      text,\n      PositionDiff.exactPosition(cursorPosition),\n    );\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    const line = vimState.cursor.stop.line;\n    this.moveLines(vimState, line, line);\n  }\n\n  public override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n    this.moveLines(vimState, start, end);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/nohl.ts",
    "content": "import { globalState } from '../../state/globalState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class NohlCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    globalState.hl = false;\n\n    // Clear the `match x of y` message from status bar\n    StatusBar.clear(vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/normal.ts",
    "content": "import { Parser, all, whitespace } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\n\nexport class NormalCommand extends ExCommand {\n  // TODO: support to parse `:normal!`\n  public static readonly argParser: Parser<NormalCommand> = whitespace\n    .then(all)\n    .map((keystrokes) => new NormalCommand(keystrokes));\n\n  private readonly keystrokes: string;\n  constructor(argument: string) {\n    super();\n    this.keystrokes = argument;\n  }\n\n  override async execute(vimState: VimState): Promise<void> {\n    vimState.recordedState.transformer.addTransformation({\n      type: 'executeNormal',\n      keystrokes: this.keystrokes,\n    });\n  }\n\n  override async executeWithRange(vimState: VimState, lineRange: LineRange): Promise<void> {\n    vimState.recordedState.transformer.addTransformation({\n      type: 'executeNormal',\n      keystrokes: this.keystrokes,\n      range: lineRange,\n    });\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/only.ts",
    "content": "import * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class OnlyCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    await Promise.allSettled([\n      vscode.commands.executeCommand('workbench.action.joinAllGroups'),\n      vscode.commands.executeCommand('workbench.action.maximizeEditor'),\n      vscode.commands.executeCommand('workbench.action.closePanel'),\n    ]);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/print.ts",
    "content": "import { Parser, succeed } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { Address, LineRange } from '../../vimscript/lineRange';\n\ntype PrintArgs = {\n  printNumbers: boolean;\n  printText: boolean;\n};\n\n// TODO: `:l[ist]` is more than an alias\n// TODO: `:z`\nexport class PrintCommand extends ExCommand {\n  // TODO: Print {count} and [flags]\n  public static readonly argParser = (args: {\n    printNumbers: boolean;\n    printText: boolean;\n  }): Parser<PrintCommand> => succeed(new PrintCommand(args));\n\n  private args: PrintArgs;\n  constructor(args: PrintArgs) {\n    super();\n    this.args = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO: Wrong default for `:=`\n    void this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { end } = range.resolve(vimState);\n\n    // For now, we just print the last line.\n    // TODO: Create a dynamic document if there's more than one line?\n    const line = vimState.document.lineAt(end);\n    let output: string;\n    if (this.args.printNumbers) {\n      if (this.args.printText) {\n        output = `${line.lineNumber + 1} ${line.text}`;\n      } else {\n        output = `${line.lineNumber + 1}`;\n      }\n    } else {\n      if (this.args.printText) {\n        output = `${line.text}`;\n      } else {\n        output = '';\n      }\n    }\n    StatusBar.setText(vimState, output);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/put.ts",
    "content": "import { configuration } from '../../configuration/configuration';\nimport { VimState } from '../../state/vimState';\n\n// eslint-disable-next-line id-denylist\nimport { Parser, all, alt, any, optWhitespace, seq, string } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { PutBeforeFromCmdLine, PutFromCmdLine } from '../../actions/commands/put';\nimport { VimError } from '../../error';\nimport { Register } from '../../register/register';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { EvaluationContext, toString } from '../../vimscript/expression/evaluate';\nimport { expressionParser } from '../../vimscript/expression/parser';\nimport { Expression } from '../../vimscript/expression/types';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { bangParser } from '../../vimscript/parserUtils';\n\nexport interface IPutCommandArguments {\n  bang: boolean;\n  register?: string;\n  fromExpression?: Expression;\n}\n\n//\n// Implements :put\n// http://vimdoc.sourceforge.net/htmldoc/change.html#:put\n//\n\nexport class PutExCommand extends ExCommand {\n  public static readonly argParser: Parser<PutExCommand> = seq(\n    bangParser,\n    optWhitespace.then(\n      alt<Partial<IPutCommandArguments>>(\n        string('=')\n          .then(optWhitespace)\n          .then(seq(expressionParser.fallback(undefined), all))\n          .map(([expression, trailing]) => {\n            trailing = trailing.trim();\n            if (expression === undefined) {\n              if (trailing) {\n                throw VimError.InvalidExpression(trailing);\n              }\n              return { register: '=' };\n            }\n            if (trailing) {\n              throw VimError.TrailingCharacters(trailing);\n            }\n            return { fromExpression: expression };\n          }),\n        // eslint-disable-next-line id-denylist\n        any.map((register) => ({ register })).fallback({ register: undefined }),\n      ),\n    ),\n  ).map(([bang, register]) => new PutExCommand({ bang, ...register }));\n\n  private static lastExpression: Expression | undefined;\n\n  public readonly arguments: IPutCommandArguments;\n\n  constructor(args: IPutCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  async doPut(vimState: VimState, position: Position): Promise<void> {\n    if (this.arguments.register === '=' && this.arguments.fromExpression === undefined) {\n      if (PutExCommand.lastExpression === undefined) {\n        return;\n      }\n      this.arguments.fromExpression = PutExCommand.lastExpression;\n    }\n\n    if (this.arguments.fromExpression) {\n      PutExCommand.lastExpression = this.arguments.fromExpression;\n\n      this.arguments.register = '=';\n\n      const value = new EvaluationContext(vimState).evaluate(this.arguments.fromExpression);\n      const stringified =\n        value.type === 'list' ? value.items.map(toString).join('\\n') : toString(value);\n      Register.overwriteRegister(vimState, this.arguments.register, stringified, 0);\n    }\n\n    const registerName = this.arguments.register || (configuration.useSystemClipboard ? '*' : '\"');\n\n    if (!Register.isValidRegister(registerName)) {\n      StatusBar.displayError(vimState, VimError.TrailingCharacters());\n      return;\n    }\n\n    vimState.recordedState.registerName = registerName;\n\n    const putCmd = this.arguments.bang ? new PutBeforeFromCmdLine() : new PutFromCmdLine();\n    putCmd.setInsertionLine(position.line);\n    await putCmd.exec(position, vimState);\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await this.doPut(vimState, vimState.cursorStopPosition);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { end } = range.resolve(vimState);\n    await this.doPut(vimState, new Position(end, 0).getLineEnd());\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/pwd.ts",
    "content": "import * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\n\n/**\n * Implements the :pwd command, which prints the current working directory.\n */\nexport class PwdCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    const workspaceFolders = vscode.workspace.workspaceFolders;\n    if (workspaceFolders && workspaceFolders.length > 0) {\n      const currentDir = workspaceFolders[0].uri.fsPath;\n      StatusBar.setText(vimState, `Current Directory: ${currentDir}`);\n    } else {\n      StatusBar.setText(vimState, 'No workspace folder is open.');\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/quit.ts",
    "content": "import { Parser } from 'parsimmon';\nimport * as vscode from 'vscode';\n\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser } from '../../vimscript/parserUtils';\n\nexport interface IQuitCommandArguments {\n  bang?: boolean;\n  quitAll?: boolean;\n}\n\n//\n//  Implements :quit\n//  http://vimdoc.sourceforge.net/htmldoc/editing.html#:quit\n//\nexport class QuitCommand extends ExCommand {\n  public static readonly argParser: (quitAll: boolean) => Parser<QuitCommand> = (\n    quitAll: boolean,\n  ) =>\n    bangParser.map(\n      (bang) =>\n        new QuitCommand({\n          bang,\n          quitAll,\n        }),\n    );\n\n  public arguments: IQuitCommandArguments;\n  constructor(args: IQuitCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // NOTE: We can't currently get all open text editors, so this isn't perfect. See #3809\n    const duplicatedInSplit =\n      vscode.window.visibleTextEditors.filter((editor) => editor.document === vimState.document)\n        .length > 1;\n    if (\n      vimState.document.isDirty &&\n      !this.arguments.bang &&\n      (!duplicatedInSplit || this.arguments.quitAll)\n    ) {\n      throw VimError.NoWriteSinceLastChange();\n    }\n\n    if (this.arguments.quitAll) {\n      if (!this.arguments.bang) {\n        await vscode.commands.executeCommand('workbench.action.closeAllEditors');\n      } else {\n        const totalTabCount = vscode.window.tabGroups.all.flatMap((group) => group.tabs).length;\n        for (let i = 0; i < totalTabCount; i++) {\n          await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');\n        }\n      }\n    } else {\n      if (!this.arguments.bang) {\n        await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n      } else {\n        await vscode.commands.executeCommand('workbench.action.revertAndCloseActiveEditor');\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/read.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { all, alt, optWhitespace, Parser, seq, string, whitespace } from 'parsimmon';\nimport { SUPPORT_READ_COMMAND } from 'platform/constants';\nimport { readFileAsync } from 'platform/fs';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { fileNameParser, FileOpt, fileOptParser } from '../../vimscript/parserUtils';\n\nexport type IReadCommandArguments =\n  | { opt: FileOpt; cmd: string }\n  | { opt: FileOpt; file: string }\n  | { opt: FileOpt };\n\n//\n//  Implements :read and :read!\n//  http://vimdoc.sourceforge.net/htmldoc/insert.html#:read\n//  http://vimdoc.sourceforge.net/htmldoc/insert.html#:read!\n//\nexport class ReadCommand extends ExCommand {\n  public static readonly argParser: Parser<ReadCommand> = seq(\n    whitespace.then(fileOptParser).fallback<FileOpt>([]),\n    optWhitespace\n      .then(\n        alt<{ cmd: string } | { file: string }>(\n          string('!')\n            .then(all)\n            .map((cmd) => ({ cmd })),\n          fileNameParser.map((file) => ({ file })),\n        ),\n      )\n      .fallback({}),\n  ).map(([opt, other]) => new ReadCommand({ opt, ...other }));\n\n  private readonly arguments: IReadCommandArguments;\n  constructor(args: IReadCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const textToInsert = await this.getTextToInsert(vimState);\n    if (textToInsert) {\n      vimState.recordedState.transformer.insert(\n        vimState.cursorStopPosition.getLineEnd(),\n        '\\n' + textToInsert,\n      );\n    }\n  }\n\n  // TODO: executeWithRange()\n\n  async getTextToInsert(vimState: VimState): Promise<string> {\n    if ('file' in this.arguments) {\n      return readFileAsync(this.arguments.file, 'utf8');\n    } else if ('cmd' in this.arguments) {\n      if (this.arguments.cmd.length > 0) {\n        if (SUPPORT_READ_COMMAND) {\n          const cmd = this.arguments.cmd;\n          return new Promise<string>(async (resolve, reject) => {\n            const { exec } = await import('child_process');\n            exec(cmd, (err, stdout, stderr) => {\n              if (err) {\n                reject(err);\n              } else {\n                resolve(stdout);\n              }\n            });\n          });\n        } else {\n          return '';\n        }\n      } else {\n        // TODO: error message?\n        return '';\n      }\n    } else {\n      return vimState.document.getText();\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/redo.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { Redo } from '../../actions/commands/undo';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { numberParser } from '../../vimscript/parserUtils';\n\n//\n//  Implements :red[o]\n//  http://vimdoc.sourceforge.net/htmldoc/undo.html#redo\n//\nexport class RedoCommand extends ExCommand {\n  public static readonly argParser: Parser<RedoCommand> = optWhitespace\n    .then(numberParser)\n    .fallback(undefined)\n    .map((count) => new RedoCommand(count));\n\n  private count?: number;\n  private constructor(count?: number) {\n    super();\n    this.count = count;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO: Use `this.count`\n    await new Redo().exec(new Position(0, 0), vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/register.ts",
    "content": "import * as vscode from 'vscode';\n\n// eslint-disable-next-line id-denylist\nimport { Parser, any, optWhitespace } from 'parsimmon';\nimport { VimError } from '../../error';\nimport { Register } from '../../register/register';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class RegisterCommand extends ExCommand {\n  public static readonly argParser: Parser<RegisterCommand> = optWhitespace.then(\n    // eslint-disable-next-line id-denylist\n    any.sepBy(optWhitespace).map((registers) => new RegisterCommand(registers)),\n  );\n\n  private readonly registers: string[];\n  constructor(registers: string[]) {\n    super();\n    this.registers = registers;\n  }\n\n  private async getRegisterDisplayValue(register: string): Promise<string | undefined> {\n    let result = (await Register.get(register))?.text;\n    if (result instanceof Array) {\n      result = result.join('\\n').substr(0, 100);\n    } else if (result instanceof RecordedState) {\n      result = result.actionsRun.map((x) => x.keysPressed.join('')).join('');\n    }\n\n    return result;\n  }\n\n  async displayRegisterValue(vimState: VimState, register: string): Promise<void> {\n    let result = await this.getRegisterDisplayValue(register);\n    if (result === undefined) {\n      StatusBar.displayError(vimState, VimError.NothingInRegister(register));\n    } else {\n      result = result.replace(/\\n/g, '\\\\n');\n      void vscode.window.showInformationMessage(`${register} ${result}`);\n    }\n  }\n\n  private regSortOrder(register: string): number {\n    const specials = ['-', '*', '+', '.', ':', '%', '#', '/', '='];\n    if (register === '\"') {\n      return 0;\n    } else if (register >= '0' && register <= '9') {\n      return 10 + parseInt(register, 10);\n    } else if (register >= 'a' && register <= 'z') {\n      return 100 + (register.charCodeAt(0) - 'a'.charCodeAt(0));\n    } else if (specials.includes(register)) {\n      return 1000 + specials.indexOf(register);\n    } else {\n      throw new Error(`Unexpected register ${register}`);\n    }\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (this.registers.length === 1) {\n      await this.displayRegisterValue(vimState, this.registers[0]);\n    } else {\n      const currentRegisterKeys = Register.getKeys()\n        .filter(\n          (reg) => reg !== '_' && (this.registers.length === 0 || this.registers.includes(reg)),\n        )\n        .sort((reg1: string, reg2: string) => this.regSortOrder(reg1) - this.regSortOrder(reg2));\n      const registerKeyAndContent = new Array<vscode.QuickPickItem>();\n\n      for (const registerKey of currentRegisterKeys) {\n        const displayValue = await this.getRegisterDisplayValue(registerKey);\n        if (typeof displayValue === 'string') {\n          registerKeyAndContent.push({\n            label: registerKey,\n            description: displayValue,\n          });\n        }\n      }\n\n      void vscode.window.showQuickPick(registerKeyAndContent).then(async (val) => {\n        if (val) {\n          const result = val.description;\n          void vscode.window.showInformationMessage(`${val.label} ${result}`);\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/retab.ts",
    "content": "import { optWhitespace, Parser, seq } from 'parsimmon';\nimport { Range } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { isVisualMode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { bangParser, numberParser } from '../../vimscript/parserUtils';\nimport { SetCommand } from './set';\n\nexport interface IRetabCommandArguments {\n  replaceSpaces: boolean;\n  newTabstop?: number;\n}\n\ninterface UpdatedLineSegment {\n  value: string;\n  length: number;\n}\n\n// :[range]ret[ab][!] [new_tabstop]\nexport class RetabCommand extends ExCommand {\n  public static readonly argParser: Parser<RetabCommand> = seq(\n    bangParser,\n    optWhitespace.then(numberParser).fallback(undefined),\n  ).map(\n    ([replaceSpaces, newTabstop]) =>\n      new RetabCommand({\n        replaceSpaces,\n        newTabstop,\n      }),\n  );\n\n  private readonly arguments: IRetabCommandArguments;\n\n  constructor(args: IRetabCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (isVisualMode(vimState.currentMode)) {\n      const { start, end } = vimState.editor.selection;\n      this.retab(vimState, start.line, end.line);\n    } else {\n      this.retab(vimState, 0, vimState.document.lineCount - 1);\n    }\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n    this.retab(vimState, start, end);\n  }\n\n  private concat(count: number, char: string): string {\n    let result = '';\n\n    for (let i = 0; i < count; i++) {\n      result += char;\n    }\n\n    return result;\n  }\n\n  private hasTabs(str: string): boolean {\n    return str.indexOf('\\t') >= 0;\n  }\n\n  expandtab(str: string, start = 0, tabstop = configuration.tabstop): string {\n    let expanded = '';\n\n    let i = start;\n    for (const char of str) {\n      if (char === '\\t') {\n        const spaces = tabstop - (i % tabstop) || tabstop;\n\n        expanded += this.concat(spaces, ' ');\n        i += spaces;\n      } else {\n        expanded += char;\n        i++;\n      }\n    }\n\n    return expanded;\n  }\n\n  retabLineSegment(\n    segment: string,\n    start: number,\n    tabstop = configuration.tabstop,\n  ): UpdatedLineSegment {\n    const retab = this.arguments.replaceSpaces || this.hasTabs(segment);\n\n    if (!retab) {\n      return {\n        value: segment,\n        length: segment.length,\n      };\n    }\n\n    const retabTabstop = this.arguments.newTabstop || tabstop;\n    const detabbed = this.expandtab(segment, start, tabstop);\n\n    const spaces = Math.min((start + detabbed.length) % retabTabstop, detabbed.length);\n    const tabs = Math.ceil((detabbed.length - spaces) / retabTabstop);\n\n    let result = '';\n\n    result += this.concat(tabs, '\\t');\n    result += this.concat(spaces, ' ');\n\n    return {\n      value: result,\n      length: detabbed.length,\n    };\n  }\n\n  retabLine(line: string, tabstop = configuration.tabstop): string {\n    const segments = line.split(/(\\s+)/);\n    let i = 0;\n\n    let retabbed = '';\n    for (const str of segments) {\n      if (!str) {\n        continue;\n      }\n\n      if (![' ', '\\t'].includes(str[0])) {\n        retabbed += str;\n        i += str.length;\n      } else {\n        const result = this.retabLineSegment(str, i, tabstop);\n\n        retabbed += result.value;\n        i += result.length;\n      }\n    }\n\n    return retabbed;\n  }\n\n  public retab(vimState: VimState, startLine: number, endLine: number) {\n    const originalLines: string[] = [];\n\n    const lastLine = Math.min(endLine, vimState.document.lineCount - 1);\n    for (let i = startLine; i <= lastLine; i++) {\n      originalLines.push(vimState.document.lineAt(i).text);\n    }\n\n    const replacedLines = originalLines.map((line: string) => {\n      return configuration.expandtab ? this.expandtab(line) : this.retabLine(line);\n    });\n\n    const replacedContent = replacedLines.join('\\n');\n    const lastLineLength = originalLines[originalLines.length - 1].length;\n\n    vimState.recordedState.transformer.replace(\n      new Range(startLine, 0, endLine, lastLineLength),\n      replacedContent,\n    );\n\n    if (this.arguments.newTabstop) {\n      const setTabstop = new SetCommand({\n        type: 'equal',\n        option: 'tabstop',\n        value: this.arguments.newTabstop.toString(),\n      });\n\n      void setTabstop.execute(vimState);\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/set.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { alt, oneOf, Parser, regexp, seq, string, whitespace } from 'parsimmon';\nimport { configuration, optionAliases } from '../../configuration/configuration';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { ExCommand } from '../../vimscript/exCommand';\n\ntype SetOperation =\n  | {\n      // :se[t]\n      // :se[t] {option}\n      type: 'show_or_set';\n      option: string | undefined;\n    }\n  | {\n      // :se[t] {option}?\n      type: 'show';\n      option: string;\n    }\n  | {\n      // :se[t] no{option}\n      type: 'unset';\n      option: string;\n    }\n  | {\n      // :se[t] {option}!\n      // :se[t] inv{option}\n      type: 'invert';\n      option: string;\n    }\n  | {\n      // :se[t] {option}&\n      // :se[t] {option}&vi\n      // :se[t] {option}&vim\n      type: 'default';\n      option: string;\n      source: 'vi' | 'vim' | '';\n    }\n  | {\n      // :se[t] {option}={value}\n      // :se[t] {option}:{value}\n      type: 'equal';\n      option: string;\n      value: string;\n    }\n  | {\n      // :se[t] {option}+={value}\n      type: 'add';\n      option: string;\n      value: string;\n    }\n  | {\n      // :se[t] {option}^={value}\n      type: 'multiply';\n      option: string;\n      value: string;\n    }\n  | {\n      // :se[t] {option}-={value}\n      type: 'subtract';\n      option: string;\n      value: string;\n    };\n\nconst optionParser = regexp(/[a-z]+/);\nconst valueParser = regexp(/\\S*/);\nconst setOperationParser: Parser<SetOperation> = whitespace\n  .then(\n    alt<SetOperation>(\n      string('no')\n        .then(optionParser)\n        .map((option) => {\n          return {\n            type: 'unset',\n            option,\n          };\n        }),\n      string('inv')\n        .then(optionParser)\n        .map((option) => {\n          return {\n            type: 'invert',\n            option,\n          };\n        }),\n      optionParser.skip(string('!')).map((option) => {\n        return {\n          type: 'invert',\n          option,\n        };\n      }),\n      optionParser.skip(string('?')).map((option) => {\n        return {\n          type: 'show',\n          option,\n        };\n      }),\n      seq(optionParser.skip(string('&')), alt(string('vim'), string('vi'), string(''))).map(\n        ([option, source]) => {\n          return {\n            type: 'default',\n            option,\n            source,\n          };\n        },\n      ),\n      seq(optionParser.skip(oneOf('=:')), valueParser).map(([option, value]) => {\n        return {\n          type: 'equal',\n          option,\n          value,\n        };\n      }),\n      seq(optionParser.skip(string('+=')), valueParser).map(([option, value]) => {\n        return {\n          type: 'add',\n          option,\n          value,\n        };\n      }),\n      seq(optionParser.skip(string('^=')), valueParser).map(([option, value]) => {\n        return {\n          type: 'multiply',\n          option,\n          value,\n        };\n      }),\n      seq(optionParser.skip(string('-=')), valueParser).map(([option, value]) => {\n        return {\n          type: 'subtract',\n          option,\n          value,\n        };\n      }),\n      optionParser.map((option) => {\n        return {\n          type: 'show_or_set',\n          option,\n        };\n      }),\n    ),\n  )\n  .fallback({ type: 'show_or_set', option: undefined });\n\nexport class SetCommand extends ExCommand {\n  public static readonly argParser: Parser<SetCommand> = setOperationParser.map(\n    (operation) => new SetCommand(operation),\n  );\n\n  private readonly operation: SetOperation;\n  constructor(operation: SetOperation) {\n    super();\n    this.operation = operation;\n  }\n\n  // Listeners for options that need to be updated when they change\n  private static listeners: { [key: string]: Array<() => void> } = {};\n  static addListener(option: string, listener: () => void) {\n    if (!(option in SetCommand.listeners)) SetCommand.listeners[option] = [];\n    SetCommand.listeners[option].push(listener);\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (this.operation.option === undefined) {\n      // TODO: Show all options that differ from their default value\n      return;\n    }\n\n    const option = optionAliases.get(this.operation.option) ?? this.operation.option;\n    const currentValue = configuration[option] as string | number | boolean | undefined;\n    if (currentValue === undefined) {\n      throw VimError.UnknownOption(option);\n    }\n    const type =\n      typeof currentValue === 'boolean'\n        ? 'boolean'\n        : typeof currentValue === 'string'\n          ? 'string'\n          : 'number';\n\n    switch (this.operation.type) {\n      case 'show_or_set': {\n        if (this.operation.option === 'all') {\n          // TODO: Show all options\n        } else {\n          if (type === 'boolean') {\n            configuration[option] = true;\n          } else {\n            this.showOption(vimState, option, currentValue);\n          }\n        }\n        break;\n      }\n      case 'show': {\n        this.showOption(vimState, option, currentValue);\n        break;\n      }\n      case 'unset': {\n        if (type === 'boolean') {\n          configuration[option] = false;\n        } else {\n          throw VimError.InvalidArgument474(`no${option}`);\n        }\n        break;\n      }\n      case 'invert': {\n        if (type === 'boolean') {\n          configuration[option] = !currentValue;\n        } else {\n          // TODO: Could also be {option}!\n          throw VimError.InvalidArgument474(`inv${option}`);\n        }\n        break;\n      }\n      case 'default': {\n        if (this.operation.option === 'all') {\n          // TODO: Set all options to default\n        } else {\n          // TODO: Set the option to default\n        }\n        break;\n      }\n      case 'equal': {\n        if (type === 'boolean') {\n          // TODO: Could also be {option}:{value}\n          throw VimError.InvalidArgument474(`${option}=${this.operation.value}`);\n        } else if (type === 'string') {\n          configuration[option] = this.operation.value;\n        } else {\n          const value = Number.parseInt(this.operation.value, 10);\n          if (isNaN(value)) {\n            // TODO: Could also be {option}:{value}\n            throw VimError.NumberRequiredAfterEqual(`${option}=${this.operation.value}`);\n          }\n          configuration[option] = value;\n        }\n        break;\n      }\n      case 'add': {\n        if (type === 'boolean') {\n          throw VimError.InvalidArgument474(`${option}+=${this.operation.value}`);\n        } else if (type === 'string') {\n          configuration[option] = currentValue + this.operation.value;\n        } else {\n          const value = Number.parseInt(this.operation.value, 10);\n          if (isNaN(value)) {\n            throw VimError.NumberRequiredAfterEqual(`${option}+=${this.operation.value}`);\n          }\n          configuration[option] = (currentValue as number) + value;\n        }\n        break;\n      }\n      case 'multiply': {\n        if (type === 'boolean') {\n          throw VimError.InvalidArgument474(`${option}^=${this.operation.value}`);\n        } else if (type === 'string') {\n          configuration[option] = this.operation.value + currentValue;\n        } else {\n          const value = Number.parseInt(this.operation.value, 10);\n          if (isNaN(value)) {\n            throw VimError.NumberRequiredAfterEqual(`${option}^=${this.operation.value}`);\n          }\n          configuration[option] = (currentValue as number) * value;\n        }\n        break;\n      }\n      case 'subtract': {\n        if (type === 'boolean') {\n          throw VimError.InvalidArgument474(`${option}-=${this.operation.value}`);\n        } else if (type === 'string') {\n          configuration[option] = (currentValue as string).split(this.operation.value).join('');\n        } else {\n          const value = Number.parseInt(this.operation.value, 10);\n          if (isNaN(value)) {\n            throw VimError.NumberRequiredAfterEqual(`${option}-=${this.operation.value}`);\n          }\n          configuration[option] = (currentValue as number) - value;\n        }\n        break;\n      }\n      default:\n        const guard: never = this.operation;\n        throw new Error('Got unexpected SetOperation.type');\n    }\n\n    if (option in SetCommand.listeners) {\n      for (const listener of SetCommand.listeners[option]) {\n        listener();\n      }\n    }\n  }\n\n  private showOption(vimState: VimState, option: string, value: boolean | string | number) {\n    if (typeof value === 'boolean') {\n      StatusBar.setText(vimState, value ? option : `no${option}`);\n    } else {\n      StatusBar.setText(vimState, `${option}=${value}`);\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/sh.ts",
    "content": "import { window } from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class ShCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    window.createTerminal().show();\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/shift.ts",
    "content": "import { Position, Selection } from 'vscode';\n\n// eslint-disable-next-line id-denylist\nimport { optWhitespace, Parser, seq, string } from 'parsimmon';\nimport { PositionDiff } from '../../common/motion/position';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { Address, LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\nexport type ShiftDirection = '>' | '<';\nexport type ShiftArgs = {\n  dir: '>' | '<';\n  depth: number;\n  numLines: number | undefined;\n};\n\nexport class ShiftCommand extends ExCommand {\n  public static readonly argParser = (dir: '>' | '<'): Parser<ShiftCommand> =>\n    optWhitespace\n      .then(\n        seq(\n          // `:>>>` indents 3 times `shiftwidth`\n          string(dir)\n            .many()\n            .map((shifts) => shifts.length + 1)\n            .skip(optWhitespace),\n          // `:> 2` indents 2 lines\n          numberParser.fallback(undefined),\n        ),\n      )\n      .map(([depth, numLines]) => new ShiftCommand({ dir, depth, numLines }));\n\n  private args: ShiftArgs;\n  constructor(args: ShiftArgs) {\n    super();\n    this.args = args;\n  }\n\n  public async execute(vimState: VimState): Promise<void> {\n    await this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  public override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    let { start, end } = range.resolve(vimState);\n    if (this.args.numLines !== undefined) {\n      start = end;\n      end = start + this.args.numLines;\n    }\n\n    vimState.editor.selection = new Selection(new Position(start, 0), new Position(end, 0));\n    for (let i = 0; i < this.args.depth; i++) {\n      if (this.args.dir === '>') {\n        vimState.recordedState.transformer.vscodeCommand('editor.action.indentLines');\n      } else if (this.args.dir === '<') {\n        vimState.recordedState.transformer.vscodeCommand('editor.action.outdentLines');\n      }\n    }\n\n    vimState.recordedState.transformer.moveCursor(PositionDiff.startOfLine());\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/smile.ts",
    "content": "import * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class SmileCommand extends ExCommand {\n  static readonly smileText: string = `\n                               oooo$$$$$$$$$$$$oooo\n                          oo$$$$$$$$$$$$$$$$$$$$$$$$o\n                       oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o         o$   $$ o$\n     o $ oo          o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o       $$ $$ $$o$\n    oo $ $ \"$      o$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$o       $$$o$$o$\n    \"$$$$$$o$     o$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$o    $$$$$$$$\n      $$$$$$$    $$$$$$$$$$$      $$$$$$$$$$$      $$$$$$$$$$$$$$$$$$$$$$$\n      $$$$$$$$$$$$$$$$$$$$$$$    $$$$$$$$$$$$$    $$$$$$$$$$$$$$  \"\"\"$$$\n       \"$$$\"\"\"\"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     \"$$$\n        $$$   o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     \"$$$o\n       o$$\"   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$       $$$o\n       $$$    $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\" \"$$$$$$ooooo$$$$o\n      o$$$oooo$$$$$  $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$   o$$$$$$$$$$$$$$$$$\n      $$$$$$$$\"$$$$   $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$     $$$$\"\"\"\"\"\"\"\"\n     \"\"\"\"       $$$$    \"$$$$$$$$$$$$$$$$$$$$$$$$$$$$\"      o$$$\n                \"$$$o     \"\"\"$$$$$$$$$$$$$$$$$$\"$$\"         $$$\n                  $$$o          \"$$\"\"$$$$$$\"\"\"\"           o$$$\n                   $$$$o                                o$$$\"\n                    \"$$$$o      o$$$$$$o\"$$$$o        o$$$$\n                      \"$$$$$oo     \"\"$$$$o$$$$$o   o$$$$\"\"\n                         \"\"$$$$$oooo  \"$$$o$$$$$$$$$\"\"\"\n                            \"\"$$$$$$$oo $$$$$$$$$$\n                                    \"\"\"\"$$$$$$$$$$$\n                                        $$$$$$$$$$$$\n                                         $$$$$$$$$$\"\n                                          \"$$$\"\"\"\"`;\n\n  constructor() {\n    super();\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');\n    await TextEditor.insert(vscode.window.activeTextEditor!, SmileCommand.smileText);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/sort.ts",
    "content": "import { oneOf, optWhitespace, Parser, seq } from 'parsimmon';\nimport * as vscode from 'vscode';\nimport { PositionDiff } from '../../common/motion/position';\nimport { NumericString, NumericStringRadix } from '../../common/number/numericString';\n\nimport { isVisualMode } from '../../mode/mode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { bangParser } from '../../vimscript/parserUtils';\n\nexport interface ISortCommandArguments {\n  reverse: boolean;\n  ignoreCase: boolean;\n  unique: boolean;\n  numeric: boolean;\n  // TODO: support other flags\n  // TODO(#6676): support pattern\n}\n\nexport class SortCommand extends ExCommand {\n  public static readonly argParser: Parser<SortCommand> = seq(\n    bangParser,\n    optWhitespace.then(oneOf('bfilnorux').many()),\n  ).map(\n    ([bang, flags]) =>\n      new SortCommand({\n        reverse: bang,\n        ignoreCase: flags.includes('i'),\n        unique: flags.includes('u'),\n        numeric: flags.includes('n'),\n      }),\n  );\n\n  private readonly arguments: ISortCommandArguments;\n  constructor(args: ISortCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  public override neovimCapable(): boolean {\n    return true;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    if (isVisualMode(vimState.currentMode)) {\n      const { start, end } = vimState.editor.selection;\n      await this.sortLines(vimState, start.line, end.line);\n    } else {\n      await this.sortLines(vimState, 0, vimState.document.lineCount - 1);\n    }\n  }\n\n  async sortLines(vimState: VimState, startLine: number, endLine: number) {\n    let originalLines: string[] = [];\n\n    for (\n      let currentLine = startLine;\n      currentLine <= endLine && currentLine < vimState.document.lineCount;\n      currentLine++\n    ) {\n      originalLines.push(vimState.document.lineAt(currentLine).text);\n    }\n\n    const lastLineLength = originalLines[originalLines.length - 1].length;\n\n    if (this.arguments.unique) {\n      const seen = new Set<string>();\n      const uniqueLines: string[] = [];\n      for (const line of originalLines) {\n        const adjustedLine = this.arguments.ignoreCase ? line.toLowerCase() : line;\n        if (!seen.has(adjustedLine)) {\n          seen.add(adjustedLine);\n          uniqueLines.push(line);\n        }\n      }\n      originalLines = uniqueLines;\n    }\n\n    let sortedLines;\n    if (this.arguments.numeric) {\n      sortedLines = originalLines.sort(\n        (a: string, b: string) =>\n          (NumericString.parse(a, NumericStringRadix.Dec)?.num.value ?? Number.MAX_VALUE) -\n          (NumericString.parse(b, NumericStringRadix.Dec)?.num.value ?? Number.MAX_VALUE),\n      );\n    } else if (this.arguments.ignoreCase) {\n      sortedLines = originalLines.sort((a: string, b: string) => a.localeCompare(b));\n    } else {\n      sortedLines = originalLines.sort();\n    }\n\n    if (this.arguments.reverse) {\n      sortedLines.reverse();\n    }\n\n    const sortedContent = sortedLines.join('\\n');\n\n    vimState.recordedState.transformer.replace(\n      new vscode.Range(startLine, 0, endLine, lastLineLength),\n      sortedContent,\n      PositionDiff.exactPosition(\n        new vscode.Position(startLine, sortedLines[0].match(/\\S/)?.index ?? 0),\n      ),\n    );\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    const { start, end } = range.resolve(vimState);\n\n    await this.sortLines(vimState, start, end);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/substitute.ts",
    "content": "import {\n  Parser,\n  alt,\n  // eslint-disable-next-line id-denylist\n  any,\n  noneOf,\n  oneOf,\n  optWhitespace,\n  regexp,\n  seq,\n  // eslint-disable-next-line id-denylist\n  string,\n} from 'parsimmon';\nimport { CancellationTokenSource, DecorationOptions, Position, Range, window } from 'vscode';\nimport { PositionDiff } from '../../common/motion/position';\nimport { configuration } from '../../configuration/configuration';\nimport { decoration } from '../../configuration/decoration';\nimport { VimError } from '../../error';\nimport { Jump } from '../../jumps/jump';\nimport { globalState } from '../../state/globalState';\nimport { SearchState } from '../../state/searchState';\nimport { SubstituteState } from '../../state/substituteState';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { SearchDecorations, ensureVisible, formatDecorationText } from '../../util/decorationUtils';\nimport { escapeCSSIcons } from '../../util/statusBarTextUtils';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { str } from '../../vimscript/expression/build';\nimport { displayValue } from '../../vimscript/expression/displayValue';\nimport { EvaluationContext } from '../../vimscript/expression/evaluate';\nimport { expressionParser } from '../../vimscript/expression/parser';\nimport { Expression } from '../../vimscript/expression/types';\nimport { Address, LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\nimport { Pattern, PatternMatch, SearchDirection } from '../../vimscript/pattern';\n\ntype ReplaceStringComponent =\n  | { type: 'string'; value: string }\n  | { type: 'capture_group'; group: number | '&' }\n  | { type: 'prev_replace_string' }\n  | { type: 'expression'; expression: Expression }\n  | { type: 'change_case'; case: 'upper' | 'lower'; duration: 'char' | 'until_end' }\n  | { type: 'change_case_end' };\n\nexport class ReplaceString {\n  private components: ReplaceStringComponent[];\n  constructor(components: ReplaceStringComponent[]) {\n    this.components = components;\n  }\n\n  public toString(): string {\n    return this.components\n      .map((component) => {\n        if (component.type === 'string') {\n          return component.value;\n        } else if (component.type === 'capture_group') {\n          return component.group === '&' ? '&' : `\\\\${component.group}`;\n        } else if (component.type === 'prev_replace_string') {\n          return '~';\n        } else if (component.type === 'expression') {\n          return '\\\\='; // TODO\n        } else if (component.type === 'change_case') {\n          if (component.case === 'upper') {\n            return component.duration === 'char' ? '\\\\u' : '\\\\U';\n          } else {\n            return component.duration === 'char' ? '\\\\l' : '\\\\L';\n          }\n        } else if (component.type === 'change_case_end') {\n          return '\\\\E';\n        } else {\n          const guard: never = component;\n          return '';\n        }\n      })\n      .join('');\n  }\n\n  public resolve(vimState: VimState, matches: string[]): string {\n    let result = '';\n    let changeCase: 'upper' | 'lower' | undefined;\n    let changeCaseChar: 'upper' | 'lower' | undefined;\n    for (const component of this.components) {\n      let newChangeCaseChar: 'upper' | 'lower' | undefined;\n      let _result: string = '';\n      if (component.type === 'string') {\n        _result = component.value;\n      } else if (component.type === 'capture_group') {\n        const group: number = component.group === '&' ? 0 : component.group;\n        _result = matches[group] ?? '';\n      } else if (component.type === 'prev_replace_string') {\n        _result = globalState.substituteState?.replaceString.toString() ?? '';\n      } else if (component.type === 'expression') {\n        const ctx = new EvaluationContext(vimState);\n        const value = (() => {\n          try {\n            return ctx.evaluate(component.expression);\n          } catch (e) {\n            return str(''); // TODO: Is this right?\n          }\n        })();\n        if (value.type === 'list') {\n          for (const item of value.items) {\n            _result += displayValue(item) + '\\n';\n          }\n        } else {\n          _result = displayValue(value);\n        }\n      } else if (component.type === 'change_case') {\n        if (component.duration === 'until_end') {\n          changeCase = component.case;\n        } else {\n          newChangeCaseChar = component.case;\n        }\n      } else if (component.type === 'change_case_end') {\n        changeCase = undefined;\n      } else {\n        const guard: never = component;\n      }\n\n      if (_result) {\n        if (changeCase) {\n          _result =\n            changeCase === 'upper' ? _result.toLocaleUpperCase() : _result.toLocaleLowerCase();\n        }\n        if (changeCaseChar) {\n          _result =\n            (changeCaseChar === 'upper'\n              ? _result[0].toLocaleUpperCase()\n              : _result[0].toLocaleLowerCase()) + _result.slice(1);\n        }\n        result += _result;\n      }\n\n      changeCaseChar = newChangeCaseChar;\n    }\n    return result;\n  }\n}\n\n/**\n * NOTE: for \"pattern\", undefined is different from an empty string.\n * when it's undefined, it means to repeat the previous REPLACEMENT and ignore \"replace\".\n * when it's an empty string, it means to use the previous SEARCH (not replacement) state,\n * and replace with whatever's set by \"replace\" (even an empty string).\n */\nexport interface ISubstituteCommandArguments {\n  pattern: Pattern | undefined;\n  replace: ReplaceString;\n  flags: SubstituteFlags;\n  count?: number;\n}\n\n/**\n * The flags that you can use for the substitute commands:\n * [&] Must be the first one: Keep the flags from the previous substitute command.\n * [c] Confirm each substitution.\n * [e] When the search pattern fails, do not issue an error message and, in\n *     particular, continue in maps as if no error occurred.\n * [g] Replace all occurrences in the line.  Without this argument, replacement\n *     occurs only for the first occurrence in each line.\n * [i] Ignore case for the pattern.\n * [I] Don't ignore case for the pattern.\n * [n] Report the number of matches, do not actually substitute.\n * [p] Print the line containing the last substitute.\n * [#] Like [p] and prepend the line number.\n * [l] Like [p] but print the text like |:list|.\n * [r] When the search pattern is empty, use the previously used search pattern\n *     instead of the search pattern from the last substitute or \":global\".\n */\nexport interface SubstituteFlags {\n  keepPreviousFlags?: true; // TODO: use this flag\n  confirmEach?: true;\n  suppressError?: true; // TODO: use this flag\n  replaceAll?: true;\n  ignoreCase?: true; // TODO: use this flag\n  noIgnoreCase?: true; // TODO: use this flag\n  printCount?: true;\n  // TODO: use the following flags:\n  printLastMatchedLine?: true;\n  printLastMatchedLineWithNumber?: true;\n  printLastMatchedLineWithList?: true;\n  usePreviousPattern?: true;\n}\n\n// TODO: `:help sub-replace-special`\nconst replaceStringParser = (delimiter: string): Parser<ReplaceString> =>\n  alt<ReplaceStringComponent[]>(\n    string('\\\\=')\n      .then(expressionParser)\n      .map((expr) => [{ type: 'expression', expression: expr }]),\n    alt<ReplaceStringComponent>(\n      string('\\\\').then(\n        // eslint-disable-next-line id-denylist\n        any.fallback(undefined).map<ReplaceStringComponent>((escaped) => {\n          if (escaped === undefined || escaped === '\\\\') {\n            return { type: 'string', value: '\\\\' };\n          } else if (escaped === '/') {\n            return { type: 'string', value: '/' };\n          } else if (escaped === 'b') {\n            return { type: 'string', value: '\\b' };\n          } else if (escaped === 'r') {\n            return { type: 'string', value: '\\r' };\n          } else if (escaped === 'n') {\n            return { type: 'string', value: '\\n' };\n          } else if (escaped === 't') {\n            return { type: 'string', value: '\\t' };\n          } else if (escaped === '&') {\n            return { type: 'string', value: '&' };\n          } else if (escaped === '~') {\n            return { type: 'string', value: '~' };\n          } else if (/[0-9]/.test(escaped)) {\n            return { type: 'capture_group', group: Number.parseInt(escaped, 10) };\n          } else if (escaped === 'u') {\n            return { type: 'change_case', case: 'upper', duration: 'char' };\n          } else if (escaped === 'l') {\n            return { type: 'change_case', case: 'lower', duration: 'char' };\n          } else if (escaped === 'U') {\n            return { type: 'change_case', case: 'upper', duration: 'until_end' };\n          } else if (escaped === 'L') {\n            return { type: 'change_case', case: 'lower', duration: 'until_end' };\n          } else if (escaped === 'e' || escaped === 'E') {\n            return { type: 'change_case_end' };\n          } else {\n            return { type: 'string', value: `\\\\${escaped}` };\n          }\n        }),\n      ),\n      string('&').result({ type: 'capture_group', group: '&' }),\n      string('~').result({ type: 'prev_replace_string' }),\n      noneOf(delimiter).map((value) => ({ type: 'string', value })),\n    ).many(),\n  ).map((components) => new ReplaceString(components));\n\nconst substituteFlagsParser: Parser<SubstituteFlags> = seq(\n  string('&').fallback(undefined),\n  oneOf('cegiInp#lr').many(),\n).map(([amp, flagChars]) => {\n  const flags: SubstituteFlags = {};\n  if (amp === '&') {\n    flags.keepPreviousFlags = true;\n  }\n  for (const flag of flagChars) {\n    switch (flag) {\n      case 'c':\n        flags.confirmEach = true;\n        break;\n      case 'e':\n        flags.suppressError = true;\n        break;\n      case 'g':\n        flags.replaceAll = true;\n        break;\n      case 'i':\n        flags.ignoreCase = true;\n        break;\n      case 'I':\n        flags.noIgnoreCase = true;\n        break;\n      case 'n':\n        flags.printCount = true;\n        break;\n      case 'p':\n        flags.printLastMatchedLine = true;\n        break;\n      case '#':\n        flags.printLastMatchedLineWithNumber = true;\n        break;\n      case 'l':\n        flags.printLastMatchedLineWithList = true;\n        break;\n      case 'r':\n        flags.usePreviousPattern = true;\n        break;\n    }\n  }\n  return flags;\n});\n\nconst countParser: Parser<number | undefined> = optWhitespace\n  .then(numberParser)\n  .fallback(undefined);\n\n/**\n * vim has a distinctly different state for previous search and for previous substitute.  However, in SOME\n * cases a substitution will also update the search state along with the substitute state.\n *\n * Also, the substitute command itself will sometimes use the search state, and other times it will use the\n * substitute state.\n *\n * These are the following cases and how vim handles them:\n * 1. :s/this/that\n *   - standard search/replace\n *   - update substitution state\n *   - update search state too!\n * 2. :s or :s [flags]\n *   - use previous SUBSTITUTION state, and repeat previous substitution pattern and replace.\n *   - do not touch search state!\n *   - changing substitution state is dont-care cause we're repeating it ;)\n * 3. :s/ or :s// or :s///\n *   - use previous SEARCH state (not substitution), and DELETE the string matching the pattern (replace with nothing)\n *   - update substitution state\n *   - updating search state is dont-care cause we're repeating it ;)\n * 4. :s/this or :s/this/ or :s/this//\n *   - input is pattern - replacement is empty (delete)\n *   - update replacement state\n *   - update search state too!\n */\nexport class SubstituteCommand extends ExCommand {\n  public static readonly argParser: Parser<SubstituteCommand> = optWhitespace.then(\n    alt(\n      // :s[ubstitute]/{pattern}/{string}/[flags] [count]\n      regexp(/[^\\w\\s\\\\|\"]{1}/).chain((delimiter) =>\n        seq(\n          Pattern.parser({ direction: SearchDirection.Forward, delimiter }),\n          replaceStringParser(delimiter),\n          string(delimiter).then(substituteFlagsParser).fallback({}),\n          countParser,\n        ).map(\n          ([pattern, replace, flags, count]) =>\n            new SubstituteCommand({ pattern, replace, flags, count }),\n        ),\n      ),\n\n      // :s[ubstitute] [flags] [count]\n      seq(substituteFlagsParser, countParser).map(\n        ([flags, count]) =>\n          new SubstituteCommand({\n            pattern: undefined,\n            replace: new ReplaceString([]),\n            flags,\n            count,\n          }),\n      ),\n    ),\n  );\n\n  public readonly arguments: ISubstituteCommandArguments;\n  protected abort: boolean;\n  private cSearchHighlights?: DecorationOptions[];\n  private confirmedSubstitutions?: DecorationOptions[];\n  constructor(args: ISubstituteCommandArguments) {\n    super();\n    this.arguments = args;\n    this.abort = false;\n  }\n\n  public override neovimCapable(): boolean {\n    // We need to use VSCode's quickpick capabilities to do confirmation\n    return !this.arguments.flags.confirmEach;\n  }\n\n  public getSubstitutionDecorations(\n    vimState: VimState,\n    lineRange = new LineRange(new Address({ type: 'current_line' })),\n  ): SearchDecorations {\n    const substitutionAppend: DecorationOptions[] = [];\n    const substitutionReplace: DecorationOptions[] = [];\n    const searchHighlight: DecorationOptions[] = [];\n\n    const subsArr: DecorationOptions[] =\n      configuration.inccommand === 'replace' ? substitutionReplace : substitutionAppend;\n\n    const { pattern, replace } = this.resolvePatterns(false);\n\n    const showReplacements =\n      this.arguments.pattern?.closed &&\n      configuration.inccommand &&\n      !this.arguments.flags.printCount;\n\n    let matches: PatternMatch[] = [];\n    if (pattern?.patternString) {\n      matches = pattern.allMatches(vimState, { lineRange });\n    }\n\n    const global =\n      (configuration.gdefault || configuration.substituteGlobalFlag) !==\n      (this.arguments.flags.replaceAll ?? false);\n    const lines = new Set<number>();\n\n    for (const match of matches) {\n      if (!global && lines.has(match.range.start.line)) {\n        // If not global, only replace one match per line\n        continue;\n      }\n\n      lines.add(match.range.start.line);\n      if (showReplacements) {\n        const contentText = formatDecorationText(\n          replace.resolve(vimState, match.groups),\n          vimState.editor.options.tabSize as number,\n        );\n\n        subsArr.push({\n          range: match.range,\n          renderOptions: {\n            [configuration.inccommand === 'append' ? 'after' : 'before']: { contentText },\n          },\n        });\n      } else {\n        searchHighlight.push(ensureVisible(match.range, vimState.document));\n      }\n    }\n    return { substitutionAppend, substitutionReplace, searchHighlight };\n  }\n\n  /**\n   * @returns If match, (# newlines added) - (# newlines removed)\n   *          Else, undefined\n   */\n  private async replaceMatchRange(\n    vimState: VimState,\n    match: PatternMatch,\n  ): Promise<number | undefined> {\n    if (this.arguments.flags.printCount) {\n      return 0;\n    }\n\n    const replaceText = this.arguments.replace.resolve(vimState, match.groups);\n\n    if (this.arguments.flags.confirmEach) {\n      if (await this.confirmReplacement(vimState, match, replaceText)) {\n        vimState.recordedState.transformer.replace(match.range, replaceText);\n      } else {\n        return undefined;\n      }\n    } else {\n      vimState.recordedState.transformer.replace(match.range, replaceText);\n    }\n\n    const addedNewlines = replaceText.split('\\n').length - 1;\n    const removedNewlines = match.groups[0].split('\\n').length - 1;\n    return addedNewlines - removedNewlines;\n  }\n\n  private async confirmReplacement(\n    vimState: VimState,\n    match: PatternMatch,\n    replaceText: string,\n  ): Promise<boolean> {\n    const cancellationToken = new CancellationTokenSource();\n    const validSelections: readonly string[] = ['y', 'n', 'a', 'q', 'l'];\n    let selection: string = '';\n    const prompt = escapeCSSIcons(\n      `Replace with ${formatDecorationText(\n        replaceText,\n        vimState.editor.options.tabSize as number,\n        '\\\\n',\n      )} (${validSelections.join('/')})?`,\n    );\n\n    const newConfirmationSearchHighlights =\n      this.cSearchHighlights?.filter((d) => !d.range.isEqual(match.range)) ?? [];\n\n    vimState.editor.revealRange(new Range(match.range.start.line, 0, match.range.start.line, 0));\n    vimState.editor.setDecorations(decoration.searchHighlight, newConfirmationSearchHighlights);\n    vimState.editor.setDecorations(decoration.searchMatch, [\n      ensureVisible(match.range, vimState.document),\n    ]);\n    vimState.editor.setDecorations(\n      decoration.confirmedSubstitution,\n      this.confirmedSubstitutions ?? [],\n    );\n    await window.showInputBox(\n      {\n        ignoreFocusOut: true,\n        prompt,\n        placeHolder: validSelections.join('/'),\n        validateInput: (input: string): string => {\n          if (validSelections.includes(input)) {\n            selection = input;\n            cancellationToken.cancel();\n          }\n          return prompt;\n        },\n      },\n      cancellationToken.token,\n    );\n\n    if (selection === 'q' || selection === 'l' || !selection) {\n      this.abort = true;\n    } else if (selection === 'a') {\n      this.arguments.flags.confirmEach = undefined;\n    }\n\n    if (selection === 'y' || selection === 'a' || selection === 'l') {\n      if (this.cSearchHighlights) {\n        this.cSearchHighlights = newConfirmationSearchHighlights;\n      }\n\n      this.confirmedSubstitutions?.push({\n        range: match.range,\n        renderOptions: {\n          before: {\n            contentText: formatDecorationText(\n              replaceText,\n              vimState.editor.options.tabSize as number,\n            ),\n          },\n        },\n      });\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * @returns the concrete Pattern and ReplaceString to be used for this substitution.\n   * If throwErrors is true, errors will be thrown :)\n   */\n  private resolvePatterns(throwErrors: boolean): {\n    pattern: Pattern | undefined;\n    replace: ReplaceString;\n  } {\n    let { pattern, replace } = this.arguments;\n    if (pattern === undefined) {\n      // If no pattern is entered, use previous SUBSTITUTION state and don't update search state\n      // i.e. :s\n      const prevSubstituteState = globalState.substituteState;\n      if (\n        prevSubstituteState?.searchPattern === undefined ||\n        prevSubstituteState.searchPattern.patternString === ''\n      ) {\n        if (throwErrors) {\n          throw VimError.NoPreviousSubstituteRegularExpression();\n        }\n      } else {\n        pattern = prevSubstituteState.searchPattern;\n        replace = prevSubstituteState.replaceString;\n      }\n    } else {\n      if (pattern.patternString === '') {\n        // If an explicitly empty pattern is entered, use previous search state (including search with * and #) and update both states\n        // e.g :s/ or :s///\n        const prevSearchState = globalState.searchState;\n        if (prevSearchState === undefined || prevSearchState.searchString === '') {\n          if (throwErrors) {\n            throw VimError.NoPreviousRegularExpression();\n          }\n        } else {\n          pattern = prevSearchState.pattern;\n        }\n      }\n    }\n    return { pattern, replace };\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await this.executeWithRange(vimState, new LineRange(new Address({ type: 'current_line' })));\n  }\n\n  override async executeWithRange(vimState: VimState, lineRange: LineRange): Promise<void> {\n    let { start, end } = lineRange.resolve(vimState);\n\n    if (this.arguments.count && this.arguments.count >= 0) {\n      start = end;\n      end = end + this.arguments.count - 1;\n    }\n\n    // TODO: this is all a bit gross\n    const { pattern, replace } = this.resolvePatterns(true);\n    this.arguments.replace = replace;\n\n    // `/g` flag inverts the default behavior (from `gdefault`)\n    const global =\n      (configuration.gdefault || configuration.substituteGlobalFlag) !==\n      (this.arguments.flags.replaceAll ?? false);\n\n    // TODO: `allMatches` lies for patterns with empty branches, which makes this wrong (not that anyone cares)\n    const allMatches =\n      pattern?.allMatches(vimState, {\n        // TODO: This method should probably take start/end lines as numbers\n        lineRange: new LineRange(\n          new Address({ type: 'number', num: start + 1 }),\n          ',',\n          new Address({ type: 'number', num: end + 1 }),\n        ),\n      }) ?? [];\n\n    let replaceableMatches;\n    if (global) {\n      // every match is replaceable\n      replaceableMatches = allMatches;\n    } else {\n      // only the first match on a line is replaceable\n      const replaceableLines = new Set<number>();\n      replaceableMatches = allMatches.filter((match) => {\n        if (replaceableLines.has(match.range.start.line)) {\n          return false;\n        }\n        replaceableLines.add(match.range.start.line);\n        return true;\n      });\n    }\n\n    if (this.arguments.flags.confirmEach) {\n      vimState.editor.setDecorations(decoration.substitutionAppend, []);\n      vimState.editor.setDecorations(decoration.substitutionReplace, []);\n\n      if (configuration.inccommand) {\n        this.confirmedSubstitutions = [];\n      }\n      if (configuration.incsearch) {\n        this.cSearchHighlights = replaceableMatches.map((match) =>\n          ensureVisible(match.range, vimState.document),\n        );\n      }\n    }\n\n    const substitutionLines = new Set<number>();\n    let substitutions = 0;\n    let netNewLines = 0;\n\n    for (const match of replaceableMatches) {\n      if (this.abort) {\n        break;\n      }\n\n      const newLines = await this.replaceMatchRange(vimState, match);\n      if (newLines !== undefined) {\n        substitutions++;\n        substitutionLines.add(match.range.start.line);\n        netNewLines += newLines;\n      }\n    }\n\n    if (substitutions > 0 && !this.arguments.flags.printCount) {\n      // if any substitutions were made, jump to latest one\n      const lastLine = Math.max(...substitutionLines.values()) + netNewLines;\n      const cursor = new Position(Math.max(0, lastLine), 0);\n      globalState.jumpTracker.recordJump(\n        new Jump({\n          document: vimState.document,\n          position: cursor,\n        }),\n        Jump.fromStateNow(vimState),\n      );\n      vimState.recordedState.transformer.moveCursor(PositionDiff.exactPosition(cursor), 0);\n    }\n\n    this.confirmedSubstitutions = undefined;\n    this.cSearchHighlights = undefined;\n    vimState.editor.setDecorations(decoration.confirmedSubstitution, []);\n\n    this.setStatusBarText(vimState, substitutions, substitutionLines.size);\n\n    if (this.arguments.pattern !== undefined) {\n      globalState.substituteState = new SubstituteState(pattern, replace);\n      globalState.searchState = new SearchState(\n        SearchDirection.Forward,\n        vimState.cursorStopPosition,\n        pattern?.patternString,\n        {},\n      );\n    }\n  }\n\n  private setStatusBarText(vimState: VimState, substitutions: number, lines: number) {\n    if (substitutions === 0) {\n      StatusBar.displayError(\n        vimState,\n        VimError.PatternNotFound(this.arguments.pattern?.patternString),\n      );\n    } else if (this.arguments.flags.printCount) {\n      StatusBar.setText(\n        vimState,\n        `${substitutions} match${substitutions > 1 ? 'es' : ''} on ${lines} line${\n          lines > 1 ? 's' : ''\n        }`,\n      );\n    } else if (substitutions > configuration.report) {\n      StatusBar.setText(\n        vimState,\n        `${substitutions} substitution${substitutions > 1 ? 's' : ''} on ${lines} line${\n          lines > 1 ? 's' : ''\n        }`,\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/tab.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { all, alt, optWhitespace, seq, string, whitespace } from 'parsimmon';\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { findTabInActiveTabGroup } from '../../util/util';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport {\n  FileCmd,\n  FileOpt,\n  bangParser,\n  fileCmdParser,\n  fileNameParser,\n  fileOptParser,\n  numberParser,\n} from '../../vimscript/parserUtils';\n\nexport enum TabCommandType {\n  Next,\n  Previous,\n  First,\n  Last,\n  Edit,\n  New,\n  Close,\n  Only,\n  Move,\n}\n\n// TODO: many of these arguments aren't used\nexport type ITabCommandArguments =\n  | {\n      type: TabCommandType.Edit;\n      cmd?: FileCmd;\n      buf: string | number;\n    }\n  | {\n      type: TabCommandType.First | TabCommandType.Last;\n      cmd?: FileCmd;\n    }\n  | {\n      type: TabCommandType.Next | TabCommandType.Previous;\n      bang: boolean;\n      cmd?: FileCmd;\n      count?: number;\n    }\n  | {\n      type: TabCommandType.Close | TabCommandType.Only;\n      bang: boolean;\n      count?: number;\n    }\n  | {\n      type: TabCommandType.New;\n      opt: FileOpt;\n      cmd?: FileCmd;\n      file?: string;\n    }\n  | {\n      type: TabCommandType.Move;\n      direction?: 'left' | 'right';\n      count?: number;\n    };\n\n//\n//  Implements most buffer and tab ex commands\n//  http://vimdoc.sourceforge.net/htmldoc/tabpage.html\n//\nexport class TabCommand extends ExCommand {\n  // TODO: `count` is parsed as a number, which is incomplete\n  public static readonly argParsers = {\n    bfirst: whitespace\n      .then(fileCmdParser)\n      .fallback(undefined)\n      .map((cmd) => {\n        return new TabCommand({ type: TabCommandType.First, cmd });\n      }),\n    blast: whitespace\n      .then(fileCmdParser)\n      .fallback(undefined)\n      .map((cmd) => {\n        return new TabCommand({ type: TabCommandType.Last, cmd });\n      }),\n    bnext: seq(\n      bangParser,\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(numberParser).fallback(undefined),\n    ).map(([bang, cmd, count]) => {\n      return new TabCommand({ type: TabCommandType.Next, bang, cmd, count });\n    }),\n    bprev: seq(\n      bangParser,\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(numberParser).fallback(undefined),\n    ).map(([bang, cmd, count]) => {\n      return new TabCommand({ type: TabCommandType.Previous, bang, cmd, count });\n    }),\n    buffer: seq(\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      alt<number | string>(\n        optWhitespace.then(numberParser),\n        optWhitespace.then(fileNameParser),\n      ).fallback(undefined),\n    ).map(([cmd, arg]) => {\n      return new TabCommand({ type: TabCommandType.Edit, cmd, buf: arg ?? 0 });\n    }),\n    tabclose: seq(bangParser, optWhitespace.then(numberParser).fallback(undefined)).map(\n      ([bang, count]) => {\n        return new TabCommand({ type: TabCommandType.Close, bang, count });\n      },\n    ),\n    tabonly: seq(bangParser, optWhitespace.then(numberParser).fallback(undefined)).map(\n      ([bang, count]) => {\n        return new TabCommand({ type: TabCommandType.Only, bang, count });\n      },\n    ),\n    tabnew: seq(\n      optWhitespace.then(fileOptParser).fallback([]),\n      optWhitespace.then(fileCmdParser).fallback(undefined),\n      optWhitespace.then(fileNameParser).fallback(undefined),\n    ).map(([opt, cmd, file]) => {\n      return new TabCommand({\n        type: TabCommandType.New,\n        opt,\n        cmd,\n        file,\n      });\n    }),\n    tabmove: optWhitespace\n      .then(\n        seq(\n          alt(string('+'), string('-')).fallback(undefined),\n          numberParser.fallback(undefined),\n          all,\n        ),\n      )\n      .map(([plusminus, count, trailing]) => {\n        if (trailing || (plusminus && count === 0)) {\n          throw VimError.InvalidArgument475((plusminus ?? '') + (count ?? '') + trailing);\n        }\n        const direction = plusminus === '+' ? 'right' : plusminus === '-' ? 'left' : undefined;\n        return new TabCommand({\n          type: TabCommandType.Move,\n          direction,\n          count,\n        });\n      }),\n  };\n\n  public readonly arguments: ITabCommandArguments;\n  constructor(args: ITabCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  private async executeCommandWithCount(count: number, command: string): Promise<void> {\n    for (let i = 0; i < count; i++) {\n      await vscode.commands.executeCommand(command);\n    }\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    switch (this.arguments.type) {\n      case TabCommandType.Edit:\n        if (\n          this.arguments.buf !== undefined &&\n          typeof this.arguments.buf === 'number' &&\n          this.arguments.buf >= 0\n        ) {\n          await vscode.commands.executeCommand(\n            'workbench.action.openEditorAtIndex',\n            this.arguments.buf - 1,\n          );\n        } else if (this.arguments.buf !== undefined && typeof this.arguments.buf === 'string') {\n          const [idx, tab] = findTabInActiveTabGroup(this.arguments.buf);\n          if ((tab.input as vscode.TextDocument).uri !== undefined) {\n            const uri = (tab.input as vscode.TextDocument).uri;\n            await vscode.commands.executeCommand('vscode.open', uri);\n          }\n        }\n        break;\n      case TabCommandType.Next:\n        if (this.arguments.count !== undefined && this.arguments.count <= 0) {\n          break;\n        }\n\n        if (this.arguments.count) {\n          const tabGroup = vscode.window.tabGroups.activeTabGroup;\n          if (0 < this.arguments.count && this.arguments.count <= tabGroup.tabs.length) {\n            const tab = tabGroup.tabs[this.arguments.count - 1];\n            if ((tab.input as vscode.TextDocument).uri !== undefined) {\n              const uri = (tab.input as vscode.TextDocument).uri;\n              await vscode.commands.executeCommand('vscode.open', uri);\n            }\n          }\n        } else {\n          await vscode.commands.executeCommand('workbench.action.nextEditorInGroup');\n        }\n\n        break;\n      case TabCommandType.Previous:\n        if (this.arguments.count !== undefined && this.arguments.count <= 0) {\n          break;\n        }\n\n        await this.executeCommandWithCount(\n          this.arguments.count || 1,\n          'workbench.action.previousEditorInGroup',\n        );\n        break;\n      case TabCommandType.First:\n        await vscode.commands.executeCommand('workbench.action.openEditorAtIndex1');\n        break;\n      case TabCommandType.Last:\n        await vscode.commands.executeCommand('workbench.action.lastEditorInGroup');\n        break;\n      case TabCommandType.New: {\n        const hasFile = !(this.arguments.file === undefined || this.arguments.file === '');\n        if (hasFile) {\n          const isAbsolute = path.isAbsolute(this.arguments.file!);\n          const currentFilePath = vscode.window.activeTextEditor!.document.uri.fsPath;\n          const isInWorkspace =\n            vscode.workspace.workspaceFolders !== undefined &&\n            vscode.workspace.workspaceFolders.length > 0;\n\n          let toOpenPath: string;\n          if (isAbsolute) {\n            toOpenPath = this.arguments.file!;\n          } else if (isInWorkspace) {\n            const workspacePath = vscode.workspace.workspaceFolders![0].uri.path;\n            if (currentFilePath.startsWith(workspacePath)) {\n              toOpenPath = path.join(path.dirname(currentFilePath), this.arguments.file!);\n            } else {\n              toOpenPath = path.join(workspacePath, this.arguments.file!);\n            }\n          } else {\n            toOpenPath = path.join(path.dirname(currentFilePath), this.arguments.file!);\n          }\n\n          if (toOpenPath !== currentFilePath) {\n            await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(toOpenPath));\n          }\n        } else {\n          await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');\n        }\n        break;\n      }\n      case TabCommandType.Close:\n        // Navigate the correct position\n        if (this.arguments.count === undefined) {\n          await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n          break;\n        }\n\n        if (this.arguments.count === 0) {\n          // Wrong paramter\n          break;\n        }\n\n        // TODO: Close Page {count}. Page count is one-based.\n        break;\n      case TabCommandType.Only:\n        await vscode.commands.executeCommand('workbench.action.closeOtherEditors');\n        break;\n      case TabCommandType.Move: {\n        const { count, direction } = this.arguments;\n        let args;\n        if (direction !== undefined) {\n          args = { to: direction, by: 'tab', value: count ?? 1 };\n        } else if (count === 0) {\n          args = { to: 'first' };\n        } else if (count === undefined) {\n          args = { to: 'last' };\n        } else {\n          args = { to: 'position', by: 'tab', value: count + 1 };\n        }\n        await vscode.commands.executeCommand('moveActiveEditor', args);\n        break;\n      }\n      default:\n        break;\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/terminal.ts",
    "content": "import { Parser, succeed } from 'parsimmon';\nimport * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class TerminalCommand extends ExCommand {\n  public static readonly argParser: Parser<TerminalCommand> = succeed(new TerminalCommand());\n\n  async execute(vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand('workbench.action.createTerminalEditor');\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/undo.ts",
    "content": "import { optWhitespace, Parser } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { Undo } from '../../actions/commands/undo';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { numberParser } from '../../vimscript/parserUtils';\n\n//\n//  Implements :u[ndo]\n//  http://vimdoc.sourceforge.net/htmldoc/undo.html\n//\nexport class UndoCommand extends ExCommand {\n  public static readonly argParser: Parser<UndoCommand> = optWhitespace\n    .then(numberParser)\n    .fallback(undefined)\n    .map((count) => new UndoCommand(count));\n\n  private count?: number;\n  private constructor(count?: number) {\n    super();\n    this.count = count;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO: Use `this.count`\n    await new Undo().exec(new Position(0, 0), vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/vscode.ts",
    "content": "import { all, Parser, whitespace } from 'parsimmon';\nimport * as vscode from 'vscode';\nimport { VimError } from '../../error';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\n\nexport class VsCodeCommand extends ExCommand {\n  public static readonly argParser: Parser<VsCodeCommand> = whitespace\n    .then(all)\n    .map((command) => new VsCodeCommand(command));\n\n  private command: string;\n\n  public constructor(command: string) {\n    super();\n    this.command = command;\n    if (!this.command) {\n      throw VimError.ArgumentRequired();\n    }\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    await vscode.commands.executeCommand(this.command);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/wall.ts",
    "content": "import { Parser } from 'parsimmon';\nimport * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser } from '../../vimscript/parserUtils';\n\n//\n//  Implements :wall (write all)\n//  http://vimdoc.sourceforge.net/htmldoc/editing.html#:wall\n//\nexport class WallCommand extends ExCommand {\n  public static readonly argParser: Parser<WallCommand> = bangParser.map(\n    (bang) => new WallCommand(bang),\n  );\n\n  private readonly bang: boolean;\n  constructor(bang?: boolean) {\n    super();\n    this.bang = bang ?? false;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO : overwrite readonly files when bang? == true\n    await vscode.workspace.saveAll(false);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/write.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { all, alt, optWhitespace, Parser, seq, string } from 'parsimmon';\nimport * as path from 'path';\nimport * as fs from 'platform/fs';\nimport * as vscode from 'vscode';\nimport { VimState } from '../../state/vimState';\nimport { StatusBar } from '../../statusBar';\nimport { Logger } from '../../util/logger';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser, fileNameParser, FileOpt, fileOptParser } from '../../vimscript/parserUtils';\n\nexport type IWriteCommandArguments = {\n  bang: boolean;\n  opt: FileOpt;\n  bgWrite: boolean;\n  file?: string;\n  cmd?: string;\n};\n\n//\n//  Implements :write\n//  http://vimdoc.sourceforge.net/htmldoc/editing.html#:write\n//\nexport class WriteCommand extends ExCommand {\n  public static readonly argParser: Parser<WriteCommand> = seq(\n    bangParser.skip(optWhitespace),\n    fileOptParser.skip(optWhitespace),\n    alt<{ cmd: string } | { file: string }>(\n      string('!')\n        .then(all)\n        .map((cmd) => {\n          return { cmd };\n        }),\n      fileNameParser.map((file) => {\n        return { file };\n      }),\n      // TODO: Support `:help :w_a` ('>>')\n    ).fallback({}),\n  ).map(([bang, opt, other]) => new WriteCommand({ bang, opt, bgWrite: true, ...other }));\n\n  public readonly arguments: IWriteCommandArguments;\n\n  constructor(args: IWriteCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    // TODO: Use arguments: opt, file, cmd\n\n    // If the file isn't on disk because it's brand new or on a remote file system, let VS Code handle it\n    if (vimState.document.isUntitled || vimState.document.uri.scheme !== 'file') {\n      await this.background(vscode.commands.executeCommand('workbench.action.files.save'));\n      return;\n    }\n\n    try {\n      if (this.arguments.file) {\n        await this.saveAs(vimState, this.arguments.file);\n      } else {\n        await fs.accessAsync(vimState.document.fileName, fs.constants.W_OK);\n        await this.save(vimState);\n      }\n    } catch (accessErr) {\n      if (this.arguments.bang) {\n        try {\n          const mode = await fs.getMode(vimState.document.fileName);\n          await fs.chmodAsync(vimState.document.fileName, 0o666);\n          // We must do a foreground write so we can await the save\n          // and chmod the file back to its original state\n          this.arguments.bgWrite = false;\n          await this.save(vimState);\n          await fs.chmodAsync(vimState.document.fileName, mode);\n        } catch (e) {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access\n          StatusBar.setText(vimState, e.message);\n        }\n      } else {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access\n        StatusBar.setText(vimState, accessErr.message);\n      }\n    }\n  }\n\n  // TODO: Aparentemente foi tudo, claro que falta alguns ERRORS e bla bla bla tipo o do :w 8/ E212 mas fds o E357 sobrepoe\n  // TODO: fazer PR (#1876)\n  private async saveAs(vimState: VimState, fileName: string): Promise<void> {\n    try {\n      const filePath = path.resolve(path.dirname(vimState.document.fileName), fileName);\n      const fileExists = await fs.existsAsync(filePath);\n      const uri = vscode.Uri.file(path.resolve(path.dirname(vimState.document.fileName), filePath));\n      // An extension to the file must be specified.\n      if (path.extname(filePath) === '') {\n        StatusBar.setText(vimState, `E357: The file extension must be specified`, true);\n        return;\n      }\n      // Checks if the file exists.\n      if (fileExists) {\n        const stats = await vscode.workspace.fs.stat(uri);\n        const isDirectory = stats.type === vscode.FileType.Directory;\n        // If it's a directory, throw an error.\n        if (isDirectory) {\n          StatusBar.setText(vimState, `E17: \"${filePath}\" is a directory`, true);\n          return;\n        }\n        // Create a pop-up asking if user wants to overwrite the file.\n        const confirmOverwrite = await vscode.window.showWarningMessage(\n          `File \"${fileName}\" already exists. Do you want to overwrite it?`,\n          { modal: true },\n          'Yes',\n          'No',\n        );\n\n        if (confirmOverwrite === 'No') {\n          return;\n        }\n      }\n\n      // Create a new file in 'filePath', appending the current's file content to it.\n      await vscode.window.showTextDocument(vimState.document, { preview: false });\n      await vscode.commands.executeCommand('workbench.action.files.save', uri);\n      await vscode.workspace.fs.copy(vimState.document.uri, uri, { overwrite: true });\n\n      StatusBar.setText(\n        vimState,\n        `\"${fileName}\" ${fileExists ? '' : '[New]'} ${vimState.document.lineCount}L ${\n          vimState.document.getText().length\n        }C written`,\n      );\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access\n      StatusBar.setText(vimState, e.message);\n    }\n  }\n\n  private async save(vimState: VimState): Promise<void> {\n    if (this.shouldShowDocument(vimState.document.uri)) {\n      await vscode.window.showTextDocument(vimState.document, { preview: false });\n    }\n\n    await this.background(\n      vimState.document.save().then((success) => {\n        if (success) {\n          StatusBar.setText(\n            vimState,\n            `\"${path.basename(vimState.document.fileName)}\" ${vimState.document.lineCount}L ${\n              vimState.document.getText().length\n            }C written`,\n          );\n        } else {\n          Logger.warn(':w failed');\n          // TODO: What's the right thing to do here?\n        }\n      }),\n    );\n  }\n\n  /**\n   * Determines whether to call showTextDocument when saving.\n   * Avoids disrupting diff views and handles preview tab pinning.\n   */\n  private shouldShowDocument(documentUri: vscode.Uri): boolean {\n    const uriString = documentUri.toString();\n    const matchingTab = vscode.window.tabGroups.activeTabGroup.tabs.find((tab: vscode.Tab) =>\n      this.tabContainsDocument(tab, uriString),\n    );\n\n    if (matchingTab) {\n      return matchingTab.isPreview;\n    }\n\n    // No matching tab found, show it\n    return true;\n  }\n\n  /**\n   * Check if a tab contains the specified document URI.\n   * Handles regular tabs and diff tabs.\n   */\n  private tabContainsDocument(tab: vscode.Tab, uriString: string): boolean {\n    const input = tab.input as\n      | { uri?: vscode.Uri; original?: vscode.Uri; modified?: vscode.Uri }\n      | undefined;\n\n    return (\n      input?.uri?.toString() === uriString ||\n      input?.original?.toString() === uriString ||\n      input?.modified?.toString() === uriString\n    );\n  }\n\n  private async background<T>(fn: Thenable<T>): Promise<void> {\n    if (!this.arguments.bgWrite) {\n      await fn;\n    }\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/writequit.ts",
    "content": "import { optWhitespace, Parser, seq } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser, fileNameParser, FileOpt, fileOptParser } from '../../vimscript/parserUtils';\nimport { QuitCommand } from './quit';\nimport { WriteCommand } from './write';\n\n//\n// Implements :writequit\n// http://vimdoc.sourceforge.net/htmldoc/editing.html#write-quit\n//\nexport interface IWriteQuitCommandArguments {\n  bang: boolean;\n  opt: FileOpt;\n  file?: string;\n}\n\nexport class WriteQuitCommand extends ExCommand {\n  public static readonly argParser: Parser<WriteQuitCommand> = seq(\n    bangParser.skip(optWhitespace),\n    fileOptParser.skip(optWhitespace),\n    fileNameParser.fallback(undefined),\n  ).map(([bang, opt, file]) => new WriteQuitCommand(file ? { bang, opt, file } : { bang, opt }));\n\n  private readonly args: IWriteQuitCommandArguments;\n  constructor(args: IWriteQuitCommandArguments) {\n    super();\n    this.args = args;\n  }\n\n  // Writing command. Taken as a basis from the \"write.ts\" file.\n  async execute(vimState: VimState): Promise<void> {\n    await new WriteCommand({ bgWrite: false, ...this.args }).execute(vimState);\n\n    await new QuitCommand({\n      // wq! fails when no file name is provided\n      bang: false,\n    }).execute(vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/writequitall.ts",
    "content": "import { Parser, seq, whitespace } from 'parsimmon';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { bangParser, FileOpt, fileOptParser } from '../../vimscript/parserUtils';\nimport * as wall from '../commands/wall';\nimport * as quit from './quit';\n\n//\n// Implements :writequitall\n// http://vimdoc.sourceforge.net/htmldoc/editing.html#:wqall\n//\nexport interface IWriteQuitAllCommandArguments {\n  bang: boolean;\n  fileOpt: FileOpt;\n}\n\nexport class WriteQuitAllCommand extends ExCommand {\n  public static readonly argParser: Parser<WriteQuitAllCommand> = seq(\n    bangParser,\n    whitespace.then(fileOptParser).fallback([]),\n  ).map(([bang, fileOpt]) => new WriteQuitAllCommand({ bang, fileOpt }));\n\n  private readonly arguments: IWriteQuitAllCommandArguments;\n  constructor(args: IWriteQuitAllCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  // Writing command. Taken as a basis from the \"write.ts\" file.\n  async execute(vimState: VimState): Promise<void> {\n    const quitArgs: quit.IQuitCommandArguments = {\n      // wq! fails when no file name is provided\n      bang: false,\n    };\n\n    const wallCmd = new wall.WallCommand(this.arguments.bang);\n    await wallCmd.execute(vimState);\n\n    // TODO: fileOpt is not used\n\n    quitArgs.quitAll = true;\n    const quitCmd = new quit.QuitCommand(quitArgs);\n    await quitCmd.execute(vimState);\n  }\n}\n"
  },
  {
    "path": "src/cmd_line/commands/yank.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { alt, any, optWhitespace, Parser, seq, whitespace } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { YankOperator } from '../../actions/operator';\nimport { RegisterMode } from '../../register/register';\nimport { VimState } from '../../state/vimState';\nimport { ExCommand } from '../../vimscript/exCommand';\nimport { LineRange } from '../../vimscript/lineRange';\nimport { numberParser } from '../../vimscript/parserUtils';\n\nexport interface YankCommandArguments {\n  register?: string;\n  count?: number;\n}\n\nexport class YankCommand extends ExCommand {\n  public static readonly argParser: Parser<YankCommand> = optWhitespace.then(\n    alt(\n      numberParser.map((count) => {\n        return { register: undefined, count };\n      }),\n      // eslint-disable-next-line id-denylist\n      seq(any.fallback(undefined), whitespace.then(numberParser).fallback(undefined)).map(\n        ([register, count]) => {\n          return { register, count };\n        },\n      ),\n    ).map(\n      ({ register, count }) =>\n        new YankCommand({\n          register,\n          count,\n        }),\n    ),\n  );\n\n  private readonly arguments: YankCommandArguments;\n  constructor(args: YankCommandArguments) {\n    super();\n    this.arguments = args;\n  }\n\n  private async yank(vimState: VimState, start: Position, end: Position) {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n    if (this.arguments.register) {\n      vimState.recordedState.registerName = this.arguments.register;\n    }\n\n    const cursorPosition = vimState.cursorStopPosition;\n\n    await new YankOperator().run(vimState, start.getLineBegin(), end.getLineEnd());\n\n    // YankOperator moves the cursor - undo that\n    vimState.cursorStopPosition = cursorPosition;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    const linesToYank = this.arguments.count ?? 1;\n    const startPosition = vimState.cursorStartPosition;\n    const endPosition = linesToYank\n      ? startPosition.getDown(linesToYank - 1).getLineEnd()\n      : vimState.cursorStopPosition;\n    await this.yank(vimState, startPosition, endPosition);\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    /**\n     * If a [cnt] and [range] is specified (e.g. :.+2y3), :yank [cnt] is called from\n     * the end of the [range].\n     * Ex. if two lines are VisualLine highlighted, :<,>y3 will :y3\n     * from the end of the selected lines.\n     */\n    const { start, end } = range.resolve(vimState);\n    if (this.arguments.count) {\n      vimState.cursorStartPosition = new Position(end, 0);\n      await this.execute(vimState);\n      return;\n    }\n\n    await this.yank(vimState, new Position(start, 0), new Position(end, 0));\n  }\n}\n"
  },
  {
    "path": "src/common/matching/matcher.ts",
    "content": "import { Position } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { VimState } from '../../state/vimState';\n\nexport type Pairing = {\n  match: string;\n  isNextMatchForward: boolean;\n  directionless?: boolean;\n};\n\n/**\n * PairMatcher finds the position matching the given character, respecting nested\n * instances of the pair.\n */\nexport class PairMatcher {\n  static pairings: {\n    [key: string]: Pairing;\n  } = {\n    '(': { match: ')', isNextMatchForward: true },\n    '{': { match: '}', isNextMatchForward: true },\n    '[': { match: ']', isNextMatchForward: true },\n    ')': { match: '(', isNextMatchForward: false },\n    '}': { match: '{', isNextMatchForward: false },\n    ']': { match: '[', isNextMatchForward: false },\n\n    // These characters can't be used for \"%\"-based matching, but are still\n    // useful for text objects.\n    // matchesWithPercentageMotion can be overwritten with configuration.matchpairs\n    '<': { match: '>', isNextMatchForward: true },\n    '>': { match: '<', isNextMatchForward: false },\n    // These are useful for deleting closing and opening quotes, but don't seem to negatively\n    // affect how text objects such as `ci\"` work, which was my worry.\n    '\"': { match: '\"', isNextMatchForward: false, directionless: true },\n    \"'\": { match: \"'\", isNextMatchForward: false, directionless: true },\n    '`': { match: '`', isNextMatchForward: false, directionless: true },\n  };\n\n  private static findPairedChar(\n    position: Position,\n    charToFind: string,\n    charToStack: string,\n    stackHeight: number,\n    isNextMatchForward: boolean,\n    vimState: VimState,\n    allowCurrentPosition: boolean,\n  ): Position | undefined {\n    let lineNumber = position.line;\n    const linePosition = position.character;\n    const lineCount = vimState.document.lineCount;\n    const cursorChar = vimState.document.lineAt(position).text[position.character];\n    if (\n      allowCurrentPosition &&\n      vimState.cursorStartPosition.isEqual(vimState.cursorStopPosition) &&\n      cursorChar === charToFind\n    ) {\n      return position;\n    }\n\n    while (PairMatcher.keepSearching(lineNumber, lineCount, isNextMatchForward)) {\n      let lineText = vimState.document.lineAt(lineNumber).text.split('');\n      const originalLineLength = lineText.length;\n      if (lineNumber === position.line) {\n        if (isNextMatchForward) {\n          lineText = lineText.slice(linePosition + 1, originalLineLength);\n        } else {\n          lineText = lineText.slice(0, linePosition);\n        }\n      }\n\n      while (true) {\n        if (lineText.length <= 0 || stackHeight <= -1) {\n          break;\n        }\n\n        let nextChar: string | undefined;\n        if (isNextMatchForward) {\n          nextChar = lineText.shift();\n        } else {\n          nextChar = lineText.pop();\n        }\n\n        if (nextChar === charToStack) {\n          stackHeight++;\n        } else if (nextChar === charToFind) {\n          stackHeight--;\n        } else {\n          continue;\n        }\n      }\n\n      if (stackHeight <= -1) {\n        let pairMemberChar: number;\n        if (isNextMatchForward) {\n          pairMemberChar = Math.max(0, originalLineLength - lineText.length - 1);\n        } else {\n          pairMemberChar = lineText.length;\n        }\n        return new Position(lineNumber, pairMemberChar);\n      }\n\n      if (isNextMatchForward) {\n        lineNumber++;\n      } else {\n        lineNumber--;\n      }\n    }\n    return undefined;\n  }\n\n  private static keepSearching(lineNumber: number, lineCount: number, isNextMatchForward: boolean) {\n    if (isNextMatchForward) {\n      return lineNumber <= lineCount - 1;\n    } else {\n      return lineNumber >= 0;\n    }\n  }\n\n  static getPercentPairing(char: string): Pairing | undefined {\n    for (const pairing of configuration.matchpairs.split(',')) {\n      const components = pairing.split(':');\n      if (components.length === 2) {\n        if (components[0] === char) {\n          return {\n            match: components[1],\n            isNextMatchForward: true,\n          };\n        } else if (components[1] === char) {\n          return {\n            match: components[0],\n            isNextMatchForward: false,\n          };\n        }\n      }\n    }\n    return undefined;\n  }\n\n  static nextPairedChar(\n    position: Position,\n    charToMatch: string,\n    vimState: VimState,\n    allowCurrentPosition: boolean,\n  ): Position | undefined {\n    /**\n     * We do a fairly basic implementation that only tracks the state of the type of\n     * character you're over and its pair (e.g. \"[\" and \"]\"). This is similar to\n     * what Vim does.\n     *\n     * It can't handle strings very well - something like \"|( ')' )\" where | is the\n     * cursor will cause it to go to the ) in the quotes, even though it should skip over it.\n     *\n     * PRs welcomed! (TODO)\n     * Though ideally VSC implements https://github.com/Microsoft/vscode/issues/7177\n     */\n    const pairing = this.pairings[charToMatch];\n\n    if (pairing === undefined || pairing.directionless) {\n      return undefined;\n    }\n\n    const stackHeight = 0;\n    const charToFind = pairing.match;\n    const charToStack = charToMatch;\n\n    return PairMatcher.findPairedChar(\n      position,\n      charToFind,\n      charToStack,\n      stackHeight,\n      pairing.isNextMatchForward,\n      vimState,\n      allowCurrentPosition,\n    );\n  }\n}\n"
  },
  {
    "path": "src/common/matching/quoteMatcher.ts",
    "content": "enum QuoteMatch {\n  Opening,\n  Closing,\n}\n\n/**\n * QuoteMatcher matches quoted strings, respecting escaped quotes (\\\") and friends\n */\nexport class QuoteMatcher {\n  static readonly escapeChar = '\\\\';\n\n  private readonly quoteMap: QuoteMatch[] = [];\n\n  constructor(quote: '\"' | \"'\" | '`', corpus: string) {\n    let openingQuote = true;\n    // Loop over corpus, marking quotes and respecting escape characters.\n    for (let i = 0; i < corpus.length; i++) {\n      if (corpus[i] === QuoteMatcher.escapeChar) {\n        i += 1;\n        continue;\n      }\n      if (corpus[i] === quote) {\n        this.quoteMap[i] = openingQuote ? QuoteMatch.Opening : QuoteMatch.Closing;\n        openingQuote = !openingQuote;\n      }\n    }\n  }\n\n  public surroundingQuotes(cursorIndex: number): [number, number] | undefined {\n    const cursorQuoteType = this.quoteMap[cursorIndex];\n    if (cursorQuoteType === QuoteMatch.Opening) {\n      const closing = this.getNextQuote(cursorIndex);\n      return closing !== undefined ? [cursorIndex, closing] : undefined;\n    } else if (cursorQuoteType === QuoteMatch.Closing) {\n      return [this.getPrevQuote(cursorIndex)!, cursorIndex];\n    } else {\n      const opening = this.getPrevQuote(cursorIndex) ?? this.getNextQuote(cursorIndex);\n\n      if (opening !== undefined) {\n        const closing = this.getNextQuote(opening);\n        if (closing !== undefined) {\n          return [opening, closing];\n        }\n      }\n    }\n\n    return undefined;\n  }\n\n  private getNextQuote(start: number): number | undefined {\n    for (let i = start + 1; i < this.quoteMap.length; i++) {\n      if (this.quoteMap[i] !== undefined) {\n        return i;\n      }\n    }\n\n    return undefined;\n  }\n\n  private getPrevQuote(start: number): number | undefined {\n    for (let i = start - 1; i >= 0; i--) {\n      if (this.quoteMap[i] !== undefined) {\n        return i;\n      }\n    }\n\n    return undefined;\n  }\n}\n"
  },
  {
    "path": "src/common/matching/tagMatcher.ts",
    "content": "import { VimState } from '../../state/vimState';\nimport { TextEditor } from '../../textEditor';\n\ntype Tag = { name: string; type: 'close' | 'open'; startPos: number; endPos: number };\ntype MatchedTag = {\n  tag: string;\n  openingTagStart: number;\n  openingTagEnd: number;\n  closingTagStart: number;\n  closingTagEnd: number;\n};\n\nexport class TagMatcher {\n  // see regexr.com/3t585\n  static TAG_REGEX = /\\<(\\/)?([^\\>\\<\\s\\/]+)(?:[^\\>\\<]*?)(\\/)?\\>/g;\n  static OPEN_FORWARD_SLASH = 1;\n  static TAG_NAME = 2;\n  static CLOSE_FORWARD_SLASH = 3;\n\n  openStart: number | undefined;\n  openEnd: number | undefined;\n  closeStart: number | undefined;\n  closeEnd: number | undefined;\n\n  constructor(corpus: string, position: number, vimState: VimState) {\n    let match = TagMatcher.TAG_REGEX.exec(corpus);\n    const tags: Tag[] = [];\n\n    // Gather all the existing tags.\n    while (match) {\n      // Node is a self closing tag, skip.\n      if (match[TagMatcher.CLOSE_FORWARD_SLASH]) {\n        match = TagMatcher.TAG_REGEX.exec(corpus);\n        continue;\n      }\n\n      tags.push({\n        name: match[TagMatcher.TAG_NAME],\n        type: match[TagMatcher.OPEN_FORWARD_SLASH] ? 'close' : 'open',\n        startPos: match.index,\n        endPos: TagMatcher.TAG_REGEX.lastIndex,\n      });\n\n      match = TagMatcher.TAG_REGEX.exec(corpus);\n    }\n\n    const stack: Tag[] = [];\n    const matchedTags: MatchedTag[] = [];\n\n    for (const tag of tags) {\n      // We have to push on the stack\n      // if it is an open tag.\n      if (tag.type === 'open') {\n        stack.push(tag);\n      } else {\n        // We have an unmatched closing tag,\n        // so try and match it with any existing tag.\n        for (let i = stack.length - 1; i >= 0; i--) {\n          const openNode = stack[i];\n\n          if (openNode.type === 'open' && openNode.name === tag.name) {\n            // A matching tag was found, ignore\n            // any tags that were in between.\n            matchedTags.push({\n              tag: openNode.name,\n              openingTagStart: openNode.startPos,\n              openingTagEnd: openNode.endPos,\n              closingTagStart: tag.startPos,\n              closingTagEnd: tag.endPos,\n            });\n\n            stack.splice(i);\n            break;\n          }\n        }\n      }\n    }\n\n    const firstNonWhitespacePositionOnLine = TextEditor.getFirstNonWhitespaceCharOnLine(\n      vimState.document,\n      vimState.cursorStartPosition.line,\n    );\n\n    /**\n     * This adjustment fixes the following situation:\n     * <foo>\n     * |  <bar>\n     *    test\n     *    </bar>\n     * </foo>\n     * Now in tag matching situations, the tag opening on the cursor line is considered as well\n     * (if there is only whitespace before the tag and the cursor is standing on these whitespaces)\n     */\n    const startPos =\n      vimState.cursorStartPosition.character < firstNonWhitespacePositionOnLine.character\n        ? firstNonWhitespacePositionOnLine\n        : vimState.cursorStartPosition;\n\n    const startPosOffset = vimState.document.offsetAt(startPos);\n    const endPosOffset = position;\n    const tagsSurrounding = matchedTags.filter((n) => {\n      return startPosOffset >= n.openingTagStart && endPosOffset < n.closingTagEnd;\n    });\n\n    if (!tagsSurrounding.length) {\n      return;\n    }\n\n    const nodeSurrounding = this.determineRelevantTag(\n      tagsSurrounding,\n      startPosOffset,\n      vimState.cursorStartPosition.compareTo(vimState.cursorStopPosition) !== 0,\n    );\n\n    if (!nodeSurrounding) {\n      return;\n    }\n\n    this.openStart = nodeSurrounding.openingTagStart;\n    this.closeEnd = nodeSurrounding.closingTagEnd;\n    // if the inner tag content is already selected, expand to enclose tags with 'it' as in vim\n    if (\n      startPosOffset === nodeSurrounding.openingTagEnd &&\n      endPosOffset + 1 === nodeSurrounding.closingTagStart\n    ) {\n      this.openEnd = this.openStart;\n      this.closeStart = this.closeEnd;\n    } else {\n      this.openEnd = nodeSurrounding.openingTagEnd;\n      this.closeStart = nodeSurrounding.closingTagStart;\n    }\n  }\n\n  /**\n   * Most of the time the relevant tag is the innermost tag, but when Visual mode is active,\n   * the rules are different.\n   * When the cursorStart is standing on the < character of the inner tag, with \"at\" we must\n   * jump to the outer tag.\n   */\n  determineRelevantTag(\n    tagsSurrounding: MatchedTag[],\n    adjustedStartPosOffset: number,\n    selectionActive: boolean,\n  ): MatchedTag | undefined {\n    const relevantTag = tagsSurrounding[0];\n\n    if (selectionActive && adjustedStartPosOffset === relevantTag.openingTagStart) {\n      // we adjusted so we have to return the outer tag\n      return tagsSurrounding[1];\n    } else {\n      return relevantTag;\n    }\n  }\n\n  findOpening(inclusive: boolean): number | undefined {\n    if (inclusive) {\n      return this.openStart;\n    }\n    return this.openEnd;\n  }\n\n  findClosing(inclusive: boolean): number | undefined {\n    if (inclusive) {\n      return this.closeEnd;\n    }\n    return this.closeStart;\n  }\n}\n"
  },
  {
    "path": "src/common/motion/cursor.ts",
    "content": "import { Position, Selection, TextDocument } from 'vscode';\n\nexport class Cursor {\n  public readonly start: Position;\n  public readonly stop: Position;\n\n  constructor(start: Position, stop: Position) {\n    this.start = start;\n    this.stop = stop;\n  }\n\n  public static atPosition(position: Position): Cursor {\n    return new Cursor(position, position);\n  }\n\n  /**\n   * Create a Cursor from a VSCode selection.\n   */\n  public static fromSelection(sel: Selection): Cursor {\n    return new Cursor(sel.anchor, sel.active);\n  }\n\n  public isValid(document: TextDocument) {\n    return this.start.isValid(document) && this.stop.isValid(document);\n  }\n\n  public equals(other: Cursor): boolean {\n    return this.start.isEqual(other.start) && this.stop.isEqual(other.stop);\n  }\n\n  /**\n   * Returns a new Cursor which is the same as this Cursor, but with the provided stop value.\n   */\n  public withNewStop(stop: Position): Cursor {\n    return new Cursor(this.start, stop);\n  }\n\n  /**\n   * Returns a new Cursor which is the same as this Cursor, but with the provided start value.\n   */\n  public withNewStart(start: Position): Cursor {\n    return new Cursor(start, this.stop);\n  }\n\n  public toString(): string {\n    return `[${this.start.toString()} | ${this.stop.toString()}]`;\n  }\n\n  public validate(document: TextDocument): Cursor {\n    return new Cursor(document.validatePosition(this.start), document.validatePosition(this.stop));\n  }\n}\n"
  },
  {
    "path": "src/common/motion/position.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { getSentenceBegin, getSentenceEnd } from '../../textobject/sentence';\nimport {\n  WordType,\n  nextWordEnd,\n  nextWordStart,\n  prevWordEnd,\n  prevWordStart,\n} from '../../textobject/word';\nimport { clamp } from '../../util/util';\nimport { configuration } from './../../configuration/configuration';\nimport { TextEditor } from './../../textEditor';\n\n/**\n * Controls how a PositionDiff affects the Position it's applied to.\n */\nenum PositionDiffType {\n  /** Sets both the line and character exactly */\n  ExactPosition,\n  /** Offsets both the line and character */\n  Offset,\n  /** Offsets the line and sets the column exactly */\n  ExactCharacter,\n  /** Brings the Position to the beginning of the line if `vim.startofline` is true */\n  ObeyStartOfLine,\n  /** Brings the Position to the end of the line */\n  EndOfLine,\n}\n\n/**\n * Represents a difference between two Positions.\n * Add it to a Position to get another Position.\n */\nexport class PositionDiff {\n  public readonly line: number;\n  public readonly character: number;\n  public readonly type: PositionDiffType;\n\n  private constructor(type: PositionDiffType, line: number, character: number) {\n    this.type = type;\n    this.line = line;\n    this.character = character;\n  }\n\n  /** Has no effect */\n  public static identity(): PositionDiff {\n    return PositionDiff.offset({ line: 0, character: 0 });\n  }\n\n  /** Offsets both the Position's line and character */\n  public static offset({ line = 0, character = 0 }): PositionDiff {\n    return new PositionDiff(PositionDiffType.Offset, line, character);\n  }\n\n  /** Sets the Position's line and character exactly */\n  public static exactPosition(position: Position): PositionDiff {\n    return new PositionDiff(PositionDiffType.ExactPosition, position.line, position.character);\n  }\n\n  /** Brings the Position to the beginning of the line if `vim.startofline` is true */\n  public static startOfLine(): PositionDiff {\n    return new PositionDiff(PositionDiffType.ObeyStartOfLine, 0, 0);\n  }\n\n  /** Brings the Position to the end of the line */\n  public static endOfLine(): PositionDiff {\n    return new PositionDiff(PositionDiffType.EndOfLine, 0, 0);\n  }\n\n  /** Offsets the Position's line and sets its character exactly */\n  public static exactCharacter({\n    lineOffset,\n    character,\n  }: {\n    lineOffset?: number;\n    character: number;\n  }): PositionDiff {\n    return new PositionDiff(PositionDiffType.ExactCharacter, lineOffset ?? 0, character);\n  }\n\n  public toString(): string {\n    switch (this.type) {\n      case PositionDiffType.Offset:\n        return `[ Diff: Offset ${this.line} ${this.character} ]`;\n      case PositionDiffType.ExactCharacter:\n        return `[ Diff: ExactCharacter ${this.line} ${this.character} ]`;\n      case PositionDiffType.ExactPosition:\n        return `[ Diff: ExactPosition ${this.line} ${this.character} ]`;\n      case PositionDiffType.ObeyStartOfLine:\n        return `[ Diff: ObeyStartOfLine ${this.line} ]`;\n      case PositionDiffType.EndOfLine:\n        return `[ Diff: EndOfLine ${this.line} ]`;\n      default:\n        const guard: never = this.type;\n        throw new Error(`Unknown PositionDiffType: ${this.type}`);\n    }\n  }\n}\n\n/**\n * @returns the Position of the 2 provided which comes earlier in the document.\n */\nexport function earlierOf(p1: Position, p2: Position): Position {\n  return p1.isBefore(p2) ? p1 : p2;\n}\n\n/**\n * @returns the Position of the 2 provided which comes later in the document.\n */\nexport function laterOf(p1: Position, p2: Position): Position {\n  return p1.isBefore(p2) ? p2 : p1;\n}\n\n/**\n * @returns the given Positions in the order they appear in the document.\n */\nexport function sorted(p1: Position, p2: Position): [Position, Position] {\n  return p1.isBefore(p2) ? [p1, p2] : [p2, p1];\n}\n\ndeclare module 'vscode' {\n  interface Position {\n    toString(): string;\n\n    add(document: vscode.TextDocument, diff: PositionDiff, boundsCheck?: boolean): Position;\n    subtract(other: Position): PositionDiff;\n\n    /**\n     * @returns a new Position with the same line and the given character.\n     * Does bounds-checking to make sure the result is valid.\n     * @deprecated use `Position.with` instead\n     */\n    withColumn(column: number): Position;\n\n    /**\n     * @returns the Position `count` characters to the left of this Position. Does not go over line breaks.\n     */\n    getLeft(count?: number): Position;\n    /**\n     * @returns the Position `count` characters to the right of this Position. Does not go over line breaks.\n     */\n    getRight(count?: number): Position;\n    /**\n     * @returns the Position `count` lines down from this Position\n     */\n    getDown(count?: number): Position;\n    /**\n     * @returns the Position `count` lines up from this Position\n     */\n    getUp(count?: number): Position;\n\n    getLeftThroughLineBreaks(includeEol?: boolean): Position;\n    getRightThroughLineBreaks(includeEol?: boolean): Position;\n    getOffsetThroughLineBreaks(offset: number): Position;\n\n    /**\n     * @returns the start of the first word to the left of the current position, like `b`\n     *\n     * @param wordType how word boundaries are determined\n     * @param inclusive if true, returns the current position if it's at the start of a word\n     */\n    prevWordStart(\n      document: vscode.TextDocument,\n      args?: { wordType?: WordType; inclusive?: boolean },\n    ): Position;\n\n    /**\n     * @returns the start of the first word to the right of the current position, like `w`\n     *\n     * @param wordType how word boundaries are determined\n     * @param inclusive if true, returns the current position if it's at the start of a word\n     */\n    nextWordStart(\n      document: vscode.TextDocument,\n      args?: { wordType?: WordType; inclusive?: boolean },\n    ): Position;\n\n    /**\n     * @returns the end of the first word to the left of the current position, like `ge`\n     *\n     * @param wordType how word boundaries are determined\n     */\n    prevWordEnd(document: vscode.TextDocument, args?: { wordType?: WordType }): Position;\n\n    /**\n     * @returns the end of the first word to the right of the current position, like `e`\n     *\n     * @param wordType how word boundaries are determined\n     * @param inclusive if true, returns the current position if it's at the end of a word\n     */\n    nextWordEnd(\n      document: vscode.TextDocument,\n      args?: { wordType?: WordType; inclusive?: boolean },\n    ): Position;\n\n    getSentenceBegin(args: { forward: boolean }): Position;\n    getSentenceEnd(): Position;\n\n    getLineBegin(): Position;\n\n    /**\n     * @returns the beginning of the line, excluding preceeding whitespace.\n     * This respects the `autoindent` setting, and returns `getLineBegin()` if auto-indent is disabled.\n     */\n    getLineBeginRespectingIndent(document: vscode.TextDocument): Position;\n\n    /**\n     * @returns a new Position at the end of this position's line.\n     */\n    getLineEnd(): Position;\n\n    /**\n     * @returns a new Position one to the left if this Position is on the EOL. Otherwise, returns this position.\n     */\n    getLeftIfEOL(): Position;\n\n    /**\n     * @returns the position that the cursor would be at if you pasted *text* at the current position.\n     */\n    advancePositionByText(text: string): Position;\n\n    /**\n     * Is this position at the beginning of the line?\n     */\n    isLineBeginning(): boolean;\n\n    /**\n     * Is this position at the end of the line?\n     */\n    isLineEnd(document: vscode.TextDocument): boolean;\n\n    isFirstWordOfLine(document: vscode.TextDocument): boolean;\n\n    isAtDocumentBegin(): boolean;\n\n    isAtDocumentEnd(document: vscode.TextDocument): boolean;\n\n    /**\n     * Returns whether the current position is in the leading whitespace of a line\n     */\n    isInLeadingWhitespace(document: vscode.TextDocument): boolean;\n\n    /**\n     * If `vim.startofline` is set, get first non-blank character's position.\n     */\n    obeyStartOfLine(document: vscode.TextDocument): Position;\n\n    isValid(document: vscode.TextDocument): boolean;\n  }\n}\n\nPosition.prototype.toString = function (this: Position) {\n  return `[${this.line}, ${this.character}]`;\n};\n\nPosition.prototype.add = function (\n  this: Position,\n  document: vscode.TextDocument,\n  diff: PositionDiff,\n  boundsCheck = true,\n): Position {\n  if (diff.type === PositionDiffType.ExactPosition) {\n    return new Position(diff.line, diff.character);\n  }\n\n  const resultLine = clamp(this.line + diff.line, 0, document.lineCount - 1);\n\n  let resultChar: number;\n  if (diff.type === PositionDiffType.Offset) {\n    resultChar = this.character + diff.character;\n  } else if (diff.type === PositionDiffType.ExactCharacter) {\n    resultChar = diff.character;\n  } else if (diff.type === PositionDiffType.ObeyStartOfLine) {\n    resultChar = this.obeyStartOfLine(document).character;\n  } else if (diff.type === PositionDiffType.EndOfLine) {\n    resultChar = this.getLineEnd().character;\n  } else {\n    throw new Error(`Unknown PositionDiffType: ${diff.type}`);\n  }\n\n  const pos = new Position(resultLine, Math.max(resultChar, 0));\n  return boundsCheck ? document.validatePosition(pos) : pos;\n};\n\nPosition.prototype.subtract = function (this: Position, other: Position): PositionDiff {\n  return PositionDiff.offset({\n    line: this.line - other.line,\n    character: this.character - other.character,\n  });\n};\n\n/**\n * @returns a new Position with the same line and the given character.\n * Does bounds-checking to make sure the result is valid.\n */\nPosition.prototype.withColumn = function (this: Position, column: number): Position {\n  column = clamp(column, 0, TextEditor.getLineLength(this.line));\n  return new Position(this.line, column);\n};\n\n/**\n * @returns the Position `count` characters to the left of this Position. Does not go over line breaks.\n */\nPosition.prototype.getLeft = function (this: Position, count = 1): Position {\n  return new Position(this.line, Math.max(this.character - count, 0));\n};\n\n/**\n * @returns the Position `count` characters to the right of this Position. Does not go over line breaks.\n */\nPosition.prototype.getRight = function (this: Position, count = 1): Position {\n  return new Position(\n    this.line,\n    Math.min(this.character + count, TextEditor.getLineLength(this.line)),\n  );\n};\n\n/**\n * @returns the Position `count` lines down from this Position\n */\nPosition.prototype.getDown = function (this: Position, count = 1): Position {\n  if (vscode.window.activeTextEditor) {\n    const line = Math.min(this.line + count, TextEditor.getLineCount() - 1);\n    return new Position(line, Math.min(this.character, TextEditor.getLineLength(line)));\n  } else {\n    return this.translate({ lineDelta: count });\n  }\n};\n\n/**\n * @returns the Position `count` lines up from this Position\n */\nPosition.prototype.getUp = function (this: Position, count = 1): Position {\n  const line = Math.max(this.line - count, 0);\n  return new Position(line, Math.min(this.character, TextEditor.getLineLength(line)));\n};\n\n/**\n * Same as getLeft, but goes up to the previous line on line breaks.\n * Equivalent to left arrow (in a non-vim editor!)\n */\nPosition.prototype.getLeftThroughLineBreaks = function (\n  this: Position,\n  includeEol = true,\n): Position {\n  if (!this.isLineBeginning()) {\n    return this.getLeft();\n  }\n\n  // First char on first line, can not go left any more\n  if (this.line === 0) {\n    return this;\n  }\n\n  if (includeEol) {\n    return this.getUp().getLineEnd();\n  } else {\n    return this.getUp().getLineEnd().getLeft();\n  }\n};\n\nPosition.prototype.getRightThroughLineBreaks = function (\n  this: Position,\n  includeEol = false,\n): Position {\n  const document = vscode.window.activeTextEditor?.document;\n  if (document === undefined) {\n    return this;\n  }\n  if (this.isAtDocumentEnd(document)) {\n    return this;\n  }\n\n  const lineLength = document.lineAt(this.line).text.length;\n  if (this.line < document.lineCount - 1) {\n    const pos = includeEol ? this : this.getRight();\n    if (pos.character === lineLength) {\n      return this.with({ character: 0 }).getDown();\n    }\n  } else if (!includeEol && this.character === lineLength - 1) {\n    // Last character of document, don't go on to non-existent EOL\n    return this;\n  }\n\n  return this.getRight();\n};\n\nPosition.prototype.getOffsetThroughLineBreaks = function (\n  this: Position,\n  offset: number,\n): Position {\n  let pos = new Position(this.line, this.character);\n\n  if (offset < 0) {\n    for (let i = 0; i < -offset; i++) {\n      pos = pos.getLeftThroughLineBreaks();\n    }\n  } else {\n    for (let i = 0; i < offset; i++) {\n      pos = pos.getRightThroughLineBreaks();\n    }\n  }\n\n  return pos;\n};\n\nPosition.prototype.prevWordStart = function (\n  this: Position,\n  document: vscode.TextDocument,\n  args?: { wordType?: WordType; inclusive?: boolean },\n): Position {\n  return prevWordStart(document, this, args?.wordType ?? WordType.Normal, args?.inclusive ?? false);\n};\n\nPosition.prototype.nextWordStart = function (\n  this: Position,\n  document: vscode.TextDocument,\n  args?: { wordType?: WordType; inclusive?: boolean },\n): Position {\n  return nextWordStart(document, this, args?.wordType ?? WordType.Normal, args?.inclusive ?? false);\n};\n\nPosition.prototype.prevWordEnd = function (\n  this: Position,\n  document: vscode.TextDocument,\n  args?: { wordType?: WordType },\n): Position {\n  return prevWordEnd(document, this, args?.wordType ?? WordType.Normal);\n};\n\nPosition.prototype.nextWordEnd = function (\n  this: Position,\n  document: vscode.TextDocument,\n  args?: { wordType?: WordType; inclusive?: boolean },\n): Position {\n  return nextWordEnd(document, this, args?.wordType ?? WordType.Normal, args?.inclusive ?? false);\n};\n\nPosition.prototype.getSentenceBegin = function (\n  this: Position,\n  args: { forward: boolean },\n): Position {\n  return getSentenceBegin(this, args);\n};\n\nPosition.prototype.getSentenceEnd = function (this: Position): Position {\n  return getSentenceEnd(this);\n};\n\n/**\n * @returns a new Position at the beginning of the current line.\n */\nPosition.prototype.getLineBegin = function (this: Position): Position {\n  return new Position(this.line, 0);\n};\n\n/**\n * @returns the beginning of the line, excluding preceeding whitespace.\n * This respects the `autoindent` setting, and returns `getLineBegin()` if auto-indent is disabled.\n */\nPosition.prototype.getLineBeginRespectingIndent = function (\n  this: Position,\n  document: vscode.TextDocument,\n): Position {\n  if (!configuration.autoindent) {\n    return this.getLineBegin();\n  }\n  return TextEditor.getFirstNonWhitespaceCharOnLine(document, this.line);\n};\n\n/**\n * @returns a new Position at the end of this position's line.\n */\nPosition.prototype.getLineEnd = function (this: Position): Position {\n  return new Position(this.line, TextEditor.getLineLength(this.line));\n};\n\n/**\n * @returns a new Position one to the left if this Position is on the EOL. Otherwise, returns this position.\n */\nPosition.prototype.getLeftIfEOL = function (this: Position): Position {\n  return this.character === TextEditor.getLineLength(this.line) ? this.getLeft() : this;\n};\n\n/**\n * @returns the position that the cursor would be at if you pasted *text* at the current position.\n */\nPosition.prototype.advancePositionByText = function (this: Position, text: string): Position {\n  const newlines: number[] = [];\n  let idx = text.indexOf('\\n', 0);\n  while (idx >= 0) {\n    newlines.push(idx);\n    idx = text.indexOf('\\n', idx + 1);\n  }\n\n  if (newlines.length === 0) {\n    return new Position(this.line, this.character + text.length);\n  } else {\n    return new Position(this.line + newlines.length, text.length - (newlines.at(-1)! + 1));\n  }\n};\n\n/**\n * Is this position at the beginning of the line?\n */\nPosition.prototype.isLineBeginning = function (this: Position): boolean {\n  return this.character === 0;\n};\n\n/**\n * Is this position at the end of the line?\n */\nPosition.prototype.isLineEnd = function (this: Position, document: vscode.TextDocument): boolean {\n  return this.character >= document.lineAt(this.line).range.end.character;\n};\n\nPosition.prototype.isFirstWordOfLine = function (\n  this: Position,\n  document: vscode.TextDocument,\n): boolean {\n  return (\n    TextEditor.getFirstNonWhitespaceCharOnLine(document, this.line).character === this.character\n  );\n};\n\nPosition.prototype.isAtDocumentBegin = function (this: Position): boolean {\n  return this.line === 0 && this.isLineBeginning();\n};\n\nPosition.prototype.isAtDocumentEnd = function (\n  this: Position,\n  document: vscode.TextDocument,\n): boolean {\n  return this.isEqual(TextEditor.getDocumentEnd(document));\n};\n\n/**\n * Returns whether the current position is in the leading whitespace of a line\n * @param allowEmpty : Use true if \"\" is valid\n */\nPosition.prototype.isInLeadingWhitespace = function (\n  this: Position,\n  document: vscode.TextDocument,\n): boolean {\n  return /^\\s+$/.test(document.getText(new vscode.Range(this.getLineBegin(), this)));\n};\n\n/**\n * If `vim.startofline` is set, get first non-blank character's position.\n */\nPosition.prototype.obeyStartOfLine = function (\n  this: Position,\n  document: vscode.TextDocument,\n): Position {\n  return configuration.startofline\n    ? TextEditor.getFirstNonWhitespaceCharOnLine(document, this.line)\n    : this;\n};\n\nPosition.prototype.isValid = function (this: Position, document: vscode.TextDocument): boolean {\n  try {\n    if (this.line >= document.lineCount) {\n      return false;\n    }\n\n    const charCount = document.lineAt(this.line).range.end.character;\n    if (this.character > charCount + 1) {\n      return false;\n    }\n  } catch (e) {\n    return false;\n  }\n\n  return true;\n};\n"
  },
  {
    "path": "src/common/number/numericString.ts",
    "content": "/**\n *      aaaa0x111bbbbbb\n *      |-------------| => NumericString\n *      |--|            => prefix\n *          |---|       => core\n *               |----| => suffix\n *          ||          => numPrefix\n *            |-|       => num\n *\n * Greedy matching, leftmost match wins.\n * If multiple matches begin at the same position, the match with the biggest\n *   span wins.\n * If multiple matches have the same begin position and span (This usually\n *   happens on octal and decimal), following priority sequence is used:\n *   (decimal => octal => hexadecimal)\n *\n * Example:\n *                    |  core  |     What we got      |     Rather than     |\n *  ------------------|--------|----------------------|---------------------|\n *  Leftmost rule:    | 010xff |    (010)xff [octal]  |    01(0xff) [hex]   |\n *  Biggest span rule:| 0xff   |     (0xff) [hex]     |   (0)xff [decimal]  |\n *  Priority rule:    | 00007  |    (00007) [octal]   |  (00007) [decimal]  |\n *\n * Side Effect:\n *  -0xf  Will be parsed as (-0)xf rather than -(0xf), current workaround is\n *          capturing '-' in hexadecimal regex but not consider '-' as a part\n *          of the number. This is achieved by using `negative` boolean value\n *          in `NumericString`.\n */\nexport enum NumericStringRadix {\n  Oct = 8,\n  Dec = 10,\n  Hex = 16,\n}\n\nexport class NumericString {\n  radix: NumericStringRadix;\n  value: number;\n  numLength: number;\n  prefix: string;\n  suffix: string;\n  // If a negative sign should be manually added when converting to string.\n  negative: boolean;\n  isCapital: boolean;\n\n  // Map radix to number prefix\n  private static numPrefix: { [key: number]: string } = {\n    [NumericStringRadix.Oct]: '0',\n    [NumericStringRadix.Dec]: '',\n    [NumericStringRadix.Hex]: '0x',\n  };\n\n  // Keep octal at the top of decimal to avoid regarding 0000007 as decimal.\n  // '000009' matches decimal.\n  // '000007' matches octal.\n  // '-0xf' matches hex rather than decimal '-0'\n  private static matchings: Array<{ regex: RegExp; radix: NumericStringRadix }> = [\n    { regex: /(-)?0[0-7]+/, radix: NumericStringRadix.Oct },\n    { regex: /(-)?\\d+/, radix: NumericStringRadix.Dec },\n    { regex: /(-)?0x[\\da-fA-F]+/, radix: NumericStringRadix.Hex },\n  ];\n\n  // Return parse result and offset of suffix\n  public static parse(\n    input: string,\n    targetRadix?: NumericStringRadix,\n  ): { num: NumericString; suffixOffset: number } | undefined {\n    const filteredMatchings =\n      targetRadix !== undefined\n        ? NumericString.matchings.filter((matching) => matching.radix === targetRadix)\n        : NumericString.matchings;\n\n    // Find core numeric part of input\n    let coreBegin = -1;\n    let coreLength = -1;\n    let coreRadix = -1;\n    let coreSign = false;\n    for (const { regex, radix } of filteredMatchings) {\n      const match = regex.exec(input);\n      if (match != null) {\n        // Get the leftmost and largest match\n        if (\n          coreRadix < 0 ||\n          match.index < coreBegin ||\n          (match.index === coreBegin && match[0].length > coreLength)\n        ) {\n          coreBegin = match.index;\n          coreLength = match[0].length;\n          coreRadix = radix;\n          coreSign = match[1] === '-';\n        }\n      }\n    }\n\n    if (coreRadix < 0) {\n      return undefined;\n    }\n\n    const coreEnd = coreBegin + coreLength;\n\n    const prefix = input.slice(0, coreBegin);\n    const core = input.slice(coreBegin, coreEnd);\n    const suffix = input.slice(coreEnd, input.length);\n\n    let value = parseInt(core, coreRadix);\n\n    // 0x00ff:  numLength = 4\n    // 077:     numLength = 2\n    // -0999:   numLength = 3\n    // The numLength is only useful for parsing non-decimal. Decimal with\n    // leading zero will be trimmed in `toString()`. If value is negative,\n    // remove the width of negative sign.\n    const numLength = coreLength - NumericString.numPrefix[coreRadix].length - (coreSign ? 1 : 0);\n\n    // According to original vim's behavior, for hex and octal, the leading\n    // '-' *should* be captured and preserved but *should not* be regarded as\n    // part of number, which means with <C-a>, `-0xf` turns into `-0x10`. So\n    // for hex and octal, we make the value absolute and set the negative\n    // sign flag.\n    let negative = false;\n    if (coreRadix !== 10 && coreSign) {\n      value = -value;\n      negative = true;\n    }\n\n    let isCapital = false;\n    if (coreRadix === 16) {\n      for (const c of Array.from(input).reverse()) {\n        if ('A' <= c && c <= 'F') {\n          isCapital = true;\n          break;\n        } else if ('a' <= c && c <= 'f') {\n          isCapital = false;\n          break;\n        }\n      }\n    }\n\n    return {\n      num: new NumericString(value, coreRadix, numLength, prefix, suffix, negative, isCapital),\n      suffixOffset: coreEnd,\n    };\n  }\n\n  private constructor(\n    value: number,\n    radix: NumericStringRadix,\n    numLength: number,\n    prefix: string,\n    suffix: string,\n    negative: boolean,\n    isCapital: boolean,\n  ) {\n    this.value = value;\n    this.radix = radix;\n    this.numLength = numLength;\n    this.prefix = prefix;\n    this.suffix = suffix;\n    this.negative = negative;\n    this.isCapital = isCapital;\n  }\n\n  public toString(): string {\n    // For decreased octal and hexadecimal\n    if (this.radix !== NumericStringRadix.Dec) {\n      const max = 0xffffffff;\n      while (this.value < 0) {\n        this.value = max + this.value + 1;\n      }\n    }\n\n    // Gen num part\n    const absValue = Math.abs(this.value);\n    let num = absValue.toString(this.radix);\n    if (this.isCapital) {\n      num = num.toUpperCase();\n    }\n    // numLength of decimal *should not* be preserved.\n    if (this.radix !== NumericStringRadix.Dec) {\n      const diff = this.numLength - num.length;\n      if (diff > 0) {\n        // Preserve num length if it's narrower.\n        num = '0'.repeat(diff) + num;\n      }\n    }\n\n    const sign = this.negative || this.value < 0 ? '-' : '';\n    const core = sign + NumericString.numPrefix[this.radix] + num;\n    return this.prefix + core + this.suffix;\n  }\n}\n"
  },
  {
    "path": "src/completion/lineCompletionProvider.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { VimState } from '../state/vimState';\nimport { TextEditor } from './../textEditor';\n\n/**\n * Return open text documents, with a given file at the top of the list.\n * @param startingFileName File that will be first in the array, typically current file\n */\nconst documentsStartingWith = (startingFileName: string) => {\n  return [...vscode.workspace.textDocuments].sort((a, b) => {\n    if (a.fileName === startingFileName) {\n      return -1;\n    } else if (b.fileName === startingFileName) {\n      return 1;\n    }\n    return 0;\n  });\n};\n\n/**\n * Get lines, with leading tabs or whitespace stripped.\n * @param document Document to get lines from.\n * @param lineToStartScanFrom Where to start looking for matches first. Closest matches are sorted first.\n * @param scanAboveFirst Whether to start scan above or below cursor. Other direction is scanned last.\n * @returns\n */\nconst linesWithoutIndentation = (\n  document: vscode.TextDocument,\n  lineToStartScanFrom: number,\n  scanAboveFirst: boolean,\n): Array<{ sortPriority: number; text: string }> => {\n  const distanceFromStartLine = (line: number) => {\n    let sortPriority = scanAboveFirst ? lineToStartScanFrom - line : line - lineToStartScanFrom;\n    if (sortPriority < 0) {\n      // We prioritized any items in the main direction searched,\n      // but now find closest items in opposite direction.\n      sortPriority = lineToStartScanFrom + Math.abs(sortPriority);\n    }\n\n    return sortPriority;\n  };\n\n  return document\n    .getText()\n    .split('\\n')\n    .map((text, line) => ({\n      sortPriority: distanceFromStartLine(line),\n      text: text.replace(/^[ \\t]*/, ''),\n    }))\n    .sort((a, b) => (a.sortPriority > b.sortPriority ? 1 : -1));\n};\n\n/**\n * Get all completions that match given text within open documents.\n * @example\n * a1\n * a2\n * a| // <--- Perform line completion here\n * a3\n * a4\n * // Returns: ['a2', 'a1', 'a3', 'a4']\n * @param text Text to partially match. Indentation is stripped.\n * @param currentFileName Current file, which is prioritized in sorting.\n * @param currentPosition Current position, which is prioritized when sorting for current file.\n */\nconst getCompletionsForText = (\n  text: string,\n  currentFileName: string,\n  currentPosition: Position,\n): string[] | null => {\n  const matchedLines: string[] = [];\n\n  for (const document of documentsStartingWith(currentFileName)) {\n    let lineToStartScanFrom = 0;\n    let scanAboveFirst = false;\n\n    if (document.fileName === currentFileName) {\n      lineToStartScanFrom = currentPosition.line;\n      scanAboveFirst = true;\n    }\n\n    for (const line of linesWithoutIndentation(document, lineToStartScanFrom, scanAboveFirst)) {\n      if (\n        !matchedLines.includes(line.text) &&\n        line.text &&\n        line.text.startsWith(text) &&\n        line.text !== text\n      ) {\n        matchedLines.push(line.text);\n      }\n    }\n  }\n\n  return matchedLines;\n};\n\n/**\n * Get all completions that match given text within open documents.\n * Results are sorted in a few ways:\n * 1) The current document is prioritized over other open documents.\n * 2) For the current document, lines above the current cursor are always prioritized over lines below it.\n * 3) For the current document, lines are also prioritized based on distance from cursor.\n * 4) For other documents, lines are prioritized based on distance from the top.\n * @example\n * a1\n * a2\n * a| // <--- Perform line completion here\n * a3\n * a4\n * // Returns: ['a2', 'a1', 'a3', 'a4']\n * @param position Position to start scan from\n * @param document Document to start scanning from, starting at the position (other open documents are scanned from top)\n */\nexport const getCompletionsForCurrentLine = (\n  position: Position,\n  document: vscode.TextDocument,\n): string[] | null => {\n  const currentLineText = document.getText(\n    new vscode.Range(TextEditor.getFirstNonWhitespaceCharOnLine(document, position.line), position),\n  );\n\n  return getCompletionsForText(currentLineText, document.fileName, position);\n};\n\nexport const lineCompletionProvider = {\n  /**\n   * Get all completions that match given text within open documents.\n   * Results are sorted by priority.\n   * @see getCompletionsForCurrentLine\n   *\n   * Any trailing characters are stripped. Trailing characters are often\n   * from auto-close, such as when importing in JavaScript ES6 and typing a\n   * curly brace. So the trailing characters are removed on purpose.\n   *\n   * Modifies vimState, adding transformations that replaces the\n   * current line's text with the chosen completion, with proper indentation.\n   *\n   * Here we use Quick Pick, instead of registerCompletionItemProvider\n   * Originally I looked at using a standard completion dropdown using that method,\n   * but it doesn't allow you to limit completions, and it became overwhelming\n   * when e.g. trying to do a line completion when the cursor is positioned after\n   * a space character (such that it shows almost any symbol in the list).\n   * Quick Pick also allows for searching, which is a nice bonus.\n   */\n  showLineCompletionsQuickPick: async (position: Position, vimState: VimState): Promise<void> => {\n    const completions = getCompletionsForCurrentLine(position, vimState.document);\n\n    if (!completions) {\n      return;\n    }\n\n    const selectedCompletion = await vscode.window.showQuickPick(completions);\n\n    if (!selectedCompletion) {\n      return;\n    }\n\n    vimState.recordedState.transformer.delete(\n      new vscode.Range(\n        TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, position.line),\n        position.getLineEnd(),\n      ),\n    );\n\n    vimState.recordedState.transformer.addTransformation({\n      type: 'insertTextVSCode',\n      text: selectedCompletion,\n    });\n  },\n};\n"
  },
  {
    "path": "src/configuration/configuration.ts",
    "content": "import * as process from 'process';\nimport * as vscode from 'vscode';\nimport { Globals } from '../globals';\nimport { VSCodeContext } from '../util/vscodeContext';\nimport { configurationValidator } from './configurationValidator';\nimport { decoration } from './decoration';\nimport { ValidatorResults } from './iconfigurationValidator';\nimport { Notation } from './notation';\n\nimport {\n  Digraph,\n  IAutoSwitchInputMethod,\n  ICamelCaseMotionConfiguration,\n  IConfiguration,\n  IHighlightedYankConfiguration,\n  IKeyRemapping,\n  IModeSpecificStrings,\n  ITargetsConfiguration,\n} from './iconfiguration';\n\nimport { SUPPORT_VIMRC } from 'platform/constants';\nimport * as packagejson from '../../package.json';\nimport { Mode } from '../mode/mode';\n\n// https://stackoverflow.com/questions/51465182/how-to-remove-index-signature-using-mapped-types/51956054#51956054\ntype RemoveIndex<T> = {\n  [P in keyof T as string extends P ? never : number extends P ? never : P]: T[P];\n};\n\nexport const extensionVersion = packagejson.version;\n\n/**\n * Most options supported by Vim have a short alias. They are provided here.\n * Please keep this list up to date and sorted alphabetically.\n */\nexport const optionAliases: ReadonlyMap<string, string> = new Map<string, string>([\n  ['ai', 'autoindent'],\n  ['et', 'expandtab'],\n  ['gd', 'gdefault'],\n  ['hi', 'history'],\n  ['hls', 'hlsearch'],\n  ['ic', 'ignorecase'],\n  ['icm', 'inccommand'],\n  ['is', 'incsearch'],\n  ['isk', 'iskeyword'],\n  ['js', 'joinspaces'],\n  ['mmd', 'maxmapdepth'],\n  ['mps', 'matchpairs'],\n  ['nu', 'number'],\n  ['rnu', 'relativenumber'],\n  ['sc', 'showcmd'],\n  ['scr', 'scroll'],\n  ['so', 'scrolloff'],\n  ['scs', 'smartcase'],\n  ['smd', 'showmode'],\n  ['sol', 'startofline'],\n  ['to', 'timeout'],\n  ['ts', 'tabstop'],\n  ['tw', 'textwidth'],\n  ['ws', 'wrapscan'],\n  ['ww', 'whichwrap'],\n]);\n\ntype OptionValue = number | string | boolean;\n\ninterface VSCodeKeybinding {\n  key: string;\n  mac?: string;\n  linux?: string;\n  command: string;\n  when: string;\n}\n\ninterface IHandleKeys {\n  [key: string]: boolean;\n}\n\ninterface IKeyBinding {\n  key: string;\n  command: string;\n}\n\n/**\n * Every Vim option we support should\n * 1. Be added to contribution section of `package.json`.\n * 2. Named as `vim.{optionName}`, `optionName` is the name we use in Vim.\n * 3. Define a public property in `Configuration` with the same name and a default value.\n *    Or define a private property and define customized Getter/Setter accessors for it.\n *    Always remember to decorate Getter accessor as @enumerable()\n * 4. If user doesn't set the option explicitly\n *    a. we don't have a similar setting in Code, initialize the option as default value.\n *    b. we have a similar setting in Code, use Code's setting.\n *\n * Vim option override sequence.\n * 1. `:set {option}` on the fly\n * 2. `vim.{option}`\n * 3. VS Code configuration\n * 4. VSCodeVim configuration default values\n *\n */\nclass Configuration implements IConfiguration {\n  [key: string]: any;\n\n  private readonly leaderDefault = '\\\\';\n  private readonly cursorTypeMap: { [key: string]: vscode.TextEditorCursorStyle } = {\n    line: vscode.TextEditorCursorStyle.Line,\n    block: vscode.TextEditorCursorStyle.Block,\n    underline: vscode.TextEditorCursorStyle.Underline,\n    'line-thin': vscode.TextEditorCursorStyle.LineThin,\n    'block-outline': vscode.TextEditorCursorStyle.BlockOutline,\n    'underline-thin': vscode.TextEditorCursorStyle.UnderlineThin,\n  };\n\n  private loadListeners: Array<() => void> = [];\n  public addLoadListener(listener: () => void): void {\n    this.loadListeners.push(listener);\n  }\n\n  public async load(): Promise<ValidatorResults> {\n    const vimConfigs: { [key: string]: any } = Globals.isTesting\n      ? Globals.mockConfiguration\n      : this.getConfiguration('vim');\n\n    // eslint-disable-next-line guard-for-in\n    for (const option in this) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      let val = vimConfigs[option];\n      if (val !== null && val !== undefined) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        if (val.constructor.name === Object.name) {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n          val = Configuration.unproxify(val);\n        }\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        this[option] = val;\n      }\n    }\n\n    if (SUPPORT_VIMRC && this.vimrc.enable) {\n      await import('./vimrc').then((vimrcModel) => {\n        return vimrcModel.vimrc.load(this);\n      });\n    }\n\n    this.leader = Notation.NormalizeKey(this.leader, this.leaderDefault);\n\n    this.clearKeyBindingsMaps();\n\n    const validatorResults = await configurationValidator.validate(configuration);\n\n    // read package.json for bound keys\n    // enable/disable certain key combinations\n    this.boundKeyCombinations = [];\n    for (const keybinding of packagejson.contributes.keybindings) {\n      if (keybinding.when.includes('listFocus')) {\n        continue;\n      }\n\n      if (keybinding.command.startsWith('notebook')) {\n        continue;\n      }\n\n      let key = keybinding.key;\n      if (process.platform === 'darwin') {\n        key = keybinding.mac || key;\n      } else if (process.platform === 'linux') {\n        key = keybinding.linux || key;\n      }\n\n      this.boundKeyCombinations.push({\n        key: Notation.NormalizeKey(key, this.leader),\n        command: keybinding.command,\n      });\n    }\n\n    // decorations\n    decoration.load(this);\n\n    for (const boundKey of this.boundKeyCombinations) {\n      // By default, all key combinations are used\n      let useKey = true;\n\n      const handleKey = this.handleKeys[boundKey.key];\n      if (handleKey !== undefined) {\n        // enabled/disabled through `vim.handleKeys`\n        useKey = handleKey;\n      } else if (!this.useCtrlKeys && boundKey.key.slice(1, 3) === 'C-') {\n        // user has disabled CtrlKeys and the current key is a CtrlKey\n        // <C-c>, still needs to be captured to overrideCopy\n        if (boundKey.key === '<C-c>' && this.overrideCopy) {\n          useKey = true;\n        } else {\n          useKey = false;\n        }\n      }\n\n      void VSCodeContext.set(`vim.use${boundKey.key}`, useKey);\n    }\n\n    void VSCodeContext.set('vim.overrideCopy', this.overrideCopy);\n    void VSCodeContext.set('vim.overrideCtrlC', this.overrideCopy || this.useCtrlKeys);\n\n    // workaround for circular dependency that would\n    // prevent packaging if we simply called `updateLangmap(configuration.langmap);`\n    this.loadListeners.forEach((listener) => listener());\n\n    return validatorResults;\n  }\n\n  getConfiguration(section: string = ''): RemoveIndex<vscode.WorkspaceConfiguration> {\n    const document = vscode.window.activeTextEditor?.document;\n    const resource = document ? { uri: document.uri, languageId: document.languageId } : undefined;\n    return vscode.workspace.getConfiguration(section, resource);\n  }\n\n  cursorStyleFromString(cursorStyle: string): vscode.TextEditorCursorStyle | undefined {\n    return this.cursorTypeMap[cursorStyle];\n  }\n\n  clearKeyBindingsMaps() {\n    // Clear the KeyBindingsMaps so that the previous configuration maps don't leak to this one\n    this.normalModeKeyBindingsMap = new Map<string, IKeyRemapping>();\n    this.insertModeKeyBindingsMap = new Map<string, IKeyRemapping>();\n    this.visualModeKeyBindingsMap = new Map<string, IKeyRemapping>();\n    this.commandLineModeKeyBindingsMap = new Map<string, IKeyRemapping>();\n    this.operatorPendingModeKeyBindingsMap = new Map<string, IKeyRemapping>();\n  }\n\n  handleKeys: IHandleKeys = {};\n\n  useSystemClipboard = false;\n\n  shell = '';\n\n  useCtrlKeys = false;\n\n  overrideCopy = true;\n\n  hlsearch = false;\n\n  ignorecase = true;\n\n  smartcase = true;\n\n  autoindent = true;\n\n  matchpairs = '(:),{:},[:]';\n\n  joinspaces = true;\n\n  camelCaseMotion: ICamelCaseMotionConfiguration = {\n    enable: true,\n  };\n\n  replaceWithRegister = false;\n\n  smartRelativeLine = false;\n\n  sneak = false;\n  sneakUseIgnorecaseAndSmartcase = false;\n  sneakReplacesF = false;\n\n  surround = true;\n\n  argumentObjectSeparators = [','];\n  argumentObjectOpeningDelimiters = ['(', '['];\n  argumentObjectClosingDelimiters = [')', ']'];\n\n  easymotion = false;\n  easymotionMarkerBackgroundColor = '#0000';\n  easymotionMarkerForegroundColorOneChar = '#ff0000';\n  easymotionMarkerForegroundColorTwoCharFirst = '#ffb400';\n  easymotionMarkerForegroundColorTwoCharSecond = '#b98300';\n  easymotionIncSearchForegroundColor = '#7fbf00';\n  easymotionDimColor = '#777777';\n  easymotionDimBackground = true;\n  easymotionMarkerFontWeight = 'bold';\n  easymotionKeys = 'hklyuiopnm,qwertzxcvbasdgjf;';\n  easymotionJumpToAnywhereRegex = '\\\\b[A-Za-z0-9]|[A-Za-z0-9]\\\\b|_.|#.|[a-z][A-Z]';\n\n  targets: ITargetsConfiguration = {\n    enable: false,\n\n    bracketObjects: {\n      enable: true,\n    },\n\n    smartQuotes: {\n      enable: false,\n      breakThroughLines: false,\n      aIncludesSurroundingSpaces: true,\n    },\n  };\n\n  autoSwitchInputMethod: IAutoSwitchInputMethod = {\n    enable: false,\n    defaultIM: '',\n    obtainIMCmd: '',\n    switchIMCmd: '',\n  };\n\n  timeout = 1000;\n\n  maxmapdepth = 1000;\n\n  showcmd = true;\n\n  showmodename = true;\n\n  leader = this.leaderDefault;\n\n  history = 50;\n\n  inccommand: '' | 'append' | 'replace' = '';\n\n  incsearch = true;\n\n  startInInsertMode = false;\n\n  startInInsertModeSchemes: string[] = ['comment'];\n\n  statusBarColorControl = false;\n\n  statusBarColors: IModeSpecificStrings<string | string[]> = {\n    normal: ['#005f5f', '#ffffff'],\n    insert: ['#5f0000', '#ffffff'],\n    visual: ['#5f00af', '#ffffff'],\n    visualline: ['#005f87', '#ffffff'],\n    visualblock: ['#86592d', '#ffffff'],\n    replace: ['#000000', '#ffffff'],\n  };\n\n  searchHighlightColor = '';\n  searchHighlightTextColor = '';\n\n  searchMatchColor = '';\n  searchMatchTextColor = '';\n\n  substitutionColor = '#50f01080';\n  substitutionTextColor = '';\n\n  highlightedyank: IHighlightedYankConfiguration = {\n    enable: false,\n    color: 'rgba(250, 240, 170, 0.5)',\n    textColor: '',\n    duration: 200,\n  };\n\n  @overlapSetting({ settingName: 'tabSize', defaultValue: 8 })\n  tabstop!: number;\n\n  @overlapSetting({ settingName: 'cursorStyle', defaultValue: 'line' })\n  private editorCursorStyleRaw!: string;\n\n  get editorCursorStyle(): vscode.TextEditorCursorStyle | undefined {\n    return this.cursorStyleFromString(this.editorCursorStyleRaw);\n  }\n  set editorCursorStyle(val: vscode.TextEditorCursorStyle | undefined) {\n    // nop\n  }\n\n  @overlapSetting({ settingName: 'insertSpaces', defaultValue: false })\n  expandtab!: boolean;\n\n  @overlapSetting({\n    settingName: 'lineNumbers',\n    defaultValue: true,\n    map: new Map([\n      ['on', true],\n      ['off', false],\n      ['relative', false],\n      ['interval', false],\n    ]),\n  })\n  // eslint-disable-next-line id-denylist\n  number!: boolean;\n\n  @overlapSetting({\n    settingName: 'lineNumbers',\n    defaultValue: false,\n    map: new Map([\n      ['on', false],\n      ['off', false],\n      ['relative', true],\n      ['interval', false],\n    ]),\n  })\n  relativenumber!: boolean;\n\n  @overlapSetting({\n    settingName: 'wordSeparators',\n    defaultValue: '/\\\\()\"\\':,.;<>~!@#$%^&*|+=[]{}`?-',\n  })\n  iskeyword!: string;\n\n  @overlapSetting({\n    settingName: 'wordWrap',\n    defaultValue: false,\n    map: new Map([\n      ['on', true],\n      ['off', false],\n      ['wordWrapColumn', true],\n      ['bounded', true],\n    ]),\n  })\n  wrap!: boolean;\n\n  @overlapSetting({\n    settingName: 'cursorSurroundingLines',\n    defaultValue: 0,\n  })\n  scrolloff!: number;\n\n  boundKeyCombinations: IKeyBinding[] = [];\n\n  visualstar = false;\n\n  mouseSelectionGoesIntoVisualMode = true;\n\n  changeWordIncludesWhitespace = false;\n\n  foldfix = false;\n\n  disableExtension: boolean = false;\n\n  enableNeovim = false;\n  neovimPath = '';\n  neovimUseConfigFile = false;\n  neovimConfigPath = '';\n\n  vimrc = {\n    enable: false,\n    path: '',\n  };\n\n  digraphs: { [shortcut: string]: Digraph } = {};\n\n  gdefault = false;\n  substituteGlobalFlag = false; // Deprecated in favor of gdefault\n\n  whichwrap = 'b,s';\n\n  startofline = true;\n\n  showMarksInGutter = false;\n\n  report = 2;\n  wrapscan = true;\n\n  scroll = 0;\n  getScrollLines(visibleRanges: vscode.Range[]): number {\n    return this.scroll === 0\n      ? Math.ceil((visibleRanges[0].end.line - visibleRanges[0].start.line) / 2)\n      : this.scroll;\n  }\n\n  cursorStylePerMode: IModeSpecificStrings<string> = {\n    normal: undefined,\n    insert: undefined,\n    visual: undefined,\n    visualline: undefined,\n    visualblock: undefined,\n    replace: undefined,\n  };\n\n  getCursorStyleForMode(mode: Mode): vscode.TextEditorCursorStyle | undefined {\n    const cursorStyle = (this.cursorStylePerMode as unknown as Record<string, string | undefined>)[\n      Mode[mode].toLowerCase()\n    ];\n    return cursorStyle ? this.cursorStyleFromString(cursorStyle) : undefined;\n  }\n\n  // remappings\n  insertModeKeyBindings: IKeyRemapping[] = [];\n  insertModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  normalModeKeyBindings: IKeyRemapping[] = [];\n  normalModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  operatorPendingModeKeyBindings: IKeyRemapping[] = [];\n  operatorPendingModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  visualModeKeyBindings: IKeyRemapping[] = [];\n  visualModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  commandLineModeKeyBindings: IKeyRemapping[] = [];\n  commandLineModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n\n  insertModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  normalModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  operatorPendingModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  visualModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  commandLineModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n\n  // langmap\n  langmapBindingsMap: Map<string, string> = new Map();\n  langmapReverseBindingsMap: Map<string, string> = new Map();\n  langmap = '';\n\n  get textwidth(): number {\n    const textwidth = this.getConfiguration('vim').get('textwidth', 80);\n\n    if (typeof textwidth !== 'number') {\n      return 80;\n    }\n\n    return textwidth;\n  }\n\n  private static unproxify(obj: { [key: string]: any }): object {\n    const result: { [key: string]: any } = {};\n    // eslint-disable-next-line guard-for-in\n    for (const key in obj) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      const val = obj[key];\n      if (val !== null && val !== undefined) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        result[key] = val;\n      }\n    }\n    return result;\n  }\n}\n\n// handle mapped settings between vscode to vim\nfunction overlapSetting(args: {\n  settingName: string;\n  defaultValue: OptionValue;\n  map?: Map<string | number | boolean, string | number | boolean>;\n}) {\n  return (target: any, propertyKey: string) => {\n    Object.defineProperty(target, propertyKey, {\n      get() {\n        // retrieve value from vim configuration\n        // if the value is not defined or empty\n        // look at the equivalent `editor` setting\n        // if that is not defined then defer to the default value\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access\n        let val = this['_' + propertyKey];\n        if (val !== undefined && val !== '') {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n          return val;\n        }\n\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n        val = this.getConfiguration('editor').get(args.settingName, args.defaultValue);\n        if (args.map && val !== undefined) {\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n          val = args.map.get(val);\n        }\n\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n        return val;\n      },\n      set(value) {\n        // synchronize the vim setting with the `editor` equivalent\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access\n        this['_' + propertyKey] = value;\n\n        if (value === undefined || value === '' || Globals.isTesting) {\n          return;\n        }\n\n        if (args.map) {\n          for (const [vscodeSetting, vimSetting] of args.map.entries()) {\n            if (value === vimSetting) {\n              value = vscodeSetting;\n              break;\n            }\n          }\n        }\n\n        // update configuration asynchronously\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n        this.getConfiguration('editor').update(\n          args.settingName,\n          value,\n          vscode.ConfigurationTarget.Global,\n        );\n      },\n      enumerable: true,\n      configurable: true,\n    });\n  };\n}\n\nexport const configuration = new Configuration();\n"
  },
  {
    "path": "src/configuration/configurationValidator.ts",
    "content": "import { IConfiguration } from './iconfiguration';\nimport { IConfigurationValidator, ValidatorResults } from './iconfigurationValidator';\n\nclass ConfigurationValidator {\n  private readonly validators: IConfigurationValidator[];\n\n  constructor() {\n    this.validators = [];\n  }\n\n  public registerValidator(validator: IConfigurationValidator) {\n    this.validators.push(validator);\n  }\n\n  public async validate(config: IConfiguration): Promise<ValidatorResults> {\n    const results = new ValidatorResults();\n\n    for (const validator of this.validators) {\n      const validatorResults = await validator.validate(config);\n      if (validatorResults.hasError) {\n        // errors found in configuration, disable feature\n        validator.disable(config);\n      }\n\n      results.concat(validatorResults);\n    }\n\n    return results;\n  }\n}\n\nexport const configurationValidator = new ConfigurationValidator();\n"
  },
  {
    "path": "src/configuration/decoration.ts",
    "content": "import * as vscode from 'vscode';\nimport { IConfiguration } from './iconfiguration';\n\nclass DecorationImpl {\n  private _default!: vscode.TextEditorDecorationType;\n  private _searchHighlight!: vscode.TextEditorDecorationType;\n  private _searchMatch!: vscode.TextEditorDecorationType;\n  private _substitutionAppend!: vscode.TextEditorDecorationType;\n  private _substitutionReplace!: vscode.TextEditorDecorationType;\n  private _easyMotionIncSearch!: vscode.TextEditorDecorationType;\n  private _easyMotionDimIncSearch!: vscode.TextEditorDecorationType;\n  private _insertModeVirtualCharacter!: vscode.TextEditorDecorationType;\n  private _operatorPendingModeCursor!: vscode.TextEditorDecorationType;\n  private _operatorPendingModeCursorChar!: vscode.TextEditorDecorationType;\n\n  private _markDecorationCache = new Map<string, vscode.TextEditorDecorationType>();\n\n  private _createMarkDecoration(name: string): vscode.TextEditorDecorationType {\n    const escape: Record<string, string> = {\n      '<': '&lt;',\n      '>': '&gt;',\n      '&': '&amp;',\n      '\"': '&quot;',\n      \"'\": '&#39;',\n    };\n    if (name in escape) {\n      name = escape[name];\n    }\n    const svg = [\n      '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 30 30\" width=\"30px\" height=\"30px\">',\n      '<style>text { font-family: sans-serif; font-size: 0.8em; }</style>',\n      '<path fill=\"rgb(3,102,214)\" d=\"M23,27l-8-7l-8,7V5c0-1.105,0.895-2,2-2h12c1.105,0,2,0.895,2,2V27z\"/>',\n      `<text x=\"50%\" y=\"40%\" fill=\"rgb(200,200,200)\" text-anchor=\"middle\" dominant-baseline=\"middle\">${name}</text>`,\n      '</svg>',\n    ].join('');\n\n    const uri = vscode.Uri.parse(`data:image/svg+xml;utf8,${encodeURI(svg)}`, true);\n\n    return vscode.window.createTextEditorDecorationType({\n      isWholeLine: false,\n      gutterIconPath: uri,\n      gutterIconSize: 'cover',\n    });\n  }\n\n  public readonly confirmedSubstitution = vscode.window.createTextEditorDecorationType({\n    letterSpacing: '-9999999px',\n    opacity: '0',\n  });\n\n  public set default(value: vscode.TextEditorDecorationType) {\n    if (this._default) {\n      this._default.dispose();\n    }\n    this._default = value;\n  }\n\n  public get default() {\n    return this._default;\n  }\n\n  public set searchHighlight(value: vscode.TextEditorDecorationType) {\n    if (this._searchHighlight) {\n      this._searchHighlight.dispose();\n    }\n    this._searchHighlight = value;\n  }\n\n  public get searchHighlight() {\n    return this._searchHighlight;\n  }\n\n  public set searchMatch(value: vscode.TextEditorDecorationType) {\n    if (this._searchMatch) {\n      this._searchMatch.dispose();\n    }\n    this._searchMatch = value;\n  }\n\n  public get searchMatch() {\n    return this._searchMatch;\n  }\n\n  public set substitutionAppend(value: vscode.TextEditorDecorationType) {\n    if (this._substitutionAppend) {\n      this._substitutionAppend.dispose();\n    }\n    this._substitutionAppend = value;\n  }\n\n  public get substitutionAppend() {\n    return this._substitutionAppend;\n  }\n\n  public set substitutionReplace(value: vscode.TextEditorDecorationType) {\n    if (this._substitutionReplace) {\n      this._substitutionReplace.dispose();\n    }\n    this._substitutionReplace = value;\n  }\n\n  public get substitutionReplace() {\n    return this._substitutionReplace;\n  }\n\n  public get easyMotionIncSearch() {\n    return this._easyMotionIncSearch;\n  }\n\n  public set easyMotionIncSearch(value: vscode.TextEditorDecorationType) {\n    if (this._easyMotionIncSearch) {\n      this._easyMotionIncSearch.dispose();\n    }\n    this._easyMotionIncSearch = value;\n  }\n\n  public get easyMotionDimIncSearch() {\n    return this._easyMotionDimIncSearch;\n  }\n\n  public set easyMotionDimIncSearch(value: vscode.TextEditorDecorationType) {\n    if (this._easyMotionDimIncSearch) {\n      this._easyMotionDimIncSearch.dispose();\n    }\n    this._easyMotionDimIncSearch = value;\n  }\n\n  public getOrCreateMarkDecoration(name: string): vscode.TextEditorDecorationType {\n    const decorationType = this.getMarkDecoration(name);\n\n    if (decorationType) {\n      return decorationType;\n    } else {\n      const type = this._createMarkDecoration(name);\n      this._markDecorationCache.set(name, type);\n      return type;\n    }\n  }\n\n  public getMarkDecoration(name: string): vscode.TextEditorDecorationType | undefined {\n    return this._markDecorationCache.get(name);\n  }\n\n  public allMarkDecorations(): IterableIterator<vscode.TextEditorDecorationType> {\n    return this._markDecorationCache.values();\n  }\n\n  public set insertModeVirtualCharacter(value: vscode.TextEditorDecorationType) {\n    if (this._insertModeVirtualCharacter) {\n      this._insertModeVirtualCharacter.dispose();\n    }\n    this._insertModeVirtualCharacter = value;\n  }\n\n  public get insertModeVirtualCharacter() {\n    return this._insertModeVirtualCharacter;\n  }\n\n  public set operatorPendingModeCursor(value: vscode.TextEditorDecorationType) {\n    if (this._operatorPendingModeCursor) {\n      this._operatorPendingModeCursor.dispose();\n    }\n    this._operatorPendingModeCursor = value;\n  }\n\n  public get operatorPendingModeCursor() {\n    return this._operatorPendingModeCursor;\n  }\n\n  public set operatorPendingModeCursorChar(value: vscode.TextEditorDecorationType) {\n    if (this._operatorPendingModeCursorChar) {\n      this._operatorPendingModeCursorChar.dispose();\n    }\n    this._operatorPendingModeCursorChar = value;\n  }\n\n  public get operatorPendingModeCursorChar() {\n    return this._operatorPendingModeCursorChar;\n  }\n\n  public load(configuration: IConfiguration) {\n    this.default = vscode.window.createTextEditorDecorationType({\n      backgroundColor: new vscode.ThemeColor('editorCursor.foreground'),\n      borderColor: new vscode.ThemeColor('editorCursor.foreground'),\n      dark: {\n        color: 'rgb(81,80,82)',\n      },\n      light: {\n        // used for light colored themes\n        color: 'rgb(255, 255, 255)',\n      },\n      borderStyle: 'solid',\n      borderWidth: '1px',\n    });\n\n    const searchHighlightBackgroundColor = configuration.searchHighlightColor\n      ? configuration.searchHighlightColor\n      : new vscode.ThemeColor('editor.findMatchHighlightBackground');\n\n    this.searchHighlight = vscode.window.createTextEditorDecorationType({\n      backgroundColor: searchHighlightBackgroundColor,\n      color: configuration.searchHighlightTextColor,\n      overviewRulerColor: new vscode.ThemeColor('editorOverviewRuler.findMatchForeground'),\n      after: {\n        color: 'transparent',\n        backgroundColor: searchHighlightBackgroundColor,\n      },\n      border: '1px solid',\n      borderColor: new vscode.ThemeColor('editor.findMatchHighlightBorder'),\n    });\n\n    const searchMatchBackgroundColor = configuration.searchMatchColor\n      ? configuration.searchMatchColor\n      : new vscode.ThemeColor('editor.findMatchBackground');\n\n    this.searchMatch = vscode.window.createTextEditorDecorationType({\n      backgroundColor: searchMatchBackgroundColor,\n      color: configuration.searchMatchTextColor,\n      overviewRulerColor: new vscode.ThemeColor('editorOverviewRuler.findMatchForeground'),\n      after: {\n        color: 'transparent',\n        backgroundColor: searchMatchBackgroundColor,\n      },\n      border: '2px solid',\n      borderColor: new vscode.ThemeColor('editor.findMatchBorder'),\n    });\n\n    const substitutionBackgroundColor = configuration.substitutionColor\n      ? configuration.substitutionColor\n      : new vscode.ThemeColor('editor.findMatchBackground');\n\n    this.substitutionAppend = vscode.window.createTextEditorDecorationType({\n      backgroundColor: searchHighlightBackgroundColor,\n      color: configuration.searchHighlightTextColor,\n      overviewRulerColor: new vscode.ThemeColor('editorOverviewRuler.findMatchForeground'),\n      after: {\n        color: configuration.substitutionTextColor,\n        backgroundColor: substitutionBackgroundColor,\n        border: '1px solid',\n        borderColor: new vscode.ThemeColor('editor.findMatchBorder'),\n      },\n      border: '1px dashed',\n      borderColor: new vscode.ThemeColor('editor.findMatchBorder'),\n    });\n\n    // Use letterSpacing and opacity to hide the decorated range, so that before text gets rendered over it\n    this.substitutionReplace = vscode.window.createTextEditorDecorationType({\n      letterSpacing: '-9999999px',\n      opacity: '0',\n      overviewRulerColor: new vscode.ThemeColor('editorOverviewRuler.findMatchForeground'),\n      before: {\n        color: configuration.substitutionTextColor,\n        backgroundColor: substitutionBackgroundColor,\n        border: '1px solid',\n        borderColor: new vscode.ThemeColor('editor.findMatchBorder'),\n      },\n    });\n\n    this.easyMotionIncSearch = vscode.window.createTextEditorDecorationType({\n      color: configuration.easymotionIncSearchForegroundColor,\n      fontWeight: configuration.easymotionMarkerFontWeight,\n    });\n\n    this.easyMotionDimIncSearch = vscode.window.createTextEditorDecorationType({\n      color: configuration.easymotionDimColor,\n    });\n\n    this.insertModeVirtualCharacter = vscode.window.createTextEditorDecorationType({\n      color: 'transparent', // no color to hide the existing character\n      before: {\n        color: 'currentColor',\n        backgroundColor: new vscode.ThemeColor('editor.background'),\n        borderColor: new vscode.ThemeColor('editor.background'),\n        margin: '0 -1ch 0 0',\n        height: '100%',\n      },\n    });\n\n    // This creates the half block cursor when on operator pending mode\n    this.operatorPendingModeCursor = vscode.window.createTextEditorDecorationType({\n      before: {\n        // no color to hide the existing character. We only need the character here to make\n        // the width be the same as the existing character.\n        color: 'transparent',\n        // The '-1ch' right margin is so that it displays on top of the existing character. The amount\n        // here doesn't really matter, it could be '-1px' it just needs to be negative so that the left\n        // of this 'before' element coincides with the left of the existing character.\n        margin: `0 -1ch 0 0;\n        position: absolute;\n        bottom: 0;\n        line-height: 0;`,\n        height: '50%',\n        backgroundColor: new vscode.ThemeColor('editorCursor.foreground'),\n      },\n    });\n\n    // This puts a character on top of the half block cursor and on top of the existing character\n    // to create the mix-blend 'magic'\n    this.operatorPendingModeCursorChar = vscode.window.createTextEditorDecorationType({\n      // We make the existing character 'black' -> rgb(0,0,0), because when using the mix-blend-mode\n      // with 'exclusion' it subtracts the darker color from the lightest color which means we will\n      // subtract zero from our 'currentcolor' leaving us with 'currentcolor' on the part above the\n      // background of the half cursor.\n      color: 'black',\n      before: {\n        color: 'currentcolor',\n        // The '-1ch' right margin is so that it displays on top of the existing character. The amount\n        // here doesn't really matter, it could be '-1px' it just needs to be negative so that the left\n        // of this 'before' element coincides with the left of the existing character.\n        margin: `0 -1ch 0 0;\n        position: absolute;\n        mix-blend-mode: exclusion;`,\n        height: '100%',\n      },\n    });\n  }\n}\n\nexport const decoration = new DecorationImpl();\n"
  },
  {
    "path": "src/configuration/iconfiguration.ts",
    "content": "import * as vscode from 'vscode';\n\nexport type Digraph = [string, number | number[]];\n\nexport interface IModeSpecificStrings<T> {\n  normal: T | undefined;\n  insert: T | undefined;\n  visual: T | undefined;\n  visualline: T | undefined;\n  visualblock: T | undefined;\n  replace: T | undefined;\n}\n\nexport interface IKeyRemapping {\n  before: string[];\n  after?: string[];\n  silent?: boolean;\n  // 'recursive' is calculated when validating, according to the config that stored the remapping\n  recursive?: boolean;\n  commands?: Array<{ command: string; args: any[] } | string>;\n  source?: 'vscode' | 'vimrc';\n}\n\nexport interface IVimrcKeyRemapping {\n  keyRemapping: IKeyRemapping;\n  keyRemappingType: string;\n}\n\nexport interface IAutoSwitchInputMethod {\n  enable: boolean;\n  defaultIM: string;\n  switchIMCmd: string;\n  obtainIMCmd: string;\n}\n\nexport interface IHighlightedYankConfiguration {\n  /**\n   * Boolean indicating whether yank highlighting should be enabled.\n   */\n  enable: boolean;\n\n  /**\n   * Color of the yank highlight.\n   */\n  color: string;\n\n  /**\n   * Color of the text being highlighted.\n   */\n  textColor: string | undefined;\n\n  /**\n   * Duration in milliseconds of the yank highlight.\n   */\n  duration: number;\n}\n\nexport interface ICamelCaseMotionConfiguration {\n  /**\n   * Enable CamelCaseMotion plugin or not\n   */\n  enable: boolean;\n}\n\nexport interface ISmartQuotesConfiguration {\n  /**\n   * Enable SmartQuotes plugin or not\n   */\n  enable: boolean;\n  /**\n   * Whether to break through lines when using [n]ext/[l]ast motion\n   */\n  breakThroughLines: boolean;\n  /**\n   * Whether to use default vim behaviour when using `a` (e.g. da') which include surrounding spaces, or not, as for other text objects.\n   */\n  aIncludesSurroundingSpaces: boolean;\n}\n\nexport interface ITargetsConfiguration {\n  /**\n   * Enable Targets plugin or not\n   */\n  enable: boolean;\n  bracketObjects: { enable: boolean };\n  smartQuotes: ISmartQuotesConfiguration;\n}\n\nexport interface IConfiguration {\n  [key: string]: any;\n\n  /**\n   * Use the system's clipboard when copying.\n   */\n  useSystemClipboard: boolean;\n\n  /**\n   * Enable ctrl- actions that would override existing VSCode actions.\n   */\n  useCtrlKeys: boolean;\n\n  /**\n   * Override default VSCode copy behavior.\n   */\n  overrideCopy: boolean;\n\n  /**\n   * Width in characters to word-wrap to.\n   */\n  textwidth: number;\n\n  /**\n   * Should we highlight incremental search matches?\n   */\n  hlsearch: boolean;\n\n  /**\n   * Ignore case when searching with / or ?.\n   */\n  ignorecase: boolean;\n\n  /**\n   * In / or ?, default to ignorecase=true unless the user types a capital\n   * letter.\n   */\n  smartcase: boolean;\n\n  /**\n   * Indent automatically?\n   */\n  autoindent: boolean;\n\n  /**\n   * Add two spaces after '.', '?', and '!' when joining or formatting?\n   */\n  joinspaces: boolean;\n\n  /**\n   * CamelCaseMotion plugin options\n   */\n  camelCaseMotion: ICamelCaseMotionConfiguration;\n\n  /**\n   * Use EasyMotion plugin?\n   */\n  easymotion: boolean;\n\n  /**\n   * Use ReplaceWithRegister plugin?\n   */\n  replaceWithRegister: boolean;\n\n  /**\n   * Use SmartRelativeLine plugin?\n   */\n  smartRelativeLine: boolean;\n\n  /**\n   * Use sneak plugin?\n   */\n  sneak: boolean;\n\n  /**\n   * Case sensitivity is determined by 'ignorecase' and 'smartcase'\n   */\n  sneakUseIgnorecaseAndSmartcase: boolean;\n\n  /**\n   * Use single-character `sneak` instead of Vim's native `f`\"\n   */\n  sneakReplacesF: boolean;\n\n  /**\n   * Use surround plugin?\n   */\n  surround: boolean;\n\n  /**\n   * Customize argument textobject delimiter and separator characters\n   */\n  argumentObjectSeparators: string[];\n  argumentObjectOpeningDelimiters: string[];\n  argumentObjectClosingDelimiters: string[];\n\n  /**\n   * Easymotion marker appearance settings\n   */\n  easymotionMarkerBackgroundColor: string;\n  easymotionMarkerForegroundColorOneChar: string;\n  easymotionMarkerForegroundColorTwoCharFirst: string;\n  easymotionMarkerForegroundColorTwoCharSecond: string;\n  easymotionIncSearchForegroundColor: string;\n  easymotionDimColor: string;\n  easymotionDimBackground: boolean;\n  easymotionMarkerFontWeight: string;\n  easymotionKeys: string;\n\n  /**\n   * Timeout in milliseconds for remapped commands.\n   */\n  timeout: number;\n\n  /**\n   * Maximum number of times a mapping is done without resulting in a\n   * character to be used. This normally catches endless mappings, like\n   * \":map x y\" with \":map y x\". It still does not catch \":map g wg\",\n   * because the 'w' is used before the next mapping is done.\n   */\n  maxmapdepth: number;\n\n  /**\n   * Display partial commands on status bar?\n   */\n  showcmd: boolean;\n\n  /**\n   * Display mode name text on status bar?\n   */\n  showmodename: boolean;\n\n  /**\n   * What key should <leader> map to in key remappings?\n   */\n  leader: string;\n\n  /**\n   * How much search or command history should be remembered\n   */\n  history: number;\n\n  /**\n   * Show substitutions while user is typing?\n   */\n  inccommand: '' | 'append' | 'replace';\n\n  /**\n   * Show results of / or ? search as user is typing?\n   */\n  incsearch: boolean;\n\n  /**\n   * Start in insert mode?\n   */\n  startInInsertMode: boolean;\n\n  /**\n   * List of document URI schemes that should automatically start in Insert mode.\n   * For example, ['comment'] for GitHub PR comment editors.\n   */\n  startInInsertModeSchemes: string[];\n\n  /**\n   * Enable changing of the status bar color based on mode\n   */\n  statusBarColorControl: boolean;\n\n  /**\n   * Status bar colors to change to based on mode\n   */\n  statusBarColors: IModeSpecificStrings<string | string[]>;\n\n  /**\n   * Color of search highlights.\n   */\n  searchHighlightColor: string;\n  searchHighlightTextColor: string;\n\n  /**\n   * Color of current match\n   */\n  searchMatchColor: string;\n  searchMatchTextColor: string;\n\n  /**\n   * Color of substituted text\n   */\n  substitutionColor: string;\n  substitutionTextColor: string;\n\n  /**\n   * Yank highlight settings.\n   */\n  highlightedyank: IHighlightedYankConfiguration;\n\n  /**\n   * Size of a tab character.\n   */\n  tabstop: number;\n\n  /**\n   * Type of cursor user is using native to vscode\n   */\n  editorCursorStyle: vscode.TextEditorCursorStyle | undefined;\n\n  /**\n   * Use spaces when the user presses tab?\n   */\n  expandtab: boolean;\n\n  /**\n   * Show line numbers\n   */\n  // eslint-disable-next-line id-denylist\n  number: boolean;\n\n  /**\n   * Show relative line numbers?\n   */\n  relativenumber: boolean;\n\n  /**\n   * keywords contain alphanumeric characters and '_'.\n   * If not configured `editor.wordSeparators` is used\n   */\n  iskeyword: string;\n\n  /**\n   * Characters that form pairs. The % command jumps from one to the other.\n   * Only character pairs are allowed that are different, thus you cannot jump between two double quotes.\n   * The characters must be separated by a colon.\n   * The pairs must be separated by a comma.\n   */\n  matchpairs: string;\n\n  /**\n   * In visual mode, start a search with * or # using the current selection\n   */\n  visualstar: boolean;\n\n  /**\n   * Does dragging with the mouse put you into visual mode\n   */\n  mouseSelectionGoesIntoVisualMode: boolean;\n\n  /**\n   * Includes trailing whitespace when changing word.\n   */\n  changeWordIncludesWhitespace: boolean;\n\n  /**\n   * Uses a hack to fix moving around folds.\n   */\n  foldfix: boolean;\n\n  /**\n   * \"Soft\"-disabling of extension.\n   * Differs from VS Code's disablng of the extension as the extension\n   * will still be loaded and activated, but all functionality will be disabled.\n   */\n  disableExtension: boolean;\n\n  /**\n   * Neovim\n   */\n  enableNeovim: boolean;\n  neovimPath: string;\n  neovimUseConfigFile: boolean;\n  neovimConfigPath: string;\n\n  /**\n   * .vimrc\n   */\n  vimrc: {\n    enable: boolean;\n    /**\n     * Do not use this directly - VimrcImpl.path resolves this to a path that's guaranteed to exist.\n     */\n    path: string;\n  };\n\n  /**\n   * Automatically apply the `/g` flag to substitute commands.\n   */\n  gdefault: boolean;\n  substituteGlobalFlag: boolean; // Deprecated in favor of gdefault\n\n  /**\n   * InputMethodSwicher\n   */\n  autoSwitchInputMethod: IAutoSwitchInputMethod;\n\n  /**\n   * Keybindings\n   */\n  insertModeKeyBindings: IKeyRemapping[];\n  insertModeKeyBindingsNonRecursive: IKeyRemapping[];\n  normalModeKeyBindings: IKeyRemapping[];\n  normalModeKeyBindingsNonRecursive: IKeyRemapping[];\n  operatorPendingModeKeyBindings: IKeyRemapping[];\n  operatorPendingModeKeyBindingsNonRecursive: IKeyRemapping[];\n  visualModeKeyBindings: IKeyRemapping[];\n  visualModeKeyBindingsNonRecursive: IKeyRemapping[];\n  commandLineModeKeyBindings: IKeyRemapping[];\n  commandLineModeKeyBindingsNonRecursive: IKeyRemapping[];\n\n  /**\n   * These are constructed by the RemappingValidator\n   */\n  insertModeKeyBindingsMap: Map<string, IKeyRemapping>;\n  normalModeKeyBindingsMap: Map<string, IKeyRemapping>;\n  operatorPendingModeKeyBindingsMap: Map<string, IKeyRemapping>;\n  visualModeKeyBindingsMap: Map<string, IKeyRemapping>;\n  commandLineModeKeyBindingsMap: Map<string, IKeyRemapping>;\n\n  /**\n   * Comma-separated list of motion keys that should wrap to next/previous line.\n   */\n  whichwrap: string;\n\n  cursorStylePerMode: IModeSpecificStrings<string>;\n\n  /**\n   * Threshold to report changed lines to status bar\n   */\n  report: number;\n\n  /**\n   * User-defined digraphs\n   */\n  digraphs: { [shortcut: string]: Digraph };\n\n  /**\n   * Searches wrap around the end of the file.\n   */\n  wrapscan: boolean;\n\n  /**\n   * Number of lines to scroll with CTRL-U and CTRL-D commands. Set to 0 to use a half page scroll.\n   */\n  scroll: number;\n\n  /**\n   * Number of line offset above or below cursor when moving.\n   */\n  scrolloff: number;\n\n  /**\n   * When `true` the commands listed below move the cursor to the first non-blank of the line. When\n   * `false` the cursor is kept in the same column (if possible). This applies to the commands:\n   * `<C-d>`, `<C-u>`, `<C-b>`, `<C-f>`, `G`, `H`, `M`, `L`, `gg`, and to the commands `d`, `<<`\n   * and `>>` with a linewise operator.\n   */\n  startofline: boolean;\n\n  /**\n   * Show the currently set mark(s) in the gutter.\n   */\n  showMarksInGutter: boolean;\n\n  /**\n   * Path to the shell to use for `!` and `:!` commands.\n   */\n  shell: string;\n\n  langmap: string;\n}\n"
  },
  {
    "path": "src/configuration/iconfigurationValidator.ts",
    "content": "import { IConfiguration } from './iconfiguration';\n\ninterface IValidatorResult {\n  level: 'error' | 'warning';\n  message: string;\n}\n\nexport class ValidatorResults {\n  errors = new Array<IValidatorResult>();\n\n  public append(validationResult: IValidatorResult) {\n    this.errors.push(validationResult);\n  }\n\n  public concat(validationResults: ValidatorResults) {\n    this.errors = this.errors.concat(validationResults.get());\n  }\n\n  public get(): readonly IValidatorResult[] {\n    return this.errors;\n  }\n\n  public get numErrors(): number {\n    return this.errors.filter((e) => e.level === 'error').length;\n  }\n\n  public get hasError(): boolean {\n    return this.numErrors > 0;\n  }\n\n  public get numWarnings(): number {\n    return this.errors.filter((e) => e.level === 'warning').length;\n  }\n\n  public get hasWarning(): boolean {\n    return this.numWarnings > 0;\n  }\n}\n\nexport interface IConfigurationValidator {\n  validate(config: IConfiguration): Promise<ValidatorResults>;\n  disable(config: IConfiguration): void;\n}\n"
  },
  {
    "path": "src/configuration/langmap.ts",
    "content": "import { SetCommand } from '../cmd_line/commands/set';\nimport { Mode } from '../mode/mode';\nimport { configuration } from './configuration';\n\nconst nonMatchable = /<(any|leader|number|alpha|character|register|macro)>/;\nconst literalKeys = /<(any|number|alpha|character)>/; // do not treat <register> or <macro> as literal!\nconst literalModes = [\n  Mode.Insert,\n  Mode.Replace,\n  Mode.CommandlineInProgress,\n  Mode.SearchInProgressMode,\n];\n\nlet lastLangmapString = '';\n\nSetCommand.addListener('langmap', () => {\n  updateLangmap(configuration.langmap);\n});\nconfiguration.addLoadListener(() => {\n  updateLangmap(configuration.langmap);\n});\nupdateLangmap(configuration.langmap);\n\nexport function updateLangmap(langmapString: string) {\n  if (lastLangmapString === langmapString) return;\n  const { bindings, reverseBindings } = parseLangmap(langmapString);\n\n  lastLangmapString = langmapString;\n  configuration.langmap = langmapString;\n  configuration.langmapBindingsMap = bindings;\n  configuration.langmapReverseBindingsMap = reverseBindings;\n}\n\n/**\n *  From :help langmap\n *  The 'langmap' option is a list of parts, separated with commas.  Each\n *      part can be in one of two forms:\n *      1.  A list of pairs.  Each pair is a \"from\" character immediately\n *          followed by the \"to\" character.  Examples: \"aA\", \"aAbBcC\".\n *      2.  A list of \"from\" characters, a semi-colon and a list of \"to\"\n *          characters.  Example: \"abc;ABC\"\n */\nfunction parseLangmap(langmapString: string): {\n  bindings: Map<string, string>;\n  reverseBindings: Map<string, string>;\n} {\n  if (!langmapString) return { bindings: new Map(), reverseBindings: new Map() };\n\n  const bindings: Map<string, string> = new Map();\n  const reverseBindings: Map<string, string> = new Map();\n\n  const getEscaped = (list: string) => {\n    return list.split(/\\\\?(.)/).filter(Boolean);\n  };\n  langmapString.split(/((?:[^\\\\,]|\\\\.)+),/).map((part) => {\n    if (!part) return;\n    const semicolon = part.split(/((?:[^\\\\;]|\\\\.)+);/);\n    if (semicolon.length === 3) {\n      const from = getEscaped(semicolon[1]);\n      const to = getEscaped(semicolon[2]);\n      if (from.length !== to.length) return; // skip over malformed part\n      for (let i = 0; i < from.length; ++i) {\n        bindings.set(from[i], to[i]);\n        reverseBindings.set(to[i], from[i]);\n      }\n    } else if (semicolon.length === 1) {\n      const pairs = getEscaped(part);\n      if (pairs.length % 2 !== 0) return; // skip over malformed part\n      for (let i = 0; i < pairs.length; i += 2) {\n        bindings.set(pairs[i], pairs[i + 1]);\n        reverseBindings.set(pairs[i + 1], pairs[i]);\n      }\n    }\n  });\n\n  return { bindings, reverseBindings };\n}\n\nexport function isLiteralMode(mode: Mode): boolean {\n  return literalModes.includes(mode);\n}\n\nfunction map(langmap: Map<string, string>, key: string): string {\n  // Notice that we're not currently remapping <C-> combinations.\n  // From my experience, Vim doesn't handle ctrl remapping either.\n  // It's possible that it's caused by my exact keyboard setup.\n  // We might need to revisit this in the future, in case some user needs it.\n  if (key.length !== 1) return key;\n  return langmap.get(key) || key;\n}\n\nexport function remapKey(key: string): string {\n  return map(configuration.langmapBindingsMap, key);\n}\n\nfunction unmapKey(key: string): string {\n  return map(configuration.langmapReverseBindingsMap, key);\n}\n\n// This is needed for bindings like \"fa\".\n// We expect this to jump to the next occurence of \"a\".\n// Thus, we need to revert \"a\" to its unmapped state.\nexport function unmapLiteral(\n  reference: readonly string[] | readonly string[][],\n  keys: readonly string[],\n): string[] {\n  if (reference.length === 0 || keys.length === 0) return [];\n\n  // find best matching if there are multiple\n  if (Array.isArray(reference[0])) {\n    for (const possibility of reference as string[][]) {\n      if (possibility.length !== keys.length) continue;\n      let allMatch = true;\n      for (let i = 0; i < possibility.length; ++i) {\n        if (nonMatchable.test(possibility[i])) continue;\n        if (possibility[i] !== keys[i]) {\n          allMatch = false;\n          break;\n        }\n      }\n      if (allMatch) return unmapLiteral(possibility, keys);\n    }\n  }\n\n  const unmapped = [...keys];\n  for (let i = 0; i < keys.length; ++i) {\n    if (literalKeys.test((reference as string[])[i])) {\n      unmapped[i] = unmapKey(keys[i]);\n    }\n  }\n  return unmapped;\n}\n"
  },
  {
    "path": "src/configuration/notation.ts",
    "content": "export class Notation {\n  // Mapping from a regex to the normalized string that it should be converted to.\n  private static readonly notationMap: ReadonlyArray<[RegExp, string]> = [\n    [/ctrl\\+|c\\-/gi, 'C-'],\n    [/cmd\\+|d\\-/gi, 'D-'],\n    [/shift\\+|s\\-/gi, 'S-'],\n    [/escape|esc/gi, 'Esc'],\n    [/backspace|bs/gi, 'BS'],\n    [/delete|del/gi, 'Del'],\n    [/home/gi, 'Home'],\n    [/end/gi, 'End'],\n    [/insert/gi, 'Insert'],\n    [/<space>/gi, ' '],\n    [/<cr>|<enter>|<return>/gi, '\\n'],\n  ];\n\n  private static shiftedLetterRegex = /<S-[a-zA-Z]>/;\n\n  /**\n   * Converts keystroke like <tab> to a single control character like \\t\n   */\n  public static ToControlCharacter(key: string) {\n    if (key === '<tab>') {\n      return '\\t';\n    }\n\n    return key;\n  }\n\n  public static IsControlKey(key: string): boolean {\n    key = key.toLocaleUpperCase();\n    return (\n      this.isSurroundedByAngleBrackets(key) && key !== '<BS>' && key !== '<S-BS>' && key !== '<TAB>'\n    );\n  }\n\n  /**\n   * Normalizes key to AngleBracketNotation\n   * (e.g. <ctrl+x>, Ctrl+x, <c-x> normalized to <C-x>)\n   * and converts the characters to their literals\n   * (e.g. <space>, <cr>, <leader>)\n   */\n  public static NormalizeKey(key: string, leaderKey: string): string {\n    if (typeof key !== 'string') {\n      return key;\n    }\n\n    if (key.length === 1) {\n      return key;\n    }\n\n    key = key.toLocaleLowerCase();\n\n    if (!this.isSurroundedByAngleBrackets(key)) {\n      key = `<${key}>`;\n    }\n\n    if (key === '<leader>') {\n      return leaderKey;\n    }\n\n    if (['<up>', '<down>', '<left>', '<right>'].includes(key)) {\n      return key;\n    }\n\n    for (const [regex, standardNotation] of this.notationMap) {\n      key = key.replace(regex, standardNotation);\n    }\n\n    if (this.shiftedLetterRegex.test(key)) {\n      key = key[3].toUpperCase();\n    }\n\n    return key;\n  }\n\n  /**\n   * Converts a key to a form which will look nice when logged, etc.\n   */\n  public static printableKey(key: string, leaderKey: string) {\n    const normalized = this.NormalizeKey(key, leaderKey);\n    return normalized === ' ' ? '<space>' : normalized === '\\n' ? '<enter>' : normalized;\n  }\n\n  private static isSurroundedByAngleBrackets(key: string): boolean {\n    return key.startsWith('<') && key.endsWith('>');\n  }\n}\n"
  },
  {
    "path": "src/configuration/remapper.ts",
    "content": "import * as vscode from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { ForceStopRemappingError, VimError } from '../error';\nimport { Mode } from '../mode/mode';\nimport { ModeHandler } from '../mode/modeHandler';\nimport { StatusBar } from '../statusBar';\nimport { Logger } from '../util/logger';\nimport { SpecialKeys } from '../util/specialKeys';\nimport { exCommandParser } from '../vimscript/exCommandParser';\nimport { IKeyRemapping } from './iconfiguration';\n\ninterface IRemapper {\n  /**\n   * Send keys to remapper\n   */\n  sendKey(keys: string[], modeHandler: ModeHandler): Promise<boolean>;\n\n  /**\n   * Given keys pressed thus far, denotes if it is a potential remap\n   */\n  readonly isPotentialRemap: boolean;\n}\n\nexport class Remappers implements IRemapper {\n  private readonly remappers = [\n    new InsertModeRemapper(),\n    new NormalModeRemapper(),\n    new VisualModeRemapper(),\n    new CommandLineModeRemapper(),\n    new OperatorPendingModeRemapper(),\n  ];\n\n  get isPotentialRemap(): boolean {\n    return this.remappers.some((r) => r.isPotentialRemap);\n  }\n\n  public async sendKey(keys: string[], modeHandler: ModeHandler): Promise<boolean> {\n    for (const remapper of this.remappers) {\n      if (await remapper.sendKey(keys, modeHandler)) {\n        return true;\n      }\n    }\n    return false;\n  }\n}\n\nexport class Remapper implements IRemapper {\n  private readonly configKey: string;\n  private readonly remappedModes: Mode[];\n\n  /**\n   * Checks if the current commandList is a potential remap.\n   */\n  private _isPotentialRemap = false;\n\n  /**\n   * If the commandList has a remap but there is still another potential remap we\n   * call it an Ambiguous Remap and we store it here. If later we need to handle it\n   * we don't need to go looking for it.\n   */\n  private hasAmbiguousRemap: IKeyRemapping | undefined;\n\n  /**\n   * If the commandList is a potential remap but has no ambiguous remap\n   * yet, we say that it has a Potential Remap.\n   *\n   * This is to distinguish the commands with ambiguous remaps and the\n   * ones without.\n   *\n   * Example 1: if 'aaaa' is mapped and so is 'aa', when the user has pressed\n   * 'aaa' we say it has an Ambiguous Remap which is 'aa', because if the\n   * user presses other key than 'a' next or waits for the timeout to finish\n   * we need to now that there was a remap to run so we first run the 'aa'\n   * remap and then handle the remaining keys.\n   *\n   * Example 2: if only 'aaaa' is mapped, when the user has pressed 'aaa'\n   * we say it has a Potential Remap, because if the user presses other key\n   * than 'a' next or waits for the timeout to finish we need to now that\n   * there was a potential remap that never came or was broken, so we can\n   * resend the keys again without allowing for a potential remap on the first\n   * key, which means we won't get to the same state because the first key\n   * will be handled as an action (in this case an 'Insert')\n   */\n  private hasPotentialRemap = false;\n\n  get isPotentialRemap(): boolean {\n    return this._isPotentialRemap;\n  }\n\n  constructor(configKey: string, remappedModes: Mode[]) {\n    this.configKey = configKey;\n    this.remappedModes = remappedModes;\n  }\n\n  public async sendKey(keys: string[], modeHandler: ModeHandler): Promise<boolean> {\n    const { vimState, remapState } = modeHandler;\n\n    this._isPotentialRemap = false;\n    const allowPotentialRemapOnFirstKey = vimState.recordedState.allowPotentialRemapOnFirstKey;\n    let remainingKeys: string[] = [];\n\n    /**\n     * Means that the timeout finished so we now can't allow the keys to be buffered again\n     * because the user already waited for timeout.\n     */\n    let allowBufferingKeys = true;\n\n    if (!this.remappedModes.includes(vimState.currentModeIncludingPseudoModes)) {\n      return false;\n    }\n\n    const userDefinedRemappings = configuration[this.configKey] as Map<string, IKeyRemapping>;\n\n    if (keys.at(-1) === SpecialKeys.TimeoutFinished) {\n      // Timeout finished. Don't let an ambiguous or potential remap start another timeout again\n      keys = keys.slice(0, keys.length - 1);\n      allowBufferingKeys = false;\n    }\n\n    if (keys.length === 0) {\n      return true;\n    }\n\n    Logger.trace(\n      `trying to find matching remap. keys=${keys}. mode=${\n        Mode[vimState.currentMode]\n      }. keybindings=${this.configKey}.`,\n    );\n\n    let remapping: IKeyRemapping | undefined = this.findMatchingRemap(userDefinedRemappings, keys);\n\n    // Check to see if a remapping could potentially be applied when more keys are received\n    let isPotentialRemap = Remapper.hasPotentialRemap(keys, userDefinedRemappings);\n\n    this._isPotentialRemap =\n      isPotentialRemap && allowBufferingKeys && allowPotentialRemapOnFirstKey;\n\n    /**\n     * Handle a broken potential or ambiguous remap\n     * 1. If this Remapper doesn't have a remapping AND\n     * 2. (It previously had an AmbiguousRemap OR a PotentialRemap) AND\n     * 3. (It doesn't have a potential remap anymore OR timeout finished) AND\n     * 4. keys length is more than 1\n     *\n     * Points 1-3: If we no longer have a remapping but previously had one or a potential one\n     * and there is no longer potential remappings because of another pressed key or because the\n     * timeout has passed we need to handle those situations by resending the keys or handling the\n     * ambiguous remap and resending any remaining keys.\n     * Point 4: if there is only one key there is no point in resending it without allowing remaps\n     * on first key, we can let the remapper go to the end because since either there was no potential\n     * remap anymore or the timeout finished so this means that the next two checks (the 'Buffer keys\n     * and create timeout' and 'Handle remapping and remaining keys') will never be hit, so it reaches\n     * the end without doing anything which means that this key will be handled as an action as intended.\n     */\n    if (\n      !remapping &&\n      (this.hasAmbiguousRemap || this.hasPotentialRemap) &&\n      (!isPotentialRemap || !allowBufferingKeys) &&\n      keys.length > 1\n    ) {\n      if (this.hasAmbiguousRemap) {\n        remapping = this.hasAmbiguousRemap;\n        isPotentialRemap = false;\n        this._isPotentialRemap = false;\n\n        // Use the commandList to get the remaining keys so that it includes any existing\n        // '<TimeoutFinished>' key\n        remainingKeys = vimState.recordedState.commandList.slice(remapping.before.length);\n        this.hasAmbiguousRemap = undefined;\n      }\n      if (!remapping) {\n        // if there is still no remapping, handle all the keys without allowing\n        // a potential remap on the first key so that we don't repeat everything\n        // again, but still allow for other ambiguous remaps after the first key.\n        //\n        // Example: if 'iiii' is mapped in normal and 'ii' is mapped in insert mode,\n        // and the user presses 'iiia' in normal mode or presses 'iii' and waits\n        // for the timeout to finish, we want the first 'i' to be handled without\n        // allowing potential remaps, which means it will go into insert mode,\n        // but then the next 'ii' should be remapped in insert mode and after the\n        // remap the 'a' should be handled.\n        if (!allowBufferingKeys) {\n          // Timeout finished and there is no remapping, so handle the buffered\n          // keys but resend the '<TimeoutFinished>' key as well so we don't wait\n          // for the timeout again but can still handle potential remaps.\n          //\n          // Example 1: if 'ccc' is mapped in normal mode and user presses 'cc' and\n          // waits for the timeout to finish, this will resend the 'cc<TimeoutFinished>'\n          // keys without allowing a potential remap on first key, which makes the\n          // first 'c' be handled as a 'ChangeOperator' and the second 'c' which has\n          // potential remaps (the 'ccc' remap) is buffered and the timeout started\n          // but then the '<TimeoutFinished>' key comes straight away that clears the\n          // timeout without waiting again, and makes the second 'c' be handled normally\n          // as another 'ChangeOperator'.\n          //\n          // Example 2: if 'iiii' is mapped in normal and 'ii' is mapped in insert\n          // mode, and the user presses 'iii' in normal mode and waits for the timeout\n          // to finish, this will resend the 'iii<TimeoutFinished>' keys without allowing\n          // a potential remap on first key, which makes the first 'i' be handled as\n          // an 'CommandInsertAtCursor' and goes to insert mode, next the second 'i'\n          // is buffered, then the third 'i' finds the insert mode remapping of 'ii'\n          // and handles that remap, after the remapping being handled the '<TimeoutFinished>'\n          // key comes that clears the timeout and since the commandList will be empty\n          // we return true as we finished handling this sequence of keys.\n\n          keys.push(SpecialKeys.TimeoutFinished); // include the '<TimeoutFinished>' key\n\n          Logger.trace(\n            `${this.configKey}. timeout finished, handling timed out buffer keys without allowing a new timeout.`,\n          );\n        }\n        Logger.trace(\n          `${this.configKey}. potential remap broken. resending keys without allowing a potential remap on first key. keys=${keys}`,\n        );\n        this.hasPotentialRemap = false;\n        vimState.recordedState.allowPotentialRemapOnFirstKey = false;\n        vimState.recordedState.resetCommandList();\n\n        if (remapState.wasPerformingRemapThatFinishedWaitingForTimeout) {\n          // Some keys that broke the possible remap were typed by the user so handle them seperatly\n          const lastRemapLength =\n            remapState.wasPerformingRemapThatFinishedWaitingForTimeout.after!.length;\n          const keysPressedByUser = keys.slice(lastRemapLength);\n          keys = keys.slice(0, lastRemapLength);\n\n          try {\n            remapState.isCurrentlyPerformingRecursiveRemapping = true;\n            await modeHandler.handleMultipleKeyEvents(keys);\n          } catch (e) {\n            if (e instanceof ForceStopRemappingError) {\n              Logger.trace(\n                `${this.configKey}. Stopped the remapping in the middle, ignoring the rest. Reason: ${e.message}`,\n              );\n            }\n          } finally {\n            remapState.isCurrentlyPerformingRecursiveRemapping = false;\n            remapState.wasPerformingRemapThatFinishedWaitingForTimeout = false;\n            await modeHandler.handleMultipleKeyEvents(keysPressedByUser);\n          }\n        } else {\n          Logger.debug(`Remapping to ${keys}`);\n          await modeHandler.handleMultipleKeyEvents(keys);\n        }\n        return true;\n      }\n    }\n\n    /**\n     * Buffer keys and create timeout\n     * 1. If the current keys have a potential remap AND\n     * 2. The timeout hasn't finished yet so we allow buffering keys AND\n     * 3. We allow potential remap on first key (check the note on RecordedState. TLDR: this will only\n     * be false for one key, the first one, when we resend keys that had a potential remap but no longer\n     * have it or the timeout finished)\n     *\n     * Points 1-3: If the current keys still have a potential remap and the timeout hasn't finished yet\n     * and we are not preventing a potential remap on the first key then we need to buffer this keys\n     * and wait for another key or the timeout to finish.\n     */\n    if (isPotentialRemap && allowBufferingKeys && allowPotentialRemapOnFirstKey) {\n      if (remapping) {\n        // There are other potential remaps (ambiguous remaps), wait for other key or for the timeout\n        // to finish. Also store this current ambiguous remap on '_hasAmbiguousRemap' so that if later\n        // this ambiguous remap is broken or the user waits for timeout we don't need to go looking for\n        // it again.\n        this.hasAmbiguousRemap = remapping;\n\n        Logger.trace(\n          `${this.configKey}. ambiguous match found. before=${remapping.before}. after=${remapping.after}. command=${remapping.commands}. waiting for other key or timeout to finish.`,\n        );\n      } else {\n        this.hasPotentialRemap = true;\n        Logger.trace(\n          `${this.configKey}. potential remap found. waiting for other key or timeout to finish.`,\n        );\n      }\n\n      // Store BufferedKeys\n      vimState.recordedState.bufferedKeys = [...keys];\n\n      // Create Timeout\n      vimState.recordedState.bufferedKeysTimeoutObj = setTimeout(() => {\n        void modeHandler.handleKeyEvent(SpecialKeys.TimeoutFinished);\n      }, configuration.timeout);\n      return true;\n    }\n\n    /**\n     * Handle Remapping and any remaining keys\n     * If we get here with a remapping that means we need to handle it.\n     */\n    if (remapping) {\n      if (!allowBufferingKeys) {\n        // If the user already waited for the timeout to finish, prevent the\n        // remapping from waiting for the timeout again by making a clone of\n        // remapping and change 'after' to send the '<TimeoutFinished>' key at\n        // the end.\n        const newRemapping = { ...remapping };\n        newRemapping.after = remapping.after?.slice(0);\n        newRemapping.after?.push(SpecialKeys.TimeoutFinished);\n        remapping = newRemapping;\n      }\n\n      this.hasAmbiguousRemap = undefined;\n      this.hasPotentialRemap = false;\n\n      let skipFirstCharacter = false;\n\n      // If we were performing a remapping already, it means this remapping has a parent remapping\n      const hasParentRemapping = remapState.isCurrentlyPerformingRemapping;\n      if (!hasParentRemapping) {\n        remapState.mapDepth = 0;\n      }\n\n      if (!remapping.recursive) {\n        remapState.isCurrentlyPerformingNonRecursiveRemapping = true;\n      } else {\n        remapState.isCurrentlyPerformingRecursiveRemapping = true;\n\n        // As per the Vim documentation: (:help recursive)\n        // If the {rhs} starts with {lhs}, the first character is not mapped\n        // again (this is Vi compatible).\n        // For example:\n        // map ab abcd\n        // will execute the \"a\" command and insert \"bcd\" in the text. The \"ab\"\n        // in the {rhs} will not be mapped again.\n        if (remapping.after?.join('').startsWith(remapping.before.join(''))) {\n          skipFirstCharacter = true;\n        }\n      }\n\n      // Increase mapDepth\n      remapState.mapDepth++;\n\n      Logger.trace(\n        `${this.configKey}. match found. before=${remapping.before}. after=${remapping.after}. command=${remapping.commands}. remainingKeys=${remainingKeys}. mapDepth=${remapState.mapDepth}.`,\n      );\n\n      let remapFailed = false;\n\n      try {\n        // Check maxMapDepth\n        if (remapState.mapDepth >= configuration.maxmapdepth) {\n          const vimError = VimError.RecursiveMapping();\n          StatusBar.displayError(vimState, vimError);\n          throw ForceStopRemappingError.fromVimError(vimError);\n        }\n\n        // Hacky code incoming!!! If someone has a better way to do this please change it\n        if (remapState.mapDepth % 10 === 0) {\n          // Allow the user to press <C-c> or <Esc> key when inside an infinite looping remap.\n          // When inside an infinite looping recursive mapping it would block the editor until it reached\n          // the maxmapdepth. This 0ms wait allows the extension to handle any key typed by the user which\n          // means it allows the user to press <C-c> or <Esc> to force stop the looping remap.\n          // This shouldn't impact the normal use case because we're only running this every 10 nested\n          // remaps. Also when the logs are set to Error only, a looping recursive remap takes around 1.5s\n          // to reach 1000 mapDepth and give back control to the user, but when logs are set to debug it\n          // can take as long as 7 seconds.\n          const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));\n          await wait(0);\n        }\n\n        remapState.remapUsedACharacter = false;\n\n        await this.handleRemapping(remapping, modeHandler, skipFirstCharacter);\n      } catch (e) {\n        if (e instanceof ForceStopRemappingError) {\n          // If a motion fails or a VimError happens during any kind of remapping or if the user presses the\n          // force stop remapping key (<C-c> or <Esc>) during a recursive remapping it should stop handling\n          // the remap and all its parent remaps if we are on a chain of recursive remaps.\n          // (Vim documentation :help map-error)\n          remapFailed = true;\n\n          // keep throwing until we reach the first parent\n          if (hasParentRemapping) {\n            throw e;\n          }\n\n          Logger.trace(\n            `${this.configKey}. Stopped the remapping in the middle, ignoring the rest. Reason: ${e.message}`,\n          );\n        } else {\n          // If some other error happens during the remapping handling it should stop the remap and rethrow\n          Logger.trace(\n            `${this.configKey}. error found in the middle of remapping, ignoring the rest of the remap. error: ${e}`,\n          );\n          throw e;\n        }\n      } finally {\n        // Check if we are still inside a recursive remap\n        if (!hasParentRemapping && remapState.isCurrentlyPerformingRecursiveRemapping) {\n          // no more recursive remappings being handled\n          if (vimState.recordedState.bufferedKeysTimeoutObj !== undefined) {\n            // In order to be able to receive other keys and at the same time wait for timeout, we need\n            // to create a timeout and return from the remapper so that modeHandler can be free to receive\n            // more keys. This means that if we are inside a recursive remapping, when we return on the\n            // last key of that remapping it will think that it is finished and set the currently\n            // performing recursive remapping flag to false, which would result in the current bufferedKeys\n            // not knowing they had a parent remapping. So we store that remapping here.\n            remapState.wasPerformingRemapThatFinishedWaitingForTimeout = { ...remapping };\n          }\n          remapState.isCurrentlyPerformingRecursiveRemapping = false;\n          remapState.forceStopRecursiveRemapping = false;\n        }\n\n        if (!hasParentRemapping) {\n          // Last remapping finished handling. Set undo step.\n          vimState.historyTracker.finishCurrentStep();\n        }\n\n        // NonRecursive remappings can't have nested remaps so after a finished remap we always set this to\n        // false, because either we were performing a non recursive remap and now we finish or we weren't\n        // performing a non recursive remapping and this was false anyway.\n        remapState.isCurrentlyPerformingNonRecursiveRemapping = false;\n\n        // if there were other remaining keys on the buffered keys that weren't part of the remapping\n        // handle them now, except if the remap failed and the remaining keys weren't typed by the user.\n        // (we know that if this remapping has a parent remapping then the remaining keys weren't typed\n        // by the user, but instead were sent by the parent remapping handler)\n        if (remainingKeys.length > 0 && !(remapFailed && hasParentRemapping)) {\n          if (remapState.wasPerformingRemapThatFinishedWaitingForTimeout) {\n            // If there was a performing remap that finished waiting for timeout then only the remaining keys\n            // that are not part of that remap were typed by the user.\n            let specialKey: string | undefined = '';\n            if (remainingKeys.at(-1) === SpecialKeys.TimeoutFinished) {\n              specialKey = remainingKeys.pop();\n            }\n            const lastRemap = remapState.wasPerformingRemapThatFinishedWaitingForTimeout.after!;\n            const lastRemapWithoutAmbiguousRemap = lastRemap.slice(remapping.before.length);\n            const keysPressedByUser = remainingKeys.slice(lastRemapWithoutAmbiguousRemap.length);\n            remainingKeys = remainingKeys.slice(0, remainingKeys.length - keysPressedByUser.length);\n            if (specialKey) {\n              remainingKeys.push(specialKey);\n              if (keysPressedByUser.length !== 0) {\n                keysPressedByUser.push(specialKey);\n              }\n            }\n            try {\n              remapState.isCurrentlyPerformingRecursiveRemapping = true;\n              await modeHandler.handleMultipleKeyEvents(remainingKeys);\n            } catch (e) {\n              Logger.trace(\n                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n                `${this.configKey}. Stopped the remapping in the middle, ignoring the rest. Reason: ${e.message}`,\n              );\n            } finally {\n              remapState.isCurrentlyPerformingRecursiveRemapping = false;\n              remapState.wasPerformingRemapThatFinishedWaitingForTimeout = false;\n              if (keysPressedByUser.length > 0) {\n                await modeHandler.handleMultipleKeyEvents(keysPressedByUser);\n              }\n            }\n          } else {\n            await modeHandler.handleMultipleKeyEvents(remainingKeys);\n          }\n        }\n      }\n\n      return true;\n    }\n\n    this.hasPotentialRemap = false;\n    this.hasAmbiguousRemap = undefined;\n    return false;\n  }\n\n  private async handleRemapping(\n    remapping: IKeyRemapping,\n    modeHandler: ModeHandler,\n    skipFirstCharacter: boolean,\n  ) {\n    const { vimState, remapState } = modeHandler;\n\n    vimState.recordedState.resetCommandList();\n    if (remapping.after) {\n      Logger.debug(`Remapping ${remapping.before} to ${remapping.after}`);\n      if (skipFirstCharacter) {\n        remapState.isCurrentlyPerformingNonRecursiveRemapping = true;\n        await modeHandler.handleKeyEvent(remapping.after[0]);\n        remapState.isCurrentlyPerformingNonRecursiveRemapping = false;\n        await modeHandler.handleMultipleKeyEvents(remapping.after.slice(1));\n      } else {\n        await modeHandler.handleMultipleKeyEvents(remapping.after);\n      }\n    }\n\n    if (remapping.commands) {\n      const count = vimState.recordedState.count || 1;\n      vimState.recordedState.count = 0;\n      for (let i = 0; i < count; i++) {\n        for (const command of remapping.commands) {\n          let commandString: string;\n          let commandArgs: string[];\n          if (typeof command === 'string') {\n            commandString = command;\n            commandArgs = [];\n          } else {\n            commandString = command.command;\n            commandArgs = Array.isArray(command.args)\n              ? (command.args as string[])\n              : command.args\n                ? [command.args]\n                : [];\n          }\n\n          if (commandString.slice(0, 1) === ':') {\n            // Check if this is a vim command by looking for :\n            // TODO: Parse once & cache?\n            const result = exCommandParser.parse(commandString);\n            if (result.status) {\n              if (result.value.lineRange) {\n                await result.value.command.executeWithRange(vimState, result.value.lineRange);\n              } else {\n                await result.value.command.execute(vimState);\n              }\n            } else {\n              throw VimError.NotAnEditorCommand(commandString);\n            }\n            modeHandler.updateView();\n          } else {\n            await vscode.commands.executeCommand(commandString, ...commandArgs);\n          }\n\n          // TODO add test cases (silent defined in IKeyRemapping)\n          if (!remapping.silent) {\n            StatusBar.setText(vimState, `${commandString} ${commandArgs.join(' ')}`);\n          }\n        }\n      }\n    }\n  }\n\n  protected findMatchingRemap(\n    userDefinedRemappings: Map<string, IKeyRemapping>,\n    inputtedKeys: string[],\n  ): IKeyRemapping | undefined {\n    if (userDefinedRemappings.size === 0) {\n      return undefined;\n    }\n\n    const range = Remapper.getRemappedKeysLengthRange(userDefinedRemappings);\n    const startingSliceLength = inputtedKeys.length;\n    const inputtedString = inputtedKeys.join('');\n    for (let sliceLength = startingSliceLength; sliceLength >= range[0]; sliceLength--) {\n      const keySlice = inputtedKeys.slice(-sliceLength).join('');\n\n      Logger.trace(`key=${inputtedKeys}. keySlice=${keySlice}.`);\n      if (userDefinedRemappings.has(keySlice)) {\n        const precedingKeys = inputtedString.slice(0, inputtedString.length - keySlice.length);\n        if (precedingKeys.length > 0 && !/^[0-9]+$/.test(precedingKeys)) {\n          Logger.trace(`key sequences need to match precisely. precedingKeys=${precedingKeys}.`);\n          break;\n        }\n\n        return userDefinedRemappings.get(keySlice);\n      }\n    }\n\n    return undefined;\n  }\n\n  /**\n   * Given list of remappings, returns the length of the shortest and longest remapped keys\n   * @param remappings\n   */\n  protected static getRemappedKeysLengthRange(\n    remappings: ReadonlyMap<string, IKeyRemapping>,\n  ): [number, number] {\n    if (remappings.size === 0) {\n      return [0, 0];\n    }\n    const keyLengths = Array.from(remappings.values()).map((remap) => remap.before.length);\n    return [Math.min(...keyLengths), Math.max(...keyLengths)];\n  }\n\n  /**\n   * Given list of keys and list of remappings, returns true if the keys are a potential remap\n   * @param keys the list of keys to be checked for potential remaps\n   * @param remappings The remappings Map\n   * @param countRemapAsPotential If the current keys are themselves a remap should they be considered a potential remap as well?\n   */\n  protected static hasPotentialRemap(\n    keys: string[],\n    remappings: ReadonlyMap<string, IKeyRemapping>,\n    countRemapAsPotential: boolean = false,\n  ): boolean {\n    const keysAsString = keys.join('');\n    const re = /^<([^>]+)>/;\n    if (keysAsString !== '') {\n      for (const remap of remappings.keys()) {\n        if (remap.startsWith(keysAsString) && (remap !== keysAsString || countRemapAsPotential)) {\n          // Don't confuse a key combination starting with '<' that is not a special key like '<C-a>'\n          // with a remap that starts with a special key.\n          if (keysAsString.startsWith('<') && !re.test(keysAsString) && re.test(remap)) {\n            continue;\n          }\n          return true;\n        }\n      }\n    }\n    return false;\n  }\n}\n\nfunction keyBindingsConfigKey(mode: string): string {\n  return `${mode}ModeKeyBindingsMap`;\n}\n\nclass InsertModeRemapper extends Remapper {\n  constructor() {\n    super(keyBindingsConfigKey('insert'), [Mode.Insert, Mode.Replace]);\n  }\n}\n\nclass NormalModeRemapper extends Remapper {\n  constructor() {\n    super(keyBindingsConfigKey('normal'), [Mode.Normal]);\n  }\n}\n\nclass OperatorPendingModeRemapper extends Remapper {\n  constructor() {\n    super(keyBindingsConfigKey('operatorPending'), [Mode.OperatorPendingMode]);\n  }\n}\n\nclass VisualModeRemapper extends Remapper {\n  constructor() {\n    super(keyBindingsConfigKey('visual'), [Mode.Visual, Mode.VisualLine, Mode.VisualBlock]);\n  }\n}\n\nclass CommandLineModeRemapper extends Remapper {\n  constructor() {\n    super(keyBindingsConfigKey('commandLine'), [\n      Mode.CommandlineInProgress,\n      Mode.SearchInProgressMode,\n    ]);\n  }\n}\n"
  },
  {
    "path": "src/configuration/validators/inputMethodSwitcherValidator.ts",
    "content": "import { existsAsync } from 'platform/fs';\nimport { Globals } from '../../globals';\nimport { configurationValidator } from '../configurationValidator';\nimport { IConfiguration } from '../iconfiguration';\nimport { IConfigurationValidator, ValidatorResults } from '../iconfigurationValidator';\n\nexport class InputMethodSwitcherConfigurationValidator implements IConfigurationValidator {\n  async validate(config: IConfiguration): Promise<ValidatorResults> {\n    const result = new ValidatorResults();\n\n    const inputMethodConfig = config.autoSwitchInputMethod;\n\n    if (!inputMethodConfig.enable || Globals.isTesting) {\n      return Promise.resolve(result);\n    }\n\n    if (!inputMethodConfig.switchIMCmd.includes('{im}')) {\n      result.append({\n        level: 'error',\n        message:\n          'vim.autoSwitchInputMethod.switchIMCmd is incorrect, it should contain the placeholder {im}.',\n      });\n    }\n\n    if (inputMethodConfig.obtainIMCmd === undefined || inputMethodConfig.obtainIMCmd === '') {\n      result.append({\n        level: 'error',\n        message: 'vim.autoSwitchInputMethod.obtainIMCmd is empty.',\n      });\n    } else if (!(await existsAsync(this.getRawCmd(inputMethodConfig.obtainIMCmd)))) {\n      result.append({\n        level: 'error',\n        message: `Unable to find ${inputMethodConfig.obtainIMCmd}. Check your 'vim.autoSwitchInputMethod.obtainIMCmd' in VSCode setting.`,\n      });\n    }\n\n    if (inputMethodConfig.defaultIM === undefined || inputMethodConfig.defaultIM === '') {\n      result.append({\n        level: 'error',\n        message: 'vim.autoSwitchInputMethod.defaultIM is empty.',\n      });\n    } else if (!(await existsAsync(this.getRawCmd(inputMethodConfig.switchIMCmd)))) {\n      result.append({\n        level: 'error',\n        message: `Unable to find ${inputMethodConfig.switchIMCmd}. Check your 'vim.autoSwitchInputMethod.switchIMCmd' in VSCode setting.`,\n      });\n    }\n\n    return Promise.resolve(result);\n  }\n\n  disable(config: IConfiguration) {\n    config.autoSwitchInputMethod.enable = false;\n  }\n\n  private getRawCmd(cmd: string): string {\n    return cmd.split(' ')[0];\n  }\n}\n\nconfigurationValidator.registerValidator(new InputMethodSwitcherConfigurationValidator());\n"
  },
  {
    "path": "src/configuration/validators/neovimValidator.ts",
    "content": "import { execFileSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport * as path from 'path';\nimport * as process from 'process';\nimport { configurationValidator } from '../configurationValidator';\nimport { IConfiguration } from '../iconfiguration';\nimport { IConfigurationValidator, ValidatorResults } from '../iconfigurationValidator';\n\nexport class NeovimValidator implements IConfigurationValidator {\n  validate(config: IConfiguration): Promise<ValidatorResults> {\n    const result = new ValidatorResults();\n\n    if (config.enableNeovim) {\n      let triedToParsePath = false;\n      try {\n        // Try to find nvim in path if it is not defined\n        if (config.neovimPath === '') {\n          const pathVar = process.env.PATH;\n          if (pathVar) {\n            pathVar.split(path.delimiter).forEach((element) => {\n              let neovimExecutable = 'nvim';\n              if (process.platform === 'win32') {\n                neovimExecutable += '.exe';\n              }\n              const testPath = path.join(element, neovimExecutable);\n              if (existsSync(testPath)) {\n                config.neovimPath = testPath;\n                triedToParsePath = true;\n                return;\n              }\n            });\n          }\n        }\n        execFileSync(config.neovimPath, ['--version']);\n      } catch (e) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        let errorMessage = `Invalid neovimPath. ${e.message}.`;\n        if (triedToParsePath) {\n          errorMessage += `Tried to parse PATH ${config.neovimPath}.`;\n        }\n        result.append({\n          level: 'error',\n          message: errorMessage,\n        });\n      }\n      // If Neovim config path doesn't exist, default to empty config path.\n      if (config.neovimUseConfigFile && config.neovimConfigPath !== '') {\n        if (!existsSync(config.neovimConfigPath)) {\n          const warningMessage = `No config file found in neovimConfigPath. Neovim will search its default config path.`;\n          config.neovimConfigPath = '';\n          result.append({\n            level: 'warning',\n            message: warningMessage,\n          });\n        }\n      }\n    }\n\n    return Promise.resolve(result);\n  }\n\n  disable(config: IConfiguration) {\n    config.enableNeovim = false;\n  }\n}\n\nconfigurationValidator.registerValidator(new NeovimValidator());\n"
  },
  {
    "path": "src/configuration/validators/remappingValidator.ts",
    "content": "import * as vscode from 'vscode';\nimport { PluginDefaultMappings } from '../../actions/plugins/pluginDefaultMappings';\nimport { configurationValidator } from '../configurationValidator';\nimport { IConfiguration, IKeyRemapping } from '../iconfiguration';\nimport { IConfigurationValidator, ValidatorResults } from '../iconfigurationValidator';\nimport { Notation } from '../notation';\n\nexport class RemappingValidator implements IConfigurationValidator {\n  private commandMap!: Map<string, boolean>;\n\n  async validate(config: IConfiguration): Promise<ValidatorResults> {\n    const result = new ValidatorResults();\n    const modeKeyBindingsKeys = [\n      'insertModeKeyBindings',\n      'insertModeKeyBindingsNonRecursive',\n      'normalModeKeyBindings',\n      'normalModeKeyBindingsNonRecursive',\n      'operatorPendingModeKeyBindings',\n      'operatorPendingModeKeyBindingsNonRecursive',\n      'visualModeKeyBindings',\n      'visualModeKeyBindingsNonRecursive',\n      'commandLineModeKeyBindings',\n      'commandLineModeKeyBindingsNonRecursive',\n    ];\n    for (const modeKeyBindingsKey of modeKeyBindingsKeys) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      const keybindings = config[modeKeyBindingsKey];\n      // add default mappings for activated plugins\n      // because we process keybindings backwards in next loop, user mapping will override\n      for (const pluginMapping of PluginDefaultMappings.getPluginDefaultMappings(\n        modeKeyBindingsKey,\n        config,\n      )) {\n        // note concat(all mappings) does not work somehow\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n        keybindings.push(pluginMapping);\n      }\n\n      const isRecursive = modeKeyBindingsKey.indexOf('NonRecursive') === -1;\n\n      const modeMapName = modeKeyBindingsKey.replace('NonRecursive', '');\n      let modeKeyBindingsMap = config[modeMapName + 'Map'] as Map<string, IKeyRemapping>;\n      if (!modeKeyBindingsMap) {\n        modeKeyBindingsMap = new Map<string, IKeyRemapping>();\n      }\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      for (let i = keybindings.length - 1; i >= 0; i--) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        const remapping = keybindings[i] as IKeyRemapping;\n\n        // set 'recursive' of the remapping according to where it was stored\n        remapping.recursive = isRecursive;\n\n        // validate\n        const remappingError = await this.isRemappingValid(remapping);\n        result.concat(remappingError);\n        if (remappingError.hasError) {\n          // errors with remapping, skip\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n          keybindings.splice(i, 1);\n          continue;\n        }\n\n        // normalize\n        if (remapping.before) {\n          remapping.before.forEach(\n            (key, idx) => (remapping.before[idx] = Notation.NormalizeKey(key, config.leader)),\n          );\n        }\n\n        if (remapping.after) {\n          remapping.after.forEach(\n            (key, idx) => (remapping.after![idx] = Notation.NormalizeKey(key, config.leader)),\n          );\n        }\n\n        // check for duplicates\n        const beforeKeys = remapping.before.join('');\n        if (modeKeyBindingsMap.has(beforeKeys)) {\n          result.append({\n            level: 'warning',\n            message: `${remapping.before}. Duplicate remapped key for ${beforeKeys}.`,\n          });\n          continue;\n        }\n\n        // add to map\n        modeKeyBindingsMap.set(beforeKeys, remapping);\n      }\n\n      config[modeMapName + 'Map'] = modeKeyBindingsMap;\n    }\n\n    return result;\n  }\n\n  disable(config: IConfiguration) {\n    // no-op\n  }\n\n  private async isRemappingValid(remapping: IKeyRemapping): Promise<ValidatorResults> {\n    const result = new ValidatorResults();\n\n    if (!remapping.after && !remapping.commands) {\n      result.append({\n        level: 'error',\n        message: `${remapping.before} missing 'after' key or 'commands'.`,\n      });\n    }\n\n    if (!(remapping.before instanceof Array)) {\n      result.append({\n        level: 'error',\n        message: `Remapping of '${remapping.before}' should be a string array.`,\n      });\n    }\n\n    if (remapping.recursive === undefined) {\n      result.append({\n        level: 'error',\n        message: `Remapping of '${remapping.before}' missing 'recursive' info.`,\n      });\n    }\n\n    if (remapping.after && !(remapping.after instanceof Array)) {\n      result.append({\n        level: 'error',\n        message: `Remapping of '${remapping.after}' should be a string array.`,\n      });\n    }\n\n    if (remapping.commands) {\n      for (const command of remapping.commands) {\n        let cmd: string;\n\n        if (typeof command === 'string') {\n          cmd = command;\n        } else if (command.command) {\n          cmd = command.command;\n\n          if (!(await this.isCommandValid(cmd))) {\n            result.append({ level: 'warning', message: `${cmd} does not exist.` });\n          }\n        } else {\n          result.append({\n            level: 'error',\n            message: `Remapping of '${remapping.before}' has wrong \"commands\" structure. Should be 'string[] | { \"command\": string, \"args\": any[] }[]'.`,\n          });\n        }\n      }\n    }\n\n    return result;\n  }\n\n  private async isCommandValid(command: string): Promise<boolean> {\n    if (command.startsWith(':')) {\n      return true;\n    }\n\n    return (await this.getCommandMap()).has(command);\n  }\n\n  private async getCommandMap(): Promise<Map<string, boolean>> {\n    if (this.commandMap == null) {\n      this.commandMap = new Map(\n        (await vscode.commands.getCommands(true)).map((x) => [x, true] as [string, boolean]),\n      );\n    }\n    return this.commandMap;\n  }\n}\n\nconfigurationValidator.registerValidator(new RemappingValidator());\n"
  },
  {
    "path": "src/configuration/validators/vimrcValidator.ts",
    "content": "import { configurationValidator } from '../configurationValidator';\nimport { IConfiguration } from '../iconfiguration';\nimport { IConfigurationValidator, ValidatorResults } from '../iconfigurationValidator';\n\nexport class VimrcValidator implements IConfigurationValidator {\n  async validate(config: IConfiguration): Promise<ValidatorResults> {\n    const result = new ValidatorResults();\n\n    // if (config.vimrc.enable && !fs.existsSync(vimrc.vimrcPath)) {\n    //   result.append({\n    //     level: 'error',\n    //     message: `.vimrc not found at ${config.vimrc.path}`,\n    //   });\n    // }\n\n    return result;\n  }\n\n  disable(config: IConfiguration): void {\n    // no-op\n  }\n}\n\nconfigurationValidator.registerValidator(new VimrcValidator());\n"
  },
  {
    "path": "src/configuration/vimrc.ts",
    "content": "import * as _ from 'lodash';\nimport * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'platform/fs';\nimport * as vscode from 'vscode';\nimport { window } from 'vscode';\nimport { Logger } from '../util/logger';\nimport { IConfiguration, IVimrcKeyRemapping } from './iconfiguration';\nimport { vimrcKeyRemappingBuilder } from './vimrcKeyRemappingBuilder';\n\nexport class VimrcImpl {\n  private _vimrcPath?: string;\n\n  /**\n   * Fully resolved path to the user's .vimrc\n   */\n  public get vimrcPath(): string | undefined {\n    return this._vimrcPath;\n  }\n\n  private static readonly SOURCE_REG_REX = /^(source)\\s+(.+)/i;\n\n  private static buildSource(line: string) {\n    const matches = VimrcImpl.SOURCE_REG_REX.exec(line);\n    if (!matches || matches.length < 3) {\n      return undefined;\n    }\n\n    const sourceKeyword = matches[1];\n    const filePath = matches[2];\n\n    return VimrcImpl.expandHome(filePath);\n  }\n\n  private static async loadConfig(config: IConfiguration, configPath: string) {\n    try {\n      const vscodeCommands = await vscode.commands.getCommands();\n      const lines = (await fs.readFileAsync(configPath, 'utf8')).split(/\\r?\\n/);\n      for (const line of lines) {\n        if (line.trimStart().startsWith('\"')) {\n          continue;\n        }\n\n        const source = this.buildSource(line);\n        if (source) {\n          if (!(await fs.existsAsync(source))) {\n            Logger.warn(`Unable to find \"${source}\" file for configuration.`);\n            continue;\n          }\n          Logger.debug(`Loading \"${source}\" file for configuration.`);\n          await VimrcImpl.loadConfig(config, source);\n          continue;\n        }\n        const remap = await vimrcKeyRemappingBuilder.build(line, vscodeCommands);\n        if (remap) {\n          VimrcImpl.addRemapToConfig(config, remap);\n          continue;\n        }\n        const unremap = await vimrcKeyRemappingBuilder.buildUnmapping(line);\n        if (unremap) {\n          VimrcImpl.removeRemapFromConfig(config, unremap);\n          continue;\n        }\n        const clearRemap = await vimrcKeyRemappingBuilder.buildClearMapping(line);\n        if (clearRemap) {\n          VimrcImpl.clearRemapsFromConfig(config, clearRemap);\n          continue;\n        }\n      }\n    } catch (err) {\n      void window.showWarningMessage(`vimrc file \"${configPath}\" is broken, err=${err}`);\n    }\n  }\n\n  public async load(config: IConfiguration) {\n    const _path = config.vimrc.path\n      ? VimrcImpl.expandHome(config.vimrc.path)\n      : await VimrcImpl.findDefaultVimrc();\n    if (!_path) {\n      await window.showWarningMessage('No .vimrc found. Please set `vim.vimrc.path`.');\n      return;\n    }\n    if (!(await fs.existsAsync(_path))) {\n      void window\n        .showWarningMessage(`No .vimrc found at ${_path}.`, 'Create it')\n        .then(async (choice: string | undefined) => {\n          if (choice === 'Create it') {\n            const newVimrc = await vscode.window.showSaveDialog({\n              defaultUri: vscode.Uri.file(_path),\n            });\n            if (newVimrc) {\n              await fs.writeFileAsync(newVimrc.fsPath, '', 'utf-8');\n              const document = vscode.window.activeTextEditor?.document;\n              const resource = document\n                ? { uri: document.uri, languageId: document.languageId }\n                : undefined;\n              void vscode.workspace\n                .getConfiguration('vim', resource)\n                .update('vimrc.path', newVimrc.fsPath, true);\n              await vscode.workspace.openTextDocument(newVimrc);\n              // TODO: add some sample remaps/settings in here?\n              await vscode.window.showTextDocument(newVimrc);\n            }\n          }\n        });\n    } else {\n      this._vimrcPath = _path;\n\n      // Remove all the old remappings from the .vimrc file\n      VimrcImpl.removeAllRemapsFromConfig(config);\n\n      // Add the new remappings\n      await VimrcImpl.loadConfig(config, this._vimrcPath);\n    }\n  }\n\n  /**\n   * Adds a remapping from .vimrc to the given configuration\n   */\n  public static addRemapToConfig(config: IConfiguration, remap: IVimrcKeyRemapping): void {\n    const mappings = (() => {\n      switch (remap.keyRemappingType) {\n        case 'map':\n          return [\n            config.normalModeKeyBindings,\n            config.visualModeKeyBindings,\n            config.operatorPendingModeKeyBindings,\n          ];\n        case 'nmap':\n        case 'nma':\n        case 'nm':\n          return [config.normalModeKeyBindings];\n        case 'vmap':\n        case 'vma':\n        case 'vm':\n        case 'xmap':\n        case 'xma':\n        case 'xm':\n          return [config.visualModeKeyBindings];\n        case 'imap':\n        case 'ima':\n        case 'im':\n          return [config.insertModeKeyBindings];\n        case 'cmap':\n        case 'cma':\n        case 'cm':\n          return [config.commandLineModeKeyBindings];\n        case 'omap':\n        case 'oma':\n        case 'om':\n          return [config.operatorPendingModeKeyBindings];\n        case 'lmap':\n        case 'lma':\n        case 'lm':\n        case 'map!':\n          return [config.insertModeKeyBindings, config.commandLineModeKeyBindings];\n        case 'noremap':\n        case 'norema':\n        case 'norem':\n        case 'nore':\n        case 'nor':\n        case 'no':\n          return [\n            config.normalModeKeyBindingsNonRecursive,\n            config.visualModeKeyBindingsNonRecursive,\n            config.operatorPendingModeKeyBindingsNonRecursive,\n          ];\n        case 'nnoremap':\n        case 'nnorema':\n        case 'nnorem':\n        case 'nnore':\n        case 'nnor':\n        case 'nno':\n        case 'nn':\n          return [config.normalModeKeyBindingsNonRecursive];\n        case 'vnoremap':\n        case 'vnorema':\n        case 'vnorem':\n        case 'vnore':\n        case 'vnor':\n        case 'vno':\n        case 'vn':\n        case 'xnoremap':\n        case 'xnorema':\n        case 'xnorem':\n        case 'xnore':\n        case 'xnor':\n        case 'xno':\n        case 'xn':\n          return [config.visualModeKeyBindingsNonRecursive];\n        case 'inoremap':\n        case 'inorema':\n        case 'inorem':\n        case 'inore':\n        case 'inor':\n        case 'ino':\n          return [config.insertModeKeyBindingsNonRecursive];\n        case 'cnoremap':\n        case 'cnorema':\n        case 'cnorem':\n        case 'cnore':\n        case 'cnor':\n        case 'cno':\n          return [config.commandLineModeKeyBindingsNonRecursive];\n        case 'onoremap':\n        case 'onorema':\n        case 'onorem':\n        case 'onore':\n        case 'onor':\n        case 'ono':\n          return [config.operatorPendingModeKeyBindingsNonRecursive];\n        case 'lnoremap':\n        case 'lnorema':\n        case 'lnorem':\n        case 'lnore':\n        case 'lnor':\n        case 'lno':\n        case 'ln':\n        case 'noremap!':\n        case 'norema!':\n        case 'norem!':\n        case 'nore!':\n        case 'nor!':\n        case 'no!':\n          return [\n            config.insertModeKeyBindingsNonRecursive,\n            config.commandLineModeKeyBindingsNonRecursive,\n          ];\n        default:\n          Logger.warn(`Encountered an unrecognized mapping type: '${remap.keyRemappingType}'`);\n          return undefined;\n      }\n    })();\n\n    mappings?.forEach((remaps) => {\n      // Don't override a mapping present in settings.json; those are more specific to VSCodeVim.\n      if (!remaps.some((r) => _.isEqual(r.before, remap.keyRemapping.before))) {\n        remaps.push(remap.keyRemapping);\n      }\n    });\n  }\n\n  /**\n   * Removes a remapping from .vimrc from the given configuration\n   */\n  public static removeRemapFromConfig(config: IConfiguration, remap: IVimrcKeyRemapping): boolean {\n    const mappings = (() => {\n      switch (remap.keyRemappingType) {\n        case 'unmap':\n        case 'unma':\n        case 'unm':\n          return [\n            config.normalModeKeyBindings,\n            config.normalModeKeyBindingsNonRecursive,\n            config.visualModeKeyBindings,\n            config.visualModeKeyBindingsNonRecursive,\n            config.operatorPendingModeKeyBindings,\n            config.operatorPendingModeKeyBindingsNonRecursive,\n          ];\n        case 'nunmap':\n        case 'nunma':\n        case 'nunm':\n        case 'nun':\n          return [config.normalModeKeyBindings, config.normalModeKeyBindingsNonRecursive];\n        case 'vunmap':\n        case 'vunma':\n        case 'vunm':\n        case 'vun':\n        case 'vu':\n        case 'xunmap':\n        case 'xunma':\n        case 'xunm':\n        case 'xun':\n        case 'xu':\n          return [config.visualModeKeyBindings, config.visualModeKeyBindingsNonRecursive];\n        case 'iunmap':\n        case 'iunma':\n        case 'iunm':\n        case 'iun':\n        case 'iu':\n          return [config.insertModeKeyBindings, config.insertModeKeyBindingsNonRecursive];\n        case 'cunmap':\n        case 'cunma':\n        case 'cunm':\n        case 'cun':\n        case 'cu':\n          return [config.commandLineModeKeyBindings, config.commandLineModeKeyBindingsNonRecursive];\n        case 'ounmap':\n        case 'ounma':\n        case 'ounm':\n        case 'oun':\n        case 'ou':\n          return [\n            config.operatorPendingModeKeyBindings,\n            config.operatorPendingModeKeyBindingsNonRecursive,\n          ];\n        case 'lunmap':\n        case 'lunma':\n        case 'lunm':\n        case 'lun':\n        case 'lu':\n        case 'unmap!':\n        case 'unma!':\n        case 'unm!':\n          return [\n            config.insertModeKeyBindings,\n            config.insertModeKeyBindingsNonRecursive,\n            config.commandLineModeKeyBindings,\n            config.commandLineModeKeyBindingsNonRecursive,\n          ];\n        default:\n          Logger.warn(`Encountered an unrecognized unmapping type: '${remap.keyRemappingType}'`);\n          return undefined;\n      }\n    })();\n\n    if (mappings) {\n      mappings.forEach((remaps) => {\n        // Don't remove a mapping present in settings.json; those are more specific to VSCodeVim.\n        _.remove(\n          remaps,\n          (r) => r.source === 'vimrc' && _.isEqual(r.before, remap.keyRemapping.before),\n        );\n      });\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Clears all remappings from .vimrc from the given configuration for specific mode\n   */\n  public static clearRemapsFromConfig(config: IConfiguration, remap: IVimrcKeyRemapping): boolean {\n    const mappings = (() => {\n      switch (remap.keyRemappingType) {\n        case 'mapclear':\n        case 'mapclea':\n        case 'mapcle':\n        case 'mapcl':\n        case 'mapc':\n          return [\n            config.normalModeKeyBindings,\n            config.normalModeKeyBindingsNonRecursive,\n            config.visualModeKeyBindings,\n            config.visualModeKeyBindingsNonRecursive,\n            config.operatorPendingModeKeyBindings,\n            config.operatorPendingModeKeyBindingsNonRecursive,\n          ];\n        case 'nmapclear':\n        case 'nmapclea':\n        case 'nmapcle':\n        case 'nmapcl':\n        case 'nmapc':\n          return [config.normalModeKeyBindings, config.normalModeKeyBindingsNonRecursive];\n        case 'vmapclear':\n        case 'vmapclea':\n        case 'vmapcle':\n        case 'vmapcl':\n        case 'vmapc':\n        case 'xmapclear':\n        case 'xmapclea':\n        case 'xmapcle':\n        case 'xmapcl':\n        case 'xmapc':\n          return [config.visualModeKeyBindings, config.visualModeKeyBindingsNonRecursive];\n        case 'imapclear':\n        case 'imapclea':\n        case 'imapcle':\n        case 'imapcl':\n        case 'imapc':\n          return [config.insertModeKeyBindings, config.insertModeKeyBindingsNonRecursive];\n        case 'cmapclear':\n        case 'cmapclea':\n        case 'cmapcle':\n        case 'cmapcl':\n        case 'cmapc':\n          return [config.commandLineModeKeyBindings, config.commandLineModeKeyBindingsNonRecursive];\n        case 'omapclear':\n        case 'omapclea':\n        case 'omapcle':\n        case 'omapcl':\n        case 'omapc':\n          return [\n            config.operatorPendingModeKeyBindings,\n            config.operatorPendingModeKeyBindingsNonRecursive,\n          ];\n        case 'lmapclear':\n        case 'lmapclea':\n        case 'lmapcle':\n        case 'lmapcl':\n        case 'lmapc':\n        case 'mapclear!':\n        case 'mapclea!':\n        case 'mapcle!':\n        case 'mapcl!':\n        case 'mapc!':\n          return [\n            config.insertModeKeyBindings,\n            config.insertModeKeyBindingsNonRecursive,\n            config.commandLineModeKeyBindings,\n            config.commandLineModeKeyBindingsNonRecursive,\n          ];\n        default:\n          Logger.warn(`Encountered an unrecognized clearMapping type: '${remap.keyRemappingType}'`);\n          return undefined;\n      }\n    })();\n\n    if (mappings) {\n      mappings.forEach((remaps) => {\n        // Don't remove a mapping present in settings.json; those are more specific to VSCodeVim.\n        _.remove(remaps, (r) => r.source === 'vimrc');\n      });\n      return true;\n    }\n    return false;\n  }\n\n  public static removeAllRemapsFromConfig(config: IConfiguration): void {\n    const remapCollections = [\n      config.normalModeKeyBindings,\n      config.operatorPendingModeKeyBindings,\n      config.visualModeKeyBindings,\n      config.insertModeKeyBindings,\n      config.commandLineModeKeyBindings,\n      config.normalModeKeyBindingsNonRecursive,\n      config.operatorPendingModeKeyBindingsNonRecursive,\n      config.visualModeKeyBindingsNonRecursive,\n      config.insertModeKeyBindingsNonRecursive,\n      config.commandLineModeKeyBindingsNonRecursive,\n    ];\n    for (const remaps of remapCollections) {\n      _.remove(remaps, (remap) => remap.source === 'vimrc');\n    }\n  }\n\n  private static async findDefaultVimrc(): Promise<string | undefined> {\n    const vscodeVimrcPath = path.join(os.homedir(), '.vscodevimrc');\n    if (await fs.existsAsync(vscodeVimrcPath)) {\n      return vscodeVimrcPath;\n    }\n\n    let vimrcPath = path.join(os.homedir(), '.vimrc');\n    if (await fs.existsAsync(vimrcPath)) {\n      return vimrcPath;\n    }\n\n    vimrcPath = path.join(os.homedir(), '_vimrc');\n    if (await fs.existsAsync(vimrcPath)) {\n      return vimrcPath;\n    }\n\n    vimrcPath = path.join(os.homedir(), '.config/', 'nvim/', 'init.vim');\n    if (await fs.existsAsync(vimrcPath)) {\n      return vimrcPath;\n    }\n\n    return undefined;\n  }\n\n  private static expandHome(filePath: string): string {\n    // regex = Anything preceded by beginning of line\n    // and immediately followed by '~' or '$HOME'\n    const regex = /(?<=^(?:~|\\$HOME)).*/;\n\n    // Matches /pathToVimrc in $HOME/pathToVimrc or ~/pathToVimrc\n    const matches = filePath.match(regex);\n\n    if (!matches || matches.length > 1) {\n      return filePath;\n    }\n\n    return path.join(os.homedir(), matches[0]);\n  }\n}\n\nexport const vimrc = new VimrcImpl();\n"
  },
  {
    "path": "src/configuration/vimrcKeyRemappingBuilder.ts",
    "content": "import { IVimrcKeyRemapping } from './iconfiguration';\n\nclass VimrcKeyRemappingBuilderImpl {\n  /**\n   * Regex for mapping lines\n   *\n   * * `^(` -> start of mapping type capture\n   *   * `map!?`\\\n   *   _matches:_\n   *    * :map\n   *    * :map!\n   *\n   *   * `|smap`\\\n   *   _matches:_\n   *    * :smap\n   *\n   *   * `|[nvxoilc]m(?:a(?:p)?)?`\\\n   *   _matches:_\n   *    * :nm[ap]\n   *    * :vm[ap]\n   *    * :xm[ap]\n   *    * :om[ap]\n   *    * :im[ap]\n   *    * :lm[ap]\n   *    * :cm[ap]\n   *\n   *   * `|(?:`\n   *     * `[nvxl]no?r?|`\\\n   *     _matches:_\n   *      * :nn[or]\n   *      * :vn[or]\n   *      * :xn[or]\n   *      * :ln[or]\n   *\n   *     * `[oic]nor?|`\\\n   *     _matches:_\n   *      * :ono[r]\n   *      * :ino[r]\n   *      * :cno[r]\n   *\n   *     * `snor`\\\n   *     _matches:_\n   *      * :snor\n   *   * `)(?:e(?:m(?:a(?:p)?)?)?)?`\\\n   *     _matches the remaining optional [emap]_\n   *\n   *   * `|no(?:r(?:e(?:m(?:a(?:p)?)?)?)?)?!?`\\\n   *   _matches:_\n   *    * :no[remap]\n   *    * :no[remap]!\n   * * `)` -> end of mapping type capture\n   *\n   * * `(?!.*(?:<expr>|<script>))` -> don't allow mappings with <expr> or <script> arguments\n   * * `(?:(?:<buffer>|<silent>|<nowait>|<special>)\\s?)*` -> allow any of these arguments without capture\n   * * `([\\S]+)\\s+` -> match the {lhs} (we call it 'before')\n   * * `(?!.*<Plug>|.*<SID>)` -> don't allow mappings with <Plug> or <SID>\n   * * `([\\S ]+)$` -> match the {rhs} (we call it 'after') allowing spaces for commands like `:edit {file}<CR>`\n   */\n  private static readonly KEY_REMAPPING_REG_EX =\n    /^(map!?|smap|[nvxoilc]m(?:a(?:p)?)?|(?:[nvxl]no?r?|[oic]nor?|snor)(?:e(?:m(?:a(?:p)?)?)?)?|no(?:r(?:e(?:m(?:a(?:p)?)?)?)?)?!?)\\s+(?!.*(?:<expr>|<script>))(?:(?:<buffer>|<silent>|<nowait>|<special>)\\s?)*([\\S]+)\\s+(?!.*<Plug>|.*<SID>)([\\S ]+)$/i;\n\n  /**\n   * Regex for unmapping lines\n   *\n   * * `^(` -> start of mapping type capture\n   *   * `unm(?:a(?:p)?)?!?`\\\n   *   _matches:_\n   *    * :unm[ap]\n   *    * :unm[ap]!\n   *\n   *   * `|[nvxoilc]u(?:n(?:m(?:a(?:p)?)?)?)?`\\\n   *   _matches:_\n   *    * :nu[nmap]\n   *    * :vu[nmap]\n   *    * :xu[nmap]\n   *    * :ou[nmap]\n   *    * :iu[nmap]\n   *    * :lu[nmap]\n   *    * :cu[nmap]\n   *\n   *   * `|sunm(?:a(?:p)?)?`\\\n   *   _matches:_\n   *    * :sunm[ap]\n   * * `)` -> end of mapping type capture\n   *\n   * * `(?:(?:<buffer>|<silent>|<nowait>|<special>)\\s?)*` -> allow any of these arguments without capture\n   * * `([\\S]+)$` -> match the {lhs} (we call it 'before')\n   */\n  private static readonly KEY_UNREMAPPING_REG_EX =\n    /^(unm(?:a(?:p)?)?!?|[nvxoilc]u(?:n(?:m(?:a(?:p)?)?)?)?|sunm(?:a(?:p)?)?)\\s+(?:(?:<buffer>|<silent>|<nowait>|<special>)\\s?)*([\\S]+)$/i;\n\n  /**\n   * Regex for clear mapping lines\n   *\n   * * `^(` -> start of mapping clear type capture\n   *   * `mapc(?:l(?:e(?:a(?:r)?)?)?)?!?`\\\n   *   _matches:_\n   *    * :mapc[lear]\n   *    * :mapc[lear]!\n   *\n   *   * `[nvxsoilc]mapc(?:l(?:e(?:a(?:r)?)?)?)?`\\\n   *   _matches:_\n   *    * :nmapc[lear]\n   *    * :vmapc[lear]\n   *    * :xmapc[lear]\n   *    * :smapc[lear]\n   *    * :omapc[lear]\n   *    * :imapc[lear]\n   *    * :lmapc[lear]\n   *    * :cmapc[lear]\n   * * `)` -> end of mapping clear type capture\n   *\n   * * `([\\S]+)$` -> match the {lhs} (we call it 'before')\n   */\n  private static readonly KEY_CLEAR_REMAPPING_REG_EX =\n    /^(mapc(?:l(?:e(?:a(?:r)?)?)?)?!?|[nvxsoilc]mapc(?:l(?:e(?:a(?:r)?)?)?)?)$/i;\n\n  /**\n   * Regex for each key of {lhs} and {rhs}\n   *\n   * `(`\\\n   * `<[^>]+>` -> match any special key of type <key>\\\n   * `|` -> or\\\n   * `.` -> any key\\\n   * `)`\n   */\n  private static readonly KEY_LIST_REG_EX = /(<[^<>]+>|.)/g;\n\n  /**\n   * Regex to match a Vim command like `:edit {file}<CR>`\n   *\n   * `^(:.+)` -> match ':' character plus 1 or more character for the command\\\n   * `<[Cc][Rr]>$` -> match <CR> at the end of the command\n   */\n  private static readonly VIM_COMMAND_REG_EX = /^(:.+)<[Cc][Rr]>$/;\n\n  /**\n   * @returns A remapping if the given `line` parses to one, and `undefined` otherwise.\n   */\n  public async build(\n    line: string,\n    vscodeCommands: string[],\n  ): Promise<IVimrcKeyRemapping | undefined> {\n    const matches = VimrcKeyRemappingBuilderImpl.KEY_REMAPPING_REG_EX.exec(line);\n    if (!matches || matches.length < 4) {\n      return undefined;\n    }\n\n    const type = matches[1];\n    const before = matches[2];\n    const after = matches[3];\n\n    const vimCommand = after.match(VimrcKeyRemappingBuilderImpl.VIM_COMMAND_REG_EX);\n\n    let command: {\n      after?: string[];\n      commands?: string[];\n    };\n    if (vscodeCommands.includes(after)) {\n      command = { commands: [after] };\n    } else if (vimCommand) {\n      command = { commands: [vimCommand[1]] };\n    } else {\n      command = { after: VimrcKeyRemappingBuilderImpl.buildKeyList(after) };\n    }\n\n    return {\n      keyRemapping: {\n        before: VimrcKeyRemappingBuilderImpl.buildKeyList(before),\n        source: 'vimrc',\n        ...command,\n      },\n      keyRemappingType: type,\n    };\n  }\n\n  /**\n   * @returns A remapping if the given `line` parses to one, and `undefined` otherwise.\n   */\n  public async buildUnmapping(line: string): Promise<IVimrcKeyRemapping | undefined> {\n    const matches = VimrcKeyRemappingBuilderImpl.KEY_UNREMAPPING_REG_EX.exec(line);\n    if (!matches || matches.length < 3) {\n      return undefined;\n    }\n\n    const type = matches[1];\n    const before = matches[2];\n\n    return {\n      keyRemapping: {\n        before: VimrcKeyRemappingBuilderImpl.buildKeyList(before),\n        source: 'vimrc',\n      },\n      keyRemappingType: type,\n    };\n  }\n\n  /**\n   * @returns An empty remapping with its type if the given `line` parses to one, and `undefined` otherwise.\n   */\n  public async buildClearMapping(line: string): Promise<IVimrcKeyRemapping | undefined> {\n    const matches = VimrcKeyRemappingBuilderImpl.KEY_CLEAR_REMAPPING_REG_EX.exec(line);\n    if (!matches || matches.length < 2) {\n      return undefined;\n    }\n\n    const type = matches[1];\n\n    return {\n      keyRemapping: {\n        before: ['<Nop>'],\n        source: 'vimrc',\n      },\n      keyRemappingType: type,\n    };\n  }\n\n  private static buildKeyList(keyString: string): string[] {\n    const keyList: string[] = [];\n    let matches: RegExpMatchArray | null = null;\n    do {\n      matches = VimrcKeyRemappingBuilderImpl.KEY_LIST_REG_EX.exec(keyString);\n      if (matches) {\n        keyList.push(matches[0]);\n      }\n    } while (matches);\n\n    return keyList;\n  }\n}\n\nexport const vimrcKeyRemappingBuilder = new VimrcKeyRemappingBuilderImpl();\n"
  },
  {
    "path": "src/error.ts",
    "content": "export enum ErrorCode {\n  InvalidAddress = 14,\n  InvalidExpression = 15,\n  InvalidRange = 16,\n  MarkNotSet = 20,\n  NoAlternateFile = 23,\n  NoInsertedTextYet = 29,\n  NoFileName = 32,\n  NoPreviousSubstituteRegularExpression = 33,\n  NoPreviousCommand = 34,\n  NoPreviousRegularExpression = 35,\n  NoWriteSinceLastChange = 37,\n  CannotChangeReadOnlyVariable = 46,\n  MultipleMatches = 93,\n  NoMatchingBuffer = 94,\n  NoSuchVariable = 108,\n  MissingQuote = 114,\n  UnknownFunction_call = 117,\n  TooManyArgs = 118,\n  NotEnoughArgs = 119,\n  UndefinedVariable = 121,\n  ErrorWritingToFile = 208,\n  FileNoLongerAvailable = 211,\n  RecursiveMapping = 223,\n  NoStringUnderCursor = 348,\n  NothingInRegister = 353,\n  InvalidRegisterName = 354,\n  SearchHitTop = 384,\n  SearchHitBottom = 385,\n  CannotCloseLastWindow = 444,\n  CantFindFileInPath = 447,\n  ArgumentRequired = 471,\n  InvalidArgument474 = 474,\n  InvalidArgument475 = 475,\n  NoRangeAllowed = 481,\n  PatternNotFound = 486,\n  TrailingCharacters = 488,\n  NotAnEditorCommand = 492,\n  NoBuffersDeleted = 516,\n  UnknownOption = 518,\n  NumberRequiredAfterEqual = 521,\n  AtStartOfChangeList = 662,\n  AtEndOfChangeList = 663,\n  ChangeListIsEmpty = 664,\n  ListIndexOutOfRange = 684,\n  ArgumentMustBeAList = 686,\n  LessTargetsThanListItems = 687,\n  MoreTargetsThanListItems = 688,\n  CanOnlyIndexAListDictionaryOrBlob = 689,\n  CanOnlyCompareListWithList = 691,\n  InvalidOperationForList = 692,\n  InvalidOperationForFuncrefs = 694,\n  CannotIndexAFuncref = 695,\n  UnknownFunction_funcref = 700,\n  InvalidTypeForLen = 701,\n  UsingAFuncrefAsANumber = 703,\n  FuncrefVariableNameMustStartWithACapital = 704,\n  SliceRequiresAListOrBlobValue = 709,\n  ListValueHasMoreItemsThanTarget = 710,\n  ListValueHasNotEnoughItems = 711,\n  ArgumentOfFuncMustBeAListOrDictionary = 712,\n  ListRequired = 714,\n  DictionaryRequired = 715,\n  KeyNotPresentInDictionary = 716,\n  CannotUseSliceWithADictionary = 719,\n  DuplicateKeyInDictionary = 721,\n  StrideIsZero = 726,\n  StartPastEnd = 727,\n  UsingADictionaryAsANumber = 728,\n  UsingListAsAString = 730,\n  UsingFuncrefAsAString = 729,\n  UsingDictionaryAsAString = 731,\n  WrongVariableType = 734,\n  CanOnlyCompareDictionaryWithDictionary = 735,\n  InvalidOperationForDictionary = 736,\n  ValueIsLocked = 741,\n  UsingAListAsANumber = 745,\n  NoPreviouslyUsedRegister = 748,\n  CannotUseModuloWithFloat = 804,\n  UsingAFloatAsANumber = 805,\n  UsingFloatAsAString = 806,\n  NumberOrFloatRequired = 808,\n  ArgumentOfMapMustBeAListDictionaryOrBlob = 896,\n  ListOrBlobRequired = 897,\n  MaxDepthMustBeANonNegativeNumber = 900,\n  ExpectedADict = 922,\n  SecondArgumentOfFunction = 923,\n  PositiveCountRequired = 939,\n  BlobLiteralShouldHaveAnEvenNumberOfHexCharacters = 973,\n  UsingABlobAsANumber = 974,\n  CanOnlyCompareBlobWithBlob = 977,\n  InvalidOperationForBlob = 978,\n  CannotModifyExistingVariable = 995,\n  CannotLock = 996,\n  ListRequiredForArgument = 1211,\n}\n\nexport class VimError extends Error {\n  public readonly code: ErrorCode;\n  public override readonly message: string;\n\n  override toString(): string {\n    return `E${this.code}: ${this.message}`;\n  }\n\n  private constructor(code: ErrorCode, message: string) {\n    super();\n    this.code = code;\n    this.message = message;\n  }\n\n  static InvalidAddress(): VimError {\n    return new VimError(ErrorCode.InvalidAddress, 'Invalid address');\n  }\n  static InvalidExpression(expr: string): VimError {\n    return new VimError(ErrorCode.InvalidExpression, `Invalid expression: \"${expr}\"`);\n  }\n  static InvalidRange(): VimError {\n    return new VimError(ErrorCode.InvalidRange, 'Invalid range');\n  }\n  static MarkNotSet(): VimError {\n    return new VimError(ErrorCode.MarkNotSet, 'Mark not set');\n  }\n  static NoAlternateFile(): VimError {\n    return new VimError(ErrorCode.NoAlternateFile, 'No alternate file');\n  }\n  static NoInsertedTextYet(): VimError {\n    return new VimError(ErrorCode.NoInsertedTextYet, 'No inserted text yet');\n  }\n  static NoFileName(): VimError {\n    return new VimError(ErrorCode.NoFileName, 'No file name');\n  }\n  static NoPreviousSubstituteRegularExpression(): VimError {\n    return new VimError(\n      ErrorCode.NoPreviousSubstituteRegularExpression,\n      'No previous substitute regular expression',\n    );\n  }\n  static NoPreviousCommand(): VimError {\n    return new VimError(ErrorCode.NoPreviousCommand, 'No previous command');\n  }\n  static NoPreviousRegularExpression(): VimError {\n    return new VimError(ErrorCode.NoPreviousRegularExpression, 'No previous regular expression');\n  }\n  static NoWriteSinceLastChange(): VimError {\n    return new VimError(\n      ErrorCode.NoWriteSinceLastChange,\n      'No write since last change (add ! to override)',\n    );\n  }\n  static CannotChangeReadOnlyVariable(variable: string): VimError {\n    return new VimError(\n      ErrorCode.CannotChangeReadOnlyVariable,\n      `Cannot change read-only variable \"${variable}\"`,\n    );\n  }\n  static MultipleMatches(pattern: string): VimError {\n    return new VimError(ErrorCode.MultipleMatches, `More than one match for ${pattern}`);\n  }\n  static NoMatchingBuffer(bufferName: string): VimError {\n    return new VimError(ErrorCode.NoMatchingBuffer, `No matching buffer for ${bufferName}`);\n  }\n  static NoSuchVariable(name: string): VimError {\n    return new VimError(ErrorCode.NoSuchVariable, `No such variable: \"${name}\"`);\n  }\n  static MissingQuote(): VimError {\n    return new VimError(ErrorCode.MissingQuote, 'Missing quote');\n  }\n  static UnknownFunction_call(func: string): VimError {\n    return new VimError(ErrorCode.UnknownFunction_call, `Unknown function: ${func}`);\n  }\n  static TooManyArgs(func: string): VimError {\n    return new VimError(ErrorCode.TooManyArgs, `Too many arguments for function: ${func}`);\n  }\n  static NotEnoughArgs(func: string): VimError {\n    return new VimError(ErrorCode.NotEnoughArgs, `Not enough arguments for function: ${func}`);\n  }\n  static UndefinedVariable(name: string): VimError {\n    return new VimError(ErrorCode.UndefinedVariable, `Undefined variable: ${name}`);\n  }\n  static ErrorWritingToFile(): VimError {\n    return new VimError(ErrorCode.ErrorWritingToFile, 'Error writing to file');\n  }\n  static FileNoLongerAvailable(): VimError {\n    return new VimError(ErrorCode.FileNoLongerAvailable, 'File no longer available');\n  }\n  static RecursiveMapping(): VimError {\n    return new VimError(ErrorCode.RecursiveMapping, 'Recursive mapping');\n  }\n  static NoStringUnderCursor(): VimError {\n    return new VimError(ErrorCode.NoStringUnderCursor, 'No string under cursor');\n  }\n  static NothingInRegister(register: string): VimError {\n    return new VimError(ErrorCode.NothingInRegister, `Nothing in register ${register}`);\n  }\n  static InvalidRegisterName(register: string): VimError {\n    return new VimError(ErrorCode.InvalidRegisterName, `Invalid register name: '${register}'`);\n  }\n  static SearchHitTop(pattern: string): VimError {\n    return new VimError(ErrorCode.SearchHitTop, `Search hit TOP without match for: ${pattern}`);\n  }\n  static SearchHitBottom(pattern: string): VimError {\n    return new VimError(\n      ErrorCode.SearchHitBottom,\n      `Search hit BOTTOM without match for: ${pattern}`,\n    );\n  }\n  static CannotCloseLastWindow(): VimError {\n    return new VimError(ErrorCode.CannotCloseLastWindow, 'Cannot close last window');\n  }\n  static CantFindFileInPath(fileName: string): VimError {\n    return new VimError(ErrorCode.CantFindFileInPath, `Can't find file \"${fileName}\" in path`);\n  }\n  static ArgumentRequired(): VimError {\n    return new VimError(ErrorCode.ArgumentRequired, 'Argument required');\n  }\n  static InvalidArgument474(arg?: string): VimError {\n    return new VimError(\n      ErrorCode.InvalidArgument474,\n      arg !== undefined ? `Invalid argument: ${arg}` : 'Invalid argument',\n    );\n  }\n  static InvalidArgument475(arg: string): VimError {\n    return new VimError(ErrorCode.InvalidArgument475, `Invalid argument: ${arg}`);\n  }\n  static NoRangeAllowed(): VimError {\n    return new VimError(ErrorCode.NoRangeAllowed, 'No range allowed');\n  }\n  static PatternNotFound(pattern: string | undefined): VimError {\n    return new VimError(\n      ErrorCode.PatternNotFound,\n      pattern !== undefined ? `Pattern not found: ${pattern}` : 'Pattern not found',\n    );\n  }\n  static TrailingCharacters(chars?: string): VimError {\n    return new VimError(\n      ErrorCode.TrailingCharacters,\n      chars !== undefined ? `Trailing characters: ${chars}` : 'Trailing characters',\n    );\n  }\n  static NotAnEditorCommand(command: string): VimError {\n    return new VimError(ErrorCode.NotAnEditorCommand, `Not an editor command: ${command}`);\n  }\n  static NoBuffersDeleted(): VimError {\n    return new VimError(ErrorCode.NoBuffersDeleted, 'No buffers were deleted');\n  }\n  static UnknownOption(option: string): VimError {\n    return new VimError(ErrorCode.UnknownOption, `Unknown option: ${option}`);\n  }\n  static NumberRequiredAfterEqual(what: string): VimError {\n    return new VimError(ErrorCode.NumberRequiredAfterEqual, `Number required after =: ${what}`);\n  }\n  static AtStartOfChangeList(): VimError {\n    return new VimError(ErrorCode.AtStartOfChangeList, 'At start of changelist');\n  }\n  static AtEndOfChangeList(): VimError {\n    return new VimError(ErrorCode.AtEndOfChangeList, 'At end of changelist');\n  }\n  static ChangeListIsEmpty(): VimError {\n    return new VimError(ErrorCode.ChangeListIsEmpty, 'changelist is empty');\n  }\n  static ListIndexOutOfRange(idx: number): VimError {\n    return new VimError(ErrorCode.ListIndexOutOfRange, `list index out of range: ${idx}`);\n  }\n  static ArgumentMustBeAList(func: string): VimError {\n    return new VimError(ErrorCode.ArgumentMustBeAList, `Argument of ${func} must be a List`);\n  }\n  static LessTargetsThanListItems(): VimError {\n    return new VimError(ErrorCode.LessTargetsThanListItems, 'Less targets than List items');\n  }\n  static MoreTargetsThanListItems(): VimError {\n    return new VimError(ErrorCode.MoreTargetsThanListItems, 'More targets than List items');\n  }\n  static CanOnlyIndexAListDictionaryOrBlob(): VimError {\n    return new VimError(\n      ErrorCode.CanOnlyIndexAListDictionaryOrBlob,\n      'Can only index a List, Dictionary or Blob',\n    );\n  }\n  static CanOnlyCompareListWithList(): VimError {\n    return new VimError(ErrorCode.CanOnlyCompareListWithList, 'Can only compare List with List');\n  }\n  static InvalidOperationForList(): VimError {\n    return new VimError(ErrorCode.InvalidOperationForList, 'Invalid operation for List');\n  }\n  static InvalidOperationForFuncrefs(): VimError {\n    return new VimError(ErrorCode.InvalidOperationForFuncrefs, 'Invalid operation for Funcrefs');\n  }\n  static CannotIndexAFuncref(): VimError {\n    return new VimError(ErrorCode.CannotIndexAFuncref, 'Cannot index a Funcref');\n  }\n  static UnknownFunction_funcref(): VimError {\n    return new VimError(ErrorCode.UnknownFunction_funcref, 'Unknown function');\n  }\n  static InvalidTypeForLen(): VimError {\n    return new VimError(ErrorCode.InvalidTypeForLen, 'Invalid type for len()');\n  }\n  static UsingAFuncrefAsANumber(): VimError {\n    return new VimError(ErrorCode.UsingAFuncrefAsANumber, 'Using a Funcref as a Number');\n  }\n  static FuncrefVariableNameMustStartWithACapital(name: string): VimError {\n    return new VimError(\n      ErrorCode.FuncrefVariableNameMustStartWithACapital,\n      `Funcref variable name must start with a capital: ${name}`,\n    );\n  }\n  static SliceRequiresAListOrBlobValue(): VimError {\n    return new VimError(\n      ErrorCode.SliceRequiresAListOrBlobValue,\n      '[:] requires a List or Blob value',\n    );\n  }\n  static ListValueHasMoreItemsThanTarget(): VimError {\n    return new VimError(\n      ErrorCode.ListValueHasMoreItemsThanTarget,\n      'List value has more items than target',\n    );\n  }\n  static ListValueHasNotEnoughItems(): VimError {\n    return new VimError(ErrorCode.ListValueHasNotEnoughItems, 'List value has not enough items');\n  }\n  static ArgumentOfFuncMustBeAListOrDictionary(func: string): VimError {\n    return new VimError(\n      ErrorCode.ArgumentOfFuncMustBeAListOrDictionary,\n      `Argument of ${func} must be a List or Dictionary`,\n    );\n  }\n  static ListRequired(): VimError {\n    return new VimError(ErrorCode.ListRequired, 'List required');\n  }\n  static DictionaryRequired(): VimError {\n    return new VimError(ErrorCode.DictionaryRequired, 'Dictionary required');\n  }\n  static KeyNotPresentInDictionary(key: string): VimError {\n    return new VimError(\n      ErrorCode.KeyNotPresentInDictionary,\n      `Key not present in Dictionary: ${key}`,\n    );\n  }\n  static CannotUseSliceWithADictionary(): VimError {\n    return new VimError(\n      ErrorCode.CannotUseSliceWithADictionary,\n      'Cannot use [:] with a Dictionary',\n    );\n  }\n  static DuplicateKeyInDictionary(key: string): VimError {\n    return new VimError(ErrorCode.DuplicateKeyInDictionary, `Duplicate key in Dictionary: ${key}`);\n  }\n  static StrideIsZero(): VimError {\n    return new VimError(ErrorCode.StrideIsZero, 'Stride is zero');\n  }\n  static StartPastEnd(): VimError {\n    return new VimError(ErrorCode.StartPastEnd, 'Start past end');\n  }\n  static UsingADictionaryAsANumber(): VimError {\n    return new VimError(ErrorCode.UsingADictionaryAsANumber, 'Using a Dictionary as a Number');\n  }\n  static UsingFuncrefAsAString(): VimError {\n    return new VimError(ErrorCode.UsingFuncrefAsAString, 'using Funcref as a String');\n  }\n  static UsingListAsAString(): VimError {\n    return new VimError(ErrorCode.UsingListAsAString, 'Using List as a String');\n  }\n  static UsingDictionaryAsAString(): VimError {\n    return new VimError(ErrorCode.UsingDictionaryAsAString, 'Using Dictionary as a String');\n  }\n  static WrongVariableType(operation: string): VimError {\n    return new VimError(ErrorCode.WrongVariableType, `Wrong variable type for ${operation}`);\n  }\n  static CanOnlyCompareDictionaryWithDictionary(): VimError {\n    return new VimError(\n      ErrorCode.CanOnlyCompareDictionaryWithDictionary,\n      'Can only compare Dictionary with Dictionary',\n    );\n  }\n  static InvalidOperationForDictionary(): VimError {\n    return new VimError(\n      ErrorCode.InvalidOperationForDictionary,\n      'Invalid operation for Dictionary',\n    );\n  }\n  static ValueIsLocked(name: string): VimError {\n    return new VimError(ErrorCode.ValueIsLocked, `Value is locked: ${name}`);\n  }\n  static UsingAListAsANumber(): VimError {\n    return new VimError(ErrorCode.UsingAListAsANumber, 'Using a List as a Number');\n  }\n  static NoPreviouslyUsedRegister(): VimError {\n    return new VimError(ErrorCode.NoPreviouslyUsedRegister, 'No previously used register');\n  }\n  static CannotUseModuloWithFloat(): VimError {\n    return new VimError(ErrorCode.CannotUseModuloWithFloat, \"Cannot use '%' with Float\");\n  }\n  static UsingAFloatAsANumber(): VimError {\n    return new VimError(ErrorCode.UsingAFloatAsANumber, 'Using a Float as a Number');\n  }\n  static UsingFloatAsAString(): VimError {\n    return new VimError(ErrorCode.UsingFloatAsAString, 'Using Float as a String');\n  }\n  static NumberOrFloatRequired(): VimError {\n    return new VimError(ErrorCode.NumberOrFloatRequired, 'Number or Float required');\n  }\n  static ArgumentOfMapMustBeAListDictionaryOrBlob(): VimError {\n    return new VimError(\n      ErrorCode.ArgumentOfMapMustBeAListDictionaryOrBlob,\n      'Argument of map() must be a List, Dictionary or Blob',\n    );\n  }\n  static ListOrBlobRequired(): VimError {\n    return new VimError(ErrorCode.ListOrBlobRequired, 'List or Blob required');\n  }\n  static MaxDepthMustBeANonNegativeNumber(): VimError {\n    return new VimError(\n      ErrorCode.MaxDepthMustBeANonNegativeNumber,\n      'maxdepth must be a non-negative number',\n    );\n  }\n  static ExpectedADict(): VimError {\n    return new VimError(ErrorCode.ExpectedADict, 'expected a dict');\n  }\n  static SecondArgumentOfFunction(): VimError {\n    return new VimError(\n      ErrorCode.SecondArgumentOfFunction,\n      'Second argument of function() must be a list or a dict',\n    );\n  }\n  static PositiveCountRequired(): VimError {\n    return new VimError(ErrorCode.PositiveCountRequired, 'Positive count required');\n  }\n  static BlobLiteralShouldHaveAnEvenNumberOfHexCharacters(): VimError {\n    return new VimError(\n      ErrorCode.BlobLiteralShouldHaveAnEvenNumberOfHexCharacters,\n      'Blob literal should have an even number of hex characters',\n    );\n  }\n  static UsingABlobAsANumber(): VimError {\n    return new VimError(ErrorCode.UsingABlobAsANumber, 'Using a Blob as a Number');\n  }\n  static CanOnlyCompareBlobWithBlob(): VimError {\n    return new VimError(ErrorCode.CanOnlyCompareBlobWithBlob, 'Can only compare Blob with Blob');\n  }\n  static InvalidOperationForBlob(): VimError {\n    return new VimError(ErrorCode.InvalidOperationForBlob, 'Invalid operation for Blob');\n  }\n  static CannotModifyExistingVariable(): VimError {\n    return new VimError(ErrorCode.CannotModifyExistingVariable, 'Cannot modify existing variable');\n  }\n  static CannotLock(\n    what: 'a range' | 'an option' | 'a list or dict' | 'an environment variable' | 'a register',\n  ): VimError {\n    return new VimError(ErrorCode.CannotLock, `Cannot lock ${what}`);\n  }\n  static ListRequiredForArgument(idx: number): VimError {\n    return new VimError(ErrorCode.ListRequiredForArgument, `List required for argument ${idx}`);\n  }\n}\n\n/**\n * Used to stop a remapping or a chain of nested remappings after a VimError, a failed action\n * or the force stop recursive mapping key (<C-c> or <Esc>). (Vim doc :help map-error)\n */\nexport class ForceStopRemappingError extends Error {\n  constructor(reason: string = 'StopRemapping') {\n    super(reason);\n  }\n\n  static fromVimError(vimError: VimError): ForceStopRemappingError {\n    return new ForceStopRemappingError(vimError.toString());\n  }\n}\n"
  },
  {
    "path": "src/globals.ts",
    "content": "import { IConfiguration } from './configuration/iconfiguration';\n\n/**\n * Global variables shared throughout extension\n */\nexport class Globals {\n  /**\n   * This is where we put files like HistoryFile. The path is given to us by VSCode.\n   */\n  static extensionStoragePath: string;\n\n  /**\n   * Used for testing.\n   */\n  static isTesting = false;\n  static mockConfiguration: IConfiguration;\n}\n"
  },
  {
    "path": "src/history/historyFile.ts",
    "content": "import { HistoryBase } from 'platform/history';\nimport { ExtensionContext } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { Globals } from '../globals';\n\n// TODO(jfields): What's going on here? Just combine HistoryFile and HistoryBase...\nexport class HistoryFile {\n  private base: HistoryBase;\n\n  get historyFilePath(): string {\n    return this.base.historyKey;\n  }\n\n  constructor(context: ExtensionContext, historyFileName: string) {\n    this.base = new HistoryBase(context, historyFileName, Globals.extensionStoragePath);\n  }\n\n  public async add(value: string | undefined): Promise<void> {\n    return this.base.add(value, configuration.history);\n  }\n\n  public get(): string[] {\n    return this.base.get(configuration.history);\n  }\n\n  public clear() {\n    this.base.clear();\n  }\n\n  public async load(): Promise<void> {\n    await this.base.load();\n  }\n}\n\nexport class SearchHistory extends HistoryFile {\n  constructor(context: ExtensionContext) {\n    super(context, '.search_history');\n  }\n}\n\nexport class CommandLineHistory extends HistoryFile {\n  constructor(context: ExtensionContext) {\n    super(context, '.cmdline_history');\n  }\n}\n"
  },
  {
    "path": "src/history/historyTracker.ts",
    "content": "/**\n * HistoryTracker is a handrolled undo/redo tracker for VSC. We currently\n * track history as a list of \"steps\", each of which consists of 1 or more\n * \"changes\".\n *\n * A Change is something like adding or deleting a few letters.\n *\n * A Step is multiple Changes.\n *\n * Undo/Redo will advance forward or backwards through Steps.\n */\nimport * as DiffMatchPatch from 'diff-match-patch';\nimport * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { Cursor } from '../common/motion/cursor';\nimport { earlierOf } from '../common/motion/position';\nimport { VimError } from '../error';\nimport { Jump } from '../jumps/jump';\nimport { Mode } from '../mode/mode';\nimport { globalState } from '../state/globalState';\nimport { StatusBar } from '../statusBar';\nimport { Logger } from '../util/logger';\nimport { VimState } from './../state/vimState';\nimport { TextEditor } from './../textEditor';\n\nconst diffEngine = new DiffMatchPatch.diff_match_patch();\ndiffEngine.Diff_Timeout = 1; // 1 second\n\nclass DocumentChange {\n  /**\n   * The Position at which this change starts\n   */\n  public readonly start: Position;\n\n  /**\n   * The text that existed before this change\n   */\n  public readonly before: string;\n\n  /**\n   * The text that exists after this change\n   */\n  public readonly after: string;\n\n  public static insert(start: Position, text: string) {\n    return new DocumentChange(start, '', text);\n  }\n\n  public static delete(start: Position, text: string) {\n    return new DocumentChange(start, text, '');\n  }\n\n  public static replace(start: Position, before: string, after: string) {\n    return new DocumentChange(start, before, after);\n  }\n\n  /**\n   * @returns A new DocumentChange that represents undoing this change\n   */\n  public reversed() {\n    return DocumentChange.replace(this.start, this.after, this.before);\n  }\n\n  private constructor(start: Position, before: string, after: string) {\n    this.start = start;\n    this.before = before;\n    this.after = after;\n  }\n\n  /**\n   * Run this change.\n   */\n  public async do(editor: vscode.TextEditor): Promise<void> {\n    await TextEditor.replace(editor, this.beforeRange, this.after);\n  }\n\n  /**\n   * Run this change in reverse.\n   */\n  public async undo(editor: vscode.TextEditor): Promise<void> {\n    await TextEditor.replace(editor, this.afterRange, this.before);\n  }\n\n  /**\n   * The Range that the before text occupied\n   */\n  public get beforeRange(): vscode.Range {\n    return new vscode.Range(this.start, this.start.advancePositionByText(this.before));\n  }\n\n  /**\n   * The Range that the after text occupies\n   */\n  public get afterRange(): vscode.Range {\n    return new vscode.Range(this.start, this.start.advancePositionByText(this.after));\n  }\n}\n\ninterface IMarkBase {\n  name: string;\n  position: Position;\n}\n\nexport interface ILocalMark extends IMarkBase {\n  isUppercaseMark: false;\n}\n\nexport interface IGlobalMark extends IMarkBase {\n  isUppercaseMark: true;\n  document: vscode.TextDocument;\n}\n\nexport type IMark = ILocalMark | IGlobalMark;\n\n/**\n * An undo's worth of changes; generally corresponds to a single action.\n */\nclass HistoryStep {\n  /**\n   * The insertions and deletions that occured in this history step.\n   */\n  public changes: DocumentChange[];\n\n  /**\n   * Whether the user is still inserting or deleting for this history step.\n   */\n  public isFinished = false;\n\n  /**\n   * When this step was finished.\n   * // TODO: we currently set it to the current time upon creation to cover some edge cases, but this is messy.\n   */\n  public timestamp: Date;\n\n  /**\n   * The cursor position at the start of this history step.\n   * Restored by `u`.\n   */\n  public cursorsAtStart: readonly Cursor[] | undefined;\n\n  /**\n   * The position of every mark at the start of this history step.\n   */\n  public marks: IMark[] = [];\n\n  /**\n   * HACK: true if this step came from `U`.\n   * In `UU`, the second `U` should undo the first, and no more.\n   */\n  public readonly cameFromU: boolean;\n\n  /**\n   * \"global\" marks which operate across files. (when IMark.name is uppercase)\n   */\n  static globalMarks: IGlobalMark[] = [];\n\n  constructor(init: { marks: IMark[]; changes?: DocumentChange[]; cameFromU?: boolean }) {\n    this.changes = init.changes ?? [];\n    this.marks = init.marks ?? [];\n    this.cameFromU = init.cameFromU ?? false;\n\n    // This will usually be overwritten when the HistoryStep is finished\n    this.timestamp = new Date();\n  }\n\n  /**\n   * Collapse individual character changes into larger blocks of changes\n   */\n  public merge(document: vscode.TextDocument): void {\n    if (this.changes.length < 2) {\n      return;\n    }\n\n    // merged will replace this.changes\n    const merged: DocumentChange[] = [];\n    // manually reduce() this.changes with variables `current` and `next`\n    // we can't use reduce() directly because the loop can emit multiple elements\n    let current = this.changes[0];\n    for (const next of this.changes.slice(1)) {\n      if (current.before.length + current.after.length === 0) {\n        // current is eliminated, replace it with top of merged, or adopt next as current\n        // see also add+del case\n        if (merged.length > 0) {\n          current = merged.pop()!;\n        } else {\n          current = next;\n          continue;\n        }\n      }\n\n      const intersect = current.afterRange.intersection(next.beforeRange);\n      if (intersect) {\n        const [first, second] = current.start.isBeforeOrEqual(next.start)\n          ? [current, next]\n          : [next, current];\n        const intersectLength =\n          document.offsetAt(intersect.end) - document.offsetAt(intersect.start);\n        current = DocumentChange.replace(\n          first.start,\n          first.before + second.before.slice(intersectLength),\n          first.after.slice(0, first.after.length - intersectLength) + second.after,\n        );\n      } else {\n        merged.push(current);\n        current = next;\n      }\n    }\n    merged.push(current);\n    this.changes = merged;\n  }\n\n  /**\n   * Returns, as a string, the time that has passed since this step took place.\n   */\n  public howLongAgo(): string {\n    const now = new Date();\n    const timeDiffMillis = now.getTime() - this.timestamp.getTime();\n    const timeDiffSeconds = Math.floor(timeDiffMillis / 1000);\n    if (timeDiffSeconds === 1) {\n      return `1 second ago`;\n    } else if (timeDiffSeconds >= 100) {\n      const hours = this.timestamp.getHours();\n      const minutes = this.timestamp.getMinutes().toString().padStart(2, '0');\n      const seconds = this.timestamp.getSeconds().toString().padStart(2, '0');\n      return `${hours}:${minutes}:${seconds}`;\n    } else {\n      return `${timeDiffSeconds} seconds ago`;\n    }\n  }\n}\n\n/**\n * A simple wrapper around a list of HistorySteps, for sanity's sake\n */\nclass UndoStack {\n  private historySteps: HistoryStep[] = [];\n  private currentStepIndex = -1;\n\n  // The marks as they existed before the first HistoryStep\n  private initialMarks: IMark[] = [];\n\n  public getHistoryStepAtIndex(idx: number): HistoryStep | undefined {\n    return this.historySteps[idx];\n  }\n\n  public getCurrentHistoryStepIndex(): number {\n    return this.currentStepIndex;\n  }\n\n  public getStackDepth(): number {\n    return this.historySteps.length;\n  }\n\n  /**\n   * @returns the current HistoryStep, or undefined if nothing's been done yet\n   */\n  public getCurrentHistoryStep(): HistoryStep | undefined {\n    if (this.currentStepIndex === -1) {\n      return undefined;\n    }\n\n    return this.historySteps[this.currentStepIndex];\n  }\n\n  /**\n   * Goes forward in time (redo), if possible\n   *\n   * @returns the new current HistoryStep, or undefined if none exists\n   */\n  public stepForward(): HistoryStep | undefined {\n    if (this.currentStepIndex === this.historySteps.length - 1) {\n      return undefined;\n    }\n\n    this.currentStepIndex++;\n    return this.getCurrentHistoryStep();\n  }\n\n  /**\n   * Goes backward in time (undo), if possible\n   *\n   * @returns the old HistoryStep, or undefined if there was none\n   */\n  public stepBackward(): HistoryStep | undefined {\n    const step = this.getCurrentHistoryStep();\n    if (step) {\n      this.currentStepIndex--;\n    }\n    return step;\n  }\n\n  /**\n   * Adds a change to the current unfinished step if there is one, or a new step if there isn't\n   */\n  public pushChange(change: DocumentChange): void {\n    let step = this.getCurrentHistoryStep();\n    if (step === undefined || step.isFinished) {\n      this.currentStepIndex++;\n      this.historySteps.splice(this.currentStepIndex);\n      step = new HistoryStep({\n        marks: step?.marks ?? this.initialMarks,\n      });\n      this.historySteps.push(step);\n    }\n\n    step.changes.push(change);\n  }\n\n  /**\n   * You probably don't want to use this.\n   * @see pushChange\n   */\n  public pushHistoryStep(step: HistoryStep) {\n    this.currentStepIndex++;\n    this.historySteps.splice(this.currentStepIndex + 1);\n    this.historySteps.push(step);\n  }\n\n  public getCurrentMarkList(): IMark[] {\n    const step = this.getCurrentHistoryStep();\n    return step?.marks ?? this.initialMarks;\n  }\n\n  public removeMarks(marks?: readonly string[]): void {\n    const step = this.getCurrentHistoryStep();\n    if (marks === undefined) {\n      if (step) {\n        step.marks = [];\n      } else {\n        this.initialMarks = [];\n      }\n    } else {\n      if (step) {\n        step.marks = step.marks.filter((m) => !marks.includes(m.name));\n      } else {\n        this.initialMarks = this.initialMarks.filter((m) => !marks.includes(m.name));\n      }\n    }\n  }\n}\n\nclass ChangeList {\n  private readonly changeLocations: Position[] = [];\n  private index: number | undefined;\n\n  public addChangePosition(position: Position) {\n    if (this.changeLocations.at(-1)?.line === position.line) {\n      this.changeLocations[this.changeLocations.length - 1] = position;\n    } else {\n      this.changeLocations.push(position);\n    }\n\n    this.index = undefined;\n  }\n\n  public nextChangePosition(): Position | VimError {\n    if (this.index === undefined) {\n      if (this.changeLocations.length === 0) {\n        return VimError.ChangeListIsEmpty();\n      }\n      this.index = this.changeLocations.length - 1;\n    } else if (this.index < this.changeLocations.length - 1) {\n      this.index++;\n    } else {\n      return VimError.AtEndOfChangeList();\n    }\n    return this.changeLocations[this.index];\n  }\n\n  public prevChangePosition(): Position | VimError {\n    if (this.index === undefined) {\n      if (this.changeLocations.length === 0) {\n        return VimError.ChangeListIsEmpty();\n      }\n      this.index = this.changeLocations.length - 1;\n    } else if (this.index > 0) {\n      this.index--;\n    } else {\n      return VimError.AtStartOfChangeList();\n    }\n    return this.changeLocations[this.index];\n  }\n}\n\nexport class HistoryTracker {\n  public currentContentChanges: vscode.TextDocumentContentChangeEvent[] = [];\n\n  private nextStepCursorsAtStart: readonly Cursor[] | undefined;\n\n  private readonly undoStack = new UndoStack();\n\n  private readonly changeList = new ChangeList();\n\n  /**\n   * The state of the document the last time HistoryTracker.addChange() or HistoryTracker.ignoreChange() was called.\n   * This is used to avoid retrieiving the document text and doing a full diff when it isn't necessary.\n   */\n  private previousDocumentState: {\n    text: string;\n    versionNumber: number;\n  };\n\n  private readonly vimState: VimState;\n\n  constructor(vimState: VimState) {\n    this.vimState = vimState;\n    this.previousDocumentState = {\n      text: this.getDocumentText(),\n      versionNumber: this.getDocumentVersion(),\n    };\n  }\n\n  private getDocumentText(): string {\n    // vimState.editor can be undefined in some unit tests\n    return this.vimState.editor?.document.getText() ?? '';\n  }\n\n  private getDocumentVersion(): number {\n    // vimState.editor can be undefined in some unit tests\n    return this.vimState.editor?.document.version ?? -1;\n  }\n\n  /**\n   * Marks refer to relative locations in the document, rather than absolute ones.\n   *\n   * This big gnarly method updates our marks such that they continue to mark\n   * the same character when the user does a document edit that would move the\n   * text that was marked.\n   */\n  private updateAndReturnMarks(document: vscode.TextDocument): IMark[] {\n    const previousMarks = this.getAllMarksInDocument(document);\n    const newMarks: IMark[] = [];\n\n    // clone old marks into new marks\n    for (const mark of previousMarks) {\n      newMarks.push({ ...mark });\n    }\n\n    for (const change of this.undoStack.getCurrentHistoryStep()?.changes ?? []) {\n      for (const newMark of newMarks) {\n        // Run through each character added/deleted, and see if it could have\n        // affected the position of this mark.\n\n        let pos = change.start;\n\n        // Pull mark back with deleted text\n        for (const ch of change.before.replace(/\\r/g, '')) {\n          if (pos.isBefore(newMark.position)) {\n            if (ch === '\\n') {\n              newMark.position = new Position(\n                Math.max(newMark.position.line - 1, 0),\n                newMark.position.character,\n              );\n            } else if (pos.line === newMark.position.line) {\n              newMark.position = new Position(\n                newMark.position.line,\n                Math.max(newMark.position.character - 1, 0),\n              );\n            }\n          }\n\n          if (ch === '\\n') {\n            // The 99999 is a bit of a hack here. It's very difficult and\n            // completely unnecessary to get the correct position, so we\n            // just fake it.\n            pos = new Position(Math.max(pos.line - 1, 0), 99999);\n          } else {\n            pos = new Position(pos.line, Math.max(pos.character - 1, 0));\n          }\n        }\n\n        pos = change.start;\n\n        // Push mark forward with added text\n        for (const ch of change.after.replace(/\\r/g, '')) {\n          if (pos.isBeforeOrEqual(newMark.position)) {\n            if (ch === '\\n') {\n              newMark.position = new Position(\n                newMark.position.line + 1,\n                newMark.position.character,\n              );\n            } else if (pos.line === newMark.position.line) {\n              newMark.position = new Position(\n                newMark.position.line,\n                newMark.position.character + 1,\n              );\n            }\n          }\n\n          if (ch === '\\n') {\n            pos = new Position(pos.line + 1, 0);\n          } else {\n            pos = new Position(pos.line, pos.character + 1);\n          }\n        }\n      }\n    }\n\n    // Ensure the position of every mark is within the range of the document.\n\n    const docEnd = TextEditor.getDocumentEnd(this.vimState.document);\n    for (const mark of newMarks) {\n      if (mark.position.isAfter(docEnd)) {\n        mark.position = docEnd;\n      }\n    }\n\n    return newMarks;\n  }\n\n  /**\n   * @returns the shared static list if isFileMark is true, otherwise returns the currentHistoryStep.marks.\n   */\n  private getMarkList(isFileMark: boolean): IMark[] {\n    return isFileMark ? HistoryStep.globalMarks : this.undoStack.getCurrentMarkList();\n  }\n\n  /**\n   * @returns all local and global marks in the given editor\n   */\n  private getAllMarksInDocument(document: vscode.TextDocument): IMark[] {\n    const globalMarks = HistoryStep.globalMarks.filter((mark) => mark.document === document);\n    return [...this.getLocalMarks(), ...globalMarks];\n  }\n\n  /**\n   * Adds a mark.\n   */\n  public addMark(document: vscode.TextDocument, position: Position, markName: string): void {\n    if (markName === \"'\" || markName === '`') {\n      globalState.jumpTracker.recordJump(Jump.fromStateNow(this.vimState));\n    } else if (markName === '<') {\n      if (this.vimState.lastVisualSelection) {\n        this.vimState.lastVisualSelection.start = position;\n      } else {\n        this.vimState.lastVisualSelection = {\n          mode: Mode.Visual,\n          start: position,\n          end: position,\n        };\n      }\n      if (\n        this.vimState.lastVisualSelection.mode === Mode.Visual &&\n        this.vimState.lastVisualSelection.end.isBefore(this.vimState.lastVisualSelection.start)\n      ) {\n        // HACK: Visual mode representation is stupid\n        this.vimState.lastVisualSelection.end = this.vimState.lastVisualSelection.start;\n      }\n    } else if (markName === '>') {\n      if (this.vimState.lastVisualSelection) {\n        this.vimState.lastVisualSelection.end = position.getRight();\n      } else {\n        this.vimState.lastVisualSelection = {\n          mode: Mode.Visual,\n          start: position.getRight(),\n          end: position.getRight(),\n        };\n      }\n      if (\n        this.vimState.lastVisualSelection.mode === Mode.Visual &&\n        this.vimState.lastVisualSelection.start.isAfter(this.vimState.lastVisualSelection.end)\n      ) {\n        // HACK: Visual mode representation is stupid\n        this.vimState.lastVisualSelection.start = this.vimState.lastVisualSelection.end.getLeft();\n        this.vimState.lastVisualSelection.end = this.vimState.lastVisualSelection.start;\n      }\n    } else {\n      const isUppercaseMark = markName.toUpperCase() === markName;\n      const newMark: IMark = isUppercaseMark\n        ? {\n            position,\n            name: markName,\n            isUppercaseMark,\n            document,\n          }\n        : {\n            position,\n            name: markName,\n            isUppercaseMark,\n          };\n      this.putMarkInList(newMark);\n    }\n  }\n\n  /**\n   * Puts the mark into either the global or local marks array depending on mark.isUppercaseMark.\n   */\n  private putMarkInList(mark: IMark): void {\n    const marks = this.getMarkList(mark.isUppercaseMark);\n    const previousIndex = marks.findIndex((existingMark) => existingMark.name === mark.name);\n    if (previousIndex !== -1) {\n      marks[previousIndex] = mark;\n    } else {\n      marks.push(mark);\n    }\n  }\n\n  /**\n   * Retrieves a mark from either the global or local array depending on mark.isUppercaseMark.\n   */\n  public getMark(name: string): IMark | undefined {\n    // First, handle \"special\" marks\n    let position: Position | undefined;\n    if (name === '<') {\n      const lvs = this.vimState.lastVisualSelection;\n      const linewise = lvs?.mode === Mode.VisualLine;\n      // If start is after end, prefer end (handles inverted visual selections)\n      const base = lvs?.start.isAfter(lvs.end) ? lvs.end : lvs?.start;\n      position = linewise ? base?.with({ character: 0 }) : base;\n    } else if (name === '>') {\n      const lvs = this.vimState.lastVisualSelection;\n      const linewise = lvs?.mode === Mode.VisualLine;\n      // If start is after end, prefer start (handles inverted visual selections)\n      const base = lvs?.start.isAfter(lvs.end) ? lvs.start : lvs?.end.getLeft();\n      position = linewise ? base?.getLineEnd() : base;\n    } else if (name === '[') {\n      position = this.getLastChangeStartPosition();\n    } else if (name === ']') {\n      position = this.getLastChangeEndPosition();\n    } else if (name === '.') {\n      position = this.getLastHistoryStartPosition();\n    } else if (name === \"'\" || name === '`') {\n      position = globalState.jumpTracker.end?.position;\n    }\n    if (position) {\n      return {\n        name,\n        position,\n        isUppercaseMark: false,\n      };\n    }\n\n    const marks = this.getMarkList(name.toUpperCase() === name);\n    return marks.find((mark) => mark.name === name);\n  }\n\n  /**\n   * Removes all local marks.\n   */\n  public removeLocalMarks(): void {\n    this.undoStack.removeMarks();\n  }\n\n  /**\n   * Removes all marks matching from either the global or local array.\n   */\n  public removeMarks(markNames: readonly string[]): void {\n    if (markNames.length === 0) {\n      return;\n    }\n\n    this.undoStack.removeMarks(markNames);\n\n    HistoryStep.globalMarks = HistoryStep.globalMarks.filter(\n      (mark) => mark.name === '' || !markNames.includes(mark.name),\n    );\n  }\n\n  /**\n   * Gets all local marks.  I.e., marks that are specific for the current\n   * editor.\n   */\n  public getLocalMarks(): readonly ILocalMark[] {\n    return this.undoStack.getCurrentMarkList().filter((mark) => !mark.isUppercaseMark);\n  }\n\n  /**\n   * Gets all global marks.  I.e., marks that are shared among all editors.\n   */\n  public getGlobalMarks(): readonly IMark[] {\n    return HistoryStep.globalMarks;\n  }\n\n  public getMarks(): readonly IMark[] {\n    return [...this.getLocalMarks(), ...HistoryStep.globalMarks];\n  }\n\n  /**\n   * Adds an individual Change to the current Step.\n   *\n   * Determines what changed by diffing the document against what it used to look like.\n   */\n  public addChange(force: boolean = false): boolean {\n    if (this.getDocumentVersion() === this.previousDocumentState.versionNumber) {\n      return false;\n    }\n\n    if (this.nextStepCursorsAtStart === undefined) {\n      this.nextStepCursorsAtStart = this.vimState.cursorsInitialState;\n      Logger.debug(`Set nextStepCursorsAtStart to ${this.nextStepCursorsAtStart}`);\n    }\n\n    if (\n      !force &&\n      (this.vimState.currentMode === Mode.Insert || this.vimState.currentMode === Mode.Replace)\n    ) {\n      // We can ignore changes while we're in insert/replace mode, since we can't interact with them (via undo, etc.) until we're back to normal mode\n      // This allows us to avoid a little bit of work per keystroke, but more importantly, it means we'll get bigger contiguous edit chunks to merge.\n      // This is particularly impactful when there are multiple cursors, which are otherwise difficult to optimize.\n      return false;\n    }\n\n    const newText = this.getDocumentText();\n    if (newText === this.previousDocumentState.text) {\n      return false;\n    }\n\n    // TODO: This is actually pretty stupid! Since we already have the cursorPosition,\n    // and most diffs are just +/- a few characters, we can just do a direct comparison rather\n    // than using jsdiff.\n\n    // The difficulty is with a few rare commands like :%s/one/two/g that make\n    // multiple changes in different places simultaneously. For those, we could require\n    // them to call addChange manually, I guess...\n\n    // Couldn't we also ditch this diffing approach entirely and just use `TextDocumentContentChangeEvent`s?\n\n    const diffs = diffEngine.diff_main(this.previousDocumentState.text, newText);\n    diffEngine.diff_cleanupEfficiency(diffs);\n\n    let currentPosition = new Position(0, 0);\n\n    for (const diff of diffs) {\n      const [whatHappened, text] = diff;\n      const added = whatHappened === DiffMatchPatch.DIFF_INSERT;\n      const removed = whatHappened === DiffMatchPatch.DIFF_DELETE;\n\n      if (added || removed) {\n        this.undoStack.pushChange(\n          added\n            ? DocumentChange.insert(currentPosition, text)\n            : DocumentChange.delete(currentPosition, text),\n        );\n      }\n\n      if (!removed) {\n        currentPosition = currentPosition.advancePositionByText(text);\n      }\n    }\n\n    this.previousDocumentState = {\n      text: newText,\n      versionNumber: this.getDocumentVersion(),\n    };\n\n    return true;\n  }\n\n  /**\n   * Tells the HistoryTracker that although the document has changed, we should simply\n   * ignore that change. Most often used when the change was itself triggered by\n   * the HistoryTracker.\n   */\n  public ignoreChange(): void {\n    this.previousDocumentState = {\n      text: this.getDocumentText(),\n      versionNumber: this.getDocumentVersion(),\n    };\n  }\n\n  /**\n   * Until we mark it as finished, the active Step will\n   * accrue multiple changes. This function will mark it as finished,\n   * and the next time we add a change, it'll be added to a new Step.\n   */\n  public finishCurrentStep(): void {\n    const currentHistoryStep = this.undoStack.getCurrentHistoryStep();\n    if (currentHistoryStep && !currentHistoryStep.isFinished) {\n      currentHistoryStep.isFinished = true;\n      currentHistoryStep.timestamp = new Date();\n\n      if (this.nextStepCursorsAtStart !== undefined) {\n        currentHistoryStep.cursorsAtStart ??= this.nextStepCursorsAtStart;\n        this.nextStepCursorsAtStart = undefined;\n      }\n\n      currentHistoryStep.merge(this.vimState.document);\n\n      currentHistoryStep.marks = this.updateAndReturnMarks(this.vimState.document);\n\n      const changes = currentHistoryStep.changes;\n      if (changes) {\n        const changePos = changes[0].after ? changes[0].afterRange.end.getLeft() : changes[0].start;\n        this.changeList.addChangePosition(changePos);\n      }\n\n      Logger.debug(`Finished history step with ${changes.length} change(s)`);\n    }\n  }\n\n  /**\n   * Undo the current HistoryStep, if there is one\n   *\n   * @returns the new cursor positions, or undefined if there are no steps to undo\n   */\n  public async goBackHistoryStep(): Promise<void> {\n    const step = this.undoStack.stepBackward();\n    if (step === undefined) {\n      StatusBar.setText(this.vimState, 'Already at oldest change');\n      return;\n    }\n\n    for (const change of step.changes.slice(0).reverse()) {\n      await change.undo(this.vimState.editor);\n    }\n\n    this.ignoreChange();\n\n    // TODO: if there are more/fewer lines after undoing the change, it should say so\n    const changes = step.changes.length === 1 ? `1 change` : `${step.changes.length} changes`;\n    StatusBar.setText(\n      this.vimState,\n      `${changes}; before #${\n        this.undoStack.getCurrentHistoryStepIndex() + 1\n      }  ${step.howLongAgo()}`,\n    );\n\n    const newCursors = step.cursorsAtStart?.map((c) => {\n      return Cursor.atPosition(earlierOf(c.start, c.stop));\n    });\n    if (newCursors) {\n      this.vimState.cursors = newCursors;\n    }\n  }\n\n  /**\n   * Redo the next HistoryStep, if there is one\n   *\n   * @returns the new cursor positions, or undefined if there are no steps to redo\n   */\n  public async goForwardHistoryStep(): Promise<void> {\n    const step = this.undoStack.stepForward();\n    if (step === undefined) {\n      StatusBar.setText(this.vimState, 'Already at newest change');\n      return;\n    }\n\n    // TODO: do these transformations in a batch\n    for (const change of step.changes) {\n      await change.do(this.vimState.editor);\n    }\n\n    this.ignoreChange();\n\n    const changes = step.changes.length === 1 ? `1 change` : `${step.changes.length} changes`;\n    StatusBar.setText(\n      this.vimState,\n      `${changes}; after #${this.undoStack.getCurrentHistoryStepIndex()}  ${step.howLongAgo()}`,\n    );\n\n    const newCursors = step.cursorsAtStart?.map((c) => {\n      return Cursor.atPosition(earlierOf(c.start, c.stop));\n    });\n    if (newCursors) {\n      this.vimState.cursors = newCursors;\n    }\n  }\n\n  /**\n   * Logic for command U.\n   *\n   * Performs an undo action for all changes which occurred on\n   * the same line as the most recent change.\n   * Returns undefined if there's no more steps back to go.\n   * Only acts upon consecutive changes on the most-recently-changed line.\n   * U itself is a change, so all the changes are reversed and added back\n   * to the history.\n   *\n   * This method contains a significant amount of extra logic to account for\n   * the difficult scenario where a newline is embedded in a change (ex: '\\nhello'), which\n   * is created by the 'o' command. Vim behavior for the 'U' command does\n   * not undo newlines, so the change text needs to be checked & trimmed.\n   * This worst-case scenario tends to offset line values and make it harder to\n   * determine the line of the change, so this behavior is also compensated.\n   */\n  public async goBackHistoryStepsOnLine(): Promise<void> {\n    const currentHistoryStep = this.undoStack.getCurrentHistoryStep();\n    if (currentHistoryStep === undefined) {\n      return;\n    }\n\n    let done: boolean = false;\n    const changesToUndo: DocumentChange[] = [];\n\n    let lastChange = currentHistoryStep.changes[currentHistoryStep.changes.length - 1];\n    const undoLine = lastChange.afterRange.end.line;\n\n    for (let stepIdx = this.undoStack.getCurrentHistoryStepIndex(); stepIdx >= 0; stepIdx--) {\n      const step = this.undoStack.getHistoryStepAtIndex(stepIdx)!;\n      for (let change of [...step.changes].reverse()) {\n        /*\n         * This conditional accounts for the behavior where the change is a newline\n         * followed by text to undo. Note the line offset behavior that must be compensated.\n         */\n        const newlines = [...change.after.matchAll(/\\n/g)];\n        if (newlines.length > 0 && change.start.line + newlines.length === undoLine) {\n          // Modify & replace the change to avoid undoing the newline embedded in the change\n          change = DocumentChange.insert(\n            new Position(change.start.line + 1, 0),\n            change.after.slice(change.after.lastIndexOf('\\n')),\n          );\n          done = true;\n        } else if (newlines.length > 0 || change.start.line !== undoLine) {\n          done = true;\n          break;\n        }\n\n        changesToUndo.push(change);\n        lastChange = change;\n        if (done) {\n          break;\n        }\n      }\n      if (step.cameFromU) {\n        done = true;\n      }\n      if (done) {\n        break;\n      }\n    }\n\n    if (changesToUndo.length > 0) {\n      for (const change of changesToUndo) {\n        await change.undo(this.vimState.editor);\n      }\n\n      const newStep = new HistoryStep({\n        marks: this.undoStack.getCurrentMarkList(),\n        changes: changesToUndo.map((change) => change.reversed()).reverse(),\n        cameFromU: true,\n      });\n      this.nextStepCursorsAtStart = [Cursor.atPosition(lastChange.start)];\n      this.undoStack.pushHistoryStep(newStep);\n\n      this.finishCurrentStep();\n    }\n\n    this.ignoreChange();\n\n    /*\n     * Unlike the goBackHistoryStep() function, this function does not trust the\n     * HistoryStep.cursorStart property. This can lead to invalid cursor position errors.\n     * Since this function reverses change-by-change, rather than step-by-step,\n     * the cursor position is based on the start of the last change that is undone.\n     */\n    if (lastChange) {\n      this.vimState.cursors = [Cursor.atPosition(lastChange.start)];\n    }\n  }\n\n  /**\n   * Gets the ending cursor position of the last Change of the last Step.\n   *\n   * In practice, this sets the cursor position to the end of\n   * the most recent text change.\n   */\n  public getLastChangeEndPosition(): Position | undefined {\n    return this.undoStack.getCurrentHistoryStep()?.changes.at(-1)?.afterRange.end;\n  }\n\n  public getLastHistoryStartPosition(): Position | undefined {\n    return this.undoStack.getCurrentHistoryStep()?.cursorsAtStart?.[0]?.start;\n  }\n\n  private getLastChangeStartPosition(): Position | undefined {\n    return this.undoStack.getCurrentHistoryStep()?.changes.at(-1)?.start;\n  }\n\n  /**\n   * Logic for `g,` command\n   */\n  public nextChangeInChangeList(): Position | VimError {\n    return this.changeList.nextChangePosition();\n  }\n\n  /**\n   * Logic for `g;` command\n   */\n  public prevChangeInChangeList(): Position | VimError {\n    return this.changeList.prevChangePosition();\n  }\n}\n"
  },
  {
    "path": "src/jumps/jump.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position } from 'vscode';\n\nimport { VimState } from '../state/vimState';\n\n/**\n * Represents a Jump in the JumpTracker.\n * Includes information necessary to determine jump actions,\n * and to be able to open the related file.\n */\nexport class Jump {\n  public readonly document: vscode.TextDocument;\n  public readonly position: Position;\n\n  /**\n   *\n   * @param options\n   * @param options.editor - The editor associated with the jump.\n   * @param options.position - The line and column number information.\n   */\n  constructor({ document, position }: { document: vscode.TextDocument; position: Position }) {\n    this.document = document;\n    this.position = position;\n  }\n\n  public get fileName() {\n    return this.document.fileName;\n  }\n\n  /**\n   * Factory method for creating a Jump from a VimState's current cursor position.\n   * @param vimState - State that contains the fileName and position for the jump\n   */\n  public static fromStateNow(vimState: VimState) {\n    return new Jump({\n      document: vimState.document,\n      position: vimState.cursorStopPosition,\n    });\n  }\n\n  /**\n   * Factory method for creating a Jump from a VimState's cursor position,\n   * before any actions or commands were performed.\n   * @param vimState - State that contains the fileName and prior position for the jump\n   */\n  public static fromStateBefore(vimState: VimState) {\n    return new Jump({\n      document: vimState.document,\n      position: vimState.cursorsInitialState[0].stop,\n    });\n  }\n\n  /**\n   * Determine whether another jump matches the same file path, line number, and character column.\n   * @param other - Another Jump to compare against\n   */\n  public isSamePosition(other: Jump): boolean {\n    return this.fileName === other.fileName && this.position.isEqual(other.position);\n  }\n}\n"
  },
  {
    "path": "src/jumps/jumpTracker.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { VimState } from '../state/vimState';\nimport { FileCommand } from './../cmd_line/commands/file';\n\nimport { existsAsync } from 'platform/fs';\nimport { Position } from 'vscode';\nimport { VimError } from '../error';\nimport { Jump } from './jump';\n\nconst MAX_JUMPS = 100;\n\n/**\n * JumpTracker is a handrolled version of VSCode's TextEditorState\n * in relation to the 'workbench.action.navigateBack' command.\n */\nexport class JumpTracker {\n  private _jumps: Jump[] = [];\n  private _currentJumpNumber = 0;\n\n  /**\n   * When receiving vscode.window.onDidChangeActiveTextEditor messages,\n   * don't record the jump if we initiated the command.\n   *\n   * Either the jump was added, or it was traversing jump history\n   * and shouldn't count as a new jump.\n   */\n  public isJumpingThroughHistory = false;\n\n  /**\n   * All recorded jumps, in the order of occurrence.\n   */\n  public get jumps(): readonly Jump[] {\n    return this._jumps;\n  }\n\n  /**\n   * Current position in the list of jumps.\n   * This will be past last index if not traveling through history.\n   */\n  public get currentJumpNumber(): number {\n    return this._currentJumpNumber;\n  }\n\n  /**\n   * Current jump in the list of jumps.\n   */\n  public get currentJump(): Jump | undefined {\n    return this._jumps.at(this._currentJumpNumber);\n  }\n\n  /**\n   * Current jump in the list of jumps.\n   */\n  public get hasJumps(): boolean {\n    return this._jumps.length > 0;\n  }\n\n  /**\n   * Last jump in list of jumps.\n   */\n  public get end(): Jump | undefined {\n    return this._jumps.at(-1);\n  }\n\n  /**\n   * Record that a jump occurred.\n   *\n   * If the current position is back in history,\n   * jumps after this position will be removed.\n   *\n   * @param from - File/position jumped from\n   * @param to - File/position jumped to\n   */\n  public recordJump(from: Jump, to?: Jump) {\n    if (to && from.isSamePosition(to)) {\n      return;\n    }\n\n    this.pushJump(from, to);\n  }\n\n  /**\n   * Record that a jump occurred from one file to another.\n   * This is likely only needed on a handler for\n   * vscode.window.onDidChangeActiveTextEditor.\n   *\n   * File jumps have extra checks in place, keeping in mind\n   * whether this plugin initiated the jump, whether the new file is\n   * a legitimate file.\n   *\n   * @param from - File/position jumped from\n   * @param to - File/position jumped to\n   */\n  public handleFileJump(from: Jump | undefined, to: Jump) {\n    if (this.isJumpingThroughHistory) {\n      this.isJumpingThroughHistory = false;\n      return;\n    }\n\n    if (to.document.isClosed) {\n      // Wallaby.js seemed to be adding an extra file jump, named e.g. extension-output-#4\n      // It was marked closed when jumping to it. Hopefully we can rely on checking isClosed\n      // when extensions get all weird on us.\n      return;\n    }\n\n    this.pushJump(from, to);\n  }\n\n  private async performFileJump(jump: Jump, vimState: VimState): Promise<void> {\n    this.isJumpingThroughHistory = true;\n\n    if (jump.document) {\n      try {\n        // Open jump file from stored editor\n        await vscode.window.showTextDocument(jump.document, {\n          selection: new vscode.Range(jump.position, jump.position),\n        });\n      } catch (e: unknown) {\n        // This can happen when the document we'd like to jump to is weird (like a search editor) or has been deleted\n        throw VimError.FileNoLongerAvailable();\n      }\n    } else if (await existsAsync(jump.fileName)) {\n      // Open jump file from disk\n      await new FileCommand({\n        name: 'edit',\n        bang: false,\n        opt: [],\n        file: jump.fileName,\n        cmd: { type: 'line_number', line: jump.position.line },\n        createFileIfNotExists: false,\n      }).execute(vimState);\n    } else {\n      // Get jump file from visible editors\n      const editor: vscode.TextEditor = vscode.window.visibleTextEditors.filter(\n        (e) => e.document.fileName === jump.fileName,\n      )[0];\n\n      if (editor) {\n        await vscode.window.showTextDocument(editor.document, {\n          selection: new vscode.Range(jump.position, jump.position),\n        });\n      }\n    }\n  }\n\n  /**\n   * Jump forward, possibly resulting in a file jump\n   */\n  public async jumpForward(position: Position, vimState: VimState): Promise<void> {\n    await this.jumpThroughHistory((jump) => this.recordJumpForward(jump), position, vimState);\n  }\n\n  /**\n   * Jump back, possibly resulting in a file jump\n   */\n  public async jumpBack(position: Position, vimState: VimState): Promise<void> {\n    await this.jumpThroughHistory((jump) => this.recordJumpBack(jump), position, vimState);\n  }\n\n  private async jumpThroughHistory(\n    getJump: (j: Jump) => Jump,\n    position: Position,\n    vimState: VimState,\n  ): Promise<void> {\n    let jump = new Jump({\n      document: vimState.document,\n      position,\n    });\n\n    const iterations = vimState.recordedState.count || 1;\n    for (let i = 0; i < iterations; i++) {\n      jump = getJump(Jump.fromStateNow(vimState));\n    }\n\n    if (!jump) {\n      return;\n    }\n\n    const jumpedFiles = jump.fileName !== vimState.document.fileName;\n\n    if (jumpedFiles) {\n      await this.performFileJump(jump, vimState);\n    } else {\n      vimState.cursorStopPosition = jump.position;\n    }\n  }\n\n  /**\n   * Get the previous jump in history.\n   * Continues further back if the current line is on the same line.\n   *\n   * @param from - File/position jumped from\n   */\n  public recordJumpBack(from: Jump): Jump {\n    if (!this.hasJumps) {\n      return from;\n    }\n\n    if (this._currentJumpNumber <= 0) {\n      return this._jumps[0];\n    }\n\n    const to: Jump = this._jumps[this._currentJumpNumber - 1];\n\n    if (this._currentJumpNumber === this._jumps.length) {\n      this.recordJump(from, to);\n      this._currentJumpNumber = this._currentJumpNumber - 2;\n    } else {\n      this._currentJumpNumber = this._currentJumpNumber - 1;\n    }\n\n    return to;\n  }\n\n  /**\n   * Get the next jump in history.\n   * Continues further ahead if the current line is on the same line.\n   *\n   * @param from - File/position jumped from\n   */\n  public recordJumpForward(from: Jump): Jump {\n    if (!this.hasJumps) {\n      return from;\n    }\n\n    if (this._currentJumpNumber >= this._jumps.length) {\n      return from;\n    }\n\n    this._currentJumpNumber = Math.min(this._currentJumpNumber + 1, this._jumps.length - 1);\n    return this._jumps[this._currentJumpNumber];\n  }\n\n  /**\n   * Update existing jumps when lines were added to a document.\n   *\n   * @param document - Document that was changed, typically a vscode.TextDocument.\n   * @param position - Location where the text was added.\n   * @param text - Text containing one or more newline characters.\n   */\n  public handleTextAdded(\n    document: { fileName: string },\n    position: vscode.Position,\n    text: string,\n  ): void {\n    // Get distance from newlines in the text added.\n    // Unlike handleTextDeleted, the range parameter distance between start/end is generally zero,\n    // just showing where the text was added.\n    const distance = text.split('').filter((c) => c === '\\n').length;\n    if (distance === 0) {\n      return;\n    }\n\n    for (const [i, jump] of this._jumps.entries()) {\n      const jumpIsAfterAddedText =\n        jump.fileName === document.fileName && jump.position.line > position.line;\n\n      if (jumpIsAfterAddedText) {\n        const newPosition = new Position(jump.position.line + distance, jump.position.character);\n\n        this.changePositionForJumpNumber(i, jump, newPosition);\n      }\n    }\n  }\n\n  /**\n   * Update existing jumps when lines were removed from a document.\n   *\n   * Vim doesn't actually remove deleted lines. Instead, it seems to shift line numbers down\n   * for any jumps after the deleted text, and preserves position for jumps on deleted lines or\n   * lines above the deleted lines. After lines are shifted, if there are multiple jumps on a line,\n   * the duplicates are removed, preserving the newest jumps (preserving latest column number).\n   *\n   * Lines are shifted based on number of lines deleted before the jump. So if e.g. the jump is on\n   * a middle line #6, where the jump above and below it were also deleted, the jump position would\n   * move down just one so it is now line #5, based on the line above it being deleted.\n   *\n   * @param document - Document that was changed, typically a vscode.TextDocument.\n   * @param range - Location where the text was removed.\n   */\n  public handleTextDeleted(document: { fileName: string }, range: vscode.Range): void {\n    // Note that this is like Array.slice, such that range.end.line is one line AFTER a deleted line,\n    // so distance is expected to be at least 1.\n    const distance = range.end.line - range.start.line;\n\n    for (let i = this._jumps.length - 1; i >= 0; i--) {\n      const jump = this._jumps[i];\n\n      if (jump.fileName !== document.fileName) {\n        continue;\n      }\n\n      const jumpIsAfterDeletedText = jump.position.line > range.start.line;\n\n      if (jumpIsAfterDeletedText) {\n        const newLineShiftedUp =\n          jump.position.line - Math.min(jump.position.line - range.start.line, distance);\n        const newPosition = new Position(newLineShiftedUp, jump.position.character);\n\n        this.changePositionForJumpNumber(i, jump, newPosition);\n      }\n    }\n\n    this.removeDuplicateJumps();\n  }\n\n  /**\n   * Clear existing jumps and reset jump position.\n   */\n  public clearJumps(): void {\n    this._jumps.splice(0, this._jumps.length);\n    this._currentJumpNumber = 0;\n  }\n\n  private pushJump(from: Jump | undefined, to: Jump | undefined) {\n    if (from) {\n      this.clearJumpsOnSameLine(from);\n    }\n\n    if (from && (!to || !from.isSamePosition(to))) {\n      if (this._jumps.length === MAX_JUMPS) {\n        this._jumps.splice(0, 1);\n      }\n\n      this._jumps.push(from);\n    }\n\n    this._currentJumpNumber = this._jumps.length;\n  }\n\n  private changePositionForJumpNumber(index: number, jump: Jump, newPosition: Position) {\n    this._jumps.splice(\n      index,\n      1,\n      new Jump({\n        document: jump.document,\n        position: newPosition,\n      }),\n    );\n  }\n\n  private clearJumpsOnSameLine(jump: Jump): void {\n    this._jumps = this._jumps.filter(\n      (j) =>\n        j === jump || !(j.fileName === jump.fileName && j.position.line === jump.position.line),\n    );\n  }\n\n  private removeDuplicateJumps() {\n    const linesSeenPerFile = new Map<string, number[]>();\n    for (let i = this._jumps.length - 1; i >= 0; i--) {\n      const jump = this._jumps[i];\n\n      if (!linesSeenPerFile.has(jump.fileName)) {\n        linesSeenPerFile.set(jump.fileName, []);\n      }\n\n      const lines = linesSeenPerFile.get(jump.fileName)!;\n\n      if (lines.includes(jump.position.line)) {\n        this._jumps.splice(i, 1);\n      } else {\n        lines.push(jump.position.line);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/mode/internalSelectionsTracker.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Logger } from '../util/logger';\nimport { areSelectionArraysEqual, hashSelectionsArray } from '../util/selections';\n\n/**\n * Expiry time for our internal selections updates tracking, after which we can reasonably expect an\n * associated VSCode selection change event to have been fired for a given selections update, if one\n * ever will be.\n *\n * We want to be generous enough to allow for delayed VSCode selection change event firings, to\n * avoid treating the delayed event as an external update and re-updating our own selections state\n * based on an internal update we've already accounted for.\n *\n * We also want to discard stale internal updates before too long. Until we untrack them, they will\n * block incoming selection change events (including external ones) from being handled.\n */\nconst SELECTIONS_UPDATE_TO_IGNORE_EXPIRY_MS = 1000;\n\n/**\n * An internal selections update that we want to track.\n */\nexport type InternalSelectionsUpdate = {\n  /** Hash for the updated selections, based on each selection's anchor and active position. */\n  selectionsHash: string;\n  /** Timestamp (in ms since Unix epoch) of when this selections update was added to the tracker. */\n  trackedAt: number;\n};\n\n/**\n * Class that helps with tracking internal selections updates and controlling / determining whether\n * to treat incoming {@link vscode.TextEditorSelectionChangeEvent}s as internal/intermediate\n * selections changes to ignore, or as external selections updates that should update our internal\n * selections state.\n *\n * Methods exposed:\n *\n * - {@link shouldIgnoreAsInternalSelectionChangeEvent} - Determines whether to ignore a given\n * VSCode selection change event as an internal/intermediate selections update's change event\n *\n * - {@link startIgnoringIntermediateSelections} - Start ignoring selection change events while\n * running an action\n *\n * - {@link stopIgnoringIntermediateSelections} - Resume handling selection change events after\n * running an action\n *\n * - {@link maybeTrackSelectionsUpdateToIgnore} - Predicts whether the given selections update will\n * trigger a selection change event, and if so, tracks it\n */\nexport class InternalSelectionsTracker {\n  // #region Determining whether to ignore a selection change event\n\n  /**\n   * Determines whether or not to view the given VSCode selection change event as being from an\n   * internal or intermediate selections update, and thus ignore it. Removes its associated\n   * selections update entry from our tracking, if found.\n   *\n   * @returns `true` if the event should be ignored, else `false`.\n   */\n  public shouldIgnoreAsInternalSelectionChangeEvent(\n    event: vscode.TextEditorSelectionChangeEvent,\n  ): boolean {\n    // First remove stale tracked updates that we shouldn't worry about anymore\n    this.cleanupStaleSelectionsUpdatesToIgnore();\n\n    const selectionsHash = hashSelectionsArray(event.selections);\n    return (\n      this.maybeUntrackSelectionsUpdateToIgnore(selectionsHash) ||\n      this.shouldIgnoreAsIntermediateSelection(selectionsHash)\n    );\n  }\n\n  /**\n   * Looks for an equivalent selections update in our tracked selections updates to ignore, and\n   * removes it if found.\n   *\n   * @returns `true` if the selection was found and removed, else `false`.\n   */\n  private maybeUntrackSelectionsUpdateToIgnore(selectionsHash: string): boolean {\n    const index = this.selectionsUpdatesToIgnore.findIndex(\n      (update) => update.selectionsHash === selectionsHash,\n    );\n    if (index !== -1) {\n      this.selectionsUpdatesToIgnore.splice(index, 1);\n      this.logTrace(\n        `Ignoring and un-tracking internal selection update's change event ${\n          selectionsHash\n        }. Remaining internal selections updates to ignore: ${\n          this.selectionsUpdatesToIgnore.length\n        }`,\n      );\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Determines whether or not to view a VSCode selection change event (whose selections hash wasn't\n   * found in our tracked selections updates to ignore) as being from an internal action's\n   * intermediate selection, and thus ignore it.\n   *\n   * If `shouldIgnoreIntermediateSelections` is `true`, the answer is an easy yes.\n   *\n   * If not, but we are still tracking internal selections updates whose change events haven't fired\n   * yet, we assume we're just processing intermediate selections' change events that have slipped\n   * past the end of an action we just finished, and we return `true`. This may sometimes result in\n   * unintentionally ignoring an external selection made during or closely after a series of\n   * internal updates, especially if any of those internal updates were misguidedly added to our\n   * tracking by {@link maybeTrackSelectionsUpdateToIgnore} even though they don't actually trigger\n   * selection change events. For now, we err on the side of allowing false negatives (missing an\n   * external update) over false positives (re-applying an internal update), but we may want to\n   * revisit this tradeoff in the future.\n   *\n   * If neither condition is met, we return `false`.\n   *\n   * Logic originally implemented in: [VSCodeVim/Vim#5015](https://github.com/VSCodeVim/Vim/pull/5015)\n   *\n   * @param untrackedEventSelectionsHash Hashed selections array from a selection change event, that\n   * wasn't found in our tracked selections updates to ignore\n   * @returns `true` if we should ignore the event as an intermediate selection, else `false`\n   */\n  private shouldIgnoreAsIntermediateSelection(untrackedEventSelectionsHash: string): boolean {\n    if (this.shouldIgnoreIntermediateSelections) {\n      this.logTrace(\n        `Ignoring intermediate selection change event ${untrackedEventSelectionsHash} while running action`,\n      );\n      return true;\n    }\n\n    if (this.selectionsUpdatesToIgnore.length > 0) {\n      this.logWarn(\n        `Treating untracked selection change event ${\n          untrackedEventSelectionsHash\n        } as an intermediate selection to ignore; assuming it slipped past the end of an action we just ran, since there are still ${\n          this.selectionsUpdatesToIgnore.length\n        } tracked internal selections updates to ignore`,\n      );\n      return true;\n    }\n\n    return false;\n  }\n\n  // #endregion\n\n  // #region Ignoring intermediate selections during an action\n\n  /**\n   * Whether or not we should be ignoring all incoming VSCode selection change events.\n   */\n  private shouldIgnoreIntermediateSelections: boolean = false;\n\n  /**\n   * To be called when starting an action, to flag that we should start ignoring all incoming\n   * VSCode selection change events until we call `stopIgnoringIntermediateSelections`.\n   */\n  public startIgnoringIntermediateSelections(): void {\n    this.shouldIgnoreIntermediateSelections = true;\n    this.logDebug('Now ignoring intermediate selection change events while running action');\n  }\n\n  /**\n   * To be called after running an action, so we can start handling selection change events again.\n   */\n  public stopIgnoringIntermediateSelections(): void {\n    this.shouldIgnoreIntermediateSelections = false;\n    this.logDebug('Resuming handling of selection change events after running action');\n  }\n\n  // #endregion\n\n  // #region Tracking internal selections updates\n\n  /**\n   * Array of hashed and timestamped internal selections updates, whose incoming VSCode selection\n   * change events should be ignored. Each tracked update should be removed when we receive its\n   * associated selection change event, or cleaned up after an expiry time if its change event never\n   * gets fired, so that we don't indefinitely block incoming selection change events from updating\n   * our internal state.\n   *\n   * Note that we intentionally use an array, rather than a set or map, to account for validly\n   * identical selection updates which will each trigger their own vscode selection change events;\n   * e.g. if we update selections to [1,1][1,1] then [1,2][1,2] and then [1,1][1,1] again.\n   */\n  private selectionsUpdatesToIgnore: InternalSelectionsUpdate[] = [];\n\n  /**\n   * Checks if the selections update will trigger a {@link vscode.TextEditorSelectionChangeEvent},\n   * and if so, tracks it so we know to ignore its incoming selection change event. Note that VSCode\n   * only fires a selection change event if the editor's selections actually change in value.\n   *\n   * However, it should be noted that our check isn't perfect here, and we might misguidedly track\n   * an update even if it won't actually trigger a selection change event; hence the need to clean\n   * up lingering updates from `selectionsUpdatesToIgnore` after an expiry time.\n   *\n   * This is because behind the scenes, VSCode seems to quickly reset `editor.selections` after it's\n   * been set, before properly processing the update and firing a change event. So it's possible for\n   * `editor.selections` to lag behind what we've set it to in the previous `updateView` call,\n   * leading us to mistakenly think that the current update will trigger a selection change event,\n   * even if it won't actually differ from `editor.selections` when VSCode processes this update.\n   * See: [VSCodeVim/Vim#9644](https://github.com/VSCodeVim/Vim/pull/9644)\n   */\n  public maybeTrackSelectionsUpdateToIgnore({\n    updatedSelections,\n    currentEditorSelections,\n  }: {\n    updatedSelections: readonly vscode.Selection[];\n    currentEditorSelections: readonly vscode.Selection[];\n  }): void {\n    if (areSelectionArraysEqual(updatedSelections, currentEditorSelections)) {\n      // VSCode won't fire a selection change event for this update, so there's no need to track it\n      return;\n    }\n    this.trackSelectionsUpdateToIgnore(updatedSelections);\n  }\n\n  private trackSelectionsUpdateToIgnore(updatedSelections: readonly vscode.Selection[]): void {\n    const selectionsHash = hashSelectionsArray(updatedSelections);\n    this.selectionsUpdatesToIgnore.push({ selectionsHash, trackedAt: Date.now() });\n    this.logTrace(\n      `Tracking ${selectionsHash} as an internal selections update to ignore. Total tracked now: ${this.selectionsUpdatesToIgnore.length}`,\n    );\n  }\n\n  /**\n   * Removes stale entries from `selectionsUpdatesToIgnore`. Ideally there shouldn't be any stale\n   * updates present, and instead every tracked update is removed before expiration when we handle\n   * its associated selection change event. But if there are, we remove them so that we don't\n   * indefinitely ignore incoming selection change events under the incorrect assumption that they\n   * were triggered by internal updates we've already accounted for.\n   */\n  private cleanupStaleSelectionsUpdatesToIgnore(): void {\n    const now = Date.now();\n    this.selectionsUpdatesToIgnore = this.selectionsUpdatesToIgnore.filter((entry) => {\n      const { trackedAt, selectionsHash } = entry;\n      const age = now - trackedAt;\n      const hasExpired = age > SELECTIONS_UPDATE_TO_IGNORE_EXPIRY_MS;\n      if (hasExpired) {\n        this.logWarn(\n          `Un-tracking stale internal selections update ${selectionsHash} after ${\n            age\n          }ms without a matching selection change event (expiry_ms: ${SELECTIONS_UPDATE_TO_IGNORE_EXPIRY_MS}ms)`,\n        );\n      }\n      return !hasExpired;\n    });\n  }\n\n  // #endregion\n\n  // #region Logging helpers\n  private logTrace(message: string): void {\n    Logger.trace(`[InternalSelectionsTracker] ${message}`);\n  }\n  private logDebug(message: string): void {\n    Logger.debug(`[InternalSelectionsTracker] ${message}`);\n  }\n  private logWarn(message: string): void {\n    Logger.warn(`[InternalSelectionsTracker] ${message}`);\n  }\n  // #endregion\n}\n"
  },
  {
    "path": "src/mode/mode.ts",
    "content": "import * as vscode from 'vscode';\nimport { Position } from 'vscode';\n\nexport enum Mode {\n  Normal,\n  Insert,\n  Visual,\n  VisualBlock,\n  VisualLine,\n  SearchInProgressMode,\n  CommandlineInProgress,\n  Replace,\n  EasyMotionMode,\n  EasyMotionInputMode,\n  SurroundInputMode,\n  OperatorPendingMode, // Pseudo-Mode, used only when remapping. DON'T SET TO THIS MODE\n  Disabled,\n}\n\nexport enum VSCodeVimCursorType {\n  Block,\n  Line,\n  LineThin,\n  Underline,\n  TextDecoration,\n  Native,\n  UnderlineThin,\n}\n\nexport enum NormalCommandState {\n  Waiting,\n  Executing,\n  Finished,\n}\n\nexport enum DotCommandStatus {\n  Waiting,\n  Executing,\n  Finished,\n}\n\nexport enum ReplayMode {\n  Insert,\n  Replace,\n}\n\n/**\n * Is the given mode visual, visual line, or visual block?\n */\nexport function isVisualMode(mode: Mode): mode is Mode.Visual | Mode.VisualLine | Mode.VisualBlock {\n  return [Mode.Visual, Mode.VisualLine, Mode.VisualBlock].includes(mode);\n}\n\n/**\n * Is the given mode one where the cursor is on the status bar?\n * This means SearchInProgess and CommandlineInProgress modes.\n */\nexport function isStatusBarMode(\n  mode: Mode,\n): mode is Mode.CommandlineInProgress | Mode.SearchInProgressMode {\n  return [Mode.SearchInProgressMode, Mode.CommandlineInProgress].includes(mode);\n}\n\nexport function getCursorStyle(cursorType: VSCodeVimCursorType) {\n  switch (cursorType) {\n    case VSCodeVimCursorType.Block:\n      return vscode.TextEditorCursorStyle.Block;\n    case VSCodeVimCursorType.Line:\n      return vscode.TextEditorCursorStyle.Line;\n    case VSCodeVimCursorType.LineThin:\n      return vscode.TextEditorCursorStyle.LineThin;\n    case VSCodeVimCursorType.Underline:\n      return vscode.TextEditorCursorStyle.Underline;\n    case VSCodeVimCursorType.UnderlineThin:\n      return vscode.TextEditorCursorStyle.UnderlineThin;\n    case VSCodeVimCursorType.TextDecoration:\n      return vscode.TextEditorCursorStyle.LineThin;\n    case VSCodeVimCursorType.Native:\n    default:\n      return vscode.TextEditorCursorStyle.Block;\n  }\n}\n\nexport function visualBlockGetTopLeftPosition(start: Position, stop: Position): Position {\n  return new Position(Math.min(start.line, stop.line), Math.min(start.character, stop.character));\n}\n\nexport function visualBlockGetBottomRightPosition(start: Position, stop: Position): Position {\n  return new Position(Math.max(start.line, stop.line), Math.max(start.character, stop.character));\n}\n"
  },
  {
    "path": "src/mode/modeData.ts",
    "content": "import { ExCommandLine, SearchCommandLine } from '../cmd_line/commandLine';\nimport { ReplaceState } from '../state/replaceState';\nimport { Mode } from './mode';\n\n/** Modes which have no extra associated data. */\nexport type SimpleMode = Exclude<\n  Mode,\n  Mode.Replace | Mode.SearchInProgressMode | Mode.CommandlineInProgress | Mode.Insert\n>;\n\n/** State associated with the current mode. */\nexport type ModeData =\n  | {\n      mode: Mode.Replace;\n      replaceState: ReplaceState;\n    }\n  | {\n      mode: Mode.CommandlineInProgress;\n      commandLine: ExCommandLine;\n    }\n  | {\n      mode: Mode.SearchInProgressMode;\n      commandLine: SearchCommandLine;\n      /** The first line number that was visible when SearchInProgressMode began */\n      firstVisibleLineBeforeSearch: number;\n    }\n  | {\n      mode: Mode.Insert;\n      /** The high surrogate of an incomplete pair */\n      highSurrogate: string | undefined;\n    }\n  | {\n      mode: SimpleMode;\n    };\n\nexport type ModeDataFor<T extends Mode> = { mode: T } & ModeData;\n"
  },
  {
    "path": "src/mode/modeHandler.ts",
    "content": "import _ from 'lodash';\nimport * as vscode from 'vscode';\n\nimport * as process from 'process';\nimport { Position, Range, Uri } from 'vscode';\nimport { BaseMovement } from '../actions/baseMotion';\nimport { DocumentContentChangeAction } from '../actions/commands/documentChange';\nimport { QuitRecordMacro } from '../actions/commands/macro';\nimport { ReplaceCharacter } from '../actions/commands/replace';\nimport { MoveLineEnd } from '../actions/motion';\nimport { BaseOperator } from '../actions/operator';\nimport { EasyMotion } from '../actions/plugins/easymotion/easymotion';\nimport { SearchByNCharCommand } from '../actions/plugins/easymotion/easymotion.cmd';\nimport { IBaseAction } from '../actions/types';\nimport { Cursor } from '../common/motion/cursor';\nimport { configuration } from '../configuration/configuration';\nimport { decoration } from '../configuration/decoration';\nimport { isLiteralMode, remapKey } from '../configuration/langmap';\nimport { Notation } from '../configuration/notation';\nimport { Remappers } from '../configuration/remapper';\nimport { Jump } from '../jumps/jump';\nimport { globalState } from '../state/globalState';\nimport { RemapState } from '../state/remapState';\nimport { StatusBar } from '../statusBar';\nimport { IModeHandler, executeTransformations } from '../transformations/execute';\nimport { Dot, isTextTransformation } from '../transformations/transformations';\nimport { SearchDecorations, getDecorationsForSearchMatchRanges } from '../util/decorationUtils';\nimport { Logger } from '../util/logger';\nimport { SpecialKeys } from '../util/specialKeys';\nimport { scrollView } from '../util/util';\nimport { VSCodeContext } from '../util/vscodeContext';\nimport { BaseAction, BaseCommand, KeypressState, getRelevantAction } from './../actions/base';\nimport { ActionOverrideCmdD, CommandNumber, CommandRegister } from './../actions/commands/actions';\nimport {\n  BackspaceInInsertMode,\n  ExitInsertMode,\n  Insert,\n  InsertCharAbove,\n  InsertCharBelow,\n  InsertPreviousText,\n  TypeInInsertMode,\n} from './../actions/commands/insert';\nimport { earlierOf, laterOf, sorted } from './../common/motion/position';\nimport { ForceStopRemappingError, VimError } from './../error';\nimport { Register, RegisterMode } from './../register/register';\nimport { RecordedState } from './../state/recordedState';\nimport { VimState } from './../state/vimState';\nimport { TextEditor } from './../textEditor';\nimport { InternalSelectionsTracker } from './internalSelectionsTracker';\nimport {\n  DotCommandStatus,\n  Mode,\n  NormalCommandState,\n  ReplayMode,\n  VSCodeVimCursorType,\n  getCursorStyle,\n  isStatusBarMode,\n  isVisualMode,\n} from './mode';\n\ninterface IModeHandlerMap {\n  get(editorId: Uri): ModeHandler | undefined;\n}\n\n/**\n * ModeHandler is the extension's backbone. It listens to events and updates the VimState.\n * One of these exists for each editor - see ModeHandlerMap\n *\n * See:  https://github.com/VSCodeVim/Vim/blob/master/.github/CONTRIBUTING.md#the-vim-state-machine\n */\nexport class ModeHandler implements vscode.Disposable, IModeHandler {\n  public readonly vimState: VimState;\n  public readonly remapState: RemapState;\n\n  public lastMovementFailed: boolean = false;\n\n  public focusChanged = false;\n\n  private searchDecorationCacheKey: { searchString: string; documentVersion: number } | undefined;\n\n  private readonly disposables: vscode.Disposable[] = [];\n  private readonly handlerMap: IModeHandlerMap;\n  private readonly remappers: Remappers;\n\n  public internalSelectionsTracker = new InternalSelectionsTracker();\n\n  /**\n   * Was the previous mouse click past EOL\n   */\n  private lastClickWasPastEol: boolean = false;\n\n  private _currentMode!: Mode;\n  private get currentMode(): Mode {\n    return this._currentMode;\n  }\n  private async setCurrentMode(mode: Mode): Promise<void> {\n    if (this.vimState.currentMode !== mode) {\n      await this.vimState.setCurrentMode(mode);\n    }\n    this._currentMode = mode;\n  }\n\n  public static async create(\n    handlerMap: IModeHandlerMap,\n    textEditor: vscode.TextEditor,\n  ): Promise<ModeHandler> {\n    const modeHandler = new ModeHandler(handlerMap, textEditor);\n    await modeHandler.vimState.load();\n\n    // Check if this editor's URI scheme should start in Insert mode\n    const scheme = textEditor.document.uri.scheme;\n    const shouldStartInsert =\n      configuration.startInInsertMode || configuration.startInInsertModeSchemes.includes(scheme);\n\n    await modeHandler.setCurrentMode(shouldStartInsert ? Mode.Insert : Mode.Normal);\n    modeHandler.syncCursors();\n    return modeHandler;\n  }\n\n  private constructor(handlerMap: IModeHandlerMap, textEditor: vscode.TextEditor) {\n    this.handlerMap = handlerMap;\n    this.remappers = new Remappers();\n\n    this.vimState = new VimState(textEditor, new EasyMotion());\n    this.remapState = new RemapState();\n    this.disposables.push(this.vimState);\n  }\n\n  /**\n   * Updates VSCodeVim's internal representation of cursors to match VSCode's selections.\n   * This loses some information, so it should only be done when necessary.\n   */\n  public syncCursors() {\n    // TODO: getCursorsAfterSync() is basically this, but stupider\n    const { selections } = this.vimState.editor;\n    // TODO: this if block is a workaround for a problem described here https://github.com/VSCodeVim/Vim/pull/8426\n    if (\n      selections.length === 1 &&\n      selections[0].isEqual(new Range(new Position(0, 0), new Position(0, 0)))\n    ) {\n      return;\n    }\n\n    if (\n      !this.vimState.cursorStartPosition.isEqual(selections[0].anchor) ||\n      !this.vimState.cursorStopPosition.isEqual(selections[0].active)\n    ) {\n      this.vimState.desiredColumn = selections[0].active.character;\n    }\n\n    this.vimState.cursors = selections.map(({ active, anchor }) =>\n      active.isBefore(anchor) ? new Cursor(anchor.getLeft(), active) : new Cursor(anchor, active),\n    );\n  }\n\n  /**\n   * This is easily the worst function in VSCodeVim.\n   *\n   * We need to know when VSCode has updated our selection, so that we can sync\n   * that internally. Unfortunately, VSCode has a habit of calling this\n   * function at weird times, or or with incomplete information, so we have to\n   * do a lot of voodoo to make sure we're updating the cursors correctly.\n   *\n   * Even worse, we don't even know how to test this stuff.\n   *\n   * Anyone who wants to change the behavior of this method should make sure\n   * all selection related test cases pass. Follow this spec\n   * https://gist.github.com/rebornix/d21d1cc060c009d4430d3904030bd4c1 to\n   * perform the manual testing. Besides this testing you should still test\n   * commands like 'editor.action.smartSelect.grow' and you should test moving\n   * continuously up/down or left/right with and without remapped movement keys\n   * because sometimes vscode lags behind and calls this function with information\n   * that is not up to date with our selections yet and we need to make sure we don't\n   * change our cursors to previous information (this usally is only an issue in visual\n   * mode because of our different ways of handling selections and in those cases\n   * updating our cursors with not up to date info might result in us changing our\n   * cursor start position).\n   */\n  public async handleSelectionChange(e: vscode.TextEditorSelectionChangeEvent): Promise<void> {\n    if (\n      vscode.window.activeTextEditor === undefined ||\n      e.textEditor.document !== vscode.window.activeTextEditor.document\n    ) {\n      // we don't care if there is no active editor\n      // or user selection changed in a paneled window (e.g debug console/terminal)\n      // This check is made before enqueuing this selection change, but sometimes\n      // between the enqueueing and the actual calling of this function the editor\n      // might close or change to other document\n      return;\n    }\n    const selection = e.selections[0];\n    Logger.debug(\n      `Selection change: ${selection.anchor.toString()}, ${selection.active}, SelectionsLength: ${\n        e.selections.length\n      }`,\n    );\n\n    // If our previous cursors are not included on any of the current selections, then a snippet\n    // must have been inserted.\n    const isSnippetSelectionChange = () => {\n      return e.selections.every((s) => {\n        return this.vimState.cursors.every((c) => !s.contains(new vscode.Range(c.start, c.stop)));\n      });\n    };\n\n    if (\n      (e.selections.length !== this.vimState.cursors.length || this.vimState.isMultiCursor) &&\n      this.vimState.currentMode !== Mode.VisualBlock\n    ) {\n      const allowedModes = [Mode.Normal];\n      if (!isSnippetSelectionChange()) {\n        allowedModes.push(Mode.Insert, Mode.Replace);\n      }\n      // Number of selections changed, make sure we know about all of them still\n      this.vimState.cursors = e.textEditor.selections.map(\n        (sel) =>\n          new Cursor(\n            // Adjust the cursor positions because cursors & selections don't match exactly\n            sel.anchor.isAfter(sel.active) ? sel.anchor.getLeft() : sel.anchor,\n            sel.active,\n          ),\n      );\n      if (\n        e.selections.some((s) => !s.anchor.isEqual(s.active)) &&\n        allowedModes.includes(this.vimState.currentMode)\n      ) {\n        // If we got a visual selection and we are on normal, insert or replace mode, enter visual mode.\n        // We shouldn't go to visual mode on any other mode, because the other visual modes are handled\n        // very differently than vscode so only our extension will create them. And the other modes\n        // like the plugin modes shouldn't be changed or else it might mess up the plugins actions.\n        await this.setCurrentMode(Mode.Visual);\n      }\n      return this.updateView({ drawSelection: false, revealRange: false });\n    }\n\n    /**\n     * We only trigger our view updating process if it's a mouse selection.\n     * Otherwise we only update our internal cursor positions accordingly.\n     */\n    if (e.kind !== vscode.TextEditorSelectionChangeKind.Mouse) {\n      if (selection) {\n        if (e.kind === vscode.TextEditorSelectionChangeKind.Command) {\n          // This 'Command' kind is triggered when using a command like 'editor.action.smartSelect.grow'\n          // but it is also triggered when we set the 'editor.selections' on 'updateView'.\n          const allowedModes = [Mode.Normal, Mode.Visual, Mode.VisualLine];\n          if (!isSnippetSelectionChange()) {\n            // if we just inserted a snippet then don't allow insert modes to go to visual mode\n            allowedModes.push(Mode.Insert, Mode.Replace);\n          }\n          if (allowedModes.includes(this.vimState.currentMode)) {\n            // Since the selections weren't ignored then probably we got change of selection from\n            // a command, so we need to update our start and stop positions. This is where commands\n            // like 'editor.action.smartSelect.grow' are handled.\n            if (this.vimState.currentMode === Mode.Visual) {\n              Logger.trace('Updating Visual Selection!');\n              this.vimState.cursor = Cursor.fromSelection(selection);\n              this.updateView({ drawSelection: false, revealRange: false });\n\n              // Store selection for commands like gv\n              this.vimState.lastVisualSelection = {\n                mode: this.vimState.currentMode,\n                start: this.vimState.cursorStartPosition,\n                end: this.vimState.cursorStopPosition,\n              };\n              return;\n            } else if (!selection.active.isEqual(selection.anchor)) {\n              Logger.trace('Creating Visual Selection from command!');\n              this.vimState.cursor = Cursor.fromSelection(selection);\n              await this.setCurrentMode(Mode.Visual);\n              this.updateView({ drawSelection: false, revealRange: false });\n\n              // Store selection for commands like gv\n              this.vimState.lastVisualSelection = {\n                mode: Mode.Visual,\n                start: this.vimState.cursorStartPosition,\n                end: this.vimState.cursorStopPosition,\n              };\n              return;\n            }\n          }\n        }\n        // Here we are on the selection changed of kind 'Keyboard' or 'undefined' which is triggered\n        // when pressing movement keys that are not caught on the 'type' override but also when using\n        // commands like 'cursorMove'.\n\n        if (isVisualMode(this.vimState.currentMode)) {\n          /**\n           * In Visual Mode, our `cursorPosition` and `cursorStartPosition` can not reflect `active`,\n           * `start`, `end` and `anchor` information in a selection.\n           * See `Fake block cursor with text decoration` section of `updateView` method.\n           * Besides this, sometimes on visual modes our start position is not the same has vscode\n           * anchor because we need to move vscode anchor one to the right of our start when our start\n           * is after our stop in order to include the start character on vscodes selection.\n           */\n          return;\n        }\n\n        const cursorEnd = laterOf(\n          this.vimState.cursorStartPosition,\n          this.vimState.cursorStopPosition,\n        );\n        if (e.textEditor.document.validatePosition(cursorEnd).isBefore(cursorEnd)) {\n          // The document changed such that our cursor position is now out of bounds, possibly by\n          // another program. Let's just use VSCode's selection.\n          // TODO: if this is the case, but we're in visual mode, we never get here (because of branch above)\n        } else if (\n          e.kind === vscode.TextEditorSelectionChangeKind.Keyboard &&\n          this.vimState.cursorStopPosition.isEqual(this.vimState.cursorStartPosition) &&\n          this.vimState.cursorStopPosition.getRight().isLineEnd(this.vimState.document) &&\n          this.vimState.cursorStopPosition.getLineEnd().isEqual(selection.active)\n        ) {\n          // We get here when we use a 'cursorMove' command (that is considered a selection changed\n          // kind of 'Keyboard') that ends past the line break. But our cursors are already on last\n          // character which is what we want. Even though our cursors will be corrected again when\n          // checking if they are in bounds on 'runAction' there is no need to be changing them back\n          // and forth so we check for this situation here.\n          return;\n        }\n\n        // Here we allow other 'cursorMove' commands to update our cursors in case there is another\n        // extension making cursor changes that we need to catch.\n        //\n        // We still need to be careful with this because this here might be changing our cursors\n        // in ways we don't want to. So with future selection issues this is a good place to start\n        // looking.\n        Logger.debug(\n          `Selections: Changing Cursors from selection handler... ${selection.anchor.toString()}, ${\n            selection.active\n          }`,\n        );\n        this.vimState.cursor = Cursor.fromSelection(selection);\n        this.vimState.desiredColumn = selection.active.character;\n        this.updateView({ drawSelection: false, revealRange: false });\n      }\n      return;\n    }\n\n    if (isStatusBarMode(this.vimState.currentMode)) {\n      return;\n    }\n\n    let toDraw = false;\n\n    if (selection) {\n      let newPosition = selection.active;\n\n      // Only check on a click, not a full selection (to prevent clicking past EOL)\n      if (newPosition.character >= newPosition.getLineEnd().character && selection.isEmpty) {\n        if (this.vimState.currentMode !== Mode.Insert) {\n          this.lastClickWasPastEol = true;\n\n          // This prevents you from mouse clicking past the EOL\n          newPosition = newPosition.withColumn(Math.max(newPosition.getLineEnd().character - 1, 0));\n\n          // Switch back to normal mode since it was a click not a selection\n          await this.setCurrentMode(Mode.Normal);\n\n          toDraw = true;\n        }\n      } else if (selection.isEmpty) {\n        this.lastClickWasPastEol = false;\n      }\n\n      this.vimState.cursorStopPosition = newPosition;\n      this.vimState.cursorStartPosition = newPosition;\n      this.vimState.desiredColumn = newPosition.character;\n\n      // start visual mode?\n      if (\n        selection.anchor.line === selection.active.line &&\n        Math.min(selection.active.character, selection.anchor.character) >=\n          newPosition.getLineEnd().character\n      ) {\n        // This prevents you from selecting EOL\n      } else if (!selection.anchor.isEqual(selection.active)) {\n        let selectionStart = selection.anchor;\n\n        if (selectionStart.character > selectionStart.getLineEnd().character) {\n          selectionStart = new Position(selectionStart.line, selectionStart.getLineEnd().character);\n        }\n\n        this.vimState.cursorStartPosition = selectionStart;\n\n        if (selectionStart.isAfter(newPosition)) {\n          this.vimState.cursorStartPosition = this.vimState.cursorStartPosition.getLeft();\n        }\n\n        // If we prevented from clicking past eol but it is part of this selection, include the last char\n        if (this.lastClickWasPastEol) {\n          const newStart = new Position(selection.anchor.line, selection.anchor.character + 1);\n          this.vimState.editor.selection = new vscode.Selection(newStart, selection.active);\n          this.vimState.cursorStartPosition = selectionStart;\n          this.lastClickWasPastEol = false;\n        }\n\n        if (\n          configuration.mouseSelectionGoesIntoVisualMode &&\n          !isVisualMode(this.vimState.currentMode) &&\n          this.currentMode !== Mode.Insert\n        ) {\n          await this.setCurrentMode(Mode.Visual);\n\n          // double click mouse selection causes an extra character to be selected so take one less character\n        }\n      } else if (this.vimState.currentMode !== Mode.Insert) {\n        await this.setCurrentMode(Mode.Normal);\n      }\n\n      if (isVisualMode(this.vimState.currentMode)) {\n        // Store selection for commands like gv\n        this.vimState.lastVisualSelection = {\n          mode: this.vimState.currentMode,\n          start: this.vimState.cursorStartPosition,\n          end: this.vimState.cursorStopPosition,\n        };\n      }\n      this.updateView({ drawSelection: toDraw, revealRange: false });\n    }\n  }\n\n  async handleMultipleKeyEvents(keys: string[], alreadyRemapped: boolean = true): Promise<void> {\n    for (const key of keys) {\n      await (alreadyRemapped ? this.handleKeyEventLangmapped(key) : this.handleKeyEvent(key));\n    }\n  }\n\n  public async handleKeyEvent(keyRaw: string): Promise<void> {\n    const key =\n      isLiteralMode(this.currentMode) || this.vimState.isReplayingMacro ? keyRaw : remapKey(keyRaw);\n    return this.handleKeyEventLangmapped(key);\n  }\n\n  private async handleKeyEventLangmapped(key: string): Promise<void> {\n    if (this.remapState.forceStopRecursiveRemapping) {\n      return;\n    }\n\n    const now = Date.now();\n\n    const printableKey = Notation.printableKey(key, configuration.leader);\n    Logger.debug(`Handling key: ${printableKey}`);\n\n    if (key === SpecialKeys.ExtensionEnable.valueOf()) {\n      this.vimState.setTextEditorLineNumbersStyle(this.currentMode);\n    }\n\n    if (\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n      (key === SpecialKeys.TimeoutFinished ||\n        this.vimState.recordedState.bufferedKeys.length > 0) &&\n      this.vimState.recordedState.bufferedKeysTimeoutObj\n    ) {\n      // Handle the bufferedKeys or append the new key to the previously bufferedKeys\n      clearTimeout(this.vimState.recordedState.bufferedKeysTimeoutObj);\n      this.vimState.recordedState.bufferedKeysTimeoutObj = undefined;\n      this.vimState.recordedState.commandList = [...this.vimState.recordedState.bufferedKeys];\n      this.vimState.recordedState.bufferedKeys = [];\n    }\n\n    // rewrite copy\n    if (configuration.overrideCopy) {\n      // The conditions when you trigger a \"copy\" rather than a ctrl-c are\n      // too sophisticated to be covered by the \"when\" condition in package.json\n      if (key === '<D-c>') {\n        key = '<copy>';\n      }\n\n      if (key === '<C-c>' && process.platform !== 'darwin') {\n        if (!configuration.useCtrlKeys || isVisualMode(this.vimState.currentMode)) {\n          key = '<copy>';\n        }\n      }\n    }\n\n    // <C-d> triggers \"add selection to next find match\" by default,\n    // unless users explicity make <C-d>: true\n    // TODO: Destroy this silliness\n    if (key === '<C-d>' && !(configuration.handleKeys['<C-d>'] === true)) {\n      key = '<D-d>';\n    }\n\n    this.vimState.cursorsInitialState = this.vimState.cursors;\n    this.vimState.recordedState.commandList.push(key);\n\n    const oldMode = this.vimState.currentMode;\n    const oldFullMode = this.vimState.currentModeIncludingPseudoModes;\n    const oldStatusBarText = StatusBar.getText();\n    const oldWaitingForAnotherActionKey = this.vimState.recordedState.waitingForAnotherActionKey;\n\n    let handledAsRemap = false;\n    let handledAsAction = false;\n    try {\n      // Handling special case for '0'. From Vim documentation (:help :map-modes)\n      // Special case: While typing a count for a command in Normal mode, mapping zero\n      // is disabled. This makes it possible to map zero without making it impossible\n      // to type a count with a zero.\n      const preventZeroRemap =\n        key === '0' && this.vimState.recordedState.actionsRun.at(-1) instanceof CommandNumber;\n\n      // Check for remapped keys if:\n      // 1. We are not currently performing a non-recursive remapping\n      // 2. We are not typing '0' after starting to type a count\n      // 3. We are not waiting for another action key\n      //    Example: jj should not remap the second 'j', if jj -> <Esc> in insert mode\n      //             0 should not be remapped if typed after another number, like 10\n      //             for actions with multiple keys like 'gg' or 'fx' the second character\n      //           shouldn't be mapped\n      if (\n        !this.remapState.isCurrentlyPerformingNonRecursiveRemapping &&\n        !preventZeroRemap &&\n        !this.vimState.recordedState.waitingForAnotherActionKey\n      ) {\n        handledAsRemap = await this.remappers.sendKey(\n          this.vimState.recordedState.commandList,\n          this,\n        );\n      }\n\n      this.vimState.recordedState.allowPotentialRemapOnFirstKey = true;\n\n      if (!handledAsRemap) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison\n        if (key === SpecialKeys.TimeoutFinished) {\n          // Remove the <TimeoutFinished> key and get the key before that. If the <TimeoutFinished>\n          // key was the last key, then 'key' will be undefined and won't be sent to handle action.\n          this.vimState.recordedState.commandList.pop();\n          key = this.vimState.recordedState.commandList.at(-1)!;\n        }\n        if (key !== undefined) {\n          handledAsAction = await this.handleKeyAsAnAction(key);\n        }\n      }\n    } catch (e) {\n      this.internalSelectionsTracker.stopIgnoringIntermediateSelections();\n      if (e instanceof VimError) {\n        StatusBar.displayError(this.vimState, e);\n        this.vimState.recordedState = new RecordedState();\n        if (this.remapState.isCurrentlyPerformingRemapping) {\n          // If we are handling a remap and we got a VimError stop handling the remap\n          // and discard the rest of the keys. We throw an Exception here to stop any other\n          // remapping handling steps and go straight to the 'finally' step of the remapper.\n          throw ForceStopRemappingError.fromVimError(e);\n        }\n      } else if (e instanceof ForceStopRemappingError) {\n        // If this is a ForceStopRemappingError rethrow it until it gets to the remapper\n        throw e;\n      } else if (e instanceof Error) {\n        e.message = `Failed to handle key \\`${key}\\`: ${e.message}`;\n        throw e;\n      } else {\n        throw new Error(`Failed to handle key \\`${key}\\` due to an unknown error.`);\n      }\n    }\n\n    this.remapState.lastKeyPressedTimestamp = now;\n\n    StatusBar.updateShowCmd(this.vimState);\n\n    // We don't want to immediately erase any message that resulted from the action just performed\n    if (StatusBar.getText() === oldStatusBarText) {\n      // Clear the status bar of high priority messages if the mode has changed, the view has scrolled\n      // or it is recording a Macro\n      const forceClearStatusBar =\n        (this.vimState.currentMode !== oldMode && this.vimState.currentMode !== Mode.Normal) ||\n        this.vimState.macro !== undefined;\n      StatusBar.clear(this.vimState, forceClearStatusBar);\n    }\n\n    // We either already ran an action or we have a potential action to run but\n    // the key is already stored on 'actionKeys' in that case we don't need it\n    // anymore on commandList that is only used for the remapper and 'showCmd'\n    // and both had already been handled at this point.\n    // If we got here it means that there is no potential remap for the key\n    // either so we need to clear it from commandList so that it doesn't interfere\n    // with the next remapper check.\n    this.vimState.recordedState.resetCommandList();\n\n    Logger.trace(`handleKeyEvent('${printableKey}') took ${Date.now() - now}ms`);\n\n    // If we are handling a remap and the last movement failed stop handling the remap\n    // and discard the rest of the keys. We throw an Exception here to stop any other\n    // remapping handling steps and go straight to the 'finally' step of the remapper.\n    if (this.remapState.isCurrentlyPerformingRemapping && this.lastMovementFailed) {\n      this.lastMovementFailed = false;\n      throw new ForceStopRemappingError('Last movement failed');\n    }\n\n    // Reset lastMovementFailed. Anyone who needed it has probably already handled it.\n    // And keeping it past this point would make any following remapping force stop.\n    this.lastMovementFailed = false;\n\n    if (!handledAsAction) {\n      // There was no action run yet but we still want to update the view to be able\n      // to show the potential remapping keys being pressed, the `\"` character when\n      // waiting on a register key or the `?` character and any following character\n      // when waiting on digraph keys. The 'oldWaitingForAnotherActionKey' is used\n      // to call the updateView after we are no longer waiting keys so that any\n      // existing overlapped key is removed.\n      if (\n        ((this.vimState.currentMode === Mode.Insert ||\n          this.vimState.currentMode === Mode.Replace) &&\n          (this.vimState.recordedState.bufferedKeys.length > 0 ||\n            this.vimState.recordedState.waitingForAnotherActionKey ||\n            this.vimState.recordedState.waitingForAnotherActionKey !==\n              oldWaitingForAnotherActionKey)) ||\n        this.vimState.currentModeIncludingPseudoModes !== oldFullMode\n      ) {\n        // TODO: this call to updateView is only used to update the virtualCharacter and halfBlock\n        // cursor decorations, if in the future we split up the updateView function there should\n        // be no need to call all of it.\n        this.updateView({ drawSelection: false, revealRange: false });\n      }\n    }\n  }\n\n  private async handleKeyAsAnAction(key: string): Promise<boolean> {\n    if (vscode.window.activeTextEditor !== this.vimState.editor) {\n      Logger.warn('Current window is not active');\n      return false;\n    }\n\n    // Catch any text change not triggered by us (example: tab completion).\n    const changeAdded = this.vimState.historyTracker.addChange();\n    if (changeAdded) {\n      this.vimState.historyTracker.finishCurrentStep();\n    }\n\n    const recordedState = this.vimState.recordedState;\n    recordedState.actionKeys.push(key);\n    void VSCodeContext.set('vim.command', recordedState.commandString);\n\n    const action = getRelevantAction(recordedState.actionKeys, this.vimState);\n    switch (action) {\n      case KeypressState.NoPossibleMatch:\n        if (this.vimState.currentMode === Mode.Insert) {\n          this.vimState.recordedState.actionKeys = [];\n        } else {\n          this.vimState.recordedState = new RecordedState();\n        }\n        // Since there is no possible action we are no longer waiting any action keys\n        this.vimState.recordedState.waitingForAnotherActionKey = false;\n        void VSCodeContext.set('vim.command', '');\n\n        return false;\n      case KeypressState.WaitingOnKeys:\n        this.vimState.recordedState.waitingForAnotherActionKey = true;\n\n        return false;\n    }\n\n    if (\n      !this.remapState.remapUsedACharacter &&\n      this.remapState.isCurrentlyPerformingRecursiveRemapping\n    ) {\n      // Used a character inside a recursive remapping so we reset the mapDepth.\n      this.remapState.remapUsedACharacter = true;\n      this.remapState.mapDepth = 0;\n    }\n\n    // Since we got an action we are no longer waiting any action keys\n    this.vimState.recordedState.waitingForAnotherActionKey = false;\n\n    // Store action pressed keys for showCmd\n    recordedState.actionsRunPressedKeys.push(...recordedState.actionKeys);\n\n    let actionToRecord: BaseAction | undefined = action;\n    const lastAction = recordedState.actionsRun.at(-1);\n    if (lastAction === undefined) {\n      recordedState.actionsRun.push(action);\n    } else {\n      const actionCanBeMergedWithDocumentChange =\n        action instanceof TypeInInsertMode ||\n        action instanceof BackspaceInInsertMode ||\n        action instanceof InsertPreviousText ||\n        action instanceof InsertCharAbove ||\n        action instanceof InsertCharBelow;\n\n      if (lastAction instanceof DocumentContentChangeAction) {\n        if (!(action instanceof ExitInsertMode)) {\n          // TODO: this includes things like <BS>, which it shouldn't\n          lastAction.keysPressed.push(key);\n        }\n\n        if (actionCanBeMergedWithDocumentChange) {\n          // delay the macro recording\n          actionToRecord = undefined;\n        } else {\n          // Push document content change to the stack\n          lastAction.addChanges(\n            this.vimState.historyTracker.currentContentChanges,\n            this.vimState.cursorStopPosition,\n          );\n          this.vimState.historyTracker.currentContentChanges = [];\n          recordedState.actionsRun.push(action);\n        }\n      } else {\n        if (actionCanBeMergedWithDocumentChange) {\n          // This means we are already in Insert Mode but there is still not DocumentContentChangeAction in stack\n          this.vimState.historyTracker.currentContentChanges = [];\n          const newContentChange = new DocumentContentChangeAction(\n            this.vimState.cursorStopPosition,\n          );\n          newContentChange.keysPressed.push(key);\n          recordedState.actionsRun.push(newContentChange);\n          actionToRecord = newContentChange;\n        } else {\n          recordedState.actionsRun.push(action);\n        }\n      }\n    }\n\n    if (\n      this.vimState.macro !== undefined &&\n      actionToRecord &&\n      !(actionToRecord instanceof QuitRecordMacro)\n    ) {\n      this.vimState.macro.actionsRun.push(actionToRecord);\n    }\n\n    await this.runAction(recordedState, action);\n\n    if (this.vimState.currentMode === Mode.Insert) {\n      recordedState.isInsertion = true;\n    }\n\n    // Update view\n    this.updateView();\n\n    if (action.isJump) {\n      globalState.jumpTracker.recordJump(\n        Jump.fromStateBefore(this.vimState),\n        Jump.fromStateNow(this.vimState),\n      );\n    }\n\n    return true;\n  }\n\n  private async runAction(recordedState: RecordedState, action: IBaseAction): Promise<void> {\n    this.internalSelectionsTracker.startIgnoringIntermediateSelections();\n\n    // We handle the end of selections different to VSCode. In order for VSCode to select\n    // including the last character we will at the end of 'runAction' shift our stop position\n    // to the right. So here we shift it back by one so that our actions have our correct\n    // position instead of the position sent to VSCode.\n    if (this.vimState.currentMode === Mode.Visual) {\n      this.vimState.cursors = this.vimState.cursors.map((c) =>\n        c.start.isBefore(c.stop) ? c.withNewStop(c.stop.getLeftThroughLineBreaks(true)) : c,\n      );\n    }\n\n    // Make sure all cursors are within the document's bounds before running any action\n    // It's not 100% clear to me that this is the correct place to do this, but it should solve a lot of issues\n    this.vimState.cursors = this.vimState.cursors.map((c) => c.validate(this.vimState.document));\n\n    let ranRepeatableAction = false;\n    let ranAction = false;\n\n    if (action instanceof BaseMovement) {\n      recordedState = await this.executeMovement(action);\n      ranAction = true;\n    } else if (action instanceof BaseCommand) {\n      await action.execCount(this.vimState.cursorStopPosition, this.vimState);\n\n      const transformer = this.vimState.recordedState.transformer;\n      await executeTransformations(this, transformer.transformations);\n\n      if (action.isCompleteAction) {\n        ranAction = true;\n      }\n\n      if (action.createsUndoPoint) {\n        ranRepeatableAction = true;\n      }\n\n      if (this.vimState.normalCommandState === NormalCommandState.Finished) {\n        ranRepeatableAction = true;\n      }\n    } else if (action instanceof BaseOperator) {\n      recordedState.operatorCount = recordedState.count;\n    } else {\n      throw new Error('Unknown action type');\n    }\n\n    // Update mode (note the ordering allows you to go into search mode,\n    // then return and have the motion immediately applied to an operator).\n    const prevMode = this.currentMode;\n    if (this.vimState.currentMode !== this.currentMode) {\n      await this.setCurrentMode(this.vimState.currentMode);\n\n      // We don't want to mark any searches as a repeatable action\n      if (\n        this.vimState.currentMode === Mode.Normal &&\n        prevMode !== Mode.SearchInProgressMode &&\n        prevMode !== Mode.EasyMotionInputMode &&\n        prevMode !== Mode.EasyMotionMode &&\n        !(\n          prevMode === Mode.CommandlineInProgress &&\n          this.vimState.normalCommandState === NormalCommandState.Executing\n        )\n      ) {\n        ranRepeatableAction = true;\n      }\n    }\n\n    // If there's an operator pending and we have a motion or visual selection, run the operator\n    if (recordedState.getOperatorState(this.vimState.currentMode) === 'ready') {\n      const operator = this.vimState.recordedState.operator;\n      if (operator) {\n        await this.executeOperator();\n        this.vimState.recordedState.hasRunOperator = true;\n        ranRepeatableAction = operator.createsUndoPoint;\n        ranAction = true;\n      }\n    }\n\n    // And then we have to do it again because an operator could\n    // have changed it as well. (TODO: do you even decomposition bro)\n    if (this.vimState.currentMode !== this.currentMode) {\n      await this.setCurrentMode(this.vimState.currentMode);\n\n      if (this.vimState.currentMode === Mode.Normal) {\n        ranRepeatableAction = true;\n      }\n    }\n\n    ranRepeatableAction &&= this.vimState.currentMode === Mode.Normal;\n\n    // We don't want to record a repeatable action when exiting from these modes\n    // by pressing <Esc>\n    if (\n      (isVisualMode(prevMode) || prevMode === Mode.CommandlineInProgress) &&\n      action.keysPressed[0] === '<Esc>'\n    ) {\n      ranRepeatableAction = false;\n    }\n\n    // Record down previous action and flush temporary state\n    if (\n      ranRepeatableAction &&\n      this.vimState.lastCommandDotRepeatable &&\n      this.vimState.dotCommandStatus !== DotCommandStatus.Finished\n    ) {\n      globalState.previousFullAction = _.cloneDeep(this.vimState.recordedState);\n\n      if (recordedState.isInsertion) {\n        Register.setReadonlyRegister('.', recordedState);\n      }\n    }\n    this.vimState.lastCommandDotRepeatable = true;\n\n    // Update desiredColumn\n    const preservesDesiredColumn =\n      action instanceof BaseOperator && !ranAction ? true : action.preservesDesiredColumn;\n    if (!preservesDesiredColumn) {\n      if (action instanceof BaseMovement) {\n        // We check !operator here because e.g. d$ should NOT set the desired column to EOL.\n        if (action instanceof MoveLineEnd && !recordedState.operator) {\n          this.vimState.desiredColumn = Number.POSITIVE_INFINITY;\n        } else {\n          this.vimState.desiredColumn = this.vimState.cursorStopPosition.character;\n        }\n      } else if (this.vimState.currentMode !== Mode.VisualBlock) {\n        // TODO: explain why not VisualBlock\n        this.vimState.desiredColumn = this.vimState.cursorStopPosition.character;\n      }\n    }\n\n    // Like previously stated we handle the end of selections different to VSCode. In order\n    // for VSCode to select including the last character we shift our stop position to the\n    // right now that all steps that need that position have already run. On the next action\n    // we will shift it back again on the start of 'runAction'.\n    if (this.vimState.currentMode === Mode.Visual) {\n      this.vimState.cursors = this.vimState.cursors.map((c) =>\n        c.start.isBeforeOrEqual(c.stop)\n          ? c.withNewStop(\n              c.stop.isLineEnd(this.vimState.document)\n                ? c.stop.getRightThroughLineBreaks()\n                : c.stop.getRight(),\n            )\n          : c,\n      );\n    }\n\n    // We've run a complete action sequence - wipe the slate clean with a new RecordedState\n    if (\n      ranAction &&\n      this.vimState.currentMode === Mode.Normal &&\n      this.vimState.dotCommandStatus !== DotCommandStatus.Executing\n    ) {\n      this.vimState.recordedState = new RecordedState();\n\n      // Return to insert mode after 1 command in this case for <C-o>\n      if (this.vimState.returnToInsertAfterCommand) {\n        if (this.vimState.actionCount > 0) {\n          await this.setCurrentMode(Mode.Insert);\n        } else {\n          this.vimState.actionCount++;\n        }\n      }\n    }\n\n    if (this.vimState.dotCommandStatus === DotCommandStatus.Finished) {\n      this.vimState.dotCommandStatus = DotCommandStatus.Waiting;\n    }\n\n    // track undo history\n    if (!this.focusChanged) {\n      // important to ensure that focus didn't change, otherwise\n      // we'll grab the text of the incorrect active window and assume the\n      // whole document changed!\n\n      this.vimState.historyTracker.addChange();\n    }\n\n    // Don't record an undo point for every action of a macro, only at the very end\n    if (\n      ranRepeatableAction &&\n      !this.vimState.isReplayingMacro &&\n      this.vimState.normalCommandState !== NormalCommandState.Executing &&\n      this.vimState.dotCommandStatus !== DotCommandStatus.Executing &&\n      !this.remapState.isCurrentlyPerformingRemapping\n    ) {\n      this.vimState.historyTracker.finishCurrentStep();\n    }\n\n    if (this.vimState.normalCommandState === NormalCommandState.Finished) {\n      this.vimState.normalCommandState = NormalCommandState.Waiting;\n    }\n\n    recordedState.actionKeys = [];\n    this.vimState.currentRegisterMode = undefined;\n\n    // If we're in Normal mode, collapse each cursor down to one character\n    if (this.currentMode === Mode.Normal) {\n      this.vimState.cursors = this.vimState.cursors.map((cursor) => Cursor.atPosition(cursor.stop));\n    }\n\n    // Ensure cursors are within bounds\n    if (\n      !this.vimState.document.isClosed &&\n      this.vimState.editor === vscode.window.activeTextEditor\n    ) {\n      this.vimState.cursors = this.vimState.cursors.map((cursor: Cursor) => {\n        cursor = cursor.validate(this.vimState.document);\n\n        // Adjust column. When getting from insert into normal mode with <C-o>,\n        // the cursor position should remain even if it is behind the last\n        // character in the line\n        if (\n          !this.vimState.returnToInsertAfterCommand &&\n          (this.vimState.currentMode === Mode.Normal || isVisualMode(this.vimState.currentMode))\n        ) {\n          const currentStopLineLength = TextEditor.getLineLength(cursor.stop.line);\n          const currentStartLineLength = TextEditor.getLineLength(cursor.start.line);\n\n          // When in visual mode you can move the cursor past the last character in order\n          // to select that character. We use this offset to allow for that, otherwise\n          // we would consider the position invalid and change it to the left of the last\n          // character.\n          const offsetStartAllowed =\n            isVisualMode(this.vimState.currentMode) && currentStartLineLength > 0 ? 1 : 0;\n          const offsetStopAllowed =\n            isVisualMode(this.vimState.currentMode) && currentStopLineLength > 0 ? 1 : 0;\n\n          if (cursor.start.character >= currentStartLineLength + offsetStartAllowed) {\n            cursor = cursor.withNewStart(\n              cursor.start.withColumn(Math.max(currentStartLineLength - 1, 0)),\n            );\n          }\n          if (cursor.stop.character >= currentStopLineLength + offsetStopAllowed) {\n            cursor = cursor.withNewStop(\n              cursor.stop.withColumn(Math.max(currentStopLineLength - 1, 0)),\n            );\n          }\n        }\n        return cursor;\n      });\n    }\n\n    if (\n      isVisualMode(this.vimState.currentMode) &&\n      this.vimState.dotCommandStatus !== DotCommandStatus.Executing\n    ) {\n      // Store selection for commands like gv\n      this.vimState.lastVisualSelection = {\n        mode: this.vimState.currentMode,\n        start: this.vimState.cursorStartPosition,\n        end: this.vimState.cursorStopPosition,\n      };\n    }\n\n    this.internalSelectionsTracker.stopIgnoringIntermediateSelections();\n  }\n\n  private async executeMovement(movement: BaseMovement): Promise<RecordedState> {\n    this.lastMovementFailed = false;\n    const recordedState = this.vimState.recordedState;\n    const cursorsToRemove: number[] = [];\n\n    for (let i = 0; i < this.vimState.cursors.length; i++) {\n      /**\n       * Essentially what we're doing here is pretending like the\n       * current VimState only has one cursor (the cursor that we just\n       * iterated to).\n       *\n       * We set the cursor position to be equal to the iterated one,\n       * and then set it back immediately after we're done.\n       *\n       * The slightly more complicated logic here allows us to write\n       * Action definitions without having to think about multiple\n       * cursors in almost all cases.\n       */\n      const oldCursorPositionStart = this.vimState.cursorStartPosition;\n      const oldCursorPositionStop = this.vimState.cursorStopPosition;\n      movement.multicursorIndex = i;\n\n      this.vimState.cursorStartPosition = this.vimState.cursors[i].start;\n      const cursorPosition = this.vimState.cursors[i].stop;\n      this.vimState.cursorStopPosition = cursorPosition;\n\n      const result = await movement.execActionWithCount(\n        cursorPosition,\n        this.vimState,\n        recordedState.count,\n      );\n\n      // We also need to update the specific cursor, in case the cursor position was modified inside\n      // the handling functions (e.g. 'it')\n      this.vimState.cursors[i] = new Cursor(\n        this.vimState.cursorStartPosition,\n        this.vimState.cursorStopPosition,\n      );\n\n      this.vimState.cursorStartPosition = oldCursorPositionStart;\n      this.vimState.cursorStopPosition = oldCursorPositionStop;\n\n      if (result instanceof Position) {\n        this.vimState.cursors[i] = this.vimState.cursors[i].withNewStop(result);\n\n        if (!isVisualMode(this.currentMode) && !this.vimState.recordedState.operator) {\n          this.vimState.cursors[i] = this.vimState.cursors[i].withNewStart(result);\n        }\n      } else {\n        if (result.failed) {\n          this.vimState.recordedState = new RecordedState();\n          this.lastMovementFailed = true;\n        }\n\n        if (result.removed) {\n          cursorsToRemove.push(i);\n        } else {\n          this.vimState.cursors[i] = new Cursor(result.start, result.stop);\n        }\n      }\n    }\n\n    if (cursorsToRemove.length > 0) {\n      // Remove the cursors that no longer exist. Remove from the end to the start\n      // so that the index values don't change.\n      for (let i = cursorsToRemove.length - 1; i >= 0; i--) {\n        const idx = cursorsToRemove[i];\n        if (idx !== 0) {\n          // We should never remove the main selection! This shouldn't happen, but just\n          // in case it does, lets protect against it. Remember kids, always use protection!\n          this.vimState.cursors.splice(idx, 1);\n        }\n      }\n    }\n\n    this.vimState.recordedState.count = 0;\n\n    // Keep the cursor within bounds\n    if (this.vimState.currentMode !== Mode.Normal || recordedState.operator) {\n      const stop = this.vimState.cursorStopPosition;\n\n      // Vim does this weird thing where it allows you to select and delete\n      // the newline character, which it places 1 past the last character\n      // in the line. This is why we use > instead of >=.\n\n      if (stop.character > TextEditor.getLineLength(stop.line)) {\n        this.vimState.cursorStopPosition = stop.getLineEnd();\n      }\n    }\n\n    return recordedState;\n  }\n\n  private async executeOperator(): Promise<void> {\n    const recordedState = this.vimState.recordedState;\n    const operator = recordedState.operator!;\n\n    // TODO - if actions were more pure, this would be unnecessary.\n    const startingMode = this.vimState.currentMode;\n    const startingRegisterMode = this.vimState.currentRegisterMode;\n\n    const resultingCursors: Cursor[] = [];\n    for (let [i, { start, stop }] of this.vimState.cursors.entries()) {\n      operator.multicursorIndex = i;\n\n      if (start.isAfter(stop)) {\n        [start, stop] = [stop, start];\n      }\n\n      if (!isVisualMode(startingMode) && startingRegisterMode !== RegisterMode.LineWise) {\n        stop = stop.getLeftThroughLineBreaks(true);\n      }\n\n      if (this.currentMode === Mode.VisualLine) {\n        start = start.getLineBegin();\n        stop = stop.getLineEnd();\n\n        this.vimState.currentRegisterMode = RegisterMode.LineWise;\n      }\n\n      await this.vimState.setCurrentMode(startingMode);\n\n      // We run the repeat version of an operator if the last 2 operators are the same.\n      const operators = recordedState.operators;\n      if (operators.length > 1 && operators[0].constructor === operators[1].constructor) {\n        await operator.runRepeat(this.vimState, start, recordedState.count);\n      } else {\n        await operator.run(this.vimState, start, stop);\n      }\n\n      for (const transformation of this.vimState.recordedState.transformer.transformations) {\n        if (isTextTransformation(transformation) && transformation.cursorIndex === undefined) {\n          transformation.cursorIndex = operator.multicursorIndex;\n        }\n      }\n\n      const resultingCursor = new Cursor(\n        this.vimState.cursorStartPosition,\n        this.vimState.cursorStopPosition,\n      );\n\n      resultingCursors.push(resultingCursor);\n    }\n\n    if (this.vimState.recordedState.transformer.transformations.length > 0) {\n      const transformer = this.vimState.recordedState.transformer;\n      await executeTransformations(this, transformer.transformations);\n    } else {\n      // Keep track of all cursors (in the case of multi-cursor).\n      this.vimState.cursors = resultingCursors;\n    }\n  }\n\n  public async rerunRecordedState(transformation: Dot): Promise<void> {\n    let recordedState = transformation.recordedState.clone();\n    const actions = [...recordedState.actionsRun];\n\n    this.vimState.dotCommandStatus = DotCommandStatus.Executing;\n\n    // If a previous visual selection exists, store it for use in replay of some commands\n    if (this.vimState.lastVisualSelection) {\n      this.vimState.dotCommandPreviousVisualSelection = new vscode.Selection(\n        this.vimState.lastVisualSelection.start,\n        this.vimState.lastVisualSelection.end,\n      );\n    }\n\n    let replayMode = null;\n    if (actions[0] instanceof Insert) {\n      replayMode = ReplayMode.Insert;\n    } else if (actions[0] instanceof ReplaceCharacter) {\n      replayMode = ReplayMode.Replace;\n    } else if (actions[0] instanceof CommandRegister) {\n      // Increment numbered registers \"1 to \"9.\n      const keyPressed = Number(actions[0].keysPressed[1]);\n      if (!isNaN(keyPressed) && keyPressed > 0 && keyPressed < 9) {\n        actions[0].keysPressed[1] = String(keyPressed + 1);\n      }\n    }\n    for (let j = 0; j < transformation.count; j++) {\n      recordedState = new RecordedState();\n      this.vimState.recordedState = recordedState;\n      for (const [i, action] of actions.entries()) {\n        if (\n          replayMode === ReplayMode.Insert &&\n          !(j === transformation.count - 1 && i === actions.length - 1) &&\n          action instanceof ExitInsertMode\n        ) {\n          continue;\n        }\n        recordedState.actionsRun = actions.slice(0, i + 1);\n        await this.runAction(recordedState, action);\n\n        if (this.lastMovementFailed) {\n          break;\n        }\n\n        this.updateView();\n        if (\n          replayMode === ReplayMode.Replace &&\n          !(j === transformation.count - 1 && i === actions.length - 1)\n        ) {\n          this.vimState.cursorStopPosition = this.vimState.cursorStartPosition = new Position(\n            this.vimState.cursorStopPosition.line,\n            this.vimState.cursorStopPosition.character + 1,\n          );\n        }\n      }\n    }\n    let combinedActions: IBaseAction[] = [];\n    for (let i = 0; i < transformation.count; i++) {\n      combinedActions = combinedActions.concat(actions);\n    }\n    recordedState.actionsRun = combinedActions;\n    this.vimState.dotCommandStatus = DotCommandStatus.Finished;\n  }\n\n  public async runMacro(recordedMacro: RecordedState): Promise<void> {\n    let recordedState = new RecordedState();\n    this.vimState.recordedState = recordedState;\n\n    for (const action of recordedMacro.actionsRun) {\n      const originalLocation = Jump.fromStateNow(this.vimState);\n\n      this.vimState.cursorsInitialState = this.vimState.cursors;\n\n      recordedState.actionsRun.push(action);\n\n      await this.runAction(recordedState, action);\n\n      // We just finished a full action; let's clear out our current state.\n      if (this.vimState.recordedState.actionsRun.length === 0) {\n        recordedState = new RecordedState();\n        this.vimState.recordedState = recordedState;\n      }\n\n      if (this.lastMovementFailed) {\n        break;\n      }\n\n      this.updateView();\n\n      if (action.isJump) {\n        globalState.jumpTracker.recordJump(originalLocation, Jump.fromStateNow(this.vimState));\n      }\n    }\n\n    this.vimState.dotCommandStatus = DotCommandStatus.Finished;\n    this.vimState.cursorsInitialState = this.vimState.cursors;\n  }\n\n  private updateSearchHighlights(showHighlights: boolean) {\n    const cacheKey = this.searchDecorationCacheKey;\n    this.searchDecorationCacheKey = undefined;\n\n    let decorations: SearchDecorations | undefined;\n    if (showHighlights) {\n      if (\n        this.vimState.modeData.mode === Mode.CommandlineInProgress ||\n        this.vimState.modeData.mode === Mode.SearchInProgressMode\n      ) {\n        decorations = this.vimState.modeData.commandLine.getDecorations(this.vimState);\n      } else if (globalState.searchState) {\n        if (\n          cacheKey &&\n          cacheKey.searchString === globalState.searchState.searchString &&\n          cacheKey.documentVersion === this.vimState.document.version\n        ) {\n          // The decorations are fine as-is, don't waste time re-calculating\n          this.searchDecorationCacheKey = cacheKey;\n          return;\n        }\n        // If there are no decorations from the command line, get decorations for previous SearchState\n        decorations = getDecorationsForSearchMatchRanges(\n          globalState.searchState.getMatchRanges(this.vimState),\n          this.vimState.document,\n        );\n        this.searchDecorationCacheKey = {\n          searchString: globalState.searchState.searchString,\n          documentVersion: this.vimState.document.version,\n        };\n      }\n    }\n\n    this.vimState.editor.setDecorations(\n      decoration.searchHighlight,\n      decorations?.searchHighlight ?? [],\n    );\n    this.vimState.editor.setDecorations(decoration.searchMatch, decorations?.searchMatch ?? []);\n    this.vimState.editor.setDecorations(\n      decoration.substitutionAppend,\n      decorations?.substitutionAppend ?? [],\n    );\n    this.vimState.editor.setDecorations(\n      decoration.substitutionReplace,\n      decorations?.substitutionReplace ?? [],\n    );\n  }\n\n  public updateView(\n    args: { drawSelection: boolean; revealRange: boolean } = {\n      drawSelection: true,\n      revealRange: true,\n    },\n  ): void {\n    // Draw selection (or cursor)\n    if (args.drawSelection) {\n      let selectionMode: Mode = this.vimState.currentMode;\n      if (this.vimState.modeData.mode === Mode.SearchInProgressMode) {\n        selectionMode = this.vimState.modeData.commandLine.previousMode;\n      } else if (this.vimState.modeData.mode === Mode.CommandlineInProgress) {\n        selectionMode = this.vimState.modeData.commandLine.previousMode;\n      } else if (this.vimState.modeData.mode === Mode.SurroundInputMode) {\n        selectionMode = this.vimState.surround!.previousMode;\n      }\n\n      let selections: vscode.Selection[] = [];\n      for (const cursor of this.vimState.cursors) {\n        let { start, stop } = cursor;\n        switch (selectionMode) {\n          case Mode.Visual:\n            /**\n             * Always select the letter that we started visual mode on, no matter\n             * if we are in front or behind it. Imagine that we started visual mode\n             * with some text like this:\n             *\n             *   abc|def\n             *\n             * (The | represents the cursor.) If we now press w, we'll select def,\n             * but if we hit b we expect to select abcd, so we need to getRight() on the\n             * start of the selection when it precedes where we started visual mode.\n             */\n            if (start.isAfterOrEqual(stop)) {\n              start = start.getRight();\n            }\n\n            selections.push(new vscode.Selection(start, stop));\n            break;\n\n          case Mode.VisualLine:\n            [start, stop] = sorted(start, stop);\n            selections.push(new vscode.Selection(start.getLineBegin(), stop.getLineEnd()));\n            break;\n\n          case Mode.VisualBlock:\n            for (const line of TextEditor.iterateLinesInBlock(this.vimState, cursor)) {\n              selections.push(\n                new vscode.Selection(\n                  this.vimState.document.validatePosition(line.start),\n                  this.vimState.document.validatePosition(line.end),\n                ),\n              );\n            }\n            break;\n\n          case Mode.Insert:\n            // Don't collapse existing selections in insert mode\n            selections.push(new vscode.Selection(start, stop));\n            break;\n\n          default:\n            // Note that this collapses the selection onto one position\n            selections.push(new vscode.Selection(stop, stop));\n            break;\n        }\n      }\n\n      /**\n       * Combine instersected selections - When we have multiple cursors\n       * sometimes those cursors selections intersect and combine, we need\n       * to check that here so that we know if our currents cursors will\n       * trigger a selectionChangeEvent or not. If we didn't check for this\n       * vscode might already have the resulting combined selection selected\n       * but since that wouldn't be the same as our selections we would think\n       * there would be a selectionChangeEvent when there wouldn't be any.\n       */\n      const getSelectionsCombined = (sel: vscode.Selection[]) => {\n        const combinedSelections: vscode.Selection[] = [];\n        sel.forEach((s, i) => {\n          if (i > 0) {\n            const previousSelection = combinedSelections.at(-1)!;\n            const overlap = s.intersection(previousSelection);\n            if (overlap) {\n              combinedSelections[combinedSelections.length - 1] = s.anchor.isBeforeOrEqual(s.active)\n                ? // Forwards Selection\n                  new vscode.Selection(\n                    earlierOf(s.anchor, previousSelection.anchor),\n                    laterOf(s.active, previousSelection.active),\n                  )\n                : // Backwards Selection\n                  new vscode.Selection(\n                    laterOf(s.anchor, previousSelection.anchor),\n                    earlierOf(s.active, previousSelection.active),\n                  );\n            } else {\n              combinedSelections.push(s);\n            }\n          } else {\n            combinedSelections.push(s);\n          }\n        });\n        return combinedSelections;\n      };\n      selections = getSelectionsCombined(selections);\n\n      this.internalSelectionsTracker.maybeTrackSelectionsUpdateToIgnore({\n        updatedSelections: selections,\n        currentEditorSelections: this.vimState.editor.selections,\n      });\n\n      this.vimState.editor.selections = selections;\n    }\n\n    // cursor style\n    let cursorStyle = configuration.getCursorStyleForMode(this.currentMode);\n    if (!cursorStyle) {\n      const cursorType = getCursorType(\n        this.vimState,\n        this.vimState.currentModeIncludingPseudoModes,\n      );\n      cursorStyle = getCursorStyle(cursorType);\n      if (\n        cursorType === VSCodeVimCursorType.Native &&\n        configuration.editorCursorStyle !== undefined\n      ) {\n        cursorStyle = configuration.editorCursorStyle;\n      }\n    }\n    this.vimState.editor.options.cursorStyle = cursorStyle;\n\n    // Scroll to position of cursor\n    // (This needs to run after cursor style as setting editor.options recomputes the scroll position and breaks when smooth scrolling is enabled: #8254)\n    if (\n      this.vimState.editor.visibleRanges.length > 0 &&\n      !this.vimState.postponedCodeViewChanges.some((change) => change.command === 'editorScroll')\n    ) {\n      /**\n       * This variable decides to which cursor we scroll the view.\n       * It is meant as a patch to #880.\n       * Extend this condition if it is the desired behaviour for other actions as well.\n       */\n      const isLastCursorTracked =\n        this.vimState.recordedState.actionsRun.at(-1) instanceof ActionOverrideCmdD;\n\n      let cursorToTrack: Cursor;\n      if (isLastCursorTracked) {\n        cursorToTrack = this.vimState.cursors.at(-1)!;\n      } else {\n        cursorToTrack = this.vimState.cursor;\n      }\n\n      const isCursorAboveRange = (visibleRange: vscode.Range): boolean =>\n        visibleRange.start.line - cursorToTrack.stop.line >= 15;\n      const isCursorBelowRange = (visibleRange: vscode.Range): boolean =>\n        cursorToTrack.stop.line - visibleRange.end.line >= 15;\n\n      const { visibleRanges } = this.vimState.editor;\n      const centerViewportAroundCursor =\n        visibleRanges.every(isCursorAboveRange) || visibleRanges.every(isCursorBelowRange);\n\n      const revealType = centerViewportAroundCursor\n        ? vscode.TextEditorRevealType.InCenter\n        : vscode.TextEditorRevealType.Default;\n\n      if (this.vimState.modeData.mode === Mode.SearchInProgressMode && configuration.incsearch) {\n        const currentMatch = this.vimState.modeData.commandLine.getCurrentMatchRange(this.vimState);\n\n        if (currentMatch) {\n          this.vimState.editor.revealRange(currentMatch.range, revealType);\n        } else if (this.vimState.modeData.mode === Mode.SearchInProgressMode) {\n          const offset =\n            this.vimState.editor.visibleRanges[0].start.line -\n            this.vimState.modeData.firstVisibleLineBeforeSearch;\n          scrollView(this.vimState, offset);\n        }\n      } else if (args.revealRange) {\n        if (\n          !isLastCursorTracked ||\n          this.vimState.cursorsInitialState.length !== this.vimState.cursors.length\n        ) {\n          /**\n           * We scroll the view if either:\n           * 1. the cursor we want to keep in view is the main one (this is the \"standard\"\n           * (before this commit) situation)\n           * 2. if we track the last cursor, but no additional cursor was created in this step\n           * (in the Cmd+D situation this means that no other matches were found)\n           */\n          this.vimState.editor.revealRange(\n            new vscode.Range(cursorToTrack.stop, cursorToTrack.stop),\n            revealType,\n          );\n        }\n      }\n    }\n\n    // cursor block\n    const cursorRange: vscode.Range[] = [];\n    if (\n      getCursorType(this.vimState, this.currentMode) === VSCodeVimCursorType.TextDecoration &&\n      this.currentMode !== Mode.Insert &&\n      !configuration.getCursorStyleForMode(this.currentMode)\n    ) {\n      // Fake block cursor with text decoration. Unfortunately we can't have a cursor\n      // in the middle of a selection natively, which is what we need for Visual Mode.\n      if (this.currentMode === Mode.Visual) {\n        for (const { start: cursorStart, stop: cursorStop } of this.vimState.cursors) {\n          if (cursorStart.isBefore(cursorStop)) {\n            cursorRange.push(new vscode.Range(cursorStop.getLeft(), cursorStop));\n          } else {\n            cursorRange.push(new vscode.Range(cursorStop, cursorStop.getRight()));\n          }\n        }\n      } else {\n        for (const { stop: cursorStop } of this.vimState.cursors) {\n          cursorRange.push(new vscode.Range(cursorStop, cursorStop.getRight()));\n        }\n      }\n    }\n\n    this.vimState.editor.setDecorations(decoration.default, cursorRange);\n\n    // Insert Mode virtual characters: used to temporarily show the remapping pressed keys on\n    // insert mode, to show the `\"` character after pressing `<C-r>`, and to show `?` and the\n    // first character when inserting digraphs with `<C-k>`.\n    const iModeVirtualCharDecorationOptions: vscode.DecorationOptions[] = [];\n    if (this.vimState.currentMode === Mode.Insert || this.vimState.currentMode === Mode.Replace) {\n      let virtualKey: string | undefined;\n      if (this.vimState.recordedState.bufferedKeys.length > 0) {\n        virtualKey = this.vimState.recordedState.bufferedKeys.at(-1);\n      } else if (this.vimState.recordedState.waitingForAnotherActionKey) {\n        virtualKey = this.vimState.recordedState.actionKeys.at(-1);\n        if (virtualKey === '<C-r>') {\n          virtualKey = '\"';\n        } else if (virtualKey === '<C-k>') {\n          virtualKey = '?';\n        }\n      }\n      // Don't show keys with `<` like `<C-x>` but show everything else\n      virtualKey = virtualKey && /<[^>]+>/.test(virtualKey) ? undefined : virtualKey;\n\n      if (virtualKey) {\n        // Normal Render Options with the key to overlap on the next character\n        const renderOptions: vscode.ThemableDecorationRenderOptions = {\n          before: {\n            contentText: virtualKey,\n          },\n        };\n\n        /**\n         * EOL Render Options:\n         * Some times when at the end of line the `currentColor` won't work, or it might be\n         * transparent, so we set the color to 'editor.foreground' when at EOL to avoid the\n         * virtualKey character not showing up.\n         */\n        const eolRenderOptions: vscode.ThemableDecorationRenderOptions = {\n          before: {\n            contentText: virtualKey,\n            color: new vscode.ThemeColor('editor.foreground'),\n          },\n        };\n\n        for (const { stop: cursorStop } of this.vimState.cursors) {\n          if (cursorStop.isLineEnd(this.vimState.document)) {\n            iModeVirtualCharDecorationOptions.push({\n              range: new vscode.Range(cursorStop, cursorStop.getLineEnd()),\n              renderOptions: eolRenderOptions,\n            });\n          } else {\n            iModeVirtualCharDecorationOptions.push({\n              range: new vscode.Range(cursorStop, cursorStop.getRightThroughLineBreaks(true)),\n              renderOptions,\n            });\n          }\n        }\n      }\n    }\n\n    this.vimState.editor.setDecorations(\n      decoration.insertModeVirtualCharacter,\n      iModeVirtualCharDecorationOptions,\n    );\n\n    // OperatorPendingMode half block cursor\n    const opCursorDecorations: vscode.DecorationOptions[] = [];\n    const opCursorCharDecorations: vscode.DecorationOptions[] = [];\n    if (this.vimState.currentModeIncludingPseudoModes === Mode.OperatorPendingMode) {\n      for (const { stop: cursorStop } of this.vimState.cursors) {\n        let text = TextEditor.getCharAt(this.vimState.document, cursorStop);\n        // the ' ' (<space>) needs to be changed to '&nbsp;'\n        text = text === ' ' ? '\\u00a0' : text;\n        const decorationOptions = {\n          range: new vscode.Range(cursorStop, cursorStop.getRight()),\n          renderOptions: {\n            before: {\n              contentText: text,\n            },\n          },\n        };\n        opCursorDecorations.push(decorationOptions);\n        opCursorCharDecorations.push(decorationOptions);\n      }\n    }\n\n    this.vimState.editor.setDecorations(decoration.operatorPendingModeCursor, opCursorDecorations);\n    this.vimState.editor.setDecorations(\n      decoration.operatorPendingModeCursorChar,\n      opCursorCharDecorations,\n    );\n\n    for (const markDecoration of decoration.allMarkDecorations()) {\n      this.vimState.editor.setDecorations(markDecoration, []);\n    }\n\n    if (configuration.showMarksInGutter) {\n      for (const mark of this.vimState.historyTracker.getMarks()) {\n        if (mark.isUppercaseMark && mark.document !== this.vimState.document) {\n          continue;\n        }\n\n        const markDecoration = decoration.getOrCreateMarkDecoration(mark.name);\n        const markLine = mark.position.getLineBegin();\n        const markRange = new vscode.Range(markLine, markLine);\n\n        this.vimState.editor.setDecorations(markDecoration, [markRange]);\n      }\n    }\n\n    const showHighlights =\n      (configuration.incsearch &&\n        (this.currentMode === Mode.SearchInProgressMode ||\n          this.currentMode === Mode.CommandlineInProgress)) ||\n      (configuration.inccommand && this.currentMode === Mode.CommandlineInProgress) ||\n      (configuration.hlsearch && globalState.hl);\n    for (const editor of vscode.window.visibleTextEditors) {\n      const mh = this.handlerMap.get(editor.document.uri);\n      if (mh) {\n        mh.updateSearchHighlights(showHighlights);\n      }\n    }\n\n    const easyMotionDimRanges =\n      this.currentMode === Mode.EasyMotionInputMode &&\n      configuration.easymotionDimBackground &&\n      this.vimState.easyMotion.searchAction instanceof SearchByNCharCommand\n        ? [TextEditor.getDocumentRange(this.vimState.document)]\n        : [];\n    const easyMotionHighlightRanges =\n      this.currentMode === Mode.EasyMotionInputMode &&\n      this.vimState.easyMotion.searchAction instanceof SearchByNCharCommand\n        ? this.vimState.easyMotion.searchAction\n            .getMatches(this.vimState.cursorStopPosition, this.vimState)\n            .map((match) => match.toRange())\n        : [];\n    this.vimState.editor.setDecorations(decoration.easyMotionDimIncSearch, easyMotionDimRanges);\n    this.vimState.editor.setDecorations(decoration.easyMotionIncSearch, easyMotionHighlightRanges);\n\n    for (const viewChange of this.vimState.postponedCodeViewChanges) {\n      void vscode.commands.executeCommand(viewChange.command, viewChange.args);\n    }\n    this.vimState.postponedCodeViewChanges = [];\n\n    if (this.currentMode === Mode.EasyMotionMode) {\n      // Update all EasyMotion decorations\n      this.vimState.easyMotion.updateDecorations(this.vimState.editor);\n    }\n\n    StatusBar.clear(this.vimState, false);\n\n    // NOTE: this is not being awaited to save the 15-20ms block - I think this is fine\n    void VSCodeContext.set('vim.mode', Mode[this.vimState.currentMode]);\n\n    // Tell VSCode that the cursor position changed, so it updates its highlights for `editor.occurrencesHighlight`.\n    const range = new vscode.Range(\n      this.vimState.cursorStartPosition,\n      this.vimState.cursorStopPosition,\n    );\n    if (!/\\s+/.test(this.vimState.document.getText(range))) {\n      void vscode.commands.executeCommand('editor.action.wordHighlight.trigger');\n    }\n  }\n\n  dispose() {\n    for (const d of this.disposables) {\n      d.dispose();\n    }\n  }\n}\n\nfunction getCursorType(vimState: VimState, mode: Mode): VSCodeVimCursorType {\n  switch (mode) {\n    case Mode.Normal:\n      return VSCodeVimCursorType.Block;\n    case Mode.Insert:\n      return VSCodeVimCursorType.Native;\n    case Mode.Visual:\n      return VSCodeVimCursorType.TextDecoration;\n    case Mode.VisualBlock:\n      return VSCodeVimCursorType.TextDecoration;\n    case Mode.VisualLine:\n      return VSCodeVimCursorType.TextDecoration;\n    case Mode.SearchInProgressMode:\n      return VSCodeVimCursorType.UnderlineThin;\n    case Mode.CommandlineInProgress:\n      return VSCodeVimCursorType.UnderlineThin;\n    case Mode.Replace:\n      return VSCodeVimCursorType.Underline;\n    case Mode.EasyMotionMode:\n      return VSCodeVimCursorType.Block;\n    case Mode.EasyMotionInputMode:\n      return VSCodeVimCursorType.Block;\n    case Mode.SurroundInputMode:\n      return getCursorType(vimState, vimState.surround!.previousMode);\n    case Mode.OperatorPendingMode:\n      return VSCodeVimCursorType.UnderlineThin;\n    case Mode.Disabled:\n    default:\n      return VSCodeVimCursorType.Line;\n  }\n}\n"
  },
  {
    "path": "src/mode/modeHandlerMap.ts",
    "content": "import { TextEditor, Uri } from 'vscode';\nimport { ModeHandler } from './modeHandler';\n\n/**\n * Stores one ModeHandler (and therefore VimState) per TextDocument.\n */\nclass ModeHandlerMapImpl {\n  private modeHandlerMap = new Map<Uri, ModeHandler>();\n\n  public async getOrCreate(editor: TextEditor): Promise<[ModeHandler, boolean]> {\n    const editorId = editor.document.uri;\n\n    let isNew = false;\n    let modeHandler: ModeHandler | undefined = this.get(editorId);\n\n    if (!modeHandler) {\n      isNew = true;\n      modeHandler = await ModeHandler.create(this, editor);\n      this.modeHandlerMap.set(editorId, modeHandler);\n    }\n    return [modeHandler, isNew];\n  }\n\n  public get(uri: Uri): ModeHandler | undefined {\n    return this.modeHandlerMap.get(uri);\n  }\n\n  public entries(): IterableIterator<[Uri, ModeHandler]> {\n    return this.modeHandlerMap.entries();\n  }\n\n  public delete(editorId: Uri) {\n    const modeHandler = this.modeHandlerMap.get(editorId);\n    if (modeHandler) {\n      modeHandler.dispose();\n      this.modeHandlerMap.delete(editorId);\n    }\n  }\n\n  public clear() {\n    for (const key of this.modeHandlerMap.keys()) {\n      this.delete(key);\n    }\n  }\n}\n\nexport const ModeHandlerMap = new ModeHandlerMapImpl();\n"
  },
  {
    "path": "src/neovim/neovim.ts",
    "content": "import { ChildProcess, spawn } from 'child_process';\nimport { exists } from 'fs';\nimport { Neovim } from 'neovim/lib/api/Neovim';\nimport { attach } from 'neovim/lib/attach';\nimport { dirname } from 'path';\nimport * as util from 'util';\nimport * as vscode from 'vscode';\nimport { Position, TextDocument } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { Register, RegisterMode } from '../register/register';\nimport { TextEditor } from '../textEditor';\nimport { Logger } from '../util/logger';\nimport { sorted } from './../common/motion/position';\nimport { VimState } from './../state/vimState';\n\nexport class NeovimWrapper implements vscode.Disposable {\n  private process?: ChildProcess;\n  private nvim?: Neovim;\n  private readonly processTimeoutInSeconds = 3;\n\n  async run(\n    vimState: VimState,\n    command: string,\n  ): Promise<{ statusBarText: string; error: boolean }> {\n    if (!this.nvim) {\n      this.nvim = await this.startNeovim(vimState.document);\n\n      try {\n        const nvimAttach = this.nvim.uiAttach(80, 20, {\n          ext_cmdline: false,\n          ext_popupmenu: false,\n          ext_tabline: false,\n          ext_wildmenu: false,\n          rgb: false,\n        });\n\n        const timeout = new Promise((resolve, reject) => {\n          setTimeout(() => reject(new Error('Timeout')), this.processTimeoutInSeconds * 1000);\n        });\n\n        await Promise.race([nvimAttach, timeout]);\n      } catch (e) {\n        configuration.enableNeovim = false;\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        throw new Error(`Failed to attach to neovim process. ${e.message}`);\n      }\n\n      const apiInfo = await this.nvim.apiInfo;\n      const version = apiInfo[1].version;\n      Logger.debug(`version: ${version.major}.${version.minor}.${version.patch}`);\n    }\n\n    await this.syncVSCodeToVim(vimState);\n    command = (':' + command + '\\n').replace('<', '<lt>');\n\n    // Clear the previous error and status messages.\n    // API does not allow setVvar so do it manually\n    await this.nvim.command('let v:errmsg=\"\" | let v:statusmsg=\"\"');\n\n    // Execute the command\n    Logger.debug(`Running ${command}.`);\n    await this.nvim.input(command);\n    const mode = await this.nvim.mode;\n    if (mode.blocking) {\n      await this.nvim.input('<esc>');\n    }\n\n    // Check if an error occurred\n    const errMsg = await this.nvim.getVvar('errmsg');\n    let statusBarText = '';\n    let error = false;\n    if (errMsg && errMsg.toString() !== '') {\n      statusBarText = errMsg.toString();\n      error = true;\n    } else {\n      // Check to see if a status message was updated\n      const statusMsg = await this.nvim.getVvar('statusmsg');\n      if (statusMsg && statusMsg.toString() !== '') {\n        statusBarText = statusMsg.toString();\n      }\n    }\n\n    // Sync buffer back to VSCode\n    await this.syncVimToVSCode(vimState);\n\n    return { statusBarText, error };\n  }\n\n  private async startNeovim(document: TextDocument) {\n    Logger.debug('Spawning Neovim process...');\n    let dir = dirname(document.uri.fsPath);\n    if (!(await util.promisify(exists)(dir))) {\n      dir = __dirname;\n    }\n    const neovimArgs: string[] = [];\n    // '-u' flag is only added if user wants to use a custom path for\n    // their config file OR they want no config file to be loaded at all.\n    // '-u' flag is omitted altogether if user wants Neovim to look for a\n    // config in its default location.\n    if (configuration.neovimUseConfigFile) {\n      if (configuration.neovimConfigPath !== '') {\n        neovimArgs.push('-u', configuration.neovimConfigPath);\n      }\n    } else {\n      neovimArgs.push('-u', 'NONE');\n    }\n    neovimArgs.push('-i', 'NONE', '-n', '--embed');\n    this.process = spawn(configuration.neovimPath, neovimArgs, {\n      cwd: dir,\n    });\n\n    this.process.on('error', (err) => {\n      Logger.error(`Error spawning neovim. ${err.message}.`);\n      configuration.enableNeovim = false;\n    });\n\n    return attach({ proc: this.process });\n  }\n\n  // Data flows from VSCode to Vim\n  private async syncVSCodeToVim(vimState: VimState) {\n    if (!this.nvim) {\n      return;\n    }\n\n    const buf = await this.nvim.buffer;\n    if (configuration.expandtab) {\n      await vscode.commands.executeCommand('editor.action.indentationToTabs');\n    }\n\n    await this.nvim.setOption('gdefault', configuration.gdefault === true);\n    await buf.setLines(vimState.document.getText().split('\\n'), {\n      start: 0,\n      end: -1,\n      strictIndexing: true,\n    });\n\n    const [rangeStart, rangeEnd] = sorted(\n      vimState.cursorStartPosition,\n      vimState.cursorStopPosition,\n    );\n    await this.nvim.callFunction('setpos', [\n      '.',\n      [0, vimState.cursorStopPosition.line + 1, vimState.cursorStopPosition.character, false],\n    ]);\n    await this.nvim.callFunction('setpos', [\n      \"'<\",\n      [0, rangeStart.line + 1, rangeEnd.character, false],\n    ]);\n    await this.nvim.callFunction('setpos', [\n      \"'>\",\n      [0, rangeEnd.line + 1, rangeEnd.character, false],\n    ]);\n    for (const mark of vimState.historyTracker.getLocalMarks()) {\n      await this.nvim.callFunction('setpos', [\n        `'${mark.name}`,\n        [0, mark.position.line + 1, mark.position.character, false],\n      ]);\n    }\n\n    // We only copy over \" register for now, due to our weird handling of macros.\n    const reg = await Register.get('\"');\n    if (reg) {\n      const vsRegTovimReg = ['c', 'l', 'b'];\n      await this.nvim.callFunction('setreg', [\n        '\"',\n        reg.text as string,\n        vsRegTovimReg[vimState.currentRegisterMode],\n      ]);\n    }\n  }\n\n  // Data flows from Vim to VSCode\n  private async syncVimToVSCode(vimState: VimState) {\n    if (!this.nvim) {\n      return;\n    }\n\n    const buf = await this.nvim.buffer;\n    const lines = await buf.getLines({ start: 0, end: -1, strictIndexing: false });\n\n    // one Windows, lines that went to nvim and back have a '\\r' at the end,\n    // which causes the issues exhibited in #1914\n    const fixedLines =\n      process.platform === 'win32' ? lines.map((line, index) => line.replace(/\\r$/, '')) : lines;\n\n    const lineCount = vimState.document.lineCount;\n\n    await TextEditor.replace(\n      vimState.editor,\n      new vscode.Range(0, 0, lineCount - 1, TextEditor.getLineLength(lineCount - 1)),\n      fixedLines.join('\\n'),\n    );\n\n    Logger.debug(`${lines.length} lines in nvim. ${lineCount} in editor.`);\n\n    const [row, character] = ((await this.nvim.callFunction('getpos', ['.'])) as number[]).slice(\n      1,\n      3,\n    );\n    vimState.editor.selection = new vscode.Selection(\n      new Position(row - 1, character),\n      new Position(row - 1, character),\n    );\n\n    if (configuration.expandtab) {\n      await vscode.commands.executeCommand('editor.action.indentationToSpaces');\n    }\n    // We're only syncing back the default register for now, due to the way we could\n    // be storing macros in registers.\n    const vimRegToVsReg: { [key: string]: RegisterMode } = {\n      v: RegisterMode.CharacterWise,\n      V: RegisterMode.LineWise,\n      '\\x16': RegisterMode.BlockWise,\n    };\n    vimState.currentRegisterMode =\n      vimRegToVsReg[(await this.nvim.callFunction('getregtype', ['\"'])) as string];\n    Register.put(vimState, (await this.nvim.callFunction('getreg', ['\"'])) as string);\n  }\n\n  dispose() {\n    if (this.nvim) {\n      this.nvim.quit();\n    }\n\n    if (this.process) {\n      this.process.kill();\n    }\n  }\n}\n"
  },
  {
    "path": "src/platform/browser/constants.ts",
    "content": "export const SUPPORT_VIMRC = false;\nexport const SUPPORT_NVIM = false;\nexport const SUPPORT_IME_SWITCHER = false;\nexport const SUPPORT_READ_COMMAND = false;\n"
  },
  {
    "path": "src/platform/browser/fs.ts",
    "content": "import * as vscode from 'vscode';\n\nexport const constants = {\n  UV_FS_SYMLINK_DIR: 1,\n  UV_FS_SYMLINK_JUNCTION: 2,\n  O_RDONLY: 0,\n  O_WRONLY: 1,\n  O_RDWR: 2,\n  UV_DIRENT_UNKNOWN: 0,\n  UV_DIRENT_FILE: 1,\n  UV_DIRENT_DIR: 2,\n  UV_DIRENT_LINK: 3,\n  UV_DIRENT_FIFO: 4,\n  UV_DIRENT_SOCKET: 5,\n  UV_DIRENT_CHAR: 6,\n  UV_DIRENT_BLOCK: 7,\n  S_IFMT: 61440,\n  S_IFREG: 32768,\n  S_IFDIR: 16384,\n  S_IFCHR: 8192,\n  S_IFBLK: 24576,\n  S_IFIFO: 4096,\n  S_IFLNK: 40960,\n  S_IFSOCK: 49152,\n  O_CREAT: 512,\n  O_EXCL: 2048,\n  UV_FS_O_FILEMAP: 0,\n  O_NOCTTY: 131072,\n  O_TRUNC: 1024,\n  O_APPEND: 8,\n  O_DIRECTORY: 1048576,\n  O_NOFOLLOW: 256,\n  O_SYNC: 128,\n  O_DSYNC: 4194304,\n  O_SYMLINK: 2097152,\n  O_NONBLOCK: 4,\n  S_IRWXU: 448,\n  S_IRUSR: 256,\n  S_IWUSR: 128,\n  S_IXUSR: 64,\n  S_IRWXG: 56,\n  S_IRGRP: 32,\n  S_IWGRP: 16,\n  S_IXGRP: 8,\n  S_IRWXO: 7,\n  S_IROTH: 4,\n  S_IWOTH: 2,\n  S_IXOTH: 1,\n  F_OK: 0,\n  R_OK: 4,\n  W_OK: 2,\n  X_OK: 1,\n  UV_FS_COPYFILE_EXCL: 1,\n  COPYFILE_EXCL: 1,\n  UV_FS_COPYFILE_FICLONE: 2,\n  COPYFILE_FICLONE: 2,\n  UV_FS_COPYFILE_FICLONE_FORCE: 4,\n  COPYFILE_FICLONE_FORCE: 4,\n};\n\nexport async function doesFileExist(fileUri: vscode.Uri) {\n  try {\n    await vscode.workspace.fs.stat(fileUri);\n    return true;\n  } catch {\n    return false;\n  }\n}\n\nexport async function existsAsync(path: string): Promise<boolean> {\n  try {\n    await vscode.workspace.fs.stat(vscode.Uri.parse(path));\n    return true;\n  } catch (_e) {\n    return false;\n  }\n}\n\nexport async function unlink(path: string): Promise<void> {\n  await vscode.workspace.fs.delete(vscode.Uri.parse(path));\n}\n\nexport async function readFileAsync(path: string, encoding: BufferEncoding): Promise<string> {\n  const ret = await vscode.workspace.fs.readFile(vscode.Uri.parse(path));\n  return ret.toString();\n}\n\nexport async function mkdirAsync(path: string, options: any): Promise<void> {\n  return vscode.workspace.fs.createDirectory(vscode.Uri.parse(path));\n}\n\nexport async function writeFileAsync(\n  path: string,\n  content: string,\n  encoding: BufferEncoding,\n): Promise<void> {\n  return vscode.workspace.fs.writeFile(vscode.Uri.parse(path), Buffer.from(content));\n}\n\nexport async function accessAsync(path: string, mode: number) {\n  // no op in nodeless\n}\n\nexport async function chmodAsync(path: string, mode: string | number) {\n  // no op in nodeless\n}\n\nexport function unlinkSync(path: string) {\n  // no op in nodeless\n}\n"
  },
  {
    "path": "src/platform/browser/history.ts",
    "content": "import * as vscode from 'vscode';\n\nexport class HistoryBase {\n  private readonly context: vscode.ExtensionContext;\n  private readonly historyFileName: string;\n  private history: string[] = [];\n\n  get historyKey(): string {\n    return `vim.${this.historyFileName}`;\n  }\n\n  constructor(\n    context: vscode.ExtensionContext,\n    historyFileName: string,\n    extensionStoragePath: string,\n  ) {\n    this.context = context;\n    this.historyFileName = historyFileName;\n  }\n\n  public async add(value: string | undefined, history: number): Promise<void> {\n    if (!value || value.length === 0) {\n      return;\n    }\n\n    // remove duplicates\n    const index: number = this.history.indexOf(value);\n    if (index !== -1) {\n      this.history.splice(index, 1);\n    }\n\n    // append to the end\n    this.history.push(value);\n\n    // resize array if necessary\n    if (this.history.length > history) {\n      this.history = this.history.slice(this.history.length - history);\n    }\n\n    return this.save();\n  }\n\n  public get(history: number): string[] {\n    // resize array if necessary\n    if (this.history.length > history) {\n      this.history = this.history.slice(this.history.length - history);\n    }\n\n    return this.history;\n  }\n\n  public async clear() {\n    void this.context.workspaceState.update(this.historyKey, undefined);\n    this.history = [];\n  }\n\n  public async load(): Promise<void> {\n    const data = this.context.workspaceState.get<string>(this.historyKey) || '';\n    if (data.length === 0) {\n      return;\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    const parsedData = JSON.parse(data);\n    if (!Array.isArray(parsedData)) {\n      throw Error('Unexpected format in history. Expected JSON.');\n    }\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    this.history = parsedData;\n  }\n\n  async save(): Promise<void> {\n    void this.context.workspaceState.update(this.historyKey, JSON.stringify(this.history));\n  }\n}\n"
  },
  {
    "path": "src/platform/node/constants.ts",
    "content": "export const SUPPORT_VIMRC = true;\nexport const SUPPORT_NVIM = true;\nexport const SUPPORT_IME_SWITCHER = true;\nexport const SUPPORT_READ_COMMAND = true;\n"
  },
  {
    "path": "src/platform/node/fs.ts",
    "content": "import * as fs from 'fs';\nimport * as util from 'util';\nimport { promisify } from 'util';\nimport * as vscode from 'vscode';\n\nexport const constants = {\n  UV_FS_SYMLINK_DIR: 1,\n  UV_FS_SYMLINK_JUNCTION: 2,\n  O_RDONLY: 0,\n  O_WRONLY: 1,\n  O_RDWR: 2,\n  UV_DIRENT_UNKNOWN: 0,\n  UV_DIRENT_FILE: 1,\n  UV_DIRENT_DIR: 2,\n  UV_DIRENT_LINK: 3,\n  UV_DIRENT_FIFO: 4,\n  UV_DIRENT_SOCKET: 5,\n  UV_DIRENT_CHAR: 6,\n  UV_DIRENT_BLOCK: 7,\n  S_IFMT: 61440,\n  S_IFREG: 32768,\n  S_IFDIR: 16384,\n  S_IFCHR: 8192,\n  S_IFBLK: 24576,\n  S_IFIFO: 4096,\n  S_IFLNK: 40960,\n  S_IFSOCK: 49152,\n  O_CREAT: 512,\n  O_EXCL: 2048,\n  UV_FS_O_FILEMAP: 0,\n  O_NOCTTY: 131072,\n  O_TRUNC: 1024,\n  O_APPEND: 8,\n  O_DIRECTORY: 1048576,\n  O_NOFOLLOW: 256,\n  O_SYNC: 128,\n  O_DSYNC: 4194304,\n  O_SYMLINK: 2097152,\n  O_NONBLOCK: 4,\n  S_IRWXU: 448,\n  S_IRUSR: 256,\n  S_IWUSR: 128,\n  S_IXUSR: 64,\n  S_IRWXG: 56,\n  S_IRGRP: 32,\n  S_IWGRP: 16,\n  S_IXGRP: 8,\n  S_IRWXO: 7,\n  S_IROTH: 4,\n  S_IWOTH: 2,\n  S_IXOTH: 1,\n  F_OK: 0,\n  R_OK: 4,\n  W_OK: 2,\n  X_OK: 1,\n  UV_FS_COPYFILE_EXCL: 1,\n  COPYFILE_EXCL: 1,\n  UV_FS_COPYFILE_FICLONE: 2,\n  COPYFILE_FICLONE: 2,\n  UV_FS_COPYFILE_FICLONE_FORCE: 4,\n  COPYFILE_FICLONE_FORCE: 4,\n};\n\nexport async function doesFileExist(fileUri: vscode.Uri) {\n  const activeTextEditor = vscode.window.activeTextEditor;\n  if (activeTextEditor) {\n    try {\n      await vscode.workspace.fs.stat(fileUri);\n      return true;\n    } catch {\n      return false;\n    }\n  } else {\n    // fallback to local fs\n    const fsExists = util.promisify(fs.exists);\n    return fsExists(fileUri.fsPath);\n  }\n}\n\nexport async function existsAsync(path: string): Promise<boolean> {\n  try {\n    await vscode.workspace.fs.stat(vscode.Uri.file(path));\n    return true;\n  } catch (_e) {\n    return false;\n  }\n}\n\nexport async function unlink(path: string): Promise<void> {\n  fs.unlinkSync(path);\n}\n\nexport async function readFileAsync(path: string, encoding: BufferEncoding): Promise<string> {\n  return promisify(fs.readFile)(path, encoding);\n}\n\nexport async function mkdirAsync(path: string, options: any): Promise<void> {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n  await promisify(fs.mkdir)(path, options);\n}\n\nexport async function writeFileAsync(\n  path: string,\n  content: string,\n  encoding: BufferEncoding,\n): Promise<void> {\n  await promisify(fs.writeFile)(path, content, encoding);\n}\n\nexport async function accessAsync(path: string, mode: number) {\n  return promisify(fs.access)(path, mode);\n}\n\nexport async function chmodAsync(path: string, mode: string | number) {\n  return promisify(fs.chmod)(path, mode);\n}\n\nexport async function getMode(path: string): Promise<number> {\n  const pathMode = await fs.promises\n    .stat(path)\n    .then((stat) => stat.mode)\n    .catch((e) => {\n      throw e;\n    });\n  return pathMode;\n}\n\nexport function unlinkSync(path: string) {\n  fs.unlinkSync(path);\n}\n"
  },
  {
    "path": "src/platform/node/history.ts",
    "content": "import * as path from 'path';\nimport { mkdirAsync, readFileAsync, unlinkSync, writeFileAsync } from 'platform/fs';\nimport * as vscode from 'vscode';\nimport { Globals } from '../../globals';\nimport { Logger } from '../../util/logger';\n\nexport class HistoryBase {\n  private readonly extensionStoragePath: string;\n  private readonly historyFileName: string;\n  private history: string[] = [];\n\n  get historyKey(): string {\n    return path.join(this.extensionStoragePath, this.historyFileName);\n  }\n\n  constructor(\n    context: vscode.ExtensionContext,\n    historyFileName: string,\n    extensionStoragePath: string,\n  ) {\n    this.historyFileName = historyFileName;\n    this.extensionStoragePath = extensionStoragePath;\n  }\n\n  public async add(value: string | undefined, history: number): Promise<void> {\n    if (!value || value.length === 0) {\n      return;\n    }\n\n    // remove duplicates\n    const index: number = this.history.indexOf(value);\n    if (index !== -1) {\n      this.history.splice(index, 1);\n    }\n\n    // append to the end\n    this.history.push(value);\n\n    // resize array if necessary\n    if (this.history.length > history) {\n      this.history = this.history.slice(this.history.length - history);\n    }\n\n    return this.save();\n  }\n\n  public get(history: number): string[] {\n    // resize array if necessary\n    if (this.history.length > history) {\n      this.history = this.history.slice(this.history.length - history);\n    }\n\n    return this.history;\n  }\n\n  public clear() {\n    try {\n      this.history = [];\n      unlinkSync(this.historyKey);\n    } catch (err) {\n      Logger.warn(`Unable to delete ${this.historyKey}. err=${err}.`);\n    }\n  }\n\n  public async load(): Promise<void> {\n    // await this._base.load();\n    let data = '';\n\n    try {\n      data = await readFileAsync(this.historyKey, 'utf-8');\n    } catch (err) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      if (err.code === 'ENOENT') {\n        Logger.debug(`History does not exist. path=${this.historyKey}`);\n      } else {\n        Logger.warn(`Failed to load history. path=${this.historyKey} err=${err}.`);\n      }\n      return;\n    }\n\n    if (data.length === 0) {\n      return;\n    }\n\n    try {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      const parsedData = JSON.parse(data);\n      if (!Array.isArray(parsedData)) {\n        throw Error('Unexpected format in history file. Expected JSON.');\n      }\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      this.history = parsedData;\n    } catch (e) {\n      Logger.warn(`Deleting corrupted history file. path=${this.historyKey} err=${e}.`);\n      this.clear();\n    }\n  }\n\n  async save(): Promise<void> {\n    try {\n      // create supplied directory. if directory already exists, do nothing and move on\n      try {\n        await mkdirAsync(Globals.extensionStoragePath, { recursive: true });\n      } catch (createDirectoryErr) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        if (createDirectoryErr.code !== 'EEXIST') {\n          throw createDirectoryErr;\n        }\n      }\n\n      // create file\n      await writeFileAsync(this.historyKey, JSON.stringify(this.history), 'utf-8');\n    } catch (err) {\n      Logger.error(`Failed to save history. filepath=${this.historyKey}. err=${err}.`);\n      throw err;\n    }\n  }\n}\n"
  },
  {
    "path": "src/register/register.ts",
    "content": "import { readFileAsync, writeFileAsync } from 'platform/fs';\nimport { Globals } from '../globals';\nimport { RecordedState } from './../state/recordedState';\nimport { VimState } from './../state/vimState';\nimport { Clipboard } from './../util/clipboard';\n\n/**\n * This is included in the register file.\n * Whenever the format saved to disk changes, so should this.\n */\nconst REGISTER_FORMAT_VERSION = '1.0';\n\n/**\n * There are two different modes of copy/paste in Vim - copy by character\n * and copy by line. Copy by line typically happens in Visual Line mode, but\n * also shows up in some other actions that work over lines (most notably dd,\n * yy).\n */\nexport enum RegisterMode {\n  CharacterWise,\n  LineWise,\n  BlockWise,\n}\n\nexport type RegisterContent = string | RecordedState;\n\nexport interface IRegisterContent {\n  text: RegisterContent;\n  registerMode: RegisterMode;\n}\n\nexport class Register {\n  private static readonly specialRegisters: readonly string[] = [\n    '\"', // Unnamed (default)\n    '*', // Clipboard\n    '+', // Clipboard\n    '.', // Last inserted text\n    '-', // Last deleted text less than a line\n    '/', // Most recently executed search\n    ':', // Most recently executed command\n    '%', // Current file path (relative to workspace root)\n    '#', // Previous file path (relative to workspace root)\n    '_', // Black hole (always empty)\n    '=', // Expression register\n  ];\n\n  private static registers: Map<string, IRegisterContent[]>;\n\n  /**\n   * Puts given content in the currently selected register, using the current RegisterMode.\n   *\n   * @param copyToUnnamed: If true, set the unnamed register (\") as well\n   */\n  public static put(\n    vimState: VimState,\n    content: RegisterContent,\n    multicursorIndex?: number,\n    copyToUnnamed?: boolean,\n  ): void {\n    const register = vimState.recordedState.registerName;\n\n    if (!Register.isValidRegister(register)) {\n      throw new Error(`Invalid register ${register}`);\n    }\n\n    if (Register.isBlackHoleRegister(register) || Register.isReadOnlyRegister(register)) {\n      return;\n    }\n\n    if (Register.isValidUppercaseRegister(register)) {\n      Register.appendToRegister(vimState, register.toLowerCase(), content, multicursorIndex ?? 0);\n    } else {\n      Register.overwriteRegister(vimState, register, content, multicursorIndex ?? 0);\n    }\n\n    if (copyToUnnamed && register !== '\"') {\n      Register.registers.set('\"', Register.registers.get(register)!);\n    }\n  }\n\n  public static isValidRegister(register: string): boolean {\n    return (\n      Register.isValidLowercaseRegister(register) ||\n      Register.isValidUppercaseRegister(register) ||\n      /^[0-9]$/.test(register) ||\n      this.specialRegisters.includes(register)\n    );\n  }\n\n  public static isValidRegisterForMacro(register: string): boolean {\n    return /^[a-zA-Z0-9:]$/.test(register);\n  }\n\n  private static isBlackHoleRegister(registerName: string): boolean {\n    return registerName === '_';\n  }\n\n  private static isClipboardRegister(registerName: string): boolean {\n    return registerName === '*' || registerName === '+';\n  }\n\n  private static isReadOnlyRegister(registerName: string): boolean {\n    return ['.', '%', ':', '#', '/'].includes(registerName);\n  }\n\n  private static isValidLowercaseRegister(register: string): boolean {\n    return /^[a-z]$/.test(register);\n  }\n\n  public static isValidUppercaseRegister(register: string): boolean {\n    return /^[A-Z]$/.test(register);\n  }\n\n  /**\n   * Puts the content at the specified index of the multicursor Register.\n   * If multicursorIndex === 0, the register will be completely overwritten. Otherwise, just that index will be.\n   */\n  public static overwriteRegister(\n    vimState: VimState,\n    register: string,\n    content: RegisterContent,\n    multicursorIndex: number,\n  ): void {\n    if (multicursorIndex === 0 || !Register.registers.has(register)) {\n      Register.registers.set(register, []);\n    }\n\n    Register.registers.get(register)![multicursorIndex] = {\n      registerMode: vimState.currentRegisterMode,\n      text: content,\n    };\n\n    if (\n      multicursorIndex === 0 &&\n      this.isClipboardRegister(register) &&\n      !(content instanceof RecordedState)\n    ) {\n      void Clipboard.Copy(content);\n    }\n\n    this.processNumberedRegisters(vimState, content);\n  }\n\n  /**\n   * Appends the content at the specified index of the multicursor Register.\n   */\n  private static appendToRegister(\n    vimState: VimState,\n    register: string,\n    content: RegisterContent,\n    multicursorIndex: number,\n  ): void {\n    if (!Register.registers.has(register)) {\n      Register.registers.set(register, []);\n    }\n\n    const contentByCursor = Register.registers.get(register)!;\n    const oldContent = contentByCursor[multicursorIndex];\n    if (oldContent === undefined) {\n      contentByCursor[multicursorIndex] = {\n        registerMode: vimState.currentRegisterMode,\n        text: content,\n      };\n    } else {\n      // Line-wise trumps other RegisterModes\n      const registerMode =\n        vimState.currentRegisterMode === RegisterMode.LineWise\n          ? RegisterMode.LineWise\n          : oldContent.registerMode;\n      let newText: RegisterContent;\n      if (oldContent.text instanceof RecordedState || content instanceof RecordedState) {\n        newText = oldContent.text;\n      } else {\n        newText = oldContent.text + (registerMode === RegisterMode.LineWise ? '\\n' : '') + content;\n      }\n      contentByCursor[multicursorIndex] = {\n        registerMode,\n        text: newText,\n      };\n    }\n\n    if (multicursorIndex === 0 && this.isClipboardRegister(register)) {\n      const newContent = contentByCursor[multicursorIndex].text;\n      if (!(newContent instanceof RecordedState)) {\n        void Clipboard.Copy(newContent);\n      }\n    }\n  }\n\n  /**\n   * Updates a readonly register's content. This is the only way to do so.\n   */\n  public static setReadonlyRegister(\n    register: '.' | '%' | ':' | '#' | '/',\n    content: RegisterContent,\n  ) {\n    Register.registers.set(register, [\n      {\n        text: content,\n        registerMode: RegisterMode.CharacterWise,\n      },\n    ]);\n  }\n\n  /**\n   * Handles special cases for Yank- and DeleteOperator.\n   */\n  private static processNumberedRegisters(vimState: VimState, content: RegisterContent): void {\n    // Find the BaseOperator of the current actions\n    const baseOperator = vimState.recordedState.operator || vimState.recordedState.command;\n    if (!baseOperator) {\n      return;\n    }\n\n    if (baseOperator.name === 'yank_op' || baseOperator.name === 'yank_full_line') {\n      // 'yank' to 0 only if no register was specified\n      const registerCommand = vimState.recordedState.actionsRun.find((value) => {\n        return value.name === 'cmd_register';\n      });\n\n      if (!registerCommand) {\n        Register.registers.set('0', [\n          {\n            text: content,\n            registerMode: vimState.currentRegisterMode,\n          },\n        ]);\n      }\n    } else if (\n      (baseOperator.name === 'delete_op' ||\n        baseOperator.name === 'delete_char' ||\n        baseOperator.name === 'delete_last_char' ||\n        baseOperator.name === 'delete_char_visual_line_mode' ||\n        baseOperator.name === 'delete_char_with_del') &&\n      !vimState.isReplayingMacro\n    ) {\n      if (\n        !content.toString().match(/\\n/g) &&\n        vimState.currentRegisterMode !== RegisterMode.LineWise\n      ) {\n        Register.registers.set('-', [\n          {\n            text: content,\n            registerMode: RegisterMode.CharacterWise,\n          },\n        ]);\n      } else {\n        // shift 'delete-history' register\n        for (let index = 9; index > 1; index--) {\n          const previous = Register.registers.get(String(index - 1));\n          if (previous) {\n            Register.registers.set(String(index), { ...previous });\n          }\n        }\n\n        // Paste last delete into register '1'\n        Register.registers.set('1', [\n          {\n            text: content,\n            registerMode: vimState.currentRegisterMode,\n          },\n        ]);\n      }\n    }\n  }\n\n  /**\n   * @returns content of the given register\n   */\n  public static async get(\n    register: string,\n    multicursorIndex = 0,\n  ): Promise<IRegisterContent | undefined> {\n    if (Register.isClipboardRegister(register)) {\n      const contentByCursor = Register.registers.get(register);\n      const clipboardContent = (await Clipboard.Paste()).replace(/\\r\\n/g, '\\n');\n      const currentRegisterContent = (contentByCursor?.[0]?.text as string)?.replace(/\\r\\n/g, '\\n');\n      if (currentRegisterContent !== clipboardContent) {\n        // System clipboard seems to have changed\n        const registerContent = {\n          text: clipboardContent,\n          registerMode: RegisterMode.CharacterWise,\n        };\n        Register.registers.set(register, [registerContent]);\n      }\n    }\n\n    return Register._get(register, multicursorIndex);\n  }\n\n  /**\n   * @returns content of the given register\n   *\n   * NOTE: The clipboard register is silently converted to the unnamed register\n   */\n  public static getSync(register: string, multicursorIndex = 0): IRegisterContent | undefined {\n    if (Register.isClipboardRegister(register)) {\n      register = '\"';\n    }\n\n    return Register._get(register, multicursorIndex);\n  }\n\n  private static _get(register: string, multicursorIndex: number): IRegisterContent | undefined {\n    if (!Register.isValidRegister(register)) {\n      throw new Error(`Invalid register ${register}`);\n    }\n\n    register = register.toLowerCase();\n\n    const contentByCursor = Register.registers.get(register);\n    if (contentByCursor === undefined) {\n      return undefined;\n    }\n\n    // Default to first cursor if the requested multicursor index doesn't exist\n    return contentByCursor[multicursorIndex] ?? contentByCursor[0];\n  }\n\n  public static has(register: string): boolean {\n    return Register.registers.has(register);\n  }\n\n  public static getKeys(): string[] {\n    return [...Register.registers.keys()];\n  }\n\n  public static clearAllRegisters(): void {\n    Register.registers.clear();\n  }\n\n  public static async saveToDisk(supportNode: boolean): Promise<void> {\n    if (supportNode) {\n      const serializableRegisters = new Array<[string, IRegisterContent[]]>();\n      for (const [key, contentByCursor] of Register.registers) {\n        if (!contentByCursor.some((content) => content instanceof RecordedState)) {\n          serializableRegisters.push([key, contentByCursor]);\n        }\n      }\n      return import('path').then((path) => {\n        return writeFileAsync(\n          path.join(Globals.extensionStoragePath, '.registers'),\n          JSON.stringify({\n            version: REGISTER_FORMAT_VERSION,\n            registers: serializableRegisters,\n          }),\n          'utf8',\n        );\n      });\n    }\n  }\n\n  public static loadFromDisk(supportNode: boolean): void {\n    if (supportNode) {\n      Register.registers = new Map();\n      void import('path').then((path) => {\n        void readFileAsync(path.join(Globals.extensionStoragePath, '.registers'), 'utf8').then(\n          (savedRegisters) => {\n            // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n            const parsed = JSON.parse(savedRegisters);\n            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n            if (parsed.version === REGISTER_FORMAT_VERSION) {\n              // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access\n              Register.registers = new Map(parsed.registers);\n            }\n          },\n        );\n      });\n    } else {\n      Register.registers = new Map();\n    }\n  }\n}\n"
  },
  {
    "path": "src/state/compositionState.ts",
    "content": "export class CompositionState {\n  isInComposition: boolean = false;\n  insertedText: boolean = false;\n  composingText: string = '';\n\n  reset() {\n    this.isInComposition = false;\n    this.insertedText = false;\n    this.composingText = '';\n  }\n}\n"
  },
  {
    "path": "src/state/globalState.ts",
    "content": "import { JumpTracker } from '../jumps/jumpTracker';\nimport { RecordedState } from './../state/recordedState';\nimport { SearchState } from './searchState';\nimport { SubstituteState } from './substituteState';\n\n/**\n * State which stores global state (across editors)\n */\nclass GlobalState {\n  /**\n   * Track jumps, and traverse jump history\n   */\n  public readonly jumpTracker: JumpTracker = new JumpTracker();\n\n  /**\n   * The keystroke sequence that made up our last complete action (that can be\n   * repeated with '.').\n   */\n  public previousFullAction: RecordedState | undefined = undefined;\n\n  public lastInvokedMacro: RecordedState | undefined = undefined;\n\n  /**\n   * Last substitute state for running :s by itself\n   */\n  public substituteState: SubstituteState | undefined = undefined;\n\n  /**\n   * The most recently active SearchState\n   * This is used for things like `n` and `hlsearch`\n   */\n  public searchState: SearchState | undefined = undefined;\n\n  /**\n   * Used internally for nohl.\n   */\n  public hl = true;\n}\n\nexport const globalState = new GlobalState();\n"
  },
  {
    "path": "src/state/recordedState.ts",
    "content": "import { IBaseAction, IBaseCommand, IBaseOperator } from '../actions/types';\nimport { configuration } from '../configuration/configuration';\nimport { Mode, isVisualMode } from '../mode/mode';\nimport { SpecialKeys } from '../util/specialKeys';\nimport { PositionDiff } from './../common/motion/position';\nimport { Transformer } from './../transformations/transformer';\n\nconst ESCAPE_REGEX = new RegExp(/[|\\\\{}()[\\]^$+*?.]/, 'g');\nconst BUFFERED_KEYS_REGEX = new RegExp(SpecialKeys.TimeoutFinished, 'g');\n\n/**\n * Much of Vim's power comes from the composition of individual actions.\n *\n * RecordedState holds the state associated with a sequence of actions,\n * generally beginning and ending in Normal mode.\n *\n * For example, each of these action sequences would be combined into a single RecordedState:\n *   - 5\"xyw      (yank 5 words into the 'x' register)\n *   - Axyz<Esc>  (append 'xyz' to end of line)\n *   - Vjj~       (reverse case of next 3 lines)\n *\n * The last action (for dot-repeating), macros, and a few other things are RecordedStates.\n */\nexport class RecordedState {\n  constructor() {\n    this.registerName = configuration.useSystemClipboard ? '*' : '\"';\n  }\n\n  /**\n   * The keys the user has pressed that have not caused an action to be executed\n   * yet and have not been stored on action keys. Used for command remapping.\n   */\n  public commandList: string[] = [];\n\n  /**\n   * String representation of the exact keys that the user entered.\n   */\n  public get commandString(): string {\n    const result =\n      this.actionsRunPressedKeys.join('') + this.actionKeys.join('') + this.bufferedKeys.join('');\n\n    if (result.length === 0 && this.commandList.length > 0) {\n      // Used for the registers and macros that only record on commandList\n      // The commandList here saves the ex command, so we should not regex replace the\n      // leader key by the literal string '<leader>', as would have been done for a normal command.\n      return this.commandList.join('');\n    }\n\n    return result\n      .replace(new RegExp(configuration.leader.replace(ESCAPE_REGEX, '\\\\$&'), 'g'), '<leader>')\n      .replace(BUFFERED_KEYS_REGEX, '');\n  }\n\n  /**\n   * String representation of the pending keys that the user entered.\n   */\n  public get pendingCommandString(): string {\n    const result = this.actionKeys.join('') + this.bufferedKeys.join('');\n\n    return result\n      .replace(new RegExp(configuration.leader.replace(ESCAPE_REGEX, '\\\\$&'), 'g'), '<leader>')\n      .replace(BUFFERED_KEYS_REGEX, '');\n  }\n\n  /**\n   * Reset the command list.\n   */\n  public resetCommandList() {\n    this.commandList = [];\n  }\n\n  /**\n   * Keeps track of keys pressed for the next action. Comes in handy when parsing\n   * multiple length movements, e.g. gg.\n   */\n  public actionKeys: string[] = [];\n\n  /**\n   * Waiting for another key for a potential action.\n   *\n   * Used to prevent the remapping of keys after a potential action key\n   * like @zZtTfF[]rm'`\"gq<C-r><C-w>. This is done to be able to use all\n   * the named registers and marks, even when the command with the same\n   * name has been mapped.\n   *\n   * Vim Documentation: (:help map-error)\n   * \"Note that the second character (argument) of the commands @zZtTfF[]rm'`\"v\n   * and CTRL-X is not mapped. This was done to be able to use all the named\n   * registers and marks, even when the command with the same name has been\n   * mapped.\"\n   *\n   * The documentation only specifies some keys, but from testing pretty much\n   * every key has this condition (keys like 'g', 'q', '<C-r>' and '<C-w>' all\n   * behave the same) so here we use 'waitingForAnotherActionKey' to prevent\n   * remapping on next keys. In the case of the 'v' key specified in the vim\n   * documentation, I don't really understand what they mean with that because\n   * it doesn't make much sense. The 'v' key puts you in Visual mode, it doesn't\n   * accept any character argument.\n   */\n  public waitingForAnotherActionKey: boolean = false;\n\n  /**\n   * Every action that has been run.\n   */\n  public actionsRun: IBaseAction[] = [];\n\n  /**\n   * Keeps track of keys pressed by the actionsRun. Used for the showCmd. If an action\n   * changes previous actions pressed keys it should change this list, like the <Del>\n   * key after a number key.\n   */\n  public actionsRunPressedKeys: string[] = [];\n\n  /**\n   * Every key that was buffered to wait for a new key or the timeout to finish\n   * in order to get another potential remap or to solve an ambiguous remap.\n   */\n  public bufferedKeys: string[] = [];\n  public bufferedKeysTimeoutObj: NodeJS.Timeout | undefined = undefined;\n\n  /**\n   * This is used when the remappers are resending the keys after a potential\n   * remap without an ambiguous remap is broken, either by a new key or by the\n   * timeout finishing.\n   *\n   * It will make it so the first key sent will not be considered as a potential\n   * remap by any of the remappers, even though it is, to prevent the remappers\n   * of doing the same thing again. This way the first key will be handled as an\n   * action but the next keys can still be remapped.\n   *\n   * Example: if you map `iiii -> i<C-A><Esc>` in normal mode and map `ii -> <Esc>`\n   * in insert mode, after pressing `iii` you want the first `i` to put you in\n   * insert mode and the next `ii` to escape to normal mode.\n   */\n  public allowPotentialRemapOnFirstKey = true;\n\n  public hasRunOperator = false;\n\n  /**\n   * This is kind of a hack and should be associated with something like this:\n   *\n   * https://github.com/VSCodeVim/Vim/issues/805\n   */\n  public operatorPositionDiff: PositionDiff | undefined;\n\n  public isInsertion = false;\n\n  /**\n   * The text transformations that we want to run. They will all be run after the action has been processed.\n   *\n   * Running an individual action will generally queue up to one of these, but if you're in\n   * multi-cursor mode, you'll queue one per cursor, or more.\n   *\n   * Note that the text transformations are run in parallel. This is useful in most cases,\n   * but will get you in trouble in others.\n   */\n  public transformer = new Transformer();\n\n  /**\n   * The operator (e.g. d, y, >) the user wants to run, if there is one.\n   */\n  public get operator(): IBaseOperator | undefined {\n    return this.operators.at(0);\n  }\n\n  public get operators(): IBaseOperator[] {\n    return this.actionsRun.filter((a): a is IBaseOperator => a.actionType === 'operator').reverse();\n  }\n\n  /**\n   * The command (e.g. i, ., R, /) the user wants to run, if there is one.\n   */\n  public get command(): IBaseCommand {\n    // TODO: this is probably wrong\n    const list = this.actionsRun\n      .filter((a): a is IBaseCommand => a.actionType === 'command')\n      .reverse();\n\n    // TODO - disregard <Esc>, then assert this is of length 1.\n\n    return list[0];\n  }\n\n  /**\n   * The number of times the user wants to repeat this action.\n   */\n  public count: number = 0;\n\n  /**\n   * The number of times the user wants to repeat the operator. If after the operator the user\n   * uses a motion with count that count will be multiplied by this count.\n   *\n   * Example: if user presses 2d3w it deletes 6 words.\n   */\n  public operatorCount: number = 0;\n\n  /**\n   * The register name for this action.\n   */\n  public registerName: string;\n\n  /**\n   * The key used to access the register with `registerName`\n   * Example: if 'q5' then key=5 and name=5\n   * Or:      if 'qA' then key=A and name=a\n   */\n  public registerKey: string = '';\n\n  public clone(): RecordedState {\n    const res = new RecordedState();\n\n    // TODO: Actual clone.\n\n    res.actionKeys = this.actionKeys.slice(0);\n    res.actionsRun = this.actionsRun.slice(0);\n    res.hasRunOperator = this.hasRunOperator;\n\n    return res;\n  }\n\n  public getOperatorState(mode: Mode): 'pending' | 'ready' | undefined {\n    const operators = this.operators;\n\n    // Do we have an operator that hasn't been run yet?\n    if (\n      operators.length === 0 ||\n      this.hasRunOperator ||\n      // TODO: Is this mode check necessary?\n      mode === Mode.SearchInProgressMode ||\n      mode === Mode.CommandlineInProgress\n    ) {\n      return undefined;\n    }\n\n    // We've got an operator - do we also have a motion or visual selection to operate on?\n    if (this.actionsRun.some((a) => a.actionType === 'motion') || isVisualMode(mode)) {\n      return 'ready';\n    }\n\n    // This case is for a \"repeated\" operator (such as `dd` or `yy`)\n    if (operators.length > 1 && operators[0].constructor === operators[1].constructor) {\n      return 'ready';\n    }\n\n    return 'pending';\n  }\n}\n"
  },
  {
    "path": "src/state/remapState.ts",
    "content": "import { IKeyRemapping } from '../configuration/iconfiguration';\n\n/**\n * State related to key remapping. Held by ModeHandler.\n */\nexport class RemapState {\n  /**\n   * For timing out remapped keys like jj to esc.\n   */\n  public lastKeyPressedTimestamp = 0;\n\n  /**\n   * Used to indicate that a non-recursive remap is being handled.\n   * This is used to prevent non-recursive remappings from looping.\n   */\n  public isCurrentlyPerformingNonRecursiveRemapping = false;\n\n  /**\n   * Used to indicate that a recursive remap is being handled. This is used to prevent recursive remappings\n   * from looping farther then maxMapDepth and to stop recursive remappings when an action fails.\n   */\n  public isCurrentlyPerformingRecursiveRemapping = false;\n\n  /**\n   * Used to indicate that a remap is being handled and the keys sent to modeHandler were not typed\n   * by the user.\n   */\n  public get isCurrentlyPerformingRemapping() {\n    return (\n      this.isCurrentlyPerformingNonRecursiveRemapping ||\n      this.isCurrentlyPerformingRecursiveRemapping\n    );\n  }\n\n  /**\n   * When performing a recursive remapping that has no parent remappings and that finishes while\n   * still waiting for timeout or another key to come we store that remapping here. This is used\n   * to be able to handle those buffered keys and any other key that the user might press to brake\n   * the timeout seperatly. Because if an error happens in the middle of a remap, the remaining\n   * remap keys shouldn't be handled but the user pressed ones should, but if an error happens on\n   * a user typed key, the following typed keys will still be handled.\n   *\n   * Example: having the following remapings:\n   * * `nmap <leader>lf Lfill`\n   * * `nmap Lfillc 4I<space><esc>`\n   * * `nmap Lfillp 2I<space><esc>`\n   * When user presses `<leader>lf` it remaps that to `Lfill` but because that is an ambiguous remap\n   * it creates the timeout and returns from remapper setting the performing remapping flag to false.\n   * This allows the user to then press `c` or `p` and the corresponding remap would run. But if the\n   * user presses another key or the timeout finishes we need to handle the `Lfill` keys and they\n   * need to know they were sent by a remap and not by the user so that in case the find 'i' in\n   * `Lfill` fails the last two `l` shouldn't be executed and any keys typed by the user after the\n   * remap that brake the timeout need to be handled seperatly from `Lfill`.\n   * (Check the tests for this example to understand better).\n   *\n   * To prevent this, we stored the remapping that finished waiting for timeout so that, if the\n   * timeout finishes or the user presses some keys that brake the potential remap, we will know\n   * what was the remapping waiting for timeout. So in case the timeout finishes we set the\n   * currently performing recursive remapping flag to true manually, send the <TimeoutFinished> key\n   * and in the end we set the flag back to false again and clear the stored remapping. In case\n   * the user presses one or more keys that brake the potential timeout we set the flag to true\n   * manually, handle the keys from the remapping and then set the flag back to false, clear the\n   * stored remapping and handle the keys pressed by the user seperatly.\n   * We do this because any VimError or ForceStopRemappingError are thrown only when performing a\n   * remapping.\n   */\n  public wasPerformingRemapThatFinishedWaitingForTimeout: IKeyRemapping | false = false;\n\n  /**\n   * Holds the current map depth count (number of nested remaps without using a character). In recursive remaps\n   * every time we map a key when already performing a remapping this number increases by one. When a remapping\n   * handling uses a character this number resets to 0.\n   *\n   * When it reaches the maxMapDepth it throws the VimError E223.\n   * (check vim documentation :help maxmapdepth)\n   */\n  public mapDepth: number = 0;\n\n  /**\n   * Used to reset the mapDepth on nested recursive remaps. Is set to false every time we get a remapping and is set to\n   * true when a character is used. We consider a character as being used when we get an action.\n   * (check vim documentation :help maxmapdepth).\n   *\n   * Example 1: if we remap `x -> y` and `y -> x` if we press any of those keys we will continuously find a new\n   * remap and increase the mapDepth without ever using an action until we hit maxMapDepth and we get E223 stopping\n   * it all.\n   *\n   * Example 2: if we map `a -> x`, `x -> y`, `y -> b` and `b -> w` and we set maxMapDepth to 4 we get 'E223 Recursive\n   * Mapping', because we get to the fourth remap without ever executing an action, but if we change the 'y' map to\n   * `y -> wb`, now the max mapDepth we hit is 3 and then we execute the action 'w' that resets the mapDepth and then\n   * call another remap of `b -> w` that executes another 'w', meaning that after pressing 'a' the result would be 'ww'.\n   * Another option would be to increase the maxMapDepth to 5 or more and then we could use the initial remaps that would\n   * turn the pressing of 'a' into a single 'w'.\n   *\n   * Example 3 (possible use case): if we remap `<leader>cb -> 0i//<Space><Esc>j<leader>cb` that recursively calls itself,\n   * every time the`0` key is sent we set remapUsedACharacter to true and reset mapDepth to 0 on all nested remaps so even\n   * if it calls itself more than 1000 times (on a file with more than 1000 lines) the mapDepth will always be reset to 0,\n   * which allows the remap to keep calling itself to comment all the lines until either we get to the last line and the 'j'\n   * action fails stopping the entire remap chain or the user presses `<C-c>` or `<Esc>` to forcelly stop the recursive remaps.\n   *\n   * P.S. This behavior is weird, because we should reduce the mapDepth by one when the remapping finished handling\n   * or if it failed. But this is the way Vim does it. This allows the user to create infinite looping remaps\n   * that call themselves and only stop after an error or the user pressing a key (usually <C-c> but we also\n   * allow <Esc> because the user might not allow the use of ctrl keys).\n   *\n   * P.S.2 This is a complicated explanation for a seemingly simple feature, but I wrote this because when I first read the\n   * Vim documentation it wasn't very clear to me how this worked, I first thought that mapDepth was like a map count but that\n   * is not the case because we can have thousands of nested remaps without ever hitting maxMapDepth like in Example 3, and I\n   * only started to understand it better when I tried Example 2 in Vim and some variations of it.\n   */\n  public remapUsedACharacter: boolean = false;\n\n  /**\n   * This will force Stop a recursive remapping. Used by <C-c> or <Esc> key when there is a recursive remapping\n   */\n  public forceStopRecursiveRemapping: boolean = false;\n}\n"
  },
  {
    "path": "src/state/replaceState.ts",
    "content": "import { Position } from 'vscode';\n\ntype ReplaceModeChange = {\n  before: string;\n  after: string;\n};\n\n/**\n * State involved with entering Replace mode (R).\n */\nexport class ReplaceState {\n  /**\n   * Number of times we're going to repeat this replace action.\n   * Comes from the count applied to the `R` command.\n   */\n  public readonly timesToRepeat: number;\n\n  private _changes: ReplaceModeChange[][];\n  public getChanges(cursorIdx: number): ReplaceModeChange[] {\n    if (this._changes[cursorIdx] === undefined) {\n      this._changes[cursorIdx] = [];\n    }\n    return this._changes[cursorIdx];\n  }\n  public resetChanges(cursorIdx: number) {\n    this._changes[cursorIdx] = [];\n  }\n\n  constructor(startPositions: Position[], timesToRepeat: number = 1) {\n    this.timesToRepeat = timesToRepeat;\n    this._changes = startPositions.map((pos) => []);\n  }\n}\n"
  },
  {
    "path": "src/state/searchState.ts",
    "content": "import { Position, Range } from 'vscode';\n\nimport { configuration } from '../configuration/configuration';\nimport { Pattern, SearchDirection, SearchOffset, searchStringParser } from '../vimscript/pattern';\nimport { VimState } from './vimState';\n\nexport type IndexedRange = {\n  range: Range;\n  index: number;\n};\n\nexport type IndexedPosition = {\n  pos: Position;\n  index: number;\n};\n\n/**\n * @returns the least residue of n mod m in the range [0, m) if m > 0, or (m, 0] if m < 0\n */\nfunction mod(n: number, m: number): number {\n  return (m + (n % m)) % m;\n}\n\n/**\n * State involved with beginning a search (/).\n */\nexport class SearchState {\n  constructor(\n    direction: SearchDirection,\n    startPosition: Position,\n    searchString = '',\n    { ignoreSmartcase = false } = {},\n  ) {\n    this._searchString = searchString;\n\n    const result = searchStringParser({ direction, ignoreSmartcase }).parse(this._searchString);\n    const { pattern, offset } = result.status\n      ? result.value\n      : { pattern: undefined, offset: undefined };\n    this.pattern = pattern;\n    this.offset = offset;\n\n    this.cursorStartPosition = startPosition;\n    this.ignoreSmartcase = ignoreSmartcase;\n  }\n\n  private _searchString: string;\n  public pattern?: Pattern;\n  private offset?: SearchOffset;\n\n  public readonly cursorStartPosition: Position;\n\n  public get searchString(): string {\n    return this._searchString;\n  }\n  public set searchString(str: string) {\n    this._searchString = str;\n    const result = searchStringParser({\n      direction: this.direction,\n      ignoreSmartcase: this.ignoreSmartcase,\n    }).parse(str);\n    const { pattern, offset } = result.status\n      ? result.value\n      : { pattern: undefined, offset: undefined };\n    if (pattern?.patternString !== this.pattern?.patternString) {\n      this.pattern = pattern;\n      this.matchRanges.clear();\n    }\n    this.offset = offset;\n  }\n\n  public get direction(): SearchDirection {\n    // TODO: Defaulting to forward is wrong - I think storing the direction in the pattern is a mistake\n    return this.pattern?.direction ?? SearchDirection.Forward;\n  }\n\n  /**\n   * Every range in the document that matches the search string.\n   *\n   * This might not be 100% complete - @see Pattern::MAX_SEARCH_RANGES\n   */\n  public getMatchRanges(vimState: VimState): Range[] {\n    return this.recalculateSearchRanges(vimState);\n  }\n  private matchRanges: Map<string, { version: number; ranges: Range[] }> = new Map();\n\n  /**\n   * If true, an all-lowercase needle will not be treated as case-insensitive, even if smartcase is enabled.\n   * This is used for [g]* and [g]#.\n   */\n  private readonly ignoreSmartcase: boolean;\n\n  private recalculateSearchRanges(vimState: VimState): Range[] {\n    if (this.searchString === '' || this.pattern === undefined) {\n      return [];\n    }\n\n    const document = vimState.document;\n\n    const cached = this.matchRanges.get(document.fileName);\n    if (cached?.version === document.version) {\n      return cached.ranges;\n    }\n\n    // TODO: It's weird to use the active selection for this...\n    const matchRanges = this.pattern\n      .allMatches(vimState, { fromPosition: vimState.editor.selection.active })\n      .map((match) => match.range);\n\n    this.matchRanges.set(document.fileName, {\n      version: document.version,\n      ranges: matchRanges,\n    });\n\n    return matchRanges;\n  }\n\n  /**\n   * @returns The start of the next match range, after applying the search offset\n   *\n   * @see getNextSearchMatchRange for parameters\n   */\n  public getNextSearchMatchPosition(\n    vimState: VimState,\n    startPosition: Position,\n    direction = SearchDirection.Forward,\n    relativeIndex = 0,\n  ): IndexedPosition | undefined {\n    const nextMatch = this.getNextSearchMatchRange(\n      vimState,\n      startPosition,\n      direction,\n      relativeIndex,\n    );\n    if (nextMatch === undefined) {\n      return undefined;\n    }\n    const { range, index } = nextMatch;\n\n    return { pos: this.offset ? this.offset.apply(range) : range.start, index };\n  }\n\n  /**\n   * @returns The next match range from the given position and its rank in the document's matches, or undefined if none exists.\n   * An optional index can be provided to target other matches relative to the next.\n   *\n   * @param direction If `SearchDirection.Backward`, this will search in the opposite of the pattern's direction\n   *\n   * @param relativeIndex Which match to return, relative to the next match. 0 (default) corresponds to the next match,\n   * 1 corresponds to the match after next (in the given direction), -1 corresponds to the match before next, etc.\n   *\n   * NOTE: This method does not take the search offset into account\n   */\n  public getNextSearchMatchRange(\n    vimState: VimState,\n    fromPosition: Position,\n    direction = SearchDirection.Forward,\n    relativeIndex = 0,\n  ): IndexedRange | undefined {\n    const matchRanges = this.recalculateSearchRanges(vimState);\n\n    if (matchRanges.length === 0) {\n      return undefined;\n    }\n\n    const effectiveDirection = (direction * this.direction) as SearchDirection;\n    let index: number | undefined;\n\n    if (effectiveDirection === SearchDirection.Forward) {\n      for (let i = 0; i < matchRanges.length; i++) {\n        if (\n          (this.offset?.apply(matchRanges[i]) ?? matchRanges[i].start).compareTo(fromPosition) > 0\n        ) {\n          index = i;\n          break;\n        }\n      }\n    } else {\n      for (let i = matchRanges.length - 1; i >= 0; i--) {\n        if (\n          (this.offset?.apply(matchRanges[i]) ?? matchRanges[i].start).compareTo(fromPosition) < 0\n        ) {\n          index = i;\n          break;\n        }\n      }\n    }\n\n    if (index === undefined) {\n      // We've hit the top/bottom of the file. Wrap around if configured to do so, or return undefined.\n      if (configuration.wrapscan) {\n        index = effectiveDirection === SearchDirection.Forward ? 0 : matchRanges.length - 1;\n      } else {\n        return undefined;\n      }\n    }\n\n    // index of the first match now stored in variable index.\n    // Offsetting it by relativeIndex in the appropriate direction gets the index of the desired match.\n    index += effectiveDirection * relativeIndex;\n\n    if (0 <= index && index < matchRanges.length) {\n      return { index, range: matchRanges[index] };\n    }\n\n    // We've hit the top/bottom of the file. Wrap around (possibly many times) if configured to do so, or return undefined\n    if (configuration.wrapscan) {\n      index = mod(index, matchRanges.length);\n      return { index, range: matchRanges[index] };\n    } else {\n      return undefined;\n    }\n  }\n\n  /**\n   * @returns the match range which contains the given Position, or undefined if none exists\n   */\n  public findContainingMatchRange(vimState: VimState, pos: Position): IndexedRange | undefined {\n    const matchRanges = this.recalculateSearchRanges(vimState);\n\n    if (matchRanges.length === 0) {\n      return undefined;\n    }\n\n    for (const [index, range] of matchRanges.entries()) {\n      if (range.start.isBeforeOrEqual(pos) && range.end.isAfter(pos)) {\n        return {\n          range,\n          index,\n        };\n      }\n    }\n\n    return undefined;\n  }\n}\n"
  },
  {
    "path": "src/state/substituteState.ts",
    "content": "import { ReplaceString } from '../cmd_line/commands/substitute';\nimport { Pattern } from '../vimscript/pattern';\n\n/**\n * State involved with Substitution commands (:s).\n */\nexport class SubstituteState {\n  /**\n   * The last pattern searched for in the substitution\n   */\n  public searchPattern: Pattern | undefined;\n\n  /**\n   * The last replacement string in the substitution\n   */\n  public replaceString: ReplaceString;\n\n  constructor(searchPattern: Pattern | undefined, replaceString: ReplaceString) {\n    this.searchPattern = searchPattern;\n    this.replaceString = replaceString;\n  }\n}\n"
  },
  {
    "path": "src/state/vimState.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { SUPPORT_IME_SWITCHER, SUPPORT_NVIM } from 'platform/constants';\nimport { Position } from 'vscode';\nimport { IMovement } from '../actions/baseMotion';\nimport { IEasyMotion } from '../actions/plugins/easymotion/types';\nimport { SurroundState } from '../actions/plugins/surround';\nimport { ExCommandLine, SearchCommandLine } from '../cmd_line/commandLine';\nimport { Cursor } from '../common/motion/cursor';\nimport { configuration } from '../configuration/configuration';\nimport { DotCommandStatus, Mode, NormalCommandState } from '../mode/mode';\nimport { ModeData } from '../mode/modeData';\nimport { Logger } from '../util/logger';\nimport { SearchDirection } from '../vimscript/pattern';\nimport { HistoryTracker } from './../history/historyTracker';\nimport { RegisterMode } from './../register/register';\nimport { ReplaceState } from './../state/replaceState';\nimport { globalState } from './globalState';\nimport { RecordedState } from './recordedState';\n\ninterface IInputMethodSwitcher {\n  switchInputMethod(prevMode: Mode, newMode: Mode): Promise<void>;\n}\n\ninterface IBaseMovement {\n  execActionWithCount(\n    position: Position,\n    vimState: VimState,\n    count: number,\n  ): Promise<Position | IMovement>;\n}\n\ninterface INVim {\n  run(vimState: VimState, command: string): Promise<{ statusBarText: string; error: boolean }>;\n\n  dispose(): void;\n}\n\n/**\n * The VimState class holds permanent state that carries over from action\n * to action.\n *\n * Actions defined in actions.ts are only allowed to mutate a VimState in order to\n * indicate what they want to do.\n *\n * Each ModeHandler holds a VimState, so there is one for each open editor.\n */\nexport class VimState implements vscode.Disposable {\n  /**\n   * The column the cursor wants to be at, or Number.POSITIVE_INFINITY if it should always\n   * be the rightmost column.\n   *\n   * Example: If you go to the end of a 20 character column, this value\n   * will be 20, even if you press j and the next column is only 5 characters.\n   * This is because if the third column is 25 characters, the cursor will go\n   * back to the 20th column.\n   */\n  public desiredColumn = 0;\n\n  public historyTracker: HistoryTracker;\n\n  public easyMotion: IEasyMotion;\n\n  public editor: vscode.TextEditor;\n\n  public get document(): vscode.TextDocument {\n    return this.editor.document;\n  }\n\n  /**\n   * Are multiple cursors currently present?\n   */\n  public get isMultiCursor(): boolean {\n    return this._cursors.length > 1;\n  }\n\n  /**\n   * Is the multicursor something like visual block \"multicursor\", where\n   * natively in vim there would only be one cursor whose changes were applied\n   * to all lines after edit.\n   */\n  public isFakeMultiCursor = false;\n\n  /**\n   * Tracks movements that can be repeated with ; (e.g. t, T, f, and F).\n   */\n  public lastSemicolonRepeatableMovement: IBaseMovement | undefined = undefined;\n\n  /**\n   * Tracks movements that can be repeated with , (e.g. t, T, f, and F).\n   */\n  public lastCommaRepeatableMovement: IBaseMovement | undefined = undefined;\n\n  /**\n   * Keep track of whether the last command that ran is able to be repeated\n   * with the dot command.\n   */\n  public lastCommandDotRepeatable: boolean = true;\n\n  public dotCommandStatus: DotCommandStatus = DotCommandStatus.Waiting;\n  public isReplayingMacro: boolean = false;\n  public normalCommandState: NormalCommandState = NormalCommandState.Waiting;\n\n  /**\n   * The last visual selection before running the dot command\n   */\n  public dotCommandPreviousVisualSelection: vscode.Selection | undefined = undefined;\n\n  public surround: SurroundState | undefined = undefined;\n\n  /**\n   * Used for `<C-o>` in insert mode, which allows you run one normal mode\n   * command, then go back to insert mode.\n   */\n  public returnToInsertAfterCommand = false;\n  public actionCount = 0;\n\n  /**\n   * Every time we invoke a VSCode command which might trigger a view update.\n   * We should postpone its view updating phase to avoid conflicting with our internal view updating mechanism.\n   * This array is used to cache every VSCode view updating event and they will be triggered once we run the inhouse `viewUpdate`.\n   */\n  public postponedCodeViewChanges: ViewChange[] = [];\n\n  /**\n   * @deprecated Use cursor.start instead\n   */\n  public get cursorStartPosition(): Position {\n    return this.cursor.start;\n  }\n  public set cursorStartPosition(value: Position) {\n    if (!value.isValid(this.document)) {\n      Logger.warn(`invalid cursor start position. ${value.toString()}.`);\n    }\n    this.cursor = this.cursor.withNewStart(value);\n  }\n\n  /**\n   * @deprecated Use cursor.stop instead\n   */\n  public get cursorStopPosition(): Position {\n    return this.cursor.stop;\n  }\n  public set cursorStopPosition(value: Position) {\n    if (!value.isValid(this.document)) {\n      Logger.warn(`invalid cursor stop position. ${value.toString()}.`);\n    }\n    this.cursor = this.cursor.withNewStop(value);\n  }\n\n  /**\n   * The position of every cursor. Will never be empty.\n   */\n  private _cursors: Cursor[] = [Cursor.atPosition(new Position(0, 0))];\n\n  /**\n   * The 'primary' or 'active' cursor.\n   * What this means is somewhat context dependent, but generally it's safe to\n   * use this when you only care about a single cursor.\n   */\n  public get cursor(): Cursor {\n    return this._cursors[0];\n  }\n  public set cursor(value: Cursor) {\n    this._cursors[0] = value;\n  }\n\n  public get cursors(): Cursor[] {\n    return this._cursors;\n  }\n  public set cursors(value: readonly Cursor[]) {\n    if (value.length === 0) {\n      Logger.warn('Tried to set VimState.cursors to an empty array');\n      return;\n    }\n\n    const map = new Map<string, Cursor>();\n    for (const cursor of value) {\n      if (!cursor.isValid(this.document)) {\n        Logger.warn(`Invalid cursor position: ${cursor.toString()}`);\n      }\n\n      // use a map to ensure no two cursors are at the same location.\n      map.set(cursor.toString(), cursor);\n    }\n\n    this._cursors = [...map.values()];\n  }\n\n  /**\n   * Initial state of cursors prior to any action being performed\n   */\n  private _cursorsInitialState!: readonly Cursor[];\n  public get cursorsInitialState(): readonly Cursor[] {\n    return this._cursorsInitialState;\n  }\n  public set cursorsInitialState(cursors: readonly Cursor[]) {\n    this._cursorsInitialState = [...cursors];\n  }\n\n  /**\n   * Stores last visual mode as well as what was selected for `gv`\n   */\n  public lastVisualSelection:\n    | {\n        mode: Mode.Visual | Mode.VisualLine | Mode.VisualBlock;\n        start: Position;\n        end: Position;\n      }\n    | undefined = undefined;\n\n  /**\n   * The current mode and its associated state.\n   */\n  public modeData: ModeData = { mode: Mode.Normal };\n\n  public get currentMode(): Mode {\n    return this.modeData.mode;\n  }\n\n  private inputMethodSwitcher?: IInputMethodSwitcher;\n  /**\n   * The mode Vim is currently including pseudo-modes like OperatorPendingMode\n   * This is to be used only by the Remappers when getting the remappings so don't\n   * use it anywhere else.\n   */\n  public get currentModeIncludingPseudoModes(): Mode {\n    return this.recordedState.getOperatorState(this.currentMode) === 'pending'\n      ? Mode.OperatorPendingMode\n      : this.currentMode;\n  }\n\n  public async setModeData(modeData: ModeData): Promise<void> {\n    if (modeData === undefined) {\n      // TODO: remove this once we're sure this is no longer an issue (#6500, #6464)\n      throw new Error('Tried setting modeData to undefined');\n    }\n\n    await this.inputMethodSwitcher?.switchInputMethod(this.currentMode, modeData.mode);\n    if (this.returnToInsertAfterCommand && modeData.mode === Mode.Insert) {\n      this.returnToInsertAfterCommand = false;\n    }\n\n    if (modeData.mode === Mode.SearchInProgressMode) {\n      globalState.searchState = modeData.commandLine.getSearchState();\n    }\n\n    this.setTextEditorLineNumbersStyle(modeData.mode);\n\n    this.modeData = modeData;\n  }\n\n  public setTextEditorLineNumbersStyle(mode: Mode): void {\n    if (configuration.smartRelativeLine) {\n      this.editor.options.lineNumbers = [Mode.Insert, Mode.Disabled].includes(mode)\n        ? vscode.TextEditorLineNumbersStyle.On\n        : vscode.TextEditorLineNumbersStyle.Relative;\n    }\n  }\n\n  public async setCurrentMode(mode: Mode): Promise<void> {\n    if (mode === undefined) {\n      // TODO: remove this once we're sure this is no longer an issue (#6500, #6464)\n      throw new Error('Tried setting currentMode to undefined');\n    }\n\n    await this.setModeData(\n      mode === Mode.Replace\n        ? {\n            mode,\n            replaceState: new ReplaceState(\n              this.cursors.map((cursor) => cursor.stop),\n              this.recordedState.count,\n            ),\n          }\n        : mode === Mode.CommandlineInProgress\n          ? {\n              mode,\n              commandLine: new ExCommandLine('', this.modeData.mode),\n            }\n          : mode === Mode.SearchInProgressMode\n            ? {\n                mode,\n                commandLine: new SearchCommandLine(this, '', SearchDirection.Forward),\n                firstVisibleLineBeforeSearch: this.editor.visibleRanges[0].start.line,\n              }\n            : mode === Mode.Insert\n              ? {\n                  mode,\n                  highSurrogate: undefined,\n                }\n              : { mode },\n    );\n  }\n\n  /**\n   * The currently active `RegisterMode`.\n   *\n   * When setting, `undefined` means \"default for current `Mode`\".\n   */\n  public set currentRegisterMode(registerMode: RegisterMode | undefined) {\n    this._currentRegisterMode = registerMode;\n  }\n  public get currentRegisterMode(): RegisterMode {\n    if (this._currentRegisterMode) {\n      return this._currentRegisterMode;\n    }\n    switch (this.currentMode) {\n      case Mode.VisualLine:\n        return RegisterMode.LineWise;\n      case Mode.VisualBlock:\n        return RegisterMode.BlockWise;\n      default:\n        return RegisterMode.CharacterWise;\n    }\n  }\n  private _currentRegisterMode: RegisterMode | undefined;\n\n  public recordedState = new RecordedState();\n\n  /** The macro currently being recorded, if one exists. */\n  public macro: RecordedState | undefined;\n\n  public nvim?: INVim;\n\n  public constructor(editor: vscode.TextEditor, easyMotion: IEasyMotion) {\n    this.editor = editor;\n    this.historyTracker = new HistoryTracker(this);\n    this.easyMotion = easyMotion;\n  }\n\n  async load() {\n    if (SUPPORT_NVIM) {\n      const m = await import('../neovim/neovim');\n      this.nvim = new m.NeovimWrapper();\n    }\n\n    if (SUPPORT_IME_SWITCHER) {\n      const ime = await import('../actions/plugins/imswitcher');\n      this.inputMethodSwitcher = new ime.InputMethodSwitcher();\n    }\n  }\n\n  dispose() {\n    this.nvim?.dispose();\n  }\n}\n\nexport interface ViewChange {\n  command: string;\n  args: any;\n}\n"
  },
  {
    "path": "src/statusBar.ts",
    "content": "import * as vscode from 'vscode';\nimport { configuration } from './configuration/configuration';\nimport { VimError } from './error';\nimport { Mode } from './mode/mode';\nimport { VimState } from './state/vimState';\nimport { Logger } from './util/logger';\n\nclass StatusBarImpl implements vscode.Disposable {\n  // Displays the current state (mode, recording macro, etc.) and messages to the user\n  private readonly statusBarItem: vscode.StatusBarItem;\n\n  // Displays the keys you've typed so far when they haven't yet resolved to a command\n  private readonly recordedStateStatusBarItem: vscode.StatusBarItem;\n\n  private previousMode: Mode | undefined = undefined;\n  private showingDefaultMessage = true;\n\n  public lastMessageTime: Date | undefined;\n\n  constructor() {\n    this.statusBarItem = vscode.window.createStatusBarItem(\n      'primary',\n      vscode.StatusBarAlignment.Left,\n      Number.MIN_SAFE_INTEGER, // Furthest right on the left\n    );\n    this.statusBarItem.name = 'Vim Command Line';\n    this.statusBarItem.command = 'toggleVim';\n    this.statusBarItem.show();\n\n    this.recordedStateStatusBarItem = vscode.window.createStatusBarItem(\n      'showcmd',\n      vscode.StatusBarAlignment.Right,\n      Number.MAX_SAFE_INTEGER, // Furthest left on the right\n    );\n    this.recordedStateStatusBarItem.name = 'Vim Pending Command Keys';\n    this.recordedStateStatusBarItem.show();\n  }\n\n  dispose() {\n    this.statusBarItem.dispose();\n    this.recordedStateStatusBarItem.dispose();\n  }\n\n  public updateShowCmd(vimState: VimState) {\n    this.recordedStateStatusBarItem.text = configuration.showcmd\n      ? statusBarCommandText(vimState)\n      : '';\n  }\n\n  /**\n   * Updates the status bar text\n   * @param isError If true, text rendered in red\n   */\n  public setText(vimState: VimState, text: string, isError = false) {\n    // Text\n    text = text.replace(/\\n/g, '^M');\n    if (this.statusBarItem.text !== text) {\n      this.statusBarItem.text = text;\n      Logger.debug(`Status bar: ${text}`);\n    }\n\n    // StatusBarItem color\n    if (!configuration.statusBarColorControl) {\n      this.statusBarItem.color = isError\n        ? new vscode.ThemeColor('statusBarItem.errorForeground')\n        : undefined;\n      this.statusBarItem.backgroundColor = isError\n        ? new vscode.ThemeColor('statusBarItem.errorBackground')\n        : undefined;\n    }\n\n    // StatusBar color\n    const shouldUpdateColor =\n      configuration.statusBarColorControl && vimState.currentMode !== this.previousMode;\n    if (shouldUpdateColor) {\n      this.updateColor(vimState.currentMode);\n    }\n\n    this.previousMode = vimState.currentMode;\n    this.showingDefaultMessage = false;\n    this.lastMessageTime = new Date();\n  }\n\n  public displayError(vimState: VimState, error: VimError) {\n    StatusBar.setText(vimState, error.toString(), true);\n  }\n\n  public getText() {\n    return this.statusBarItem.text.replace(/\\^M/g, '\\n');\n  }\n\n  /**\n   * Clears any messages from the status bar, leaving the default info, such as\n   * the current mode and macro being recorded.\n   * @param force If true, will clear even high priority messages like errors.\n   */\n  public clear(vimState: VimState, force = true) {\n    if (!this.showingDefaultMessage && !force) {\n      return;\n    }\n\n    const text: string[] = [];\n\n    if (\n      configuration.showmodename ||\n      vimState.currentMode === Mode.CommandlineInProgress ||\n      vimState.currentMode === Mode.SearchInProgressMode\n    ) {\n      text.push(statusBarText(vimState));\n      if (vimState.isMultiCursor) {\n        text.push(' MULTI CURSOR ');\n      }\n    }\n\n    if (vimState.macro) {\n      text.push('recording @' + vimState.macro.registerKey);\n    }\n\n    StatusBar.setText(vimState, text.join(' '));\n\n    this.showingDefaultMessage = true;\n  }\n\n  private updateColor(mode: Mode) {\n    let foreground: string | undefined;\n    let background: string | undefined;\n\n    const colorToSet = (\n      configuration.statusBarColors as unknown as Record<string, string | string[] | undefined>\n    )[Mode[mode].toLowerCase()];\n\n    if (colorToSet !== undefined) {\n      if (typeof colorToSet === 'string') {\n        background = colorToSet;\n      } else {\n        [background, foreground] = colorToSet;\n      }\n    }\n\n    const workbenchConfiguration = configuration.getConfiguration('workbench');\n    const currentColorCustomizations: {\n      [index: string]: string;\n    } = workbenchConfiguration.get('colorCustomizations') ?? {};\n\n    const colorCustomizations = { ...currentColorCustomizations };\n\n    // If colors are undefined, return to VSCode defaults\n    if (background !== undefined) {\n      colorCustomizations['statusBar.background'] = background;\n      colorCustomizations['statusBar.noFolderBackground'] = background;\n      colorCustomizations['statusBar.debuggingBackground'] = background;\n      colorCustomizations['statusBarItem.prominentBackground'] = background;\n    }\n\n    if (foreground !== undefined) {\n      colorCustomizations['statusBar.foreground'] = foreground;\n      colorCustomizations['statusBar.debuggingForeground'] = foreground;\n      colorCustomizations['statusBarItem.prominentForeground'] = foreground;\n    }\n\n    if (currentColorCustomizations !== colorCustomizations) {\n      void workbenchConfiguration.update('colorCustomizations', colorCustomizations, true);\n    }\n  }\n}\n\nexport const StatusBar = new StatusBarImpl();\n\nexport function statusBarText(vimState: VimState) {\n  const cursorChar =\n    vimState.recordedState.actionKeys[vimState.recordedState.actionKeys.length - 1] === '<C-r>'\n      ? '\"'\n      : '|';\n  switch (vimState.modeData.mode) {\n    case Mode.Normal:\n      return '-- NORMAL --';\n    case Mode.Insert:\n      return '-- INSERT --';\n    case Mode.Visual:\n      return '-- VISUAL --';\n    case Mode.VisualBlock:\n      return '-- VISUAL BLOCK --';\n    case Mode.VisualLine:\n      return '-- VISUAL LINE --';\n    case Mode.Replace:\n      return '-- REPLACE --';\n    case Mode.EasyMotionMode:\n      return '-- EASYMOTION --';\n    case Mode.EasyMotionInputMode:\n      return '-- EASYMOTION INPUT --';\n    case Mode.SurroundInputMode:\n      return '-- SURROUND INPUT --';\n    case Mode.Disabled:\n      return '-- VIM: DISABLED --';\n    case Mode.SearchInProgressMode:\n      return vimState.modeData.commandLine.display(cursorChar);\n    case Mode.CommandlineInProgress:\n      return vimState.modeData.commandLine.display(cursorChar);\n    default:\n      return '';\n  }\n}\n\nexport function statusBarCommandText(vimState: VimState): string {\n  switch (vimState.currentMode) {\n    case Mode.SurroundInputMode:\n      return vimState.surround && vimState.surround.replacement\n        ? vimState.surround.replacement\n        : '';\n    case Mode.EasyMotionMode:\n      return `Target key: ${vimState.easyMotion.accumulation}`;\n    case Mode.EasyMotionInputMode:\n      if (!vimState.easyMotion) {\n        return '';\n      }\n\n      const searchCharCount = vimState.easyMotion.searchAction.searchCharCount;\n      const message =\n        searchCharCount > 0\n          ? `Search for ${searchCharCount} character(s): `\n          : 'Search for characters: ';\n      return message + vimState.easyMotion.searchAction.searchString;\n    case Mode.Visual: {\n      // TODO: holy shit, this is SO much more complicated than it should be because\n      // our representation of a visual selection is so weird and inconsistent\n      let [start, end] = [vimState.cursorStartPosition, vimState.cursorStopPosition];\n      let wentOverEOL = false;\n      if (start.isAfter(end)) {\n        start = start.getRightThroughLineBreaks();\n        [start, end] = [end, start];\n      } else if (end.isAfter(start) && end.character === 0) {\n        end = end.getLeftThroughLineBreaks(true);\n        wentOverEOL = true;\n      }\n      const lines = end.line - start.line + 1;\n      if (lines > 1) {\n        return `${lines} ${vimState.recordedState.pendingCommandString}`;\n      } else {\n        const chars = Math.max(end.character - start.character, 1) + (wentOverEOL ? 1 : 0);\n        return `${chars} ${vimState.recordedState.pendingCommandString}`;\n      }\n    }\n    case Mode.VisualLine:\n      return `${\n        Math.abs(vimState.cursorStopPosition.line - vimState.cursorStartPosition.line) + 1\n      } ${vimState.recordedState.pendingCommandString}`;\n    case Mode.VisualBlock: {\n      const lines =\n        Math.abs(vimState.cursorStopPosition.line - vimState.cursorStartPosition.line) + 1;\n      const chars =\n        Math.abs(vimState.cursorStopPosition.character - vimState.cursorStartPosition.character) +\n        1;\n      return `${lines}x${chars} ${vimState.recordedState.pendingCommandString}`;\n    }\n    case Mode.Insert:\n    case Mode.Replace:\n      return vimState.recordedState.pendingCommandString;\n    case Mode.Normal:\n    case Mode.Disabled:\n      return vimState.recordedState.commandString;\n    default:\n      return '';\n  }\n}\n"
  },
  {
    "path": "src/taskQueue.ts",
    "content": "import Queue from 'queue';\nimport { Logger } from './util/logger';\n\nclass TaskQueue {\n  private readonly taskQueue = new Queue({ autostart: true, concurrency: 1 });\n\n  constructor() {\n    this.taskQueue.addListener('error', (err, task) => {\n      // TODO: Report via telemetry API?\n      Logger.error(`Error running task: ${err}`);\n    });\n  }\n\n  /**\n   * Adds a task to the task queue.\n   */\n  public enqueueTask(task: () => Promise<void>): void {\n    this.taskQueue.push(task);\n  }\n}\n\nexport const taskQueue = new TaskQueue();\n"
  },
  {
    "path": "src/textEditor.ts",
    "content": "import * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { Cursor } from './common/motion/cursor';\nimport { configuration } from './configuration/configuration';\nimport { visualBlockGetBottomRightPosition, visualBlockGetTopLeftPosition } from './mode/mode';\nimport { VimState } from './state/vimState';\nimport { Logger } from './util/logger';\nimport { clamp } from './util/util';\n\n/**\n * Collection of helper functions around vscode.window.activeTextEditor\n */\nexport class TextEditor {\n  private static readonly whitespaceRegExp = new RegExp('\\\\s+');\n\n  /**\n   * @deprecated Use InsertTextTransformation (or InsertTextVSCodeTransformation) instead.\n   */\n  static async insert(\n    editor: vscode.TextEditor,\n    text: string,\n    at?: Position,\n    letVSCodeHandleKeystrokes?: boolean,\n  ): Promise<void> {\n    // If we insert \"blah(\" with default:type, VSCode will insert the closing ).\n    // We *probably* don't want that to happen if we're inserting a lot of text.\n    letVSCodeHandleKeystrokes ??= text.length === 1;\n\n    if (!letVSCodeHandleKeystrokes) {\n      await editor.edit((editBuilder) => {\n        if (!at) {\n          at = editor.selection.active;\n        }\n\n        editBuilder.insert(at, text);\n      });\n    } else {\n      await vscode.commands.executeCommand('default:type', { text });\n    }\n  }\n\n  /**\n   * @deprecated. Use ReplaceTextTransformation instead.\n   */\n  static async replace(\n    editor: vscode.TextEditor,\n    range: vscode.Range,\n    text: string,\n  ): Promise<boolean> {\n    return editor.edit((editBuilder) => {\n      editBuilder.replace(range, text);\n    });\n  }\n\n  /** @deprecated Use vimState.document.lineCount */\n  static getLineCount(textEditor?: vscode.TextEditor): number {\n    textEditor ??= vscode.window.activeTextEditor;\n    return textEditor?.document.lineCount ?? -1;\n  }\n\n  public static getLineLength(line: number): number {\n    if (line < 0 || line >= TextEditor.getLineCount()) {\n      Logger.warn(`getLineLength() called with out-of-bounds line ${line}`);\n      return 0;\n    }\n\n    return vscode.window.activeTextEditor!.document.lineAt(line).text.length;\n  }\n\n  /** @deprecated Use `vimState.document.lineAt()` directly */\n  static getLine(lineNumber: number): vscode.TextLine {\n    return vscode.window.activeTextEditor!.document.lineAt(lineNumber);\n  }\n\n  static getCharAt(document: vscode.TextDocument, position: Position): string {\n    position = document.validatePosition(position);\n    return document.lineAt(position).text[position.character];\n  }\n\n  /**\n   * Retrieves the word at the given position.\n   *\n   * Respects `iskeyword`:\n   *    - Will go right (but not over line boundaries) until it finds a \"real\" word\n   *    - Will settle for a \"fake\" word only if it hits the line end\n   */\n  static getWord(document: vscode.TextDocument, position: Position): string | undefined {\n    const line = document.lineAt(position).text;\n\n    // Skip over whitespace\n    let firstNonBlank = position.character;\n    while (this.whitespaceRegExp.test(line[firstNonBlank])) {\n      firstNonBlank++;\n      if (firstNonBlank === line.length) {\n        // Hit end of line without finding a non-whitespace character\n        return undefined;\n      }\n    }\n\n    // Now skip over word separators and whitespace to find a \"real\" word\n    let start = firstNonBlank;\n    while (\n      configuration.iskeyword.includes(line[start]) ||\n      this.whitespaceRegExp.test(line[start])\n    ) {\n      start++;\n      if (start === line.length) {\n        // No keyword found - just settle for the word we're on\n        start = firstNonBlank;\n        break;\n      }\n    }\n\n    const foundRealWord = !configuration.iskeyword.includes(line[start]);\n    const includeInWord = (char: string) =>\n      !this.whitespaceRegExp.test(char) && configuration.iskeyword.includes(char) !== foundRealWord;\n\n    // Expand left and right to find the whole word\n    let end = start;\n    while (start > 0 && includeInWord(line[start - 1])) {\n      start--;\n    }\n    while (end < line.length && includeInWord(line[end + 1])) {\n      end++;\n    }\n\n    return line.substring(start, end + 1);\n  }\n\n  static getTabCharacter(editor: vscode.TextEditor): string {\n    if (editor.options.insertSpaces) {\n      // This will always be a number when we're getting it from the options\n      const tabSize = editor.options.tabSize as number;\n      return ' '.repeat(tabSize);\n    }\n    return '\\t';\n  }\n\n  /**\n   * @returns the number of visible columns that the given line begins with\n   */\n  static getIndentationLevel(line: string, tabSize: number): number {\n    let visibleColumn = 0;\n    for (const char of line) {\n      switch (char) {\n        case '\\t':\n          visibleColumn += tabSize;\n          break;\n        case ' ':\n          visibleColumn += 1;\n          break;\n        default:\n          return visibleColumn;\n      }\n    }\n\n    return visibleColumn;\n  }\n\n  /**\n   * @returns `line` with its indentation replaced with `screenCharacters` visible columns of whitespace\n   */\n  static setIndentationLevel(line: string, screenCharacters: number, expandtab: boolean): string {\n    const tabSize = configuration.tabstop;\n\n    if (screenCharacters < 0) {\n      screenCharacters = 0;\n    }\n\n    const indentString = expandtab\n      ? ' '.repeat(screenCharacters)\n      : '\\t'.repeat(screenCharacters / tabSize) + ' '.repeat(screenCharacters % tabSize);\n\n    return line.replace(/^\\s*/, indentString);\n  }\n\n  static getDocumentBegin(): Position {\n    return new Position(0, 0);\n  }\n\n  static getDocumentEnd(document: vscode.TextDocument): Position {\n    const line = Math.max(document.lineCount, 1) - 1;\n    return document.lineAt(line).range.end;\n  }\n\n  static getDocumentRange(document: vscode.TextDocument): vscode.Range {\n    return new vscode.Range(TextEditor.getDocumentBegin(), TextEditor.getDocumentEnd(document));\n  }\n\n  /**\n   * @returns the Position of the first character on the given line which is not whitespace.\n   * If it's all whitespace, will return the Position of the EOL character.\n   */\n  public static getFirstNonWhitespaceCharOnLine(\n    document: vscode.TextDocument,\n    line: number,\n  ): Position {\n    line = clamp(line, 0, document.lineCount - 1);\n    return new Position(line, document.lineAt(line).firstNonWhitespaceCharacterIndex);\n  }\n\n  /**\n   * Iterate over every line in the block defined by the two positions (Range) passed in.\n   * If no range is given, the primary cursor will be used as the block.\n   *\n   * This is intended for visual block mode.\n   */\n  public static *iterateLinesInBlock(\n    vimState: VimState,\n    cursor?: Cursor,\n    options: { reverse?: boolean } = { reverse: false },\n  ): Iterable<{ line: string; start: Position; end: Position }> {\n    const { reverse } = options;\n\n    cursor ??= vimState.cursor;\n\n    const topLeft = visualBlockGetTopLeftPosition(cursor.start, cursor.stop);\n    const bottomRight = visualBlockGetBottomRightPosition(cursor.start, cursor.stop);\n\n    const [itrStart, itrEnd] = reverse\n      ? [bottomRight.line, topLeft.line]\n      : [topLeft.line, bottomRight.line];\n\n    const runToLineEnd = vimState.desiredColumn === Number.POSITIVE_INFINITY;\n\n    for (\n      let lineIndex = itrStart;\n      reverse ? lineIndex >= itrEnd : lineIndex <= itrEnd;\n      reverse ? lineIndex-- : lineIndex++\n    ) {\n      const line = vimState.document.lineAt(lineIndex).text;\n      const endCharacter = runToLineEnd\n        ? line.length + 1\n        : Math.min(line.length, bottomRight.character + 1);\n\n      yield {\n        line: line.substring(topLeft.character, endCharacter),\n        start: new Position(lineIndex, topLeft.character),\n        end: new Position(lineIndex, endCharacter),\n      };\n    }\n  }\n\n  /**\n   * Iterates through words on the same line, starting from the current position.\n   */\n  public static *iterateWords(\n    document: vscode.TextDocument,\n    start: Position,\n  ): Iterable<{ start: Position; end: Position; word: string }> {\n    const text = document.lineAt(start).text;\n    if (/\\s/.test(text[start.character])) {\n      start = start.nextWordStart(document);\n    }\n    let wordEnd = start.nextWordEnd(document, { inclusive: true });\n    do {\n      const word = text.substring(start.character, wordEnd.character + 1);\n      yield {\n        start,\n        end: wordEnd,\n        word,\n      };\n\n      if (wordEnd.getRight().isLineEnd(document)) {\n        return;\n      }\n      start = start.nextWordStart(document);\n      wordEnd = start.nextWordEnd(document, { inclusive: true });\n    } while (true);\n  }\n}\n\n/**\n * Directions in the view for editor scroll command.\n */\nexport type EditorScrollDirection = 'up' | 'down';\n\n/**\n * Units for editor scroll 'by' argument\n */\nexport type EditorScrollByUnit = 'line' | 'wrappedLine' | 'page' | 'halfPage';\n\n/**\n * Positions in the view for cursor move command.\n */\nexport type CursorMovePosition =\n  | 'left'\n  | 'right'\n  | 'up'\n  | 'down'\n  | 'wrappedLineStart'\n  | 'wrappedLineFirstNonWhitespaceCharacter'\n  | 'wrappedLineColumnCenter'\n  | 'wrappedLineEnd'\n  | 'wrappedLineLastNonWhitespaceCharacter'\n  | 'viewPortTop'\n  | 'viewPortCenter'\n  | 'viewPortBottom'\n  | 'viewPortIfOutside';\n\n/**\n * Units for Cursor move 'by' argument\n */\nexport type CursorMoveByUnit = 'line' | 'wrappedLine' | 'character' | 'halfLine';\n"
  },
  {
    "path": "src/textobject/paragraph.ts",
    "content": "import { Position } from 'vscode';\nimport { TextEditor } from '../textEditor';\n\n/**\n * Get the end of the current paragraph.\n */\nexport function getCurrentParagraphEnd(pos: Position, trimWhite: boolean = false): Position {\n  const lastLine = TextEditor.getLineCount() - 1;\n\n  let line = pos.line;\n\n  // If we're not in a paragraph yet, go down until we are.\n  while (line < lastLine && isLineBlank(line, trimWhite)) {\n    line++;\n  }\n\n  // Go until we're outside of the paragraph, or at the end of the document.\n  while (line < lastLine && !isLineBlank(line, trimWhite)) {\n    line++;\n  }\n\n  return pos.with({ line }).getLineEnd();\n}\n\n/**\n * Get the beginning of the current paragraph.\n */\nexport function getCurrentParagraphBeginning(pos: Position, trimWhite: boolean = false): Position {\n  let line = pos.line;\n\n  // If we're not in a paragraph yet, go up until we are.\n  while (line > 0 && isLineBlank(line, trimWhite)) {\n    line--;\n  }\n\n  // Go until we're outside of the paragraph, or at the beginning of the document.\n  while (line > 0 && !isLineBlank(line, trimWhite)) {\n    line--;\n  }\n\n  return new Position(line, 0);\n}\n\nfunction isLineBlank(line: number, trimWhite: boolean = false): boolean {\n  const text = TextEditor.getLine(line).text;\n  return (trimWhite ? text.trim() : text) === '';\n}\n"
  },
  {
    "path": "src/textobject/sentence.ts",
    "content": "import { Position } from 'vscode';\nimport { TextEditor } from '../textEditor';\nimport { getCurrentParagraphBeginning, getCurrentParagraphEnd } from './paragraph';\nimport { getAllEndPositions, getAllPositions } from './util';\n\nconst sentenceEndRegex = /[\\.!\\?][\"')\\]]*?([ \\n\\t]+|$)/g;\n\nexport function getSentenceBegin(position: Position, args: { forward: boolean }): Position {\n  if (args.forward) {\n    return getNextSentenceBegin(position);\n  } else {\n    return getPreviousSentenceBegin(position);\n  }\n}\n\nexport function getSentenceEnd(pos: Position): Position {\n  const paragraphEnd = getCurrentParagraphEnd(pos);\n  for (let currentLine = pos.line; currentLine <= paragraphEnd.line; currentLine++) {\n    const allPositions = getAllPositions(TextEditor.getLine(currentLine).text, sentenceEndRegex);\n    const newCharacter = allPositions.find(\n      (index) => index > pos.character || currentLine !== pos.line,\n    );\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter);\n    }\n  }\n\n  return getFirstNonWhitespaceInParagraph(pos, paragraphEnd, false);\n}\n\nfunction getPreviousSentenceBegin(pos: Position): Position {\n  const paragraphBegin = getCurrentParagraphBeginning(pos);\n  for (let currentLine = pos.line; currentLine >= paragraphBegin.line; currentLine--) {\n    const endPositions = getAllEndPositions(TextEditor.getLine(currentLine).text, sentenceEndRegex);\n    const newCharacter = endPositions.reverse().find((index) => {\n      const newPositionBeforeThis = new Position(currentLine, index)\n        .getRightThroughLineBreaks()\n        .compareTo(pos);\n\n      return newPositionBeforeThis && (index < pos.character || currentLine < pos.line);\n    });\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter).getRightThroughLineBreaks();\n    }\n  }\n\n  if (paragraphBegin.line + 1 === pos.line || paragraphBegin.line === pos.line) {\n    return paragraphBegin;\n  } else {\n    return new Position(paragraphBegin.line + 1, 0);\n  }\n}\n\nfunction getNextSentenceBegin(pos: Position): Position {\n  // A paragraph and section boundary is also a sentence boundary.\n  const paragraphEnd = getCurrentParagraphEnd(pos);\n  for (let currentLine = pos.line; currentLine <= paragraphEnd.line; currentLine++) {\n    const endPositions = getAllEndPositions(TextEditor.getLine(currentLine).text, sentenceEndRegex);\n    const newCharacter = endPositions.find(\n      (index) => index > pos.character || currentLine !== pos.line,\n    );\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter).getRightThroughLineBreaks();\n    }\n  }\n\n  return getFirstNonWhitespaceInParagraph(pos, paragraphEnd, false);\n}\n\nfunction getFirstNonWhitespaceInParagraph(\n  pos: Position,\n  paragraphEnd: Position,\n  inclusive: boolean,\n): Position {\n  // If the cursor is at an empty line, it's the end of a paragraph and the begin of another paragraph\n  // Find the first non-whitespace character.\n  if (TextEditor.getLine(pos.line).text) {\n    return paragraphEnd;\n  } else {\n    for (let currentLine = pos.line; currentLine <= paragraphEnd.line; currentLine++) {\n      const nonWhitePositions = getAllPositions(TextEditor.getLine(currentLine).text, /\\S/g);\n      const newCharacter = nonWhitePositions.find(\n        (index) =>\n          (index > pos.character && !inclusive) ||\n          (index >= pos.character && inclusive) ||\n          currentLine !== pos.line,\n      );\n\n      if (newCharacter !== undefined) {\n        return new Position(currentLine, newCharacter);\n      }\n    }\n  }\n\n  // Only happens at end of document\n  return pos;\n}\n"
  },
  {
    "path": "src/textobject/textobject.ts",
    "content": "import { Position, TextDocument } from 'vscode';\nimport { RegisterAction } from '../actions/base';\nimport { BaseMovement, IMovement, failedMovement } from '../actions/baseMotion';\nimport {\n  ExpandingSelection,\n  MoveAroundBacktick,\n  MoveAroundCurlyBrace,\n  MoveAroundDoubleQuotes,\n  MoveAroundParentheses,\n  MoveAroundSingleQuotes,\n  MoveAroundSquareBracket,\n  MoveAroundTag,\n} from '../actions/motion';\nimport { ChangeOperator } from '../actions/operator';\nimport { Cursor } from '../common/motion/cursor';\nimport { configuration } from '../configuration/configuration';\nimport { Mode } from '../mode/mode';\nimport { RegisterMode } from '../register/register';\nimport { VimState } from '../state/vimState';\nimport { TextEditor } from '../textEditor';\nimport { getCurrentParagraphBeginning, getCurrentParagraphEnd } from './paragraph';\nimport { WordType } from './word';\n\nexport abstract class TextObject extends BaseMovement {\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualBlock];\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<IMovement> {\n    const res = await this.execAction(position, vimState);\n    // Since we need to handle leading spaces, we cannot use MoveWordBegin.execActionForOperator\n    // In normal mode, the character on the stop position will be the first character after the operator executed\n    // and we do left-shifting in operator-pre-execution phase, here we need to right-shift the stop position accordingly.\n    res.stop = new Position(res.stop.line, res.stop.character + 1);\n\n    return res;\n  }\n\n  public abstract override execAction(position: Position, vimState: VimState): Promise<IMovement>;\n}\n\n@RegisterAction\nexport class SelectWord extends TextObject {\n  keys = ['a', 'w'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n    const currentChar = TextEditor.getCharAt(vimState.document, position);\n\n    if (currentChar === undefined) {\n      start = position;\n      stop = position.nextWordEnd(vimState.document);\n    } else if (/\\s/.test(currentChar)) {\n      start = position.prevWordEnd(vimState.document).getRight();\n      stop = position.nextWordEnd(vimState.document);\n    } else {\n      stop = position.nextWordStart(vimState.document);\n      // If the next word is not at the beginning of the next line, we want to pretend it is.\n      // This is because 'aw' has two fundamentally different behaviors distinguished by whether\n      // the next word is directly after the current word, as described in the following comment.\n      // The only case that's not true is in cases like #1350.\n      if (stop.isEqual(TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, stop.line))) {\n        stop = stop.getLineBegin();\n      }\n      stop = stop.getLeftThroughLineBreaks().getLeftIfEOL();\n      // If we aren't separated from the next word by whitespace(like in \"horse ca|t,dog\" or at the end of the line)\n      // then we delete the spaces to the left of the current word. Otherwise, we delete to the right.\n      // Also, if the current word is the leftmost word, we only delete from the start of the word to the end.\n      if (\n        stop.isEqual(position.nextWordEnd(vimState.document, { inclusive: true })) &&\n        !position\n          .prevWordStart(vimState.document, { inclusive: true })\n          .isEqual(TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, stop.line)) &&\n        vimState.recordedState.count === 0\n      ) {\n        start = position.prevWordEnd(vimState.document).getRight();\n      } else {\n        start = position.prevWordStart(vimState.document, { inclusive: true });\n      }\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor position is before cursor start position, we are selecting words in reverser order.\n        if (/\\s/.test(currentChar)) {\n          stop = position.prevWordStart(vimState.document, { inclusive: true });\n        } else {\n          stop = position.prevWordEnd(vimState.document).getRight();\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectABigWord extends TextObject {\n  keys = ['a', 'W'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n\n    const currentChar = vimState.document.lineAt(position).text[position.character];\n\n    if (currentChar === undefined) {\n      start = position;\n      stop = position.nextWordEnd(vimState.document);\n    } else if (/\\s/.test(currentChar)) {\n      start = position.prevWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n      stop = position.nextWordEnd(vimState.document, { wordType: WordType.Big });\n    } else {\n      // Check 'aw' code for much of the reasoning behind this logic.\n      const nextWord = position.nextWordStart(vimState.document, { wordType: WordType.Big });\n      if (\n        (nextWord.line > position.line || nextWord.isAtDocumentEnd(vimState.document)) &&\n        vimState.recordedState.count === 0\n      ) {\n        if (position.prevWordEnd(vimState.document, { wordType: WordType.Big }).isLineBeginning()) {\n          start = position.prevWordEnd(vimState.document, { wordType: WordType.Big });\n        } else {\n          start = position.prevWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n        }\n        stop = position.getLineEnd();\n      } else if (\n        (nextWord.isEqual(\n          TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, nextWord.line),\n        ) ||\n          nextWord.isLineEnd(vimState.document)) &&\n        vimState.recordedState.count === 0\n      ) {\n        start = position.prevWordEnd(vimState.document).getRight();\n        stop = position.getLineEnd();\n      } else {\n        start = position.prevWordStart(vimState.document, {\n          wordType: WordType.Big,\n          inclusive: true,\n        });\n        stop = position.nextWordStart(vimState.document, { wordType: WordType.Big }).getLeft();\n      }\n    }\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting words in reverser order.\n        if (/\\s/.test(currentChar)) {\n          stop = position.prevWordStart(vimState.document, { wordType: WordType.Big });\n        } else {\n          stop = position.prevWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n/**\n * This is a custom action that I (johnfn) added. It selects procedurally\n * larger blocks. e.g. if you had \"blah (foo [bar 'ba|z'])\" then it would\n * select 'baz' first. If you pressed af again, it'd then select [bar 'baz'],\n * and if you did it a third time it would select \"(foo [bar 'baz'])\".\n *\n * Very similar is the now built-in `editor.action.smartSelect.expand`\n */\n@RegisterAction\nexport class SelectAnExpandingBlock extends ExpandingSelection {\n  keys = ['a', 'f'];\n  override modes = [Mode.Visual, Mode.VisualLine];\n\n  public override async execAction(\n    position: Position,\n    vimState: VimState,\n    firstIteration: boolean,\n    lastIteration: boolean,\n  ): Promise<IMovement> {\n    const blocks = [\n      new MoveAroundDoubleQuotes(),\n      new MoveAroundSingleQuotes(),\n      new MoveAroundBacktick(),\n      new MoveAroundCurlyBrace(),\n      new MoveAroundParentheses(),\n      new MoveAroundSquareBracket(),\n      new MoveAroundTag(),\n    ];\n    // ideally no state would change as we test each of the possible expansions\n    // a deep copy of vimState could work here but may be expensive\n    let ranges: IMovement[] = [];\n    for (const block of blocks) {\n      const cursorPos = new Position(position.line, position.character);\n      const cursorStartPos = new Position(\n        vimState.cursorStartPosition.line,\n        vimState.cursorStartPosition.character,\n      );\n      ranges.push(await block.execAction(cursorPos, vimState, firstIteration, lastIteration));\n      vimState.cursorStartPosition = cursorStartPos;\n    }\n\n    ranges = ranges.filter((range) => {\n      return !range.failed;\n    });\n\n    let smallestRange: Cursor | undefined;\n\n    for (const iMotion of ranges) {\n      const currentSelectedRange = new Cursor(\n        vimState.cursorStartPosition,\n        vimState.cursorStopPosition,\n      );\n      if (iMotion.failed) {\n        continue;\n      }\n\n      const range = new Cursor(iMotion.start, iMotion.stop);\n      let contender: Cursor | undefined;\n\n      if (\n        range.start.isBefore(currentSelectedRange.start) &&\n        range.stop.isAfter(currentSelectedRange.stop)\n      ) {\n        if (!smallestRange) {\n          contender = range;\n        } else {\n          if (range.start.isAfter(smallestRange.start) && range.stop.isBefore(smallestRange.stop)) {\n            contender = range;\n          }\n        }\n      }\n\n      if (contender) {\n        const areTheyEqual =\n          contender.equals(new Cursor(vimState.cursorStartPosition, vimState.cursorStopPosition)) ||\n          (vimState.currentMode === Mode.VisualLine &&\n            contender.start.line === vimState.cursorStartPosition.line &&\n            contender.stop.line === vimState.cursorStopPosition.line);\n\n        if (!areTheyEqual) {\n          smallestRange = contender;\n        }\n      }\n    }\n    if (!smallestRange) {\n      return {\n        start: vimState.cursorStartPosition,\n        stop: vimState.cursorStopPosition,\n      };\n    } else {\n      // revert relevant state changes\n      vimState.cursorStartPosition = new Position(\n        smallestRange.start.line,\n        smallestRange.start.character,\n      );\n      vimState.cursorStopPosition = new Position(\n        smallestRange.stop.line,\n        smallestRange.stop.character,\n      );\n      vimState.recordedState.operatorPositionDiff = undefined;\n      return {\n        start: smallestRange.start,\n        stop: smallestRange.stop,\n      };\n    }\n  }\n}\n\n@RegisterAction\nexport class SelectInnerWord extends TextObject {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['i', 'w'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n    const currentChar = vimState.document.lineAt(position).text[position.character];\n\n    if (currentChar === undefined) {\n      start = position;\n      stop = position.nextWordStart(vimState.document).getLeftThroughLineBreaks();\n    } else if (/\\s/.test(currentChar)) {\n      start = position.prevWordEnd(vimState.document).getRight();\n      stop = position.nextWordStart(vimState.document).getLeftThroughLineBreaks();\n    } else {\n      start = position.prevWordStart(vimState.document, { inclusive: true });\n      stop = position.nextWordEnd(vimState.document, { inclusive: true });\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting words in reverser order.\n        if (/\\s/.test(currentChar)) {\n          stop = position.prevWordEnd(vimState.document).getRight();\n        } else {\n          stop = position.prevWordStart(vimState.document, { inclusive: true });\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectInnerBigWord extends TextObject {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['i', 'W'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n    const currentChar = vimState.document.lineAt(position).text[position.character];\n\n    if (currentChar === undefined) {\n      start = position;\n      stop = position.nextWordStart(vimState.document).getLeftThroughLineBreaks();\n    } else if (/\\s/.test(currentChar)) {\n      start = position.prevWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n      stop = position.nextWordStart(vimState.document, { wordType: WordType.Big }).getLeft();\n    } else {\n      start = position.prevWordStart(vimState.document, {\n        wordType: WordType.Big,\n        inclusive: true,\n      });\n      stop = position.nextWordEnd(vimState.document, {\n        wordType: WordType.Big,\n        inclusive: true,\n      });\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting words in reverser order.\n        if (/\\s/.test(currentChar)) {\n          stop = position.prevWordEnd(vimState.document, { wordType: WordType.Big }).getRight();\n        } else {\n          stop = position.prevWordStart(vimState.document, { wordType: WordType.Big });\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectSentence extends TextObject {\n  keys = ['a', 's'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n\n    const currentSentenceBegin = position.getSentenceBegin({ forward: false });\n    const currentSentenceNonWhitespaceEnd = currentSentenceBegin.getSentenceEnd();\n\n    if (currentSentenceNonWhitespaceEnd.isBefore(position)) {\n      // The cursor is on a trailing white space.\n      start = currentSentenceNonWhitespaceEnd.getRight();\n      stop = currentSentenceBegin.getSentenceBegin({ forward: true }).getSentenceEnd();\n    } else {\n      const nextSentenceBegin = currentSentenceBegin.getSentenceBegin({ forward: true });\n\n      // If the sentence has no trailing white spaces, `as` should include its leading white spaces.\n      if (nextSentenceBegin.isEqual(currentSentenceBegin.getSentenceEnd())) {\n        start = currentSentenceBegin\n          .getSentenceBegin({ forward: false })\n          .getSentenceEnd()\n          .getRight();\n        stop = nextSentenceBegin;\n      } else {\n        start = currentSentenceBegin;\n        stop = nextSentenceBegin.getLeft();\n      }\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting sentences in reverser order.\n        if (currentSentenceNonWhitespaceEnd.isAfter(vimState.cursorStopPosition)) {\n          stop = currentSentenceBegin\n            .getSentenceBegin({ forward: false })\n            .getSentenceEnd()\n            .getRight();\n        } else {\n          stop = currentSentenceBegin;\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectInnerSentence extends TextObject {\n  keys = ['i', 's'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position;\n    let stop: Position;\n\n    const currentSentenceBegin = position.getSentenceBegin({ forward: false });\n    const currentSentenceNonWhitespaceEnd = currentSentenceBegin.getSentenceEnd();\n\n    if (currentSentenceNonWhitespaceEnd.isBefore(position)) {\n      // The cursor is on a trailing white space.\n      start = currentSentenceNonWhitespaceEnd.getRight();\n      stop = currentSentenceBegin.getSentenceBegin({ forward: true }).getLeft();\n    } else {\n      start = currentSentenceBegin;\n      stop = currentSentenceNonWhitespaceEnd;\n    }\n\n    if (\n      vimState.currentMode === Mode.Visual &&\n      !vimState.cursorStopPosition.isEqual(vimState.cursorStartPosition)\n    ) {\n      start = vimState.cursorStartPosition;\n\n      if (vimState.cursorStopPosition.isBefore(vimState.cursorStartPosition)) {\n        // If current cursor postion is before cursor start position, we are selecting sentences in reverser order.\n        if (currentSentenceNonWhitespaceEnd.isAfter(vimState.cursorStopPosition)) {\n          stop = currentSentenceBegin;\n        } else {\n          stop = currentSentenceNonWhitespaceEnd.getRight();\n        }\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectParagraph extends TextObject {\n  keys = ['a', 'p'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    let start: Position;\n    const currentParagraphBegin = getCurrentParagraphBeginning(position, true);\n\n    if (vimState.document.lineAt(position).isEmptyOrWhitespace) {\n      // The cursor is at an empty line, it can be both the start of next paragraph and the end of previous paragraph\n      start = getCurrentParagraphEnd(getCurrentParagraphBeginning(position, true), true);\n    } else {\n      if (\n        currentParagraphBegin.isLineBeginning() &&\n        currentParagraphBegin.isLineEnd(vimState.document)\n      ) {\n        start = currentParagraphBegin.getRightThroughLineBreaks();\n      } else {\n        start = currentParagraphBegin;\n      }\n    }\n\n    // Include additional blank lines.\n    let stop = getCurrentParagraphEnd(position, true);\n    while (\n      stop.line < vimState.document.lineCount - 1 &&\n      vimState.document.lineAt(stop.getDown()).isEmptyOrWhitespace\n    ) {\n      stop = stop.with({ character: 0 }).getDown();\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectInnerParagraph extends TextObject {\n  keys = ['i', 'p'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    vimState.currentRegisterMode = RegisterMode.LineWise;\n\n    let start: Position;\n    let stop: Position;\n\n    if (vimState.document.lineAt(position).isEmptyOrWhitespace) {\n      // The cursor is at an empty line, so white lines are the paragraph.\n      start = position.getLineBegin();\n      stop = position.getLineEnd();\n      while (start.line > 0 && vimState.document.lineAt(start.getUp()).isEmptyOrWhitespace) {\n        start = start.getUp();\n      }\n      while (\n        stop.line < vimState.document.lineCount - 1 &&\n        vimState.document.lineAt(stop.getDown()).isEmptyOrWhitespace\n      ) {\n        stop = stop.with({ character: 0 }).getDown();\n      }\n    } else {\n      const currentParagraphBegin = getCurrentParagraphBeginning(position, true);\n      stop = getCurrentParagraphEnd(position, true);\n      if (vimState.document.lineAt(currentParagraphBegin).isEmptyOrWhitespace) {\n        start = currentParagraphBegin.getRightThroughLineBreaks();\n      } else {\n        start = currentParagraphBegin;\n      }\n\n      // Exclude additional blank lines.\n      while (stop.line > 0 && vimState.document.lineAt(stop).isEmptyOrWhitespace) {\n        stop = stop.getUp().getLineEnd();\n      }\n    }\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\n@RegisterAction\nexport class SelectEntire extends TextObject {\n  keys = ['a', 'e'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    const range = TextEditor.getDocumentRange(vimState.document);\n    return { start: range.start, stop: range.end };\n  }\n}\n\n@RegisterAction\nexport class SelectEntireIgnoringLeadingTrailing extends TextObject {\n  keys = ['i', 'e'];\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    let start: Position = TextEditor.getDocumentBegin();\n    let stop: Position = TextEditor.getDocumentEnd(vimState.document);\n\n    while (start.line < stop.line && vimState.document.lineAt(start).isEmptyOrWhitespace) {\n      start = start.getDown();\n    }\n\n    while (stop.line > start.line && vimState.document.lineAt(stop).isEmptyOrWhitespace) {\n      stop = stop.getUp();\n    }\n    stop = stop.getLineEnd();\n\n    return {\n      start,\n      stop,\n    };\n  }\n}\n\nabstract class IndentObjectMatch extends TextObject {\n  protected includeLineAbove = false;\n  protected includeLineBelow = false;\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    const isChangeOperator = vimState.recordedState.operator instanceof ChangeOperator;\n    const firstValidLineNumber = IndentObjectMatch.findFirstValidLine(vimState.document, position);\n    const firstValidLine = vimState.document.lineAt(firstValidLineNumber);\n    const cursorIndent = firstValidLine.firstNonWhitespaceCharacterIndex;\n\n    let startLineNumber = IndentObjectMatch.findRangeStartOrEnd(\n      vimState.document,\n      firstValidLineNumber,\n      cursorIndent,\n      -1,\n    );\n    let endLineNumber = IndentObjectMatch.findRangeStartOrEnd(\n      vimState.document,\n      firstValidLineNumber,\n      cursorIndent,\n      1,\n    );\n\n    // Adjust the start line as needed.\n    if (this.includeLineAbove) {\n      startLineNumber -= 1;\n    }\n    // Check for OOB.\n    if (startLineNumber < 0) {\n      startLineNumber = 0;\n    }\n\n    // Adjust the end line as needed.\n    if (this.includeLineBelow) {\n      endLineNumber += 1;\n    }\n    // Check for OOB.\n    if (endLineNumber > vimState.document.lineCount - 1) {\n      endLineNumber = vimState.document.lineCount - 1;\n    }\n\n    // If initiated by a change operation, adjust the cursor to the indent level\n    // of the block.\n    let startCharacter = 0;\n    if (isChangeOperator) {\n      startCharacter = vimState.document.lineAt(startLineNumber).firstNonWhitespaceCharacterIndex;\n    }\n    // TextEditor.getLineMaxColumn throws when given line 0, which we don't\n    // care about here since it just means this text object wouldn't work on a\n    // single-line document.\n    let endCharacter: number;\n    if (\n      endLineNumber === vimState.document.lineCount - 1 ||\n      vimState.currentMode === Mode.Visual ||\n      vimState.currentMode === Mode.VisualLine\n    ) {\n      endCharacter = TextEditor.getLineLength(endLineNumber);\n    } else {\n      endCharacter = 0;\n      endLineNumber++;\n    }\n    return {\n      start: new Position(startLineNumber, startCharacter),\n      stop: new Position(endLineNumber, endCharacter),\n    };\n  }\n\n  public override async execActionForOperator(\n    position: Position,\n    vimState: VimState,\n  ): Promise<IMovement> {\n    return this.execAction(position, vimState);\n  }\n\n  /**\n   * Searches up from the cursor for the first non-empty line.\n   */\n  public static findFirstValidLine(document: TextDocument, cursorPosition: Position): number {\n    for (let i = cursorPosition.line; i >= 0; i--) {\n      if (!document.lineAt(i).isEmptyOrWhitespace) {\n        return i;\n      }\n    }\n\n    return cursorPosition.line;\n  }\n\n  /**\n   * Searches up or down from a line finding the first with a lower indent level.\n   */\n  public static findRangeStartOrEnd(\n    document: TextDocument,\n    startIndex: number,\n    cursorIndent: number,\n    step: -1 | 1,\n  ): number {\n    let i = startIndex;\n    let ret = startIndex;\n    const end = step === 1 ? document.lineCount : -1;\n\n    for (; i !== end; i += step) {\n      const line = document.lineAt(i);\n      if (line.firstNonWhitespaceCharacterIndex < cursorIndent && !line.isEmptyOrWhitespace) {\n        break;\n      }\n\n      ret = i;\n    }\n\n    return ret;\n  }\n}\n\n@RegisterAction\nclass InsideIndentObject extends IndentObjectMatch {\n  keys = ['i', 'i'];\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n}\n\n@RegisterAction\nclass InsideIndentObjectAbove extends IndentObjectMatch {\n  keys = ['a', 'i'];\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override includeLineAbove = true;\n}\n\n@RegisterAction\nclass InsideIndentObjectBoth extends IndentObjectMatch {\n  keys = ['a', 'I'];\n  override modes = [Mode.Normal, Mode.Visual, Mode.VisualLine, Mode.VisualBlock];\n  override includeLineAbove = true;\n  override includeLineBelow = true;\n}\n\nabstract class SelectArgument extends TextObject {\n  override modes = [Mode.Normal, Mode.Visual];\n\n  private static openingDelimiterCharacters(): string[] {\n    return configuration.argumentObjectOpeningDelimiters;\n  }\n  private static closingDelimiterCharacters(): string[] {\n    return configuration.argumentObjectClosingDelimiters;\n  }\n  private static separatorCharacters(): string[] {\n    return configuration.argumentObjectSeparators;\n  }\n\n  // SelectArgument supports two select types: inner and around.\n  //\n  // Inner will adjust start/stop positions, so that they are inside\n  // the delimiters (excluding the delimiters themselves).\n  // Around will adjust start/stop positions, so that ONE of them includes\n  // a separator character (optionally including extra whitespace).\n  protected selectAround = false;\n\n  // Requirement is that below example still works as expected, i.e.\n  // when we have nested pairs of parens\n  //\n  //        ( a, b, (void*) | c(void*, void*), a)\n  //\n  // Warning: For now, mismatched opening and closing delimiters, e.g.\n  // in (foo] will still be matched by this movement.\n  //\n  // Procedure:\n  //\n  // 1   Find boundaries left/right (i.e. where the argument starts/ends)\n  // 1.1 Walk left until we find a comma or an opening paren, that does not\n  //     have a matching closed one. This way we can ignore pairs\n  //     of parentheses which are part of the current argument.\n  // 1.2 Vice versa for walking right.\n  // 2   Depending on our mode (inner or around), improve the start/stop\n  //     locations for most consistent behaviour, especially in case of\n  //     multi-line statements.\n\n  public async execAction(position: Position, vimState: VimState): Promise<IMovement> {\n    const failure = failedMovement(vimState);\n\n    let leftSearchStartPosition = position;\n    let rightSearchStartPosition = position;\n\n    const charAtPos = TextEditor.getCharAt(vimState.document, position);\n\n    // When the cursor is on a delimiter already, pre-advance the cursor,\n    // so that our search actually spans a range. We will advance to the next argument,\n    // in case of opening delimiters or separators, and advance to the\n    // previous on closing delimiters.\n    if (\n      SelectArgument.separatorCharacters().includes(charAtPos) ||\n      SelectArgument.openingDelimiterCharacters().includes(charAtPos)\n    ) {\n      rightSearchStartPosition = position.getRightThroughLineBreaks(true);\n    } else if (SelectArgument.closingDelimiterCharacters().includes(charAtPos)) {\n      leftSearchStartPosition = position.getLeftThroughLineBreaks(true);\n    }\n\n    // Early abort, if no delimiters (i.e. (), [], etc.) surround us.\n    // This prevents applying the movement to surrounding separators across the buffer.\n    if (\n      SelectInnerArgument.findLeftArgumentBoundary(\n        vimState.document,\n        leftSearchStartPosition,\n        true,\n      ) === undefined ||\n      SelectInnerArgument.findRightArgumentBoundary(\n        vimState.document,\n        rightSearchStartPosition,\n        true,\n      ) === undefined\n    ) {\n      return failure;\n    }\n\n    const leftArgumentBoundary = SelectInnerArgument.findLeftArgumentBoundary(\n      vimState.document,\n      leftSearchStartPosition,\n    );\n    if (leftArgumentBoundary === undefined) {\n      return failure;\n    }\n\n    const rightArgumentBoundary = SelectInnerArgument.findRightArgumentBoundary(\n      vimState.document,\n      rightSearchStartPosition,\n    );\n    if (rightArgumentBoundary === undefined) {\n      return failure;\n    }\n\n    let start: Position;\n    let stop: Position;\n\n    if (this.selectAround) {\n      const isLeftOnOpening: boolean = SelectArgument.openingDelimiterCharacters().includes(\n        TextEditor.getCharAt(vimState.document, leftArgumentBoundary),\n      );\n      const isRightOnClosing: boolean = SelectArgument.closingDelimiterCharacters().includes(\n        TextEditor.getCharAt(vimState.document, rightArgumentBoundary),\n      );\n\n      // Edge-case:\n      // Ensure we do not select anything if we have an empty argument list, e.g. \"()\"\n      const isEmptyArgumentList =\n        leftArgumentBoundary.getRight().isEqual(rightArgumentBoundary) &&\n        isLeftOnOpening &&\n        isRightOnClosing;\n      if (isEmptyArgumentList) {\n        return failure;\n      }\n\n      // Only when we are in the first argument we outset the right boundary\n      // until the first non-whitespace, so we do not end up with whitespace\n      // at the beginning of the parens.\n      const isInFirstArgument = isLeftOnOpening && !isRightOnClosing;\n      if (isInFirstArgument) {\n        stop = rightArgumentBoundary.getRight();\n        // Walk right until non-whitespace\n        while (/\\s/.test(TextEditor.getCharAt(vimState.document, stop.getRight()))) {\n          stop = stop.getRight();\n        }\n      } else {\n        // In any other case, we inset\n        stop = rightArgumentBoundary.getLeftThroughLineBreaks(true);\n      }\n\n      // In case the left boundary is on a opening delimiter, move that position inwards\n      if (isLeftOnOpening) {\n        start = leftArgumentBoundary.getRightThroughLineBreaks(true);\n      } else {\n        start = leftArgumentBoundary;\n      }\n    } else {\n      // Inset the start once to get off the boundary and then keep\n      // going until the first non whitespace.\n      // This ensures that indented argument-lists keep the indentation.\n      start = leftArgumentBoundary.getRightThroughLineBreaks(false);\n      while (/\\s/.test(TextEditor.getCharAt(vimState.document, start))) {\n        start = start.getRightThroughLineBreaks(false);\n      }\n\n      // Same procedure for stop.\n      stop = rightArgumentBoundary.getLeftThroughLineBreaks(false);\n      while (/\\s/.test(TextEditor.getCharAt(vimState.document, stop))) {\n        stop = stop.getLeftThroughLineBreaks(false);\n      }\n\n      // Edge-case: Seems there is only whitespace in this argument.\n      // Omit any weird handling and just clear all whitespace.\n      if (stop.isBeforeOrEqual(start)) {\n        start = leftArgumentBoundary.getRightThroughLineBreaks(true);\n        stop = rightArgumentBoundary.getLeftThroughLineBreaks(true);\n      }\n    }\n\n    // Handle case when cursor is not inside the anticipated movement range\n    if (position.isBefore(start)) {\n      vimState.recordedState.operatorPositionDiff = start.subtract(position);\n    }\n    vimState.cursorStartPosition = start;\n\n    return {\n      start,\n      stop,\n    };\n  }\n\n  private static findLeftArgumentBoundary(\n    document: TextDocument,\n    position: Position,\n    ignoreSeparators: boolean = false,\n  ): Position | undefined {\n    let delimiterPosition: Position | undefined;\n    let walkingPosition = position;\n    let closedParensCount = 0;\n\n    while (true) {\n      const char = TextEditor.getCharAt(document, walkingPosition);\n      if (closedParensCount === 0) {\n        let isOnBoundary: boolean = SelectArgument.openingDelimiterCharacters().includes(char);\n        if (!ignoreSeparators) {\n          isOnBoundary ||= SelectArgument.separatorCharacters().includes(char);\n        }\n\n        if (isOnBoundary) {\n          // We have found the left most delimiter or the first proper delimiter\n          // in our cursor's list 'depth' and thus can abort.\n          delimiterPosition = walkingPosition;\n          break;\n        }\n      }\n      if (SelectArgument.closingDelimiterCharacters().includes(char)) {\n        closedParensCount++;\n      }\n      if (SelectArgument.openingDelimiterCharacters().includes(char)) {\n        closedParensCount--;\n      }\n\n      if (walkingPosition.isAtDocumentBegin()) {\n        break;\n      }\n\n      walkingPosition = walkingPosition.getLeftThroughLineBreaks(true);\n    }\n\n    return delimiterPosition;\n  }\n\n  private static findRightArgumentBoundary(\n    document: TextDocument,\n    position: Position,\n    ignoreSeparators: boolean = false,\n  ): Position | undefined {\n    let delimiterPosition: Position | undefined;\n    let walkingPosition = position;\n    let openedParensCount = 0;\n\n    while (true) {\n      const char = TextEditor.getCharAt(document, walkingPosition);\n      if (openedParensCount === 0) {\n        let isOnBoundary: boolean = SelectArgument.closingDelimiterCharacters().includes(char);\n        if (!ignoreSeparators) {\n          isOnBoundary ||= SelectArgument.separatorCharacters().includes(char);\n        }\n\n        if (isOnBoundary) {\n          delimiterPosition = walkingPosition;\n          break;\n        }\n      }\n      if (SelectArgument.openingDelimiterCharacters().includes(char)) {\n        openedParensCount++;\n      }\n      if (SelectArgument.closingDelimiterCharacters().includes(char)) {\n        openedParensCount--;\n      }\n\n      if (walkingPosition.isAtDocumentEnd(document)) {\n        break;\n      }\n\n      // We need to include the EOL so that isAtDocumentEnd actually\n      // becomes true.\n      walkingPosition = walkingPosition.getRightThroughLineBreaks(true);\n    }\n\n    return delimiterPosition;\n  }\n}\n\n@RegisterAction\nexport class SelectInnerArgument extends SelectArgument {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['i', 'a'];\n}\n\n@RegisterAction\nexport class SelectAroundArgument extends SelectArgument {\n  override modes = [Mode.Normal, Mode.Visual];\n  keys = ['a', 'a'];\n  override selectAround = true;\n}\n"
  },
  {
    "path": "src/textobject/util.ts",
    "content": "export function getAllPositions(line: string, regex: RegExp): number[] {\n  const positions: number[] = [];\n  let result = regex.exec(line);\n\n  while (result) {\n    positions.push(result.index);\n\n    // Handles the case where an empty string match causes lastIndex not to advance,\n    // which gets us in an infinite loop.\n    if (result.index === regex.lastIndex) {\n      regex.lastIndex++;\n    }\n    result = regex.exec(line);\n  }\n\n  return positions;\n}\n\nexport function getAllEndPositions(line: string, regex: RegExp): number[] {\n  const positions: number[] = [];\n  let result = regex.exec(line);\n\n  while (result) {\n    if (result[0].length) {\n      positions.push(result.index + result[0].length - 1);\n    }\n\n    // Handles the case where an empty string match causes lastIndex not to advance,\n    // which gets us in an infinite loop.\n    if (result.index === regex.lastIndex) {\n      regex.lastIndex++;\n    }\n    result = regex.exec(line);\n  }\n\n  return positions;\n}\n"
  },
  {
    "path": "src/textobject/word.ts",
    "content": "import * as _ from 'lodash';\nimport { Position, TextDocument } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { getAllEndPositions, getAllPositions } from './util';\n\nexport enum WordType {\n  Normal,\n  Big,\n  CamelCase,\n  FileName,\n  TagName,\n}\n\nconst nonBigWordCharRegex = makeWordRegex('');\nconst nonFileNameRegex = makeWordRegex('\"\\'`;<>{}[]()');\nconst nonTagNameRegex = makeWordRegex('</>');\n\nfunction regexForWordType(wordType: WordType): RegExp {\n  switch (wordType) {\n    case WordType.Normal:\n      return makeUnicodeWordRegex(configuration.iskeyword);\n    case WordType.Big:\n      return nonBigWordCharRegex;\n    case WordType.CamelCase:\n      return makeCamelCaseWordRegex(configuration.iskeyword);\n    case WordType.FileName:\n      return nonFileNameRegex;\n    case WordType.TagName:\n      return nonTagNameRegex;\n  }\n}\n\n/**\n * Get the position of the word counting from the position specified.\n * @param text The string to search from.\n * @param pos The position of text to search from.\n * @returns The character position of the word to the left relative to the text and the pos.\n *          undefined if there is no word to the left of the postion.\n */\nexport function getWordLeftInText(\n  text: string,\n  pos: number,\n  wordType: WordType,\n): number | undefined {\n  return getWordLeftOnLine(text, pos, wordType);\n}\n\nexport function getWordRightInText(\n  text: string,\n  pos: number,\n  wordType: WordType,\n): number | undefined {\n  return getAllPositions(text, regexForWordType(wordType)).find((index) => index > pos);\n}\n\nexport function prevWordStart(\n  document: TextDocument,\n  pos: Position,\n  wordType: WordType,\n  inclusive: boolean = false,\n): Position {\n  for (let currentLine = pos.line; currentLine >= 0; currentLine--) {\n    const newCharacter = getWordLeftOnLine(\n      document.lineAt(currentLine).text,\n      pos.character,\n      wordType,\n      currentLine !== pos.line,\n      inclusive,\n    );\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter);\n    }\n  }\n\n  return new Position(0, 0);\n}\n\nfunction getWordLeftOnLine(\n  text: string,\n  pos: number,\n  wordType: WordType,\n  forceFirst: boolean = false,\n  inclusive: boolean = false,\n): number | undefined {\n  return getAllPositions(text, regexForWordType(wordType))\n    .reverse()\n    .find((index) => (index < pos && !inclusive) || (index <= pos && inclusive) || forceFirst);\n}\n\nexport function nextWordStart(\n  document: TextDocument,\n  pos: Position,\n  wordType: WordType,\n  inclusive: boolean = false,\n): Position {\n  for (let currentLine = pos.line; currentLine < document.lineCount; currentLine++) {\n    const positions = getAllPositions(\n      document.lineAt(currentLine).text,\n      regexForWordType(wordType),\n    );\n    const newCharacter = positions.find(\n      (index) =>\n        (index > pos.character && !inclusive) ||\n        (index >= pos.character && inclusive) ||\n        currentLine !== pos.line,\n    );\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter);\n    }\n  }\n\n  return new Position(document.lineCount - 1, 0).getLineEnd();\n}\n\nexport function nextWordEnd(\n  document: TextDocument,\n  pos: Position,\n  wordType: WordType,\n  inclusive: boolean = false,\n): Position {\n  for (let currentLine = pos.line; currentLine < document.lineCount; currentLine++) {\n    const positions = getAllEndPositions(\n      document.lineAt(currentLine).text,\n      regexForWordType(wordType),\n    );\n    const newCharacter = positions.find(\n      (index) =>\n        (index > pos.character && !inclusive) ||\n        (index >= pos.character && inclusive) ||\n        currentLine !== pos.line,\n    );\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter);\n    }\n  }\n\n  return new Position(document.lineCount - 1, 0).getLineEnd();\n}\n\nexport function prevWordEnd(document: TextDocument, pos: Position, wordType: WordType): Position {\n  for (let currentLine = pos.line; currentLine > -1; currentLine--) {\n    let positions = getAllEndPositions(\n      document.lineAt(currentLine).text,\n      regexForWordType(wordType),\n    );\n    // if one line is empty, use the 0 position as the default value\n    if (positions.length === 0) {\n      positions.push(0);\n    }\n    // reverse the list to find the biggest element smaller than pos.character\n    positions = positions.reverse();\n    const index = positions.findIndex((i) => i < pos.character || currentLine !== pos.line);\n    let newCharacter = 0;\n    if (index === -1) {\n      if (currentLine > -1) {\n        continue;\n      }\n      newCharacter = positions.at(-1)!;\n    } else {\n      newCharacter = positions[index];\n    }\n\n    if (newCharacter !== undefined) {\n      return new Position(currentLine, newCharacter);\n    }\n  }\n\n  return new Position(0, 0);\n}\n\nfunction makeWordRegex(characterSet: string): RegExp {\n  const escaped = characterSet && _.escapeRegExp(characterSet).replace(/-/g, '\\\\-');\n  const segments = [`([^\\\\s${escaped}]+)`, `[${escaped}]+`, `$^`];\n\n  return new RegExp(segments.join('|'), 'g');\n}\n\nfunction makeCamelCaseWordRegex(characterSet: string): RegExp {\n  const escaped = characterSet && _.escapeRegExp(characterSet).replace(/-/g, '\\\\-');\n  const segments: string[] = [];\n\n  // Older browsers don't support lookbehind - in this case, use an inferior regex rather than crashing\n  let supportsLookbehind = true;\n  try {\n    new RegExp('(?<=x)');\n  } catch {\n    supportsLookbehind = false;\n  }\n\n  // prettier-ignore\n  const firstSegment =\n      '(' +                                             // OPEN: group for matching camel case words\n      `[^\\\\s${escaped}_]` +                             //   words can start with any non-keyword non-underscore character\n      '(?:' +                                           //   OPEN: group for characters after initial char\n      `(?:${supportsLookbehind ? '(?<=[A-Z_])' : ''}` + //     If first char was a capital\n      `[A-Z](?=[\\\\sA-Z0-9${escaped}_]))+` +             //       the word can continue with all caps\n      '|' +                                             //     OR\n      `(?:${supportsLookbehind ? '(?<=[0-9_])' : ''}` + //     If first char was a digit\n      `[0-9](?=[\\\\sA-Z0-9${escaped}_]))+` +             //       the word can continue with all digits\n      '|' +                                             //     OR\n      `(?:${supportsLookbehind ? '(?<=[_])' : ''}` +    //     If first char was an underscore\n      `[_](?=[\\\\s${escaped}_]))+`  +                    //       the word can continue with all underscores\n      '|' +                                             //     OR\n      `[^\\\\sA-Z0-9${escaped}_]*` +                      //     Continue with regular characters\n      ')' +                                             //   END: group for characters after initial char\n      ')' +                                             // END: group for matching camel case words\n      '';\n\n  segments.push(firstSegment);\n  segments.push(`[${escaped}]+`);\n  segments.push(`$^`);\n\n  // it can be difficult to grok the behavior of the above regex\n  // feel free to check out https://regex101.com/r/mkVeiH/1 as a live example\n  return new RegExp(segments.join('|'), 'g');\n}\n\nfunction makeUnicodeWordRegex(keywordChars: string): RegExp {\n  // Distinct categories of characters\n  enum CharKind {\n    Punctuation,\n    Superscript,\n    Subscript,\n    Braille,\n    Ideograph,\n    Hiragana,\n    Katakana,\n    Hangul,\n  }\n\n  // List of printable characters (code point intervals) and their character kinds.\n  // Latin alphabets (e.g., ASCII alphabets and numbers,  Latin-1 Supplement, European Latin) are excluded.\n  // Imported from utf_class_buf in src/mbyte.c of Vim.\n  const symbolTable: Array<[[number, number], CharKind]> = [\n    [[0x00a1, 0x00bf], CharKind.Punctuation], // Latin-1 punctuation\n    [[0x037e, 0x037e], CharKind.Punctuation], // Greek question mark\n    [[0x0387, 0x0387], CharKind.Punctuation], // Greek ano teleia\n    [[0x055a, 0x055f], CharKind.Punctuation], // Armenian punctuation\n    [[0x0589, 0x0589], CharKind.Punctuation], // Armenian full stop\n    [[0x05be, 0x05be], CharKind.Punctuation],\n    [[0x05c0, 0x05c0], CharKind.Punctuation],\n    [[0x05c3, 0x05c3], CharKind.Punctuation],\n    [[0x05f3, 0x05f4], CharKind.Punctuation],\n    [[0x060c, 0x060c], CharKind.Punctuation],\n    [[0x061b, 0x061b], CharKind.Punctuation],\n    [[0x061f, 0x061f], CharKind.Punctuation],\n    [[0x066a, 0x066d], CharKind.Punctuation],\n    [[0x06d4, 0x06d4], CharKind.Punctuation],\n    [[0x0700, 0x070d], CharKind.Punctuation], // Syriac punctuation\n    [[0x0964, 0x0965], CharKind.Punctuation],\n    [[0x0970, 0x0970], CharKind.Punctuation],\n    [[0x0df4, 0x0df4], CharKind.Punctuation],\n    [[0x0e4f, 0x0e4f], CharKind.Punctuation],\n    [[0x0e5a, 0x0e5b], CharKind.Punctuation],\n    [[0x0f04, 0x0f12], CharKind.Punctuation],\n    [[0x0f3a, 0x0f3d], CharKind.Punctuation],\n    [[0x0f85, 0x0f85], CharKind.Punctuation],\n    [[0x104a, 0x104f], CharKind.Punctuation], // Myanmar punctuation\n    [[0x10fb, 0x10fb], CharKind.Punctuation], // Georgian punctuation\n    [[0x1361, 0x1368], CharKind.Punctuation], // Ethiopic punctuation\n    [[0x166d, 0x166e], CharKind.Punctuation], // Canadian Syl. punctuation\n    [[0x169b, 0x169c], CharKind.Punctuation],\n    [[0x16eb, 0x16ed], CharKind.Punctuation],\n    [[0x1735, 0x1736], CharKind.Punctuation],\n    [[0x17d4, 0x17dc], CharKind.Punctuation], // Khmer punctuation\n    [[0x1800, 0x180a], CharKind.Punctuation], // Mongolian punctuation\n    [[0x200c, 0x2027], CharKind.Punctuation], // punctuation and symbols\n    [[0x202a, 0x202e], CharKind.Punctuation], // punctuation and symbols\n    [[0x2030, 0x205e], CharKind.Punctuation], // punctuation and symbols\n    [[0x2060, 0x27ff], CharKind.Punctuation], // punctuation and symbols\n    [[0x2070, 0x207f], CharKind.Superscript], // superscript\n    [[0x2080, 0x2094], CharKind.Subscript], // subscript\n    [[0x20a0, 0x27ff], CharKind.Punctuation], // all kinds of symbols\n    [[0x2800, 0x28ff], CharKind.Braille], // braille\n    [[0x2900, 0x2998], CharKind.Punctuation], // arrows, brackets, etc.\n    [[0x29d8, 0x29db], CharKind.Punctuation],\n    [[0x29fc, 0x29fd], CharKind.Punctuation],\n    [[0x2e00, 0x2e7f], CharKind.Punctuation], // supplemental punctuation\n    [[0x3001, 0x3020], CharKind.Punctuation], // ideographic punctuation\n    [[0x3030, 0x3030], CharKind.Punctuation],\n    [[0x303d, 0x303d], CharKind.Punctuation],\n    [[0x3040, 0x309f], CharKind.Hiragana], // Hiragana\n    [[0x30a0, 0x30ff], CharKind.Katakana], // Katakana\n    [[0x3300, 0x9fff], CharKind.Ideograph], // CJK Ideographs\n    [[0xac00, 0xd7a3], CharKind.Hangul], // Hangul Syllables\n    [[0xf900, 0xfaff], CharKind.Ideograph], // CJK Ideographs\n    [[0xfd3e, 0xfd3f], CharKind.Punctuation],\n    [[0xfe30, 0xfe6b], CharKind.Punctuation], // punctuation forms\n    [[0xff00, 0xff0f], CharKind.Punctuation], // half/fullwidth ASCII\n    [[0xff1a, 0xff20], CharKind.Punctuation], // half/fullwidth ASCII\n    [[0xff3b, 0xff40], CharKind.Punctuation], // half/fullwidth ASCII\n    [[0xff5b, 0xff65], CharKind.Punctuation], // half/fullwidth ASCII\n    [[0x20000, 0x2a6df], CharKind.Ideograph], // CJK Ideographs\n    [[0x2a700, 0x2b73f], CharKind.Ideograph], // CJK Ideographs\n    [[0x2b740, 0x2b81f], CharKind.Ideograph], // CJK Ideographs\n    [[0x2f800, 0x2fa1f], CharKind.Ideograph], // CJK Ideographs\n  ];\n\n  const codePointRangePatterns: string[][] = [];\n  for (const kind in CharKind) {\n    if (!isNaN(Number(kind))) {\n      codePointRangePatterns[kind] = [];\n    }\n  }\n\n  for (const [[first, last], kind] of symbolTable) {\n    if (first === last) {\n      // '\\u{hhhh}'\n      codePointRangePatterns[kind].push(`\\\\u{${first.toString(16)}}`);\n    } else {\n      // '\\u{hhhh}-\\u{hhhh}'\n      codePointRangePatterns[kind].push(`\\\\u{${first.toString(16)}}-\\\\u{${last.toString(16)}}`);\n    }\n  }\n\n  // Symbols in vim.iskeyword or editor.wordSeparators\n  // are treated as CharKind.Punctuation\n  const escapedKeywordChars = _.escapeRegExp(keywordChars).replace(/-/g, '\\\\-');\n  codePointRangePatterns[Number(CharKind.Punctuation)].push(escapedKeywordChars);\n\n  const codePointRanges = codePointRangePatterns.map((patterns) => patterns.join(''));\n  const symbolSegments = codePointRanges.map((range) => `([${range}]+)`);\n\n  // wordSegment matches word characters.\n  // A word character is a symbol which is neither\n  // - space\n  // - a symbol listed in the table\n  // - a keyword (vim.iskeyword)\n  const wordSegment = `([^\\\\s${codePointRanges.join('')}]+)`;\n\n  // https://regex101.com/r/X1agK6/2\n  const segments = symbolSegments.concat(wordSegment, '$^');\n  return new RegExp(segments.join('|'), 'ug');\n}\n"
  },
  {
    "path": "src/transformations/execute.ts",
    "content": "import * as vscode from 'vscode';\nimport { ExCommandLine } from '../cmd_line/commandLine';\nimport { Cursor } from '../common/motion/cursor';\nimport { PositionDiff } from '../common/motion/position';\nimport { Globals } from '../globals';\nimport { Mode, NormalCommandState } from '../mode/mode';\nimport { Register } from '../register/register';\nimport { globalState } from '../state/globalState';\nimport { RecordedState } from '../state/recordedState';\nimport { VimState } from '../state/vimState';\nimport { TextEditor } from '../textEditor';\nimport { Logger } from '../util/logger';\nimport {\n  keystrokesExpressionForMacroParser,\n  keystrokesExpressionParser,\n} from '../vimscript/parserUtils';\nimport {\n  Dot,\n  ExecuteNormalTransformation,\n  InsertTextVSCodeTransformation,\n  TextTransformations,\n  Transformation,\n  areAllSameTransformation,\n  isMultiCursorTextTransformation,\n  isTextTransformation,\n  overlappingTransformations,\n} from './transformations';\nimport { Transformer } from './transformer';\n\nexport interface IModeHandler {\n  vimState: VimState;\n  lastMovementFailed: boolean;\n\n  updateView(args?: { drawSelection: boolean; revealRange: boolean }): void;\n  runMacro(recordedMacro: RecordedState): Promise<void>;\n  handleMultipleKeyEvents(keys: string[]): Promise<void>;\n  handleKeyEvent(key: string): Promise<void>;\n  rerunRecordedState(transformation: Dot): Promise<void>;\n}\n\nexport async function executeTransformations(\n  modeHandler: IModeHandler,\n  transformations: Transformation[],\n) {\n  if (transformations.length === 0) {\n    return;\n  }\n\n  const vimState = modeHandler.vimState;\n\n  const textTransformations = transformations.filter((x): x is TextTransformations =>\n    isTextTransformation(x),\n  );\n  const multicursorTextTransformations: InsertTextVSCodeTransformation[] = transformations.filter(\n    (x): x is InsertTextVSCodeTransformation => isMultiCursorTextTransformation(x),\n  );\n\n  const otherTransformations = transformations.filter(\n    (x) => !isTextTransformation(x) && !isMultiCursorTextTransformation(x),\n  );\n\n  const accumulatedPositionDifferences: { [key: number]: PositionDiff[] } = {};\n\n  const doTextEditorEdit = (command: TextTransformations, edit: vscode.TextEditorEdit) => {\n    switch (command.type) {\n      case 'insertText':\n        edit.insert(command.position, command.text);\n        break;\n      case 'replaceText':\n        edit.replace(command.range, command.text);\n        break;\n      case 'deleteRange':\n        edit.delete(command.range);\n        break;\n      case 'moveCursor':\n        break;\n      default:\n        Logger.warn(`Unhandled text transformation type: ${command.type}.`);\n        break;\n    }\n\n    if (command.diff) {\n      if (command.cursorIndex === undefined) {\n        throw new Error('No cursor index - this should never ever happen!');\n      }\n\n      if (!accumulatedPositionDifferences[command.cursorIndex]) {\n        accumulatedPositionDifferences[command.cursorIndex] = [];\n      }\n\n      accumulatedPositionDifferences[command.cursorIndex].push(command.diff);\n    }\n  };\n\n  if (textTransformations.length > 0) {\n    const overlapping = overlappingTransformations(textTransformations);\n    if (overlapping !== undefined) {\n      const msg = `Transformations overlapping: ${JSON.stringify(overlapping)}`;\n      Logger.warn(msg);\n      if (Globals.isTesting) {\n        throw new Error(msg);\n      }\n\n      // TODO: Select one transformation for every cursor and run them all\n      // in parallel. Repeat till there are no more transformations.\n      for (const transformation of textTransformations) {\n        await vimState.editor.edit((edit) => doTextEditorEdit(transformation, edit));\n      }\n    } else {\n      // This is the common case!\n\n      /**\n       * batch all text operations together as a single operation\n       * (this is primarily necessary for multi-cursor mode, since most\n       * actions will trigger at most one text operation).\n       */\n      try {\n        await vimState.editor.edit((edit) => {\n          for (const command of textTransformations) {\n            doTextEditorEdit(command, edit);\n          }\n        });\n      } catch (e) {\n        // Messages like \"TextEditor(vs.editor.ICodeEditor:1,$model8) has been disposed\" can be ignored.\n        // They occur when the user switches to a new tab while an action is running.\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        if (e.name !== 'DISPOSED') {\n          throw e;\n        }\n      }\n    }\n  }\n\n  if (multicursorTextTransformations.length > 0) {\n    if (areAllSameTransformation(multicursorTextTransformations)) {\n      /**\n       * Apply the transformation only once instead of to each cursor\n       * if they are all the same.\n       *\n       * This lets VSCode do multicursor snippets, auto braces and\n       * all the usual jazz VSCode does on text insertion.\n       */\n      const { text } = multicursorTextTransformations[0];\n\n      // await vscode.commands.executeCommand('default:type', { text });\n      await TextEditor.insert(vimState.editor, text);\n    } else {\n      Logger.warn(`Unhandled multicursor transformations. Not all transformations are the same!`);\n    }\n  }\n\n  for (const transformation of otherTransformations) {\n    switch (transformation.type) {\n      case 'insertTextVSCode':\n        await TextEditor.insert(vimState.editor, transformation.text);\n        vimState.cursor = Cursor.fromSelection(vimState.editor.selection);\n        break;\n\n      case 'replayRecordedState':\n        await modeHandler.rerunRecordedState(transformation);\n        break;\n\n      case 'macro':\n        const recordedMacro = (await Register.get(transformation.register))?.text;\n        if (!recordedMacro) {\n          return;\n        } else if (typeof recordedMacro === 'string') {\n          // A string was set to the register. We need to execute the characters as if they were typed (in normal mode).\n          const keystrokes = keystrokesExpressionForMacroParser.parse(recordedMacro);\n          if (!keystrokes.status) {\n            throw new Error(`Failed to execute macro: ${recordedMacro}`);\n          }\n\n          vimState.isReplayingMacro = true;\n\n          vimState.recordedState = new RecordedState();\n          await modeHandler.handleMultipleKeyEvents(keystrokes.value);\n\n          // Set the executed register as the registerName, otherwise the last action register is used.\n          vimState.recordedState.registerName = transformation.register;\n\n          globalState.lastInvokedMacro = vimState.recordedState;\n          vimState.isReplayingMacro = false;\n\n          if (modeHandler.lastMovementFailed) {\n            // movement in last invoked macro failed then we should stop all following repeating macros.\n            // Besides, we should reset `lastMovementFailed`.\n            modeHandler.lastMovementFailed = false;\n            return;\n          }\n        } else {\n          vimState.isReplayingMacro = true;\n\n          vimState.recordedState = new RecordedState();\n          if (transformation.register === ':') {\n            await new ExCommandLine(recordedMacro.commandString, vimState.currentMode).run(\n              vimState,\n            );\n          } else if (transformation.replay === 'contentChange') {\n            await modeHandler.runMacro(recordedMacro);\n          } else {\n            let keyStrokes: string[] = [];\n            for (const action of recordedMacro.actionsRun) {\n              keyStrokes = keyStrokes.concat(action.keysPressed);\n            }\n            await modeHandler.handleMultipleKeyEvents(keyStrokes);\n          }\n\n          // TODO: Copied from `BaseAction.execCount`. This is all terrible.\n          for (const t of vimState.recordedState.transformer.transformations) {\n            if (isTextTransformation(t) && t.cursorIndex === undefined) {\n              t.cursorIndex = 0;\n            }\n          }\n\n          await executeTransformations(\n            modeHandler,\n            vimState.recordedState.transformer.transformations,\n          );\n\n          globalState.lastInvokedMacro = recordedMacro;\n          vimState.isReplayingMacro = false;\n\n          if (modeHandler.lastMovementFailed) {\n            // movement in last invoked macro failed then we should stop all following repeating macros.\n            // Besides, we should reset `lastMovementFailed`.\n            modeHandler.lastMovementFailed = false;\n            return;\n          }\n        }\n        break;\n\n      case 'contentChange':\n        for (const change of transformation.changes) {\n          await TextEditor.insert(vimState.editor, change.text);\n          vimState.cursorStopPosition = vimState.editor.selection.start;\n        }\n        const newPos = vimState.cursorStopPosition.add(vimState.document, transformation.diff);\n        vimState.editor.selection = new vscode.Selection(newPos, newPos);\n        break;\n\n      case 'executeNormal':\n        await doExecuteNormal(modeHandler, transformation);\n        break;\n\n      case 'vscodeCommand':\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n        await vscode.commands.executeCommand(transformation.command, ...transformation.args);\n        break;\n\n      default:\n        Logger.warn(`Unhandled text transformation type: ${transformation.type}.`);\n        break;\n    }\n  }\n\n  let selections;\n  if (vimState.currentMode === Mode.Insert) {\n    // Insert mode selections do not need to be modified\n    selections = vimState.editor.selections;\n  } else {\n    selections = vimState.editor.selections.map((sel) => {\n      let range = Cursor.fromSelection(sel);\n      if (range.start.isBefore(range.stop)) {\n        range = range.withNewStop(range.stop.getLeftThroughLineBreaks(true));\n      }\n      return new vscode.Selection(range.start, range.stop);\n    });\n  }\n  const firstTransformation = transformations[0];\n  const manuallySetCursorPositions =\n    (firstTransformation.type === 'deleteRange' ||\n      firstTransformation.type === 'replaceText' ||\n      firstTransformation.type === 'insertText') &&\n    firstTransformation.manuallySetCursorPositions;\n\n  // We handle multiple cursors in a different way in visual block mode, unfortunately.\n  // TODO - refactor that out!\n  if (vimState.currentMode !== Mode.VisualBlock && !manuallySetCursorPositions) {\n    vimState.cursors = selections.map((sel, idx) => {\n      const diffs = accumulatedPositionDifferences[idx] ?? [];\n      if (vimState.recordedState.operatorPositionDiff) {\n        diffs.push(vimState.recordedState.operatorPositionDiff);\n      }\n\n      return diffs.reduce(\n        (cursor, diff) =>\n          new Cursor(\n            cursor.start.add(vimState.document, diff),\n            cursor.stop.add(vimState.document, diff),\n          ),\n        Cursor.fromSelection(sel),\n      );\n    });\n\n    vimState.recordedState.operatorPositionDiff = undefined;\n  } else if (accumulatedPositionDifferences[0]?.length > 0) {\n    const diff = accumulatedPositionDifferences[0][0];\n    vimState.cursorStopPosition = vimState.cursorStopPosition.add(vimState.document, diff);\n    vimState.cursorStartPosition = vimState.cursorStartPosition.add(vimState.document, diff);\n  }\n\n  vimState.recordedState.transformer = new Transformer();\n}\n\nconst doExecuteNormal = async (\n  modeHandler: IModeHandler,\n  transformation: ExecuteNormalTransformation,\n) => {\n  const vimState = modeHandler.vimState;\n  const { keystrokes, range } = transformation;\n  const strokes = keystrokesExpressionParser.parse(keystrokes);\n  if (!strokes.status) {\n    throw new Error(`Failed to execute normal command: ${keystrokes}`);\n  }\n\n  const resultLineNumbers: number[] = [];\n  if (range) {\n    const { start: startLineNumber, end: endLineNumber } = range.resolve(vimState);\n    for (let i = startLineNumber; i <= endLineNumber; i++) {\n      resultLineNumbers.push(i);\n    }\n  } else {\n    const selectionList = vimState.editor.selections;\n    for (const selection of selectionList) {\n      const { start, end } = selection;\n\n      for (let i = start.line; i <= end.line; i++) {\n        resultLineNumbers.push(i);\n      }\n    }\n  }\n\n  vimState.normalCommandState = NormalCommandState.Executing;\n  vimState.recordedState = new RecordedState();\n  await vimState.setCurrentMode(Mode.Normal);\n  for (const lineNumber of resultLineNumbers) {\n    if (range) {\n      vimState.cursorStopPosition = vimState.cursorStartPosition =\n        TextEditor.getFirstNonWhitespaceCharOnLine(vimState.document, lineNumber);\n    }\n    await modeHandler.handleMultipleKeyEvents(strokes.value);\n    if (vimState.currentMode === Mode.Insert) {\n      await modeHandler.handleKeyEvent('<Esc>');\n    }\n  }\n  vimState.normalCommandState = NormalCommandState.Finished;\n};\n"
  },
  {
    "path": "src/transformations/transformations.ts",
    "content": "import { Position, Range, TextDocumentContentChangeEvent } from 'vscode';\nimport { RecordedState } from '../state/recordedState';\nimport { LineRange } from '../vimscript/lineRange';\nimport { PositionDiff } from './../common/motion/position';\n\n/**\n * This file contains definitions of objects that represent text\n * additions/deletions/replacements on the document. You'll add them\n * to vimState.recordedState.transformer.transformations and then they will be applied\n * later on.\n *\n * We do it in this way so they can all be processed in parallel and merged\n * if necessary.\n */\n\n/**\n * Represents inserting text at a position in the document.\n */\nexport interface InsertTextTransformation {\n  /**\n   * Type of this insertion (used for type checking with discriminated\n   * union types).\n   */\n  type: 'insertText';\n\n  /**\n   * Text content of this insertion.\n   */\n  text: string;\n\n  /**\n   * The location to insert the text.\n   */\n  position: Position;\n\n  /**\n   * The index of the cursor that this transformation applies to.\n   */\n  cursorIndex?: number;\n\n  /**\n   * A position diff that will be added to the position of the cursor after\n   * the replace transformation has been applied.\n   *\n   * If you don't know what this is, just ignore it. You probably don't need it.\n   */\n  diff?: PositionDiff;\n\n  manuallySetCursorPositions?: boolean;\n}\n\nexport interface ReplaceTextTransformation {\n  type: 'replaceText';\n\n  /**\n   * Text to insert.\n   */\n  text: string;\n\n  /**\n   * Range of characters to replace.\n   */\n  range: Range;\n\n  /**\n   * The index of the cursor that this transformation applies to.\n   */\n  cursorIndex?: number;\n\n  /**\n   * A position diff that will be added to the position of the cursor after\n   * the replace transformation has been applied.\n   *\n   * If you don't know what this is, just ignore it. You probably don't need it.\n   */\n  diff?: PositionDiff;\n\n  /**\n   * Please don't use this! It's a hack.\n   */\n  manuallySetCursorPositions?: boolean;\n}\n\n/**\n * Represents inserting a character and allowing visual studio to do\n * its post-character stuff if it wants. (e.g., if you type \"(\" this\n * will automatically add the closing \")\").\n */\nexport interface InsertTextVSCodeTransformation {\n  type: 'insertTextVSCode';\n\n  /**\n   * Text to insert.\n   */\n  text: string;\n\n  /**\n   * Whether this transformation was created in multicursor mode.\n   */\n  isMultiCursor?: boolean;\n\n  /**\n   * The index of the cursor that this transformation applies to.\n   */\n  cursorIndex?: number;\n\n  /**\n   * A position diff that will be added to the position of the cursor after\n   * the replace transformation has been applied.\n   *\n   * If you don't know what this is, just ignore it. You probably don't need it.\n   */\n  diff?: PositionDiff;\n}\n\n/**\n * Represents deleting a range of characters.\n */\nexport interface DeleteTextRangeTransformation {\n  type: 'deleteRange';\n\n  /**\n   * Range of characters to delete.\n   */\n  range: Range;\n\n  /**\n   * A position diff that will be added to the position of the cursor after\n   * the replace transformation has been applied.\n   *\n   * If you don't know what this is, just ignore it. You probably don't need it.\n   */\n  diff?: PositionDiff;\n\n  /**\n   * The index of the cursor that this transformation applies to.\n   */\n  cursorIndex?: number;\n\n  /**\n   * Please don't use this! It's a hack.\n   */\n  manuallySetCursorPositions?: boolean;\n}\n\nexport interface MoveCursorTransformation {\n  type: 'moveCursor';\n\n  cursorIndex?: number;\n\n  /**\n   * Move the cursor this much.\n   */\n  diff: PositionDiff;\n}\n\n/**\n * Replays a RecordedState. Used for `.`, primarily.\n */\nexport interface Dot {\n  type: 'replayRecordedState';\n  count: number;\n  recordedState: RecordedState;\n}\n\nexport interface VSCodeCommandTransformation {\n  type: 'vscodeCommand';\n  command: string;\n  args: any[];\n}\n\n/**\n * Represents macro\n */\nexport interface Macro {\n  type: 'macro';\n  register: string;\n  replay: 'contentChange' | 'keystrokes';\n}\n\n/**\n * Represents updating document content changes\n */\nexport interface ContentChangeTransformation {\n  type: 'contentChange';\n  changes: TextDocumentContentChangeEvent[];\n  diff: PositionDiff;\n}\n\nexport interface ExecuteNormalTransformation {\n  type: 'executeNormal';\n  keystrokes: string;\n  range?: LineRange;\n}\n\nexport type Transformation =\n  | InsertTextTransformation\n  | InsertTextVSCodeTransformation\n  | ReplaceTextTransformation\n  | DeleteTextRangeTransformation\n  | MoveCursorTransformation\n  | Dot\n  | Macro\n  | ContentChangeTransformation\n  | ExecuteNormalTransformation\n  | VSCodeCommandTransformation;\n\n/**\n * Text Transformations\n *\n * Using these indicates that you want Visual Studio Code to execute your text\n * actions as a batch operation. It's a bit tricky because we defer cursor updating\n * behavior to whatever the batch operation returns, so if you update the cursor in your\n * Action, VSCode will override whatever you did.\n *\n * If your cursor isn't ending up in the right place, you can adjust it by passing along\n * a PositionDiff.\n *\n * (There are a LOT of weird edge cases with cursor behavior that we don't want to have to reimplement. Trust\n * me... I tried.)\n */\nexport type TextTransformations =\n  | InsertTextTransformation\n  | InsertTextVSCodeTransformation\n  | DeleteTextRangeTransformation\n  | MoveCursorTransformation\n  | ReplaceTextTransformation;\n\nexport const isTextTransformation = (x: Transformation): x is TextTransformations => {\n  return (\n    x.type === 'insertText' ||\n    x.type === 'replaceText' ||\n    x.type === 'deleteRange' ||\n    x.type === 'moveCursor'\n  );\n};\nexport const isMultiCursorTextTransformation = (x: Transformation): boolean => {\n  return (x.type === 'insertTextVSCode' && x.isMultiCursor) ?? false;\n};\n\nconst getRangeFromTextTransformation = (transformation: TextTransformations): Range | undefined => {\n  switch (transformation.type) {\n    case 'insertText':\n      return new Range(\n        transformation.position,\n        transformation.position.advancePositionByText(transformation.text),\n      );\n    case 'replaceText':\n      // TODO: Do we need to do the same sort of thing here as for insertText?\n      return transformation.range;\n    case 'deleteRange':\n      return transformation.range;\n    case 'moveCursor':\n      return undefined;\n  }\n\n  // eslint-disable-next-line @typescript-eslint/restrict-plus-operands\n  throw new Error('Unhandled text transformation: ' + transformation);\n};\n\nexport function overlappingTransformations(\n  transformations: TextTransformations[],\n): [TextTransformations, TextTransformations] | undefined {\n  for (let i = 0; i < transformations.length; i++) {\n    for (let j = i + 1; j < transformations.length; j++) {\n      const first = transformations[i];\n      const second = transformations[j];\n\n      const firstRange = getRangeFromTextTransformation(first);\n      const secondRange = getRangeFromTextTransformation(second);\n\n      if (!firstRange || !secondRange) {\n        continue;\n      }\n\n      const intersection = firstRange.intersection(secondRange);\n      if (intersection && !intersection.start.isEqual(intersection.end)) {\n        return [first, second];\n      }\n    }\n  }\n\n  return undefined;\n}\n\nexport const areAllSameTransformation = (transformations: Transformation[]): boolean => {\n  const firstTransformation = transformations[0];\n\n  return transformations.every((t) => {\n    return Object.entries(t).every(([key, value]) => {\n      // TODO: this is all quite janky\n      return (firstTransformation as unknown as Record<string, any>)[key] === value;\n    });\n  });\n};\n\nexport function stringify(transformation: Transformation): string {\n  if (transformation.type === 'replayRecordedState') {\n    return `Replay: ${transformation.recordedState.actionsRun\n      .map((x) => x.keysPressed.join(''))\n      .join('')}`;\n  } else {\n    return JSON.stringify(transformation);\n  }\n}\n"
  },
  {
    "path": "src/transformations/transformer.ts",
    "content": "import { Position, Range } from 'vscode';\nimport { PositionDiff } from '../common/motion/position';\nimport { Logger } from '../util/logger';\nimport { stringify, Transformation } from './transformations';\n\n/**\n * This class is (ideally) responsible for managing all changes made to document state, via @see Transformation.\n * Currently, changes are queued up within Actions and then executed (more or less) all at once.\n *\n * NOTE: This whole system is heavily WIP as I work through a large piecemeal refactor.\n */\nexport class Transformer {\n  public readonly transformations: Transformation[] = [];\n\n  public addTransformation(transformation: Transformation): void {\n    Logger.debug(`Adding Transformation ${stringify(transformation)}`);\n    this.transformations.push(transformation);\n  }\n\n  public insert(position: Position, text: string, diff?: PositionDiff): void {\n    this.addTransformation({ type: 'insertText', position, text, diff });\n  }\n\n  public delete(range: Range, diff?: PositionDiff): void {\n    this.addTransformation({ type: 'deleteRange', range, diff });\n  }\n\n  public replace(range: Range, text: string, diff?: PositionDiff): void {\n    this.addTransformation({ type: 'replaceText', range, text, diff });\n  }\n\n  public moveCursor(diff: PositionDiff, cursorIndex?: number): void {\n    this.addTransformation({ type: 'moveCursor', diff, cursorIndex });\n  }\n\n  public vscodeCommand(command: string, ...args: any[]): void {\n    this.addTransformation({ type: 'vscodeCommand', command, args });\n  }\n}\n"
  },
  {
    "path": "src/util/child_process.ts",
    "content": "import * as child_process from 'child_process';\nimport { promisify } from 'util';\n\nexport function exec(\n  command: string,\n  options?: child_process.ExecOptions,\n): child_process.PromiseWithChild<{ stdout: string | Buffer; stderr: string | Buffer }> {\n  return promisify(child_process.exec)(command, options);\n}\n"
  },
  {
    "path": "src/util/clipboard.ts",
    "content": "import * as vscode from 'vscode';\nimport { Logger } from './logger';\n\n/**\n * A thin wrapper around `vscode.env.clipboard`\n */\nexport class Clipboard {\n  public static async Copy(text: string): Promise<void> {\n    try {\n      await vscode.env.clipboard.writeText(text);\n    } catch (e) {\n      Logger.error(`Error copying to clipboard. err=${e}`);\n    }\n  }\n\n  public static async Paste(): Promise<string> {\n    return vscode.env.clipboard.readText();\n  }\n}\n"
  },
  {
    "path": "src/util/decorationUtils.ts",
    "content": "import { DecorationOptions, Range, TextDocument } from 'vscode';\n\n/**\n * Alias for the types of arrays that can be passed to a TextEditor's setDecorations method\n */\nexport type EditorDecorationArray = Range[] | DecorationOptions[];\n\n/**\n * Decorations associated with search/substitute operations\n */\nexport type SearchDecorations = {\n  searchHighlight?: EditorDecorationArray;\n  searchMatch?: EditorDecorationArray;\n  substitutionAppend?: EditorDecorationArray;\n  substitutionReplace?: EditorDecorationArray;\n};\n\n/**\n * @returns a DecorationOptions object representing the given range. If the\n * given range is empty, the range of the returned object will be extended one\n * character to the right. If the given range cannot be extended right, or\n * represents the end of a line (possibly containing EOL characters), the\n * returned object will specify an after element with the width of a single\n * character.\n */\nexport function ensureVisible(range: Range, document: TextDocument): DecorationOptions {\n  return (range.isEmpty || range.end.isLineBeginning()) && range.start.isLineEnd(document)\n    ? {\n        // range is at EOL, possibly containing EOL char(s).\n        range: range.with(undefined, range.start),\n        renderOptions: {\n          after: {\n            color: 'transparent',\n            contentText: '$', // non-whitespace character to set width.\n          },\n        },\n      }\n    : range.isEmpty\n      ? { range: range.with(undefined, range.end.translate(0, 1)) } // extend range one character right\n      : { range };\n}\n\n/**\n * @returns a version of the input string suitable for use as the contentText of a decoration's before or after element\n */\nexport function formatDecorationText(\n  text: string,\n  tabsize: number,\n  newlineReplacement: string | ((substring: string, ...args: any[]) => string) = '\\u23ce', // \"⏎\" RETURN SYMBOL\n) {\n  // surround with zero-width space to prevent trimming\n  return `\\u200b${text\n    // vscode collapses whitespace in decorations; modify text to prevent this.\n    .replace(/ /g, '\\u00a0') // \" \" NO-BREAK SPACE\n    .replace(/\\t/g, '\\u00a0'.repeat(tabsize))\n    // Decorations can't change the apparent # of lines in the editor, so we must settle for a single-line version of our text\n    .replace(/\\r\\n|[\\r\\n]/g, newlineReplacement as string)}\\u200b`;\n}\n\n/**\n * @returns search decorations for the given ranges, taking into account the current match\n */\nexport function getDecorationsForSearchMatchRanges(\n  ranges: Range[],\n  document: TextDocument,\n  currentMatchIndex?: number,\n): SearchDecorations {\n  const searchHighlight: DecorationOptions[] = [];\n  const searchMatch: DecorationOptions[] = [];\n\n  for (let i = 0; i < ranges.length; i++) {\n    (i === currentMatchIndex ? searchMatch : searchHighlight).push(\n      ensureVisible(ranges[i], document),\n    );\n  }\n\n  return { searchHighlight, searchMatch };\n}\n"
  },
  {
    "path": "src/util/externalCommand.ts",
    "content": "import { configuration } from '../configuration/configuration';\nimport { VimError } from '../error';\n\nclass ExternalCommand {\n  private previousExternalCommand: string | undefined;\n\n  /**\n   * Expands the given command by replacing any '!' with the previous external\n   * command. The '!' can be escaped if there is a backslash preceeding the\n   * '!', then the backslash is removed and the '!' is kept.\n   *\n   * If a '!' is present but there is no previous external command, then a\n   * VimError is thrown.\n   * @param command the command to expand\n   */\n  private expandCommand(command: string): string {\n    const result: string[] = [];\n\n    for (let i = 0; i < command.length; i++) {\n      if (command[i] === '!') {\n        if (i > 0 && command[i - 1] === '\\\\') {\n          // escape the '!' and keep it\n          result.pop();\n          result.push('!');\n        } else if (!this.previousExternalCommand) {\n          // no previous command available to substitute\n          throw VimError.NoPreviousCommand();\n        } else {\n          result.push(this.previousExternalCommand);\n        }\n      } else {\n        result.push(command[i]);\n      }\n    }\n    return result.join('');\n  }\n\n  /**\n   * Executes `command` and returns the output.\n   * @param command the command to run\n   * @param stdin string to pipe into stdin\n   */\n  private async execute(command: string, stdin: string): Promise<string> {\n    const output: string[] = [];\n    const options = {\n      shell: configuration.shell || undefined,\n    };\n\n    try {\n      const exec = (await import('../util/child_process')).exec;\n\n      const promise = exec(command, options);\n      const process = promise.child;\n\n      if (process.stdin !== null) {\n        process.stdin.on('error', () => {\n          // Make write EPIPE errors silent (e.g. when writing to program not expecting stdin)\n        });\n        process.stdin.write(stdin);\n        process.stdin.end();\n      }\n\n      if (process.stdout !== null) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n        process.stdout.on('data', (chunk) => output.push(chunk));\n      }\n      if (process.stderr !== null) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n        process.stderr.on('data', (chunk) => output.push(chunk));\n      }\n\n      await promise;\n    } catch (e) {\n      // exec throws an error if exit code != 0\n      // keep going and read the output anyway (just like vim)\n    }\n    return output.join('');\n  }\n\n  /**\n   * Runs the given command and returns the output (stdout and stderr).\n   * Optionally, `stdin` can be piped into stdin during execution.\n   *\n   * @param command the command to run\n   * @param stdin string to pipe into stdin, by default the empty string\n   */\n  public async run(command: string, stdin: string = ''): Promise<string> {\n    command = this.expandCommand(command);\n    this.previousExternalCommand = command;\n    // combines stdout and stderr (compatible for all platforms)\n    command += ' 2>&1';\n\n    let output = await this.execute(command, stdin);\n    // vim behavior, trim newlines\n    if (output.endsWith('\\r\\n')) {\n      output = output.slice(0, -2);\n    } else if (output.endsWith('\\n')) {\n      output = output.slice(0, -1);\n    }\n\n    return output;\n  }\n}\n\nexport const externalCommand = new ExternalCommand();\n"
  },
  {
    "path": "src/util/logger.ts",
    "content": "import { LogOutputChannel, window } from 'vscode';\n\nexport class Logger {\n  private static output: LogOutputChannel;\n\n  public static init(): void {\n    Logger.output = window.createOutputChannel('Vim', { log: true });\n  }\n\n  public static error(msg: string): void {\n    Logger.output.error(msg);\n  }\n  public static warn(msg: string): void {\n    Logger.output.warn(msg);\n  }\n  public static info(msg: string): void {\n    Logger.output.info(msg);\n  }\n  public static debug(msg: string): void {\n    Logger.output.debug(msg);\n  }\n  public static trace(msg: string): void {\n    Logger.output.trace(msg);\n  }\n}\n"
  },
  {
    "path": "src/util/os.ts",
    "content": "import * as os from 'os';\n\nexport function tmpdir(): string {\n  return os.tmpdir();\n}\n"
  },
  {
    "path": "src/util/path.ts",
    "content": "import * as path from 'path';\nimport * as vscode from 'vscode';\n// TODO:\n// eslint-disable-next-line @typescript-eslint/no-require-imports\nimport untildify = require('untildify');\n\n/**\n * A interface to the path in the node.js.\n */\ninterface Path {\n  normalize(p: string): string;\n  join(...paths: string[]): string;\n  resolve(...pathSegments: string[]): string;\n  isAbsolute(p: string): boolean;\n  relative(from: string, to: string): string;\n  dirname(p: string): string;\n  basename(p: string, ext?: string): string;\n  extname(p: string): string;\n  sep: string;\n  delimiter: string;\n}\n\n/**\n * Separate a partial path or full path into dirname and the basename.\n * @param searchPath The path to separate.\n * @param sep The separator of the searchPath.\n * @return A two-element array where the first element is the dirname and the second\n * is the basename.\n */\nexport function separatePath(searchPath: string, sep: string) {\n  // Special handle for UNC path on windows\n  if (sep === path.win32.sep) {\n    if (searchPath[0] === sep && searchPath[1] === sep) {\n      const idx = searchPath.indexOf(sep, 2);\n      if (idx === -1) {\n        // If there isn't a complete UNC path,\n        // return the incomplete UNC as baseName\n        // e.g. \\\\test-server is an incomplete path\n        // and \\\\test-server\\ is a complete path\n        return [searchPath, ''];\n      }\n    }\n  }\n\n  const baseNameIndex = searchPath.lastIndexOf(sep) + 1;\n  const baseName = searchPath.slice(baseNameIndex);\n  const dirName = searchPath.slice(0, baseNameIndex);\n  return [dirName, baseName];\n}\n\n/**\n * The comment is used conjunction with getPathDetails.\n */\ninterface PathDetails {\n  /**\n   * A full absolute path resolved from directory of the currently active document.\n   * If the active document is an untitled document, full path will be dirName of\n   * the input partialPath.\n   */\n  fullPath: string;\n  /**\n   * A full absolute path of the directory of fullPath.\n   * If the active document is an untitled document, full path will be the input partialPath.\n   */\n  fullDirPath: string;\n  /**\n   * The dir name of partialPath.\n   * If the partialPath is an absolute path, this will be equal to fullDirPat\n   * If partialPath is ./abc/xyz.txt, baseName will be './abc/'\n   * If partialPath is /abc/xyz.txt, baseName will be '/abc/'\n   */\n  dirName: string;\n  /**\n   * A base name of the partialPath.\n   * If partialPath is ./abc/xyz.txt, baseName will be 'xyz.txt'\n   * If partialPath is /abc/xyz.txt, baseName will be 'xyz.txt'\n   */\n  baseName: string;\n  /**\n   * An updated partialPath which has its / changed to \\ on Windows.\n   */\n  partialPath: string;\n  /**\n   * The correct node js path for the partial path. This will be either\n   * path.win32 or path.posix for further processing.\n   */\n  path: Path;\n}\n\n/**\n * Get path detail.\n *\n * If the currently active document is an untitled document, we will assume the partialPath\n * is a Windows path only when the VS Code is running on Windows, and not remote session; else, posix path.\n *\n * If the currently active document is not an untitled document, we will assume the partialPath\n * is a Windows path when the current uri is local file where the first character of fsPath of the\n * current uri is not \"/\"; otherwise, posix path. fsPath can return C:\\path\\avc.txt or \\\\drive\\location\\abc.txt\n * on Windows.\n *\n * This is to maximize usability of the combination of Windows and posix machine using remote while browsing\n * file on both local and remote.\n *\n * @param partialPath A string of relative path to the directory of the currentUri,\n * or an absolute path in the environment of the currentUri.\n * ~/ can be used only if active document is local document, or local untitled document.\n * @param currentUri A uri of the currently active document.\n * @param isRemote A boolean to indicate if the current instance is in remote.\n * @return A PathDetail.\n */\nexport function getPathDetails(\n  partialPath: string,\n  currentUri: vscode.Uri,\n  isRemote: boolean,\n): PathDetails {\n  let isWindows: boolean;\n  if (currentUri.scheme === 'untitled') {\n    // Assume remote server is nix only\n    isWindows = path === path.win32 && !isRemote;\n  } else {\n    // Assuming other schemes return full path\n    // e.g. 'file' and 'vscode-remote' both return full path\n    // Also only scheme that support Windows is 'file', so we can\n    // safely check if fsPath returns '/' as the first character\n    // (fsPath in 'vscode-remote' on Windows return \\ as separator instead of /)\n    isWindows = currentUri.scheme === 'file' && currentUri.fsPath[0] !== '/';\n  }\n\n  const p = isWindows ? path.win32 : path.posix;\n  if (isWindows) {\n    // normalize / to \\ on windows\n    partialPath = partialPath.replace(/\\//g, '\\\\');\n  }\n  const updatedPartialPath = partialPath;\n\n  if (currentUri.scheme === 'file' || (currentUri.scheme === 'untitled' && !isRemote)) {\n    // We can untildify when the scheme is 'file' or 'untitled' on local fs because\n    // because we only support opening files mounted locally.\n    partialPath = untildify(partialPath);\n  }\n\n  const [dirName, baseName] = separatePath(partialPath, p.sep);\n  let fullDirPath: string;\n  if (p.isAbsolute(dirName)) {\n    fullDirPath = dirName;\n  } else {\n    fullDirPath = p.join(\n      // On Windows machine:\n      // fsPath returns Windows drive path (C:\\xxx\\) or UNC path (\\\\server\\xxx)\n      // fsPath returns path with \\ as separator even if 'vscode-remote' is connect to a linux box\n      //\n      // path will return /home/user for example even 'vscode-remote' is used on windows\n      // as we relied of our isWindows detection\n      separatePath(isWindows ? currentUri.fsPath : currentUri.path, p.sep)[0],\n      dirName,\n    );\n  }\n\n  const fullPath = p.join(fullDirPath, baseName);\n  return {\n    fullPath,\n    fullDirPath,\n    dirName,\n    baseName,\n    partialPath: updatedPartialPath,\n    path: p,\n  };\n}\n\n/**\n * Resolve the absolutePath to Uri.\n *\n * @param absolutePath A string of absolute path.\n * @param sep The separator of the absolutePath.\n * This is used to determine we should consider absolutePath a Windows path.\n * @param currentUri A uri to resolve the absolutePath to Uri.\n * @param isRemote A boolean to indicate if the current instance is in remote.\n * @return null if the absolutePath is invalid. A uri resolved with the currentUri.\n */\nexport function resolveUri(\n  absolutePath: string,\n  sep: string,\n  currentUri: vscode.Uri,\n  isRemote: boolean,\n) {\n  const isWindows = sep === path.win32.sep;\n  if (isWindows && !/^(\\\\\\\\.+\\\\)|([a-zA-Z]:\\\\)/.test(absolutePath)) {\n    // if it is windows and but don't have either\n    // UNC path or the windows drive\n    return null;\n  }\n  if (!isWindows && absolutePath[0] !== sep) {\n    // if it is not windows, but the absolute path doesn't begin with /\n    return null;\n  }\n\n  const isLocalUntitled = !isRemote && currentUri.scheme === 'untitled';\n  return isWindows\n    ? // Create new local Uri when it's on windows.\n      // Only local resource is support (vscode-remote doesn't have windows path)\n      // UNC path like //server1/folder should also work.\n      vscode.Uri.file(absolutePath)\n    : currentUri.with({\n        // search local file with currently active document is a local untitled doc\n        scheme: isLocalUntitled ? 'file' : currentUri.scheme,\n        path: absolutePath,\n      });\n}\n\n/**\n * Get the name of the items in a directory.\n * @param absolutePath A string of absolute path.\n * @param sep The separator of the absolutePath.\n * @param currentUri A uri of the currently active document.\n * @param isRemote A boolean to indicate if the current instance is in remote.\n * @param addCurrentAndUp A boolean to indicate if .{$sep} and ..${sep} should be add to the result\n * @return A Promise which resolves to an array of string. The array can be empty if the path specified is actual\n * empty, of if the absolutePath specified is invalid, or if any error occurred during directory reading.\n * The string in the array will have sep appended if it is a directory.\n */\nexport async function readDirectory(\n  absolutePath: string,\n  sep: string,\n  currentUri: vscode.Uri,\n  isRemote: boolean,\n  addCurrentAndUp: boolean,\n) {\n  try {\n    const directoryUri = resolveUri(absolutePath, sep, currentUri, isRemote);\n    if (directoryUri === null) {\n      return [];\n    }\n    const directoryResult = await vscode.workspace.fs.readDirectory(directoryUri);\n    return (\n      directoryResult\n        // Add the separator at the end to the path if it is a directory\n        .map((d) => d[0] + (d[1] === vscode.FileType.Directory ? sep : ''))\n        // Add ./ and ../ to the result if specified\n        .concat(addCurrentAndUp ? [`.${sep}`, `..${sep}`] : [])\n    );\n  } catch {\n    return [];\n  }\n}\n\nexport function join(...paths: string[]): string {\n  return path.join(...paths);\n}\n"
  },
  {
    "path": "src/util/selections.ts",
    "content": "import * as vscode from 'vscode';\n\n/**\n * Hashes the given selections array into a string, based on the anchor and active positions of each\n * selection.\n */\nexport function hashSelectionsArray(selections: readonly vscode.Selection[]): string {\n  return selections.reduce((hash, s) => hash + hashSelection(s), '');\n}\n\n/**\n * Hashes the given selection into a string, based on its anchor and active positions.\n */\nfunction hashSelection(selection: vscode.Selection): string {\n  const { anchor, active } = selection;\n  return `[${anchor.line}, ${anchor.character}; ${active.line}, ${active.character}]`;\n}\n\n/**\n * Returns whether the two arrays of selections are equal (i.e. have the same number of selections,\n * and the same anchor and active positions at each index).\n */\nexport function areSelectionArraysEqual(\n  selectionsA: readonly vscode.Selection[],\n  selectionsB: readonly vscode.Selection[],\n): boolean {\n  return (\n    selectionsA.length === selectionsB.length &&\n    selectionsA.every((sA, i) => areSelectionsEqual(sA, selectionsB[i]))\n  );\n}\n\n/**\n * Returns whether two selections are equal (i.e. have the same anchor and active positions).\n *\n * Note that `{@link vscode.Selection.isEqual}` is not used here because it's derived from\n * `Range.isEqual`, and only checks if the `start` and `end` positions are equal, without\n * considering `anchor` and `active` (i.e. which end of the range the cursor is on).\n */\nfunction areSelectionsEqual(selectionA: vscode.Selection, selectionB: vscode.Selection): boolean {\n  return (\n    selectionA.anchor.isEqual(selectionB.anchor) && selectionA.active.isEqual(selectionB.active)\n  );\n}\n"
  },
  {
    "path": "src/util/specialKeys.ts",
    "content": "export enum SpecialKeys {\n  ExtensionEnable = '<ExtensionEnable>',\n  ExtensionDisable = '<ExtensionDisable>',\n  TimeoutFinished = '<TimeoutFinished>',\n}\n"
  },
  {
    "path": "src/util/statusBarTextUtils.ts",
    "content": "import { Position } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { Mode } from '../mode/mode';\nimport { VimState } from '../state/vimState';\nimport { StatusBar } from '../statusBar';\n\n/**\n * Escapes substrings that would be interpreted as css icon markdown in certain\n * ui labels, including the status bar.\n */\nexport function escapeCSSIcons(text: string): string {\n  // regex from iconLabel implementation at\n  // https://github.com/microsoft/vscode/blob/9b75bd1f813e683bf46897d85387089ec083fb24/src/vs/base/browser/ui/iconLabel/iconLabels.ts#L9\n  return text.replace(/\\\\?\\$\\([A-Za-z0-9\\-]+(?:~[A-Za-z]+)?\\)/g, '\\\\$&');\n}\n\n/**\n * Shows the number of lines you just changed (with `dG`, for instance), if it\n * crosses a configured threshold.\n * @param numLinesChanged The number of lines changed\n */\nexport function reportLinesChanged(numLinesChanged: number, vimState: VimState) {\n  if (numLinesChanged > configuration.report) {\n    StatusBar.setText(vimState, `${numLinesChanged} more lines`);\n  } else if (-numLinesChanged > configuration.report) {\n    StatusBar.setText(vimState, `${Math.abs(numLinesChanged)} fewer lines`);\n  } else {\n    StatusBar.clear(vimState);\n  }\n}\n\n/**\n * Shows the number of lines you just yanked, if it crosses a configured threshold.\n * @param numLinesYanked The number of lines yanked\n */\nexport function reportLinesYanked(numLinesYanked: number, vimState: VimState) {\n  if (numLinesYanked > configuration.report) {\n    if (vimState.currentMode === Mode.VisualBlock) {\n      StatusBar.setText(vimState, `block of ${numLinesYanked} lines yanked`);\n    } else {\n      StatusBar.setText(vimState, `${numLinesYanked} lines yanked`);\n    }\n  } else {\n    StatusBar.clear(vimState);\n  }\n}\n\n/**\n * Shows the active file's path and line count as well as position in the file as a percentage.\n * Triggered via `<C-g>` or `:f[ile]`.\n */\nexport function reportFileInfo(position: Position, vimState: VimState) {\n  const doc = vimState.document;\n  const fileName = doc.isUntitled ? '[No Name]' : doc.fileName;\n  const modified = doc.isDirty ? ' [Modified]' : '';\n\n  if (doc.lineCount === 1 && doc.lineAt(0).text.length === 0) {\n    // TODO: Vim behaves slightly differently - seems this is only shown for new buffer that hasn't been saved to disk\n    StatusBar.setText(vimState, `\"${fileName}\"${modified} --No lines in buffer--`);\n  } else {\n    const progress = Math.floor(((position.line + 1) / doc.lineCount) * 100);\n    StatusBar.setText(\n      vimState,\n      `\"${fileName}\"${modified} ${doc.lineCount} line${\n        doc.lineCount > 1 ? 's' : ''\n      } --${progress}%--`,\n    );\n  }\n}\n\n/**\n * Shows the number of matches and current match index of a search.\n * @param matchIdx Index of current match, starting at 0\n * @param numMatches Total number of matches\n * @param vimState The current `VimState`\n */\nexport function reportSearch(matchIdx: number, numMatches: number, vimState: VimState) {\n  StatusBar.setText(vimState, `match ${matchIdx + 1} of ${numMatches}`);\n}\n"
  },
  {
    "path": "src/util/util.ts",
    "content": "import * as vscode from 'vscode';\nimport { Cursor } from '../common/motion/cursor';\nimport { VimError } from '../error';\nimport { VimState } from '../state/vimState';\n\n/**\n * We used to have an issue where we would do something like execute a VSCode\n * command, and would encounter race conditions because the cursor positions\n * wouldn't yet be updated. So we waited for a selection change event, but\n * this doesn't seem to be necessary any more.\n *\n * @deprecated Calls to this should probably be replaced with calls to `ModeHandler::syncCursors()` or something...\n */\nexport function getCursorsAfterSync(editor: vscode.TextEditor): Cursor[] {\n  return editor.selections.map((x) => Cursor.fromSelection(x));\n}\n\nexport function clamp(num: number, min: number, max: number) {\n  return Math.min(Math.max(num, min), max);\n}\n\nexport function scrollView(vimState: VimState, offset: number) {\n  if (offset !== 0) {\n    vimState.postponedCodeViewChanges.push({\n      command: 'editorScroll',\n      args: {\n        to: offset > 0 ? 'up' : 'down',\n        by: 'line',\n        value: Math.abs(offset),\n        revealCursor: false,\n        select: false,\n      },\n    });\n  }\n}\n\nexport function assertDefined<X>(x: X | undefined, err: string): asserts x {\n  if (x === undefined) {\n    throw new Error(err);\n  }\n}\n\nexport function isHighSurrogate(charCode: number): boolean {\n  return 0xd800 <= charCode && charCode <= 0xdbff;\n}\n\nexport function isLowSurrogate(charCode: number): boolean {\n  return 0xdc00 <= charCode && charCode <= 0xdfff;\n}\n\nexport function findTabInActiveTabGroup(name: string): [number, vscode.Tab] {\n  const foundBuffers: Array<[number, vscode.Tab]> = [];\n  const tabs = vscode.window.tabGroups.activeTabGroup.tabs;\n  for (let t = 0; t < tabs.length; t++) {\n    const tab = tabs[t];\n    if (tab.label.includes(name)) {\n      foundBuffers.push([t, tab]);\n      if (foundBuffers.length > 1) {\n        throw VimError.MultipleMatches(name);\n      }\n    }\n  }\n  if (foundBuffers.length === 0) {\n    throw VimError.NoMatchingBuffer(name);\n  }\n  return foundBuffers[0];\n}\n"
  },
  {
    "path": "src/util/vscodeContext.ts",
    "content": "import * as vscode from 'vscode';\nimport { Logger } from './logger';\n\ntype ContextValue = boolean | string;\n\n/**\n * Wrapper around VS Code's `setContext`.\n * The API call takes several milliseconds to seconds to complete,\n * so let's cache the values and only call the API when necessary.\n */\nexport abstract class VSCodeContext {\n  private static readonly cache: Map<string, ContextValue> = new Map();\n\n  public static async set(key: string, value: ContextValue): Promise<void> {\n    const prev = this.get(key);\n    if (prev !== value) {\n      Logger.trace(`Setting key='${key}' to value='${value}'`);\n      this.cache.set(key, value);\n      await vscode.commands.executeCommand('setContext', key, value);\n    }\n  }\n\n  public static get(key: string): ContextValue | undefined {\n    return this.cache.get(key);\n  }\n}\n"
  },
  {
    "path": "src/vimscript/exCommand.ts",
    "content": "import { VimError } from '../error';\nimport { VimState } from '../state/vimState';\nimport { LineRange } from './lineRange';\n\nexport abstract class ExCommand {\n  /**\n   * If this returns true and Neovim integration is enabled, we'll send this command to Neovim.\n   */\n  public neovimCapable(): boolean {\n    return false;\n  }\n\n  public readonly isRepeatableWithDot: boolean = false;\n\n  abstract execute(vimState: VimState): Promise<void>;\n\n  async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    // By default, throw E481 (\"No range allowed\")\n    throw VimError.NoRangeAllowed();\n  }\n}\n"
  },
  {
    "path": "src/vimscript/exCommandParser.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { all, alt, optWhitespace, Parser, regexp, seq, string, succeed } from 'parsimmon';\nimport { AsciiCommand } from '../cmd_line/commands/ascii';\nimport { BangCommand } from '../cmd_line/commands/bang';\nimport { Breakpoints } from '../cmd_line/commands/breakpoints';\nimport { BufferDeleteCommand } from '../cmd_line/commands/bufferDelete';\nimport { ChangeCommand } from '../cmd_line/commands/change';\nimport { CloseCommand } from '../cmd_line/commands/close';\nimport { CopyCommand } from '../cmd_line/commands/copy';\nimport { DeleteCommand } from '../cmd_line/commands/delete';\nimport { DigraphsCommand } from '../cmd_line/commands/digraph';\nimport { EchoCommand } from '../cmd_line/commands/echo';\nimport { CallCommand, EvalCommand } from '../cmd_line/commands/eval';\nimport { ExploreCommand } from '../cmd_line/commands/explore';\nimport { FileCommand } from '../cmd_line/commands/file';\nimport { FileInfoCommand } from '../cmd_line/commands/fileInfo';\nimport { GotoCommand } from '../cmd_line/commands/goto';\nimport { GotoLineCommand } from '../cmd_line/commands/gotoLine';\nimport { GrepCommand } from '../cmd_line/commands/grep';\nimport { HistoryCommand } from '../cmd_line/commands/history';\nimport { ClearJumpsCommand, JumpsCommand } from '../cmd_line/commands/jumps';\nimport { CenterCommand, LeftCommand, RightCommand } from '../cmd_line/commands/leftRightCenter';\nimport { LetCommand, UnletCommand } from '../cmd_line/commands/let';\nimport { DeleteMarksCommand, MarkCommand, MarksCommand } from '../cmd_line/commands/marks';\nimport { MoveCommand } from '../cmd_line/commands/move';\nimport { NohlCommand } from '../cmd_line/commands/nohl';\nimport { NormalCommand } from '../cmd_line/commands/normal';\nimport { OnlyCommand } from '../cmd_line/commands/only';\nimport { PrintCommand } from '../cmd_line/commands/print';\nimport { PutExCommand } from '../cmd_line/commands/put';\nimport { PwdCommand } from '../cmd_line/commands/pwd';\nimport { QuitCommand } from '../cmd_line/commands/quit';\nimport { ReadCommand } from '../cmd_line/commands/read';\nimport { RedoCommand } from '../cmd_line/commands/redo';\nimport { RegisterCommand } from '../cmd_line/commands/register';\nimport { RetabCommand } from '../cmd_line/commands/retab';\nimport { SetCommand } from '../cmd_line/commands/set';\nimport { ShCommand } from '../cmd_line/commands/sh';\nimport { ShiftCommand } from '../cmd_line/commands/shift';\nimport { SmileCommand } from '../cmd_line/commands/smile';\nimport { SortCommand } from '../cmd_line/commands/sort';\nimport { SubstituteCommand } from '../cmd_line/commands/substitute';\nimport { TabCommand } from '../cmd_line/commands/tab';\nimport { TerminalCommand } from '../cmd_line/commands/terminal';\nimport { UndoCommand } from '../cmd_line/commands/undo';\nimport { VsCodeCommand } from '../cmd_line/commands/vscode';\nimport { WallCommand } from '../cmd_line/commands/wall';\nimport { WriteCommand } from '../cmd_line/commands/write';\nimport { WriteQuitCommand } from '../cmd_line/commands/writequit';\nimport { WriteQuitAllCommand } from '../cmd_line/commands/writequitall';\nimport { YankCommand } from '../cmd_line/commands/yank';\nimport { VimError } from '../error';\nimport { VimState } from '../state/vimState';\nimport { StatusBar } from '../statusBar';\nimport { ExCommand } from './exCommand';\nimport { LineRange } from './lineRange';\nimport { nameAbbrevParser } from './parserUtils';\n\ntype ArgParser = Parser<ExCommand>;\n\n/**\n * A list of all builtin ex commands and their argument parsers\n * Each element is [[abbrev, rest], argParser]\n * If argParser is undefined, it's yet to be implemented (PRs are welcome!)\n *\n * This list comes directly from nvim's `:help index` (except a few additions, which are commented)\n */\nexport const builtinExCommands: ReadonlyArray<[[string, string], ArgParser | undefined]> = [\n  [['', ''], succeed(new GotoLineCommand())],\n  [['\"', ''], all.map((_) => new NoOpCommand())],\n  [['!', ''], BangCommand.argParser],\n  [['#', ''], PrintCommand.argParser({ printNumbers: true, printText: true })],\n  [['#!', ''], all.map((_) => new NoOpCommand())],\n  [['&', ''], undefined],\n  [['*', ''], undefined],\n  [['<', ''], ShiftCommand.argParser('<')],\n  [['=', ''], PrintCommand.argParser({ printNumbers: true, printText: false })],\n  [['>', ''], ShiftCommand.argParser('>')],\n  [['@', ''], undefined],\n  [['@@', ''], undefined],\n  [['N', 'ext'], undefined],\n  [['a', 'ppend'], undefined],\n  [['ab', 'breviate'], undefined],\n  [['abc', 'lear'], undefined],\n  [['abo', 'veleft'], undefined],\n  [['al', 'l'], undefined],\n  [['am', 'enu'], undefined],\n  [['an', 'oremenu'], undefined],\n  [['ar', 'gs'], undefined],\n  [['arga', 'dd'], undefined],\n  [['argd', 'elete'], undefined],\n  [['argdo', ''], undefined],\n  [['arge', 'dit'], undefined],\n  [['argg', 'lobal'], undefined],\n  [['argl', 'ocal'], undefined],\n  [['argu', 'ment'], undefined],\n  [['as', 'cii'], succeed(new AsciiCommand())],\n  [['au', 'tocmd'], undefined],\n  [['aug', 'roup'], undefined],\n  [['aun', 'menu'], undefined],\n  [['b', 'uffer'], TabCommand.argParsers.buffer],\n  [['bN', 'ext'], TabCommand.argParsers.bprev],\n  [['ba', 'll'], undefined],\n  [['bad', 'd'], undefined],\n  [['balt', ''], undefined],\n  [['bd', 'elete'], BufferDeleteCommand.argParser],\n  [['be', 'have'], undefined],\n  [['bel', 'owright'], undefined],\n  [['bf', 'irst'], TabCommand.argParsers.bfirst],\n  [['bl', 'ast'], TabCommand.argParsers.blast],\n  [['bm', 'odified'], undefined],\n  [['bn', 'ext'], TabCommand.argParsers.bnext],\n  [['bo', 'tright'], undefined],\n  [['bp', 'revious'], TabCommand.argParsers.bprev],\n  [['br', 'ewind'], TabCommand.argParsers.bfirst],\n  [['brea', 'k'], undefined],\n  [['breaka', 'dd'], Breakpoints.argParsers.add],\n  [['breakd', 'el'], Breakpoints.argParsers.del],\n  [['breakl', 'ist'], Breakpoints.argParsers.list],\n  [['bro', 'wse'], undefined],\n  [['bufdo', ''], undefined],\n  [['buffers', ''], undefined],\n  [['bun', 'load'], undefined],\n  [['bw', 'ipeout'], undefined],\n  [['c', 'hange'], ChangeCommand.argParser],\n  [['cN', 'ext'], undefined],\n  [['cNf', 'ile'], undefined],\n  [['ca', 'bbrev'], undefined],\n  [['cabc', 'lear'], undefined],\n  [['cabo', 've'], undefined],\n  [['cad', 'dbuffer'], undefined],\n  [['cadde', 'xpr'], undefined],\n  [['caddf', 'ile'], undefined],\n  [['caf', 'ter'], undefined],\n  [['cal', 'l'], CallCommand.argParser],\n  [['cat', 'ch'], undefined],\n  [['cb', 'uffer'], undefined],\n  [['cbef', 'ore'], undefined],\n  [['cbel', 'ow'], undefined],\n  [['cbo', 'ttom'], undefined],\n  [['cc', ''], undefined],\n  [['ccl', 'ose'], succeed(new VsCodeCommand('workbench.action.closePanel'))],\n  [['cd', ''], undefined],\n  [['cdo', ''], undefined],\n  [['ce', 'nter'], CenterCommand.argParser],\n  [['cex', 'pr'], undefined],\n  [['cf', 'ile'], undefined],\n  [['cfd', 'o'], undefined],\n  [['cfir', 'st'], undefined],\n  [['cg', 'etfile'], undefined],\n  [['cgetb', 'uffer'], undefined],\n  [['cgete', 'xpr'], undefined],\n  [['changes', ''], undefined],\n  [['chd', 'ir'], undefined],\n  [['che', 'ckpath'], undefined],\n  [['checkh', 'ealth'], undefined],\n  [['checkt', 'ime'], undefined],\n  [['chi', 'story'], undefined],\n  [['cl', 'ist'], undefined],\n  [['cla', 'st'], undefined],\n  [['cle', 'arjumps'], succeed(new ClearJumpsCommand())],\n  [['clo', 'se'], CloseCommand.argParser],\n  [['cm', 'ap'], undefined],\n  [['cmapc', 'lear'], undefined],\n  [['cme', 'nu'], undefined],\n  [['cn', 'ext'], succeed(new VsCodeCommand('editor.action.marker.nextInFiles'))],\n  [['cnew', 'er'], undefined],\n  [['cnf', 'ile'], succeed(new VsCodeCommand('editor.action.marker.nextInFiles'))],\n  [['cno', 'remap'], undefined],\n  [['cnorea', 'bbrev'], undefined],\n  [['cnoreme', 'nu'], undefined],\n  [['co', 'py'], CopyCommand.argParser],\n  [['col', 'der'], undefined],\n  [['colo', 'rscheme'], undefined],\n  [['com', 'mand'], undefined],\n  [['comc', 'lear'], undefined],\n  [['comp', 'iler'], undefined],\n  [['con', 'tinue'], undefined],\n  [['conf', 'irm'], undefined],\n  [['cons', 't'], LetCommand.argParser(true)],\n  [['cope', 'n'], succeed(new VsCodeCommand('workbench.panel.markers.view.focus'))],\n  [['cp', 'revious'], succeed(new VsCodeCommand('editor.action.marker.prevInFiles'))],\n  [['cpf', 'ile'], succeed(new VsCodeCommand('editor.action.marker.prevInFiles'))],\n  [['cq', 'uit'], undefined],\n  [['cr', 'ewind'], undefined],\n  [['cs', 'cope'], undefined],\n  [['cst', 'ag'], undefined],\n  [['cu', 'nmap'], undefined],\n  [['cuna', 'bbrev'], undefined],\n  [['cunme', 'nu'], undefined],\n  [['cw', 'indow'], succeed(new VsCodeCommand('workbench.panel.markers.view.focus'))],\n  [['d', 'elete'], DeleteCommand.argParser],\n  [['deb', 'ug'], undefined],\n  [['debugg', 'reedy'], undefined],\n  [['delc', 'ommand'], undefined],\n  [['delf', 'unction'], undefined],\n  [['delm', 'arks'], DeleteMarksCommand.argParser],\n  [['di', 'splay'], RegisterCommand.argParser],\n  [['dif', 'fupdate'], undefined],\n  [['diffg', 'et'], undefined],\n  [['diffo', 'ff'], undefined],\n  [['diffp', 'atch'], undefined],\n  [['diffpu', 't'], undefined],\n  [['diffs', 'plit'], undefined],\n  [['diffthis', ''], undefined],\n  [['dig', 'raphs'], DigraphsCommand.argParser],\n  [['dj', 'ump'], undefined],\n  [['dl', ''], undefined],\n  [['dli', 'st'], undefined],\n  [['do', 'autocmd'], undefined],\n  [['doautoa', 'll'], undefined],\n  [['dr', 'op'], undefined],\n  [['ds', 'earch'], undefined],\n  [['dsp', 'lit'], undefined],\n  [['e', 'dit'], FileCommand.argParsers.edit],\n  [['ea', 'rlier'], undefined],\n  [['ec', 'ho'], EchoCommand.argParser({ sep: ' ', error: false })],\n  [['echoe', 'rr'], EchoCommand.argParser({ sep: ' ', error: true })],\n  [['echoh', 'l'], undefined],\n  [['echom', 'sg'], undefined],\n  [['echon', ''], EchoCommand.argParser({ sep: '', error: false })],\n  [['el', 'se'], undefined],\n  [['elsei', 'f'], undefined],\n  [['em', 'enu'], undefined],\n  [['en', 'dif'], undefined],\n  [['endf', 'unction'], undefined],\n  [['endfo', 'r'], undefined],\n  [['endt', 'ry'], undefined],\n  [['endw', 'hile'], undefined],\n  [['ene', 'w'], FileCommand.argParsers.enew],\n  [['ev', 'al'], EvalCommand.argParser],\n  [['ex', ''], FileCommand.argParsers.edit],\n  [['exe', 'cute'], undefined],\n  [['exi', 't'], WriteQuitCommand.argParser],\n  [['Ex', 'plore'], succeed(new ExploreCommand())],\n  [['exu', 'sage'], undefined],\n  [['f', 'ile'], FileInfoCommand.argParser],\n  [['files', ''], undefined],\n  [['filet', 'ype'], undefined],\n  [['filt', 'er'], undefined],\n  [['fin', 'd'], undefined],\n  [['fina', 'lly'], undefined],\n  [['fini', 'sh'], undefined],\n  [['fir', 'st'], undefined],\n  [['fo', 'ld'], undefined],\n  [['foldc', 'lose'], undefined],\n  [['foldd', 'oopen'], undefined],\n  [['folddoc', 'losed'], undefined],\n  [['foldo', 'pen'], undefined],\n  [['for', ''], undefined],\n  [['fu', 'nction'], undefined],\n  [['g', 'lobal'], undefined],\n  [['go', 'to'], GotoCommand.argParser],\n  [['gr', 'ep'], GrepCommand.argParser],\n  [['grepa', 'dd'], undefined],\n  [['gu', 'i'], undefined],\n  [['gv', 'im'], undefined],\n  [['h', 'elp'], undefined],\n  [['ha', 'rdcopy'], undefined],\n  [['helpc', 'lose'], undefined],\n  [['helpg', 'rep'], undefined],\n  [['helpt', 'ags'], undefined],\n  [['hi', 'ghlight'], undefined],\n  [['hid', 'e'], undefined],\n  [['his', 'tory'], HistoryCommand.argParser],\n  [['i', 'nsert'], undefined],\n  [['ia', 'bbrev'], undefined],\n  [['iabc', 'lear'], undefined],\n  [['if', ''], undefined],\n  [['ij', 'ump'], undefined],\n  [['il', 'ist'], undefined],\n  [['im', 'ap'], undefined],\n  [['imapc', 'lear'], undefined],\n  [['ime', 'nu'], undefined],\n  [['ino', 'remap'], undefined],\n  [['inorea', 'bbrev'], undefined],\n  [['inoreme', 'nu'], undefined],\n  [['int', 'ro'], undefined],\n  [['is', 'earch'], undefined],\n  [['isp', 'lit'], undefined],\n  [['iu', 'nmap'], undefined],\n  [['iuna', 'bbrev'], undefined],\n  [['iunme', 'nu'], undefined],\n  [['j', 'oin'], undefined],\n  [['ju', 'mps'], succeed(new JumpsCommand())],\n  [['k', ''], undefined],\n  [['kee', 'pmarks'], undefined],\n  [['keepa', 'lt'], undefined],\n  [['keepj', 'umps'], undefined],\n  [['keepp', 'atterns'], undefined],\n  [['l', 'ist'], PrintCommand.argParser({ printNumbers: false, printText: true })],\n  [['lN', 'ext'], undefined],\n  [['lNf', 'ile'], undefined],\n  [['la', 'st'], undefined],\n  [['lab', 'ove'], undefined],\n  [['lad', 'dexpr'], undefined],\n  [['laddb', 'uffer'], undefined],\n  [['laddf', 'ile'], undefined],\n  [['laf', 'ter'], undefined],\n  [['lan', 'guage'], undefined],\n  [['lat', 'er'], undefined],\n  [['lb', 'uffer'], undefined],\n  [['lbef', 'ore'], undefined],\n  [['lbel', 'ow'], undefined],\n  [['lbo', 'ttom'], undefined],\n  [['lc', 'd'], undefined],\n  [['lch', 'dir'], undefined],\n  [['lcl', 'ose'], succeed(new VsCodeCommand('workbench.action.closePanel'))],\n  [['lcs', 'cope'], undefined],\n  [['ld', 'o'], undefined],\n  [['le', 'ft'], LeftCommand.argParser],\n  [['lefta', 'bove'], undefined],\n  [['let', ''], LetCommand.argParser(false)],\n  [['lex', 'pr'], undefined],\n  [['lf', 'ile'], undefined],\n  [['lfd', 'o'], undefined],\n  [['lfir', 'st'], undefined],\n  [['lg', 'etfile'], undefined],\n  [['lgetb', 'uffer'], undefined],\n  [['lgete', 'xpr'], undefined],\n  [['lgr', 'ep'], undefined],\n  [['lgrepa', 'dd'], undefined],\n  [['lh', 'elpgrep'], undefined],\n  [['lhi', 'story'], undefined],\n  [['ll', ''], undefined],\n  [['lla', 'st'], undefined],\n  [['lli', 'st'], undefined],\n  [['lm', 'ap'], undefined],\n  [['lmak', 'e'], undefined],\n  [['lmapc', 'lear'], undefined],\n  [['ln', 'oremap'], undefined],\n  [['lne', 'xt'], succeed(new VsCodeCommand('editor.action.nextCommentThreadAction'))],\n  [['lnew', 'er'], undefined],\n  [['lnf', 'ile'], undefined],\n  [['lo', 'adview'], undefined],\n  [['loadk', 'eymap'], undefined],\n  [['loc', 'kmarks'], undefined],\n  [['lockv', 'ar'], undefined],\n  [['lol', 'der'], undefined],\n  [['lope', 'n'], succeed(new VsCodeCommand('workbench.action.focusCommentsPanel'))],\n  [['lp', 'revious'], succeed(new VsCodeCommand('editor.action.previousCommentThreadAction'))],\n  [['lpf', 'ile'], undefined],\n  [['lr', 'ewind'], undefined],\n  [\n    ['ls', ''],\n    succeed(new VsCodeCommand('workbench.action.quickOpenLeastRecentlyUsedEditorInGroup')),\n  ],\n  [['lt', 'ag'], undefined],\n  [['lu', 'nmap'], undefined],\n  [['lua', ''], undefined],\n  [['luad', 'o'], undefined],\n  [['luaf', 'ile'], undefined],\n  [['lv', 'imgrep'], undefined],\n  [['lvimgrepa', 'dd'], undefined],\n  [['lw', 'indow'], succeed(new VsCodeCommand('workbench.action.focusCommentsPanel'))],\n  [['m', 'ove'], MoveCommand.argParser],\n  [['ma', 'rk'], MarkCommand.argParser],\n  [['mak', 'e'], undefined],\n  [['map', ''], undefined],\n  [['mapc', 'lear'], undefined],\n  [['marks', ''], MarksCommand.argParser],\n  [['mat', 'ch'], undefined],\n  [['me', 'nu'], undefined],\n  [['menut', 'ranslate'], undefined],\n  [['mes', 'sages'], undefined],\n  [['mk', 'exrc'], undefined],\n  [['mks', 'ession'], undefined],\n  [['mksp', 'ell'], undefined],\n  [['mkv', 'imrc'], undefined],\n  [['mkvie', 'w'], undefined],\n  [['mod', 'e'], undefined],\n  [['n', 'ext'], undefined],\n  [['new', ''], FileCommand.argParsers.new],\n  [['nm', 'ap'], undefined],\n  [['nmapc', 'lear'], undefined],\n  [['nme', 'nu'], undefined],\n  [['nn', 'oremap'], undefined],\n  [['nnoreme', 'nu'], undefined],\n  [['no', 'remap'], undefined],\n  [['noa', 'utocmd'], undefined],\n  [['noh', 'lsearch'], succeed(new NohlCommand())],\n  [['norea', 'bbrev'], undefined],\n  [['noreme', 'nu'], undefined],\n  [['norm', 'al'], NormalCommand.argParser],\n  [['nos', 'wapfile'], undefined],\n  [['nu', 'mber'], PrintCommand.argParser({ printNumbers: true, printText: true })],\n  [['nun', 'map'], undefined],\n  [['nunme', 'nu'], undefined],\n  [['ol', 'dfiles'], undefined],\n  [['om', 'ap'], undefined],\n  [['omapc', 'lear'], undefined],\n  [['ome', 'nu'], undefined],\n  [['on', 'ly'], succeed(new OnlyCommand())],\n  [['ono', 'remap'], undefined],\n  [['onoreme', 'nu'], undefined],\n  [['opt', 'ions'], undefined],\n  [['ou', 'nmap'], undefined],\n  [['ounme', 'nu'], undefined],\n  [['ow', 'nsyntax'], undefined],\n  [['p', 'rint'], PrintCommand.argParser({ printNumbers: false, printText: true })],\n  [['pa', 'ckadd'], undefined],\n  [['packl', 'oadall'], undefined],\n  [['pc', 'lose'], undefined],\n  [['pe', 'rl'], undefined],\n  [['ped', 'it'], undefined],\n  [['perld', 'o'], undefined],\n  [['perlf', 'ile'], undefined],\n  [['po', 'p'], undefined],\n  [['popu', 'p'], undefined],\n  [['pp', 'op'], undefined],\n  [['pre', 'serve'], undefined],\n  [['prev', 'ious'], undefined],\n  [['prof', 'ile'], undefined],\n  [['profd', 'el'], undefined],\n  [['ps', 'earch'], undefined],\n  [['pt', 'ag'], undefined],\n  [['ptN', 'ext'], undefined],\n  [['ptf', 'irst'], undefined],\n  [['ptj', 'ump'], undefined],\n  [['ptl', 'ast'], undefined],\n  [['ptn', 'ext'], undefined],\n  [['ptp', 'revious'], undefined],\n  [['ptr', 'ewind'], undefined],\n  [['pts', 'elect'], undefined],\n  [['pu', 't'], PutExCommand.argParser],\n  [['pw', 'd'], succeed(new PwdCommand())],\n  [['py', 'thon'], undefined],\n  [['py3', ''], undefined],\n  [['py3d', 'o'], undefined],\n  [['py3f', 'ile'], undefined],\n  [['pyd', 'o'], undefined],\n  [['pyf', 'ile'], undefined],\n  [['python3', ''], undefined],\n  [['pythonx', ''], undefined],\n  [['pyx', ''], undefined],\n  [['pyxd', 'o'], undefined],\n  [['pyxf', 'ile'], undefined],\n  [['q', 'uit'], QuitCommand.argParser(false)],\n  [['qa', 'll'], QuitCommand.argParser(true)],\n  [['quita', 'll'], QuitCommand.argParser(true)],\n  [['r', 'ead'], ReadCommand.argParser],\n  [['rec', 'over'], undefined],\n  [['red', 'o'], RedoCommand.argParser],\n  [['redi', 'r'], undefined],\n  [['redr', 'aw'], undefined],\n  [['redraws', 'tatus'], undefined],\n  [['redrawt', 'abline'], undefined],\n  [['reg', 'isters'], RegisterCommand.argParser],\n  [['res', 'ize'], undefined],\n  [['ret', 'ab'], RetabCommand.argParser],\n  [['retu', 'rn'], undefined],\n  [['rew', 'ind'], undefined],\n  [['ri', 'ght'], RightCommand.argParser],\n  [['rightb', 'elow'], undefined],\n  [['rsh', 'ada'], undefined],\n  [['ru', 'ntime'], undefined],\n  [['rub', 'y'], undefined],\n  [['rubyd', 'o'], undefined],\n  [['rubyf', 'ile'], undefined],\n  [['rund', 'o'], undefined],\n  [['s', 'ubstitute'], SubstituteCommand.argParser],\n  [['sN', 'ext'], undefined],\n  [['sa', 'rgument'], undefined],\n  [['sal', 'l'], undefined],\n  [['san', 'dbox'], undefined],\n  [['sav', 'eas'], undefined],\n  [['sb', 'uffer'], undefined],\n  [['sbN', 'ext'], undefined],\n  [['sba', 'll'], undefined],\n  [['sbf', 'irst'], undefined],\n  [['sbl', 'ast'], undefined],\n  [['sbm', 'odified'], undefined],\n  [['sbn', 'ext'], undefined],\n  [['sbp', 'revious'], undefined],\n  [['sbr', 'ewind'], undefined],\n  [['scr', 'iptnames'], undefined],\n  [['scripte', 'ncoding'], undefined],\n  [['scs', 'cope'], undefined],\n  [['se', 't'], SetCommand.argParser],\n  [['setf', 'iletype'], undefined],\n  [['setg', 'lobal'], undefined],\n  [['setl', 'ocal'], undefined],\n  [['sf', 'ind'], undefined],\n  [['sfir', 'st'], undefined],\n  [['sh', 'ell'], succeed(new ShCommand())], // Taken from Vim; not in nvim\n  [['sig', 'n'], undefined],\n  [['sil', 'ent'], undefined],\n  [['sl', 'eep'], undefined],\n  [['sla', 'st'], undefined],\n  [['sm', 'agic'], undefined],\n  [['smap', ''], undefined],\n  [['smapc', 'lear'], undefined],\n  [['sme', 'nu'], undefined],\n  [['smile', ''], succeed(new SmileCommand())], // Taken from Vim; not in nvim\n  [['sn', 'ext'], undefined],\n  [['sno', 'magic'], undefined],\n  [['snor', 'emap'], undefined],\n  [['snoreme', 'nu'], undefined],\n  [['so', 'urce'], undefined],\n  [['sor', 't'], SortCommand.argParser],\n  [['sp', 'lit'], FileCommand.argParsers.split],\n  [['spe', 'llgood'], undefined],\n  [['spelld', 'ump'], undefined],\n  [['spelli', 'nfo'], undefined],\n  [['spellr', 'epall'], undefined],\n  [['spellra', 're'], undefined],\n  [['spellu', 'ndo'], undefined],\n  [['spellw', 'rong'], undefined],\n  [['spr', 'evious'], undefined],\n  [['sre', 'wind'], undefined],\n  [['st', 'op'], undefined],\n  [['sta', 'g'], undefined],\n  [['star', 'tinsert'], undefined],\n  [['startg', 'replace'], undefined],\n  [['startr', 'eplace'], undefined],\n  [['stj', 'ump'], undefined],\n  [['stopi', 'nsert'], undefined],\n  [['sts', 'elect'], undefined],\n  [['sun', 'hide'], undefined],\n  [['sunm', 'ap'], undefined],\n  [['sunme', 'nu'], undefined],\n  [['sus', 'pend'], undefined],\n  [['sv', 'iew'], undefined],\n  [['sw', 'apname'], undefined],\n  [['sy', 'ntax'], undefined],\n  [['sync', 'bind'], undefined],\n  [['synti', 'me'], undefined],\n  [['t', ''], CopyCommand.argParser],\n  [['tN', 'ext'], undefined],\n  [['ta', 'g'], undefined],\n  [['tab', ''], undefined],\n  [['tabN', 'ext'], TabCommand.argParsers.bprev],\n  [['tabc', 'lose'], TabCommand.argParsers.tabclose],\n  [['tabdo', ''], undefined],\n  [['tabe', 'dit'], TabCommand.argParsers.tabnew],\n  [['tabf', 'ind'], undefined],\n  [['tabfir', 'st'], TabCommand.argParsers.bfirst],\n  [['tabl', 'ast'], TabCommand.argParsers.blast],\n  [['tabm', 'ove'], TabCommand.argParsers.tabmove],\n  [['tabn', 'ext'], TabCommand.argParsers.bnext],\n  [['tabnew', ''], TabCommand.argParsers.tabnew],\n  [['tabo', 'nly'], TabCommand.argParsers.tabonly],\n  [['tabp', 'revious'], TabCommand.argParsers.bprev],\n  [['tabr', 'ewind'], TabCommand.argParsers.bfirst],\n  [['tabs', ''], undefined],\n  [['tags', ''], undefined],\n  [['tc', 'd'], undefined],\n  [['tch', 'dir'], undefined],\n  [['te', 'rminal'], TerminalCommand.argParser],\n  [['tf', 'irst'], undefined],\n  [['th', 'row'], undefined],\n  [['tj', 'ump'], undefined],\n  [['tl', 'ast'], undefined],\n  [['tm', 'enu'], undefined],\n  [['tma', 'p'], undefined],\n  [['tmapc', 'lear'], undefined],\n  [['tn', 'ext'], undefined],\n  [['tno', 'remap'], undefined],\n  [['to', 'pleft'], undefined],\n  [['tp', 'revious'], undefined],\n  [['tr', 'ewind'], undefined],\n  [['try', ''], undefined],\n  [['ts', 'elect'], undefined],\n  [['tu', 'nmenu'], undefined],\n  [['tunma', 'p'], undefined],\n  [['u', 'ndo'], UndoCommand.argParser],\n  [['una', 'bbreviate'], undefined],\n  [['undoj', 'oin'], undefined],\n  [['undol', 'ist'], undefined],\n  [['unh', 'ide'], undefined],\n  [['unl', 'et'], UnletCommand.argParser],\n  [['unlo', 'ckvar'], undefined],\n  [['unm', 'ap'], undefined],\n  [['unme', 'nu'], undefined],\n  [['uns', 'ilent'], undefined],\n  [['up', 'date'], WriteCommand.argParser],\n  [['v', 'global'], undefined],\n  [['ve', 'rsion'], undefined],\n  [['verb', 'ose'], undefined],\n  [['vert', 'ical'], undefined],\n  [['vi', 'sual'], undefined],\n  [['vie', 'w'], undefined],\n  [['vim', 'grep'], GrepCommand.argParser],\n  [['vimgrepa', 'dd'], undefined],\n  [['viu', 'sage'], undefined],\n  [['vm', 'ap'], undefined],\n  [['vmapc', 'lear'], undefined],\n  [['vme', 'nu'], undefined],\n  [['vn', 'oremap'], undefined],\n  [['vne', 'w'], FileCommand.argParsers.vnew],\n  [['vnoreme', 'nu'], undefined],\n  [['vs', 'plit'], FileCommand.argParsers.vsplit],\n  [['vsc', 'ode'], VsCodeCommand.argParser], // Special: run VS Code command\n  [['vu', 'nmap'], undefined],\n  [['vunme', 'nu'], undefined],\n  [['w', 'rite'], WriteCommand.argParser],\n  [['wN', 'ext'], undefined],\n  [['wa', 'll'], WallCommand.argParser],\n  [['wh', 'ile'], undefined],\n  [['wi', 'nsize'], undefined],\n  [['winc', 'md'], undefined],\n  [['windo', ''], undefined],\n  [['winp', 'os'], undefined],\n  [['wn', 'ext'], undefined],\n  [['wp', 'revious'], undefined],\n  [['wq', ''], WriteQuitCommand.argParser],\n  [['wqa', 'll'], WriteQuitAllCommand.argParser],\n  [['wsh', 'ada'], undefined],\n  [['wu', 'ndo'], undefined],\n  [['x', 'it'], WriteQuitCommand.argParser],\n  [['xa', 'll'], WriteQuitAllCommand.argParser],\n  [['xm', 'ap'], undefined],\n  [['xmapc', 'lear'], undefined],\n  [['xme', 'nu'], undefined],\n  [['xn', 'oremap'], undefined],\n  [['xnoreme', 'nu'], undefined],\n  [['xu', 'nmap'], undefined],\n  [['xunme', 'nu'], undefined],\n  [['y', 'ank'], YankCommand.argParser],\n  [['z', ''], undefined],\n  [['~', ''], undefined],\n];\n\nclass UnimplementedCommand extends ExCommand {\n  name: string;\n\n  public override neovimCapable(): boolean {\n    // If the user has neovim integration enabled, don't stop them from using these commands\n    return true;\n  }\n\n  constructor(name: string) {\n    super();\n    this.name = name;\n  }\n\n  async execute(vimState: VimState): Promise<void> {\n    StatusBar.setText(\n      vimState,\n      `Command :${this.name} is not yet implemented (PRs are welcome!)`,\n      true,\n    );\n  }\n\n  override async executeWithRange(vimState: VimState, range: LineRange): Promise<void> {\n    await this.execute(vimState);\n  }\n}\n\nexport class NoOpCommand extends ExCommand {\n  async execute(vimState: VimState): Promise<void> {\n    // nothing\n  }\n}\n\nfunction nameParser(\n  name: [string, string],\n  argParser: ArgParser | undefined,\n): Parser<[string, Parser<ExCommand>]> {\n  argParser ??= all.result(new UnimplementedCommand(name[1] ? `${name[0]}[${name[1]}]` : name[0]));\n\n  const fullName = name[0] + name[1];\n  let parser = nameAbbrevParser(name[0], name[1]).result(argParser);\n  if (fullName === '' || /[a-z]$/i.test(fullName)) {\n    parser = parser.notFollowedBy(regexp(/[a-z]/i));\n  }\n  return parser.map((p) => [fullName, p]);\n}\n\nconst commandNameParser: Parser<[string, Parser<ExCommand>] | undefined> = alt(\n  ...[...builtinExCommands]\n    .reverse()\n    .map(([name, argParser]) => nameParser(name, argParser?.skip(optWhitespace))),\n);\n\nexport const exCommandParser: Parser<{\n  name: string;\n  lineRange: LineRange | undefined;\n  command: ExCommand;\n}> = optWhitespace\n  .then(string(':').skip(optWhitespace).many())\n  .then(\n    seq(\n      LineRange.parser.fallback(undefined),\n      optWhitespace,\n      commandNameParser.fallback(undefined),\n      all,\n    ),\n  )\n  .map(([lineRange, whitespace, fullNameAndArgParser, args]) => {\n    if (fullNameAndArgParser === undefined) {\n      throw VimError.NotAnEditorCommand(`${lineRange?.toString() ?? ''}${whitespace}${args}`);\n    }\n    const [name, argParser] = fullNameAndArgParser;\n    if (name === '' && lineRange === undefined && args !== '') {\n      // HACK: Handle edge cases like `:^`, which will be incorrectly parsed as a GotoLineCommand\n      throw VimError.NotAnEditorCommand(`${whitespace}${args}`);\n    }\n    const result = seq(argParser, optWhitespace.then(all)).parse(args);\n    if (result.status === false) {\n      if (result.index.offset === args.length) {\n        throw VimError.ArgumentRequired();\n      }\n      throw VimError.InvalidArgument474();\n    }\n    const [command, trailing] = result.value;\n    if (trailing) {\n      // TODO: Implement `:help :bar`\n      throw VimError.TrailingCharacters(trailing);\n    }\n    return { name, lineRange, command };\n  });\n"
  },
  {
    "path": "src/vimscript/expression/build.ts",
    "content": "import {\n  BinaryExpression,\n  BinaryOp,\n  BlobExpression,\n  BlobValue,\n  DictionaryExpression,\n  DictionaryValue,\n  Expression,\n  FloatExpression,\n  FloatValue,\n  FuncrefCallExpression,\n  FuncRefExpression,\n  FuncRefValue,\n  FunctionCallExpression,\n  LambdaExpression,\n  ListExpression,\n  ListValue,\n  Namespace,\n  NumberExpression,\n  NumberValue,\n  StringExpression,\n  StringValue,\n  UnaryExpression,\n  Value,\n  VariableExpression,\n} from './types';\n\nclass UniqueIdGenerator {\n  private prefix: string;\n  private nextId: number = 1;\n\n  constructor(prefix: string) {\n    this.prefix = prefix;\n  }\n\n  public next(): string {\n    return `${this.prefix}${this.nextId++}`;\n  }\n}\n\nconst listIdGen = new UniqueIdGenerator('list');\nconst dictIdGen = new UniqueIdGenerator('dict');\nconst funcIdGen = new UniqueIdGenerator('func');\nconst blobIdGen = new UniqueIdGenerator('blob');\n\nexport function int(value: number): NumberValue {\n  return {\n    type: 'number',\n    value: Math.trunc(value),\n  };\n}\n\nexport function float(value: number): FloatValue {\n  return {\n    type: 'float',\n    value,\n  };\n}\n\nexport function bool(value: boolean): NumberValue {\n  return int(value ? 1 : 0);\n}\n\nexport function str(value: string): StringValue {\n  return {\n    type: 'string',\n    value,\n  };\n}\n\nexport function list(items: Value[]): ListValue {\n  return {\n    type: 'list',\n    items,\n    id: listIdGen.next(),\n  };\n}\n\nexport function dictionary(items: Map<string, Value>): DictionaryValue {\n  return {\n    type: 'dictionary',\n    items,\n    id: dictIdGen.next(),\n  };\n}\n\nexport function funcref(args: {\n  name: string;\n  body?: (args: Value[]) => Value;\n  arglist?: ListValue;\n  dict?: DictionaryValue;\n}): FuncRefValue {\n  return {\n    type: 'funcref',\n    ...args,\n    id: funcIdGen.next(),\n  };\n}\n\nexport function blob(data: Uint8Array<ArrayBuffer>): BlobValue {\n  return {\n    type: 'blob',\n    data,\n    id: blobIdGen.next(),\n  };\n}\n\nexport function toExpr(value: NumberValue): NumberExpression;\nexport function toExpr(value: FloatValue): FloatExpression;\nexport function toExpr(value: StringValue): StringExpression;\nexport function toExpr(value: ListValue): ListExpression;\nexport function toExpr(value: DictionaryValue): DictionaryExpression;\nexport function toExpr(value: FuncRefValue): FuncRefExpression;\nexport function toExpr(value: BlobValue): BlobExpression;\nexport function toExpr(value: Value): Expression;\nexport function toExpr(value: Value): Expression {\n  if (value.type === 'number' || value.type === 'float' || value.type === 'string') {\n    return value;\n  } else if (value.type === 'list') {\n    return listExpr(value.items.map(toExpr));\n  } else if (value.type === 'dictionary') {\n    return {\n      type: 'dictionary',\n      items: [...value.items.entries()].map(([key, val]) => [str(key), toExpr(val)]),\n    };\n  } else if (value.type === 'funcref') {\n    return {\n      type: 'funcref',\n      name: value.name,\n      body: value.body,\n      arglist: value.arglist ? toExpr(value.arglist) : undefined,\n      dict: value.dict ? toExpr(value.dict) : undefined,\n    };\n  } else if (value.type === 'blob') {\n    return {\n      type: 'blob',\n      data: value.data,\n    };\n  }\n  const guard: never = value;\n  throw new Error(`Unknown value type in toExpr()`);\n}\n\nexport function listExpr(items: Expression[]): ListExpression {\n  return {\n    type: 'list',\n    items,\n  };\n}\n\nexport function variable(name: string, namespace?: Namespace): VariableExpression {\n  return {\n    type: 'variable',\n    name,\n    namespace,\n  };\n}\n\nexport function lambda(args: string[], body: Expression): LambdaExpression {\n  return {\n    type: 'lambda',\n    args,\n    body,\n  };\n}\n\nexport function negative(operand: Expression): UnaryExpression {\n  return {\n    type: 'unary',\n    operator: '-',\n    operand,\n  };\n}\n\nexport function positive(operand: Expression): UnaryExpression {\n  return {\n    type: 'unary',\n    operator: '+',\n    operand,\n  };\n}\n\nexport function binary(lhs: Expression, operator: BinaryOp, rhs: Expression): BinaryExpression {\n  return {\n    type: 'binary',\n    operator,\n    lhs,\n    rhs,\n  };\n}\n\nexport function add(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '+', rhs);\n}\n\nexport function subtract(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '-', rhs);\n}\n\nexport function multiply(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '*', rhs);\n}\n\nexport function divide(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '/', rhs);\n}\n\nexport function modulo(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '%', rhs);\n}\n\nexport function concat(lhs: Expression, rhs: Expression): BinaryExpression {\n  return binary(lhs, '..', rhs);\n}\n\nexport function funcCall(func: string, args: Expression[]): FunctionCallExpression {\n  return {\n    type: 'function_call',\n    func,\n    args,\n  };\n}\n\nexport function funcrefCall(expression: Expression, args: Expression[]): FuncrefCallExpression {\n  return {\n    type: 'funcrefCall',\n    expression,\n    args,\n  };\n}\n"
  },
  {
    "path": "src/vimscript/expression/displayValue.ts",
    "content": "import { Value } from './types';\n\nexport function displayValue(value: Value): string {\n  return _displayValue(value, true);\n}\n\nfunction _displayValue(value: Value, topLevel = false): string {\n  switch (value.type) {\n    case 'number':\n      return value.value.toString();\n    case 'float': {\n      // TODO: this is incorrect for float with exponent\n      const result = value.value.toFixed(6).replace(/0*$/, '');\n      if (result.endsWith('.')) {\n        return result + '0';\n      }\n      return result;\n    }\n    case 'string':\n      return topLevel ? value.value : `'${value.value.replace(\"'\", \"''\")}'`;\n    case 'list':\n      return `[${value.items.map((v) => _displayValue(v, false)).join(', ')}]`;\n    case 'dictionary':\n      return `{${[...value.items]\n        .map(([k, v]) => `'${k}': ${_displayValue(v, false)}`)\n        .join(', ')}}`;\n    case 'funcref':\n      if (!value.arglist?.items.length) {\n        if (value.dict) {\n          return `function('${value.name}', ${_displayValue(value.dict)})`;\n        }\n        return value.name;\n      } else {\n        if (value.dict) {\n          return `function('${value.name}', ${_displayValue(value.arglist)}, ${_displayValue(\n            value.dict,\n          )})`;\n        }\n        return `function('${value.name}', ${_displayValue(value.arglist)})`;\n      }\n    case 'blob':\n      return (\n        '0z' +\n        [...new Uint8Array(value.data)]\n          .map((byte) => byte.toString(16).padStart(2, '0'))\n          .join('')\n          .toUpperCase()\n      );\n  }\n}\n"
  },
  {
    "path": "src/vimscript/expression/evaluate.ts",
    "content": "import { escapeRegExp, isInteger } from 'lodash';\nimport { all, alt } from 'parsimmon';\nimport { Position } from 'vscode';\nimport { configuration } from '../../configuration/configuration';\nimport { VimError } from '../../error';\nimport { Mode } from '../../mode/mode';\nimport { Register, RegisterMode } from '../../register/register';\nimport { globalState } from '../../state/globalState';\nimport { RecordedState } from '../../state/recordedState';\nimport { VimState } from '../../state/vimState';\nimport { Pattern, SearchDirection } from '../pattern';\nimport {\n  blob,\n  bool,\n  dictionary,\n  float,\n  funcCall,\n  funcref,\n  funcrefCall,\n  int,\n  list,\n  str,\n  toExpr,\n  variable,\n} from './build';\nimport { displayValue } from './displayValue';\nimport { expressionParser, floatParser, numberParser } from './parser';\nimport {\n  BinaryOp,\n  ComparisonOp,\n  DictionaryValue,\n  Expression,\n  FloatValue,\n  FuncRefValue,\n  FunctionCallExpression,\n  ListValue,\n  NumberValue,\n  UnaryOp,\n  Value,\n  VariableExpression,\n} from './types';\n\n// ID of next lambda; incremented each time one is created\nlet lambdaNumber = 1;\n\nexport function toInt(value: Value): number {\n  switch (value.type) {\n    case 'number':\n      return value.value;\n    case 'float':\n      throw VimError.UsingAFloatAsANumber();\n    case 'string':\n      const parsed = numberParser.skip(all).parse(value.value);\n      if (parsed.status === false) {\n        return 0;\n      }\n      return parsed.value.value;\n    case 'list':\n      throw VimError.UsingAListAsANumber();\n    case 'dictionary':\n      throw VimError.UsingADictionaryAsANumber();\n    case 'funcref':\n      throw VimError.UsingAFuncrefAsANumber();\n    case 'blob':\n      throw VimError.UsingABlobAsANumber();\n  }\n}\n\nfunction toFloat(value: Value): number {\n  switch (value.type) {\n    case 'number':\n      return value.value;\n    case 'float':\n      return value.value;\n    case 'string':\n    case 'list':\n    case 'dictionary':\n    case 'funcref':\n    case 'blob':\n      throw VimError.NumberOrFloatRequired();\n  }\n}\n\nexport function toString(value: Value): string {\n  switch (value.type) {\n    case 'number':\n      return value.value.toString();\n    case 'float':\n      throw VimError.UsingFloatAsAString();\n    case 'string':\n      return value.value;\n    case 'list':\n      throw VimError.UsingListAsAString();\n    case 'dictionary':\n      throw VimError.UsingDictionaryAsAString();\n    case 'funcref':\n      throw VimError.UsingFuncrefAsAString();\n    case 'blob':\n      return displayValue(value);\n  }\n}\n\nfunction toList(value: Value): ListValue {\n  switch (value.type) {\n    case 'number':\n    case 'float':\n    case 'string':\n    case 'funcref':\n    case 'dictionary':\n    case 'blob':\n      throw VimError.ListRequired();\n    case 'list':\n      return value;\n  }\n}\n\nfunction toDict(value: Value): DictionaryValue {\n  switch (value.type) {\n    case 'number':\n    case 'float':\n    case 'string':\n    case 'list':\n    case 'funcref':\n    case 'blob':\n      throw VimError.DictionaryRequired();\n    case 'dictionary':\n      return value;\n  }\n}\n\nfunction mapNumber(value: Value, f: (x: number) => number): NumberValue | FloatValue {\n  switch (value.type) {\n    case 'float':\n      return float(f(value.value));\n    default:\n      return int(f(toInt(value)));\n  }\n}\n\nexport class Variable {\n  public value: Value;\n  public locked: boolean = false;\n\n  constructor(value: Value, locked: boolean = false) {\n    this.value = value;\n    this.locked = locked;\n  }\n}\n\nexport type VariableStore = Map<string, Variable>;\n\nexport class EvaluationContext {\n  private static globalVariables: VariableStore = new Map();\n\n  private vimState: VimState | undefined;\n  private localScopes: VariableStore[] = [];\n  private errors: string[] = [];\n\n  constructor(vimState: VimState | undefined) {\n    this.vimState = vimState;\n  }\n\n  /**\n   * Fully evaluates the given expression and returns the resulting value.\n   * May throw a variety of VimErrors if the expression is semantically invalid.\n   */\n  public evaluate(expression: Expression): Value {\n    switch (expression.type) {\n      case 'number':\n      case 'float':\n      case 'string':\n        return expression;\n      case 'blob':\n        return blob(expression.data);\n      case 'list':\n        return list(expression.items.map((x) => this.evaluate(x)));\n      case 'dictionary': {\n        const items = new Map<string, Value>();\n        for (const [key, val] of expression.items) {\n          const keyStr = toString(this.evaluate(key));\n          if (items.has(keyStr)) {\n            throw VimError.DuplicateKeyInDictionary(keyStr);\n          } else {\n            items.set(keyStr, this.evaluate(val));\n          }\n        }\n        return dictionary(items);\n      }\n      case 'funcref':\n        const arglist = expression.arglist\n          ? list(expression.arglist.items.map((e) => this.evaluate(e)))\n          : undefined;\n        const dict = expression.dict\n          ? dictionary(\n              new Map(\n                [...expression.dict.items].map(([k, v]) => [\n                  toString(this.evaluate(k)),\n                  this.evaluate(v),\n                ]),\n              ),\n            )\n          : undefined;\n        return funcref({ name: expression.name, body: expression.body, arglist, dict });\n      case 'variable':\n        return this.evaluateVariable(expression);\n      case 'register':\n        const reg = Register.getSync(expression.name);\n        if (reg === undefined || reg.text instanceof RecordedState) {\n          return str(''); // TODO: Handle RecordedState?\n        }\n        return str(reg.text);\n      case 'option':\n        return str(''); // TODO\n      case 'env_variable':\n        return str(process.env[expression.name] ?? '');\n      case 'function_call':\n        return this.evaluateFunctionCall(expression);\n      case 'index': {\n        return this.evaluateIndex(\n          this.evaluate(expression.expression),\n          this.evaluate(expression.index),\n        );\n      }\n      case 'slice': {\n        return this.evaluateSlice(\n          this.evaluate(expression.expression),\n          expression.start ? this.evaluate(expression.start) : int(0),\n          expression.end ? this.evaluate(expression.end) : int(-1),\n        );\n      }\n      case 'entry': {\n        const entry = toDict(this.evaluate(expression.expression)).items.get(expression.entryName);\n        if (!entry) {\n          throw VimError.KeyNotPresentInDictionary(expression.entryName);\n        }\n        return entry;\n      }\n      case 'funcrefCall': {\n        const fref = this.evaluate(expression.expression);\n        if (fref.type !== 'funcref') {\n          // TODO\n          throw new Error(`Expected funcref, got ${fref.type}`);\n        }\n        // TODO: use `fref.dict`\n        if (fref.body) {\n          return fref.body(expression.args.map((x) => this.evaluate(x)));\n        } else {\n          return this.evaluateFunctionCall(\n            funcCall(fref.name, (fref.arglist?.items.map(toExpr) ?? []).concat(expression.args)),\n          );\n        }\n      }\n      case 'methodCall': {\n        const obj = this.evaluate(expression.expression);\n        return this.evaluateFunctionCall(\n          funcCall(expression.methodName, [toExpr(obj), ...expression.args]),\n        );\n      }\n      case 'lambda': {\n        return funcref({\n          name: `<lambda>${lambdaNumber++}`,\n          arglist: list([]),\n          body: (args: Value[]) => {\n            // TODO: handle wrong # of args\n            const store: VariableStore = new Map();\n            for (let i = 0; i < args.length; i++) {\n              store.set(expression.args[i], new Variable(args[i]));\n            }\n\n            this.localScopes.push(store);\n            const retval = this.evaluate(expression.body);\n            this.localScopes.pop();\n            return retval;\n          },\n        });\n      }\n      case 'unary':\n        return this.evaluateUnary(expression.operator, expression.operand);\n      case 'binary':\n        return this.evaluateBinary(expression.operator, expression.lhs, expression.rhs);\n      case 'ternary':\n        return this.evaluate(\n          toInt(this.evaluate(expression.if)) !== 0 ? expression.then : expression.else,\n        );\n      case 'comparison':\n        return bool(\n          this.evaluateComparison(\n            expression.operator,\n            expression.matchCase ?? configuration.ignorecase,\n            this.evaluate(expression.lhs),\n            this.evaluate(expression.rhs),\n          ),\n        );\n      default: {\n        const guard: never = expression;\n        throw new Error(`evaluate() got unexpected expression type`);\n      }\n    }\n  }\n\n  public setVariable(varExpr: VariableExpression, value: Value, lock: boolean): void {\n    if (value.type === 'funcref' && varExpr.name[0] === varExpr.name[0].toLowerCase()) {\n      throw VimError.FuncrefVariableNameMustStartWithACapital(varExpr.name);\n    }\n\n    const store = this.getVariableStore(varExpr.namespace);\n\n    if (store) {\n      const _var = store.get(varExpr.name);\n      if (_var) {\n        if (lock) {\n          throw VimError.CannotModifyExistingVariable();\n        }\n        if (_var.locked) {\n          throw VimError.ValueIsLocked(varExpr.name);\n        }\n        _var.value = value;\n      } else {\n        store.set(varExpr.name, new Variable(value, lock));\n      }\n    }\n  }\n\n  private evaluateVariable(varExpr: VariableExpression): Value {\n    if (varExpr.namespace === undefined) {\n      for (let i = this.localScopes.length - 1; i >= 0; i--) {\n        const _var = this.localScopes[i].get(varExpr.name);\n        if (_var !== undefined) {\n          return _var.value;\n        }\n      }\n    }\n\n    if (varExpr.namespace === 'g' || varExpr.namespace === undefined) {\n      const _var = EvaluationContext.globalVariables.get(varExpr.name);\n      if (_var === undefined) {\n        throw VimError.UndefinedVariable(\n          varExpr.namespace ? `${varExpr.namespace}:${varExpr.name}` : varExpr.name,\n        );\n      } else {\n        return _var.value;\n      }\n    } else if (varExpr.namespace === 'v') {\n      // TODO: v:count, v:count1, v:prevcount\n      // TODO: v:operator\n      // TODO: v:register\n      // TODO: v:statusmsg, v:warningmsg, v:errmsg\n      if (varExpr.name === 'true') {\n        return bool(true);\n      } else if (varExpr.name === 'false') {\n        return bool(false);\n      } else if (varExpr.name === 'hlsearch') {\n        return bool(globalState.hl);\n      } else if (varExpr.name === 't_number') {\n        return int(0);\n      } else if (varExpr.name === 't_string') {\n        return int(1);\n      } else if (varExpr.name === 't_func') {\n        return int(2);\n      } else if (varExpr.name === 't_list') {\n        return int(3);\n      } else if (varExpr.name === 't_dict') {\n        return int(4);\n      } else if (varExpr.name === 't_float') {\n        return int(5);\n      } else if (varExpr.name === 't_bool') {\n        return int(6);\n      } else if (varExpr.name === 't_blob') {\n        return int(10);\n      } else if (varExpr.name === 'numbermax') {\n        return int(Number.MAX_SAFE_INTEGER);\n      } else if (varExpr.name === 'numbermin') {\n        return int(Number.MIN_SAFE_INTEGER);\n      } else if (varExpr.name === 'numbersize') {\n        // NOTE: In VimScript this refers to a 64 bit integer; we have a 64 bit float because JavaScript\n        return int(64);\n      } else if (varExpr.name === 'errors') {\n        return list(this.errors.map(str));\n      } else if (varExpr.name === 'searchforward') {\n        return int(globalState.searchState?.direction === SearchDirection.Backward ? 0 : 1);\n      }\n\n      // HACK: for things like v:key & v:val\n      return this.evaluate(variable(`v:${varExpr.name}`));\n    }\n\n    throw VimError.UndefinedVariable(\n      varExpr.namespace ? `${varExpr.namespace}:${varExpr.name}` : varExpr.name,\n    );\n  }\n\n  public getVariableStore(namespace: string | undefined): VariableStore | undefined {\n    if (this.localScopes.length > 0 && namespace === undefined) {\n      return this.localScopes.at(-1);\n    } else if (namespace === 'g' || namespace === undefined) {\n      return EvaluationContext.globalVariables;\n    }\n    // TODO\n    return undefined;\n  }\n\n  private evaluateIndex(sequence: Value, index: Value): Value {\n    switch (sequence.type) {\n      case 'string':\n      case 'number':\n      case 'float': {\n        const idx = toInt(index);\n        return str(idx >= 0 ? (toString(sequence)[idx] ?? '') : '');\n      }\n      case 'list': {\n        let idx = toInt(index);\n        idx = idx < 0 ? sequence.items.length - idx : idx;\n        if (idx < 0 || idx >= sequence.items.length) {\n          throw VimError.ListIndexOutOfRange(idx);\n        }\n        return sequence.items[idx];\n      }\n      case 'dictionary': {\n        const key = toString(index);\n        const result = sequence.items.get(key);\n        if (result === undefined) {\n          throw VimError.KeyNotPresentInDictionary(key);\n        }\n        return result;\n      }\n      case 'funcref': {\n        throw VimError.CannotIndexAFuncref();\n      }\n      case 'blob': {\n        const bytes = new Uint8Array(sequence.data);\n        return int(bytes[toInt(index)]);\n      }\n    }\n  }\n\n  private evaluateSlice(sequence: Value, start: Value, end: Value): Value {\n    let _start = toInt(start);\n    let _end = toInt(end);\n    switch (sequence.type) {\n      case 'string':\n      case 'number':\n      case 'float': {\n        const _sequence = toString(sequence);\n        while (_start < 0) {\n          _start += _sequence.length;\n        }\n        while (_end < 0) {\n          _end += _sequence.length;\n        }\n        if (_end < _start) {\n          return str('');\n        }\n        return str(_sequence.substring(_start, _end + 1));\n      }\n      case 'list': {\n        while (_start < 0) {\n          _start += sequence.items.length;\n        }\n        while (_end < 0) {\n          _end += sequence.items.length;\n        }\n        if (_end < _start) {\n          return list([]);\n        }\n        return list(sequence.items.slice(_start, _end + 1));\n      }\n      case 'dictionary': {\n        throw VimError.CannotUseSliceWithADictionary();\n      }\n      case 'funcref': {\n        throw VimError.CannotIndexAFuncref();\n      }\n      case 'blob': {\n        return blob(new Uint8Array(sequence.data).slice(_start, _end + 1));\n      }\n    }\n  }\n\n  private evaluateUnary(operator: UnaryOp, operand: Expression): NumberValue | FloatValue {\n    return mapNumber(this.evaluate(operand), (x: number) => {\n      switch (operator) {\n        case '+':\n          return x;\n        case '-':\n          return -x;\n        case '!':\n          return x === 0 ? 1 : 0;\n        default:\n          throw new Error('Impossible');\n      }\n    });\n  }\n\n  private evaluateBinary(operator: BinaryOp, lhsExpr: Expression, rhsExpr: Expression): Value {\n    let [lhs, rhs] = [this.evaluate(lhsExpr), this.evaluate(rhsExpr)];\n\n    const arithmetic = (f: (x: number, y: number) => number) => {\n      const numType = lhs.type === 'float' || rhs.type === 'float' ? float : int;\n      if (lhs.type === 'string') {\n        lhs = int(toInt(lhs));\n      }\n      if (rhs.type === 'string') {\n        rhs = int(toInt(rhs));\n      }\n      return numType(f(toFloat(lhs), toFloat(rhs)));\n    };\n\n    switch (operator) {\n      case '+':\n        if (lhs.type === 'list' && rhs.type === 'list') {\n          return list(lhs.items.concat(rhs.items));\n        } else {\n          return arithmetic((x, y) => x + y);\n        }\n      case '-':\n        return arithmetic((x, y) => x - y);\n      case '*':\n        return arithmetic((x, y) => x * y);\n      case '/':\n        return arithmetic((x, y) => x / y);\n      case '.':\n      case '..':\n        return str(toString(lhs) + toString(rhs));\n      case '%': {\n        if (lhs.type === 'float' || rhs.type === 'float') {\n          throw VimError.CannotUseModuloWithFloat();\n        }\n        const [_lhs, _rhs] = [toInt(lhs), toInt(rhs)];\n        if (_rhs === 0) {\n          return int(0);\n        }\n\n        return int(_lhs % _rhs);\n      }\n      case '&&':\n        return bool(toInt(lhs) !== 0 && toInt(rhs) !== 0);\n      case '||':\n        return bool(toInt(lhs) !== 0 || toInt(rhs) !== 0);\n    }\n  }\n\n  private evaluateComparison(\n    operator: ComparisonOp,\n    matchCase: boolean,\n    lhs: Value,\n    rhs: Value,\n  ): boolean {\n    switch (operator) {\n      case '==':\n        return this.evaluateBasicComparison('==', matchCase, lhs, rhs);\n      case '!=':\n        return !this.evaluateBasicComparison('==', matchCase, lhs, rhs);\n      case '>':\n        return this.evaluateBasicComparison('>', matchCase, lhs, rhs);\n      case '>=':\n        return (\n          this.evaluateBasicComparison('>', matchCase, lhs, rhs) ||\n          this.evaluateBasicComparison('==', matchCase, lhs, rhs)\n        );\n      case '<':\n        return this.evaluateBasicComparison('>', matchCase, rhs, lhs);\n      case '<=':\n        return !this.evaluateBasicComparison('>', matchCase, lhs, rhs);\n      case '=~':\n        return this.evaluateBasicComparison('=~', matchCase, lhs, rhs);\n      case '!~':\n        return !this.evaluateBasicComparison('=~', matchCase, lhs, rhs);\n      case 'is':\n        return this.evaluateBasicComparison('is', matchCase, lhs, rhs);\n      case 'isnot':\n        return !this.evaluateBasicComparison('is', matchCase, lhs, rhs);\n    }\n  }\n\n  private evaluateBasicComparison(\n    operator: '==' | '>' | '=~' | 'is',\n    matchCase: boolean,\n    lhs: Value,\n    rhs: Value,\n    topLevel: boolean = true,\n  ): boolean {\n    if (operator === 'is' && lhs.type !== rhs.type) {\n      return false;\n    }\n\n    if (lhs.type === 'list') {\n      if (rhs.type === 'list') {\n        switch (operator) {\n          case '==':\n            const rhsItems = rhs.items;\n            return (\n              lhs.items.length === rhsItems.length &&\n              lhs.items.every((left, idx) =>\n                this.evaluateBasicComparison('==', matchCase, left, rhsItems[idx], false),\n              )\n            );\n          case 'is':\n            // NOTE: `id` field should match if and only if they are the same list\n            return lhs.items === rhs.items;\n          default:\n            throw VimError.InvalidOperationForList();\n        }\n      } else {\n        throw VimError.CanOnlyCompareListWithList();\n      }\n    } else if (rhs.type === 'list') {\n      throw VimError.CanOnlyCompareListWithList();\n    } else if (lhs.type === 'dictionary') {\n      if (rhs.type === 'dictionary') {\n        switch (operator) {\n          case '==':\n            const rhsItems = rhs.items;\n            return (\n              lhs.items.size === rhsItems.size &&\n              [...lhs.items.entries()].every(\n                ([key, value]) =>\n                  rhsItems.has(key) &&\n                  this.evaluateBasicComparison('==', matchCase, value, rhsItems.get(key)!, false),\n              )\n            );\n          case 'is':\n            // NOTE: `id` field should match if and only if they are the same dictionary\n            return lhs.items === rhs.items;\n          default:\n            throw VimError.InvalidOperationForDictionary();\n        }\n      } else {\n        throw VimError.CanOnlyCompareDictionaryWithDictionary();\n      }\n    } else if (rhs.type === 'dictionary') {\n      throw VimError.CanOnlyCompareDictionaryWithDictionary();\n    } else if (lhs.type === 'funcref') {\n      if (rhs.type === 'funcref') {\n        switch (operator) {\n          case '==':\n            return lhs.name === rhs.name && lhs.dict === rhs.dict;\n          case 'is':\n            // NOTE: `id` field should match if and only if they are the same funcref\n            return lhs === rhs;\n          default:\n            throw VimError.InvalidOperationForFuncrefs();\n        }\n      } else {\n        return false;\n      }\n    } else if (rhs.type === 'funcref') {\n      return false;\n    } else if (lhs.type === 'blob') {\n      if (rhs.type === 'blob') {\n        switch (operator) {\n          case '==':\n            const [_lhs, _rhs] = [new Uint8Array(lhs.data), new Uint8Array(rhs.data)];\n            return _lhs.length === _rhs.length && _lhs.every((byte, idx) => byte === _rhs[idx]);\n          case 'is':\n            // NOTE: `id` field should match if and only if they are the same blob\n            return lhs.data === rhs.data;\n          default:\n            throw VimError.InvalidOperationForBlob();\n        }\n      } else {\n        throw VimError.CanOnlyCompareBlobWithBlob();\n      }\n    } else if (rhs.type === 'blob') {\n      throw VimError.CanOnlyCompareBlobWithBlob();\n    } else {\n      if (lhs.type === 'float' || rhs.type === 'float') {\n        [lhs, rhs] = [float(toFloat(lhs)), float(toFloat(rhs))];\n      } else if (lhs.type === 'number' || rhs.type === 'number') {\n        if (topLevel) {\n          // Strings are automatically coerced to numbers, except within a list/dict\n          // i.e. 4 == \"4\" but [4] != [\"4\"]\n          [lhs, rhs] = [int(toInt(lhs)), int(toInt(rhs))];\n        }\n      } else if (!matchCase) {\n        lhs.value = lhs.value.toLowerCase();\n        rhs.value = rhs.value.toLowerCase();\n      }\n      switch (operator) {\n        case '==':\n          return lhs.value === rhs.value;\n        case 'is':\n          return lhs.type === rhs.type && lhs.value === rhs.value;\n        case '>':\n          return lhs.value > rhs.value;\n        case '=~':\n          const pattern = Pattern.parser({\n            direction: SearchDirection.Forward,\n            delimiter: '/', // TODO: Are these params right?\n          }).tryParse(toString(rhs));\n          return pattern.regex.test(toString(lhs));\n      }\n    }\n  }\n\n  private evaluateFunctionCall(call: FunctionCallExpression): Value {\n    const assertPassed = () => {\n      return int(0);\n    };\n    const assertFailed = (msg: string) => {\n      // TODO: Include file & line\n      this.errors.push(msg);\n      return int(1);\n    };\n\n    const getpos = (arg: string) => {\n      const pos: Position | undefined = (() => {\n        if (arg === '.') {\n          return this.vimState!.cursorStopPosition;\n        } else if (arg === '$') {\n          return new Position(this.vimState!.document.lineCount, 0);\n        } else if (arg.startsWith(\"'\") && arg.length === 2) {\n          const mark = this.vimState!.historyTracker.getMark(arg[1]);\n          return mark?.position;\n        } else if (arg === 'w0') {\n          return new Position(this.vimState!.editor.visibleRanges[0].start.line, 0);\n        } else if (arg === 'w$') {\n          return new Position(this.vimState!.editor.visibleRanges[0].end.line, 0);\n        } else if (arg === 'v') {\n          return this.vimState!.cursorStartPosition;\n        }\n        return undefined;\n      })();\n      return {\n        bufnum: 0, // TODO\n        lnum: (pos?.line ?? -1) + 1,\n        col: (pos?.character ?? -1) + 1,\n        off: 0,\n      };\n    };\n\n    // See `:help non-zero-arg`\n    const nonZeroArg = (arg: Value): boolean => {\n      if (arg.type === 'number' && arg.value !== 0) {\n        return true;\n      } else if (arg.type === 'string' && arg.value.length !== 0) {\n        return true;\n      }\n      return false;\n    };\n\n    const copy = (arg: Value, deep: boolean): Value => {\n      switch (arg.type) {\n        case 'list':\n          return list(deep ? arg.items.map((item) => copy(item, true)) : arg.items);\n        case 'dictionary':\n          return dictionary(\n            new Map(deep ? [...arg.items].map(([k, v]) => [k, copy(v, true)]) : arg.items),\n          );\n      }\n      return arg;\n    };\n\n    const getArgs = (min: number, max?: number) => {\n      if (max === undefined) {\n        max = min;\n      }\n      if (call.args.length < min) {\n        throw VimError.NotEnoughArgs(call.func);\n      }\n      if (call.args.length > max) {\n        throw VimError.TooManyArgs(call.func);\n      }\n      const args: Array<Value | undefined> = call.args.map((arg) => this.evaluate(arg));\n      while (args.length < max) {\n        args.push(undefined);\n      }\n      return args;\n    };\n    switch (call.func) {\n      case 'abs': {\n        const [x] = getArgs(1);\n        return float(Math.abs(toFloat(x!)));\n      }\n      case 'acos': {\n        const [x] = getArgs(1);\n        return float(Math.acos(toFloat(x!)));\n      }\n      case 'add': {\n        const [l, item] = getArgs(2);\n        if (l!.type === 'blob') {\n          const newBytes = new Uint8Array(l!.data.byteLength + 1);\n          newBytes.set(new Uint8Array(l!.data));\n          newBytes[newBytes.length - 1] = toInt(item!);\n          l!.data = newBytes;\n          return blob(newBytes);\n        }\n        const lst = toList(l!);\n        lst.items.push(item!);\n        return lst;\n      }\n      case 'asin': {\n        const [x] = getArgs(1);\n        return float(Math.asin(toFloat(x!)));\n      }\n      case 'atan2': {\n        const [x, y] = getArgs(2);\n        return float(Math.atan2(toFloat(x!), toFloat(y!)));\n      }\n      case 'and': {\n        const [x, y] = getArgs(2);\n        // eslint-disable-next-line no-bitwise\n        return int(toInt(x!) & toInt(y!));\n      }\n      case 'assert_beeps': {\n        return assertFailed('VSCodeVim does not support beeps');\n      }\n      case 'assert_equal': {\n        const [expected, actual, msg] = getArgs(2, 3);\n        if (\n          expected!.type === actual!.type &&\n          this.evaluateComparison('==', true, expected!, actual!)\n        ) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg\n            ? toString(msg)\n            : `Expected ${displayValue(expected!)} but got ${displayValue(actual!)}`,\n        );\n      }\n      // TODO: assert_equalfile()\n      // TODO: assert_exception()\n      // TODO: assert_fails()\n      case 'assert_false': {\n        const [actual, msg] = getArgs(1, 2);\n        if (this.evaluateComparison('==', true, bool(false), actual!)) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg ? toString(msg) : `Expected False but got ${displayValue(actual!)}`,\n        );\n      }\n      case 'assert_inrange': {\n        const [lower, upper, actual, msg] = getArgs(3, 4);\n        if (\n          this.evaluateComparison('>=', true, actual!, lower!) &&\n          this.evaluateComparison('<=', true, actual!, upper!)\n        ) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg\n            ? toString(msg)\n            : `Expected range ${displayValue(lower!)} - ${displayValue(upper!)} but got ${displayValue(actual!)}`,\n        );\n      }\n      case 'assert_match': {\n        const [pattern, actual, msg] = getArgs(2, 3);\n        if (this.evaluateComparison('=~', true, actual!, pattern!)) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg\n            ? toString(msg)\n            : `Pattern '${toString(pattern!)}' does not match '${toString(actual!)}'`,\n        );\n      }\n      case 'assert_nobeep': {\n        return assertPassed();\n      }\n      case 'assert_notequal': {\n        const [expected, actual, msg] = getArgs(2, 3);\n        if (this.evaluateComparison('!=', true, expected!, actual!)) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg ? toString(msg) : `Expected not equal to ${displayValue(expected!)}`,\n        );\n      }\n      case 'assert_notmatch': {\n        const [pattern, actual, msg] = getArgs(2, 3);\n        if (!this.evaluateComparison('=~', true, actual!, pattern!)) {\n          return assertPassed();\n        }\n        return assertFailed(\n          msg ? toString(msg) : `Pattern '${toString(pattern!)}' does match '${toString(actual!)}'`,\n        );\n      }\n      case 'assert_report': {\n        return assertFailed(toString(getArgs(1)[0]!));\n      }\n      case 'assert_true': {\n        const [actual, msg] = getArgs(1, 2);\n        if (this.evaluateComparison('!=', true, bool(false), actual!)) {\n          return assertPassed();\n        }\n        return assertFailed(msg ? toString(msg) : `Expected True but got ${displayValue(actual!)}`);\n      }\n      case 'byte2line': {\n        const [_byte] = getArgs(1);\n        const byte = toInt(_byte!);\n        if (byte <= 0) {\n          return int(-1);\n        }\n        return int(this.vimState!.document.positionAt(byte - 1).line + 1);\n      }\n      // TODO: byteidx[comp]\n      case 'call': {\n        const [_func, arglist, dict] = getArgs(2, 3);\n        if (arglist!.type !== 'list') {\n          throw VimError.ListRequiredForArgument(2);\n        }\n        const func: FuncRefValue = (() => {\n          if (_func?.type === 'funcref') {\n            return _func;\n          }\n          return funcref({\n            name: toString(_func!),\n            arglist: list([]),\n            dict: dict ? toDict(dict) : undefined,\n          });\n        })();\n        return this.evaluate(funcrefCall(toExpr(func), arglist!.items.map(toExpr)));\n      }\n      case 'ceil': {\n        const [x] = getArgs(1);\n        return float(Math.ceil(toFloat(x!)));\n      }\n      case 'col': {\n        const [s] = getArgs(1);\n        return int(getpos(toString(s!)).col);\n      }\n      case 'copy': {\n        const [x] = getArgs(1);\n        return copy(x!, false);\n      }\n      case 'cos': {\n        const [x] = getArgs(1);\n        return float(Math.cos(toFloat(x!)));\n      }\n      case 'cosh': {\n        const [x] = getArgs(1);\n        return float(Math.cosh(toFloat(x!)));\n      }\n      case 'count': {\n        let [comp, expr, ic, start] = getArgs(2, 4);\n        const matchCase = toInt(ic ?? bool(false)) === 0;\n        if (start !== undefined) {\n          if (comp!.type !== 'list') {\n            throw VimError.InvalidArgument474();\n          }\n          if (toInt(start) >= comp!.items.length) {\n            throw VimError.ListIndexOutOfRange(toInt(start));\n          }\n          while (toInt(start) < 0) {\n            start = int(toInt(start) + comp!.items.length);\n          }\n        }\n        let count = 0;\n        switch (comp!.type) {\n          case 'string':\n            const s = toString(expr!);\n            if (s) {\n              const regex = new RegExp(escapeRegExp(s), matchCase ? 'g' : 'ig');\n              count = [...comp!.value.matchAll(regex)].length;\n            }\n            break;\n          case 'list':\n            const startIdx = start ? toInt(start) : 0;\n            for (let i = startIdx; i < comp!.items.length; i++) {\n              if (this.evaluateComparison('==', matchCase, comp!.items[i], expr!)) {\n                count++;\n              }\n            }\n            break;\n          case 'dictionary':\n            for (const val of comp!.items.values()) {\n              if (this.evaluateComparison('==', matchCase, val, expr!)) {\n                count++;\n              }\n            }\n            break;\n          default:\n            throw VimError.ArgumentOfFuncMustBeAListOrDictionary(call.func);\n        }\n        return int(count);\n      }\n      // TODO: cursor()\n      case 'deepcopy': {\n        const [x] = getArgs(1);\n        return copy(x!, true);\n      }\n      case 'empty': {\n        let [x] = getArgs(1);\n        x = x!;\n        switch (x.type) {\n          case 'number':\n          case 'float':\n            return bool(x.value === 0);\n          case 'string':\n            return bool(x.value.length === 0);\n          case 'list':\n            return bool(x.items.length === 0);\n          case 'dictionary':\n            return bool(x.items.size === 0);\n          case 'blob':\n            return bool(x.data.byteLength === 0);\n          default:\n            return bool(false);\n        }\n      }\n      case 'environ': {\n        return dictionary(new Map(Object.entries(process.env).map(([k, v]) => [k, str(v ?? '')])));\n      }\n      case 'escape': {\n        const [s, chars] = getArgs(2);\n        return str(\n          toString(s!).replace(new RegExp(`[${escapeRegExp(toString(chars!))}]`, 'g'), '\\\\$&'),\n        );\n      }\n      case 'eval': {\n        const [expr] = getArgs(1);\n        return this.evaluate(expressionParser.tryParse(toString(expr!)));\n      }\n      // TODO: exists()\n      case 'exp': {\n        const [x] = getArgs(1);\n        return float(Math.exp(toFloat(x!)));\n      }\n      // TODO: extend/extendnew()\n      // TODO: filecopy()\n      // TODO: filereadable()/filewritable()\n      // TODO: filter()\n      case 'flatten':\n      case 'flattennew': {\n        const [l, _depth] = getArgs(1, 2);\n        if (l!.type !== 'list') {\n          throw VimError.ArgumentMustBeAList(call.func);\n        }\n        const depth = _depth ? toInt(_depth) : 99999999;\n        if (depth < 0) {\n          throw VimError.MaxDepthMustBeANonNegativeNumber();\n        }\n        let newItems: Value[] = [...l!.items];\n        for (let i = 0; i < depth; ++i) {\n          const temp: Value[] = [];\n          let foundList = false;\n          for (const item of newItems) {\n            if (item.type === 'list') {\n              foundList = true;\n              for (const _item of item.items) {\n                temp.push(_item);\n              }\n            } else {\n              temp.push(item);\n            }\n          }\n          if (!foundList) {\n            break;\n          }\n          newItems = temp;\n        }\n        if (call.func === 'flatten') {\n          l!.items = newItems;\n        }\n        return list(newItems);\n      }\n      case 'float2nr': {\n        const [x] = getArgs(1);\n        return int(toFloat(x!));\n      }\n      case 'floor': {\n        const [x] = getArgs(1);\n        return float(Math.floor(toFloat(x!)));\n      }\n      case 'fmod': {\n        const [x, y] = getArgs(2);\n        return float(toFloat(x!) % toFloat(y!));\n      }\n      // TODO: Fix circular dependency\n      // case 'fullcommand': {\n      //   const [name] = getArgs(1);\n      //   try {\n      //     return str(exCommandParser.tryParse(toString(name!)).name);\n      //   } catch {\n      //     return str('');\n      //   }\n      // }\n      case 'function': {\n        const [name, arglist, dict] = getArgs(1, 3);\n        if (arglist) {\n          if (arglist.type === 'list') {\n            if (dict && dict.type !== 'dictionary') {\n              throw VimError.ExpectedADict();\n            }\n            return funcref({ name: toString(name!), arglist, dict });\n          } else if (arglist.type === 'dictionary') {\n            if (dict) {\n              // function('abs', {}, {})\n              throw VimError.SecondArgumentOfFunction();\n            }\n            return funcref({ name: toString(name!), dict: arglist });\n          } else {\n            throw VimError.SecondArgumentOfFunction();\n          }\n        }\n        if (dict && dict.type !== 'dictionary') {\n          throw VimError.ExpectedADict();\n        }\n        // TODO:\n        // if (toString(name!) is invalid function) {\n        //   throw VimError.fromCode(ErrorCode.UnknownFunction_funcref, toString(name!));\n        // }\n        return funcref({ name: toString(name!), arglist, dict });\n      }\n      // TODO: funcref()\n      case 'garbagecollect': {\n        const [atexit] = getArgs(0, 1);\n        return int(0); // No-op\n      }\n      case 'get': {\n        const [_haystack, _idx, _default] = getArgs(2, 3);\n        const haystack: Value = _haystack!;\n        if (haystack.type === 'list') {\n          let idx = toInt(_idx!);\n          idx = idx < 0 ? haystack.items.length + idx : idx;\n          return idx < haystack.items.length ? haystack.items[idx] : (_default ?? int(0));\n        } else if (haystack.type === 'blob') {\n          const bytes = new Uint8Array(haystack.data);\n          let idx = toInt(_idx!);\n          idx = idx < 0 ? bytes.length + idx : idx;\n          return idx < bytes.length ? int(bytes[idx]) : (_default ?? int(-1));\n        } else if (haystack.type === 'dictionary') {\n          const val = haystack.items.get(toString(_idx!));\n          return val ? val : (_default ?? int(0));\n        }\n        return _default ?? int(0);\n        // TODO: get({func}, {what})\n      }\n      case 'getcurpos': {\n        const { bufnum, lnum, col, off } = getpos('.');\n        const curswant = this.vimState!.desiredColumn + 1;\n        return list([int(bufnum), int(lnum), int(col), int(off), int(curswant)]);\n      }\n      case 'getline': {\n        const [lnum, _end] = getArgs(1, 2);\n        // TODO: When {lnum} is a String that doesn't start with a digit, line() is called\n        if (_end === undefined) {\n          return str(this.vimState!.document.lineAt(toInt(lnum!)).text);\n        }\n        const lines: string[] = [];\n        for (let i = toInt(lnum!); i <= toInt(_end); i++) {\n          lines.push(this.vimState!.document.lineAt(i).text);\n        }\n        return list(lines.map(str));\n      }\n      case 'getpid': {\n        return int(process.pid);\n      }\n      case 'getpos': {\n        const [s] = getArgs(1);\n        const { bufnum, lnum, col, off } = getpos(toString(s!));\n        return list([int(bufnum), int(lnum), int(col), int(off)]);\n      }\n      // TODO: getreg()\n      // TODO: getreginfo()\n      case 'getregtype': {\n        const [regname] = getArgs(1);\n        const reg = Register.getSync(toString(regname!));\n        if (reg === undefined) {\n          return str('');\n        }\n        if (reg.registerMode === RegisterMode.CharacterWise) {\n          return str('v');\n        } else if (reg.registerMode === RegisterMode.LineWise) {\n          return str('V');\n        } else if (reg.registerMode === RegisterMode.BlockWise) {\n          const text = reg.text as string;\n          const idx = text.indexOf('\\n');\n          const width = idx === -1 ? text.length : idx;\n          const ctrlV = '\\x16';\n          return str(`${ctrlV}${width}`);\n        }\n        const guard: never = reg.registerMode;\n        return str('');\n      }\n      case 'gettext': {\n        const [s] = getArgs(1);\n        return str(toString(s!));\n      }\n      // TODO: glob2regpat()\n      case 'has': {\n        const [feature] = getArgs(1);\n        return bool(toString(feature!) === 'vscode');\n      }\n      case 'has_key': {\n        const [d, k] = getArgs(2);\n        return bool(toDict(d!).items.has(toString(k!)));\n      }\n      // TODO: hasmapto()\n      // TODO: histadd()/histdel()/histget()/histnr()\n      case 'id': {\n        // NOTE: Vim behaves differently (generally returning pointer addresses), but this serves the purpose\n        const [x] = getArgs(1);\n        if (x!.type === 'number' || x!.type === 'float' || x!.type === 'string') {\n          return str(x!.value.toString());\n        }\n        return str(x!.id);\n      }\n      case 'index': {\n        const [_haystack, _needle, _start, ic] = getArgs(2, 4);\n        const haystack: Value = _haystack!;\n\n        if (haystack.type === 'list') {\n          let start: number | undefined;\n          if (_start) {\n            start = toInt(_start);\n            start = start < 0 ? haystack.items.length + start : start;\n          }\n\n          for (const [idx, item] of haystack.items.entries()) {\n            if (start && idx < start) {\n              continue;\n            }\n            if (this.evaluateComparison('==', true, item, _needle!)) {\n              return int(idx);\n            }\n          }\n          return int(-1);\n        }\n        // TODO: handle blob\n        throw VimError.ListOrBlobRequired();\n      }\n      // TODO: indexof()\n      // TODO: input()/inputlist()\n      case 'insert': {\n        const [l, item, _idx] = getArgs(2, 3);\n        const idx = _idx ? toInt(_idx) : 0;\n        if (l!.type === 'blob') {\n          if (idx > l!.data.byteLength) {\n            throw VimError.InvalidArgument475(idx.toString());\n          }\n          const bytes = new Uint8Array(l!.data);\n          const newBytes = new Uint8Array(bytes.length + 1);\n          newBytes.set(bytes.subarray(0, idx), 0);\n          newBytes[idx] = toInt(item!);\n          newBytes.set(bytes.subarray(idx), idx + 1);\n          l!.data = newBytes;\n          return blob(newBytes);\n        }\n        const lst = toList(l!);\n        if (idx > lst.items.length) {\n          throw VimError.ListIndexOutOfRange(idx);\n        }\n        lst.items.splice(idx, 0, item!);\n        return lst;\n      }\n      case 'invert': {\n        const [x] = getArgs(1);\n        // eslint-disable-next-line no-bitwise\n        return int(~toInt(x!));\n      }\n      // TODO: isabsolutepath()\n      // TODO: isdirectory()\n      case 'isinf': {\n        const [x] = getArgs(1);\n        const _x = toFloat(x!);\n        return int(_x === Infinity ? 1 : _x === -Infinity ? -1 : 0);\n      }\n      // TODO: islocked()\n      case 'isnan': {\n        const [x] = getArgs(1);\n        return bool(isNaN(toFloat(x!)));\n      }\n      case 'items': {\n        const [d] = getArgs(1);\n        return list([...toDict(d!).items.entries()].map(([k, v]) => list([str(k), v])));\n      }\n      case 'join': {\n        const [l, sep] = getArgs(1, 2);\n        return str(\n          toList(l!)\n            .items.map(toString)\n            .join(sep ? toString(sep) : ''),\n        );\n      }\n      case 'json_decode': {\n        const fromJSObj = (x: any): Value => {\n          if (Array.isArray(x)) {\n            return list(x.map(fromJSObj));\n          } else if (typeof x === 'number') {\n            return isInteger(x) ? int(x) : float(x);\n          } else if (typeof x === 'string') {\n            return str(x);\n          } else {\n            const items: Map<string, Value> = new Map();\n            for (const key in x) {\n              if (Object.hasOwnProperty.call(x, key)) {\n                // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n                items.set(key, fromJSObj(x[key]));\n              }\n            }\n            return dictionary(items);\n          }\n        };\n        const [expr] = getArgs(1);\n        return fromJSObj(JSON.parse(toString(expr!)));\n      }\n      case 'json_encode': {\n        const toJSObj = (x: Value): unknown => {\n          switch (x.type) {\n            case 'number':\n              return x.value;\n            case 'string':\n              return x.value;\n            case 'list':\n              return x.items.map(toJSObj);\n            case 'dictionary':\n              const d: Record<string, unknown> = {};\n              for (const [key, val] of x.items) {\n                d[key] = toJSObj(val);\n              }\n              return d;\n            case 'blob':\n              return Array.from(new Uint8Array(x.data));\n            case 'float':\n              return x.value;\n            case 'funcref':\n              throw VimError.InvalidArgument474();\n          }\n        };\n        const [expr] = getArgs(1);\n        return str(JSON.stringify(toJSObj(expr!), null, 0)); // TODO: Fix whitespace\n      }\n      case 'keys': {\n        const [d] = getArgs(1);\n        return list([...toDict(d!).items.keys()].map(str));\n      }\n      case 'len': {\n        const [x] = getArgs(1);\n        switch (x!.type) {\n          case 'number':\n            return int(x!.value.toString().length);\n          case 'string':\n            return int(x!.value.length);\n          case 'list':\n            return int(x!.items.length);\n          case 'dictionary':\n            return int(x!.items.size);\n          case 'blob':\n            return int(x!.data.byteLength);\n          default:\n            throw VimError.InvalidTypeForLen();\n        }\n      }\n      case 'line': {\n        const [s, winid] = getArgs(1, 2);\n        return int(getpos(toString(s!)).lnum);\n      }\n      case 'line2byte': {\n        const [_line] = getArgs(1);\n        const line = toInt(_line!);\n        if (line <= 0) {\n          return int(-1);\n        }\n        return int(this.vimState!.document.offsetAt(new Position(line - 1, 0)) + 1);\n      }\n      case 'localtime': {\n        return int(Date.now() / 1000);\n      }\n      case 'log': {\n        const [x] = getArgs(1);\n        return float(Math.log(toFloat(x!)));\n      }\n      case 'log10': {\n        const [x] = getArgs(1);\n        return float(Math.log10(toFloat(x!)));\n      }\n      case 'map':\n      case 'mapnew': {\n        const [seq, fn] = getArgs(2);\n        switch (seq?.type) {\n          case 'list':\n            const newItems = seq.items.map((val, idx) => {\n              switch (fn?.type) {\n                case 'funcref':\n                  return this.evaluate(funcrefCall(toExpr(fn), [int(idx), toExpr(val)]));\n                default:\n                  this.localScopes.push(\n                    new Map([\n                      ['v:key', new Variable(int(idx))],\n                      ['v:val', new Variable(val)],\n                    ]),\n                  );\n                  const retval = this.evaluate(expressionParser.tryParse(toString(fn!)));\n                  this.localScopes.pop();\n                  return retval;\n              }\n            });\n            if (call.func === 'map') {\n              seq.items = newItems;\n            }\n            return list(newItems);\n          case 'dictionary':\n          // TODO\n          // case 'blob':\n          // TODO\n          // eslint-disable-next-line no-fallthrough\n          default:\n            throw VimError.ArgumentOfMapMustBeAListDictionaryOrBlob();\n        }\n      }\n      // TODO: matchadd()/matchaddpos()/matcharg()/matchdelete()\n      // TODO: match()/matchend()/matchlist()/matchstr()/matchstrpos()\n      case 'max': {\n        const [l] = getArgs(1);\n        let values: Value[];\n        if (l?.type === 'list') {\n          values = l.items;\n        } else if (l?.type === 'dictionary') {\n          values = [...l.items.values()];\n        } else {\n          throw VimError.ArgumentOfFuncMustBeAListOrDictionary(call.func);\n        }\n        return int(values.length === 0 ? 0 : Math.max(...values.map(toInt)));\n      }\n      case 'min': {\n        const [l] = getArgs(1);\n        let values: Value[];\n        if (l?.type === 'list') {\n          values = l.items;\n        } else if (l?.type === 'dictionary') {\n          values = [...l.items.values()];\n        } else {\n          throw VimError.ArgumentOfFuncMustBeAListOrDictionary(call.func);\n        }\n        return int(values.length === 0 ? 0 : Math.min(...values.map(toInt)));\n      }\n      case 'mode': {\n        const [arg] = getArgs(1);\n        switch (this.vimState!.currentModeIncludingPseudoModes) {\n          case Mode.Normal:\n            return str('n');\n          case Mode.OperatorPendingMode:\n            return nonZeroArg(arg!) ? str('n') : str('no');\n          case Mode.Visual:\n            return str('v');\n          case Mode.VisualLine:\n            return str('V');\n          case Mode.VisualBlock:\n            return str('\\x16');\n          case Mode.Insert:\n            return str('i');\n          case Mode.Replace:\n            return str('R');\n          case Mode.CommandlineInProgress:\n          case Mode.SearchInProgressMode:\n            return str('c');\n          default:\n            return str(''); // TODO: Other modes\n        }\n      }\n      case 'nextnonblank': {\n        const [_line] = getArgs(1);\n        const line = toInt(_line!);\n        if (line <= 0) {\n          return int(0);\n        }\n        for (let i = line - 1; i < this.vimState!.document.lineCount; i++) {\n          if (this.vimState!.document.lineAt(i).text.length > 0) {\n            return int(i + 1);\n          }\n        }\n        return int(0);\n      }\n      case 'or': {\n        const [x, y] = getArgs(2);\n        // eslint-disable-next-line no-bitwise\n        return int(toInt(x!) | toInt(y!));\n      }\n      case 'pow': {\n        const [x, y] = getArgs(2);\n        return float(Math.pow(toFloat(x!), toFloat(y!)));\n      }\n      case 'prevnonblank': {\n        const [_line] = getArgs(1);\n        const line = toInt(_line!);\n        if (line > this.vimState!.document.lineCount) {\n          return int(0);\n        }\n        for (let i = line - 1; i >= 0; i--) {\n          if (this.vimState!.document.lineAt(i).text.length > 0) {\n            return int(i + 1);\n          }\n        }\n        return int(0);\n      }\n      // TODO: printf()\n      // TODO: rand()\n      case 'range': {\n        const [val, max, stride] = getArgs(1, 3);\n        const start = max !== undefined ? toInt(val!) : 0;\n        const end = max !== undefined ? toInt(max) : toInt(val!) - 1;\n        const step = stride !== undefined ? toInt(stride) : 1;\n        if (step === 0) {\n          throw VimError.StrideIsZero();\n        }\n        if (step > 0 !== start < end && Math.abs(start - end) > 1) {\n          throw VimError.StartPastEnd();\n        }\n        const items: Value[] = [];\n        for (let i = start; step > 0 ? i <= end : i >= end; i += step) {\n          items.push(int(i));\n        }\n        return list(items);\n      }\n      // TODO: reduce()\n      case 'reg_executing': {\n        return str(this.vimState?.isReplayingMacro ? this.vimState.recordedState.registerName : '');\n      }\n      case 'reg_recording': {\n        return str(this.vimState?.macro?.registerName ?? '');\n      }\n      // TODO: reltime*()\n      case 'repeat': {\n        const [val, count] = getArgs(2);\n        if (val?.type === 'list') {\n          const items: Value[] = new Array<Value[]>(toInt(count!)).fill(val.items).flat();\n          return list(items);\n        } else {\n          return str(toString(val!).repeat(toInt(count!)));\n        }\n      }\n      case 'remove': {\n        const [_haystack, _idx, _end] = getArgs(2, 3);\n        const haystack: Value = _haystack!;\n        if (haystack.type === 'list') {\n          let idx = toInt(_idx!);\n          idx = idx < 0 ? haystack.items.length + idx : idx;\n          if (_end === undefined) {\n            return haystack.items.splice(idx, 1)[0]; // TODO: This doesn't remove the item?\n          } else {\n            // TODO: remove({list}, {idx}, {end})\n          }\n        }\n        // TODO: remove({blob}, {idx}, [{end}])\n        else if (haystack.type === 'dictionary') {\n          const key = toString(_idx!);\n          const val = haystack.items.get(key);\n          if (val) {\n            haystack.items.delete(key);\n            return val;\n          }\n        }\n        return int(0);\n      }\n      case 'reverse': {\n        const [l] = getArgs(1);\n        if (l?.type === 'list') {\n          l.items.reverse();\n          return l;\n        } else if (l?.type === 'blob') {\n          l.data = new Uint8Array(l.data).reverse();\n          return l;\n        }\n        return int(0);\n      }\n      case 'round': {\n        const [x] = getArgs(1);\n        const _x = toFloat(x!);\n        // Halfway between integers, Math.round() rounds toward infinity while Vim's round() rounds away from 0.\n        return float(_x < 0 ? -Math.round(-_x) : Math.round(_x));\n      }\n      // TODO: setpos()\n      // TODO: setreg()\n      case 'sin': {\n        const [x] = getArgs(1);\n        return float(Math.sin(toFloat(x!)));\n      }\n      case 'sinh': {\n        const [x] = getArgs(1);\n        return float(Math.sinh(toFloat(x!)));\n      }\n      case 'sort': {\n        // TODO: use dict\n        const [l, func, dict] = getArgs(1, 3);\n        if (l?.type !== 'list') {\n          throw VimError.ArgumentMustBeAList(call.func);\n        }\n        let compare: (x: Value, y: Value) => number;\n        if (func !== undefined) {\n          if (func.type === 'string' || func.type === 'number') {\n            if (func.value === 1 || func.value === '1' || func.value === 'i') {\n              // Ignore case\n              compare = (x, y) => {\n                const [_x, _y] = [displayValue(x).toLowerCase(), displayValue(y).toLowerCase()];\n                return _x === _y ? 0 : _x > _y ? 1 : -1;\n              };\n            } else {\n              // TODO: handle other special cases ('l', 'n', 'N', 'f')\n              throw Error('compare() with function name is not yet implemented');\n            }\n          } else if (func.type === 'funcref') {\n            compare = (x, y) =>\n              toInt(this.evaluate(funcrefCall(toExpr(func), [toExpr(x), toExpr(y)])));\n          } else {\n            throw VimError.InvalidArgument474();\n          }\n        } else {\n          compare = (x, y) => (displayValue(x) > displayValue(y) ? 1 : -1);\n        }\n        // TODO: Numbers after Strings, Lists after Numbers\n        return list(l.items.sort(compare));\n      }\n      case 'split': {\n        const [s, pattern, keepempty] = getArgs(1, 3);\n        // TODO: Actually parse pattern\n        const result = toString(s!).split(pattern && toString(pattern) ? toString(pattern) : /\\s+/);\n        if (!(keepempty && toInt(keepempty))) {\n          if (result[0] === '') {\n            result.shift();\n          }\n          if (result.at(-1) === '') {\n            result.pop();\n          }\n        }\n        return list(result.map(str));\n      }\n      case 'sqrt': {\n        const [x] = getArgs(1);\n        return float(Math.sqrt(toFloat(x!)));\n      }\n      case 'str2float': {\n        // TODO: Support 1e40 (rather than 1.0e40)\n        const [_s, quoted] = getArgs(1, 2);\n        const s = toString(_s!);\n        if (/^inf/i.test(s)) return float(Infinity);\n        if (/^-inf/i.test(s)) return float(-Infinity);\n        if (/^nan/i.test(s)) return float(NaN);\n        const result = alt<FloatValue | NumberValue>(floatParser, numberParser).skip(all).parse(s);\n        return float(result.status ? result.value.value : 0);\n      }\n      case 'str2list': {\n        const [s, _ignored] = getArgs(1, 2);\n        const result: number[] = [];\n        for (const char of toString(s!)) {\n          result.push(char.charCodeAt(0));\n        }\n        return list(result.map(int));\n      }\n      case 'str2nr': {\n        const [_s, _base] = getArgs(1, 2);\n        const base = _base ? toInt(_base) : 10;\n        let s = toString(_s!);\n\n        if (base === 16) {\n          s = s.replace(/^0x/i, '');\n        } else if (base === 8) {\n          s = s.replace(/^0o/i, '');\n        } else if (base === 2) {\n          s = s.replace(/^0b/i, '');\n        } else if (base !== 10) {\n          throw VimError.InvalidArgument474();\n        }\n        const parsed = Number.parseInt(s, base);\n        return int(isNaN(parsed) ? 0 : parsed);\n      }\n      case 'stridx': {\n        const [haystack, needle, start] = getArgs(2, 3);\n\n        return int(toString(haystack!).indexOf(toString(needle!), start ? toInt(start) : 0));\n      }\n      case 'string': {\n        const [x] = getArgs(1);\n        return str(displayValue(x!));\n      }\n      case 'strlen': {\n        const [s] = getArgs(1);\n        return int(toString(s!).length);\n      }\n      case 'strpart': {\n        const [_src, _start, _len, chars] = getArgs(2, 4);\n        const src = toString(_src!);\n        const start = toInt(_start!);\n        const len = _len ? toInt(_len) : src.length - start;\n        return str(src.substring(start, start + len));\n      }\n      // TODO: submatch()\n      // TODO: substitute()\n      case 'tan': {\n        const [x] = getArgs(1);\n        return float(Math.tan(toFloat(x!)));\n      }\n      case 'tanh': {\n        const [x] = getArgs(1);\n        return float(Math.tanh(toFloat(x!)));\n      }\n      // TODO: timer*()\n      case 'tolower': {\n        const [s] = getArgs(1);\n        return str(toString(s!).toLowerCase());\n      }\n      case 'toupper': {\n        const [s] = getArgs(1);\n        return str(toString(s!).toUpperCase());\n      }\n      case 'tr': {\n        const [_src, _from, _to] = getArgs(1, 3);\n        const src = toString(_src!);\n        const from = toString(_from!);\n        const to = toString(_to!);\n        if (from.length !== to.length) {\n          throw VimError.InvalidArgument475(from);\n        }\n        const charMap = new Map<string, string>();\n        for (let i = 0; i < from.length; i++) {\n          charMap.set(from[i], to[i]);\n        }\n        return str([...src].map((c) => charMap.get(c) ?? c).join(''));\n      }\n      case 'trim': {\n        const [_s, mask, _dir] = getArgs(1, 3);\n        // TODO: use mask\n        let s = toString(_s!);\n        const dir = _dir ? toInt(_dir) : 0;\n        if (dir === 0) {\n          // Trim start and end\n          s = s.trimStart().trimEnd();\n        } else if (dir === 1) {\n          // Trim start\n          s = s.trimStart();\n        } else if (dir === 2) {\n          // Trim end\n          s = s.trimEnd();\n        } else {\n          throw VimError.InvalidArgument475(dir.toString());\n        }\n        return str(s);\n      }\n      case 'trunc': {\n        const [x] = getArgs(1);\n        return float(Math.trunc(toFloat(x!)));\n      }\n      case 'type': {\n        let [x] = getArgs(1);\n        x = x!;\n        switch (x.type) {\n          case 'number':\n            return int(0);\n          case 'string':\n            return int(1);\n          case 'funcref':\n            return int(2);\n          case 'list':\n            return int(3);\n          case 'dictionary':\n            return int(4);\n          case 'float':\n            return int(5);\n          // case 'bool':\n          //   return int(6);\n          // case 'null':\n          //   return int(7);\n          case 'blob':\n            return int(8);\n          default:\n            const guard: never = x;\n            throw new Error('type() got unexpected type');\n        }\n      }\n      case 'uniq': {\n        const [l, func, dict] = getArgs(1, 3);\n        // TODO: Use func (see sort() and try to re-use implementation)\n        // TODO: Use dict\n        if (l!.type !== 'list') {\n          throw VimError.ArgumentMustBeAList(call.func);\n        }\n        if (l!.items.length > 1) {\n          let prev: Value = l!.items[0];\n          for (let i = 1; i < l!.items.length; ) {\n            const val = l!.items[i];\n            if (prev.type === val.type && this.evaluateComparison('==', true, prev, val)) {\n              l!.items.splice(i, 1);\n            } else {\n              prev = val;\n              i++;\n            }\n          }\n        }\n        return l!;\n      }\n      case 'values': {\n        const [d] = getArgs(1);\n        return list([...toDict(d!).items.values()]);\n      }\n      case 'visualmode': {\n        const [arg] = getArgs(1); // TODO: Use arg\n        const mode = this.vimState?.lastVisualSelection?.mode;\n        if (mode === undefined) {\n          return str('');\n        }\n        return mode === Mode.Visual ? str('v') : mode === Mode.VisualLine ? str('V') : str('\\x16');\n      }\n      // TODO: wordcount()\n      case 'xor': {\n        const [x, y] = getArgs(2);\n        // eslint-disable-next-line no-bitwise\n        return int(toInt(x!) ^ toInt(y!));\n      }\n      default: {\n        throw VimError.UnknownFunction_call(call.func);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/vimscript/expression/parser.ts",
    "content": "import {\n  alt,\n  // eslint-disable-next-line id-denylist\n  any,\n  lazy,\n  noneOf,\n  optWhitespace,\n  Parser,\n  regexp,\n  seq,\n  // eslint-disable-next-line id-denylist\n  string,\n  takeWhile,\n} from 'parsimmon';\nimport { VimError } from '../../error';\nimport { binary, blob, float, int, lambda, listExpr, str } from './build';\nimport {\n  BinaryOp,\n  BlobValue,\n  DictionaryExpression,\n  EntryExpression,\n  EnvVariableExpression,\n  Expression,\n  FloatValue,\n  FuncrefCallExpression,\n  FunctionCallExpression,\n  IndexExpression,\n  LambdaExpression,\n  ListExpression,\n  MethodCallExpression,\n  NumberValue,\n  OptionExpression,\n  RegisterExpression,\n  SliceExpression,\n  StringValue,\n  VariableExpression,\n} from './types';\n\nconst blobParser: Parser<BlobValue> = regexp(/0[z]/i).then(\n  regexp(/[0-1a-f]{1,2}/i)\n    .sepBy(string('.').fallback(undefined))\n    .map<BlobValue>((bytes) => {\n      const lastByte = bytes.at(-1);\n      if (lastByte && lastByte.length !== 2) {\n        throw VimError.BlobLiteralShouldHaveAnEvenNumberOfHexCharacters();\n      }\n      const data = new Uint8Array(new ArrayBuffer(bytes.length));\n      let i = 0;\n      for (const byte of bytes) {\n        data[i] = Number.parseInt(byte, 16);\n        ++i;\n      }\n      return blob(data);\n    }),\n);\n\nconst binaryNumberParser: Parser<NumberValue> = regexp(/0[b]/i).then(\n  regexp(/[0-1]+/).map((x) => {\n    return int(Number.parseInt(x, 2));\n  }),\n);\n\nconst hexadecimalNumberParser: Parser<NumberValue> = regexp(/0[x]/i)\n  .then(regexp(/[0-9a-f]+/i))\n  .map((x) => {\n    return int(Number.parseInt(x, 16));\n  });\n\nconst decimalOrOctalNumberParser: Parser<NumberValue> = regexp(/\\d+/).map((x) => {\n  const base = x.startsWith('0') && /^[0-7]+$/.test(x) ? 8 : 10;\n  return int(Number.parseInt(x, base));\n});\n\nexport const floatParser: Parser<FloatValue> = seq(\n  regexp(/\\d+\\.\\d+/).map((x) => Number.parseFloat(x)),\n  alt(string('e'), string('E'))\n    .then(\n      seq(alt(string('+'), string('-')).fallback(undefined), regexp(/\\d+/)).map(([sign, _num]) => {\n        const num = Number.parseInt(_num, 10);\n        if (sign === '-') {\n          return -num;\n        }\n        return num;\n      }),\n    )\n    .fallback(0),\n)\n  .map(([num, exp]) => float(num * Math.pow(10, exp)))\n  .desc('a float');\n\nexport const numberParser: Parser<NumberValue> = seq(\n  alt(string('+'), string('-')).fallback(undefined),\n  alt(binaryNumberParser, hexadecimalNumberParser, decimalOrOctalNumberParser),\n)\n  .map(([sign, num]) => {\n    if (sign === '-') {\n      num.value = -num.value;\n    }\n    return num;\n  })\n  .desc('a number');\n\nconst stringParser: Parser<StringValue> = alt(\n  string('\\\\')\n    // eslint-disable-next-line id-denylist\n    .then(any.fallback(undefined))\n    .map((escaped) => {\n      // TODO: handle other special chars (:help expr-quote)\n      if (escaped === undefined) {\n        throw VimError.MissingQuote(); // TODO: parameter\n      } else if (escaped === '\\\\') {\n        return '\\\\';\n      } else if (escaped === '\"') {\n        return '\"';\n      } else if (escaped === 'n') {\n        return '\\n';\n      } else if (escaped === 't') {\n        return '\\t';\n      } else {\n        return `\\\\${escaped}`;\n      }\n    }),\n  noneOf('\"'),\n)\n  .many()\n  .wrap(string('\"'), string('\"'))\n  .desc('a string')\n  .map((segments) => {\n    return { type: 'string', value: segments.join('') };\n  });\n\nconst literalStringParser: Parser<StringValue> = regexp(/[^']*/)\n  .sepBy(string(\"''\"))\n  .wrap(string(\"'\"), string(\"'\"))\n  .desc('a literal string')\n  .map((segments) => {\n    return { type: 'string', value: segments.join(\"'\") };\n  });\n\nconst listParser: Parser<ListExpression> = lazy(() => expressionParser)\n  .sepBy(string(',').trim(optWhitespace))\n  .skip(string(',').atMost(1))\n  .trim(optWhitespace)\n  .wrap(string('['), string(']'))\n  .map((items) => listExpr(items))\n  .desc('a list');\n\nconst dictionaryParser: Parser<DictionaryExpression> = lazy(() =>\n  alt(\n    string('#').then(\n      seq(\n        takeWhile((char) => char !== ':')\n          .map((x) => str(x))\n          .skip(string(':'))\n          .trim(optWhitespace),\n        expressionParser,\n      )\n        .sepBy(string(',').trim(optWhitespace))\n        .skip(string(',').atMost(1))\n        .trim(optWhitespace)\n        .wrap(string('{'), string('}')),\n    ),\n    seq(expressionParser.skip(string(':').trim(optWhitespace)), expressionParser)\n      .sepBy(string(',').trim(optWhitespace))\n      .skip(string(',').atMost(1))\n      .trim(optWhitespace)\n      .wrap(string('{'), string('}')),\n  ).desc('a dictionary'),\n).map((items) => {\n  return {\n    type: 'dictionary',\n    items,\n  };\n});\n\nexport const optionParser: Parser<OptionExpression> = string('&')\n  .then(\n    seq(\n      alt(string('g'), string('l')).skip(string(':')).atMost(1),\n      regexp(/[a-z]+/).desc('&option'),\n    ),\n  )\n  .map(([scope, name]) => {\n    return { type: 'option', scope: scope ? scope[0] : undefined, name };\n  });\n\nconst nestedExpressionParser: Parser<Expression> = lazy(() => expressionParser)\n  .trim(optWhitespace)\n  .wrap(string('('), string(')'))\n  .desc('a nested expression');\n\nexport const varNameParser = regexp(/[a-zA-Z0-9_]+/);\n\nexport const variableParser: Parser<VariableExpression> = seq(\n  alt(\n    string('b'),\n    string('w'),\n    string('t'),\n    string('g'),\n    string('l'),\n    string('s'),\n    string('a'),\n    string('v'),\n  )\n    .skip(string(':'))\n    .fallback(undefined),\n  varNameParser.desc('a variable'),\n).map(([namespace, name]) => {\n  return { type: 'variable', namespace, name };\n});\n\nexport const envVariableParser: Parser<EnvVariableExpression> = string('$')\n  .then(regexp(/[a-z]+/i))\n  .desc('$ENV')\n  .map((name) => {\n    return { type: 'env_variable', name };\n  });\n\nexport const registerParser: Parser<RegisterExpression> = string('@')\n  .then(any)\n  .desc('@register')\n  .map((name) => {\n    return { type: 'register', name };\n  });\n\nconst functionArgsParser: Parser<Expression[]> = lazy(() =>\n  expressionParser\n    .sepBy(string(',').trim(optWhitespace))\n    .trim(optWhitespace)\n    .wrap(string('('), string(')')),\n);\n\nexport const functionCallParser: Parser<FunctionCallExpression> = seq(\n  regexp(/[a-z0-9_]+/).skip(optWhitespace),\n  functionArgsParser,\n)\n  .desc('a function call')\n  .map(([func, args]) => {\n    return {\n      type: 'function_call',\n      func,\n      args,\n    };\n  });\n\nconst lambdaParser: Parser<LambdaExpression> = seq(\n  regexp(/[a-z]+/i)\n    .sepBy(string(',').trim(optWhitespace))\n    .skip(string('->').trim(optWhitespace)),\n  lazy(() => expressionParser).desc('a lambda'),\n)\n  .trim(optWhitespace)\n  .wrap(string('{'), string('}'))\n  .map(([args, body]) => {\n    return lambda(args, body);\n  });\n\n// TODO: Function call with funcref\n// TODO: Variable/function with curly braces\nconst expr9Parser: Parser<Expression> = alt(\n  blobParser,\n  floatParser,\n  numberParser,\n  stringParser,\n  literalStringParser,\n  listParser,\n  dictionaryParser,\n  optionParser,\n  nestedExpressionParser,\n  functionCallParser, // NOTE: this is out of order with :help expr, but it seems necessary\n  variableParser,\n  envVariableParser,\n  registerParser,\n  lambdaParser,\n);\n\nconst indexParser: Parser<(expr: Expression) => IndexExpression> = lazy(() =>\n  expressionParser.trim(optWhitespace).wrap(string('['), string(']')),\n).map((index) => {\n  return (expression: Expression) => {\n    return { type: 'index', expression, index };\n  };\n});\n\nconst sliceParser: Parser<(expr: Expression) => SliceExpression> = lazy(() =>\n  seq(expressionParser.atMost(1).skip(string(':').trim(optWhitespace)), expressionParser.atMost(1))\n    .trim(optWhitespace)\n    .wrap(string('['), string(']')),\n).map(([start, end]) => {\n  return (expression: Expression) => {\n    return { type: 'slice', expression, start: start[0], end: end[0] };\n  };\n});\n\nconst entryParser: Parser<(expr: Expression) => EntryExpression> = string('.')\n  .then(regexp(/[a-z0-9]+/i))\n  .map((entryName) => {\n    return (expression: Expression) => {\n      return {\n        type: 'entry',\n        expression,\n        entryName,\n      };\n    };\n  });\n\nconst funcrefCallParser: Parser<(expr: Expression) => FuncrefCallExpression> = functionArgsParser\n  .desc('a funcref call')\n  .map((args) => {\n    return (expression: Expression) => {\n      return {\n        type: 'funcrefCall',\n        expression,\n        args,\n      };\n    };\n  });\n\n// TODO: Support method call with lambda\nconst methodCallParser: Parser<(expr: Expression) => MethodCallExpression> = string('->')\n  .then(seq(regexp(/[a-z]+/i), functionArgsParser))\n  .desc('a method call')\n  .map(([methodName, args]) => {\n    return (expression: Expression) => {\n      return {\n        type: 'methodCall',\n        methodName,\n        expression,\n        args,\n      };\n    };\n  });\n\nconst expr8Parser: Parser<Expression> = seq(\n  expr9Parser,\n  alt<(expr: Expression) => Expression>(\n    indexParser,\n    sliceParser,\n    entryParser,\n    funcrefCallParser,\n    methodCallParser,\n  ).many(),\n)\n  .desc('expr8')\n  .map(([expression, things]) => things.reduce((expr, thing) => thing(expr), expression));\n\n// Logical NOT, unary plus/minus\nconst expr7Parser: Parser<Expression> = alt<Expression>(\n  seq(\n    alt(string('!'), string('-'), string('+')),\n    lazy(() => expr7Parser),\n  ).map(([operator, operand]) => {\n    return { type: 'unary', operator, operand };\n  }),\n  expr8Parser,\n).desc('expr7');\n\n// Number multiplication/division/modulo\nconst expr6Parser: Parser<Expression> = seq(\n  expr7Parser,\n  seq(alt(string('*'), string('/'), string('%')).trim(optWhitespace), expr7Parser).many(),\n)\n  .map(leftAssociative)\n  .desc('expr6');\n\n// Number addition/subtraction, string/list/blob concatenation\nconst expr5Parser: Parser<Expression> = seq(\n  expr6Parser,\n  seq(\n    alt(string('+'), string('-'), string('..'), string('.')).trim(optWhitespace),\n    expr6Parser,\n  ).many(),\n)\n  .map(leftAssociative)\n  .desc('expr5');\n\n// Comparison\nconst expr4Parser: Parser<Expression> = alt<Expression>(\n  seq(\n    expr5Parser,\n    seq(\n      alt(\n        string('=='),\n        string('!='),\n        string('>'),\n        string('>='),\n        string('<'),\n        string('<='),\n        string('=~'),\n        string('!~'),\n        string('isnot'),\n        string('is'),\n      ),\n      regexp(/[#\\?]?/),\n    ).trim(optWhitespace),\n    expr5Parser,\n  ).map(([lhs, [operator, matchCase], rhs]) => {\n    return {\n      type: 'comparison',\n      operator,\n      matchCase: matchCase === '#' ? true : matchCase === '?' ? false : undefined,\n      lhs,\n      rhs,\n    };\n  }),\n  expr5Parser,\n).desc('expr4');\n\n// Logical AND\nconst expr3Parser: Parser<Expression> = seq(\n  expr4Parser,\n  seq(string('&&').trim(optWhitespace), expr4Parser).many(),\n)\n  .map(leftAssociative)\n  .desc('expr3');\n\n// Logical OR\nconst expr2Parser: Parser<Expression> = seq(\n  expr3Parser,\n  seq(string('||').trim(optWhitespace), expr3Parser).many(),\n)\n  .map(leftAssociative)\n  .desc('expr2');\n\n// If-then-else\nconst expr1Parser: Parser<Expression> = alt<Expression>(\n  seq(\n    expr2Parser,\n    string('?').trim(optWhitespace),\n    expr2Parser,\n    string(':').trim(optWhitespace),\n    expr2Parser,\n  ).map(([_if, x, _then, y, _else]) => {\n    return { type: 'ternary', if: _if, then: _then, else: _else };\n  }),\n  expr2Parser,\n).desc('an expression');\n\nfunction leftAssociative(args: [Expression, Array<[BinaryOp, Expression]>]) {\n  let lhs = args[0];\n  for (const [operator, rhs] of args[1]) {\n    lhs = binary(lhs, operator, rhs);\n  }\n  return lhs;\n}\n\nexport const expressionParser = expr1Parser;\n"
  },
  {
    "path": "src/vimscript/expression/types.ts",
    "content": "// -------------------- Values --------------------\n\nexport type NumberValue = {\n  type: 'number';\n  value: number;\n};\n\nexport type FloatValue = {\n  type: 'float';\n  value: number;\n};\n\nexport type StringValue = {\n  type: 'string';\n  value: string;\n};\n\nexport type ListValue = {\n  type: 'list';\n  items: Value[];\n  id: string;\n};\n\nexport type DictionaryValue = {\n  type: 'dictionary';\n  items: Map<string, Value>;\n  id: string;\n};\n\nexport type FuncRefValue = {\n  type: 'funcref';\n  name: string;\n  body?: (args: Value[]) => Value;\n  arglist?: ListValue;\n  dict?: DictionaryValue;\n  id: string;\n};\n\nexport type BlobValue = {\n  type: 'blob';\n  data: Uint8Array<ArrayBuffer>;\n  id: string;\n};\n\nexport type Value =\n  | NumberValue\n  | FloatValue\n  | StringValue\n  | ListValue\n  | DictionaryValue\n  | FuncRefValue\n  | BlobValue;\n\n// -------------------- Expressions --------------------\n\nexport type NumberExpression = NumberValue;\nexport type FloatExpression = FloatValue;\nexport type StringExpression = StringValue;\n\nexport type ListExpression = {\n  type: 'list';\n  items: Expression[];\n};\n\nexport type DictionaryExpression = {\n  type: 'dictionary';\n  items: Array<[Expression, Expression]>;\n};\n\nexport type FuncRefExpression = {\n  type: 'funcref';\n  name: string;\n  body?: (args: Value[]) => Value;\n  arglist?: ListExpression;\n  dict?: DictionaryExpression;\n};\n\nexport type BlobExpression = {\n  type: 'blob';\n  data: Uint8Array<ArrayBuffer>;\n};\n\nexport type OptionExpression = {\n  type: 'option';\n  scope: 'l' | 'g' | undefined;\n  name: string;\n};\n\nexport type Namespace = 'b' | 'w' | 't' | 'g' | 'l' | 's' | 'a' | 'v';\nexport type VariableExpression = {\n  type: 'variable';\n  namespace: Namespace | undefined;\n  name: string;\n};\n\nexport type EnvVariableExpression = {\n  type: 'env_variable';\n  name: string;\n};\n\nexport type RegisterExpression = {\n  type: 'register';\n  name: string;\n};\n\nexport type FunctionCallExpression = {\n  type: 'function_call';\n  func: string;\n  args: Expression[];\n};\n\nexport type LambdaExpression = {\n  type: 'lambda';\n  args: string[];\n  body: Expression;\n};\n\nexport type IndexExpression = {\n  type: 'index';\n  expression: Expression;\n  index: Expression;\n};\n\nexport type SliceExpression = {\n  type: 'slice';\n  expression: Expression;\n  start: Expression | undefined;\n  end: Expression | undefined;\n};\n\nexport type EntryExpression = {\n  type: 'entry';\n  expression: Expression;\n  entryName: string;\n};\n\nexport type FuncrefCallExpression = {\n  type: 'funcrefCall';\n  expression: Expression;\n  args: Expression[];\n};\n\nexport type MethodCallExpression = {\n  type: 'methodCall';\n  expression: Expression;\n  methodName: string;\n  args: Expression[];\n};\n\nexport type UnaryOp = '!' | '-' | '+';\nexport type UnaryExpression = {\n  type: 'unary';\n  operator: UnaryOp;\n  operand: Expression;\n};\n\nexport type ComparisonOp = '==' | '!=' | '>' | '>=' | '<' | '<=' | '=~' | '!~' | 'is' | 'isnot';\nexport type ComparisonExpression = {\n  type: 'comparison';\n  operator: ComparisonOp;\n  matchCase: boolean | undefined;\n  lhs: Expression;\n  rhs: Expression;\n};\n\nexport type BinaryOp = '*' | '/' | '%' | '.' | '..' | '-' | '+' | '&&' | '||';\nexport type BinaryExpression = {\n  type: 'binary';\n  operator: BinaryOp;\n  lhs: Expression;\n  rhs: Expression;\n};\n\nexport type TernaryExpression = {\n  type: 'ternary';\n  if: Expression;\n  then: Expression;\n  else: Expression;\n};\n\nexport type Expression =\n  | NumberExpression\n  | FloatExpression\n  | StringExpression\n  | ListExpression\n  | DictionaryExpression\n  | FuncRefExpression\n  | BlobExpression\n  | OptionExpression\n  | VariableExpression\n  | LambdaExpression\n  | IndexExpression\n  | SliceExpression\n  | EntryExpression\n  | FuncrefCallExpression\n  | MethodCallExpression\n  | EnvVariableExpression\n  | RegisterExpression\n  | FunctionCallExpression\n  | ComparisonExpression\n  | BinaryExpression\n  | UnaryExpression\n  | TernaryExpression;\n"
  },
  {
    "path": "src/vimscript/lineRange.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { alt, any, optWhitespace, Parser, seq, string, succeed } from 'parsimmon';\nimport { Position, Range } from 'vscode';\nimport { VimError } from '../error';\nimport { globalState } from '../state/globalState';\nimport { SearchState } from '../state/searchState';\nimport { VimState } from '../state/vimState';\nimport { numberParser } from './parserUtils';\nimport { Pattern, SearchDirection } from './pattern';\n\n/**\n * Specifies the start or end of a line range.\n * See :help {address}\n */\ntype LineSpecifier =\n  | {\n      // {number}\n      type: 'number';\n      num: number;\n    }\n  | {\n      // .\n      type: 'current_line';\n    }\n  | {\n      // $\n      type: 'last_line';\n    }\n  | {\n      // %\n      type: 'entire_file';\n    }\n  | {\n      // *\n      type: 'last_visual_range';\n    }\n  | {\n      // 't\n      type: 'mark';\n      mark: string;\n    }\n  | {\n      // /{pattern}[/]\n      type: 'pattern_next';\n      pattern: Pattern;\n    }\n  | {\n      // ?{pattern}[?]\n      type: 'pattern_prev';\n      pattern: Pattern;\n    }\n  | {\n      // \\/\n      type: 'last_search_pattern_next';\n    }\n  | {\n      // \\?\n      type: 'last_search_pattern_prev';\n    }\n  | {\n      // \\&\n      type: 'last_substitute_pattern_next';\n    };\n\nconst lineSpecifierParser: Parser<LineSpecifier> = alt<LineSpecifier>(\n  numberParser.map((num) => {\n    return { type: 'number', num };\n  }),\n  string('.').result({ type: 'current_line' }),\n  string('$').result({ type: 'last_line' }),\n  string('%').result({ type: 'entire_file' }),\n  string('*').result({ type: 'last_visual_range' }),\n  string(\"'\")\n    .then(any)\n    .map((mark) => {\n      return { type: 'mark', mark };\n    }),\n  string('/')\n    .then(Pattern.parser({ direction: SearchDirection.Forward }))\n    .map((pattern) => {\n      return {\n        type: 'pattern_next',\n        pattern,\n      };\n    }),\n  string('?')\n    .then(Pattern.parser({ direction: SearchDirection.Backward }))\n    .map((pattern) => {\n      return {\n        type: 'pattern_prev',\n        pattern,\n      };\n    }),\n  string('\\\\/').result({ type: 'last_search_pattern_next' }),\n  string('\\\\?').result({ type: 'last_search_pattern_prev' }),\n  string('\\\\&').result({ type: 'last_substitute_pattern_next' }),\n);\n\nconst offsetParser: Parser<number> = alt(\n  string('+').then(numberParser.fallback(1)),\n  string('-')\n    .then(numberParser.fallback(1))\n    .map((num) => -num),\n  numberParser,\n)\n  .skip(optWhitespace)\n  .atLeast(1)\n  .map((nums) => nums.reduce((x, y) => x + y, 0));\n\nexport class Address {\n  public readonly specifier: LineSpecifier;\n  public readonly offset: number;\n\n  constructor(specifier: LineSpecifier, offset?: number) {\n    this.specifier = specifier;\n    this.offset = offset ?? 0;\n  }\n\n  public static parser: Parser<Address> = alt(\n    seq(lineSpecifierParser.skip(optWhitespace), offsetParser.fallback(0)),\n    seq(succeed({ type: 'current_line' as const }), offsetParser),\n  ).map(([specifier, offset]) => {\n    return new Address(specifier, offset);\n  });\n\n  public resolve(vimState: VimState, side: 'left' | 'right', boundsCheck = true): number {\n    const line = (() => {\n      switch (this.specifier.type) {\n        case 'number':\n          if (boundsCheck) {\n            return this.specifier.num ? this.specifier.num - 1 : 0;\n          } else {\n            return this.specifier.num - 1;\n          }\n        case 'current_line':\n          return vimState.cursorStopPosition.line;\n        case 'last_line':\n          return vimState.document.lineCount - 1;\n        case 'entire_file':\n          return vimState.document.lineCount - 1;\n        case 'last_visual_range':\n          const res =\n            side === 'left'\n              ? vimState.lastVisualSelection?.start.line\n              : vimState.lastVisualSelection?.end.line;\n          if (res === undefined) {\n            throw VimError.MarkNotSet();\n          }\n          return res;\n        case 'mark':\n          const mark = vimState.historyTracker.getMark(this.specifier.mark);\n          if (!mark || (mark.isUppercaseMark && mark.document !== vimState.document)) {\n            throw VimError.MarkNotSet();\n          }\n          return mark.position.line;\n        case 'pattern_next':\n          const m = this.specifier.pattern.nextMatch(\n            vimState.document,\n            vimState.cursorStopPosition,\n          );\n          if (m === undefined) {\n            // TODO: throw proper errors for nowrapscan\n            throw VimError.PatternNotFound(this.specifier.pattern.patternString);\n          } else {\n            return m.start.line;\n          }\n        case 'pattern_prev':\n          throw new Error('Using a backward pattern in a line range is not yet supported'); // TODO\n        case 'last_search_pattern_next':\n          if (!globalState.searchState) {\n            throw VimError.NoPreviousRegularExpression();\n          }\n          const nextMatch = globalState.searchState.getNextSearchMatchPosition(\n            vimState,\n            vimState.cursorStopPosition,\n            SearchDirection.Forward,\n          );\n          if (nextMatch === undefined) {\n            // TODO: throw proper errors for nowrapscan\n            throw VimError.PatternNotFound(globalState.searchState.searchString);\n          }\n          return nextMatch.pos.line;\n        case 'last_search_pattern_prev':\n          if (!globalState.searchState) {\n            throw VimError.NoPreviousRegularExpression();\n          }\n          const prevMatch = globalState.searchState.getNextSearchMatchPosition(\n            vimState,\n            vimState.cursorStopPosition,\n            SearchDirection.Backward,\n          );\n          if (prevMatch === undefined) {\n            // TODO: throw proper errors for nowrapscan\n            throw VimError.PatternNotFound(globalState.searchState.searchString);\n          }\n          return prevMatch.pos.line;\n        case 'last_substitute_pattern_next':\n          if (!globalState.substituteState) {\n            throw VimError.NoPreviousSubstituteRegularExpression();\n          }\n          const searchState = globalState.substituteState.searchPattern\n            ? new SearchState(\n                SearchDirection.Forward,\n                vimState.cursorStopPosition,\n                globalState.substituteState.searchPattern.patternString,\n                {},\n              )\n            : undefined;\n          const match = searchState?.getNextSearchMatchPosition(\n            vimState,\n            vimState.cursorStopPosition,\n          );\n          if (match === undefined) {\n            // TODO: throw proper errors for nowrapscan\n            throw VimError.PatternNotFound(searchState?.searchString);\n          }\n          return match.pos.line;\n        default:\n          const guard: never = this.specifier;\n          throw new Error('Got unexpected LineSpecifier.type');\n      }\n    })();\n    const result = line + this.offset;\n    if (boundsCheck && (result < 0 || result > vimState.document.lineCount)) {\n      throw VimError.InvalidRange();\n    }\n    return result;\n  }\n\n  public toString(): string {\n    switch (this.specifier.type) {\n      case 'number':\n        return this.specifier.num.toString();\n      case 'current_line':\n        return '.';\n      case 'last_line':\n        return '$';\n      case 'entire_file':\n        return '%';\n      case 'last_visual_range':\n        return '*';\n      case 'mark':\n        return `'${this.specifier.mark}`;\n      case 'pattern_next':\n        return `/${this.specifier.pattern}/`;\n      case 'pattern_prev':\n        return `?${this.specifier.pattern}?`;\n      case 'last_search_pattern_next':\n        return '\\\\/';\n      case 'last_search_pattern_prev':\n        return '\\\\?';\n      case 'last_substitute_pattern_next':\n        return '\\\\&';\n      default:\n        const guard: never = this.specifier;\n        throw new Error('Got unexpected LineSpecifier.type');\n    }\n  }\n}\n\nexport class LineRange {\n  private readonly start: Address;\n  private readonly end?: Address;\n  public readonly separator?: ',' | ';';\n\n  constructor(start: Address, separator?: ',' | ';', end?: Address) {\n    this.start = start;\n    this.end = end;\n    this.separator = separator;\n  }\n\n  public static parser: Parser<LineRange> = alt(\n    seq(\n      // with the start line\n      Address.parser.skip(optWhitespace),\n      seq(\n        alt(string(','), string(';')).skip(optWhitespace),\n        Address.parser.fallback(undefined),\n      ).fallback(undefined),\n    ).map(([start, sepEnd]) => {\n      if (sepEnd) {\n        const [sep, end] = sepEnd;\n        return new LineRange(start, sep, end);\n      }\n      return new LineRange(start);\n    }),\n    seq(\n      // without the start line\n      alt(string(','), string(';')).skip(optWhitespace),\n      Address.parser.fallback(undefined),\n    ).map((sepEnd) => {\n      const [sep, end] = sepEnd;\n      return new LineRange(new Address({ type: 'current_line' }), sep, end);\n    }),\n  );\n\n  public resolve(vimState: VimState): { start: number; end: number } {\n    // TODO: *,4 is not a valid range\n    const end = this.end ?? this.start;\n\n    if (end.specifier.type === 'entire_file') {\n      return { start: 0, end: vimState.document.lineCount - 1 };\n    } else if (end.specifier.type === 'last_visual_range') {\n      if (vimState.lastVisualSelection === undefined) {\n        throw VimError.MarkNotSet();\n      }\n      return {\n        start: vimState.lastVisualSelection.start.line,\n        end: vimState.lastVisualSelection.end.line,\n      };\n    }\n\n    const left = this.start.resolve(vimState, 'left');\n    if (this.separator === ';') {\n      vimState.cursorStartPosition = vimState.cursorStopPosition = new Position(left, 0);\n    }\n    const right = end.resolve(vimState, 'right');\n    if (left > right) {\n      // Reverse the range to keep start < end\n      // NOTE: Vim generally makes you confirm before doing this, but we do it automatically.\n      return {\n        start: end.resolve(vimState, 'left'),\n        end: this.start.resolve(vimState, 'right'),\n      };\n    } else {\n      return {\n        start: left,\n        end: end.resolve(vimState, 'right'),\n      };\n    }\n  }\n\n  public resolveToRange(vimState: VimState): Range {\n    const { start, end } = this.resolve(vimState);\n    return new Range(new Position(start, 0), new Position(end, 0).getLineEnd());\n  }\n\n  public toString(): string {\n    return `${this.start.toString()}${this.separator ?? ''}${this.end?.toString() ?? ''}`;\n  }\n}\n"
  },
  {
    "path": "src/vimscript/parserUtils.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { alt, any, noneOf, Parser, regexp, seq, string, succeed, whitespace } from 'parsimmon';\nimport { configuration } from '../configuration/configuration';\n\nexport const numberParser: Parser<number> = regexp(/\\d+/)\n  .map((num) => Number.parseInt(num, 10))\n  .desc('a number');\nexport const integerParser: Parser<number> = regexp(/-?\\d+/).map((num) => Number.parseInt(num, 10));\n\nexport const bangParser: Parser<boolean> = string('!')\n  .fallback(undefined)\n  .map((bang) => bang !== undefined);\n\nexport function nameAbbrevParser(abbrev: string, rest: string): Parser<string> {\n  const suffixes = [...Array(rest.length + 1).keys()]\n    .reverse()\n    .map((idx) => rest.substring(0, idx));\n  return string(abbrev)\n    .then(alt(...suffixes.map(string)))\n    .map((suffix) => abbrev + suffix);\n}\n\n// TODO: `:help cmdline-special`\n// TODO: `:help filename-modifiers`\nexport const fileNameParser: Parser<string> = alt<string>(\n  string('\\\\').then(\n    // eslint-disable-next-line id-denylist\n    any.fallback(undefined).map((escaped) => {\n      if (escaped === undefined || escaped === '\\\\') {\n        return '\\\\';\n      } else if (escaped === ' ') {\n        return ' ';\n      } else {\n        // TODO: anything else that needs escaping?\n        return `\\\\${escaped}`;\n      }\n    }),\n  ),\n  regexp(/\\S/),\n)\n  .atLeast(1)\n  .map((chars) => chars.join(''));\n\n/**\n * Options for how a file should be encoded\n * See `:help ++opt`\n */\nexport type FileOpt = Array<[string, string | undefined]>;\nexport const fileOptParser: Parser<FileOpt> = string('++')\n  .then(\n    seq(\n      alt(\n        alt(string('ff'), string('fileformat')).result('ff'),\n        alt(string('enc'), string('encoding')).result('enc'),\n        alt(string('bin'), string('binary')).result('bin'),\n        alt(string('nobin'), string('nobinary')).result('nobin'),\n        string('bad'),\n        string('edit'),\n      ),\n      string('=').then(regexp(/\\S+/)).fallback(undefined),\n    ),\n  )\n  .sepBy(whitespace)\n  .desc('[++opt]');\n\n/**\n * A Command which will be run after opening a file\n * See `:help +cmd`\n */\nexport type FileCmd =\n  | {\n      type: 'line_number';\n      line: number;\n    }\n  | {\n      type: 'last_line';\n    };\nexport const fileCmdParser: Parser<FileCmd | undefined> = string('+')\n  .then(\n    alt<FileCmd>(\n      // Exact line number\n      numberParser.map((line) => ({ type: 'line_number', line })),\n      // TODO: Next match of pattern\n      // string('/').then(Pattern.parser({ direction: SearchDirection.Forward })),\n      // TODO: Ex command\n      // lazy(() => exCommandParser),\n      // Last line\n      succeed({ type: 'last_line' }),\n    ),\n  )\n  .fallback(undefined)\n  .desc('[+cmd]');\n\n// TODO: re-create parser when leader changes\nconst leaderParser = regexp(/<leader>/).map(() => configuration.leader); // lazy evaluation of configuration.leader\nconst specialCharacters = regexp(/<(?:Esc|C-\\w|A-\\w|C-A-\\w)>/);\n\nconst specialCharacterParser = alt(specialCharacters, leaderParser);\n\n// TODO: Add more special characters\nconst escapedParser = string('\\\\')\n  // eslint-disable-next-line id-denylist\n  .then(any.fallback(undefined))\n  .map((escaped) => {\n    if (escaped === undefined) {\n      return '\\\\\\\\';\n    } else if (escaped === 'n') {\n      return '\\n';\n    }\n    return '\\\\' + escaped;\n  });\n\nexport const keystrokesExpressionParser: Parser<string[]> = alt(\n  escapedParser,\n  specialCharacterParser,\n  regexp(/./),\n).many();\n\nexport const keystrokesExpressionForMacroParser: Parser<string[]> = alt(\n  escapedParser,\n  specialCharacterParser,\n  noneOf('\"'),\n).many();\n"
  },
  {
    "path": "src/vimscript/pattern.ts",
    "content": "// eslint-disable-next-line id-denylist\nimport { Parser, alt, any, eof, lazy, noneOf, oneOf, seq, seqMap, string } from 'parsimmon';\nimport { Position, Range, TextDocument } from 'vscode';\nimport { configuration } from '../configuration/configuration';\nimport { VimState } from '../state/vimState';\nimport { TextEditor } from '../textEditor';\nimport { LineRange } from './lineRange';\nimport { numberParser } from './parserUtils';\n\nexport function searchStringParser(args: {\n  direction: SearchDirection;\n  ignoreSmartcase?: boolean;\n}): Parser<{\n  pattern: Pattern;\n  offset: SearchOffset | undefined;\n}> {\n  return seq(\n    Pattern.parser(args),\n    lazy(() => SearchOffset.parser.fallback(undefined)),\n  ).map(([pattern, offset]) => {\n    return { pattern, offset };\n  });\n}\n\nexport enum SearchDirection {\n  Forward = 1,\n  Backward = -1,\n}\n\nexport type PatternMatch = { range: Range; groups: string[] };\n\n/**\n * See `:help pattern`\n *\n * TODO(#3996): Currently, this is a thin wrapper around JavaScript's regex engine.\n *              We should either re-implement Vim's regex engine from scratch or (more likely)\n *              implement a best-effort translation from Vim's syntax to JavaScript's.\n */\nexport class Pattern {\n  public readonly patternString: string;\n  public readonly direction: SearchDirection;\n  public readonly regex: RegExp;\n  public readonly ignorecase: boolean | undefined;\n  public readonly inSelection: boolean;\n  public readonly closed: boolean; // was an ending delimiter present?\n\n  /** `true` if this pattern has an empty branch (/foo|/, for instance) */\n  private readonly emptyBranch: boolean;\n\n  private static readonly MAX_SEARCH_TIME = 1000;\n  private static readonly SPECIAL_CHARS_REGEX = /[\\-\\[\\]{}()*+?.,\\\\\\^$|#\\s]/g;\n\n  public nextMatch(document: TextDocument, fromPosition: Position): Range | undefined {\n    if (this.emptyBranch) {\n      const pos = fromPosition.getRightThroughLineBreaks();\n      return new Range(pos, pos);\n    }\n    const haystack = document.getText();\n    this.regex.lastIndex = document.offsetAt(fromPosition) + 1;\n    const match = this.regex.exec(haystack);\n    return match\n      ? new Range(document.positionAt(match.index), document.positionAt(match.index + match.length))\n      : undefined;\n  }\n\n  /**\n   * Every range in the document that matches the search string.\n   *\n   * This might not be 100% complete - @see Pattern::MAX_SEARCH_RANGES\n   */\n  public allMatches(\n    vimState: VimState,\n    args:\n      | {\n          fromPosition: Position;\n        }\n      | {\n          lineRange: LineRange;\n        },\n  ): PatternMatch[] {\n    if (this.emptyBranch) {\n      // HACK: This pattern matches each character, but for purposes of perf when highlighting, merge them.\n      return [\n        {\n          range: TextEditor.getDocumentRange(vimState.document),\n          groups: [],\n        },\n      ];\n    }\n\n    let fromPosition: Position;\n    let lineRange:\n      | {\n          start: number;\n          end: number;\n        }\n      | undefined;\n    if ('lineRange' in args) {\n      // TODO: We should be able to get away with only getting part of the document text in this case\n      lineRange = args.lineRange.resolve(vimState);\n      fromPosition = new Position(lineRange.start, 0);\n    } else {\n      fromPosition = args.fromPosition;\n    }\n\n    let haystack: string;\n    let searchOffset: number;\n    let startOffset: number;\n    if (this.inSelection && vimState.lastVisualSelection) {\n      // TODO: This is not exactly how Vim implements in-selection search (\\%V), see :help \\%V for more info.\n      const searchRange = new Range(\n        vimState.lastVisualSelection.start,\n        vimState.lastVisualSelection.end,\n      );\n      haystack = vimState.document.getText(searchRange);\n      searchOffset = vimState.document.offsetAt(vimState.lastVisualSelection.start);\n      startOffset = searchRange.contains(fromPosition)\n        ? vimState.document.offsetAt(fromPosition) - searchOffset\n        : 0;\n    } else {\n      haystack = vimState.document.getText();\n      searchOffset = 0;\n      startOffset = vimState.document.offsetAt(fromPosition) - searchOffset;\n    }\n\n    this.regex.lastIndex = startOffset;\n\n    const start = Date.now();\n\n    const matchRanges = {\n      beforeWrapping: [] as PatternMatch[],\n      afterWrapping: [] as PatternMatch[],\n    };\n    let wrappedOver = false;\n    while (true) {\n      const match = this.regex.exec(haystack);\n\n      if (match) {\n        if (wrappedOver && match.index >= startOffset) {\n          // We've found our first match again\n          break;\n        }\n\n        const matchRange = new Range(\n          vimState.document.positionAt(searchOffset + match.index),\n          vimState.document.positionAt(searchOffset + match.index + match[0].length),\n        );\n        if (\n          !this.inSelection &&\n          lineRange &&\n          (matchRange.start.line < lineRange.start || matchRange.end.line > lineRange.end)\n        ) {\n          break;\n        }\n\n        (wrappedOver ? matchRanges.afterWrapping : matchRanges.beforeWrapping).push({\n          range: matchRange,\n          groups: match,\n        });\n\n        if (Date.now() - start > Pattern.MAX_SEARCH_TIME) {\n          break;\n        }\n\n        // When we find a zero-length match, nudge the search position forward to avoid getting stuck\n        if (matchRange.start.isEqual(matchRange.end)) {\n          this.regex.lastIndex++;\n        }\n      } else if (!wrappedOver) {\n        // We need to wrap around to the back if we reach the end.\n        this.regex.lastIndex = 0;\n        wrappedOver = true;\n      } else {\n        break;\n      }\n    }\n\n    return matchRanges.afterWrapping.concat(matchRanges.beforeWrapping);\n  }\n\n  private static compileRegex(regexString: string, ignoreCase?: boolean): RegExp {\n    const flags = (ignoreCase ?? configuration.ignorecase) ? 'gim' : 'gm';\n    try {\n      return new RegExp(regexString, flags);\n    } catch (err) {\n      // Couldn't compile the regexp, try again with special characters escaped\n      return new RegExp(regexString.replace(Pattern.SPECIAL_CHARS_REGEX, '\\\\$&'), flags);\n    }\n  }\n\n  public static parser(args: {\n    direction: SearchDirection;\n    ignoreSmartcase?: boolean;\n    delimiter?: string;\n  }): Parser<Pattern> {\n    const delimiter = args.delimiter\n      ? args.delimiter\n      : args.direction === SearchDirection.Forward\n        ? '/'\n        : '?';\n    // TODO: Some escaped characters need special treatment\n    return seqMap(\n      string('|').result(true).fallback(false), // Leading | matches everything\n      alt(\n        string('\\\\%V').map((_) => ({ inSelection: true })),\n        string('$').map(() => '(?:$(?<!\\\\r))'), // prevents matching \\r\\n as two lines\n        string('^').map(() => '(?:^(?<!\\\\r))'), // prevents matching \\r\\n as two lines\n        string('|')\n          .then(eof)\n          .map(() => ({ emptyBranch: true })), // Trailing | matches everything\n        string('\\\\')\n          // eslint-disable-next-line id-denylist\n          .then(any.fallback(undefined))\n          .map((escaped) => {\n            if (escaped === undefined) {\n              return '\\\\\\\\';\n            } else if (escaped === delimiter) {\n              return delimiter;\n            } else if (escaped === 'c') {\n              return { ignorecase: true };\n            } else if (escaped === 'C') {\n              return { ignorecase: false };\n            } else if (escaped === 'x') {\n              return '[0-9A-Fa-f]'; // Hex digit\n            } else if (escaped === 'X') {\n              return '[^0-9A-Fa-f]'; // Non-hex digit\n            } else if (escaped === 'o') {\n              return '[0-7]'; // Octal digit\n            } else if (escaped === 'o') {\n              return '[^0-7]'; // Non-octal digit\n            } else if (escaped === 'h') {\n              return '[A-Za-z_]'; // Head of word character\n            } else if (escaped === 'H') {\n              return '[^A-Za-z_]'; // Non-head of word character\n            } else if (escaped === 'a') {\n              return '[A-Za-z]'; // Alphabetic character\n            } else if (escaped === 'A') {\n              return '[^A-Za-z]'; // Non-alphabetic character\n            } else if (escaped === 'l') {\n              // TODO: Lowercase and uppercase don't work with ignorecase\n              return '[a-z]'; // Lowercase character\n            } else if (escaped === 'L') {\n              return '[^a-z]'; // Non-lowercase character\n            } else if (escaped === 'u') {\n              return '[A-Z]'; // Uppercase character\n            } else if (escaped === 'U') {\n              return '[^A-Z]'; // Non-uppercase character\n            } else if (escaped === '<' || escaped === '>') {\n              // TODO: not QUITE the same\n              return '\\\\b';\n            } else if (escaped === 'n') {\n              return '\\\\r?\\\\n';\n            }\n            return '\\\\' + escaped;\n          }),\n        alt(\n          // Allow unescaped delimiter inside [], and don't transform ^ or $\n          string('\\\\')\n            // eslint-disable-next-line id-denylist\n            .then(any.fallback(undefined))\n            .map((escaped) => '\\\\' + (escaped ?? '\\\\')),\n          noneOf(']'),\n        )\n          .many()\n          .wrap(string('['), string(']'))\n          .map((result) => '[' + result.join('') + ']'),\n        noneOf(delimiter),\n      ).many(),\n      string(delimiter).fallback(undefined),\n      (leadingBar, atoms, delim) => {\n        let patternString = leadingBar ? '|' : '';\n        let caseOverride: boolean | undefined;\n        let inSelection: boolean | undefined;\n        let emptyBranch: boolean = leadingBar;\n        for (const atom of atoms) {\n          if (typeof atom === 'string') {\n            patternString += atom;\n          } else {\n            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n            if (atom.emptyBranch) {\n              emptyBranch = true;\n              patternString += '|';\n              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n            } else if (atom.ignorecase) {\n              caseOverride = true;\n              // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n            } else if (atom.inSelection) {\n              // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access\n              inSelection = atom.inSelection;\n            } else if (caseOverride === undefined) {\n              caseOverride = false;\n            }\n          }\n        }\n        return {\n          patternString,\n          caseOverride,\n          inSelection,\n          closed: delim !== undefined,\n          emptyBranch,\n        };\n      },\n    ).map(({ patternString, caseOverride, inSelection, closed, emptyBranch }) => {\n      const ignoreCase = Pattern.getIgnoreCase(patternString, {\n        caseOverride,\n        ignoreSmartcase: args.ignoreSmartcase ?? false,\n      });\n      return new Pattern(\n        patternString,\n        args.direction,\n        Pattern.compileRegex(patternString, ignoreCase),\n        inSelection ?? false,\n        closed,\n        emptyBranch,\n      );\n    });\n  }\n\n  private static getIgnoreCase(\n    patternString: string,\n    flags: { caseOverride?: boolean; ignoreSmartcase: boolean },\n  ): boolean {\n    if (flags.caseOverride !== undefined) {\n      return flags.caseOverride;\n    } else if (configuration.smartcase && !flags.ignoreSmartcase && /[A-Z]/.test(patternString)) {\n      return false;\n    }\n    return configuration.ignorecase;\n  }\n\n  private constructor(\n    patternString: string,\n    direction: SearchDirection,\n    regex: RegExp,\n    inSelection: boolean,\n    closed: boolean,\n    emptyBranch: boolean,\n  ) {\n    this.patternString = patternString;\n    this.direction = direction;\n    // TODO: Recalculate ignorecase if relevant config changes?\n    this.regex = regex;\n    this.inSelection = inSelection;\n    this.closed = closed;\n    this.emptyBranch = emptyBranch;\n  }\n}\n\ntype SearchOffsetData =\n  | {\n      type: 'lines' | 'chars_from_start' | 'chars_from_end';\n      delta: number;\n    }\n  | {\n      type: 'pattern';\n      direction: SearchDirection;\n      pattern: Pattern;\n      offset?: SearchOffset;\n    };\n\nconst searchOffsetTypeParser = oneOf('esb')\n  .fallback(undefined)\n  .map((esb) => {\n    if (esb === undefined) {\n      return 'lines';\n    } else {\n      return esb === 'e' ? 'chars_from_end' : 'chars_from_start';\n    }\n  });\n\n/**\n * See `:help search-offset`\n */\nexport class SearchOffset {\n  private readonly data: SearchOffsetData;\n\n  public static parser: Parser<SearchOffset> = alt(\n    seq(searchOffsetTypeParser, oneOf('+-').fallback('+'), numberParser).map(\n      ([type, sign, num]) =>\n        new SearchOffset({\n          type,\n          delta: sign === '-' ? -num : num,\n        }),\n    ),\n    seq(searchOffsetTypeParser, oneOf('+-')).map(\n      ([type, sign]) =>\n        new SearchOffset({\n          type,\n          delta: sign === '-' ? -1 : 1,\n        }),\n    ),\n    seq(searchOffsetTypeParser).map(([type]) => new SearchOffset({ type, delta: 0 })),\n    string(';/')\n      .then(searchStringParser({ direction: SearchDirection.Forward }))\n      .map(({ pattern, offset }) => {\n        return new SearchOffset({\n          type: 'pattern',\n          direction: SearchDirection.Forward,\n          pattern,\n          offset,\n        });\n      }),\n    string(';?')\n      .then(searchStringParser({ direction: SearchDirection.Backward }))\n      .map(({ pattern, offset }) => {\n        return new SearchOffset({\n          type: 'pattern',\n          direction: SearchDirection.Backward,\n          pattern,\n          offset,\n        });\n      }),\n  );\n\n  public constructor(data: SearchOffsetData) {\n    this.data = data;\n  }\n\n  public apply(match: Range): Position {\n    switch (this.data.type) {\n      case 'lines':\n        return this.data.delta === 0\n          ? match.start\n          : new Position(match.end.line + this.data.delta, 0);\n      case 'chars_from_start':\n        return match.start.getOffsetThroughLineBreaks(this.data.delta);\n      case 'chars_from_end':\n        return match.end.getOffsetThroughLineBreaks(this.data.delta - 1);\n      case 'pattern': // TODO(#3919): Support `;` offset (`:help //;`)\n      default:\n        const guard: unknown = this.data;\n        throw new Error('Unexpected SearchOffset type');\n    }\n  }\n}\n"
  },
  {
    "path": "syntaxes/vimscript.tmLanguage.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json\",\n  \"name\": \"Vimscript\",\n  \"fileTypes\": [\".vim\", \".vimrc\"],\n  \"scopeName\": \"source.vimscript\",\n  \"patterns\": [\n    {\n      \"name\": \"comment.line\",\n      \"match\": \"(^| |\\t)\\\".*$\"\n    },\n    {\n      \"name\": \"entity.name.function\",\n      \"match\": \"^( |\\t)*(let|const|eval|call)\"\n    },\n    {\n      \"name\": \"entity.name.function\",\n      \"match\": \"^( |\\t)*(map|nmap|vmap|smap|xmap|omap|map!|imap|lmap|cmap)\"\n    },\n    {\n      \"name\": \"entity.name.function\",\n      \"match\": \"^( |\\t)*(noremap|nnoremap|vnoremap|snoremap|xnoremap|onoremap|noremap!|inoremap|lnoremap|cnoremap)\"\n    },\n    {\n      \"name\": \"entity.name.function\",\n      \"match\": \"^( |\\t)*(unmap|nunmap|vunmap|sunmap|xunmap|ounmap|unmap!|iunmap|lunmap|cunmap)\"\n    },\n    {\n      \"name\": \"entity.name.function\",\n      \"match\": \"^( |\\t)*set\"\n    },\n    {\n      \"name\": \"constant.numeric\",\n      \"match\": \"\\\\d+(\\\\.\\\\d+)?\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<Leader>\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<(CR|Enter|Return)>\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<(BS|Del|Space)>\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<Esc>\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<(Up|Down|Left|Right)>\"\n    },\n    {\n      \"name\": \"constant\",\n      \"match\": \"(?i)<[CD]-.>\"\n    },\n    {\n      \"name\": \"string.unquoted\",\n      \"match\": \"(?i)[\\\\w-]+(\\\\.[\\\\w-]+)+\"\n    }\n  ]\n}\n"
  },
  {
    "path": "test/actions/baseAction.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { BaseAction } from '../../src/actions/base';\nimport { EasyMotion } from '../../src/actions/plugins/easymotion/easymotion';\nimport { Mode } from '../../src/mode/mode';\nimport { VimState } from '../../src/state/vimState';\n\nclass TestAction1D extends BaseAction {\n  keys = ['a', 'b'];\n  actionType = 'command' as const;\n  modes = [Mode.Normal];\n}\n\nclass TestAction2D extends BaseAction {\n  keys = [\n    ['a', 'b'],\n    ['c', 'd'],\n  ];\n  actionType = 'command' as const;\n  modes = [Mode.Normal];\n}\n\nsuite('base action', () => {\n  const action1D = new TestAction1D();\n  const action2D = new TestAction2D();\n  let vimState: VimState;\n\n  suiteSetup(async () => {\n    vimState = new VimState(vscode.window.activeTextEditor!, new EasyMotion());\n    await vimState.load();\n  });\n\n  test('compare key presses', () => {\n    const testCases: Array<[string[] | string[][], string[], boolean]> = [\n      [['a'], ['a'], true],\n      [[['a']], ['a'], true],\n      [[['a'], ['b']], ['b'], true],\n      [[['a'], ['b']], ['c'], false],\n      [['a', 'b'], ['a', 'b'], true],\n      [['a', 'b'], ['a', 'c'], false],\n      [\n        [\n          ['a', 'b'],\n          ['c', 'd'],\n        ],\n        ['c', 'd'],\n        true,\n      ],\n      [[''], ['a'], false],\n      [['<Esc>'], ['<Esc>'], true],\n    ];\n\n    for (const test of testCases) {\n      const [left, right, expected] = test;\n\n      const actual = BaseAction.CompareKeypressSequence(left, right);\n      assert.strictEqual(actual, expected, `${left}. ${right}.`);\n    }\n  });\n\n  test('couldActionApply 1D keys positive', () => {\n    assert.strictEqual(action1D.couldActionApply(vimState, ['a']), true);\n  });\n\n  test('couldActionApply 1D keys negative', () => {\n    assert.strictEqual(action1D.couldActionApply(vimState, ['b']), false);\n  });\n\n  test('couldActionApply 2D keys positive', () => {\n    assert.strictEqual(action2D.couldActionApply(vimState, ['c']), true);\n  });\n\n  test('couldActionApply 2D keys negative', () => {\n    assert.strictEqual(action2D.couldActionApply(vimState, ['b']), false);\n  });\n\n  test('doesActionApply 1D keys positive', () => {\n    assert.strictEqual(action1D.doesActionApply(vimState, ['a', 'b']), true);\n  });\n\n  test('doesActionApply 1D keys negative', () => {\n    assert.strictEqual(action1D.doesActionApply(vimState, ['a', 'a']), false);\n  });\n\n  test('doesActionApply 2D keys positive', () => {\n    assert.strictEqual(action2D.doesActionApply(vimState, ['c', 'd']), true);\n  });\n\n  test('doesActionApply 2D keys negative', () => {\n    assert.strictEqual(action2D.doesActionApply(vimState, ['a', 'a']), false);\n  });\n});\n"
  },
  {
    "path": "test/actions/insertLine.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('insertLineBefore', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        tabstop: 4,\n        expandtab: true,\n      },\n    });\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('tabs are added to match previous line even if line above does not match', async () => {\n    // Setup the test\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', 'G']);\n    await modeHandler.handleMultipleKeyEvents('i\\na'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('2G>>ob\\nc'.split(''));\n\n    // This is the current state of the document\n    //\n    //    a\n    //    b\n    //    c\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '2', 'G', 'O', 'a']);\n    const text = vscode.window.activeTextEditor?.document.getText().split('\\n');\n    assert.ok(text);\n    assert.strictEqual(text[1].replace(/[\\n\\r]/g, ''), text[2].replace(/[\\n\\r]/g, ''));\n  });\n\n  test('no extra whitespace added when insertLineBefore inserts correct amount', async () => {\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', 'G']);\n    await modeHandler.handleMultipleKeyEvents('i\\na'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('2G>>ob\\nc'.split(''));\n\n    // This is the current state of the document\n    //\n    //    a\n    //    b\n    //    c\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '3', 'G', 'O', 'b']);\n    const text = vscode.window.activeTextEditor?.document.getText().split('\\n');\n    assert.ok(text);\n    assert.strictEqual(text[2].replace(/[\\n\\r]/g, ''), text[3].replace(/[\\n\\r]/g, ''));\n  });\n\n  test('no extra whitespace left when insertLineBefore inserts more than correct amount', async () => {\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', 'G']);\n    await modeHandler.handleMultipleKeyEvents('i\\na'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('2G>>ob\\nc'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('3G>>'.split(''));\n\n    // This is the current state of the document\n    //\n    //    a\n    //        b\n    //    c\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '4', 'G', '2', 'O', 'c']);\n    const text = vscode.window.activeTextEditor?.document.getText().split('\\n');\n    //\n    //    a\n    //        b\n    //    c\n    //    c\n    //    c\n    assert.ok(text);\n    assert.strictEqual(text[3].replace(/[\\n\\r]/g, ''), text[4].replace(/[\\n\\r]/g, ''));\n    assert.strictEqual(text[4].replace(/[\\n\\r]/g, ''), text[5].replace(/[\\n\\r]/g, ''));\n  });\n\n  test('works at the top of the document', async () => {\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', 'G']);\n    await modeHandler.handleMultipleKeyEvents('ia'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('gg>>'.split(''));\n\n    // This is the current state of the document\n    //    a\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'O', 'a']);\n    const text = vscode.window.activeTextEditor?.document.getText().split('\\n');\n    assert.ok(text);\n    assert.strictEqual(text[0].replace(/[\\n\\r]/g, ''), text[1].replace(/[\\n\\r]/g, ''));\n  });\n\n  test('works with multiple cursors', async () => {\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', 'G']);\n    await modeHandler.handleMultipleKeyEvents('oa'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n    await modeHandler.handleMultipleKeyEvents('2G>>'.split(''));\n    // This is the current state of the document\n    //\n    //    a\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '2', 'G', '2', 'O', 'a']);\n    // After\n    //\n    //    a\n    //    a\n    //    a\n    const text = vscode.window.activeTextEditor?.document.getText().split('\\n');\n    assert.ok(text);\n    assert.strictEqual(text[1].replace(/[\\n\\r]/g, ''), text[2].replace(/[\\n\\r]/g, ''));\n    assert.strictEqual(text[2].replace(/[\\n\\r]/g, ''), text[3].replace(/[\\n\\r]/g, ''));\n  });\n});\n"
  },
  {
    "path": "test/actions/languages/python/motion.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { Position, TextDocument } from 'vscode';\nimport { PythonDocument } from '../../../../src/actions/languages/python/motion';\n\nsuite('PythonDocument lines generator', () => {\n  let _lines: string[];\n  let doc: TextDocument;\n\n  setup(() => {\n    _lines = ['x', 'y', 'z'];\n\n    doc = {\n      lineCount: _lines.length,\n      lineAt: (line: number) => {\n        return { text: _lines[line] };\n      },\n    } as TextDocument;\n  });\n\n  test('lines() generator', () => {\n    // GIVEN\n\n    // WHEN\n    const array = [...PythonDocument.lines(doc)];\n\n    // THEN\n    assert.equal(array.length, 3);\n    assert.equal(array[0], 'x');\n    assert.equal(array[1], 'y');\n    assert.equal(array[2], 'z');\n  });\n});\n\nsuite('PythonDocument constructor without parsing', () => {\n  test('constructor', () => {\n    // GIVEN\n    const doc = { lineCount: 0 } as TextDocument;\n\n    // WHEN\n    const pydoc = new PythonDocument(doc);\n\n    // THEN: Object construction succeeds\n  });\n});\n\n/*\n * Create a fake TextDocument object from a provided array of strings.\n * This has the minimal interface required by the find functionality.\n */\nfunction fakeTextDocument(lines: string[]): TextDocument {\n  return {\n    lineCount: lines.length,\n    lineAt: (line: number) => {\n      return { text: lines[line] };\n    },\n  } as TextDocument;\n}\n\nsuite('PythonDocument parse lines to extract structure', () => {\n  let lines: string[];\n  let doc: TextDocument;\n\n  setup(() => {\n    lines = [\n      'def first(x, y):', // 0\n      '    pass',\n      '',\n      'class A:',\n      '',\n      '    def inner(self):', // 5\n      '        pass',\n      '',\n      'def second():',\n      '    pass',\n    ];\n\n    doc = fakeTextDocument(lines);\n  });\n\n  test('parse doc to calculate indentation', () => {\n    // WHEN\n    const parsed = PythonDocument._parseLines(doc);\n    const line1 = parsed[0];\n    const line2 = parsed[1];\n    const line4 = parsed[3];\n    const line5 = parsed[4];\n\n    // THEN\n    assert.equal(parsed.length, 7);\n\n    assert.equal(line1.line, 0);\n    assert.equal(line1.indentation, 0);\n    assert.equal(line1.text, 'def first(x, y):');\n\n    assert.equal(line2.line, 1);\n    assert.equal(line2.indentation, 4);\n\n    assert.equal(line4.line, 5);\n    assert.equal(line4.indentation, 4);\n\n    assert.equal(line5.line, 6);\n    assert.equal(line5.indentation, 8);\n  });\n\n  test('document structure extraction', () => {\n    // GIVEN\n    const pydoc = new PythonDocument(doc);\n\n    // WHEN\n    const structure = pydoc.structure;\n\n    const func1 = structure[0];\n    const class1 = structure[1];\n    const func2 = structure[2];\n    const func3 = structure[3];\n\n    // THEN\n    assert.equal(func1.type, 'function');\n    assert.equal(func1.start.line, 0);\n    assert.equal(func1.start.character, 0);\n    assert.equal(func1.end.line, 1);\n    assert.equal(func1.end.character, 7);\n\n    assert.equal(class1.type, 'class');\n    assert.equal(class1.start.line, 3);\n    assert.equal(class1.start.character, 0);\n    assert.equal(class1.end.line, 6);\n    assert.equal(class1.end.character, 11);\n\n    assert.equal(func2.type, 'function');\n    assert.equal(func2.start.line, 5);\n    assert.equal(func2.start.character, 4);\n    assert.equal(func2.end.line, 6);\n    assert.equal(func2.end.character, 11);\n\n    assert.equal(func3.type, 'function');\n    assert.equal(func3.start.line, 8);\n    assert.equal(func3.start.character, 0);\n    assert.equal(func3.end.line, 9);\n    assert.equal(func3.end.character, 7);\n  });\n});\n\nsuite('PythonDocument._textIndentation and PythonDocument._parseLine', () => {\n  test('indentation of line with none', () => {\n    // GIVEN\n    const line = 'x = 42';\n\n    // WHEN\n    const indent = PythonDocument._indentation(line);\n    const info = PythonDocument._parseLine(42, line);\n\n    // THEN\n    assert.equal(indent, 0);\n\n    assert.notEqual(info, undefined);\n    assert(info !== undefined);\n    assert.equal(info.line, 42);\n    assert.equal(info.indentation, 0);\n    assert.equal(info.text, line);\n  });\n\n  test('indentation of line with 4 spaces', () => {\n    // GIVEN\n    const line = '    x = 42';\n\n    // WHEN\n    const indent = PythonDocument._indentation(line);\n    const info = PythonDocument._parseLine(42, line);\n\n    // THEN\n    assert.equal(indent, 4);\n\n    assert.notEqual(info, undefined);\n    assert(info !== undefined);\n    assert.equal(info.line, 42);\n    assert.equal(info.indentation, 4);\n    assert.equal(info.text, line);\n  });\n\n  test('indentation of line starting with a comment', () => {\n    // GIVEN\n    const line = '    # x = 42';\n\n    // WHEN\n    const indent = PythonDocument._indentation(line);\n    const info = PythonDocument._parseLine(42, line);\n\n    // THEN\n    assert.equal(indent, undefined);\n    assert.equal(info, undefined);\n  });\n\n  test('indentation of line containing only whitespace', () => {\n    // GIVEN\n    const line = '    ';\n\n    // WHEN\n    const indent = PythonDocument._indentation(line);\n    const info = PythonDocument._parseLine(42, line);\n\n    // THEN\n    assert.equal(indent, undefined);\n    assert.equal(info, undefined);\n  });\n\n  test('indentation of empty line', () => {\n    // GIVEN\n    const line = '';\n\n    // WHEN\n    const indent = PythonDocument._indentation(line);\n    const info = PythonDocument._parseLine(42, line);\n\n    // THEN\n    assert.equal(indent, undefined);\n    assert.equal(info, undefined);\n  });\n});\n\n// Type of the find function after all but the last arg have been binded\ntype Find = (position: Position) => Position | undefined;\n\n/*\n * Use fakeDocument to create a fake PythonDocument based on the passed in\n * array of strings.\n */\nfunction fakePythonDocument(lines: string[]): PythonDocument {\n  const doc = fakeTextDocument(lines);\n  return new PythonDocument(doc);\n}\n\nsuite('PythonDocument find function functionality', () => {\n  let findNextFunctionStart: Find;\n  let findPrevFunctionStart: Find;\n  let findNextFunctionEnd: Find;\n  let findPrevFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      \"'''Module docstring.'''\", // 0\n      '',\n      'def first(x, y):',\n      '# a mis-placed comment',\n      '    pass',\n      '', // 5\n      'p = 42',\n      '',\n      'def second(a, b):',\n      '',\n      '    def inner():', // 10\n      '        pass',\n      '',\n      'x = 139',\n      'greeting = \"Hello\"',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findNextFunctionStart = pydoc.find.bind(pydoc, 'function', 'next', 'start');\n    findPrevFunctionStart = pydoc.find.bind(pydoc, 'function', 'prev', 'start');\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n    findPrevFunctionEnd = pydoc.find.bind(pydoc, 'function', 'prev', 'end');\n  });\n\n  test('valid findNextFunctionStart, start of file', () => {\n    // GIVEN\n    const position = new Position(0, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextFunctionStart, past outer function', () => {\n    // GIVEN\n    const position = new Position(8, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 10);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('Invalid findNextFunctionStart, past last function', () => {\n    // GIVEN\n    const position = new Position(10, 6);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('valid findPrevFunctionStart, middle of function', () => {\n    // GIVEN\n    const position = new Position(3, 8);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findPrevFunctionStart, start of inner function', () => {\n    // GIVEN\n    const position = new Position(10, 4);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 8);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findPrevFunctionStart, start of second function', () => {\n    // GIVEN\n    const position = new Position(8, 0);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('invalid findPrevFunctionStart, above first function', () => {\n    // GIVEN\n    const position = new Position(0, 7);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('Invalid findNextFunctionEnd, past last indented block', () => {\n    // GIVEN\n    const position = new Position(13, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('valid findNextFuntionEnd, inside inner function', () => {\n    // GIVEN\n    const position = new Position(10, 10);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextFuntionEnd, from inside outer function', () => {\n    // GIVEN\n    const position = new Position(9, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextFunctionEnd, from start of function', () => {\n    // GIVEN\n    const position = new Position(2, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 4);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findNextFunctionEnd, in middle from outside any function', () => {\n    // GIVEN\n    const position = new Position(6, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextFunctionEnd, at exact end of first function', () => {\n    // GIVEN\n    const position = new Position(4, 7);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findPrevFunctionEnd, after first function, outside any function', () => {\n    // GIVEN\n    const position = new Position(6, 2);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 4);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findPrevFunctionEnd, inside second function', () => {\n    // GIVEN\n    const position = new Position(9, 0);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 4);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findPrevFunctionEnd, inside second function-s inner function', () => {\n    // GIVEN\n    const position = new Position(10, 7);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 4);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findPrevFunctionEnd, after last function', () => {\n    // GIVEN\n    const position = new Position(13, 4);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('invalid findPrevFunctionEnd, before first function', () => {\n    // GIVEN\n    const position = new Position(1, 0);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n});\n\nsuite('PythonDocument find mixed async/regular function functionality', () => {\n  let findNextFunctionStart: Find;\n  let findPrevFunctionStart: Find;\n  let findNextFunctionEnd: Find;\n  let findPrevFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      \"'''Module docstring.'''\", // 0\n      '',\n      'async def first(x, y):',\n      '# a mis-placed comment',\n      '    pass',\n      '', // 5\n      'def regular(z):',\n      '    pass',\n      '',\n      'async def second(a, b):',\n      '', // 10\n      '    def inner():',\n      '        pass',\n      '',\n      '    async def inner_async():',\n      '        pass', // 15\n      '',\n      'x = 139',\n      'greeting = \"Hello\"',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findNextFunctionStart = pydoc.find.bind(pydoc, 'function', 'next', 'start');\n    findPrevFunctionStart = pydoc.find.bind(pydoc, 'function', 'prev', 'start');\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n    findPrevFunctionEnd = pydoc.find.bind(pydoc, 'function', 'prev', 'end');\n  });\n\n  test('valid findNextFunctionStart, start of file to async function', () => {\n    // GIVEN\n    const position = new Position(0, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextFunctionStart, async to regular function', () => {\n    // GIVEN\n    const position = new Position(4, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 6);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextFunctionStart, regular to async function', () => {\n    // GIVEN\n    const position = new Position(7, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextFunctionStart, outer async to inner regular', () => {\n    // GIVEN\n    const position = new Position(9, 2);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('valid findNextFunctionStart, inner regular to inner async', () => {\n    // GIVEN\n    const position = new Position(11, 8);\n\n    // WHEN\n    const newPosition = findNextFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 14);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('valid findPrevFunctionStart, from inner async to inner regular', () => {\n    // GIVEN\n    const position = new Position(14, 8);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 11);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('valid findPrevFunctionStart, from inner regular to outer async', () => {\n    // GIVEN\n    const position = new Position(11, 8);\n\n    // WHEN\n    const newPosition = findPrevFunctionStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextFunctionEnd, from async function start', () => {\n    // GIVEN\n    const position = new Position(2, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 4);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findNextFunctionEnd, from regular function to end of async function', () => {\n    // GIVEN\n    const position = new Position(6, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 7);\n    assert.equal(newPosition.character, 7);\n  });\n\n  test('valid findPrevFunctionEnd, from end of inner async to inner regular', () => {\n    // GIVEN\n    const position = new Position(15, 11);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 12);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findPrevFunctionEnd, from end of inner regular to regular function', () => {\n    // GIVEN\n    const position = new Position(12, 11);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 7);\n    assert.equal(newPosition.character, 7);\n  });\n});\n\nsuite('PythonDocument find function functionality in doc containing class', () => {\n  let findNextFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'def first(x, y):', // 0\n      '    pass',\n      '',\n      'class A:',\n      '',\n      '    def inner(self):', // 5\n      '        pass',\n      '',\n      'def second():',\n      '    pass',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n  });\n\n  test('valid findNextFunctionEnd, from within a class that follows a function', () => {\n    // GIVEN\n    const position = new Position(4, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 6);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextFunctionEnd, from inside last function at end of file', () => {\n    // GIVEN\n    const position = new Position(8, 6);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 7);\n  });\n});\n\nsuite('PythonDocument find async function functionality in doc containing class', () => {\n  let findNextFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'async def first(x, y):', // 0\n      '    pass',\n      '',\n      'class A:',\n      '',\n      '    async def inner(self):', // 5\n      '        pass',\n      '',\n      'async def second():',\n      '    pass',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n  });\n\n  test('valid findNextFunctionEnd, from within a class that follows an async function', () => {\n    // GIVEN\n    const position = new Position(4, 0);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 6);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextFunctionEnd, from inside last async function at end of file', () => {\n    // GIVEN\n    const position = new Position(8, 6);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 7);\n  });\n});\n\nsuite('PythonDocument find function functionality near end of file', () => {\n  let findNextFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'def first(x, y):', // 0\n      '    pass',\n      '',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n  });\n\n  test('valid findNextFunctionEnd, function ends with single empty line after it before file ends', () => {\n    // GIVEN\n    const position = new Position(0, 6);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 1);\n    assert.equal(newPosition.character, 7);\n  });\n});\n\nsuite('PythonDocument findPrevFunctionEnd with nested async functions', () => {\n  let findPrevFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'async def outer(a, b):', // 0\n      '',\n      '    async def inner():',\n      '        pass',\n      '',\n      '    return inner', // 5\n      '',\n      'x = 139',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findPrevFunctionEnd = pydoc.find.bind(pydoc, 'function', 'prev', 'end');\n  });\n\n  test('findPrevFunctionEnd from after nested async function should find outer function-s end', () => {\n    // GIVEN\n    const position = new Position(7, 4);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 5);\n    assert.equal(newPosition.character, 15);\n  });\n});\n\nsuite('PythonDocument findPrevFunctionEnd with nested functions', () => {\n  let findPrevFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'def outer(a, b):', // 0\n      '',\n      '    def inner():',\n      '        pass',\n      '',\n      '    return inner', // 5\n      '',\n      'x = 139',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n\n    findPrevFunctionEnd = pydoc.find.bind(pydoc, 'function', 'prev', 'end');\n  });\n\n  test('findPrevFunctoinEnd from after nested function should find outer function-s end', () => {\n    // GIVEN\n    const position = new Position(7, 4);\n\n    // WHEN\n    const newPosition = findPrevFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 5);\n    assert.equal(newPosition.character, 15);\n  });\n});\n\nsuite('PythonDocument find async function functionality near end of file', () => {\n  let findNextFunctionEnd: Find;\n\n  setup(() => {\n    const lines = [\n      'async def first(x, y):', // 0\n      '    pass',\n      '',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n    findNextFunctionEnd = pydoc.find.bind(pydoc, 'function', 'next', 'end');\n  });\n\n  test('valid findNextFunctionEnd, async function ends with single empty line after it before file ends', () => {\n    // GIVEN\n    const position = new Position(0, 6);\n\n    // WHEN\n    const newPosition = findNextFunctionEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 1);\n    assert.equal(newPosition.character, 7);\n  });\n});\n\nsuite('PythonDocument find class functionality', () => {\n  let findNextClassStart: Find;\n  let findPrevClassStart: Find;\n  let findNextClassEnd: Find;\n  let findPrevClassEnd: Find;\n\n  setup(() => {\n    const lines = [\n      \"'''Module docstring.'''\", // 0\n      '',\n      'class First:',\n      '# a mis-placed comment',\n      '    def __init__(self):',\n      '        pass', // 5\n      '',\n      'p = 42',\n      '',\n      'class Second:',\n      '', // 10\n      '    def __init__(self):',\n      '        pass',\n      '',\n      '    class Inner:',\n      '        def __init__(self):', // 15\n      '            pass',\n      '',\n      'x = 139',\n    ];\n\n    const pydoc = fakePythonDocument(lines);\n    findNextClassStart = pydoc.find.bind(pydoc, 'class', 'next', 'start');\n    findPrevClassStart = pydoc.find.bind(pydoc, 'class', 'prev', 'start');\n    findNextClassEnd = pydoc.find.bind(pydoc, 'class', 'next', 'end');\n    findPrevClassEnd = pydoc.find.bind(pydoc, 'class', 'prev', 'end');\n  });\n\n  test('valid findNextClassStart, start of file', () => {\n    // GIVEN\n    const position = new Position(0, 0);\n\n    // WHEN\n    const newPosition = findNextClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextClassStart, past first class', () => {\n    // GIVEN\n    const position = new Position(8, 2);\n\n    // WHEN\n    const newPosition = findNextClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findNextClassStart, past second outer class', () => {\n    // GIVEN\n    const position = new Position(9, 3);\n\n    // WHEN\n    const newPosition = findNextClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 14);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('Invalid findNextClassStart, past last class', () => {\n    // GIVEN\n    const position = new Position(14, 6);\n\n    // WHEN\n    const newPosition = findNextClassStart(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('valid findPrevClassStart, end of file', () => {\n    // GIVEN\n    const position = new Position(18, 0);\n\n    // WHEN\n    const newPosition = findPrevClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 14);\n    assert.equal(newPosition.character, 4);\n  });\n\n  test('valid findPrevClassStart, past first class', () => {\n    // GIVEN\n    const position = new Position(7, 2);\n\n    // WHEN\n    const newPosition = findPrevClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 2);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('valid findPrevClassStart, past second outer class, before inner class', () => {\n    // GIVEN\n    const position = new Position(11, 8);\n\n    // WHEN\n    const newPosition = findPrevClassStart(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 9);\n    assert.equal(newPosition.character, 0);\n  });\n\n  test('Invalid findPrevClassStart, before first class', () => {\n    // GIVEN\n    const position = new Position(0, 6);\n\n    // WHEN\n    const newPosition = findPrevClassStart(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('valid findNextClassEnd, start of file', () => {\n    // GIVEN\n    const position = new Position(0, 0);\n\n    // WHEN\n    const newPosition = findNextClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 5);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findNextClassEnd, past first class', () => {\n    // GIVEN\n    const position = new Position(7, 2);\n\n    // WHEN\n    const newPosition = findNextClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 16);\n    assert.equal(newPosition.character, 15);\n  });\n\n  test('valid findNextClassEnd, past second outer class', () => {\n    // GIVEN\n    const position = new Position(9, 3);\n\n    // WHEN\n    const newPosition = findNextClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 16);\n    assert.equal(newPosition.character, 15);\n  });\n\n  test('Invalid findNextClassEnd, past last class', () => {\n    // GIVEN\n    const position = new Position(18, 3);\n\n    // WHEN\n    const newPosition = findNextClassEnd(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n\n  test('valid findPrevClassEnd, end of file', () => {\n    // GIVEN\n    const position = new Position(18, 4);\n\n    // WHEN\n    const newPosition = findPrevClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 16);\n    assert.equal(newPosition.character, 15);\n  });\n\n  test('valid findPrevClassEnd, past first class', () => {\n    // GIVEN\n    const position = new Position(7, 2);\n\n    // WHEN\n    const newPosition = findPrevClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 5);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('valid findPrevClassEnd, past second outer class', () => {\n    // GIVEN\n    const position = new Position(9, 3);\n\n    // WHEN\n    const newPosition = findPrevClassEnd(position);\n\n    // THEN\n    assert.notEqual(newPosition, undefined);\n    assert(newPosition !== undefined);\n    assert.equal(newPosition.line, 5);\n    assert.equal(newPosition.character, 11);\n  });\n\n  test('Invalid findPrevClassEnd, before end of first class', () => {\n    // GIVEN\n    const position = new Position(4, 8);\n\n    // WHEN\n    const newPosition = findPrevClassEnd(position);\n\n    // THEN\n    assert.equal(newPosition, undefined);\n  });\n});\n"
  },
  {
    "path": "test/actions/markMovement.test.ts",
    "content": "import assert from 'assert';\nimport { Position, Selection, TextDocument, window, workspace } from 'vscode';\nimport { failedMovement } from '../../src/actions/baseMotion';\nimport { BaseMarkMovement, MarkMovement, MarkMovementBOL } from '../../src/actions/motion';\nimport { EasyMotion } from '../../src/actions/plugins/easymotion/easymotion';\nimport { Cursor } from '../../src/common/motion/cursor';\nimport { VimState } from '../../src/state/vimState';\nimport { replaceContent, setupWorkspace } from '../testUtils';\n\nclass Location {\n  public position: Position;\n  private getDocument?: () => TextDocument;\n\n  public get document(): TextDocument | null {\n    return this.getDocument?.() ?? null;\n  }\n\n  public constructor(line: number, column: number, getDocument?: () => TextDocument) {\n    this.position = new Position(line, column);\n    this.getDocument = getDocument;\n  }\n}\n\ninterface IMarkTestCase {\n  name: string;\n  start: Location;\n  /** Defaults to lowercase if not set (local mark) */\n  markName?: string;\n  mark: Location;\n  /** Overrides `markLocation` for when the expected location is different from the mark's location */\n  expect?: Location;\n  expectError?: Error;\n  documentAContent?: string;\n  documentBContent?: string;\n}\n\nsuite('mark movement', () => {\n  let vimState: VimState;\n  let documentA: TextDocument;\n  let documentB: TextDocument;\n\n  setup(async () => {\n    await setupWorkspace();\n    vimState = new VimState(window.activeTextEditor!, new EasyMotion());\n    await vimState.load();\n\n    documentB = await workspace.openTextDocument({\n      content: `line one\\n    line two with four spaces of indentation\\nline three\\n`,\n    });\n\n    documentA = await workspace.openTextDocument({\n      content: `line 1\\n    line 2 with 4 spaces of indentation\\nline 3\\n`,\n    });\n  });\n\n  const newMarkTests = (\n    testCases: IMarkTestCase[],\n    movementFactory: (testCase: IMarkTestCase, markName: string) => BaseMarkMovement,\n  ) => {\n    for (const testCase of testCases) {\n      const {\n        name,\n        markName,\n        start,\n        mark,\n        documentAContent,\n        documentBContent,\n        expect,\n        expectError,\n      } = testCase;\n\n      const arrangeTest = async () => {\n        // Arrange\n        if (documentAContent) await replaceContent(documentA, documentAContent);\n        if (documentBContent) await replaceContent(documentB, documentBContent);\n\n        const selection = new Selection(start.position, start.position);\n        const editor = await window.showTextDocument(start.document ?? documentA, {\n          selection,\n        });\n\n        vimState.cursor = Cursor.fromSelection(selection);\n        vimState.editor = editor;\n\n        const markToUse = markName || 'a';\n        vimState.historyTracker.addMark(\n          mark.document ?? vimState.document,\n          mark.position,\n          markToUse,\n        );\n        return movementFactory(testCase, markToUse);\n      };\n\n      if (expectError) {\n        test(name, async () => {\n          // Arrange\n          const movement = await arrangeTest();\n\n          // Act & Assert\n          await assert.rejects(\n            () => movement.execAction(vimState.cursorStartPosition, vimState),\n            (err) => {\n              assert.deepStrictEqual(err, expectError);\n              return true;\n            },\n          );\n        });\n      } else {\n        test(name, async () => {\n          // Arrange\n          const movement = await arrangeTest();\n\n          // Act\n          const result = await movement.execAction(vimState.cursorStartPosition, vimState);\n\n          // Assert\n          const _expect = expect ?? mark;\n          if (_expect.document === start.document) {\n            assert.deepStrictEqual(result, _expect.position, 'returned position must be correct');\n          } else {\n            // Movement fails so our position in the starting document doesn't get changed\n            assert.deepStrictEqual(result, failedMovement(vimState), 'movement should have failed');\n          }\n\n          if (_expect.document) {\n            assert.deepStrictEqual(\n              window.activeTextEditor?.document,\n              _expect.document,\n              'active document must be correct',\n            );\n          }\n\n          // If this test case is moving between documents then we need to check the actual editor's selection to\n          // verify the cursor has been moved. This is because we rely on `vscode.window.showTextDocument` when jumping\n          // between documents\n          if (_expect.document !== start.document) {\n            const editorPosition = window.activeTextEditor?.selection.active;\n            assert.deepStrictEqual(\n              editorPosition,\n              _expect.position,\n              'active selection must be correct when moving between documents',\n            );\n          }\n        });\n      }\n    }\n  };\n\n  suite('MarkMovementBOL', () => {\n    const sameLocationTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to correct position when mark is at same location',\n        start: new Location(1, 4),\n        mark: new Location(1, 4),\n      },\n      {\n        name: 'global mark moves to correct position when mark is at same location',\n        markName: 'A',\n        start: new Location(1, 4),\n        mark: new Location(1, 4),\n      },\n      {\n        name: 'local mark stays at column 0 on empty line',\n        start: new Location(1, 0),\n        mark: new Location(1, 0),\n        documentAContent: 'test\\n',\n      },\n      {\n        name: 'global mark stays at column 0 on empty line when mark is in same document',\n        markName: 'A',\n        start: new Location(1, 0),\n        mark: new Location(1, 0),\n        documentAContent: 'test\\n',\n      },\n    ];\n\n    const whitespaceTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to EOL on space only line',\n        start: new Location(0, 0),\n        mark: new Location(0, 0),\n        documentAContent: ' '.repeat(4),\n        expect: new Location(0, 4),\n      },\n      {\n        name: 'global mark moves to EOL on space only line if in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 0),\n        documentAContent: ' '.repeat(4),\n        expect: new Location(0, 4),\n      },\n      {\n        name: 'global mark moves to EOL on space only line if in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 0, () => documentB),\n        documentBContent: ' '.repeat(4),\n        expect: new Location(0, 4, () => documentB),\n      },\n      {\n        name: 'local mark moves to EOL on tab only line',\n        start: new Location(0, 0),\n        mark: new Location(0, 0),\n        documentAContent: '\\t'.repeat(4),\n        expect: new Location(0, 4),\n      },\n      {\n        name: 'global mark moves to EOL on tab only line if in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 0),\n        documentAContent: '\\t'.repeat(4),\n        expect: new Location(0, 4),\n      },\n      {\n        name: 'global mark moves to EOL on tab only line if in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 0, () => documentB),\n        documentBContent: '\\t'.repeat(4),\n        expect: new Location(0, 4, () => documentB),\n      },\n    ];\n\n    const nonWhitespaceTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to first non-whitespace character',\n        start: new Location(0, 0),\n        mark: new Location(1, 0),\n        expect: new Location(1, 4),\n      },\n      {\n        name: 'global mark moves to first non-whitespace character in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(1, 0),\n        expect: new Location(1, 4),\n      },\n      {\n        name: 'global mark moves to first non-whitespace character in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(1, 0, () => documentB),\n        expect: new Location(1, 4, () => documentB),\n      },\n    ];\n\n    const pastLastLineEmptyTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to start of last line when mark is past last line and last line is empty',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(3, 0),\n      },\n      {\n        name: 'global mark moves to start of last line when mark is past last line and last line is empty in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(3, 0),\n      },\n      {\n        name: 'global mark moves to start of last line when mark is past last line and last line is empty in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0, () => documentB),\n        expect: new Location(3, 0, () => documentB),\n      },\n    ];\n\n    const pastLastLineNonEmptyTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to first non-whitespace character of last line when mark is past last line',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(1, 4),\n        documentAContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n      {\n        name: 'global mark moves to first non-whitespace character of last line when mark is past last line in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(1, 4),\n        documentAContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n      {\n        name: 'global mark moves to first non-whitespace character of last line when mark is past last line in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0, () => documentB),\n        expect: new Location(1, 4, () => documentB),\n        documentBContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n    ];\n\n    const pastEndOfLineTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to first non-whitespace character when mark is past EOL',\n        start: new Location(1, 0),\n        mark: new Location(1, 11),\n        expect: new Location(1, 4),\n        documentAContent: 'line 1\\n    line 2',\n      },\n      {\n        name: 'global mark moves to first non-whitespace character when mark is past EOL in same document',\n        markName: 'A',\n        start: new Location(1, 0),\n        mark: new Location(1, 11),\n        expect: new Location(1, 4),\n        documentAContent: 'line 1\\n    line 2',\n      },\n      {\n        name: 'global mark moves to first non-whitespace character when mark is past EOL in different document',\n        markName: 'A',\n        start: new Location(1, 0),\n        mark: new Location(1, 11, () => documentB),\n        expect: new Location(1, 4, () => documentB),\n        documentBContent: 'line 1\\n    line 2',\n      },\n    ];\n\n    const testCases: IMarkTestCase[] = [\n      ...sameLocationTests,\n      ...whitespaceTests,\n      ...nonWhitespaceTests,\n      ...pastLastLineEmptyTests,\n      ...pastLastLineNonEmptyTests,\n      ...pastEndOfLineTests,\n    ];\n\n    newMarkTests(testCases, (testCase, markName) => new MarkMovementBOL([\"'\", markName]));\n\n    test('throws error when mark is not set', async () => {\n      // Arrange\n      const movement = new MarkMovementBOL([\"'\", 'a']);\n\n      // Act & Assert\n      await assert.rejects(() => movement.execAction(vimState.cursorStartPosition, vimState));\n    });\n  });\n\n  suite('MarkMovement', () => {\n    const sameLocationTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to correct position when mark is at same location',\n        start: new Location(1, 4),\n        mark: new Location(1, 4),\n      },\n      {\n        name: 'global mark moves to correct position when mark is at same location',\n        markName: 'A',\n        start: new Location(1, 4),\n        mark: new Location(1, 4),\n      },\n      {\n        name: 'local mark stays at column 0 on empty line',\n        start: new Location(1, 0),\n        mark: new Location(1, 0),\n        documentAContent: 'test\\n',\n      },\n      {\n        name: 'global mark stays at column 0 on empty line when mark is in same document',\n        markName: 'A',\n        start: new Location(1, 0),\n        mark: new Location(1, 0),\n        documentAContent: 'test\\n',\n      },\n    ];\n\n    const whitespaceTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to exact position on space only line',\n        start: new Location(0, 1),\n        mark: new Location(0, 1),\n        documentAContent: ' '.repeat(4),\n      },\n      {\n        name: 'global mark moves to exact position on space only line if in same document',\n        markName: 'A',\n        start: new Location(0, 1),\n        mark: new Location(0, 1),\n        documentAContent: ' '.repeat(4),\n      },\n      {\n        name: 'global mark moves to exact position on space only line if in different document',\n        markName: 'A',\n        start: new Location(0, 1),\n        mark: new Location(0, 1, () => documentB),\n        documentBContent: ' '.repeat(4),\n      },\n      {\n        name: 'local mark moves to exact position on tab only line',\n        start: new Location(0, 1),\n        mark: new Location(0, 1),\n        documentAContent: '\\t'.repeat(4),\n      },\n      {\n        name: 'global mark moves to exact position on tab only line if in same document',\n        markName: 'A',\n        start: new Location(0, 1),\n        mark: new Location(0, 1),\n        documentAContent: '\\t'.repeat(4),\n      },\n      {\n        name: 'global mark moves to exact position on tab only line if in different document',\n        markName: 'A',\n        start: new Location(0, 1),\n        mark: new Location(0, 1, () => documentB),\n        documentBContent: '\\t'.repeat(4),\n      },\n    ];\n\n    const nonWhitespaceTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to exact position with whitespace',\n        start: new Location(0, 0),\n        mark: new Location(1, 2),\n      },\n      {\n        name: 'global mark moves to exact position with whitespace in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(1, 2),\n      },\n      {\n        name: 'global mark moves to exact position with whitespace in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(1, 2, () => documentB),\n      },\n    ];\n\n    const pastLastLineEmptyTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to end of last line when mark is past last line and last line is empty',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(3, 0),\n      },\n      {\n        name: 'global mark moves to end of last line when mark is past last line and last line is empty in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(3, 0),\n      },\n      {\n        name: 'global mark moves to end of last line when mark is past last line and last line is empty in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0, () => documentB),\n        expect: new Location(3, 0, () => documentB),\n      },\n    ];\n\n    const pastLastLineNonEmptyTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to end of last line when mark is past last line',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(1, 10),\n        documentAContent: 'line 1\\n    line 2',\n      },\n      {\n        name: 'global mark moves to end of last line when mark is past last line in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0),\n        expect: new Location(1, 10),\n        documentAContent: 'line 1\\n    line 2',\n      },\n      {\n        name: 'global mark moves to end of last line when mark is past last line in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(4, 0, () => documentB),\n        expect: new Location(1, 10, () => documentB),\n        documentBContent: 'line 1\\n    line 2',\n      },\n    ];\n\n    const pastEndOfLineTests: IMarkTestCase[] = [\n      {\n        name: 'local mark moves to EOL when mark is past EOL',\n        start: new Location(0, 0),\n        mark: new Location(0, 7),\n        expect: new Location(0, 6),\n        documentAContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n      {\n        name: 'global mark moves to EOL when mark is past EOL in same document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 7),\n        expect: new Location(0, 6),\n        documentAContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n      {\n        name: 'global mark moves to EOL when mark is past EOL in different document',\n        markName: 'A',\n        start: new Location(0, 0),\n        mark: new Location(0, 7, () => documentB),\n        expect: new Location(0, 6, () => documentB),\n        documentBContent: 'line 1\\n    line 2 with 4 spaces of indentation',\n      },\n    ];\n\n    const testCases: IMarkTestCase[] = [\n      ...sameLocationTests,\n      ...whitespaceTests,\n      ...nonWhitespaceTests,\n      ...pastLastLineEmptyTests,\n      ...pastLastLineNonEmptyTests,\n      ...pastEndOfLineTests,\n    ];\n\n    newMarkTests(testCases, (testCase, markName) => new MarkMovement(['`', markName]));\n\n    test('throws error when mark is not set', async () => {\n      // Arrange\n      const movement = new MarkMovement(['`', 'a']);\n\n      // Act & Assert\n      await assert.rejects(() => movement.execAction(vimState.cursorStartPosition, vimState));\n    });\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/bang.test.ts",
    "content": "import { getAndUpdateModeHandler } from '../../extension';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest } from '../../test/testSimplifier';\nimport { assertEqualLines, setupWorkspace } from './../testUtils';\n\n// TODO(#4844): this fails on Windows\nsuite('bang (!) cmd_line', () => {\n  if (process.platform === 'win32') {\n    return;\n  }\n\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  suite('parsing', () => {\n    test('simple !', async () => {\n      await modeHandler.handleMultipleKeyEvents(':.!echo hello world\\n'.split(''));\n      assertEqualLines(['hello world']);\n    });\n\n    test('simple ! with space between bang and command', async () => {\n      await modeHandler.handleMultipleKeyEvents(':.! echo hello world\\n'.split(''));\n      assertEqualLines(['hello world']);\n    });\n\n    test('! with line', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':2!echo hello world\\n'.split(''));\n      assertEqualLines(['123', 'hello world', '789']);\n    });\n\n    test('! with line range', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':2,3!echo hello world\\n'.split(''));\n      assertEqualLines(['123', 'hello world']);\n    });\n  });\n\n  suite('previous external commands (embedded bangs)', () => {\n    test('!! should execute previous command', async () => {\n      await modeHandler.handleMultipleKeyEvents(':.! echo hello world\\n'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['o', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':.!!\\n'.split(''));\n      assertEqualLines(['hello world', 'hello world']);\n    });\n\n    test('!! should support command concatenation', async () => {\n      await modeHandler.handleMultipleKeyEvents(':.! echo hello world\\n'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['o', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':.!! world\\n'.split(''));\n      assertEqualLines(['hello world', 'hello world world']);\n    });\n\n    test('any ! in cmd is replaced with previous external command', async () => {\n      await modeHandler.handleMultipleKeyEvents(':!dir\\n'.split(''));\n      await modeHandler.handleMultipleKeyEvents(':.!echo !! hello\\n'.split(''));\n      assertEqualLines(['dirdir hello']);\n    });\n\n    test('only the ! character can be unescaped, with a backslash', async () => {\n      await modeHandler.handleMultipleKeyEvents(':!dir\\n'.split(''));\n      await modeHandler.handleMultipleKeyEvents(':.!echo \"! \\\\! \\\\\\\\! \\\\p\"\\n'.split(''));\n      assertEqualLines(['dir ! \\\\! \\\\p']);\n    });\n  });\n\n  suite('stdin/stdout/stderr', () => {\n    test('! can read from stdin', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', 'abc', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':.!cat\\n'.split(''));\n      assertEqualLines(['abc']);\n    });\n\n    test(':{line}!{cmd} should pass in line as stdin', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', 'hello world', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':1!cat\\n'.split(''));\n      assertEqualLines(['hello world']);\n    });\n\n    test(':{range}!{cmd} should pass in line range as stdin', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', '4\\n3\\n2\\n1', '<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(':2,4!sort -n\\n'.split(''));\n      assertEqualLines(['4', '1', '2', '3']);\n    });\n\n    test('! with commands expecting stdin do not block when no stdin is supplied', async () => {\n      await modeHandler.handleMultipleKeyEvents(':!cat\\n'.split(''));\n      assertEqualLines(['']);\n    });\n\n    test('! can read from both stdout and stderr', async () => {\n      await modeHandler.handleMultipleKeyEvents(\n        ':.!echo \"stdout\" && >&2 echo \"stderr\"\\n'.split(''),\n      );\n      assertEqualLines(['stdout', 'stderr']);\n    });\n\n    test('piping commands works', async () => {\n      await modeHandler.handleMultipleKeyEvents(':.!printf \"c\\\\nb\\\\na\" | sort\\n'.split(''));\n      assertEqualLines(['a', 'b', 'c']);\n    });\n  });\n});\n\nsuite('custom bang shell', () => {\n  if (process.platform === 'win32') {\n    return;\n  }\n\n  for (const shell of ['sh', 'bash']) {\n    suite(shell, () => {\n      setup(async () => {\n        await setupWorkspace({\n          config: { shell: `/bin/${shell}` },\n        });\n      });\n\n      newTest({\n        title: `! supports /bin/${shell}`,\n        start: ['|'],\n        keysPressed: '<Esc>:.!echo $0\\n',\n        end: [`|/bin/${shell}`],\n      });\n    });\n  }\n});\n"
  },
  {
    "path": "test/cmd_line/breakpoints.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { exCommandParser } from '../../src/vimscript/exCommandParser';\nimport { setupWorkspace } from '../testUtils';\n\nfunction clearBreakpoints() {\n  vscode.debug.removeBreakpoints(vscode.debug.breakpoints);\n}\n\nsuite('Breakpoints command', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('`:breaka` adds breakpoint', async () => {\n    clearBreakpoints();\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'o', '<Esc>']); // make sure it's working not only for the first line\n    await new ExCommandLine('breaka', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assert.strictEqual(vscode.debug.breakpoints.length, 1);\n    const breakpoint = vscode.debug.breakpoints[0] as vscode.SourceBreakpoint;\n    assert.strictEqual(\n      breakpoint.location.uri.fsPath,\n      modeHandler.vimState.editor.document.uri.fsPath,\n    );\n    assert.strictEqual(\n      breakpoint.location.range.start.line,\n      modeHandler.vimState.cursorStartPosition.line,\n    );\n  });\n\n  test('`:breakd` delete breakpoint', async () => {\n    clearBreakpoints();\n    await new ExCommandLine('breaka', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assert.strictEqual(vscode.debug.breakpoints.length, 1);\n    await new ExCommandLine('breakd', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assert.strictEqual(vscode.debug.breakpoints.length, 0);\n  });\n\n  test('test \"here\" is redundant', async () => {\n    assert.deepStrictEqual(\n      exCommandParser.tryParse(':breaka here'),\n      exCommandParser.tryParse(':breaka'),\n    );\n    assert.deepStrictEqual(\n      exCommandParser.tryParse(':breakd here'),\n      exCommandParser.tryParse(':breakd'),\n    );\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/bufferDelete.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { join } from 'path';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { VimError } from '../../src/error';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport * as t from '../testUtils';\n\nsuite('Buffer delete', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await t.setupWorkspace({ forceNewFile: true });\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  for (const cmd of ['bdelete', 'bdel', 'bd']) {\n    test(`${cmd} deletes the current buffer`, async () => {\n      await new ExCommandLine(cmd, modeHandler.vimState.currentMode).run(modeHandler.vimState);\n      await t.waitForEditorsToClose();\n\n      assert.strictEqual(vscode.window.visibleTextEditors.length, 0);\n    });\n  }\n\n  test('bd does not delete buffer when there are unsaved changes', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '<Esc>']);\n    try {\n      await new ExCommandLine('bd', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    } catch (e) {\n      assert.strictEqual(e, VimError.NoWriteSinceLastChange());\n    }\n  });\n\n  test('bd! deletes the current buffer regardless of unsaved changes', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '<Esc>']);\n\n    await new ExCommandLine('bd!', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    await t.waitForEditorsToClose();\n\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 0);\n  });\n\n  test.skip(\"bd 'N' deletes the Nth buffer open\", async () => {\n    const dirPath = await t.createDir();\n    const filePaths: string[] = [];\n\n    try {\n      for (let i = 0; i < 3; i++) {\n        const uri: vscode.Uri = vscode.Uri.parse(join(dirPath, `${i}`));\n        filePaths.push(uri.toString());\n        void vscode.workspace.openTextDocument(uri).then((doc: vscode.TextDocument) => {\n          void doc.save();\n        });\n      }\n\n      await new ExCommandLine('bd 2', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n      await vscode.commands.executeCommand('workbench.action.openEditorAtIndex2');\n\n      assert.strictEqual(vscode.window.visibleTextEditors.length, 2);\n      assert.strictEqual(vscode.window.activeTextEditor?.document.uri.fsPath, filePaths[2]);\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(dirPath), { recursive: true });\n    }\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/change.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\n\nsuite('cmd_line change', () => {\n  newTest({\n    title: 'c deletes current line and enters insert mode',\n    start: ['first line', 'sec|ond line', 'third line'],\n    keysPressed: ':c\\n',\n    end: ['first line', '|', 'third line'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'c with count deletes multiple lines and enters insert mode',\n    start: ['first line', 'sec|ond line', 'third line', 'fourth line'],\n    keysPressed: ':c2\\n',\n    end: ['first line', '|', 'fourth line'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'c with range deletes specified lines and enters insert mode',\n    start: ['first line', 'sec|ond line', 'third line', 'fourth line'],\n    keysPressed: ':2, 3c\\n',\n    end: ['first line', '|', 'fourth line'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'c with range and visual selection',\n    start: ['first line', 'sec|ond line', 'third line', 'fourth line'],\n    keysPressed: 'V:c\\n',\n    end: ['first line', '|', 'third line', 'fourth line'],\n    endMode: Mode.Insert,\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/command.test.ts",
    "content": "import * as assert from 'assert';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { assertStatusBarEqual, setupWorkspace } from '../testUtils';\n\nsuite('cmd_line/search command', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('command <C-w> can remove word in cmd line', async () => {\n    await modeHandler.handleMultipleKeyEvents([':', 'a', 'b', 'c', '-', '1', '2', '3', '<C-w>']);\n    assertStatusBarEqual(':abc-|', 'Failed to remove \"123\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual(':abc|', 'Failed to remove \"-\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual(':|', 'Failed to remove \"abc\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual(':|', 'Failed to remove nothing');\n  });\n\n  test('command <C-w> can remove word in search mode', async () => {\n    await modeHandler.handleMultipleKeyEvents(['/', 'a', 'b', 'c', '-', '1', '2', '3', '<C-w>']);\n    assertStatusBarEqual('/abc-|', 'Failed to remove \"123\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual('/abc|', 'Failed to remove \"-\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual('/|', 'Failed to remove \"abc\"');\n\n    await modeHandler.handleKeyEvent('<C-w>');\n    assertStatusBarEqual('/|', 'Failed to remove nothing');\n  });\n\n  test('command <C-w> can remove word in cmd line while retrain cmd on the right of the cursor', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      ':',\n      'a',\n      'b',\n      'c',\n      ' ',\n      '1',\n      '2',\n      '3',\n      '<left>',\n      '<left>',\n      '<left>',\n      '<C-w>',\n    ]);\n    assertStatusBarEqual(':|123', 'Failed to retain the text on the right of the cursor');\n  });\n\n  test('command <C-w> can remove word in search mode while retrain cmd on the right of the cursor', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      '/',\n      'a',\n      'b',\n      'c',\n      ' ',\n      '1',\n      '2',\n      '3',\n      '<left>',\n      '<left>',\n      '<left>',\n      '<C-w>',\n    ]);\n    assertStatusBarEqual('/|123', 'Failed to retain the text on the right of the cursor');\n  });\n\n  test('<C-u> deletes from cursor to first character', async () => {\n    await modeHandler.handleMultipleKeyEvents(':s/abc/xyz'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<left>', '<left>', '<C-u>']);\n    assertStatusBarEqual(':|yz');\n  });\n\n  test('<C-b> puts cursor at start of command line', async () => {\n    await modeHandler.handleMultipleKeyEvents(':s/abc/xyz'.split(''));\n    await modeHandler.handleKeyEvent('<C-b>');\n    assertStatusBarEqual(':|s/abc/xyz');\n  });\n\n  test('<Home> puts cursor at start of command line', async () => {\n    await modeHandler.handleMultipleKeyEvents(':s/abc/xyz'.split(''));\n    await modeHandler.handleKeyEvent('<Home>');\n    assertStatusBarEqual(':|s/abc/xyz');\n  });\n\n  test('<C-e> puts cursor at end of command line', async () => {\n    await modeHandler.handleMultipleKeyEvents(':s/abc/xyz'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<C-b>', '<C-e>']);\n    assertStatusBarEqual(':s/abc/xyz|');\n  });\n\n  test('<End> puts cursor at end of command line', async () => {\n    await modeHandler.handleMultipleKeyEvents(':s/abc/xyz'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Home>', '<End>']);\n    assertStatusBarEqual(':s/abc/xyz|');\n  });\n\n  test('<C-p>/<C-n> go to the previous/next command', async () => {\n    // Establish a history - :s/a/b, then :s/x/y.\n    // :w is the current one, not yet confirmed\n    await modeHandler.handleMultipleKeyEvents(':s/a/b\\n'.split(''));\n    await modeHandler.handleMultipleKeyEvents(':s/x/y\\n'.split(''));\n    await modeHandler.handleMultipleKeyEvents([':', 'w', '<C-p>']);\n\n    // Going backward - :s/x/y, then :s/a/b\n    assertStatusBarEqual(':s/x/y|');\n    await modeHandler.handleKeyEvent('<C-p>');\n    assertStatusBarEqual(':s/a/b|');\n\n    // Going forward again - :s/x/y, then :w (the one we started typing)\n    await modeHandler.handleKeyEvent('<C-n>');\n    assertStatusBarEqual(':s/x/y|');\n    await modeHandler.handleKeyEvent('<C-n>');\n\n    // TODO: Really, this should be `:w|`. See #4093.\n    assertStatusBarEqual(':|');\n  });\n\n  test('<C-r> <C-w> insert word under cursor on command line', async () => {\n    await modeHandler.handleMultipleKeyEvents('iabc'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', ':', '<C-r>', '<C-w>']);\n    assertStatusBarEqual(':abc|', 'Failed to insert word under cursor');\n  });\n\n  test('<C-r> <C-w> insert word right of cursor on command line', async () => {\n    await modeHandler.handleMultipleKeyEvents('i::abc'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '0', ':', '<C-r>', '<C-w>']);\n    assertStatusBarEqual(':abc|', 'Failed to insert word to right of cursor');\n  });\n\n  test('<C-r> <C-w> insert word under cursor in search mode', async () => {\n    await modeHandler.handleMultipleKeyEvents('iabc'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '/', '<C-r>', '<C-w>']);\n    assertStatusBarEqual('/abc|', 'Failed to insert word under cursor');\n  });\n\n  test('<C-p> go to previous search string', async () => {\n    await modeHandler.handleMultipleKeyEvents('/abc'.split('').concat('<Esc>'));\n    await modeHandler.handleMultipleKeyEvents(['/', '<C-p>']);\n    assertStatusBarEqual('/abc|', 'Failed to go to previous search string');\n  });\n\n  for (const key of ['<BS>', '<S-BS>', '<C-h>']) {\n    test(`${key} removes one character from command line`, async () => {\n      await modeHandler.handleMultipleKeyEvents(':abc'.split(''));\n      await modeHandler.handleKeyEvent(key);\n      assertStatusBarEqual(':ab|');\n    });\n\n    test(`${key} with empty command line goes to normal mode`, async () => {\n      await modeHandler.handleKeyEvent(':');\n      await modeHandler.handleKeyEvent(key);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    });\n\n    test(`${key} at start of non-empty command does nothing`, async () => {\n      await modeHandler.handleMultipleKeyEvents(':abc'.split(''));\n      await modeHandler.handleKeyEvent('<Home>');\n      await modeHandler.handleKeyEvent(key);\n      assertStatusBarEqual(':|abc');\n    });\n  }\n\n  test(`<Del> removes one character from command line`, async () => {\n    await modeHandler.handleMultipleKeyEvents(':abc'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<left>', '<left>']);\n    await modeHandler.handleMultipleKeyEvents(['<Del>']);\n    assertStatusBarEqual(':a|c');\n  });\n\n  test(`<Del> with empty command line goes to normal mode`, async () => {\n    await modeHandler.handleKeyEvent(':');\n    await modeHandler.handleKeyEvent('<Del>');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test(`<Del> at start of non-empty command acts normally`, async () => {\n    await modeHandler.handleMultipleKeyEvents(':abc'.split(''));\n    await modeHandler.handleKeyEvent('<Home>');\n    await modeHandler.handleKeyEvent('<Del>');\n    assertStatusBarEqual(':|bc');\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/cursorLocation.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { StatusBar } from '../../src/statusBar';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('cursor location', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('cursor location in command line', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      ':',\n      't',\n      'e',\n      's',\n      't',\n      '<right>',\n      '<right>',\n      '<right>',\n      '<left>',\n    ]);\n\n    const statusBarAfterCursorMovement = StatusBar.getText();\n    await modeHandler.handleKeyEvent('<Esc>');\n\n    const statusBarAfterEsc = StatusBar.getText();\n    assert.strictEqual(\n      statusBarAfterCursorMovement.trim(),\n      ':tes|t',\n      'Command Tab Completion Failed',\n    );\n  });\n\n  test('cursor location in search', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      '/',\n      't',\n      'e',\n      's',\n      't',\n      '<right>',\n      '<right>',\n      '<right>',\n      '<left>',\n    ]);\n\n    const statusBarAfterCursorMovement = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    const statusBarAfterEsc = StatusBar.getText();\n    assert.strictEqual(\n      statusBarAfterCursorMovement.trim(),\n      '/tes|t',\n      'Command Tab Completion Failed',\n    );\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/delete.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite(':d[elete]', () => {\n  newTest({\n    title: ':d',\n    start: ['line 1', 'li|ne 2', 'line 3'],\n    keysPressed: ':d' + '\\n',\n    end: ['line 1', '|line 3'],\n  });\n\n  newTest({\n    title: ':2,3d',\n    start: ['line 1', 'li|ne 2', 'line 3'],\n    keysPressed: ':2,3d' + '\\n',\n    end: ['|line 1'],\n  });\n\n  newTest({\n    title: ':d 2',\n    start: ['line 1', 'li|ne 2', 'line 3'],\n    keysPressed: ':d 2' + '\\n',\n    end: ['|line 1'],\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/grep.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { GrepCommand } from '../../src/cmd_line/commands/grep';\nimport { Mode } from '../../src/mode/mode';\nimport { Pattern, SearchDirection } from '../../src/vimscript/pattern';\nimport { createFile, setupWorkspace } from '../testUtils';\n\nfunction grep(pattern: Pattern, files: string[]): GrepCommand {\n  return new GrepCommand({ pattern, files });\n}\n\nsuite('Basic grep command', () => {\n  // when you search.action.focusNextSearchResult , it will enter the file in visual mode for some reason, we can test whether it is in visual mode or not after running that command\n  // that only happens if the search panel is not open already\n  // if the search panel is open, it will be in normal mode\n  // it will also be in normal mode if you run vimgrep from another file\n  setup(async () => {\n    await setupWorkspace();\n  });\n  test('GrepCommand executes correctly', async () => {\n    if (process.platform === 'win32') {\n      return; // TODO: Why does this fail on Windows?\n    }\n    // first file, will have matches\n    let file1 = await createFile({\n      fileExtension: '.txt',\n      contents: 'test, pattern nnnn, t*st, ttst',\n    });\n    // second file without a match\n    let file2 = await createFile({\n      fileExtension: '.txt',\n      contents: 'no pattern match here ',\n    });\n    // We open the second file where we know there is no match\n    const document1 = await vscode.workspace.openTextDocument(vscode.Uri.file(file1));\n    await vscode.window.showTextDocument(document1, { preview: false });\n    const document2 = await vscode.workspace.openTextDocument(vscode.Uri.file(file2));\n    await vscode.window.showTextDocument(document2, { preview: false });\n    const pattern = Pattern.parser({ direction: SearchDirection.Backward });\n    // The vscode's search doesn't work with the paths of the extension test host, so we strip to the file names only\n    file1 = file1.substring(file1.lastIndexOf('/') + 1);\n    file2 = file2.substring(file2.lastIndexOf('/') + 1);\n    const command = grep(pattern.tryParse('t*st'), [file1, file2]);\n    await command.execute();\n    // Despite the fact that we already execute this command in the grep itself, without this focus, there is no active editor\n    // I've tested visually and without this command you are still in the editor in the file with the match, I have no idea why it won't work without this\n    await vscode.commands.executeCommand('search.action.focusNextSearchResult');\n    const activeEditor = vscode.window.activeTextEditor;\n    const modeHandler = await getAndUpdateModeHandler();\n    assert.ok(activeEditor, 'There should be an active editor');\n    assert.ok(modeHandler, 'modeHandler should be defined');\n    const docs = vscode.workspace.textDocuments.map((doc) => doc.fileName);\n    // After grep, the active editor should be the first file because the search panel focuses the first match and therefore opens the file\n    assert.ok(\n      activeEditor.document.fileName.endsWith(file1),\n      'Active editor should be first file after grep',\n    );\n    assert.notStrictEqual(\n      modeHandler.vimState,\n      Mode.Visual,\n      'Should not be in visual mode after grep',\n    );\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/historyFile.test.ts",
    "content": "import * as assert from 'assert';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport { configuration } from '../../src/configuration/configuration';\nimport { Globals } from '../../src/globals';\nimport { HistoryFile } from '../../src/history/historyFile';\nimport { rndName, TestExtensionContext } from '../testUtils';\n\nsuite('HistoryFile', () => {\n  let history: HistoryFile;\n  let runCmds: string[];\n\n  const assertArrayEquals = <T>(expected: T[], actual: T[]) => {\n    assert.strictEqual(expected.length, actual.length);\n    for (let i: number = 0; i < expected.length; i++) {\n      assert.strictEqual(expected[i], actual[i]);\n    }\n  };\n\n  setup(async () => {\n    runCmds = [];\n    for (let i = 0; i < configuration.history; i++) {\n      runCmds.push(i.toString());\n    }\n\n    Globals.extensionStoragePath = os.tmpdir();\n    history = new HistoryFile(new TestExtensionContext(), rndName());\n    await history.load();\n  });\n\n  teardown(async () => {\n    history.clear();\n  });\n\n  test('add command', async () => {\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n    assertArrayEquals(runCmds.slice(), history.get());\n  });\n\n  test('add empty command', async () => {\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n    await history.add('');\n    assertArrayEquals(runCmds.slice(), history.get());\n    await history.add(undefined);\n    assertArrayEquals(runCmds.slice(), history.get());\n  });\n\n  test('add command over configuration.history', async () => {\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n    const addedCmd: string = String(configuration.history);\n    runCmds.push(addedCmd);\n    await history.add(addedCmd);\n\n    assertArrayEquals(runCmds.slice(1), history.get());\n  });\n\n  test('add command that exists in history', async () => {\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n    const existedCmd: string = '0';\n    await history.add(existedCmd);\n    const expectedRawHistory: string[] = runCmds.slice();\n    expectedRawHistory.splice(expectedRawHistory.indexOf(existedCmd), 1);\n    expectedRawHistory.push(existedCmd);\n    assertArrayEquals(expectedRawHistory, history.get());\n  });\n\n  test('file system', async () => {\n    // history file is lazily created, should not exist\n    assert.strictEqual(fs.existsSync(history.historyFilePath), false);\n\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n\n    // history file should exist after an `add` operation\n    assert.strictEqual(fs.existsSync(history.historyFilePath), true);\n\n    history.clear();\n\n    // expect history file to be deleted from file system and empty\n    assert.strictEqual(fs.existsSync(history.historyFilePath), false);\n  });\n\n  test('change configuration.history', async () => {\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n\n    assert.strictEqual(history.get().length, configuration.history);\n\n    configuration.history = 10;\n    for (const cmd of runCmds) {\n      await history.add(cmd);\n    }\n\n    assertArrayEquals(runCmds.slice(runCmds.length - configuration.history), history.get());\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/move.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite(':[range]m[ove] [address] command', () => {\n  newTest({\n    title: ':move [address] will move the cursor line to below the line given by {address}',\n    start: ['|one', 'two', 'three'],\n    keysPressed: '<Esc>:m2\\n',\n    end: ['two', '|one', 'three'],\n  });\n\n  newTest({\n    title:\n      ':[range]move [address] will move the lines given by [range] to below the line given by {address}',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:1,3m4\\n',\n    end: ['four', 'one', 'two', '|three', 'five'],\n  });\n\n  newTest({\n    title: ':[range]move [address] will move the visual range',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: 'vjj:m4\\n',\n    end: ['four', 'one', 'two', '|three', 'five'],\n  });\n\n  newTest({\n    title: ':[range]move [address], boundary test: move 3 lines to the buttom',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:1,3m5\\n',\n    end: ['four', 'five', 'one', 'two', '|three'],\n  });\n\n  newTest({\n    title: ':[range]move [address], boundary test: move 3 lines to the top',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:3,4m0\\n',\n    end: ['three', '|four', 'one', 'two', 'five'],\n  });\n\n  newTest({\n    title: ':[range]move [address] will do nothing when move a range of line into itself',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:1,3m2\\n',\n    end: ['|one', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':[range]move [address] will do nothing when move a range of line right below itself',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:1,3m3\\n',\n    end: ['|one', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':[range]move [address] will do nothing when move a range of line right above itself',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:1,3m1\\n',\n    end: ['|one', 'two', 'three', 'four', 'five'],\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/normal.test.ts",
    "content": "import { newTest, newTestSkip } from '../testSimplifier';\n\nsuite('Execute normal command', () => {\n  newTest({\n    title: 'One liner',\n    start: ['foo =| bar = 1'],\n    keysPressed: ':normal f=i!=\\n',\n    end: ['foo = bar !|== 1'],\n  });\n\n  newTest({\n    title: 'One liner with selection',\n    start: ['foo =| bar = 1'],\n    keysPressed: 'V:normal f=i!=\\n',\n    end: ['foo !|== bar = 1'],\n  });\n\n  newTest({\n    title: 'Multiple liner with selection',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: 'Vj:normal f=i!=\\n',\n    end: ['foo !== bar = 1', 'foo !|== bar = 2'],\n  });\n\n  newTest({\n    title: 'Multiple liner with line range',\n    start: ['foo =| bar = 1', 'foo = bar = 2', 'foo = bar = 3'],\n    keysPressed: ':2,3normal f=i!=\\n',\n    end: ['foo = bar = 1', 'foo !== bar = 2', 'foo !|== bar = 3'],\n  });\n\n  newTest({\n    title: 'One liner with dot',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: 'f=i!=<Esc>j^f=:normal .\\n',\n    end: ['foo = bar !== 1', 'foo !|== bar = 2'],\n  });\n\n  newTest({\n    title: 'One liner with multiple dot',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: 'f=i!=<Esc>j^f=:normal 2.\\n',\n    end: ['foo = bar !== 1', 'foo !=!|== bar = 2'],\n  });\n\n  newTest({\n    title: 'One liner with macro',\n    start: ['|1. one, 2. two, 3. three, 4. four'],\n    keysPressed: 'qaf.r)q:normal @a\\n',\n    end: ['1) one, 2|) two, 3. three, 4. four'],\n  });\n\n  newTest({\n    title: 'One liner with multiple macro',\n    start: ['|1. one, 2. two, 3. three, 4. four'],\n    keysPressed: 'qaf.r)q:normal 3@a\\n',\n    end: ['1) one, 2) two, 3) three, 4|) four'],\n  });\n\n  newTest({\n    title: 'Multiple liner with multiple macro',\n    start: ['|0. zero', '1. one, 2. two, 3. three, 4. four', '5. five, 6. six, 7. seven, 8. eight'],\n    keysPressed: 'qaf.r)qjVj:normal 4@a\\n',\n    end: ['0) zero', '1) one, 2) two, 3) three, 4) four', '5) five, 6) six, 7) seven, 8|) eight'],\n  });\n\n  newTest({\n    title: 'Incomplete operation',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: ':normal ddd\\n',\n    end: ['|foo = bar = 2'],\n  });\n\n  // TODO: implement to stop when operation fails\n  newTestSkip({\n    title: 'Operation stops after command fails',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: ':normal llllllllllllllllllllllllllllll j\\n',\n    end: ['foo = bar = |1', 'foo = bar = 2'],\n  });\n\n  newTest({\n    title: 'Multiple liner with selection and undo',\n    start: ['foo =| bar = 1', 'foo = bar = 2'],\n    keysPressed: 'Vj:normal f=i!=\\nu',\n    end: ['foo |= bar = 1', 'foo = bar = 2'],\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/only.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { cleanUpWorkspace, setupWorkspace, waitForEditorsToClose } from '../testUtils';\n\nconst isPanelVisible = async () =>\n  withinIsolatedEditor(async () => {\n    // Insert 1000 lines (ie. beyond veritical viewport)\n    await vscode.window.activeTextEditor!.edit((editBuilder) => {\n      editBuilder.insert(new vscode.Position(0, 0), 'Line\\n'.repeat(1000));\n    });\n\n    // Toggle the panel's visibility to see which has a larger vertical viewport\n    const initialVisibleLineCount = await getNumberOfVisibleLines();\n    await vscode.commands.executeCommand('workbench.action.togglePanel');\n    const postToggleVisibleLineCount = await getNumberOfVisibleLines();\n    await vscode.commands.executeCommand('workbench.action.togglePanel');\n\n    return postToggleVisibleLineCount > initialVisibleLineCount;\n  });\n\nconst withinIsolatedEditor = async (lambda: () => Thenable<unknown>) => {\n  await vscode.commands.executeCommand('workbench.action.files.newUntitledFile');\n  const result = await lambda();\n  await vscode.commands.executeCommand('workbench.action.closeActiveEditor');\n  return result;\n};\n\nconst getNumberOfVisibleLines = async () =>\n  vscode.window.activeTextEditor!.visibleRanges[0].end.line;\n\nsuite(':only command', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  teardown(cleanUpWorkspace);\n\n  test('Run :only', async () => {\n    // Ensure we have multiple editors in a split\n    await vscode.commands.executeCommand('workbench.action.splitEditorRight');\n    await waitForEditorsToClose(2);\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 2, 'Editor did not split into 2');\n\n    // Ensure panel is visible\n    if ((await isPanelVisible()) !== true) {\n      await vscode.commands.executeCommand('workbench.action.togglePanel');\n    }\n    assert.strictEqual(await isPanelVisible(), true);\n\n    // Run 'only' command\n    await new ExCommandLine('only', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assert.strictEqual(\n      vscode.window.visibleTextEditors.length,\n      1,\n      'Did not reduce to single editor',\n    );\n    assert.strictEqual(await isPanelVisible(), false, 'Panel is still visible');\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/put.test.ts",
    "content": "import * as assert from 'assert';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { Register, RegisterMode } from '../../src/register/register';\nimport { assertEqualLines, setupWorkspace } from './../testUtils';\n\nsuite('put cmd_line', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('put in empty file', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['', 'abc']);\n  });\n\n  test('put in middle of file', async () => {\n    Register.put(modeHandler.vimState, '123');\n    await modeHandler.handleMultipleKeyEvents(['i', 'abc\\ndef\\nghi', '<Esc>', 'k']);\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['abc', 'def', '123', 'ghi']);\n  });\n\n  test('put at end of file', async () => {\n    Register.put(modeHandler.vimState, '123');\n    await modeHandler.handleMultipleKeyEvents(['i', 'abc\\ndef\\nghi', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['abc', 'def', 'ghi', '123']);\n  });\n\n  test('put ignores current indentation', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '\\t\\t123', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['\\t\\t123', 'abc']);\n  });\n\n  test('put text with newlines', async () => {\n    Register.put(modeHandler.vimState, '\\nabc\\ndef\\n\\n');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>', 'k']);\n\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['123', '456', '', 'abc', 'def', '', '', '789']);\n  });\n\n  test('put from specified register', async () => {\n    modeHandler.vimState.recordedState.registerName = '1';\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(':put 1\\n'.split(''));\n    assertEqualLines(['', 'abc']);\n  });\n\n  test('put in visual mode executes at cursor end position', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>', 'v', 'k', 'k']);\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['123', '456', '789', 'abc']);\n  });\n\n  test('put forces linewise put regardless of register mode', async () => {\n    modeHandler.vimState.currentRegisterMode = RegisterMode.CharacterWise;\n    Register.put(modeHandler.vimState, 'abc\\n');\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['', 'abc', '']);\n    await modeHandler.handleMultipleKeyEvents(':1,$d\\n'.split(''));\n\n    modeHandler.vimState.currentRegisterMode = RegisterMode.BlockWise;\n    Register.put(modeHandler.vimState, 'abc\\n');\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['', 'abc', '']);\n    await modeHandler.handleMultipleKeyEvents(':1,$d\\n'.split(''));\n\n    modeHandler.vimState.currentRegisterMode = RegisterMode.LineWise;\n    Register.put(modeHandler.vimState, 'abc\\n');\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['', 'abc', '']);\n  });\n\n  test('put! puts before current line', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':put!\\n'.split(''));\n    assertEqualLines(['123', 'abc', '456']);\n  });\n\n  test('put leaves cursor on last line, first non-whitespace character of pasted content', async () => {\n    Register.put(modeHandler.vimState, 'abc\\n    def');\n    await modeHandler.handleMultipleKeyEvents(':put\\n'.split(''));\n    assertEqualLines(['', 'abc', '    def']);\n    assert.strictEqual(modeHandler.vimState.cursorStopPosition.line, 2);\n    assert.strictEqual(modeHandler.vimState.cursorStopPosition.character, 4);\n  });\n\n  test('put! leaves cursor on last line, first non-whitespace character of pasted content', async () => {\n    Register.put(modeHandler.vimState, 'abc\\n    def');\n    await modeHandler.handleMultipleKeyEvents(':put!\\n'.split(''));\n    assertEqualLines(['abc', '    def', '']);\n    assert.strictEqual(modeHandler.vimState.cursorStopPosition.line, 1);\n    assert.strictEqual(modeHandler.vimState.cursorStopPosition.character, 4);\n  });\n\n  test('put with specified line', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':2put\\n'.split(''));\n    assertEqualLines(['123', '456', 'abc', '789']);\n  });\n\n  test('put! with specified line', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':2put!\\n'.split(''));\n    assertEqualLines(['123', 'abc', '456', '789']);\n  });\n\n  test('put with line range should insert at ending line', async () => {\n    Register.put(modeHandler.vimState, 'abc');\n    await modeHandler.handleMultipleKeyEvents(['i', '123\\n456\\n789', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(':1,2put\\n'.split(''));\n    assertEqualLines(['123', '456', 'abc', '789']);\n  });\n\n  test('put range expression', async () => {\n    Register.put(modeHandler.vimState, '');\n    await modeHandler.handleMultipleKeyEvents(':put=range(1,3)\\n'.split(''));\n    assertEqualLines(['', '1', '2', '3']);\n  });\n\n  test('put range expression with step', async () => {\n    Register.put(modeHandler.vimState, '');\n    await modeHandler.handleMultipleKeyEvents(':put=range(4,1,-2)\\n'.split(''));\n    assertEqualLines(['', '4', '2']);\n  });\n\n  test('`:put=` repeats last expression', async () => {\n    Register.put(modeHandler.vimState, '');\n    await modeHandler.handleMultipleKeyEvents(':put=[1,2,3]\\n'.split(''));\n    assertEqualLines(['', '1', '2', '3']);\n    await modeHandler.handleMultipleKeyEvents(':put=\\n'.split(''));\n    assertEqualLines(['', '1', '2', '3', '1', '2', '3']);\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/redo.test.ts",
    "content": "import { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { assertEqualLines, setupWorkspace } from '../testUtils';\n\nsuite('Redo command', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('redoes last undoed action after insert mode', async () => {\n    await modeHandler.handleMultipleKeyEvents(['I', 'a', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(['I', 'b', '<Esc>']);\n    await new ExCommandLine('undo', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    await new ExCommandLine('redo', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assertEqualLines(['ba']);\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/retab.test.ts",
    "content": "import * as assert from 'assert';\nimport { RetabCommand } from '../../src/cmd_line/commands/retab';\nimport { Configuration } from '../testConfiguration';\nimport { newTest } from '../testSimplifier';\nimport * as testUtils from './../testUtils';\n\nsuite(':retab', () => {\n  suiteSetup(testUtils.setupWorkspace);\n\n  suite('Retab line segments', () => {\n    test('replaceSpaces=false', () => {\n      const r = new RetabCommand({\n        replaceSpaces: false,\n      });\n\n      assert.deepStrictEqual(r.retabLineSegment('    ', 0, 4), { value: '    ', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('    ', 2, 4), { value: '    ', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t', 0, 4), { value: '\\t', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t', 1, 4), { value: '\\t', length: 3 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t ', 3, 4), { value: '\\t ', length: 2 });\n\n      assert.deepStrictEqual(r.retabLineSegment('       ', 0, 7), { value: '       ', length: 7 });\n      assert.deepStrictEqual(r.retabLineSegment('    ', 3, 7), { value: '    ', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t  ', 0, 7), { value: '\\t  ', length: 9 });\n      assert.deepStrictEqual(r.retabLineSegment('  \\t ', 0, 7), { value: '\\t ', length: 8 });\n      assert.deepStrictEqual(r.retabLineSegment('      \\t', 0, 7), { value: '\\t', length: 7 });\n    });\n\n    test('replaceSpaces=true', () => {\n      const r = new RetabCommand({\n        replaceSpaces: true,\n      });\n\n      assert.deepStrictEqual(r.retabLineSegment('    ', 0, 4), { value: '\\t', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('    ', 2, 4), { value: '\\t  ', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t', 0, 4), { value: '\\t', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t', 1, 4), { value: '\\t', length: 3 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t ', 3, 4), { value: '\\t ', length: 2 });\n\n      assert.deepStrictEqual(r.retabLineSegment('       ', 0, 7), { value: '\\t', length: 7 });\n      assert.deepStrictEqual(r.retabLineSegment('    ', 3, 7), { value: '\\t', length: 4 });\n      assert.deepStrictEqual(r.retabLineSegment('\\t  ', 0, 7), { value: '\\t  ', length: 9 });\n      assert.deepStrictEqual(r.retabLineSegment('  \\t ', 0, 7), { value: '\\t ', length: 8 });\n      assert.deepStrictEqual(r.retabLineSegment('      \\t', 0, 7), { value: '\\t', length: 7 });\n    });\n  });\n\n  suite('Retab lines', () => {\n    test('replaceSpaces=false', () => {\n      const r = new RetabCommand({\n        replaceSpaces: false,\n      });\n\n      assert.strictEqual(r.retabLine('', 4), '');\n      assert.strictEqual(r.retabLine('abcd', 4), 'abcd');\n      assert.strictEqual(r.retabLine('ab  ', 4), 'ab  ');\n      assert.strictEqual(r.retabLine(' bc ', 4), ' bc ');\n      assert.strictEqual(r.retabLine('  cd', 4), '  cd');\n    });\n\n    test('replaceSpaces=true', () => {\n      const r = new RetabCommand({\n        replaceSpaces: true,\n      });\n\n      assert.strictEqual(r.retabLine('', 4), '');\n      assert.strictEqual(r.retabLine('abcd', 4), 'abcd');\n      assert.strictEqual(r.retabLine('ab  ', 4), 'ab\\t');\n      assert.strictEqual(r.retabLine(' bc ', 4), ' bc\\t');\n      assert.strictEqual(r.retabLine('  cd', 4), '  cd');\n    });\n  });\n\n  suite('Expand lines segments', () => {\n    test('replaceSpaces=false', () => {\n      const r = new RetabCommand({\n        replaceSpaces: false,\n      });\n\n      assert.strictEqual(r.expandtab('    ', 0, 4), '    ');\n      assert.strictEqual(r.expandtab('    ', 2, 4), '    ');\n      assert.strictEqual(r.expandtab('\\t', 0, 4), '    ');\n      assert.strictEqual(r.expandtab('\\t', 1, 4), '   ');\n      assert.strictEqual(r.expandtab('\\t ', 3, 4), '  ');\n\n      assert.strictEqual(r.expandtab('       ', 0, 7), '       ');\n      assert.strictEqual(r.expandtab('    ', 3, 7), '    ');\n      assert.strictEqual(r.expandtab('\\t  ', 0, 7), '         ');\n      assert.strictEqual(r.expandtab('  \\t ', 0, 7), '        ');\n      assert.strictEqual(r.expandtab('      \\t', 0, 7), '       ');\n    });\n\n    test('replaceSpaces=true', () => {\n      const r = new RetabCommand({\n        replaceSpaces: true,\n      });\n\n      assert.strictEqual(r.expandtab('    ', 0, 4), '    ');\n      assert.strictEqual(r.expandtab('    ', 2, 4), '    ');\n      assert.strictEqual(r.expandtab('\\t', 0, 4), '    ');\n      assert.strictEqual(r.expandtab('\\t', 1, 4), '   ');\n      assert.strictEqual(r.expandtab('\\t ', 3, 4), '  ');\n\n      assert.strictEqual(r.expandtab('       ', 0, 7), '       ');\n      assert.strictEqual(r.expandtab('    ', 3, 7), '    ');\n      assert.strictEqual(r.expandtab('\\t  ', 0, 7), '         ');\n      assert.strictEqual(r.expandtab('  \\t ', 0, 7), '        ');\n      assert.strictEqual(r.expandtab('      \\t', 0, 7), '       ');\n    });\n  });\n\n  const start = ['|a       b   \\tc   d'];\n\n  suite(':retab (tabstop=4, noexpandtab)', () => {\n    setup(async () => {\n      await testUtils.reloadConfiguration(\n        new Configuration({\n          tabstop: 4,\n          expandtab: false,\n        }),\n      );\n    });\n\n    newTest({ title: 'retab', start, keysPressed: ':retab\\n', end: ['|a       b\\t\\tc   d'] });\n    newTest({ title: 'retab!', start, keysPressed: ':retab!\\n', end: ['|a\\t\\tb\\t\\tc\\td'] });\n    newTest({ title: 'retab 4', start, keysPressed: ':retab 4\\n', end: ['|a       b\\t\\tc   d'] });\n    newTest({ title: 'retab! 4', start, keysPressed: ':retab! 4\\n', end: ['|a\\t\\tb\\t\\tc\\td'] });\n    newTest({ title: 'retab 7', start, keysPressed: ':retab 7\\n', end: ['|a       b\\t  c   d'] });\n    newTest({ title: 'retab! 7', start, keysPressed: ':retab! 7\\n', end: ['|a\\t b\\t  c   d'] });\n  });\n\n  suite(':retab (tabstop=7, noexpandtab)', () => {\n    setup(async () => {\n      await testUtils.reloadConfiguration(\n        new Configuration({\n          tabstop: 7,\n          expandtab: false,\n        }),\n      );\n    });\n\n    newTest({ title: 'retab', start, keysPressed: ':retab\\n', end: ['|a       b\\tc   d'] });\n    newTest({ title: 'retab!', start, keysPressed: ':retab!\\n', end: ['|a\\t b\\tc   d'] });\n    newTest({ title: 'retab 4', start, keysPressed: ':retab 4\\n', end: ['|a       b\\t  c   d'] });\n    newTest({ title: 'retab! 4', start, keysPressed: ':retab! 4\\n', end: ['|a\\t\\tb\\t  c\\t  d'] });\n    newTest({ title: 'retab 7', start, keysPressed: ':retab 7\\n', end: ['|a       b\\tc   d'] });\n    newTest({ title: 'retab! 7', start, keysPressed: ':retab! 7\\n', end: ['|a\\t b\\tc   d'] });\n  });\n\n  suite(':retab (tabstop=4, expandtab)', () => {\n    setup(async () => {\n      await testUtils.reloadConfiguration(\n        new Configuration({\n          tabstop: 4,\n          expandtab: true,\n        }),\n      );\n    });\n\n    const end = ['|a       b       c   d'];\n\n    newTest({ title: 'retab', start, keysPressed: ':retab\\n', end });\n    newTest({ title: 'retab!', start, keysPressed: ':retab!\\n', end });\n    newTest({ title: 'retab 4', start, keysPressed: ':retab 4\\n', end });\n    newTest({ title: 'retab! 4', start, keysPressed: ':retab! 4\\n', end });\n    newTest({ title: 'retab 7', start, keysPressed: ':retab 7\\n', end });\n    newTest({ title: 'retab! 7', start, keysPressed: ':retab! 7\\n', end });\n  });\n\n  suite(':retab (tabstop=7, expandtab)', () => {\n    setup(async () => {\n      await testUtils.reloadConfiguration(\n        new Configuration({\n          tabstop: 7,\n          expandtab: true,\n        }),\n      );\n    });\n\n    const end = ['|a       b     c   d'];\n\n    newTest({ title: 'retab', start, keysPressed: ':retab\\n', end });\n    newTest({ title: 'retab!', start, keysPressed: ':retab!\\n', end });\n    newTest({ title: 'retab 4', start, keysPressed: ':retab 4\\n', end });\n    newTest({ title: 'retab! 4', start, keysPressed: ':retab! 4\\n', end });\n    newTest({ title: 'retab 7', start, keysPressed: ':retab 7\\n', end });\n    newTest({ title: 'retab! 7', start, keysPressed: ':retab! 7\\n', end });\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/smile.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { SmileCommand } from '../../src/cmd_line/commands/smile';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { assertEqualLines, setupWorkspace, waitForTabChange } from './../testUtils';\n\nsuite('Smile command', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test(':smile creates new tab containing smile', async () => {\n    await new ExCommandLine('smile', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    await waitForTabChange();\n\n    assert.strictEqual(\n      vscode.window.visibleTextEditors.length,\n      1,\n      ':smile did not create a new untitled file',\n    );\n\n    assertEqualLines(SmileCommand.smileText.split('\\n'));\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/sort.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite(':sort', () => {\n  newTest({\n    title: 'Sort whole file, ascending',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':sort\\n',\n    end: ['|Banana', 'Eggplant', 'apple', 'cabbage', 'dragonfruit'],\n  });\n\n  newTest({\n    title: 'Sort whole file, ascending, ignore case',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':sort i\\n',\n    end: ['|apple', 'Banana', 'cabbage', 'dragonfruit', 'Eggplant'],\n  });\n\n  newTest({\n    title: 'Sort whole file, descending',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':sort!\\n',\n    end: ['|dragonfruit', 'cabbage', 'apple', 'Eggplant', 'Banana'],\n  });\n\n  newTest({\n    title: 'Sort whole file, descending, ignore case',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':sort! i\\n',\n    end: ['|Eggplant', 'dragonfruit', 'cabbage', 'Banana', 'apple'],\n  });\n\n  newTest({\n    title: 'Sort range, ascending',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':2,4sort\\n',\n    end: ['Eggplant', '|Banana', 'apple', 'dragonfruit', 'cabbage'],\n  });\n\n  newTest({\n    title: 'Sort range, ascending, ignore case',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':2,4sort i\\n',\n    end: ['Eggplant', '|apple', 'Banana', 'dragonfruit', 'cabbage'],\n  });\n\n  newTest({\n    title: 'Sort range, descending',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':2,4sort!\\n',\n    end: ['Eggplant', '|dragonfruit', 'apple', 'Banana', 'cabbage'],\n  });\n\n  newTest({\n    title: 'Sort range, descending, ignore case',\n    start: ['Eggplant', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage'],\n    keysPressed: ':2,4sort! i\\n',\n    end: ['Eggplant', '|dragonfruit', 'Banana', 'apple', 'cabbage'],\n  });\n\n  newTest({\n    title: 'Sort whole file, ascending, unique',\n    start: ['Eggplant', 'apple', 'Banana', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage', 'apple'],\n    keysPressed: ':sort u\\n',\n    end: ['|Banana', 'Eggplant', 'apple', 'cabbage', 'dragonfruit'],\n  });\n\n  newTest({\n    title: 'Sort whole file, ascending, ignore case, unique',\n    start: ['Eggplant', 'Apple', 'banana', 'dragonfruit', 'ap|ple', 'Banana', 'cabbage', 'apple'],\n    keysPressed: ':sort iu\\n',\n    end: ['|Apple', 'banana', 'cabbage', 'dragonfruit', 'Eggplant'],\n  });\n\n  newTest({\n    title: 'Sort whole file, numeric',\n    start: ['2', '|10', '1', '2'],\n    keysPressed: ':sort n\\n',\n    end: ['|1', '2', '2', '10'],\n  });\n\n  newTest({\n    title: 'Sort range, numeric',\n    start: ['2', '|10', '1', '2', '-1', '5'],\n    keysPressed: ':2,4sort n\\n',\n    end: ['2', '|1', '2', '10', '-1', '5'],\n  });\n\n  newTest({\n    title: 'Sort range descending, numeric',\n    start: ['2', '|10', '1', '2', '-1', '5'],\n    keysPressed: ':2,5sort! n\\n',\n    end: ['2', '|10', '2', '1', '-1', '5'],\n  });\n\n  newTest({\n    title: 'Sort whole file ascending, numeric mixed with ascii',\n    start: ['banana2', 'apple|10', 'cabbage1', 'App2le'],\n    keysPressed: ':sort n\\n',\n    end: ['|cabbage1', 'banana2', 'App2le', 'apple10'],\n  });\n\n  newTest({\n    title: 'Sort whole file descending, numeric mixed with ascii',\n    start: ['banana2', 'apple|10', 'cabbage1', 'App2le'],\n    keysPressed: ':sort! n\\n',\n    end: ['|apple10', 'App2le', 'banana2', 'cabbage1'],\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/split.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { setupWorkspace, waitForEditorsToClose } from './../testUtils';\n\nsuite('Horizontal split', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  for (const cmd of ['sp', 'split', 'new']) {\n    test(`:${cmd} creates a second split`, async () => {\n      await new ExCommandLine(cmd, modeHandler.vimState.currentMode).run(modeHandler.vimState);\n      await waitForEditorsToClose(2);\n\n      assert.strictEqual(\n        vscode.window.visibleTextEditors.length,\n        2,\n        'Editor did not split in 1 sec',\n      );\n    });\n  }\n});\n"
  },
  {
    "path": "test/cmd_line/substitute.test.ts",
    "content": "import { Configuration } from '../testConfiguration';\nimport { newTest } from '../testSimplifier';\nimport { reloadConfiguration, setupWorkspace } from './../testUtils';\n\nfunction sub(\n  pattern: string,\n  replace: string,\n  args?: { lineRange?: string; flags?: string; count?: number },\n): string {\n  const lineRange = args?.lineRange ?? '';\n  const flags = args?.flags !== undefined ? `/${args.flags}` : '';\n  const count = args?.count !== undefined ? ` ${args.count}` : '';\n  return `:${lineRange}s/${pattern}/${replace}${flags}${count}\\n`;\n}\n\nsuite('Basic substitute', () => {\n  setup(setupWorkspace);\n\n  newTest({\n    title: 'Replace single word once',\n    start: ['|aba'],\n    keysPressed: sub('a', 'd', { lineRange: '%' }),\n    end: ['|dba'],\n  });\n\n  newTest({\n    title: 'Replace with `g` flag',\n    start: ['|aba'],\n    keysPressed: sub('a', 'd', { lineRange: '%', flags: 'g' }),\n    end: ['|dbd'],\n  });\n\n  newTest({\n    title: 'Replace with flags AND count',\n    start: ['|blah blah', 'blah', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.', flags: 'g', count: 2 }),\n    end: ['yay yay', '|yay', 'blah blah', 'blah blah'],\n  });\n\n  /*\n  newTest({ title: 'Replace with `c` flag',\n    const confirmStub = sinon\n      .stub(SubstituteCommand.prototype, 'confirmReplacement')\n      .resolves(true);\n    start: [ 'aba'],\n    keysPressed: ':%s/a/d/c\\n',\n\n    end: ['dba']\n    confirmStub.restore();\n  });\n  */\n\n  /*\n  newTest({ title: 'Replace with `gc` flag',\n    const confirmStub = sinon\n      .stub(SubstituteCommand.prototype, 'confirmReplacement')\n      .resolves(true);\n    start: [ 'f', 'f', 'b', 'a', 'r', 'f'],\n    keysPressed: ':%s/f/foo/gc\\n',\n\n    end: ['foofoobarfoo']\n    confirmStub.restore();\n  });\n  */\n\n  newTest({\n    title: 'Replace across all lines',\n    start: ['|aba', 'ab'],\n    keysPressed: sub('a', 'd', { lineRange: '%', flags: 'g' }),\n    end: ['dbd', '|db'],\n    statusBar: '3 substitutions on 2 lines',\n  });\n\n  newTest({\n    title: 'Replace on specific single line',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '3' }),\n    end: ['blah blah', 'blah', '|yay blah', 'blah blah'],\n  });\n\n  newTest({\n    title: 'Replace on current line using dot',\n    start: ['blah blah', '|blah', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.' }),\n    end: ['blah blah', '|yay', 'blah blah', 'blah blah'],\n  });\n\n  newTest({\n    title: 'Replace single relative line using dot and plus',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.+2' }),\n    end: ['blah blah', 'blah', 'blah blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Replace across specific line range',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '3,4' }),\n    end: ['blah blah', 'blah', 'yay blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Replace across relative line range using dot, plus, and minus',\n    start: ['blah blah', '|blah', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.-1,.+1' }),\n    end: ['yay blah', 'yay', '|yay blah', 'blah blah'],\n    statusBar: '3 substitutions on 3 lines',\n  });\n\n  newTest({\n    title: 'Replace across relative line range using numLines+colon shorthand',\n    start: ['blah blah', '|blah', 'blah blah', 'blah blah'],\n    keysPressed: '3' + sub('blah', 'yay'),\n    end: ['blah blah', 'yay', 'yay blah', '|yay blah'],\n    statusBar: '3 substitutions on 3 lines',\n  });\n\n  newTest({\n    title: 'Repeat replacement across relative line range',\n    start: ['|blah blah', 'blah', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay') + 'j' + '3:s\\n',\n    end: ['yay blah', 'yay', 'yay blah', '|yay blah'],\n    statusBar: '3 substitutions on 3 lines',\n  });\n\n  newTest({\n    title: 'Replace with range AND count but no flags',\n    start: ['|blah blah', 'blah', 'blah blah', 'blah blah'],\n    keysPressed: '3' + sub('blah', 'yay', { flags: '', count: 2 }),\n    end: ['blah blah', 'blah', 'yay blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Undocumented: operator without LHS assumes dot as LHS',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '+2' }),\n    end: ['blah blah', 'blah', 'blah blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Undocumented: multiple consecutive operators use 1 as RHS',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.++1' }),\n    end: ['blah blah', 'blah', 'blah blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Undocumented: trailing operators use 1 as RHS',\n    start: ['blah blah', 'bla|h', 'blah blah', 'blah blah'],\n    keysPressed: sub('blah', 'yay', { lineRange: '.+1+' }),\n    end: ['blah blah', 'blah', 'blah blah', '|yay blah'],\n  });\n\n  newTest({\n    title: 'Preserve \\\\b in regular expression',\n    start: ['one |two three thirteen'],\n    keysPressed: sub('\\\\bt', 'x', { flags: 'g' }),\n    end: ['|one xwo xhree xhirteen'],\n    statusBar: '3 substitutions on 1 line',\n  });\n\n  newTest({\n    title: 'Preserve \\\\\\\\ in regular expression',\n    start: ['one |\\\\two \\\\three thirteen'],\n    keysPressed: sub('\\\\\\\\t', 'x', { flags: 'g' }),\n    end: ['|one xwo xhree thirteen'],\n  });\n\n  newTest({\n    title: 'Replace with \\\\n',\n    start: ['one |two three'],\n    keysPressed: sub('t', '\\\\n', { flags: 'g' }),\n    end: ['one ', 'wo ', '|hree'],\n  });\n\n  newTest({\n    title: 'Replace with \\\\t',\n    start: ['one |two three'],\n    keysPressed: sub('t', '\\\\t', { flags: 'g' }),\n    end: ['|one \\two \\three'],\n  });\n\n  newTest({\n    title: 'Replace with \\\\',\n    start: ['one |two three'],\n    keysPressed: sub('t', '\\\\\\\\', { flags: 'g' }),\n    end: ['|one \\\\wo \\\\hree'],\n  });\n\n  newTest({\n    title: 'Replace trailing \\\\ with \\\\',\n    start: ['one |two three'],\n    keysPressed: sub('t', '\\\\'),\n    end: ['|one \\\\wo three'],\n  });\n\n  newTest({\n    title: 'Replace specific single equal lines',\n    start: ['|aba', 'ab'],\n    keysPressed: sub('a', 'd', { lineRange: '1,1', flags: 'g' }),\n    end: ['|dbd', 'ab'],\n  });\n\n  newTest({\n    title: 'Replace current line with no active selection',\n    start: ['aba', '|ab'],\n    keysPressed: sub('a', 'd', { flags: 'g' }),\n    end: ['aba', '|db'],\n  });\n\n  newTest({\n    title: 'Replace text in selection',\n    start: ['|aba', 'ab'],\n    keysPressed: 'Vj' + sub('a', 'd', { flags: 'g' }),\n    end: ['dbd', '|db'],\n  });\n\n  newTest({\n    title: 'Replace in selection with \\\\n',\n    start: ['1,|2', '3,4'],\n    keysPressed: 'Vj' + sub(',', '\\\\n', { flags: 'g' }),\n    end: ['1', '2', '3', '|4'],\n  });\n\n  newTest({\n    title: 'Substitute support marks',\n    start: ['|aba', 'aba', 'abc'],\n    keysPressed: 'ma' + 'jmb' + sub('a', 'd', { lineRange: \"'a,'b\", flags: 'g' }),\n    end: ['dbd', '|dbd', 'abc'],\n  });\n\n  newTest({\n    title: 'Substitute in last visual selection with \\\\%V',\n    start: ['aba', '|aba', 'abc', ''],\n    keysPressed: 'vjj<Esc>' + sub('\\\\%Va', 'd', { flags: 'g' }),\n    end: ['aba', 'dbd', '|dbc', ''],\n  });\n\n  newTest({\n    title: '`n` flag (report count)',\n    start: ['apple', 'ban|ana', 'celery', 'dragonfruit'],\n    keysPressed: sub('a', '', { lineRange: '%', flags: 'gn' }),\n    end: ['apple', 'ban|ana', 'celery', 'dragonfruit'],\n    statusBar: '5 matches on 3 lines',\n  });\n\n  suite('Effects of gdefault=true', () => {\n    setup(async () => {\n      await reloadConfiguration(new Configuration({ gdefault: true }));\n    });\n\n    newTest({\n      title: 'Replace all matches in the line',\n      start: ['|aba'],\n      keysPressed: sub('a', 'd', { lineRange: '%' }),\n      end: ['|dbd'],\n    });\n\n    newTest({\n      title: 'Replace with `g` flag inverts global flag',\n      start: ['|aba'],\n      keysPressed: sub('a', 'd', { lineRange: '%', flags: 'g' }),\n      end: ['|dba'],\n    });\n\n    /*\n    newTest({ title: 'Replace with `c` flag inverts global flag',\n      const confirmStub = sinon\n        .stub(SubstituteCommand.prototype, 'confirmReplacement')\n        .resolves(true);\n      start: [ 'f', 'f', 'b', 'a', 'r', 'f'],\n      keysPressed: ':%s/f/foo/c\\n',\n      end: ['foofoobarfoo']\n      confirmStub.restore();\n    });\n    */\n\n    newTest({\n      title: 'Replace multiple lines',\n      start: ['|aba', 'ab'],\n      keysPressed: sub('a', 'd', { lineRange: '%' }),\n      end: ['dbd', '|db'],\n    });\n\n    newTest({\n      title: 'Replace across specific lines',\n      start: ['|aba', 'ab'],\n      keysPressed: sub('a', 'd', { lineRange: '1,1' }),\n      end: ['|dbd', 'ab'],\n    });\n\n    newTest({\n      title: 'Replace current line with no active selection',\n      start: ['aba', '|ab'],\n      keysPressed: sub('a', 'd'),\n      end: ['aba', '|db'],\n    });\n\n    newTest({\n      title: 'Replace text in selection',\n      start: ['|aba', 'ab'],\n      keysPressed: 'Vj' + sub('a', 'd'),\n      end: ['dbd', '|db'],\n    });\n\n    newTest({\n      title: 'Substitute support marks',\n      start: ['|aba', 'aba', 'abc'],\n      keysPressed: 'ma' + 'jmb' + sub('a', 'd', { lineRange: \"'a,'b\" }),\n      end: ['dbd', '|dbd', 'abc'],\n    });\n\n    newTest({\n      title: 'Substitute with escaped delimiter',\n      start: ['|b//f'],\n      keysPressed: ':s/\\\\/\\\\/f/z/g\\n',\n      end: ['|bz'],\n    });\n  });\n\n  suite('Substitute should use various previous search/substitute states', () => {\n    newTest({\n      title: 'Substitute with previous search using *',\n      start: ['|foo', 'bar', 'foo', 'bar'],\n      keysPressed: '*' + sub('', 'fighters', { lineRange: '%' }),\n      end: ['fighters', 'bar', '|fighters', 'bar'],\n    });\n\n    newTest({\n      title: 'Substitute with previous search using #',\n      start: ['foo', 'bar', 'f|oo', 'bar'],\n      keysPressed: '#' + sub('', 'fighters', { lineRange: '%' }),\n      end: ['fighters', 'bar', '|fighters', 'bar'],\n    });\n\n    newTest({\n      title: 'Substitute with previous search using /',\n      start: ['foo|', 'bar', 'foo', 'bar'],\n      keysPressed: '/foo\\n' + sub('', 'fighters', { lineRange: '%' }),\n      end: ['fighters', 'bar', '|fighters', 'bar'],\n    });\n\n    newTest({\n      title: '`~` in replace string uses previous replace string',\n      start: ['|one two three', 'two three four'],\n      keysPressed: sub('two', 'xyz') + 'j' + sub('three', '~ ~'),\n      end: ['one xyz three', '|two xyz xyz four'],\n    });\n\n    newTest({\n      title: 'Substitute with parameters should update search state',\n      start: ['foo', 'bar', 'foo', 'bar|'],\n      keysPressed:\n        '/bar\\n' + // search for bar (search state now = bar)\n        ':s/ar/ite\\n' + // change first bar to bite (search state now = ar, not bar)\n        'n' + // repeat search (ar, not bar)\n        'rr', // and replace a with r\n      end: ['foo', 'bite', 'foo', 'b|rr'],\n    });\n\n    newTest({\n      title:\n        'Substitute with empty replacement should delete previous substitution (all variants) and accepts flags',\n      start: [\n        'link',\n        '|ganon is here',\n        'link',\n        'ganon is here',\n        'link',\n        'ganon is here',\n        'link',\n        'ganon is here',\n        'link',\n        'ganon ganon is here',\n      ],\n      keysPressed:\n        sub('ganon', 'zelda') + // replace ganon with zelda (ensuring we have a prior replacement state)\n        'n' + // find next ganon\n        ':s/\\n' + // replace ganon with nothing (using prior state)\n        sub('ganon', 'zelda') + // does nothing (just ensuring we have a prior replacement state)\n        'n' + // find next ganon\n        ':s//\\n' + // replace ganon with nothing (using prior state)\n        'n' + // find next ganon\n        ':s/ganon\\n' + // replace ganon with nothing (using single input)\n        sub('ganon', 'zelda') + // does nothing (just ensuring we have a prior replacement state)\n        'n' + // find next ganon\n        ':s///g\\n', // replace ganon with nothing\n      end: [\n        'link',\n        'zelda is here',\n        'link',\n        ' is here',\n        'link',\n        ' is here',\n        'link',\n        ' is here',\n        'link',\n        '|  is here',\n      ],\n    });\n\n    newTest({\n      title:\n        'Substitute with no pattern should repeat previous substitution and not alter search state',\n      start: ['|link', 'zelda', 'link', 'zelda', 'link'],\n      keysPressed:\n        sub('ink', 'egend') + // replace link with legend (search state now = egend, and substitute state set)\n        '/link\\n' + // search for link (search state now = link, not ink)\n        ':s\\n' + // repeat replacement (using substitute state, so ink, not link - note: search state should NOT change)\n        'n' + // repeat search for link, not ink\n        'rp', // and replace l with p (confirming search state was unaltered)\n      end: ['legend', 'zelda', 'legend', 'zelda', '|pink'],\n    });\n\n    newTest({\n      title: 'Substitute repeat previous should accept flags',\n      start: ['|fooo'],\n      keysPressed: sub('o', 'un') + ':s g\\n', // repeated replacement accepts g flag, replacing all other occurrences\n      end: ['|fununun'],\n    });\n\n    newTest({\n      title: 'Ampersand (&) should repeat the last substitution',\n      start: ['|foo bar baz'],\n      keysPressed: sub('ba', 't') + '&',\n      end: ['|foo tr tz'],\n    });\n\n    suite('Change case', () => {\n      newTest({\n        title: '\\\\U',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s\\\\S*', '\\\\U&x', { flags: 'g' }),\n        end: ['|SHEX SELLSX SEASHELLSX by the SEASHOREX'],\n      });\n\n      newTest({\n        title: '\\\\U then \\\\E',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s\\\\S*', '\\\\U&\\\\Ex', { flags: 'g' }),\n        end: ['|SHEx SELLSx SEASHELLSx by the SEASHOREx'],\n      });\n\n      newTest({\n        title: '\\\\u',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s\\\\S*', '\\\\u&x', { flags: 'g' }),\n        end: ['|Shex Sellsx Seashellsx by the Seashorex'],\n      });\n\n      newTest({\n        title: '\\\\L',\n        start: ['|SHE SELLS SEASHELLS BY THE SEASHORE'],\n        keysPressed: sub('S\\\\S*', '\\\\L&X', { flags: 'g' }),\n        end: ['|shex sellsx seashellsx BY THE seashorex'],\n      });\n\n      newTest({\n        title: '\\\\L then \\\\E',\n        start: ['|SHE SELLS SEASHELLS BY THE SEASHORE'],\n        keysPressed: sub('S\\\\S*', '\\\\L&\\\\EX', { flags: 'g' }),\n        end: ['|sheX sellsX seashellsX BY THE seashoreX'],\n      });\n\n      newTest({\n        title: '\\\\l',\n        start: ['|SHE SELLS SEASHELLS BY THE SEASHORE'],\n        keysPressed: sub('S\\\\S*', '\\\\l&X', { flags: 'g' }),\n        end: ['|sHEX sELLSX sEASHELLSX BY THE sEASHOREX'],\n      });\n    });\n\n    suite('Capture groups', () => {\n      newTest({\n        title: '& capture group',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s\\\\S*', '(&)', { flags: 'g' }),\n        end: ['|(she) (sells) (seashells) by the (seashore)'],\n      });\n\n      newTest({\n        title: '\\\\0 capture group',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s\\\\S*', '(\\\\0)', { flags: 'g' }),\n        end: ['|(she) (sells) (seashells) by the (seashore)'],\n      });\n\n      newTest({\n        title: '\\\\1 capture group',\n        start: ['|she sells seashells by the seashore'],\n        keysPressed: sub('s(\\\\S*)', '(\\\\1)', { flags: 'g' }),\n        end: ['|(he) (ells) (eashells) by the (eashore)'],\n      });\n    });\n\n    newTest({\n      title: 'Replace new line',\n      start: ['|one two', 'three', 'one two', 'four', 'one two', 'three'],\n      keysPressed: sub('(two)\\\\n(three)', '\\\\1 \\\\2', { lineRange: '%' }),\n      // TODO: Cursor position is wrong\n      end: ['one two three', 'one two', '|four', 'one two three'],\n    });\n  });\n\n  suite('Replace with expression', () => {\n    newTest({\n      title: 'Replace with string',\n      start: ['|one', 'two', 'one'],\n      keysPressed: sub('one', '\\\\=\"X\" .. \"Y\" .. \"Z\"', { lineRange: '%' }),\n      end: ['XYZ', 'two', '|XYZ'],\n    });\n\n    newTest({\n      title: 'Replace with int',\n      start: ['|one', 'two', 'one'],\n      keysPressed: sub('one', '\\\\=1 + 2 + 3', { lineRange: '%' }),\n      end: ['6', 'two', '|6'],\n    });\n\n    newTest({\n      title: 'Replace with list',\n      start: ['|one', 'two', 'one'],\n      keysPressed: sub('one', '\\\\=[1, \"2\", [3, \"4\"]]', { lineRange: '%' }),\n      end: ['1', '2', \"[3, '4']\", '', 'two', '1', '2', \"[3, '4']\", '|'],\n    });\n\n    // TODO: Test submatch()\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/tab.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { createFile, setupWorkspace } from '../testUtils';\n\nsuite('cmd_line tab', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('tabe with no arguments when not in workspace opens an untitled file', async () => {\n    const beforeEditor = vscode.window.activeTextEditor;\n    await new ExCommandLine('tabe', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    const afterEditor = vscode.window.activeTextEditor;\n\n    assert.notStrictEqual(beforeEditor, afterEditor, 'Active editor did not change');\n  });\n\n  test('tabedit with no arguments when not in workspace opens an untitled file', async () => {\n    const beforeEditor = vscode.window.activeTextEditor;\n    await new ExCommandLine('tabedit', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    const afterEditor = vscode.window.activeTextEditor;\n\n    assert.notStrictEqual(beforeEditor, afterEditor, 'Active editor did not change');\n  });\n\n  test('tabe with absolute path when not in workspace opens file', async () => {\n    const filePath = await createFile();\n    await new ExCommandLine(`tabe ${filePath}`, modeHandler.vimState.currentMode).run(\n      modeHandler.vimState,\n    );\n    const editor = vscode.window.activeTextEditor;\n\n    if (editor === undefined) {\n      assert.fail('File did not open');\n    } else {\n      if (process.platform !== 'win32') {\n        assert.strictEqual(editor.document.fileName, filePath, 'Opened wrong file');\n      } else {\n        assert.strictEqual(\n          editor.document.fileName.toLowerCase(),\n          filePath.toLowerCase(),\n          'Opened wrong file',\n        );\n      }\n    }\n  });\n\n  test('tabe with current file path does nothing', async () => {\n    const filePath = await createFile();\n    await new ExCommandLine(`tabe ${filePath}`, modeHandler.vimState.currentMode).run(\n      modeHandler.vimState,\n    );\n\n    const beforeEditor = vscode.window.activeTextEditor;\n    await new ExCommandLine(`tabe ${filePath}`, modeHandler.vimState.currentMode).run(\n      modeHandler.vimState,\n    );\n    const afterEditor = vscode.window.activeTextEditor;\n\n    assert.strictEqual(\n      beforeEditor,\n      afterEditor,\n      'Active editor changed even though :tabe opened the same file',\n    );\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/tabCompletion.test.ts",
    "content": "import * as assert from 'assert';\nimport * as os from 'os';\nimport { basename, join, sep } from 'path';\nimport * as vscode from 'vscode';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { StatusBar } from '../../src/statusBar';\nimport * as t from '../testUtils';\n\nsuite('cmd_line tabComplete', () => {\n  let modeHandler: ModeHandler;\n  suiteSetup(async () => {\n    await t.setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('command line command tab completion', async () => {\n    await modeHandler.handleMultipleKeyEvents([':', 'e', 'd', 'i']);\n    await modeHandler.handleKeyEvent('<tab>');\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.strictEqual(statusBarAfterTab.trim(), ':edit|', 'Command Tab Completion Failed');\n  });\n\n  test('command line command shift+tab', async () => {\n    await modeHandler.handleMultipleKeyEvents([':', 'e', '<tab>']);\n    const firstTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<tab>');\n    const secondTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<S-tab>');\n    const actual = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(firstTab, secondTab);\n    assert.strictEqual(actual, firstTab, \"Command can't go back with shift+tab\");\n  });\n\n  test('command line file tab completion with no base path', async () => {\n    await modeHandler.handleKeyEvent(':');\n    const statusBarBeforeTab = StatusBar.getText();\n\n    await modeHandler.handleMultipleKeyEvents(['e', ' ', '<tab>']);\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(statusBarBeforeTab, statusBarAfterTab, 'Status Bar did not change');\n  });\n\n  test('command line file tab completion with / as base path', async () => {\n    await modeHandler.handleKeyEvent(':');\n    const statusBarBeforeTab = StatusBar.getText();\n\n    await modeHandler.handleMultipleKeyEvents(['e', ' ', '/', '<tab>']);\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(statusBarBeforeTab, statusBarAfterTab, 'Status Bar did not change');\n  });\n\n  test('command line file tab completion with ~/ as base path', async () => {\n    await modeHandler.handleKeyEvent(':');\n    const statusBarBeforeTab = StatusBar.getText();\n\n    await modeHandler.handleMultipleKeyEvents(['e', ' ', '~', '/', '<tab>']);\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(statusBarBeforeTab, statusBarAfterTab, 'Status Bar did not change');\n  });\n\n  test('command line file tab completion with ./ as base path', async () => {\n    await modeHandler.handleKeyEvent(':');\n    const statusBarBeforeTab = StatusBar.getText();\n\n    await modeHandler.handleMultipleKeyEvents(['e', ' ', '.', '/', '<tab>']);\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(statusBarBeforeTab, statusBarAfterTab, 'Status Bar did not change');\n  });\n\n  test('command line file tab completion with ../ as base path', async () => {\n    await modeHandler.handleKeyEvent(':');\n    const statusBarBeforeTab = StatusBar.getText();\n\n    await modeHandler.handleMultipleKeyEvents(['e', ' ', '.', '.', '/', '<tab>']);\n    const statusBarAfterTab = StatusBar.getText();\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    assert.notStrictEqual(statusBarBeforeTab, statusBarAfterTab, 'Status Bar did not change');\n  });\n\n  test('command line file tab completion directory with / at the end', async () => {\n    const dirPath = await t.createDir();\n\n    try {\n      const baseCmd = `:e ${dirPath.slice(0, -1)}`.split('');\n      await modeHandler.handleMultipleKeyEvents(baseCmd);\n      await modeHandler.handleKeyEvent('<tab>');\n      const statusBarAfterTab = StatusBar.getText().trim();\n      await modeHandler.handleKeyEvent('<Esc>');\n      assert.strictEqual(\n        statusBarAfterTab,\n        `:e ${dirPath}${sep}|`,\n        'Cannot complete with / at the end',\n      );\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(dirPath), { recursive: true });\n    }\n  });\n\n  test('command line file navigate tab completion', async () => {\n    // tmpDir --- inner0\n    //         |- inner1 --- inner10 --- inner100\n    const tmpDir = await t.createDir();\n    const inner0 = await t.createDir(join(tmpDir, 'inner0'));\n    const inner1 = await t.createDir(join(tmpDir, 'inner1'));\n    const inner10 = await t.createDir(join(inner1, 'inner10'));\n    const inner100 = await t.createDir(join(inner10, 'inner100'));\n\n    try {\n      // Tab to see the completion of tempDir\n      const cmd = `:e ${tmpDir}${sep}`.split('');\n      await modeHandler.handleMultipleKeyEvents(cmd);\n      await modeHandler.handleKeyEvent('<tab>');\n      let statusBarAfterTab = StatusBar.getText().trim();\n      let expectedPath = `${tmpDir}${sep}inner0${sep}`;\n      assert.strictEqual(statusBarAfterTab, `:e ${expectedPath}|`, '123');\n\n      // Tab to cycle the completion of tempDir\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      expectedPath = `${tmpDir}${sep}inner1${sep}`;\n      assert.strictEqual(statusBarAfterTab, `:e ${expectedPath}|`, '123');\n\n      // <right> and <tab> to select and complete the content in\n      // the inner1 directory\n      // Since there is only one directory in inner1 which is inner10,\n      // The completion is complete.\n      await modeHandler.handleMultipleKeyEvents(['<right>', '<tab>']);\n      statusBarAfterTab = StatusBar.getText().trim();\n      expectedPath = `${tmpDir}${sep}inner1${sep}inner10${sep}`;\n      assert.strictEqual(statusBarAfterTab, `:e ${expectedPath}|`, '123');\n\n      // A tab would try to complete the content in the inner10.\n      // Since the pervious completion is complete, no <right> is needed to select\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      expectedPath = `${tmpDir}${sep}inner1${sep}inner10${sep}inner100${sep}`;\n      assert.strictEqual(statusBarAfterTab, `:e ${expectedPath}|`, '123');\n\n      // Since there isn't any files or directories in inner100, no completion.\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      expectedPath = `${tmpDir}${sep}inner1${sep}inner10${sep}inner100${sep}`;\n      assert.strictEqual(statusBarAfterTab, `:e ${expectedPath}|`, '123');\n\n      await modeHandler.handleKeyEvent('<Esc>');\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(tmpDir), { recursive: true });\n    }\n  });\n\n  test('command line tab completion on the content on the left of the cursor', async () => {\n    await modeHandler.handleMultipleKeyEvents([':', 'e', 'd', 'i']);\n    await modeHandler.handleKeyEvent('<tab>');\n    let statusBarAfterTab = StatusBar.getText().trim();\n    assert.strictEqual(statusBarAfterTab, ':edit|', 'Command Tab Completion Failed');\n\n    await modeHandler.handleMultipleKeyEvents(['<left>', '<left>']);\n    statusBarAfterTab = StatusBar.getText().trim();\n    assert.strictEqual(statusBarAfterTab, ':ed|it', 'Failed to move the cursor to the left');\n\n    await modeHandler.handleKeyEvent('<tab>');\n    statusBarAfterTab = StatusBar.getText().trim();\n    assert.strictEqual(\n      statusBarAfterTab,\n      ':edit|it',\n      'Failed to complete content left of the cursor',\n    );\n\n    await modeHandler.handleKeyEvent('<Esc>');\n  });\n\n  test('command line file tab completion with .', async () => {\n    const dirPath = await t.createDir();\n    const testFilePath = await t.createFile({ fsPath: join(dirPath, '.testfile') });\n\n    try {\n      // There should only be one auto-completion\n      const baseCmd = `:e ${dirPath}${sep}`.split('');\n      await modeHandler.handleMultipleKeyEvents(baseCmd);\n      // First tab - resolve to .testfile\n      await modeHandler.handleKeyEvent('<tab>');\n      let statusBarAfterTab = StatusBar.getText();\n      assert.strictEqual(\n        statusBarAfterTab.trim(),\n        `:e ${testFilePath}|`,\n        'Cannot complete to .testfile',\n      );\n      // Second tab - resolve to .testfile\n      // ./ and ../ because . is not explicitly typed in.\n      // This should be consistent with Vim\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      assert.strictEqual(statusBarAfterTab, `:e ${testFilePath}|`, 'Cannot complete to .testfile');\n      await modeHandler.handleKeyEvent('<Esc>');\n\n      await modeHandler.handleMultipleKeyEvents(baseCmd.concat('.'));\n      // First tab - resolve to ../\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      assert.strictEqual(\n        statusBarAfterTab,\n        `:e ${dirPath}${sep}..${sep}|`,\n        'Cannot complete to ../',\n      );\n      // Second tab - resolve to ./\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      assert.strictEqual(statusBarAfterTab, `:e ${dirPath}${sep}.${sep}|`, 'Cannot complete to ./');\n      // Third tab - resolve to .testfile\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      assert.strictEqual(statusBarAfterTab, `:e ${testFilePath}|`, 'Cannot complete to .testfile');\n      await modeHandler.handleKeyEvent('<Esc>');\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(dirPath), { recursive: true });\n    }\n  });\n\n  test('command line file tab completion with space in file path', async () => {\n    // Create a random file in temp folder with a space in the file name\n    const prefix = t.rndName();\n    const spacedFilePath = await t.createFile({\n      fsPath: join(os.tmpdir(), prefix + 'vscode-vim completion-test'),\n    });\n    try {\n      // Get the base name of the path which is '<prefix>vscode-vim completion-test'\n      const baseName = basename(spacedFilePath);\n      // Get the base name exclude the space which is '<prefix>vscode-vim'\n      const baseNameExcludeSpace = baseName.substring(0, baseName.lastIndexOf(' '));\n      const fullPathExcludeSpace = spacedFilePath.substring(0, spacedFilePath.lastIndexOf(' '));\n      const failMsg = 'Cannot complete to a path with space';\n\n      // With no base path\n      let cmd = `:e ${baseNameExcludeSpace}`.split('');\n      await modeHandler.handleMultipleKeyEvents(cmd);\n      await modeHandler.handleKeyEvent('<tab>');\n      let statusBarAfterTab = StatusBar.getText().trim();\n      await modeHandler.handleKeyEvent('<Esc>');\n      assert.strictEqual(statusBarAfterTab, `:e ${baseName}|`, `${failMsg} (no base path)`);\n\n      // With multiple ./ ./ as base name\n      cmd = `:e ././${baseNameExcludeSpace}`.split('');\n      await modeHandler.handleMultipleKeyEvents(cmd);\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      await modeHandler.handleKeyEvent('<Esc>');\n      assert.strictEqual(statusBarAfterTab, `:e .${sep}.${sep}${baseName}|`, `${failMsg} (w ././)`);\n\n      // With full path excluding the last space portion\n      cmd = `:e ${fullPathExcludeSpace}`.split('');\n      await modeHandler.handleMultipleKeyEvents(cmd);\n      await modeHandler.handleKeyEvent('<tab>');\n      statusBarAfterTab = StatusBar.getText().trim();\n      await modeHandler.handleKeyEvent('<Esc>');\n      assert.strictEqual(statusBarAfterTab, `:e ${spacedFilePath}|`, `(${failMsg} full path)`);\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(spacedFilePath));\n    }\n  });\n\n  test('command line file tab completion case-sensitivity platform dependent', async () => {\n    const dirPath = await t.createDir();\n    const filePath = await t.createFile({ fsPath: join(dirPath, 'testfile') });\n    const fileAsTyped = join(dirPath, 'TESTFIL');\n    const cmd = `:e ${fileAsTyped}`.split('');\n\n    try {\n      if (process.platform === 'win32') {\n        await modeHandler.handleMultipleKeyEvents(cmd);\n        await modeHandler.handleKeyEvent('<tab>');\n        const statusBarAfterTab = StatusBar.getText().trim();\n        await modeHandler.handleKeyEvent('<Esc>');\n        assert.strictEqual(\n          statusBarAfterTab.toLowerCase(),\n          `:e ${filePath}|`.toLowerCase(),\n          'Cannot complete path case-insensitive on windows',\n        );\n      } else {\n        await modeHandler.handleMultipleKeyEvents(cmd);\n        const statusBarBeforeTab = StatusBar.getText();\n        await modeHandler.handleKeyEvent('<tab>');\n        const statusBarAfterTab = StatusBar.getText().trim();\n        await modeHandler.handleKeyEvent('<Esc>');\n        assert.strictEqual(\n          statusBarBeforeTab,\n          statusBarAfterTab,\n          'Is case-insensitive on non-windows',\n        );\n      }\n    } finally {\n      await vscode.workspace.fs.delete(vscode.Uri.file(dirPath), { recursive: true });\n    }\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/undo.test.ts",
    "content": "import { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { assertEqualLines, setupWorkspace } from '../testUtils';\n\nsuite('Undo command', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('undoes last action after insert mode', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 'a', '<Esc>']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'b', '<Esc>']);\n    await new ExCommandLine('undo', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    assertEqualLines(['a']);\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/vsplit.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { setupWorkspace, waitForEditorsToClose } from './../testUtils';\n\nsuite('Vertical split', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  for (const cmd of ['vs', 'vsp', 'vsplit', 'vnew', 'vne']) {\n    test(`:${cmd} creates a second split`, async () => {\n      await new ExCommandLine(cmd, modeHandler.vimState.currentMode).run(modeHandler.vimState);\n      await waitForEditorsToClose(2);\n\n      assert.strictEqual(\n        vscode.window.visibleTextEditors.length,\n        2,\n        'Editor did not split in 1 sec',\n      );\n    });\n  }\n});\n"
  },
  {
    "path": "test/cmd_line/write.test.ts",
    "content": "import * as assert from 'assert';\nimport * as sinon from 'sinon';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { assertEqualLines, setupWorkspace } from '../testUtils';\n\nsuite('Write command (:w)', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  /**\n   * Helper to type \"hello world!\" in insert mode\n   */\n  const typeHelloWorld = async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      'i',\n      'h',\n      'e',\n      'l',\n      'l',\n      'o',\n      ' ',\n      'w',\n      'o',\n      'r',\n      'l',\n      'd',\n      '!',\n      '<Esc>',\n    ]);\n  };\n\n  /**\n   * Helper to run the :w command\n   */\n  const runWriteCommand = async () => {\n    await new ExCommandLine('w', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n  };\n\n  suite('Basic functionality', () => {\n    test('write (:w) - basic functionality', async () => {\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assertEqualLines(['hello world!']);\n    });\n\n    test('write (:w) - should save document', async () => {\n      await modeHandler.handleMultipleKeyEvents([\n        'i',\n        't',\n        'e',\n        's',\n        't',\n        ' ',\n        'c',\n        'o',\n        'n',\n        't',\n        'e',\n        'n',\n        't',\n        '<Esc>',\n      ]);\n\n      await new ExCommandLine('w', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n\n      assertEqualLines(['test content']);\n    });\n  });\n  suite('Tab Groups API - diff view preservation', () => {\n    let showTextDocumentStub: sinon.SinonStub;\n    let tabGroupsStub: sinon.SinonStub;\n\n    setup(() => {\n      showTextDocumentStub = sinon.stub(vscode.window, 'showTextDocument').resolves();\n      tabGroupsStub = sinon.stub(vscode.window, 'tabGroups');\n    });\n\n    teardown(() => {\n      showTextDocumentStub.restore();\n      tabGroupsStub.restore();\n    });\n\n    test('should pin preview tabs when saving', async () => {\n      const docUri = modeHandler.vimState.document.uri.toString();\n      const mockTab = {\n        isPreview: true,\n        input: { uri: { toString: () => docUri } },\n      };\n      tabGroupsStub.get(() => ({ activeTabGroup: { tabs: [mockTab] } }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.calledOnce,\n        true,\n        'showTextDocument should be called to pin preview tabs',\n      );\n    });\n\n    test('should preserve diff view for non-preview tabs', async () => {\n      const docUri = modeHandler.vimState.document.uri.toString();\n      const mockTab = {\n        isPreview: false,\n        input: { uri: { toString: () => docUri } },\n      };\n      tabGroupsStub.get(() => ({ activeTabGroup: { tabs: [mockTab] } }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.called,\n        false,\n        'showTextDocument should not be called for non-preview tabs',\n      );\n    });\n\n    test('should handle diff tabs correctly (original side)', async () => {\n      const docUri = modeHandler.vimState.document.uri.toString();\n      const mockDiffTab = {\n        isPreview: false,\n        input: {\n          original: { toString: () => docUri },\n          modified: { toString: () => 'other-file-uri' },\n        },\n      };\n      tabGroupsStub.get(() => ({ activeTabGroup: { tabs: [mockDiffTab] } }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.called,\n        false,\n        'should not disrupt diff view when document is original side',\n      );\n    });\n\n    test('should handle diff tabs correctly (modified side)', async () => {\n      const docUri = modeHandler.vimState.document.uri.toString();\n      const mockDiffTab = {\n        isPreview: false,\n        input: {\n          original: { toString: () => 'other-file-uri' },\n          modified: { toString: () => docUri },\n        },\n      };\n      tabGroupsStub.get(() => ({ activeTabGroup: { tabs: [mockDiffTab] } }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.called,\n        false,\n        'should not disrupt diff view when document is modified side',\n      );\n    });\n\n    test('should call showTextDocument when document not in active tab group', async () => {\n      tabGroupsStub.get(() => ({ activeTabGroup: { tabs: [] } }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.calledOnce,\n        true,\n        'showTextDocument should be called when document not in active tab group',\n      );\n    });\n\n    test('should only check active tab group, not other tab groups', async () => {\n      const docUri = modeHandler.vimState.document.uri.toString();\n      // Active tab group has a different file\n      tabGroupsStub.get(() => ({\n        activeTabGroup: {\n          tabs: [{ isPreview: false, input: { uri: { toString: () => 'other-file' } } }],\n        },\n      }));\n\n      await typeHelloWorld();\n      await runWriteCommand();\n\n      assert.strictEqual(\n        showTextDocumentStub.calledOnce,\n        true,\n        'should call showTextDocument when document not in active tab group',\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/writequit.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { ExCommandLine } from '../../src/cmd_line/commandLine';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace, waitForEditorsToClose } from './../testUtils';\n\nsuite('Basic write-quit', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('Run write and quit', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 'a', 'b', 'a', '<Esc>']);\n\n    await new ExCommandLine('wq', modeHandler.vimState.currentMode).run(modeHandler.vimState);\n    await waitForEditorsToClose();\n\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 0, 'Window after 1sec still open');\n  });\n\n  newTest({\n    title: ':q[uit] cannot close dirty file',\n    start: ['one', 't|wo', 'three'],\n    keysPressed: 'x' + ':q\\n',\n    end: ['one', 't|o', 'three'],\n    statusBar: 'E37: No write since last change (add ! to override)',\n  });\n});\n"
  },
  {
    "path": "test/cmd_line/yank.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite(':[range]y[ank] [count] command', () => {\n  newTest({\n    title: ':yank will yank a single line',\n    start: ['|one', 'two', 'three'],\n    keysPressed: '<Esc>:yank\\n' + 'p',\n    end: ['one', '|one', 'two', 'three'],\n  });\n\n  newTest({\n    title: ':yank [cnt] will yank 3 lines',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:y3\\n' + 'p',\n    end: ['one', '|one', 'two', 'three', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':yank [x] [cnt] will yank [cnt] lines',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:ya3\\n' + 'p',\n    end: ['one', '|one', 'two', 'three', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':yank [register] [cnt] will yank [cnt] lines',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:yan3\\n' + 'p',\n    end: ['one', '|one', 'two', 'three', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title:\n      ':[range]yank [cnt] will yank from the end of the range, if range is VisualLine highlight',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: 'vjj:yan3\\nG' + 'p',\n    end: ['one', 'two', 'three', 'four', 'five', '|three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':[range]yank [cnt] will yank [cnt] from the end of the range, if range a line number',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:.+3yan2\\n' + 'p',\n    end: ['one', '|four', 'five', 'two', 'three', 'four', 'five'],\n  });\n\n  newTest({\n    title: ':[range]yank will yank from the end of the range, if range a line number',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '<Esc>:.+3yan\\n' + 'p',\n    end: ['one', '|four', 'two', 'three', 'four', 'five'],\n  });\n});\n"
  },
  {
    "path": "test/completion/lineCompletion.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { Position } from 'vscode';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { getCompletionsForCurrentLine } from '../../src/completion/lineCompletionProvider';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { VimState } from '../../src/state/vimState';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('Provide line completions', () => {\n  let modeHandler: ModeHandler;\n  let vimState: VimState;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n    vimState = modeHandler.vimState;\n  });\n\n  const setupTestWithLines = async (lines: string[]) => {\n    vimState.cursorStopPosition = new Position(0, 0);\n\n    await modeHandler.handleKeyEvent('<Esc>');\n    await vimState.editor.edit((builder) => {\n      builder.insert(new Position(0, 0), lines.join('\\n'));\n    });\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'j', 'j', 'A']);\n  };\n\n  suite('Line Completion Provider unit tests', () => {\n    // TODO(#4844): this fails on Windows\n    test('Can complete lines in file, prioritizing above cursor, near cursor', async () => {\n      if (process.platform === 'win32') {\n        return;\n      }\n      const lines = ['a1', 'a2', 'a', 'a3', 'b1', 'a4'];\n      await setupTestWithLines(lines);\n      const expectedCompletions = ['a2', 'a1', 'a3', 'a4'];\n      const topCompletions = getCompletionsForCurrentLine(\n        vimState.cursorStopPosition,\n        vimState.document,\n      )!.slice(0, expectedCompletions.length);\n\n      assert.deepStrictEqual(topCompletions, expectedCompletions, 'Unexpected completions found');\n    });\n\n    // TODO(#4844): this fails on Windows (and now linux too, for some reason?)\n    test.skip('Can complete lines in file with different indentation', async () => {\n      if (process.platform === 'win32') {\n        return;\n      }\n      const lines = ['a1', '   a 2', 'a', 'a3  ', 'b1', 'a4'];\n      await setupTestWithLines(lines);\n      const expectedCompletions = ['a 2', 'a1', 'a3  ', 'a4'];\n      const topCompletions = getCompletionsForCurrentLine(\n        vimState.cursorStopPosition,\n        vimState.document,\n      )!.slice(0, expectedCompletions.length);\n\n      assert.deepStrictEqual(topCompletions, expectedCompletions, 'Unexpected completions found');\n    });\n\n    test('Returns no completions for unmatched line', async () => {\n      const lines = ['a1', '   a2', 'azzzzzzzzzzzzzzzzzzzzzzzz', 'a3  ', 'b1', 'a4'];\n      await setupTestWithLines(lines);\n      const expectedCompletions = [];\n      const completions = getCompletionsForCurrentLine(\n        vimState.cursorStopPosition,\n        vimState.document,\n      )!.slice(0, expectedCompletions.length);\n\n      assert.strictEqual(completions.length, 0, 'Completions found, but none were expected');\n    });\n  });\n});\n"
  },
  {
    "path": "test/configuration/configuration.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\nimport * as srcConfiguration from '../../src/configuration/configuration';\nimport { IConfiguration } from '../../src/configuration/iconfiguration';\nimport { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nconst testConfig: Partial<IConfiguration> = {\n  leader: '<space>',\n  normalModeKeyBindingsNonRecursive: [\n    {\n      before: ['leader', 'o'],\n      after: ['o', 'eSc', 'k'],\n    },\n    {\n      before: ['<leader>', 'f', 'e', 's'],\n      after: ['v'],\n    },\n  ],\n  whichwrap: 'h,l',\n};\n\nsuite('Configuration', () => {\n  setup(async () => {\n    await setupWorkspace({ config: testConfig });\n  });\n\n  test('remappings are normalized', async () => {\n    const normalizedKeybinds = srcConfiguration.configuration.normalModeKeyBindingsNonRecursive;\n    const normalizedKeybindsMap = srcConfiguration.configuration.normalModeKeyBindingsMap;\n    const testingKeybinds = testConfig.normalModeKeyBindingsNonRecursive;\n\n    assert.strictEqual(normalizedKeybinds.length, testingKeybinds!.length);\n    assert.strictEqual(normalizedKeybinds.length, normalizedKeybindsMap.size);\n    assert.deepStrictEqual(normalizedKeybinds[0].before, [' ', 'o']);\n    assert.deepStrictEqual(normalizedKeybinds[0].after, ['o', '<Esc>', 'k']);\n  });\n\n  test('textwidth is configurable per-language', async () => {\n    const globalVimConfig = vscode.workspace.getConfiguration('vim');\n    const jsVimConfig = vscode.workspace.getConfiguration('vim', { languageId: 'javascript' });\n\n    try {\n      assert.strictEqual(jsVimConfig.get('textwidth'), 80);\n      await jsVimConfig.update('textwidth', 120, vscode.ConfigurationTarget.Global, true);\n\n      const updatedGlobalVimConfig = vscode.workspace.getConfiguration('vim');\n      assert.strictEqual(updatedGlobalVimConfig.get('textwidth'), 80);\n\n      const updatedJsVimConfig = vscode.workspace.getConfiguration('vim', {\n        languageId: 'javascript',\n      });\n      assert.strictEqual(updatedJsVimConfig.get('textwidth'), 120);\n    } finally {\n      await globalVimConfig.update('textwidth', undefined, vscode.ConfigurationTarget.Global);\n      await jsVimConfig.update('textwidth', undefined, vscode.ConfigurationTarget.Global);\n    }\n  });\n\n  newTest({\n    title: 'Can handle long key chords',\n    start: ['|'],\n    // <leader>fes\n    keysPressed: ' fes',\n    end: ['|'],\n    endMode: Mode.Visual,\n  });\n});\n"
  },
  {
    "path": "test/configuration/langmap.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nconst dvorakLangmap =\n  '\\'q,\\\\,w,.e,pr,yt,fy,gu,ci,ro,lp,/[,=],aa,os,ed,uf,ig,dh,hj,tk,nl,s\\\\;,-\\',\\\\;z,qx,jc,kv,xb,bn,mm,w\\\\,,v.,z/,[-,]=,\"Q,<W,>E,PR,YT,FY,GU,CI,RO,LP,?{,+},AA,OS,ED,UF,IG,DH,HJ,TK,NL,S:,_\",:Z,QX,JC,KV,XB,BN,MM,W<,V>,Z?';\n\nsuite('Langmap', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        langmap: dvorakLangmap,\n      },\n    });\n  });\n\n  newTest({\n    title: 'Test example binding (ee → dd)',\n    start: ['lorem ispum', 'dolor |sit amet', 'consectetur adipiscing elit'],\n    keysPressed: 'ee',\n    end: ['lorem ispum', '|consectetur adipiscing elit'],\n  });\n\n  newTest({\n    title: \"Remapped keys shouldn't behave like their original mappings. (dd → hh)\",\n    start: ['lorem ispum', 'dolor |sit amet', 'consectetur adipiscing elit'],\n    keysPressed: 'dd',\n    end: ['lorem ispum', 'dolo|r sit amet', 'consectetur adipiscing elit'],\n  });\n\n  newTest({\n    title: \"Test macros ('aee'@a → qaeeq@a)\",\n    start: ['|a', 'b', 'c'],\n    keysPressed: \"'aee'@a\",\n    end: ['|c'],\n  });\n\n  newTest({\n    title: \"Test macro register mapping (''ee'@' → qqeeq@q)\",\n    start: ['|a', 'b', 'c'],\n    keysPressed: \"''ee'@'\",\n    end: ['|c'],\n  });\n\n  newTest({\n    title: \"Test no double macro register mapping (',ee'@, → qweeq@w)\",\n    start: ['|a', 'b', 'c'],\n    keysPressed: \"',ee'@,\",\n    end: ['|c'],\n  });\n\n  newTest({\n    title: \"Test marks (mah-a, → maj'a)\",\n    start: ['|a', 'b'],\n    keysPressed: 'mah-a',\n    end: ['|a', 'b'],\n  });\n\n  newTest({\n    title: \"Test mark register remapping (m'h-' → mqj'q)\",\n    start: ['|a', 'b'],\n    keysPressed: \"m'h-'\",\n    end: ['|a', 'b'],\n  });\n\n  newTest({\n    title: \"Test no double mark register remapping (m,h-, → mwj'w)\",\n    start: ['|a', 'b'],\n    keysPressed: 'm,h-,',\n    end: ['|a', 'b'],\n  });\n\n  newTest({\n    title: 'Test <character> search (uu → fu)',\n    start: ['|Hello, how are you?'],\n    keysPressed: 'uu',\n    end: ['Hello, how are yo|u?'],\n  });\n\n  newTest({\n    title: 'Test <character> replacement (pp → rp)',\n    start: ['|r'],\n    keysPressed: 'pp',\n    end: ['|p'],\n  });\n\n  newTest({\n    title: 'Test no ctrl remapping (<C-v> → <C-v>)',\n    start: ['one', 't|wo', 'three'],\n    keysPressed: '<C-v>' + 'h' + 'e',\n    end: ['one', 't|o', 'tree'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Test no insert mode remapping (cc → ic)',\n    start: ['|'],\n    keysPressed: 'c' + 'c',\n    end: ['c|'],\n    endMode: Mode.Insert,\n  });\n});\n"
  },
  {
    "path": "test/configuration/notation.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { Notation } from '../../src/configuration/notation';\n\nsuite('Notation', () => {\n  test('Normalize', () => {\n    const leaderKey = '//';\n    const testCases: { [key: string]: string } = {\n      '<cTrL+w>': '<C-w>',\n      'cTrL+x': '<C-x>',\n      'CtRl+y': '<C-y>',\n      'c-z': '<C-z>',\n      '<CmD+a>': '<D-a>',\n      eScapE: '<Esc>',\n      hOme: '<Home>',\n      inSert: '<Insert>',\n      eNd: '<End>',\n      '<LeAder>': '//',\n      LEaDer: '//',\n      '<cR>': '\\n',\n      '<EnTeR>': '\\n',\n      '<space>': ' ',\n      '<uP>': '<up>',\n      '<Shift+Tab>': '<S-tab>',\n      '<S-j>': 'J',\n      '<S-J>': 'J',\n      '<s-j>': 'J',\n      '<s-J>': 'J',\n    };\n\n    for (const test in testCases) {\n      if (testCases.hasOwnProperty(test)) {\n        const expected = testCases[test];\n\n        const actual = Notation.NormalizeKey(test, leaderKey);\n        assert.strictEqual(actual, expected);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "test/configuration/remapper.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { IKeyRemapping } from '../../src/configuration/iconfiguration';\nimport { Remapper, Remappers } from '../../src/configuration/remapper';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { IRegisterContent, Register } from '../../src/register/register';\nimport { VimState } from '../../src/state/vimState';\nimport { StatusBar } from '../../src/statusBar';\nimport { Configuration } from '../testConfiguration';\nimport { assertEqualLines, setupWorkspace } from '../testUtils';\n\nsuite('Remapper', () => {\n  let modeHandler: ModeHandler;\n  let vimState: VimState;\n  const leaderKey = '\\\\';\n  const defaultInsertModeKeyBindings: IKeyRemapping[] = [\n    {\n      before: ['j', 'j'],\n      after: ['<Esc>'],\n    },\n    {\n      before: ['<c-e>'],\n      after: ['<Esc>'],\n    },\n  ];\n  const defaultNormalModeKeyBindings: IKeyRemapping[] = [\n    {\n      before: ['leader', 'w'],\n      commands: [\n        {\n          command: 'workbench.action.closeActiveEditor',\n          args: [],\n        },\n      ],\n    },\n    {\n      before: ['0'],\n      commands: [\n        {\n          command: ':wq',\n          args: [],\n        },\n      ],\n    },\n    {\n      before: ['y', 'y'],\n      after: ['y', 'l'],\n    },\n    {\n      before: ['e'],\n      after: ['$'],\n    },\n  ];\n  const defaultNormalModeKeyBindingsNonRecursive: IKeyRemapping[] = [\n    {\n      before: ['d'],\n      after: ['\"', '_', 'd'],\n    },\n  ];\n  const defaultVisualModeKeyBindings: IKeyRemapping[] = [\n    {\n      before: ['leader', 'c'],\n      commands: [\n        {\n          command: 'workbench.action.closeActiveEditor',\n          args: [],\n        },\n      ],\n    },\n  ];\n\n  class TestRemapper extends Remapper {\n    constructor() {\n      super('configKey', [Mode.Insert]);\n    }\n\n    public override findMatchingRemap(\n      userDefinedRemappings: Map<string, IKeyRemapping>,\n      inputtedKeys: string[],\n    ) {\n      return super.findMatchingRemap(userDefinedRemappings, inputtedKeys);\n    }\n\n    public getRemappedKeySequenceLengthRange(\n      remappings: Map<string, IKeyRemapping>,\n    ): [number, number] {\n      return TestRemapper.getRemappedKeysLengthRange(remappings);\n    }\n  }\n\n  const setupWithBindings = async ({\n    insertModeKeyBindings,\n    normalModeKeyBindings,\n    normalModeKeyBindingsNonRecursive,\n    visualModeKeyBindings,\n  }: {\n    insertModeKeyBindings?: IKeyRemapping[];\n    normalModeKeyBindings?: IKeyRemapping[];\n    normalModeKeyBindingsNonRecursive?: IKeyRemapping[];\n    visualModeKeyBindings?: IKeyRemapping[];\n  }) => {\n    await setupWorkspace({\n      config: {\n        leader: leaderKey,\n        insertModeKeyBindings: insertModeKeyBindings || [],\n        normalModeKeyBindings: normalModeKeyBindings || [],\n        normalModeKeyBindingsNonRecursive: normalModeKeyBindingsNonRecursive || [],\n        visualModeKeyBindings: visualModeKeyBindings || [],\n      },\n    });\n    modeHandler = (await getAndUpdateModeHandler())!;\n    vimState = modeHandler.vimState;\n  };\n\n  test('getLongestedRemappedKeySequence', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remappings: Map<string, IKeyRemapping> = new Map([\n      ['abc', { before: ['a', 'b', 'c'] }],\n      ['de', { before: ['d', 'e'] }],\n      ['f', { before: ['f'] }],\n    ]);\n\n    // act\n    const testRemapper = new TestRemapper();\n    const actual = testRemapper.getRemappedKeySequenceLengthRange(remappings);\n\n    // assert\n    assert.strictEqual(actual[0], 1);\n    assert.strictEqual(actual[1], 3);\n  });\n\n  test('getMatchingRemap', async () => {\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const testCases = [\n      {\n        // able to match number in normal mode\n        before: '0',\n        after: ':wq',\n        input: '0',\n        mode: Mode.Normal,\n        expectedAfter: ':wq',\n      },\n      {\n        // able to match characters in normal mode\n        before: 'abc',\n        after: ':wq',\n        input: 'abc',\n        mode: Mode.Normal,\n        expectedAfter: ':wq',\n      },\n      {\n        // able to match with preceding count in normal mode\n        before: 'abc',\n        after: ':wq',\n        input: '0abc',\n        mode: Mode.Normal,\n        expectedAfter: ':wq',\n      },\n      {\n        // must match exactly in normal mode\n        before: 'abc',\n        after: ':wq',\n        input: 'defabc',\n        mode: Mode.Normal,\n      },\n      {\n        // able to match in insert mode\n        before: 'jj',\n        after: '<Esc>',\n        input: 'jj',\n        mode: Mode.Insert,\n        expectedAfter: '<Esc>',\n        expectedAfterMode: Mode.Normal,\n      },\n    ];\n\n    for (const testCase of testCases) {\n      // setup\n      const remappings: Map<string, IKeyRemapping> = new Map();\n      remappings.set(testCase.before, {\n        before: testCase.before.split(''),\n        after: testCase.after.split(''),\n      });\n\n      // act\n      const testRemapper = new TestRemapper();\n      const actual = testRemapper.findMatchingRemap(remappings, testCase.input.split(''));\n\n      // assert\n      if (testCase.expectedAfter) {\n        assert(\n          actual,\n          `Expected remap for before=${testCase.before}. input=${testCase.input}. mode=${\n            Mode[testCase.mode]\n          }.`,\n        );\n        assert.deepStrictEqual(actual.after, testCase.expectedAfter.split(''));\n      } else {\n        assert.strictEqual(actual, undefined);\n      }\n\n      if (testCase.expectedAfterMode) {\n        assert.strictEqual(modeHandler.vimState.currentMode, testCase.expectedAfterMode);\n        assert.strictEqual(modeHandler.vimState.currentMode, testCase.expectedAfterMode);\n      }\n    }\n  });\n\n  test('jj -> <Esc> through modehandler', async () => {\n    const expectedDocumentContent = 'lorem ipsum';\n\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remapper = new Remappers();\n\n    const edit = new vscode.WorkspaceEdit();\n    edit.insert(\n      vscode.window.activeTextEditor!.document.uri,\n      new vscode.Position(0, 0),\n      expectedDocumentContent,\n    );\n    await vscode.workspace.applyEdit(edit);\n\n    await modeHandler.handleKeyEvent('i');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n\n    // act\n    let actual = false;\n    try {\n      actual = await remapper.sendKey(['j', 'j'], modeHandler);\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(e);\n    }\n\n    // assert\n    assert.strictEqual(actual, true);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    assert.strictEqual(vscode.window.activeTextEditor!.document.getText(), expectedDocumentContent);\n  });\n\n  test('0 -> :wq through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remapper = new Remappers();\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    let actual = false;\n    try {\n      actual = await remapper.sendKey(['0'], modeHandler);\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(e);\n    }\n\n    // assert\n    assert.strictEqual(actual, true);\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 0);\n  });\n\n  test('<c-e> -> <esc> in insert mode should go to normal mode', async () => {\n    const expectedDocumentContent = 'lorem ipsum';\n\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remapper = new Remappers();\n\n    const edit = new vscode.WorkspaceEdit();\n    edit.insert(\n      vscode.window.activeTextEditor!.document.uri,\n      new vscode.Position(0, 0),\n      expectedDocumentContent,\n    );\n    await vscode.workspace.applyEdit(edit);\n\n    await modeHandler.handleKeyEvent('i');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n\n    // act\n    let actual = false;\n    try {\n      actual = await remapper.sendKey(['<C-e>'], modeHandler);\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(e);\n    }\n\n    // assert\n    assert.strictEqual(actual, true);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    assert.strictEqual(vscode.window.activeTextEditor!.document.getText(), expectedDocumentContent);\n  });\n\n  test('leader, w -> closeActiveEditor in normal mode through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remapper = new Remappers();\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    let actual = false;\n    try {\n      actual = await remapper.sendKey([leaderKey, 'w'], modeHandler);\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(e);\n    }\n\n    // assert\n    assert.strictEqual(actual, true);\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 0);\n  });\n\n  test('leader, c -> closeActiveEditor in visual mode through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    const remapper = new Remappers();\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    await modeHandler.handleKeyEvent('v');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n    // act\n    let actual = false;\n    try {\n      actual = await remapper.sendKey([leaderKey, 'c'], modeHandler);\n    } catch (e) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(e);\n    }\n\n    // assert\n    assert.strictEqual(actual, true);\n    assert.strictEqual(vscode.window.visibleTextEditors.length, 0);\n  });\n\n  test('d -> black hole register delete in normal mode through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'line1', '<Esc>', '0']);\n\n    const expected = 'text-to-put-on-register';\n    let actual: IRegisterContent | undefined;\n    Register.put(modeHandler.vimState, expected);\n    actual = await Register.get(vimState.recordedState.registerName);\n    assert.strictEqual(actual?.text, expected);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['d', 'd']);\n\n    // assert\n    actual = await Register.get(vimState.recordedState.registerName);\n    assert.strictEqual(actual?.text, expected);\n  });\n\n  test('d -> black hole register delete in normal mode through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: defaultInsertModeKeyBindings,\n      normalModeKeyBindings: defaultNormalModeKeyBindings,\n      normalModeKeyBindingsNonRecursive: defaultNormalModeKeyBindingsNonRecursive,\n      visualModeKeyBindings: defaultVisualModeKeyBindings,\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'word1 word2', '<Esc>', '0']);\n\n    const expected = 'text-to-put-on-register';\n    let actual: IRegisterContent | undefined;\n    Register.put(modeHandler.vimState, expected);\n    actual = await Register.get(vimState.recordedState.registerName);\n    assert.strictEqual(actual?.text, expected);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['d', 'w']);\n\n    // assert\n    actual = await Register.get(vimState.recordedState.registerName);\n    assert.strictEqual(actual?.text, expected);\n  });\n\n  test('jj -> <Esc> after ciw operator through modehandler', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: [\n        {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n        },\n      ],\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'word1 word2', '<Esc>', '0']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['c', 'i', 'w']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    await modeHandler.handleMultipleKeyEvents(['j', 'j']);\n\n    // assert\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('jj -> <Esc> after using <Count>i=jj should insert ===', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: [\n        {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n        },\n      ],\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'word1 word2', '<Esc>', 'b']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['3', 'i', '=']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    await modeHandler.handleMultipleKeyEvents(['j', 'j']);\n\n    // assert\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    assertEqualLines(['word1 ===word2']);\n  });\n\n  test('jj -> <Esc> does not leave behind character a j', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: [\n        {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n        },\n      ],\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'foo', '<Esc>']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['a', 'bar']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    await modeHandler.handleMultipleKeyEvents(['j', 'j']);\n    assertEqualLines(['foobar']);\n\n    // assert\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('jj -> <Esc> does not modify undo stack', async () => {\n    // setup\n    await setupWithBindings({\n      insertModeKeyBindings: [\n        {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n        },\n      ],\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'foo', '<Esc>']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['a', 'bar']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    await modeHandler.handleMultipleKeyEvents(['j', 'j']);\n    assertEqualLines(['foobar']);\n\n    // assert\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    await modeHandler.handleMultipleKeyEvents(['u']);\n    assertEqualLines(['foo']);\n  });\n\n  test('Recursive remap throws E223', async () => {\n    // setup\n    await setupWithBindings({\n      normalModeKeyBindings: [\n        {\n          before: ['x'],\n          after: ['y'],\n        },\n        {\n          before: ['y'],\n          after: ['x'],\n        },\n      ],\n    });\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'foo', '<Esc>']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n    // act\n    await modeHandler.handleMultipleKeyEvents(['x']);\n\n    // assert\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    assert.strictEqual(StatusBar.getText(), 'E223: Recursive mapping');\n    assertEqualLines(['foo']);\n  });\n\n  test('ambiguous and potential remaps and timeouts', async () => {\n    // setup\n    await setupWithBindings({\n      normalModeKeyBindings: [\n        {\n          before: ['w', 'w'],\n          after: ['d', 'w'],\n        },\n        {\n          before: ['w', 'w', 'w', 'w'],\n          after: ['d', 'd'],\n        },\n        {\n          before: ['b', 'b'],\n          after: ['d', 'd'],\n        },\n      ],\n    });\n\n    // Using the default timeout\n    const timeout = new Configuration().timeout;\n    // Offset because the timeout might not finish exactly on time.\n    const timeoutOffset = 250;\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n    await modeHandler.handleMultipleKeyEvents(['i', 'foo', ' ', 'bar', ' ', 'biz', '<Esc>', '0']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    assertEqualLines(['foo bar biz']);\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.character,\n      0,\n      'Cursor is not on the right position, should be at the start of line',\n    );\n\n    // act and assert\n\n    // check that 'ww' -> 'dw' waits for timeout to finish and timeout isn't run twice\n    const result1: string[] = await new Promise(async (r1Resolve, r1Reject) => {\n      const p1: Promise<string> = new Promise((p1Resolve, p1Reject) => {\n        setTimeout(() => {\n          // get line after half timeout finishes\n          const currentLine = modeHandler.vimState.document.lineAt(0).text;\n          p1Resolve(currentLine);\n        }, timeout / 2);\n      });\n      const p2: Promise<string> = new Promise((p2Resolve, p2Reject) => {\n        setTimeout(() => {\n          // get line after timeout + offset finishes\n          const currentLine = modeHandler.vimState.document.lineAt(0).text;\n          p2Resolve(currentLine);\n        }, timeout + timeoutOffset);\n      });\n      const p3: Promise<string> = new Promise(async (p3Resolve, p3Reject) => {\n        await modeHandler.handleMultipleKeyEvents(['w', 'w']);\n        p3Resolve('modeHandler.handleMultipleKeyEvents finished');\n      });\n      await Promise.all([p1, p2, p3]).then((results) => {\n        r1Resolve(results);\n      });\n    });\n\n    // Before the timeout finishes it shouldn't have changed anything yet,\n    // because it is still waiting for a key or timeout to finish.\n    assert.strictEqual(result1[0], 'foo bar biz');\n\n    // After the timeout finishes (plus an offset to be sure it finished)\n    // it should have handled the remapping, if it wrongly ran the timeout\n    // twice this should fail too.\n    assert.strictEqual(result1[1], 'bar biz');\n\n    // check that 'www' -> 'dw' and then 'w' waits for timeout to finish\n    const result2: Array<{ line: string; position: number }> = await new Promise(\n      async (r2Resolve, r2Reject) => {\n        const p1: Promise<{ line: string; position: number }> = new Promise(\n          (p1Resolve, p1Reject) => {\n            setTimeout(() => {\n              // get line and cursor character after half timeout finishes\n              const currentLine = modeHandler.vimState.document.lineAt(0).text;\n              const cursorCharacter = modeHandler.vimState.cursorStopPosition.character;\n              p1Resolve({ line: currentLine, position: cursorCharacter });\n            }, timeout / 2);\n          },\n        );\n        const p2: Promise<{ line: string; position: number }> = new Promise(\n          (p2Resolve, p2Reject) => {\n            setTimeout(() => {\n              // get line and cursor character after timeout + offset finishes\n              const currentLine = modeHandler.vimState.document.lineAt(0).text;\n              const cursorCharacter = modeHandler.vimState.cursorStopPosition.character;\n              p2Resolve({ line: currentLine, position: cursorCharacter });\n            }, timeout + timeoutOffset);\n          },\n        );\n        const p3: Promise<{ line: string; position: number }> = new Promise(\n          async (p3Resolve, p3Reject) => {\n            await modeHandler.handleMultipleKeyEvents(['w', 'w', 'w']);\n            p3Resolve({ line: 'modeHandler.handleMultipleKeyEvents finished', position: -1 });\n          },\n        );\n        await Promise.all([p1, p2, p3]).then((results) => {\n          r2Resolve(results);\n        });\n      },\n    );\n\n    // Before the timeout finishes it shouldn't have changed anything yet,\n    // because it is still waiting for a key or timeout to finish.\n    assert.strictEqual(result2[0].line, 'bar biz');\n    assert.strictEqual(\n      result2[0].position,\n      0,\n      'Cursor is not on the right position, should be at the start of line',\n    );\n\n    // After the timeout finishes (plus an offset to be sure it finished)\n    // it should have handled the remapping, if it wrongly ran the timeout\n    // twice this should fail too.\n    assert.strictEqual(result2[1].line, 'biz');\n    assert.strictEqual(\n      result2[1].position,\n      2,\n      'Cursor is not on the right position, should be at the end of line',\n    );\n\n    // add new line\n    await modeHandler.handleMultipleKeyEvents(['a', '\\n', 'foo', '<Esc>']);\n    assertEqualLines(['biz', 'foo']);\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.character,\n      2,\n      'Cursor is not on the right position, should be at the end of line',\n    );\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.line,\n      1,\n      'Cursor is not on the right position, should be on second line',\n    );\n\n    // check that 'wwww' -> 'dd' doesn't wait for timeout\n    const result3 = await new Promise(async (r3Resolve, r3Reject) => {\n      const start = Number(new Date());\n\n      await modeHandler.handleMultipleKeyEvents(['w', 'w', 'w', 'w']).then(() => {\n        const now = Number(new Date());\n        const elapsed = now - start;\n\n        assertEqualLines(['biz']);\n        assert.strictEqual(\n          modeHandler.vimState.cursorStopPosition.character,\n          0,\n          'Cursor is not on the right position, shoul be at the start of line',\n        );\n        assert.strictEqual(\n          modeHandler.vimState.cursorStopPosition.line,\n          0,\n          'Cursor is not on the right position, should be on first line',\n        );\n\n        // We check if the elapsed time is less than half the timeout instead of\n        // just a few miliseconds to prevent any performance issue marking the\n        // test as failed when it would've succeeded. If it is less than half the\n        // timeout we can be sure the setTimeout was never ran.\n        assert.strictEqual(elapsed < timeout / 2, true);\n        r3Resolve(\"wwww -> dd doesn't wait for timeout to finish\");\n      });\n    });\n\n    assert.strictEqual(result3, \"wwww -> dd doesn't wait for timeout to finish\");\n\n    // add new line again\n    await modeHandler.handleMultipleKeyEvents(['$', 'a', '\\n', 'foo', '<Esc>']);\n    assertEqualLines(['biz', 'foo']);\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.character,\n      2,\n      'Cursor is not on the right position, should be at the end of line',\n    );\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.line,\n      1,\n      'Cursor is not on the right position, should be on second line',\n    );\n\n    // check 'bb' -> 'dd' sending each 'b' one by one checking between them to see\n    // that the remapping hasn't been handled yet and that the whole process\n    // doesn't take the timeout to finish\n\n    const startTime = Number(new Date());\n\n    // send first 'b'\n    await modeHandler.handleKeyEvent('b');\n\n    // should be the same\n    assertEqualLines(['biz', 'foo']);\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.character,\n      2,\n      'Cursor is not on the right position, should be at the end of line',\n    );\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.line,\n      1,\n      'Cursor is not on the right position, should be on second line',\n    );\n\n    // wait for 500 miliseconds (half of timeout) to simulate the time the user takes\n    // between presses. Not using a fixed value here in case the default configuration\n    // gets changed to use a lower value for timeout.\n    const waited: boolean = await new Promise((wResolve, wReject) => {\n      setTimeout(() => {\n        wResolve(true);\n      }, timeout / 2);\n    });\n    assert.strictEqual(waited, true);\n\n    // send second 'b'\n    await modeHandler.handleKeyEvent('b');\n\n    // check result and time elapsed\n    const elapsedTime = Number(new Date()) - startTime;\n\n    assertEqualLines(['biz']);\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.character,\n      0,\n      'Cursor is not on the right position, shoul be at the start of line',\n    );\n    assert.strictEqual(\n      modeHandler.vimState.cursorStopPosition.line,\n      0,\n      'Cursor is not on the right position, should be on first line',\n    );\n\n    // We check if the elapsedTime is less than the timeout minus an offset just\n    // to be sure that some performance issue doesn't make the test fail when it\n    // would succeed but this might not be foolproof, since we are dealing with\n    // times here.\n    //\n    // Note: I didn't want to use a Promise here again like previously, because I\n    // wanted to have both methods of testing (with and without promises) and this\n    // method should simulate better the real use from the user.\n    assert.strictEqual(elapsedTime < timeout - timeoutOffset, true);\n  });\n});\n"
  },
  {
    "path": "test/configuration/remaps.test.ts",
    "content": "import { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTestWithRemaps, newTestWithRemapsSkip } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('Remaps', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    // The timeout shouldn't be lower then 200ms when testing because it might result in some tests\n    // failing when they shouldn't. I ran this test successfully with this timeout as low as 50ms, but\n    // lower than that I start getting some issues. I still set this a little bit higher because it\n    // might change from machine to machine.\n    // Edit: Let's try 100ms because we're rebellious and have places to be\n    await setupWorkspace({\n      config: {\n        timeout: 100,\n        leader: ' ',\n      },\n    });\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  newTestWithRemaps({\n    title: 'Can handle ambiguous remaps on different recursiveness mappings',\n    start: ['|one two three'],\n    remaps: {\n      normalModeKeyBindings: [\n        {\n          before: ['<leader>', 'w'],\n          after: ['w'],\n        },\n        {\n          before: ['w', 'w'],\n          after: ['b'],\n        },\n      ],\n      normalModeKeyBindingsNonRecursive: [\n        {\n          before: ['w'],\n          after: ['w', 'w'],\n        },\n      ],\n    },\n    steps: [\n      {\n        // Step 0: Press keys '<space>w' that remap to 'w' but since 'w' is an ambiguous remap it waits\n        // for timeout or another key to come. In step result with 'end' we assert the result right after\n        // the keys are handled. When we use the 'endAfterTimeout' we are telling the test to wait for\n        // timeout to end and then assert the result.\n        keysPressed: ' w',\n        stepResult: {\n          end: ['|one two three'],\n          endAfterTimeout: ['one two |three'],\n        },\n      },\n      {\n        // Step 1: Again we press the keys '<space>w' that remaps to 'w' and waits for timeout and we assert\n        // that nothing has changed since the last result after the keys are handled.\n        keysPressed: ' w',\n        stepResult: {\n          end: ['one two |three'],\n        },\n      },\n      {\n        // Step 2: Since we didn't wait for timeout on the previous step it is still waiting for timeout to\n        // finish or another key to come so when we now press the key 'w' it gets 'ww' and remaps it to 'b'.\n        keysPressed: 'w',\n        stepResult: {\n          end: ['one |two three'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Potential and ambiguous remaps on different recursiveness mappings with different sizes are handled by the correct Remapper',\n    start: ['|one two three'],\n    remaps: {\n      normalModeKeyBindings: [\n        {\n          before: ['<leader>', 'b', 'b'],\n          after: ['w'],\n        },\n        {\n          before: ['<leader>', 'w'],\n          after: ['$'],\n        },\n        {\n          before: ['<leader>', 'e', 'e'],\n          after: ['i', 'e', 'e', '<Esc>'],\n        },\n        {\n          before: ['<leader>', 'l', 'l', 'l'],\n          after: ['i', 'l', 'l', 'l', '<Esc>'],\n        },\n      ],\n      normalModeKeyBindingsNonRecursive: [\n        {\n          before: ['<leader>', 'b'],\n          after: ['0'],\n        },\n        {\n          before: ['<leader>', 'w', 'w'],\n          after: ['b'],\n        },\n        {\n          before: ['<leader>', 'e', 'e', 'e'],\n          after: ['i', 'e', 'e', 'e', '<Esc>'],\n        },\n        {\n          before: ['<leader>', 'l', 'l'],\n          after: ['i', 'l', 'l', '<Esc>'],\n        },\n      ],\n    },\n    steps: [\n      {\n        // Step 0: Press keys '<space>w' that remap to '$' but since there is the '<space>ww' potential\n        // remap it waits for timeout or another key to come. The recursive remap needs to know that it\n        // has a potential remap and needs to wait but after timeout finishes it also needs to know that\n        // there is a NonRecursive remap with a potential remap that needs to be checked and vice-versa.\n        // In step result with 'end' we assert the result right after the keys are handled. When we use\n        // the 'endAfterTimeout' we are telling the test to wait for timeout to end and then assert the\n        // result.\n        keysPressed: ' w',\n        stepResult: {\n          end: ['|one two three'],\n          endAfterTimeout: ['one two thre|e'],\n        },\n      },\n      {\n        // Step 1: Again we press the keys '<space>w' and we assert that nothing has changed since the\n        // last result after the keys are handled.\n        keysPressed: ' w',\n        stepResult: {\n          end: ['one two thre|e'],\n        },\n      },\n      {\n        // Step 2: Since we didn't wait for timeout on the previous step it is still waiting for timeout to\n        // finish or another key to come so when we now press the key 'w' it gets '<space>ww' and remaps it\n        // to 'b'.\n        keysPressed: 'w',\n        stepResult: {\n          end: ['one two |three'],\n        },\n      },\n      {\n        // Step 3: Press keys '<space>b' that remap to '0' but since there is the '<space>bb' potential\n        // remap it waits for timeout or another key to come. Same thing as step 0 applies but in different\n        // recursiveness.\n        keysPressed: ' b',\n        stepResult: {\n          end: ['one two |three'],\n          endAfterTimeout: ['|one two three'],\n        },\n      },\n      {\n        // Step 4: Again we press the keys '<space>b' and we assert that nothing has changed since the\n        // last result after the keys are handled.\n        keysPressed: ' b',\n        stepResult: {\n          end: ['|one two three'],\n        },\n      },\n      {\n        // Step 5: Since we didn't wait for timeout on the previous step it is still waiting for timeout to\n        // finish or another key to come so when we now press the key 'b' it gets '<space>bb' and remaps it\n        // to 'w'.\n        keysPressed: 'b',\n        stepResult: {\n          end: ['one |two three'],\n        },\n      },\n      {\n        // Step 6: '<space>ee' should be handled by the RecursiveRemapper after timeout\n        keysPressed: ' ee',\n        stepResult: {\n          end: ['one |two three'],\n          endAfterTimeout: ['one e|etwo three'],\n        },\n      },\n      {\n        // Step 7: '<space>eee' should be handled by the NonRecursiveRemapper without timeout\n        keysPressed: ' eee',\n        stepResult: {\n          end: ['one eee|eetwo three'],\n        },\n      },\n      {\n        // Step 8: '<space>ll' should be handled by the NonRecursiveRemapper after timeout\n        keysPressed: ' ll',\n        stepResult: {\n          end: ['one eee|eetwo three'],\n          endAfterTimeout: ['one eeel|leetwo three'],\n        },\n      },\n      {\n        // Step 9: '<space>lll' should be handled by the RecursiveRemapper without timeout\n        keysPressed: ' lll',\n        stepResult: {\n          end: ['one eeelll|lleetwo three'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'A multiple key sequence with potential remaps on both recursiveness but without a \\\n    remapping, handles all its keys after timeout',\n    start: ['|one two three'],\n    remaps: {\n      normalModeKeyBindings: [\n        {\n          before: ['x', 'x', 'x', 'x'],\n          after: ['i', '4', 'x', '<Esc>'],\n        },\n        {\n          before: ['l', 'l', 'l', 'l', 'l'],\n          after: ['i', '5', 'l', '<Esc>'],\n        },\n      ],\n      normalModeKeyBindingsNonRecursive: [\n        {\n          before: ['x', 'x', 'x', 'x', 'x'],\n          after: ['i', '5', 'x', '<Esc>'],\n        },\n        {\n          before: ['l', 'l', 'l', 'l'],\n          after: ['i', '4', 'l', '<Esc>'],\n        },\n      ],\n    },\n    steps: [\n      {\n        // Step 0: 'xxx' has no remapping and not actions so it does nothing but should still\n        // wait for timeout because of the potential remaps.\n        title: '\"xxx\" has no remapping but still needs to wait for timeout to finish.',\n        keysPressed: 'xxx',\n        stepResult: {\n          end: ['|one two three'],\n          endAfterTimeout: ['| two three'],\n        },\n      },\n      {\n        // Step 1: 'lll' has no remapping and not actions so it does nothing but should still\n        // wait for timeout because of the potential remaps.\n        title: '\"lll\" has no remapping but still needs to wait for timeout to finish.',\n        keysPressed: 'lll',\n        stepResult: {\n          end: ['| two three'],\n          endAfterTimeout: [' tw|o three'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Can handle vimrc mappings',\n    start: ['one |two three'],\n    remaps: ['nmap <leader>w w', 'nmap <leader>a <leader>w', 'nmap <leader>ww b'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: ' a',\n        stepResult: {\n          end: ['one |two three'],\n          endAfterTimeout: ['one two |three'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: \"Recursive mappings where the after starts with before don't loop\",\n    remaps: ['nmap ab abcd'],\n    start: ['|'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: 'ab',\n        stepResult: {\n          end: ['bcd|'],\n          endMode: Mode.Insert,\n        },\n      },\n    ],\n  });\n\n  // TODO: skipped (flaky)\n  newTestWithRemapsSkip({\n    title:\n      'Ambiguous Mappings with a long remapping still succeed after timeout or when a key is pressed to break ambiguity',\n    remaps: [\n      'nmap ab abcdefghijklmnopqrstuvwxyz',\n      'nmap abc aAmbiguous<Esc>',\n      'imap jj <Esc>',\n      'imap jn AnotherLongAmbiguousRemap<Esc>',\n      'imap jnbn IExistJustToCreateAmbiguityWithRemainingKeys<Esc>',\n    ],\n    start: ['|'],\n    steps: [\n      {\n        // Step 0:\n        title: 'Before timeout should be equal to start and waits for timeout before remapping',\n        keysPressed: 'ab',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['bcdefghijklmnopqrstuvwxyz|'],\n          endModeAfterTimeout: Mode.Insert,\n        },\n      },\n      {\n        // Step 1:\n        title:\n          'Key given after the ambiguous remap that breaks ambiguity makes it run straight away',\n        keysPressed: '<Esc>ddab<Esc>',\n        stepResult: {\n          end: ['bcdefghijklmnopqrstuvwxy|z'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title:\n          'Key given after the ambiguous remap that breaks ambiguity makes it run straight away even if that key is itself a remap',\n        keysPressed: 'jjddabjj',\n        stepResult: {\n          end: ['bcdefghijklmnopqrstuvwxy|z'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 3:\n        title:\n          'Key given after the ambiguous remap that breaks ambiguity makes it run straight away even if that key is itself another potential remap that waits for timeout',\n        keysPressed: 'jjddabj',\n        stepResult: {\n          end: ['bcdefghijklmnopqrstuvwxyz|'],\n          endMode: Mode.Insert,\n          endAfterTimeout: ['bcdefghijklmnopqrstuvwxyzj|'],\n          endModeAfterTimeout: Mode.Insert,\n        },\n      },\n      {\n        // Step 4:\n        title:\n          'Key given after the ambiguous remap that breaks ambiguity makes it run straight away even if that key is itself another long ambiguous remap that waits for timeout and has remaining keys to be handled',\n        keysPressed: 'jjddabjnb',\n        stepResult: {\n          end: ['bcdefghijklmnopqrstuvwxyz|'],\n          endMode: Mode.Insert,\n          endAfterTimeout: ['|bcdefghijklmnopqrstuvwxyzAnotherLongAmbiguousRemap'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Remaps that call themselves run until an error',\n    remaps: ['nmap <leader>$ 0f£xA$00<Esc>', 'nmap <leader>$G <leader>$j<leader>$G'],\n    start: ['|10£', '15£', '350£', '2£', '5£'],\n    steps: [\n      {\n        // Step 0:\n        title:\n          'Calls itself until it errors (if this test fails with wrong cursor on last character instead of first it might mean that someone made the \"j\" fail when on last line YAY! :) if so please update this step to change the expected cursor to be on last character of last line)',\n        keysPressed: ' $G',\n        stepResult: {\n          end: ['10$00', '15$00', '350$00', '2$00', '|5$00'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title: 'Prepare for next step',\n        keysPressed: 'uG5o<Esc>gg',\n        stepResult: {\n          end: ['|10£', '15£', '350£', '2£', '5£', '', '', '', '', ''],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title:\n          'Calls itself until it errors and checks that the remaining keys that break ambiguity are not handled because they are not typed by the user',\n        keysPressed: ' $G',\n        stepResult: {\n          end: ['10$00', '15$00', '350$00', '2$00', '5$00', '|', '', '', '', ''],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Potential remaps that have remaining keys when broken should only handle those keys if typed by the user (test mentioned on remapper.ts)',\n    remaps: [\n      'nmap <leader>lf Lfill',\n      'nmap <leader>lF Lfillr',\n      'nmap Lfillc 4I<space><esc>',\n      'nmap Lfillp 2I<space><esc>',\n      'nmap Lfillrs 2I<space><esc>',\n    ],\n    start: ['|Hello World again!', 'Hello World!'],\n    steps: [\n      {\n        // Step 0:\n        title:\n          'Lfill typed by the user should handle all keys even if there is a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'Lfill',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', 'He|llo World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title: 'Lfill typed via remap should handle all keys if there is no failed action',\n        keysPressed: 'ai<Esc>k0 lf',\n        stepResult: {\n          end: ['|Hello World again!', 'Helilo World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', 'Helil|o World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title:\n          'Lfill typed via remap should not handle the remaining keys after a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'hhxk0 lf',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', '|Hello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 3:\n        title:\n          'Lfill typed via remap with a user pressed key after should run the corresponding remap',\n        keysPressed: 'k0 lfc',\n        stepResult: {\n          end: ['   | Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 4:\n        title:\n          'Lfill typed via remap with a wrong user pressed key after should not handle the keys after failed action but should handle the user pressed key',\n        keysPressed: 'j0 lfx',\n        stepResult: {\n          end: ['    Hello World again!', '|ello World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 5:\n        title:\n          'Lfill typed via remap with multiple wrong user pressed keys after should not handle the keys after failed action but should handle the user pressed keys',\n        keysPressed: ' lfrcl',\n        stepResult: {\n          end: ['    Hello World again!', 'c|llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 6:\n        title:\n          'Lfillr typed via remap with a wrong user pressed key after should not handle the keys after failed action but should handle the user pressed key',\n        keysPressed: ' lFdl',\n        stepResult: {\n          end: ['    Hello World again!', '|llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 7:\n        title:\n          'Lfill typed via remap with multiple right user pressed keys after should run the corresponding remap and the rest of the keys',\n        keysPressed: ' lfrsl',\n        stepResult: {\n          end: ['    Hello World again!', '  |llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 8:\n        title: 'Lfillrdl typed by the user should handle all the keys',\n        keysPressed: 'Lfillrdl',\n        stepResult: {\n          end: ['    Hello World again!', '  d|lo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Ambiguous remaps that have remaining keys when broken should only handle those keys if typed by the user (test mentioned on remapper.ts)',\n    remaps: [\n      'nmap <leader>lf Lfill',\n      'nmap <leader>lF Lfillr',\n      'nmap L G',\n      'nmap Lfillc 4I<space><esc>',\n      'nmap Lfillp 2I<space><esc>',\n      'nmap Lfillrs 2I<space><esc>',\n    ],\n    start: ['|Hello World again!', 'Hello World!'],\n    steps: [\n      {\n        // Step 0:\n        title:\n          'Lfill typed by the user should handle all keys even if there is a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'Lfill',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', 'He|llo World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title: 'Lfill typed via remap should handle all keys if there is no failed action',\n        keysPressed: 'ai<Esc>k0 lf',\n        stepResult: {\n          end: ['|Hello World again!', 'Helilo World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', 'Helil|o World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title:\n          'Lfill typed via remap should not handle the remaining keys after a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'hhxk0 lf',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['Hello World again!', '|Hello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 3:\n        title:\n          'Lfill typed via remap with a user pressed key after should run the corresponding remap',\n        keysPressed: 'k0 lfc',\n        stepResult: {\n          end: ['   | Hello World again!', 'Hello World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 4:\n        title:\n          'Lfill typed via remap with a wrong user pressed key after should not handle the keys after failed action but should handle the user pressed key',\n        keysPressed: 'j0 lfx',\n        stepResult: {\n          end: ['    Hello World again!', '|ello World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 5:\n        title:\n          'Lfill typed via remap with multiple wrong user pressed keys after should not handle the keys after failed action but should handle the user pressed keys',\n        keysPressed: ' lfrcl',\n        stepResult: {\n          end: ['    Hello World again!', 'c|llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 6:\n        title:\n          'Lfillr typed via remap with a wrong user pressed key after should not handle the keys after failed action but should handle the user pressed key',\n        keysPressed: ' lFdl',\n        stepResult: {\n          end: ['    Hello World again!', '|llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 7:\n        title:\n          'Lfill typed via remap with multiple right user pressed keys after should run the corresponding remap',\n        keysPressed: ' lfrsl',\n        stepResult: {\n          end: ['    Hello World again!', '  |llo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 8:\n        title: 'Lfillrdl typed by the user should handle all the keys',\n        keysPressed: 'Lfillrdl',\n        stepResult: {\n          end: ['    Hello World again!', '  d|lo World!'],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Ambiguous remaps that have remaining keys when broken should only handle those keys if typed by the user (with changing Modes)',\n    remaps: ['vmap jk <Esc>', 'vmap jkfila <Esc>', 'vmap jkff jkfil'],\n    start: ['|Hello World again!', 'Hello World!'],\n    steps: [\n      {\n        // Step 0:\n        title: 'jkfil typed by the user should handle all keys',\n        keysPressed: 'vjkfil',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Visual,\n          endAfterTimeout: ['Hello World agai|n!', 'Hello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title:\n          'jkfil typed by the user should handle all keys even if there is a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'j0vjkfil',\n        stepResult: {\n          end: ['Hello World again!', '|Hello World!'],\n          endMode: Mode.Visual,\n          endAfterTimeout: ['Hello World again!', 'H|ello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title: 'jkfil typed via remap should handle all keys if there is no failed action',\n        keysPressed: 'k0vjkff',\n        stepResult: {\n          end: ['|Hello World again!', 'Hello World!'],\n          endMode: Mode.Visual,\n          endAfterTimeout: ['Hello World agai|n!', 'Hello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 3:\n        title:\n          'jkfil typed via remap should not handle the remaining keys after a failed action in the middle (in this case the \"fi\")',\n        keysPressed: 'j0vjkff',\n        stepResult: {\n          end: ['Hello World again!', '|Hello World!'],\n          endMode: Mode.Visual,\n          endAfterTimeout: ['Hello World again!', '|Hello World!'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Non recursive remaps should stop running when they encounter a failed action',\n    remaps: ['nn <leader>a 0f£r€'],\n    start: ['|10£', '15€'],\n    steps: [\n      {\n        keysPressed: ' a',\n        stepResult: {\n          end: ['10|€', '15€'],\n        },\n      },\n      {\n        keysPressed: 'j a',\n        stepResult: {\n          end: ['10€', '|15€'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Can remap 0 and still use 0 in count',\n    remaps: ['nmap 0 dw'],\n    start: ['|one two three four five six seven eight nine ten'],\n    steps: [\n      {\n        title: 'Here 0 should be remapped',\n        keysPressed: '0',\n        stepResult: {\n          end: ['|two three four five six seven eight nine ten'],\n        },\n      },\n      {\n        title: 'Here 0 should not be remapped',\n        keysPressed: '10l',\n        stepResult: {\n          end: ['two three |four five six seven eight nine ten'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Can handle operator pending mode remaps',\n    remaps: ['omap L g_', 'ono w aw'],\n    start: ['|Just another test sentence', 'Just another test sentence'],\n    steps: [\n      {\n        keysPressed: 'dL',\n        stepResult: {\n          end: ['|', 'Just another test sentence'],\n        },\n      },\n      {\n        keysPressed: 'jldw',\n        stepResult: {\n          end: ['', '|another test sentence'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Can handle remaps with multiple keys other than the ones starting with <leader>, g or z',\n    remaps: [\n      'map ,l $', // this map creates the mapping for normal, operatorPending and visual modes\n    ],\n    start: ['|Yet another test sentence'],\n    steps: [\n      {\n        keysPressed: ',l',\n        stepResult: {\n          end: ['Yet another test sentenc|e'],\n        },\n      },\n      {\n        keysPressed: '0d,l',\n        stepResult: {\n          end: ['|'],\n        },\n      },\n      {\n        keysPressed: 'u',\n        stepResult: {\n          end: ['|Yet another test sentence'],\n        },\n      },\n      {\n        keysPressed: 'lv,ld',\n        stepResult: {\n          end: ['|Y'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Can handle d -> \"_d remaps even when doing dd',\n    remaps: ['nnoremap d \"_d'],\n    start: ['|one two three'],\n    steps: [\n      {\n        keysPressed: 'yww',\n        stepResult: {\n          end: ['one |two three'],\n        },\n      },\n      {\n        keysPressed: 'dw',\n        stepResult: {\n          end: ['one |three'],\n        },\n      },\n      {\n        keysPressed: 'P',\n        stepResult: {\n          end: ['one one| three'],\n        },\n      },\n      {\n        keysPressed: 'dd',\n        stepResult: {\n          end: ['|'],\n        },\n      },\n      {\n        keysPressed: 'p',\n        stepResult: {\n          end: ['one| '],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Can handle multiple insert remaps even when starting one with a leading key that is the start of another possible remap',\n    remaps: {\n      insertModeKeyBindings: [\n        {\n          before: ['q', 'r'],\n          after: ['<Esc>'],\n        },\n        {\n          before: ['t', 'h', 'r', 'u', 'n'],\n          after: [\n            't',\n            'h',\n            'r',\n            'o',\n            'w',\n            ' ',\n            '\"',\n            'U',\n            'n',\n            'i',\n            'm',\n            'p',\n            'l',\n            'e',\n            'm',\n            'e',\n            'n',\n            't',\n            'e',\n            'd',\n            '\"',\n            ';',\n            ' ',\n            '/',\n            '/',\n            'T',\n            'O',\n            'D',\n            'O',\n            ' ',\n            'i',\n            'm',\n            'p',\n            'l',\n            'e',\n            'm',\n            'e',\n            'n',\n            't',\n            'q',\n            'r',\n            '=',\n            '0',\n          ],\n        },\n      ],\n    },\n    start: ['|'],\n    steps: [\n      {\n        keysPressed: 'atest\\nqr',\n        stepResult: {\n          end: ['test', '|'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        keysPressed: 'ithrun',\n        stepResult: {\n          end: ['test', '|throw \"Unimplemented\"; //TODO implement'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        keysPressed: 'itqr',\n        stepResult: {\n          end: ['test', '|tthrow \"Unimplemented\"; //TODO implement'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        keysPressed: 'oqthrun',\n        stepResult: {\n          end: [\n            'test',\n            'tthrow \"Unimplemented\"; //TODO implement',\n            '|qthrow \"Unimplemented\"; //TODO implement',\n          ],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Can handle remaps with ambiguous remaps on both types of recursiveness with longer NonRecursive remaps',\n    remaps: [\n      'nmap ab aab<Esc>',\n      'nmap abcdef aabcdef<Esc>',\n      'nmap abcdefghij aabcdefghi<Esc>',\n      'nnoremap abcd aabcd<Esc>',\n      'nnoremap abcdefg aabcdefg<Esc>',\n      'nnoremap abcdefgh aabcdefgh<Esc>',\n    ],\n    start: ['|'],\n    steps: [\n      {\n        // Step 0: there is no timeout because 'b' breaks ambiguity\n        title: 'Can handle \"abb\" has \"ab\" recursive remap + \"b\"',\n        keysPressed: 'abb',\n        stepResult: {\n          end: ['|ab'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 1: there will be timeout\n        title: 'Can handle \"abcd\" has \"abcd\" non recursive remap',\n        keysPressed: 'ddabcd',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abc|d'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 2: there will be timeout\n        title: 'Can handle \"abcde\" has \"abcd\" non recursive remap + \"e\"',\n        keysPressed: '0abcde',\n        stepResult: {\n          end: ['|abcd'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['aabcdbc|d'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 3: No timeout because 'x' breaks ambiguity\n        title: 'Can handle \"abcdex\" has \"abcd\" non recursive remap + \"ex\"',\n        keysPressed: '0abcdex',\n        stepResult: {\n          end: ['aabcdabcdb|c'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 4: there will be timeout\n        title: 'Can handle \"abcdef\" has \"abcdef\" recursive remap',\n        keysPressed: 'ddabcdef',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abcde|f'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 5: there will be timeout\n        title: 'Can handle \"abcdefghi\" has \"abcdefgh\" non recursive remap + \"i\"',\n        keysPressed: 'ddabcdefghi',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abcdefg|h'],\n          endModeAfterTimeout: Mode.Insert,\n        },\n      },\n      {\n        // Step 6: there will be no timeout because \"x\" breaks ambiguity\n        title: 'Can handle \"abcdefghix\" has \"abcdefgh\" non recursive remap + \"ix\"',\n        keysPressed: '<Esc>ddabcdefghix',\n        stepResult: {\n          end: ['abcdefgx|h'],\n          endMode: Mode.Insert,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Can handle remaps with ambiguous remaps on both types of recursiveness with longer Recursive remaps',\n    remaps: [\n      'nnoremap ab aab<Esc>',\n      'nnoremap abcdef aabcdef<Esc>',\n      'nnoremap abcdefghij aabcdefghi<Esc>',\n      'nmap abcd aabcd<Esc>',\n      'nmap abcdefg aabcdefg<Esc>',\n      'nmap abcdefgh aabcdefgh<Esc>',\n    ],\n    start: ['|'],\n    steps: [\n      {\n        // Step 0: there is no timeout because 'b' breaks ambiguity\n        title: 'Can handle \"abb\" has \"ab\" recursive remap + \"b\"',\n        keysPressed: 'abb',\n        stepResult: {\n          end: ['|ab'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 1: there will be timeout\n        title: 'Can handle \"abcd\" has \"abcd\" non recursive remap',\n        keysPressed: 'ddabcd',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abc|d'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 2: there will be timeout\n        title: 'Can handle \"abcde\" has \"abcd\" non recursive remap + \"e\"',\n        keysPressed: '0abcde',\n        stepResult: {\n          end: ['|abcd'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['aabcdbc|d'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 3: No timeout because 'x' breaks ambiguity\n        title: 'Can handle \"abcdex\" has \"abcd\" non recursive remap + \"ex\"',\n        keysPressed: '0abcdex',\n        stepResult: {\n          end: ['aabcdabcdb|c'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 4: there will be timeout\n        title: 'Can handle \"abcdef\" has \"abcdef\" recursive remap',\n        keysPressed: 'ddabcdef',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abcde|f'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 5: there will be timeout\n        title: 'Can handle \"abcdefghi\" has \"abcdefgh\" non recursive remap + \"i\"',\n        keysPressed: 'ddabcdefghi',\n        stepResult: {\n          end: ['|'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['abcdefg|h'],\n          endModeAfterTimeout: Mode.Insert,\n        },\n      },\n      {\n        // Step 6: there will be no timeout because \"x\" breaks ambiguity\n        title: 'Can handle \"abcdefghix\" has \"abcdefgh\" non recursive remap + \"ix\"',\n        keysPressed: '<Esc>ddabcdefghix',\n        stepResult: {\n          end: ['abcdefgx|h'],\n          endMode: Mode.Insert,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Can handle timeout finished with a sequence of multiple potential remaps that end on a key that waits for other keys like `f`',\n    remaps: {\n      normalModeKeyBindings: [\n        {\n          before: ['l', 'f', 'f'],\n          after: ['A', 'l', 'f', 'f', '<Esc>'],\n        },\n        {\n          before: ['l', 't'],\n          after: ['A', 'l', 't', '<Esc>'],\n        },\n      ],\n    },\n    start: ['hello |world'],\n    steps: [\n      {\n        // Step 0:\n        title: 'Can handle sequential potential remaps that waited for timeout',\n        keysPressed: 'llf',\n        stepResult: {\n          end: ['hello w|orld'],\n          endMode: Mode.Normal,\n          endAfterTimeout: ['hello wo|rld'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title:\n          'After the previous step there is no undefined key left that messes up the next action',\n        keysPressed: 'd',\n        stepResult: {\n          end: ['hello worl|d'],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Can handle a remapping right after a failed movement',\n    remaps: ['nmap j gj'],\n    start: ['|first line', 'second line'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: 'fxj',\n        stepResult: {\n          end: ['first line', '|second line'],\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: 'Remaps create an undo point only at the end of the remap handling',\n    remaps: [\n      'nmap <leader>$ 0f£xA$00<Esc>',\n      'nmap <leader>$G <leader>$j<leader>$G',\n      'nno <leader>a oThis is a Non Recursive Remap!<Esc>',\n    ],\n    start: ['|10£', '15£', '350£', '2£', '5£'],\n    steps: [\n      {\n        // Step 0:\n        title: 'Run remap once that executes two different steps',\n        keysPressed: ' $',\n        stepResult: {\n          end: ['|10£', '15£', '350£', '2£', '5£'],\n          endAfterTimeout: ['10$0|0', '15£', '350£', '2£', '5£'],\n          endModeAfterTimeout: Mode.Normal,\n        },\n      },\n      {\n        // Step 1:\n        title: 'Undo removes the $00 that was inserted and reinserts the deleted £ all at once',\n        keysPressed: 'u',\n        stepResult: {\n          end: ['10|£', '15£', '350£', '2£', '5£'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 2:\n        title: 'Calls itself until it errors',\n        keysPressed: ' $G',\n        stepResult: {\n          end: ['10$00', '15$00', '350$00', '2$00', '|5$00'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 3:\n        title: 'Undo should undo all the lines that were changed by the recursive remap',\n        keysPressed: 'u',\n        stepResult: {\n          end: ['10|£', '15£', '350£', '2£', '5£'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 4:\n        title: 'Make a non recursive remap',\n        keysPressed: 'G a',\n        stepResult: {\n          end: ['10£', '15£', '350£', '2£', '5£', 'This is a Non Recursive Remap|!'],\n          endMode: Mode.Normal,\n        },\n      },\n      {\n        // Step 5:\n        title: 'Undo works on NonRecursive remaps',\n        keysPressed: 'u',\n        stepResult: {\n          end: ['10£', '15£', '350£', '2£', '|5£'],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Potential remap key followed by a remapped key in insert mode should insert first potential remap key and then handle the following remapped key.',\n    remaps: ['imap jk <Esc>', 'imap <C-e> <C-o>A'],\n    start: ['|Test'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: 'ij<C-e>',\n        stepResult: {\n          end: ['jTest|'],\n          endMode: Mode.Insert,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title: `Don't confuse a '<' keystroke with a potential special key like '<C-e>'`,\n    remaps: ['inoremap <C-e> <C-o>$'],\n    start: ['|test'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: 'i<',\n        stepResult: {\n          end: ['<|test'],\n          endMode: Mode.Insert,\n        },\n      },\n    ],\n  });\n\n  newTestWithRemaps({\n    title:\n      'Forced stop recursive remaps that are not infinite remaps should stop without throwing error',\n    remaps: {\n      insertModeKeyBindings: [\n        {\n          before: ['i', 'i'],\n          commands: ['extension.vim_escape'],\n        },\n      ],\n    },\n    start: ['|test'],\n    steps: [\n      {\n        // Step 0:\n        keysPressed: 'aii<Esc>l',\n        stepResult: {\n          end: ['t|est'],\n          endMode: Mode.Normal,\n        },\n      },\n    ],\n  });\n});\n"
  },
  {
    "path": "test/configuration/validators/neovimValidator.test.ts",
    "content": "import * as assert from 'assert';\nimport childProcess from 'child_process';\nimport fs from 'fs';\nimport * as path from 'path';\nimport * as sinon from 'sinon';\nimport { NeovimValidator } from '../../../src/configuration/validators/neovimValidator';\nimport { Configuration } from '../../testConfiguration';\n\nsuite('Neovim Validator', () => {\n  let sandbox: sinon.SinonSandbox;\n\n  setup(() => {\n    sandbox = sinon.createSandbox();\n  });\n\n  teardown(() => {\n    sandbox.restore();\n  });\n\n  test('neovim enabled without path', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.enableNeovim = true;\n    configuration.neovimPath = '';\n\n    const oldPath = process.env.PATH?.slice();\n    process.env.PATH = '';\n\n    // test\n    const validator = new NeovimValidator();\n    const actual = await validator.validate(configuration);\n    validator.disable(configuration);\n\n    process.env.PATH = oldPath;\n\n    // assert\n    assert.strictEqual(actual.numErrors, 1);\n    assert.strictEqual(actual.hasError, true);\n    assert.strictEqual(configuration.enableNeovim, false);\n  });\n\n  // TODO(#4844): this fails on Windows\n  test('neovim enabled with nvim in path', async () => {\n    if (process.platform === 'win32') {\n      return;\n    }\n\n    // setup\n    const configuration = new Configuration();\n    configuration.enableNeovim = true;\n    configuration.neovimPath = '';\n\n    const oldPath = process.env.PATH?.slice();\n    process.env.PATH = `/usr/bin${path.delimiter}/some/other/path`;\n    sandbox.stub(fs, 'existsSync').withArgs('/usr/bin/nvim').returns(true);\n    sandbox.stub(childProcess, 'execFileSync').withArgs('/usr/bin/nvim', sinon.match.array);\n\n    // test\n    const validator = new NeovimValidator();\n    const actual = await validator.validate(configuration);\n\n    process.env.PATH = oldPath;\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(configuration.enableNeovim, true);\n    assert.strictEqual(configuration.neovimPath, '/usr/bin/nvim');\n  });\n\n  test('neovim disabled', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.enableNeovim = false;\n    configuration.neovimPath = '';\n\n    // test\n    const validator = new NeovimValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n  });\n});\n"
  },
  {
    "path": "test/configuration/validators/remappingValidator.test.ts",
    "content": "import * as assert from 'assert';\nimport { RemappingValidator } from '../../../src/configuration/validators/remappingValidator';\nimport { Configuration } from '../../testConfiguration';\n\nsuite('Remapping Validator', () => {\n  test('no remappings', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [];\n    configuration.insertModeKeyBindingsNonRecursive = [];\n    configuration.normalModeKeyBindings = [];\n    configuration.normalModeKeyBindingsNonRecursive = [];\n    configuration.operatorPendingModeKeyBindings = [];\n    configuration.operatorPendingModeKeyBindingsNonRecursive = [];\n    configuration.visualModeKeyBindings = [];\n    configuration.visualModeKeyBindingsNonRecursive = [];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(actual.hasError, false);\n    assert.strictEqual(actual.numWarnings, 0);\n    assert.strictEqual(actual.hasWarning, false);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.operatorPendingModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 0);\n  });\n\n  test('jj->esc', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [\n      {\n        before: ['j', 'j'],\n        after: ['<Esc>'],\n      },\n    ];\n    configuration.insertModeKeyBindingsNonRecursive = [];\n    configuration.normalModeKeyBindings = [];\n    configuration.normalModeKeyBindingsNonRecursive = [];\n    configuration.visualModeKeyBindings = [];\n    configuration.visualModeKeyBindingsNonRecursive = [];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(actual.hasError, false);\n    assert.strictEqual(actual.numWarnings, 0);\n    assert.strictEqual(actual.hasWarning, false);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 1);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 0);\n\n    assert.strictEqual(\n      configuration.insertModeKeyBindingsMap.get('jj'),\n      configuration.insertModeKeyBindings[0],\n    );\n  });\n\n  test('remapping missing after and command', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [\n      {\n        before: ['j', 'j'],\n      },\n    ];\n    configuration.insertModeKeyBindingsNonRecursive = [];\n    configuration.normalModeKeyBindings = [];\n    configuration.normalModeKeyBindingsNonRecursive = [];\n    configuration.visualModeKeyBindings = [];\n    configuration.visualModeKeyBindingsNonRecursive = [];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 1);\n    assert.strictEqual(actual.hasError, true);\n    assert.strictEqual(actual.numWarnings, 0);\n    assert.strictEqual(actual.hasWarning, false);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 0);\n  });\n\n  test('remappings are de-duped', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [];\n    configuration.insertModeKeyBindingsNonRecursive = [];\n    configuration.normalModeKeyBindings = [\n      {\n        before: ['c', 'o', 'p', 'y'],\n        after: ['c', 'o', 'p', 'y'],\n      },\n      {\n        before: ['c', 'o', 'p', 'y'],\n        after: ['c', 'o', 'p', 'y'],\n      },\n    ];\n    configuration.normalModeKeyBindingsNonRecursive = [];\n    configuration.visualModeKeyBindings = [];\n    configuration.visualModeKeyBindingsNonRecursive = [];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(actual.hasError, false);\n    assert.strictEqual(actual.numWarnings, 1);\n    assert.strictEqual(actual.hasWarning, true);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 1);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 0);\n  });\n\n  test('remappings are de-duped even when on different recursive types', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [];\n    configuration.insertModeKeyBindingsNonRecursive = [];\n    configuration.normalModeKeyBindings = [\n      {\n        before: ['c', 'o', 'p', 'y'],\n        after: ['c', 'o', 'p', 'y'],\n      },\n    ];\n    configuration.normalModeKeyBindingsNonRecursive = [\n      {\n        before: ['c', 'o', 'p', 'y'],\n        after: ['c', 'o', 'p', 'y'],\n      },\n    ];\n    configuration.visualModeKeyBindings = [];\n    configuration.visualModeKeyBindingsNonRecursive = [];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(actual.hasError, false);\n    assert.strictEqual(actual.numWarnings, 1);\n    assert.strictEqual(actual.hasWarning, true);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 0);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 1);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 0);\n  });\n\n  test('remappings added on different recursive types are combined in one Map', async () => {\n    // setup\n    const configuration = new Configuration();\n    configuration.insertModeKeyBindings = [\n      {\n        before: ['a'],\n        after: ['b'],\n      },\n    ];\n    configuration.insertModeKeyBindingsNonRecursive = [\n      {\n        before: ['c'],\n        after: ['d'],\n      },\n    ];\n    configuration.normalModeKeyBindings = [\n      {\n        before: ['a'],\n        after: ['b'],\n      },\n    ];\n    configuration.normalModeKeyBindingsNonRecursive = [\n      {\n        before: ['c'],\n        after: ['d'],\n      },\n    ];\n    configuration.visualModeKeyBindings = [\n      {\n        before: ['a'],\n        after: ['b'],\n      },\n    ];\n    configuration.visualModeKeyBindingsNonRecursive = [\n      {\n        before: ['c'],\n        after: ['d'],\n      },\n    ];\n    configuration.commandLineModeKeyBindings = [\n      {\n        before: ['a'],\n        after: ['b'],\n      },\n    ];\n    configuration.commandLineModeKeyBindingsNonRecursive = [\n      {\n        before: ['c'],\n        after: ['d'],\n      },\n    ];\n    configuration.operatorPendingModeKeyBindings = [\n      {\n        before: ['a'],\n        after: ['b'],\n      },\n    ];\n    configuration.operatorPendingModeKeyBindingsNonRecursive = [\n      {\n        before: ['c'],\n        after: ['d'],\n      },\n    ];\n\n    // test\n    const validator = new RemappingValidator();\n    const actual = await validator.validate(configuration);\n\n    // assert\n    assert.strictEqual(actual.numErrors, 0);\n    assert.strictEqual(actual.hasError, false);\n    assert.strictEqual(actual.numWarnings, 0);\n    assert.strictEqual(actual.hasWarning, false);\n\n    assert.strictEqual(configuration.insertModeKeyBindingsMap.size, 2);\n    assert.strictEqual(configuration.normalModeKeyBindingsMap.size, 2);\n    assert.strictEqual(configuration.visualModeKeyBindingsMap.size, 2);\n    assert.strictEqual(configuration.commandLineModeKeyBindingsMap.size, 2);\n    assert.strictEqual(configuration.operatorPendingModeKeyBindingsMap.size, 2);\n  });\n});\n"
  },
  {
    "path": "test/configuration/vimrc.test.ts",
    "content": "import * as assert from 'assert';\nimport * as os from 'os';\nimport { vimrc } from '../../src/configuration/vimrc';\nimport * as testConfiguration from '../testConfiguration';\n\nsuite('Vimrc', () => {\n  const configuration = new testConfiguration.Configuration();\n  const vimrcpath = os.homedir();\n  configuration.vimrc.enable = true;\n\n  test(\"Can expand $HOME to user's home directory\", async () => {\n    configuration.vimrc.path = '$HOME';\n\n    await vimrc.load(configuration);\n    assert.strictEqual(vimrc.vimrcPath, vimrcpath);\n  });\n\n  test(\"Can expand ~ to user's home directory\", async () => {\n    configuration.vimrc.path = '~';\n\n    await vimrc.load(configuration);\n    assert.strictEqual(vimrc.vimrcPath, vimrcpath);\n  });\n});\n"
  },
  {
    "path": "test/configuration/vimrcKeyRemappingBuilder.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\nimport { vimrcKeyRemappingBuilder } from '../../src/configuration/vimrcKeyRemappingBuilder';\n\nsuite('VimrcKeyRemappingBuilder', () => {\n  test('Build IKeyRemapping objects from .vimrc lines', async () => {\n    const testCases = [\n      {\n        vimrcLine: 'nnoremap <C-h> <<',\n        keyRemapping: {\n          before: ['<C-h>'],\n          after: ['<', '<'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nnoremap',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'imap jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'imap',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'ino jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'ino',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'nore! jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nore!',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'vnoremap <leader>\" c\"\"<Esc>P',\n        keyRemapping: {\n          before: ['<leader>', '\"'],\n          after: ['c', '\"', '\"', '<Esc>', 'P'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'vnoremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with a vim command\n        vimrcLine: 'nnoremap <C-s> :w<CR>',\n        keyRemapping: {\n          before: ['<C-s>'],\n          commands: [':w'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nnoremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with a VSCode command\n        vimrcLine: 'nnoremap <C-t> workbench.action.files.newUntitledFile',\n        keyRemapping: {\n          before: ['<C-t>'],\n          commands: ['workbench.action.files.newUntitledFile'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nnoremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with <silent> argument (argument is ignored)\n        vimrcLine: 'noremap <silent> jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'noremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with <buffer> argument (argument is ignored)\n        vimrcLine: 'noremap <buffer> jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'noremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with multiple arguments (arguments are ignored)\n        vimrcLine: 'noremap <buffer> <silent> jj <Esc>',\n        keyRemapping: {\n          before: ['j', 'j'],\n          after: ['<Esc>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'noremap',\n        expectNull: false,\n      },\n      {\n        // Mapping with multiple arguments with weird spacing (arguments are ignored)\n        vimrcLine: 'noremap <nowait> <silent><C-s> :w<CR>',\n        keyRemapping: {\n          before: ['<C-s>'],\n          commands: [':w'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'noremap',\n        expectNull: false,\n      },\n      {\n        // Ignore non-mapping lines\n        vimrcLine: 'set scrolloff=8',\n        expectNull: true,\n      },\n      {\n        // Ignore lines attempting to remap a plug in using <Plug>\n        vimrcLine: 'nmap s <Plug>(easymotion-s2)',\n        expectNull: true,\n      },\n      {\n        // Ignore lines attempting to remap an expression using <expr>\n        vimrcLine: \"nnoremap <expr> <Leader>o 'mm' . v:count . 'o<ESC>`m\",\n        expectNull: true,\n      },\n      {\n        // Ignore lines with unmappings\n        vimrcLine: 'unmap jj',\n        expectNull: true,\n      },\n      {\n        // Ignore lines with clearmappings\n        vimrcLine: 'mapclear jj',\n        expectNull: true,\n      },\n    ];\n\n    const vscodeCommands = await vscode.commands.getCommands();\n    for (const testCase of testCases) {\n      const vimrcKeyRemapping = await vimrcKeyRemappingBuilder.build(\n        testCase.vimrcLine,\n        vscodeCommands,\n      );\n\n      if (testCase.expectNull) {\n        assert.strictEqual(vimrcKeyRemapping, undefined);\n      } else {\n        assert.deepStrictEqual(vimrcKeyRemapping!.keyRemapping, testCase.keyRemapping);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemappingType, testCase.keyRemappingType);\n      }\n    }\n  });\n  test('Build IKeyRemapping unmapping objects from .vimrc lines', async () => {\n    const testCases = [\n      {\n        vimrcLine: 'unmap <C-h>',\n        keyRemapping: {\n          before: ['<C-h>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'unmap',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'iunmap jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'iunmap',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'iun jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'iun',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'unm! jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'unm!',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'vu <leader>\"',\n        keyRemapping: {\n          before: ['<leader>', '\"'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'vu',\n        expectNull: false,\n      },\n      {\n        // Unmapping with <silent> argument (argument is ignored)\n        vimrcLine: 'nunmap <silent> jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nunmap',\n        expectNull: false,\n      },\n      {\n        // Unmapping with <buffer> argument (argument is ignored)\n        vimrcLine: 'nunmap <buffer> jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nunmap',\n        expectNull: false,\n      },\n      {\n        // Unmapping with multiple arguments (arguments are ignored)\n        vimrcLine: 'nunmap <buffer> <silent> jj',\n        keyRemapping: {\n          before: ['j', 'j'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nunmap',\n        expectNull: false,\n      },\n      {\n        // Unmapping with multiple arguments with weird spacing (arguments are ignored)\n        vimrcLine: 'nunmap <nowait> <silent><C-s>',\n        keyRemapping: {\n          before: ['<C-s>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'nunmap',\n        expectNull: false,\n      },\n      {\n        // Ignore non-mapping lines\n        vimrcLine: 'set scrolloff=8',\n        expectNull: true,\n      },\n      {\n        // Ignore lines with mappings\n        vimrcLine: 'imap jj <Esc>',\n        expectNull: true,\n      },\n      {\n        // Ignore lines with clearmappings\n        vimrcLine: 'mapclear jj',\n        expectNull: true,\n      },\n    ];\n\n    for (const testCase of testCases) {\n      const vimrcKeyRemapping = await vimrcKeyRemappingBuilder.buildUnmapping(testCase.vimrcLine);\n\n      if (testCase.expectNull) {\n        assert.strictEqual(vimrcKeyRemapping, undefined);\n      } else {\n        assert.deepStrictEqual(vimrcKeyRemapping!.keyRemapping, testCase.keyRemapping);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemappingType, testCase.keyRemappingType);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemapping.after, undefined);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemapping.commands, undefined);\n      }\n    }\n  });\n  test('Build IKeyRemapping clearMapping objects from .vimrc lines', async () => {\n    const testCases = [\n      {\n        vimrcLine: 'mapclear',\n        keyRemapping: {\n          before: ['<Nop>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'mapclear',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'imapc',\n        keyRemapping: {\n          before: ['<Nop>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'imapc',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'imapcl',\n        keyRemapping: {\n          before: ['<Nop>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'imapcl',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'mapclear!',\n        keyRemapping: {\n          before: ['<Nop>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'mapclear!',\n        expectNull: false,\n      },\n      {\n        vimrcLine: 'vmapc',\n        keyRemapping: {\n          before: ['<Nop>'],\n          source: 'vimrc',\n        },\n        keyRemappingType: 'vmapc',\n        expectNull: false,\n      },\n      {\n        // Clear mapping with <silent> argument (argument is ignored)\n        vimrcLine: 'mapc <silent>',\n        expectNull: true,\n      },\n      {\n        // Clear mapping with <buffer> argument (argument is ignored)\n        vimrcLine: 'nmapclear <buffer>',\n        expectNull: true,\n      },\n      {\n        // Clear mapping with multiple arguments with weird spacing (arguments are ignored)\n        vimrcLine: 'nmapc <nowait> <silent>',\n        expectNull: true,\n      },\n      {\n        // Ignore non-mapping lines\n        vimrcLine: 'set scrolloff=8',\n        expectNull: true,\n      },\n      {\n        // Ignore lines with mappings\n        vimrcLine: 'imap jj <Esc>',\n        expectNull: true,\n      },\n      {\n        // Ignore lines with unmappings\n        vimrcLine: 'unmap jj',\n        expectNull: true,\n      },\n    ];\n\n    for (const testCase of testCases) {\n      const vimrcKeyRemapping = await vimrcKeyRemappingBuilder.buildClearMapping(\n        testCase.vimrcLine,\n      );\n\n      if (testCase.expectNull) {\n        assert.strictEqual(vimrcKeyRemapping, undefined);\n      } else {\n        assert.deepStrictEqual(vimrcKeyRemapping!.keyRemapping, testCase.keyRemapping);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemappingType, testCase.keyRemappingType);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemapping.after, undefined);\n        assert.strictEqual(vimrcKeyRemapping!.keyRemapping.commands, undefined);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "test/extension.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport * as srcConfiguration from '../src/configuration/configuration';\nimport * as testConfiguration from './testConfiguration';\n\nimport { IConfiguration } from 'src/configuration/iconfiguration';\nimport * as packagejson from '../package.json';\n\nsuite('package.json', () => {\n  test('all keys have handlers', async () => {\n    const registeredCommands = await vscode.commands.getCommands();\n    const keybindings = packagejson.contributes.keybindings;\n    assert.ok(keybindings);\n\n    for (const keybinding of keybindings) {\n      const found = registeredCommands.includes(keybinding.command);\n      assert.ok(\n        found,\n        'Missing handler for key=' + keybinding.key + '. Expected handler=' + keybinding.command,\n      );\n    }\n  });\n\n  test('all defined configurations in package.json have handlers', async () => {\n    // package.json\n    const pkgConfigurations = packagejson.contributes.configuration.properties;\n    assert.ok(pkgConfigurations);\n    const keys = Object.keys(pkgConfigurations);\n    assert.notStrictEqual(keys.length, 0);\n    assert.ok(keys.every((key) => key.startsWith('vim.')));\n\n    const isUnhandled = (configuration: IConfiguration, key: string): boolean => {\n      const keyFirstSegment = key.split('.')[1]; // Extract the first segment without the `vim.` prefix from `key`, e.g. get `foo` from 'vim.foo.bar.baz'.\n\n      const handlers = Object.keys(configuration);\n      const propertyExists = handlers.includes(keyFirstSegment);\n      if (propertyExists) {\n        return false;\n      }\n\n      // If the property doesn't exist, check the possibility that the field is implemented as a get proxy by calling it.\n      if (configuration[keyFirstSegment]) {\n        return false;\n      }\n\n      return true;\n    };\n\n    // configuration\n    const srcUnhandled = keys.filter(isUnhandled.bind(null, srcConfiguration.configuration));\n    assert.strictEqual(\n      srcUnhandled.length,\n      0,\n      'Missing src handlers for ' + srcUnhandled.join(','),\n    );\n\n    // test configuration\n    const testConfigurationInstance = new testConfiguration.Configuration();\n    const testUnhandled = keys.filter(isUnhandled.bind(null, testConfigurationInstance));\n    assert.strictEqual(\n      testUnhandled.length,\n      0,\n      'Missing test handlers for ' + testUnhandled.join(','),\n    );\n  });\n});\n"
  },
  {
    "path": "test/historyTracker.test.ts",
    "content": "import * as assert from 'assert';\nimport * as sinon from 'sinon';\nimport vscode, { Position } from 'vscode';\n\nimport { HistoryTracker, ILocalMark, IMark } from '../src/history/historyTracker';\nimport { Jump } from '../src/jumps/jump';\nimport { globalState } from '../src/state/globalState';\nimport { VimState } from '../src/state/vimState';\n\nsuite('historyTracker unit tests', () => {\n  let sandbox: sinon.SinonSandbox;\n  let historyTracker: HistoryTracker;\n  const document = { fileName: 'file name' } as vscode.TextDocument;\n\n  const retrieveLocalMark = (markName: string): ILocalMark | undefined =>\n    historyTracker.getLocalMarks().find((mark) => mark.name === markName);\n\n  const retrieveFileMark = (markName: string): IMark | undefined =>\n    historyTracker.getGlobalMarks().find((mark) => mark.name === markName);\n\n  const setupVimState = () => sandbox.createStubInstance(VimState) as unknown as VimState;\n\n  const setupHistoryTracker = (vimState = setupVimState()) => new HistoryTracker(vimState);\n\n  const buildMockPosition = (): Position => sandbox.createStubInstance(Position);\n\n  setup(() => {\n    sandbox = sinon.createSandbox();\n  });\n\n  teardown(() => {\n    sandbox.restore();\n  });\n\n  suite('addMark', () => {\n    setup(() => {\n      historyTracker = setupHistoryTracker();\n    });\n\n    test('can set previous context mark from single quote', () => {\n      const spy = sandbox.spy(globalState.jumpTracker, 'recordJump');\n      const position = buildMockPosition();\n      const mockJump = new Jump({\n        document,\n        position,\n      });\n      sandbox.stub(Jump, 'fromStateNow').returns(mockJump);\n\n      historyTracker.addMark(document, position, \"'\");\n\n      sinon.assert.calledWith(spy, mockJump);\n    });\n    test('can set previous context mark from backtick', () => {\n      const spy = sandbox.spy(globalState.jumpTracker, 'recordJump');\n      const position = buildMockPosition();\n      const mockJump = new Jump({\n        document,\n        position,\n      });\n      sandbox.stub(Jump, 'fromStateNow').returns(mockJump);\n\n      historyTracker.addMark(document, position, '`');\n\n      sinon.assert.calledWith(spy, mockJump);\n    });\n\n    test('can create lowercase mark', () => {\n      const position = buildMockPosition();\n      historyTracker.addMark(document, position, 'a');\n      const mark = retrieveLocalMark('a');\n      assert.notStrictEqual(mark, undefined, 'failed to store lowercase mark');\n      if (mark !== undefined) {\n        assert.strictEqual(mark.position, position);\n        assert.strictEqual(mark.isUppercaseMark, false);\n      }\n    });\n\n    test('can create uppercase mark', () => {\n      const position = buildMockPosition();\n      historyTracker.addMark(document, position, 'A');\n      const mark = retrieveFileMark('A');\n      assert.notStrictEqual(mark, undefined, 'failed to store file mark');\n      if (mark !== undefined) {\n        assert.strictEqual(mark.position, position);\n        assert.strictEqual(mark.isUppercaseMark, true);\n        assert.strictEqual(mark.document, document);\n      }\n    });\n\n    test('shares uppercase marks between editor instances', () => {\n      const position = buildMockPosition();\n      const firstHistoryTrackerInstance = historyTracker;\n      const otherHistoryTrackerInstance = setupHistoryTracker(setupVimState());\n      assert.notStrictEqual(firstHistoryTrackerInstance, otherHistoryTrackerInstance);\n      otherHistoryTrackerInstance.addMark(document, position, 'A');\n      const mark = retrieveFileMark('A');\n      assert.notStrictEqual(mark, undefined);\n      if (mark !== undefined) {\n        assert.strictEqual(position, mark.position);\n      }\n    });\n\n    test('does not share lower marks between editor instances', () => {\n      const position = buildMockPosition();\n      const firstHistoryTrackerInstance = historyTracker;\n      const otherHistoryTrackerInstance = setupHistoryTracker(setupVimState());\n      assert.notStrictEqual(firstHistoryTrackerInstance, otherHistoryTrackerInstance);\n      otherHistoryTrackerInstance.addMark(document, position, 'a');\n      const mark = retrieveLocalMark('a');\n      assert.strictEqual(mark, undefined);\n    });\n  });\n\n  suite('removeLocalMarks', () => {\n    setup(() => {\n      historyTracker = setupHistoryTracker();\n    });\n\n    test('removes only local marks', () => {\n      const position = buildMockPosition();\n      historyTracker.addMark(document, position, 'a');\n      historyTracker.addMark(document, position, 'A');\n      const mark = historyTracker.getMark('A');\n\n      historyTracker.removeLocalMarks();\n\n      assert.strictEqual(historyTracker.getMark('a'), undefined);\n      assert.strictEqual(historyTracker.getMark('A'), mark);\n    });\n  });\n\n  suite('removeMarks', () => {\n    setup(() => {\n      historyTracker = setupHistoryTracker();\n    });\n\n    test('removes multiple local and global', () => {\n      const position = buildMockPosition();\n      const markTargets = 'AHZced'.split('');\n\n      markTargets.forEach((m) => historyTracker.addMark(document, position, m));\n\n      historyTracker.removeMarks(markTargets);\n\n      markTargets.forEach((m) => assert.strictEqual(historyTracker.getMark(m), undefined));\n    });\n\n    test(\"does not remove ''\", () => {\n      const position = buildMockPosition();\n      historyTracker.addMark(document, position, '');\n      const mark = historyTracker.getMark('');\n\n      historyTracker.removeMarks(['']);\n\n      assert.strictEqual(mark, historyTracker.getMark(''));\n    });\n\n    test('does nothing on empty', () => {\n      const position = buildMockPosition();\n      historyTracker.addMark(document, position, 'a');\n      const mark = historyTracker.getMark('a');\n\n      historyTracker.removeMarks([]);\n\n      assert.strictEqual(mark, historyTracker.getMark('a'));\n    });\n  });\n});\n\nclass TextEditorStub implements vscode.TextEditor {\n  readonly document!: vscode.TextDocument;\n  selection!: vscode.Selection;\n  selections!: vscode.Selection[];\n  readonly visibleRanges!: vscode.Range[];\n  options!: vscode.TextEditorOptions;\n  readonly viewColumn!: vscode.ViewColumn;\n\n  constructor() {\n    // NoOp\n  }\n  async edit(\n    callback: (editBuilder: vscode.TextEditorEdit) => void,\n    options?: { undoStopBefore: boolean; undoStopAfter: boolean },\n  ) {\n    return true;\n  }\n  async insertSnippet(\n    snippet: vscode.SnippetString,\n    location?: vscode.Position | vscode.Range | readonly Position[] | readonly vscode.Range[],\n    options?: { undoStopBefore: boolean; undoStopAfter: boolean },\n  ) {\n    return true;\n  }\n  setDecorations(\n    decorationType: vscode.TextEditorDecorationType,\n    rangesOrOptions: vscode.Range[] | vscode.DecorationOptions[],\n  ) {\n    // NoOp\n  }\n  revealRange(range: vscode.Range, revealType?: vscode.TextEditorRevealType) {\n    // NoOp\n  }\n  show(column?: vscode.ViewColumn) {\n    // NoOp\n  }\n  hide() {\n    // NoOp\n  }\n}\n"
  },
  {
    "path": "test/index.ts",
    "content": "//\n// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING\n//\n// This file is providing the test runner to use when running extension tests.\n// By default the test runner in use is Mocha based.\n//\n// You can provide your own test runner if you want to override it by exporting\n// a function run(testRoot: string, clb: (error:Error) => void) that the extension\n// host can call to run the tests. The test runner is expected to use console.log\n// to report the results back to the caller. When the tests are finished, return\n// a possible error to the callback or null if none.\nimport glob from 'glob';\nimport Mocha from 'mocha';\nimport * as path from 'path';\n\nimport { Globals } from '../src/globals';\nimport { Configuration } from './testConfiguration';\n\nGlobals.isTesting = true;\nGlobals.mockConfiguration = new Configuration();\n\nexport function run(): Promise<void> {\n  const mochaGrep = new RegExp(process.env.MOCHA_GREP || '');\n\n  // Create the mocha test\n  const mocha = new Mocha({\n    ui: 'tdd',\n    color: true,\n    timeout: 5000,\n    grep: mochaGrep,\n  });\n\n  const testsRoot = path.resolve(__dirname, '.');\n\n  return new Promise((c, e) => {\n    glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {\n      if (err) {\n        return e(err);\n      }\n\n      // Add files to the test suite\n      files.forEach((f) => mocha.addFile(path.resolve(testsRoot, f)));\n\n      try {\n        // Run the mocha test\n        mocha.run((failures) => {\n          if (failures > 0) {\n            e(new Error(`${failures} tests failed.`));\n          } else {\n            c();\n          }\n        });\n      } catch (error) {\n        console.error(error);\n        e(error as Error);\n      }\n    });\n  });\n}\n"
  },
  {
    "path": "test/jumpTracker.test.ts",
    "content": "import assert from 'assert/strict';\nimport * as vscode from 'vscode';\n\nimport { Position } from 'vscode';\nimport { JumpTracker } from '../src/jumps/jumpTracker';\nimport { Jump } from './../src/jumps/jump';\nimport { ITestObject, newTest, newTestSkip } from './testSimplifier';\n\nsuite('Record and navigate jumps', () => {\n  const newJumpTest = (options: ITestObject | Omit<ITestObject, 'title'>) => {\n    return newTest({\n      title: `Can track jumps for keys: ${options.keysPressed.replace(/\\n/g, '<CR>')}`,\n      ...options,\n    });\n  };\n\n  const newJumpTestSkipOnWindows = (options: ITestObject | Omit<ITestObject, 'title'>) => {\n    return newTestSkip(\n      {\n        title: `Can track jumps for keys: ${options.keysPressed.replace(/\\n/g, '<CR>')}`,\n        ...options,\n      },\n      process.platform === 'win32',\n    );\n  };\n\n  suite('Jump Tracker unit tests', () => {\n    const jump = (lineNumber: number, columnNumber: number, fileName?: string) =>\n      new Jump({\n        document: { fileName: fileName ?? 'Untitled' } as vscode.TextDocument,\n        position: new Position(lineNumber, columnNumber),\n      });\n    const file1 = jump(0, 0, 'file1');\n    const file2 = jump(0, 0, 'file2');\n    const file3 = jump(0, 0, 'file3');\n    const file4 = jump(0, 0, 'file4');\n    const range = (n: number) => Array.from(Array(n).keys());\n\n    test('Can record jumps between files', async () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.handleFileJump(undefined, file1);\n      jumpTracker.handleFileJump(file1, file2);\n      jumpTracker.handleFileJump(file2, file3);\n      jumpTracker.recordJumpBack(file3);\n      jumpTracker.recordJumpBack(file2);\n\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => j.fileName),\n        ['file1', 'file2', 'file3'],\n        'Unexpected jumps found',\n      );\n      assert.equal(jumpTracker.currentJump?.fileName, 'file1', 'Unexpected current jump found');\n      assert.equal(jumpTracker.currentJumpNumber, 0, 'Unexpected current jump number found');\n    });\n\n    test('Can handle file jump events sent by vscode in response to recordJumpBack', async () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.handleFileJump(undefined, file1);\n      jumpTracker.handleFileJump(file1, file2);\n      jumpTracker.handleFileJump(file2, file3);\n      jumpTracker.handleFileJump(file3, file4);\n\n      jumpTracker.isJumpingThroughHistory = true;\n      jumpTracker.recordJumpBack(file4);\n      jumpTracker.handleFileJump(file4, file3);\n\n      jumpTracker.isJumpingThroughHistory = true;\n      jumpTracker.recordJumpBack(file3);\n      jumpTracker.handleFileJump(file3, file2);\n\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => j.fileName),\n        ['file1', 'file2', 'file3', 'file4'],\n        'Unexpected jumps found',\n      );\n      assert.equal(jumpTracker.currentJump?.fileName, 'file2', 'Unexpected current jump found');\n      assert.equal(jumpTracker.currentJumpNumber, 1, 'Unexpected current jump number found');\n    });\n\n    test('Can record jumps between files after switching files', async () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.handleFileJump(undefined, file1);\n      jumpTracker.handleFileJump(file1, file2);\n      jumpTracker.handleFileJump(file2, file3);\n      jumpTracker.recordJumpBack(file3);\n      jumpTracker.handleFileJump(file2, file4);\n\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => j.fileName),\n        ['file1', 'file2', 'file3', 'file2'],\n        'Unexpected jumps found',\n      );\n      assert.equal(jumpTracker.currentJump, undefined, 'Unexpected current jump found');\n    });\n\n    test('Can handle jumps to the same file multiple times', async () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.handleFileJump(undefined, file1);\n      jumpTracker.handleFileJump(file1, file2);\n      jumpTracker.handleFileJump(file2, file3);\n      jumpTracker.handleFileJump(file3, file2);\n\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => j.fileName),\n        ['file1', 'file2', 'file3'],\n        'Unexpected jumps found',\n      );\n      assert.equal(jumpTracker.currentJump, undefined, 'Unexpected current jump found');\n    });\n\n    test('Can record up to 100 jumps, the fixed length in vanilla Vim', async () => {\n      const jumpTracker = new JumpTracker();\n\n      range(102).forEach((iteration: number) => {\n        jumpTracker.recordJump(jump(iteration, 0), jump(iteration + 1, 0));\n      });\n\n      assert.equal(jumpTracker.jumps.length, 100, 'Jump tracker should cut off jumps at 100');\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => j.position.line),\n        range(102).slice(2, 102),\n        \"Jump tracker doesn't contain the expected jumps after removing old jumps\",\n      );\n    });\n\n    test('Can handle recording \"from\" jump with no corresponding \"to\" jump', () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.recordJump(jump(0, 0));\n\n      assert.equal(jumpTracker.jumps.length, 1, 'Jump tracker failed to record \"from\"-only jump');\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => [j.position.line, j.position.character, j.fileName]),\n        [[0, 0, 'Untitled']],\n        `Jump tracker doesn't contain expected jumps after recording \"from\"-only jump`,\n      );\n    });\n\n    test('Can handle text deleted from a file', async () => {\n      const jumpTracker = new JumpTracker();\n\n      jumpTracker.recordJump(jump(0, 0, 'file2'), jump(5, 0, 'file2'));\n      jumpTracker.recordJump(jump(5, 0, 'file2'), jump(0, 0, 'file1'));\n      jumpTracker.recordJump(jump(0, 0, 'file1'), jump(3, 0, 'file1'));\n      jumpTracker.recordJump(jump(3, 0, 'file1'), jump(5, 0, 'file1'));\n      jumpTracker.recordJump(jump(5, 5, 'file1'), jump(6, 0, 'file1'));\n      jumpTracker.recordJump(jump(6, 0, 'file1'), jump(2, 0, 'file1'));\n\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => [j.position.line, j.position.character, j.fileName]),\n        [\n          [0, 0, 'file2'],\n          [5, 0, 'file2'],\n          [0, 0, 'file1'],\n          [3, 0, 'file1'],\n          [5, 5, 'file1'],\n          [6, 0, 'file1'],\n        ],\n        `Jump tracker doesn't contain the expected jumps before handling deleted text`,\n      );\n\n      // Note that this is just deleting lines 3 and 4.\n      // vscode sends us a range where the end position is just AFTER the deleted text,\n      // kind of like Array.slice.\n      jumpTracker.handleTextDeleted(\n        { fileName: 'file1' },\n        new vscode.Range(new vscode.Position(3, 0), new vscode.Position(5, 0)),\n      );\n\n      // Vim doesn't delete jumps at the deleted line, it just shifts other lines down\n      // Note the column number was preserved for newer jump when it found duplicates on a line.\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => [j.position.line, j.position.character, j.fileName]),\n        [\n          [0, 0, 'file2'],\n          [5, 0, 'file2'],\n          [0, 0, 'file1'],\n          [3, 5, 'file1'],\n          [4, 0, 'file1'],\n        ],\n        `Jump tracker doesn't contain the expected jumps after deleting two lines`,\n      );\n\n      jumpTracker.handleTextDeleted(\n        { fileName: 'file1' },\n        new vscode.Range(new vscode.Position(3, 0), new vscode.Position(4, 0)),\n      );\n\n      // If that results in multiple jumps on a line, though the duplicate is deleted\n      // Preserve the newest jump in that case\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => [j.position.line, j.position.character, j.fileName]),\n        [\n          [0, 0, 'file2'],\n          [5, 0, 'file2'],\n          [0, 0, 'file1'],\n          [3, 0, 'file1'],\n        ],\n        `Jump tracker doesn't contain the expected jumps after deleting another line`,\n      );\n\n      jumpTracker.handleTextDeleted(\n        { fileName: 'file1' },\n        new vscode.Range(new vscode.Position(0, 0), new vscode.Position(3, 0)),\n      );\n\n      // If you delete lines such that jumps are past EOF, delete the jumps\n      assert.deepEqual(\n        jumpTracker.jumps.map((j) => [j.position.line, j.position.character, j.fileName]),\n        [\n          [0, 0, 'file2'],\n          [5, 0, 'file2'],\n          [0, 0, 'file1'],\n        ],\n        `Jump tracker doesn't contain the expected jumps after deleting all lines in file`,\n      );\n    });\n  });\n\n  suite('Can record jumps for actions the same as vanilla Vim', () => {\n    suite('Can track basic jumps', () => {\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'Ggg',\n        end: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['start', 'end'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'GggG',\n        end: ['start', '{', 'a1', 'b1', 'a2', 'b2', '}', '|end'],\n        jumps: ['end', 'start'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'GggGgg',\n        end: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['start', 'end'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: '/b\\nn',\n        end: ['start', '{', 'a1', 'b1', 'a2', '|b2', '}', 'end'],\n        jumps: ['start', 'b1'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'G?b\\nggG',\n        end: ['start', '{', 'a1', 'b1', 'a2', 'b2', '}', '|end'],\n        jumps: ['end', 'b2', 'start'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%',\n        end: ['start', '|{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['{', '}'],\n      });\n      newJumpTest({\n        start: ['one', 'two', 'th|ree', 'four', 'five'],\n        keysPressed: 'gg' + '3gg' + '4gg',\n        end: ['one', 'two', 'three', '|four', 'five'],\n        jumps: ['one', 'three'],\n      });\n    });\n\n    suite('Can track jumps with back/forward', () => {\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%<C-o>',\n        end: ['start', '{', 'a1', 'b1', 'a2', 'b2', '|}', 'end'],\n        jumps: ['|}', '{'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%<C-o><C-i>',\n        end: ['start', '|{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['}', '|{'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%<C-o>%',\n        end: ['start', '|{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['{', '}'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%<C-o>gg',\n        end: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['{', '}'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'j%%<C-o><C-o>gg',\n        end: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['{', '}'],\n      });\n      newJumpTest({\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: '/^\\nnnn<C-o><C-o><C-o><C-i>gg',\n        end: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        jumps: ['start', '{', 'b1', 'a2', 'a1'],\n      });\n      newJumpTest({\n        title: 'Can enter number to jump back multiple times',\n        start: ['|start', '{', 'a1', 'b1', 'a2', 'b2', '}', 'end'],\n        keysPressed: 'Gggj%2<C-o>',\n        end: ['start', '{', 'a1', 'b1', 'a2', 'b2', '}', '|end'],\n        jumps: ['start', '|end', '{', '}'],\n      });\n      newJumpTest({\n        title: 'Can track one-line `` jumps',\n        start: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        keysPressed: 'jf{%r]``r[',\n        end: ['start', 'var foo = |[\"a\", \"b\"]', 'end'],\n        jumps: ['var foo = [\"a\", \"b\"]'],\n      });\n      newJumpTest({\n        title: 'Can track one-line double `` jumps',\n        start: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        keysPressed: 'jf{%r]``r[``',\n        end: ['start', 'var foo = [\"a\", \"b\"|]', 'end'],\n        jumps: ['var foo = [\"a\", \"b\"]'],\n      });\n      newJumpTest({\n        title: \"Can track one-line '' jumps\",\n        start: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        keysPressed: \"jf{%r]``r[''\",\n        end: ['start', '|var foo = [\"a\", \"b\"]', 'end'],\n        jumps: ['var foo = [\"a\", \"b\"]'],\n      });\n      newJumpTest({\n        title: \"Can track one-line double '' jumps\",\n        start: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        keysPressed: \"jf{%r]``r[''''\",\n        end: ['start', '|var foo = [\"a\", \"b\"]', 'end'],\n        jumps: ['var foo = [\"a\", \"b\"]'],\n      });\n      newJumpTest({\n        title: \"Can handle '' jumps with no previous jump\",\n        start: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        keysPressed: \"''\",\n        end: ['|start', 'var foo = {\"a\", \"b\"}', 'end'],\n        jumps: [],\n      });\n    });\n\n    suite('Can shifts jump lines up after deleting a line with Visual Line Mode', () => {\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkkdd',\n        end: ['start', 'a1', '|a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a3'],\n      });\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkdd',\n        end: ['start', 'a1', 'a2', '|a4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a2', 'a4'],\n      });\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnnn<C-o><C-o><C-o><C-o>dd',\n        end: ['start', 'a1', '|a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a1', '|a3', 'a4', 'a5', 'end'],\n      });\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/a4\\n/a5\\nkkkdd',\n        end: ['start', 'a1', '|a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a4'],\n      });\n    });\n\n    suite('Can shifts jump lines up after deleting a line with Visual Mode', () => {\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkklvjjhx',\n        end: ['start', 'a1', 'a|4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a4'],\n      });\n    });\n\n    suite('Can shift jump lines down after inserting a line', () => {\n      // TODO(#4844): this fails on Windows\n      newJumpTestSkipOnWindows({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkkoINSERTED<Esc>0',\n        end: ['start', 'a1', 'a2', '|INSERTED', 'a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a2', 'a3'],\n      });\n      // TODO(#4844): this fails on Windows\n      newJumpTestSkipOnWindows({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkoINSERTED<Esc>0',\n        end: ['start', 'a1', 'a2', 'a3', '|INSERTED', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a2', 'a3'],\n      });\n      // TODO(#4844): this fails on Windows\n      newJumpTestSkipOnWindows({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/^\\nnnnkOINSERTED<Esc>0',\n        end: ['start', 'a1', 'a2', '|INSERTED', 'a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a1', 'a2', 'a3'],\n      });\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'end'],\n        keysPressed: '/a4\\n/a5\\nkkkoINSERTED<Esc>0',\n        end: ['start', 'a1', 'a2', '|INSERTED', 'a3', 'a4', 'a5', 'end'],\n        jumps: ['start', 'a4'],\n      });\n    });\n\n    suite('Can track jumps from substitutes', () => {\n      newJumpTest({\n        start: ['|a1', 'a2', 'a3'],\n        keysPressed: ':%s/a/b\\n',\n        end: ['b1', 'b2', '|b3'],\n        jumps: ['b3'],\n      });\n    });\n\n    suite('Can track jumps from macros', () => {\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'end'],\n        keysPressed: 'qq/^\\nnq@q@q<C-o><C-o>',\n        end: ['start', 'a1', 'a2', 'a3', '|a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'end'],\n        jumps: ['start', 'a1', 'a2', 'a3', '|a4', 'a5', 'a6'],\n      });\n    });\n\n    suite('Can track jumps from marks', () => {\n      newJumpTest({\n        start: ['|start', 'a1', 'a2', 'a3', 'end'],\n        keysPressed: 'maG`a',\n        end: ['|start', 'a1', 'a2', 'a3', 'end'],\n        jumps: ['start', 'end'],\n      });\n    });\n\n    // TODO: Test that jumps are adjusted properly when document is modified externally\n  });\n});\n"
  },
  {
    "path": "test/macro.test.ts",
    "content": "import { Mode } from '../src/mode/mode';\nimport { newTest, newTestWithRemaps } from './testSimplifier';\nimport { setupWorkspace } from './testUtils';\n\nsuite('Record and execute a macro', () => {\n  setup(async () => {\n    await setupWorkspace({\n      config: {\n        // for testing with <leader>\n        camelCaseMotion: { enable: true },\n      },\n    });\n  });\n\n  newTest({\n    title: 'Can record and execute',\n    start: ['|foo = 1', \"bar = 'a'\", 'foobar = foo + bar'],\n    keysPressed: 'qaA;<Esc>Ivar <Esc>qj@a',\n    end: ['var foo = 1;', \"var| bar = 'a';\", 'foobar = foo + bar'],\n  });\n\n  newTest({\n    title: 'Can repeat last invoked macro',\n    start: ['|foo = 1', \"bar = 'a'\", 'foobar = foo + bar'],\n    keysPressed: 'qaA;<Esc>Ivar <Esc>qj@aj@@',\n    end: ['var foo = 1;', \"var bar = 'a';\", 'var| foobar = foo + bar;'],\n  });\n\n  newTest({\n    title: 'Can play back with count',\n    start: ['|\"(\"+a+\",\"+b+\",\"+c+\",\"+d+\",\"+e+\")\"'],\n    keysPressed: 'f+s + <Esc>qq;.q8@q',\n    end: ['\"(\" + a + \",\" + b + \",\" + c + \",\" + d + \",\" + e +| \")\"'],\n  });\n\n  newTest({\n    title: 'Can play back with count, abort when a motion fails',\n    start: ['|\"(\"+a+\",\"+b+\",\"+c+\",\"+d+\",\"+e+\")\"'],\n    keysPressed: 'f+s + <Esc>qq;.q22@q',\n    end: ['\"(\" + a + \",\" + b + \",\" + c + \",\" + d + \",\" + e +| \")\"'],\n  });\n\n  newTest({\n    title: 'Repeat change on contiguous lines',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: 'qa0f.r)w~jq3@a',\n    end: ['1) One', '2) Two', '3) Three', '4) F|our'],\n  });\n\n  newTest({\n    title: 'Repeat insertion with arrow keys and <BS>',\n    start: ['o|ne two three', 'four five six'],\n    keysPressed: 'qk' + 'A' + ' tpyo' + '<left><BS><left>y' + '<Esc>' + 'q' + 'j0' + '@k',\n    end: ['one two three typo', 'four five six t|ypo'],\n  });\n\n  newTest({\n    title: 'Append command to a macro',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: 'qa0f.r)qqAw~jq3@a',\n    end: ['1) One', '2) Two', '3) Three', '4) F|our'],\n  });\n\n  newTest({\n    title: 'Append command to a not yet created register creates a new register',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: 'qB0f.r)w~jq3@b',\n    end: ['1) One', '2) Two', '3) Three', '4) F|our'],\n  });\n\n  newTest({\n    title: 'Can handle calling an uppercase register',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: 'qa0f.r)w~jq3@A',\n    end: ['1) One', '2) Two', '3) Three', '4) F|our'],\n  });\n\n  newTest({\n    title: 'Can handle calling a non existing macro',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: '@x',\n    end: ['1. |one', '2. two', '3. three', '4. four'],\n  });\n\n  newTest({\n    title: 'Can handle calling a non existing macro with uppercase letter',\n    start: ['1. |one', '2. two', '3. three', '4. four'],\n    keysPressed: '@Z',\n    end: ['1. |one', '2. two', '3. three', '4. four'],\n  });\n\n  newTest({\n    title: 'Can record Ctrl Keys and repeat',\n    start: ['1|.'],\n    keysPressed: 'qayyp<C-a>q4@a',\n    end: ['1.', '2.', '3.', '4.', '5.', '|6.'],\n  });\n\n  newTest({\n    title: 'Can execute macros with dot commands properly',\n    start: ['|test', 'test', 'test', 'test', 'test', 'test', 'test'],\n    keysPressed: 'qadd.q@a@a',\n    end: ['|test'],\n  });\n\n  suite('`:` (command) register used as macro', () => {\n    newTest({\n      title: 'Repeat :s',\n      start: ['|old', 'old', 'old'],\n      keysPressed: ':s/old/new\\nj@:j@@',\n      end: ['new', 'new', '|new'],\n    });\n\n    newTest({\n      title: 'Repeat :d',\n      start: ['one', 't|wo', 'three', 'four', 'five'],\n      keysPressed: ':d/\\n' + '@:' + '@@',\n      end: ['one', '|five'],\n    });\n\n    newTest({\n      title: 'Repeat :co',\n      start: ['|one', 'two'],\n      keysPressed: ':.co$\\n' + '@:',\n      end: ['one', 'two', '|one', 'one'], // TODO: Cursor should be on line 3, not 4\n    });\n  });\n\n  suite('`:` (command) register used as macro and command with leader key', () => {\n    newTest({\n      title: 'Repeat :s and command with leader key',\n      config: { leader: 'o' },\n      start: ['|old', 'old', 'old'],\n      keysPressed: ':s/old/new\\nj@:j@@',\n      end: ['new', 'new', '|new'],\n    });\n  });\n\n  newTest({\n    title: 'Can record and execute macro that handles multiple lines',\n    start: ['|Countdown:', '1', 'LAUNCH!!!'],\n    keysPressed: 'qajyyP<C-a>kq8@a',\n    end: ['C|ountdown:', '10', '9', '8', '7', '6', '5', '4', '3', '2', '1', 'LAUNCH!!!'],\n  });\n\n  newTest({\n    title: 'Failed `n` stops macro from repeating',\n    config: { wrapscan: false },\n    start: ['|one two three', 'one two three', 'one two three'],\n    keysPressed: '/two\\n0' + 'qq' + 'nea XXX<Esc>q' + '5@q',\n    end: ['one two XXX three', 'one two XXX three', 'one two XX|X three'],\n  });\n\n  newTest({\n    title: 'q[A-Z] (action) Can record and append to a macro',\n    start: ['|'],\n    keysPressed:\n      'qb' +\n      'i' +\n      'one two ' +\n      '<Esc>q' +\n      'o<Esc>@b' +\n      'o<Esc>' +\n      'qB' +\n      'i' +\n      'three four' +\n      '<Esc>q' +\n      'o<Esc>@b',\n    end: ['one two ', 'one two ', 'three four', 'one twothree fou|r '],\n  });\n\n  newTest({\n    title: 'q[A-Z] (action) Creates new register, accessible by [a-z]',\n    start: ['|'],\n    keysPressed: 'qB' + 'i' + 'one two' + '<Esc>q' + 'o<Esc>@b',\n    end: ['one two', 'one tw|o'],\n  });\n\n  newTest({\n    title: 'Invalid register throws E354',\n    start: ['one t|wo three'],\n    keysPressed: '@~',\n    end: ['one t|wo three'],\n    statusBar: \"E354: Invalid register name: '~'\",\n  });\n\n  for (const register of ['%', '#']) {\n    newTest({\n      title: `Filename register '${register}' throw E354`,\n      start: ['one t|wo three'],\n      keysPressed: `@${register}`,\n      end: ['one t|wo three'],\n      statusBar: `E354: Invalid register name: '${register}'`,\n    });\n  }\n\n  newTest({\n    title: '`@@` before a macro has been run throws E748',\n    start: ['one t|wo three'],\n    keysPressed: '@@',\n    end: ['one t|wo three'],\n    statusBar: 'E748: No previously used register',\n  });\n\n  suite('Text copied into register can be run as a macro', () => {\n    const start = ['one', 'two', 'three'];\n    const register = 'x';\n    const testCases: Array<[string, string[]]> = [\n      ['', ['|one', 'two', 'three']],\n      ['j', ['one', '|two', 'three']],\n      ['2j', ['one', 'two', '|three']],\n\n      ['A' + ', uno<Esc>', ['one, un|o', 'two', 'three']],\n\n      ['dd', ['|two', 'three']],\n\n      ['yyp', ['one', '|one', 'two', 'three']],\n\n      ['VGJ', ['one two| three']],\n\n      ['gUU' + 'j.' + 'j.', ['ONE', 'TWO', '|THREE']],\n\n      [':2d\\\\n', ['one', '|three']],\n\n      ['jjl~l~0' + '<leader>w' + '<leader>w', ['one', 'two', 'tHr|Ee']],\n\n      // TODO: control characters...\n    ];\n    for (const [macro, end] of testCases) {\n      newTest({\n        title: `macro='${macro}'`,\n        start: [`|${macro}`, ...start],\n        keysPressed: `\"${register}dd` + `@${register}`,\n        end,\n        endMode: Mode.Normal,\n      });\n    }\n\n    newTest({\n      title: `test @@ - 1`,\n      start: [`|j`, ...start],\n      keysPressed: `\"${register}dd` + `@${register}` + `@@`,\n      end: ['one', 'two', '|three'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: `test @@ - 2`,\n      start: [`|dd`, ...start],\n      keysPressed: `\"${register}dd` + `@${register}` + `@@`,\n      end: ['|three'],\n      endMode: Mode.Normal,\n    });\n\n    newTestWithRemaps({\n      title: 'test with remaps: simple',\n      start: [`|J`, ...start],\n      remaps: ['nmap J jj'],\n      steps: [\n        {\n          // Step 0:\n          keysPressed: `\"${register}dd` + `@${register}`,\n          stepResult: {\n            end: ['one', 'two', '|three'],\n          },\n        },\n      ],\n    });\n\n    newTestWithRemaps({\n      title: 'test with remaps: repeat',\n      start: [`|Pm`, ...start],\n      remaps: ['nmap Pm Cabc<Esc>'],\n      steps: [\n        {\n          // Step 0:\n          keysPressed: `\"${register}dd` + `@${register}`,\n          stepResult: {\n            end: ['ab|c', 'two', 'three'],\n          },\n        },\n        {\n          // Step 1:\n          keysPressed: 'j0' + `@@`,\n          stepResult: {\n            end: ['abc', 'ab|c', 'three'],\n          },\n        },\n      ],\n    });\n\n    newTestWithRemaps({\n      title: 'test with remaps: leader',\n      start: [`|<leader>J`, ...start],\n      remaps: ['nmap <leader>J jj'],\n      steps: [\n        {\n          // Step 0:\n          keysPressed: `\"${register}dd` + `@${register}`,\n          stepResult: {\n            end: ['one', 'two', '|three'],\n          },\n        },\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "test/marks.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport * as vscode from 'vscode';\nimport { getAndUpdateModeHandler } from '../extensionBase';\nimport { Mode } from '../src/mode/mode';\nimport { ModeHandler } from '../src/mode/modeHandler';\nimport { ModeHandlerMap } from '../src/mode/modeHandlerMap';\nimport { newTest, newTestSkip } from './testSimplifier';\nimport { setupWorkspace } from './testUtils';\n\nsuite('Marks', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  const jumpToNewFile = async () => {\n    await setupWorkspace({\n      config: {\n        tabstop: 4,\n        expandtab: false,\n      },\n      forceNewFile: true,\n      disableCleanUp: true,\n    });\n    return (await getAndUpdateModeHandler())!;\n  };\n\n  newTest({\n    title: 'Can jump to lowercase mark',\n    start: ['|hello world and mars'],\n    keysPressed: 'wma2w`a',\n    end: ['hello |world and mars'],\n    endMode: Mode.Normal,\n  });\n\n  suite(\"'< and '>\", () => {\n    newTest({\n      title: \"'< set by Visual mode\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'vjl<Esc>' + 'gg' + '`<',\n      end: ['one', 't|wo', 'three'],\n    });\n    newTest({\n      title: \"'> set by Visual mode\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'vjl<Esc>' + 'gg' + '`>',\n      end: ['one', 'two', 'th|ree'],\n    });\n\n    newTest({\n      title: \"'< set by Visual mode (backward selection)\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'vjlo<Esc>' + 'gg' + '`<',\n      end: ['one', 't|wo', 'three'],\n    });\n    newTest({\n      title: \"'> set by Visual mode (backward selection)\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'vjlo<Esc>' + 'gg' + '`>',\n      end: ['one', 'two', 'th|ree'],\n    });\n\n    newTest({\n      title: \"'< set by m<\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'm<' + 'gg' + '`<',\n      end: ['one', 't|wo', 'three'],\n    });\n    newTest({\n      title: \"'> set by m>\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'm>' + 'gg' + '`>',\n      end: ['one', 't|wo', 'three'],\n    });\n\n    newTest({\n      title: \"gv fails if '< is not set\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'm>' + 'gg' + 'gv',\n      end: ['|one', 'two', 'three'],\n      endMode: Mode.Normal,\n    });\n    newTest({\n      title: \"gv fails if '> is not set\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'm<' + 'gg' + 'gv',\n      end: ['|one', 'two', 'three'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'gv is affected by m<',\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'v<Esc>' + 'k' + 'm<' + 'gg' + 'gvd',\n      end: ['o|o', 'three'],\n    });\n    newTest({\n      title: 'gv is affected by m>',\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'v<Esc>' + 'j' + 'm>' + 'gg' + 'gvd',\n      end: ['one', 't|ree'],\n    });\n    newTestSkip({\n      // TODO: Seems to work... why does this fail?\n      title: 'gv is affected by m< and m>',\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'm<' + 'j' + 'm>' + 'gg' + 'gvd',\n      end: ['one', 't|ree'],\n    });\n\n    newTest({\n      title: \"m< AFTER '> sets '> too\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'v<Esc>' + 'j' + 'm<' + 'gg' + '`>',\n      end: ['one', 'two', 't|hree'],\n    });\n    newTest({\n      title: \"m> BEFORE '< sets '< too\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'v<Esc>' + 'k' + 'm>' + 'gg' + '`<',\n      end: ['o|ne', 'two', 'three'],\n    });\n    newTest({\n      title: \"gv fails if '< is after '>\",\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'v<Esc>' + 'j' + 'm<' + 'gg' + 'gv',\n      end: ['|one', 'two', 'three'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('global marks', () => {\n    let otherModeHandler: ModeHandler;\n\n    setup(async () => {\n      await setupWorkspace();\n      modeHandler = (await getAndUpdateModeHandler())!;\n\n      const firstDocumentName = vscode.window.activeTextEditor!.document.fileName;\n      await modeHandler.handleMultipleKeyEvents('mA'.split(''));\n      otherModeHandler = await jumpToNewFile();\n\n      const otherDocumentName = vscode.window.activeTextEditor!.document.fileName;\n      assert.notStrictEqual(firstDocumentName, otherDocumentName);\n    });\n\n    teardown(async () => {\n      ModeHandlerMap.delete(otherModeHandler.vimState.document.uri);\n    });\n\n    newTest({\n      title: 'changes the active document',\n      start: ['|'],\n      keysPressed: \"'A\",\n      end: ['|'],\n      endFsPath: () => modeHandler.vimState.document.uri.fsPath,\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/modeHandler.test.ts",
    "content": "import * as assert from 'assert';\nimport { window, workspace } from 'vscode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { ModeHandlerMap } from '../../src/mode/modeHandlerMap';\n\nsuite('ModeHandler', () => {\n  setup(() => {\n    ModeHandlerMap.clear();\n  });\n\n  teardown(() => {\n    ModeHandlerMap.clear();\n  });\n\n  test('ModeHandlerMap', async () => {\n    let isNew: boolean;\n\n    assert.deepStrictEqual([...ModeHandlerMap.entries()], []);\n\n    const document1 = await workspace.openTextDocument({ content: 'document1' });\n    const editor1 = await window.showTextDocument(document1);\n\n    let modeHandler1: ModeHandler;\n    {\n      [modeHandler1, isNew] = await ModeHandlerMap.getOrCreate(editor1);\n      assert.strictEqual(isNew, true);\n      assert.notStrictEqual(modeHandler1, undefined);\n      assert.deepStrictEqual(\n        new Set(ModeHandlerMap.entries()),\n        new Set([[document1.uri, modeHandler1]]),\n      );\n    }\n\n    const document2 = await workspace.openTextDocument({ content: 'document2' });\n    const editor2 = await window.showTextDocument(document2);\n\n    let modeHandler2: ModeHandler;\n    {\n      [modeHandler2, isNew] = await ModeHandlerMap.getOrCreate(editor2);\n      assert.strictEqual(isNew, true);\n      assert.notStrictEqual(modeHandler2, undefined);\n      assert.notStrictEqual(modeHandler1, modeHandler2);\n      assert.deepStrictEqual(\n        new Set(ModeHandlerMap.entries()),\n        new Set([\n          [document1.uri, modeHandler1],\n          [document2.uri, modeHandler2],\n        ]),\n      );\n    }\n\n    // TODO: test closing editor, opening another for same document\n    // TODO: test delete\n  });\n});\n"
  },
  {
    "path": "test/mode/modeInsert.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { Configuration } from '../testConfiguration';\nimport { newTest } from '../testSimplifier';\nimport { assertEqualLines, reloadConfiguration, setupWorkspace } from './../testUtils';\n\nsuite('Mode Insert', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('can be activated', async () => {\n    const activationKeys = ['o', 'I', 'i', 'O', 'a', 'A', '<Insert>'];\n\n    for (const key of activationKeys) {\n      await modeHandler.handleKeyEvent('<Esc>');\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n      await modeHandler.handleKeyEvent(key);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    }\n  });\n\n  test('can handle key events', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', '!']);\n\n    return assertEqualLines(['!']);\n  });\n\n  test('<Esc> should change cursor position', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 'h', 'e', 'l', 'l', 'o', '<Esc>']);\n\n    assert.strictEqual(\n      vscode.window.activeTextEditor!.selection.start.character,\n      4,\n      '<Esc> moved cursor position.',\n    );\n  });\n\n  test('<C-c> can exit insert', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 't', 'e', 'x', 't', '<C-c>', 'o']);\n\n    return assertEqualLines(['text', '']);\n  });\n\n  test('<copy> should not override system-clipboard after exiting insert mode', async () => {\n    const yankTextAtSystemClipboard = ['i', 't', 'e', 'x', 't', '<Esc>', 'v', 'i', 'w', '<copy>'];\n\n    const pasteTextAtInsertMode = ['a', '<C-r>', '+', '<copy>'];\n\n    await modeHandler.handleMultipleKeyEvents([\n      ...yankTextAtSystemClipboard,\n      ...pasteTextAtInsertMode,\n      '$',\n      ...pasteTextAtInsertMode,\n    ]);\n\n    return assertEqualLines(['texttexttext']);\n  });\n\n  test('<Esc> can exit insert', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 't', 'e', 'x', 't', '<Esc>', 'o']);\n\n    return assertEqualLines(['text', '']);\n  });\n\n  test('Stay in insert when entering characters', async () => {\n    await modeHandler.handleKeyEvent('i');\n    for (let i = 0; i < 10; i++) {\n      await modeHandler.handleKeyEvent('1');\n      assert.strictEqual(modeHandler.vimState.currentMode === Mode.Insert, true);\n    }\n  });\n\n  test(\"Can handle 'O'\", async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', 't', 'e', 'x', 't', '<Esc>', 'O']);\n\n    return assertEqualLines(['', 'text']);\n  });\n\n  newTest({\n    title: \"'i' puts you in insert mode before the cursor\",\n    start: ['text|text'],\n    keysPressed: 'i!',\n    end: ['text!|text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'I' puts you in insert mode at start of line\",\n    start: ['text|text'],\n    keysPressed: 'I!',\n    end: ['!|texttext'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'a' puts you in insert mode after the cursor\",\n    start: ['text|text'],\n    keysPressed: 'a!',\n    end: ['textt!|ext'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'A' appends to end of line\",\n    start: ['t|ext'],\n    keysPressed: 'A!',\n    end: ['text!|'],\n    endMode: Mode.Insert,\n  });\n\n  suite('<C-w>', () => {\n    newTest({\n      title: '`<C-w>` deletes a word',\n      start: ['text text| text'],\n      keysPressed: 'i<C-w>',\n      end: ['text | text'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`<C-w>` in whitespace deletes whitespace and prior word',\n      start: ['one two     | three'],\n      keysPressed: 'i<C-w>',\n      end: ['one | three'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`<C-w>` on leading whitespace deletes to start of line',\n      start: ['foo', '  |bar'],\n      keysPressed: 'i<C-w>',\n      end: ['foo', '|bar'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`<C-w>` at beginning of line deletes line break',\n      start: ['foo', '|bar'],\n      keysPressed: 'i<C-w>',\n      end: ['foo|bar'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`<C-w>` at beginning of document does nothing',\n      start: ['|foo', 'bar'],\n      keysPressed: 'i<C-w>',\n      end: ['|foo', 'bar'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('<C-u>', () => {\n    newTest({\n      title: '<C-u> deletes to start of line',\n      start: ['text |text'],\n      keysPressed: 'i<C-u>',\n      end: ['|text'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'Can handle <C-u> on leading characters',\n      start: ['{', '  foo: |true', '}'],\n      keysPressed: 'i<C-u>',\n      end: ['{', '  |true', '}'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'Can handle <C-u> on leading whitespace',\n      start: ['{', '  |true', '}'],\n      keysPressed: 'i<C-u>',\n      end: ['{', '|true', '}'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-u> at start of line deletes line break',\n      start: ['one', '|two', 'three'],\n      keysPressed: 'i<C-u>',\n      end: ['one|two', 'three'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  test('Correctly places the cursor after deleting the previous line break', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      'i',\n      'o',\n      'n',\n      'e',\n      '\\n',\n      't',\n      'w',\n      'o',\n      '<left>',\n      '<left>',\n      '<left>',\n      '<BS>',\n    ]);\n\n    assertEqualLines(['onetwo']);\n\n    assert.strictEqual(\n      vscode.window.activeTextEditor!.selection.start.character,\n      3,\n      '<BS> moved cursor to correct position',\n    );\n  });\n\n  test('will not remove leading spaces input by user', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', ' ', ' ', '<Esc>']);\n\n    assertEqualLines(['  ']);\n  });\n\n  test('<BS> removes closing bracket just inserted', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', '(']);\n\n    assertEqualLines(['()']);\n\n    await modeHandler.handleMultipleKeyEvents(['<BS>', '<Esc>']);\n\n    assertEqualLines(['']);\n  });\n\n  test('<BS> does not remove closing bracket inserted before', async () => {\n    await modeHandler.handleMultipleKeyEvents(['i', '(', '<Esc>']);\n\n    assertEqualLines(['()']);\n\n    await modeHandler.handleMultipleKeyEvents(['a', '<BS>', '<Esc>']);\n\n    assertEqualLines([')']);\n  });\n\n  newTest({\n    title: 'Backspace works on end of whitespace only lines',\n    start: ['abcd', '     | '],\n    keysPressed: 'a<BS><Esc>',\n    end: ['abcd', '   | '],\n  });\n\n  newTest({\n    title: 'Backspace works at beginning of file',\n    start: ['|bcd'],\n    keysPressed: 'i<BS>a<Esc>',\n    end: ['|abcd'],\n  });\n\n  newTest({\n    title: 'Backspace in leading whitespace 1',\n    start: ['        |    xyz'],\n    editorOptions: {\n      tabSize: 4,\n    },\n    keysPressed: 'i<BS>',\n    end: ['    |    xyz'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Backspace in leading whitespace 2',\n    start: ['       |    xyz'],\n    editorOptions: {\n      tabSize: 4,\n    },\n    keysPressed: 'i<BS>',\n    end: ['    |    xyz'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Delete works in insert mode',\n    start: ['leather ma|an'],\n    keysPressed: 'i<Del><Esc>',\n    end: ['leather m|an'],\n  });\n\n  newTest({\n    title: 'Delete works at line end',\n    start: ['boy next| ', 'door'],\n    keysPressed: 'a<Del><Esc>',\n    end: ['boy next| door'],\n  });\n\n  newTest({\n    title: 'Delete works at end of file',\n    start: ['yes si|r'],\n    keysPressed: 'a<Del><Esc>',\n    end: ['yes si|r'],\n  });\n\n  newTest({\n    title: 'Delete works with repeat',\n    start: ['This is| not good'],\n    keysPressed: 'a<Del><Del><Del><Del><Esc>.aawesome!<Esc>',\n    end: ['This is awesome|!'],\n  });\n\n  newTest({\n    title: 'Repeat insert by count times with dot',\n    start: ['give me an a|:'],\n    keysPressed: 'aa<Esc>4.',\n    end: ['give me an a:aaaa|a'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title:\n      'Can perform <Esc> to exit and move cursor back one character from the most right position',\n    start: ['|testtest'],\n    keysPressed: 'A<Esc>',\n    end: ['testtes|t'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can perform <Esc> to exit and move cursor back one character from middle of text',\n    start: ['test|test'],\n    keysPressed: 'i<Esc>',\n    end: ['tes|ttest'],\n    endMode: Mode.Normal,\n  });\n\n  suite('<C-o>', () => {\n    newTest({\n      title: 'Can <Esc> after entering insert mode from <ctrl+o>',\n      start: ['|'],\n      keysPressed: 'i<C-o>i<Esc>',\n      end: ['|'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can perform <ctrl-o> after entering insert mode from <ctrl-o>',\n      start: ['test|test'],\n      keysPressed: 'i<C-o>i<C-o>',\n      end: ['test|test'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title:\n        'Can perform <ctrl-o> to exit and perform one command in normal at the beginning of a line',\n      start: ['|testtest'],\n      keysPressed: 'i<C-o>l123',\n      end: ['t123|esttest'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title:\n        'Can perform <ctrl-o> to exit and perform one command in normal at the middle of a row',\n      start: ['test|test'],\n      keysPressed: 'i<C-o>l123',\n      end: ['testt123|est'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'Can perform <ctrl-o> to exit and perform one command in normal at the end of a row',\n      start: ['testtest|'],\n      keysPressed: 'a123<C-o>zz',\n      end: ['testtest123|'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'Can perform <ctrl-o> to exit and paste',\n      start: ['|XXX', '123456'],\n      keysPressed: 'ye' + 'j' + 'A<C-o>p',\n      end: ['XXX', '123456XXX|'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'Can perform <ctrl-o> to exit and paste',\n      start: ['|XXX', '123456'],\n      keysPressed: 'ye' + 'j2|' + 'i<C-o>p',\n      end: ['XXX', '12XXX|3456'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  newTest({\n    title: 'Can perform insert command prefixed with count',\n    start: ['tes|t'],\n    keysPressed: '2i_<Esc>',\n    end: ['tes_|_t'],\n  });\n\n  newTest({\n    title: 'Can perform append command prefixed with count',\n    start: ['tes|t'],\n    keysPressed: '3a=<Esc>',\n    end: ['test==|='],\n  });\n\n  newTest({\n    title: 'Can perform insert at start of line command prefixed with count',\n    start: ['tes|t'],\n    keysPressed: '2I_<Esc>',\n    end: ['_|_test'],\n  });\n\n  newTest({\n    title: 'Can perform append to end of line command prefixed with count',\n    start: ['t|est'],\n    keysPressed: '3A=<Esc>',\n    end: ['test==|='],\n  });\n\n  newTest({\n    title: 'Can perform change char (s) command prefixed with count',\n    start: ['tes|ttest'],\n    keysPressed: '3s=====<Esc>',\n    end: ['tes====|=st'],\n  });\n\n  newTest({\n    title: 'Can perform command prefixed with count with <C-[>',\n    start: ['|'],\n    keysPressed: '3i*<C-[>',\n    end: ['**|*'],\n  });\n\n  newTest({\n    title: \"Can handle 'o' with count\",\n    start: ['|foobar'],\n    keysPressed: '5ofun<Esc>',\n    end: ['foobar', 'fu|n', 'fun', 'fun', 'fun', 'fun'],\n  });\n\n  newTest({\n    title: \"Can handle 'O' with count\",\n    start: ['|foobar'],\n    keysPressed: '5Ofun<Esc>',\n    end: ['fun', 'fun', 'fun', 'fun', 'fu|n', 'foobar'],\n  });\n\n  // This corner case caused an issue, see #3915\n  newTest({\n    title: 'Can handle backspace at beginning of line with all spaces',\n    start: ['abc', '|     '],\n    keysPressed: 'i<BS><Esc>',\n    end: ['ab|c     '],\n  });\n\n  test('Can handle digraph insert', async () => {\n    await modeHandler.handleMultipleKeyEvents([\n      'i',\n      't',\n      'e',\n      'x',\n      't',\n      '<C-k>',\n      '-',\n      '>',\n      't',\n      'e',\n      'x',\n      't',\n      '<C-k>',\n      '>',\n      '-',\n    ]);\n    assertEqualLines(['text→text→']);\n  });\n\n  test('Can handle custom digraph insert', async () => {\n    await reloadConfiguration(new Configuration({ digraphs: { 'R!': ['🚀', [55357, 56960]] } }));\n    await modeHandler.handleMultipleKeyEvents(['i', '<C-k>', 'R', '!', '<C-k>', '!', 'R']);\n    assertEqualLines(['🚀🚀']);\n  });\n\n  newTest({\n    title: 'Can insert custom digraph made with :dig[raphs]`',\n    start: ['|'],\n    keysPressed: ':dig R! 55357 56960\\n' + 'i<C-k>R!',\n    end: ['🚀|'],\n    endMode: Mode.Insert,\n  });\n\n  suite('<C-a>', () => {\n    newTest({\n      title: 'Basic <C-a> test',\n      start: ['tes|t'],\n      keysPressed: 'a' + 'hello' + '<Esc>' + 'a' + '<C-a>',\n      end: ['testhellohello|'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-a> with <BS>',\n      start: ['tes|t'],\n      keysPressed: 'i' + '<BS>' + '<Esc>' + 'a' + '<C-a>',\n      end: ['t|t'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-a> with <BS> then regular character',\n      start: ['tes|t'],\n      keysPressed: 'i' + '<BS>1' + '<Esc>' + 'i' + '<C-a>',\n      end: ['t1|1t'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-a> with arrows ignores everything before last arrow',\n      start: ['one |two three'],\n      keysPressed: 'i' + 'X<left>Y<left>Z' + '<Esc>' + 'W' + 'i' + '<C-a>',\n      end: ['one ZYXtwo Z|three'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-a> insertion with arrows always inserts just before cursor',\n      start: ['o|ne two three'],\n      keysPressed: 'A' + 'X<left>Y<left>Z' + '<Esc>' + '0W' + 'i' + '<C-a>',\n      end: ['one Z|two threeZYX'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-a> before entering any text',\n      start: ['tes|t'],\n      keysPressed: 'i' + '<C-a>',\n      end: ['tes|t'],\n      endMode: Mode.Insert,\n      statusBar: 'E29: No inserted text yet',\n    });\n  });\n\n  suite('<C-y>', () => {\n    newTest({\n      title: '<C-y> inserts character above cursor',\n      start: ['abcde', '12|3', 'ABCDE'],\n      keysPressed: 'i' + '<C-y><C-y>',\n      end: ['abcde', '12cd|3', 'ABCDE'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-y> does nothing if line below is too short',\n      start: ['abcde', '12|3', 'ABCDE'],\n      keysPressed: 'i' + '<C-y><C-y><C-y><C-y><C-y><C-y>',\n      end: ['abcde', '12cde|3', 'ABCDE'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-y> does nothing on first line',\n      start: ['|', 'ABCDE'],\n      keysPressed: 'i' + '<C-y><C-y>',\n      end: ['|', 'ABCDE'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('<C-e>', () => {\n    newTest({\n      title: '<C-e> inserts character below cursor',\n      start: ['abcde', '12|3', 'ABCDE'],\n      keysPressed: 'i' + '<C-e><C-e>',\n      end: ['abcde', '12CD|3', 'ABCDE'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-e> does nothing if line below is too short',\n      start: ['abcde', '12|3', 'ABCDE'],\n      keysPressed: 'i' + '<C-e><C-e><C-e><C-e><C-e><C-e>',\n      end: ['abcde', '12CDE|3', 'ABCDE'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-e> does nothing on last line',\n      start: ['abcde', '|'],\n      keysPressed: 'i' + '<C-e><C-e>',\n      end: ['abcde', '|'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  newTest({\n    title: \"Can handle '<C-r>' paste register\",\n    start: ['foo |bar'],\n    keysPressed: 'yei<C-r>\"',\n    end: ['foo bar|bar'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle '<C-r>' paste register with multiple cursors\",\n    start: ['foo |bar', 'foo bar'],\n    // create two cursors on bar, yank. Then paste it in insert mode\n    keysPressed: 'gbgby' + 'i<C-r>\"',\n    end: ['foo bar|bar', 'foo bar|bar'],\n    endMode: Mode.Insert,\n  });\n\n  suite('<C-t>', () => {\n    newTest({\n      title: '<C-t> increases indent (2 spaces)',\n      editorOptions: { insertSpaces: true, tabSize: 2 },\n      start: ['    x|yz'],\n      keysPressed: 'i' + '<C-t>',\n      end: ['      x|yz'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-t> increases indent (4 spaces)',\n      editorOptions: { insertSpaces: true, tabSize: 4 },\n      start: ['    x|yz'],\n      keysPressed: 'i' + '<C-t>',\n      end: ['        x|yz'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-t> increases indent (tab)',\n      editorOptions: { insertSpaces: false },\n      start: ['\\tx|yz'],\n      keysPressed: 'i' + '<C-t>',\n      end: ['\\t\\tx|yz'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('<C-d>', () => {\n    newTest({\n      title: '<C-d> decreases indent (2 spaces)',\n      editorOptions: { insertSpaces: true, tabSize: 2 },\n      start: ['        x|yz'],\n      keysPressed: 'i' + '<C-d>',\n      end: ['      x|yz'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-d> decreases indent (4 spaces)',\n      editorOptions: { insertSpaces: true, tabSize: 4 },\n      start: ['        x|yz'],\n      keysPressed: 'i' + '<C-d>',\n      end: ['    x|yz'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '<C-d> decreases indent (tab)',\n      editorOptions: { insertSpaces: false },\n      start: ['\\t\\tx|yz'],\n      keysPressed: 'i' + '<C-d>',\n      end: ['\\tx|yz'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('VSCode auto-surround', () => {\n    test('preserves selection', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', 's', 'e', 'l', 'e', 'c', 't']);\n      await vscode.commands.executeCommand('editor.action.selectAll');\n      await modeHandler.handleKeyEvent('\"');\n      assertEqualLines(['\"select\"']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n      assert.strictEqual(vscode.window.activeTextEditor!.selection.start.character, 1);\n      assert.strictEqual(vscode.window.activeTextEditor!.selection.end.character, 7);\n    });\n\n    test('replaces selection', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', 't', 'e', 'm', 'p']);\n      await vscode.commands.executeCommand('editor.action.selectAll');\n      await modeHandler.handleMultipleKeyEvents(['\"', 'f', 'i', 'n', 'a', 'l']);\n      assertEqualLines(['\"final\"']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n      assert.strictEqual(vscode.window.activeTextEditor!.selection.start.character, 6);\n      assert.strictEqual(vscode.window.activeTextEditor!.selection.end.character, 6);\n    });\n\n    test('stacks', async () => {\n      await modeHandler.handleMultipleKeyEvents(['i', 't', 'e', 'x', 't']);\n      await vscode.commands.executeCommand('editor.action.selectAll');\n\n      await modeHandler.handleMultipleKeyEvents(['\"', \"'\", '(', '[', '{', '<', '`']);\n      assertEqualLines(['\"\\'([{<`text`>}])\\'\"']);\n    });\n\n    test('handles snippet', async () => {\n      await modeHandler.handleKeyEvent('i');\n      await vscode.commands.executeCommand('editor.action.insertSnippet', {\n        snippet: '${3:foo} ${1:bar} ${2:baz}',\n      });\n      await modeHandler.handleMultipleKeyEvents(['(', 'o', 'n', 'e']);\n      await vscode.commands.executeCommand('jumpToNextSnippetPlaceholder');\n      await modeHandler.handleMultipleKeyEvents(['<', 't', 'w', 'o']);\n      await vscode.commands.executeCommand('jumpToNextSnippetPlaceholder');\n      await modeHandler.handleKeyEvent('`');\n      assertEqualLines(['`foo` (one) <two>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/modeNormal.test.ts",
    "content": "import * as assert from 'assert';\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest, newTestSkip } from '../testSimplifier';\nimport { cleanUpWorkspace, setupWorkspace } from './../testUtils';\n\nsuite('Mode Normal', () => {\n  let modeHandler: ModeHandler;\n\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        tabstop: 4,\n        expandtab: false,\n      },\n    });\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('Can be activated', async () => {\n    const activationKeys = ['<Esc>', '<C-[>'];\n\n    for (const key of activationKeys) {\n      await modeHandler.handleKeyEvent('i');\n      await modeHandler.handleKeyEvent(key);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal, `${key} doesn't work.`);\n    }\n\n    await modeHandler.handleKeyEvent('v');\n    await modeHandler.handleKeyEvent('v');\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  newTest({\n    title: 'Can handle dw',\n    start: ['one |two three'],\n    keysPressed: 'dw',\n    end: ['one |three'],\n  });\n\n  newTest({\n    title: 'Can handle dw',\n    start: ['one | '],\n    keysPressed: 'dw',\n    end: ['one| '],\n  });\n\n  newTest({\n    title: 'Can handle dw',\n    start: ['one |two'],\n    keysPressed: 'dw',\n    end: ['one| '],\n  });\n\n  newTest({\n    title: 'Can handle dw across lines (1)',\n    start: ['one |two', '  three'],\n    keysPressed: 'dw',\n    end: ['one| ', '  three'],\n  });\n\n  newTest({\n    title: 'Can handle dw across lines (2)',\n    start: ['one |two', '', 'three'],\n    keysPressed: 'dw',\n    end: ['one| ', '', 'three'],\n  });\n\n  newTest({\n    title: 'Can handle dd last line',\n    start: ['one', '|two'],\n    keysPressed: 'dd',\n    end: ['|one'],\n  });\n\n  newTest({\n    title: 'Can handle dd single line',\n    start: ['|one'],\n    keysPressed: 'dd',\n    end: ['|'],\n  });\n\n  newTest({\n    title: 'Can handle dd',\n    start: ['|one', 'two'],\n    keysPressed: 'dd',\n    end: ['|two'],\n  });\n\n  newTest({\n    title: 'Can handle 3dd',\n    start: ['|one', 'two', 'three', 'four', 'five'],\n    keysPressed: '3dd',\n    end: ['|four', 'five'],\n    statusBar: '3 fewer lines',\n  });\n\n  newTest({\n    title: 'Can handle 3dd off end of document',\n    start: ['one', 'two', 'three', '|four', 'five'],\n    keysPressed: '3dd',\n    end: ['one', 'two', '|three'],\n  });\n\n  newTest({\n    title: 'Can handle d2d',\n    start: ['one', 'two', '|three', 'four', 'five'],\n    keysPressed: 'd2d',\n    end: ['one', 'two', '|five'],\n  });\n\n  newTest({\n    title: 'Can handle dd empty line',\n    start: ['one', '|', 'two'],\n    keysPressed: 'dd',\n    end: ['one', '|two'],\n  });\n\n  for (const useSystemClipboard of [true, false]) {\n    newTest({\n      title: 'Can handle ddp',\n      config: { useSystemClipboard },\n      start: ['|one', 'two'],\n      keysPressed: 'ddp',\n      end: ['two', '|one'],\n    });\n  }\n\n  newTest({\n    title: \"Can handle 'de'\",\n    start: ['text tex|t'],\n    keysPressed: '^de',\n    end: ['| text'],\n  });\n\n  newTest({\n    title: \"Can handle 'de' then 'de' again\",\n    start: ['text tex|t'],\n    keysPressed: '^dede',\n    end: ['|'],\n  });\n\n  newTest({\n    title: \"Can handle 'db'\",\n    start: ['One tw|o'],\n    keysPressed: '$db',\n    end: ['One |o'],\n  });\n\n  newTest({\n    title: \"Can handle 'db then 'db' again\",\n    start: ['One tw|o'],\n    keysPressed: '$dbdb',\n    end: ['|o'],\n  });\n\n  newTest({\n    title: \"Can handle 'dl' at end of line\",\n    start: ['bla|h'],\n    keysPressed: '$dldldl',\n    end: ['|b'],\n  });\n\n  newTest({\n    title: \"Can handle 'dF'\",\n    start: ['abcdefg|h'],\n    keysPressed: 'dFd',\n    end: ['abc|h'],\n  });\n\n  newTest({\n    title: \"Can handle 'dT'\",\n    start: ['abcdefg|h'],\n    keysPressed: 'dTd',\n    end: ['abcd|h'],\n  });\n\n  newTest({\n    title: \"Can handle 'd3' then <enter>\",\n    start: ['|1', '2', '3', '4', '5', '6'],\n    keysPressed: 'd3\\n',\n    end: ['|5', '6'],\n  });\n\n  newTest({\n    title: \"Can handle 'dj'\",\n    start: ['|11', '22', '\\t33', '44', '55', '66'],\n    keysPressed: 'dj',\n    end: ['\\t|33', '44', '55', '66'],\n  });\n\n  newTest({\n    title: \"Can handle 'dk'\",\n    start: ['11', '22', '33', '44', '55', '|66'],\n    keysPressed: 'dk',\n    end: ['11', '22', '33', '|44'],\n  });\n\n  newTest({\n    title: \"Can handle 'd])' without deleting closing parenthesis\",\n    start: ['(hello, |world)'],\n    keysPressed: 'd])',\n    end: ['(hello, |)'],\n  });\n\n  newTest({\n    title: \"Can handle 'd]}' without deleting closing bracket\",\n    start: ['{hello, |world}'],\n    keysPressed: 'd]}',\n    end: ['{hello, |}'],\n  });\n\n  newTest({\n    title: \"Can handle 'd/'\",\n    start: ['one |two three four'],\n    keysPressed: 'd/four\\n',\n    end: ['one |four'],\n  });\n\n  newTest({\n    title: \"Can handle 'd/' with count ([count]d/[word])\",\n    start: ['one |two two two two'],\n    keysPressed: '3d/two\\n',\n    end: ['one |two'],\n  });\n\n  newTest({\n    title: \"Can handle 'd/' with count (d[count]/[word])\",\n    start: ['one |two two two two'],\n    keysPressed: 'd3/two\\n',\n    end: ['one |two'],\n  });\n\n  newTest({\n    title: \"Can handle 'cw'\",\n    start: ['text text tex|t'],\n    keysPressed: '^lllllllcw',\n    end: ['text te| text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cw' without deleting following white spaces\",\n    start: ['|const a = 1;'],\n    keysPressed: 'cw',\n    end: ['| a = 1;'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cw' on white space\",\n    start: ['|  const a = 1;'],\n    keysPressed: '0cw',\n    end: ['|const a = 1;'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cW' on last character of word\",\n    start: ['first secon|d third fourth'],\n    keysPressed: 'cW',\n    end: ['first secon| third fourth'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'c2w'\",\n    start: ['|const a = 1;'],\n    keysPressed: 'c2w',\n    end: ['| = 1;'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cw' without removing EOL\",\n    start: ['|text;', 'text'],\n    keysPressed: 'llllcw',\n    end: ['text|', 'text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'v$c deletes newline',\n    start: ['one', 't|wo', 'three'],\n    keysPressed: 'v$c',\n    end: ['one', 't|three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'c])' without deleting closing parenthesis\",\n    start: ['(hello, |world)'],\n    keysPressed: 'c])',\n    end: ['(hello, |)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'c]}' without deleting closing bracket\",\n    start: ['{hello, |world}'],\n    keysPressed: 'c]}',\n    end: ['{hello, |}'],\n    endMode: Mode.Insert,\n  });\n\n  suite('`s` (synonym for `cl`)', () => {\n    newTest({\n      title: '`s` deletes a character and enters Insert mode',\n      start: ['12|345'],\n      keysPressed: 's',\n      end: ['12|45'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`[count]s` deletes [count] characters',\n      start: ['12|345678'],\n      keysPressed: '5s',\n      end: ['12|8'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`[count]s` does not go over EOL or delete characters before cursor',\n      start: ['12345|678', 'nextline'],\n      keysPressed: '5s',\n      end: ['12345|', 'nextline'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`s` deletes nothing on an empty line',\n      start: ['123', '|', '456'],\n      keysPressed: 's',\n      end: ['123', '|', '456'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`[count]s` deletes nothing on an empty line',\n      start: ['123', '|', '456'],\n      keysPressed: '8s',\n      end: ['123', '|', '456'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  newTest({\n    title: \"Can handle 'yiw' with correct cursor ending position\",\n    start: ['tes|t'],\n    keysPressed: 'yiwp',\n    end: ['ttes|test'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'ciw'\",\n    start: ['text text tex|t'],\n    keysPressed: '^lllllllciw',\n    end: ['text | text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ciw' on blanks\",\n    start: ['text   text tex|t'],\n    keysPressed: '^lllllciw',\n    end: ['text|text text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'caw'\",\n    start: ['text text tex|t'],\n    keysPressed: '^llllllcaw',\n    end: ['text |text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'caw' on first letter\",\n    start: ['text text tex|t'],\n    keysPressed: '^lllllcaw',\n    end: ['text |text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'caw' on blanks\",\n    start: ['text   tex|t'],\n    keysPressed: '^lllllcaw',\n    end: ['text|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'caw' on blanks\",\n    start: ['text |   text text'],\n    keysPressed: 'caw',\n    end: ['text| text'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cae'\",\n    start: ['Two roads diverged in a |wood, and I', 'I took the one less traveled by'],\n    keysPressed: 'cae',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cae' with caret at document's top\",\n    start: ['|Two roads diverged in a wood, and I', 'I took the one less traveled by'],\n    keysPressed: 'cae',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cae' with caret at document's end\",\n    start: ['Two roads diverged in a wood, and I', 'I took the one less traveled by|'],\n    keysPressed: 'cae',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cie' on blank content\",\n    start: ['|'],\n    keysPressed: 'cie',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cie' with leading space\",\n    start: [\n      '     ',\n      '    ',\n      '|Two roads diverged in a wood, and I',\n      'I took the one less traveled by',\n    ],\n    keysPressed: 'cie',\n    end: ['     ', '    ', '|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cie' with trailing space\",\n    start: [\n      'Two roads |diverged in a wood, and I',\n      'I took the one less traveled by',\n      '    ',\n      '   ',\n    ],\n    keysPressed: 'cie',\n    end: ['|', '    ', '   '],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cie' with both leading and trailing space\",\n    start: [\n      '  ',\n      ' ',\n      'Two roads diverged in a |wood, and I',\n      'I took the one less traveled by',\n      '    ',\n      '   ',\n    ],\n    keysPressed: 'cie',\n    end: ['  ', ' ', '|', '    ', '   '],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cie' on blank content\",\n    start: ['|'],\n    keysPressed: 'cie',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' on first parentheses\",\n    start: ['print(|\"hello\")'],\n    keysPressed: 'ci(',\n    end: ['print(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' with nested parentheses\",\n    start: ['call|(() => 5)'],\n    keysPressed: 'ci(',\n    end: ['call(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' on closing inner parenthesis\",\n    start: ['one ((|)) two'],\n    keysPressed: 'ci(',\n    end: ['one ((|)) two'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' backwards through nested parens\",\n    start: ['call(() => |5)'],\n    keysPressed: 'ci(',\n    end: ['call(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cib' on first parentheses\",\n    start: ['print(|\"hello\")'],\n    keysPressed: 'cib',\n    end: ['print(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cib' between sets of parentheses\",\n    start: ['one (two) th|ree (four) five'],\n    keysPressed: 'cib',\n    end: ['one (two) three (|) five'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' across multiple lines with last character at beginning\",\n    start: ['(|a', 'b)'],\n    keysPressed: 'ci)',\n    end: ['(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle count prefixed 'ci)'\",\n    start: [' b(l(baz(f|oo)baz)a)h '],\n    keysPressed: 'c3i)',\n    end: [' b(|)h '],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle count prefixed 'ca)'\",\n    start: [' b(l(baz(f|oo)baz)a)h '],\n    keysPressed: 'c3a)',\n    end: [' b|h '],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ca(' spanning multiple lines\",\n    start: ['call(', '  |arg1)'],\n    keysPressed: 'ca(',\n    end: ['call|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'cab' spanning multiple lines\",\n    start: ['call(', '  |arg1)'],\n    keysPressed: 'cab',\n    end: ['call|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci{' spanning multiple lines\",\n    start: ['one {', '|', '}'],\n    keysPressed: 'ci{',\n    end: ['one {', '|', '}'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci{' spanning multiple lines and handle whitespaces correctly\",\n    start: ['one {  ', '|', '}'],\n    keysPressed: 'ci{',\n    end: ['one {|', '}'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci{' spanning multiple lines and handle whitespaces correctly\",\n    start: ['one {', '|', '  }'],\n    keysPressed: 'ci{',\n    end: ['one {', '|', '  }'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci(' on the closing bracket\",\n    start: ['(one|)'],\n    keysPressed: 'ci(',\n    end: ['(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ciB' spanning multiple lines\",\n    start: ['one {', '|', '}'],\n    keysPressed: 'ciB',\n    end: ['one {', '|', '}'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'will fail when ca( with no ()',\n    start: ['|blaaah'],\n    keysPressed: 'ca(',\n    end: ['|blaaah'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'will fail when ca{ with no {}',\n    start: ['|blaaah'],\n    keysPressed: 'ca{',\n    end: ['|blaaah'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'will fail when caB with no {}',\n    start: ['|blaaah'],\n    keysPressed: 'caB',\n    end: ['|blaaah'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'ci[' spanning multiple lines\",\n    start: ['one [', '|', ']'],\n    keysPressed: 'ci[',\n    end: ['one [', '|', ']'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci]' on first bracket\",\n    start: ['one[|\"two\"]'],\n    keysPressed: 'ci]',\n    end: ['one[|]'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ca[' on first bracket\",\n    start: ['one[|\"two\"]'],\n    keysPressed: 'ca[',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ca]' on first bracket\",\n    start: ['one[|\"two\"]'],\n    keysPressed: 'ca]',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  // TODO: these tests should be organanized and combined with the ones below - vi'c should be the same as ci', for instance\n  newTest({\n    title: \"Can handle 'vi'c' on first quote\",\n    start: [\"one |'two' three\"],\n    keysPressed: \"vi'c\",\n    end: [\"one '|' three\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'vi'c' inside quoted string\",\n    start: [\"one 't|wo' three\"],\n    keysPressed: \"vi'c\",\n    end: [\"one '|' three\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'vi'c' on closing quote\",\n    start: [\"one 'two|' three\"],\n    keysPressed: \"vi'c\",\n    end: [\"one '|' three\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'vi'c' when string is ahead\",\n    start: [\"on|e 'two' three\"],\n    keysPressed: \"vi'c\",\n    end: [\"one '|' three\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci'' on first quote\",\n    start: [\"|'one'\"],\n    keysPressed: \"ci'\",\n    end: [\"'|'\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci'' inside quoted string\",\n    start: [\"'o|ne'\"],\n    keysPressed: \"ci'\",\n    end: [\"'|'\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci'' on closing quote\",\n    start: [\"'one|'\"],\n    keysPressed: \"ci'\",\n    end: [\"'|'\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci'' when string is ahead\",\n    start: [\"on|e 'two'\"],\n    keysPressed: \"ci'\",\n    end: [\"one '|'\"],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' on opening quote\",\n    start: ['|\"one\"'],\n    keysPressed: 'ci\"',\n    end: ['\"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' starting behind the quoted word\",\n    start: ['|one \"two\"'],\n    keysPressed: 'ci\"',\n    end: ['one \"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'ci\\\"' correctly matches quotes on line when starting on quote character\",\n    start: ['one \"two|\" three \"four\"'],\n    keysPressed: 'ci\"',\n    end: ['one \"|\" three \"four\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'ci\\\"' fails when starting on unmatched quote character\",\n    start: ['one \"two\" three \"four\" five|\" six'],\n    keysPressed: 'ci\"',\n    end: ['one \"two\" three \"four\" five|\" six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'ca\\\"' starting behind the quoted word\",\n    start: ['|one \"two\"'],\n    keysPressed: 'ca\"',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ca\\\"' starting on the opening quote\",\n    start: ['one |\"two\"'],\n    keysPressed: 'ca\"',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'ca\\\"' includes trailing whitespace\",\n    start: ['one \"t|wo\"            three'],\n    keysPressed: 'ca\"',\n    end: ['one |three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'ca\\\"' includes trailing whitespace 2\",\n    start: ['one \"t|wo\"   ', 'three'],\n    keysPressed: 'ca\"',\n    end: ['one |', 'three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'ca\\\"' includes leading whitespace if there is no trailing whitespace\",\n    start: ['one      \"t|wo\"three'],\n    keysPressed: 'ca\"',\n    end: ['one|three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'c2i\\\"' includes quotes, but not trailing whitespace\",\n    start: ['one \"t|wo\"   ', 'three'],\n    keysPressed: 'c2i\"',\n    end: ['one |   ', 'three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'c2i\\\"' includes quotes, but not trailing whitespace 2\",\n    start: ['one \"t|wo\"   ', 'three'],\n    keysPressed: 'c2i\"',\n    end: ['one |   ', 'three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"'c2i\\\"' includes quotes, but not leading whitespace\",\n    start: ['one      \"t|wo\"three'],\n    keysPressed: 'c2i\"',\n    end: ['one      |three'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with escaped quotes\",\n    start: ['\"one \\\\\"tw|o\\\\\"\"'],\n    keysPressed: 'ci\"',\n    end: ['\"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with a single escaped quote\",\n    start: ['|\"one \\\\\" two\"'],\n    keysPressed: 'ci\"',\n    end: ['\"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with a single escaped quote behind\",\n    start: ['one \"two \\\\\" |three\"'],\n    keysPressed: 'ci\"',\n    end: ['one \"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with an escaped backslash\",\n    start: ['one \"tw|o \\\\\\\\three\"'],\n    keysPressed: 'ci\"',\n    end: ['one \"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with an escaped backslash on closing quote\",\n    start: ['\"\\\\\\\\|\"'],\n    keysPressed: 'ci\"',\n    end: ['\"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ca\\\"' starting on the closing quote\",\n    start: ['one \"two|\"'],\n    keysPressed: 'ca\"',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'ci\\\"' with complex escape sequences\",\n    start: ['\"two|\\\\\\\\\\\\\"\"'],\n    keysPressed: 'ci\"',\n    end: ['\"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can pick the correct open quote between two strings for 'ci\\\"'\",\n    start: ['\"one\" |\"two\"'],\n    keysPressed: 'ci\"',\n    end: ['\"one\" \"|\"'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'will fail when ca\" ahead of quoted string',\n    start: ['\"one\" |two'],\n    keysPressed: 'ca\"',\n    end: ['\"one\" |two'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'ca`' inside word\",\n    start: ['one `t|wo`'],\n    keysPressed: 'ca`',\n    end: ['one|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'can handle cj',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: 'cjasdf<Esc>',\n    end: ['asd|f', 'ghi'],\n  });\n\n  newTest({\n    title: 'can handle ck',\n    start: ['abc', '|def', 'ghi'],\n    keysPressed: 'ckasdf<Esc>',\n    end: ['asd|f', 'ghi'],\n  });\n\n  newTest({\n    title: 'can handle c2j',\n    start: ['|abc', 'foo', 'bar', 'ghi'],\n    keysPressed: 'c2jasdf<Esc>',\n    end: ['asd|f', 'ghi'],\n  });\n\n  newTest({\n    title: 'can handle c2k',\n    start: ['abc', 'foo', 'ba|r', 'ghi'],\n    keysPressed: 'c2kasdf<Esc>',\n    end: ['asd|f', 'ghi'],\n  });\n\n  newTest({\n    title: 'can handle cj on last line',\n    start: ['abc', 'foo', 'bar', 'gh|i'],\n    keysPressed: 'cjasdf<Esc>',\n    end: ['abc', 'foo', 'bar', 'asd|f'],\n  });\n\n  newTest({\n    title: 'can handle ck on first line',\n    start: ['|abc', 'foo', 'bar', 'ghi'],\n    keysPressed: 'ckasdf<Esc>',\n    end: ['asd|f', 'foo', 'bar', 'ghi'],\n  });\n\n  newTest({\n    title: 'can handle c2j on last line',\n    start: ['abc', 'foo', 'bar', 'gh|i'],\n    keysPressed: 'c2jasdf<Esc>',\n    end: ['abc', 'foo', 'bar', 'asd|f'],\n  });\n\n  newTest({\n    title: 'can handle c2k on first line',\n    start: ['a|bc', 'foo', 'bar', 'ghi'],\n    keysPressed: 'c2kasdf<Esc>',\n    end: ['asd|f', 'foo', 'bar', 'ghi'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with cursor inside spaces\",\n    start: ['one   two |  three,   four  '],\n    keysPressed: 'daw',\n    end: ['one   two|,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with trailing spaces\",\n    start: ['one   tw|o   three,   four  '],\n    keysPressed: 'daw',\n    end: ['one   |three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with leading spaces\",\n    start: ['one   two   th|ree,   four  '],\n    keysPressed: 'daw',\n    end: ['one   two|,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with numeric prefix\",\n    start: ['on|e   two   three,   four  '],\n    keysPressed: 'd3aw',\n    end: ['|,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with numeric prefix and across lines\",\n    start: ['one   two   three,   fo|ur  ', 'five  six'],\n    keysPressed: 'd2aw',\n    end: ['one   two   three,   |six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with numeric prefix and across lines\",\n    start: ['one two fo|ur', 'five  six'],\n    keysPressed: 'd2aw',\n    end: ['one two |six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title:\n      \"Can handle 'daw' on word with numeric prefix and across lines, containing words end with `.`\",\n    start: ['one   two   three,   fo|ur  ', 'five.  six'],\n    keysPressed: 'd2aw',\n    end: ['one   two   three,   |.  six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word with numeric prefix and across lines with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'd2aw',\n    end: ['one   two   three', '|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on end of word\",\n    start: ['one   two   three   fou|r'],\n    keysPressed: 'daw',\n    end: ['one   two   thre|e'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on words at beginning of line with leading whitespace\",\n    start: ['if (something){', '  |this.method();'],\n    keysPressed: 'daw',\n    end: ['if (something){', '  |.method();'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on words at ends of lines in the middle of whitespace\",\n    start: ['one two | ', 'four'],\n    keysPressed: 'daw',\n    end: ['one tw|o'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word at beginning of file\",\n    start: ['o|ne'],\n    keysPressed: 'daw',\n    end: ['|'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word at beginning of line\",\n    start: ['one two', 'th|ree'],\n    keysPressed: 'daw',\n    end: ['one two', '|'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on word at end of line with trailing whitespace\",\n    start: ['one tw|o  ', 'three four'],\n    keysPressed: 'daw',\n    end: ['one| ', 'three four'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' around word at end of line\",\n    start: ['one t|wo', ' three'],\n    keysPressed: 'daw',\n    end: ['on|e', ' three'],\n  });\n\n  newTest({\n    title: \"Can handle 'daw' on line with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'daw',\n    end: ['one   two   three', '| five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on big word with cursor inside spaces\",\n    start: ['one   two |  three,   four  '],\n    keysPressed: 'daW',\n    end: ['one   two|   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' around word at whitespace\",\n    start: ['<div  | class=\"btn\"> foo'],\n    keysPressed: 'daW',\n    end: ['<div| foo'],\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on word with trailing spaces\",\n    start: ['one   tw|o   three,   four  '],\n    keysPressed: 'daW',\n    end: ['one   |three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on word with leading spaces\",\n    start: ['one   two   th|ree,   four  '],\n    keysPressed: 'daW',\n    end: ['one   two   |four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on word with numeric prefix\",\n    start: ['on|e   two   three,   four  '],\n    keysPressed: 'd3aW',\n    end: ['|four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on word with numeric prefix and across lines\",\n    start: ['one   two   three,   fo|ur  ', 'five.  six'],\n    keysPressed: 'd2aW',\n    end: ['one   two   three,   |six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on word with numeric prefix and across lines with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'd2aW',\n    end: ['one   two   three', '|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on beginning of word\",\n    start: ['one |two three'],\n    keysPressed: 'daW',\n    end: ['one |three'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on end of one line\",\n    start: ['one |two'],\n    keysPressed: 'daW',\n    end: ['on|e'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' on line with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'daW',\n    end: ['one   two   three', '| five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'daW' around word at the last WORD (t|wo)\",\n    start: ['one t|wo', ' three'],\n    keysPressed: 'daW',\n    end: ['on|e', ' three'],\n  });\n\n  newTest({\n    title: \"Can handle 'daW' around word at the last WORD (tw|o)\",\n    start: ['one tw|o', ' three'],\n    keysPressed: 'daW',\n    end: ['on|e', ' three'],\n  });\n\n  newTest({\n    title: 'Can handle \\'daW\\' around word at the last WORD (class=\"btn\"|>)',\n    start: ['<div class=\"btn\"|>', 'foo'],\n    keysPressed: 'daW',\n    end: ['<di|v', 'foo'],\n  });\n\n  newTest({\n    title: 'Can handle \\'daW\\' around word at the last WORD of the end of document (class=\"btn\"|>)',\n    start: ['<div class=\"btn\"|>'],\n    keysPressed: 'daW',\n    end: ['<di|v'],\n  });\n\n  newTest({\n    title: 'Can handle \\'daW\\' around word at the last WORD (c|lass=\"btn\">)',\n    start: ['<div c|lass=\"btn\">', 'foo'],\n    keysPressed: 'daW',\n    end: ['<di|v', 'foo'],\n  });\n\n  newTest({\n    title: 'Can handle \\'daW\\' around word at the last WORD of the end of document (c|lass=\"btn\">)',\n    start: ['<div c|lass=\"btn\">'],\n    keysPressed: 'daW',\n    end: ['<di|v'],\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on word with cursor inside spaces\",\n    start: ['one   two |  three,   four  '],\n    keysPressed: 'diw',\n    end: ['one   two|three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on word\",\n    start: ['one   tw|o   three,   four  '],\n    keysPressed: 'diw',\n    end: ['one   |   three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on word with numeric prefix\",\n    start: ['on|e   two   three,   four  '],\n    keysPressed: 'd3iw',\n    end: ['|   three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on trailing spaces at the end of line\",\n    start: ['one   two   three  | ', 'five  six'],\n    keysPressed: 'diw',\n    end: ['one   two   thre|e', 'five  six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on line with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'diw',\n    end: ['one   two   three', '|', 'four five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on word with numeric prefix and across lines\",\n    start: ['one   two   three,   fo|ur  ', 'five  six'],\n    keysPressed: 'd3iw',\n    end: ['one   two   three,   |  six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title:\n      \"Can handle 'diw' on word with numeric prefix and across lines, containing words end with `.`\",\n    start: ['one   two   three,   fo|ur  ', 'five.  six'],\n    keysPressed: 'd3iw',\n    end: ['one   two   three,   |.  six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diw' on word with numric prefix and across lines with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'd3iw',\n    end: ['one   two   three', '|five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on big word with cursor inside spaces\",\n    start: ['one   two |  three,   four  '],\n    keysPressed: 'diW',\n    end: ['one   two|three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on word with trailing spaces\",\n    start: ['one   tw|o,   three,   four  '],\n    keysPressed: 'diW',\n    end: ['one   |   three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on word with leading spaces\",\n    start: ['one   two   th|ree,   four  '],\n    keysPressed: 'diW',\n    end: ['one   two   |   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on line with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'diW',\n    end: ['one   two   three', '|', 'four five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on word with numeric prefix\",\n    start: ['on|e   two   three,   four  '],\n    keysPressed: 'd3iW',\n    end: ['|   three,   four  '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on word with numeric prefix and across lines\",\n    start: ['one   two   three,   fo|ur  ', 'five.  six'],\n    keysPressed: 'd3iW',\n    end: ['one   two   three,   |  six'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on word with numric prefix and across lines with no text\",\n    start: ['one   two   three', '|', 'four five'],\n    keysPressed: 'd3iw',\n    end: ['one   two   three', '|five'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'diW' on beginning of word\",\n    start: ['one |two three'],\n    keysPressed: 'diW',\n    end: ['one | three'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'dae'\",\n    start: ['Two roads diverged in a |wood, and I', 'I took the one less traveled by'],\n    keysPressed: 'dae',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'dae' with caret at document's top\",\n    start: ['|Two roads diverged in a wood, and I', 'I took the one less traveled by'],\n    keysPressed: 'dae',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'dae' with caret at document's end\",\n    start: ['Two roads diverged in a wood, and I', 'I took the one less traveled by|'],\n    keysPressed: 'dae',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'die' on blank content\",\n    start: ['|'],\n    keysPressed: 'die',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'die' with leading space\",\n    start: [\n      '     ',\n      '    ',\n      '|Two roads diverged in a wood, and I',\n      'I took the one less traveled by',\n    ],\n    keysPressed: 'die',\n    end: ['     ', '    ', '|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'die' with trailing space\",\n    start: [\n      'Two roads |diverged in a wood, and I',\n      'I took the one less traveled by',\n      '    ',\n      '   ',\n    ],\n    keysPressed: 'die',\n    end: ['|', '    ', '   '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'die' with both leading and trailing space\",\n    start: [\n      '  ',\n      ' ',\n      'Two roads diverged in a |wood, and I',\n      'I took the one less traveled by',\n      '    ',\n      '   ',\n    ],\n    keysPressed: 'die',\n    end: ['  ', ' ', '|', '    ', '   '],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'die' on blank content\",\n    start: ['|'],\n    keysPressed: 'die',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle d} at beginning of line',\n    start: ['|foo', 'bar', '', 'fun'],\n    keysPressed: 'd}',\n    end: ['|', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle y} at beginning of line',\n    start: ['|foo', 'bar', '', 'fun'],\n    keysPressed: 'y}p',\n    end: ['foo', '|foo', 'bar', 'bar', '', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle d} when not at beginning of line',\n    start: ['f|oo', 'bar', '', 'fun'],\n    keysPressed: 'd}',\n    end: ['|f', '', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle } with operator and count, at beginning of line',\n    start: ['|foo', '', 'bar', '', 'fun'],\n    keysPressed: 'd2}',\n    end: ['|', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle } with operator and count, and not at beginning of line',\n    start: ['f|oo', '', 'bar', '', 'fun'],\n    keysPressed: 'd2}',\n    end: ['|f', '', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle dip',\n    start: ['foo', '', 'bar baz', 'bar |baz', '', 'fun'],\n    keysPressed: 'dip',\n    end: ['foo', '', '|', 'fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle dip on empty lines',\n    start: ['foo', '', '|', '', 'fun'],\n    keysPressed: 'dip',\n    end: ['foo', '|fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle dap',\n    start: ['foo', '', 'bar baz', 'bar |baz', '', 'fun'],\n    keysPressed: 'dap',\n    end: ['foo', '', '|fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle dap with two blank lines',\n    start: ['foo', '', 'bar baz', 'bar |baz', '', '', 'fun'],\n    keysPressed: 'dap',\n    end: ['foo', '', '|fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle dap one single line with two blank lines',\n    start: ['foo', '', 'bar |baz', '', '', 'fun'],\n    keysPressed: 'dap',\n    end: ['foo', '', '|fun'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Select sentence with trailing spaces',\n    start: [\"That's my sec|ret, Captain. I'm always angry.\"],\n    keysPressed: 'das',\n    end: [\"|I'm always angry.\"],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Select sentence with leading spaces',\n    start: [\"That's my secret, Captain. I'm a|lways angry.\"],\n    keysPressed: 'das',\n    end: [\"That's my secret, Captain|.\"],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Select inner sentence with trailing spaces',\n    start: [\"That's my sec|ret, Captain. I'm always angry.\"],\n    keysPressed: 'dis',\n    end: [\"| I'm always angry.\"],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Select inner sentence with leading spaces',\n    start: [\"That's my secret, Captain. I'm a|lways angry.\"],\n    keysPressed: 'dis',\n    end: [\"That's my secret, Captain.| \"],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Select spaces between sentences',\n    start: [\"That's my secret, Captain.  |  I'm always angry.\"],\n    keysPressed: 'visd',\n    end: [\"That's my secret, Captain.|I'm always angry.\"],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'df'\",\n    start: ['aext tex|t'],\n    keysPressed: '^dft',\n    end: ['| text'],\n  });\n\n  newTest({\n    title: \"Can handle 'dt'\",\n    start: ['aext tex|t'],\n    keysPressed: '^dtt',\n    end: ['|t text'],\n  });\n\n  for (const key of ['<BS>', '<C-BS>', '<S-BS>']) {\n    newTest({\n      title: `${key} moves one character to the left`,\n      start: ['text |text'],\n      keysPressed: key,\n      end: ['text| text'],\n    });\n\n    newTest({\n      title: `${key} goes across line boundaries when 'whichwrap' contains 'b'`,\n      config: { whichwrap: 'b' },\n      start: ['one', '|two'],\n      keysPressed: key,\n      end: ['on|e', 'two'],\n    });\n\n    newTest({\n      title: `${key} does not go across line boundaries when 'whichwrap' does not contain 'b'`,\n      config: { whichwrap: '' },\n      start: ['one', '|two'],\n      keysPressed: key,\n      end: ['one', '|two'],\n    });\n  }\n\n  newTest({\n    title: 'Can handle A and backspace',\n    start: ['|text text'],\n    keysPressed: 'A<BS><Esc>',\n    end: ['text te|x'],\n  });\n\n  newTest({\n    title: 'A should update desiredColumn',\n    start: ['|longer line', 'short'],\n    keysPressed: 'A<Esc>jk',\n    end: ['longer lin|e', 'short'],\n  });\n\n  newTest({\n    title: 'I should updated desiredColumn',\n    start: ['     hell|o', 'a'],\n    keysPressed: 'I<Esc>jk',\n    end: ['    | hello', 'a'],\n  });\n\n  newTest({\n    title: 'leaving insert mode should update desired column when entered with a',\n    start: ['a long line of text', 'shor|t'],\n    keysPressed: 'amore<Esc>kj',\n    end: ['a long line of text', 'shortmor|e'],\n  });\n\n  newTest({\n    title: 'leaving insert mode should update desired column when entered with i',\n    start: ['a long line of text', 'shor|t'],\n    keysPressed: 'imore<Esc>kj',\n    end: ['a long line of text', 'shormor|et'],\n  });\n\n  newTest({\n    title: \"Can handle 'yy' without changing cursor position\",\n    start: ['one', 'tw|o'],\n    keysPressed: 'yy',\n    end: ['one', 'tw|o'],\n  });\n\n  suite('I', () => {\n    newTest({\n      title: 'I enters insert mode at start of line after any whitespace',\n      start: ['  on|e'],\n      keysPressed: 'I',\n      end: ['  |one'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '[count]I',\n      start: ['  on|e'],\n      keysPressed: '2Itest<Esc>',\n      end: ['  testtes|tone'],\n    });\n  });\n\n  suite('gI', () => {\n    newTest({\n      title: 'gI enters insert mode at start of line',\n      start: ['    o|ne'],\n      keysPressed: 'gItest',\n      end: ['test|    one'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '[count]gI',\n      start: ['    o|ne'],\n      keysPressed: '3gIab<Esc>',\n      end: ['ababa|b    one'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('gi', () => {\n    newTest({\n      title: '`gi` enters insert mode at point of last insertion',\n      start: ['|'],\n      keysPressed: 'ione<Esc>otwo<Esc>0gi',\n      end: ['one', 'two|'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`[count]gi`',\n      start: ['|'],\n      keysPressed: 'ione<Esc>otwo<Esc>03giab<Esc>',\n      end: ['one', 'twoababa|b'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"`gi` enters insert mode at start of document if there's no prior insertion\",\n      start: ['one', 'two', 'thr|ee'],\n      keysPressed: 'gi',\n      end: ['|one', 'two', 'three'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`gi` after (c)hange',\n      start: ['one |two three'],\n      keysPressed: 'cwab<Esc>0gi',\n      end: ['one ab| three'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('g;', () => {\n    newTest({\n      title: 'g; before any changes throws E664',\n      start: ['one t|wo three'],\n      keysPressed: 'g;',\n      end: ['one t|wo three'],\n      statusBar: 'E664: changelist is empty',\n    });\n\n    newTest({\n      title: 'g; works correctly after insert',\n      start: ['one', 'tw|o', 'three'],\n      keysPressed: 'iXYZ<Esc>Gg;',\n      end: ['one', 'twXY|Zo', 'three'],\n    });\n\n    newTest({\n      title: 'g; works correctly after delete',\n      start: ['one', 'two', 'th|ree'],\n      keysPressed: 'xggg;',\n      end: ['one', 'two', 'th|ee'],\n    });\n\n    newTest({\n      title: 'g; works correctly after change',\n      start: ['one', 'two', 'th|ree'],\n      keysPressed: 'clXYZ<Esc>ggg;',\n      end: ['one', 'two', 'thXY|Zee'],\n    });\n\n    // TODO: Test with multiple changes\n  });\n\n  newTest({\n    title: 'g, before any changes throws E664',\n    start: ['one t|wo three'],\n    keysPressed: 'g,',\n    end: ['one t|wo three'],\n    statusBar: 'E664: changelist is empty',\n  });\n\n  newTest({\n    title: 'g, works correctly',\n    start: ['|'],\n    keysPressed: 'ione<Esc>atwo<Esc>g;g;g,',\n    end: ['onetw|o'],\n  });\n\n  newTest({\n    title: 'g_ works correctly',\n    start: ['te|sttest'],\n    keysPressed: 'g_',\n    end: ['testtes|t'],\n  });\n\n  newTest({\n    title: '3g_ works correctly',\n    start: ['tes|ttest', 'testtest', 'testtest'],\n    keysPressed: '3g_',\n    end: ['testtest', 'testtest', 'testtes|t'],\n  });\n\n  newTest({\n    title: 'gq handles spaces after single line comments correctly',\n    start: [\n      '//    We choose to write a vim extension, not because it is easy, but because it is hard|.',\n    ],\n    keysPressed: 'Vgq',\n    end: [\n      '//    We choose to write a vim extension, not because it is easy, but because it',\n      '|//    is hard.',\n    ],\n  });\n\n  newTest({\n    title: 'gq handles spaces before single line comments correctly',\n    start: [\n      '    // We choose to write a vim extension, not because it is easy, but because it is hard|.',\n    ],\n    keysPressed: 'Vgq',\n    end: [\n      '    // We choose to write a vim extension, not because it is easy, but because',\n      '|    // it is hard.',\n    ],\n  });\n\n  newTest({\n    title: 'gq handles tabs before single line comments correctly',\n    start: [\n      '\\t\\t// We choose to write a vim extension, not because it is easy, but because it is hard|.',\n    ],\n    keysPressed: 'Vgq',\n    end: [\n      '\\t\\t// We choose to write a vim extension, not because it is easy, but',\n      '|\\t\\t// because it is hard.',\n    ],\n  });\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq work correctly with cursor in the middle of a line',\n      start: [\n        '// We choose to write a vim extension, not |because it is easy, but because it is hard.',\n        '// We choose to write a vim extension, not because it is easy, but because it is hard.',\n      ],\n      keysPressed: 'gqj',\n      end: [\n        '|// We choose to write a vim extension, not because it is easy, but because it is',\n        '// hard.  We choose to write a vim extension, not because it is easy, but',\n        '// because it is hard.',\n      ],\n    },\n    process.platform === 'win32',\n  );\n\n  newTest({\n    title: 'gq preserves newlines',\n    start: ['|abc', '', '', '', 'def'],\n    keysPressed: 'gqG',\n    end: ['|abc', '', '', '', 'def'],\n  });\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq handles single-line comments',\n      start: ['|// abc', '// def'],\n      keysPressed: 'gqG',\n      end: ['|// abc def'],\n    },\n    process.platform === 'win32',\n  );\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq handles multiline comments',\n      start: ['|/*', ' * abc', ' * def', ' */'],\n      keysPressed: 'gqG',\n      end: ['|/*', ' * abc def', ' */'],\n    },\n    process.platform === 'win32',\n  );\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq handles multiline comments with inner and final on same line',\n      start: ['|/*', ' * abc', ' * def */'],\n      keysPressed: 'gqG',\n      end: ['|/*', ' * abc def */'],\n    },\n    process.platform === 'win32',\n  );\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq handles multiline comments with content on start line',\n      start: ['|/* abc', ' * def', '*/'],\n      keysPressed: 'gqG',\n      end: ['|/* abc def', ' */'],\n    },\n    process.platform === 'win32',\n  );\n\n  newTest({\n    title: 'gq handles multiline comments with start and final on same line',\n    start: ['|/* abc def */'],\n    keysPressed: 'gqG',\n    end: ['|/* abc def */'],\n  });\n\n  newTest({\n    title: 'gq preserves blank lines in multiline comments',\n    start: ['|/* abc', ' *', ' *', ' * def */'],\n    keysPressed: 'gqG',\n    end: ['|/* abc', ' *', ' *', ' * def */'],\n  });\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq does not merge adjacent multiline comments',\n      start: ['|/* abc */', '/* def */'],\n      keysPressed: 'gqG',\n      end: ['|/* abc */', '/* def */'],\n    },\n    process.platform === 'win32',\n  );\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq does not merge adjacent multiline comments',\n      start: ['|/* abc', ' */', '/* def', ' */'],\n      keysPressed: 'gqG',\n      end: ['|/* abc', ' */', '/* def', ' */'],\n    },\n    process.platform === 'win32',\n  );\n\n  // TODO(#4844): this fails on Windows\n  newTestSkip(\n    {\n      title: 'gq leaves alone whitespace within a line',\n      start: [\"|Good morning, how are you?  I'm Dr. Worm.\", \"I'm interested\", 'in      things.'],\n      keysPressed: 'gqG',\n      end: [\"|Good morning, how are you?  I'm Dr. Worm.  I'm interested in      things.\"],\n    },\n    process.platform === 'win32',\n  );\n\n  newTest({\n    title: 'gq breaks at exactly textwidth',\n    start: [\n      '|1 3 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9x split',\n    ],\n    keysPressed: 'gqG',\n    end: [\n      '|1 3 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9x',\n      'split',\n    ],\n  });\n\n  newTest({\n    title: 'gq breaks before textwidth',\n    start: [\n      '|1 3 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9xs split',\n    ],\n    keysPressed: 'gqG',\n    end: [\n      '|1 3 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7',\n      '9xs split',\n    ],\n  });\n\n  newTest({\n    title: 'gq breaks at exactly textwidth with indent and comment',\n    start: [\n      '| // 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9xs split',\n    ],\n    keysPressed: 'gqG',\n    end: [\n      '| // 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9',\n      ' // 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7 9',\n      ' // 5 7 911 3 5 7 921 3 5 7 931 3 5 7 941 3 5 7 951 3 5 7 961 3 5 7 971 3 5 7',\n      ' // 9xs split',\n    ],\n  });\n\n  newTest({\n    title: 'gq breaks around long words',\n    start: [\n      '|this is a suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuper long looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong                    word',\n    ],\n    keysPressed: 'gqG',\n    end: [\n      '|this is a',\n      'suuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuper',\n      'long looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong',\n      'word',\n    ],\n  });\n\n  newTest({\n    title: '<Space> moves cursor one character right',\n    start: ['|abc', 'def'],\n    keysPressed: ' ',\n    end: ['a|bc', 'def'],\n  });\n\n  newTest({\n    title: \"<Space> goes over line break when whichwrap includes 's'\",\n    config: { whichwrap: 's' },\n    start: ['ab|c', 'def'],\n    keysPressed: ' ',\n    end: ['abc', '|def'],\n  });\n\n  newTest({\n    title: \"<Space> does not go over line break when whichwrap does not include 's'\",\n    config: { whichwrap: '' },\n    start: ['ab|c', 'def'],\n    keysPressed: ' ',\n    end: ['ab|c', 'def'],\n  });\n\n  newTest({\n    title: 'Can handle u',\n    start: ['|ABC DEF'],\n    keysPressed: 'vwu',\n    end: ['|abc dEF'],\n  });\n\n  newTest({\n    title: 'Can handle guw',\n    start: ['|ABC DEF'],\n    keysPressed: 'guw',\n    end: ['|abc DEF'],\n  });\n\n  newTest({\n    title: 'Can handle guae',\n    start: ['|ABC', 'DEF', 'GHI'],\n    keysPressed: 'guae',\n    end: ['|abc', 'def', 'ghi'],\n  });\n\n  newTest({\n    title: 'Can handle guie',\n    start: [' ', ' ', '|ABC', 'DEF', 'GHI'],\n    keysPressed: 'guie',\n    end: [' ', ' ', '|abc', 'def', 'ghi'],\n  });\n\n  newTest({\n    title: 'Can handle gUw',\n    start: ['|abc def'],\n    keysPressed: 'gUw',\n    end: ['|ABC def'],\n  });\n\n  newTest({\n    title: 'Can handle gUae',\n    start: ['|Abc', 'def', 'GhI'],\n    keysPressed: 'gUae',\n    end: ['|ABC', 'DEF', 'GHI'],\n  });\n\n  newTest({\n    title: 'Can handle gUie',\n    start: [' ', ' ', '|abc', 'def', 'ghi'],\n    keysPressed: 'gUie',\n    end: [' ', ' ', '|ABC', 'DEF', 'GHI'],\n  });\n\n  newTest({\n    title: 'Can handle u over line breaks',\n    start: ['|ABC', 'DEF'],\n    keysPressed: 'vG$u',\n    end: ['|abc', 'def'],\n  });\n\n  newTest({\n    title: 'can handle s in visual mode',\n    start: ['|abc def ghi'],\n    keysPressed: 'vwshi <Esc>',\n    end: ['hi| ef ghi'],\n  });\n\n  newTest({\n    title: 'can repeat backspace twice',\n    start: ['|11223344'],\n    keysPressed: 'A<BS><BS><Esc>0.',\n    end: ['112|2'],\n  });\n\n  newTest({\n    title: 'Can repeat dw',\n    start: ['one |two three four'],\n    keysPressed: 'dw.',\n    end: ['one |four'],\n  });\n\n  newTest({\n    title: 'Can repeat dw with count',\n    start: ['one |two three four five'],\n    keysPressed: 'dw2.',\n    end: ['one |five'],\n  });\n\n  newTest({\n    title: \"Can handle 'dW' on last character of word\",\n    start: ['first secon|d third fourth'],\n    keysPressed: 'dW',\n    end: ['first secon|third fourth'],\n  });\n\n  newTest({\n    title: 'can delete linewise with d2G',\n    start: ['on|e', 'two', 'three'],\n    keysPressed: 'd2G',\n    end: ['|three'],\n  });\n\n  newTest({\n    title: 'can delete linewise with d2gg',\n    start: ['on|e', 'two', 'three'],\n    keysPressed: 'd2gg',\n    end: ['|three'],\n  });\n\n  newTest({\n    title: 'can delete linewise with d2gg backwards',\n    start: ['one', 'two', 'thr|ee', 'four'],\n    keysPressed: 'd2gg',\n    end: ['one', '|four'],\n  });\n\n  newTest({\n    title: 'can delete with + motion and count',\n    start: ['one', 'two', 'three', 'fo|ur', 'five', 'six', 'seven'],\n    keysPressed: 'd2+',\n    end: ['one', 'two', 'three', '|seven'],\n  });\n\n  newTest({\n    title: 'can delete with - motion and count',\n    start: ['one', 'two', 'three', 'four', 'five', 's|ix', 'seven'],\n    keysPressed: 'd3-',\n    end: ['one', 'two', '|seven'],\n  });\n\n  newTest({\n    title: 'can delete with count before and after operator, 2d12w deletes 24 words',\n    start: [\n      '|one two three four five six seven eight nine ten',\n      'one two three four five six seven eight nine ten',\n      'one two three four five six seven eight nine ten',\n    ],\n    keysPressed: '2d12w',\n    end: ['|five six seven eight nine ten'],\n  });\n\n  newTest({\n    title: 'can dE correctly',\n    start: ['|one two three'],\n    keysPressed: 'dE',\n    end: ['| two three'],\n  });\n\n  newTest({\n    title: 'can dE correctly',\n    start: ['|one((( two three'],\n    keysPressed: 'dE',\n    end: ['| two three'],\n  });\n\n  newTest({\n    title: 'can dE correctly',\n    start: ['one two |three'],\n    keysPressed: 'dE',\n    end: ['one two| '],\n  });\n\n  newTest({\n    title: 'can do Y',\n    start: ['|blah blah'],\n    keysPressed: 'Yp',\n    end: ['blah blah', '|blah blah'],\n  });\n\n  newTest({\n    title: 'can do [count]Y',\n    start: ['|one', 'two', 'three'],\n    keysPressed: '2Yp',\n    end: ['one', '|one', 'two', 'two', 'three'],\n  });\n\n  newTest({\n    title: 'can do [count]Y if count is larger than EOF',\n    start: ['|one', 'two', 'three'],\n    keysPressed: '100Yp',\n    end: ['one', '|one', 'two', 'three', 'two', 'three'],\n  });\n\n  newTest({\n    title: 'Can do S',\n    start: ['    one', '    tw|o', '    three'],\n    keysPressed: '2S',\n    end: ['    one', '    |'],\n  });\n\n  newTest({\n    title: 'Can do C',\n    start: ['export const options = {', '|', '};'],\n    keysPressed: 'C',\n    end: ['export const options = {', '|', '};'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit on a matching tag',\n    start: ['<blink>he|llo</blink>'],\n    keysPressed: 'cit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Ignores cit on a non-matching tag',\n    start: ['<blink>he|llo</unblink>'],\n    keysPressed: 'cit',\n    end: ['<blink>he|llo</unblink>'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Ignores cit on a nested tag',\n    start: ['<blink>he|llo<hello></blink>'],\n    keysPressed: 'cit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit on a tag with an attribute tag',\n    start: ['<blink |level=\"extreme\">hello</blink>'],\n    keysPressed: 'cit',\n    end: ['<blink level=\"extreme\">|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cat on a matching tag',\n    start: ['one <blink>he|llo</blink> two'],\n    keysPressed: 'cat',\n    end: ['one | two'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit on a multiline tag',\n    start: [' <blink>', 'he|llo', 'text</blink>'],\n    keysPressed: 'cit',\n    end: [' <blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit on a multiline tag with nested tags',\n    start: [' <blink>', '<h1>hello</h1>', 'h<br>e|llo', 'te</h1>xt</blink>'],\n    keysPressed: 'cit',\n    end: [' <blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit inside of a tag with another non closing tag inside tags',\n    start: ['<blink>hello<br>wo|rld</blink>'],\n    keysPressed: 'cit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cit inside of a tag with another empty closing tag inside tags',\n    start: ['<blink>hel|lo</h1>world</blink>'],\n    keysPressed: 'cit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do dit on empty tag block, cursor moves to inside',\n    start: ['<bli|nk></blink>'],\n    keysPressed: 'dit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do cit on empty tag block, cursor moves to inside',\n    start: ['<bli|nk></blink>'],\n    keysPressed: 'cit',\n    end: ['<blink>|</blink>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'can do cit with self closing tags',\n    start: ['<div><div a=1/>{{c|ursor here}}</div>'],\n    keysPressed: 'cit',\n    end: ['<div>|</div>'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'yk moves cursor up',\n    start: ['one', 't|wo', 'three'],\n    keysPressed: 'yk',\n    end: ['o|ne', 'two', 'three'],\n  });\n\n  newTest({\n    title: 'yh moves cursor left',\n    start: ['one', 'two', 'thr|ee'],\n    keysPressed: 'yh',\n    end: ['one', 'two', 'th|ree'],\n  });\n\n  newTest({\n    title: 'yat yanks the correct tag when inside one',\n    start: ['<foo>', '  <b|ar>asd</bar>', '</foo>'],\n    keysPressed: 'yat$p',\n    end: ['<foo>', '  <bar>asd</bar><bar>asd</bar|>', '</foo>'],\n  });\n\n  newTest({\n    title: 'yat yanks the correct tag when cursor is on the opening angle bracket',\n    start: ['<foo>', '  |<bar>asd</bar>', '</foo>'],\n    keysPressed: 'yat$p',\n    end: ['<foo>', '  <bar>asd</bar><bar>asd</bar|>', '</foo>'],\n  });\n\n  newTest({\n    title: 'yat yanks the correct tag when cursor is between the beginning of the line and the tag',\n    start: ['<foo>', ' | <bar>asd</bar>', '</foo>'],\n    keysPressed: 'yat$p',\n    end: ['<foo>', '  <bar>asd</bar><bar>asd</bar|>', '</foo>'],\n  });\n\n  newTest({\n    title:\n      'dat deletes the outer tag when there are other characters (not just WS) before the opening tag',\n    start: ['<foo>', 'a | <bar>asd</bar>', '</foo>'],\n    keysPressed: 'dat',\n    end: ['|'],\n  });\n\n  newTest({\n    title: 'dat deletes the outer tag when the cursor is after the inner tag',\n    start: ['<foo>', '  <bar>asd</bar> |', '</foo>'],\n    keysPressed: 'dat',\n    end: ['|'],\n  });\n\n  newTest({\n    title: 'Respects indentation with cc',\n    start: ['{', '  int| a;'],\n    keysPressed: 'cc',\n    end: ['{', '  |'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Resets cursor to indent end with cc',\n    start: ['{', ' | int a;'],\n    keysPressed: 'cc',\n    end: ['{', '  |'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"can handle 'cc' on empty line\",\n    start: ['foo', '|', 'bar'],\n    keysPressed: 'cc',\n    end: ['foo', '|', 'bar'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'cc copies linewise',\n    start: ['foo', '|fun', 'bar'],\n    keysPressed: 'cc<Esc>jp',\n    end: ['foo', '', 'bar', '|fun'],\n  });\n\n  newTest({\n    title: 'Vc preserves indentation of first line',\n    start: ['one', '  t|wo', '      three', 'four'],\n    keysPressed: 'Vj' + 'c',\n    end: ['one', '  |', 'four'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'cj preserves indentation of first line',\n    start: ['one', '  t|wo', '      three', 'four'],\n    keysPressed: 'cj',\n    end: ['one', '  |', 'four'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Indent current line with correct Vim Mode',\n    start: ['|one', 'two'],\n    keysPressed: '>>',\n    end: ['\\t|one', 'two'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle <Esc> and do nothing',\n    start: ['te|st'],\n    keysPressed: '<Esc>',\n    end: ['te|st'],\n  });\n\n  newTest({\n    title: 'Can handle # on consecutive words',\n    start: ['test test test test |test'],\n    keysPressed: '#',\n    end: ['test test test |test test'],\n  });\n\n  newTest({\n    title: 'Can handle # on skipped words',\n    start: ['test aaa test aaa test aaa test aaa |test'],\n    keysPressed: '#',\n    end: ['test aaa test aaa test aaa |test aaa test'],\n  });\n\n  newTest({\n    title: \"Can 'D'elete the characters under the cursor until the end of the line\",\n    start: ['test aaa test aaa test aaa test |aaa test'],\n    keysPressed: 'D',\n    end: ['test aaa test aaa test aaa test| '],\n  });\n\n  newTest({\n    title: \"Can 'D'elete the characters under multiple cursors until the end of the line\",\n    start: [\n      'test aaa test aaa test aaa test |aaa test',\n      'test aaa test aaa test aaa test aaa test',\n    ],\n    keysPressed: '<C-alt+down>D<Esc>',\n    end: ['test aaa test aaa test aaa test| ', 'test aaa test aaa test aaa test '],\n  });\n\n  newTest({\n    title: 'cc on whitespace-only treats whitespace as indent',\n    start: ['|     '],\n    keysPressed: 'cc',\n    end: ['     |'],\n  });\n\n  newTest({\n    title: 'Can do cai',\n    start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n    keysPressed: 'cai',\n    end: ['|', 'do_something_else()'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do cii',\n    start: ['if foo > 3:', '\\tlog(\"foo is big\")', '\\tfoo = 3', '|', 'do_something_else()'],\n    keysPressed: 'cii',\n    end: ['if foo > 3:', '\\t|', 'do_something_else()'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do caI',\n    start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n    keysPressed: 'caI',\n    end: ['|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: 'Can do dai',\n    start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n    keysPressed: 'dai',\n    end: ['|do_something_else()'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do dii',\n    start: ['if foo > 3:', '    log(\"foo is big\")', '    foo = 3', '|', 'do_something_else()'],\n    keysPressed: 'dii',\n    end: ['if foo > 3:', '|do_something_else()'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do daI',\n    start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n    keysPressed: 'daI',\n    end: ['|'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Will fail 'cia' with no delimiters\",\n    start: ['f|oo'],\n    keysPressed: 'cia',\n    end: ['f|oo'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Will fail 'cia' with flipped delimiters\",\n    start: [')f|oo('],\n    keysPressed: 'cia',\n    end: [')f|oo('],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Will fail 'cia' with separators but no delimiters\",\n    start: ['alpha,', 'b|eta,', 'gamma'],\n    keysPressed: 'cia',\n    end: ['alpha,', 'b|eta,', 'gamma'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single argument\",\n    start: ['(f|oo)'],\n    keysPressed: 'cia',\n    end: ['(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in () with cursor at opening delimiter\",\n    start: ['|()'],\n    keysPressed: 'cia',\n    end: ['(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in () with cursor at closing delimiter\",\n    start: ['(|)'],\n    keysPressed: 'cia',\n    end: ['(|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,) with cursor at opening delimiter\",\n    start: ['|(,)'],\n    keysPressed: 'cia',\n    end: ['(|,)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,) with cursor at regular delimiter\",\n    start: ['(|,)'],\n    keysPressed: 'cia',\n    end: ['(,|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,) with cursor at closing delimiter\",\n    start: ['(,|)'],\n    keysPressed: 'cia',\n    end: ['(,|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,,,) with cursor at regular delimiter\",\n    start: ['(|,,,)'],\n    keysPressed: 'cia',\n    end: ['(,|,,)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,,,) with cursor at second-to-last delimiter\",\n    start: ['(,,|,)'],\n    keysPressed: 'cia',\n    end: ['(,,,|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in (,,,) with cursor at closing delimiter\",\n    start: ['(,,,|)'],\n    keysPressed: 'cia',\n    end: ['(,,,|)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' with nested parentheses in argument\",\n    start: ['(foo, (void*) ba|r(Foo<T>), baz)'],\n    keysPressed: 'cia',\n    end: ['(foo, |, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor in first argument\",\n    start: ['(f|oo, bar, baz)'],\n    keysPressed: 'cia',\n    end: ['(|, bar, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor on opening delimiter\",\n    start: ['|(foo, bar, baz)'],\n    keysPressed: 'cia',\n    end: ['(|, bar, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor in middle argument\",\n    start: ['(foo, ba|r, baz)'],\n    keysPressed: 'cia',\n    end: ['(foo, |, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor on regular delimiter\",\n    start: ['(foo|, bar, baz)'],\n    keysPressed: 'cia',\n    end: ['(foo, |, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor in last argument\",\n    start: ['(foo, bar, b|az)'],\n    keysPressed: 'cia',\n    end: ['(foo, bar, |)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a single-line comma seperated list with cursor on closing delimiter\",\n    start: ['(foo, bar, baz|)'],\n    keysPressed: 'cia',\n    end: ['(foo, bar, |)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a whitespace-only argument\",\n    start: ['(foo, | , baz)'],\n    keysPressed: 'cia',\n    end: ['(foo,|, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a whitespace-only argument across multiple lines\",\n    start: ['(foo,', '  ', ' | ', '  ', ' , baz)'],\n    keysPressed: 'cia',\n    end: ['(foo,|, baz)'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' with trailing whitespace after separator\",\n    start: ['(', '   foo, ', '   b|ar,', '   baz', ')'],\n    keysPressed: 'cia',\n    end: ['(', '   foo, ', '   |,', '   baz', ')'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' with trailing whitespace after separator and empty line\",\n    start: ['(', '   foo, ', '    ', '   b|ar,', '   baz', ')'],\n    keysPressed: 'cia',\n    end: ['(', '   foo, ', '    ', '   |,', '   baz', ')'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a multiline-line comma seperated list with cursor in first argument\",\n    start: ['(', '   f|oo,', '   bar,', '   baz', ')'],\n    keysPressed: 'cia',\n    end: ['(', '   |,', '   bar,', '   baz', ')'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a multiline-line comma seperated list with cursor in center argument\",\n    start: ['(', '   foo,', '   b|ar,', '   baz', ')'],\n    keysPressed: 'cia',\n    end: ['(', '   foo,', '   |,', '   baz', ')'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a multiline-line comma seperated list with cursor in last argument\",\n    start: ['(', '   foo,', '   bar,', '   |baz', ')'],\n    keysPressed: 'cia',\n    end: ['(', '   foo,', '   bar,', '   |', ')'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'cia' in a multi-line indented statement with one argument.\",\n    start: ['   functionCall(', '      fo|o', '   )'],\n    keysPressed: 'cia',\n    end: ['   functionCall(', '      |', '   )'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a single argument\",\n    start: ['(f|oo)'],\n    keysPressed: 'daa',\n    end: ['(|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Will fail 'daa' in ()\",\n    start: ['(|)'],\n    keysPressed: 'daa',\n    end: ['(|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a single-line comma seperated list with cursor in first argument\",\n    start: ['(f|oo, bar, baz)'],\n    keysPressed: 'daa',\n    end: ['(|bar, baz)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a single-line comma seperated list with cursor in middle argument\",\n    start: ['(foo, b|ar, baz)'],\n    keysPressed: 'daa',\n    end: ['(foo|, baz)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a single-line comma seperated list with cursor in last argument\",\n    start: ['(foo, bar, |baz)'],\n    keysPressed: 'daa',\n    end: ['(foo, bar|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a multiline-line comma seperated list with cursor in first argument\",\n    start: ['(', '   |foo,', '   bar,', '   baz', ')'],\n    keysPressed: 'daa',\n    end: ['|(', '   bar,', '   baz', ')'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a multiline-line comma seperated list with cursor in center argument\",\n    start: ['(', '   foo,', '   b|ar,', '   baz', ')'],\n    keysPressed: 'daa',\n    end: ['(', '   foo|,', '   baz', ')'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can do 'daa' in a multiline-line comma seperated list with cursor in last argument\",\n    start: ['(', '   foo,', '   bar,', '   ba|z', ')'],\n    keysPressed: 'daa',\n    end: ['(', '   foo,', '   ba|r', ')'],\n    endMode: Mode.Normal,\n  });\n\n  suite('<C-u> / <C-d>', () => {\n    newTest({\n      title: 'can handle <C-u> when first line is visible and starting column is at the beginning',\n      start: ['\\t hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '<C-u>',\n      end: ['\\t |hello world', 'hello', 'hi hello', 'foo'],\n    });\n\n    newTest({\n      title: 'can handle <C-u> when first line is visible and starting column is at the end',\n      start: ['\\t hello world', 'hello', 'hi hello', 'very long line at the bottom|'],\n      keysPressed: '<C-u>',\n      end: ['\\t |hello world', 'hello', 'hi hello', 'very long line at the bottom'],\n    });\n\n    newTest({\n      title: 'can handle <C-u> when first line is visible and starting column is in the middle',\n      start: ['\\t hello world', 'hello', 'hi hello', 'very long line |at the bottom'],\n      keysPressed: '<C-u>',\n      end: ['\\t |hello world', 'hello', 'hi hello', 'very long line at the bottom'],\n    });\n\n    newTest({\n      title: '[count]<C-u> sets and adheres to scroll option',\n      start: ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'st|u'],\n      keysPressed: '2<C-u><C-u>',\n      end: ['abc', 'def', '|ghi', 'jkl', 'mno', 'pqr', 'stu'],\n    });\n\n    newTest({\n      title: '[count]<C-d> sets and adheres to scroll option',\n      start: ['ab|c', 'def', 'ghi', 'jkl', 'mno', 'pqr', 'stu'],\n      keysPressed: '2<C-d><C-d>',\n      end: ['abc', 'def', 'ghi', 'jkl', '|mno', 'pqr', 'stu'],\n    });\n  });\n\n  suite('<C-g>', () => {\n    // TODO: test with untitled file\n    // TODO: test [count]<C-g>\n\n    suiteSetup(cleanUpWorkspace);\n\n    newTest({\n      title: '<C-g> displays info about the file in status bar (line 1 of 3)',\n      start: ['o|ne', 'two', 'three'],\n      keysPressed: '<C-g>',\n      end: ['o|ne', 'two', 'three'],\n      statusBar: '\"{FILENAME}\" 3 lines --33%--',\n      saveDocBeforeTest: true,\n    });\n\n    newTest({\n      title: '<C-g> displays info about the file in status bar (line 2 of 3)',\n      start: ['one', '|two', 'three'],\n      keysPressed: '<C-g>',\n      end: ['one', '|two', 'three'],\n      statusBar: '\"{FILENAME}\" 3 lines --66%--',\n      saveDocBeforeTest: true,\n    });\n\n    newTest({\n      title: '<C-g> displays info about the file in status bar (line 3 of 3)',\n      start: ['one', 'two', 'thr|ee'],\n      keysPressed: '<C-g>',\n      end: ['one', 'two', 'thr|ee'],\n      statusBar: '\"{FILENAME}\" 3 lines --100%--',\n      saveDocBeforeTest: true,\n    });\n\n    newTest({\n      title: '<C-g> displays info about the file in status bar (line 1 of 1)',\n      start: ['o|ne'],\n      keysPressed: '<C-g>',\n      end: ['o|ne'],\n      statusBar: '\"{FILENAME}\" 1 line --100%--',\n      saveDocBeforeTest: true,\n    });\n\n    newTest({\n      title: '<C-g> has special message for empty file',\n      start: ['|'],\n      keysPressed: '<C-g>',\n      end: ['|'],\n      statusBar: '\"{FILENAME}\" --No lines in buffer--',\n      saveDocBeforeTest: true,\n    });\n\n    newTest({\n      title: '<C-g> includes \"[Modified]\" when file is dirty',\n      start: ['one', 't|wo', 'three'],\n      keysPressed: 'x' + '<C-g>',\n      end: ['one', 't|o', 'three'],\n      statusBar: '\"{FILENAME}\" [Modified] 3 lines --66%--',\n      saveDocBeforeTest: true,\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/modeReplace.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\n\nsuite('Mode Replace', () => {\n  newTest({\n    title: 'Can activate with <Insert> from Insert mode',\n    start: ['|'],\n    keysPressed: 'ia<Insert>',\n    end: ['a|'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can activate with R from Normal mode',\n    start: ['123|456'],\n    keysPressed: 'R',\n    end: ['123|456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle R',\n    start: ['123|456'],\n    keysPressed: 'Rab',\n    end: ['123ab|6'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle R past current line',\n    start: ['123|456'],\n    keysPressed: 'Rabcd',\n    end: ['123abcd|'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle R and exit Replace Mode',\n    start: ['|123456'],\n    keysPressed: 'Rabc<Esc>',\n    end: ['ab|c456'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle R across lines',\n    start: ['123|456', '789'],\n    keysPressed: 'Rabcd\\nefg',\n    end: ['123abcd', 'efg|', '789'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle R across lines and exit Replace Mode',\n    start: ['123|456', '789'],\n    keysPressed: 'Rabcd\\nefg<Esc>',\n    end: ['123abcd', 'ef|g', '789'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle R with {count}',\n    start: ['123|456', '789'],\n    keysPressed: '3Rabc<Esc>',\n    end: ['123abcabcab|c', '789'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle R with {count}',\n    start: ['123|456', '789'],\n    keysPressed: '3Rabc\\ndef<Esc>',\n    end: ['123abc', 'defabc', 'defabc', 'de|f', '789'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle backspace',\n    start: ['123|456'],\n    keysPressed: 'Rabc<BS><BS><BS>',\n    end: ['123|456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle tab',\n    start: ['123|456'],\n    keysPressed: 'R<tab>',\n    end: ['123 |56'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle backspace',\n    start: ['123|456'],\n    keysPressed: 'Rabcd<BS><BS><BS><BS><BS>',\n    end: ['12|3456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle backspace',\n    start: ['123|456'],\n    keysPressed: 'R<BS>abc<BS><BS><BS>',\n    end: ['12|3456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle backspace across lines',\n    start: ['123|456'],\n    keysPressed: 'Rabcd\\nef<BS><BS><BS><BS><BS>',\n    end: ['123ab|6'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: '`<BS>` goes across EOL',\n    start: ['123', '|456'],\n    keysPressed: 'R<BS><BS><BS>X',\n    end: ['1X|3', '456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: '`<BS>` goes across EOL',\n    start: ['123', '|456'],\n    keysPressed: 'R<BS><BS><BS>X<BS>',\n    end: ['1|23', '456'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle arrows',\n    start: ['123|456'],\n    keysPressed: 'Rabc<left><BS><BS>',\n    end: ['123|abc'],\n    endMode: Mode.Replace,\n  });\n\n  newTest({\n    title: 'Can handle .',\n    start: ['123|456', '123456'],\n    keysPressed: 'Rabc<Esc>j0.',\n    end: ['123abc', 'ab|c456'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can handle . across lines',\n    start: ['123|456', '123456'],\n    keysPressed: 'Rabc\\ndef<Esc>j0.',\n    end: ['123abc', 'def', 'abc', 'de|f'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Delete in replace mode',\n    start: ['|123456'],\n    keysPressed: 'Rabc<Del><Esc>',\n    end: ['ab|c56'],\n    endMode: Mode.Normal,\n  });\n});\n"
  },
  {
    "path": "test/mode/modeVisual.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { Configuration } from '../testConfiguration';\nimport { newTest, newTestSkip } from '../testSimplifier';\nimport { assertEqualLines, reloadConfiguration, setupWorkspace } from './../testUtils';\n\nsuite('Mode Visual', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('can be activated', async () => {\n    await modeHandler.handleKeyEvent('v');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n    await modeHandler.handleKeyEvent('v');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  newTest({\n    title: '[count]v',\n    start: ['a|bcde'],\n    keysPressed: '3vd',\n    end: ['a|e'],\n  });\n\n  newTest({\n    title: '[count]v past EOL',\n    start: ['a|bcde', '12345'],\n    keysPressed: '100vd',\n    end: ['a|12345'],\n  });\n\n  test('Can handle w', async () => {\n    await modeHandler.handleMultipleKeyEvents('itest test test\\ntest\\n'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'v', 'w']);\n\n    const sel = modeHandler.vimState.editor.selection;\n    assert.strictEqual(sel.start.character, 0);\n    assert.strictEqual(sel.start.line, 0);\n\n    // The input cursor comes BEFORE the block cursor. Try it out, this\n    // is how Vim works.\n    assert.strictEqual(sel.end.character, 6);\n    assert.strictEqual(sel.end.line, 0);\n  });\n\n  test('Can handle wd', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'd']);\n\n    assertEqualLines(['wo three']);\n  });\n\n  test('Can handle x', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'x']);\n\n    assertEqualLines(['ne two three']);\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('Can handle x across a selection', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'x']);\n\n    assertEqualLines(['wo three']);\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('Can do vwd in middle of sentence', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three foar'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'l', 'l', 'l', 'l', 'v', 'w', 'd']);\n\n    assertEqualLines(['one hree foar']);\n  });\n\n  test('Can do vwd in middle of sentence', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'l', 'l', 'l', 'l', 'v', 'w', 'd']);\n\n    assertEqualLines(['one hree']);\n  });\n\n  test('Can do vwd multiple times', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three four'.split(''));\n    await modeHandler.handleMultipleKeyEvents([\n      '<Esc>',\n      '^',\n      'v',\n      'w',\n      'd',\n      'v',\n      'w',\n      'd',\n      'v',\n      'w',\n      'd',\n    ]);\n\n    assertEqualLines(['our']);\n  });\n\n  test('handles case where we go from selecting on right side to selecting on left side', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents([\n      '<Esc>',\n      '^',\n      'l',\n      'l',\n      'l',\n      'l',\n      'v',\n      'w',\n      'b',\n      'b',\n      'd',\n    ]);\n\n    assertEqualLines(['wo three']);\n  });\n\n  newTest({\n    title: 'Can handle H key',\n    start: ['1', '2', '|3', '4', '5'],\n    keysPressed: 'vH',\n    end: ['|1', '2', '3', '4', '5'],\n  });\n\n  newTest({\n    title: 'Can handle backspace key',\n    start: ['blah', 'duh', 'd|ur', 'hur'],\n    keysPressed: 'v<BS>x',\n    end: ['blah', 'duh', '|r', 'hur'],\n  });\n\n  test('handles case where we delete over a newline', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two\\n\\nthree four'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '0', 'k', 'k', 'v', '}', 'd']);\n\n    assertEqualLines(['three four']);\n  });\n\n  test('handles change operator', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'c']);\n\n    assertEqualLines(['wo three']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n  });\n\n  suite(\"Vim's EOL handling is weird\", () => {\n    test('delete through eol', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'g', 'g', 'v', 'l', 'l', 'l', 'd']);\n\n      assertEqualLines(['two']);\n    });\n\n    test('join 2 lines by deleting through eol', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'l', 'v', 'l', 'l', 'd']);\n\n      assertEqualLines(['otwo']);\n    });\n\n    test(\"d$ doesn't delete whole line\", async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', '$']);\n\n      assertEqualLines(['', 'two']);\n    });\n\n    test('vd$ does delete whole line', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'v', '$', 'd']);\n\n      assertEqualLines(['two']);\n    });\n\n    newTest({\n      title: 'Paste over selection copies the selection',\n      start: ['|from to'],\n      keysPressed: 'dewvep0P',\n      end: ['t|o from'],\n    });\n\n    newTest({\n      title: 'Paste over selection copies the selection linewise',\n      start: ['foo', 'bar', '|fun'],\n      keysPressed: 'viwykVkpp',\n      end: ['fun', '|foo', 'bar', 'fun'],\n    });\n  });\n\n  suite('Arrow keys work perfectly in Visual Mode', () => {\n    newTest({\n      title: 'Can handle <up> key',\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'v<up>x',\n      end: ['blah', '|ur', 'hur'],\n    });\n\n    newTest({\n      title: 'Can handle <down> key',\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'v<down>x',\n      end: ['blah', 'duh', '|ur'],\n    });\n\n    newTest({\n      title: 'Can handle <left> key',\n      start: ['blah', 'duh', 'd|ur', 'hur'],\n      keysPressed: 'v<left>x',\n      end: ['blah', 'duh', '|r', 'hur'],\n    });\n\n    newTest({\n      title: 'Can handle <right> key',\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'v<right>x',\n      end: ['blah', 'duh', '|r', 'hur'],\n    });\n  });\n\n  suite('Screen line motions in Visual Mode', () => {\n    newTest({\n      title: \"Can handle 'gk'\",\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'vgkx',\n      end: ['blah', '|ur', 'hur'],\n    });\n\n    newTest({\n      title: \"Can handle 'gj'\",\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'vgjx',\n      end: ['blah', 'duh', '|ur'],\n    });\n\n    newTestSkip({\n      title: \"Preserves cursor position when handling 'gk'\",\n      start: ['blah', 'word', 'a', 'la|st'],\n      keysPressed: 'vgkgkx',\n      end: ['blah', 'wo|t'],\n    });\n\n    newTestSkip({\n      title: \"Preserves cursor position when handling 'gj'\",\n      start: ['blah', 'wo|rd', 'a', 'last'],\n      keysPressed: 'vgjgjx',\n      end: ['blah', 'wo|t'],\n    });\n  });\n\n  suite('handles aw in visual mode', () => {\n    newTest({\n      title: \"Can handle 'vawd' on word with cursor inside spaces\",\n      start: ['one   two |  three,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   two|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with trailing spaces\",\n      start: ['one   tw|o   three,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   |three,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with leading spaces\",\n      start: ['one   two   th|ree,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   two|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with numeric prefix\",\n      start: ['on|e   two   three,   four  '],\n      keysPressed: 'v3awd',\n      end: ['|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with numeric prefix and across lines\",\n      start: ['one   two   three,   fo|ur  ', 'five  six'],\n      keysPressed: 'v2awd',\n      end: ['one   two   three,   |six'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title:\n        \"Can handle 'vawd' on word with numeric prefix and across lines, containing words end with `.`\",\n      start: ['one   two   three,   fo|ur  ', 'five.  six'],\n      keysPressed: 'v2awd',\n      end: ['one   two   three,   |.  six'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles aW in visual mode', () => {\n    newTest({\n      title: \"Can handle 'vaWd' on big word with cursor inside spaces\",\n      start: ['one   two |  three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two|   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with trailing spaces\",\n      start: ['one   tw|o   three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   |three,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with leading spaces\",\n      start: ['one   two   th|ree,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two   |four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix\",\n      start: ['on|e   two   three,   four  '],\n      keysPressed: 'v3aWd',\n      end: ['|four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix and across lines\",\n      start: ['one   two   three,   fo|ur  ', 'five.  six'],\n      keysPressed: 'v2aWd',\n      end: ['one   two   three,   |six'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles aW in visual mode', () => {\n    newTest({\n      title: \"Can handle 'vaWd' on big word with cursor inside spaces\",\n      start: ['one   two |  three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two|   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with trailing spaces\",\n      start: ['one   tw|o   three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   |three,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with leading spaces\",\n      start: ['one   two   th|ree,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two   |four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix\",\n      start: ['on|e   two   three,   four  '],\n      keysPressed: 'v3aWd',\n      end: ['|four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix and across lines\",\n      start: ['one   two   three,   fo|ur  ', 'five.  six'],\n      keysPressed: 'v2aWd',\n      end: ['one   two   three,   |six'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles aw in visual mode', () => {\n    newTest({\n      title: \"Can handle 'vawd' on word with cursor inside spaces\",\n      start: ['one   two |  three,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   two|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with trailing spaces\",\n      start: ['one   tw|o   three,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   |three,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with leading spaces\",\n      start: ['one   two   th|ree,   four  '],\n      keysPressed: 'vawd',\n      end: ['one   two|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with numeric prefix\",\n      start: ['on|e   two   three,   four  '],\n      keysPressed: 'v3awd',\n      end: ['|,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vawd' on word with numeric prefix and across lines\",\n      start: ['one   two   three,   fo|ur  ', 'five  six'],\n      keysPressed: 'v2awd',\n      end: ['one   two   three,   |six'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title:\n        \"Can handle 'vawd' on word with numeric prefix and across lines, containing words end with `.`\",\n      start: ['one   two   three,   fo|ur  ', 'five.  six'],\n      keysPressed: 'v2awd',\n      end: ['one   two   three,   |.  six'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles aW in visual mode', () => {\n    newTest({\n      title: \"Can handle 'vaWd' on big word with cursor inside spaces\",\n      start: ['one   two |  three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two|   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with trailing spaces\",\n      start: ['one   tw|o   three,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   |three,   four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with leading spaces\",\n      start: ['one   two   th|ree,   four  '],\n      keysPressed: 'vaWd',\n      end: ['one   two   |four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix\",\n      start: ['on|e   two   three,   four  '],\n      keysPressed: 'v3aWd',\n      end: ['|four  '],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'vaWd' on word with numeric prefix and across lines\",\n      start: ['one   two   three,   fo|ur  ', 'five.  six'],\n      keysPressed: 'v2aWd',\n      end: ['one   two   three,   |six'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'Y' in visual mode\",\n      start: ['one', '|two'],\n      keysPressed: 'vwYP',\n      end: ['one', '|two', 'two'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles as in visual mode', () => {\n    newTest({\n      title: 'Select sentence with trailing spaces in visual mode',\n      start: [\"That's my sec|ret, Captain. I'm always angry.\"],\n      keysPressed: 'vlasd',\n      end: [\"That's my sec|I'm always angry.\"],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Select sentence with leading spaces in visual mode',\n      start: [\"That's my secret, Captain. I'm a|lways angry.\"],\n      keysPressed: 'vhasd',\n      end: [\"That's my secret, Captain.|ways angry.\"],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Select multiple sentences in visual mode',\n      start: [\"That's my secret, Captain. I|'m always angry.\"],\n      keysPressed: 'vhhasd',\n      end: ['|m always angry.'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles is in visual mode', () => {\n    newTest({\n      title: 'Select inner sentence with trailing spaces in visual mode',\n      start: [\"That's my sec|ret, Captain. I'm always angry.\"],\n      keysPressed: 'vlisd',\n      end: [\"That's my sec| I'm always angry.\"],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Select inner sentence with leading spaces in visual mode',\n      start: [\"That's my secret, Captain. I'm a|lways angry.\"],\n      keysPressed: 'vhisd',\n      end: [\"That's my secret, Captain. |ways angry.\"],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Select spaces between sentences in visual mode',\n      start: [\"That's my secret, Captain.  |  I'm always angry.\"],\n      keysPressed: 'vhisd',\n      end: [\"That's my secret, Captain.| I'm always angry.\"],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles bracket blocks in visual mode', () => {\n    const brackets = [\n      {\n        start: '{',\n        end: '}',\n      },\n      {\n        start: '[',\n        end: ']',\n      },\n      {\n        start: '(',\n        end: ')',\n      },\n      {\n        start: '<',\n        end: '>',\n      },\n    ];\n\n    // each test case gets run with start bracket and end bracket\n    const bracketsToTest = brackets.flatMap(({ start, end }) => [\n      { start, end, buttonToPress: start },\n      { start, end, buttonToPress: end },\n    ]);\n    bracketsToTest.forEach(({ start, end, buttonToPress }) => {\n      newTest({\n        title: `Can do di${buttonToPress} on a matching bracket`,\n        start: [`${start} one ${start} two ${start} th|ree ${end} four ${end} five ${end}`],\n        keysPressed: `di${buttonToPress}`,\n        end: [`${start} one ${start} two ${start}|${end} four ${end} five ${end}`],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: `Can do di${buttonToPress} on a matching bracket from outside bracket`,\n        start: [`| ${start} one ${start} two ${start} three ${end} four ${end} five ${end}`],\n        keysPressed: `di${buttonToPress}`,\n        end: [` ${start}|${end}`],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: `Can do i${buttonToPress} on multiple matching brackets`,\n        start: [`${start} one ${start} two ${start} th|ree ${end} four ${end} five ${end}`],\n        keysPressed: `vi${buttonToPress}i${buttonToPress}i${buttonToPress}d`,\n        end: [`${start}|${end}`],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: `Can do d3i${buttonToPress} on matching brackets`,\n        start: [`${start} one ${start} two ${start} th|ree ${end} four ${end} five ${end}`],\n        keysPressed: `d3i${buttonToPress}`,\n        end: [`${start}|${end}`],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: `Can do d3i${buttonToPress} on matching brackets for multiple lines`,\n        start: [start, 'one', start, 'two', start, 'th|ree', end, 'four', end, 'five', end],\n        keysPressed: `d3i${buttonToPress}`,\n        end: [start, `|${end}`],\n        endMode: Mode.Normal,\n      });\n    });\n  });\n\n  suite('handles tag blocks in visual mode', () => {\n    newTest({\n      title: 'Can do vit on a matching tag',\n      start: ['one <blink>he|llo</blink> two'],\n      keysPressed: 'vitd',\n      end: ['one <blink>|</blink> two'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Cannot do vit on a matching tag from outside tag',\n      start: ['|one <blink>hello</blink> two'],\n      keysPressed: 'vitd',\n      end: ['|ne <blink>hello</blink> two'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title:\n        'Count-prefixed vit alternates expanding selection between inner and outer tag brackets',\n      start: ['<div> one <p> t|wo </p> three </div>'],\n      keysPressed: 'v3itd',\n      end: ['<div>|</div>'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do vat on a matching tag',\n      start: ['one <blink>he|llo</blink> two'],\n      keysPressed: 'vatd',\n      end: ['one | two'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Cannot do vat on a matching tag from from outside tag',\n      start: ['|one <blink>hello</blink> two'],\n      keysPressed: 'vatd',\n      end: ['|ne <blink>hello</blink> two'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: 'Can do vat on multiple matching tags',\n    start: ['one <blank>two <blink>he|llo</blink> three</blank> four'],\n    keysPressed: 'vatatd',\n    end: ['one | four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can maintain selection on failure with vat on multiple matching tags',\n    start: ['one <blank>two <blink>he|llo</blink> three</blank> four'],\n    keysPressed: 'vatatatatd',\n    end: ['one | four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can maintain selection on failure with repeat-prefixed vat on multiple matching tags',\n    start: ['one <blank>two <blink>he|llo</blink> three</blank> four'],\n    keysPressed: 'v4atd',\n    end: ['one | four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Repeat-prefixed vat does not bleed below',\n    start: ['<p>', '\\t<p>', '\\t|test', '\\t</p>', '</p>', '', 'do not delete'],\n    keysPressed: 'v8atd',\n    end: ['|', '', 'do not delete'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Failed vat does not expand or move selection, remains in visual mode',\n    start: ['one | two'],\n    keysPressed: 'v4atd',\n    end: ['one |two'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi) on a matching parenthesis',\n    start: ['test(te|st)'],\n    keysPressed: 'vi)d',\n    end: ['test(|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi) on a matching parenthesis from outside parathesis',\n    start: ['|test(test)'],\n    keysPressed: 'vi)d',\n    end: ['test(|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi) on a matching parenthesis from outside parathesis for multiple lines',\n    start: ['|test(test)', 'test(test)'],\n    keysPressed: 'vi)d',\n    end: ['test(|)', 'test(test)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi) on multiple matching parens',\n    start: ['test(te(te|st)st)'],\n    keysPressed: 'vi)i)d',\n    end: ['test(|)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va) on a matching parenthesis',\n    start: ['test(te|st);'],\n    keysPressed: 'va)d',\n    end: ['test|;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va) on a matching parenthesis from outside parenthesis',\n    start: ['|test(test);'],\n    keysPressed: 'va)d',\n    end: ['test|;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va) on multiple matching parens',\n    start: ['test(te(te|st)st);'],\n    keysPressed: 'va)a)d',\n    end: ['test|;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Failed va) does not expand or move selection, remains in visual mode',\n    start: ['one | two'],\n    keysPressed: 'v4a)d',\n    end: ['one |two'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Repeat-prefixed va) does not bleed below',\n    start: ['(', '\\t(', '\\t|', '\\t)', ')', '', 'do not delete'],\n    keysPressed: 'v8a)d',\n    end: ['|', '', 'do not delete'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va} on a matching bracket as first character',\n    start: ['1|{', 'test', '}1'],\n    keysPressed: 'va}d',\n    end: ['1|1'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va} on multiple matching brackets',\n    start: ['test{te{te|st}st};'],\n    keysPressed: 'va}a}d',\n    end: ['test|;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi( on a matching bracket near first character',\n    start: ['test(()=>{', '|', '});'],\n    keysPressed: 'vi(d',\n    end: ['test(|);'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi{ on outer pair of nested braces',\n    start: ['{', '  te|st', '  {', '    test', '  }', '}'],\n    keysPressed: 'vi{d',\n    end: ['{', '|}'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vi{ on braces indented by 1 and preserve indent',\n    start: ['{', '  t|est', ' }'],\n    keysPressed: 'vi{d',\n    end: ['{', '| }'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do va] on multiple matching brackets',\n    start: ['test[te[te|st]st];'],\n    keysPressed: 'va]a]d',\n    end: ['test|;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do repeat-prefixed vaf on multiple matching pairs of different types',\n    start: ['test <div><p>[[{{((|))}}]]</p></div> test;'],\n    keysPressed: 'v8afd',\n    end: ['test | test;'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Repeat-prefixed vaf does not bleed below',\n    start: ['<p>', '\\t(', '\\t|', '\\t)', '</p>', '', 'do not delete'],\n    keysPressed: 'v8afd',\n    end: ['|', '', 'do not delete'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'vaf only expands to enclosing pairs',\n    start: ['test (f|oo) \"hi\" test;'],\n    keysPressed: 'vafd',\n    end: ['test | \"hi\" test;'],\n    endMode: Mode.Normal,\n  });\n  suite('handles replace in visual mode', () => {\n    newTest({\n      title: 'Can do a single line replace',\n      start: ['one |two three four five'],\n      keysPressed: 'vwwer1',\n      end: ['one |11111111111111 five'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do a multi line replace',\n      start: ['one |two three four five', 'one two three four five'],\n      keysPressed: 'vjer1',\n      end: ['one |1111111111111111111', '1111111 three four five'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: 'Can use . to repeat indent in visual',\n    start: ['|foobar'],\n    keysPressed: 'v>.',\n    end: ['    |foobar'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do v_x to delete to first char',\n    start: ['', 'test te|st test', ''],\n    keysPressed: 'v_x',\n    end: ['', '|t test', ''],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do vg_x to delete to last char with no EOL',\n    start: ['', 'test te|st test', ''],\n    keysPressed: 'vg_x',\n    end: ['', 'test t|e', ''],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do v3g_x to delete to last char with no EOL with count',\n    start: ['te|st', 'test', 'test', 'test'],\n    keysPressed: 'v3g_x',\n    end: ['t|e', 'test'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can do v$x to delete to last char including EOL',\n    start: ['', 'test te|st test', ''],\n    keysPressed: 'v$x',\n    end: ['', 'test t|e'],\n    endMode: Mode.Normal,\n  });\n\n  suite('`gv` restores previous visual selection', () => {\n    suite('Visual mode', () => {\n      newTest({\n        title: 'Single char',\n        start: ['one t|wo three'],\n        keysPressed: 'v' + '<Esc>' + '0' + 'gv' + 'd',\n        end: ['one t|o three'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Forward selection, within line',\n        start: ['one |two three'],\n        keysPressed: 've' + '<Esc>' + '0' + 'gv' + 'd',\n        end: ['one | three'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Backward selection, within line',\n        start: ['one tw|o three'],\n        keysPressed: 'vb' + '<Esc>' + '0' + 'gv' + 'd',\n        end: ['one | three'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Forward selection, on EOL',\n        start: ['one', 't|wo', 'three'],\n        keysPressed: 'v$' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['one', 't|three'],\n        endMode: Mode.Normal,\n      });\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Single line',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'V' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['one', '|three'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Forward selection',\n        start: ['one', '|two', 'three', 'four', 'five'],\n        keysPressed: 'Vjj' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['one', '|five'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Backward selection',\n        start: ['one', 'two', 'three', '|four', 'five'],\n        keysPressed: 'Vkk' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['one', '|five'],\n        endMode: Mode.Normal,\n      });\n    });\n\n    suite('VisualBlock mode', () => {\n      newTest({\n        title: 'Single char',\n        start: ['one', 't|wo', 'three'],\n        keysPressed: '<C-v>' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['one', 't|o', 'three'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Forward selection, 3x3',\n        start: ['abcde', 'b|cdea', 'cdeab', 'deabc', 'eabcd'],\n        keysPressed: '<C-v>jjll' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['abcde', 'b|a', 'cb', 'dc', 'eabcd'],\n        endMode: Mode.Normal,\n      });\n\n      newTest({\n        title: 'Backward selection, 3x3',\n        start: ['abcde', 'bcdea', 'cdeab', 'dea|bc', 'eabcd'],\n        keysPressed: '<C-v>kkhh' + '<Esc>' + 'gg' + 'gv' + 'd',\n        end: ['abcde', 'b|a', 'cb', 'dc', 'eabcd'],\n        endMode: Mode.Normal,\n      });\n    });\n  });\n\n  suite('D command will remove all selected lines', () => {\n    newTest({\n      title: 'D deletes all selected lines',\n      start: ['first line', 'test| line1', 'test line2', 'second line'],\n      keysPressed: 'vjD',\n      end: ['first line', '|second line'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'D deletes the current line',\n      start: ['first line', 'test| line1', 'second line'],\n      keysPressed: 'vlllD',\n      end: ['first line', '|second line'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('handles indent blocks in visual mode', () => {\n    newTest({\n      title: 'Can do vai',\n      start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n      keysPressed: 'vaid',\n      end: ['|do_something_else()'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do vii',\n      start: ['if foo > 3:', '    bar|', '    if baz:', '        foo = 3', 'do_something_else()'],\n      keysPressed: 'viid',\n      end: ['if foo > 3:', '|do_something_else()'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Doesn't naively select the next line\",\n      start: ['if foo > 3:', '    bar|', 'if foo > 3:', '    bar'],\n      keysPressed: 'viid',\n      end: ['if foo > 3:', '|if foo > 3:', '    bar'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Searches backwards if cursor line is empty',\n      start: ['if foo > 3:', '    log(\"foo is big\")', '|', '    foo = 3', 'do_something_else()'],\n      keysPressed: 'viid',\n      end: ['if foo > 3:', '|do_something_else()'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do vaI',\n      start: ['if foo > 3:', '    log(\"foo is big\")|', '    foo = 3', 'do_something_else()'],\n      keysPressed: 'vaId',\n      end: ['|'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('visualstar', () => {\n    setup(async () => {\n      await reloadConfiguration(new Configuration({ visualstar: true }));\n    });\n\n    newTest({\n      title: 'Works with *',\n      start: [\n        '|public modes = [ModeName.Visual',\n        'public modes = [ModeName.VisualBlock',\n        'public modes = [ModeName.VisualLine',\n      ],\n      // This is doing a few things:\n      // - select to the end of \"Visual\"\n      // - press \"*\", the cursor will go to the next line since it matches\n      // - press \"n\", the cursor will go to the last line since it matches\n      keysPressed: '2vfl*n',\n      end: [\n        'public modes = [ModeName.Visual',\n        'public modes = [ModeName.VisualBlock',\n        '|public modes = [ModeName.VisualLine',\n      ],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '`*` escapes `/` properly',\n      start: ['one |two//three four', 'one two//three four'],\n      keysPressed: 'vE*',\n      end: ['one two//three four', 'one |two//three four'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Works with #',\n      start: [\n        'public modes = [ModeName.Visual',\n        'public modes = [ModeName.VisualBlock',\n        '|public modes = [ModeName.VisualLine',\n      ],\n      // This is doing a few things:\n      // - select to the end of \"Visual\"\n      // - press \"#\", the cursor will go to the previous line since it matches\n      // - press \"n\", the cursor will go to the first line since it matches\n      keysPressed: '2vfl#n',\n      end: [\n        '|public modes = [ModeName.Visual',\n        'public modes = [ModeName.VisualBlock',\n        'public modes = [ModeName.VisualLine',\n      ],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('search works in visual mode', () => {\n    newTest({\n      title: 'Works with /',\n      start: ['f|oo', 'bar', 'fun', 'baz'],\n      keysPressed: 'v/baz\\nx',\n      end: ['f|az'],\n    });\n\n    newTest({\n      title: 'Works with ?',\n      start: ['foo', 'bar', 'fun', 'b|az'],\n      keysPressed: 'v?foo\\nx',\n      end: ['|z'],\n    });\n\n    test('Selects correct range', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo bar fun baz'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'v', 'w', '/']);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selection range starts from the beginning\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 4);\n      assert.strictEqual(selection.end.line, 0);\n    });\n  });\n\n  suite('X will delete linewise', () => {\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vjX',\n      end: ['this is', '|the world'],\n    });\n\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vj$X',\n      end: ['this is', '|the world'],\n    });\n\n    newTest({\n      title: 'Backward selection',\n      start: ['one', 'two', 't|hree', 'four'],\n      keysPressed: 'vkX',\n      end: ['one', '|four'],\n    });\n  });\n\n  suite('C will delete linewise', () => {\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vjC',\n      end: ['this is', '|', 'the world'],\n    });\n\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vj$C',\n      end: ['this is', '', '|the world'],\n    });\n  });\n\n  suite('R will delete linewise', () => {\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vjR',\n      end: ['this is', '|', 'the world'],\n    });\n\n    newTest({\n      title: 'normal selection',\n      start: ['this is', 'the| best', 'test i have seen in', 'the world'],\n      keysPressed: 'vj$R',\n      end: ['this is', '', '|the world'],\n    });\n  });\n\n  suite('Linewise Registers will be inserted properly', () => {\n    newTest({\n      title: 'downward selection',\n      start: ['i ya|nked', 'this line', '', '1.line', 'a123456', 'b123456', '2.line'],\n      keysPressed: 'vjY4j3lvjllp',\n      end: ['i yanked', 'this line', '', '1.line', 'a12', '|i yanked', 'this line', '6', '2.line'],\n    });\n\n    newTest({\n      title: 'upward selection',\n      start: ['i yanked', 'this| line', '', '1.line', 'a123456', 'b123456', '2.line'],\n      keysPressed: 'vkY4j3lvjllp',\n      end: ['i yanked', 'this line', '', '1.line', 'a12', '|i yanked', 'this line', '6', '2.line'],\n    });\n  });\n\n  suite('Indent Tests using > on visual selections', () => {\n    newTest({\n      title: 'multiline indent top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'Vjj>',\n      end: ['111', '  |222', '  333', '  444', '555'],\n    });\n\n    newTest({\n      title: 'multiline indent bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'Vkk>',\n      end: ['111', '  |222', '  333', '  444', '555'],\n    });\n\n    newTest({\n      title: 'repeat multiline indent top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'Vjj>.',\n      end: ['111', '    |222', '    333', '    444', '555'],\n    });\n\n    newTest({\n      title: 'repeat multiline indent bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'Vkk>.',\n      end: ['111', '    |222', '    333', '    444', '555'],\n    });\n  });\n\n  suite('Outdent Tests using < on visual selections', () => {\n    newTest({\n      title: 'multiline outdent top down selection',\n      start: ['    111', '    2|22', '    333', '   444', '    555'],\n      keysPressed: 'Vjj<',\n      end: ['    111', '  |222', '  333', '  444', '    555'],\n    });\n\n    newTest({\n      title: 'multiline outdent bottom up selection',\n      start: ['    111', '    222', '    333', '   4|44', '    555'],\n      keysPressed: 'Vkk<',\n      end: ['    111', '  |222', '  333', '  444', '    555'],\n    });\n\n    newTest({\n      title: 'repeat multiline outdent top down selection',\n      start: ['    111', '    2|22', '    333', '   444', '    555'],\n      keysPressed: 'Vjj<.',\n      end: ['    111', '|222', '333', '444', '    555'],\n    });\n\n    newTest({\n      title: 'repeat multiline outdent bottom up selection',\n      start: ['    111', '    222', '    333', '   4|44', '    555'],\n      keysPressed: 'Vkk<.',\n      end: ['    111', '|222', '333', '444', '    555'],\n    });\n  });\n\n  suite('Non-darwin <C-c> tests', () => {\n    if (process.platform === 'darwin') {\n      return;\n    }\n\n    test('<C-c> copies and sets mode to normal', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'e', '<C-c>']);\n\n      // ensuring we're back in normal\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n      assertEqualLines(['one two three']);\n\n      // test copy by pasting back\n      await modeHandler.handleMultipleKeyEvents(['^', '\"', '+', 'P']);\n\n      assertEqualLines(['oneone two three']);\n    });\n  });\n\n  suite('vi{ will go to end of second to last line', () => {\n    newTest({\n      title: 'select',\n      start: ['    func() {', '    |    hi;', '        alw;', '    }'],\n      keysPressed: 'vi{yG0P',\n      end: ['    func() {', '        hi;', '        alw;', '|        hi;', '        alw;', '    }'],\n    });\n  });\n\n  suite('Transition between visual mode', () => {\n    test('vv will back to normal mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    });\n\n    test('vV will transit to visual line mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n    });\n\n    test('v<C-v> will transit to visual block mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n    });\n\n    test('Vv will transit to visual (char) mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n    });\n\n    test('VV will back to normal mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    });\n\n    test('V<C-v> will transit to visual block mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n    });\n\n    test('<C-v>v will transit to visual (char) mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n      await modeHandler.handleMultipleKeyEvents(['v']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n    });\n\n    test('<C-v>V will back to visual line mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n      await modeHandler.handleMultipleKeyEvents(['V']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n    });\n\n    test('<C-v><C-v> will back to normal mode', async () => {\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n      await modeHandler.handleMultipleKeyEvents(['<C-v>']);\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n    });\n  });\n\n  suite('replace text in characterwise visual-mode with characterwise register content', () => {\n    test('gv selects the last pasted text (which is shorter than original)', async () => {\n      await modeHandler.handleMultipleKeyEvents(\n        'ireplace this\\nwith me\\nor with me longer than the target'.split(''),\n      );\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        '2ggv$hy'.split(''), // yank the second line\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggv$hp'.split(''), // replace the first line\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      assertEqualLines(['with me', 'with me', 'or with me longer than the target']);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'with me' at the first line\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 'with me'.length);\n      assert.strictEqual(selection.end.line, 0);\n    });\n\n    test('gv selects the last pasted text (which is longer than original)', async () => {\n      await modeHandler.handleMultipleKeyEvents(\n        'ireplace this\\nwith me\\nor with me longer than the target'.split(''),\n      );\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        'v0y'.split(''), // yank the last line\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggv$hp'.split(''), // replace the first line\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      assertEqualLines([\n        'or with me longer than the target',\n        'with me',\n        'or with me longer than the target',\n      ]);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'or with me longer than the target' at the first line\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 'or with me longer than the target'.length);\n      assert.strictEqual(selection.end.line, 0);\n    });\n\n    test('gv selects the last pasted text (multiline)', async () => {\n      await modeHandler.handleMultipleKeyEvents('ireplace this\\nfoo\\nbar'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        '2ggvjey'.split(''), // yank 'foo\\nbar'\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggvep'.split(''), // replace 'replace'\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n      assertEqualLines(['foo', 'bar this', 'foo', 'bar']);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'foo\\nbar'\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 3);\n      assert.strictEqual(selection.end.line, 1);\n    });\n  });\n\n  suite('can handle gn', () => {\n    test('gn selects the next match text', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('ggv'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 'hello'.length);\n      assert.strictEqual(selection.end.line, 1);\n    });\n\n    test('gn selects the current word at |hello', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2ggv'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 5);\n      assert.strictEqual(selection.end.line, 1);\n    });\n\n    test('gn selects the current word at h|ello', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2gglv'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 1);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 5);\n      assert.strictEqual(selection.end.line, 1);\n    });\n\n    test('gn selects the current word at hel|lo', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2ggehv'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 3);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 5);\n      assert.strictEqual(selection.end.line, 1);\n    });\n\n    test('gn selects the next word at hell|o', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2ggev'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 4);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 5);\n      assert.strictEqual(selection.end.line, 2);\n    });\n\n    test('gn selects the next word at hello|', async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2ggelv'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 5);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 5);\n      assert.strictEqual(selection.end.line, 2);\n    });\n  });\n\n  suite('can prepend text with I', () => {\n    newTest({\n      title: 'multiline insert from bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'vkkI_',\n      end: ['111', '2_|22', '_|333', '_|444', '555'],\n    });\n\n    newTest({\n      title: 'multiline insert from top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'vjjI_',\n      end: ['111', '2_|22', '_|333', '_|444', '555'],\n    });\n\n    newTest({\n      title: 'skips blank lines',\n      start: ['111', '2|22', ' ', '444', '555'],\n      keysPressed: 'vjjI_',\n      end: ['111', '2_|22', ' ', '_|444', '555'],\n    });\n  });\n\n  suite('can append text with A', () => {\n    newTest({\n      title: 'multiline append from bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'vkkA_',\n      end: ['111', '222_|', '333_|', '44_|4', '555'],\n    });\n\n    newTest({\n      title: 'multiline append from top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'vjjA_',\n      end: ['111', '222_|', '333_|', '44_|4', '555'],\n    });\n\n    newTest({\n      title: 'skips blank lines',\n      start: ['111', '2|22', ' ', '444', '555'],\n      keysPressed: 'vjjA_',\n      end: ['111', '222_|', ' ', '44_|4', '555'],\n    });\n  });\n\n  suite('Can handle u/gu, U/gU', () => {\n    newTest({\n      title: 'U/gU on single character',\n      start: ['|one two three'],\n      keysPressed: 'vUwwvgU',\n      end: ['One two |Three'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'U/gU across a selection',\n      start: ['|one two three'],\n      keysPressed: 'vllllUwwvlgU',\n      end: ['ONE Two |THree'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'U/gU across a selection (reverse)',\n      start: ['|one two three'],\n      keysPressed: 'wvhhUwwvhhgU',\n      end: ['onE Tw|O Three'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'u/gu on single character',\n      start: ['|ONE TWO THREE'],\n      keysPressed: 'vuwwvgu',\n      end: ['oNE TWO |tHREE'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'u/gu across a selection',\n      start: ['|ONE TWO THREE'],\n      keysPressed: 'vlllluwwvlgu',\n      end: ['one tWO |thREE'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'u/gu across a selection (reverse)',\n      start: ['|ONE TWO THREE'],\n      keysPressed: 'wvhhuwwvhhgu',\n      end: ['ONe tW|o tHREE'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('Can handle ~/g~', () => {\n    newTest({\n      title: '~/g~ on single character',\n      start: ['|one TWO three FOUR'],\n      keysPressed: 'v~wvg~wv~wvg~',\n      end: ['One tWO Three |fOUR'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '~/g~ across a selection',\n      start: ['|OnE TwO tHrEe'],\n      keysPressed: 'vllll~wwvlg~',\n      end: ['oNe twO |ThrEe'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: \"Can handle 'J' when the selected area spans multiple lines\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: 'vjjJ',\n    end: ['one two| three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the entire selected area is on the same line\",\n    start: ['one', '|two', 'three', 'four'],\n    keysPressed: 'vlgJ',\n    end: ['one', 'two|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the selected area spans multiple lines\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: 'vjjgJ',\n    end: ['onetwo|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the selected area spans multiple lines and line has whitespaces\",\n    start: ['o|ne  ', 'two', '  three', 'four'],\n    keysPressed: 'vjjgJ',\n    end: ['one  two|  three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when start position of the selected area is below the stop\",\n    start: ['one', 'two', 't|hree', 'four'],\n    keysPressed: 'vkkgJ',\n    end: ['onetwo|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Preserves desired column correctly when moving in visual mode',\n    start: ['|one', '', 'two', 'three'],\n    keysPressed: 'vjj<Esc>',\n    end: ['one', '', '|two', 'three'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Updates desired column correctly when moving in visual mode',\n    start: ['|one', 'two', 'three'],\n    keysPressed: 'vlj<Esc>',\n    end: ['one', 't|wo', 'three'],\n    endMode: Mode.Normal,\n  });\n\n  suite('Can handle o', () => {\n    newTest({\n      title: 'Can select the space after the last character',\n      start: ['ab', 'ab|c', 'abcd'],\n      keysPressed: 'vkd',\n      end: ['a|b', 'abcd'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title:\n        'After executing o twice, can keep the selection of the space after the last character',\n      start: ['ab', 'ab|c', 'abcd'],\n      keysPressed: 'vkood',\n      end: ['a|b', 'abcd'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('C, R, and S', () => {\n    for (const command of ['C', 'R', 'S']) {\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode`,\n        start: ['AAAAA', 'BB|BBB', 'CCCCC', 'DDDDD', 'EEEEE'],\n        keysPressed: `vjjh${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode (backward selection)`,\n        start: ['AAAAA', 'BBBBB', 'CCCCC', 'DD|DDD', 'EEEEE'],\n        keysPressed: `vkkl${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n    }\n  });\n\n  suite('Visual mode with command editor.action.smartSelect.grow', () => {\n    newTest({\n      title: 'Command editor.action.smartSelect.grow enters visual mode',\n      config: {\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        leader: ' ',\n      },\n      start: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"be|fore\": [\"j\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      keysPressed: ' aflh',\n      end: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"before|\": [\"j\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      endMode: Mode.Visual,\n    });\n\n    newTest({\n      title: 'Command editor.action.smartSelect.grow enters visual mode and increases selection',\n      config: {\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        visualModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        leader: ' ',\n      },\n      start: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"be|fore\": [\"j\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      keysPressed: ' af afd',\n      end: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `   | `,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Command editor.action.smartSelect.grow enters visual mode on single character',\n      config: {\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        visualModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        leader: ' ',\n      },\n      start: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"before\": [\"|j\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      keysPressed: ' afd',\n      end: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"before\": [\"|\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Command editor.action.smartSelect.grow enters visual mode on multicursors',\n      config: {\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        visualModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        leader: ' ',\n      },\n      start: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"before\": [\"|j\"],`,\n        `    \"after\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      // the initial 'vlgb<Esc>_ll' is just to create a multicursor on the words 'before' and 'after'\n      keysPressed: 'vlgb<Esc>_ll afd<Esc>',\n      end: [\n        `\"vim.normalModeKeyBindingsNonRecursive\": [`,\n        `  {`,\n        `    \"|\": [\"j\"],`,\n        `    \"\": [\"g\", \"j\"],`,\n        `  },`,\n        `]`,\n      ],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Command editor.action.smartSelect.expand on visual mode linewise',\n      config: {\n        visualModeKeyBindings: [\n          {\n            before: ['J'],\n            commands: ['editor.action.smartSelect.expand'],\n          },\n        ],\n        leader: ' ',\n      },\n      start: [\n        `{`,\n        `  \"vim.visualModeKeyBindingsNonRecursive\": [`,\n        `    {|`,\n        `      \"before\": [\"J\"],`,\n        `      \"commands\": [\"editor.action.smartSelect.expand\"]`,\n        `    },`,\n        `    {`,\n        `      \"before\": [\"K\"],`,\n        `      \"commands\": [\"editor.action.smartSelect.shrink\"]`,\n        `    }`,\n        `  ],`,\n        `}`,\n      ],\n      keysPressed: 'VJd',\n      end: [\n        `{`,\n        `  \"vim.visualModeKeyBindingsNonRecursive\": [`,\n        `|`,\n        `    {`,\n        `      \"before\": [\"K\"],`,\n        `      \"commands\": [\"editor.action.smartSelect.shrink\"]`,\n        `    }`,\n        `  ],`,\n        `}`,\n      ],\n      endMode: Mode.Normal,\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/modeVisualBlock.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest } from '../testSimplifier';\nimport { assertEqualLines, setupWorkspace } from './../testUtils';\n\nsuite('VisualBlock mode', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('can be activated', async () => {\n    modeHandler.vimState.editor = vscode.window.activeTextEditor!;\n\n    await modeHandler.handleKeyEvent('<C-v>');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualBlock);\n\n    await modeHandler.handleKeyEvent('<C-v>');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  newTest({\n    title: '`[count]<C-v>` selects [count characters]',\n    start: ['a|bcde'],\n    keysPressed: '3<C-v>d',\n    end: ['a|e'],\n  });\n\n  newTest({\n    title: '`[count]<C-v>` does not go past EOL',\n    start: ['a|bcde', '12345'],\n    keysPressed: '100<C-v>d',\n    end: ['|a', '12345'],\n  });\n\n  suite('`A` (append at end of each line of block)', () => {\n    newTest({\n      title: '`A` with forward selection',\n      start: ['t|est', 'test'],\n      keysPressed: '<C-v>' + 'lj' + 'A123',\n      end: ['tes123|t', 'tes123|t'],\n    });\n\n    newTest({\n      title: '`A` with backward selection',\n      start: ['te|st', 'test'],\n      keysPressed: '<C-v>' + 'hj' + 'A123',\n      end: ['tes123|t', 'tes123|t'],\n    });\n\n    newTest({\n      title: '`A` over shorter line adds necessary spaces',\n      start: ['te|st', 'te', 't'],\n      keysPressed: '<C-v>' + 'jj' + 'A123',\n      end: ['tes123|t', 'te 123|', 't  123|'],\n    });\n  });\n\n  suite('`I` (insert at start of each line of block)', () => {\n    newTest({\n      title: '`I` with forward selection',\n      start: ['t|est', 'test'],\n      keysPressed: '<C-v>' + 'lj' + 'I123',\n      end: ['t123|est', 't123|est'],\n    });\n\n    newTest({\n      title: '`I` with backward selection',\n      start: ['te|st', 'test'],\n      keysPressed: '<C-v>' + 'hj' + 'I123',\n      end: ['t123|est', 't123|est'],\n    });\n\n    newTest({\n      title: 'Can handle `I` with empty lines on first character (inserts on empty line)',\n      start: ['|test', '', 'test'],\n      keysPressed: '<C-v>' + 'lljj' + 'I123',\n      end: ['123|test', '123|', '123|test'],\n    });\n\n    newTest({\n      title:\n        'Can handle `I` with empty lines on non-first character (does not insert on empty line)',\n      start: ['t|est', '', 'test'],\n      keysPressed: '<C-v>' + 'lljj' + 'I123',\n      end: ['t123|est', '', 't123|est'],\n    });\n  });\n\n  for (const key of ['c', 's']) {\n    suite(`Change ('${key}')`, () => {\n      newTest({\n        title: 'With forward selection',\n        start: ['t|est', 'test'],\n        keysPressed: '<C-v>' + 'lj' + 'c123',\n        end: ['t123|t', 't123|t'],\n      });\n\n      newTest({\n        title: 'With backward selection',\n        start: ['te|st', 'test'],\n        keysPressed: '<C-v>' + 'hj' + 'c123',\n        end: ['t123|t', 't123|t'],\n      });\n\n      newTest({\n        title: 'Skips short lines',\n        start: ['te|st', '', 'x', 'test'],\n        keysPressed: '<C-v>' + 'h3j' + 'c123',\n        end: ['t123|t', '', 'x', 't123|t'],\n      });\n    });\n  }\n\n  newTest({\n    title: '`rX` replaces all characters in block with X',\n    start: ['1234', '2|345', '3456', '4567'],\n    keysPressed: '<C-v>' + 'lj' + 'rX',\n    end: ['1234', '2|XX5', '3XX6', '4567'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: '`D` deletes from block to end of each line',\n    start: ['te|st', 'test'],\n    keysPressed: '<C-v>jD',\n    end: ['t|e', 'te'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: '`C` deletes from block to end of each line, enters multi-cursor Insert mode',\n    start: ['t|est', 'test'],\n    keysPressed: '<C-v>jC' + 'xyz',\n    end: ['txyz|', 'txyz|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'gj'\",\n    start: ['t|est', 'test'],\n    keysPressed: '<C-v>gjI123',\n    end: ['t123|est', 't123|est'],\n  });\n\n  suite('`>` (indent at left edge of block)', () => {\n    newTest({\n      title: 'Repeated multiline `[count]>` indent (2 spaces) top down selection',\n      editorOptions: { tabSize: 2 },\n      start: ['This is |a long line', 'Short', 'Another long line'],\n      keysPressed: '<C-v>jj3>jhh.',\n      end: ['This is       a long line', 'Sh|      ort', 'An      other       long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]>` indent (2 spaces) bottom up selection',\n      editorOptions: { tabSize: 2 },\n      start: ['This is a long line', 'Short', 'Another |long line'],\n      keysPressed: '<C-v>kk3>jhh.',\n      end: ['This is       a long line', 'Sh|      ort', 'An      other       long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]>` indent (4 spaces) top down selection',\n      editorOptions: { tabSize: 4 },\n      start: ['This is |a long line', 'Short', 'Another long line'],\n      keysPressed: '<C-v>jj3>jhh.',\n      end: [\n        'This is             a long line',\n        'Sh|            ort',\n        'An            other             long line',\n      ],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]>` indent (4 spaces) bottom up selection',\n      editorOptions: { tabSize: 4 },\n      start: ['This is a long line', 'Short', 'Another |long line'],\n      keysPressed: '<C-v>kk3>jhh.',\n      end: [\n        'This is             a long line',\n        'Sh|            ort',\n        'An            other             long line',\n      ],\n    });\n  });\n\n  suite('`<` (outdent at left edge of block)', () => {\n    newTest({\n      title: '`[count]<` outdent (2 spaces) should have no effect on non-whitespace character',\n      editorOptions: { tabSize: 2 },\n      start: ['This is |a long line', 'Short', 'Another long line'],\n      keysPressed: '<C-v>2<',\n      end: ['This is |a long line', 'Short', 'Another long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]<` outdent (2 spaces) top down selection',\n      editorOptions: { tabSize: 2 },\n      start: ['This is |  a long line', 'Short', 'Another           long line'],\n      keysPressed: '<C-v>jj2<jj.',\n      end: ['This is a long line', 'Short', 'Another |  long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]<` outdent (2 spaces) bottom up selection',\n      editorOptions: { tabSize: 2 },\n      start: ['This is   a long line', 'Short', 'Another |          long line'],\n      keysPressed: '<C-v>kk2<jj.',\n      end: ['This is a long line', 'Short', 'Another |  long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]<` outdent (4 spaces) top down selection',\n      editorOptions: { tabSize: 4 },\n      start: ['This is |  a long line', 'Short', 'Another           long line'],\n      keysPressed: '<C-v>jj2<jj.',\n      end: ['This is a long line', 'Short', 'Another |long line'],\n    });\n    newTest({\n      title: 'Repeated multiline `[count]<` outdent (4 spaces) bottom up selection',\n      editorOptions: { tabSize: 4 },\n      start: ['This is   a long line', 'Short', 'Another |          long line'],\n      keysPressed: '<C-v>kk2<jj.',\n      end: ['This is a long line', 'Short', 'Another |long line'],\n    });\n  });\n\n  suite('Non-darwin `<C-c>` tests', () => {\n    if (process.platform === 'darwin') {\n      return;\n    }\n\n    test('`<C-c>` copies and sets mode to normal', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'Y', 'p', 'p']);\n\n      assertEqualLines(['one two three', 'one two three', 'one two three']);\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'H', '<C-v>', 'e', 'j', 'j', '<C-c>']);\n\n      // ensuring we're back in normal\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n      // test copy by pasting back\n      await modeHandler.handleMultipleKeyEvents(['H', '\"', '+', 'P']);\n\n      // TODO: should be\n      // assertEqualLines(['oneone two three', 'oneone two three', 'oneone two three']);\n      // unfortunately it is\n      assertEqualLines(['one', 'one', 'one', 'one two three', 'one two three', 'one two three']);\n    });\n  });\n\n  newTest({\n    title: 'Properly add to end of line (`j` then `$`)',\n    start: ['|Dog', 'Angry', 'Dog', 'Angry', 'Dog'],\n    keysPressed: '<C-v>4j$Aaa',\n    end: ['Dogaa|', 'Angryaa|', 'Dogaa|', 'Angryaa|', 'Dogaa|'],\n  });\n\n  newTest({\n    title: 'Properly add to end of lines (`$` then `j`)',\n    start: ['|Dog', 'Angry', 'Dog', 'Angry', 'Dog'],\n    keysPressed: '<C-v>' + '$' + '4j' + 'Aaa',\n    end: ['Dogaa|', 'Angryaa|', 'Dogaa|', 'Angryaa|', 'Dogaa|'],\n  });\n\n  newTest({\n    title: '`o` works in VisualBlock mode',\n    start: ['|foo', 'bar', 'baz'],\n    keysPressed: '<C-v>' + 'jjll' + 'o' + 'l' + 'd',\n    end: ['|f', 'b', 'b'],\n  });\n\n  // TODO: `O` works in VisualBlock mode\n\n  for (const cmd of ['s', 'd', 'x', 'X']) {\n    newTest({\n      title: `'${cmd}' deletes block`,\n      start: ['11111', '2|2222', '33333', '44444', '55555'],\n      keysPressed: `<C-v>jjll${cmd}`,\n      end: ['11111', '2|2', cmd === 's' ? '3|3' : '33', cmd === 's' ? '4|4' : '44', '55555'],\n      endMode: cmd === 's' ? Mode.Insert : Mode.Normal,\n    });\n  }\n\n  newTest({\n    title: 'Select register using `\"` works in VisualBlock mode',\n    start: ['abcde', '0|1234', 'abcde', '01234'],\n    keysPressed: '<C-v>' + 'llj' + '\"ay' + 'Go' + '<C-r>a<Esc>',\n    end: ['abcde', '01234', 'abcde', '01234', '123', 'bc|d'],\n  });\n\n  newTest({\n    title: 'Copy to register using `c` works in Visual Block mode',\n    start: ['1|111', '2222', ''],\n    keysPressed: '<C-v>' + 'lj' + 'c<Esc>' + 'jjp',\n    end: ['11', '22', '|11', '22'],\n  });\n\n  newTest({\n    title: 'Copy to register using `s` works in Visual Block mode',\n    start: ['11|11', '2222', ''],\n    keysPressed: '<C-v>' + 'hj' + 's<Esc>' + 'jjp',\n    end: ['11', '22', '|11', '22'],\n  });\n\n  newTest({\n    title: 'Copy to register using `D` works in Visual Block mode',\n    start: ['1|111', '22222', ''],\n    keysPressed: '<C-v>' + 'lj' + 'D<Esc>' + 'jjp',\n    end: ['1', '2', '|111', '2222'],\n  });\n\n  suite('`J`', () => {\n    newTest({\n      title: \"Can handle 'J' when the entire visual block is on the same line\",\n      start: ['one', '|two', 'three', 'four'],\n      keysPressed: '<C-v>lJ',\n      end: ['one', 'two| three', 'four'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'J' when the visual block spans multiple lines\",\n      start: ['o|ne', 'two', 'three', 'four'],\n      keysPressed: '<C-v>jjlJ',\n      end: ['one two| three', 'four'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'J' when start position of the visual block is below the stop\",\n      start: ['one', 'two', 't|hree', 'four'],\n      keysPressed: '<C-v>kkJ',\n      end: ['one two| three', 'four'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  // TODO(9830)\n  suite.skip('`gJ`', () => {\n    newTest({\n      title: \"Can handle 'gJ' when the entire visual block is on the same line\",\n      start: ['one', '|two', 'three', 'four'],\n      keysPressed: '<C-v>lgJ',\n      end: ['one', 'two|three', 'four'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'gJ' when the visual block spans multiple lines\",\n      start: ['o|ne', 'two', 'three', 'four'],\n      keysPressed: '<C-v>jjlgJ',\n      end: ['onetwo|t|hre|e', 'four'], // TODO(#9830): Should have only one cursor\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'gJ' when the visual block spans multiple lines and line has whitespaces\",\n      start: ['o|ne  ', 'two', '  three', 'four'],\n      keysPressed: '<C-v>jjlgJ',\n      end: ['one  two| | thre|e', 'four'], // TODO(#9830): Should have only one cursor\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: \"Can handle 'gJ' when start position of the visual block is below the stop\",\n      start: ['one', 'two', 't|hree', 'four'],\n      keysPressed: '<C-v>kkgJ',\n      end: ['onetwo|thre|e', 'four'], // TODO(#9830): Should have only one cursor\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: 'Can handle `~` and `g~` in VisualBlock mode',\n    start: ['|OnE', 'tWo', 'ThReE', 'fOuR'],\n    keysPressed: '<C-v>' + 'jl' + '~' + 'jjl' + '<C-v>' + 'jl' + 'g~',\n    end: ['oNE', 'Two', 'T|HreE', 'foUR'],\n  });\n\n  suite('`R` and `S`', () => {\n    for (const command of ['R', 'S']) {\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode`,\n        start: ['AAAAA', 'BB|BBB', 'CCCCC', 'DDDDD', 'EEEEE'],\n        keysPressed: `<C-v>jjh${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode (backward selection)`,\n        start: ['AAAAA', 'BBBBB', 'CCCCC', 'DD|DDD', 'EEEEE'],\n        keysPressed: `<C-v>kkl${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "test/mode/modeVisualLine.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest } from '../testSimplifier';\nimport { assertEqualLines, setupWorkspace } from './../testUtils';\n\nsuite('Mode Visual Line', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  test('can be activated', async () => {\n    await modeHandler.handleKeyEvent('V');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n\n    await modeHandler.handleKeyEvent('V');\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('<count>V selects <count> lines', async () => {\n    await modeHandler.handleMultipleKeyEvents('iLine 1\\nLine 2\\nLine 3\\nLine 4\\nLine 5'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g']);\n\n    await modeHandler.handleMultipleKeyEvents(['j', '3', 'V']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n    assert.strictEqual(modeHandler.vimState.cursorStartPosition.line, 1);\n    assert.strictEqual(modeHandler.vimState.cursorStopPosition.line, 3);\n  });\n\n  test('Can handle w', async () => {\n    await modeHandler.handleMultipleKeyEvents('itest test test\\ntest\\n'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'v', 'w']);\n\n    const sel = modeHandler.vimState.editor.selection;\n    assert.strictEqual(sel.start.character, 0);\n    assert.strictEqual(sel.start.line, 0);\n\n    // The input cursor comes BEFORE the block cursor. Try it out, this\n    // is how Vim works.\n    assert.strictEqual(sel.end.character, 6);\n    assert.strictEqual(sel.end.line, 0);\n  });\n\n  test('Can handle wd', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'd']);\n\n    assertEqualLines(['wo three']);\n  });\n\n  test('Can handle x', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'x']);\n\n    assertEqualLines(['ne two three']);\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('Can handle x across a selection', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'x']);\n\n    assertEqualLines(['wo three']);\n\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n  });\n\n  test('Can do vwd in middle of sentence', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three foar'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'l', 'l', 'l', 'l', 'v', 'w', 'd']);\n\n    assertEqualLines(['one hree foar']);\n  });\n\n  test('Can do vwd in middle of sentence', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'l', 'l', 'l', 'l', 'v', 'w', 'd']);\n\n    assertEqualLines(['one hree']);\n  });\n\n  test('Can do vwd multiple times', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three four'.split(''));\n    await modeHandler.handleMultipleKeyEvents([\n      '<Esc>',\n      '^',\n      'v',\n      'w',\n      'd',\n      'v',\n      'w',\n      'd',\n      'v',\n      'w',\n      'd',\n    ]);\n\n    assertEqualLines(['our']);\n  });\n\n  test('handles case where we go from selecting on right side to selecting on left side', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents([\n      '<Esc>',\n      '^',\n      'l',\n      'l',\n      'l',\n      'l',\n      'v',\n      'w',\n      'b',\n      'b',\n      'd',\n    ]);\n\n    assertEqualLines(['wo three']);\n  });\n\n  test('handles case where we delete over a newline', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two\\n\\nthree four'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '0', 'k', 'k', 'v', '}', 'd']);\n\n    assertEqualLines(['three four']);\n  });\n\n  test('handles change operator', async () => {\n    await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'v', 'w', 'c']);\n\n    assertEqualLines(['wo three']);\n    assert.strictEqual(modeHandler.vimState.currentMode, Mode.Insert);\n  });\n\n  suite(\"Vim's EOL handling is weird\", () => {\n    test('delete through eol', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', '^', 'g', 'g', 'v', 'l', 'l', 'l', 'd']);\n\n      assertEqualLines(['two']);\n    });\n\n    test('join 2 lines by deleting through eol', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'l', 'v', 'l', 'l', 'd']);\n\n      assertEqualLines(['otwo']);\n    });\n\n    test(\"d$ doesn't delete whole line\", async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'd', '$']);\n\n      assertEqualLines(['', 'two']);\n    });\n\n    test('vd$ does delete whole line', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione\\ntwo'.split(''));\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'g', 'g', 'v', '$', 'd']);\n\n      assertEqualLines(['two']);\n    });\n  });\n\n  suite('Arrow keys work perfectly in Visual Line Mode', () => {\n    newTest({\n      title: 'Can handle <up> key',\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'V<up>x',\n      end: ['blah', '|hur'],\n    });\n\n    newTest({\n      title: 'Can handle <down> key',\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'V<down>x',\n      end: ['blah', '|duh'],\n    });\n  });\n\n  suite('Screen line motions in Visual Line Mode', () => {\n    newTest({\n      title: \"Can handle 'gk'\",\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'Vgkx',\n      end: ['blah', '|hur'],\n    });\n\n    newTest({\n      title: \"Can handle 'gj'\",\n      start: ['blah', 'duh', '|dur', 'hur'],\n      keysPressed: 'Vgjx',\n      end: ['blah', '|duh'],\n    });\n  });\n\n  suite('Can handle d/c correctly in Visual Line Mode', () => {\n    newTest({\n      title: 'Can handle d key',\n      start: ['|{', '  a = 1;', '}'],\n      keysPressed: 'VGdp',\n      end: ['', '|{', '  a = 1;', '}'],\n    });\n\n    newTest({\n      title: 'Can handle d key',\n      start: ['|{', '  a = 1;', '}'],\n      keysPressed: 'VGdP',\n      end: ['|{', '  a = 1;', '}', ''],\n    });\n\n    newTest({\n      title: 'Can handle d key',\n      start: ['1', '2', '|{', '  a = 1;', '}'],\n      keysPressed: 'VGdp',\n      end: ['1', '2', '|{', '  a = 1;', '}'],\n    });\n\n    newTest({\n      title: 'Can handle d key',\n      start: ['1', '2', '|{', '  a = 1;', '}'],\n      keysPressed: 'VGdP',\n      end: ['1', '|{', '  a = 1;', '}', '2'],\n    });\n\n    newTest({\n      title: \"can handle 'c'\",\n      start: ['foo', 'b|ar', 'fun'],\n      keysPressed: 'Vc',\n      end: ['foo', '|', 'fun'],\n      endMode: Mode.Insert,\n    });\n  });\n\n  suite('handles replace in visual line mode', () => {\n    newTest({\n      title: 'Can do a single line replace',\n      start: ['one |two three four five', 'one two three four five'],\n      keysPressed: 'Vr1',\n      end: ['|11111111111111111111111', 'one two three four five'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do a multi visual line replace',\n      start: ['one |two three four five', 'one two three four five'],\n      keysPressed: 'Vjr1',\n      end: ['|11111111111111111111111', '11111111111111111111111'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'Can do a multi visual line replace from the bottom up',\n      start: ['test', 'test', 'test', '|test', 'test'],\n      keysPressed: 'Vkkr1',\n      end: ['test', '|1111', '1111', '1111', 'test'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('search works in visual line mode', () => {\n    newTest({\n      title: 'Works with /',\n      start: ['f|oo', 'bar', 'fun', 'baz'],\n      keysPressed: 'V/fun\\nx',\n      end: ['|baz'],\n    });\n\n    newTest({\n      title: 'Works with ?',\n      start: ['foo', 'bar', 'fun', 'b|az'],\n      keysPressed: 'V?bar\\nx',\n      end: ['|foo'],\n    });\n  });\n\n  suite('Non-darwin <C-c> tests', () => {\n    if (process.platform === 'darwin') {\n      return;\n    }\n\n    test('<C-c> copies and sets mode to normal', async () => {\n      await modeHandler.handleMultipleKeyEvents('ione two three'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'Y', 'p']);\n\n      assertEqualLines(['one two three', 'one two three']);\n\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', 'H', 'V', '<C-c>']);\n\n      // ensuring we're back in normal\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Normal);\n\n      // test copy by pasting back from clipboard\n      await modeHandler.handleMultipleKeyEvents(['H', '\"', '+', 'P']);\n\n      // TODO: should be assertEqualLines(['one two three', 'one two three', 'one two three']);\n      // unfortunately it is\n      assertEqualLines(['one two threeone two three', 'one two three']);\n    });\n  });\n\n  newTest({\n    title: 'Vp updates register content',\n    start: ['|hello', 'world'],\n    keysPressed: 'ddVpP',\n    end: ['|world', 'hello'],\n  });\n\n  newTest({\n    title: '\"xVp only updates default register content',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: 'V\"ayjVj\"ap\"app',\n    end: ['abc', 'abc', 'abc', '|def', 'ghi'],\n  });\n\n  newTest({\n    title: 'Vp does not append unnecessary newlines (first line)',\n    start: ['|begin', 'middle', 'end'],\n    keysPressed: 'yyVp',\n    end: ['|begin', 'middle', 'end'],\n  });\n\n  newTest({\n    title: 'Vp does not append unnecessary newlines (middle line)',\n    start: ['begin', '|middle', 'end'],\n    keysPressed: 'yyVp',\n    end: ['begin', '|middle', 'end'],\n  });\n\n  newTest({\n    title: 'Vp does not append unnecessary newlines (last line)',\n    start: ['begin', 'middle', '|end'],\n    keysPressed: 'yyVp',\n    end: ['begin', 'middle', '|end'],\n  });\n\n  newTest({\n    title: 'Vp places the cursor on first non-whitespace character on line',\n    start: ['begin', '|    middle', 'end'],\n    keysPressed: 'yyjVp',\n    end: ['begin', '    middle', '    |middle'],\n  });\n\n  suite('replace text in linewise visual-mode with linewise register content', () => {\n    newTest({\n      title: 'yyVp does not change the content but changes cursor position',\n      start: ['fo|o', 'bar', 'fun', 'baz'],\n      keysPressed: 'yyVp',\n      end: ['|foo', 'bar', 'fun', 'baz'],\n    });\n\n    newTest({\n      title: 'linewise visual put works also in the end of document',\n      start: ['foo', 'bar', 'fun', '|baz'],\n      keysPressed: 'yyVp',\n      end: ['foo', 'bar', 'fun', '|baz'],\n    });\n\n    test('gv selects the last pasted text (which is shorter than original)', async () => {\n      await modeHandler.handleMultipleKeyEvents(\n        'ireplace this\\nwith me\\nor with me longer than the target'.split(''),\n      );\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        '2ggyy'.split(''), // yank the second line\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggVp'.split(''), // replace the first line\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      assertEqualLines(['with me', 'with me', 'or with me longer than the target']);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'with me' at the first line\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 'with me'.length);\n      assert.strictEqual(selection.end.line, 0);\n    });\n\n    test('gv selects the last pasted text (which is longer than original)', async () => {\n      await modeHandler.handleMultipleKeyEvents(\n        'ireplace this\\nwith me\\nor with me longer than the target'.split(''),\n      );\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        'yy'.split(''), // yank the last line\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggVp'.split(''), // replace the first line\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      assertEqualLines([\n        'or with me longer than the target',\n        'with me',\n        'or with me longer than the target',\n      ]);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'or with me longer than the target' at the first line\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 'or with me longer than the target'.length);\n      assert.strictEqual(selection.end.line, 0);\n    });\n\n    test('gv selects the last pasted text (multiline)', async () => {\n      await modeHandler.handleMultipleKeyEvents('ireplace this\\nfoo\\nbar'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>']);\n      await modeHandler.handleMultipleKeyEvents(\n        'Vky'.split(''), // yank 'foo\\nbar\\n'\n      );\n      await modeHandler.handleMultipleKeyEvents(\n        'ggVp'.split(''), // replace the first line\n      );\n      await modeHandler.handleMultipleKeyEvents(['g', 'v']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.VisualLine);\n      assertEqualLines(['foo', 'bar', 'foo', 'bar']);\n\n      const selection = modeHandler.vimState.editor.selection;\n      // ensuring selecting 'foo\\nbar\\n'\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 0);\n      assert.strictEqual(selection.end.character, 3);\n      assert.strictEqual(selection.end.line, 1);\n    });\n  });\n\n  suite('can prepend text with I', () => {\n    newTest({\n      title: 'multiline insert from bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'VkkI_',\n      end: ['111', '_|222', '_|333', '_|444', '555'],\n    });\n\n    newTest({\n      title: 'multiline insert from top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'VjjI_',\n      end: ['111', '_|222', '_|333', '_|444', '555'],\n    });\n\n    newTest({\n      title: 'skips blank lines',\n      start: ['111', '2|22', ' ', '444', '555'],\n      keysPressed: 'VjjI_',\n      end: ['111', '_|222', ' ', '_|444', '555'],\n    });\n  });\n\n  suite('can append text with A', () => {\n    newTest({\n      title: 'multiline append from bottom up selection',\n      start: ['111', '222', '333', '4|44', '555'],\n      keysPressed: 'VkkA_',\n      end: ['111', '222_|', '333_|', '444_|', '555'],\n    });\n\n    newTest({\n      title: 'multiline append from top down selection',\n      start: ['111', '2|22', '333', '444', '555'],\n      keysPressed: 'VjjA_',\n      end: ['111', '222_|', '333_|', '444_|', '555'],\n    });\n\n    newTest({\n      title: 'skips blank lines',\n      start: ['111', '2|22', ' ', '444', '555'],\n      keysPressed: 'VjjA_',\n      end: ['111', '222_|', ' ', '444_|', '555'],\n    });\n\n    newTest({\n      title: 'updates desired column correctly',\n      start: ['|111111', '222', '333'],\n      keysPressed: 'VjjA<Esc>jk',\n      end: ['11111|1', '222', '333'],\n    });\n  });\n\n  newTest({\n    title: 'Exiting via <Esc> returns cursor to original column',\n    start: ['rocinante', 'nau|voo', 'anubis', 'canterbury'],\n    keysPressed: 'Vj<Esc>',\n    end: ['rocinante', 'nauvoo', 'anu|bis', 'canterbury'],\n  });\n\n  newTest({\n    title: 'Exiting via `VV` returns cursor to original column',\n    start: ['rocinante', 'nau|voo', 'anubis', 'canterbury'],\n    keysPressed: 'VjV',\n    end: ['rocinante', 'nauvoo', 'anu|bis', 'canterbury'],\n  });\n\n  suite('Can handle ~/g~ in visual line mode', () => {\n    newTest({\n      title: '~/g~ on single line',\n      start: ['|OnE', 'tWo', 'ThReE', 'fOuR'],\n      keysPressed: 'jV~jjVg~',\n      end: ['OnE', 'TwO', 'ThReE', '|FoUr'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '~/g~ on multiple lines',\n      start: ['|OnE', 'tWo', 'ThReE', 'fOuR'],\n      keysPressed: 'Vj~jjVjg~',\n      end: ['oNe', 'TwO', '|tHrEe', 'FoUr'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: \"Can handle 'J' when the selected area spans multiple lines\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: 'VjjJ',\n    end: ['one two| three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the entire selected area is on the same line\",\n    start: ['one', '|two', 'three', 'four'],\n    keysPressed: 'VlgJ',\n    end: ['one', 'two|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the selected area spans multiple lines\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: 'VjjgJ',\n    end: ['onetwo|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when the selected area spans multiple lines and line has whitespaces\",\n    start: ['o|ne  ', 'two', '  three', 'four'],\n    keysPressed: 'VjjgJ',\n    end: ['one  two|  three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' when start position of the selected area is below the stop\",\n    start: ['one', 'two', 't|hree', 'four'],\n    keysPressed: 'VkkgJ',\n    end: ['onetwo|three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  suite('C, R, and S', () => {\n    for (const command of ['C', 'R', 'S']) {\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode`,\n        start: ['AAAAA', 'BB|BBB', 'CCCCC', 'DDDDD', 'EEEEE'],\n        keysPressed: `Vjj${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n\n      newTest({\n        title: `'${command}' deletes selected lines and puts you into insert mode (backward selection)`,\n        start: ['AAAAA', 'BBBBB', 'CCCCC', 'DD|DDD', 'EEEEE'],\n        keysPressed: `Vkk${command}`,\n        end: ['AAAAA', '|', 'EEEEE'],\n        endMode: Mode.Insert,\n      });\n    }\n  });\n\n  suite('Vi{ should not select the ending brace, if it is on a new line.', () => {\n    test('Vi{ selection content test', async () => {\n      // Insert the full block using insert mode simulation\n      await modeHandler.handleMultipleKeyEvents('i{\\nsome text on new line\\n}'.split(''));\n\n      // Back to normal mode\n      await modeHandler.handleKeyEvent('<Esc>');\n\n      // Move cursor to start of \"some text...\"\n      await modeHandler.handleMultipleKeyEvents(['g', 'g', 'j', 'l', 'l', 'l', 'l']);\n\n      // Simulate Vi{\n      await modeHandler.handleMultipleKeyEvents(['V', 'i', '{']);\n\n      const doc = modeHandler.vimState.editor.document;\n      const sel = modeHandler.vimState.editor.selection;\n      const selectedText = doc.getText(sel);\n\n      assert.strictEqual(selectedText, '  some text on new line');\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/commands.test.ts",
    "content": "import { Mode } from '../../../src/mode/mode';\nimport { newTest } from '../../testSimplifier';\n\nsuite('Mode Normal', () => {\n  newTest({\n    title: \"Can handle 'x'\",\n    start: ['te|xt'],\n    keysPressed: 'x',\n    end: ['te|t'],\n  });\n\n  newTest({\n    title: \"Can handle 'Nx'\",\n    start: ['te|xt'],\n    keysPressed: '2x',\n    end: ['t|e'],\n  });\n\n  newTest({\n    title: \"Can handle 'Nx' and paste\",\n    start: ['t|ext'],\n    keysPressed: '2xo<Esc>p',\n    end: ['tt', 'e|x'],\n  });\n\n  newTest({\n    title: \"Can handle 'x' at end of line\",\n    start: ['one tw|o'],\n    keysPressed: '^llxxxxxxxxx',\n    end: ['|'],\n  });\n\n  newTest({\n    title: \"'x' with count does not go over EOL\",\n    start: ['one t|wo', 'three four'],\n    keysPressed: '1000x',\n    end: ['one |t', 'three four'],\n  });\n\n  newTest({\n    title: \"Can handle 'Ns'\",\n    start: ['|text'],\n    keysPressed: '3s',\n    end: ['|t'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'Ns' at end of line\",\n    start: ['te|xt'],\n    keysPressed: '3s',\n    end: ['te|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle '<Del>'\",\n    start: ['te|xt'],\n    keysPressed: '<Del>',\n    end: ['te|t'],\n  });\n\n  newTest({\n    title: \"Can handle '<Del>' with counts, which removes the last character of the count\",\n    start: ['|text'],\n    keysPressed: '10<Del>x',\n    end: ['|ext'],\n  });\n\n  newTest({\n    title: \"Can handle '<Del>' at end of line\",\n    start: ['one tw|o'],\n    keysPressed: '^ll<Del><Del><Del><Del><Del><Del><Del><Del><Del>',\n    end: ['|'],\n  });\n\n  newTest({\n    title: \"Can handle 'cc'\",\n    start: ['one', '|one two', 'three'],\n    keysPressed: 'cca<Esc>',\n    end: ['one', '|a', 'three'],\n  });\n\n  newTest({\n    title: \"Can handle 'Ncc'\",\n    start: ['one', '|one two', 'three four', 'five'],\n    keysPressed: '2cca<Esc>',\n    end: ['one', '|a', 'five'],\n  });\n\n  newTest({\n    title: \"Can handle 'yy'\",\n    start: ['|one'],\n    keysPressed: 'yyO<Esc>p',\n    end: ['', '|one', 'one'],\n  });\n\n  newTest({\n    title: \"Can handle 'D'\",\n    start: ['tex|t'],\n    keysPressed: '^llD',\n    end: ['t|e'],\n  });\n\n  newTest({\n    title: \"Can handle 'D' on empty lines\",\n    start: ['text', '|', 'text'],\n    keysPressed: 'D',\n    end: ['text', '|', 'text'],\n  });\n\n  newTest({\n    title: \"Can handle 'D' with count 1\",\n    start: ['o|ne', 'two', 'three'],\n    keysPressed: '1D',\n    end: ['|o', 'two', 'three'],\n  });\n\n  newTest({\n    title: \"Can handle 'D' with count 3\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: '3D',\n    end: ['|o', 'four'],\n  });\n\n  newTest({\n    title: \"Can handle 'D' with count exceeding max number of rows\",\n    start: ['o|ne', 'two', 'three', 'four'],\n    keysPressed: '100D',\n    end: ['|o'],\n  });\n\n  newTest({\n    title: \"Can handle 'D' with count when end position is on blank line\",\n    start: ['o|ne', '', 'three'],\n    keysPressed: '2D',\n    end: ['|o', 'three'],\n  });\n\n  newTest({\n    title: \"Can handle 'DD'\",\n    start: ['tex|t'],\n    keysPressed: '^llDD',\n    end: ['|t'],\n  });\n\n  newTest({\n    title: \"Can handle 'C'\",\n    start: ['tex|t'],\n    keysPressed: '^llC',\n    end: ['te|'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'NC'\",\n    start: ['tex|t', 'one', 'two'],\n    keysPressed: '^ll2C',\n    end: ['te|', 'two'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'NC' and put\",\n    start: ['tex|t', 'one', 'two'],\n    keysPressed: '\"a2C<C-r>a',\n    end: ['text', 'one|', 'two'],\n    endMode: Mode.Insert,\n  });\n\n  newTest({\n    title: \"Can handle 'r'\",\n    start: ['tex|t'],\n    keysPressed: 'hrs',\n    end: ['te|st'],\n  });\n\n  newTest({\n    title: \"Can handle '<Count>r'\",\n    start: ['123|456', '789'],\n    keysPressed: '2ra',\n    end: ['123a|a6', '789'],\n  });\n\n  newTest({\n    title: \"Can handle '<Count>r'\",\n    start: ['123|456', '789'],\n    keysPressed: '4ra',\n    end: ['123|456', '789'],\n  });\n\n  newTest({\n    title: \"Can handle 'r' after 'dd'\",\n    start: ['one', 'two', 'thre|e'],\n    keysPressed: 'kddrT',\n    end: ['one', '|Three'],\n  });\n\n  newTest({\n    title: \"Can handle 'r\\n'\",\n    start: ['abc|defg', '12345'],\n    keysPressed: 'r\\n',\n    end: ['abc', '|efg', '12345'],\n  });\n\n  // `r` only ever inserts one newline, regardless of count prefix\n  newTest({\n    title: \"Can handle '<Count>r\\n'\",\n    start: ['abc|defg', '12345'],\n    keysPressed: '3r\\n',\n    end: ['abc', '|g', '12345'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' once\",\n    start: ['one', 'tw|o'],\n    keysPressed: 'kJ',\n    end: ['one| two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' twice\",\n    start: ['one', 'two', 'thre|e'],\n    keysPressed: 'kkJJ',\n    end: ['one two| three'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with empty last line\",\n    start: ['one', 'two', '|'],\n    keysPressed: 'kJ',\n    end: ['one', 'tw|o'],\n  });\n\n  newTest({\n    title: \"Can handle 'J's with multiple empty last lines\",\n    start: ['one', 'two', '', '', '', '|'],\n    keysPressed: 'kkkkkJJJJJ',\n    end: ['one tw|o'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with leading white space on next line\",\n    start: ['on|e', ' two'],\n    keysPressed: 'kJ',\n    end: ['one| two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with only white space on next line\",\n    start: ['on|e', '    '],\n    keysPressed: 'J',\n    end: ['on|e'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with TWO indented lines\",\n    start: ['   on|e', '    two'],\n    keysPressed: 'kJ',\n    end: ['   one| two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with ')' first character on next line\",\n    start: ['one(', ')tw|o'],\n    keysPressed: 'kJ',\n    end: ['one(|)two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with line ending with '.'\",\n    start: ['one.', 'tw|o'],\n    keysPressed: 'kJ',\n    end: ['one.|  two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with line ending with '?'\",\n    start: ['one?', 'tw|o'],\n    keysPressed: 'kJ',\n    end: ['one?|  two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with line ending with '!'\",\n    start: ['one!', 'tw|o'],\n    keysPressed: 'kJ',\n    end: ['one!|  two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with line ending with tab\",\n    start: ['one.\\t', 'tw|o'],\n    keysPressed: 'kJ',\n    end: ['one.\\t|two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with a following delete\",\n    start: ['on|e', 'two'],\n    keysPressed: 'Jx',\n    end: ['one|two'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with count\",\n    start: ['|one', 'two', 'three', 'four'],\n    keysPressed: '3J',\n    end: ['one two| three', 'four'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' with count if count is larger than EOF\",\n    start: ['|one', 'two', 'three', 'four'],\n    keysPressed: '100J',\n    end: ['one two three| four'],\n  });\n\n  newTest({\n    title: \"Can handle 'J' in Visual Line mode\",\n    start: ['on|e', 'two'],\n    keysPressed: 'VJ',\n    end: ['one| two'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' once\",\n    start: ['|one', 'two'],\n    keysPressed: 'kgJ',\n    end: ['one|two'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' once when line has whitespaces\",\n    start: ['|one', '  two'],\n    keysPressed: 'kgJ',\n    end: ['one|  two'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' with count\",\n    start: ['|one', 'two', 'three', 'four'],\n    keysPressed: '3gJ',\n    end: ['onetwo|three', 'four'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' with count when line has whitespaces\",\n    start: ['|one', '  two  ', 'three', 'four'],\n    keysPressed: '3gJ',\n    end: ['one  two  |three', 'four'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' with count and end position is blank line\",\n    start: ['|one', 'two', '', 'three', 'four'],\n    keysPressed: '3gJ',\n    end: ['onetw|o', 'three', 'four'],\n  });\n\n  newTest({\n    title: \"Can handle 'gJ' with count exceeding max number of rows\",\n    start: ['|one', 'two', 'three', 'four'],\n    keysPressed: '100gJ',\n    end: ['onetwothree|four'],\n  });\n\n  newTest({\n    title: \"Can handle '~'\",\n    start: ['|text'],\n    keysPressed: '~',\n    end: ['T|ext'],\n  });\n\n  newTest({\n    title: \"'~' goes over line boundaries if whichwrap contains '~'\",\n    config: { whichwrap: '~' },\n    start: ['on|e', 'two'],\n    keysPressed: '~',\n    end: ['onE', '|two'],\n  });\n\n  newTest({\n    title: \"'~' does not goes over line boundaries if whichwrap does not contain '~'\",\n    config: { whichwrap: '' },\n    start: ['on|e', 'two'],\n    keysPressed: '~',\n    end: ['on|E', 'two'],\n  });\n\n  newTest({\n    title: \"Can handle 'g~{motion}'\",\n    start: ['|one two'],\n    keysPressed: 'g~w',\n    end: ['|ONE two'],\n  });\n\n  newTest({\n    title: \"Can handle 'g~~'\",\n    start: ['oNe', 'Tw|O', 'tHrEe'],\n    keysPressed: 'g~~',\n    end: ['oNe', '|tWo', 'tHrEe'],\n  });\n\n  newTest({\n    title: \"Can handle '<BS>' in insert mode\",\n    start: ['one', '|'],\n    keysPressed: 'i<BS><Esc>',\n    end: ['on|e'],\n  });\n\n  newTest({\n    title: 'Can handle undo with P',\n    start: ['one', '|two', 'three'],\n    keysPressed: 'ddkPjddu',\n    end: ['two', '|one', 'three'],\n  });\n\n  newTest({\n    title: \"Can handle 'ge' in multiple lines case1\",\n    start: ['one two', 'three', 'four fiv|e'],\n    keysPressed: 'gege',\n    end: ['one two', 'thre|e', 'four five'],\n  });\n\n  newTest({\n    title: \"Can handle 'ge' in multiple lines case2\",\n    start: ['one two', 'three', 'four fiv|e'],\n    keysPressed: 'gegegegege',\n    end: ['|one two', 'three', 'four five'],\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/dot.test.ts",
    "content": "import { Mode } from '../../../src/mode/mode';\nimport { newTest } from '../../testSimplifier';\nimport { setupWorkspace } from './../../testUtils';\n\nsuite('Dot Operator', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        tabstop: 4,\n        expandtab: false,\n      },\n    });\n  });\n\n  newTest({\n    title: \"Can repeat '~' with <num>\",\n    start: ['|teXt'],\n    keysPressed: '4~',\n    end: ['TEx|T'],\n  });\n\n  newTest({\n    title: \"Can repeat '~' with dot\",\n    start: ['|teXt'],\n    keysPressed: '~...',\n    end: ['TEx|T'],\n  });\n\n  newTest({\n    title: \"Can repeat 'x'\",\n    start: ['|text'],\n    keysPressed: 'x.',\n    end: ['|xt'],\n  });\n\n  newTest({\n    title: \"Can repeat 'J'\",\n    start: ['|one', 'two', 'three'],\n    keysPressed: 'J.',\n    end: ['one two| three'],\n  });\n\n  newTest({\n    title: 'Can handle dot with A',\n    start: ['|one', 'two', 'three'],\n    keysPressed: 'A!<Esc>j.j.',\n    end: ['one!', 'two!', 'three|!'],\n  });\n\n  newTest({\n    title: 'Can handle dot with I',\n    start: ['on|e', 'two', 'three'],\n    keysPressed: 'I!<Esc>j.j.',\n    end: ['!one', '!two', '|!three'],\n  });\n\n  newTest({\n    title: 'Can repeat actions that require selections',\n    start: ['on|e', 'two'],\n    keysPressed: 'Vj>.',\n    end: ['\\t\\t|one', '\\t\\ttwo'],\n  });\n});\n\nsuite('Repeat content change', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        tabstop: 4,\n        expandtab: false,\n      },\n    });\n  });\n\n  newTest({\n    title: 'Can repeat `<BS>`',\n    start: ['abcd|e', 'ABCDE'],\n    keysPressed: 'i<BS><Esc>' + 'j$.',\n    end: ['abce', 'AB|CE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<BS><BS>`',\n    start: ['abcd|e', 'ABCDE'],\n    keysPressed: 'i<BS><BS><Esc>' + 'j$.',\n    end: ['abe', 'A|BE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<BS>` within larger insertion',\n    start: ['abcd|e', 'ABCDE'],\n    keysPressed: 'ixy<BS>z<Esc>' + 'j$.',\n    end: ['abcdxze', 'ABCDx|zE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<Del>`',\n    start: ['|abcde', 'ABCDE'],\n    keysPressed: 'a<Del><Esc>' + 'j0.',\n    end: ['acde', '|ACDE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<Del><Del>`',\n    start: ['|abcde', 'ABCDE'],\n    keysPressed: 'a<Del><Del><Esc>' + 'j0.',\n    end: ['ade', '|ADE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<Del>` within larger insertion',\n    start: ['|abcde', 'ABCDE'],\n    keysPressed: 'axy<Del>z<Esc>' + 'j0.',\n    end: ['axyzcde', 'Axy|zCDE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<BS>` and `<Del>`',\n    start: ['abc|def', 'ABCDEF'],\n    keysPressed: 'i<BS><Del>0<Esc>' + 'j0fD.',\n    end: ['ab0ef', 'AB|0EF'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat insertion with newline',\n    start: ['ab|cde', 'ABCDE'],\n    keysPressed: 'i1\\n2<Esc>' + 'j0ll.',\n    end: ['ab1', '2cde', 'AB1', '|2CDE'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat insertion with auto-matched brackets',\n    start: ['|', ''],\n    keysPressed: 'ifoo(bar<Esc>' + 'j.',\n    end: ['foo(bar)', 'foo(ba|r)'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Repeat insertion with auto-matched parentheses in the middle',\n    start: ['geometry.append(|width);', 'geometry.append(height);'],\n    keysPressed: 'ce' + 'std::to_string(' + '<C-r>\"' + '<Esc>' + 'j0fh' + '.',\n    end: ['geometry.append(std::to_string(width));', 'geometry.append(std::to_string(heigh|t));'],\n  });\n\n  newTest({\n    title: 'Repeat insertion that deletes auto-matched closing parenthesis',\n    start: ['|', ''],\n    keysPressed: 'i' + '[(' + '<Del>' + 'xyz' + '<Esc>' + 'j.',\n    end: ['[(xyz]', '[(xy|z]'],\n  });\n\n  newTest({\n    title: 'Can repeat `<C-y>`',\n    start: ['abcde', '|12', 'ABCDE', '12'],\n    keysPressed: 'A<C-y><C-y><Esc>' + 'jj0.',\n    end: ['abcde', '12cd', 'ABCDE', '12c|d'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can repeat `<C-e>`',\n    start: ['abcde', '|12', 'ABCDE', '12'],\n    keysPressed: 'A<C-e><C-e><Esc>' + 'jj0.',\n    end: ['abcde', '12CD', 'ABCDE', '12C|D'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: \"Can repeat '<C-t>'\",\n    start: ['on|e', 'two'],\n    keysPressed: 'a<C-t><Esc>j.',\n    end: ['\\tone', '\\ttw|o'],\n  });\n\n  newTest({\n    title: \"Can repeat insert change and '<C-t>'\",\n    start: ['on|e', 'two'],\n    keysPressed: 'a<C-t>b<Esc>j.',\n    end: ['\\toneb', '\\ttwo|b'],\n  });\n\n  newTest({\n    title: 'Can repeat change by `<C-a>`',\n    start: ['on|e', 'two'],\n    keysPressed: 'a<C-t>b<Esc>ja<C-a><Esc>',\n    end: ['\\toneb', '\\ttwo|b'],\n  });\n\n  newTest({\n    title: 'Repeating insertion with arrows ignores everything before last arrow',\n    start: ['one |two three'],\n    keysPressed: 'i' + 'X<left>Y<left>Z' + '<Esc>' + 'W.',\n    end: ['one ZYXtwo |Zthree'],\n  });\n\n  newTest({\n    title: 'Repeating insertion with arrows always inserts just before cursor',\n    start: ['o|ne two three'],\n    keysPressed: 'A' + 'X<left>Y<left>Z' + '<Esc>' + '0W.',\n    end: ['one |Ztwo threeZYX'],\n  });\n\n  newTest({\n    title: 'Cached content change will be cleared by arrow keys',\n    start: ['on|e', 'two'],\n    keysPressed: 'a<C-t>b<left>c<Esc>j.',\n    end: ['\\tonecb', 'tw|co'],\n  });\n\n  newTest({\n    title: 'Can repeat change after v<Esc> and :<Esc>',\n    start: ['aaa bbb ccc dd|d'],\n    keysPressed: 'ciwxxx<Esc>' + 'bb.' + 'bbv<Esc>.' + 'bb:<Esc>.',\n    end: ['xx|x xxx xxx xxx'],\n  });\n\n  newTest({\n    title: 'Can repeat change after V<Esc> and <C-q><Esc>',\n    start: ['aaa bbb ccc dd|d'],\n    keysPressed: 'ciwxxx<Esc>' + 'bb.' + 'bbV<Esc>.' + 'bb<C-q><Esc>.',\n    end: ['xx|x xxx xxx xxx'],\n  });\n\n  newTest({\n    title: 'Can repeat insertion with increasing numbered register',\n    start: ['|1 2 3'],\n    keysPressed: '\"1daw..' + '\"1p..',\n    end: ['1 2 |3'],\n  });\n\n  newTest({\n    title: 'Does not increase the \"0 register',\n    start: ['|lorem', 'ipsum'],\n    keysPressed: 'dd' + 'yy' + '\"0p.',\n    end: ['ipsum', 'ipsum', '|ipsum'],\n  });\n\n  newTest({\n    title: 'Can repeat line insertion with 9 as highest numbered register',\n    start: ['|one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'],\n    keysPressed: 'dd' + 'dd' + 'dd' + 'dd' + 'dd' + 'dd' + 'dd' + 'dd' + 'dd' + '\"1p.........',\n    end: ['', 'nine', 'eight', 'seven', 'six', 'five', 'four', 'three', 'two', 'one', '|one'],\n  });\n});\n\nsuite('Dot Operator repeat with remap', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        insertModeKeyBindings: [\n          {\n            before: ['j', 'j', 'k'],\n            after: ['<esc>'],\n          },\n        ],\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'w'],\n            after: ['d', 'w'],\n          },\n        ],\n        leader: ' ',\n      },\n    });\n  });\n\n  newTest({\n    title: \"Can repeat content change using 'jjk' mapped to '<Esc>' without trailing characters\",\n    start: ['on|e', 'two'],\n    keysPressed: 'ciwfoojjkj.',\n    end: ['foo', 'fo|o'],\n  });\n\n  newTest({\n    title: \"Can repeat '<leader>w' when mapped to 'dw'\",\n    start: ['|one two three'],\n    keysPressed: ' w.',\n    end: ['|three'],\n  });\n\n  newTest({\n    title: 'Repeatable dot with insert mode',\n    start: ['|', ''],\n    keysPressed: 'ivar<Esc>j4.',\n    end: ['var', 'varvarvarva|r'],\n  });\n\n  newTest({\n    title: 'Repeatable dot with replace mode',\n    start: ['|aaaaa', 'aaaaa'],\n    keysPressed: 'r.j4.',\n    end: ['.aaaa', '...|.a'],\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/matchingBracket.test.ts",
    "content": "import { newTest } from '../../testSimplifier';\nimport { setupWorkspace } from './../../testUtils';\n\nsuite('Matching Bracket (%)', () => {\n  suiteSetup(setupWorkspace);\n\n  newTest({\n    title: 'before opening parenthesis',\n    start: ['|one (two)'],\n    keysPressed: '%',\n    end: ['one (two|)'],\n  });\n\n  newTest({\n    title: 'inside parenthesis',\n    start: ['(|one { two })'],\n    keysPressed: '%',\n    end: ['(one { two |})'],\n  });\n\n  newTest({\n    title: 'nested parenthesis beginning',\n    start: ['|((( )))'],\n    keysPressed: '%',\n    end: ['((( ))|)'],\n  });\n\n  newTest({\n    title: 'nested parenthesis end',\n    start: ['((( ))|)'],\n    keysPressed: '%',\n    end: ['|((( )))'],\n  });\n\n  newTest({\n    title: 'nested bracket and parenthesis beginning',\n    start: ['|[(( ))]'],\n    keysPressed: '%',\n    end: ['[(( ))|]'],\n  });\n\n  newTest({\n    title: 'nested bracket, parenthesis, braces beginning',\n    start: ['|[(( }}} ))]'],\n    keysPressed: '%',\n    end: ['[(( }}} ))|]'],\n  });\n\n  newTest({\n    title: 'nested bracket, parenthesis, braces end',\n    start: ['[(( }}} ))|]'],\n    keysPressed: '%',\n    end: ['|[(( }}} ))]'],\n  });\n\n  newTest({\n    title: 'parentheses after >',\n    start: ['|foo->bar(baz);'],\n    keysPressed: '%',\n    end: ['foo->bar(baz|);'],\n  });\n\n  newTest({\n    title: 'parentheses after \"',\n    start: ['|test \"in quotes\" [(in brackets)]'],\n    keysPressed: '%',\n    end: ['test \"in quotes\" [(in brackets)|]'],\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/motionMatchpairs.test.ts",
    "content": "import { newTest } from '../../testSimplifier';\nimport { setupWorkspace } from './../../testUtils';\n\nsuite('matchpair empty', () => {\n  setup(async () => {\n    await setupWorkspace({ config: { matchpairs: '' } });\n  });\n\n  newTest({\n    title: \"basic motion doesn't work\",\n    start: ['bla |< blubb >'],\n    keysPressed: '%',\n    end: ['bla |< blubb >'],\n  });\n});\n\nsuite('matchpairs enabled', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { matchpairs: '<:>' } });\n  });\n\n  suite('Tests for % with matchpairs', () => {\n    newTest({\n      title: 'basic jump with %',\n      start: ['for |< bar > baz'],\n      keysPressed: '%',\n      end: ['for < bar |> baz'],\n    });\n\n    newTest({\n      title: 'basic jump with %. cursor before pair',\n      start: ['|for < bar > baz'],\n      keysPressed: '%',\n      end: ['for < bar |> baz'],\n    });\n\n    newTest({\n      title: 'backwards jump with %',\n      start: ['for < bar |> baz'],\n      keysPressed: '%',\n      end: ['for |< bar > baz'],\n    });\n\n    newTest({\n      title: 'nested jump with %',\n      start: ['for |< < bar > <boo> > baz'],\n      keysPressed: '%',\n      end: ['for < < bar > <boo> |> baz'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/motions.test.ts",
    "content": "import { Mode } from '../../../src/mode/mode';\nimport { newTest, newTestSkip } from '../../testSimplifier';\nimport { setupWorkspace } from './../../testUtils';\n\nsuite('Motions in Normal Mode', () => {\n  suiteSetup(setupWorkspace);\n\n  suite('w', () => {\n    newTest({\n      title: 'w moves to next word',\n      start: ['one |two three four'],\n      keysPressed: 'w',\n      end: ['one two |three four'],\n    });\n\n    newTest({\n      title: 'w goes over all whitespace',\n      start: ['one |two \\t  \\t three four'],\n      keysPressed: 'w',\n      end: ['one two \\t  \\t |three four'],\n    });\n\n    newTest({\n      title: 'w stops at punctuation',\n      start: ['one |two,.;/three four'],\n      keysPressed: 'w',\n      end: ['one two|,.;/three four'],\n    });\n\n    newTest({\n      title: 'w on punctuation jumps over punctuation',\n      start: ['one two|,.;/three four'],\n      keysPressed: 'w',\n      end: ['one two,.;/|three four'],\n    });\n\n    newTest({\n      title: 'w goes over EOL',\n      start: ['o|ne  ', '  two three'],\n      keysPressed: 'w',\n      end: ['one  ', '  |two three'],\n    });\n\n    newTest({\n      title: '[count]w',\n      start: ['|one two three four'],\n      keysPressed: '2w',\n      end: ['one two |three four'],\n    });\n  });\n\n  newTest({\n    title: 'Can handle [(',\n    start: ['({|})'],\n    keysPressed: '[(',\n    end: ['|({})'],\n  });\n\n  newTest({\n    title: 'Can handle nested [(',\n    start: ['(({|})'],\n    keysPressed: '[(',\n    end: ['(|({})'],\n  });\n\n  newTest({\n    title: 'Can handle <number>[(',\n    start: ['(({|})'],\n    keysPressed: '2[(',\n    end: ['|(({})'],\n  });\n\n  newTest({\n    title: 'Can handle [( and character under cursor exclusive',\n    start: ['(|({})'],\n    keysPressed: '[(',\n    end: ['|(({})'],\n  });\n\n  newTest({\n    title: 'Can handle ])',\n    start: ['({|})'],\n    keysPressed: '])',\n    end: ['({}|)'],\n  });\n\n  newTest({\n    title: 'Can handle nested ])',\n    start: ['(({|}))'],\n    keysPressed: '])',\n    end: ['(({}|))'],\n  });\n\n  newTest({\n    title: 'Can handle <number>])',\n    start: ['(({|}))'],\n    keysPressed: '2])',\n    end: ['(({})|)'],\n  });\n\n  newTest({\n    title: 'Can handle ]) and character under cursor exclusive',\n    start: ['(({}|))'],\n    keysPressed: '])',\n    end: ['(({})|)'],\n  });\n\n  newTest({\n    title: 'Can handle [{',\n    start: ['{(|)}'],\n    keysPressed: '[{',\n    end: ['|{()}'],\n  });\n\n  newTest({\n    title: 'Can handle nested [{',\n    start: ['{{(|)}'],\n    keysPressed: '[{',\n    end: ['{|{()}'],\n  });\n\n  newTest({\n    title: 'Can handle <number>[{',\n    start: ['{{(|)}'],\n    keysPressed: '2[{',\n    end: ['|{{()}'],\n  });\n\n  newTest({\n    title: 'Can handle [{ and character under cursor exclusive',\n    start: ['{|{()}'],\n    keysPressed: '[{',\n    end: ['|{{()}'],\n  });\n\n  newTest({\n    title: 'Can handle 99[{ (past start of file)',\n    start: ['{{{|()}}}'],\n    keysPressed: '99[{',\n    end: ['|{{{()}}}'],\n  });\n\n  newTest({\n    title: 'Can handle ]}',\n    start: ['{(|)}'],\n    keysPressed: ']}',\n    end: ['{()|}'],\n  });\n\n  newTest({\n    title: 'Can handle nested ]}',\n    start: ['{{(|)}}'],\n    keysPressed: ']}',\n    end: ['{{()|}}'],\n  });\n\n  newTest({\n    title: 'Can handle <number>]}',\n    start: ['{{(|)}}'],\n    keysPressed: '2]}',\n    end: ['{{()}|}'],\n  });\n\n  newTest({\n    title: 'Can handle ]} and character under cursor exclusive',\n    start: ['{{()|}}'],\n    keysPressed: ']}',\n    end: ['{{()}|}'],\n  });\n\n  newTest({\n    title: 'Can handle 99]} (past end of file)',\n    start: ['{{{|()}}}'],\n    keysPressed: '99]}',\n    end: ['{{{()}}|}'],\n  });\n\n  newTest({\n    title: \"Can handle 'ge'\",\n    start: ['text tex|t'],\n    keysPressed: '$ge',\n    end: ['tex|t text'],\n  });\n\n  suite('gg', () => {\n    newTest({\n      title: 'gg (startofline=true)',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: 'gg',\n      end: [' |123456', ' 123456', ' 123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]gg (startofline=true)',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '3gg',\n      end: [' 123456', ' 123456', ' |123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]gg (startofline=true), count greater than last line',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '9gg',\n      end: [' 123456', ' 123456', ' |123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'gg (startofline=false)',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: 'gg',\n      end: [' 123|456', ' 123456', ' 123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]gg (startofline=false)',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '3gg',\n      end: [' 123456', ' 123456', ' 123|456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]gg (startofline=false), count greater than last line',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '9gg',\n      end: [' 123456', ' 123456', ' 123|456'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('G', () => {\n    newTest({\n      title: 'G (startofline=true)',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: 'G',\n      end: [' 123456', ' 123456', ' |123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]G (startofline=true)',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '1G',\n      end: [' |123456', ' 123456', ' 123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]G (startofline=true), count greater than last line',\n      config: { startofline: true },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '9G',\n      end: [' 123456', ' 123456', ' |123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'G (startofline=false)',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: 'G',\n      end: [' 123456', ' 123456', ' 123|456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]G (startofline=false)',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '1G',\n      end: [' 123|456', ' 123456', ' 123456'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: '[count]G (startofline=false), count greater than last line',\n      config: { startofline: false },\n      start: [' 123456', ' 123|456', ' 123456'],\n      keysPressed: '9G',\n      end: [' 123456', ' 123456', ' 123|456'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  newTest({\n    title: 'Retain same column when moving up/down',\n    start: ['text text', 'text', 'text tex|t'],\n    keysPressed: 'kk',\n    end: ['text tex|t', 'text', 'text text'],\n  });\n\n  newTest({\n    title: 'Can handle <enter>',\n    start: ['text te|xt', 'text'],\n    keysPressed: '\\n',\n    end: ['text text', '|text'],\n  });\n\n  newTest({\n    title: '$ always keeps cursor on EOL',\n    start: ['text text', 'text', 'text tex|t'],\n    keysPressed: 'gg$jj',\n    end: ['text text', 'text', 'text tex|t'],\n  });\n\n  newTest({\n    title: 'Can handle $ with a count',\n    start: ['te|xt text', 'text', 'text text text'],\n    keysPressed: '3$',\n    end: ['text text', 'text', 'text text tex|t'],\n  });\n\n  newTest({\n    title: 'Can handle $ with a count at end of file',\n    start: ['te|xt text text'],\n    keysPressed: '3$',\n    end: ['text text tex|t'],\n  });\n\n  newTest({\n    title: 'Can handle <End> with a count',\n    start: ['te|xt text', 'text', 'text text text'],\n    keysPressed: '3<End>',\n    end: ['text text', 'text', 'text text tex|t'],\n  });\n\n  newTest({\n    title: 'Can handle <D-right> with a count',\n    start: ['te|xt text', 'text', 'text text text'],\n    keysPressed: '3<D-right>',\n    end: ['text text', 'text', 'text text tex|t'],\n  });\n\n  newTest({\n    title: \"Can handle 'f'\",\n    start: ['text tex|t'],\n    keysPressed: '^ft',\n    end: ['tex|t text'],\n  });\n\n  newTest({\n    title: \"Can handle 'f' twice\",\n    start: ['text tex|t'],\n    keysPressed: '^ftft',\n    end: ['text |text'],\n  });\n\n  newTest({\n    title: \"Can handle 'f' with <tab>\",\n    start: ['|text\\tttext'],\n    keysPressed: 'f<tab>',\n    end: ['text|\\tttext'],\n  });\n\n  newTest({\n    title: \"Can handle 'f' and find back search\",\n    start: ['text tex|t'],\n    keysPressed: 'fe,',\n    end: ['text t|ext'],\n  });\n\n  newTest({\n    title: \"Can handle 'F'\",\n    start: ['text tex|t'],\n    keysPressed: '$Ft',\n    end: ['text |text'],\n  });\n\n  newTest({\n    title: \"Can handle 'F' twice\",\n    start: ['text tex|t'],\n    keysPressed: '$FtFt',\n    end: ['tex|t text'],\n  });\n\n  newTest({\n    title: \"Can handle 'F' and find back search\",\n    start: ['|text text'],\n    keysPressed: 'Fx,',\n    end: ['te|xt text'],\n  });\n\n  // See #4313\n  newTest({\n    title: \"Can handle 'f' and multiple back searches\",\n    start: ['|a a a a a'],\n    keysPressed: 'fa;;;,,,',\n    end: ['a |a a a a'],\n  });\n\n  newTest({\n    title: \"Can handle 't'\",\n    start: ['text tex|t'],\n    keysPressed: '^tt',\n    end: ['te|xt text'],\n  });\n\n  newTest({\n    title: \"Can handle 't' twice\",\n    start: ['text tex|t'],\n    keysPressed: '^tttt',\n    end: ['te|xt text'],\n  });\n\n  newTest({\n    title: \"Can handle 't' and find back search\",\n    start: ['text tex|t'],\n    keysPressed: 'te,',\n    end: ['text te|xt'],\n  });\n\n  newTest({\n    title: \"Can handle 'T'\",\n    start: ['text tex|t'],\n    keysPressed: '$Tt',\n    end: ['text t|ext'],\n  });\n\n  newTest({\n    title: \"Can handle 'T' twice\",\n    start: ['text tex|t'],\n    keysPressed: '$TtTt',\n    end: ['text t|ext'],\n  });\n\n  newTest({\n    title: \"Can handle 'T' and find back search\",\n    start: ['|text text'],\n    keysPressed: 'Tx,',\n    end: ['t|ext text'],\n  });\n\n  newTest({\n    title: 'Can run a forward search',\n    start: ['|one two three'],\n    keysPressed: '/thr\\n',\n    end: ['one two |three'],\n  });\n\n  newTest({\n    title: 'Can run a forward and find next search',\n    start: ['|one two two two'],\n    keysPressed: '/two\\nn',\n    end: ['one two |two two'],\n  });\n\n  newTest({\n    title: 'Can run a forward and find previous search from end of word',\n    start: ['|one two one two'],\n    keysPressed: '/two/e\\nN',\n    end: ['one two one tw|o'],\n  });\n\n  newTest({\n    title: 'Can run a forward search with count 1',\n    start: ['|one two two two'],\n    keysPressed: '1/tw\\n',\n    end: ['one |two two two'],\n  });\n\n  newTest({\n    title: 'Can run a forward search with count 3',\n    start: ['|one two two two'],\n    keysPressed: '3/tw\\n',\n    end: ['one two two |two'],\n  });\n\n  newTest({\n    title: 'Can run a forward search with count exceeding max number of matches and wrapscan',\n    start: ['|one two two two'],\n    keysPressed: '5/tw\\n',\n    end: ['one two |two two'],\n  });\n\n  newTest({\n    title: 'Can run a forward search with count exceeding max number of matches and nowrapscan',\n    config: { wrapscan: false },\n    start: ['|one two two two'],\n    keysPressed: '5/tw\\n',\n    end: ['|one two two two'],\n    statusBar: 'E385: Search hit BOTTOM without match for: tw',\n  });\n\n  // These \"remembering history between editor\" tests have started\n  // breaking. Since I don't remember these tests ever breaking for real, and\n  // because they're the cause of a lot of flaky tests, I'm disabling these for\n  // now.\n\n  // test('Remembers a forward search from another editor', async function() {\n  //   // adding another editor\n  //   await setupWorkspace();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['|one two two two'],\n  //     keysPressed: '/two\\n',\n  //     end: ['one |two two two'],\n  //   });\n\n  //   await modeHandler.handleMultipleKeyEvents(['g', 'T', '<Esc>']);\n\n  //   await waitForTabChange();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['|three four two one'],\n  //     keysPressed: '<Esc>n',\n  //     end: ['three four |two one'],\n  //   });\n  // });\n\n  // test('Shares forward search history from another editor', async () => {\n  //   // adding another editor\n  //   await setupWorkspace();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['|one two two two'],\n  //     keysPressed: '/two\\n',\n  //     end: ['one |two two two'],\n  //   });\n\n  //   await modeHandler.handleMultipleKeyEvents(['g', 'T', '<Esc>']);\n\n  //   await waitForTabChange();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['|three four two one'],\n  //     keysPressed: '/\\n',\n  //     end: ['three four |two one'],\n  //   });\n  // });\n\n  newTest({\n    title: 'Can run a reverse search',\n    start: ['one two thre|e'],\n    keysPressed: '?two\\n',\n    end: ['one |two three'],\n  });\n\n  newTest({\n    title: 'Can run a reverse and find next search',\n    start: ['one two two thre|e'],\n    keysPressed: '?two\\nn',\n    end: ['one |two two three'],\n  });\n\n  newTest({\n    title: 'Can run a reverse search with count 1',\n    start: ['one one one |two'],\n    keysPressed: '1?on\\n',\n    end: ['one one |one two'],\n  });\n\n  newTest({\n    title: 'Can run a reverse search with count 3',\n    start: ['one one one |two'],\n    keysPressed: '3?on\\n',\n    end: ['|one one one two'],\n  });\n\n  newTest({\n    title: 'Can run a reverse search with count exceeding max number of matches',\n    start: ['one one one |two'],\n    keysPressed: '5?on\\n',\n    end: ['one |one one two'],\n  });\n\n  // test('Remembers a reverse search from another editor', async () => {\n  //   // adding another editor\n  //   await setupWorkspace();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['one two two two|'],\n  //     keysPressed: '?two\\n',\n  //     end: ['one two two |two'],\n  //   });\n\n  //   await modeHandler.handleMultipleKeyEvents(['g', 'T', '<Esc>']);\n\n  //   await waitForTabChange();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['three four two one|'],\n  //     keysPressed: '<Esc>n',\n  //     end: ['three four |two one'],\n  //   });\n  // });\n\n  // test('Shares reverse search history from another editor', async () => {\n  //   // adding another editor\n  //   await setupWorkspace();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['one two two two|'],\n  //     keysPressed: '?two\\n',\n  //     end: ['one two two |two'],\n  //   });\n\n  //   await modeHandler.handleMultipleKeyEvents(['g', 'T', '<Esc>']);\n\n  //   await waitForTabChange();\n\n  //   await testIt(modeHandler, {\n  //     title: '',\n  //     start: ['three four two one|'],\n  //     keysPressed: '?\\n',\n  //     end: ['three four |two one'],\n  //   });\n  // });\n\n  newTest({\n    title: 'cancelled search reverts to previous search state',\n    start: ['|one', 'two two', 'three three three'],\n    keysPressed: '/two\\n/three<Esc>n',\n    end: ['one', 'two |two', 'three three three'],\n  });\n\n  newTest({\n    title: 'Backspace on empty search cancels',\n    start: ['|one two three'],\n    keysPressed: '/tw<BS><BS><BS>',\n    end: ['|one two three'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Search offsets: b does nothing',\n    start: ['|hayneedlehay'],\n    keysPressed: '/needle/b\\n',\n    end: ['hay|needlehay'],\n  });\n\n  newTest({\n    title: 'Search offsets: b2 goes 2 to the right',\n    start: ['|hayneedlehay'],\n    keysPressed: '/needle/b2\\n',\n    end: ['hayne|edlehay'],\n  });\n\n  newTest({\n    title: 'Search offsets: b+3 goes 3 to the right',\n    start: ['|hayneedlehay'],\n    keysPressed: '/needle/b+3\\n',\n    end: ['haynee|dlehay'],\n  });\n\n  newTest({\n    title: 'Search offsets: e goes to the end',\n    start: ['|hayneedlehay'],\n    keysPressed: '/needle/e\\n',\n    end: ['hayneedl|ehay'],\n  });\n\n  newTest({\n    title: 'Search offsets: character offset goes across line boundaries',\n    start: ['|hayneedlehay', '123'],\n    keysPressed: '/needle/e+5\\n',\n    end: ['hayneedlehay', '1|23'],\n  });\n\n  newTest({\n    title: 'Search offsets: 2 goes 2 down',\n    start: ['|hayneedlehay', 'abc', 'def'],\n    keysPressed: '/needle/2\\n',\n    end: ['hayneedlehay', 'abc', '|def'],\n  });\n\n  newTest({\n    title: 'Search offsets: -2 goes 2 up',\n    start: ['abc', '|def', 'hayneedlehay', 'abc', 'def'],\n    keysPressed: '/needle/-2\\n',\n    end: ['|abc', 'def', 'hayneedlehay', 'abc', 'def'],\n  });\n\n  newTest({\n    title: 'maintains column position correctly',\n    start: ['|one one one', 'two', 'three'],\n    keysPressed: 'lllljj',\n    end: ['one one one', 'two', 'thre|e'],\n  });\n\n  newTest({\n    title: 'maintains column position correctly with $',\n    start: ['|one one one', 'two', 'three'],\n    keysPressed: '$jj',\n    end: ['one one one', 'two', 'thre|e'],\n  });\n\n  // This is still currently not possible.\n  // newTest({\n  //   title: \"Can handle dot with I with tab\",\n  //   start: ['on|e', 'two', 'three'],\n  //   keysPressed: 'I<tab><Esc>j.j.',\n  //   end: ['  one', '  two', ' | three']\n  // });\n\n  newTest({\n    title: 'Can handle 0',\n    start: ['blah blah bla|h'],\n    keysPressed: '0',\n    end: ['|blah blah blah'],\n  });\n\n  newTest({\n    title: 'Can handle 0 as part of a repeat',\n    start: ['|blah blah blah'],\n    keysPressed: '10l',\n    end: ['blah blah |blah'],\n  });\n\n  for (const config of [{ visualstar: true }, { visualstar: false }]) {\n    newTest({\n      title: 'Can handle g*',\n      config,\n      start: ['|blah duh blahblah duh blah'],\n      keysPressed: 'g*',\n      end: ['blah duh |blahblah duh blah'],\n    });\n\n    newTest({\n      title: 'Can handle g*n',\n      config,\n      start: ['|blah duh blahblah duh blah'],\n      keysPressed: 'g*n',\n      end: ['blah duh blah|blah duh blah'],\n    });\n\n    newTest({\n      title: 'Can handle *',\n      config,\n      start: ['|blah blahblah duh blah blah'],\n      keysPressed: '*',\n      end: ['blah blahblah duh |blah blah'],\n    });\n\n    newTest({\n      title: 'Can handle **',\n      config,\n      start: ['|blah duh blah duh blah'],\n      keysPressed: '**',\n      end: ['blah duh blah duh |blah'],\n    });\n\n    newTest({\n      title: '* ignores smartcase (ignorecase=true)',\n      config: { ignorecase: true, smartcase: true, ...config },\n      start: ['|test TEST test'],\n      keysPressed: '*',\n      end: ['test |TEST test'],\n    });\n\n    newTest({\n      title: '* ignores smartcase (ignorecase=false)',\n      config: { ignorecase: false, smartcase: true, ...config },\n      start: ['|test TEST test'],\n      keysPressed: '*',\n      end: ['test TEST |test'],\n    });\n\n    newTest({\n      title: '* skips over word separators',\n      config,\n      start: ['const x| = 2 + 2;'],\n      // TODO: this should only require a single *\n      keysPressed: '**',\n      end: ['const x = 2 + |2;'],\n    });\n\n    newTest({\n      title: '* uses word separator if no word characters found before EOL',\n      config,\n      start: ['if (x === 2)| {', '  if (y === 3) {'],\n      // TODO: this should only require a single *\n      keysPressed: '**',\n      end: ['if (x === 2) {', '  if (y === 3) |{'],\n    });\n\n    newTest({\n      title: '* does not go over line boundaries',\n      config,\n      start: ['one  |   ', 'one two one two'],\n      keysPressed: '*',\n      end: ['one  |   ', 'one two one two'],\n      statusBar: 'E348: No string under cursor',\n    });\n\n    newTest({\n      title: 'Can handle # on whitespace',\n      config,\n      start: ['abc abcdef| abc'],\n      keysPressed: '#',\n      end: ['|abc abcdef abc'],\n    });\n\n    newTest({\n      title: 'Can handle # on EOL',\n      config,\n      start: ['abc abcdef abc| '],\n      keysPressed: '#',\n      end: ['abc abcdef abc| '],\n    });\n\n    newTest({\n      title: 'Can handle g#',\n      config,\n      start: ['blah duh blahblah duh |blah'],\n      keysPressed: 'g#',\n      end: ['blah duh blah|blah duh blah'],\n    });\n\n    newTest({\n      title: 'Can handle g#n',\n      config,\n      start: ['blah duh blahblah duh |blah'],\n      keysPressed: 'g#n',\n      end: ['blah duh |blahblah duh blah'],\n    });\n\n    newTest({\n      title: 'Can handle #',\n      config,\n      start: ['blah blah blahblah duh |blah'],\n      keysPressed: '#',\n      end: ['blah |blah blahblah duh blah'],\n    });\n\n    newTest({\n      title: 'Can handle # already on the word',\n      config,\n      start: ['one o|ne'],\n      keysPressed: '#',\n      end: ['|one one'],\n    });\n\n    newTest({\n      title: 'Can handle ##',\n      config,\n      start: ['blah duh blah duh |blah'],\n      keysPressed: '##',\n      end: ['|blah duh blah duh blah'],\n    });\n\n    // These tests take advantage of the fact that an empty search repeats the last search\n    newTest({\n      title: '* adds to search history',\n      config,\n      start: ['|ONE two three ONE two three four ONE'],\n      keysPressed: '*/\\n',\n      end: ['ONE two three ONE two three four |ONE'],\n    });\n    newTest({\n      title: '# adds to search history',\n      config,\n      start: ['ONE two three ONE two three four |ONE'],\n      keysPressed: '#?\\n',\n      end: ['|ONE two three ONE two three four ONE'],\n    });\n  }\n\n  newTest({\n    title: 'Can handle |',\n    start: ['blah duh blah duh |blah'],\n    keysPressed: '|',\n    end: ['|blah duh blah duh blah'],\n  });\n\n  newTest({\n    title: 'Can handle <number> |',\n    start: ['blah duh blah duh |blah'],\n    keysPressed: '3|',\n    end: ['bl|ah duh blah duh blah'],\n  });\n\n  newTest({\n    title: 'Can handle +',\n    start: ['|blah', 'duh'],\n    keysPressed: '+',\n    end: ['blah', '|duh'],\n  });\n\n  newTest({\n    title: 'Can handle + indent',\n    start: ['|blah', '   duh'],\n    keysPressed: '+',\n    end: ['blah', '   |duh'],\n  });\n\n  newTest({\n    title: 'Can handle + with count prefix',\n    start: ['|blah', 'duh', 'dur', 'hur'],\n    keysPressed: '2+',\n    end: ['blah', 'duh', '|dur', 'hur'],\n  });\n\n  newTest({\n    title: 'Can handle -',\n    start: ['blah', '|duh'],\n    keysPressed: '-',\n    end: ['|blah', 'duh'],\n  });\n\n  newTest({\n    title: 'Can handle - indent',\n    start: ['   blah', '|duh'],\n    keysPressed: '-',\n    end: ['   |blah', 'duh'],\n  });\n\n  newTest({\n    title: 'Can handle - with count prefix',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '2-',\n    end: ['|blah', 'duh', 'dur', 'hur'],\n  });\n\n  newTest({\n    title: 'Can handle _',\n    start: ['blah', '|duh'],\n    keysPressed: '_',\n    end: ['blah', '|duh'],\n  });\n\n  newTest({\n    title: 'Can handle _ with count prefix',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '2_',\n    end: ['blah', 'duh', 'dur', '|hur'],\n  });\n\n  newTest({\n    title: 'Can handle g_',\n    start: ['blah', '|duh'],\n    keysPressed: 'g_',\n    end: ['blah', 'du|h'],\n  });\n\n  newTest({\n    title: 'Can handle g_ with count prefix',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '2g_',\n    end: ['blah', 'duh', 'dur', 'hu|r'],\n  });\n\n  suite('`go` motion', () => {\n    newTest({\n      title: '`go` without count goes to start of document',\n      start: ['abc', 'de|f', 'ghi'],\n      keysPressed: 'go',\n      end: ['|abc', 'def', 'ghi'],\n    });\n\n    newTest({\n      title: '`[count]go` goes to offset <count>',\n      start: ['abc', 'de|f', 'ghi'],\n      keysPressed: '3go',\n      end: ['ab|c', 'def', 'ghi'],\n    });\n\n    // TODO(#4844): this fails on Windows due to \\r\\n\n    newTestSkip(\n      {\n        title: '`[count]go` goes to offset <count>, newlines disregarded',\n        start: ['abc', 'de|f', 'ghi'],\n        keysPressed: '10go',\n        end: ['abc', 'def', 'g|hi'],\n      },\n      process.platform === 'win32',\n    );\n  });\n\n  newTest({\n    title: 'Can handle <up> key',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '<up>',\n    end: ['blah', '|duh', 'dur', 'hur'],\n  });\n\n  newTest({\n    title: 'Can handle <down> key',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '<down>',\n    end: ['blah', 'duh', 'dur', '|hur'],\n  });\n\n  newTest({\n    title: 'Can handle <left> key',\n    start: ['blah', 'duh', 'd|ur', 'hur'],\n    keysPressed: '<left>',\n    end: ['blah', 'duh', '|dur', 'hur'],\n  });\n\n  newTest({\n    title: 'Can handle <right> key',\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: '<right>',\n    end: ['blah', 'duh', 'd|ur', 'hur'],\n  });\n\n  newTest({\n    title: \"Can handle 'gk'\",\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: 'gk',\n    end: ['blah', '|duh', 'dur', 'hur'],\n  });\n\n  newTest({\n    title: \"Can handle 'gj'\",\n    start: ['blah', 'duh', '|dur', 'hur'],\n    keysPressed: 'gj',\n    end: ['blah', 'duh', 'dur', '|hur'],\n  });\n\n  newTestSkip({\n    title: \"Preserves cursor position when handling 'gk'\",\n    start: ['blah', 'duh', 'a', 'hu|r '],\n    keysPressed: 'gkgk',\n    end: ['blah', 'du|h', 'a', 'hur '],\n  });\n\n  newTestSkip({\n    title: \"Preserves cursor position when handling 'gj'\",\n    start: ['blah', 'du|h', 'a', 'hur '],\n    keysPressed: 'gjgj',\n    end: ['blah', 'duh', 'a', 'hu|r '],\n  });\n\n  newTest({\n    title: 'special treatment of curly braces',\n    start: ['{', '|    {  {  }   }   ', '}'],\n    keysPressed: 'di{',\n    end: ['{', '    {|}   ', '}'],\n  });\n\n  newTest({\n    title: 'no special treatment of curly braces if any other character is present',\n    start: ['{', 'a  |  {  {  }   }   ', '}'],\n    keysPressed: 'di{',\n    end: ['{', '|}'],\n  });\n\n  suite(\"doesn't update desiredColumn when it shouldn't\", () => {\n    newTest({\n      title: 'Preserves desired cursor position when pressing zz',\n      start: ['very long line of text....|.', 'short line'],\n      keysPressed: 'jzzk',\n      end: ['very long line of text....|.', 'short line'],\n    });\n\n    newTest({\n      title: 'Preserves desired cursor position when pressing zt',\n      start: ['very long line of text....|.', 'short line'],\n      keysPressed: 'jztk',\n      end: ['very long line of text....|.', 'short line'],\n    });\n\n    newTest({\n      title: 'Preserves desired cursor position when pressing zb',\n      start: ['very long line of text....|.', 'short line'],\n      keysPressed: 'jzbk',\n      end: ['very long line of text....|.', 'short line'],\n    });\n\n    newTest({\n      title: 'Preserves desired cursor position when pressing <C-e>',\n      start: ['very long line of text....|.', 'short line'],\n      keysPressed: 'j<C-e>k',\n      end: ['very long line of text....|.', 'short line'],\n    });\n\n    newTest({\n      title: 'Preserves desired cursor position when pressing <C-y>',\n      start: ['short line', 'very long line of text....|.'],\n      keysPressed: 'k<C-y>j',\n      end: ['short line', 'very long line of text....|.'],\n    });\n\n    newTest({\n      title: 'Preserves desired cursor position when starting, but not completing, operator',\n      start: ['short line', 'very long line of text....|.'],\n      keysPressed: 'k' + 'd<Esc>' + 'j',\n      end: ['short line', 'very long line of text....|.'],\n    });\n  });\n\n  suite('Special marks', () => {\n    newTest({\n      title: 'Jump to visual start `<',\n      start: ['one |Xx two three'],\n      keysPressed: 'v2ev`<',\n      end: ['one |Xx two three'],\n    });\n\n    newTest({\n      title: 'Jump to visual end `>',\n      start: ['|one two Xx three'],\n      keysPressed: 'v2wv1G`>',\n      end: ['one two |Xx three'],\n    });\n\n    newTest({\n      title: \"Jump (line) to visual start '<\",\n      start: ['one', 'Xx|x', 'two', '  three'],\n      keysPressed: \"vjjv'<\",\n      end: ['one', '|Xxx', 'two', '  three'],\n    });\n\n    newTest({\n      title: \"Jump (line) to visual end '>\",\n      start: ['|one', '  Xxx', 'two', '  three'],\n      keysPressed: \"vjv1G'>\",\n      end: ['one', '  |Xxx', 'two', '  three'],\n    });\n\n    newTest({\n      title: 'Jump to visual line start `<',\n      start: ['one', 't|wo', 'three', 'four'],\n      keysPressed: 'Vj<Esc>' + 'gg' + '`<',\n      end: ['one', '|two', 'three', 'four'],\n    });\n\n    newTest({\n      title: 'Jump to visual line end `>',\n      start: ['one', 't|wo', 'three', 'four'],\n      keysPressed: 'Vj<Esc>' + 'gg' + '`>',\n      end: ['one', 'two', 'thre|e', 'four'],\n    });\n\n    newTest({\n      title: '`] go to the end of the previously operated or put text',\n      start: ['hello|'],\n      keysPressed: 'a world<Esc>`]',\n      end: ['hello worl|d'],\n    });\n\n    newTest({\n      title: \"'] go to the end of the previously operated or put text\",\n      start: ['hello|'],\n      keysPressed: \"a world<Esc>']\",\n      end: ['|hello world'],\n    });\n\n    newTest({\n      title: '`[ go to the start of the previously operated or put text',\n      start: ['hello|'],\n      keysPressed: 'a world<Esc>`[',\n      end: ['hello| world'],\n    });\n\n    newTest({\n      title: \"'[ go to the start of the previously operated or put text\",\n      start: ['hello|'],\n      keysPressed: \"a world<Esc>'[\",\n      end: ['|hello world'],\n    });\n\n    newTest({\n      title: '`. works correctly',\n      start: ['on|e'],\n      keysPressed: 'atwo<Esc>`.',\n      end: ['one|two'],\n    });\n\n    newTest({\n      title: \"'. works correctly\",\n      start: ['on|e'],\n      keysPressed: \"atwo<Esc>'.\",\n      end: ['|onetwo'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/mode/normalModeTests/undo.test.ts",
    "content": "import { newTest, newTestSkip } from '../../testSimplifier';\nimport { setupWorkspace } from '../../testUtils';\n\nsuite('Undo', () => {\n  suiteSetup(setupWorkspace);\n\n  suite('u', () => {\n    newTest({\n      title: 'Undo 1',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>uu',\n      end: ['|'],\n    });\n\n    newTest({\n      title: 'Undo 2',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>u',\n      end: ['ab|c'],\n    });\n\n    newTest({\n      title: 'Undo cursor',\n      start: ['|'],\n      keysPressed: 'Iabc<Esc>Idef<Esc>Ighi<Esc>uuu',\n      end: ['|'],\n    });\n\n    newTest({\n      title: 'Undo cursor 2',\n      start: ['|'],\n      keysPressed: 'Iabc<Esc>Idef<Esc>Ighi<Esc>uu',\n      end: ['|abc'],\n    });\n\n    newTest({\n      title: 'Undo cursor 3',\n      start: ['|'],\n      keysPressed: 'Iabc<Esc>Idef<Esc>Ighi<Esc>u',\n      end: ['|defabc'],\n    });\n\n    newTest({\n      title: 'Undo with movement first',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>hlhlu',\n      end: ['ab|c'],\n    });\n\n    newTest({\n      title: \"Can handle 'u' after :s/abc/def\",\n      start: ['Xa|bcX'],\n      keysPressed: ':s/abc/def/\\nu',\n      // TODO: Cursor position is wrong\n      end: ['Xa|bcX'],\n    });\n\n    newTest({\n      title: \"Can handle 'u' after :s/abc/def twice\",\n      start: ['Xa|bcX', 'YabcY', 'ZabcZ'],\n      keysPressed: ':s/abc/def/\\n' + 'j' + ':s/abc/def/\\n' + 'u',\n      end: ['XdefX', '|YabcY', 'ZabcZ'],\n    });\n\n    newTest({\n      title: 'Can handle undo delete',\n      start: ['one |two three four five'],\n      keysPressed: 'dwdwu',\n      end: ['one |three four five'],\n    });\n\n    newTest({\n      title: 'Can handle undo delete twice',\n      start: ['one |two three four five'],\n      keysPressed: 'dwdwuu',\n      end: ['one |two three four five'],\n    });\n\n    newTest({\n      title: 'Can handle undo delete with count',\n      start: ['one |two three four five'],\n      keysPressed: 'dwdw2u',\n      end: ['one |two three four five'],\n    });\n\n    newTest({\n      title: 'Undo Visual delete',\n      start: ['one |two three four five'],\n      keysPressed: 'vww' + 'd' + 'u',\n      end: ['one |two three four five'],\n    });\n\n    newTest({\n      title: 'Undo VisualBlock delete',\n      start: ['one two', 'th|ree four', 'five six', 'seven eight'],\n      keysPressed: '<C-v>jll' + 'd' + 'u',\n      end: ['one two', 'th|ree four', 'five six', 'seven eight'],\n    });\n\n    newTest({\n      title: 'Undo macro (`Jx`)',\n      start: ['ap|ple', 'banana', 'carrot'],\n      keysPressed: 'qq' + 'Jx' + 'q' + '@q' + 'u',\n      end: ['apple|banana', 'carrot'],\n    });\n\n    newTest({\n      title: '<Left> in Insert mode creates undo point',\n      start: ['Say a |word for '],\n      keysPressed:\n        'A' + 'Jimmy Brown' + '<left>'.repeat(6) + '<BS>'.repeat(5) + 'Ginger' + '<Esc>' + 'u',\n      end: ['Say a word for Jimmy| Brown'],\n    });\n\n    newTest({\n      title: '<Down> in Insert mode creates undo point',\n      start: ['|', '', ''],\n      keysPressed: 'i' + 'one' + '<down>' + 'two' + '<down>' + 'three' + '<Esc>' + 'u',\n      end: ['one', 'two', '|'],\n    });\n\n    newTest({\n      title: '<C-g>u in Insert mode creates undo point',\n      start: ['|'],\n      keysPressed: 'i' + 'one' + '<C-g>u' + ' two' + '<Esc>' + 'u',\n      end: ['on|e'],\n    });\n\n    newTest({\n      title: '<Left> in Replace mode creates undo point',\n      start: ['|one_two_three'],\n      keysPressed: 'R' + 'ONE' + '<left>' + 'TWO' + '<Esc>' + 'u',\n      // TODO: Cursor position is wrong\n      end: ['ON|E_two_three'],\n    });\n\n    newTest({\n      title: '<Down> in Replace mode creates undo point',\n      start: ['|one', 'two', 'three'],\n      keysPressed: 'R' + 'ONE' + '<down>' + 'TWO' + '<Esc>' + 'u',\n      // TODO: Cursor position is wrong\n      end: ['ONE', 'tw|o', 'three'],\n    });\n\n    newTest({\n      title: '<C-g>u in Replace mode creates undo point',\n      start: ['|ABCDEF'],\n      keysPressed: 'R' + '123' + '<C-g>u' + '456' + '<Esc>' + 'u',\n      end: ['123|DEF'],\n    });\n\n    // TODO: Make change X, git revert, make change Y, undo\n    // TODO: Test interaction with VS Code's native undo\n  });\n\n  suite('U', () => {\n    newTest({\n      title: \"Can handle 'U'\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>U',\n      end: ['|'],\n    });\n\n    newTest({\n      title: \"Can handle 'U' for multiple changes\",\n      start: ['|'],\n      keysPressed: 'idef<Esc>aghi<Esc>U',\n      end: ['|'],\n    });\n\n    newTest({\n      title: \"Can handle 'U' for new line below\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>odef<Esc>U',\n      end: ['abc', '|'],\n    });\n\n    newTestSkip({\n      title: \"Can handle 'U' for new line above\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>Odef<Esc>U',\n      end: ['|', 'abc'],\n    });\n\n    newTest({\n      title: \"Can handle 'U' for consecutive changes only\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>odef<Esc>kAghi<Esc>U',\n      end: ['ab|c', 'def'],\n    });\n\n    newTest({\n      title: \"Can handle 'u' to undo 'U'\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>Uu',\n      end: ['|abc'],\n    });\n\n    newTest({\n      title: \"Can handle 'U' to undo 'U'\",\n      start: ['|'],\n      keysPressed: 'iabc<Esc>UU',\n      end: ['|abc'],\n    });\n  });\n\n  suite('Redo', () => {\n    newTest({\n      title: 'Can handle undo delete with count and redo',\n      start: ['one |two three four five'],\n      keysPressed: 'dwdw2u<C-r>',\n      end: ['one |three four five'],\n    });\n\n    newTest({\n      title: 'Redo',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>uu<C-r>',\n      end: ['|abc'],\n    });\n\n    newTest({\n      title: 'Redo',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>uu<C-r><C-r>',\n      end: ['abc|def'],\n    });\n\n    newTest({\n      title: 'Redo',\n      start: ['|'],\n      keysPressed: 'iabc<Esc>adef<Esc>uuhlhl<C-r><C-r>',\n      end: ['abc|def'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/motion.test.ts",
    "content": "import * as assert from 'assert';\nimport { Position, window } from 'vscode';\nimport { getCurrentParagraphBeginning, getCurrentParagraphEnd } from '../src/textobject/paragraph';\nimport { WordType } from '../src/textobject/word';\nimport { TextEditor } from './../src/textEditor';\nimport { setupWorkspace } from './testUtils';\n\nsuite('basic motion', () => {\n  const text: string[] = ['mary had', 'a', 'little lamb', ' whose fleece was '];\n\n  suiteSetup(async () => {\n    await setupWorkspace({ fileContent: text });\n  });\n\n  test('char right: should move one column right', () => {\n    const position = new Position(0, 0);\n    assert.strictEqual(position.line, 0);\n    assert.strictEqual(position.character, 0);\n\n    const next = position.getRight();\n    assert.strictEqual(next.line, 0);\n    assert.strictEqual(next.character, 1);\n  });\n\n  test('char right', () => {\n    const motion = new Position(0, 8).getRight();\n\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 8);\n  });\n\n  test('char left: should move cursor one column left', () => {\n    let position = new Position(0, 5);\n    assert.strictEqual(position.line, 0);\n    assert.strictEqual(position.character, 5);\n\n    position = position.getLeft();\n    assert.strictEqual(position.line, 0);\n    assert.strictEqual(position.character, 4);\n  });\n\n  test('char left: left-most column should stay at the same location', () => {\n    let motion = new Position(0, 0);\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 0);\n\n    motion = motion.getLeft();\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  test('line down: should move cursor one line down', () => {\n    let motion = new Position(1, 0);\n    assert.strictEqual(motion.line, 1);\n    assert.strictEqual(motion.character, 0);\n\n    motion = motion.getDown();\n    assert.strictEqual(motion.line, 2);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  test('line down: bottom-most line should stay at the same location', () => {\n    let motion = new Position(3, 0);\n    assert.strictEqual(motion.line, 3);\n    assert.strictEqual(motion.character, 0);\n\n    motion = motion.getDown();\n    assert.strictEqual(motion.line, 3);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  suite('line up', () => {\n    test('should move cursor one line up', () => {\n      let position = new Position(1, 0);\n      assert.strictEqual(position.line, 1);\n      assert.strictEqual(position.character, 0);\n\n      position = position.getUp();\n      assert.strictEqual(position.line, 0);\n      assert.strictEqual(position.character, 0);\n    });\n\n    test('top-most line should stay at the same location', () => {\n      let motion = new Position(0, 1);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 1);\n\n      motion = motion.getUp(0);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 1);\n    });\n  });\n\n  test('line begin', () => {\n    const motion = new Position(0, 3).getLineBegin();\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  test('line end', () => {\n    let motion = new Position(0, 0).getLineEnd();\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, text[0].length);\n\n    motion = new Position(2, 0).getLineEnd();\n    assert.strictEqual(motion.line, 2);\n    assert.strictEqual(motion.character, text[2].length);\n  });\n\n  test('document begin', () => {\n    const motion = TextEditor.getDocumentBegin();\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  test('document end', () => {\n    const motion = TextEditor.getDocumentEnd(window.activeTextEditor!.document);\n    assert.strictEqual(motion.line, text.length - 1);\n    assert.strictEqual(motion.character, text[text.length - 1].length);\n  });\n\n  test('line begin cursor on first non-blank character', () => {\n    const motion = TextEditor.getFirstNonWhitespaceCharOnLine(window.activeTextEditor!.document, 0);\n    assert.strictEqual(motion.line, 0);\n    assert.strictEqual(motion.character, 0);\n  });\n\n  test('last line begin cursor on first non-blank character', () => {\n    const motion = TextEditor.getFirstNonWhitespaceCharOnLine(window.activeTextEditor!.document, 3);\n    assert.strictEqual(motion.line, 3);\n    assert.strictEqual(motion.character, 1);\n  });\n});\n\nsuite('word motion', () => {\n  const text: string[] = [\n    'if (true) {',\n    '  return true;',\n    '} else {',\n    '',\n    '  return false;',\n    '  ',\n    '} // endif',\n  ];\n\n  suiteSetup(async () => {\n    await setupWorkspace({ fileContent: text });\n  });\n\n  suite('word right', () => {\n    test('move to word right', () => {\n      const motion = new Position(0, 3).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 4);\n    });\n\n    test('last word should move to next line', () => {\n      const motion = new Position(0, 10).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 2);\n    });\n\n    test('last word should move to next line stops on empty line', () => {\n      const motion = new Position(2, 7).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 3);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('last word should move to next line skips whitespace only line', () => {\n      const motion = new Position(4, 14).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 6);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('last word on last line should go to end of document (special case!)', () => {\n      const motion = new Position(6, 6).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 6);\n      assert.strictEqual(motion.character, 10);\n    });\n  });\n\n  suite('word left', () => {\n    test('move cursor word left across spaces', () => {\n      const motion = new Position(0, 3).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move cursor word left within word', () => {\n      const motion = new Position(0, 5).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 4);\n    });\n\n    test('first word should move to previous line, beginning of last word', () => {\n      const motion = new Position(1, 2).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 10);\n    });\n\n    test('first word should move to previous line, stops on empty line', () => {\n      const motion = new Position(4, 2).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 3);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('first word should move to previous line, skips whitespace only line', () => {\n      const motion = new Position(6, 0).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 14);\n    });\n  });\n\n  suite('WORD right', () => {\n    test('move to WORD right', () => {\n      const motion = new Position(0, 3).nextWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 10);\n    });\n\n    test('last WORD should move to next line', () => {\n      const motion = new Position(1, 10).nextWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 2);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('last WORD should move to next line stops on empty line', () => {\n      const motion = new Position(2, 7).nextWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 3);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('last WORD should move to next line skips whitespace only line', () => {\n      const motion = new Position(4, 12).nextWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 6);\n      assert.strictEqual(motion.character, 0);\n    });\n  });\n\n  suite('WORD left', () => {\n    test('move cursor WORD left across spaces', () => {\n      const motion = new Position(0, 3).prevWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move cursor WORD left within WORD', () => {\n      const motion = new Position(0, 5).prevWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 3);\n    });\n\n    test('first WORD should move to previous line, beginning of last WORD', () => {\n      const motion = new Position(2, 0).prevWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 9);\n    });\n\n    test('first WORD should move to previous line, stops on empty line', () => {\n      const motion = new Position(4, 2).prevWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 3);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('first WORD should move to previous line, skips whitespace only line', () => {\n      const motion = new Position(6, 0).prevWordStart(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 9);\n    });\n  });\n\n  suite('end of word right', () => {\n    test('move to end of current word right', () => {\n      const motion = new Position(0, 4).nextWordEnd(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 7);\n    });\n\n    test('move to end of next word right', () => {\n      const motion = new Position(0, 7).nextWordEnd(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 8);\n    });\n\n    test('end of last word should move to next line', () => {\n      const motion = new Position(0, 10).nextWordEnd(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 7);\n    });\n\n    test('end of last word should move to next line skips empty line', () => {\n      const motion = new Position(2, 7).nextWordEnd(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 7);\n    });\n\n    test('end of last word should move to next line skips whitespace only line', () => {\n      const motion = new Position(4, 14).nextWordEnd(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 6);\n      assert.strictEqual(motion.character, 0);\n    });\n  });\n\n  suite('end of WORD right', () => {\n    test('move to end of current WORD right', () => {\n      const motion = new Position(0, 4).nextWordEnd(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 8);\n    });\n\n    test('move to end of next WORD right', () => {\n      const motion = new Position(0, 8).nextWordEnd(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 10);\n    });\n\n    test('end of last WORD should move to next line', () => {\n      const motion = new Position(0, 10).nextWordEnd(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 7);\n    });\n\n    test('end of last WORD should move to next line skips empty line', () => {\n      const motion = new Position(2, 7).nextWordEnd(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 7);\n    });\n\n    test('end of last WORD should move to next line skips whitespace only line', () => {\n      const motion = new Position(4, 14).nextWordEnd(window.activeTextEditor!.document, {\n        wordType: WordType.Big,\n      });\n      assert.strictEqual(motion.line, 6);\n      assert.strictEqual(motion.character, 0);\n    });\n  });\n\n  test('line begin cursor on first non-blank character', () => {\n    const motion = TextEditor.getFirstNonWhitespaceCharOnLine(window.activeTextEditor!.document, 4);\n    assert.strictEqual(motion.line, 4);\n    assert.strictEqual(motion.character, 2);\n  });\n\n  test('last line begin cursor on first non-blank character', () => {\n    const motion = TextEditor.getFirstNonWhitespaceCharOnLine(window.activeTextEditor!.document, 6);\n    assert.strictEqual(motion.line, 6);\n    assert.strictEqual(motion.character, 0);\n  });\n});\n\nsuite('unicode word motion', () => {\n  const text: string[] = [\n    '漢字ひらがなカタカナalphabets、いろいろな文字。',\n    'Καλημέρα κόσμε',\n    'Die früh sich einst dem trüben Blick gezeigt.',\n    'Được tiếp đãi ân cần',\n    '100£and100$and100¥#♯x',\n  ];\n\n  suiteSetup(async () => {\n    await setupWorkspace({ fileContent: text });\n  });\n\n  suite('word right', () => {\n    test('move cursor word right stops at different kind of character (ideograph -> hiragana)', () => {\n      const motion = new Position(0, 0).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 2);\n    });\n\n    test('move cursor word right stops at different kind of character (katakana -> ascii)', () => {\n      const motion = new Position(0, 7).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 10);\n    });\n\n    test('move cursor word right stops at different kind of chararacter (ascii -> punctuation)', () => {\n      const motion = new Position(0, 10).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 19);\n    });\n\n    test('move cursor word right on non-ascii text', () => {\n      const motion = new Position(1, 0).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 9);\n    });\n\n    test('move cursor word right recognizes a latin string which has diacritics as a single word', () => {\n      const motion = new Position(2, 4).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 2);\n      assert.strictEqual(motion.character, 9);\n    });\n\n    test('move cursor word right recognizes a latin-1 symbol as punctuation', () => {\n      let motion = new Position(4, 3).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 4);\n\n      motion = motion.nextWordStart(window.activeTextEditor!.document); // issue #3680\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 10);\n    });\n\n    test('move cursor word right recognizes a sequence of latin-1 symbols and other symbols as a word', () => {\n      const motion = new Position(4, 17).nextWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 20);\n    });\n  });\n\n  suite('word left', () => {\n    test('move cursor word left across the different char kind', () => {\n      const motion = new Position(0, 2).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move cursor word left within the same char kind', () => {\n      const motion = new Position(0, 5).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 2);\n    });\n\n    test('move cursor word left across spaces on non-ascii text', () => {\n      const motion = new Position(1, 9).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move cursor word left within word on non-ascii text', () => {\n      const motion = new Position(1, 11).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 9);\n    });\n\n    test('move cursor word left recognizes a latin string which has diacritics as a single word', () => {\n      const motion = new Position(3, 10).prevWordStart(window.activeTextEditor!.document);\n      assert.strictEqual(motion.line, 3);\n      assert.strictEqual(motion.character, 5);\n    });\n  });\n});\n\nsuite('sentence motion', () => {\n  const text: string[] = [\n    'This text has many sections in it. What do you think?',\n    '',\n    'A paragraph boundary is also a sentence boundry, see',\n    '',\n    'Weird things happen when there is no appropriate sentence ending',\n    '',\n    'Next line is just whitespace',\n    '   ',\n    'Wow!',\n    'Another sentence inside one paragraph.',\n    '',\n    '\"Sentence in quotes.\" Sentence out of quotes. \\'Sentence in singlequotes.\\' (Sentence in parens.) [Sentence in square brackets.]',\n  ];\n\n  suiteSetup(async () => {\n    await setupWorkspace({ fileContent: text });\n  });\n\n  suite('sentence forward', () => {\n    test('next concrete sentence', () => {\n      const motion = new Position(0, 0).getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 35);\n    });\n\n    test('next sentence when cursor is at the end of previous paragraph', () => {\n      const motion = new Position(3, 0).getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('next sentence when paragraph contains a line of white spaces', () => {\n      const motion = new Position(6, 2).getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 9);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('next sentence when sentences have closing punctuation', () => {\n      let motion = new Position(11, 0).getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 22);\n\n      motion = motion.getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 46);\n\n      motion = motion.getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 74);\n\n      motion = motion.getSentenceBegin({ forward: true });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 96);\n    });\n  });\n\n  suite('sentence backward', () => {\n    test('current sentence begin', () => {\n      const motion = new Position(0, 37).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 35);\n    });\n\n    test('sentence backward when cursor is at the beginning of the second sentence', () => {\n      const motion = new Position(0, 35).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('current sentence begin with no concrete sentence inside', () => {\n      const motion = new Position(3, 0).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 2);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test(\"current sentence begin when it's not the same as current paragraph begin\", () => {\n      const motion = new Position(2, 0).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('current sentence begin when previous line ends with a concrete sentence', () => {\n      const motion = new Position(9, 5).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 9);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('sentence backward when sentences have closing punctuation', () => {\n      let motion = new Position(11, 125).getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 96);\n\n      motion = motion.getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 74);\n\n      motion = motion.getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 46);\n\n      motion = motion.getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 11);\n      assert.strictEqual(motion.character, 22);\n\n      motion = motion.getSentenceBegin({ forward: false });\n      assert.strictEqual(motion.line, 10);\n      assert.strictEqual(motion.character, 0);\n    });\n  });\n});\n\nsuite('paragraph motion', () => {\n  const text: string[] = [\n    'this text has', // 0\n    '', // 1\n    'many', // 2\n    'paragraphs', // 3\n    '', // 4\n    '', // 5\n    'in it.', // 6\n    '', // 7\n    'WOW', // 8\n  ];\n\n  suiteSetup(async () => {\n    await setupWorkspace({ fileContent: text });\n  });\n\n  suite('paragraph down', () => {\n    test('move down normally', () => {\n      const motion = getCurrentParagraphEnd(new Position(0, 0));\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move down longer paragraph', () => {\n      const motion = getCurrentParagraphEnd(new Position(2, 0));\n      assert.strictEqual(motion.line, 4);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move down starting inside empty line', () => {\n      const motion = getCurrentParagraphEnd(new Position(4, 0));\n      assert.strictEqual(motion.line, 7);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('paragraph at end of document', () => {\n      const motion = getCurrentParagraphEnd(new Position(7, 0));\n      assert.strictEqual(motion.line, 8);\n      assert.strictEqual(motion.character, 3);\n    });\n  });\n\n  suite('paragraph up', () => {\n    test('move up short paragraph', () => {\n      const motion = getCurrentParagraphBeginning(new Position(1, 0));\n      assert.strictEqual(motion.line, 0);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move up longer paragraph', () => {\n      const motion = getCurrentParagraphBeginning(new Position(3, 0));\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 0);\n    });\n\n    test('move up starting inside empty line', () => {\n      const motion = getCurrentParagraphBeginning(new Position(5, 0));\n      assert.strictEqual(motion.line, 1);\n      assert.strictEqual(motion.character, 0);\n    });\n  });\n});\n"
  },
  {
    "path": "test/motionLineWrapping.test.ts",
    "content": "import { newTest } from './testSimplifier';\nimport { setupWorkspace } from './testUtils';\n\nsuite('motion line wrapping', () => {\n  suite('whichwrap enabled', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          tabstop: 4,\n          expandtab: false,\n          whichwrap: 'h,l,<,>,[,]',\n        },\n      });\n    });\n\n    suite('normal mode', () => {\n      newTest({\n        title: 'h wraps to previous line',\n        start: ['line 1', '|line 2'],\n        keysPressed: 'h',\n        end: ['line |1', 'line 2'],\n      });\n\n      newTest({\n        title: 'l wraps to next line',\n        start: ['line |1', 'line 2'],\n        keysPressed: 'l',\n        end: ['line 1', '|line 2'],\n      });\n\n      newTest({\n        title: '<left> wraps to previous line',\n        start: ['line 1', '|line 2'],\n        keysPressed: '<left>',\n        end: ['line |1', 'line 2'],\n      });\n\n      newTest({\n        title: '<right> wraps to next line',\n        start: ['line |1', 'line 2'],\n        keysPressed: '<right>',\n        end: ['line 1', '|line 2'],\n      });\n    });\n\n    suite('insert mode', () => {\n      newTest({\n        title: '<left> wraps to previous line',\n        start: ['line 1', '|line 2'],\n        // insert mode moves cursor one space to the left,\n        // but not at beginning of line\n        keysPressed: 'i<left>',\n        end: ['line 1|', 'line 2'],\n      });\n\n      newTest({\n        title: '<right> once goes to end of line',\n        start: ['line |1', 'line 2'],\n        // insert mode moves cursor one space to the left\n        // so <right> once should go to eol\n        keysPressed: 'i<right>',\n        end: ['line 1|', 'line 2'],\n      });\n\n      newTest({\n        title: '<right> twice wraps to next line',\n        start: ['line |1', 'line 2'],\n        // insert mode moves cursor one space to the left\n        // so need to go right twice to wrap\n        keysPressed: 'i<right><right>',\n        end: ['line 1', '|line 2'],\n      });\n    });\n  });\n\n  suite('whichwrap disabled', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          tabstop: 4,\n          expandtab: false,\n        },\n      });\n    });\n\n    suite('normal mode', () => {\n      newTest({\n        title: 'h does not wrap to previous line',\n        start: ['line 1', '|line 2'],\n        keysPressed: 'h',\n        end: ['line 1', '|line 2'],\n      });\n\n      newTest({\n        title: 'l does not wrap to next line',\n        start: ['line |1', 'line 2'],\n        keysPressed: 'l',\n        end: ['line |1', 'line 2'],\n      });\n\n      newTest({\n        title: '<left> does not wrap to previous line',\n        start: ['line 1', '|line 2'],\n        keysPressed: '<left>',\n        end: ['line 1', '|line 2'],\n      });\n\n      newTest({\n        title: '<right> does not wrap to next line',\n        start: ['line |1', 'line 2'],\n        keysPressed: '<right>',\n        end: ['line |1', 'line 2'],\n      });\n    });\n\n    suite('insert mode', () => {\n      newTest({\n        title: '<left> does not wrap to previous line',\n        start: ['line 1', '|line 2'],\n        keysPressed: 'i<left>',\n        end: ['line 1', '|line 2'],\n      });\n\n      newTest({\n        title: '<right> does not wrap to next line',\n        start: ['line |1', 'line 2'],\n        keysPressed: 'i<right><right>',\n        end: ['line 1|', 'line 2'],\n      });\n    });\n  });\n\n  suite('wrapscan enabled', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          wrapscan: true,\n        },\n      });\n    });\n\n    newTest({\n      title: 'search wraps around the end of the file',\n      start: ['|line 1', 'line 2'],\n      keysPressed: '/line\\nn',\n      end: ['|line 1', 'line 2'],\n    });\n\n    newTest({\n      title: 'search wraps around the start of the file',\n      start: ['|line 1', 'line 2'],\n      keysPressed: '/line\\nNN',\n      end: ['line 1', '|line 2'],\n    });\n  });\n\n  suite('wrapscan disabled', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          wrapscan: false,\n        },\n      });\n    });\n\n    newTest({\n      title: 'search stops at the end of the file',\n      start: ['|line 1', 'line 2'],\n      keysPressed: '/line\\nn',\n      end: ['line 1', '|line 2'],\n      statusBar: 'E385: Search hit BOTTOM without match for: line',\n    });\n\n    newTest({\n      title: 'search stops at the start of the file',\n      start: ['|line 1', 'line 2'],\n      keysPressed: '/line\\nNN',\n      end: ['|line 1', 'line 2'],\n      statusBar: 'E384: Search hit TOP without match for: line',\n    });\n  });\n});\n"
  },
  {
    "path": "test/multicursor.test.ts",
    "content": "import * as assert from 'assert';\nimport { getAndUpdateModeHandler } from '../extension';\nimport { ModeHandler } from '../src/mode/modeHandler';\nimport { newTest, newTestSkip } from './testSimplifier';\nimport { assertEqualLines, setupWorkspace } from './testUtils';\n\nsuite('Multicursor', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  suite('Motions', () => {\n    for (const foldfix of [true, false]) {\n      suite(`j and k ${foldfix ? '(foldfix)' : ''}`, () => {\n        newTest({\n          title: 'j',\n          config: { foldfix },\n          start: ['l|ine 1', 'lin|e 2', 'line 3'],\n          keysPressed: 'j',\n          end: ['line 1', 'l|ine 2', 'lin|e 3'],\n        });\n\n        newTest({\n          title: 'j (at bottom)',\n          config: { foldfix },\n          start: ['line 1', 'l|ine 2', 'lin|e 3'],\n          keysPressed: 'j',\n          end: ['line 1', 'line 2', 'l|in|e 3'],\n        });\n\n        newTest({\n          title: 'k',\n          config: { foldfix },\n          start: ['line 1', 'l|ine 2', 'lin|e 3'],\n          keysPressed: 'k',\n          end: ['l|ine 1', 'lin|e 2', 'line 3'],\n        });\n\n        // TODO: Fix for foldfix\n        (foldfix ? newTestSkip : newTest)({\n          title: 'k (at top)',\n          config: { foldfix },\n          start: ['l|ine 1', 'lin|e 2', 'line 3'],\n          keysPressed: 'k',\n          end: ['l|in|e 1', 'line 2', 'line 3'],\n        });\n      });\n    }\n  });\n\n  suite('Macros', () => {\n    newTest({\n      title: 'Can record and play macros with multiple cursors',\n      start: ['|one', '|two', '|three'],\n      keysPressed: 'qx' + 'A!' + '<Esc>' + 'q' + '2@x',\n      end: ['one!!|!', 'two!!|!', 'three!!|!'],\n    });\n  });\n\n  suite('Undo/redo', () => {\n    newTest({\n      title: 'Can undo with multiple cursors',\n      start: ['|one', '|two', '|three'],\n      keysPressed: 'l' + 'iXXX<Esc>' + '$' + 'u',\n      end: ['o|ne', 't|wo', 't|hree'],\n    });\n  });\n\n  suite('Delete', () => {\n    newTest({\n      title: 'x (Normal mode)',\n      start: ['|cat', 'c|at', 'ca|t'],\n      keysPressed: 'x',\n      end: ['|at', 'c|t', 'c|a'],\n    });\n\n    // TODO: `D`\n\n    newTest({\n      title: 'd (Visual mode)',\n      start: ['|cat', 'c|at', 'ca|t'],\n      keysPressed: 'vl' + 'd',\n      end: ['|t', '|c', 'c|a'],\n    });\n\n    // TODO: VisualBlock mode\n  });\n\n  suite('Replace', () => {\n    newTest({\n      title: 'r (Normal mode)',\n      start: ['|cat', 'c|at', 'ca|t'],\n      keysPressed: 'rX',\n      end: ['|Xat', 'c|Xt', 'ca|X'],\n    });\n\n    newTest({\n      title: 'r (Visual mode)',\n      start: ['|cat', 'c|at', 'ca|t'],\n      keysPressed: 've' + 'rX',\n      end: ['|XXX', 'c|XX', 'ca|X'],\n    });\n\n    newTest({\n      title: 'r (VisualBlock mode)',\n      start: ['|ca|t', 'cat', 'cat'],\n      keysPressed: '<C-v>jj' + 'rX',\n      end: ['|Xa|X', 'XaX', 'XaX'],\n    });\n  });\n\n  test('can add multiple cursors below', async () => {\n    await setupWorkspace({ fileContent: ['11', '22'] });\n    await modeHandler.handleMultipleKeyEvents(['g', 'g']);\n\n    if (process.platform === 'darwin') {\n      await modeHandler.handleMultipleKeyEvents(['<D-alt+down>']);\n    } else {\n      await modeHandler.handleMultipleKeyEvents(['<C-alt+down>']);\n    }\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['c', 'w', '3', '3', '<Esc>']);\n    assertEqualLines(['33', '33']);\n  });\n\n  test('can add multiple cursors above', async () => {\n    await setupWorkspace({ fileContent: ['11', '22', '33'] });\n    await modeHandler.handleMultipleKeyEvents(['G']);\n\n    if (process.platform === 'darwin') {\n      await modeHandler.handleMultipleKeyEvents(['<D-alt+up>', '<D-alt+up>']);\n    } else {\n      await modeHandler.handleMultipleKeyEvents(['<C-alt+up>', '<C-alt+up>']);\n    }\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 3, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['c', 'w', '4', '4', '<Esc>']);\n    assertEqualLines(['44', '44', '44']);\n  });\n\n  test('viwd with multicursors deletes the words and keeps the cursors', async () => {\n    await setupWorkspace({ fileContent: ['foo dont delete', 'bar', 'dont foo'] });\n    await modeHandler.handleMultipleKeyEvents(['g', 'g']);\n\n    await modeHandler.handleMultipleKeyEvents(['g', 'b', 'g', 'b', '<Esc>', 'b']);\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['v', 'i', 'w', 'd']);\n    assertEqualLines([' dont delete', 'bar', 'dont ']);\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2);\n  });\n\n  test('vibd with multicursors deletes the content between brackets and keeps the cursors', async () => {\n    await setupWorkspace({ fileContent: ['[(foo) asd ]', '[(bar) asd ]', '[(foo) asd ]'] });\n    await modeHandler.handleMultipleKeyEvents(['g', 'g', 'l', 'l']);\n\n    await modeHandler.handleMultipleKeyEvents(['g', 'b', 'g', 'b', '<Esc>', 'b']);\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['v', 'i', 'b', 'd']);\n    assertEqualLines(['[() asd ]', '[(bar) asd ]', '[() asd ]']);\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2);\n  });\n\n  test('vi[d with multicursors deletes the content between brackets and keeps the cursors', async () => {\n    await setupWorkspace({ fileContent: ['[(foo) asd ]', '[(bar) asd ]', '[(foo) asd ]'] });\n    await modeHandler.handleMultipleKeyEvents(['g', 'g', 'l', 'l']);\n\n    await modeHandler.handleMultipleKeyEvents(['g', 'b', 'g', 'b', '<Esc>', 'b']);\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['v', 'i', '[', 'd']);\n    assertEqualLines(['[]', '[(bar) asd ]', '[]']);\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2);\n  });\n\n  test('vitd with multicursors deletes the content between tags and keeps the cursors', async () => {\n    await setupWorkspace({ fileContent: ['<div> foo bar</div> asd', '<div>foo asd</div>'] });\n    await modeHandler.handleMultipleKeyEvents(['g', 'g', 'W']);\n\n    await modeHandler.handleMultipleKeyEvents(['g', 'b', 'g', 'b', '<Esc>', 'b']);\n\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2, 'Cursor successfully created.');\n    await modeHandler.handleMultipleKeyEvents(['v', 'i', 't', 'd']);\n    assertEqualLines(['<div></div> asd', '<div></div>']);\n    assert.strictEqual(modeHandler.vimState.cursors.length, 2);\n  });\n\n  newTest({\n    title: 'Can use \"/\" search with multicursors',\n    start: ['|line 1', 'line 2', 'line 3', 'line 4', 'line 5'],\n    keysPressed: '3<C-alt+down>v/ne \\nd<Esc>',\n    end: ['|e 1', 'e 2', 'e 3', 'e 4', 'line 5'],\n  });\n\n  newTest({\n    title: 'Can use \"?\" search with multicursors',\n    start: ['line 1', 'line 2', 'line 3', 'line 4', 'line |5'],\n    keysPressed: '3<C-alt+up>v?ine\\nd<Esc>',\n    end: ['line 1', '|l', 'l', 'l', 'l'],\n  });\n});\n\nsuite('Multicursor with remaps', () => {\n  setup(async () => {\n    await setupWorkspace({\n      config: {\n        insertModeKeyBindings: [\n          {\n            before: ['j', 'j', 'k'],\n            after: ['<esc>'],\n          },\n        ],\n      },\n    });\n  });\n\n  newTest({\n    title: \"Using 'jjk' mapped to '<Esc>' doesn't leave trailing characters\",\n    start: ['o|ne', 'two'],\n    keysPressed: '<C-v>jAfoojjk<Esc>',\n    end: ['onfo|oe', 'twfooo'],\n  });\n});\n\nsuite('Multicursor selections', () => {\n  setup(async () => {\n    await setupWorkspace({\n      config: {\n        normalModeKeyBindings: [\n          {\n            before: ['<leader>', 'a', 'f'],\n            commands: ['editor.action.smartSelect.grow'],\n          },\n        ],\n        leader: ' ',\n      },\n    });\n  });\n\n  newTest({\n    title:\n      'Can handle combined multicursor selections without leaving ghost selection changes behind',\n    start: ['|this is a test', '1', '2', 'this is another test', '1', '2', '3', '4', '5'],\n    keysPressed: 'gbgb<Esc>Vjjj<Esc><Esc>gg afd',\n    end: ['| is a test', '1', '2', 'this is another test', '1', '2', '3', '4', '5'],\n  });\n});\n"
  },
  {
    "path": "test/number/incrementDecrement.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\n\nsuite('Increment/decrement (<C-a> and <C-x>)', () => {\n  newTest({\n    title: 'can ctrl-a correctly behind a word',\n    start: ['|one 9'],\n    keysPressed: '<C-a>',\n    end: ['one 1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a the right word (always the one AFTER the cursor)',\n    start: ['1 |one 2'],\n    keysPressed: '<C-a>',\n    end: ['1 one |3'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on word',\n    start: ['one -|11'],\n    keysPressed: '<C-a>',\n    end: ['one -1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on a hex number',\n    start: ['|0xf'],\n    keysPressed: '<C-a>',\n    end: ['0x1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on decimal',\n    start: ['1|1.123'],\n    keysPressed: '<C-a>',\n    end: ['1|2.123'],\n  });\n\n  newTest({\n    title: 'can ctrl-a with numeric prefix',\n    start: ['|-10'],\n    keysPressed: '15<C-a>',\n    end: ['|5'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on a decimal',\n    start: ['-10.|1'],\n    keysPressed: '10<C-a>',\n    end: ['-10.1|1'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on an octal ',\n    start: ['0|7'],\n    keysPressed: '<C-a>',\n    end: ['01|0'],\n  });\n\n  newTest({\n    title: 'Correctly increments in the middle of a number',\n    start: ['10|1'],\n    keysPressed: '<C-a>',\n    end: ['10|2'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on a hex number behind a word',\n    start: ['|test0xf'],\n    keysPressed: '<C-a>',\n    end: ['test0x1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a distinguish fake hex number',\n    start: ['|00xf'],\n    keysPressed: '<C-a>',\n    end: ['0|1xf'],\n  });\n\n  newTest({\n    title: 'can ctrl-a can preserve uppercase',\n    start: ['|0xDEAD'],\n    keysPressed: '<C-a>',\n    end: ['0xDEA|E'],\n  });\n\n  newTest({\n    title: 'can ctrl-a can transform to lowercase',\n    start: ['|0xDEAd'],\n    keysPressed: '<C-a>',\n    end: ['0xdea|e'],\n  });\n\n  newTest({\n    title: 'can ctrl-a can transform to uppercase 1',\n    start: ['|0xdeaD'],\n    keysPressed: '<C-a>',\n    end: ['0xDEA|E'],\n  });\n\n  newTest({\n    title: 'can ctrl-a can transform to uppercase 2',\n    start: ['|0xDeaD1'],\n    keysPressed: '<C-a>',\n    end: ['0xDEAD|2'],\n  });\n\n  newTest({\n    title: 'can ctrl-a preserve leading zeros of octal',\n    start: ['|000007'],\n    keysPressed: '<C-a>',\n    end: ['00001|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a trim leading zeros of decimal',\n    start: ['|000009'],\n    keysPressed: '<C-a>',\n    end: ['1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a process `-0x0` correctly',\n    start: ['|-0x0'],\n    keysPressed: '<C-a>',\n    end: ['-0x|1'],\n  });\n\n  newTest({\n    title: 'can ctrl-a regard `0` as decimal',\n    start: ['|0'],\n    keysPressed: '10<C-a>',\n    end: ['1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on octal ignore negative sign',\n    start: ['|test-0116'],\n    keysPressed: '<C-a>',\n    end: ['test-011|7'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on octal ignore positive sign',\n    start: ['|test+0116'],\n    keysPressed: '<C-a>',\n    end: ['test+011|7'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on hex number ignore negative sign',\n    start: ['|test-0xf'],\n    keysPressed: '<C-a>',\n    end: ['test-0x1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on hex number ignore positive sign',\n    start: ['|test+0xf'],\n    keysPressed: '<C-a>',\n    end: ['test+0x1|0'],\n  });\n\n  newTest({\n    title: 'can ctrl-x correctly behind a word',\n    start: ['|one 10'],\n    keysPressed: '<C-x>',\n    end: ['one |9'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on an number with word before ',\n    start: ['|test3'],\n    keysPressed: '<C-a>',\n    end: ['test|4'],\n  });\n\n  newTest({\n    title: 'can ctrl-a on an number with word before and after ',\n    start: ['|test3abc'],\n    keysPressed: '<C-a>',\n    end: ['test|4abc'],\n  });\n\n  newTest({\n    title: 'can ctrl-x on a negative number with word before and after ',\n    start: ['|test-2abc'],\n    keysPressed: '<C-x><C-x><C-x>',\n    end: ['test-|5abc'],\n  });\n\n  newTest({\n    title: 'can ctrl-a properly on multiple lines',\n    start: ['id: 1|,', 'someOtherId: 1'],\n    keysPressed: '<C-a>',\n    end: ['id: 1|,', 'someOtherId: 1'],\n  });\n\n  newTest({\n    title: 'can <C-a> on word with multiple numbers (incrementing first number)',\n    start: ['f|oo1bar2'],\n    keysPressed: '<C-a>',\n    end: ['foo|2bar2'],\n  });\n\n  newTest({\n    title: 'can <C-a> on word with multiple numbers (incrementing second number)',\n    start: ['foo1|bar2'],\n    keysPressed: '<C-a>',\n    end: ['foo1bar|3'],\n  });\n\n  newTest({\n    title: 'can <C-a> on word with - in front of it',\n    start: ['-fo|o2'],\n    keysPressed: '<C-a>',\n    end: ['-foo|3'],\n  });\n\n  newTest({\n    title: '<C-a> in visual mode',\n    start: ['9 9 9', '9| 9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: 'vjj3<C-a>',\n    end: ['9 9 9', '9| 12 9', '12 9 9', '12 9 9', '9 9 9'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: '<C-a> in visual line mode',\n    start: ['9 9 9', '9| 9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: 'Vjj3<C-a>',\n    end: ['9 9 9', '|12 9 9', '12 9 9', '12 9 9', '9 9 9'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: '<C-a> in visual block mode',\n    start: ['9 9 9', '9 |9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: '<C-v>jj3<C-a>',\n    end: ['9 9 9', '9 |12 9', '9 1|2 9', '9 1|2 9', '9 9 9'], // TODO: Bad cursor position?\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: '<C-a> in visual block mode does not go past selection',\n    start: ['9 9 9', '9| 9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: '<C-v>jj3<C-a>',\n    end: ['9 9 9', '9| 9 9', '9| 9 9', '9| 9 9', '9 9 9'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'g<C-a> in visual mode',\n    start: ['9 9 9', '9| 9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: 'vjj3g<C-a>',\n    end: ['9 9 9', '9| 12 9', '15 9 9', '18 9 9', '9 9 9'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'g<C-a> in visual line mode',\n    start: ['9 9 9', '9| 9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: 'Vjj3g<C-a>',\n    end: ['9 9 9', '|12 9 9', '15 9 9', '18 9 9', '9 9 9'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'g<C-a> in visual block mode',\n    start: ['9 9 9', '9 |9 9', '9 9 9', '9 9 9', '9 9 9'],\n    keysPressed: '<C-v>jj3g<C-a>',\n    end: ['9 9 9', '9 |12 9', '9 1|5 9', '9 1|8 9', '9 9 9'], // TODO: Bad cursor position?\n    endMode: Mode.Normal,\n  });\n});\n"
  },
  {
    "path": "test/number/numericString.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { NumericString, NumericStringRadix } from '../../src/common/number/numericString';\n\nsuite('numeric string', () => {\n  test('fails on non-string', () => {\n    assert.strictEqual(undefined, NumericString.parse('hi'));\n  });\n\n  test('handles hex round trip', () => {\n    const input = '0xa1';\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n    // run each assertion twice to make sure that regex state doesn't cause failures\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n  });\n\n  test('handles hex with capitals round trip', () => {\n    const input = '0xAb1';\n    assert.strictEqual('0xab1', NumericString.parse(input)?.num.toString());\n  });\n\n  test('handles decimal round trip', () => {\n    const input = '9';\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n  });\n\n  test('handles octal trip', () => {\n    const input = '07';\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n  });\n\n  test('handles octal trip', () => {\n    const input = '07';\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n    assert.strictEqual(input, NumericString.parse(input)?.num.toString());\n  });\n\n  test('handles decimal radix', () => {\n    assert.strictEqual(NumericString.parse('07', NumericStringRadix.Dec)?.num.value, 7);\n    assert.strictEqual(NumericString.parse('hi-07hello', NumericStringRadix.Dec)?.num.value, -7);\n  });\n});\n"
  },
  {
    "path": "test/operator/comment.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nsuite('comment operator', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ fileExtension: '.js' });\n  });\n\n  newTest({\n    title: 'gcc comments out current line',\n    start: ['first line', '|second line'],\n    keysPressed: 'gcc',\n    end: ['first line', '|// second line'],\n  });\n\n  newTest({\n    title: 'gcj comments in current and next line',\n    start: ['// first| line', '// second line', 'third line'],\n    keysPressed: 'gcj',\n    end: ['|first line', 'second line', 'third line'],\n  });\n\n  newTest({\n    title: 'block comment with motion',\n    start: ['function test(arg|1, arg2, arg3) {'],\n    keysPressed: 'gCi)',\n    end: ['function test(|/* arg1, arg2, arg3 */) {'],\n  });\n\n  newTest({\n    title: 'block comment in Visual Mode',\n    start: ['blah |blah blah'],\n    keysPressed: 'vlllgC',\n    end: ['blah |/* blah */ blah'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'comment in visual line mode',\n    start: ['one', '|two', 'three', 'four'],\n    keysPressed: 'Vjgc',\n    end: ['one', '|// two', '// three', 'four'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'comment in visual block mode',\n    start: ['one', '|two', 'three', 'four'],\n    keysPressed: '<C-v>lljgc',\n    end: ['one', '|// two', '// three', 'four'],\n    endMode: Mode.Normal,\n  });\n});\n"
  },
  {
    "path": "test/operator/filter.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\n// TODO(#4844): this fails on Windows\nsuite('filter operator', () => {\n  if (process.platform === 'win32') {\n    return;\n  }\n\n  newTest({\n    title: '!! with no count',\n    start: ['|'],\n    keysPressed: '!!echo hello world\\n',\n    end: ['|hello world'],\n  });\n\n  newTest({\n    title: '!! with whitespace moves cursor to first non-whitespace character',\n    start: ['|'],\n    keysPressed: '!!echo \" hello world\"\\n',\n    end: [' |hello world'],\n  });\n\n  newTest({\n    title: '!! with count',\n    start: ['|abc', 'def'],\n    keysPressed: '2!!echo hello world\\n',\n    end: ['|hello world'],\n  });\n\n  newTest({\n    title: '!{forwards motion}{filter}',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: '!jecho hello world\\n',\n    end: ['|hello world', 'ghi'],\n  });\n\n  newTest({\n    title: '!{backwards motion}{filter}',\n    start: ['abc', 'def', '|ghi'],\n    keysPressed: '!{echo hello world\\n',\n    end: ['|hello world', 'ghi'],\n  });\n\n  newTest({\n    title: 'v!{filter}',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: 'vjj!echo hello world\\n',\n    end: ['|hello world'],\n  });\n\n  newTest({\n    title: 'V!{filter}',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: 'Vjj!echo hello world\\n',\n    end: ['|hello world'],\n  });\n\n  newTest({\n    title: '<Ctrl-v>!{filter}',\n    start: ['|abc', 'def', 'ghi'],\n    keysPressed: '<C-v>jj!echo hello world\\n',\n    end: ['|hell|o wo|rld'], // TODO: Bad cursors\n  });\n});\n"
  },
  {
    "path": "test/operator/format.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nsuite('format operator', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ fileExtension: '.ts' });\n  });\n\n  newTest({\n    title: '== formats current line',\n    start: [' |let a;', '  let b;'],\n    keysPressed: '==',\n    end: ['|let a;', '  let b;'],\n  });\n\n  newTest({\n    title: '=$ formats entire line',\n    start: [' function f() {|let a;', 'let b;', '}'],\n    keysPressed: '=$',\n    end: ['|function f() {', '  let a;', 'let b;', '}'],\n  });\n\n  newTest({\n    title: '=j formats two lines',\n    start: [' |let a;', '  let b;', '  let c;'],\n    keysPressed: '=j',\n    end: ['|let a;', 'let b;', '  let c;'],\n  });\n\n  newTest({\n    title: '3=k formats three lines',\n    start: [' let a;', '  let b;', '|  let c;'],\n    keysPressed: '3=k',\n    end: ['|let a;', 'let b;', 'let c;'],\n  });\n\n  newTest({\n    title: '=gg formats to top of file',\n    start: [' let a;', '  let b;', '|  let c;'],\n    keysPressed: '=gg',\n    end: ['|let a;', 'let b;', 'let c;'],\n  });\n\n  newTest({\n    title: '=G formats to bottom of file',\n    start: ['|  let a;', '  let b;', '  let c;'],\n    keysPressed: '=G',\n    end: ['|let a;', 'let b;', 'let c;'],\n  });\n\n  newTest({\n    title: '=ip formats paragraph',\n    start: ['  function f() {', '|let a;', '  }', '', '  let b;'],\n    keysPressed: '=ip',\n    end: ['|function f() {', '  let a;', '}', '', '  let b;'],\n  });\n\n  newTest({\n    title: 'format in visual mode',\n    start: ['  function f() {', 'let a;', '|  }', '', '  let b;'],\n    keysPressed: 'vkk=',\n    end: ['|function f() {', '  let a;', '}', '', '  let b;'],\n  });\n});\n"
  },
  {
    "path": "test/operator/put.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite('put operator', () => {\n  suite('p', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Putting empty register throws E353',\n        start: ['one t|wo three'],\n        keysPressed: '\"xp',\n        end: ['one t|wo three'],\n        statusBar: 'E353: Nothing in register x',\n      });\n\n      newTest({\n        title: 'Yank character-wise, <count>p',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2p',\n        end: ['one tXYZXY|Zwo three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>p',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '2p',\n        end: ['one t|XYZ', 'ABCXYZ', 'ABCwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>p',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'j' + '2p',\n        end: ['one', 'two', '|abc', 'abc', 'three'],\n      });\n\n      newTest({\n        title: 'Yank two lines line-wise, <count>p',\n        start: ['|abc', 'def', 'one', 'two', 'three'],\n        keysPressed: 'd2d' + 'j' + '2p',\n        end: ['one', 'two', '|abc', 'def', 'abc', 'def', 'three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>p on last line',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'G' + '2p',\n        end: ['one', 'two', 'three', '|abc', 'abc'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + '2p',\n        end: ['ABCD', 'ab|[1][1]cd', 'wx[2][2]yz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank ragged block-wise selection, <count>p',\n        start: ['|ABCDEFG', 'abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '3l' + '<C-v>j$' + 'y' + 'jh' + '2p',\n        end: ['ABCDEFG', 'abc|DEFGDEFGd', 'wxyd   d   z', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p past last line',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'Gl' + '2p',\n        end: ['ABCD', 'abcd', 'wxyz', 'WX|[1][1]YZ', '  [2][2]'],\n      });\n\n      newTest({\n        title: 'Record macro, <count>p',\n        start: ['|one', 'two', 'three'],\n        keysPressed: 'qx' + 'ccblah<Esc>' + 'q' + '' + '\"xp',\n        end: ['blahccblah<Esc|>', 'two', 'three'],\n      });\n    });\n\n    suite('Visual mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>p in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'R123<Esc>' + 'viw' + '3p',\n        end: ['one', 'twotwotw|o', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>p in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'R123<Esc>' + 'viw' + '3p',\n        end: ['one', '', '|two', 'two', 'two', '', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p in Visual mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + 'vj' + '2p',\n        end: ['ABCD', 'a|[1][1]yz', 'W[2][2]XYZ'],\n      });\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>p in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'V' + '3p',\n        end: ['one', '|two', 'two', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>p in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'V' + '3p',\n        end: ['one', '|two', 'two', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p in VisualLine mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jVj' + '2p',\n        end: ['ABCD', '|[1]', '[2]', '[1]', '[2]', 'WXYZ'],\n      });\n    });\n\n    suite('VisualBlock mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>p in VisualBlock mode',\n        start: ['ABCDE', '|TESTabcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'd4l' + 'l<C-v>jjll' + '2p',\n        end: ['ABCDE', 'aTESTTES|Te', '1TESTTES|T5', 'vTESTTES|Tz', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>p in VisualBlock mode',\n        start: ['ABCDE', '|TEST', 'abcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'dd' + 'l<C-v>jjll' + '2p',\n        end: ['ABCDE', 'ae', '1|5', 'v|z', '|TEST', 'TEST', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p in VisualBlock mode (into smaller block)',\n        start: ['|[1]ABCD', '[2]abcd', '[3]wxyz', 'WXYZ'],\n        keysPressed: '<C-v>lljj' + 'd' + 'jl<C-v>lj' + '2p',\n        end: ['ABCD', 'a|[1][1]d', 'w[2][2|]z', 'W[3][3]XYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>p in VisualBlock mode (into larger block)',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl<C-v>ljj' + '2p',\n        end: ['ABCD', 'a|[1][1]d', 'w[2][2|]z', 'W|Z'], // TODO(#9831)\n      });\n    });\n  });\n\n  suite('P', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>P',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2P',\n        end: ['one XYZXY|Ztwo three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>P',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '2P',\n        end: ['one |XYZ', 'ABCXYZ', 'ABCtwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>P',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'j' + '2P',\n        end: ['one', '|abc', 'abc', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>P on first line',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + '2P',\n        end: ['|abc', 'abc', 'one', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + '2P',\n        end: ['ABCD', 'a|[1][1]bcd', 'w[2][2]xyz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank ragged block-wise selection, <count>P',\n        start: ['|ABCDEFG', 'abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '3l' + '<C-v>j$' + 'y' + 'jh' + '2P',\n        end: ['ABCDEFG', 'ab|DEFGDEFGcd', 'wxd   d   yz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P past last line',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'Gl' + '2P',\n        end: ['ABCD', 'abcd', 'wxyz', 'W|[1][1]XYZ', ' [2][2]'],\n      });\n    });\n\n    suite('Visual mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>P in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'R123<Esc>' + 'viw' + '3P',\n        end: ['one', 'twotwotw|o', 'three'],\n        registers: { '\"': 'two' }, // NOTE: unnamed register not overwritten\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>P in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'R123<Esc>' + 'viw' + '3P',\n        end: ['one', '', '|two', 'two', 'two', '', 'three'],\n        registers: { '\"': 'two' }, // NOTE: unnamed register not overwritten\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P in Visual mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + 'vj' + '2P',\n        end: ['ABCD', 'a|[1][1]yz', 'W[2][2]XYZ'],\n      });\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>P in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'V' + '3P',\n        end: ['one', '|two', 'two', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>P in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'V' + '3P',\n        end: ['one', '|two', 'two', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P in VisualLine mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jVj' + '2P',\n        end: ['ABCD', '|[1]', '[2]', '[1]', '[2]', 'WXYZ'],\n      });\n    });\n\n    suite('VisualBlock mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>P in VisualBlock mode',\n        start: ['ABCDE', '|TESTabcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'd4l' + 'l<C-v>jjll' + '2P',\n        end: ['ABCDE', 'aTESTTES|Te', '1TESTTES|T5', 'vTESTTES|Tz', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>P in VisualBlock mode',\n        start: ['ABCDE', '|TEST', 'abcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'dd' + 'l<C-v>jjll' + '2P',\n        end: ['ABCDE', '|TEST', 'TEST', 'ae', '1|5', 'v|z', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P in VisualBlock mode (into smaller block)',\n        start: ['|[1]ABCD', '[2]abcd', '[3]wxyz', 'WXYZ'],\n        keysPressed: '<C-v>lljj' + 'd' + 'jl<C-v>lj' + '2P',\n        end: ['ABCD', 'a|[1][1]d', 'w[2][2|]z', 'W[3][3]XYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>P in VisualBlock mode (into larger block)',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl<C-v>ljj' + '2P',\n        end: ['ABCD', 'a|[1][1]d', 'w[2][2|]z', 'W|Z'], // TODO(#9831)\n      });\n    });\n  });\n\n  suite('gp', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gp',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2gp',\n        end: ['one tXYZXYZ|wo three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>gp',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '3gp',\n        end: ['one tXYZ', 'ABC|XYZ', 'ABCXYZ', 'ABCwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gp',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'j' + '2gp',\n        end: ['one', 'two', 'abc', 'abc', '|three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gp on last line',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'G' + '2gp',\n        end: ['one', 'two', 'three', 'abc', '|abc'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + '2gp',\n        end: ['ABCD', 'ab[1][1]cd', 'wx[2][2]|yz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank ragged block-wise selection, <count>gp',\n        start: ['|abcd', 'ABCDEFG', 'wxyz', 'WXYZ'],\n        keysPressed: '2l' + '<C-v>j$' + 'y' + '2gp',\n        end: ['abccd   cd   d', 'ABCCDEFGCDEFG|DEFG', 'wxyz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp past last line',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'Gl' + '2gp',\n        end: ['ABCD', 'abcd', 'wxyz', 'WX[1][1]YZ', '  [2][2|]'],\n      });\n    });\n\n    suite('Visual mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gp in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'R123<Esc>' + 'viw' + '3gp',\n        end: ['one', 'twotwotw|o', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>gp in Visual mode',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'wl' + 'v' + '3gp',\n        end: ['one tXYZ', 'ABC|XYZ', 'ABCXYZ', 'ABCo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gp in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'R123<Esc>' + 'viw' + '3gp',\n        end: ['one', '', 'two', 'two', 'two', '|', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp in Visual mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + 'vj' + '2gp',\n        end: ['ABCD', 'a[1][1]yz', 'W[2][2]|XYZ'],\n      });\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gp in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'V' + '3gp',\n        end: ['one', 'two', 'two', 'two', '|three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gp in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'V' + '3gp',\n        end: ['one', 'two', 'two', 'two', '|three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp in VisualLine mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jVj' + '2gp',\n        end: ['ABCD', '[1]', '[2]', '[1]', '[2]', '|WXYZ'],\n      });\n    });\n\n    suite('VisualBlock mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gp in VisualBlock mode',\n        start: ['ABCDE', '|TESTabcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'd4l' + 'l<C-v>jjll' + '2gp',\n        end: ['ABCDE', 'aTESTTEST|e', '1TESTTES|T5', 'vTESTTES|Tz', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gp in VisualBlock mode',\n        start: ['ABCDE', '|TEST', 'abcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'dd' + 'l<C-v>jjll' + '2gp',\n        end: ['ABCDE', 'ae', '1|5', 'v|z', 'TEST', 'TEST', '|VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp in VisualBlock mode (into smaller block)',\n        start: ['|[1]ABCD', '[2]abcd', '[3]wxyz', 'WXYZ'],\n        keysPressed: '<C-v>lljj' + 'd' + 'jl<C-v>lj' + '2gp',\n        end: ['ABCD', 'a[1][1]d', 'w[2][2|]z', 'W[3][3]|XYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gp in VisualBlock mode (into larger block)',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl<C-v>ljj' + '2gp',\n        end: ['ABCD', 'a[1][1]d', 'w[2][2|]|z', 'W|Z'], // TODO(#9831)\n      });\n    });\n  });\n\n  suite('gP', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gP',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2gP',\n        end: ['one XYZXYZ|two three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>gP',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '3gP',\n        end: ['one XYZ', 'ABC|XYZ', 'ABCXYZ', 'ABCtwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gP',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + 'j' + '2gP',\n        end: ['one', 'abc', 'abc', '|two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gP on first line',\n        start: ['|abc', 'one', 'two', 'three'],\n        keysPressed: 'dd' + '2gP',\n        end: ['abc', 'abc', '|one', 'two', 'three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + '2gP',\n        end: ['ABCD', 'a[1][1]bcd', 'w[2][2]|xyz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank ragged block-wise selection, <count>gP',\n        start: ['|abcd', 'ABCDEFG', 'wxyz', 'WXYZ'],\n        keysPressed: '2l' + '<C-v>j$' + 'y' + '2gP',\n        end: ['abcd   cd   cd', 'ABCDEFGCDEFG|CDEFG', 'wxyz', 'WXYZ'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP past last line',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'Gl' + '2gP',\n        end: ['ABCD', 'abcd', 'wxyz', 'W[1][1]XYZ', ' [2][2|]'],\n      });\n    });\n\n    suite('Visual mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gP in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'R123<Esc>' + 'viw' + '3gP',\n        end: ['one', 'twotwotw|o', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>gP in Visual mode',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'wl' + 'v' + '3gP',\n        end: ['one tXYZ', 'ABC|XYZ', 'ABCXYZ', 'ABCo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gP in Visual mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'R123<Esc>' + 'viw' + '3gP',\n        end: ['one', '', 'two', 'two', 'two', '|', 'three'],\n        registers: { '\"': '123' },\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP in Visual mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl' + 'vj' + '2gP',\n        end: ['ABCD', 'a[1][1]yz', 'W[2][2]|XYZ'],\n      });\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gP in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'ye' + 'V' + '3gP',\n        end: ['one', 'two', 'two', 'two', '|three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gP in VisualLine mode',\n        start: ['one', '|two', 'three'],\n        keysPressed: 'yy' + 'V' + '3gP',\n        end: ['one', 'two', 'two', 'two', '|three'],\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP in VisualLine mode',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jVj' + '2gP',\n        end: ['ABCD', '[1]', '[2]', '[1]', '[2]', '|WXYZ'],\n      });\n    });\n\n    suite('VisualBlock mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>gP in VisualBlock mode',\n        start: ['ABCDE', '|TESTabcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'd4l' + 'l<C-v>jjll' + '2gP',\n        end: ['ABCDE', 'aTESTTEST|e', '1TESTTES|T5', 'vTESTTES|Tz', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>gP in VisualBlock mode',\n        start: ['ABCDE', '|TEST', 'abcde', '12345', 'vwxyz', 'VWXYZ'],\n        keysPressed: 'dd' + 'l<C-v>jjll' + '2gP',\n        end: ['ABCDE', 'TEST', 'TEST', '|ae', '1|5', 'v|z', 'VWXYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP in VisualBlock mode (into smaller block)',\n        start: ['|[1]ABCD', '[2]abcd', '[3]wxyz', 'WXYZ'],\n        keysPressed: '<C-v>lljj' + 'd' + 'jl<C-v>lj' + '2gP',\n        end: ['ABCD', 'a[1][1]d', 'w[2][2|]z', 'W[3][3]|XYZ'], // TODO(#9831)\n      });\n\n      newTest({\n        title: 'Yank block-wise, <count>gP in VisualBlock mode (into larger block)',\n        start: ['|[1]ABCD', '[2]abcd', 'wxyz', 'WXYZ'],\n        keysPressed: '<C-v>llj' + 'd' + 'jl<C-v>ljj' + '2gP',\n        end: ['ABCD', 'a[1][1]d', 'w[2][2|]|z', 'W|Z'], // TODO(#9831)\n      });\n    });\n  });\n\n  suite(']p', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>]p',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2]p',\n        end: ['one tXYZXY|Zwo three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>]p',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '2]p',\n        end: ['one t|XYZ', 'ABCXYZ', 'ABCwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>]p',\n        start: ['|abc', 'one', '  two', 'three'],\n        keysPressed: 'dd' + 'j' + '2]p',\n        end: ['one', '  two', '  |abc', '  abc', 'three'],\n      });\n\n      // TODO: Yank block-wise, <count>]p\n    });\n\n    suite('Visual mode', () => {\n      // TODO\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank line-wise, <count>]p in VisualLine mode',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'j' + 'V' + '2]p',\n        end: [' one', '   |XYZ', '   XYZ', '   three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>]p in VisualLine mode on last line',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'G' + 'V' + '2]p',\n        end: [' one', '  two', '  |XYZ', '  XYZ'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>]p in VisualLine mode with whole document selected',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'V' + 'G' + '2]p',\n        end: ['|XYZ', 'XYZ'],\n      });\n\n      // TODO: Yank block-wise, <count>]p in VisualLine\n    });\n\n    suite('VisualBlock mode', () => {\n      // TODO\n    });\n  });\n\n  suite('[p', () => {\n    suite('Normal mode', () => {\n      newTest({\n        title: 'Yank character-wise, <count>[p',\n        start: ['|XYZone two three'],\n        keysPressed: 'd3l' + 'w' + '2[p',\n        end: ['one XYZXY|Ztwo three'],\n      });\n\n      newTest({\n        title: 'Yank two lines character-wise, <count>[p',\n        start: ['|XYZ', 'ABCone two three'],\n        keysPressed: 'vj2ld' + 'w' + '2[p',\n        end: ['one |XYZ', 'ABCXYZ', 'ABCtwo three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>[p',\n        start: ['|abc', 'one', '  two', 'three'],\n        keysPressed: 'dd' + 'j' + '2[p',\n        end: ['one', '  |abc', '  abc', '  two', 'three'],\n      });\n\n      // TODO: Yank block-wise, <count>[p in Visual\n    });\n\n    suite('Visual mode', () => {\n      // TODO\n    });\n\n    suite('VisualLine mode', () => {\n      newTest({\n        title: 'Yank line-wise, <count>[p in VisualLine mode',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'j' + 'V' + '2[p',\n        end: [' one', '   |XYZ', '   XYZ', '   three'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>[p in VisualLine mode on last line',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'G' + 'V' + '2[p',\n        end: [' one', '  two', '  |XYZ', '  XYZ'],\n      });\n\n      newTest({\n        title: 'Yank line-wise, <count>[p in VisualLine mode with whole document selected',\n        start: ['|XYZ', ' one', '  two', '   three'],\n        keysPressed: 'dd' + 'V' + 'G' + '2[p',\n        end: ['|XYZ', 'XYZ'],\n      });\n\n      // TODO: Yank block-wise, <count>[p in VisualLine\n    });\n\n    suite('VisualBlock mode', () => {\n      // TODO\n    });\n  });\n});\n"
  },
  {
    "path": "test/operator/rot13.test.ts",
    "content": "import * as assert from 'assert';\n\nimport { ROT13Operator } from '../../src/actions/operator';\nimport { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\n\nsuite('rot13 operator', () => {\n  test('rot13() unit test', () => {\n    const testCases = [\n      ['abcdefghijklmnopqrstuvwxyz', 'nopqrstuvwxyzabcdefghijklm'],\n      ['ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'NOPQRSTUVWXYZABCDEFGHIJKLM'],\n      ['!@#$%^&*()', '!@#$%^&*()'],\n      ['âéü', 'âéü'],\n    ];\n    for (const [input, output] of testCases) {\n      assert.strictEqual(ROT13Operator.rot13(input), output);\n    }\n  });\n\n  newTest({\n    title: 'g?j works',\n    start: ['a|bc', 'def', 'ghi'],\n    keysPressed: 'g?j',\n    end: ['n|op', 'qrs', 'ghi'],\n  });\n\n  // TODO: Fix cursor position in Visual modes\n  newTest({\n    title: 'g? in visual mode works',\n    start: ['a|bc', 'def', 'ghi'],\n    keysPressed: 'vj$g?',\n    end: ['aop', 'qrs', '|ghi'],\n    endMode: Mode.Visual,\n  });\n\n  newTest({\n    title: 'g? in visual line mode works',\n    start: ['a|bc', 'def', 'ghi'],\n    keysPressed: 'Vj$g?',\n    end: ['nop', 'qr|s', 'ghi'],\n    endMode: Mode.VisualLine,\n  });\n\n  newTest({\n    title: 'g? in visual block mode works',\n    start: ['a|bc', 'def', 'ghi'],\n    keysPressed: '<C-v>j$g?',\n    end: ['aop', 'drs|', 'ghi'],\n    endMode: Mode.VisualBlock,\n  });\n});\n"
  },
  {
    "path": "test/operator/shift.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('shift operator', () => {\n  suiteSetup(setupWorkspace);\n\n  newTest({\n    title: 'basic shift left test',\n    start: ['  |zxcv', '  zxcv', '  zxcv'],\n    keysPressed: '<<',\n    end: ['|zxcv', '  zxcv', '  zxcv'],\n  });\n\n  newTest({\n    title: 'shift left goto end test',\n    start: ['  |zxcv', '  zxcv', '  zxcv'],\n    keysPressed: '<G',\n    end: ['|zxcv', 'zxcv', 'zxcv'],\n  });\n\n  newTest({\n    title: 'shift left goto line test',\n    start: ['  |zxcv', '  zxcv', '  zxcv'],\n    keysPressed: '<2G',\n    end: ['|zxcv', 'zxcv', '  zxcv'],\n  });\n\n  newTest({\n    title: 'shift right goto end test',\n    start: ['|zxcv', 'zxcv', 'zxcv'],\n    keysPressed: '>G',\n    end: ['  |zxcv', '  zxcv', '  zxcv'],\n  });\n\n  newTest({\n    title: 'shift right goto line test',\n    start: ['|zxcv', 'zxcv', 'zxcv'],\n    keysPressed: '>2G',\n    end: ['  |zxcv', '  zxcv', 'zxcv'],\n  });\n});\n"
  },
  {
    "path": "test/operator/surrogate.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite('surrogate-pair', () => {\n  newTest({\n    title: 'yank single hokke',\n    start: ['|𩸽'],\n    keysPressed: 'vyp',\n    end: ['𩸽|𩸽'],\n  });\n\n  newTest({\n    title: 'move across hokke',\n    start: ['|𩸽𩸽𩸽𩸽𩸽'],\n    keysPressed: 'lll',\n    end: ['𩸽𩸽𩸽|𩸽𩸽'],\n  });\n\n  newTest({\n    title: 'move and yank triple hokke',\n    start: ['|𩸽𩸽𩸽'],\n    keysPressed: 'vllyp',\n    end: ['𩸽𩸽𩸽|𩸽𩸽𩸽'],\n  });\n\n  newTest({\n    title: 'yank cute dog and hokke across lines',\n    start: ['|𩸽𩸽𩸽🐕🐕🐕', '🐕🐕🐕𩸽𩸽𩸽'],\n    keysPressed: 'vjllyP',\n    end: ['|𩸽𩸽𩸽🐕🐕🐕', '🐕🐕🐕𩸽𩸽𩸽🐕🐕🐕', '🐕🐕🐕𩸽𩸽𩸽'],\n  });\n\n  newTest({\n    title: 'insert a cute dog',\n    start: ['|'],\n    keysPressed: 'i🐕weee<ESC>',\n    end: ['🐕weee|'],\n  });\n\n  newTest({\n    title: 'insert some more cute dogs',\n    start: ['|'],\n    keysPressed: 'i🐕🐕<ESC>',\n    end: ['🐕🐕|'],\n  });\n\n  newTest({\n    title: 'move left over cute dog',\n    start: ['|𩸽🐕', 'text'],\n    keysPressed: 'jlllkh',\n    end: ['|𩸽🐕', 'text'],\n  });\n});\n"
  },
  {
    "path": "test/plugins/camelCaseMotion.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nsuite('camelCaseMotion plugin if not enabled', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { camelCaseMotion: { enable: false } } });\n  });\n\n  newTest({\n    title: \"basic motion doesn't work\",\n    start: ['|camelWord'],\n    keysPressed: '<leader>w',\n    end: ['|camelWord'],\n  });\n});\n\nsuite('camelCaseMotion plugin', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { camelCaseMotion: { enable: true } } });\n  });\n\n  suite('handles <leader>w for camelCaseText', () => {\n    newTest({\n      title: 'step over whitespace',\n      start: ['|var testCamelVARWithNums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var |testCamelVARWithNums555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step to Camel word',\n      start: ['var |testCamelVARWithNums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var test|CamelVARWithNums555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step to CAP word',\n      start: ['var test|CamelVARWithNums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamel|VARWithNums555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step after CAP word',\n      start: ['var testCamel|VARWithNums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamelVAR|WithNums555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step from middle of word to Camel word',\n      start: ['var testCamelVARW|ithNums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamelVARWith|Nums555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step to number word',\n      start: ['var testCamelVARWith|Nums555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamelVARWithNums|555&&&Ops'],\n    });\n\n    newTest({\n      title: 'step to operator word',\n      start: ['var testCamelVARWithNums|555&&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamelVARWithNums555|&&&Ops'],\n    });\n\n    newTest({\n      title: 'step from inside operator word',\n      start: ['var testCamelVARWithNums555&|&&Ops'],\n      keysPressed: '<leader>w',\n      end: ['var testCamelVARWithNums555&&&|Ops'],\n    });\n\n    newTest({\n      title: 'step to operator and then over',\n      start: ['|camel.camelWord'],\n      keysPressed: '2<leader>w',\n      end: ['camel.|camelWord'],\n    });\n  });\n\n  suite('handles <leader>w for underscore_var', () => {\n    newTest({\n      title: 'step to _word',\n      start: ['|some_var and_other23_var'],\n      keysPressed: '<leader>w',\n      end: ['some_|var and_other23_var'],\n    });\n\n    newTest({\n      title: 'step over whitespace to word',\n      start: ['some|_var and_other23_var'],\n      keysPressed: '<leader>w',\n      end: ['some_|var and_other23_var'],\n    });\n\n    newTest({\n      title: 'step from inside word to word',\n      start: ['some_var a|nd_other23_var'],\n      keysPressed: '<leader>w',\n      end: ['some_var and_|other23_var'],\n    });\n\n    newTest({\n      title: 'step form _word to number',\n      start: ['some_var and_|other23_var'],\n      keysPressed: '<leader>w',\n      end: ['some_var and_other|23_var'],\n    });\n\n    newTest({\n      title: 'step from number word to word',\n      start: ['some_var and_other2|3_var'],\n      keysPressed: '<leader>w',\n      end: ['some_var and_other23_|var'],\n    });\n\n    newTest({\n      title: 'step from in whitespace to word',\n      start: ['variable  |  more_vars'],\n      keysPressed: '<leader>w',\n      end: ['variable    |more_vars'],\n    });\n\n    newTest({\n      title: 'step in ALL_CAPS_WORD',\n      start: ['A|LL_CAPS_WORD'],\n      keysPressed: '2<leader>w',\n      end: ['ALL_CAPS_|WORD'],\n    });\n  });\n\n  suite('handles d<leader>w', () => {\n    newTest({\n      title: 'delete from start of camelWord',\n      start: ['|camelTwoWord'],\n      keysPressed: 'd<leader>w',\n      end: ['|TwoWord'],\n    });\n\n    newTest({\n      title: 'delete from middle of camelWord',\n      start: ['ca|melTwoWord'],\n      keysPressed: 'd<leader>w',\n      end: ['ca|TwoWord'],\n    });\n\n    newTest({\n      title: 'delete from start of CamelWord',\n      start: ['camel|TwoWord'],\n      keysPressed: 'd<leader>w',\n      end: ['camel|Word'],\n    });\n\n    newTest({\n      title: 'delete two words from camelWord',\n      start: ['ca|melTwoWord'],\n      keysPressed: '2d<leader>w',\n      end: ['ca|Word'],\n    });\n\n    newTest({\n      title: 'delete from start of underscore_word',\n      start: ['|camel_two_word'],\n      keysPressed: 'd<leader>w',\n      end: ['|two_word'],\n    });\n\n    newTest({\n      title: 'delete from middle of underscore_word',\n      start: ['ca|mel_two_word'],\n      keysPressed: 'd<leader>w',\n      end: ['ca|two_word'],\n    });\n\n    newTest({\n      title: 'delete two words from camel_word',\n      start: ['ca|mel_two_word'],\n      keysPressed: '2d<leader>w',\n      end: ['ca|word'],\n    });\n  });\n\n  suite('handles di<leader>w', () => {\n    newTest({\n      title: 'delete from start of camelWord',\n      start: ['|camelTwoWord'],\n      keysPressed: 'di<leader>w',\n      end: ['|TwoWord'],\n    });\n\n    newTest({\n      title: 'delete from middle of camelWord',\n      start: ['ca|melTwoWord'],\n      keysPressed: 'di<leader>w',\n      end: ['|TwoWord'],\n    });\n\n    newTest({\n      title: 'delete from start of CamelWord',\n      start: ['camel|TwoWord'],\n      keysPressed: 'di<leader>w',\n      end: ['camel|Word'],\n    });\n\n    newTest({\n      title: 'delete two words from camelWord',\n      start: ['ca|melTwoWord'],\n      keysPressed: '2di<leader>w',\n      end: ['|Word'],\n    });\n\n    newTest({\n      title: 'delete from start of underscore_word',\n      start: ['|camel_two_word'],\n      keysPressed: 'di<leader>w',\n      end: ['|_two_word'],\n    });\n\n    newTest({\n      title: 'delete from middle of underscore_word',\n      start: ['ca|mel_two_word'],\n      keysPressed: 'di<leader>w',\n      end: ['|_two_word'],\n    });\n\n    newTest({\n      title: 'delete two words from camel_word',\n      start: ['ca|mel_two_word'],\n      keysPressed: '2di<leader>w',\n      end: ['|_word'],\n    });\n  });\n\n  suite('handles <leader>b', () => {\n    newTest({\n      title: 'back from middle of word',\n      start: ['camel.camelWord oth|er'],\n      keysPressed: '<leader>b',\n      end: ['camel.camelWord |other'],\n    });\n\n    newTest({\n      title: 'back over whitespace to camelWord',\n      start: ['camel.camelWord |other'],\n      keysPressed: '<leader>b',\n      end: ['camel.camel|Word other'],\n    });\n\n    newTest({\n      title: 'back twice over operator',\n      start: ['camel.camel|Word other'],\n      keysPressed: '2<leader>b',\n      end: ['camel|.camelWord other'],\n    });\n  });\n\n  suite('handles <leader>e', () => {\n    newTest({\n      title: 'from start to middle of underscore_word',\n      start: ['|foo_bar && camelCase'],\n      keysPressed: '<leader>e',\n      end: ['fo|o_bar && camelCase'],\n    });\n\n    newTest({\n      title: 'from middle to end of underscore_word',\n      start: ['fo|o_bar && camelCase'],\n      keysPressed: '<leader>e',\n      end: ['foo_ba|r && camelCase'],\n    });\n\n    newTest({\n      title: 'twice to end of word over operator',\n      start: ['foo_ba|r && camelCase'],\n      keysPressed: '2<leader>e',\n      end: ['foo_bar && came|lCase'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/plugins/easymotion.test.ts",
    "content": "import {\n  buildTriggerKeys,\n  EasymotionTrigger,\n} from '../../src/actions/plugins/easymotion/easymotion.cmd';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nfunction easymotionCommand(trigger: EasymotionTrigger, searchWord: string, jumpKey: string) {\n  return [...buildTriggerKeys(trigger), searchWord, jumpKey].join('');\n}\n\nsuite('easymotion plugin', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: { easymotion: true },\n    });\n  });\n\n  newTest({\n    title: 'Can handle s move',\n    start: ['a|bcdabcd'],\n    keysPressed: easymotionCommand({ key: 's' }, 'a', 'k'),\n    end: ['|abcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle 2s move',\n    start: ['ab|cdabcd'],\n    keysPressed: easymotionCommand({ key: '2s' }, 'ab', 'k'),\n    end: ['|abcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle f move',\n    start: ['a|bcdabcdabcd'],\n    keysPressed: easymotionCommand({ key: 'f' }, 'a', 'k'),\n    end: ['abcdabcd|abcd'],\n  });\n\n  newTest({\n    title: 'Can handle 2f move',\n    start: ['a|bcdabcdabcd'],\n    keysPressed: easymotionCommand({ key: '2f' }, 'ab', 'k'),\n    end: ['abcdabcd|abcd'],\n  });\n\n  newTest({\n    title: 'Can handle F move',\n    start: ['abcdabc|dabcd'],\n    keysPressed: easymotionCommand({ key: 'F' }, 'a', 'k'),\n    end: ['|abcdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle 2F move',\n    start: ['abcdabc|dabcd'],\n    keysPressed: easymotionCommand({ key: '2F' }, 'ab', 'k'),\n    end: ['|abcdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle t move',\n    start: ['abcd|abcdabcd'],\n    keysPressed: easymotionCommand({ key: 't' }, 'c', 'k'),\n    end: ['abcdabcda|bcd'],\n  });\n\n  newTest({\n    title: 'Can handle bd-t move',\n    start: ['abcd|abcdabcd'],\n    keysPressed: easymotionCommand({ key: 'bdt', leaderCount: 3 }, 'c', 'k'),\n    end: ['a|bcdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle 2t move',\n    start: ['abcd|abcdabcd'],\n    keysPressed: easymotionCommand({ key: '2t' }, 'cd', 'k'),\n    end: ['abcdabcda|bcd'],\n  });\n\n  newTest({\n    title: 'Can handle bd-t2 move',\n    start: ['abcd|abcdabcd'],\n    keysPressed: easymotionCommand({ key: 'bd2t', leaderCount: 3 }, 'cd', 'k'),\n    end: ['a|bcdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle T move',\n    start: ['abcdab|cdabcd'],\n    keysPressed: easymotionCommand({ key: 'T' }, 'a', 'k'),\n    end: ['a|bcdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle 2T move',\n    start: ['abcdabc|dabcd'],\n    keysPressed: easymotionCommand({ key: '2T' }, 'ab', 'k'),\n    end: ['ab|cdabcdabcd'],\n  });\n\n  newTest({\n    title: 'Can handle w move',\n    start: ['abc |def ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'w' }, '', 'k'),\n    end: ['abc def ghi |jkl'],\n  });\n\n  newTest({\n    title: 'Can handle bd-w move',\n    start: ['abc |def ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'bdw', leaderCount: 3 }, '', 'k'),\n    end: ['|abc def ghi jkl'],\n  });\n\n  newTest({\n    title: 'Can handle b move',\n    start: ['abc def |ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'b' }, '', 'k'),\n    end: ['|abc def ghi jkl'],\n  });\n\n  newTest({\n    title: 'Can handle e move',\n    start: ['abc |def ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'e' }, '', 'k'),\n    end: ['abc def ghi jk|l'],\n  });\n\n  newTest({\n    title: 'Can handle bd-e move',\n    start: ['abc |def ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'bde', leaderCount: 3 }, '', 'k'),\n    end: ['ab|c def ghi jkl'],\n  });\n\n  newTest({\n    title: 'Can handle ge move',\n    start: ['abc def |ghi jkl'],\n    keysPressed: easymotionCommand({ key: 'ge' }, '', 'k'),\n    end: ['ab|c def ghi jkl'],\n  });\n\n  newTest({\n    title: 'Can handle n-char move',\n    start: ['abc |def ghi jkl', 'abc def ghi jkl'],\n    keysPressed: easymotionCommand({ key: '/' }, 'ghi\\n', 'k'),\n    end: ['abc def ghi jkl', 'abc def |ghi jkl'],\n  });\n\n  newTest({\n    title: 'Can handle j move',\n    start: ['abc', 'd|ef', 'ghi', 'jkl'],\n    keysPressed: easymotionCommand({ key: 'j' }, '', 'k'),\n    end: ['abc', 'def', 'ghi', '|jkl'],\n  });\n\n  newTest({\n    title: 'Can handle k move',\n    start: ['abc', 'def', 'g|hi', 'jkl'],\n    keysPressed: easymotionCommand({ key: 'k' }, '', 'k'),\n    end: ['abc', '|def', 'ghi', 'jkl'],\n  });\n\n  newTest({\n    title: 'Can handle bd-jk move (1)',\n    start: ['abc', 'def', '|ghi', 'jkl'],\n    keysPressed: easymotionCommand({ key: 'bdjk', leaderCount: 3 }, '', 'k'),\n    end: ['abc', '|def', 'ghi', 'jkl'],\n  });\n\n  newTest({\n    title: 'Can handle bd-jk move (2)',\n    start: ['abc', 'def', '|ghi', 'jkl'],\n    keysPressed: easymotionCommand({ key: 'bdjk', leaderCount: 3 }, '', 'h'),\n    end: ['abc', 'def', 'ghi', '|jkl'],\n  });\n\n  newTest({\n    title: 'Can handle lineforward move (1)',\n    start: ['|abcDefGhi'],\n    keysPressed: easymotionCommand({ key: 'l', leaderCount: 2 }, '', 'h'),\n    end: ['abc|DefGhi'],\n  });\n\n  newTest({\n    title: 'Can handle lineforward move (2)',\n    start: ['|abcDefGhi'],\n    keysPressed: easymotionCommand({ key: 'l', leaderCount: 2 }, '', 'k'),\n    end: ['abcDef|Ghi'],\n  });\n\n  newTest({\n    title: 'Can handle linebackward move (1)',\n    start: ['abcDefGh|i'],\n    keysPressed: easymotionCommand({ key: 'h', leaderCount: 2 }, '', 'k'),\n    end: ['abc|DefGhi'],\n  });\n\n  newTest({\n    title: 'Can handle linebackward move (2)',\n    start: ['abcDefGh|i'],\n    keysPressed: easymotionCommand({ key: 'h', leaderCount: 2 }, '', 'h'),\n    end: ['abcDef|Ghi'],\n  });\n\n  newTest({\n    title: 'Can handle searching for backslash (\\\\)',\n    start: ['|https:\\\\\\\\www.google.com'],\n    keysPressed: easymotionCommand({ key: 'f' }, '\\\\', 'k'),\n    end: ['https:\\\\|\\\\www.google.com'],\n  });\n\n  newTest({\n    title: 'Can handle searching for carat (^)',\n    start: ['|<^_^>'],\n    keysPressed: easymotionCommand({ key: 'f' }, '^', 'h'),\n    end: ['<|^_^>'],\n  });\n\n  newTest({\n    title: 'Can handle searching for dot (.)',\n    start: ['|https:\\\\\\\\www.google.com'],\n    keysPressed: easymotionCommand({ key: 'f' }, '.', 'k'),\n    end: ['https:\\\\\\\\www.google|.com'],\n  });\n});\n"
  },
  {
    "path": "test/plugins/imswitcher.test.ts",
    "content": "import * as assert from 'assert';\nimport { InputMethodSwitcher } from '../../src/actions/plugins/imswitcher';\nimport { Mode } from '../../src/mode/mode';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('Input method plugin', () => {\n  let savedCmd = '';\n\n  const fakeExecuteChinese = (cmd: string): Promise<string> => {\n    return new Promise<string>((resolve, reject) => {\n      if (cmd === 'im-select') {\n        resolve('chinese');\n      } else {\n        savedCmd = cmd;\n        resolve('');\n      }\n    });\n  };\n\n  const fakeExecuteDefault = (cmd: string): Promise<string> => {\n    return new Promise<string>((resolve, reject) => {\n      if (cmd === 'im-select') {\n        resolve('default');\n      } else {\n        savedCmd = cmd;\n        resolve('');\n      }\n    });\n  };\n\n  setup(async () => {\n    await setupWorkspace({\n      config: {\n        autoSwitchInputMethod: {\n          enable: true,\n          defaultIM: 'default',\n          obtainIMCmd: 'im-select',\n          switchIMCmd: 'im-select {im}',\n        },\n      },\n    });\n  });\n\n  test('use default im in insert mode', async () => {\n    savedCmd = '';\n    const inputMethodSwitcher = new InputMethodSwitcher(fakeExecuteDefault);\n    await inputMethodSwitcher.switchInputMethod(Mode.Normal, Mode.Insert);\n    assert.strictEqual('', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Insert, Mode.Normal);\n    assert.strictEqual('', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Normal, Mode.Insert);\n    assert.strictEqual('', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Insert, Mode.Normal);\n    assert.strictEqual('', savedCmd);\n  });\n\n  test('use other im in insert mode', async () => {\n    savedCmd = '';\n    const inputMethodSwitcher = new InputMethodSwitcher(fakeExecuteChinese);\n    await inputMethodSwitcher.switchInputMethod(Mode.Normal, Mode.Insert);\n    assert.strictEqual('', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Insert, Mode.Normal);\n    assert.strictEqual('im-select default', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Normal, Mode.Insert);\n    assert.strictEqual('im-select chinese', savedCmd);\n    await inputMethodSwitcher.switchInputMethod(Mode.Insert, Mode.Normal);\n    assert.strictEqual('im-select default', savedCmd);\n  });\n});\n"
  },
  {
    "path": "test/plugins/lastNextObject.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('lastNextObject plugin', () => {\n  suite('lastNextObject plugin disabled', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        fileExtension: '.js',\n      });\n    });\n\n    // test next\n    newTest({\n      title: \"next object - should not work as it's disabled\",\n      start: [\n        '|a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        '|a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n  });\n  suite('lastNextObject plugin', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          targets: {\n            enable: true,\n            bracketObjects: { enable: true },\n            smartQuotes: {\n              enable: false,\n              breakThroughLines: false,\n              aIncludesSurroundingSpaces: true,\n            },\n          },\n        },\n        fileExtension: '.js',\n      });\n    });\n\n    // test next\n    newTest({\n      title: 'next object - 1',\n      start: [\n        '|a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 2',\n      start: [\n        'a|(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 3',\n      start: [\n        'a(|b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 4',\n      start: [\n        'a(b|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 5',\n      start: [\n        'a(b)c|(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a (|) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 6',\n      start: [\n        'a(b)c(d)e', //\n        'a |( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b (|) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 7',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b |( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d (|) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 8',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g|', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 9',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d |( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 10',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '|(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '|(a)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 11',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        '|a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|)b', //\n      ],\n    });\n    newTest({\n      title: 'next object - 12',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '|', //\n        '(a)b', //\n      ],\n      keysPressed: 'din(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|)b', //\n      ],\n    });\n    // test last\n    newTest({\n      title: 'last object - 1',\n      start: [\n        '|a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        '|a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 2',\n      start: [\n        'a|(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a|(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 3',\n      start: [\n        'a(|b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(|b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 4',\n      start: [\n        'a(b|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 5',\n      start: [\n        'a(b)|c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 6',\n      start: [\n        'a(b)c|(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 7',\n      start: [\n        'a(b)c(d|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(|)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 8',\n      start: [\n        'a(b)c(d)|e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 9',\n      start: [\n        'a(b)c(d)e', //\n        '|a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 10',\n      start: [\n        'a(b)c(d)e', //\n        'a |( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 11',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b |( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(|)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 12',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) |d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b (|) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 13',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d |( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b (|) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 14',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e )| f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d (|) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 15',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f |) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d (|) f ) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 16',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) |g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a (|) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 17',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        '|a', //\n        '', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a (|) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 18',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '|', //\n        '(a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a (|) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 19',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|a)b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a (|) g', //\n        'a', //\n        '', //\n        '(a)b', //\n      ],\n    });\n    newTest({\n      title: 'last object - 20',\n      start: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(a)|b', //\n      ],\n      keysPressed: 'dil(',\n      end: [\n        'a(b)c(d)e', //\n        'a ( b ( c ) d ( e ) f ) g', //\n        'a', //\n        '', //\n        '(|)b', //\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "test/plugins/replaceWithRegister.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('replaceWithRegister plugin', () => {\n  const YankInnerWord = 'yiw';\n  const ReplaceOperator = 'gr';\n\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { replaceWithRegister: true } });\n  });\n\n  newTest({\n    title: 'Replaces within inner word',\n    start: ['|first second'],\n    keysPressed: `${YankInnerWord}w${ReplaceOperator}iw`,\n    end: ['first firs|t'],\n  });\n\n  newTest({\n    title: 'Replaces within inner Word',\n    start: ['|first sec-ond'],\n    keysPressed: `${YankInnerWord}w${ReplaceOperator}iW`,\n    end: ['first firs|t'],\n  });\n\n  newTest({\n    title: \"Replaces within ''\",\n    start: [\"|first 'second'\"],\n    keysPressed: `${YankInnerWord}ww${ReplaceOperator}i'`,\n    end: [\"first 'firs|t'\"],\n  });\n\n  newTest({\n    title: \"Replaces within '' including spaces\",\n    start: [\"|first ' second '\"],\n    keysPressed: `${YankInnerWord}ww${ReplaceOperator}i'`,\n    end: [\"first 'firs|t'\"],\n  });\n\n  newTest({\n    title: 'Replaces within ()',\n    start: ['|first (second)'],\n    keysPressed: `${YankInnerWord}ww${ReplaceOperator}i)`,\n    end: ['first (firs|t)'],\n  });\n\n  newTest({\n    title: 'Replaces within () including spaces',\n    start: ['|first ( second )'],\n    keysPressed: `${YankInnerWord}ww${ReplaceOperator}i)`,\n    end: ['first (firs|t)'],\n  });\n\n  newTest({\n    title: 'Replaces within a paragraph',\n    start: ['  |first', '  second'],\n    keysPressed: `${YankInnerWord}${ReplaceOperator}ap`,\n    end: ['|first'],\n  });\n\n  newTest({\n    title: 'Replaces using a specified register',\n    start: ['|first second'],\n    keysPressed: `\"a${YankInnerWord}w\"a${ReplaceOperator}iw`,\n    end: ['first firs|t'],\n  });\n\n  newTest({\n    title: 'Replaces within {} over multiple lines',\n    start: ['{', '  first', '  s|econd', '  third', '}'],\n    keysPressed: `${YankInnerWord}${ReplaceOperator}i}`,\n    end: ['{', '|second', '}'],\n  });\n\n  newTest({\n    title: 'Replaces a multiline register within {} over multiple lines',\n    start: ['{', '  first', '  s|econd', '  third', '}'],\n    keysPressed: `yj${ReplaceOperator}i}`,\n    end: ['{', '  |second', '  third', '}'],\n  });\n\n  newTest({\n    title: 'Replaces a multiline register within {} over multiple lines',\n    start: ['{', '  first', '  s|econd', '  third', '}'],\n    keysPressed: `yj${ReplaceOperator}i}`,\n    end: ['{', '  |second', '  third', '}'],\n  });\n\n  newTest({\n    title: 'Yanking inside {} then replacing inside {} in a noop, besides the cursor movement',\n    start: ['{', '  first', '  s|econd', '  third', '}'],\n    keysPressed: `yi}${ReplaceOperator}i}`,\n    end: ['{', '  |first', '  second', '  third', '}'],\n  });\n\n  newTest({\n    title: 'grr replaces the entire line with the register',\n    start: ['first sec|ond third'],\n    keysPressed: `${YankInnerWord}grr`,\n    end: ['|second'],\n  });\n\n  newTest({\n    title: 'grr can replace multiple lines',\n    start: ['|first', 'second', 'third'],\n    keysPressed: `${YankInnerWord}2grr`,\n    end: ['|first', 'third'],\n  });\n\n  newTest({\n    title: 'Replaces in visual mode',\n    start: ['|first second'],\n    keysPressed: `${YankInnerWord}wviw${ReplaceOperator}`,\n    end: ['first firs|t'],\n  });\n\n  newTest({\n    title: 'Replaces in visual mode using a specified register',\n    start: ['|first second'],\n    keysPressed: `\"a${YankInnerWord}wviw\"a${ReplaceOperator}`,\n    end: ['first firs|t'],\n  });\n\n  newTest({\n    title: 'Replaces in visual line mode',\n    start: ['|first second'],\n    keysPressed: `${YankInnerWord}wV${ReplaceOperator}`,\n    end: ['firs|t'],\n  });\n\n  newTest({\n    title: 'grj is linewise',\n    start: ['|first second', 'third fourth', 'fifth sixth'],\n    keysPressed: `${YankInnerWord}w${ReplaceOperator}j`,\n    end: ['|first', 'fifth sixth'],\n  });\n});\n"
  },
  {
    "path": "test/plugins/smartQuotes.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('smartQuotes plugin', () => {\n  suite('smartQuotes.breakThroughLines = false', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          targets: {\n            enable: true,\n            bracketObjects: { enable: true },\n            smartQuotes: {\n              enable: true,\n              breakThroughLines: false,\n              aIncludesSurroundingSpaces: true,\n            },\n          },\n        },\n        fileExtension: '.js',\n      });\n    });\n\n    // test quotes types\n    newTest({\n      title: 'single quote - 1',\n      start: ['|aaa \"bbb\" c \\'d\\' '],\n      keysPressed: \"di'\",\n      end: ['aaa \"bbb\" c \\'|\\' '],\n    });\n    newTest({\n      title: 'single quote - 2',\n      start: ['aaa \"bbb\" |c \\'d\\' '],\n      keysPressed: \"di'\",\n      end: ['aaa \"bbb\" c \\'|\\' '],\n    });\n    newTest({\n      title: 'backtick - 1',\n      start: ['|aaa \"bbb\" c `d` '],\n      keysPressed: 'di`',\n      end: ['aaa \"bbb\" c `|` '],\n    });\n    newTest({\n      title: 'backtick - 2',\n      start: ['aaa \"bbb\" |c `d` '],\n      keysPressed: 'di`',\n      end: ['aaa \"bbb\" c `|` '],\n    });\n    newTest({\n      title: 'any-quote - 1',\n      start: ['|  \\'aaa\\' \"bbb\" c `d` '],\n      keysPressed: 'diq',\n      end: ['  \\'|\\' \"bbb\" c `d` '],\n    });\n    newTest({\n      title: 'any-quote - 2',\n      start: ['  \\'aaa\\'| \"bbb\" c `d` '],\n      keysPressed: 'diq',\n      end: ['  \\'aaa\\' \"|\" c `d` '],\n    });\n    newTest({\n      title: 'any-quote - 3',\n      start: ['  \\'aaa\\' \"bbb\" |c `d` '],\n      keysPressed: 'diq',\n      end: ['  \\'aaa\\' \"bbb\" c `|` '],\n    });\n    // test basic usage\n    newTest({\n      title: 'no quotes at all',\n      start: ['|aaabcd'],\n      keysPressed: 'di\"',\n      end: ['|aaabcd'],\n    });\n    newTest({\n      title: 'single quote - 1',\n      start: ['|aaa\"b'],\n      keysPressed: 'di\"',\n      end: ['|aaa\"b'],\n    });\n    newTest({\n      title: 'single quote - 2',\n      start: ['aaa|\"b'],\n      keysPressed: 'di\"',\n      end: ['aaa|\"b'],\n    });\n    newTest({\n      title: 'single quote - 3',\n      start: ['aaa\"|b'],\n      keysPressed: 'di\"',\n      end: ['aaa\"|b'],\n    });\n    newTest({\n      title: 'one quotes object - 1',\n      start: ['|aaa \"bbb\" c'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'one quotes object - 2',\n      start: ['aaa |\"bbb\" c'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'one quotes object - 3',\n      start: ['aaa \"|bbb\" c'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'one quotes object - 4',\n      start: ['aaa \"bbb|\" c'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'one quotes object - 5',\n      start: ['aaa \"bbb\"| c'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'even quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'even quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'even quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'even quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'even quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'even quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'even quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'even quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'even quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e '],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'odd quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e \" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"d\"| e \" f'],\n    });\n    newTest({\n      title: 'odd quotes - 10',\n      start: ['aaa \"bbb\" c \"d\" e |\" f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"d\" e |\" f'],\n    });\n    newTest({\n      title: 'odd quotes - 11',\n      start: ['aaa \"bbb\" c \"d\" e \"| f'],\n      keysPressed: 'di\"',\n      end: ['aaa \"bbb\" c \"d\" e \"| f'],\n    });\n    newTest({\n      title: 'no space between - 1',\n      start: ['|\"a\"\"bbb\"'],\n      keysPressed: 'di\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'no space between - 2',\n      start: ['\"|a\"\"bbb\"'],\n      keysPressed: 'di\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'no space between - 3',\n      start: ['\"a|\"\"bbb\"'],\n      keysPressed: 'di\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'no space between - 4',\n      start: ['\"a\"|\"bbb\"'],\n      keysPressed: 'di\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'no space between - 5',\n      start: ['\"a\"\"|bbb\"'],\n      keysPressed: 'di\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'no space between - 6',\n      start: ['\"a\"\"bbb|\"'],\n      keysPressed: 'di\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'no space between - 7',\n      start: ['\"a\"\"bbb\"| '],\n      keysPressed: 'di\"',\n      end: ['\"a\"\"|\" '],\n    });\n    // test next usage\n    newTest({\n      title: 'next: no quotes at all',\n      start: ['|aaabcd'],\n      keysPressed: 'din\"',\n      end: ['|aaabcd'],\n    });\n    newTest({\n      title: 'next: single quote - 1',\n      start: ['|aaa\"b'],\n      keysPressed: 'din\"',\n      end: ['|aaa\"b'],\n    });\n    newTest({\n      title: 'next: single quote - 2',\n      start: ['aaa|\"b'],\n      keysPressed: 'din\"',\n      end: ['aaa|\"b'],\n    });\n    newTest({\n      title: 'next: single quote - 3',\n      start: ['aaa\"|b'],\n      keysPressed: 'din\"',\n      end: ['aaa\"|b'],\n    });\n    newTest({\n      title: 'next: one quotes object - 1',\n      start: ['|aaa \"bbb\" c'],\n      keysPressed: 'din\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'next: one quotes object - 2',\n      start: ['aaa |\"bbb\" c'],\n      keysPressed: 'din\"',\n      end: ['aaa |\"bbb\" c'],\n    });\n    newTest({\n      title: 'next: one quotes object - 3',\n      start: ['aaa \"|bbb\" c'],\n      keysPressed: 'din\"',\n      end: ['aaa \"|bbb\" c'],\n    });\n    newTest({\n      title: 'next: one quotes object - 4',\n      start: ['aaa \"bbb|\" c'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb|\" c'],\n    });\n    newTest({\n      title: 'next: one quotes object - 5',\n      start: ['aaa \"bbb\"| c'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\"| c'],\n    });\n    newTest({\n      title: 'next: even quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c |\"d\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|d\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d|\" e '],\n    });\n    newTest({\n      title: 'next: even quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e '],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d\"| e '],\n    });\n    newTest({\n      title: 'next: odd quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c |\"d\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"|d\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d|\" e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e \" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d\"| e \" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 10',\n      start: ['aaa \"bbb\" c \"d\" e |\" f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d\" e |\" f'],\n    });\n    newTest({\n      title: 'next: odd quotes - 11',\n      start: ['aaa \"bbb\" c \"d\" e \"| f'],\n      keysPressed: 'din\"',\n      end: ['aaa \"bbb\" c \"d\" e \"| f'],\n    });\n    newTest({\n      title: 'next: no space between - 1',\n      start: ['|\"a\"\"bbb\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'next: no space between - 2',\n      start: ['\"|a\"\"bbb\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'next: no space between - 3',\n      start: ['\"a|\"\"bbb\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"|\"'],\n    });\n    newTest({\n      title: 'next: no space between - 4',\n      start: ['\"a\"|\"bbb\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"|\"bbb\"'],\n    });\n    newTest({\n      title: 'next: no space between - 5',\n      start: ['\"a\"\"|bbb\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"|bbb\"'],\n    });\n    newTest({\n      title: 'next: no space between - 6',\n      start: ['\"a\"\"bbb|\"'],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"bbb|\"'],\n    });\n    newTest({\n      title: 'next: no space between - 7',\n      start: ['\"a\"\"bbb\"| '],\n      keysPressed: 'din\"',\n      end: ['\"a\"\"bbb\"| '],\n    });\n    // test last usage\n    newTest({\n      title: 'last: no quotes at all',\n      start: ['|aaabcd'],\n      keysPressed: 'dil\"',\n      end: ['|aaabcd'],\n    });\n    newTest({\n      title: 'last: single quote - 1',\n      start: ['|aaa\"b'],\n      keysPressed: 'dil\"',\n      end: ['|aaa\"b'],\n    });\n    newTest({\n      title: 'last: single quote - 2',\n      start: ['aaa|\"b'],\n      keysPressed: 'dil\"',\n      end: ['aaa|\"b'],\n    });\n    newTest({\n      title: 'last: single quote - 3',\n      start: ['aaa\"|b'],\n      keysPressed: 'dil\"',\n      end: ['aaa\"|b'],\n    });\n    newTest({\n      title: 'last: one quotes object - 1',\n      start: ['|aaa \"bbb\" c'],\n      keysPressed: 'dil\"',\n      end: ['|aaa \"bbb\" c'],\n    });\n    newTest({\n      title: 'last: one quotes object - 2',\n      start: ['aaa |\"bbb\" c'],\n      keysPressed: 'dil\"',\n      end: ['aaa |\"bbb\" c'],\n    });\n    newTest({\n      title: 'last: one quotes object - 3',\n      start: ['aaa \"|bbb\" c'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|bbb\" c'],\n    });\n    newTest({\n      title: 'last: one quotes object - 4',\n      start: ['aaa \"bbb|\" c'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb|\" c'],\n    });\n    newTest({\n      title: 'last: one quotes object - 5',\n      start: ['aaa \"bbb\"| c'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c'],\n    });\n    newTest({\n      title: 'last: even quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['|aaa \"bbb\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa |\"bbb\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|bbb\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e '],\n    });\n    newTest({\n      title: 'last: even quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e '],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb\" c \"|\" e '],\n    });\n    newTest({\n      title: 'last: odd quotes - 1',\n      start: ['|aaa \"bbb\" c \"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['|aaa \"bbb\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 2',\n      start: ['aaa |\"bbb\" c \"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa |\"bbb\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 3',\n      start: ['aaa \"|bbb\" c \"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|bbb\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 4',\n      start: ['aaa \"bbb|\" c \"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 5',\n      start: ['aaa \"bbb\"| c \"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 6',\n      start: ['aaa \"bbb\" c |\"d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 7',\n      start: ['aaa \"bbb\" c \"|d\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 8',\n      start: ['aaa \"bbb\" c \"d|\" e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"|\" c \"d\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 9',\n      start: ['aaa \"bbb\" c \"d\"| e \" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 10',\n      start: ['aaa \"bbb\" c \"d\" e |\" f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'last: odd quotes - 11',\n      start: ['aaa \"bbb\" c \"d\" e \"| f'],\n      keysPressed: 'dil\"',\n      end: ['aaa \"bbb\" c \"|\" e \" f'],\n    });\n    newTest({\n      title: 'last: no space between - 1',\n      start: ['|\"a\"\"bbb\"'],\n      keysPressed: 'dil\"',\n      end: ['|\"a\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 2',\n      start: ['\"|a\"\"bbb\"'],\n      keysPressed: 'dil\"',\n      end: ['\"|a\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 3',\n      start: ['\"a|\"\"bbb\"'],\n      keysPressed: 'dil\"',\n      end: ['\"a|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 4',\n      start: ['\"a\"|\"bbb\"'],\n      keysPressed: 'dil\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 5',\n      start: ['\"a\"\"|bbb\"'],\n      keysPressed: 'dil\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 6',\n      start: ['\"a\"\"bbb|\"'],\n      keysPressed: 'dil\"',\n      end: ['\"|\"\"bbb\"'],\n    });\n    newTest({\n      title: 'last: no space between - 7',\n      start: ['\"a\"\"bbb\"| '],\n      keysPressed: 'dil\"',\n      end: ['\"a\"\"|\" '],\n    });\n  });\n\n  suite('smartQuotes.breakThroughLines = true', () => {\n    suiteSetup(async () => {\n      await setupWorkspace({\n        config: {\n          targets: {\n            enable: true,\n            bracketObjects: { enable: true },\n            smartQuotes: {\n              enable: true,\n              breakThroughLines: true,\n              aIncludesSurroundingSpaces: true,\n            },\n          },\n        },\n        fileExtension: '.js',\n      });\n    });\n\n    // test next\n    newTest({\n      title: 'next: should go next line - 1',\n      start: [\n        'aaa \"bbb\" c', //\n        'd |\"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g \"|\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should go next line - 2',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"|e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g \"|\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should go next line - 3',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e|\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g \"|\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should go next line - 4',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e\"| f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g \"|\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should not go next line - 1',\n      start: [\n        'aaa \"bbb\" c', //\n        'd| \"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"|\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should not go next line - 2',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g |\"h\" k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e\" f', //\n        'g |\"h\" k',\n      ],\n    });\n    newTest({\n      title: 'next: should do nothing - 1',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e|\" f', //\n        'g `h` k',\n      ],\n      keysPressed: 'din\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"e|\" f', //\n        'g `h` k',\n      ],\n    });\n    // test last\n    newTest({\n      title: 'last: should go previous line - 1',\n      start: [\n        'aaa \"bbb\" c', //\n        'd |\"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 2',\n      start: [\n        'aaa \"bbb\" c', //\n        'd| \"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 3',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"|e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 4',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e|\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 5',\n      start: [\n        'aaa \"bbb\" c', //\n        '|\"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        '\"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 6',\n      start: [\n        'aaa \"bbb\" c', //\n        '\"|e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        '\"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should go previous line - 7',\n      start: [\n        'aaa \"bbb\" c', //\n        '\"e|\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        '\"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should not go previous line - 1',\n      start: [\n        'aaa \"bbb\" c', //\n        'd \"e\"| f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"bbb\" c', //\n        'd \"|\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should not go previous line - 2',\n      start: [\n        'aaa \"bbb\" |c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa \"|\" c', //\n        'd \"e\" f', //\n        'g \"h\" k',\n      ],\n    });\n    newTest({\n      title: 'last: should do nothing - 1',\n      start: [\n        'aaa `b` c', //\n        'd \"e|\" f', //\n        'g \"h\" k',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        'aaa `b` c', //\n        'd \"e|\" f', //\n        'g \"h\" k',\n      ],\n    });\n\n    // test case after - bug fix\n    newTest({\n      title: 'last: long string cursor after',\n      start: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"property ${properyName} is not defined\";          |   ',\n        ' this.message = message `property ${properyName} is not defined`; ',\n        ' } ',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"|\";             ',\n        ' this.message = message `property ${properyName} is not defined`; ',\n        ' } ',\n      ],\n    });\n    newTest({\n      title: 'last: long string cursor after, next line - 1',\n      start: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"property ${properyName} is not defined\";             ',\n        '| this.message = message `property ${properyName} is not defined`; ',\n        ' } ',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"|\";             ',\n        ' this.message = message `property ${properyName} is not defined`; ',\n        ' } ',\n      ],\n    });\n    newTest({\n      title: 'last: long string cursor after, next line - 2',\n      start: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"property ${properyName} is not defined\";             ',\n        ' this.message = message `property ${properyName} is not defined|`; ',\n        ' } ',\n      ],\n      keysPressed: 'dil\"',\n      end: [\n        ' function MissingProperty(properyName) { ',\n        ' this.name = \"|\";             ',\n        ' this.message = message `property ${properyName} is not defined`; ',\n        ' } ',\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "test/plugins/sneak.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nsuite('sneak plugin', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { sneak: true } });\n  });\n\n  newTest({\n    title: 'Can handle s motion',\n    start: ['|abc abc'],\n    keysPressed: 'sab',\n    end: ['abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle S motion',\n    start: ['abc |abc'],\n    keysPressed: 'Sab',\n    end: ['|abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle <operator>z motion',\n    start: ['|abc abc'],\n    keysPressed: 'dzab',\n    end: ['|abc'],\n  });\n\n  newTest({\n    title: 'Can handle <operator>Z motion',\n    start: ['abc |abc'],\n    keysPressed: 'dZab',\n    end: ['|abc'],\n  });\n\n  newTest({\n    title: 'Can handle s; motion',\n    start: ['|abc abc abc'],\n    keysPressed: 'sab;',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle s, motion',\n    start: ['abc abc| abc'],\n    keysPressed: 'sab,',\n    end: ['abc |abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle S; motion',\n    start: ['abc abc |abc'],\n    keysPressed: 'Sab;',\n    end: ['|abc abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle S, motion',\n    start: ['abc abc| abc'],\n    keysPressed: 'Sab,',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter s motion',\n    start: ['|abc abc'],\n    keysPressed: 'sa\\n',\n    end: ['abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter S motion',\n    start: ['abc |abc'],\n    keysPressed: 'Sa\\n',\n    end: ['|abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter <operator>z motion',\n    start: ['|abc abc'],\n    keysPressed: 'dza\\n',\n    end: ['|abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter <operator>Z motion',\n    start: ['abc |abc'],\n    keysPressed: 'dZa\\n',\n    end: ['|abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter s; motion',\n    start: ['|abc abc abc'],\n    keysPressed: 'sa\\n;',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter s, motion',\n    start: ['abc abc| abc'],\n    keysPressed: 'sa\\n,',\n    end: ['abc |abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter S; motion',\n    start: ['abc abc |abc'],\n    keysPressed: 'Sa\\n;',\n    end: ['|abc abc abc'],\n  });\n\n  newTest({\n    title: 'Can handle single letter S, motion',\n    start: ['abc abc| abc'],\n    keysPressed: 'Sa\\n,',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can handle multiline single char <number>s motion',\n    start: ['|abc', 'aac', 'abc'],\n    keysPressed: '3sa\\n',\n    end: ['abc', 'aac', '|abc'],\n  });\n\n  newTest({\n    title: 'Can go back using <C-o> once when going forward',\n    start: ['|abc abc'],\n    keysPressed: 'sab<C-o>',\n    end: ['|abc abc'],\n  });\n\n  newTest({\n    title: 'Can go back using <C-o> once when going backward',\n    start: ['abc |abc'],\n    keysPressed: 'Sab<C-o>',\n    end: ['abc |abc'],\n  });\n\n  newTest({\n    title: 'Can go back using <C-o> when repeating forward movement',\n    start: ['|abc abc abc'],\n    keysPressed: 'sab;<C-o>',\n    end: ['|abc abc abc'],\n  });\n\n  newTest({\n    title: 'Can go back using <C-o> when repeating backward movement',\n    start: ['abc abc |abc'],\n    keysPressed: 'sab;<C-o>',\n    end: ['abc abc |abc'],\n  });\n});\n\nsuite('sneakReplacesF', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ config: { sneak: true, sneakReplacesF: true } });\n  });\n\n  newTest({\n    title: 'sneakReplacesF forward',\n    start: ['|apple', 'banana', 'carrot'],\n    keysPressed: 'fa;',\n    end: ['apple', 'ban|ana', 'carrot'],\n  });\n\n  newTest({\n    title: 'sneakReplacesF backward',\n    start: ['apple', 'banana', '|carrot'],\n    keysPressed: 'Fa;',\n    end: ['apple', 'ban|ana', 'carrot'],\n  });\n});\n"
  },
  {
    "path": "test/plugins/surround.test.ts",
    "content": "import {\n  CommandSurroundAddSurroundingFunction,\n  CommandSurroundAddSurroundingTag,\n} from '../../src/actions/plugins/surround';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from './../testUtils';\n\nsuite('surround plugin', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        surround: true,\n      },\n      fileExtension: '.js',\n    });\n  });\n\n  newTest({\n    title: \"'ysiw)' surrounds word without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw)',\n    end: ['first |(line) test'],\n  });\n\n  newTest({\n    title: \"'ysiw(' surrounds word with space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw(',\n    end: ['first |( line ) test'],\n  });\n\n  newTest({\n    title: \"'ysw)' surrounds word without space\",\n    start: ['first |line test'],\n    keysPressed: 'ysw)',\n    end: ['first |(line) test'],\n  });\n\n  newTest({\n    title: \"'ysw)' surrounds word, two spaces next word\",\n    start: ['first |line  test'],\n    keysPressed: 'ysw)',\n    end: ['first |(line)  test'],\n  });\n\n  newTest({\n    title: \"'ysw)' surrounds word, tab to next word\",\n    start: ['first |line\\ttest'],\n    keysPressed: 'ysw)',\n    end: ['first |(line)\\ttest'],\n  });\n\n  newTest({\n    title: \"'ysw(' surrounds word with space\",\n    start: ['first |line test'],\n    keysPressed: 'ysw(',\n    end: ['first |( line ) test'],\n  });\n\n  newTest({\n    title: \"'ysw)' surrounds word at EOL\",\n    start: ['one two |three', 'four five six'],\n    keysPressed: 'ysw)',\n    end: ['one two |(three)', 'four five six'],\n  });\n\n  newTest({\n    title: \"'ysaw)' surrounds word without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysaw)',\n    end: ['first |(line) test'],\n  });\n\n  newTest({\n    title: \"'ysaw(' surrounds word with space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysaw(',\n    end: ['first |( line ) test'],\n  });\n\n  newTest({\n    title: \"'ysiw(' surrounds word with space and ignores punctuation\",\n    start: ['first li|ne.test'],\n    keysPressed: 'ysiw(',\n    end: ['first |( line ).test'],\n  });\n\n  newTest({\n    title: 'add surround with repeat',\n    start: ['o|ne two three'],\n    keysPressed: 'ysiw\"Ww.',\n    end: ['\"one\" two |\"three\"'],\n  });\n\n  newTest({\n    title: \"'ysiw<' surrounds word with tags\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw<',\n    end: ['first |<123>line</123> test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: '123',\n    },\n  });\n\n  newTest({\n    title: 'surround word with tag and repeat',\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw<W.',\n    end: ['first <123>line</123> |<123>test</123>'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: '123',\n    },\n  });\n\n  newTest({\n    title: \"'cst<' surrounds word with tags that have a dot in them\",\n    start: ['first <test>li|ne</test> test'],\n    keysPressed: 'cst<',\n    end: ['first <abc.def>li|ne</abc.def> test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'abc.def',\n    },\n  });\n\n  newTest({\n    title: \"'ysiwf' surrounds word with function\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwf',\n    end: ['first |print(line) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: 'surround word with function and repeat',\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwfW.',\n    end: ['first print(line) |print(test)'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: \"'ysiwF' surrounds word with function with space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwF',\n    end: ['first |print( line ) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: 'surround word with function with space and repeat',\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwFWWW.',\n    end: ['first print( line ) |print( test )'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: \"'ysiw<C-f>' surrounds word and function with parentheses\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw<C-f>',\n    end: ['first |(print line) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: 'surround word and function with pathentheses and repeat',\n    start: ['first li|ne test'],\n    keysPressed: 'ysiw<C-f>WW.',\n    end: ['first (print line) |(print test)'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'print',\n    },\n  });\n\n  newTest({\n    title: \"'yss)' surrounds entire line respecting whitespace\",\n    start: ['foo', '    foob|ar  '],\n    keysPressed: 'yss)',\n    end: ['foo', '    |(foobar)  '],\n  });\n\n  newTest({\n    title: 'change surround',\n    start: [\"first 'li|ne' test\"],\n    keysPressed: \"cs')\",\n    end: ['first (li|ne) test'],\n  });\n\n  newTest({\n    title: 'change surround with two pairs of quotes',\n    start: [\"first ''li|ne'' test\"],\n    keysPressed: \"cs')\",\n    end: [\"first '(li|ne)' test\"],\n  });\n\n  newTest({\n    title: 'change surround with two pairs of parens',\n    start: ['first ((li|ne)) test'],\n    keysPressed: \"cs)'\",\n    end: [\"first ('li|ne') test\"],\n  });\n\n  newTest({\n    title: 'change surround with alias',\n    start: ['first (li|ne) test'],\n    keysPressed: 'csb]',\n    end: ['first [li|ne] test'],\n  });\n\n  newTest({\n    title: 'change surround with cursor before quotes',\n    start: ['one |two \"three\" four'],\n    keysPressed: 'cs\")',\n    end: ['one two |(three) four'],\n  });\n\n  newTest({\n    title: \"'ysiwb' surrounds word with alias without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwb',\n    end: ['first |(line) test'],\n  });\n\n  newTest({\n    title: \"'ysiwB' surrounds word with alias without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwB',\n    end: ['first |{line} test'],\n  });\n\n  newTest({\n    title: \"'ysiwr' surrounds word with alias without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwr',\n    end: ['first |[line] test'],\n  });\n\n  newTest({\n    title: \"'ysiwa' surrounds word with alias without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'ysiwa',\n    end: ['first |<line> test'],\n  });\n\n  newTest({\n    title: 'change surround to tags',\n    start: ['first [li|ne] test'],\n    keysPressed: 'cs]t',\n    end: ['first <abc>li|ne</abc> test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'abc',\n    },\n  });\n\n  newTest({\n    title: 'change surround to function',\n    start: ['first {li|ne} test'],\n    keysPressed: 'cs}f',\n    end: ['first hello(li|ne) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'hello',\n    },\n  });\n\n  newTest({\n    title: 'change surround to function with space',\n    start: ['first (li|ne) test'],\n    keysPressed: 'cs)F',\n    end: ['first hello( li|ne ) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'hello',\n    },\n  });\n\n  newTest({\n    title: 'change surround to function surrounded by parentheses',\n    start: ['first <tag>li|ne</tag> test'],\n    keysPressed: 'cst<C-f>',\n    end: ['first (hello li|ne) test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'hello',\n    },\n  });\n\n  newTest({\n    title: 'delete surround',\n    start: [\"first 'li|ne' test\"],\n    keysPressed: \"ds'\",\n    end: ['first li|ne test'],\n  });\n\n  newTest({\n    title: 'delete surround with quotes',\n    start: ['first \"li|ne\" test'],\n    keysPressed: 'ds\"',\n    end: ['first li|ne test'],\n  });\n\n  newTest({\n    title: 'delete surround with nested of quotes',\n    start: ['first \"\"li|ne\"\" test'],\n    keysPressed: 'ds\"',\n    end: ['first \"li|ne\" test'],\n  });\n\n  newTest({\n    title: 'delete surround with inconsistent quotes',\n    start: ['first \"\"li|ne\" test'],\n    keysPressed: 'ds\"',\n    end: ['first \"li|ne test'],\n  });\n\n  newTest({\n    title: 'delete surround with mixed quotes',\n    start: ['first \"\\'li|ne\"\\' test'],\n    keysPressed: 'ds\"',\n    end: [\"first 'li|ne' test\"],\n  });\n\n  newTest({\n    title: 'delete surround with empty quotes cursor at start',\n    start: ['first |\"\" test'],\n    keysPressed: 'ds\"',\n    end: ['first | test'],\n  });\n\n  newTest({\n    title: 'delete surround with empty quotes cursor at end',\n    start: ['first \"|\" test'],\n    keysPressed: 'ds\"',\n    end: ['first | test'],\n  });\n\n  newTest({\n    title: \"don't delete surround if cursor is after closing match\",\n    start: ['first \"line\"| test'],\n    keysPressed: 'ds\"',\n    end: ['first \"line\"| test'],\n  });\n\n  newTest({\n    title: 'delete surround if cursor is before opening match',\n    start: ['first | \"line\" test'],\n    keysPressed: 'ds\"',\n    end: ['first  |line test'],\n  });\n\n  newTest({\n    title: 'delete surround with two pairs of parens',\n    start: ['first ((li|ne)) test'],\n    keysPressed: 'ds)',\n    end: ['first (li|ne) test'],\n  });\n\n  newTest({\n    title: 'delete outer surround with count',\n    start: ['(first (li|ne) test)'],\n    keysPressed: '2ds)',\n    end: ['first (li|ne) test'],\n  });\n\n  newTest({\n    title: 'delete surround with alias',\n    start: ['first {li|ne} test'],\n    keysPressed: 'dsB',\n    end: ['first li|ne test'],\n  });\n\n  newTest({\n    title: 'delete surround with repeat',\n    start: ['( |one ) two ( three )'],\n    keysPressed: 'ds(ww.',\n    end: ['one two |three'],\n  });\n\n  newTest({\n    title: 'delete surround with no space',\n    start: ['(on|e) two'],\n    keysPressed: 'ds(',\n    end: ['on|e two'],\n  });\n\n  newTest({\n    title: 'delete surround with tags',\n    start: ['first <test>li|ne</test> test'],\n    keysPressed: 'dst',\n    end: ['first li|ne test'],\n  });\n\n  newTest({\n    title: 'change outer surround with count',\n    start: ['(first (li|ne) test)'],\n    keysPressed: 'cs2)[',\n    end: ['[ first (li|ne) test ]'],\n  });\n\n  newTest({\n    title: 'change surround brackets at end of line',\n    start: ['func() |{', '}'],\n    keysPressed: 'cs{]',\n    end: ['func() |[', ']'],\n  });\n\n  newTest({\n    title: 'changing brackets with surround works again',\n    start: ['func() {', '    |foo()', '}'],\n    keysPressed: 'cs{[',\n    end: ['func() [ ', '    |foo()', ' ]'],\n  });\n\n  newTest({\n    title: 'change surround with tags that contain an attribute and preserve them',\n    start: ['<h2 test class=\"foo\">b|ar</h2>'],\n    keysPressed: 'cstt',\n    end: ['<h3 test class=\"foo\">b|ar</h3>'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'h3',\n    },\n  });\n\n  newTest({\n    title: 'change surround with tags with kebab case names',\n    start: ['<custom-tag>|</custom-tag>'],\n    keysPressed: 'cstt',\n    end: ['<h1>|</h1>'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'h1',\n    },\n  });\n\n  newTest({\n    title: 'change surround with tags that contain an attribute and remove them',\n    start: ['<h2 test class=\"foo\">b|ar</h2>'],\n    keysPressed: 'cstt',\n    end: ['<h3>b|ar</h3>'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'h3>',\n    },\n  });\n\n  newTest({\n    title:\n      'performing surround after cancelling surround action with <Esc> does not move the cursor',\n    start: ['foo b|ar'],\n    keysPressed: 'ys<Esc>ys',\n    end: ['foo b|ar'],\n  });\n\n  // Visual mode tests\n\n  newTest({\n    title: \"'S)' surrounds visual selection without space\",\n    start: ['first li|ne test'],\n    keysPressed: 'viwS)',\n    end: ['first (l|ine) test'],\n  });\n\n  newTest({\n    title: \"'S(' surrounds visual selection with space\",\n    start: ['first li|ne test'],\n    keysPressed: 'viwS(',\n    end: ['first ( |line ) test'],\n  });\n\n  newTest({\n    title: \"'S<div>' surrounds selection with <div></div>\",\n    start: ['first li|ne test'],\n    // I've added the '0' key press at the end because the test was behaving weirdly, if I ran\n    // the extension and did this test manually the cursor would end up on the first '<'. But\n    // when running the tests this test was failing saying the cursor was actually on the\n    // second '<' (character 15) instead of the first (character 6) as expected.\n    keysPressed: 'viwS<0',\n    end: ['|first <div>line</div> test'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'div',\n    },\n  });\n\n  newTest({\n    title: \"'VStt' surrounds selection and correctly trims class attribute in closing tag\",\n    start: ['first li|ne test'],\n    keysPressed: 'VStt',\n    end: ['<div class=\"test\">', 'first line test', '|</div>'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'div class=\"test\"',\n    },\n  });\n\n  newTest({\n    title: \"'S)' surrounds visual line selection without space\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VS)',\n    end: ['first', '(', 'second', '|)', 'third'],\n  });\n\n  newTest({\n    title: \"'S(' surrounds visual line selection with space\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VS(',\n    end: ['first', '( ', 'second', '| )', 'third'],\n  });\n\n  newTest({\n    title: \"'S<div>' surrounds visual line selection with <div></div>\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VS<',\n    end: ['first', '<div>', 'second', '|</div>', 'third'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingTag,\n      methodName: 'readTag',\n      returnValue: 'div',\n    },\n  });\n\n  newTest({\n    title: \"'Sfcall' surrounds visual line selection with call()\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VSf',\n    end: ['first', 'call(', 'second', '|)', 'third'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'call',\n    },\n  });\n\n  newTest({\n    title: \"'SFcall' surrounds visual line selection with call(  )\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VSF',\n    end: ['first', 'call( ', 'second', '| )', 'third'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'call',\n    },\n  });\n\n  newTest({\n    title: \"'S<C-f>call' surrounds visual line selection with (call )\",\n    start: ['first', 'sec|ond', 'third'],\n    keysPressed: 'VS<C-f>',\n    end: ['first', '(call ', 'second', '|)', 'third'],\n    stub: {\n      stubClass: CommandSurroundAddSurroundingFunction,\n      methodName: 'readFunction',\n      returnValue: 'call',\n    },\n  });\n\n  suite('multicursor', () => {\n    newTest({\n      title: 'visual surround with multicursor',\n      start: ['one |two three, one two three'],\n      keysPressed: 'gbgb' + 'S)' + '<esc>' + '0', // 0: fix cursor pos, see above\n      end: ['|one (two) three, one (two) three'],\n    });\n\n    newTest({\n      title: 'yank surround with multicursor',\n      start: ['one |two three, one two three'],\n      // gbgbv results in two cursors in normal mode\n      keysPressed: 'gbgbv' + 'ysiw)' + '<esc>',\n      end: ['one |(two) three, one |(two) three'],\n    });\n\n    newTest({\n      title: 'yank surround with multicursor and repeat',\n      start: ['one |two three, one two three'],\n      keysPressed: 'gbgbv' + 'ysiw)' + 'W.' + '<esc>',\n      end: ['one (two) |(three), one (two) |(three)'],\n    });\n\n    newTest({\n      title: 'delete surround with multicursor',\n      start: ['one (tw|o) three, one (two) three'],\n      keysPressed: 'gbgbv' + 'ds)' + '<esc>',\n      end: ['one tw|o three, one tw|o three'],\n    });\n\n    newTest({\n      title: 'delete surround with multicursor and repeat',\n      start: ['one (tw|o) (three), one (two) (three)'],\n      keysPressed: 'gbgbv' + 'ds)' + 'W.' + '<esc>',\n      end: ['one two |three, one two |three'],\n    });\n\n    newTest({\n      title: 'change surround with multicursor',\n      start: ['one (tw|o) three, one (two) three'],\n      keysPressed: 'gbgbv' + 'cs)[' + '<esc>',\n      end: ['one [ tw|o ] three, one [ tw|o ] three'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/register/register.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\n\nimport { getAndUpdateModeHandler } from '../../extension';\nimport { EasyMotion } from '../../src/actions/plugins/easymotion/easymotion';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { Register } from '../../src/register/register';\nimport { RecordedState } from '../../src/state/recordedState';\nimport { VimState } from '../../src/state/vimState';\nimport { Clipboard } from '../../src/util/clipboard';\nimport { newTest } from '../testSimplifier';\nimport { assertEqualLines, setupWorkspace } from '../testUtils';\n\nsuite('register', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  suite('clipboard', () => {\n    setup(async () => {\n      await Clipboard.Copy('12345');\n    });\n\n    newTest({\n      title: \"Can access '*' (clipboard) register\",\n      start: ['|one'],\n      keysPressed: '\"*P',\n      end: ['1234|5one'],\n    });\n\n    newTest({\n      title: \"Can access '+' (clipboard) register\",\n      start: ['|one'],\n      keysPressed: '\"+P',\n      end: ['1234|5one'],\n    });\n  });\n\n  newTest({\n    title: 'Can copy to a register',\n    start: ['|one', 'two'],\n    keysPressed: '\"add\"ap',\n    end: ['two', '|one'],\n  });\n\n  newTest({\n    title: 'Can use two registers together',\n    start: ['|one', 'two'],\n    keysPressed: '\"ayyj\"byy\"ap\"bp',\n    end: ['one', 'two', 'one', '|two'],\n  });\n\n  newTest({\n    title: 'Can use black hole register',\n    start: ['|asdf', 'qwer'],\n    keysPressed: 'yyj\"_ddkp',\n    end: ['asdf', '|asdf'],\n  });\n\n  test('System clipboard works with chinese characters', async () => {\n    const testString = '你好';\n    await Clipboard.Copy(testString);\n    assert.strictEqual(testString, await Clipboard.Paste());\n\n    modeHandler.vimState.editor = vscode.window.activeTextEditor!;\n\n    // Paste from our paste handler\n    await modeHandler.handleMultipleKeyEvents(['<Esc>', '\"', '*', 'P', 'a']);\n    assertEqualLines([testString]);\n\n    // Now try the built in vscode paste\n    await vscode.commands.executeCommand('editor.action.clipboardPasteAction');\n\n    // TODO: Not sure why this sleep should be necessary\n    await new Promise((resolve) => {\n      setTimeout(resolve, 100);\n    });\n\n    assertEqualLines([testString + testString]);\n  });\n\n  newTest({\n    title: \"Yank stores text in Register '0'\",\n    start: ['|test1', 'test2', 'test3'],\n    keysPressed: 'yy' + 'j' + 'yy' + 'gg' + 'dd' + '\"0P',\n    end: ['|test2', 'test2', 'test3'],\n  });\n\n  newTest({\n    title: \"Multiline yank (`[count]yy`) stores text in Register '0'\",\n    start: ['|test1', 'test2', 'test3'],\n    keysPressed: '2yy' + 'dd' + '\"0P',\n    end: ['|test1', 'test2', 'test2', 'test3'],\n  });\n\n  newTest({\n    title: \"Multiline yank (`[count]Y`) stores text in Register '0'\",\n    start: ['|test1', 'test2', 'test3'],\n    keysPressed: '2Y' + 'dd' + '\"0P',\n    end: ['|test1', 'test2', 'test2', 'test3'],\n  });\n\n  newTest({\n    title: \"Register '1'-'9' stores delete content\",\n    start: ['|test1', 'test2', 'test3', ''],\n    keysPressed: 'dd' + 'dd' + 'dd' + '\"1p' + '\"2p' + '\"3p',\n    end: ['', 'test3', 'test2', '|test1'],\n  });\n\n  newTest({\n    title: '\"A appends linewise text to \"a',\n    start: ['|test1', 'test2', 'test3'],\n    keysPressed: 'vll' + '\"ay' + 'jV' + '\"Ay' + 'j\"ap',\n    end: ['test1', 'test2', 'test3', '|tes', 'test2'],\n  });\n\n  newTest({\n    title: '\"A appends character wise text to \"a',\n    start: ['|test1', 'test2', ''],\n    keysPressed: 've' + '\"ay' + 'jve' + '\"Ay' + 'j\"ap',\n    end: ['test1', 'test2', 'test1test|2'],\n  });\n\n  test('Can put and get to register', async () => {\n    const expected = 'text-to-put-on-register';\n    const vimState = new VimState(vscode.window.activeTextEditor!, new EasyMotion());\n    await vimState.load();\n    vimState.recordedState.registerName = '0';\n\n    try {\n      Register.put(vimState, expected);\n      const actual = await Register.get(vimState.recordedState.registerName);\n      assert.strictEqual(actual?.text, expected);\n    } catch (err) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n      assert.fail(err);\n    }\n  });\n\n  suite('Small delete register (-)', () => {\n    newTest({\n      title: '`x` sets small delete register',\n      start: ['|test1', 'test2', 'test3'],\n      keysPressed: '2x' + 'j' + '\"-p',\n      end: ['st1', 'tt|eest2', 'test3'],\n    });\n\n    newTest({\n      title: '`Del` sets small delete register',\n      start: ['|test1', 'test2', 'test3'],\n      keysPressed: '<Del>' + 'j' + '\"-p',\n      end: ['est1', 't|test2', 'test3'],\n    });\n\n    newTest({\n      title: '`X` sets small delete register',\n      start: ['te|st1', 'test2', 'test3'],\n      keysPressed: '2X' + 'j' + '\"-p',\n      end: ['st1', 'tt|eest2', 'test3'],\n    });\n\n    newTest({\n      title: '`d$` sets small delete register',\n      start: ['te|st1', 'test2', 'test3'],\n      keysPressed: 'd$',\n      end: ['t|e', 'test2', 'test3'],\n      registers: {\n        '-': 'st1',\n      },\n    });\n\n    newTest({\n      title: '`dd` does not set small delete register',\n      start: ['te|st1', 'test2', 'test3'],\n      keysPressed: 'dd',\n      end: ['|test2', 'test3'],\n      registers: {\n        '-': undefined,\n      },\n    });\n\n    newTest({\n      title: 'Small delete register is not set when another register is specified',\n      start: ['|test1', 'test2', 'test3'],\n      keysPressed: '\"xD',\n      end: ['|', 'test2', 'test3'],\n      registers: {\n        '-': undefined,\n      },\n    });\n  });\n\n  test('Search register (/) is set by forward search', async () => {\n    await modeHandler.handleMultipleKeyEvents(\n      'iWake up early in Karakatu, Alaska'.split('').concat(['<Esc>', '0']),\n    );\n\n    // Register changed by forward search\n    await modeHandler.handleMultipleKeyEvents('/katu\\n'.split(''));\n    assert.strictEqual((await Register.get('/'))?.text, 'katu');\n\n    // Register changed even if search doesn't exist\n    await modeHandler.handleMultipleKeyEvents('0/notthere\\n'.split(''));\n    assert.strictEqual((await Register.get('/'))?.text, 'notthere');\n\n    // Not changed if search is canceled\n    await modeHandler.handleMultipleKeyEvents('0/Alaska'.split('').concat(['<Esc>']));\n    assert.strictEqual((await Register.get('/'))?.text, 'notthere');\n  });\n\n  test('Search register (/) is set by backward search', async () => {\n    await modeHandler.handleMultipleKeyEvents(\n      'iWake up early in Karakatu, Alaska'.split('').concat(['<Esc>', '$']),\n    );\n\n    // Register changed by forward search\n    await modeHandler.handleMultipleKeyEvents('?katu\\n'.split(''));\n    assert.strictEqual((await Register.get('/'))?.text, 'katu');\n\n    // Register changed even if search doesn't exist\n    await modeHandler.handleMultipleKeyEvents('$?notthere\\n'.split(''));\n    assert.strictEqual((await Register.get('/'))?.text, 'notthere');\n\n    // Not changed if search is canceled\n    await modeHandler.handleMultipleKeyEvents('$?Alaska'.split('').concat(['<Esc>']));\n    assert.strictEqual((await Register.get('/'))?.text, 'notthere');\n  });\n\n  test('Search register (/) is set by star search', async () => {\n    await modeHandler.handleMultipleKeyEvents(\n      'iWake up early in Karakatu, Alaska'.split('').concat(['<Esc>', '0']),\n    );\n\n    await modeHandler.handleKeyEvent('*');\n    assert.strictEqual((await Register.get('/'))?.text, '\\\\<Wake\\\\>');\n\n    await modeHandler.handleMultipleKeyEvents(['g', '*']);\n    assert.strictEqual((await Register.get('/'))?.text, 'Wake');\n\n    await modeHandler.handleKeyEvent('#');\n    assert.strictEqual((await Register.get('/'))?.text, '\\\\<Wake\\\\>');\n\n    await modeHandler.handleMultipleKeyEvents(['g', '#']);\n    assert.strictEqual((await Register.get('/'))?.text, 'Wake');\n  });\n\n  test('Command register (:) is set by command line', async () => {\n    const command = '%s/old/new/g';\n    await modeHandler.handleMultipleKeyEvents((':' + command + '\\n').split(''));\n\n    // :reg should not update the command register\n    await modeHandler.handleMultipleKeyEvents(':reg\\n'.split(''));\n\n    const regStr = ((await Register.get(':'))?.text as RecordedState).commandString;\n    assert.strictEqual(regStr, command);\n  });\n\n  test('Read-only registers cannot be written to', async () => {\n    await modeHandler.handleMultipleKeyEvents('iShould not be copied'.split('').concat(['<Esc>']));\n\n    Register.setReadonlyRegister('/', 'Expected for /');\n    Register.setReadonlyRegister('.', 'Expected for .');\n    Register.setReadonlyRegister('%', 'Expected for %');\n    Register.setReadonlyRegister(':', 'Expected for :');\n\n    await modeHandler.handleMultipleKeyEvents('\"/yy'.split(''));\n    await modeHandler.handleMultipleKeyEvents('\".yy'.split(''));\n    await modeHandler.handleMultipleKeyEvents('\"%yy'.split(''));\n    await modeHandler.handleMultipleKeyEvents('\":yy'.split(''));\n\n    assert.strictEqual((await Register.get('/'))?.text, 'Expected for /');\n    assert.strictEqual((await Register.get('.'))?.text, 'Expected for .');\n    assert.strictEqual((await Register.get('%'))?.text, 'Expected for %');\n    assert.strictEqual((await Register.get(':'))?.text, 'Expected for :');\n  });\n});\n"
  },
  {
    "path": "test/register/repeatableMovement.test.ts",
    "content": "import { newTest } from '../testSimplifier';\n\nsuite('Repeatable movements with f and t', () => {\n  newTest({\n    title: 'Can repeat f<character>',\n    start: ['|abc abc abc'],\n    keysPressed: 'fa;',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can repeat reversed F<character>',\n    start: ['|abc abc abc'],\n    keysPressed: 'fa$,',\n    end: ['abc abc |abc'],\n  });\n\n  newTest({\n    title: 'Can repeat t<character>',\n    start: ['|abc abc abc'],\n    keysPressed: 'tc;',\n    end: ['abc a|bc abc'],\n  });\n\n  newTest({\n    title: 'Can repeat N times reversed t<character>',\n    start: ['|abc abc abc abc'],\n    keysPressed: 'tc$3,',\n    end: ['abc| abc abc abc'],\n  });\n});\n"
  },
  {
    "path": "test/runTest.ts",
    "content": "import * as path from 'path';\n\nimport { runTests } from '@vscode/test-electron';\n\nasync function main() {\n  try {\n    // The folder containing the Extension Manifest package.json\n    // Passed to `--extensionDevelopmentPath`\n    const extensionDevelopmentPath = path.resolve(__dirname, '../../');\n\n    // The path to the extension test runner script\n    // Passed to --extensionTestsPath\n    const extensionTestsPath = path.resolve(__dirname, './index');\n\n    // Download VS Code, unzip it and run the integration test\n    await runTests({\n      extensionDevelopmentPath,\n      extensionTestsPath,\n      // Disable other extensions while running tests for avoiding unexpected side-effect\n      launchArgs: ['--disable-extensions'],\n    });\n  } catch (err) {\n    console.error(err);\n    console.error('Failed to run tests');\n    process.exit(1);\n  }\n}\n\nvoid main();\n"
  },
  {
    "path": "test/search/motionIncSearch.test.ts",
    "content": "import { newTest } from '../testSimplifier';\nimport { cleanUpWorkspace, setupWorkspace } from '../testUtils';\n\nsuite('incsearch motion', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({\n      config: {\n        wrapscan: true,\n        incsearch: true,\n      },\n    });\n  });\n  suiteTeardown(cleanUpWorkspace);\n\n  suite('<C-g>', () => {\n    newTest({\n      title: '<C-g> advances current match forward during forward search.',\n      start: ['|one two two two'],\n      keysPressed: '/two<C-g>\\n',\n      end: ['one two |two two'],\n    });\n\n    newTest({\n      title: '<C-g> advances current match forward during backward search.',\n      start: ['one two |two two'],\n      keysPressed: '?two<C-g>\\n',\n      end: ['one two |two two'],\n    });\n\n    newTest({\n      title: '<C-g> advances current match forward by search count',\n      start: ['|one two two two two two two'],\n      keysPressed: '3/tw<C-g>\\n',\n      end: ['one two two two two two |two'],\n    });\n\n    newTest({\n      title: '<C-g> wraps to top of buffer when wrapscan is true',\n      start: ['|one two two two'],\n      keysPressed: '/tw<C-g><C-g><C-g>\\n',\n      end: ['one |two two two'],\n    });\n\n    newTest({\n      title: '<C-g> stops at last match in buffer when wrapscan is false',\n      config: { wrapscan: false },\n      start: ['|one two two two'],\n      keysPressed: '/tw<C-g><C-g><C-g>\\n',\n      end: ['one two two |two'],\n    });\n\n    newTest({\n      title:\n        '<C-g> during search with count stops at last reachable match in buffer when wrapscan is false',\n      config: { wrapscan: false },\n      start: ['|one two two two'],\n      keysPressed: '2/tw<C-g><C-g>\\n',\n      end: ['one two |two two'],\n    });\n  });\n\n  suite('<C-t>', () => {\n    newTest({\n      title: '<C-t>  moves backward during forward search.',\n      start: ['one |two two two'],\n      keysPressed: '/two<C-t>\\n',\n      end: ['one |two two two'],\n    });\n\n    newTest({\n      title: '<C-t> moves backward during backward search.',\n      start: ['one two two |two'],\n      keysPressed: '?two<C-t>\\n',\n      end: ['one |two two two'],\n    });\n\n    newTest({\n      title: '<C-t> wraps to last match in buffer with wrapscan',\n      start: ['one two |two two'],\n      keysPressed: '/tw<C-t><C-t><C-t>\\n',\n      end: ['one two two |two'],\n    });\n\n    newTest({\n      title: '<C-t> stays at first match in buffer with nowrapscan',\n      config: { wrapscan: false },\n      start: ['one two |two two'],\n      keysPressed: '/tw<C-t><C-t><C-t>\\n',\n      end: ['one |two two two'],\n    });\n\n    newTest({\n      title:\n        '<C-t> during search with count stays at first reachable match in buffer with nowrapscan',\n      config: { wrapscan: false },\n      start: ['one two |two two'],\n      keysPressed: '2/tw<C-t><C-t>\\n',\n      end: ['one two |two two'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/search/search.test.ts",
    "content": "import { Mode } from '../../src/mode/mode';\nimport { newTest } from '../testSimplifier';\n\nsuite('Search (/ and ?)', () => {\n  newTest({\n    title: '/ does not affect mark',\n    start: ['|one', 'twooo', 'thurr'],\n    keysPressed: \"ma/two\\n'a\",\n    end: ['|one', 'twooo', 'thurr'],\n  });\n\n  newTest({\n    title: '/ can search with regex',\n    start: ['|', 'one two2o'],\n    keysPressed: '/o\\\\do\\n',\n    end: ['', 'one tw|o2o'],\n  });\n\n  newTest({\n    title: '/ can search with newline',\n    start: ['|asdf', '__asdf', 'asdf'],\n    keysPressed: '/\\\\nasdf\\n',\n    end: ['asdf', '__asd|f', 'asdf'],\n  });\n\n  newTest({\n    title: '/ can search through multiple newlines',\n    start: ['|asdf', '__asdf', 'asdf', 'abc', '   abc'],\n    keysPressed: '/asdf\\\\nasdf\\\\nabc\\n',\n    end: ['asdf', '__|asdf', 'asdf', 'abc', '   abc'],\n  });\n\n  newTest({\n    title: '/ with noignorecase, nosmartcase',\n    config: { ignorecase: false, smartcase: false },\n    start: ['bl|ah', 'blAh', 'BLAH', 'blah'],\n    keysPressed: '/blah\\n',\n    end: ['blah', 'blAh', 'BLAH', '|blah'],\n  });\n\n  newTest({\n    title: '/ with \\\\%V will search in last selection',\n    start: ['', 'asdf', '|asdf', 'asdf', 'asdf'],\n    keysPressed: 'vjj<Esc>gg/\\\\%Vasdf\\n',\n    end: ['', 'asdf', '|asdf', 'asdf', 'asdf'],\n  });\n\n  newTest({\n    title: '/ with \\\\%V will search in last selection, starting from the cursor postion',\n    start: ['', 'asdf', '|asdf', '', 'asdf', 'asdf'],\n    keysPressed: 'vjjj<Esc>kk/\\\\%Vasdf\\nn',\n    end: ['', 'asdf', '|asdf', '', 'asdf', 'asdf'],\n  });\n\n  newTest({\n    title: '/ matches ^ per line',\n    start: ['|  asdf', 'asasdf', 'asdf', 'asdf'],\n    keysPressed: '/^asdf\\n',\n    end: ['  asdf', 'asasdf', '|asdf', 'asdf'],\n  });\n\n  newTest({\n    title: '/ matches $ per line',\n    start: ['|asdfjkl', 'asdf  ', 'asdf', 'asdf'],\n    keysPressed: '/asdf$\\n',\n    end: ['asdfjkl', 'asdf  ', '|asdf', 'asdf'],\n  });\n\n  newTest({\n    title: '/ search $, walk over matches',\n    start: ['|start', '', '', 'end'],\n    keysPressed: '/$\\nnnn',\n    end: ['start', '', '', 'en|d'],\n  });\n\n  newTest({\n    title: '?, match at EOL, walk over matches',\n    start: ['x end', 'x', 'x', '|start'],\n    keysPressed: '?x\\nnn',\n    end: ['|x end', 'x', 'x', 'start'],\n  });\n\n  newTest({\n    title: 'Search for `(`',\n    start: ['|one (two) three'],\n    keysPressed: '/(\\n',\n    end: ['one |(two) three'],\n  });\n\n  /**\n   * The escaped `/` and `?` the next tests are necessary because otherwise they denote a search offset.\n   */\n  newTest({\n    title: 'Can search for forward slash',\n    start: ['|One/two/three/four'],\n    keysPressed: '/\\\\/\\nn',\n    end: ['One/two|/three/four'],\n  });\n\n  newTest({\n    title: 'Can search backward for question mark',\n    start: ['|One?two?three?four'],\n    keysPressed: '?\\\\?\\nn',\n    end: ['One?two|?three?four'],\n  });\n\n  newTest({\n    title: '/\\\\c forces case insensitive search',\n    start: ['|__ASDF', 'asdf'],\n    keysPressed: '/\\\\casdf\\n',\n    end: ['__|ASDF', 'asdf'],\n  });\n\n  newTest({\n    title: '/\\\\C forces case sensitive search',\n    start: ['|__ASDF', 'asdf'],\n    keysPressed: '/\\\\Casdf\\n',\n    end: ['__ASDF', '|asdf'],\n  });\n\n  newTest({\n    title: '/\\\\\\\\c does not trigger case (in)sensitivity',\n    start: ['|__\\\\c__'],\n    keysPressed: '/\\\\\\\\c\\n',\n    end: ['__|\\\\c__'],\n  });\n\n  newTest({\n    title: '/\\\\\\\\\\\\c triggers case insensitivity',\n    start: ['|__\\\\ASDF', 'asdf'],\n    keysPressed: '/\\\\\\\\\\\\c\\n',\n    end: ['__|\\\\ASDF', 'asdf'],\n  });\n\n  newTest({\n    title: '<C-l> adds the next character in the first match to search term',\n    start: ['|foo', 'bar', 'abcd'],\n    keysPressed: '/ab<C-l>d\\n',\n    end: ['foo', 'bar', '|abcd'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can delete with search forward',\n    start: ['foo |junk junk bar'],\n    keysPressed: 'd/bar\\n',\n    end: ['foo |bar'],\n    endMode: Mode.Normal,\n  });\n\n  newTest({\n    title: 'Can delete with search backward',\n    start: ['foo junk garbage trash |bar'],\n    keysPressed: 'd?junk\\n',\n    end: ['foo |bar'],\n    endMode: Mode.Normal,\n  });\n});\n"
  },
  {
    "path": "test/search/searchTextObject.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { getAndUpdateModeHandler } from '../../extensionBase';\nimport { Mode } from '../../src/mode/mode';\nimport { ModeHandler } from '../../src/mode/modeHandler';\nimport { newTest } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nsuite('Search text objects (gn and gN)', () => {\n  let modeHandler: ModeHandler;\n\n  setup(async () => {\n    await setupWorkspace();\n    modeHandler = (await getAndUpdateModeHandler())!;\n  });\n\n  suite('can handle gn', () => {\n    test(`gn selects the next match text`, async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('gg'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 'hello'.length);\n      assert.strictEqual(selection.end.line, 1);\n    });\n\n    const gnSelectsCurrentWord = async (jumpCmd: string) => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents(jumpCmd.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 'hello'.length);\n      assert.strictEqual(selection.end.line, 1);\n    };\n\n    test(`gn selects the current word at |hello`, async () => {\n      await gnSelectsCurrentWord('2gg');\n    });\n\n    test(`gn selects the current word at h|ello`, async () => {\n      await gnSelectsCurrentWord('2ggl');\n    });\n\n    test(`gn selects the current word at hel|lo`, async () => {\n      await gnSelectsCurrentWord('2ggeh');\n    });\n\n    test(`gn selects the current word at hell|o`, async () => {\n      await gnSelectsCurrentWord('2gge');\n    });\n\n    test(`gn selects the next word at hello|`, async () => {\n      await modeHandler.handleMultipleKeyEvents('ifoo\\nhello world\\nhello\\nhello'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('2ggel'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'n']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 2);\n      assert.strictEqual(selection.end.character, 'hello'.length);\n      assert.strictEqual(selection.end.line, 2);\n    });\n  });\n\n  suite('can handle dgn', () => {\n    newTest({\n      title: 'dgn deletes the next match text (from first line)',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\nggdgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn deletes the current word when cursor is at |hello',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\ndgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn deletes the current word when cursor is at h|ello',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\nldgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn deletes the current word when cursor is at hel|lo',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\n3ldgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn deletes the current word when cursor is at hell|o',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\nedgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn deletes the next word when cursor is at hello|',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\neldgn',\n      end: ['foo', 'hello world', '|', 'hello'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgn with single-character match',\n      start: ['O|ne Two Three Four Five Six'],\n      keysPressed: '/T\\n' + 'e' + 'dgn',\n      end: ['One Two |hree Four Five Six'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('can handle cgn', () => {\n    newTest({\n      title: 'cgn deletes the next match text (from first line)',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\nggcgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgn deletes the current word when cursor is at |hello',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\ncgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgn deletes the current word when cursor is at h|ello',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\nlcgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgn deletes the current word when cursor is at hel|lo',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\n3lcgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgn deletes the current word when cursor is at hell|o',\n      start: ['|foo', 'hello world', 'hello', 'hello'],\n      keysPressed: '/hello\\necgn',\n      end: ['foo', '| world', 'hello', 'hello'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgn with single-character match',\n      start: ['O|ne Two Three Four Five Six'],\n      keysPressed: '/T\\n' + 'e' + 'cgn',\n      end: ['One Two |hree Four Five Six'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: '`cgn` can be repeated by dot',\n      start: ['|', 'one', 'two', 'one', 'three'],\n      keysPressed: '/one\\n' + 'cgn' + 'XYZ' + '<Esc>' + '..',\n      end: ['', 'XYZ', 'two', 'XY|Z', 'three'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('can handle gN', () => {\n    test(`gN selects the previous match text`, async () => {\n      await modeHandler.handleMultipleKeyEvents('ihello world\\nhello\\nhi hello\\nfoo'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents(['G']);\n      await modeHandler.handleMultipleKeyEvents(['g', 'N']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 'hi '.length);\n      assert.strictEqual(selection.start.line, 2);\n      assert.strictEqual(selection.end.character, 'hi hello'.length);\n      assert.strictEqual(selection.end.line, 2);\n    });\n\n    const gnSelectsCurrentWord = async (jumpCmd: string) => {\n      await modeHandler.handleMultipleKeyEvents('ihello world\\nhello\\nhi hello\\nfoo'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents(jumpCmd.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'N']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 'hi '.length);\n      assert.strictEqual(selection.start.line, 2);\n      assert.strictEqual(selection.end.character, 'hi hello'.length);\n      assert.strictEqual(selection.end.line, 2);\n    };\n\n    test(`gN selects the current word at hell|o`, async () => {\n      await gnSelectsCurrentWord('3gg7l');\n    });\n\n    test(`gN selects the current word at hel|lo`, async () => {\n      await gnSelectsCurrentWord('3gg6l');\n    });\n\n    test(`gN selects the current word at h|ello`, async () => {\n      await gnSelectsCurrentWord('3gg4l');\n    });\n\n    test(`gN selects the current word at |hello`, async () => {\n      await gnSelectsCurrentWord('3gg3l');\n    });\n\n    test(`gN selects the previous word at | hello`, async () => {\n      await modeHandler.handleMultipleKeyEvents('ihello world\\nhello\\nhi hello\\nfoo'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['<Esc>', ...'/hello\\n'.split('')]);\n      await modeHandler.handleMultipleKeyEvents('3gg2l'.split(''));\n      await modeHandler.handleMultipleKeyEvents(['g', 'N']);\n\n      assert.strictEqual(modeHandler.vimState.currentMode, Mode.Visual);\n\n      const selection = modeHandler.vimState.editor.selection;\n      assert.strictEqual(selection.start.character, 0);\n      assert.strictEqual(selection.start.line, 1);\n      assert.strictEqual(selection.end.character, 'hello'.length);\n      assert.strictEqual(selection.end.line, 1);\n    });\n  });\n\n  suite('can handle dgN', () => {\n    newTest({\n      title: 'dgN deletes the previous match text (from first line)',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\nGdgN',\n      end: ['hello world', 'hello', 'hi| ', 'foo'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgN deletes the current word when cursor is at hell|o',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3gg$dgN',\n      end: ['hello world', 'hello', 'hi| ', 'foo'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgN deletes the current word when cursor is at hel|lo',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3gg$hdgN',\n      end: ['hello world', 'hello', 'hi| ', 'foo'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgN deletes the current word when cursor is at h|ello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwldgN',\n      end: ['hello world', 'hello', 'hi| ', 'foo'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgN deletes the current word when cursor is at |hello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwdgN',\n      end: ['hello world', 'hello', 'hi| ', 'foo'],\n      endMode: Mode.Normal,\n    });\n\n    newTest({\n      title: 'dgN deletes the previous word when cursor is at | hello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwhdgN',\n      end: ['hello world', '|', 'hi hello', 'foo'],\n      endMode: Mode.Normal,\n    });\n  });\n\n  suite('can handle cgN', () => {\n    newTest({\n      title: 'cgN deletes the previous match text (from first line)',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\nGcgN',\n      end: ['hello world', 'hello', 'hi |', 'foo'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgN deletes the current word when cursor is at hell|o',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3gg$cgN',\n      end: ['hello world', 'hello', 'hi |', 'foo'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgN deletes the current word when cursor is at hel|lo',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3gg$hcgN',\n      end: ['hello world', 'hello', 'hi |', 'foo'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgN deletes the current word when cursor is at h|ello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwlcgN',\n      end: ['hello world', 'hello', 'hi |', 'foo'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgN deletes the current word when cursor is at |hello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwcgN',\n      end: ['hello world', 'hello', 'hi |', 'foo'],\n      endMode: Mode.Insert,\n    });\n\n    newTest({\n      title: 'cgN deletes the previous word when cursor is at | hello',\n      start: ['hello world', 'hello', 'hi hello', '|foo'],\n      keysPressed: '/hello\\n3ggwhcgN',\n      end: ['hello world', '|', 'hi hello', 'foo'],\n      endMode: Mode.Insert,\n    });\n  });\n});\n"
  },
  {
    "path": "test/sentenceMotion.test.ts",
    "content": "import { newTest } from './testSimplifier';\nimport { setupWorkspace } from './testUtils';\n\nsuite('sentence motion', () => {\n  suiteSetup(async () => {\n    await setupWorkspace({ fileExtension: '.js' });\n  });\n\n  suite('[count] sentences backward', () => {\n    newTest({\n      title: 'move one sentence backward',\n      start: ['lorem ipsum. lorem ipsum|'],\n      keysPressed: '(',\n      end: ['lorem ipsum. |lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward',\n      start: ['lorem ipsum. lorem ipsum|'],\n      keysPressed: '1(',\n      end: ['lorem ipsum. |lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move [count] sentences backward',\n      start: ['lorem ipsum. lorem ipsum. lorem ipsum|'],\n      keysPressed: '2(',\n      end: ['lorem ipsum. |lorem ipsum. lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward single line - middle',\n      start: ['lorem ipsum. |lorem ipsum'],\n      keysPressed: '(',\n      end: ['|lorem ipsum. lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward no space',\n      start: ['lorem ipsum.lorem ipsum|'],\n      keysPressed: '(',\n      end: ['|lorem ipsum.lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward no space - middle',\n      start: ['lorem ipsum.|lorem ipsum'],\n      keysPressed: '(',\n      end: ['|lorem ipsum.lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward closing quotes',\n      start: ['\"lorem ipsum.\" lorem ipsum|'],\n      keysPressed: '(',\n      end: ['\"lorem ipsum.\" |lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward closing singlequote',\n      start: [\"'lorem ipsum.' lorem ipsum|\"],\n      keysPressed: '(',\n      end: [\"'lorem ipsum.' |lorem ipsum\"],\n    });\n\n    newTest({\n      title: 'move one sentence backward closing paren',\n      start: ['(lorem ipsum.) lorem ipsum|'],\n      keysPressed: '(',\n      end: ['(lorem ipsum.) |lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward closing square bracket',\n      start: ['[lorem ipsum.] lorem ipsum|'],\n      keysPressed: '(',\n      end: ['[lorem ipsum.] |lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward - multiline',\n      start: ['lorem ipsum', 'lorem ipsum|'],\n      keysPressed: '(',\n      end: ['|lorem ipsum', 'lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward - multiline - period',\n      start: ['lorem ipsum.', 'lorem ipsum|'],\n      keysPressed: '(',\n      end: ['lorem ipsum.', '|lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward - multiline - previous line',\n      start: ['lorem ipsum', '|lorem ipsum'],\n      keysPressed: '(',\n      end: ['|lorem ipsum', 'lorem ipsum'],\n    });\n\n    newTest({\n      title: 'move one sentence backward - multiline - previous line - period',\n      start: ['lorem ipsum.', '|lorem ipsum'],\n      keysPressed: '(',\n      end: ['|lorem ipsum.', 'lorem ipsum'],\n    });\n  });\n});\n"
  },
  {
    "path": "test/state/vimState.test.ts",
    "content": "import * as assert from 'assert';\nimport * as vscode from 'vscode';\nimport { Position } from 'vscode';\nimport { EasyMotion } from '../../src/actions/plugins/easymotion/easymotion';\nimport { Cursor } from '../../src/common/motion/cursor';\nimport { VimState } from '../../src/state/vimState';\n\nsuite('VimState', () => {\n  test('de-dupes cursors', async () => {\n    // setup\n    const vimState = new VimState(vscode.window.activeTextEditor!, new EasyMotion());\n    await vimState.load();\n    const cursorStart = new Position(0, 0);\n    const cursorStop = new Position(0, 1);\n    const initialCursors = [\n      new Cursor(cursorStart, cursorStop),\n      new Cursor(cursorStart, cursorStop),\n    ];\n\n    // test\n    vimState.cursors = initialCursors;\n\n    // assert\n    assert.strictEqual(vimState.cursors.length, 1);\n  });\n\n  test('cursorStart/cursorStop should be first cursor in cursors', async () => {\n    // setup\n    const vimState = new VimState(vscode.window.activeTextEditor!, new EasyMotion());\n    await vimState.load();\n    const cursorStart = new Position(0, 0);\n    const cursorStop = new Position(0, 1);\n    const initialCursors = [\n      new Cursor(cursorStart, cursorStop),\n      new Cursor(new Position(1, 0), new Position(1, 1)),\n    ];\n\n    // test\n    vimState.cursors = initialCursors;\n\n    // assert\n    assert.strictEqual(vimState.cursors.length, 2);\n    assert.strictEqual(vimState.isMultiCursor, true);\n    vimState.cursorStartPosition = cursorStart;\n    vimState.cursorStopPosition = cursorStop;\n  });\n});\n"
  },
  {
    "path": "test/testConfiguration.ts",
    "content": "import * as vscode from 'vscode';\n\nimport {\n  IConfiguration,\n  IHighlightedYankConfiguration,\n  IKeyRemapping,\n  IModeSpecificStrings,\n  ITargetsConfiguration,\n} from '../src/configuration/iconfiguration';\n\nexport class Configuration implements IConfiguration {\n  constructor(overrides: Partial<IConfiguration> = {}) {\n    Object.assign(this, overrides);\n  }\n\n  [key: string]: any;\n\n  useSystemClipboard = false;\n  useCtrlKeys = false;\n  overrideCopy = true;\n  textwidth = 80;\n  hlsearch = false;\n  ignorecase = true;\n  smartcase = true;\n  autoindent = true;\n  joinspaces = true;\n  camelCaseMotion = {\n    enable: false,\n  };\n  replaceWithRegister = false;\n  smartRelativeLine = false;\n  sneak = false;\n  sneakUseIgnorecaseAndSmartcase = false;\n  sneakReplacesF = false;\n  surround = false;\n  argumentObjectSeparators = [','];\n  argumentObjectOpeningDelimiters = ['(', '['];\n  argumentObjectClosingDelimiters = [')', ']'];\n  easymotion = false;\n  easymotionMarkerBackgroundColor = '#0000';\n  easymotionMarkerForegroundColorOneChar = '#ff0000';\n  easymotionMarkerForegroundColorTwoCharFirst = '#ffb400';\n  easymotionMarkerForegroundColorTwoCharSecond = '#b98300';\n  easymotionIncSearchForegroundColor = '#7fbf00';\n  easymotionDimColor = '#777777';\n  easymotionDimBackground = true;\n  easymotionMarkerFontWeight = 'bold';\n  easymotionKeys = 'hklyuiopnm,qwertzxcvbasdgjf;';\n  easymotionJumpToAnywhereRegex = '\\\\b[A-Za-z0-9]|[A-Za-z0-9]\\\\b|_.|#.|[a-z][A-Z]';\n\n  targets: ITargetsConfiguration = {\n    enable: false,\n    bracketObjects: {\n      enable: true,\n    },\n    smartQuotes: {\n      enable: false,\n      breakThroughLines: true,\n      aIncludesSurroundingSpaces: true,\n    },\n  };\n  autoSwitchInputMethod = {\n    enable: false,\n    defaultIM: '',\n    switchIMCmd: '',\n    obtainIMCmd: '',\n  };\n  timeout = 1000;\n  maxmapdepth = 100;\n  showcmd = true;\n  showmodename = true;\n  leader = '//';\n  history = 50;\n  incsearch = true;\n  inccommand = '' as const;\n  startInInsertMode = false;\n  startInInsertModeSchemes = ['comment'];\n  statusBarColorControl = false;\n  statusBarColors: IModeSpecificStrings<string | string[]> = {\n    normal: ['#8FBCBB', '#434C5E'],\n    insert: '#BF616A',\n    visual: '#B48EAD',\n    visualline: '#B48EAD',\n    visualblock: '#A3BE8C',\n    replace: '#D08770',\n  };\n  searchHighlightColor = 'rgba(150, 150, 255, 0.3)';\n  searchHighlightTextColor = '';\n  searchMatchColor = 'rgba(255, 150, 150, 0.3)';\n  searchMatchTextColor = '';\n  substitutionColor = 'rgba(100, 255, 150, 0.3)';\n  substitutionTextColor = '';\n  highlightedyank: IHighlightedYankConfiguration = {\n    enable: false,\n    color: 'rgba(250, 240, 170, 0.5)',\n    textColor: '',\n    duration: 200,\n  };\n  tabstop = 2;\n  editorCursorStyle = vscode.TextEditorCursorStyle.Line;\n  expandtab = true;\n  // eslint-disable-next-line id-denylist\n  number = true;\n  relativenumber = false;\n  iskeyword = ''; // Use `editor.wordSeparators`\n  matchpairs = '(:),{:},[:]';\n  visualstar = false;\n  mouseSelectionGoesIntoVisualMode = true;\n  changeWordIncludesWhitespace = false;\n  foldfix = false;\n  disableExtension = false;\n  enableNeovim = false;\n  gdefault = false;\n  substituteGlobalFlag = false; // Deprecated in favor of gdefault\n  neovimPath = 'nvim';\n  neovimUseConfigFile = false;\n  neovimConfigPath = '';\n  vimrc = {\n    enable: false,\n    path: '',\n  };\n  cursorStylePerMode: IModeSpecificStrings<string> = {\n    normal: 'line',\n    insert: 'block',\n    visual: 'underline',\n    visualline: 'line-thin',\n    visualblock: 'block-outline',\n    replace: 'underline-thin,',\n  };\n  insertModeKeyBindings: IKeyRemapping[] = [];\n  insertModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  normalModeKeyBindings: IKeyRemapping[] = [];\n  normalModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  operatorPendingModeKeyBindings: IKeyRemapping[] = [];\n  operatorPendingModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  visualModeKeyBindings: IKeyRemapping[] = [];\n  visualModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  commandLineModeKeyBindings: IKeyRemapping[] = [];\n  commandLineModeKeyBindingsNonRecursive: IKeyRemapping[] = [];\n  insertModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  normalModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  operatorPendingModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  visualModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  commandLineModeKeyBindingsMap: Map<string, IKeyRemapping> = new Map();\n  whichwrap = 'b,s';\n  wrapKeys = {};\n  report = 2;\n  digraphs = {};\n  wrapscan = true;\n  scroll = 20;\n  scrolloff = 5;\n  startofline = true;\n  showMarksInGutter = true;\n  shell = '';\n  handleKeys = {\n    '<C-d>': true,\n  };\n  langmap = '';\n}\n"
  },
  {
    "path": "test/testSimplifier.ts",
    "content": "import { strict as assert } from 'assert';\nimport * as sinon from 'sinon';\nimport * as vscode from 'vscode';\n\nimport * as os from 'os';\nimport { Position } from 'vscode';\nimport { IConfiguration, IKeyRemapping } from '../src/configuration/iconfiguration';\nimport { VimrcImpl } from '../src/configuration/vimrc';\nimport { vimrcKeyRemappingBuilder } from '../src/configuration/vimrcKeyRemappingBuilder';\nimport { Globals } from '../src/globals';\nimport { Mode } from '../src/mode/mode';\nimport { ModeHandler } from '../src/mode/modeHandler';\nimport { ModeHandlerMap } from '../src/mode/modeHandlerMap';\nimport { Register } from '../src/register/register';\nimport { globalState } from '../src/state/globalState';\nimport { VimState } from '../src/state/vimState';\nimport { StatusBar } from '../src/statusBar';\nimport { TextEditor } from '../src/textEditor';\nimport { assertEqualLines, reloadConfiguration, setupWorkspace } from './testUtils';\n\nfunction newTestGeneric<T extends ITestObject | ITestWithRemapsObject>(\n  testObj: T,\n  testFunc: Mocha.TestFunction | Mocha.ExclusiveTestFunction | Mocha.PendingTestFunction,\n  innerTest: (testObj: T) => Promise<ModeHandler>,\n): void {\n  const stack = ((s) => (s ? s.split('\\n').splice(2, 1).join('\\n') : 'no stack available :('))(\n    new Error().stack,\n  );\n\n  testFunc(testObj.title, async () => {\n    const prevConfig = { ...Globals.mockConfiguration };\n    try {\n      if (testObj.config) {\n        Object.assign(Globals.mockConfiguration, testObj.config);\n        await reloadConfiguration(Globals.mockConfiguration);\n      }\n\n      if (vscode.window.activeTextEditor === undefined) {\n        await setupWorkspace();\n      }\n\n      await innerTest(testObj);\n    } catch (reason) {\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      reason.stack = stack;\n      throw reason;\n    } finally {\n      if (testObj.config) {\n        await reloadConfiguration(prevConfig);\n      }\n    }\n  });\n}\n\nexport const newTest = (testObj: ITestObject) => newTestGeneric(testObj, test, testIt);\n\nexport const newTestOnly = (testObj: ITestObject) => {\n  console.warn('!!! Running single test !!!');\n  return newTestGeneric(testObj, test.only, testIt);\n};\n\nexport const newTestSkip = (testObj: ITestObject, skipCondition: boolean = true) =>\n  newTestGeneric(testObj, skipCondition ? test.skip : test, testIt);\n\nexport const newTestWithRemaps = (testObj: ITestWithRemapsObject) =>\n  newTestGeneric(testObj, test, testItWithRemaps);\nexport const newTestWithRemapsOnly = (testObj: ITestWithRemapsObject) => {\n  console.warn('!!! Running single test !!!');\n  return newTestGeneric(testObj, test.only, testItWithRemaps);\n};\nexport const newTestWithRemapsSkip = (testObj: ITestWithRemapsObject) =>\n  newTestGeneric(testObj, test.skip, testItWithRemaps);\n\ninterface ITestObject {\n  title: string;\n  config?: Partial<IConfiguration>;\n  editorOptions?: vscode.TextEditorOptions;\n  start: string[];\n  keysPressed: string;\n  end: string[];\n  endMode?: Mode;\n  endFsPath?: string | (() => string);\n  registers?: { [name: string]: string | undefined };\n  statusBar?: string;\n  jumps?: string[];\n  stub?: {\n    stubClass: any;\n    methodName: string;\n    returnValue: any;\n  };\n  saveDocBeforeTest?: boolean;\n}\n\ntype Step = {\n  title?: string;\n  keysPressed: string;\n  stepResult: {\n    end: string[];\n    endAfterTimeout?: string[];\n    endMode?: Mode;\n    endModeAfterTimeout?: Mode;\n  };\n};\n\ninterface ITestWithRemapsObject {\n  title: string;\n  config?: Partial<IConfiguration>;\n  start: string[];\n  remaps?:\n    | {\n        normalModeKeyBindings?: IKeyRemapping[];\n        normalModeKeyBindingsNonRecursive?: IKeyRemapping[];\n        insertModeKeyBindings?: IKeyRemapping[];\n        insertModeKeyBindingsNonRecursive?: IKeyRemapping[];\n        visualModeKeyBindings?: IKeyRemapping[];\n        visualModeKeyBindingsNonRecursive?: IKeyRemapping[];\n        operatorPendingModeKeyBindings?: IKeyRemapping[];\n        operatorPendingModeKeyBindingsNonRecursive?: IKeyRemapping[];\n      }\n    | string[];\n  steps: Step[];\n}\n\nclass DocState {\n  public static parse(lines: string[]): DocState {\n    lines = [...lines];\n\n    const cursors: Position[] = [];\n    for (let i = 0; i < lines.length; ) {\n      const columnIdx = lines[i].indexOf('|');\n      if (columnIdx >= 0) {\n        lines[i] = lines[i].replace('|', '');\n        cursors.push(new Position(i, columnIdx));\n      } else {\n        i++;\n      }\n    }\n    if (cursors.length === 0) {\n      throw new Error(\"Missing '|' in test object\");\n    }\n\n    return new DocState(cursors, lines);\n  }\n\n  constructor(cursors: Position[], lines: string[]) {\n    this.cursors = cursors;\n    this.lines = lines;\n  }\n\n  public readonly cursors: Position[];\n  public readonly lines: string[];\n}\n\n/**\n * Tokenize a string like `\"abc<Esc>d<C-c>\"` into `[\"a\", \"b\", \"c\", \"<Esc>\", \"d\", \"<C-c>\"]`\n */\nfunction tokenizeKeySequence(sequence: string): string[] {\n  let isBracketedKey = false;\n  let key = '';\n  const result: string[] = [];\n\n  // no close bracket, probably trying to do a left shift, take literal\n  // char sequence\n  const rawTokenize = (characters: string): void => {\n    for (const c of characters) {\n      result.push(c);\n    }\n  };\n\n  // don't use a for of here, since the iterator doesn't split surrogate pairs\n  // eslint-disable-next-line @typescript-eslint/prefer-for-of\n  for (let i = 0; i < sequence.length; i++) {\n    const char = sequence[i];\n\n    key += char;\n\n    if (char === '<') {\n      if (isBracketedKey) {\n        rawTokenize(key.slice(0, key.length - 1));\n        key = '<';\n      } else {\n        isBracketedKey = true;\n      }\n    }\n\n    if (char === '>') {\n      isBracketedKey = false;\n    }\n\n    if (isBracketedKey) {\n      continue;\n    }\n\n    result.push(key);\n    key = '';\n  }\n\n  if (isBracketedKey) {\n    rawTokenize(key);\n  }\n\n  return result;\n}\n\nasync function applyDocState(editor: vscode.TextEditor, docState: DocState): Promise<void> {\n  assert.ok(\n    await editor.edit((builder) => {\n      builder.replace(TextEditor.getDocumentRange(editor.document), docState.lines.join('\\n'));\n    }),\n    'Edit failed',\n  );\n  editor.selections = docState.cursors.map((cursor) => new vscode.Selection(cursor, cursor));\n}\n\nfunction assertDocState(vimState: VimState, docState: DocState): void {\n  assertEqualLines(docState.lines);\n\n  const simplify = (position: Position) => ({ line: position.line, character: position.character });\n  const compare = (\n    a: { line: number; character: number },\n    b: { line: number; character: number },\n  ) => {\n    if (a.line !== b.line) {\n      return a.line - b.line;\n    }\n    return a.character - b.character;\n  };\n  // TODO: Assert selections match vimState.cursors\n  assert.deepEqual(\n    vimState.cursors.map((cursor) => simplify(cursor.stop)).sort(compare),\n    docState.cursors.map(simplify).sort(compare),\n    'Cursors are wrong.',\n  );\n}\n\nexport async function testIt(testObj: ITestObject): Promise<ModeHandler> {\n  const editor = vscode.window.activeTextEditor;\n  assert(editor, 'Expected an active editor');\n\n  const start = DocState.parse(testObj.start);\n  const end = DocState.parse(testObj.end);\n\n  if (testObj.editorOptions) {\n    editor.options = testObj.editorOptions;\n  }\n\n  await applyDocState(editor, start);\n\n  if (testObj.saveDocBeforeTest) {\n    assert.ok(await editor.document.save(), 'Save failed');\n  }\n\n  // Generate a brand new ModeHandler for this editor\n  ModeHandlerMap.clear();\n  const [modeHandler, _] = await ModeHandlerMap.getOrCreate(editor);\n\n  assertDocState(modeHandler.vimState, start);\n\n  globalState.lastInvokedMacro = undefined;\n  globalState.jumpTracker.clearJumps();\n\n  Register.clearAllRegisters();\n\n  if (testObj.stub) {\n    const confirmStub = sinon\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      .stub(testObj.stub.stubClass.prototype, testObj.stub.methodName)\n      .resolves(testObj.stub.returnValue);\n    await modeHandler.handleMultipleKeyEvents(tokenizeKeySequence(testObj.keysPressed), false);\n    confirmStub.restore();\n  } else {\n    // Assumes key presses are single characters for now\n    await modeHandler.handleMultipleKeyEvents(tokenizeKeySequence(testObj.keysPressed), false);\n  }\n\n  assertDocState(modeHandler.vimState, end);\n\n  if (testObj.endMode !== undefined) {\n    assert.equal(\n      Mode[modeHandler.vimState.currentMode],\n      Mode[testObj.endMode],\n      \"Didn't enter correct mode.\",\n    );\n  }\n\n  if (testObj.endFsPath !== undefined) {\n    assert.equal(\n      vscode.window.activeTextEditor?.document.uri.fsPath,\n      typeof testObj.endFsPath === 'string' ? testObj.endFsPath : testObj.endFsPath(),\n      'Active document is wrong.',\n    );\n  }\n\n  if (testObj.registers !== undefined) {\n    for (const reg in testObj.registers) {\n      if (testObj.registers[reg] !== undefined) {\n        assert.equal((await Register.get(reg))?.text, testObj.registers[reg]);\n      } else {\n        assert.equal(await Register.get(reg), undefined);\n      }\n    }\n  }\n\n  if (testObj.statusBar !== undefined) {\n    assert.equal(\n      StatusBar.getText(),\n      testObj.statusBar.replace('{FILENAME}', modeHandler.vimState.document.fileName),\n      'Status bar text is wrong.',\n    );\n  }\n\n  // jumps: check jumps are correct if given\n  if (testObj.jumps !== undefined) {\n    // TODO: Jumps should be specified by Positions, not line contents\n    assert.deepEqual(\n      globalState.jumpTracker.jumps.map((j) => end.lines[j.position.line] || '<MISSING>'),\n      testObj.jumps.map((t) => t.replace('|', '')),\n      'Incorrect jumps found',\n    );\n\n    const stripBar = (text: string | undefined) => (text ? text.replace('|', '') : text);\n    const actualJumpPosition =\n      (globalState.jumpTracker.currentJump &&\n        end.lines[globalState.jumpTracker.currentJump.position.line]) ||\n      '<FRONT>';\n    const expectedJumpPosition = stripBar(testObj.jumps.find((t) => t.includes('|'))) || '<FRONT>';\n\n    assert.deepEqual(actualJumpPosition, expectedJumpPosition, 'Incorrect jump position found');\n  }\n\n  return modeHandler;\n}\n\nasync function testItWithRemaps(testObj: ITestWithRemapsObject): Promise<ModeHandler> {\n  const editor = vscode.window.activeTextEditor;\n  assert(editor, 'Expected an active editor');\n\n  await applyDocState(editor, DocState.parse(testObj.start));\n\n  // Generate a brand new ModeHandler for this editor\n  ModeHandlerMap.clear();\n  const [modeHandler, _] = await ModeHandlerMap.getOrCreate(editor);\n\n  const config = Globals.mockConfiguration;\n\n  // Change remappings\n  if (testObj.remaps) {\n    if (!(testObj.remaps instanceof Array)) {\n      config.normalModeKeyBindings = testObj.remaps?.normalModeKeyBindings ?? [];\n      config.normalModeKeyBindingsNonRecursive =\n        testObj.remaps?.normalModeKeyBindingsNonRecursive ?? [];\n      config.insertModeKeyBindings = testObj.remaps?.insertModeKeyBindings ?? [];\n      config.insertModeKeyBindingsNonRecursive =\n        testObj.remaps?.insertModeKeyBindingsNonRecursive ?? [];\n      config.visualModeKeyBindings = testObj.remaps?.visualModeKeyBindings ?? [];\n      config.visualModeKeyBindingsNonRecursive =\n        testObj.remaps?.visualModeKeyBindingsNonRecursive ?? [];\n      config.operatorPendingModeKeyBindings = testObj.remaps?.operatorPendingModeKeyBindings ?? [];\n      config.operatorPendingModeKeyBindingsNonRecursive =\n        testObj.remaps?.operatorPendingModeKeyBindingsNonRecursive ?? [];\n    } else {\n      await parseVimRCMappings(testObj.remaps);\n    }\n  }\n\n  const timeout = config.timeout;\n  const timeoutOffset = timeout / 2;\n\n  await reloadConfiguration(config);\n\n  for (const [index, step] of testObj.steps.entries()) {\n    const resolvedStep = (() => {\n      let start: DocState;\n      if (index === 0) {\n        start = DocState.parse(testObj.start);\n      } else {\n        const prevStepResult = testObj.steps[index - 1].stepResult;\n        start = DocState.parse(prevStepResult.endAfterTimeout ?? prevStepResult.end);\n      }\n\n      const stepResult = testObj.steps[index].stepResult;\n      return {\n        start,\n        end: DocState.parse(stepResult.end),\n        endAfterTimeout: stepResult.endAfterTimeout\n          ? DocState.parse(stepResult.endAfterTimeout)\n          : undefined,\n      };\n    })();\n\n    const stepTitleOrIndex = step.title ? `nr. ${index} - \"${step.title}\"` : index;\n\n    const jumpTracker = globalState.jumpTracker;\n    jumpTracker.clearJumps();\n\n    // Checks if this step should wait for timeout or not\n    const waitsForTimeout = step.stepResult.endAfterTimeout !== undefined;\n\n    type ResultType = {\n      lines: string;\n      position: vscode.Position;\n      endMode: Mode;\n    };\n\n    const p1 = () => {\n      return new Promise<ResultType>((p1Resolve, p1Reject) => {\n        setTimeout(() => {\n          // Get lines, position and mode after half timeout finishes\n          p1Resolve({\n            lines: modeHandler.vimState.document.getText(),\n            position: modeHandler.vimState.editor.selection.start,\n            endMode: modeHandler.vimState.currentMode,\n          });\n        }, timeoutOffset);\n      });\n    };\n\n    const p2 = () => {\n      return new Promise<ResultType | undefined>((p2Resolve, p2Reject) => {\n        if (waitsForTimeout) {\n          setTimeout(async () => {\n            if (modeHandler.remapState.isCurrentlyPerformingRemapping) {\n              // Performing a remapping, which means it started at the right time but it has not\n              // finished yet (maybe the remapping has a lot of keys to handle) so we wait for the\n              // remapping to finish\n              const wait = (ms: number) => new Promise((res) => setTimeout(res, ms));\n              while (modeHandler.remapState.isCurrentlyPerformingRemapping) {\n                // Wait a little bit longer here because the currently performing remap might have\n                // some remaining keys to handle after it finishes performing the remap and there\n                // might even be there some keys still to be sent that might create another remap.\n                // Example: if you have and ambiguous remap like 'ab -> abcd' and 'abc -> abcdef'\n                // and an insert remap like 'jj -> <Esc>' and you press 'abjj' the first 'j' breaks\n                // the ambiguity and makes the remap start performing, but when the remap finishes\n                // performing there is still the 'jj' to be handled and remapped.\n                await wait(10);\n              }\n            }\n            // Get lines, position and mode after timeout + offset finishes\n            p2Resolve({\n              lines: modeHandler.vimState.document.getText(),\n              position: modeHandler.vimState.editor.selection.start,\n              endMode: modeHandler.vimState.currentMode,\n            });\n          }, timeout + timeoutOffset);\n        } else {\n          p2Resolve(undefined);\n        }\n      });\n    };\n\n    // Assumes key presses are single characters for now\n    await modeHandler.handleMultipleKeyEvents(tokenizeKeySequence(step.keysPressed), false);\n\n    // Only start the end check promises after the keys were handled to make sure they don't\n    // finish before all the keys are pressed. The keys handler above will resolve when the\n    // keys are handled even if it buffered some keys to wait for a timeout.\n    const [result1, result2] = await Promise.all([p1(), p2()]);\n\n    // Lines after keys pressed but before any timeout\n\n    // Check given end output is correct\n    assert.equal(\n      result1.lines,\n      resolvedStep.end.lines.join(os.EOL),\n      `Document content does not match on step ${stepTitleOrIndex}.`,\n    );\n\n    // Check end cursor position\n    const actualEndPosition = result1.position;\n    const expectedEndPosition = resolvedStep.end.cursors[0];\n    assert.deepEqual(\n      { line: actualEndPosition.line, character: actualEndPosition.character },\n      { line: expectedEndPosition.line, character: expectedEndPosition.character },\n      `Cursor position is wrong on step ${stepTitleOrIndex}.`,\n    );\n\n    // endMode: check end mode is correct if given\n    const expectedEndMode = step.stepResult.endMode;\n    if (expectedEndMode !== undefined) {\n      assert.equal(\n        Mode[result1.endMode],\n        Mode[expectedEndMode],\n        `Didn't enter correct mode on step ${stepTitleOrIndex}.`,\n      );\n    }\n\n    if (result2) {\n      // After the timeout finishes (plus an offset to be sure it finished)\n      assert.notEqual(result2, undefined);\n\n      // Check given endAfterTimeout output is correct\n      assert.equal(\n        result2.lines,\n        resolvedStep.endAfterTimeout?.lines.join(os.EOL),\n        `Document content does not match on step ${stepTitleOrIndex} after timeout.`,\n      );\n\n      // Check endAfterTimeout cursor position\n      const actualEndAfterTimeoutPosition = result2.position;\n      const expectedEndAfterTimeoutPosition = resolvedStep.endAfterTimeout!.cursors[0];\n      assert.deepEqual(\n        {\n          line: actualEndAfterTimeoutPosition.line,\n          character: actualEndAfterTimeoutPosition.character,\n        },\n        {\n          line: expectedEndAfterTimeoutPosition.line,\n          character: expectedEndAfterTimeoutPosition.character,\n        },\n        `Cursor position is wrong on step ${stepTitleOrIndex} after Timeout.`,\n      );\n\n      // endMode: check end mode is correct if given\n      const expectedEndAfterTimeoutMode = step.stepResult.endModeAfterTimeout;\n      if (expectedEndAfterTimeoutMode !== undefined) {\n        assert.equal(\n          Mode[result2.endMode],\n          Mode[expectedEndAfterTimeoutMode],\n          `Didn't enter correct mode on step ${stepTitleOrIndex} after Timeout.`,\n        );\n      }\n    }\n  }\n  return modeHandler;\n}\n\nasync function parseVimRCMappings(lines: string[]): Promise<void> {\n  const config = Globals.mockConfiguration;\n\n  // Remove all the old remappings from the .vimrc file\n  VimrcImpl.removeAllRemapsFromConfig(config);\n\n  const vscodeCommands = await vscode.commands.getCommands();\n  // Add the new remappings\n  for (const line of lines) {\n    const remap = await vimrcKeyRemappingBuilder.build(line, vscodeCommands);\n    if (remap) {\n      VimrcImpl.addRemapToConfig(config, remap);\n      continue;\n    }\n    const unremap = await vimrcKeyRemappingBuilder.buildUnmapping(line);\n    if (unremap) {\n      VimrcImpl.removeRemapFromConfig(config, unremap);\n      continue;\n    }\n    const clearRemap = await vimrcKeyRemappingBuilder.buildClearMapping(line);\n    if (clearRemap) {\n      VimrcImpl.clearRemapsFromConfig(config, clearRemap);\n      continue;\n    }\n  }\n}\n\nexport type { ITestObject };\n"
  },
  {
    "path": "test/testUtils.ts",
    "content": "import { strict as assert } from 'assert';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport * as vscode from 'vscode';\n\nimport { ExCommandLine } from '../src/cmd_line/commandLine';\nimport { IConfiguration } from '../src/configuration/iconfiguration';\nimport { Globals } from '../src/globals';\nimport { ModeHandlerMap } from '../src/mode/modeHandlerMap';\nimport { StatusBar } from '../src/statusBar';\nimport { TextEditor } from '../src/textEditor';\nimport { Configuration } from './testConfiguration';\n\nclass TestMemento implements vscode.Memento {\n  private mapping = new Map<string, any>();\n  get<T>(key: string): T | undefined;\n  get<T>(key: string, defaultValue: T): T;\n  get(key: any, defaultValue?: any) {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument\n    return this.mapping.get(key) || defaultValue;\n  }\n\n  setKeysForSync(keys: string[]): void {\n    throw new Error('`TestMemento.setKeysForSync` is currently unimplemented');\n  }\n\n  async update(key: string, value: any): Promise<void> {\n    this.mapping.set(key, value);\n  }\n\n  keys(): readonly string[] {\n    return Array.from(this.mapping.keys());\n  }\n}\n\nexport class TestExtensionContext implements vscode.ExtensionContext {\n  extension!: vscode.Extension<any>;\n  subscriptions: Array<{ dispose(): any }> = [];\n  workspaceState: vscode.Memento = new TestMemento();\n  globalState: vscode.Memento & {\n    setKeysForSync(keys: string[]): void;\n  } = new TestMemento();\n  secrets!: vscode.SecretStorage;\n  extensionUri!: vscode.Uri;\n  extensionPath: string = 'inmem:///test';\n  environmentVariableCollection!: vscode.EnvironmentVariableCollection;\n\n  asAbsolutePath(relativePath: string): string {\n    return path.resolve(this.extensionPath, relativePath);\n  }\n\n  storageUri: vscode.Uri | undefined;\n  storagePath: string | undefined;\n  globalStorageUri!: vscode.Uri;\n  globalStoragePath!: string;\n  logUri!: vscode.Uri;\n  logPath!: string;\n  extensionMode!: vscode.ExtensionMode;\n}\n\nexport function rndName(): string {\n  return Math.random()\n    .toString(36)\n    .replace(/[^a-z]+/g, '')\n    .substring(0, 10);\n}\n\nexport async function createFile(\n  args: {\n    fsPath?: string;\n    fileExtension?: string;\n    contents?: string;\n  } = {},\n): Promise<string> {\n  if (args.fileExtension) {\n    assert.ok(args.fileExtension.startsWith('.'));\n  }\n  args.fsPath ??= join(os.tmpdir(), rndName() + (args.fileExtension ?? ''));\n  await promisify(fs.writeFile)(args.fsPath, args.contents ?? '');\n  return args.fsPath;\n}\n\nexport async function createDir(fsPath?: string): Promise<string> {\n  fsPath ??= join(os.tmpdir(), rndName());\n  await promisify(fs.mkdir)(fsPath);\n  return fsPath;\n}\n\n/**\n * Waits for the number of text editors in the current window to equal the\n * given expected number of text editors.\n *\n * @param numExpectedEditors Expected number of editors in the window\n */\nexport async function waitForEditorsToClose(numExpectedEditors: number = 0): Promise<void> {\n  const waitForTextEditorsToClose = new Promise<void>((c, e) => {\n    if (vscode.window.visibleTextEditors.length === numExpectedEditors) {\n      return c();\n    }\n\n    const subscription = vscode.window.onDidChangeVisibleTextEditors(() => {\n      if (vscode.window.visibleTextEditors.length === numExpectedEditors) {\n        subscription.dispose();\n        c();\n      }\n    });\n  });\n\n  try {\n    await waitForTextEditorsToClose;\n  } catch (error) {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n    assert.fail(error);\n  }\n}\n\nexport function assertEqualLines(expectedLines: string[]) {\n  assert.equal(\n    vscode.window.activeTextEditor?.document.getText(),\n    expectedLines.join(os.EOL),\n    'Document content does not match.',\n  );\n}\n\nexport function assertStatusBarEqual(\n  expectedText: string,\n  message: string = 'Status bar text does not match',\n) {\n  assert.equal(StatusBar.getText(), expectedText, message);\n}\n\nexport async function setupWorkspace(\n  args: {\n    config?: Partial<IConfiguration>;\n    fileExtension?: string;\n    fileContent?: string[];\n    forceNewFile?: boolean;\n    disableCleanUp?: boolean;\n  } = {},\n): Promise<void> {\n  await ExCommandLine.loadHistory(new TestExtensionContext());\n\n  const newFile =\n    vscode.window.activeTextEditor === undefined ||\n    vscode.window.activeTextEditor.document.isUntitled ||\n    vscode.window.visibleTextEditors.length > 1 ||\n    (args.fileExtension &&\n      !vscode.window.activeTextEditor.document.fileName.endsWith(args.fileExtension)) ||\n    args.forceNewFile;\n  const fileContent = (args.fileContent ?? []).join(os.EOL);\n\n  if (newFile) {\n    if (!args.disableCleanUp) await cleanUpWorkspace();\n    const filePath = await createFile({\n      fileExtension: args.fileExtension,\n      contents: fileContent,\n    });\n    const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath));\n    await vscode.window.showTextDocument(doc);\n  }\n\n  const config = new Configuration(args?.config);\n  await reloadConfiguration(config);\n\n  const activeTextEditor = vscode.window.activeTextEditor;\n  assert.ok(activeTextEditor);\n\n  activeTextEditor.options.tabSize = config.tabstop;\n  activeTextEditor.options.insertSpaces = config.expandtab;\n\n  if (!newFile) {\n    assert.ok(\n      await activeTextEditor.edit((builder) => {\n        builder.replace(TextEditor.getDocumentRange(activeTextEditor.document), fileContent);\n      }),\n      'Edit failed',\n    );\n  }\n\n  if (!args.disableCleanUp) ModeHandlerMap.clear();\n}\n\nexport async function cleanUpWorkspace(): Promise<void> {\n  await vscode.commands.executeCommand('workbench.action.closeAllEditors');\n  assert.equal(vscode.window.visibleTextEditors.length, 0, 'Expected all editors closed.');\n  assert(!vscode.window.activeTextEditor, 'Expected no active text editor.');\n}\n\nexport async function reloadConfiguration(config: IConfiguration) {\n  Globals.mockConfiguration = config;\n\n  const validatorResults = await (\n    await import('../src/configuration/configuration')\n  ).configuration.load();\n  for (const validatorResult of validatorResults.get()) {\n    console.warn(validatorResult);\n  }\n}\n\n/**\n * Waits for the tabs to change after a command like 'gt' or 'gT' is run.\n * Sometimes it is not immediate, so we must busy wait\n * On certain versions, the tab changes are synchronous\n * For those, a timeout is given\n */\nexport async function waitForTabChange(): Promise<void> {\n  await new Promise((resolve, reject) => {\n    setTimeout(resolve, 500);\n\n    const subscription = vscode.window.onDidChangeActiveTextEditor((textEditor) => {\n      subscription.dispose();\n\n      resolve(textEditor);\n    });\n  });\n}\n\nexport async function replaceContent(\n  document: vscode.TextDocument,\n  content: string,\n): Promise<void> {\n  const edit = new vscode.WorkspaceEdit();\n  edit.replace(document.uri, TextEditor.getDocumentRange(document), content);\n  const isApplied = await vscode.workspace.applyEdit(edit);\n\n  if (!isApplied) throw new Error(`Failed to replace content`);\n}\n"
  },
  {
    "path": "test/util/path.test.ts",
    "content": "import * as assert from 'assert';\nimport * as path from 'path';\nimport * as vscode from 'vscode';\nimport { resolveUri, separatePath } from '../../src/util/path';\n\nsuite('util path', () => {\n  suite('separatePath', () => {\n    test('can separate drive letter path on Windows', () => {\n      let [dirName, baseName] = separatePath('C:', path.win32.sep);\n      assert.strictEqual(dirName, '');\n      assert.strictEqual(baseName, 'C:');\n\n      [dirName, baseName] = separatePath('C:\\\\', path.win32.sep);\n      assert.strictEqual(dirName, 'C:\\\\');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('C:\\\\text', path.win32.sep);\n      assert.strictEqual(dirName, 'C:\\\\');\n      assert.strictEqual(baseName, 'text');\n\n      [dirName, baseName] = separatePath('C:\\\\text\\\\123', path.win32.sep);\n      assert.strictEqual(dirName, 'C:\\\\text\\\\');\n      assert.strictEqual(baseName, '123');\n    });\n\n    test('can separate UNC path on Windows', () => {\n      let [dirName, baseName] = separatePath('\\\\\\\\test', path.win32.sep);\n      assert.strictEqual(dirName, '\\\\\\\\test');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('\\\\\\\\test\\\\', path.win32.sep);\n      assert.strictEqual(dirName, '\\\\\\\\test\\\\');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('\\\\\\\\test\\\\abc', path.win32.sep);\n      assert.strictEqual(dirName, '\\\\\\\\test\\\\');\n      assert.strictEqual(baseName, 'abc');\n\n      [dirName, baseName] = separatePath('\\\\\\\\test\\\\abc\\\\123', path.win32.sep);\n      assert.strictEqual(dirName, '\\\\\\\\test\\\\abc\\\\');\n      assert.strictEqual(baseName, '123');\n    });\n\n    test('can separate relative path on Windows', () => {\n      let [dirName, baseName] = separatePath('.', path.win32.sep);\n      assert.strictEqual(dirName, '');\n      assert.strictEqual(baseName, '.');\n\n      [dirName, baseName] = separatePath('.\\\\', path.win32.sep);\n      assert.strictEqual(dirName, '.\\\\');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('.\\\\test', path.win32.sep);\n      assert.strictEqual(dirName, '.\\\\');\n      assert.strictEqual(baseName, 'test');\n\n      [dirName, baseName] = separatePath('~\\\\..\\\\.\\\\test\\\\123', path.win32.sep);\n      assert.strictEqual(dirName, '~\\\\..\\\\.\\\\test\\\\');\n      assert.strictEqual(baseName, '123');\n    });\n\n    test('can separate absolute path on posix', () => {\n      let [dirName, baseName] = separatePath('/', path.posix.sep);\n      assert.strictEqual(dirName, '/');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('/abc', path.posix.sep);\n      assert.strictEqual(dirName, '/');\n      assert.strictEqual(baseName, 'abc');\n\n      [dirName, baseName] = separatePath('/abc/123', path.posix.sep);\n      assert.strictEqual(dirName, '/abc/');\n      assert.strictEqual(baseName, '123');\n    });\n\n    test('can separate relative path on posix', () => {\n      let [dirName, baseName] = separatePath('.', path.posix.sep);\n      assert.strictEqual(dirName, '');\n      assert.strictEqual(baseName, '.');\n\n      [dirName, baseName] = separatePath('./', path.posix.sep);\n      assert.strictEqual(dirName, './');\n      assert.strictEqual(baseName, '');\n\n      [dirName, baseName] = separatePath('./test', path.posix.sep);\n      assert.strictEqual(dirName, './');\n      assert.strictEqual(baseName, 'test');\n\n      [dirName, baseName] = separatePath('~/.././test/123', path.posix.sep);\n      assert.strictEqual(dirName, '~/.././test/');\n      assert.strictEqual(baseName, '123');\n    });\n  });\n\n  suite('resolveUri', () => {\n    test('posix', () => {\n      let testUri = vscode.Uri.file('/test/path');\n      let resultUri = resolveUri('C:\\\\', path.posix.sep, testUri, false);\n      assert.strictEqual(resultUri, null, 'Failed to return null when it is not posix path');\n\n      testUri = vscode.Uri.file('/test/path');\n      resultUri = resolveUri('/abc/123', path.posix.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'file');\n        assert.strictEqual(resultUri.path, '/abc/123');\n      }\n\n      // Convert Uri to local fs Uri if Uri is local untitled\n      testUri = testUri.with({ scheme: 'untitled' });\n      resultUri = resolveUri('/abc/123', path.posix.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'file');\n        assert.strictEqual(resultUri.path, '/abc/123');\n      }\n\n      testUri = testUri.with({ scheme: 'vscode-remote' });\n      assert.ok(resultUri);\n      resultUri = resolveUri('/abc/123', path.posix.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'vscode-remote');\n        assert.strictEqual(resultUri.path, '/abc/123');\n      }\n    });\n\n    test('win32', () => {\n      let testUri = vscode.Uri.file('C:\\\\123');\n      let resultUri = resolveUri('/', path.win32.sep, testUri, false);\n      assert.strictEqual(resultUri, null, 'Failed to return null when it is not win32 path');\n\n      testUri = vscode.Uri.file('C:\\\\test');\n      resultUri = resolveUri('C:\\\\123\\\\abc', path.win32.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'file');\n        assert.strictEqual(resultUri.fsPath, `c:\\\\123\\\\abc`);\n      }\n\n      // Even if remote is true, we can only return local file scheme\n      resultUri = resolveUri('C:\\\\123\\\\abc', path.win32.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'file');\n        assert.strictEqual(resultUri.fsPath, `c:\\\\123\\\\abc`);\n      }\n\n      // Convert Uri to local fs Uri if Uri is local untitled\n      testUri = testUri.with({ scheme: 'untitled' });\n      resultUri = resolveUri('/abc/123', path.posix.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'file');\n        assert.strictEqual(resultUri.path, '/abc/123');\n      }\n\n      testUri = testUri.with({ scheme: 'vscode-remote' });\n      assert.ok(resultUri);\n      resultUri = resolveUri('/abc/123', path.posix.sep, testUri, false);\n      if (resultUri === null) {\n        assert.fail(\"null shouldn't be returned.\");\n      } else {\n        assert.strictEqual(resultUri.scheme, 'vscode-remote');\n        assert.strictEqual(resultUri.path, '/abc/123');\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "test/vimscript/exCommandParse.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { BufferDeleteCommand } from '../../src/cmd_line/commands/bufferDelete';\nimport { CloseCommand } from '../../src/cmd_line/commands/close';\nimport { CopyCommand } from '../../src/cmd_line/commands/copy';\nimport { DeleteCommand } from '../../src/cmd_line/commands/delete';\nimport { DigraphsCommand } from '../../src/cmd_line/commands/digraph';\nimport { EchoCommand } from '../../src/cmd_line/commands/echo';\nimport { EvalCommand } from '../../src/cmd_line/commands/eval';\nimport { FileCommand } from '../../src/cmd_line/commands/file';\nimport { GotoCommand } from '../../src/cmd_line/commands/goto';\nimport { GotoLineCommand } from '../../src/cmd_line/commands/gotoLine';\nimport { GrepCommand } from '../../src/cmd_line/commands/grep';\nimport { HistoryCommand, HistoryCommandType } from '../../src/cmd_line/commands/history';\nimport { LeftCommand, RightCommand } from '../../src/cmd_line/commands/leftRightCenter';\nimport { LetCommand } from '../../src/cmd_line/commands/let';\nimport { DeleteMarksCommand, MarkCommand, MarksCommand } from '../../src/cmd_line/commands/marks';\nimport { PutExCommand } from '../../src/cmd_line/commands/put';\nimport { QuitCommand } from '../../src/cmd_line/commands/quit';\nimport { ReadCommand } from '../../src/cmd_line/commands/read';\nimport { RegisterCommand } from '../../src/cmd_line/commands/register';\nimport { RetabCommand } from '../../src/cmd_line/commands/retab';\nimport { SetCommand } from '../../src/cmd_line/commands/set';\nimport { ShiftCommand } from '../../src/cmd_line/commands/shift';\nimport { SortCommand } from '../../src/cmd_line/commands/sort';\nimport { ReplaceString, SubstituteCommand } from '../../src/cmd_line/commands/substitute';\nimport { TabCommand, TabCommandType } from '../../src/cmd_line/commands/tab';\nimport { VsCodeCommand } from '../../src/cmd_line/commands/vscode';\nimport { WriteCommand } from '../../src/cmd_line/commands/write';\nimport { YankCommand } from '../../src/cmd_line/commands/yank';\nimport { VimError } from '../../src/error';\nimport { ExCommand } from '../../src/vimscript/exCommand';\nimport { exCommandParser, NoOpCommand } from '../../src/vimscript/exCommandParser';\nimport {\n  add,\n  dictionary,\n  funcCall,\n  int,\n  list,\n  listExpr,\n  str,\n  toExpr,\n  variable,\n} from '../../src/vimscript/expression/build';\nimport { Expression } from '../../src/vimscript/expression/types';\nimport { Address } from '../../src/vimscript/lineRange';\nimport { Pattern, SearchDirection } from '../../src/vimscript/pattern';\n\nfunction exParseTest(input: string, parsed: ExCommand) {\n  test(input, () => {\n    const { command } = exCommandParser.tryParse(input);\n    assert.deepStrictEqual(command, parsed);\n  });\n}\n\nfunction exParseFails(input: string, error: VimError) {\n  test(input, () => {\n    assert.throws(() => exCommandParser.tryParse(input), error);\n  });\n}\n\nsuite('Ex command parsing', () => {\n  suite('Unknown command', () => {\n    exParseFails(':fakecmd', VimError.NotAnEditorCommand('fakecmd'));\n    exParseFails(':^', VimError.NotAnEditorCommand('^'));\n  });\n\n  suite(':[range]', () => {\n    exParseTest(':', new GotoLineCommand());\n    exParseTest(':5', new GotoLineCommand());\n    exParseTest(':%', new GotoLineCommand());\n    exParseTest(':.+5', new GotoLineCommand());\n    exParseTest(':.+,.+-+3', new GotoLineCommand());\n    exParseTest(':,5', new GotoLineCommand());\n    exParseTest(':,', new GotoLineCommand());\n  });\n\n  suite(':\"', () => {\n    exParseTest(':\"', new NoOpCommand());\n    exParseTest(':\"I am a comment', new NoOpCommand());\n  });\n\n  suite(':!', () => {\n    // TODO\n  });\n\n  suite(':#!', () => {\n    exParseTest(':#!', new NoOpCommand());\n    exParseTest(':#!123 abc! | s/one/two', new NoOpCommand());\n  });\n\n  suite(':>', () => {\n    exParseTest(':>', new ShiftCommand({ dir: '>', depth: 1, numLines: undefined }));\n    exParseTest(':>>', new ShiftCommand({ dir: '>', depth: 2, numLines: undefined }));\n    exParseTest(':>  >', new ShiftCommand({ dir: '>', depth: 2, numLines: undefined }));\n    exParseTest(':>>5', new ShiftCommand({ dir: '>', depth: 2, numLines: 5 }));\n    exParseTest(':> >   5', new ShiftCommand({ dir: '>', depth: 2, numLines: 5 }));\n  });\n  suite(':<', () => {\n    exParseTest(':<', new ShiftCommand({ dir: '<', depth: 1, numLines: undefined }));\n    exParseTest(':<<', new ShiftCommand({ dir: '<', depth: 2, numLines: undefined }));\n    exParseTest(':<  <', new ShiftCommand({ dir: '<', depth: 2, numLines: undefined }));\n    exParseTest(':<<5', new ShiftCommand({ dir: '<', depth: 2, numLines: 5 }));\n    exParseTest(':< <   5', new ShiftCommand({ dir: '<', depth: 2, numLines: 5 }));\n  });\n\n  suite(':bd[elete]', () => {\n    exParseTest(':bd', new BufferDeleteCommand({ bang: false, buffers: [] }));\n    exParseTest(':bd 2 5 3', new BufferDeleteCommand({ bang: false, buffers: [2, 5, 3] }));\n    exParseTest(':bd abc.txt', new BufferDeleteCommand({ bang: false, buffers: ['abc.txt'] }));\n    exParseTest(\n      ':bd abc.txt 5 blah.cc',\n      new BufferDeleteCommand({ bang: false, buffers: ['abc.txt', 5, 'blah.cc'] }),\n    );\n    exParseTest(':bd!', new BufferDeleteCommand({ bang: true, buffers: [] }));\n    exParseTest(':bd! 2 5 3', new BufferDeleteCommand({ bang: true, buffers: [2, 5, 3] }));\n    exParseTest(':bd! abc.txt', new BufferDeleteCommand({ bang: true, buffers: ['abc.txt'] }));\n    exParseTest(\n      ':bd! abc.txt 5 blah.cc',\n      new BufferDeleteCommand({ bang: true, buffers: ['abc.txt', 5, 'blah.cc'] }),\n    );\n  });\n\n  suite(':bn[ext]', () => {\n    exParseTest(\n      ':bn',\n      new TabCommand({ type: TabCommandType.Next, bang: false, cmd: undefined, count: undefined }),\n    );\n    exParseTest(\n      ':bn!',\n      new TabCommand({ type: TabCommandType.Next, bang: true, cmd: undefined, count: undefined }),\n    );\n    exParseTest(\n      ':bn 5',\n      new TabCommand({\n        type: TabCommandType.Next,\n        bang: false,\n        cmd: undefined,\n        count: 5,\n      }),\n    );\n    exParseTest(\n      ':bn! 5',\n      new TabCommand({\n        type: TabCommandType.Next,\n        bang: true,\n        cmd: undefined,\n        count: 5,\n      }),\n    );\n    exParseTest(\n      ':bn +20 5',\n      new TabCommand({\n        type: TabCommandType.Next,\n        bang: false,\n        cmd: { type: 'line_number', line: 20 },\n        count: 5,\n      }),\n    );\n    exParseTest(\n      ':bn! +20 5',\n      new TabCommand({\n        type: TabCommandType.Next,\n        bang: true,\n        cmd: { type: 'line_number', line: 20 },\n        count: 5,\n      }),\n    );\n  });\n\n  suite(':b[uffer]', () => {\n    exParseTest(':b', new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 0 }));\n    exParseTest(':b 5', new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 5 }));\n    exParseTest(':b5', new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 5 }));\n    exParseTest(\n      ':b +20 5',\n      new TabCommand({ type: TabCommandType.Edit, cmd: { type: 'line_number', line: 20 }, buf: 5 }),\n    );\n    exParseTest(\n      ':b bufname',\n      new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 'bufname' }),\n    );\n    exParseTest(\n      ':b +20 bufname',\n      new TabCommand({\n        type: TabCommandType.Edit,\n        cmd: { type: 'line_number', line: 20 },\n        buf: 'bufname',\n      }),\n    );\n    exParseTest(':buffer', new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 0 }));\n    exParseTest(':buffer 5', new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 5 }));\n    exParseTest(\n      ':buffer bufname',\n      new TabCommand({ type: TabCommandType.Edit, cmd: undefined, buf: 'bufname' }),\n    );\n    exParseTest(\n      ':buffer +20 bufname',\n      new TabCommand({\n        type: TabCommandType.Edit,\n        cmd: { type: 'line_number', line: 20 },\n        buf: 'bufname',\n      }),\n    );\n  });\n\n  suite(':clo[se]', () => {\n    exParseTest(':clo', new CloseCommand(false));\n    exParseTest(':clo!', new CloseCommand(true));\n    exParseTest(':close', new CloseCommand(false));\n    exParseTest(':close!', new CloseCommand(true));\n  });\n\n  suite(':co[py]', () => {\n    exParseTest(':copy', new CopyCommand());\n    exParseTest(':copy 0', new CopyCommand(new Address({ type: 'number', num: 0 })));\n    exParseTest(':copy 1', new CopyCommand(new Address({ type: 'number', num: 1 })));\n    exParseTest(':copy 56', new CopyCommand(new Address({ type: 'number', num: 56 })));\n    exParseTest(':copy .', new CopyCommand(new Address({ type: 'current_line' })));\n    exParseTest(':copy .+2', new CopyCommand(new Address({ type: 'current_line' }, 2)));\n  });\n\n  suite(':d[elete]', () => {\n    exParseTest(':d', new DeleteCommand({ register: undefined, count: undefined }));\n    exParseTest(':d a', new DeleteCommand({ register: 'a', count: undefined }));\n    exParseTest(':d 5', new DeleteCommand({ register: undefined, count: 5 }));\n    exParseTest(':d a 5', new DeleteCommand({ register: 'a', count: 5 }));\n  });\n\n  suite(':delm[arks]', () => {\n    exParseTest(':delm!', new DeleteMarksCommand('!'));\n\n    exParseTest(':delm a', new DeleteMarksCommand(['a']));\n    exParseTest(':delm aA1 ^k', new DeleteMarksCommand(['a', 'A', '1', '^', 'k']));\n    exParseTest(':delm a-z', new DeleteMarksCommand([{ start: 'a', end: 'z' }]));\n    exParseTest(':delm A-Z', new DeleteMarksCommand([{ start: 'A', end: 'Z' }]));\n    exParseTest(':delm 1-9', new DeleteMarksCommand([{ start: '1', end: '9' }]));\n    exParseTest(':delm 24-9', new DeleteMarksCommand(['2', { start: '4', end: '9' }]));\n    exParseTest(\n      ':delm A-K2-4',\n      new DeleteMarksCommand([\n        { start: 'A', end: 'K' },\n        { start: '2', end: '4' },\n      ]),\n    );\n\n    exParseFails(':delm', VimError.ArgumentRequired());\n\n    exParseFails(':delm -', VimError.TrailingCharacters('-')); // TODO: Should throw `E475: Invalid argument: -`\n    exParseFails(':delm a-', VimError.TrailingCharacters('-')); // TODO: Should throw `E475: Invalid argument: a-`\n    exParseFails(':delm -z', VimError.TrailingCharacters('-z')); // TODO: Should throw `E475: Invalid argument: -z`\n    exParseFails(':delm a-Z', VimError.TrailingCharacters('-Z')); // TODO: Should throw `E475: Invalid argument: a-Z`\n\n    exParseFails(':delm! a', VimError.TrailingCharacters('a')); // TODO: Should throw `E474: Invalid argument`\n  });\n\n  suite(':dig[raphs]', () => {\n    exParseTest(':dig', new DigraphsCommand({ bang: false, newDigraph: undefined }));\n    exParseTest(':dig!', new DigraphsCommand({ bang: true, newDigraph: undefined }));\n    exParseTest(':dig e: 235', new DigraphsCommand({ bang: false, newDigraph: ['e', ':', [235]] }));\n    exParseTest(\n      ':dig R! 55357 56960',\n      new DigraphsCommand({\n        bang: false,\n        newDigraph: ['R', '!', [55357, 56960]],\n      }),\n    );\n\n    exParseFails(':dig e:', VimError.TrailingCharacters('e:')); // TODO: Should throw `E39: Number expected`\n  });\n\n  suite(':ec[ho]', () => {\n    const echo = (exprs: Expression[]) => new EchoCommand({ sep: ' ', error: false }, exprs);\n    exParseTest(':echo', echo([]));\n    exParseTest(':echo []{}', echo([listExpr([]), toExpr(dictionary(new Map()))]));\n    exParseTest(':echo 5 + 5', echo([add(int(5), int(5))]));\n    exParseTest(':echo 5 + 5 5', echo([add(int(5), int(5)), int(5)]));\n\n    exParseFails(':echo 5 (', VimError.InvalidExpression('('));\n    // TODO: exParseFails(':echo 5 6abc', VimError.InvalidExpression('6abc'));\n  });\n\n  suite(':e[dit]', () => {\n    exParseTest(\n      ':edit',\n      new FileCommand({ name: 'edit', bang: false, opt: [], cmd: undefined, file: undefined }),\n    );\n    exParseTest(\n      ':edit!',\n      new FileCommand({ name: 'edit', bang: true, opt: [], cmd: undefined, file: undefined }),\n    );\n\n    exParseTest(\n      ':edit abc.txt',\n      new FileCommand({ name: 'edit', bang: false, opt: [], cmd: undefined, file: 'abc.txt' }),\n    );\n    exParseTest(\n      ':edit! abc.txt',\n      new FileCommand({ name: 'edit', bang: true, opt: [], cmd: undefined, file: 'abc.txt' }),\n    );\n\n    exParseTest(\n      ':edit abc\\\\ 1.txt',\n      new FileCommand({ name: 'edit', bang: false, opt: [], cmd: undefined, file: 'abc 1.txt' }),\n    );\n    exParseTest(\n      ':edit! abc\\\\ 1.txt',\n      new FileCommand({ name: 'edit', bang: true, opt: [], cmd: undefined, file: 'abc 1.txt' }),\n    );\n\n    // TODO: Test with [++opt]\n    // TODO: Test with [+cmd]\n    // TODO: Test with #[count]\n  });\n\n  suite(':ene[w]', () => {\n    exParseTest(':enew', new FileCommand({ name: 'enew', bang: false }));\n    exParseTest(':enew!', new FileCommand({ name: 'enew', bang: true }));\n  });\n\n  suite(':eval', () => {\n    exParseTest(':eval 2 + 2', new EvalCommand(add(int(2), int(2))));\n\n    exParseFails(':eval', VimError.InvalidExpression(''));\n    // TODO: exParseFails(':eval 6abc', VimError.InvalidExpression('6abc'));\n    exParseFails(':eval 1 1', VimError.TrailingCharacters('1'));\n  });\n\n  suite(':go[to]', () => {\n    exParseTest(':goto', new GotoCommand());\n    exParseTest(':goto 123', new GotoCommand(123));\n  });\n\n  suite(':his[tory]', () => {\n    exParseTest(':his', new HistoryCommand({ type: HistoryCommandType.Cmd }));\n\n    exParseTest(':his c', new HistoryCommand({ type: HistoryCommandType.Cmd }));\n    exParseTest(':his cmd', new HistoryCommand({ type: HistoryCommandType.Cmd }));\n    exParseTest(':his :', new HistoryCommand({ type: HistoryCommandType.Cmd }));\n\n    exParseTest(':his s', new HistoryCommand({ type: HistoryCommandType.Search }));\n    exParseTest(':his search', new HistoryCommand({ type: HistoryCommandType.Search }));\n    exParseTest(':his /', new HistoryCommand({ type: HistoryCommandType.Search }));\n\n    exParseTest(':his e', new HistoryCommand({ type: HistoryCommandType.Expr }));\n    exParseTest(':his expr', new HistoryCommand({ type: HistoryCommandType.Expr }));\n    exParseTest(':his =', new HistoryCommand({ type: HistoryCommandType.Expr }));\n\n    exParseTest(':his i', new HistoryCommand({ type: HistoryCommandType.Input }));\n    exParseTest(':his input', new HistoryCommand({ type: HistoryCommandType.Input }));\n    exParseTest(':his @', new HistoryCommand({ type: HistoryCommandType.Input }));\n\n    exParseTest(':his d', new HistoryCommand({ type: HistoryCommandType.Debug }));\n    exParseTest(':his debug', new HistoryCommand({ type: HistoryCommandType.Debug }));\n    exParseTest(':his >', new HistoryCommand({ type: HistoryCommandType.Debug }));\n\n    exParseTest(':his a', new HistoryCommand({ type: HistoryCommandType.All }));\n    exParseTest(':his all', new HistoryCommand({ type: HistoryCommandType.All }));\n\n    // TODO parse indices\n  });\n\n  suite(':le[ft]', () => {\n    exParseTest(':left', new LeftCommand({ indent: 0 }));\n    exParseTest(':left4', new LeftCommand({ indent: 4 }));\n    exParseTest(':left 8', new LeftCommand({ indent: 8 }));\n  });\n\n  suite(':let', () => {\n    exParseTest(':let', new LetCommand({ operation: 'print', variables: [] }));\n    exParseTest(\n      ':let foo bar',\n      new LetCommand({ operation: 'print', variables: [variable('foo'), variable('bar')] }),\n    );\n\n    exParseTest(\n      ':let foo = 5',\n      new LetCommand({\n        operation: '=',\n        variable: variable('foo'),\n        expression: int(5),\n        lock: false,\n      }),\n    );\n    exParseTest(\n      ':let foo += 5',\n      new LetCommand({\n        operation: '+=',\n        variable: variable('foo'),\n        expression: int(5),\n        lock: false,\n      }),\n    );\n    exParseTest(\n      ':let foo -= 5',\n      new LetCommand({\n        operation: '-=',\n        variable: variable('foo'),\n        expression: int(5),\n        lock: false,\n      }),\n    );\n    exParseTest(\n      \":let foo .= 'bar'\",\n      new LetCommand({\n        operation: '.=',\n        variable: variable('foo'),\n        expression: str('bar'),\n        lock: false,\n      }),\n    );\n\n    exParseTest(\n      ':let [a, b, c] = [1, 2, 3]',\n      new LetCommand({\n        operation: '=',\n        variable: { type: 'unpack', names: ['a', 'b', 'c'] },\n        expression: toExpr(list([int(1), int(2), int(3)])),\n        lock: false,\n      }),\n    );\n\n    exParseTest(\n      ':let x[7] = \"foo\"',\n      new LetCommand({\n        operation: '=',\n        variable: {\n          type: 'index',\n          variable: { type: 'variable', namespace: undefined, name: 'x' },\n          index: int(7),\n        },\n        expression: str('foo'),\n        lock: false,\n      }),\n    );\n\n    exParseTest(\n      ':let s:arr[start:end] = [1, 2, 3]',\n      new LetCommand({\n        operation: '=',\n        variable: {\n          type: 'slice',\n          variable: { type: 'variable', namespace: 's', name: 'arr' },\n          start: { type: 'variable', namespace: undefined, name: 'start' },\n          end: { type: 'variable', namespace: undefined, name: 'end' },\n        },\n        expression: toExpr(list([int(1), int(2), int(3)])),\n        lock: false,\n      }),\n    );\n\n    exParseTest(\n      ':const foo = 5',\n      new LetCommand({ operation: '=', variable: variable('foo'), expression: int(5), lock: true }),\n    );\n\n    // TODO\n\n    exParseFails(':let x = ', VimError.InvalidExpression(''));\n    // TODO: exParseFails(':let x = 6abc', VimError.InvalidExpression('6abc'));\n    exParseFails(':let x = 6 abc', VimError.TrailingCharacters('abc'));\n  });\n\n  suite(':marks', () => {\n    exParseTest(':marks', new MarksCommand([]));\n    exParseTest(':marks aB', new MarksCommand(['a', 'B']));\n    exParseTest(':marks 0 1', new MarksCommand(['0', '1']));\n  });\n\n  suite(':mark', () => {\n    exParseTest(':mark a', new MarkCommand('a'));\n    exParseTest(':mark `', new MarkCommand('`'));\n\n    exParseFails(':mark', VimError.ArgumentRequired());\n  });\n\n  suite(':p[rint]', () => {\n    // TODO\n  });\n\n  suite(':pu[t]', () => {\n    exParseTest(':put', new PutExCommand({ bang: false, register: undefined }));\n    exParseTest(':put!', new PutExCommand({ bang: true, register: undefined }));\n    exParseTest(':put x', new PutExCommand({ bang: false, register: 'x' }));\n    exParseTest(':put! x', new PutExCommand({ bang: true, register: 'x' }));\n\n    // No space, non-alpha register\n    exParseTest(':put\"', new PutExCommand({ bang: false, register: '\"' }));\n    exParseTest(':put!\"', new PutExCommand({ bang: true, register: '\"' }));\n\n    // No space, alpha register\n    exParseFails(':putx', VimError.NotAnEditorCommand('putx'));\n    exParseTest(':put!x', new PutExCommand({ bang: true, register: 'x' }));\n\n    // Expression register\n    exParseTest(':put=', new PutExCommand({ bang: false, register: '=' }));\n    exParseTest(':put=5+2', new PutExCommand({ bang: false, fromExpression: add(int(5), int(2)) }));\n    exParseTest(\n      ':put = range(4)',\n      new PutExCommand({ bang: false, fromExpression: funcCall('range', [int(4)]) }),\n    );\n    exParseTest(\n      ':put!=[1,2,3]',\n      new PutExCommand({ bang: true, fromExpression: toExpr(list([int(1), int(2), int(3)])) }),\n    );\n    exParseFails(':put=)', VimError.InvalidExpression(')'));\n    // TODO: exParseFails(':put=1+', VimError.InvalidExpression('1+'));\n    exParseFails(':put=1 1', VimError.TrailingCharacters('1'));\n  });\n\n  suite(':q[uit] and :qa[ll]', () => {\n    exParseTest(':q', new QuitCommand({ bang: false, quitAll: false }));\n    exParseTest(':q!', new QuitCommand({ bang: true, quitAll: false }));\n    exParseTest(':qa', new QuitCommand({ bang: false, quitAll: true }));\n    exParseTest(':qa!', new QuitCommand({ bang: true, quitAll: true }));\n  });\n\n  suite(':r[ead]', () => {\n    exParseTest(':r', new ReadCommand({ opt: [] }));\n    exParseTest(':r abc.txt', new ReadCommand({ opt: [], file: 'abc.txt' }));\n    exParseTest(':r !ls', new ReadCommand({ opt: [], cmd: 'ls' }));\n    exParseTest(\n      ':r ++enc=foo abc.txt',\n      new ReadCommand({ opt: [['enc', 'foo']], file: 'abc.txt' }),\n    );\n    exParseTest(':r ++enc=foo !ls', new ReadCommand({ opt: [['enc', 'foo']], cmd: 'ls' }));\n  });\n\n  suite(':reg[isters]', () => {\n    exParseTest(':reg', new RegisterCommand([]));\n    exParseTest(':reg b1\"2a', new RegisterCommand(['b', '1', '\"', '2', 'a']));\n    exParseTest(':reg b 1 \" 2 a', new RegisterCommand(['b', '1', '\"', '2', 'a']));\n  });\n\n  suite(':ret[ab]', () => {\n    exParseTest(':retab', new RetabCommand({ replaceSpaces: false, newTabstop: undefined }));\n    exParseTest(':retab 8', new RetabCommand({ replaceSpaces: false, newTabstop: 8 }));\n    exParseTest(':ret4', new RetabCommand({ replaceSpaces: false, newTabstop: 4 }));\n    exParseTest(':retab!', new RetabCommand({ replaceSpaces: true, newTabstop: undefined }));\n    exParseTest(':ret! 8', new RetabCommand({ replaceSpaces: true, newTabstop: 8 }));\n    exParseTest(':retab!4', new RetabCommand({ replaceSpaces: true, newTabstop: 4 }));\n  });\n\n  suite(':ri[ght]', () => {\n    exParseTest(':right', new RightCommand({ width: 80 })); // Defaults to 'textwidth'\n    exParseTest(':right40', new RightCommand({ width: 40 }));\n    exParseTest(':right 20', new RightCommand({ width: 20 }));\n  });\n\n  suite(':se[t]', () => {\n    exParseTest(':set', new SetCommand({ type: 'show_or_set', option: undefined }));\n    exParseTest(':set all', new SetCommand({ type: 'show_or_set', option: 'all' }));\n    exParseTest(':set all&', new SetCommand({ type: 'default', option: 'all', source: '' }));\n\n    for (const option of ['ws', 'wrapscan']) {\n      exParseTest(`:set ${option}`, new SetCommand({ type: 'show_or_set', option }));\n      exParseTest(`:set ${option}?`, new SetCommand({ type: 'show', option }));\n      exParseTest(`:set no${option}`, new SetCommand({ type: 'unset', option }));\n      exParseTest(`:set inv${option}`, new SetCommand({ type: 'invert', option }));\n      exParseTest(`:set ${option}!`, new SetCommand({ type: 'invert', option }));\n      exParseTest(`:set ${option}&`, new SetCommand({ type: 'default', option, source: '' }));\n      exParseTest(`:set ${option}&vi`, new SetCommand({ type: 'default', option, source: 'vi' }));\n      exParseTest(`:set ${option}&vim`, new SetCommand({ type: 'default', option, source: 'vim' }));\n      // TODO: :set {option}<\n    }\n\n    for (const option of ['sw', 'shiftwidth']) {\n      exParseTest(`:set ${option}=4`, new SetCommand({ type: 'equal', option, value: '4' }));\n      exParseTest(`:set ${option}:4`, new SetCommand({ type: 'equal', option, value: '4' }));\n      exParseTest(`:set ${option}+=4`, new SetCommand({ type: 'add', option, value: '4' }));\n      exParseTest(`:set ${option}^=4`, new SetCommand({ type: 'multiply', option, value: '4' }));\n      exParseTest(`:set ${option}-=4`, new SetCommand({ type: 'subtract', option, value: '4' }));\n    }\n  });\n\n  suite(':sor[t]', () => {\n    exParseTest(\n      ':sort',\n      new SortCommand({ reverse: false, ignoreCase: false, unique: false, numeric: false }),\n    );\n    exParseTest(\n      ':sort i',\n      new SortCommand({ reverse: false, ignoreCase: true, unique: false, numeric: false }),\n    );\n    exParseTest(\n      ':sort u',\n      new SortCommand({ reverse: false, ignoreCase: false, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort iu',\n      new SortCommand({ reverse: false, ignoreCase: true, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort ui',\n      new SortCommand({ reverse: false, ignoreCase: true, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort n',\n      new SortCommand({ reverse: false, ignoreCase: false, unique: false, numeric: true }),\n    );\n    exParseTest(\n      ':sort nu',\n      new SortCommand({ reverse: false, ignoreCase: false, unique: true, numeric: true }),\n    );\n\n    exParseTest(\n      ':sort!',\n      new SortCommand({ reverse: true, ignoreCase: false, unique: false, numeric: false }),\n    );\n    exParseTest(\n      ':sort! i',\n      new SortCommand({ reverse: true, ignoreCase: true, unique: false, numeric: false }),\n    );\n    exParseTest(\n      ':sort! u',\n      new SortCommand({ reverse: true, ignoreCase: false, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort! iu',\n      new SortCommand({ reverse: true, ignoreCase: true, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort! ui',\n      new SortCommand({ reverse: true, ignoreCase: true, unique: true, numeric: false }),\n    );\n    exParseTest(\n      ':sort! n',\n      new SortCommand({ reverse: true, ignoreCase: false, unique: false, numeric: true }),\n    );\n    exParseTest(\n      ':sort! nu',\n      new SortCommand({ reverse: true, ignoreCase: false, unique: true, numeric: true }),\n    );\n\n    // TODO\n  });\n\n  suite(':s[ubstitute]', () => {\n    const pattern = Pattern.parser({ direction: SearchDirection.Forward, delimiter: '/' });\n\n    exParseTest(\n      ':s/a/b/g',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: { replaceAll: true },\n        count: undefined,\n      }),\n    );\n    exParseTest(\n      ':s/a/b/g 3',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: { replaceAll: true },\n        count: 3,\n      }),\n    );\n    exParseTest(\n      ':s/a/b/g3',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: { replaceAll: true },\n        count: 3,\n      }),\n    );\n    exParseTest(\n      ':s/a/b/3',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: {},\n        count: 3,\n      }),\n    );\n    // Can use weird delimiter\n    exParseTest(\n      ':s#a#b#g',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: { replaceAll: true },\n        count: undefined,\n      }),\n    );\n    // Can escape delimiter\n    exParseTest(\n      ':s/\\\\/\\\\/a/b',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('\\\\/\\\\/a/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: {},\n        count: undefined,\n      }),\n    );\n    // Can use pattern escapes\n    exParseTest(\n      ':s/\\\\ba/b',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('\\\\ba/'),\n        replace: new ReplaceString([{ type: 'string', value: 'b' }]),\n        flags: {},\n        count: undefined,\n      }),\n    );\n    // Can escape replacement\n    exParseTest(\n      ':s/a/\\\\b',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([{ type: 'string', value: '\\b' }]),\n        flags: {},\n        count: undefined,\n      }),\n    );\n\n    exParseTest(\n      ':s/a/\\\\=x+1',\n      new SubstituteCommand({\n        pattern: pattern.tryParse('a/'),\n        replace: new ReplaceString([\n          { type: 'expression', expression: add(variable('x'), int(1)) },\n        ]),\n        flags: {},\n        count: undefined,\n      }),\n    );\n    // TODO\n  });\n\n  suite(':tabm[ove]', () => {\n    exParseTest(\n      ':tabm',\n      new TabCommand({ type: TabCommandType.Move, count: undefined, direction: undefined }),\n    );\n    exParseTest(\n      ':tabm 0',\n      new TabCommand({ type: TabCommandType.Move, count: 0, direction: undefined }),\n    );\n    exParseTest(\n      ':tabm 10',\n      new TabCommand({ type: TabCommandType.Move, count: 10, direction: undefined }),\n    );\n    exParseTest(\n      ':tabm +',\n      new TabCommand({ type: TabCommandType.Move, count: undefined, direction: 'right' }),\n    );\n    exParseTest(\n      ':tabm +10',\n      new TabCommand({ type: TabCommandType.Move, count: 10, direction: 'right' }),\n    );\n    exParseTest(\n      ':tabm -',\n      new TabCommand({ type: TabCommandType.Move, count: undefined, direction: 'left' }),\n    );\n    exParseTest(\n      ':tabm -10',\n      new TabCommand({ type: TabCommandType.Move, count: 10, direction: 'left' }),\n    );\n\n    exParseFails(':tabm +0', VimError.InvalidArgument475('+0'));\n    exParseFails(':tabm -0', VimError.InvalidArgument475('-0'));\n    exParseFails(':tabm ++', VimError.InvalidArgument475('++'));\n    exParseFails(':tabm --', VimError.InvalidArgument475('--'));\n    exParseFails(':tabm 1+', VimError.InvalidArgument475('1+'));\n    exParseFails(':tabm 1-', VimError.InvalidArgument475('1-'));\n    exParseFails(':tabm x', VimError.InvalidArgument475('x'));\n    exParseFails(':tabm 1x', VimError.InvalidArgument475('1x'));\n    exParseFails(':tabm x1', VimError.InvalidArgument475('x1'));\n  });\n\n  suite(':tabo[nly]', () => {\n    exParseTest(\n      ':tabonly',\n      new TabCommand({ type: TabCommandType.Only, bang: false, count: undefined }),\n    );\n    exParseTest(\n      ':tabonly!',\n      new TabCommand({ type: TabCommandType.Only, bang: true, count: undefined }),\n    );\n    exParseTest(':tabonly5', new TabCommand({ type: TabCommandType.Only, bang: false, count: 5 }));\n    exParseTest(':tabonly!5', new TabCommand({ type: TabCommandType.Only, bang: true, count: 5 }));\n    exParseTest(':tabonly 5', new TabCommand({ type: TabCommandType.Only, bang: false, count: 5 }));\n    exParseTest(':tabonly! 5', new TabCommand({ type: TabCommandType.Only, bang: true, count: 5 }));\n  });\n\n  suite(':vim[grep]', () => {\n    exParseTest(\n      ':vimgrep t*st foo.txt',\n      new GrepCommand({\n        // It expects pattern.closed to be false (check Pattern.parser), so unless there's a delimiter in the pattern, it will fail the test\n        pattern: Pattern.parser({ direction: SearchDirection.Backward, delimiter: ' ' }).tryParse(\n          't*st ',\n        ),\n        files: ['foo.txt'],\n      }),\n    );\n    exParseTest(\n      ':vimgrep t*st foo.txt bar.txt baz.txt',\n      new GrepCommand({\n        pattern: Pattern.parser({ direction: SearchDirection.Backward, delimiter: ' ' }).tryParse(\n          't*st ',\n        ),\n        files: ['foo.txt', 'bar.txt', 'baz.txt'],\n      }),\n    );\n  });\n\n  suite(':vscode', () => {\n    exParseTest(\n      ':vscode editor.action.commentLine',\n      new VsCodeCommand('editor.action.commentLine'),\n    );\n    exParseFails(':vscode', VimError.ArgumentRequired());\n  });\n\n  suite(':y[ank]', () => {\n    exParseTest(':y', new YankCommand({ register: undefined, count: undefined }));\n    exParseTest(':y a', new YankCommand({ register: 'a', count: undefined }));\n    exParseTest(':y 5', new YankCommand({ register: undefined, count: 5 }));\n    exParseTest(':y a 5', new YankCommand({ register: 'a', count: 5 }));\n  });\n\n  suite(':w[rite]', () => {\n    exParseTest(':w', new WriteCommand({ bang: false, opt: [], bgWrite: true }));\n    exParseTest(':w!', new WriteCommand({ bang: true, opt: [], bgWrite: true }));\n\n    exParseTest(\n      ':w ++bin',\n      new WriteCommand({ bang: false, opt: [['bin', undefined]], bgWrite: true }),\n    );\n    exParseTest(\n      ':w ++enc=foo',\n      new WriteCommand({ bang: false, opt: [['enc', 'foo']], bgWrite: true }),\n    );\n    exParseTest(\n      ':w ++bin ++enc=foo',\n      new WriteCommand({\n        bang: false,\n        opt: [\n          ['bin', undefined],\n          ['enc', 'foo'],\n        ],\n        bgWrite: true,\n      }),\n    );\n\n    exParseTest(\n      ':w ++enc=foo blah.txt',\n      new WriteCommand({ bang: false, opt: [['enc', 'foo']], file: 'blah.txt', bgWrite: true }),\n    );\n\n    exParseTest(':w !foo', new WriteCommand({ bang: false, opt: [], cmd: 'foo', bgWrite: true }));\n    exParseTest(':w !', new WriteCommand({ bang: false, opt: [], cmd: '', bgWrite: true }));\n\n    // TODO\n  });\n});\n"
  },
  {
    "path": "test/vimscript/expression.test.ts",
    "content": "import * as assert from 'assert';\nimport { VimError } from '../../src/error';\nimport {\n  add,\n  blob,\n  bool,\n  dictionary,\n  float,\n  funcCall,\n  int,\n  lambda,\n  list,\n  listExpr,\n  multiply,\n  negative,\n  positive,\n  str,\n  variable,\n} from '../../src/vimscript/expression/build';\nimport { displayValue } from '../../src/vimscript/expression/displayValue';\nimport { EvaluationContext } from '../../src/vimscript/expression/evaluate';\nimport { expressionParser } from '../../src/vimscript/expression/parser';\nimport { Expression, Value } from '../../src/vimscript/expression/types';\n\nfunction removeIds(value: Value): unknown {\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n  const { id, ...rest } = value as any;\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n  const _value: any = { ...rest };\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n  if (value.type === 'list') {\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access\n    _value.items = value.items.map(removeIds);\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n  } else if (value.type === 'dictionary') {\n    const items = new Map<string, unknown>();\n    for (const [key, val] of value.items) {\n      items.set(key, removeIds(val));\n    }\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    _value.items = items;\n  }\n  return _value;\n}\n\nfunction exprTest(\n  input: string,\n  asserts: { expr?: Expression } & ({ value?: Value; display?: string } | { error: VimError }),\n) {\n  test(input, () => {\n    try {\n      const expression = expressionParser.tryParse(input);\n      if (asserts.expr) {\n        assert.deepStrictEqual(expression, asserts.expr);\n      }\n      if ('error' in asserts) {\n        const ctx = new EvaluationContext(undefined);\n        ctx.evaluate(expression);\n      } else {\n        if (asserts.value !== undefined) {\n          const ctx = new EvaluationContext(undefined);\n          assert.deepStrictEqual(removeIds(ctx.evaluate(expression)), removeIds(asserts.value));\n        }\n        if (asserts.display !== undefined) {\n          const ctx = new EvaluationContext(undefined);\n          assert.deepStrictEqual(displayValue(ctx.evaluate(expression)), asserts.display);\n        }\n      }\n    } catch (e: unknown) {\n      if (e instanceof VimError) {\n        if ('error' in asserts) {\n          assert.deepStrictEqual(e, asserts.error);\n        } else {\n          throw e;\n        }\n      } else {\n        throw e;\n      }\n    }\n  });\n}\n\nsuite('Vimscript expressions', () => {\n  suite('Parse & evaluate expression', () => {\n    suite('Numbers', () => {\n      exprTest('0', { expr: int(0) });\n      exprTest('123', { expr: int(123) });\n\n      // Hexadecimal\n      exprTest('0xff', { expr: int(255) });\n      exprTest('0Xff', { expr: int(255) });\n\n      // Binary\n      exprTest('0b01111', { expr: int(15) });\n      exprTest('0B01111', { expr: int(15) });\n\n      // Octal\n      exprTest('012345', { expr: int(5349) });\n\n      // Looks like octal, but is not (has 8 or 9 as digit)\n      exprTest('012345678', { expr: int(12345678) });\n\n      exprTest('-47', { expr: negative(int(47)), value: int(-47) });\n      exprTest('--47', { expr: negative(negative(int(47))), value: int(47) });\n      exprTest('+47', { expr: positive(int(47)), value: int(47) });\n    });\n\n    suite('Floats', () => {\n      exprTest('1.2', { expr: float(1.2) });\n      exprTest('0.583', { expr: float(0.583) });\n      exprTest('-5.3', { expr: negative(float(5.3)), value: float(-5.3) });\n      exprTest('-5.3', { expr: negative(float(5.3)), value: float(-5.3) });\n\n      exprTest('1.23e5', { expr: float(123000) });\n      exprTest('-4.56E-3', { expr: negative(float(0.00456)) });\n      exprTest('0.424e0', { expr: float(0.424) });\n\n      // By default, 6 decimal places when displayed (:help floating-point-precision)\n      exprTest('0.123456789', { expr: float(0.123456789), display: '0.123457' });\n    });\n\n    suite('Strings', () => {\n      exprTest('\"\"', { expr: str('') });\n      exprTest('\"\\\\\"\"', { expr: str('\"') });\n      exprTest('\"one\\\\ntwo\\\\tthree\"', { expr: str('one\\ntwo\\tthree') });\n    });\n\n    suite('Literal strings', () => {\n      exprTest(\"''\", { expr: str('') });\n      exprTest(\"''''\", { expr: str(\"'\") });\n      exprTest(\"'one two three'\", { expr: str('one two three') });\n      exprTest(\"'one ''two'' three'\", { expr: str(\"one 'two' three\") });\n      exprTest(\"'one\\\\ntwo\\\\tthree'\", { expr: str('one\\\\ntwo\\\\tthree') });\n    });\n\n    suite('Blobs', () => {\n      exprTest('0z', {\n        value: blob(new Uint8Array([])),\n      });\n      exprTest('0zabcd', {\n        value: blob(new Uint8Array([171, 205])),\n      });\n      exprTest('0ZABCD', {\n        value: blob(new Uint8Array([171, 205])),\n      });\n      exprTest('0zAB.CD', {\n        value: blob(new Uint8Array([171, 205])),\n      });\n      exprTest('0zabc', {\n        error: VimError.BlobLiteralShouldHaveAnEvenNumberOfHexCharacters(),\n      });\n    });\n\n    suite('Option', () => {\n      exprTest('&wrapscan', {\n        expr: {\n          type: 'option',\n          scope: undefined,\n          name: 'wrapscan',\n        },\n      });\n      exprTest('&g:wrapscan', {\n        expr: {\n          type: 'option',\n          scope: 'g',\n          name: 'wrapscan',\n        },\n      });\n      exprTest('&l:wrapscan', {\n        expr: {\n          type: 'option',\n          scope: 'l',\n          name: 'wrapscan',\n        },\n      });\n    });\n\n    suite('List', () => {\n      exprTest('[1,2,3]', { expr: listExpr([int(1), int(2), int(3)]) });\n      exprTest('[1,2,3,]', { expr: listExpr([int(1), int(2), int(3)]) });\n      exprTest('[   1   ,   2   ,   3   ]', { expr: listExpr([int(1), int(2), int(3)]) });\n      exprTest('[-1,7*8,3]', {\n        expr: listExpr([negative(int(1)), multiply(int(7), int(8)), int(3)]),\n      });\n      exprTest('[[1,2],[3,4]]', {\n        expr: listExpr([listExpr([int(1), int(2)]), listExpr([int(3), int(4)])]),\n      });\n    });\n\n    suite('Index', () => {\n      exprTest(\"'xyz'[0]\", {\n        expr: {\n          type: 'index',\n          expression: str('xyz'),\n          index: int(0),\n        },\n        value: str('x'),\n      });\n\n      exprTest(\"'xyz'[1]\", {\n        value: str('y'),\n      });\n\n      exprTest(\"'xyz'[0][1]\", {\n        expr: {\n          type: 'index',\n          expression: {\n            type: 'index',\n            expression: str('xyz'),\n            index: int(0),\n          },\n          index: int(1),\n        },\n      });\n\n      exprTest(\"['a','b','c'][1]\", { value: str('b') });\n\n      exprTest(\"#{one: 1, two: 2, three: 3}['one']\", { value: int(1) });\n      exprTest(\"#{one: 1, two: 2, three: 3}['two']\", { value: int(2) });\n      exprTest(\"#{one: 1, two: 2, three: 3}['three']\", { value: int(3) });\n      exprTest(\"#{one: 1, two: 2, three: 3}['four']\", {\n        error: VimError.KeyNotPresentInDictionary('four'),\n      });\n\n      exprTest('0zABCD[0]', { value: int(171) });\n      exprTest('0zABCD[1]', { value: int(205) });\n      // TODO: Blob, negative index\n    });\n\n    suite('Entry', () => {\n      exprTest('#{one: 1, two: 2, three: 3}.one', { value: int(1) });\n      exprTest('#{one: 1, two: 2, three: 3}.two', { value: int(2) });\n      exprTest('#{one: 1, two: 2, three: 3}.three', { value: int(3) });\n      exprTest('#{one: 1, two: 2, three: 3}.four', {\n        error: VimError.KeyNotPresentInDictionary('four'),\n      });\n    });\n\n    suite('Slice', () => {\n      suite('String', () => {\n        exprTest(\"'abcde'[2:3]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: int(2),\n            end: int(3),\n          },\n          value: str('cd'),\n        });\n\n        exprTest(\"'abcde'[2:]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: int(2),\n            end: undefined,\n          },\n          value: str('cde'),\n        });\n\n        exprTest(\"'abcde'[:3]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: undefined,\n            end: int(3),\n          },\n          value: str('abcd'),\n        });\n\n        exprTest(\"'abcde'[-4:-2]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: negative(int(4)),\n            end: negative(int(2)),\n          },\n          value: str('bcd'),\n        });\n\n        exprTest(\"'abcde'[-2:-4]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: negative(int(2)),\n            end: negative(int(4)),\n          },\n          value: str(''),\n        });\n\n        exprTest(\"'abcde'[:]\", {\n          expr: {\n            type: 'slice',\n            expression: str('abcde'),\n            start: undefined,\n            end: undefined,\n          },\n          value: str('abcde'),\n        });\n      });\n\n      suite('List', () => {\n        exprTest('[1,2,3,4,5][2:3]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: int(2),\n            end: int(3),\n          },\n          value: list([int(3), int(4)]),\n        });\n\n        exprTest('[1,2,3,4,5][2:]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: int(2),\n            end: undefined,\n          },\n          value: list([int(3), int(4), int(5)]),\n        });\n\n        exprTest('[1,2,3,4,5][:3]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: undefined,\n            end: int(3),\n          },\n          value: list([int(1), int(2), int(3), int(4)]),\n        });\n\n        exprTest('[1,2,3,4,5][-4:-2]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: negative(int(4)),\n            end: negative(int(2)),\n          },\n          value: list([int(2), int(3), int(4)]),\n        });\n\n        exprTest('[1,2,3,4,5][-2:-4]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: negative(int(2)),\n            end: negative(int(4)),\n          },\n          value: list([]),\n        });\n\n        exprTest('[1,2,3,4,5][:]', {\n          expr: {\n            type: 'slice',\n            expression: listExpr([int(1), int(2), int(3), int(4), int(5)]),\n            start: undefined,\n            end: undefined,\n          },\n          value: list([int(1), int(2), int(3), int(4), int(5)]),\n        });\n      });\n\n      suite('Blob', () => {\n        exprTest('0zDEADBEEF[1:2]', {\n          display: '0zADBE',\n        });\n      });\n    });\n\n    suite('Entry', () => {\n      exprTest('dict.one', {\n        expr: {\n          type: 'entry',\n          expression: variable('dict'),\n          entryName: 'one',\n        },\n      });\n\n      exprTest('dict.1', {\n        expr: {\n          type: 'entry',\n          expression: variable('dict'),\n          entryName: '1',\n        },\n      });\n\n      exprTest('dict.1two', {\n        expr: {\n          type: 'entry',\n          expression: variable('dict'),\n          entryName: '1two',\n        },\n      });\n    });\n\n    suite('Arithmetic', () => {\n      exprTest('5*6', { expr: multiply(int(5), int(6)), value: int(30) });\n      exprTest('5*-6', { expr: multiply(int(5), negative(int(6))), value: int(-30) });\n      exprTest('12*34*56', {\n        expr: multiply(multiply(int(12), int(34)), int(56)),\n        value: int(22848),\n      });\n\n      exprTest('5/0', { value: int(Infinity) }); // TODO: Neovim returns `v:numbermax`\n      exprTest('-5/0', { value: int(-Infinity) }); // TODO: Neovim returns `v:numbermin`\n      exprTest('0/0', { value: int(NaN) }); // TODO: Neovim returns `v:numbermax`\n\n      // TODO: Grok what Neovim does with 5/0.0\n\n      exprTest('4/5', { value: int(0) });\n      exprTest('4/5.0', { display: '0.8' });\n      exprTest('4.0/5', { display: '0.8' });\n    });\n\n    suite('Precedence', () => {\n      exprTest('23+3*9', { expr: add(int(23), multiply(int(3), int(9))), value: int(50) });\n      exprTest('(23+3)*9', { expr: multiply(add(int(23), int(3)), int(9)), value: int(234) });\n    });\n\n    suite('Function calls', () => {\n      exprTest('getcmdpos()', { expr: funcCall('getcmdpos', []) });\n      exprTest('sqrt(9)', { expr: funcCall('sqrt', [int(9)]), value: float(3.0) });\n      exprTest('fmod(21,2)', { expr: funcCall('fmod', [int(21), int(2)]) });\n      exprTest('fmod(2*10,2)', { expr: funcCall('fmod', [multiply(int(2), int(10)), int(2)]) });\n      exprTest('add([1,2,3],4)', {\n        expr: funcCall('add', [listExpr([int(1), int(2), int(3)]), int(4)]),\n      });\n      exprTest('reverse([1,2,3])', {\n        expr: funcCall('reverse', [listExpr([int(1), int(2), int(3)])]),\n        value: list([int(3), int(2), int(1)]),\n      });\n    });\n\n    suite('Method calls', () => {\n      exprTest('[1,2,3]->reverse()', {\n        value: list([int(3), int(2), int(1)]),\n      });\n      // exprTest('[1,2,3,4,5,6]->filter({x->x%2==0})->map({x->x*10})', {\n      //   value: list([int(20), int(40), int(60)]),\n      // });\n      exprTest('[1,2,3]->map({k,v->v*10})->join(\"|\")', {\n        value: str('10|20|30'),\n      });\n    });\n\n    suite('Lambda', () => {\n      exprTest('{x->x}', { expr: lambda(['x'], variable('x')) });\n      exprTest('{x,y->x+y}', { expr: lambda(['x', 'y'], add(variable('x'), variable('y'))) });\n    });\n  });\n\n  suite('Comparisons', () => {\n    suite('String equality', () => {\n      exprTest(\"'abc' == 'Abc'\", { value: bool(false) }); // TODO: this should depend on 'ignorecase'\n      exprTest(\"'abc' ==# 'Abc'\", { value: bool(false) });\n      exprTest(\"'abc' ==? 'Abc'\", { value: bool(true) });\n    });\n\n    suite('Pattern matching', () => {\n      exprTest(\"'apple' =~ '^a.*e$'\", { value: bool(true) });\n      // TODO\n    });\n\n    suite('Different types', () => {\n      exprTest(\"4 == '4'\", { value: bool(true) });\n      exprTest(\"4 is '4'\", { value: bool(false) });\n      exprTest('0 is []', { value: bool(false) });\n      exprTest('0 is {}', { value: bool(false) });\n      exprTest('0 isnot 0', { value: bool(false) });\n      exprTest('[4] == [\"4\"]', { value: bool(false) });\n      exprTest('3.2 > 3', { value: bool(true) });\n      exprTest('5 == [5]', { error: VimError.CanOnlyCompareListWithList() });\n      exprTest('[] == {}', { error: VimError.CanOnlyCompareListWithList() });\n      exprTest('{} == []', { error: VimError.CanOnlyCompareListWithList() });\n      exprTest('{} == 10', { error: VimError.CanOnlyCompareDictionaryWithDictionary() });\n      exprTest('0 == 0z00', { error: VimError.CanOnlyCompareBlobWithBlob() });\n      exprTest('2 == function(\"abs\")', { value: bool(false) });\n    });\n  });\n\n  suite('Conversions', () => {\n    suite('A stringified number can have only 1 sign in front of it', () => {\n      exprTest(\"+'123'\", { value: int(123) });\n\n      exprTest(\"+'-123'\", { value: int(-123) });\n      exprTest(\"+'+123'\", { value: int(123) });\n\n      exprTest(\"+'--123'\", { value: int(0) });\n      exprTest(\"+'++123'\", { value: int(0) });\n      exprTest(\"+'-+123'\", { value: int(0) });\n      exprTest(\"+'+-123'\", { value: int(0) });\n    });\n  });\n\n  suite('Operators', () => {\n    suite('Unary', () => {\n      suite('!', () => {\n        exprTest('!0', { value: int(1) });\n        exprTest('!1', { value: int(0) });\n        exprTest('!123', { value: int(0) });\n\n        exprTest('!0.0', { value: float(1.0) });\n        exprTest('!1.0', { value: float(0.0) });\n        exprTest('!123.0', { value: float(0.0) });\n\n        exprTest(\"!'0'\", { value: int(1) });\n        exprTest(\"!'1'\", { value: int(0) });\n        exprTest(\"!'xyz'\", { value: int(1) });\n        exprTest('![]', { error: VimError.UsingAListAsANumber() });\n        exprTest('!{}', { error: VimError.UsingADictionaryAsANumber() });\n      });\n\n      suite('+', () => {\n        exprTest('+5', { value: int(5) });\n        exprTest('+-5', { value: int(-5) });\n\n        exprTest('+5.0', { value: float(5) });\n        exprTest('+-5.0', { value: float(-5) });\n\n        exprTest(\"+'5'\", { value: int(5) });\n        exprTest(\"+'-5'\", { value: int(-5) });\n        exprTest(\"+'xyz'\", { value: int(0) });\n        exprTest('+[]', { error: VimError.UsingAListAsANumber() });\n        exprTest('+{}', { error: VimError.UsingADictionaryAsANumber() });\n      });\n\n      suite('-', () => {\n        exprTest('-5', { value: int(-5) });\n        exprTest('--5', { value: int(5) });\n\n        exprTest('-5.0', { value: float(-5) });\n        exprTest('--5.0', { value: float(5) });\n\n        exprTest(\"-'5'\", { value: int(-5) });\n        exprTest(\"-'-5'\", { value: int(5) });\n        exprTest(\"-'xyz'\", { value: int(-0) });\n        exprTest('-[]', { error: VimError.UsingAListAsANumber() });\n        exprTest('-{}', { error: VimError.UsingADictionaryAsANumber() });\n      });\n    });\n\n    suite('Binary', () => {\n      exprTest(\"'123' + '456'\", { value: int(579) });\n      exprTest(\"'123' . '456'\", { value: str('123456') });\n      exprTest(\"'123' .. '456'\", { value: str('123456') });\n      exprTest('123 . 456', { value: str('123456') });\n      exprTest('123 .. 456', { value: str('123456') });\n\n      suite('%', () => {\n        exprTest('75 % 22', { value: int(9) });\n        exprTest('75 % -22', { value: int(9) });\n        exprTest('-75 % 22', { value: int(-9) });\n        exprTest('-75 % -22', { value: int(-9) });\n\n        exprTest('5 % 0', { value: int(0) });\n        exprTest('-5 % 0', { value: int(0) });\n\n        exprTest('5.2 % 2.1', { error: VimError.CannotUseModuloWithFloat() });\n        exprTest('5.2 % 2', { error: VimError.CannotUseModuloWithFloat() });\n        exprTest('5 % 2.1', { error: VimError.CannotUseModuloWithFloat() });\n      });\n    });\n  });\n\n  suite('Builtin functions', () => {\n    suite('assert_*', () => {\n      const PASS = { value: int(0) };\n      const FAIL = { value: int(1) };\n\n      exprTest('assert_equal(1, 1)', PASS);\n      exprTest('assert_equal(1, 2)', FAIL);\n      exprTest('assert_equal(4, \"4\")', FAIL);\n\n      exprTest('assert_true(-123)', PASS);\n      exprTest('assert_true(0)', FAIL);\n\n      exprTest('assert_false(-123)', FAIL);\n      exprTest('assert_false(0)', PASS);\n\n      exprTest('assert_inrange(-123, 123, 4)', PASS);\n      exprTest('assert_inrange(-123, 123, -123)', PASS);\n      exprTest('assert_inrange(-123, 123, 123)', PASS);\n      exprTest('assert_inrange(-123, 123, 123.001)', FAIL);\n      exprTest('assert_inrange(-123, 123, -123.001)', FAIL);\n\n      exprTest(\"assert_match('^f.*o$', 'foo')\", PASS);\n      exprTest(\"assert_match('^f.*o$', 'foobar')\", FAIL);\n\n      exprTest('assert_report(\"whatever\")', FAIL);\n    });\n\n    suite('add', () => {\n      exprTest('add([1,2,3], 4)', { display: '[1, 2, 3, 4]' });\n      exprTest('add(add(add([], 1), 2), 3)', { display: '[1, 2, 3]' });\n\n      exprTest('add(0zABCD, 0xEF)', { display: '0zABCDEF' });\n    });\n\n    // TODO: byte2line()/line2byte()\n\n    suite('call', () => {\n      exprTest('call(\"abs\", [-1])', { value: float(1) });\n      exprTest('call(function(\"abs\"), [-1])', { value: float(1) });\n    });\n\n    suite('count', () => {\n      exprTest('count([1,2,3,2,3,2,1], 2)', { value: int(3) });\n      exprTest('count([\"apple\", \"banana\", \"Apple\", \"carrot\", \"APPLE\"], \"Apple\")', {\n        value: int(1),\n      });\n      exprTest('count([\"apple\", \"banana\", \"Apple\", \"carrot\", \"APPLE\"], \"Apple\", v:true)', {\n        value: int(3),\n      });\n      exprTest('count([\"apple\", \"banana\", \"Apple\", \"carrot\", \"APPLE\"], \"Apple\", v:true, 2)', {\n        value: int(2),\n      });\n      exprTest('count([\"apple\", \"banana\", \"Apple\", \"carrot\", \"APPLE\"], \"Apple\", v:true, -1)', {\n        value: int(1),\n      });\n\n      exprTest('count(#{a:3,b:2,c:3}, 3)', { value: int(2) });\n      exprTest('count(#{apple:\"apple\",b:\"banana\",c:\"APPLE\"}, \"apple\")', { value: int(1) });\n      exprTest('count(#{apple:\"apple\",b:\"banana\",c:\"APPLE\"}, \"apple\", v:true)', { value: int(2) });\n\n      exprTest('count(\"abcababaB\", \"ab\")', { value: int(3) });\n      exprTest('count(\"abcababaB\", \"ab\", v:true)', { value: int(4) });\n      exprTest('count(\"aaaaaaaaa\", \"aa\")', { value: int(4) });\n      exprTest('count(\"abc\", \"\")', { value: int(0) });\n    });\n\n    suite('empty', () => {\n      exprTest('empty(0)', { value: bool(true) });\n      exprTest('empty(0.0)', { value: bool(true) });\n      exprTest(\"empty('')\", { value: bool(true) });\n      exprTest('empty([])', { value: bool(true) });\n      exprTest('empty({})', { value: bool(true) });\n      exprTest('empty(0z)', { value: bool(true) });\n\n      exprTest('empty(1)', { value: bool(false) });\n      exprTest('empty(1.0)', { value: bool(false) });\n      exprTest(\"empty('xyz')\", { value: bool(false) });\n      exprTest('empty([0])', { value: bool(false) });\n      exprTest(\"empty({'k': 'v'})\", { value: bool(false) });\n      exprTest('empty(0z00)', { value: bool(false) });\n    });\n\n    suite('escape', () => {\n      exprTest(\"escape('abc', '')\", {\n        value: str('abc'),\n      });\n      exprTest(\"escape('c:\\\\program files\\\\vim', ' \\\\')\", {\n        value: str('c:\\\\\\\\program\\\\ files\\\\\\\\vim'),\n      });\n    });\n\n    suite('function', () => {\n      exprTest(\"function('abs')\", { display: 'abs' });\n      exprTest(\"function('abs', [])\", { display: 'abs' });\n      exprTest(\"function('abs', [-5])\", { display: \"function('abs', [-5])\" });\n      exprTest(\"function('abs', -5)\", { error: VimError.SecondArgumentOfFunction() });\n      exprTest(\"function('abs', '-5')\", { error: VimError.SecondArgumentOfFunction() });\n      exprTest(\"function('abs', [], [])\", { error: VimError.ExpectedADict() });\n      exprTest(\"function('abs', {}, {})\", { error: VimError.SecondArgumentOfFunction() });\n      exprTest(\"function('abs', [], {})\", { display: \"function('abs', {})\" });\n      exprTest(\"function('abs', [], #{x:5})\", { display: \"function('abs', {'x': 5})\" });\n\n      // Immediately invoke the funcref\n      exprTest(\"function('abs')(-5)\", { value: float(5) });\n      exprTest(\"function('abs', [-5])()\", { value: float(5) });\n      exprTest(\"function('or', [1])(64)\", { value: int(65) });\n    });\n\n    suite('flatten', () => {\n      exprTest('flatten([1, [2, [3, 4]], 5])', { display: '[1, 2, 3, 4, 5]' });\n      exprTest('flatten([1, [2, [3, 4]], 5], 1)', { display: '[1, 2, [3, 4], 5]' });\n      exprTest('flatten([1, [2, [3, 4]], 5], 0)', { display: '[1, [2, [3, 4]], 5]' });\n\n      exprTest('flatten({})', { error: VimError.ArgumentMustBeAList('flatten') });\n      exprTest('flatten([], -2)', { error: VimError.MaxDepthMustBeANonNegativeNumber() });\n    });\n\n    suite('float2nr', () => {\n      exprTest('float2nr(123)', { value: int(123) });\n      exprTest('float2nr(40.0)', { value: int(40) });\n      exprTest('float2nr(65.7)', { value: int(65) });\n      exprTest('float2nr(-20.7)', { value: int(-20) });\n\n      // TODO: Infinity, -Infinity, NaN\n    });\n\n    suite('fmod', () => {\n      exprTest('fmod(11, 3)', { value: float(2.0) });\n      exprTest('fmod(4.2, 1.0)', { display: '0.2' });\n      exprTest('fmod(4.2, -1.0)', { display: '0.2' });\n      exprTest('fmod(-4.2, 1.0)', { display: '-0.2' });\n      exprTest('fmod(-4.2, -1.0)', { display: '-0.2' });\n    });\n\n    // TODO: Re-enable after we fix circular dependency\n    suite.skip('fullcommand', () => {\n      for (const cmd of ['s', 'sub', ':%substitute']) {\n        exprTest(`fullcommand('${cmd}')`, { value: str('substitute') });\n      }\n      exprTest(`fullcommand('notarealthing')`, { value: str('') });\n    });\n\n    suite('get', () => {\n      exprTest('get([2,4,6], 1)', { value: int(4) });\n      exprTest('get([2,4,6], -1)', { value: int(6) });\n      exprTest('get([2,4,6], 3)', { value: int(0) });\n      exprTest('get([2,4,6], 3, 999)', { value: int(999) });\n\n      exprTest('get(0zABCDEF, 1)', { value: int(205) });\n      exprTest('get(0zABCDEF, -1)', { value: int(239) });\n      exprTest('get(0zABCDEF, 3)', { value: int(-1) });\n      exprTest('get(0zABCDEF, 3, 999)', { value: int(999) });\n\n      exprTest('get(#{a: 1, b: 2, c: 3}, \"b\")', { value: int(2) });\n      exprTest('get(#{a: 1, b: 2, c: 3}, \"x\")', { value: int(0) });\n      exprTest('get(#{a: 1, b: 2, c: 3}, \"x\", 999)', { value: int(999) });\n    });\n\n    suite('has_key', () => {\n      exprTest('has_key(#{a:1, b:2, c:3}, \"b\")', { value: bool(true) });\n      exprTest('has_key(#{a:1, b:2, c:3}, \"d\")', { value: bool(false) });\n    });\n\n    suite('id', () => {\n      exprTest('id(2+2) == id(4)', { value: bool(true) });\n      exprTest('id(2+2) == id(5)', { value: bool(false) });\n      // TODO: Everything else\n    });\n\n    suite('index', () => {\n      exprTest('index([\"a\",\"b\",\"c\"], \"c\")', { value: int(2) });\n      exprTest('index([\"a\",\"b\",\"c\"], \"k\")', { value: int(-1) });\n      exprTest('index([\"A\",\"C\",\"D\",\"C\"], \"C\", 1)', { value: int(1) });\n      exprTest('index([\"A\",\"C\",\"D\",\"C\"], \"C\", 2)', { value: int(3) });\n      exprTest('index([\"A\",\"C\",\"D\",\"C\"], \"C\", -2)', { value: int(3) });\n      exprTest('index([\"A\",\"C\",\"D\",\"C\"], \"C\", 5)', { value: int(-1) });\n    });\n\n    suite('insert', () => {\n      exprTest('insert([1,2,3], 4)', { display: '[4, 1, 2, 3]' });\n      exprTest('insert([1,2,3], 4, 2)', { display: '[1, 2, 4, 3]' });\n      exprTest('insert(insert(insert([], 1), 2), 3)', { display: '[3, 2, 1]' });\n\n      exprTest('insert(0zABCD, 0xEF)', { display: '0zEFABCD' });\n      exprTest('insert(0zABCD, 0xEF, 1)', { display: '0zABEFCD' });\n    });\n\n    suite('invert', () => {\n      exprTest('invert(123)', { value: int(-124) });\n    });\n\n    suite('isnan/isinf', () => {\n      exprTest('isnan(2.0 / 3.0)', { value: bool(false) });\n      exprTest('isnan(0.0 / 0.0)', { value: bool(true) });\n\n      exprTest('isinf(2.0 / 3.0)', { value: int(0) });\n      exprTest('isinf(1.0 / 0.0)', { value: int(1) });\n      exprTest('isinf(-1.0 / 0.0)', { value: int(-1) });\n    });\n\n    suite('join', () => {\n      exprTest('join([1,2,3])', { value: str('123') });\n      exprTest('join([1,2,3], \",\")', { value: str('1,2,3') });\n    });\n\n    suite('json_decode', () => {\n      exprTest(`json_decode('[1, 2.3, {\"a\": \"apple\", \"b\": [{}]}]')`, {\n        value: list([\n          int(1),\n          float(2.3),\n          dictionary(\n            new Map<string, Value>([\n              ['a', str('apple')],\n              ['b', list([dictionary(new Map())])],\n            ]),\n          ),\n        ]),\n      });\n    });\n\n    suite('json_encode', () => {\n      exprTest('json_encode([1, 2.3, #{a: 1, b: 2}])', { value: str('[1,2.3,{\"a\":1,\"b\":2}]') }); // TODO: Fix whitespace\n    });\n\n    suite('len', () => {\n      exprTest('len(12345)', { value: int(5) });\n      exprTest('len(012345)', { value: int(4) });\n      exprTest('len(-8)', { value: int(2) });\n      exprTest('len(\"hello world!\")', { value: int(12) });\n      exprTest('len([5, 2, 3, 7])', { value: int(4) });\n      exprTest('len(#{a:1, b:2, c:3})', { value: int(3) });\n      exprTest('len(function(\"abs\"))', { error: VimError.InvalidTypeForLen() });\n    });\n\n    suite('map', () => {\n      exprTest(\"map([10, 20, 30], 'v:key')\", {\n        value: list([int(0), int(1), int(2)]),\n      });\n      exprTest(\"map([10, 20, 30], 'v:val')\", {\n        value: list([int(10), int(20), int(30)]),\n      });\n      exprTest(\"map([10, 20, 30], 'v:key + v:val')\", {\n        value: list([int(10), int(21), int(32)]),\n      });\n\n      exprTest('map([10, 20, 30], {k -> 2 * k})', {\n        value: list([int(0), int(2), int(4)]),\n      });\n      exprTest('map([10, 20, 30], {k, v -> k + v})', {\n        value: list([int(10), int(21), int(32)]),\n      });\n\n      // TODO: map() with builtin Funcref\n    });\n\n    suite('max', () => {\n      exprTest('max([])', { value: int(0) });\n      exprTest('max({})', { value: int(0) });\n      exprTest('max([4, 3, 1, 5, 2])', { value: int(5) });\n      exprTest('max(#{ten:10,twenty:20,thirty:30})', { value: int(30) });\n      exprTest('max([1.2, 1.5])', { error: VimError.UsingAFloatAsANumber() });\n      exprTest(\"max('1,2,3')\", { error: VimError.ArgumentOfFuncMustBeAListOrDictionary('max') });\n    });\n    suite('min', () => {\n      exprTest('min([])', { value: int(0) });\n      exprTest('min({})', { value: int(0) });\n      exprTest('min([4, 3, 1, 5, 2])', { value: int(1) });\n      exprTest('min(#{ten:10,twenty:20,thirty:30})', { value: int(10) });\n      exprTest('min([1.2, 1.5])', { error: VimError.UsingAFloatAsANumber() });\n      exprTest(\"min('1,2,3')\", { error: VimError.ArgumentOfFuncMustBeAListOrDictionary('min') });\n    });\n\n    suite('tolower', () => {\n      exprTest(\"tolower('Hello, World!')\", { display: 'hello, world!' });\n      exprTest('tolower(123)', { display: '123' });\n      exprTest('tolower(1.23)', { error: VimError.UsingFloatAsAString() });\n    });\n    suite('toupper', () => {\n      exprTest(\"toupper('Hello, World!')\", { display: 'HELLO, WORLD!' });\n      exprTest('toupper(123)', { display: '123' });\n      exprTest('toupper(1.23)', { error: VimError.UsingFloatAsAString() });\n    });\n\n    suite('range', () => {\n      exprTest('range(4)', { display: '[0, 1, 2, 3]' });\n      exprTest('range(2, 4)', { display: '[2, 3, 4]' });\n      exprTest('range(2, 9, 3)', { display: '[2, 5, 8]' });\n      exprTest('range(2, -2, -1)', { display: '[2, 1, 0, -1, -2]' });\n      exprTest('range(2, -2, -2)', { display: '[2, 0, -2]' });\n      exprTest('range(0)', { display: '[]' });\n      exprTest('range(1, 10, 0)', { error: VimError.StrideIsZero() });\n      exprTest('range(2, 0)', { error: VimError.StartPastEnd() });\n      exprTest('range(0, 2, -1)', { error: VimError.StartPastEnd() });\n    });\n\n    suite('remove', () => {\n      exprTest('remove([1, 2, 3], 0)', { value: int(1) });\n      exprTest('remove([1, 2, 3], 1)', { value: int(2) });\n      exprTest('remove([1, 2, 3], -1)', { value: int(3) });\n      exprTest('remove(#{a:1, b:2}, \"a\")', { value: int(1) });\n    });\n\n    suite('repeat', () => {\n      exprTest('repeat(3, 5)', { display: '33333' });\n      exprTest('repeat(\"abc\", 3)', { display: 'abcabcabc' });\n      exprTest('repeat(\"\", 8)', { display: '' });\n      exprTest('repeat([], 3)', { display: '[]' });\n      exprTest('repeat([1,2], 3)', { display: '[1, 2, 1, 2, 1, 2]' });\n      exprTest('repeat(range(2,6,2), 3)', { display: '[2, 4, 6, 2, 4, 6, 2, 4, 6]' });\n      exprTest('repeat(1.0, 3)', { error: VimError.UsingFloatAsAString() });\n    });\n\n    suite('reverse', () => {\n      exprTest('reverse([1, 2, 3])', { display: '[3, 2, 1]' });\n      exprTest('reverse(0zABCDEF)', { display: '0zEFCDAB' });\n    });\n\n    suite('str2float', () => {\n      exprTest('str2float(\"5IGNORED\")', { value: float(5.0) });\n      exprTest('str2float(\"2.34IGNORED\")', { value: float(2.34) });\n      exprTest('str2float(\"infIGNORED\")', { value: float(Infinity) });\n      exprTest('str2float(\"-infIGNORED\")', { value: float(-Infinity) });\n      exprTest('str2float(\"nanIGNORED\")', { value: float(NaN) });\n      exprTest('str2float(\"12,345.67\")', { value: float(12.0) });\n      exprTest('str2float(\"1.2e4\")', { value: float(12000) });\n    });\n\n    suite('str2list', () => {\n      exprTest('str2list(\"ABC\")', { value: list([int(65), int(66), int(67)]) });\n      exprTest('str2list(\"á\")', { value: list([int(97), int(769)]) });\n    });\n\n    suite('str2nr', () => {\n      exprTest('str2nr(\"123\")', { value: int(123) });\n      exprTest('str2nr(\"1001010110\", 2)', { value: int(598) });\n      exprTest('str2nr(\"123\", 8)', { value: int(83) });\n      exprTest('str2nr(\"123\", 10)', { value: int(123) });\n      exprTest('str2nr(\"DEADBEEF\", 16)', { value: int(3735928559) });\n      exprTest('str2nr(\"DEADBEEF\", 10)', { value: int(0) });\n      exprTest('str2nr(\"DEADBEEF\", 9)', { error: VimError.InvalidArgument474() });\n\n      exprTest('str2nr(\"0xDEADBEEF\", 16)', { value: int(3735928559) });\n      exprTest('str2nr(\"0XDEADBEEF\", 16)', { value: int(3735928559) });\n      exprTest('str2nr(\"0o123\", 8)', { value: int(83) });\n      exprTest('str2nr(\"0O123\", 8)', { value: int(83) });\n      exprTest('str2nr(\"0b1001010110\", 2)', { value: int(598) });\n      exprTest('str2nr(\"0B1001010110\", 2)', { value: int(598) });\n    });\n\n    suite('stridx', () => {\n      exprTest('stridx(\"0123456789\", \"6\")', { value: int(6) });\n      exprTest('stridx(\"0123456789\", \"456\")', { value: int(4) });\n      exprTest('stridx(\"0123456789\", \"X\")', { value: int(-1) });\n    });\n\n    suite('string', () => {\n      exprTest('string(\"\")', { value: str('') });\n      exprTest('string(123)', { value: str('123') });\n      exprTest('string(123.0)', { value: str('123.0') });\n      exprTest('string([1,2,3])', { value: str('[1, 2, 3]') });\n      exprTest('string(#{a:1,b:2})', { value: str(\"{'a': 1, 'b': 2}\") });\n    });\n\n    suite('strlen', () => {\n      exprTest('strlen(\"\")', { value: int(0) });\n      exprTest('strlen(\"654321\")', { value: int(6) });\n      exprTest('strlen(654321)', { value: int(6) });\n      exprTest('strlen([1,2,3])', { error: VimError.UsingListAsAString() });\n    });\n\n    suite('strpart', () => {\n      exprTest('strpart(\"abcdefg\", 3, 2)', { value: str('de') });\n      exprTest('strpart(\"abcdefg\", -2, 4)', { value: str('ab') });\n      exprTest('strpart(\"abcdefg\", 5, 4)', { value: str('fg') });\n      exprTest('strpart(\"abcdefg\", 3)', { value: str('defg') });\n    });\n\n    suite('split', () => {\n      exprTest('split(\"  a\\t\\tb    c  \")', { value: list([str('a'), str('b'), str('c')]) });\n      exprTest('split(\"  a\\t\\tb    c  \", \"\", 1)', {\n        value: list([str(''), str('a'), str('b'), str('c'), str('')]),\n      });\n      exprTest('split(\"a,b,c,\", \",\")', { value: list([str('a'), str('b'), str('c')]) });\n      exprTest('split(\"a,b,c,\", \",\", v:true)', {\n        value: list([str('a'), str('b'), str('c'), str('')]),\n      });\n    });\n\n    suite('tr', () => {\n      exprTest(\"tr('whatever', 'short', 'longer')\", {\n        error: VimError.InvalidArgument475('short'),\n      });\n      exprTest(\"tr('hello there', 'ht', 'HT')\", { value: str('Hello THere') });\n    });\n\n    suite('trim', () => {\n      exprTest(\"trim('  me  ')\", { value: str('me') });\n      exprTest(\"trim('  me  ', ' ', 0)\", { value: str('me') });\n      exprTest(\"trim('  me  ', ' ', 1)\", { value: str('me  ') });\n      exprTest(\"trim('  me  ', ' ', 2)\", { value: str('  me') });\n      // TODO: Test mask\n    });\n\n    suite('uniq', () => {\n      exprTest('uniq([1, 2, 3])', { display: '[1, 2, 3]' });\n      exprTest('uniq([1, 1, 2, 2, 3, 3])', { display: '[1, 2, 3]' });\n      exprTest('uniq([1, 2, 1, 3, 2, 3])', { display: '[1, 2, 1, 3, 2, 3]' });\n      exprTest(\"uniq([1, '1'])\", { display: \"[1, '1']\" });\n      exprTest(\"uniq(['1', 1])\", { display: \"['1', 1]\" });\n      exprTest(\"uniq([1, 2, 1, 1, 1, '1', 3, 2, 2, 3])\", { display: \"[1, 2, 1, '1', 3, 2, 3]\" });\n    });\n\n    suite('floor/ceil/round/trunc', () => {\n      exprTest('floor(3.5)', { value: float(3) });\n      exprTest('floor(-3.5)', { value: float(-4) });\n\n      exprTest('ceil(3.5)', { value: float(4) });\n      exprTest('ceil(-3.5)', { value: float(-3) });\n\n      exprTest('round(3.5)', { value: float(4) });\n      exprTest('round(-3.5)', { value: float(-4) });\n\n      exprTest('trunc(3.5)', { value: float(3) });\n      exprTest('trunc(-3.5)', { value: float(-3) });\n    });\n\n    suite('keys/values/items', () => {\n      exprTest('keys({})', { value: list([]) });\n      exprTest('sort(keys({\"a\": 1, \"b\": 2}))', {\n        value: list([str('a'), str('b')]),\n      });\n\n      exprTest('values({})', { value: list([]) });\n      exprTest('sort(values({\"a\": 1, \"b\": 2}))', {\n        value: list([int(1), int(2)]),\n      });\n\n      exprTest('items({})', { value: list([]) });\n      exprTest('sort(items({\"a\": 1, \"b\": 2}))', {\n        value: list([list([str('a'), int(1)]), list([str('b'), int(2)])]),\n      });\n    });\n\n    suite('sort', () => {\n      exprTest('sort([])', { value: list([]) });\n\n      exprTest(\"sort(['A', 'c', 'B', 'a', 'C', 'b'])\", {\n        display: \"['A', 'B', 'C', 'a', 'b', 'c']\",\n      });\n\n      for (const func of [\"'i'\", \"'1'\", '1']) {\n        exprTest(`sort(['A', 'c', 'B', 'a', 'C', 'b'], ${func})`, {\n          display: \"['A', 'a', 'B', 'b', 'c', 'C']\",\n        });\n      }\n\n      exprTest('sort([4,2,1,3,5])', { display: '[1, 2, 3, 4, 5]' });\n      exprTest('sort([4,2,1,3,5], {x,y->x-y})', { display: '[1, 2, 3, 4, 5]' });\n      exprTest('sort([4,2,1,3,5], {x,y->y-x})', { display: '[5, 4, 3, 2, 1]' });\n      // TODO\n    });\n  });\n});\n"
  },
  {
    "path": "test/vimscript/lineRangeParse.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { Address, LineRange } from '../../src/vimscript/lineRange';\nimport { Pattern, SearchDirection } from '../../src/vimscript/pattern';\n\nfunction parseTest(name: string, input: string, output: LineRange) {\n  test(name, () => {\n    assert.deepStrictEqual(LineRange.parser.tryParse(input), output);\n  });\n}\n\nsuite('LineRange parsing', () => {\n  suite('Basic', () => {\n    parseTest('number', '123', new LineRange(new Address({ type: 'number', num: 123 })));\n    parseTest('current_line', '.', new LineRange(new Address({ type: 'current_line' })));\n    parseTest('last_line', '$', new LineRange(new Address({ type: 'last_line' })));\n    parseTest('entire_file', '%', new LineRange(new Address({ type: 'entire_file' })));\n    parseTest('last_visual_range', '*', new LineRange(new Address({ type: 'last_visual_range' })));\n    parseTest(\"mark ('a)\", \"'a\", new LineRange(new Address({ type: 'mark', mark: 'a' })));\n    parseTest(\"mark ('A)\", \"'A\", new LineRange(new Address({ type: 'mark', mark: 'A' })));\n    parseTest(\"mark ('<)\", \"'<\", new LineRange(new Address({ type: 'mark', mark: '<' })));\n    parseTest(\n      'pattern_next (no closing /)',\n      '/abc',\n      new LineRange(\n        new Address({\n          type: 'pattern_next',\n          pattern: Pattern.parser({ direction: SearchDirection.Forward }).tryParse('abc'),\n        }),\n      ),\n    );\n    parseTest(\n      'pattern_next (closing /)',\n      '/abc/',\n      new LineRange(\n        new Address({\n          type: 'pattern_next',\n          pattern: Pattern.parser({\n            direction: SearchDirection.Forward,\n            delimiter: '/',\n          }).tryParse('abc/'),\n        }),\n      ),\n    );\n    parseTest(\n      'pattern_prev (no closing ?)',\n      '?abc',\n      new LineRange(\n        new Address({\n          type: 'pattern_prev',\n          pattern: Pattern.parser({ direction: SearchDirection.Backward }).tryParse('abc'),\n        }),\n      ),\n    );\n    parseTest(\n      'pattern_prev (closing ?)',\n      '?abc?',\n      new LineRange(\n        new Address({\n          type: 'pattern_prev',\n          pattern: Pattern.parser({\n            direction: SearchDirection.Backward,\n            delimiter: '?',\n          }).tryParse('abc?'),\n        }),\n      ),\n    );\n    parseTest(\n      'last_search_pattern_next',\n      '\\\\/',\n      new LineRange(new Address({ type: 'last_search_pattern_next' })),\n    );\n    parseTest(\n      'last_search_pattern_prev',\n      '\\\\?',\n      new LineRange(new Address({ type: 'last_search_pattern_prev' })),\n    );\n    parseTest(\n      'last_substitute_pattern_next',\n      '\\\\&',\n      new LineRange(new Address({ type: 'last_substitute_pattern_next' })),\n    );\n  });\n\n  for (const sep of [',', ';'] as const) {\n    suite(`Start and end (${sep})`, () => {\n      parseTest(\n        'Separator but no second address',\n        `5${sep}`,\n        new LineRange(new Address({ type: 'number', num: 5 }), sep),\n      );\n      parseTest(\n        'Separator but no first address',\n        `${sep}5`,\n        new LineRange(\n          new Address({ type: 'current_line' }),\n          sep,\n          new Address({ type: 'number', num: 5 }),\n        ),\n      );\n      parseTest(\n        'Separator but no address at all',\n        `${sep}`,\n        new LineRange(new Address({ type: 'current_line' }), sep),\n      );\n      parseTest(\n        'Two numbers',\n        `14${sep}23`,\n        new LineRange(\n          new Address({ type: 'number', num: 14 }),\n          sep,\n          new Address({ type: 'number', num: 23 }),\n        ),\n      );\n      parseTest(\n        'Two numbers (out of order)',\n        `123${sep}6`,\n        new LineRange(\n          new Address({ type: 'number', num: 123 }),\n          sep,\n          new Address({ type: 'number', num: 6 }),\n        ),\n      );\n      parseTest(\n        'Visual selection using marks',\n        `'<${sep}'>`,\n        new LineRange(\n          new Address({ type: 'mark', mark: '<' }),\n          sep,\n          new Address({ type: 'mark', mark: '>' }),\n        ),\n      );\n    });\n\n    suite('Whitespace', () => {\n      parseTest(\n        'Spacing between numbers adds them',\n        '1 2 3 , 4  5  6',\n        new LineRange(\n          new Address({ type: 'number', num: 1 }, 5),\n          ',',\n          new Address({ type: 'number', num: 4 }, 11),\n        ),\n      );\n    });\n  }\n});\n"
  },
  {
    "path": "test/vimscript/lineRangeResolve.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { LineRange } from '../../src/vimscript/lineRange';\nimport { ITestObject, testIt } from '../testSimplifier';\nimport { setupWorkspace } from '../testUtils';\n\nfunction resolveTest(input: ITestObject & { lineRanges: Record<string, [number, number]> }) {\n  suite(input.title, () => {\n    for (const lineRange in input.lineRanges) {\n      if (lineRange in input.lineRanges) {\n        test(lineRange, async () => {\n          const modeHandler = await testIt(input);\n          assert.deepStrictEqual(\n            LineRange.parser.tryParse(lineRange).resolve(modeHandler.vimState),\n            {\n              start: input.lineRanges[lineRange][0],\n              end: input.lineRanges[lineRange][1],\n            },\n          );\n        });\n      }\n    }\n  });\n}\n\nsuite('LineRange resolving', () => {\n  setup(async () => {\n    await setupWorkspace();\n  });\n\n  resolveTest({\n    title: 'Basic',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: '',\n    end: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    lineRanges: {\n      '0': [0, 0],\n      '1': [0, 0],\n      '2': [1, 1],\n      '.': [2, 2],\n      $: [5, 5],\n      '%': [0, 5],\n    },\n  });\n\n  resolveTest({\n    title: 'Marks',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: 'ma' + 'j' + 'mB' + 'j',\n    end: ['one', 'two', 'three', 'four', 'fi|ve', 'six'],\n    lineRanges: {\n      \"'a\": [2, 2],\n      \"'B\": [3, 3],\n    },\n  });\n\n  resolveTest({\n    title: 'Last visual selection',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: 'v' + 'j' + 'j' + '<Esc>',\n    end: ['one', 'two', 'three', 'four', 'fi|ve', 'six'],\n    lineRanges: {\n      // TODO: *,4 is not a valid range\n      '*': [2, 4],\n      \"'<,'>\": [2, 4],\n      '1,*': [2, 4],\n      '1;*': [2, 4],\n    },\n  });\n\n  resolveTest({\n    title: 'Offsets',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: '',\n    end: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    lineRanges: {\n      '+': [3, 3],\n      '-': [1, 1],\n      '5+': [5, 5],\n      '5++': [6, 6],\n      '5+2': [6, 6],\n      '5-': [3, 3],\n      '5--': [2, 2],\n      '5-2': [2, 2],\n      '5+-': [4, 4],\n      '5+-2': [3, 3],\n      '5+2-': [5, 5],\n      '5+2-2': [4, 4],\n    },\n  });\n\n  resolveTest({\n    title: 'Comma vs. Semicolon',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: '',\n    end: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    lineRanges: {\n      '2,.': [1, 2],\n      '2;.': [1, 1],\n      '.+1,.+2': [3, 4],\n      '.+1;.+2': [3, 5],\n    },\n  });\n\n  resolveTest({\n    title: '% used as start or end',\n    start: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    keysPressed: '',\n    end: ['one', 'two', 'th|ree', 'four', 'five', 'six'],\n    lineRanges: {\n      '%,%': [0, 5],\n      '4,%': [0, 5],\n      '%,4': [3, 5],\n    },\n  });\n\n  resolveTest({\n    title: 'Explicit pattern (forward)',\n    start: ['ap|ple', 'banana', 'carrot', 'dragonfruit', 'eggplant'],\n    keysPressed: '',\n    end: ['ap|ple', 'banana', 'carrot', 'dragonfruit', 'eggplant'],\n    lineRanges: {\n      '/carrot': [2, 2],\n      '/carrot/': [2, 2],\n      '/carrot/,/dragonfruit': [2, 3],\n      '/carrot/,/dragonfruit/': [2, 3],\n      '/(an){2}/,/[^a]g/': [1, 4],\n    },\n  });\n\n  resolveTest({\n    title: 'Last searched pattern',\n    start: ['apple', 'banana', '|carrot', 'dragonfruit', 'eggplant'],\n    keysPressed: '/n\\n',\n    end: ['apple', 'banana', 'carrot', 'drago|nfruit', 'eggplant'],\n    lineRanges: {\n      '\\\\?,\\\\/': [1, 4],\n    },\n  });\n\n  resolveTest({\n    title: 'Last substitute pattern',\n    start: ['apple', 'ba|nana', 'carrot', 'dragonfruit', 'eggplant'],\n    keysPressed: ':s/gonf/x\\n',\n    end: ['apple', 'ba|nana', 'carrot', 'dragonfruit', 'eggplant'],\n    lineRanges: {\n      '\\\\&': [3, 3],\n    },\n  });\n});\n"
  },
  {
    "path": "test/vimscript/searchOffset.test.ts",
    "content": "import { strict as assert } from 'assert';\nimport { SearchOffset } from '../../src/vimscript/pattern';\n\nfunction parseTest(name: string, input: string, output: SearchOffset) {\n  test(name, () => {\n    assert.deepStrictEqual(SearchOffset.parser.tryParse(input), output);\n  });\n}\n\nsuite('SearchOffset parsing', () => {\n  parseTest('+', '+', new SearchOffset({ type: 'lines', delta: 1 }));\n  parseTest('-', '-', new SearchOffset({ type: 'lines', delta: -1 }));\n\n  parseTest('[num]', '123', new SearchOffset({ type: 'lines', delta: 123 }));\n  parseTest('+[num]', '+123', new SearchOffset({ type: 'lines', delta: 123 }));\n  parseTest('-[num]', '-123', new SearchOffset({ type: 'lines', delta: -123 }));\n\n  parseTest('e', 'e', new SearchOffset({ type: 'chars_from_end', delta: 0 }));\n  parseTest('e+', 'e+', new SearchOffset({ type: 'chars_from_end', delta: 1 }));\n  parseTest('e-', 'e-', new SearchOffset({ type: 'chars_from_end', delta: -1 }));\n  parseTest('e[num]', 'e123', new SearchOffset({ type: 'chars_from_end', delta: 123 }));\n  parseTest('e+[num]', 'e+123', new SearchOffset({ type: 'chars_from_end', delta: 123 }));\n  parseTest('e-[num]', 'e-123', new SearchOffset({ type: 'chars_from_end', delta: -123 }));\n\n  parseTest('s', 's', new SearchOffset({ type: 'chars_from_start', delta: 0 }));\n  parseTest('s+', 's+', new SearchOffset({ type: 'chars_from_start', delta: 1 }));\n  parseTest('s-', 's-', new SearchOffset({ type: 'chars_from_start', delta: -1 }));\n  parseTest('s[num]', 's123', new SearchOffset({ type: 'chars_from_start', delta: 123 }));\n  parseTest('s+[num]', 's+123', new SearchOffset({ type: 'chars_from_start', delta: 123 }));\n  parseTest('s-[num]', 's-123', new SearchOffset({ type: 'chars_from_start', delta: -123 }));\n\n  parseTest('b', 'b', new SearchOffset({ type: 'chars_from_start', delta: 0 }));\n  parseTest('b+', 'b+', new SearchOffset({ type: 'chars_from_start', delta: 1 }));\n  parseTest('b-', 'b-', new SearchOffset({ type: 'chars_from_start', delta: -1 }));\n  parseTest('b[num]', 'b123', new SearchOffset({ type: 'chars_from_start', delta: 123 }));\n  parseTest('b+[num]', 'b+123', new SearchOffset({ type: 'chars_from_start', delta: 123 }));\n  parseTest('b-[num]', 'b-123', new SearchOffset({ type: 'chars_from_start', delta: -123 }));\n\n  // TODO: ;{pattern}\n});\n\n// TODO: Write these tests\n// suite('SearchOffset application', () => {});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"ES2021\"],\n    \"module\": \"commonjs\",\n    \"target\": \"ES2021\",\n    \"outDir\": \"out\",\n    \"noImplicitOverride\": true,\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"sourceMap\": true,\n    \"strict\": true,\n    \"useUnknownInCatchVariables\": false,\n    \"experimentalDecorators\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"platform/*\": [\"src/platform/node/*\"]\n    },\n    \"resolveJsonModule\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"esModuleInterop\": true\n    // \"isolatedModules\": true,\n  },\n  \"exclude\": [\"node_modules\", \"!node_modules/@types\"]\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "//@ts-check\n\n'use strict';\n\nconst path = require('path');\nconst webpack = require('webpack');\nconst { CleanWebpackPlugin } = require('clean-webpack-plugin');\nconst ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');\nconst CircularDependencyPlugin = require('circular-dependency-plugin');\n\n/**@type {import('webpack').Configuration}*/\nconst config = {\n  target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/\n\n  mode: 'production',\n\n  entry: './extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/\n  output: {\n    // the bundle is stored in the 'out' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/\n    path: path.resolve(__dirname, 'out'),\n    filename: 'extension.js',\n    libraryTarget: 'commonjs2',\n    devtoolModuleFilenameTemplate: '../[resource-path]',\n  },\n  externals: {\n    vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/\n  },\n  resolve: {\n    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader\n    extensions: ['.ts', '.js'],\n    alias: {\n      path: 'path-browserify',\n      platform: path.resolve(__dirname, 'src', 'platform', 'node'),\n    },\n  },\n  optimization: {\n    minimize: true,\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        exclude: /node_modules/,\n        loader: 'ts-loader',\n        options: {\n          // Don't type check - ForkTsCheckerWebpackPlugin does this faster\n          transpileOnly: true,\n        },\n      },\n    ],\n  },\n  plugins: [\n    new CleanWebpackPlugin({\n      cleanOnceBeforeBuildPatterns: [], // disable initial clean\n    }),\n    new ForkTsCheckerWebpackPlugin(),\n    new CircularDependencyPlugin({\n      // exclude detection of files based on a RegExp\n      exclude: /node_modules/,\n      // include specific files based on a RegExp\n      include: /src/,\n      // add errors to webpack instead of warnings\n      failOnError: true,\n      // allow import cycles that include an asyncronous import,\n      // e.g. via import(/* webpackMode: \"weak\" */ './file.js')\n      allowAsyncCycles: true,\n      // set the current working directory for displaying module paths\n      cwd: process.cwd(),\n    }),\n  ],\n};\n\n/**@type {import('webpack').Configuration}*/\nconst nodelessConfig = {\n  target: 'webworker', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/\n\n  mode: 'development',\n\n  entry: './extensionWeb.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/\n  output: {\n    // the bundle is stored in the 'out' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/\n    path: path.resolve(__dirname, 'out'),\n    filename: 'extensionWeb.js',\n    libraryTarget: 'umd',\n  },\n  externals: {\n    vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/\n  },\n  resolve: {\n    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader\n    extensions: ['.ts', '.js'],\n    alias: {\n      platform: path.resolve(__dirname, 'src', 'platform', 'browser'),\n    },\n    fallback: {\n      os: require.resolve('os-browserify/browser'),\n      path: require.resolve('path-browserify'),\n      process: require.resolve('process/browser'),\n      util: require.resolve('util'),\n    },\n  },\n  optimization: {\n    minimize: true,\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.ts$/,\n        exclude: /node_modules/,\n        loader: 'ts-loader',\n        options: {\n          // Don't type check - ForkTsCheckerWebpackPlugin does this faster\n          transpileOnly: true,\n        },\n      },\n    ],\n  },\n  plugins: [\n    new webpack.IgnorePlugin({\n      resourceRegExp: /\\/neovim$/,\n    }),\n    new webpack.IgnorePlugin({\n      resourceRegExp: /\\/imswitcher$/,\n    }),\n    new webpack.IgnorePlugin({\n      resourceRegExp: /\\/vimrc$/,\n    }),\n    new webpack.IgnorePlugin({\n      resourceRegExp: /child_process$/,\n    }),\n    new webpack.ProvidePlugin({\n      process: 'process/browser', // util requires this internally\n    }),\n    new ForkTsCheckerWebpackPlugin(),\n  ],\n};\n\nmodule.exports = [config, nodelessConfig];\n"
  },
  {
    "path": "webpack.dev.js",
    "content": "const merge = require('webpack-merge');\nconst prod_configs = require('./webpack.config.js');\n\nmodule.exports = [\n  merge.merge(prod_configs[0], {\n    mode: 'development',\n    devtool: 'source-map',\n    optimization: {\n      minimize: false,\n      removeAvailableModules: false,\n      removeEmptyChunks: false,\n      splitChunks: false,\n    },\n  }),\n];\n"
  }
]