{"id":16369747,"url":"https://github.com/dcsunset/vscode-modal-editor","last_synced_at":"2025-03-16T15:33:12.091Z","repository":{"id":41174856,"uuid":"479856779","full_name":"DCsunset/vscode-modal-editor","owner":"DCsunset","description":"Customizable extension to turn VS Code into a modal editor","archived":false,"fork":false,"pushed_at":"2024-08-13T22:28:02.000Z","size":439,"stargazers_count":40,"open_issues_count":3,"forks_count":4,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-16T03:41:28.757Z","etag":null,"topics":["helix","keymaps","modal-editing","vscode","vscode-extension"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DCsunset.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-09T22:23:34.000Z","updated_at":"2025-03-15T03:08:07.000Z","dependencies_parsed_at":"2024-08-14T01:22:59.301Z","dependency_job_id":null,"html_url":"https://github.com/DCsunset/vscode-modal-editor","commit_stats":null,"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DCsunset%2Fvscode-modal-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DCsunset%2Fvscode-modal-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DCsunset%2Fvscode-modal-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DCsunset%2Fvscode-modal-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DCsunset","download_url":"https://codeload.github.com/DCsunset/vscode-modal-editor/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243891359,"owners_count":20364577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["helix","keymaps","modal-editing","vscode","vscode-extension"],"created_at":"2024-10-11T02:56:11.872Z","updated_at":"2025-03-16T15:33:11.780Z","avatar_url":"https://github.com/DCsunset.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# vscode-modal-editor\n\n[![Open VSX](https://badgen.net/open-vsx/version/DCsunset/vscode-modal-editor?label=Open%20VSX)](https://open-vsx.org/extension/DCsunset/vscode-modal-editor)\n[![VS Marketplace](https://badgen.net/vs-marketplace/v/DCsunset.vscode-modal-editor)](https://marketplace.visualstudio.com/items?itemName=DCsunset.vscode-modal-editor)\n\nCustomizable extension to turn VS Code into a modal editor.\n\n## Installation\n\nThis extension is published on both [Open VSX](https://open-vsx.org/extension/DCsunset/vscode-modal-editor)\nand [VS Marketplace](https://marketplace.visualstudio.com/items?itemName=DCsunset.vscode-modal-editor).\nYou can install it via either registry.\n\nYou can also download the extension directly from GitHub [releases](https://github.com/DCsunset/vscode-modal-editor/releases).\n\nTo try the latest unpublished version,\nrun the following commands in a shell (without comments):\n\n```sh\n# clone the repo\ngit clone https://github.com/DCsunset/vscode-modal-editor.git\ncd vscode-modal-editor\n# Install dependencies\nnpm i\n# Build extension\nnpm run build\n```\n\nThen you should see a `.vsix` file in the same directory.\nInstall it manually into VSCode and reload the window.\n\n## Usage\n\nTo use this extension, first you need to import keybindings.\nYou can define your own keybindings based on the tutorial.\nOr you can use the preset in this repository as a start point.\n\nYou can put different keybindings in the preset directory (default is `~/.config/vscode-modal-editor`),\nso that you can switch between them quickly.\n\nTo get started, you may want to try the helix preset provided in this repo.\nTo load it, import the preset using URI directly:\n\u003chttps://raw.githubusercontent.com/DCsunset/vscode-modal-editor/main/presets/helix.js\u003e\n\n\n## Features\n\n* Easy to customize your key type to emulate other modal editors.\n* Recursive keymaps (easy to add multi-stage keybindings)\n\n## Todo\n\n- [ ] Support binding commands to events\n\n\n## How it works\n\nThis extension can only capture normal characters typed in modes except for insert mode.\nFor special keys like `Esc`, `Ctrl` or `Alt`, they are handled by VS Code directly.\nSo if you want to bind those keys to commands,\nyou can directly map them in `keybindings.json`.\n\nThis extension sets the context `modalEditor.mode`\nso you can use it in `when` conditions in `keybinding.json`.\n\n## Importing Keybindings\n\nTo add keybindings, you can create a config file in `json`, `jsonc`, or `js` format.\n(for `js` format, export the configuration object using `module.exports`).\n\nTo import existing keybindings, run the command \"Modal Editor: Import Keybindings\" from the command palette.\nYou can select a file or use a URI.\n\nThere is a preset for helix in this repository.\nIt is just a demo and you can easily create your own based on it.\n\n\n## Presets\n\nThe design of this extension mainly follows [helix](https://github.com/helix-editor/helix),\nso I create a preliminary [preset](./presets/helix.js) for it.\nIt doesn't implement all helix features and some actions may have slight differences.\n\nYou are encouraged to define your own keybindings\nsince this extension aims at a general modal editor.\n\n\n## Commands\n\nThis extension define some extra VS commands.\nThey are listed as follows:\n\n| Command | Arguments | Description |\n| - | - | - |\n| `modalEditor.setMode` | `string` | Set the current mode |\n| `modalEditor.setInsertMode` | - | Set to insert mode (and clear selections if `clearSelectionsOnInsertMode` is enabled) |\n| `modalEditor.setNormalMode` | - | Set to normal mode |\n| `modalEditor.setSelectMode` | - | Set to select mode |\n| `modalEditor.setCommandMode` | - | Set to command mode |\n| `modalEditor.setKeys` | `string` | Change current key sequence without applying it. Value should be a js expression (useful for modifying unexecuted commands) |\n| `modalEditor.gotoLine` | `number` | Go to the specified line |\n| `modalEditor.findText` | `FindTextArgs` | Find and move cursor to text |\n| `modalEditor.cut` | `YankArgs` | Cut the selection to a register |\n| `modalEditor.yank` | `YankArgs` | Yank the selection to a register |\n| `modalEditor.paste` | `PasteArgs` | Paste content from a register |\n| `modalEditor.delete` | - | Delete content in selection |\n| `modalEditor.halfPageUp` | - | Move cursor half page up |\n| `modalEditor.halfPageDown` | - | Move cursor half page down |\n| `modalEditor.toUpperCase` | - | Transform current selection(s) to upper case |\n| `modalEditor.toLowerCase` | - | Transform current selection(s) to lower case |\n| `modalEditor.transform` | `TransformArgs` | Transform current selection(s) using a user-defined function |\n| `modalEditor.clearSelections` | - | Clear existing selections but keep all cursors |\n| `modalEditor.replayRecord` | `string` | Replay last recorded key sequence |\n| `modalEditor.executeCommand` | `Command` | Execute a command based on the current context |\n| `modalEditor.resetState` | - | Reset internal state |\n| `modalEditor.importKeybindings` | - | Import keybindings |\n| `modalEditor.importPreset` | `string?` | Import keybindings from preset dir or a specified dir |\n\n\nTypes defined in the above table:\n\n```ts\ntype FindTextArgs = {\n  /// String to find\n  text: string,\n  /// Whether to select text\n  select: boolean,\n  /// Whether to move till the text rather than to the text (default: false)\n  till?: boolean,\n  /// Within the ine of current cursor (default: false)\n  withinLine?: boolean,\n  /// Search backward (default: false)\n  backward?: boolean,\n  /// Whether to search using regex (default: false)\n  regex?: boolean,\n};\n\ntype YankArgs = {\n  /**\n   * Yank to a register (default: `\"`)\n   * (empty string for system clipboard)\n   */\n  register?: string\n};\n\ntype PasteArgs = {\n  /**\n   * Paste from a register (default: `\"`)\n   * (empty string for system clipboard)\n   */\n  register?: string,\n  /// Paste before the current selection\n  before?: boolean\n}\n\ntype TransformArgs = {\n  /// A function for transforming text in selections.\n  transformer: (_: string) =\u003e string\n};\n```\n\n## Settings\n\nVarious settings can be found in settings page by searching `modalEditor`.\n\nOne important setting is `keybindingsInSettings`.\nit decides whether this extension should store the current keybindings in User Settings.\nDefault is `true`.\nIf set to `false`, you may want to set `autoloadPreset` to a preset file that should be autoloaded on startup.\n\nFor the default mode, initial valid options are `normal`, `insert`, and `select`.\nIf you add custom mode, you can also set it here.\nThat's why its type is string instead of enum.\n\nStyles are also customizable for each mode.\nTo change the cursor style or status text for a mode, you can add the following settings to `modalEditor.styles`:\n(you can set just one property and the remaining properties are default values)\n\n```json\n{\n  \"insert\": {\n    \"cursorStyle\": \"line\",\n    \"statusText\": \"-- insert --\"\n  }\n}\n```\n\nAvailable cursor styles can be found [here](https://github.com/microsoft/vscode/blob/0b5aad37bd56218493863731950980d1c3a90c26/src/vs/editor/common/config/editorOptions.ts#L223).\n\n\n## Tutorial to Customize Keybindings\n\n### Basics\n\nThere are 4 predefined modes (`normal`, `insert`, `select`, `command`) in this extension,\nbut you are free to add more modes.\nNote that the mode name shouldn't start with underscore `_` as it is reserved for other config.\n\nKeybindings can be defined for all modes except for insert mode,\nbecause this extension will handle over to VS Code in insert mode.\n\nEach key sequence can be prefixed with a number indicating the count.\nThe count value will be stored in the `CommandContext`,\nwhich can be used in the js expression of `ComplexCommand`.\n\nCommand mode is another different mode because it maps a key sequence instead of each key to a command.\nIt doesn't support sub-keymap or count prefix as well.\nA newline character is used to indicate the end of a command.\n\nThe keybindings object is defined in the following format (in TypeScript):\n\n```ts\ntype Keymap = {\n  [key: string]: Keymap | Command\n}\ntype Keybindings = {\n  [mode: string]: Keymap\n}\n```\n\nThe above definition means that each mode has a separate keymap,\neach keymap maps a key to a sub-keymap or a command.\nRecursive keymaps are useful to define multi-stage commands.\nNote that the key must be a single character.\nIf you need map a key sequence to a command, you can use a recursive keymap.\n\nYour config file should export a `Keybindings` object.\n\nThere's a special mode `\"\"` in `Keybindings` which means common keybindings.\nIt is shared by all the modes (except insert mode),\nand it can be overwritten by other modes.\n\nThere's also a special key `\"\"` in `Keymap` which represents a wildcard character\nto match any character.\nIt has the lowest priority and can be overwritten by other keys.\n\n### Commands\n\nThe command can be a string (which means a VS Code command),\na list of commands,\nor a complex command object:\n\n```ts\ntype ComplexCommand = {\n  command: Command,\n  /// args for that command (only if it's a simple command)\n  args?: any,\n  /// whether to use JS expression for args\n  computedArgs?: boolean,\n  /// condition to execute the above command\n  when?: string,\n  /// run this command for count times (a js expression)\n  count?: string,\n  /**\n   * Whether to record the key sequence for this command in a register\n   * (only works for top-level command)\n   */\n  record?: string\n};\n```\n\nThe arguments of a complex command can be a JS expression,\nwhich depends on the `computedArgs` field.\nFor computed arguments, the `args` must be a string for JS expressions.\n\nThe condition `when` and `count` are also JS expressions.\n\n\n### Command Context\n\nIn the JS expression in a complex command, a context object `_ctx` is available.\n\nThe `_ctx` is defined as follows:\n\n```ts\ntype Context = {\n  // Key sequence to invoke this command or unexecuted keys\n  keys: string,\n  // Count of the current command\n  count?: number,\n  // cursor position before last command (undefined only when no editor is available)\n  lastPos: vscode.Position | undefined,\n  // current cursor position\n  pos: vscode.Position | undefined,\n  // get the line\n  lineAt: (() =\u003e vscode.TextLine) | undefined,\n  // primary selection before last command\n  // (alias for lastSelections?.[0])\n  lastSelection: vscode.Selection | undefined,\n  // selections before last command\n  lastSelections: readonly vscode.Selections[] | undefined,\n  // current primary selection\n  selection: vscode.Selection | undefined,\n  // current selections\n  selections: readonly vscode.Selections[] | undefined\n  // language Id of current document\n  languageId: string | undefined,\n};\n```\n\nIn order to use the context in VS Code `keybindings.json`,\nuse the command `modalEditor.executeCommand`.\n\nFor example, to emulate `alt+shift+c` in helix,\nadd the following to your `keybindings.json`:\n\n```json\n{\n    \"key\": \"alt+shift+c\",\n    \"command\": \"modalEditor.executeCommand\",\n    \"args\": [\n        {\n            \"command\": \"editor.action.insertCursorAbove\",\n            \"count\": \"_ctx.count\"\n        },\n        \"modalEditor.resetState\"\n    ]\n}\n```\n\n\n### Examples\n\nHere is an example code snippet for `helix.js` preset:\n\n```js\n// add count argument\nfunction repeatable(command) {\n  return {\n    command,\n    count: \"_ctx.count\"\n  };\n}\n\nmodule.exports = {\n  \"\": {\n    // Common keybindings\n    i: \"modalEditor.setInsertMode\",\n  },\n  normal: {\n    // cursor movement\n    h: repeatable(\"cursorLeft\"),\n    j: repeatable(\"cursorDown\"),\n    k: repeatable(\"cursorUp\"),\n    l: repeatable(\"cursorRight\"),\n    w: repeatable([\n      \"cancelSelection\",\n      \"cursorWordStartRightSelect\",\n    ]),\n    b: repeatable([\n      \"cancelSelection\",\n      \"cursorWordStartLeftSelect\"\n    ]),\n  }\n}\n```\n\nSo it maps some movement keys to some existing commands in VS Code,\nwhich leverages the built-in text manipulation.\nYou can refer to the `presets/helix.js` for more examples.\n\nWith the power of JavaScript,\nyou can also easily reference other keybindings in the keymap:\n\n``` js\nlet keymap = {\n  normal: {\n    h: \"cursorLeft\",\n    j: \"cursorDown\",\n    k: \"cursorUp\",\n    l: \"cursorRight\",\n    w: [\n      \"cancelSelection\",\n      \"cursorWordStartRightSelect\",\n    ]\n    // using getter function (this can access keymap in the same level)\n    get Y() { return [ this.h, this.w ]; }\n  }\n};\n// or define other recursive keys in another stage\nkeymap.normal.Z = [ keymap.normal.h, keymap.normal.w ];\n\nmodule.exports = keymap;\n```\n\n\n## Migrations\n\n### From v1.9 to v1.10+\n\nThe `setMode` command no longer clear selections when changing to `insert` mode.\nUse `setInsertMode` or run `clearSelections` explicitly instead if you want to keep the old behaviour.\n\n\n## Contributing\n\nContributions to this repo are welcome.\nIt's recommended to open an issue for discussion first if it's a new feature.\n\nThe commit messages should roughly follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).\n\n\n## Acknowledgement\n\nThis extension is greatly inspired by\n[ModalKeys](https://github.com/haberdashPI/vscode-modal-keys)\nand\n[ModalEdit](https://github.com/johtela/vscode-modaledit).\nThis extension borrows a lot of ideas from them\nthanks to their detailed documentation.\n\nThe main difference between this extension and the above two is the philosophy:\nthis extension tries to reuse most VS Code native commands\nand avoids adding more state control.\nThis makes it easier to understand the code and further extend it.\n\nThe logo of this extension is modified based on the icon credited to\n[Material Design Icons](https://materialdesignicons.com/).\n\n\n## License\n\nCopyright (C) 2022 DCsunset\n\nLicensed under the [AGPLv3](LICENSE) license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcsunset%2Fvscode-modal-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdcsunset%2Fvscode-modal-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdcsunset%2Fvscode-modal-editor/lists"}