Vim Advanced — Registers, Macros, Marks, Quickfix, Vimscript, Plugin Development
Vim is the ultimate text editor for speed and precision. This advanced guide covers the features that separate Vim novices from Vim masters — registers, macros, marks, the quickfix system, Vimscript programming, and creating your own plugins.
What You’ll Learn
You’ll use registers to store and replay text, record and edit macros for complex repetitive edits, navigate code with marks and jumps, manage build errors with quickfix lists, write Vimscript functions and commands, and create your own plugins. DodaZIP’s developers use Vim macros for batch configuration file editing across hundreds of servers.
Why Advanced Vim Matters
Vim’s modal editing is already the fastest way to edit text. But its real power comes from the advanced features: macros that replay complex edits, registers that store multiple pieces of text, marks that let you jump across files, and Vimscript that turns Vim into a fully programmable editing environment.
Learning Path
flowchart LR
A[Vim Basics] --> B[Vim Advanced<br/>You are here]
B --> C[Vimscript Programming]
C --> D[Plugin Development]
style B fill:#f90,color:#fff
Registers
Registers are named storage locations for text and operations:
Register Types
| Register | Identifier | Purpose |
|---|---|---|
| Unnamed | "" | Last yank, delete, or change |
| Numbered | "0 to "9 | "0 = last yank, "1-"9 = last 9 deletes |
| Small delete | "- | Deletes < 1 line |
| Named | "a to "z | User-assigned storage |
| Read-only | ":, "., "% | Last command, last inserted text, current file |
| Expression | "= | Evaluate expression |
| Black hole | "_ | Discard deleted text (like /dev/null) |
| Selection | "*, "+ | System clipboard |
Using Registers
" Yank into register a
"ayw
" Paste from register a
"ap
" Append to register a (uppercase)
"Ayw
" View all registers
:reg
:reg a b c " Specific registers
" Paste from system clipboard
"+p
" Or from primary selection
"*p
" Execute register as commands (on command-line)
" Ctrl+R a (inserts contents of register a into command line)Practical Register Examples
" Swap two words using registers
" Delete first word into register a: "adiw
" Delete second word into register b: "bdiw
" Paste a over second word: "bP (since buffer is gone)
" Paste b over first word: "ap
" Collect matching lines
:g/pattern/yank A " Append all matching lines to register a
"ap " Paste all collected lines
" Numbered registers — recover deleted text
dd " Delete current line (goes to "1)
dd " Delete another (shifts to "2)
"1p " Paste the first deletionMacros
Macros record a sequence of commands into a register for replay:
Recording and Playing
" Start recording into register a
qa
" Perform your edit sequence (any Vim commands)
Iprefix_<Esc> " Insert prefix at line start
A_suffix<Esc> " Append suffix at line end
j " Move to next line
" Stop recording
q
" Play the macro once
@a
" Play the macro from register a, 10 times
10@a
" Play the last recorded macro
@@Advanced Macro Techniques
" Repeat until end of file
99@a " Play macro 99 times — stop at EOF with error
" Better: recursive macro
" Record macro q: execute commands then @q
" Stop when line is empty or pattern doesn't match
" Edit a macro
" Open register a
:put a
" Edit the commands as text
" Yank back into register
"ayy
" Normalize whitespace with a macro
qa
:s/\s\+/ /g<CR> " Collapse whitespace
:g/^$/d<CR> " Delete empty lines
%s/foo/bar/g<CR> " Replace
q
" Run macro on visual selection lines
" Select lines, then:
:normal @aExample: CSV Transformation Macro
" Input:
name,email,role
alice,alice@example.com,admin
bob,bob@example.com,user
" Macro in register c:
qa
0f, " Jump to first comma
r; " Replace with semicolon
f, " Jump to next comma
r; " Replace with semicolon
f, " Jump to next comma
r; " Replace with semicolon
j " Next line
q
" Run on all lines
:1,$normal @a
" Result:
name;email;role
alice;alice@example.com;admin
bob;bob@example.com;userMarks
Marks let you save positions and jump across files:
Mark Types
| Mark | Scope | Usage |
|---|---|---|
a-z | File-local | Within current file |
A-Z | Global | Across files (file + position) |
0-9 | Read-only | .viminfo positions from previous sessions |
| `` ` | Automatic | Last jump position |
Using Marks
" Set mark a at current position
ma
" Jump to mark a (first non-blank in line)
'a
" Jump to mark a (exact line and column)
`a
" Jump to last edit position
`.
" List all marks
:marks
:marks a b c
" Delete mark
:delm a
:delm A-Z " Delete all global marksPractical Mark Usage
" During code review:
" Set marks at key checkpoints
ma " Top of function
mb " Conditional logic
mc " Return statement
" Jump between them
'a, 'b, 'c
" Global marks for project navigation
mG " Mark important function in any file
mT " Mark TODO location
'G " Jump there from anywhereQuickfix and Location Lists
Quickfix lists manage build errors, search results, and diagnostics:
Using Quickfix
" Fill quickfix from a command
:grep -r "TODO" src/
" Fill from vimgrep
:vimgrep /pattern/ src/**/*.js
" Fill from make output
:make
" Navigation
:copen " Open quickfix window
:cclose " Close quickfix window
:cnext " Next item
:cprev " Previous item
:cfirst " First item
:clast " Last item
:cnfile " Next file
:cpfile " Previous file
" Filter quickfix
:cexpr getqflist() " Get as list
:call setqflist([]) " Clear
" Location list (buffer-local)
:lopen
:lnext / :lprevQuickfix with grep
" Define a grepprg (use ripgrep if available)
:set grepprg=rg\ --vimgrep\ --no-heading
:set grepformat=%f:%l:%c:%m
:grep "function.*validate" --type js
:copen
" Now use :cn, :cp to navigate matchesVimscript
Vimscript (VimL) is Vim’s scripting language for configuration and plugins:
Variables and Types
" Variable types
let g:global_var = "global"
let s:script_var = "script-local"
let l:local_var = "function-local"
let w:window_var = "window-local"
let b:buffer_var = "buffer-local"
" Data types
let string = "hello"
let number = 42
let float = 3.14
let list = ['a', 'b', 'c']
let dict = {'key': 'value', 'num': 42}
" String concatenation
let full = "Hello" . " " . "World"
" List operations
let first = list[0]
call add(list, 'd')
let len = len(list)Functions
function! Greet(name) abort
return "Hello, " . a:name . "!"
endfunction
" Call it
echo Greet("Alice")
" Function with default argument
function! Multiply(a, ...) abort
let b = get(a:000, 0, 1) " Default to 1
return a:a * b
endfunction
" Range function
function! SumLines() range abort
let total = 0
for line in getline(a:firstline, a:lastline)
let total += str2nr(line)
endfor
return total
endfunction
" Call on visual selection
:'<,'>call SumLines()Commands
" Define a custom command
command! -nargs=1 Grep call GrepWithRipgrep(<q-args>)
command! -nargs=0 FormatCode call FormatCurrentFile()
function! FormatCurrentFile()
write
execute "!prettier --write " . expand('%')
edit!
endfunction
" Command with completion
command! -nargs=? -complete=dir ListFiles
\ echo globpath(<q-args> . '/', '*')Autocommands
" Automatically run actions on events
augroup mygroup
autocmd!
" Auto-indent JSON files
autocmd BufRead,BufNewFile *.json setlocal tabstop=2 shiftwidth=2
" Strip trailing whitespace on save
autocmd BufWritePre * :%s/\s\+$//e
" Reload vimrc on save
autocmd BufWritePost $MYVIMRC source $MYVIMRC
augroup ENDPlugin Development
Minimal Plugin Structure
~/.vim/pack/myplugins/opt/myplugin/
├── plugin/
│ └── myplugin.vim " Loaded on startup
├── autoload/
│ └── myplugin.vim " Lazy-loaded functions
├── doc/
│ └── myplugin.txt " Help file
└── syntax/
└── myfiletype.vim " Syntax highlightingA Simple Plugin
" ~/.vim/pack/myplugins/opt/hello/plugin/hello.vim
if exists('g:loaded_hello')
finish
endif
let g:loaded_hello = 1
command! Hello echo "Hello from my plugin!"
function! hello#greet(name)
return "Hello, " . a:name . "!"
endfunctionAutoload Functions
" ~/.vim/pack/myplugins/opt/hello/autoload/hello.vim
" These functions are only loaded when called
function! hello#format_json()
%!python3 -m json.tool
setlocal filetype=json
endfunction
function! hello#timestamp()
put =strftime('%Y-%m-%d %H:%M:%S')
endfunctionCommon Advanced Vim Mistakes
1. Forgetting to Use Named Registers
Using ""p pastes the last yank or delete — which might be from a macro. Use "0p for the last explicit yank. Named registers ("a-"z) prevent accidental overwrites.
2. Recording Macros Without Error Handling
If a macro uses j (move down) and hits the last line, the rest fails silently. Add error handling: use :normal! @a with silent! or structure macros to stop at clear boundaries.
3. Overwriting "0 Register
Every yank or delete overwrites registers. Use uppercase registers ("A) to append. Use "0p to paste the last yank even after other operations.
4. Not Using Relative Line Numbers
set relativenumber shows distances from the cursor, enabling 5j (5 lines down) instead of counting absolute line numbers.
5. Ignoring the .vimrc
A default Vim installation is painful. Every Vim user should customize settings. Start with basic settings (line numbers, tabs as spaces, search highlight) and add features as needed.
6. Manual Repeated Edits
If you do the same edit on 20 lines, don’t repeat manually 20 times. Record a macro once and replay it 20 times. If you do a task more than 3 times, macro it.
7. Not Using Quickfix for Search
:vimgrep + quickfix is faster and more structured than :grep. It shows matches in a navigable list with file paths and line numbers, and you can jump to each match with :cn.
Practice Questions
1. What’s the difference between 'a and `a when jumping to a mark?
'a jumps to the first non-blank character of the line containing mark a. `a jumps to the exact line and column where the mark was set.
2. How do you run a macro on a visual selection of lines?
Select the lines, then press :normal @a (the :'<,'> range is automatically inserted). This runs macro a on each selected line.
3. How do you copy text to the system clipboard from Vim?
Use "+y to yank into the system clipboard register. Use "*y for the primary selection (middle-click paste on Linux).
4. What is the difference between quickfix and location lists? Quickfix lists are global — shared across all windows. Location lists are per-window. Use quickfix for project-wide results (compile errors, search), location lists for buffer-specific results.
5. Challenge: You need to rename a variable oldName to newName in 15 files, but only when oldName is used as a method receiver (not a local variable). Write a Vimscript function using quickfix to do this safely.
Answer: Use :vimgrep with a pattern matching the specific context. Navigate quickfix with :cn, check each match, and use :s if correct. Or write a function that iterates the quickfix list and conditionally substitutes.
Mini Project: Build a Log Navigation Plugin
Create a Vim plugin that helps navigate structured log files:
" ~/.vim/pack/myplugins/opt/lognav/plugin/lognav.vim
if exists('g:loaded_lognav')
finish
endif
let g:loaded_lognav = 1
" Command to open quickfix with all ERROR lines
command! -range=% LogErrors call lognav#find_errors(<line1>, <line2>)
" Command to navigate to next WARNING
command! LogNextWarning call lognav#next_warning()
" Command to show log summary
command! LogSummary call lognav#summary()" ~/.vim/pack/myplugins/opt/lognav/autoload/lognav.vim
function! lognav#find_errors(line1, line2) range
cexpr []
silent execute a:line1 . ',' . a:line2 . 'g/ERROR/caddexpr expand("%") . ":" . line(".") . ": " . getline(".")'
copen
echo "Found " . len(getqflist()) . " errors"
endfunction
function! lognav#next_warning()
if !exists('w:warning_pattern')
let w:warning_pattern = 'WARNING'
endif
call search(w:warning_pattern)
endfunction
function! lognav#summary()
let errors = 0
let warnings = 0
let infos = 0
for line in getline(1, '$')
if line =~ 'ERROR' | let errors += 1
elseif line =~ 'WARNING' | let warnings += 1
elseif line =~ 'INFO' | let infos += 1
endif
endfor
echo printf("Log Summary: %d ERROR, %d WARNING, %d INFO", errors, warnings, infos)
endfunctionFAQ
What’s Next
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro