Est. 1991 Intermediate

VIM Macro

Vim's built-in automation language — from recorded keystroke macros and key mappings, present since Vim's 1991 debut, to the full Vim script (VimL) language of vimrc files and plugins and its compiled successor, Vim9 script.

Created by Bram Moolenaar

Paradigm Scripting: imperative and event-driven, with functional features; plus recorded keystroke macros
Typing Dynamic, weak (legacy Vim script); Vim9 script adds static type checking with type annotations
First Appeared 1991
Latest Version Vim9 script as shipped in Vim 9.2 (February 2026)

VIM Macro — catalogued under that name in programming-language encyclopedias, and better known today as Vim script, Vimscript, or VimL — is the automation language built into Bram Moolenaar’s Vim editor. It spans a spectrum: at one end, the macro facilities Vim shipped with from its first release in 1991 (key mappings and recorded keystroke sequences replayed from registers); at the other, a full dynamically typed scripting language that grew inside the editor from 1998 onward and now, as the compiled Vim9 script dialect, sports type checking, classes, enums, and generics. It is arguably one of the most widely deployed domain-specific languages in the world — every .vimrc file is a program in it — and the foundation on which Vim’s vast plugin ecosystem was built.

History & Origins

The macro layer (1991)

Vim’s automation story begins before Vim. Bill Joy’s vi, and the ex line editor underneath it, already let users bind keystroke sequences to keys with :map and execute the contents of a named register as commands with @. When Bram Moolenaar released Vim 1.14 for the Amiga on November 2, 1991, it inherited this macro layer and improved on it — Vim lets users record keystrokes live into a register with q{register} and replay them with @{register}, a workflow so central to the editor that “Vim macros” usually refers to exactly this. Language catalogues that date “VIM Macro” to 1991 are dating this layer: automation by keystroke, with no variables, functions, or control flow beyond what the editing commands themselves provide.

The scripting language (1998)

The language proper arrived with Vim 5.0 in February 1998, which added expression evaluation, control-flow commands such as :if and :while, and user-defined functions. The design was deeply shaped by its ancestry: a Vim script file is, at bottom, a sequence of ex commands — the same colon commands a user types interactively — so set number, map <F5> :make<CR>, and if has('gui_running') are all statements in the language. Scripting turned Vim’s configuration file from a list of settings into a program, and Vim 6.0’s plugin architecture (September 2001) turned those programs into a shareable format, seeding one of the largest editor-extension ecosystems in open source.

Becoming a real language (2006–2019)

Vim 7.0 (May 2006) was the language’s coming of age: it added Lists and Dictionaries, function references (Funcrefs), and the autoload mechanism that lets plugins defer loading code until it is called. With map() and filter() built in, Vim script even supported a basic functional style. Vim 8.0 (September 2016) added lambdas, closures, and partials, alongside asynchronous jobs, channels, and timers that finally let plugins do slow work — linting, completion, version-control queries — without freezing the editor. Vim 8.2 (December 2019) contributed popup windows and text properties, the building blocks for modern plugin interfaces.

Vim9 script (2022–present)

Legacy Vim script was interpreted line by line and famously slow, which became a real constraint as plugins grew ambitious. Moolenaar’s answer was Vim9 script, shipped in Vim 9.0 on June 28, 2022: a redesigned dialect in which def functions are compiled to internal instructions and checked against declared types at compile time. The Vim9 documentation cited expected speedups of roughly 10 to 100 times for compiled :def functions versus interpreted legacy functions — achieved by compiling functions once instead of re-parsing each line on every execution. The dialect has evolved quickly since: Vim 9.1 (January 2, 2024) added classes and objects, and Vim 9.2 (February 2026) added enums, generic functions, and tuples. Legacy and Vim9 script coexist in the same editor, and legacy script remains fully supported.

Design Philosophy

