Skip to content

Instantly share code, notes, and snippets.

@pizofreude
Last active April 22, 2026 18:17
Show Gist options
  • Select an option

  • Save pizofreude/86a88175e388f60cb80105780f7d87fd to your computer and use it in GitHub Desktop.

Select an option

Save pizofreude/86a88175e388f60cb80105780f7d87fd to your computer and use it in GitHub Desktop.
Covers 14 sections: modal model, survival kit, navigation, insert modes, operators + text objects, search/replace, visual/block mode, registers + macros, marks, buffers/windows/tabs, ex commands, full `.vimrc`, full Neovim `init.lua` + `lazy.nvim` + LSP setup, analytics recipes, and the quick reference card. A few things worth calling out: - The…

Vi / Vim / Neovim Cheatsheet — Fellow Analytics Engineer

vi = POSIX baseline (available everywhere).
vim-only = requires Vim 8+.
nvim-only = Neovim-specific.
<leader> defaults to \ — most configs remap it to , or <Space>.


The Mental Model: Modes

Vim is a language, not a list of shortcuts. Commands compose:
[count] [operator] [motion] → e.g. 3dw = delete 3 words, ci" = change inside quotes.

Mode Enter With Purpose
Normal Esc / Ctrl+[ Navigate, issue commands — default mode
Insert i a o I A O Type text
Visual v V Ctrl+V Select characters / lines / blocks
Command : Ex commands (save, quit, search-replace)
Replace R Overwrite text
Operator-pending after d c y etc. Waiting for a motion

Analogy: Normal mode is like a CLI — you compose verbs (d, c, y) with nouns (motions like w, }, gg). Insert mode is just a dumb text field.


Saving & Exiting (Survival Kit)

Command Action
:w Save (write)
:wq or ZZ Save and quit
:q! or ZQ Quit without saving
:w !sudo tee % Save with sudo (forgot to open as root)
:wa Save all open buffers
:xa Save all and exit

Navigation — Normal Mode

Character / Line

Key Action
h j k l Left / down / up / right
0 / ^ / $ Start of line / first non-blank / end of line
gg / G First / last line of file
42G or :42 Jump to line 42
H / M / L Top / middle / bottom of screen
Ctrl+D / Ctrl+U Half-page down / up
Ctrl+F / Ctrl+B Full-page forward / back
zz / zt / zb Center / top / bottom cursor line on screen

Word Motion

Key Action
w / W Next word start (word / WORD)
b / B Previous word start
e / E Next word end
ge / gE Previous word end

word = alphanumeric+underscore; WORD = anything delimited by whitespace. Use W/B to jump over my_column_name as one unit.

File / Block

Key Action
% Jump to matching bracket () [] {}
{ / } Jump to previous / next blank line (paragraph)
[[ / ]] Previous / next section (function/class in Python)
Ctrl+O / Ctrl+I Jump back / forward in jump list
'' or `` Jump to position before last jump
'. Jump to last edit position
gd Go to local definition
gf Open file under cursor (great for source paths)

Entering Insert Mode

Key Action
i / a Insert before / after cursor
I / A Insert at line start / end
o / O Open new line below / above
s / S Substitute char / whole line
C Change to end of line
gi Re-enter Insert at last insert position

Operators (Verbs)

Combine with any motion or text object: [operator][motion/object]

Operator Action
d Delete (cut)
c Change (delete + enter Insert)
y Yank (copy)
> / < Indent right / left
= Auto-indent
gU / gu / g~ Uppercase / lowercase / toggle case
! Filter through external command

Text Objects (Nouns)

Object Meaning
iw / aw Inner word / a word (includes surrounding space)
is / as Inner sentence / a sentence
ip / ap Inner paragraph / a paragraph
i" i' i` Inside quotes
i( i[ i{ Inside brackets
i< Inside <tag>
it Inside HTML/XML tag

Analytics examples:
ci" → change SQL string literal
da{ → delete a Jinja {% block %} body including braces
=ip → re-indent a Python function block
gUiw → uppercase a SQL keyword under cursor


Common Edit Patterns

Command Action
dd Delete (cut) current line
yy or Y Yank current line
p / P Paste after / before cursor
x / X Delete char under / before cursor
r{c} Replace char under cursor with c
R Enter Replace mode (overwrite)
u / Ctrl+R Undo / redo
U Undo all changes on current line
. Repeat last change — the most powerful key in Vim
J Join next line to current
~ Toggle case of char under cursor
Ctrl+A / Ctrl+X Increment / decrement number under cursor

Search & Replace

Search (Normal mode)

Command Action
/pattern Search forward
?pattern Search backward
n / N Next / previous match
* / # Search word under cursor forward / backward
g* / g# Like */# but partial match
:noh Clear search highlight

Regex flavour

Vim uses its own regex. Key differences from Python re:

  • \+ = one or more (not +)
  • \| = alternation (not |)
  • Use \v (very magic) for Python-like syntax: /\vref\(\w+\)/

Substitute (Command mode)

:[range]s/pattern/replacement/[flags]
Example Action
:s/foo/bar/ Replace first match on current line
:s/foo/bar/g Replace all on current line
:%s/foo/bar/g Replace all in file
:%s/foo/bar/gc Replace all, confirm each
:'<,'>s/foo/bar/g Replace in visual selection
:5,20s/foo/bar/g Replace in lines 5–20
:%s/\v(ref|source)\(/func(/g Regex: rename dbt macros
:%s/foo/bar/gI Case-sensitive (override ignorecase)

Analytics tip: :5,42s/^/-- / comments out a SQL block from lines 5 to 42.


Visual Mode

Key Mode
v Character-wise visual
V Line-wise visual
Ctrl+V Block (column) visual
gv Re-select last visual selection
o Move to other end of selection

With block visual (Ctrl+V):

Action How
Insert same text in every selected line Ctrl+V → select lines → I → type → Esc
Delete a column Ctrl+V → select column → d
Indent a block V → select lines → > (repeat with .)

dbt tip: Block-insert -- across 10 lines to bulk-comment a CTE block.


Registers & Macros

Registers

Register Content
" Default (unnamed)
0 Last yank
19 Delete history (1 = most recent)
az Named — use deliberately
+ / * System clipboard / primary selection
% Current filename
: Last command
/ Last search pattern

Usage: "ayy = yank line into register a; "ap = paste from a.
System clipboard: "+y to copy, "+p to paste. (vim-only: requires +clipboard build.)

Macros

  1. q{a} — start recording into register a
  2. Perform actions
  3. q — stop recording
  4. @a — replay; @@ — replay last; 10@a — replay 10 times

Example: Record a macro to convert a Python print(x) to logger.info(x) across 50 lines, then 50@a.


Marks

Command Action
m{a-z} Set local mark
m{A-Z} Set global mark (cross-file)
`{mark} Jump to exact position of mark
'{mark} Jump to line of mark
:marks List all marks
`[ / `] Start / end of last yank or change
`< / `> Start / end of last visual selection

Buffers, Windows & Tabs

Buffers

Command Action
:e file Open file in new buffer
:ls or :buffers List open buffers
:b{n} Switch to buffer n
:bn / :bp Next / previous buffer
:bd Delete (close) buffer
:b# Alternate (last) buffer

Windows (splits)

Command Action
:sp file or Ctrl+W s Horizontal split
:vsp file or Ctrl+W v Vertical split
Ctrl+W hjkl Move between windows
Ctrl+W = Equalise window sizes
Ctrl+W _ / Ctrl+W | Maximise height / width
Ctrl+W r Rotate windows
Ctrl+W q Close window

Tabs (vim-only)

Command Action
:tabnew file Open file in new tab
gt / gT Next / previous tab
:tabclose Close current tab
:tabmove n Move tab to position n

Ex Commands (Command Mode)

Command Action
:!{cmd} Run shell command
:r !{cmd} Insert shell output at cursor
:%!python3 -m json.tool Pretty-print JSON in buffer
:%!sqlformat -r -k upper - Format SQL via sqlformat
:g/pattern/d Delete all lines matching pattern
:g/pattern/y A Yank all matching lines into register a
:v/pattern/d Delete all lines NOT matching pattern
:sort Sort lines
:sort u Sort and deduplicate
:sort n Numeric sort
:set ft=sql Force filetype (syntax highlighting) for current buffer
:syntax on Enable syntax highlighting

Analytics tip: :%!python3 -m json.tool is a fast in-place JSON formatter — useful when inspecting API responses or Airflow task instance JSON.


.vimrc — Analytics Engineer Config

" ============================================================
" BASICS
" ============================================================
set nocompatible               " Disable vi compatibility
filetype plugin indent on      " Enable filetype detection
syntax enable                  " Syntax highlighting

" ============================================================
" DISPLAY
" ============================================================
set number                     " Absolute line numbers
set relativenumber             " Relative numbers for motion planning
set cursorline                 " Highlight current line
set colorcolumn=88             " PEP8/Black line length guide
set signcolumn=yes             " Always show sign column (for git/lsp signs)
set scrolloff=8                " Keep 8 lines above/below cursor
set sidescrolloff=8
set wrap                       " Soft wrap
set linebreak                  " Wrap at word boundaries
set showmatch                  " Highlight matching brackets
set termguicolors              " 24-bit colour

" ============================================================
" EDITING BEHAVIOUR
" ============================================================
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab                  " Spaces not tabs
set autoindent
set smartindent
set backspace=indent,eol,start " Sane backspace
set hidden                     " Allow unsaved buffers in background
set updatetime=300             " Faster CursorHold (used by plugins)
set timeoutlen=500             " Key sequence timeout

" ============================================================
" SEARCH
" ============================================================
set incsearch                  " Incremental search
set hlsearch                   " Highlight matches
set ignorecase                 " Case-insensitive...
set smartcase                  " ...unless uppercase used
set grepprg=rg\ --vimgrep\ --smart-case  " Use ripgrep for :grep

" ============================================================
" FILES & HISTORY
" ============================================================
set undofile                   " Persistent undo across sessions
set undodir=~/.vim/undo//
set backup
set backupdir=~/.vim/backup//
set directory=~/.vim/swap//
set history=1000

" Create dirs if absent
silent !mkdir -p ~/.vim/undo ~/.vim/backup ~/.vim/swap

" ============================================================
" LEADER KEY
" ============================================================
let mapleader = " "            " Space as leader

" ============================================================
" KEY MAPPINGS
" ============================================================
" Clear search highlight
nnoremap <leader>h :noh<CR>

" Save / quit shortcuts
nnoremap <leader>w :w<CR>
nnoremap <leader>q :q<CR>

" Switch buffers
nnoremap <leader>bn :bn<CR>
nnoremap <leader>bp :bp<CR>
nnoremap <leader>bd :bd<CR>

" Window navigation (no Ctrl+W prefix)
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

" Keep visual selection after indent
vnoremap < <gv
vnoremap > >gv

" Y consistent with D and C (yank to end of line)
nnoremap Y y$

" Centre cursor after search jumps
nnoremap n nzzzv
nnoremap N Nzzzv

" Paste over selection without clobbering register
vnoremap p "_dP

" ============================================================
" PLUGIN MANAGER — vim-plug
" ============================================================
" Bootstrap: curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
"   https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

call plug#begin('~/.vim/plugged')

" --- Statusline ---
Plug 'itchyny/lightline.vim'         " Lightweight statusline
" Alternative: Plug 'vim-airline/vim-airline'

" --- File navigation ---
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'              " :Files :Rg :Buffers :Lines

" --- Git ---
Plug 'tpope/vim-fugitive'            " :Git blame, :Gdiff, :GBrowse
Plug 'airblade/vim-gitgutter'        " Inline diff signs in gutter

" --- Editing utilities ---
Plug 'tpope/vim-surround'            " cs"' — change surrounding " to '
Plug 'tpope/vim-commentary'          " gc{motion} — toggle comments
Plug 'tpope/vim-repeat'              " Extend . to plugin commands
Plug 'jiangmiao/auto-pairs'          " Auto-close brackets

" --- Syntax / Language ---
Plug 'sheerun/vim-polyglot'          " 100+ language packs (Python, SQL, YAML…)
Plug 'Vimjas/vimindent'              " Better Python indentation

" --- LSP / Completion (vim8 async) ---
Plug 'dense-analysis/ale'            " Async lint + fix (flake8, sqlfluff, yamllint)

" --- Theme ---
Plug 'catppuccin/vim', { 'as': 'catppuccin' }
" Alternative: Plug 'gruvbox-community/gruvbox'

call plug#end()

" ============================================================
" PLUGIN CONFIG
" ============================================================

" --- lightline ---
set laststatus=2
set noshowmode
let g:lightline = { 'colorscheme': 'catppuccin_mocha' }

" --- fzf ---
nnoremap <leader>f  :Files<CR>
nnoremap <leader>r  :Rg<CR>
nnoremap <leader>b  :Buffers<CR>
nnoremap <leader>/  :Lines<CR>
nnoremap <leader>gc :Commits<CR>
" fzf layout
let g:fzf_layout = { 'down': '40%' }

" --- vim-fugitive ---
nnoremap <leader>gs :Git<CR>
nnoremap <leader>gb :Git blame<CR>
nnoremap <leader>gd :Gdiffsplit<CR>

" --- ALE (linting + fixing) ---
let g:ale_linters = {
\  'python':     ['flake8', 'pylint'],
\  'sql':        ['sqlfluff'],
\  'yaml':       ['yamllint'],
\  'dockerfile': ['hadolint'],
\}
let g:ale_fixers = {
\  'python': ['black', 'isort'],
\  'sql':    ['sqlfluff'],
\  '*':      ['remove_trailing_lines', 'trim_whitespace'],
\}
let g:ale_fix_on_save = 1
let g:ale_sign_error   = ''
let g:ale_sign_warning = ''
nnoremap <leader>an :ALENextWrap<CR>
nnoremap <leader>ap :ALEPreviousWrap<CR>

" --- Colorscheme ---
colorscheme catppuccin_mocha

Neovim Section

Why Switch?

Feature Vim 8 Neovim
Lua config init.lua
Built-in LSP vim.lsp
Tree-sitter plugin built-in
Async everywhere partial
vim.fn / vim.api

Config Layout (~/.config/nvim/)

nvim/
├── init.lua              ← entry point
└── lua/
    └── config/
        ├── options.lua   ← set options
        ├── keymaps.lua   ← key bindings
        ├── lazy.lua      ← plugin manager bootstrap
        └── plugins/
            ├── lsp.lua
            ├── treesitter.lua
            ├── fzf.lua
            └── ...

init.lua (entry point)

require("config.options")
require("config.keymaps")
require("config.lazy")

lua/config/options.lua

local opt = vim.opt

opt.number         = true
opt.relativenumber = true
opt.tabstop        = 4
opt.softtabstop    = 4
opt.shiftwidth     = 4
opt.expandtab      = true
opt.autoindent     = true
opt.smartindent    = true
opt.termguicolors  = true
opt.scrolloff      = 8
opt.signcolumn     = "yes"
opt.updatetime     = 300
opt.undofile       = true
opt.ignorecase     = true
opt.smartcase      = true
opt.hlsearch       = true
opt.incsearch      = true
opt.colorcolumn    = "88"
opt.hidden         = true
opt.completeopt    = { "menuone", "noselect" }

lua/config/lazy.lua — Plugin Manager Bootstrap

-- Auto-install lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({ "git", "clone", "--filter=blob:none",
    "https://github.com/folke/lazy.nvim.git",
    "--branch=stable", lazypath })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup("config.plugins")

Key Analytics Plugins (lua/config/plugins/)

-- lua/config/plugins/init.lua
return {

  -- Statusline
  { "nvim-lualine/lualine.nvim",
    opts = { options = { theme = "catppuccin" } } },

  -- Fuzzy finder
  { "nvim-telescope/telescope.nvim",
    dependencies = { "nvim-lua/plenary.nvim" },
    keys = {
      { "<leader>f",  "<cmd>Telescope find_files<cr>" },
      { "<leader>r",  "<cmd>Telescope live_grep<cr>" },   -- needs ripgrep
      { "<leader>b",  "<cmd>Telescope buffers<cr>" },
      { "<leader>gs", "<cmd>Telescope git_status<cr>" },
    }
  },

  -- Treesitter (better syntax highlighting + text objects)
  { "nvim-treesitter/nvim-treesitter",
    build = ":TSUpdate",
    config = function()
      require("nvim-treesitter.configs").setup({
        ensure_installed = { "python", "sql", "yaml", "toml",
                             "json", "bash", "lua", "dockerfile" },
        highlight = { enable = true },
        indent    = { enable = true },
      })
    end
  },

  -- Git
  { "tpope/vim-fugitive" },
  { "lewis6991/gitsigns.nvim", opts = {} },

  -- Editing
  { "tpope/vim-surround" },
  { "tpope/vim-commentary" },
  { "windwp/nvim-autopairs", opts = {} },

  -- Theme
  { "catppuccin/nvim", name = "catppuccin", priority = 1000,
    config = function() vim.cmd("colorscheme catppuccin-mocha") end },
}

lua/config/plugins/lsp.lua — Built-in LSP

return {
  -- LSP installer
  { "williamboman/mason.nvim", opts = {} },
  { "williamboman/mason-lspconfig.nvim",
    opts = {
      ensure_installed = {
        "pyright",       -- Python (type checking, go-to-def)
        "ruff_lsp",      -- Python linting (replaces flake8/isort)
        "yamlls",        -- YAML (dbt profiles, GitHub Actions)
        "jsonls",        -- JSON
        "sqlls",         -- SQL (basic)
        "dockerls",      -- Dockerfile
      }
    }
  },
  -- LSP config
  { "neovim/nvim-lspconfig",
    config = function()
      local lsp = require("lspconfig")
      local on_attach = function(_, bufnr)
        local map = function(k, v)
          vim.keymap.set("n", k, v, { buffer = bufnr })
        end
        map("gd",         vim.lsp.buf.definition)
        map("gr",         vim.lsp.buf.references)
        map("K",          vim.lsp.buf.hover)
        map("<leader>rn", vim.lsp.buf.rename)
        map("<leader>ca", vim.lsp.buf.code_action)
        map("[d",         vim.diagnostic.goto_prev)
        map("]d",         vim.diagnostic.goto_next)
      end
      for _, server in ipairs({ "pyright", "ruff_lsp", "yamlls",
                                 "jsonls", "dockerls" }) do
        lsp[server].setup({ on_attach = on_attach })
      end
    end
  },
  -- Completion engine
  { "hrsh7th/nvim-cmp",
    dependencies = {
      "hrsh7th/cmp-nvim-lsp",
      "hrsh7th/cmp-buffer",
      "hrsh7th/cmp-path",
      "L3MON4D3/LuaSnip",
      "saadparwaiz1/cmp_luasnip",
    },
    config = function()
      local cmp = require("cmp")
      cmp.setup({
        snippet = { expand = function(a) require("luasnip").lsp_expand(a.body) end },
        mapping = cmp.mapping.preset.insert({
          ["<Tab>"]   = cmp.mapping.select_next_item(),
          ["<S-Tab>"] = cmp.mapping.select_prev_item(),
          ["<CR>"]    = cmp.mapping.confirm({ select = true }),
          ["<C-Space>"] = cmp.mapping.complete(),
        }),
        sources = {
          { name = "nvim_lsp" },
          { name = "luasnip" },
          { name = "buffer" },
          { name = "path" },
        }
      })
    end
  },
}

Analytics Workflow Recipes

" Format SQL in buffer via sqlformat
:%!sqlformat -r -k upper --indent_width 4 -

" Pretty-print JSON (e.g. Airflow task metadata)
:%!python3 -m json.tool

" Strip trailing whitespace across file
:%s/\s\+$//e

" Comment out dbt SQL block (lines 10-25)
:10,25s/^/-- /

" Uncomment dbt SQL block
:10,25s/^-- //

" Find all {{ ref( calls in dbt model
/\v\{\{[ ]*ref\(

" Global yank all lines containing 'TODO' into register t
:let @t = "" | :g/TODO/y T

" Count occurrences of a pattern
:%s/pattern//gn

" Open a vertical split with the current dbt model's schema.yml
:vsp schema.yml

Quick Reference Card

MODES          i=Insert  v=Visual  V=Line-V  ^V=Block-V  :=Command  Esc=Normal

NAVIGATION     gg/G=top/bot  ^D/^U=half-page  %=match  gd=definition  gf=open-file
               {/}=paragraph  Ctrl+O/I=jump back/fwd  '.=last edit

OPERATORS      d=delete  c=change  y=yank  >=indent  gu/gU=case  .=repeat
TEXT OBJECTS   iw/aw  i"/i'  i(/i[/i{  it=inside-tag  ip=paragraph

SEARCH         /pat  n/N=next/prev  */#=word-under-cursor  :noh=clear
REPLACE        :%s/foo/bar/gc   :'<,'>s/foo/bar/g   :g/pat/d

REGISTERS      "ay=yank→a  "ap=paste←a  "+y/p=clipboard  @a=macro  @@=repeat
MARKS          ma=set  `a=jump  mA=global  ''=before-jump  '.=last-edit

WINDOWS        ^W s/v=split  ^W hjkl=navigate  ^W ==equalise
BUFFERS        :ls  :bn/:bp  :bd  :b#=alternate
FILES (fzf)    <Space>f=files  <Space>r=grep  <Space>b=buffers

SAVE/QUIT      :w  :wq / ZZ  :q! / ZQ  :wa  :xa

External Tools Worth Pairing

Tool Install Use
ripgrep brew install ripgrep Powers :Rg / Telescope grep
fd brew install fd Fast file find for fzf/Telescope
sqlfluff pip install sqlfluff SQL lint + fix via ALE/LSP
black / ruff pip install black ruff Python format/lint on save
yamllint pip install yamllint dbt profiles.yml, GHA workflows
tree-sitter-cli npm i -g tree-sitter-cli Build custom TS grammars
pyright npm i -g pyright Python type checker via LSP

Last updated: 2026-04 | Vim 9+ / Neovim 0.10+ assumed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment