Skip to main content

Overview

Ex commands are Vim’s command-line commands that you access by typing : in normal mode. CodeMirror Vim allows you to define custom ex commands to extend functionality.

Defining Ex Commands

Use Vim.defineEx() to create custom ex commands:
import { Vim, getCM } from "@replit/codemirror-vim";

Vim.defineEx('write', 'w', function(cm) {
  // Save the file
  console.log('Saving file...');
  // Your save logic here
});
The second parameter is the short name for the command. In this example, both :write and :w will trigger the command.

Command Syntax

Basic Command Definition

Vim.defineEx(
  name: string,
  shortName: string,
  callback: (cm: CodeMirror, params: ExParams) => void
): void
Parameters:
  • name - Full command name
  • shortName - Abbreviated command name (can be the same as name)
  • callback - Function to execute when the command is called

Command Parameters

The callback function receives two arguments:
1

CodeMirror Instance (cm)

The CodeMirror adapter instance that you can use to access the CM5 API:
Vim.defineEx('uppercase', 'up', function(cm) {
  const selection = cm.getSelection();
  cm.replaceSelection(selection.toUpperCase());
});
2

ExParams Object

Contains information about the command invocation:
type ExParams = {
  commandName: string,      // The command name used
  argString: string,        // The argument string
  input: string,            // Full input line
  args?: string[],          // Parsed arguments array
  line: number,             // Starting line number
  lineEnd?: number,         // Ending line number (for ranges)
  selectionLine: number,    // Selected line start
  selectionLineEnd?: number // Selected line end
}

Practical Examples

Save Command

Vim.defineEx('write', 'w', function(cm) {
  // Integrate with your application's save functionality
  const content = cm.getValue();
  saveToBackend(content);
});

// Usage in editor: :w or :write

Custom Search and Replace

Vim.defineEx('replace', 'rep', function(cm, params) {
  if (!params.args || params.args.length < 2) {
    console.error('Usage: :replace <search> <replace>');
    return;
  }
  
  const [search, replace] = params.args;
  const content = cm.getValue();
  const newContent = content.replace(new RegExp(search, 'g'), replace);
  cm.setValue(newContent);
});

// Usage: :replace oldText newText

Insert Date Command

Vim.defineEx('date', 'date', function(cm) {
  const date = new Date().toISOString().split('T')[0];
  const cursor = cm.getCursor();
  cm.replaceRange(date, cursor);
});

// Usage: :date

Line Range Commands

Vim.defineEx('comment', 'com', function(cm, params) {
  const startLine = params.line;
  const endLine = params.lineEnd || startLine;
  
  for (let i = startLine; i <= endLine; i++) {
    const lineContent = cm.getLine(i);
    cm.replaceRange('// ', { line: i, ch: 0 });
  }
});

// Usage: :5,10comment (comments lines 5-10)
Vim.defineEx('write', 'w', function(cm) {
  const content = cm.getValue();
  // Save logic
  console.log('File saved');
});

Vim.defineEx('quit', 'q', function(cm) {
  // Close editor logic
  console.log('Closing editor');
});

Vim.defineEx('wq', 'wq', function(cm) {
  // Save and quit
  const content = cm.getValue();
  // Save logic
  // Close logic
});

Built-in Ex Commands

CodeMirror Vim includes many built-in ex commands from the defaultExCommandMap:
  • :write / :w - Write file (if implemented)
  • :quit / :q - Quit (if implemented)
  • :substitute / :s - Search and replace
  • :set - Set options
  • :map / :imap / :nmap / :vmap - Create mappings
  • :undo / :u - Undo
  • :redo / :red - Redo
  • :yank / :y - Yank lines
  • :delete / :d - Delete lines
  • :join / :j - Join lines
  • :sort - Sort lines
  • :global / :g - Execute command on matching lines
  • :nohlsearch / :noh - Clear search highlighting
  • :marks - Show marks
  • :registers / :reg - Show registers
Some built-in commands may require additional implementation in your application (like :write for saving files).

Accessing the CodeMirror Instance

You can access the CM5 API through the cm parameter:
Vim.defineEx('info', 'info', function(cm) {
  const lineCount = cm.lineCount();
  const cursor = cm.getCursor();
  const selection = cm.getSelection();
  
  console.log(`Lines: ${lineCount}`);
  console.log(`Cursor: line ${cursor.line}, ch ${cursor.ch}`);
  console.log(`Selection: ${selection.length} characters`);
});

Error Handling

Always validate parameters and provide helpful error messages:
Vim.defineEx('goto', 'go', function(cm, params) {
  if (!params.args || params.args.length === 0) {
    console.error('Usage: :goto <line_number>');
    return;
  }
  
  const lineNum = parseInt(params.args[0], 10);
  
  if (isNaN(lineNum)) {
    console.error('Invalid line number');
    return;
  }
  
  if (lineNum < 0 || lineNum >= cm.lineCount()) {
    console.error('Line number out of range');
    return;
  }
  
  cm.setCursor({ line: lineNum, ch: 0 });
});

Complete Integration Example

import { basicSetup, EditorView } from 'codemirror';
import { vim, Vim, getCM } from "@replit/codemirror-vim";

let view = new EditorView({
  doc: "Hello, Vim!",
  extensions: [vim(), basicSetup],
  parent: document.querySelector('#editor'),
});

// Get the CodeMirror adapter
const cm = getCM(view);

// Define custom commands
Vim.defineEx('write', 'w', function() {
  const content = view.state.doc.toString();
  localStorage.setItem('document', content);
  console.log('Document saved!');
});

Vim.defineEx('load', 'lo', function() {
  const content = localStorage.getItem('document');
  if (content) {
    view.dispatch({
      changes: { from: 0, to: view.state.doc.length, insert: content }
    });
    console.log('Document loaded!');
  }
});

Build docs developers (and LLMs) love