Vim script was never designed as a language in the usual sense; it accreted around an editor, and its character follows from that:

  • Editor-first. Every statement is an ex command; every function can move the cursor, change a buffer, or set an option. The language’s “standard library” is the editor itself — hundreds of built-in functions like getline(), setline(), search(), and expand() manipulate editing state directly.
  • Event-driven. Autocommands (:autocmd) attach script to editor events — opening a file, writing a buffer, gaining focus — making Vim script naturally reactive long before that style was fashionable.
  • Scoped by sigil. Variables carry explicit scope prefixes: g: global, s: script-local, l: function-local, b: buffer-local, w: window-local, a: function arguments — a pragmatic solution to namespacing in a language that grew without modules.
  • Idiosyncratic by accretion. Legacy Vim script is full of famous warts: string comparison with == depends on the user’s ignorecase setting (careful authors write ==# or ==?), string concatenation used . (later ..), and line continuations require a leading backslash on the following line. Vim9 script was designed largely to shed these inconsistencies in favor of conventional syntax.

A legacy Vim script example of the classic “clean up on save” idiom:

1
2
3
4
5
6
7
8
" Legacy Vim script
function! TrimTrailingWhitespace() abort
  let l:save = winsaveview()
  keeppatterns %s/\s\+$//e
  call winrestview(l:save)
endfunction

autocmd BufWritePre * call TrimTrailingWhitespace()

And the same spirit in Vim9 script — typed, compiled, and without the :call/let ceremony:

1
2
3
4
5
6
7
8
9
vim9script

def TrimTrailingWhitespace()
  var save = winsaveview()
  keeppatterns :%s/\s\+$//e
  winrestview(save)
enddef

autocmd BufWritePre * TrimTrailingWhitespace()

Key Features

  • Recorded macros: qa records keystrokes into register a, @a replays them, 5@a replays them five times — programming by demonstration, no syntax required
  • Mappings: :map and its non-recursive, mode-specific descendants (:nnoremap, :inoremap, …) bind arbitrary command sequences to keys
  • User-defined functions and commands: :function / :def and :command extend the editor’s own command set
  • Rich built-in types: strings, numbers, floats, Lists, Dictionaries, Funcrefs; Vim9 adds declared types, classes, enums, tuples, and generics
  • Autocommands: hook script to editor events for reactive automation
  • Autoload: lazy loading of plugin functions by naming convention (plugin#module#Function)
  • Async primitives: jobs, channels, and timers (since Vim 8.0) for non-blocking plugin work
  • Embedded interface to other languages: Vim builds can expose Python, Lua, Perl, Ruby, and Tcl scripting interfaces alongside Vim script

One practical caveat, documented in Vim’s own help: the scripting language requires Vim to be built with the +eval feature — minimal builds that omit it can still run mappings and recorded macros, but not the programming language.

Evolution

Language milestoneVim versionDate
Mappings and register playback (macro layer)1.14Nov 1991
Expressions, control flow, user functions5.0Feb 1998
Plugin architecture6.0Sep 2001
Lists, Dictionaries, Funcrefs, autoload7.0May 2006
Lambdas, closures, jobs, channels, timers8.0Sep 2016
Popup windows, text properties8.2Dec 2019
Vim9 script: compiled, typed :def functions9.0Jun 2022
Vim9 classes and objects9.1Jan 2024
Vim9 enums, generics, tuples9.2Feb 2026

The language’s community matured alongside it. Steve Losh’s free online book Learn Vimscript the Hard Way became a standard introduction for a generation of plugin authors, and vim.org’s script archive — later largely supplanted by GitHub — accumulated thousands of Vim script plugins.

The most consequential fork in the language’s history came from Neovim, the 2014 community fork of Vim. Neovim maintains an implementation of legacy Vim script for compatibility but chose not to adopt Vim9 script, standardizing on Lua as its extension language instead. The result is a genuine dialect split: configuration and plugins written in legacy Vim script generally run on both editors, while new Vim-side development increasingly targets Vim9 script and new Neovim-side development targets Lua.

Current Relevance

Vim script today is simultaneously ubiquitous and contested. Ubiquitous, because every Vim installation with +eval interprets it, Vim’s own runtime files are written in it, decades of plugins depend on it, and millions of vimrc files are programs in it. Contested, because the ecosystem’s energy has split: the Vim project is actively developing Vim9 script — the 9.1 and 9.2 releases added classes, enums, generics, and tuples — while the Neovim community writes new tooling in Lua and treats legacy Vim script as a compatibility layer. What is not in dispute is the macro layer: recording with q and replaying with @ remains a signature Vim skill, emulated in “Vim mode” implementations across VS Code, JetBrains IDEs, and beyond.

Why It Matters

VIM Macro is a case study in how languages actually happen. Nobody sat down to design it; it grew from key remapping, to a configuration file, to control flow, to data structures, to async runtimes, to a typed compiled dialect — each step demanded by real users automating real editing. Along the way it demonstrated ideas ahead of their time: event-driven extension through autocommands, an editor as a programmable platform decades before “extensions marketplace” entered the vocabulary, and programming-by-demonstration through recorded macros that even non-programmers use fluently. Its warts became teaching material, its limitations motivated both Vim9 script and Neovim’s embrace of Lua, and its programs — the world’s vimrc files — constitute one of the largest bodies of code in any domain-specific language. To study Vim script is to study thirty-five years of one editor’s users refusing to do anything by hand twice.

Timeline

1991
Vim 1.14, released November 2, 1991 for the Amiga, ships with the macro layer inherited from vi — :map key remappings and playback of register contents with @ — the seed of Vim's automation language
1998
Vim 5.0 (February 1998) introduces the scripting language proper: expression evaluation, control flow such as :if and :while, and user-defined functions — the birth of Vim script (VimL)
2001
Vim 6.0 (September 2001) adds the plugin architecture — plugin directories and filetype plugins — turning Vim script into a distribution format for shareable editor extensions
2006
Vim 7.0 (May 2006) transforms Vim script into a general-purpose language with Lists, Dictionaries, function references (Funcrefs), and the autoload mechanism for lazily loading plugin code
2016
Vim 8.0 (September 2016) brings lambdas, closures, and partials to the language, plus asynchronous jobs, channels, and timers that let Vim script plugins run work in the background
2019
Vim 8.2 (December 2019) adds popup windows and text properties, giving Vim script plugins the primitives to build richer user interfaces inside the terminal
2022
Vim 9.0 (June 28, 2022) introduces Vim9 script — a redesigned dialect with compiled :def functions, type annotations, and compile-time checking, designed to execute far faster than legacy Vim script
2024
Vim 9.1 (January 2, 2024) adds classes and objects to Vim9 script, bringing object-oriented programming to the language
2026
Vim 9.2 (February 2026) extends Vim9 script with enums, generic functions, and tuples — evidence of continued language evolution under Vim's post-Moolenaar maintainer team

Notable Uses & Legacy

The vimrc files of millions of users

Every ~/.vimrc (or ~/.vim/vimrc) configuration file is a Vim script program — from two-line option settings to elaborate, thousand-line personal environments with custom functions, mappings, and autocommands — making Vim script arguably one of the most widely deployed niche languages in computing.

The classic Vim plugin ecosystem

Landmark plugins such as Tim Pope's fugitive.vim (git integration) and surround.vim, NERDTree, ALE, and vim-airline are written substantially in Vim script, distributed for decades through vim.org and later GitHub.

Vim's own runtime files

The syntax highlighting definitions, indentation rules, filetype detection, and colorschemes that ship with Vim itself are Vim script programs — hundreds of runtime files covering hundreds of file types, maintained by the community in the main Vim repository.

Everyday recorded macros

The original 'VIM macro' workflow is still a daily tool: recording a keystroke sequence into a register with q, then replaying it with @ (or across thousands of lines with a count or :normal), remains one of the fastest ways to perform one-off repetitive edits without writing any code.

Neovim's compatibility layer

Neovim, though Lua-first, ships and maintains an implementation of legacy Vim script and inherits much of Vim's script-based runtime, so a large share of Vim script code — vimrc configurations and classic plugins alike — runs on both editors.

Language Influence

Influenced By

ex vi

Influenced

Neovim

Running Today

Run examples using the official Docker image:

docker pull thinca/vim:latest

Example usage:

docker run --rm -it thinca/vim:latest -c ':echo "Hello from Vim script"'
Last updated: