DAY TO DAY VIM
Learning Vim
Chapter 8 — Day to Day Vim
Introduction
Vim is not just a text editor — it is a power tool for working with files, diffs, archives, and external programs, all without leaving the keyboard. This chapter covers the features you will reach for every day once you know they exist: startup options that automate editing tasks, a proper understanding of diff mode, editing files inside archives, jumping to files by name, and piping buffer content through external programs.
Individually, none of these is complex. Together they turn Vim into a surprisingly versatile part of your shell workflow.
Command-Line Startup Options
When you open Vim from the command line you can pass instructions that run automatically the moment the file loads. This is Vim's scripting entry point — a way to drive the editor without interactive input.
The + prefix: jump to a line or pattern
Any argument beginning with + is a command Vim executes after opening the file.
# Open a file at a specific line number vim hello.go +8 # cursor lands on line 8 vim notes.txt +42 # jump straight to line 42 vim report.md +$ # open at last line of file # Search on open — jump to the first match vim sherlock.txt +/crumb # positions cursor at first "crumb" vim app.log +/ERROR # jump to first ERROR in a log file vim config.yaml +/host # find the "host" key immediately
filename:line: message. You can open the offending file directly at the error line from the shell: vim src/main.c +47 — no scrolling required.
Multiple + commands: automated editing
You can chain several + commands. They execute in order, left to right, before you see the file. This makes Vim usable as a one-liner scriptable editor in shell pipelines.
# Delete lines 1–2 (header rows) then save and quit vim +"1,2d" +wq conway.txt # Delete line 4 and save vim +4d +wq time.txt # Replace all occurrences in a file without opening it interactively vim +"%s/localhost/prod.server.com/g" +wq config.txt # Append a line to the end of a file vim +"normal G" +"put ='# end of file'" +wq notes.md
sed: For simple one-off substitutions sed -i is often shorter. But for multi-step operations (delete a range, reformat, save), or when you want to preview the result first, Vim's + chain gives you the full power of the editor's command language in a single shell command.
Other useful startup flags
| Flag | Effect |
|---|---|
vim +{n} file | Open at line n |
vim +/{pattern} file | Open and jump to first match of pattern |
vim -R file | Read-only mode — prevents accidental edits |
vim -d file1 file2 | Diff mode — compare two files side by side |
vim -u NONE file | Skip vimrc — useful for diagnosing config issues |
vim -n file | No swap file — faster for read-only browsing |
vim -c "{cmd}" file | Equivalent to +"{cmd}" — run command after open |
Diff Mode
Diff mode is one of Vim's most powerful features and frequently overlooked. It opens two (or more) files in synchronised split windows, highlights every difference, and lets you move changes between files with single-key commands.
Opening diff mode
# From the command line — the most common way vim -d road1.txt road2.txt # horizontal split by default vimdiff road1.txt road2.txt # alias — identical to vim -d vimdiff v1.py v2.py v3.py # three-way diff is possible # From inside Vim, with a file already open :diffsplit road2.txt # open road2 below in diff mode :vert diffsplit road2.txt # open road2 to the right (vertical)
Reading the diff display
│
Two roads diverged │ Two roads diverged
− in a yellow wood, │+ in a yellow wood
│ and looked down one
And sorry I could not│ And sorry I could not
Highlighted lines are differences. Fold-collapsed sections are identical text. The vertical bar separates the two windows.
Navigating between differences
]c # jump to next difference (change) [c # jump to previous difference Ctrl+W w # move to the other diff window Ctrl+W h/l # move left/right between windows explicitly
Applying changes between files
This is the heart of diff mode. You can pull a change from the other file (obtain) or push your version to the other file (put) without copying and pasting.
do # diff Obtain — pull change from the OTHER file into THIS one dp # diff Put — push change from THIS file into the OTHER one
do → copies the OTHER window's version of this diff block into YOUR window
dp → copies YOUR window's version of this diff block into the OTHER window
Think: "do = take" and "dp = give".
Updating the diff after manual edits
:diffupdate # recalculate the diff (if the highlighting looks stale) :set diffopt+=iwhite # ignore whitespace-only differences
Making vertical diff the default
By default vimdiff opens a horizontal split. Most people find a vertical (side-by-side) layout easier to read. Add this to your .vimrc:
" In .vimrc — always use vertical (side-by-side) diff layout
set diffopt+=vertical
vimdiff supports up to eight files simultaneously. Git's mergetool integration uses three-way diff (local / base / remote) when you run git mergetool with Vim configured as the merge tool. This is a genuinely useful alternative to GUI merge tools once you are comfortable with do and dp.
Editing Files Inside Archives
Vim can open and edit files inside ZIP and TAR archives directly — without you needing to extract them first. This works through Vim's netrw and archive plugins, which are bundled with a standard Vim installation.
Opening a ZIP file
vim weather.zip # shows a directory listing of the archive's contents
" Browsing zipfile /home/user/weather.zip
" Select a file with cursor and press ENTER
weather/
weather/forecast.txt
weather/historical.csv
weather/README.md
- Navigate the listing with
j/k - Press
Enterto open a file for editing - Edit it as normal
- Save with
:w— changes are written back into the ZIP - Press
-or:bpreviousto return to the archive listing
zip and unzip utilities must be available in your PATH. They almost always are on macOS and most Linux distributions. On Windows, ensure that a ZIP utility is installed and accessible from the command line (Git Bash or WSL both provide this automatically).
TAR archives
The same workflow applies to .tar, .tar.gz, and .tar.bz2 files. Vim detects the archive type automatically from the file extension and delegates to the appropriate utility.
vim project.tar.gz # shows contents of the tar archive vim backup.tar.bz2 # works the same way
File Navigation: gf and gF
The gf command is deceptively simple and enormously useful: it opens whatever filename or path is currently under the cursor. If you are reading code that imports another module, reviewing a config that references a path, or looking at a compiler error, gf jumps straight there.
gf — go to file
# With cursor on the filename in any of these contexts: # Source code imports require('./utils/helper') # cursor on helper → gf opens the file import "config/settings.go" # gf opens settings.go include "auth.h" # gf opens auth.h # Log and error output main.py:47: NameError: name 'foo' not defined # cursor on main.py → gf opens main.py
gf # open file under cursor (replaces current window) Ctrl+W f # open file under cursor in a NEW horizontal split Ctrl+W gf# open file under cursor in a NEW TAB
gf, press Ctrl+O to return to your previous position (it is a jump, so it lands in the jump list).
gF — go to file at line
gF extends gf — it also reads a line number from the text under the cursor. This is the format that compilers, linters, and most error-reporting tools produce.
# Compiler / linter error format: filename:line:column: message src/parser.c:83:12: error: expected ';' before '}' # gF on src/parser.c:83:12 → opens parser.c AND moves cursor to line 83 # Also works with just filename:line app.py:47 # gF jumps to app.py line 47 # Or with :line syntax written inline config/database.yml:2 # gF opens database.yml at line 2
Helping Vim find files: path setting
By default Vim looks for files relative to the current directory. If your imports use bare names without paths (common in C and Python projects), add the project source directories to Vim's path so gf can find them:
" In .vimrc or set interactively: :set path+=src/** # search src/ and all subdirectories :set path+=include/** # also search include/
URL fetching with gf
If wget or curl is installed, Vim can use gf to fetch and display a URL directly in the buffer. With your cursor on a URL like https://example.com/data.json, pressing gf will download and display the content. This requires the netrw plugin to be active (it is by default).
# Cursor on any URL in the buffer: https://raw.githubusercontent.com/user/repo/main/README.md # gf → downloads and displays the file content in Vim
External Command Integration
Vim's relationship with the shell goes beyond just running commands and viewing output. You can insert shell output directly into a buffer, and — crucially — you can pipe your entire buffer (or a range of lines) through an external program and replace it with the result. This makes Vim a composable part of your command-line toolkit.
Running shell commands from Vim
:!date # display the current date/time in a shell overlay :!ls -la # list directory contents :!git status # check git status without leaving Vim :!python3 % # run the current file (% = current filename) :!make # build the project
The output appears in a full-screen shell overlay. Press Enter to return to your buffer. Nothing is inserted into the file.
Reading command output into the buffer
The :r ! form (read from command) inserts the stdout of a shell command below the cursor. This is for when you want the output to become part of the file.
:r !ls # insert file listing below cursor :r !date # insert current timestamp :r !ping -c 3 www.vim.org # insert ping results :r !cat /etc/hosts # insert hosts file content :r !curl -s https://api.example.com/data # fetch and insert API response # Specify where to insert: :0r !date # insert at top of file (before line 1) :$r !date # insert at end of file
Filtering buffer content through external programs
This is the most powerful form of external integration. The :%! command sends your entire buffer as stdin to an external program, then replaces the buffer with the program's stdout. You are using the external tool as a transformer — a formatter, a converter, a filter.
# Format / pretty-print :%!jq . # format JSON with proper indentation (requires jq) :%!python3 -m json.tool # format JSON with Python (no extra install) :%!tidy -xml -i # format XML/HTML with tidy :%!xmllint --format - # format XML with xmllint # Sort and deduplicate :%!sort # sort all lines alphabetically :%!sort -u # sort and remove duplicate lines :%!uniq # remove consecutive duplicate lines # Binary / hex editing :%!xxd # convert buffer to hex dump view :%!xxd -r # convert hex dump back to binary (after editing) # Text processing :%!tr 'a-z' 'A-Z' # convert all text to uppercase :%!wc -l # replace buffer with just the line count
Filtering a range, not the whole file
You can filter just a selection of lines. This is useful for sorting a block, formatting a specific JSON object, or processing only part of a file.
# Range-based filtering: :5,15!sort # sort only lines 5 through 15 :'<,'>!sort # sort the current Visual selection :'<,'>!jq . # format the selected JSON fragment
In Visual mode, after selecting lines with V, type ! and Vim will automatically fill in :'<,'>! — just complete the command.
:%! is destructive — back up first. If the external command fails or produces unexpected output, the buffer is replaced with the error message or empty output. Fortunately, this is a single undoable change — press u immediately to restore the original content. Always check your command works on a test input before running it on a file you care about.
The xxd hex editing workflow
The combination of :%!xxd and :%!xxd -r gives you a genuine hex editor inside Vim. This is how binary files (executables, images, compiled bytecode) can be edited: