This Visual Studio Code extension provides Emacs-like keybindings and operations. This is inspired by the great VSCcode extension by hiro-sun and its forks such as vscode-emacs-friendly by Sebastian Zaha, vscode-emacs-improved by rkwan94, and vscode-emacs-neon by NotKyon.
Though there were such great extensions, this extension is written from scratch because it was hard to achieve the goal listed below by extending the existent codebase.
This extension aims
- to provide Emacs-like keybindings
- to be fully compatible with multi cursor
- to support kill-ring integrated with the system clipboard
- to support mark-ring
- to support prefix argument
- to support sexp
- to fix some bugs in the existing extensions such as
- mark-mode states are shared among all editors
This extension makes use of code in the existent extensions listed above and, in addition, VSCode and VSCode Vim extension. Thanks for all these great works. Mainly, almost all keybinding settings are derived from vscode-emacs-friendly by Sebastian Zaha.
It's an intended design that simulates the original Emacs' behavior.
You can disable it with emacs-mcx.cursorMoveOnFindWidget
option described below.
See whitphx#137 for the details about this topic.
i-search (C-s
) is initialized with the currently selected string and the previous search is removed.
This is VSCode's design that an extension cannot control.
To disable it, you should set editor.find.seedSearchStringFromSelection
VSCode setting as "never"
.
It makes the find widget work similarly to Emacs.
Refs:
- The official doc about
editor.find.seedSearchStringFromSelection
setting - The GitHub issue where we discuss about it
Post a bug report or a feature request to GitHub Issues.
This extension has some custom settings named with a prefix emacs-mcx
.
You can configure those settings.
(See this page to know how to change the settings.)
Configurable options of this extension are the followings.
If set as true, the original emacs's cursor movements are strictly simulated.
If set as false, the VSCode's native cursor movements are preserved.
For example, if set as true, when you type C-a
, the cursor moves to the beginning of the line (Emacs' original behavior).
If set as false, on the other hand, the cursor move to the first non-empty character in the line (VSCode's native behavior of Home key).
If set as true, Escape key works as the Meta prefix like original Emacs.
If set as false, Escape key works as cancel, the VSCode's native behavior.
For example, if set as true, M-f
(forward-word) can be issued by both alt+f
and escape f
.
The only exception is the commands which begin with M-g
(M-g g
, M-g n
, M-g p
).
It is because VSCode can handle only up to two key strokes as the key bindings.
So, as the special case, Escape g
works as follows.
Command | Desc |
---|---|
Escape g |
Jump to line (command palette) |
If set as true, ctrl+[
works as the Meta prefix like original Emacs.
If set as true, Command (⌘) key works as the Meta prefix like original Emacs on macOS. This option only works on macOS.
Configures the maximum number of kill ring entries. The default is 60.
This simulates the original Emacs' kill-whole-line
variable.
The default is false.
If set to true, cursor move commands of this extension (C-f
, C-b
, C-p
, C-n
, C-a
, C-e
, M-f
, M-b
, and M-m
) are disabled when the find widget is focused, to allow the widget to keep open and the cursor to move on it.
Prefix arguments do not work on character inputs with IMEs by default and you can set this config to true
in order to enable it.
Note that this config makes use of VSCode API's type
command under the hood and can cause problems in some cases.
- If you are using IME, text input may sometimes fail.
- If another extension that also uses the
type
command is installed, an error occurs (See microsoft/vscode#13441).
Indicates whether M-<digit>
(the emacs-mcx.digitArgument
command) is enabled.
Set false
when M-<digit>
conflicts with some other necessary commands. See whitphx#1208 for the background.
Configurations for debugging.
This extension provides some contexts that you can refer to in "when"
clauses of your keybindings.json
.
boolean
This indicates whether mark-mode is enabled.
boolean
This indicates the editor is accepting argument input following C-u
.
boolean
This indicates if a prefix argument exists.
Use this boolean context to check the existence of a prefix argument, instead of using emacs-mcx.prefixArgument
with null check.
number | undefined
This is a currently input prefix argument.
Alt key is mapped to the Meta prefix (M
) by default and you can configure for Escape, ctrl+[
, or Command (⌘) key (macOS only) to work as it with the settings above.
Command | Prefix argument | Desc |
---|---|---|
C-f |
✓ | Move forward (forward-char) |
C-b |
✓ | Move backward (backward-char) |
C-n |
✓ | Move to the next line (next-line) |
C-p |
✓ | Move to the previous line (previous-line) |
C-a |
✓ | Move to the beginning of line (move-beginning-of-line) |
C-e |
✓ | Move to the end of line (move-end-of-line) |
M-f |
✓ | Move forward by one word unit (forward-word) |
M-b |
✓ | Move backward by one word unit (backward-word) |
C-<right> , M-<right> |
✓ | This command (right-word) behaves like M-f |
C-<left> , M-<left> |
✓ | This command (left-word) behaves like M-b |
M-m |
Move (forward or back) to the first non-whitespace character on the current line (back-to-indentation) | |
C-v |
✓ | Scroll down by one screen unit (scroll-up-command) |
M-v |
✓ | Scroll up by one screen unit (scroll-down-command) |
M-S-[ (M-{ with US keyboard), C-<up> |
✓ | Move back to previous paragraph beginning (backward-paragraph) |
M-S-] (M-} with US keyboard), C-<down> |
✓ | Move forward to next paragraph end (forward-paragraph) |
M-S-, (M-< with US keyboard) |
Move to the top of the buffer (beginning-of-buffer) | |
M-S-. (M-> with US keyboard) |
Move to the end of the buffer (end-of-buffer) | |
M-g g (M-g M-g ) |
Jump to line (command palette) | |
M-g n (M-g M-n , C-x ` ) |
Jump to next error | |
M-g p (M-g M-p ) |
Jump to previous error | |
C-l |
Center screen on current line (recenter-top-bottom) |
Command | Desc |
---|---|
C-s |
Incremental search forward (isearch-forward). |
C-r |
Incremental search backward (isearch-backward). |
C-M-s |
Begin incremental regexp search (isearch-forward-regexp). |
C-M-r |
Begin reverse incremental regexp search (isearch-backward-regexp). |
M-S-5 (M-% with US keyboard) |
Replace (query-replace) |
C-M-S-5 (C-M-% with US keyboard) |
Replace with regexp (query-replace-regexp) |
C-M-n |
Add selection to next find match |
C-M-p |
Add selection to previous find match |
Command | Prefix argument | Desc |
---|---|---|
C-d |
✓ | Delete right (DEL) |
C-h |
✓ | Delete left (BACKSPACE) |
M-d |
✓ | Kill word (kill-word) |
M-Bksp |
✓ | Kill word left (backward-kill-word) |
C-k |
✓ | Kill from the current cursor to the end of line (kill-line) |
C-S-Bksp |
Kill whole line (kill-whole-line) | |
C-w |
Kill region | |
M-w |
Copy region to kill ring | |
C-y |
Yank | |
M-y |
Yank pop | |
C-o |
Open line | |
C-j |
✓ | New line |
C-m |
✓ | New line |
C-x C-o |
Delete blank lines around (delete-blank-lines) | |
C-x h |
Select All | |
C-x u , C-/ , C-S-- (C-_ with US keyboard) |
Undo | |
C-; |
Toggle line comment in and out | |
M-; |
Toggle region comment in and out | |
C-x C-l (M-l ) |
Convert to lower case (On the Emacs' original behavior, C-x C-l and M-l are assigned to the different functionalities. However, this extension assigns these keys to the same emacs-mcx.transformToLowercase command which calls editor.action.transformToLowercase command internally and works similarly to both the original Emacs' functionalities based on the context. Upper case and title case (below) are same) |
|
C-x C-u (M-u ) |
Convert to upper case | |
M-c |
Convert to title case | |
M-S-6 (M-^ with US keyboard) |
Merge the previous and the current line (delete-indentation) |
Command | Desc |
---|---|
C-SPC , C-S-2 (C-@ with US keyboard) |
Set the mark at point, and activate it (set-mark-command). |
C-SPC C-SPC |
Set the mark, pushing it onto the mark ring, without activating it. |
C-u C-SPC |
Move point to where the mark was, and restore the mark from the ring of former marks. |
C-x C-x |
Set the mark at point, and activate it; then move point where the mark used to be (exchange-point-and-mark). |
See this page and this page about the mark and the mark ring.
Command | Desc |
---|---|
C-x r k |
Kill the text of the region-rectangle, saving its contents as the last killed rectangle (kill-rectangle). |
C-x r M-w |
Save the text of the region-rectangle as the last killed rectangle (copy-rectangle-as-kill). |
C-x r d |
Delete the text of the region-rectangle (delete-rectangle). |
C-x r y |
Yank the last killed rectangle with its upper left corner at point (yank-rectangle). |
C-x r p |
Replace last kill ring to each line of rectangle if the kill ring top only contains one line. |
C-x r o |
Insert blank space to fill the space of the region-rectangle (open-rectangle). |
C-x r c |
Clear the region-rectangle by replacing all of its contents with spaces (clear-rectangle). |
C-x r t |
Replace rectangle contents with string on each line (string-rectangle). |
C-x SPC |
Toggle Rectangle Mark mode (rectangle-mark-mode). |
Command | Desc |
---|---|
C-g (ESC ) |
Cancel |
C-' (M-/ ) |
IntelliSense Suggestion |
M-x |
Open command palette |
C-M-SPC |
Toggle SideBar visibility |
C-x z |
Toggle Zen Mode |
C-x C-c |
Close window (save-buffers-kill-terminal) |
Command | Desc |
---|---|
C-x C-f |
QuickOpen a file (Tips: This extension assigns C-x C-f to the VSCode's native quick file navigation. If you prefer more Emacs-like behavior, The "File Browser" extension by Bodil Stokke (bodil.file-browser ) may help.) |
C-x C-s |
Save |
C-x C-w |
Save as |
C-x s |
Save all files |
C-x C-n |
Open new window |
Command | Desc |
---|---|
C-x b |
Switch to another open buffer |
C-x k |
Close current tab (buffer) |
C-x 0 |
Close editors in the current group. |
C-x 1 |
Close editors in other (split) group. |
C-x 2 |
Split editor horizontal |
C-x 3 |
Split editor vertical |
C-x 4 |
Toggle split layout (vertical to horizontal) |
C-x o |
Focus other split editor |
C-x LEFT |
Select the previous tab (previous-buffer). |
C-x RIGHT |
Select the next tab (next-buffer). |
See https://www.gnu.org/software/emacs/manual/html_node/emacs/Arguments.html for detail
Command | Desc |
---|---|
C-u |
universal-argument |
M-<number> |
digit-argument |
M-- |
negative-argument |
Command | Prefix argument | Desc |
---|---|---|
C-M-f |
✓ | Move forward over a balanced expression (forward-sexp) |
C-M-b |
✓ | Move backward over a balanced expression (backward-sexp) |
C-M-S-2 (C-M-@ with US keyboard) |
✓ | Set mark after end of following balanced expression (mark-sexp). This does not move point. |
C-M-k |
✓ | Kill balanced expression forward (kill-sexp) |
C-M-Bksp |
✓ | Kill balanced expression backward (backward-kill-sexp) |
This extension makes use of paredit.js to provide sexp functionalities. Thank you for this great library.
This command calls another command with the prefix argument. This is mainly for extension developers who want to make the extensions collaborative with this extension's prefix argument. See the issue #1146 for the discussion about it.
For example, if you define the keybinding below,
C-x e
will call the commandfoo
with the argument{}
.C-u C-x e
will call the commandfoo
with the argument{ prefixArgument: 4 }
.
{
"key": "ctrl+x e",
"command": "emacs-mcx.executeCommandWithPrefixArgument",
"args": {
"command": "foo"
}
}
You can pass the arguments to the target command as below. In this case,
C-x e
will call the commandfoo
with the argument{ baz: 42 }
.C-u C-x e
will call the commandfoo
with the argument{ prefixArgument: 4, baz: 42 }
.
{
"key": "ctrl+x e",
"command": "emacs-mcx.executeCommandWithPrefixArgument",
"args": {
"command": "foo",
"args": {
"baz": 42
}
}
}
You can change the key name of the prefix argument.
{
"key": "ctrl+x e",
"command": "emacs-mcx.executeCommandWithPrefixArgument",
"args": {
"command": "foo",
"prefixArgumentKey": "repeat"
}
}
C-x e
will call the commandfoo
with the argument{}
.C-u C-x e
will call the commandfoo
with the argument{ repeat: 4 }
.
ctrl+d
: editor.action.addSelectionToNextFindMatch => Usectrl+alt+n
instead;ctrl+g
: workbench.action.gotoLine => Usealt+g g
instead;ctrl+b
: workbench.action.toggleSidebarVisibility => Usectrl+alt+space
instead;ctrl+j
: workbench.action.togglePanel => Usectrl+x j
instead;ctrl+space
: toggleSuggestionDetails, editor.action.triggerSuggest => Usectrl+'
instead;ctrl+x
: editor.action.clipboardCutAction => Usectrl+w
instead;ctrl+v
: editor.action.clipboardPasteAction => Usectrl+y
instead;ctrl+k
: editor.debug.action.showDebugHover, editor.action.trimTrailingWhitespace, editor.action.showHover, editor.action.removeCommentLine, editor.action.addCommentLine, editor.action.openDeclarationToTheSide;ctrl+k z
: workbench.action.toggleZenMode => Usectrl+x z
instead;ctrl+y
: redo;ctrl+m
: editor.action.toggleTabFocusMode;ctrl+/
: editor.action.commentLine => Usectrl+;
instead;ctrl+p
&ctrl+e
: workbench.action.quickOpen => Usectrl+x b
instead;ctrl+p
: workbench.action.quickOpenNavigateNext => Usectrl+n
instead.ctrl+o
: workbench.action.files.openFileFolder;
Your contributions are very welcome!
Please see DEVELOPMENT.md about development of this extension.