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
Why this matters for compiler errors: Most build systems print errors as 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
When to use this vs. 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

FlagEffect
vim +{n} fileOpen at line n
vim +/{pattern} fileOpen and jump to first match of pattern
vim -R fileRead-only mode — prevents accidental edits
vim -d file1 file2Diff mode — compare two files side by side
vim -u NONE fileSkip vimrc — useful for diagnosing config issues
vim -n fileNo swap file — faster for read-only browsing
vim -c "{cmd}" fileEquivalent 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

vimdiff road1.txt road2.txt
road1.txt                │ road2.txt
                         │
 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
How do / dp work
You have two windows open side by side. Your cursor is on a highlighted difference.

docopies the OTHER window's version of this diff block into YOUR window
dpcopies 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
Three-way merges: 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
vim weather.zip — what you see
" zip.vim version v28
" 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 Enter to open a file for editing
  • Edit it as normal
  • Save with :w — changes are written back into the ZIP
  • Press - or :bprevious to return to the archive listing
Platform requirements: On Linux and macOS, the 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
When is this useful? Typical use cases: quickly checking what is inside a downloaded archive, editing a config file buried in a deployment ZIP, or patching a single file in a release tarball without unpacking the entire thing. It is a surprisingly handy trick that most editors cannot match.

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
Return from gf: After jumping with 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:

Binary editing with xxd

1 vim binaryfile.bin Open the binary file
2 :%!xxd Convert to hex dump — you now see hex values and ASCII on the right
3 Edit the hex values directly (only edit the hex column, not the ASCII column)
4 :%!xxd -r Convert the hex dump back to binary
5 :w Save — the file is written as binary

Putting It All Together

Practical workflow: fix a compiler error

From error message to fix without leaving the terminal

1 make 2>&1 | tee errors.txt Build and save compiler output to a file
2 vim errors.txt Open the error log in Vim
3 Move cursor to the filename:line in an error line
4 gF Jump directly to the file at the exact error line
5 Fix the error
6 :!make Rebuild without leaving Vim
7 Ctrl+O Return to errors.txt to check the next error

Practical workflow: review and merge a diff

Compare two versions and merge selectively

1 vimdiff old.conf new.conf Open both files in diff mode side by side
2 ]c Jump to the first difference
3 Review the change — decide which version to keep
4 do Obtain — accept the other file's version here
5 dp Put — push your version to the other file instead
6 ]c Move to the next difference and repeat
7 :wqa Save all and quit when done

Commands Introduced in This Chapter

Chapter 8 — Command Reference

Startup flags
vim +{n} fileOpen file at line n
vim +/{pattern} fileOpen file and jump to first match of pattern
vim +"{cmd}" fileRun a command after opening the file
vim -d file1 file2Open two files in diff mode
vim -R fileOpen file in read-only mode
vim -u NONE fileOpen without loading vimrc
vimdiff file1 file2Alias for vim -d
Diff mode
:diffsplit {file}Open file in diff mode (horizontal split)
:vert diffsplit {file}Open file in diff mode (vertical split)
]cJump to next difference
[cJump to previous difference
doDiff Obtain — pull change from the other file into this one
dpDiff Put — push change from this file into the other one
:diffupdateRecalculate and refresh the diff highlights
set diffopt+=verticalAlways use vertical (side-by-side) diff layout (vimrc)
set diffopt+=iwhiteIgnore whitespace differences in diff
Archive editing
vim {file}.zipBrowse and edit files inside a ZIP archive
vim {file}.tar.gzBrowse and edit files inside a TAR archive
File navigation
gfOpen file whose name is under the cursor
gFOpen file under cursor at the line number also under cursor
Ctrl+W fOpen file under cursor in a new horizontal split
Ctrl+W gfOpen file under cursor in a new tab
:set path+=src/**Add directories to the search path for gf
External commands
:! {cmd}Run a shell command; show output in overlay
:r ! {cmd}Insert shell command output below cursor
:0r ! {cmd}Insert shell command output at top of file
:%! {cmd}Filter entire buffer through external command (replaces content)
:{range}! {cmd}Filter a line range through external command
'<,'>! {cmd}Filter Visual selection through external command
:%!jq .Format JSON with jq
:%!sortSort all lines alphabetically
:%!xxdConvert buffer to hex dump
:%!xxd -rConvert hex dump back to binary