Overview
The Vim.exitInsertMode() function programmatically exits insert mode and returns to normal mode. This is equivalent to pressing <Esc> in insert mode, but provides a direct API for mode switching.
Signature
Vim.exitInsertMode(cm: CodeMirror): void
Parameters
The CodeMirror editor instance. You can get this from the EditorView using getCM(view).
Return Value
This function does not return a value.
Examples
Basic Usage
Exit insert mode after a timeout:
import { Vim, getCM } from "@replit/codemirror-vim";
function autoExitInsertMode(view, delay = 5000) {
const cm = getCM(view);
// Set a timeout to exit insert mode after inactivity
let timeout;
cm.on('vim-mode-change', (e) => {
if (e.mode === 'insert') {
timeout = setTimeout(() => {
Vim.exitInsertMode(cm);
}, delay);
} else {
clearTimeout(timeout);
}
});
}
Integration with Save
Exit insert mode when saving:
function saveFile(view) {
const cm = getCM(view);
// Exit insert mode before saving
if (cm.state.vim?.insertMode) {
Vim.exitInsertMode(cm);
}
// Save the file
const content = cm.getValue();
saveToServer(content);
}
Automatically format code when exiting insert mode:
function setupAutoFormat(view) {
const cm = getCM(view);
// Store original exitInsertMode
const originalExit = Vim.exitInsertMode;
// Override to add formatting
Vim.exitInsertMode = function(cm) {
// Format the current line
const cursor = cm.getCursor();
const line = cm.getLine(cursor.line);
const formatted = formatLine(line);
if (formatted !== line) {
cm.replaceRange(
formatted,
{ line: cursor.line, ch: 0 },
{ line: cursor.line, ch: line.length }
);
}
// Call original exit
originalExit.call(this, cm);
};
}
function formatLine(line) {
// Simple formatting example
return line.trim();
}
Command Palette Integration
Add a command to exit insert mode:
function registerCommands(view) {
const cm = getCM(view);
const commands = [
{
name: 'Vim: Exit Insert Mode',
execute: () => {
if (cm.state.vim?.insertMode) {
Vim.exitInsertMode(cm);
}
},
when: () => cm.state.vim?.insertMode === true
},
{
name: 'Vim: Enter Insert Mode',
execute: () => {
if (!cm.state.vim?.insertMode) {
Vim.handleKey(cm, 'i');
}
},
when: () => cm.state.vim?.insertMode === false
}
];
return commands;
}
Auto-Save on Mode Change
Automatically save when exiting insert mode:
function setupAutoSave(view) {
const cm = getCM(view);
let insertModeChanges = false;
cm.on('change', () => {
if (cm.state.vim?.insertMode) {
insertModeChanges = true;
}
});
cm.on('vim-mode-change', (e) => {
// When exiting insert mode
if (e.mode === 'normal' && insertModeChanges) {
saveFile(cm.getValue());
insertModeChanges = false;
}
});
}
Completion Integration
Exit insert mode after accepting a completion:
function setupCompletion(view) {
const cm = getCM(view);
cm.on('completion-selected', (completion) => {
// Insert the completion
cm.replaceRange(
completion.text,
cm.getCursor()
);
// Exit insert mode
setTimeout(() => {
Vim.exitInsertMode(cm);
}, 0);
});
}
Collaborative Editing
Exit insert mode when another user starts editing:
function setupCollaboration(view) {
const cm = getCM(view);
// When remote user starts editing near cursor
socket.on('remote-edit', (edit) => {
const cursor = cm.getCursor();
const distance = Math.abs(cursor.line - edit.line);
// If edit is close to cursor, exit insert mode
if (distance < 3 && cm.state.vim?.insertMode) {
Vim.exitInsertMode(cm);
showNotification('User ' + edit.user + ' is editing nearby');
}
});
}
Mode Indicator
Update UI when mode changes:
function setupModeIndicator(view) {
const cm = getCM(view);
const indicator = document.getElementById('mode-indicator');
cm.on('vim-mode-change', (e) => {
indicator.textContent = e.mode.toUpperCase();
indicator.className = `mode-${e.mode}`;
});
// Add button to exit insert mode
const exitButton = document.getElementById('exit-insert');
exitButton.addEventListener('click', () => {
if (cm.state.vim?.insertMode) {
Vim.exitInsertMode(cm);
}
});
}
Behavior
When exitInsertMode() is called:
- The editor switches from insert mode to normal mode
- The cursor moves one character to the left (unless at the start of the line)
- Any pending insert mode changes are finalized
- A
'vim-mode-change' event is fired with {mode: 'normal'}
- The visual cursor changes from a line to a block
Notes
- This function only has an effect if the editor is currently in insert mode
- If already in normal mode, calling this function has no effect
- This is equivalent to pressing
<Esc> but does not trigger any <Esc> key mappings
- The cursor position after exiting is adjusted to be on a valid character
- Any unsaved changes in insert mode are preserved
Checking Current Mode
You can check if the editor is in insert mode before calling:
const cm = getCM(view);
if (cm.state.vim?.insertMode) {
Vim.exitInsertMode(cm);
console.log('Exited insert mode');
} else {
console.log('Not in insert mode');
}
Events
Listen for mode changes:
cm.on('vim-mode-change', (event) => {
console.log('Mode changed to:', event.mode);
// event.mode can be: 'normal', 'insert', 'visual', 'replace'
if (event.subMode) {
console.log('SubMode:', event.subMode);
// event.subMode can be: 'linewise', 'blockwise'
}
});
See Also