Vim for Productivity โ Practical Motions, Macros, and the Plugins That Matter
Not a Vim-vs-VSCode argument. Just the motions, macros, and habits that made me genuinely faster at editing text after years of resisting modal editing.

Tried to learn Vim three times before it stuck. First attempt: opened it, couldn't figure out how to type, closed the terminal. Second attempt: completed vimtutor, used Vim for a day, went back to VSCode because I was slower at everything. Third attempt: I was SSH'd into a production server at 11 PM debugging a config file, and nano wasn't installed. Only Vim was available. Had to actually use it. Fixed the config. Something clicked. Not "I love Vim now" clicked, but "oh, this movement model actually makes sense for specific tasks" clicked.
I'm not here to tell you Vim is better than your editor. I use Neovim as my primary editor now, but I understand why most people use VSCode and I don't think they're wrong. What I want to share is the subset of Vim that made a practical difference in my editing speed โ the motions, the text objects, the macros, and the few plugins that earn their complexity.
The Modal Editing Concept (and Why It Feels Backwards)
Every other editor works in one mode: you press a key, a character appears. Vim has modes. Normal mode is for navigation and commands. Insert mode is for typing. Visual mode is for selecting. The Escape key returns you to Normal mode from anywhere.
This feels backwards because you spend most of your time in Normal mode, not Insert mode. In a typical editing session, you're not typing new text most of the time. You're reading, navigating, deleting, copying, pasting, restructuring. Vim's argument is: the mode you're in most should be optimized for the thing you do most, which is moving around and manipulating existing text.
Once this reframed my thinking, the keybindings started making sense. h, j, k, l for left, down, up, right. Your fingers stay on the home row. No reaching for arrow keys.
But nobody gets fast at Vim by learning hjkl. That's the equivalent of walking everywhere in a city that has a subway system. The speed comes from the higher-level motions.
Motions That Actually Matter
The motions I use most, ordered by how much time they save:
Word motions. w moves to the start of the next word. b moves to the start of the previous word. e moves to the end of the current word. These replace arrow-key tapping entirely. Need to move five words forward? 5w. Precise. Fast.
Line motions. 0 goes to the start of the line. $ goes to the end. ^ goes to the first non-blank character. I use ^ more than 0 because most lines start with indentation.
Search. /searchterm and press Enter. Jumps to the next occurrence. n goes to the next match. N goes to the previous. * searches for the word under the cursor โ this one is wildly useful for finding all occurrences of a variable name. Put your cursor on userId, press *, and you jump through every instance in the file.
Find character. f followed by a character jumps to the next occurrence of that character on the current line. fc jumps to the next c. t does the same but stops one character before. ; repeats the last find. I use f constantly for precise horizontal movement โ faster than counting words.
Paragraph motions. { and } jump to the previous/next blank line. In code, this usually means jumping between function definitions or block boundaries. Faster than scrolling.
Marks. ma sets a mark at the current position named a. `a jumps back to that exact position. When I'm working on something, need to reference another part of the file, and want to come back โ set a mark, go look, jump back. Like a bookmark in your editor, but faster to set and use.
The compound effect of these motions is significant. Instead of "hold arrow key until cursor reaches the right place," every movement is a deliberate jump. 3w to move three words. f( to jump to the opening parenthesis. /return to find the next return statement. Each motion is precise and intentional.
Text Objects โ The Feature Nobody Teaches First
Text objects are what made me stay with Vim. They should be taught in the first five minutes. Instead, most tutorials save them for "advanced" content. They're the most practical feature in the editor.
A text object is a structural selection: a word, a sentence, a paragraph, the contents inside quotes, the contents inside parentheses, an HTML tag. You combine them with operators to do things like "delete inside quotes" or "change inside parentheses."
The syntax is: {operator}{a or i}{object}
ameans "around" (includes the delimiters)imeans "inside" (excludes the delimiters)
di" โ delete inside quotes
da" โ delete the quotes and their contents
ci( โ change inside parentheses (deletes contents and enters insert mode)
yi{ โ yank (copy) inside curly braces
vat โ visually select around an HTML tag
dap โ delete around paragraph (including trailing blank line)
ciw โ change inside word (deletes word and enters insert mode)
Practical examples of what this looks like:
// Cursor anywhere inside the string:
const name = "Anurag Sinha";
// Type ci" โ deletes "Anurag Sinha", puts cursor inside empty quotes in insert mode
// Type the new name โ done
// Cursor anywhere inside the parentheses:
function greet(name, greeting, language) {
// Type di( โ deletes all parameters, leaves empty parens: greet()
// Cursor anywhere inside the object:
const config = { host: 'localhost', port: 3000, debug: true };
// Type ci{ โ deletes object contents, cursor inside empty braces in insert mode
No selecting with mouse. No shift-arrow to extend selection. No "did I select the right thing?" anxiety. ci" means "change inside quotes" regardless of where your cursor is within the quoted string. The text object handles the selection. You just specify what to do and what the target is.
I use ciw (change inside word) probably fifty times a day, from what I've seen in my own workflow. Cursor on a variable name, ciw, type the new name. No double-click to select, no shift-ctrl-arrow to select the whole word. Three keystrokes and I'm typing the replacement.
The Dot Command โ Repeating Actions
The . key repeats the last change. This sounds trivial. In practice, it's one of the most powerful features.
Say I need to change var to const in several places:
/varโ find the first occurrenceciwโ change inside word (deletesvar, enters insert mode)- Type
constand press Escape nโ go to the next occurrence.โ repeat the change (changesvartoconstagain)n.n.n.โ keep going
Steps 4-6 are each one keystroke. Find, replace, find, replace. No find-and-replace dialog. No "replace all" that might catch things you didn't want. Visual confirmation at each step with a single key to apply the change.
The dot command combines with any operation. Delete a line with dd, then . deletes another line. Indent a block with >ap, then . indents the next paragraph. Whatever compound action you just did, . does it again.
Macros โ Automation in Real Time
Macros are, I think, the dot command on steroids. Record a sequence of commands, replay them any number of times.
Real scenario: I have a CSV file with 500 lines and need to convert each line to a SQL INSERT statement.
John,Doe,john@email.com
Jane,Smith,jane@email.com
Needs to become:
INSERT INTO users (first, last, email) VALUES ('John', 'Doe', 'john@email.com');
INSERT INTO users (first, last, email) VALUES ('Jane', 'Smith', 'jane@email.com');
Record a macro on the first line:
qaโ start recording into registeraIโ insert at beginning of line- Type
INSERT INTO users (first, last, email) VALUES (' - Press Escape
f,โ find the first commas', 'โ substitute comma with', '- Press Escape
f,โ find the next commas', 'โ substitute again- Press Escape
A');โ append at end of line- Press Escape
j0โ move to the beginning of the next lineqโ stop recording
Play it on the remaining 499 lines: 499@a
The whole transformation takes about 15 seconds. The alternative โ writing a Python script, or doing find-and-replace in multiple passes โ takes longer for a one-off task, in my experience. Macros excel at these ad-hoc text transformations that don't justify writing a script.
Key insight: end your macro on the start of the next line (j0 or j^). That way the macro naturally chains โ each replay starts where the previous one ended.
Splits and Buffers โ Working with Multiple Files
Vim's split model is different from tabs. A buffer is an open file. A window is a viewport into a buffer. A tab is a collection of windows.
:e path/to/file.tsx โ open a file in the current window
:split โ horizontal split
:vsplit โ vertical split
Ctrl-w h/j/k/l โ move between splits
Ctrl-w = โ equalize split sizes
:ls โ list all open buffers
:b 3 โ switch to buffer 3
:bn โ next buffer
:bp โ previous buffer
I keep two or three vertical splits open most of the time. Test file on the left, implementation on the right. Or component on the left, its types on the right. The Ctrl-w commands become muscle memory after a few days.
A detail that confused me initially: closing a window (:q or Ctrl-w c) doesn't close the buffer. The file is still open in Vim's memory. You can switch back to it with :b filename. This means you can have 20 files "open" and only display 2-3 at a time. Lightweight and fast.
Plugins That Earn Their Keep
Neovim's plugin ecosystem is vast. Most plugins aren't worth the configuration time. Here are the ones that made a measurable difference in my workflow.
Telescope (nvim-telescope/telescope.nvim) โ Fuzzy finder for everything. Files, grep results, buffers, git commits, LSP symbols. Press a key, type a few characters, results narrow in real time. This is the Vim equivalent of VSCode's Ctrl+P. Without it, navigating a large project is painful. With it, I can jump to any file in the codebase in under two seconds.
LSP config (neovim/nvim-lspconfig) โ Language Server Protocol support. Gives you autocompletion, go-to-definition, find references, rename across files, inline diagnostics. The same language intelligence that VSCode provides, running the same language servers. Setting this up was the moment Neovim stopped feeling like "a text editor" and started feeling like "an IDE that happens to use modal editing."
Treesitter (nvim-treesitter/nvim-treesitter) โ Syntax-aware highlighting and text objects. Instead of regex-based syntax highlighting (which breaks on edge cases), Treesitter parses your code into an AST and highlights based on structure. Also enables text objects based on syntax: select a function, select a class, select a conditional block. vaf to select around a function. vic to select inside a class.
Oil.nvim (stevearc/oil.nvim) โ File explorer that works like a buffer. Your file system is displayed as editable text. Rename a file by changing its name in the buffer. Delete by removing the line. Move by cutting and pasting to another directory. Bizarre concept. Surprisingly natural in practice.
Gitsigns (lewis6991/gitsigns.nvim) โ Shows git diff markers in the sign column. Which lines are added, modified, deleted. Navigate between changes with ]c and [c. Stage individual hunks without leaving the editor. This replaced most of my git GUI usage.
That's it. Five plugins. Some people run 50+ plugins. I tried that phase. The configuration overhead, the plugin conflicts, the startup time โ not worth it. These five cover the gaps between "text editor" and "development environment" without turning Neovim into a fragile configuration project.
My init.lua โ The Practical Parts
Not sharing my entire config โ that would take a separate post. But a few settings that make a big quality-of-life difference:
-- Line numbers: relative in normal mode, absolute in insert
vim.opt.number = true
vim.opt.relativenumber = true
-- Search: case-insensitive unless I use uppercase
vim.opt.ignorecase = true
vim.opt.smartcase = true
-- Indentation: spaces, not tabs. 2-wide for most things.
vim.opt.expandtab = true
vim.opt.shiftwidth = 2
vim.opt.tabstop = 2
-- Keep cursor centered when scrolling
vim.opt.scrolloff = 8
-- System clipboard integration
vim.opt.clipboard = 'unnamedplus'
-- Leader key: space (most accessible key on the keyboard)
vim.g.mapleader = ' '
-- Quick save
vim.keymap.set('n', '<leader>w', ':w<CR>')
-- Clear search highlighting
vim.keymap.set('n', '<leader>h', ':nohlsearch<CR>')
-- Move selected lines up/down in visual mode
vim.keymap.set('v', 'J', ":m '>+1<CR>gv=gv")
vim.keymap.set('v', 'K', ":m '<-2<CR>gv=gv")
Relative line numbers are the setting that made number-prefixed motions practical. With relative numbers, the line 7 lines above shows 7 in the margin. Want to jump there? 7k. No counting. The number is right there.
Smart case search means /user matches user, User, and USER. But /User (with the capital U) matches only User. You get case-insensitive by default, case-sensitive when you specify.
System clipboard integration means y and p work with the system clipboard. Copy in Vim, paste in the browser. Copy in the browser, paste in Vim. Without this setting, Vim uses its own internal registers and you need "+y to yank to the system clipboard, which nobody wants to type.
The Muscle Memory Timeline
Being honest about the learning curve: I was slower in Vim for roughly three weeks. Not slightly slower โ significantly slower. Tasks that took 30 seconds in VSCode took 2 minutes because I kept pausing to think about which key to press.
Week 1-2: Painful. Constantly forgetting which mode I'm in. Typing jjjjjjj into my code because I forgot to press Escape. Having to Google basic operations. Considered quitting daily.
Week 3-4: Functional. Basic motions are becoming automatic. ciw, dd, yy, p are muscle memory. Still slower for complex operations but not frustratingly so.
Month 2-3: Productive. Text objects and the dot command are natural. Starting to use macros for repetitive tasks. Speed is comparable to my old editor for most tasks, faster for some.
Month 4+: Genuinely faster for text manipulation. The combination of motions, text objects, and macros means certain tasks that required multiple mouse interactions or multiple find-and-replace passes are now a few keystrokes. Not faster for everything โ auto-imports and multi-cursor editing are areas where VSCode's approach is more intuitive.
The key to surviving the slow period: don't switch cold turkey. I used the VSCode Vim extension for months before moving to Neovim. Got the modal editing muscle memory in a familiar environment with all my existing keybindings and extensions as a safety net. When the Vim motions were automatic, the transition to actual Neovim was about configuring the editor, not relearning how to edit text.
When Vim Isn't the Right Tool
Vim excels at text editing, code manipulation, and terminal-based workflows. It's not the best choice for everything.
Heavy IDE features. If you rely on advanced debugging with breakpoints and variable inspection, GUI-based git merge conflict resolution, or visual database tools โ a full IDE does those better. You can set up most of these in Neovim with plugins, but the setup cost is real and the result is often a worse version of what IntelliJ or VSCode provides out of the box.
Collaborative editing. Real-time pair programming with shared cursors? VSCode Live Share handles this natively. Vim has no equivalent that works as smoothly.
Quick one-off edits. If someone sends me a JSON file to look at, I might open it in whatever's fastest. The overhead of opening a terminal and launching Vim for a 30-second task doesn't always make sense.
The honest assessment: Vim made me faster at the kind of editing I do most โ writing and refactoring code, editing configuration files, processing text. It didn't make me faster at everything. The investment was worth it for me because text editing is most of my job, though I could be wrong about how universal that benefit is, not sure. If your work is more visual โ designing UIs, working with graphical tools, managing complex project configurations โ a graphical editor might be a better fit and that's completely fine.
What I'd tell someone considering Vim: try the VSCode Vim extension for a month. If the modal editing model clicks for you, explore Neovim. If it feels like fighting your tools instead of using them, move on. There are better uses of your time than forcing yourself to use an editor that doesn't match how you think.
Keep Reading
- Linux CLI: The Commands I Use Every Day โ Vim lives in the terminal; mastering the surrounding command-line tools makes the workflow complete.
- Post-Mortem: A Disastrous Git Merge and the Resulting Workflow โ Vim and Git are a natural pair; this covers the branching discipline that keeps your commits clean.
Written by
Anurag Sinha
Full-stack developer specializing in React, Next.js, cloud infrastructure, and AI. Writing about web development, DevOps, and the tools I actually use in production.
Stay Updated
New articles and tutorials sent to your inbox. No spam, no fluff, unsubscribe whenever.
I send one email per week, max. Usually less.
Comments
Loading comments...
Related Articles

automation_scripts.py: A Blog Post in 150 Lines of Code
Four Python scripts I actually use. Bulk renamer, downloads folder organizer, duplicate finder, and a website change detector.

Contributing to Open Source โ From First-PR Anxiety to Merged Code
How I got past the fear of my first pull request, found projects worth contributing to, and learned to read unfamiliar codebases without drowning.

Design Patterns in JavaScript โ The Ones That Actually Show Up in Real Code
Forget the Gang of Four textbook. These are the patterns I see in production JavaScript and TypeScript codebases every week โ observer, factory, strategy, and the ones nobody names but everyone uses.