refactor: modular architecture — split .vimrc into 12 self-contained modules

Replace the monolithic 1268-line .vimrc with a thin loader that sources
12 modules under modules/ (env, plugins, core, ui, editing, navigation,
lsp, lint, git, writing, languages, tools). Each module is independently
readable and can be toggled by commenting one line.

Add interactive tutorial (tutor/chopsticks.tutor) with 10 lessons covering
all features. Users can run :ChopsticksLearn to start.
This commit is contained in:
m1ngsama 2026-04-22 00:05:10 +08:00
parent 84d999f91f
commit 6044fc5fcb
16 changed files with 1470 additions and 1261 deletions

1287
.vimrc

File diff suppressed because it is too large Load diff

View file

@ -26,6 +26,7 @@ Vim is **modal** — the keyboard behaves differently depending on which mode yo
| `,w` or `Ctrl+s` | Save |
Once in Normal mode, press `,?` to open the cheat sheet.
Run `:ChopsticksLearn` for a full interactive tutorial.
---

View file

@ -260,10 +260,55 @@ let g:ale_python_black_options = '--line-length=100'
---
## Learn
New to chopsticks? Run the interactive tutorial:
```vim
:ChopsticksLearn
```
It walks you through every feature — file finding, LSP, git, zen mode,
and more — with exercises you can try in real time. (For Vim basics, run
`:Tutor` first.)
---
## Architecture
chopsticks follows the Unix philosophy: each concern lives in its own file.
```
~/.vim/
├── .vimrc ← thin loader (sources modules in order)
├── modules/
│ ├── env.vim ← environment detection (TTY, truecolor)
│ ├── plugins.vim ← vim-plug bootstrap + 30 plugin declarations
│ ├── core.vim ← general settings, keymaps, performance
│ ├── ui.vim ← colorscheme, statusline, startify
│ ├── editing.vim ← EasyMotion, yank highlight, search auto-clear
│ ├── navigation.vim ← FZF, netrw, window management, terminal
│ ├── lsp.vim ← vim-lsp + asyncomplete
│ ├── lint.vim ← ALE linting and format-on-save
│ ├── git.vim ← Fugitive, GitGutter, conflict navigation
│ ├── writing.vim ← Markdown, previm, goyo + limelight
│ ├── languages.vim ← vim-go, per-filetype settings
│ └── tools.vim ← cheat sheet, run file, helpers
└── tutor/
└── chopsticks.tutor ← interactive tutorial
```
Each module is self-contained. Want to disable git integration? Remove
`call s:load('git')` from `.vimrc`. Want to add your own module? Create
`modules/mine.vim` and add `call s:load('mine')`.
---
## Customization
Edit `~/.vimrc` directly (`,ev` opens it, `,sv` reloads). Per-project overrides
go in a `.vimrc` at your project root.
Edit `~/.vimrc` directly (`,ev` opens it, `,sv` reloads), or edit individual
modules under `modules/`. Per-project overrides go in a `.vimrc` at your
project root.
---

220
modules/core.vim Normal file
View file

@ -0,0 +1,220 @@
" core.vim — general settings, basic keymaps, performance, indentation
filetype on
filetype plugin on
filetype indent on
syntax on
set number
set relativenumber
if !g:is_tty
set cursorline
endif
set nobackup
set scrolloff=10
set nowrap
set incsearch
set ignorecase
set smartcase
set showcmd
set showmode
set hlsearch
set history=1000
set wildmenu
set wildmode=list:longest
set wildignorecase
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/*
set mouse=a
set encoding=utf-8
set foldmethod=indent
set foldlevel=99
set splitbelow
set splitright
set backspace=indent,eol,start
set nrformats-=octal
set autoread
set cmdheight=1
set hidden
set whichwrap+=<,>,h,l
set magic
set showmatch
set mat=2
set noerrorbells
set novisualbell
set t_vb=
set ttimeout
set ttimeoutlen=10
if $COLORTERM ==# 'gnome-terminal'
set t_Co=256
endif
if has("gui_running")
set guioptions-=T
set guioptions-=e
set t_Co=256
set guitablabel=%M\ %t
endif
set display+=lastline
set ffs=unix,dos,mac
set nowb
set noswapfile
if has('persistent_undo')
set undofile
let &undodir = expand('~/.vim/.undo')
silent! call mkdir(&undodir, 'p', 0700)
endif
" ── Text, Tab and Indent ────────────────────────────────────────────────────
if g:is_tty
set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_
else
set listchars=tab:→\ ,trail,extends:▸,precedes:◂,nbsp
endif
set expandtab
set smarttab
set shiftwidth=4
set tabstop=4
set lbr
set tw=0
set autoindent
set smartindent
" ── Leader ──────────────────────────────────────────────────────────────────
let mapleader = ","
" ── Basic Keymaps ───────────────────────────────────────────────────────────
nnoremap <leader>w :w!<cr>
nnoremap <leader>q :q<cr>
nnoremap <leader>x :x<cr>
nnoremap <silent> <leader><cr> :noh<cr>
nnoremap <leader>bd :Bclose<cr>
nnoremap <leader>ba :bufdo bd<cr>
nnoremap <leader>l :bnext<cr>
nnoremap <leader>h :bprevious<cr>
nnoremap <leader>tn :tabnew<cr>
nnoremap <leader>to :tabonly<cr>
nnoremap <leader>tc :tabclose<cr>
nnoremap <leader>tm :tabmove
nnoremap <leader>t<leader> :tabnext<cr>
let g:lasttab = 1
nnoremap <Leader>tl :exe "tabn ".g:lasttab<CR>
augroup ChopstickTabHistory
autocmd!
autocmd TabLeave * let g:lasttab = tabpagenr()
augroup END
nnoremap <leader>te :tabedit <C-r>=expand("%:p:h")<cr>/
nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
nnoremap 0 ^
nnoremap gV `[v`]
cnoremap <C-p> <Up>
cnoremap <C-n> <Down>
nnoremap <M-j> :m .+1<CR>==
nnoremap <M-k> :m .-2<CR>==
vnoremap <M-j> :m '>+1<CR>gv=gv
vnoremap <M-k> :m '<-2<CR>gv=gv
nnoremap <leader>ss :setlocal spell!<cr>
nnoremap <leader>sn ]s
nnoremap <leader>sp [s
nnoremap <leader>sa zg
nnoremap <leader>s? z=
set pastetoggle=<F2>
nnoremap <F3> :set invnumber<CR>
nnoremap <F4> :set invrelativenumber<CR>
nnoremap <F6> :set list!<CR>
nnoremap <space> za
nnoremap Y y$
nnoremap Q <nop>
inoremap jk <Esc>
vnoremap < <gv
vnoremap > >gv
nnoremap n nzzzv
nnoremap N Nzzzv
vnoremap // y/\V<C-r>=escape(@",'/\')<CR><CR>
nnoremap <silent> <C-s> :w<CR>
inoremap <silent> <C-s> <C-o>:w<CR>
nnoremap <C-d> <C-d>zz
nnoremap <C-u> <C-u>zz
if has('clipboard')
nnoremap <leader>y "+y
vnoremap <leader>y "+y
nnoremap <leader>Y "+Y
nnoremap <leader>p "+p
nnoremap <leader>P "+P
endif
nnoremap <leader>qo :copen<CR>
nnoremap <leader>qc :cclose<CR>
augroup ChopstickResize
autocmd!
autocmd VimResized * wincmd =
augroup END
" ── Performance ─────────────────────────────────────────────────────────────
set synmaxcol=200
set ttyfast
set lazyredraw
set complete-=i
set updatetime=300
set shortmess+=c
if g:is_tty
set signcolumn=auto
set synmaxcol=120
else
if has("patch-8.1.1564")
set signcolumn=number
else
set signcolumn=yes
endif
endif
" ── Project-Local Config ────────────────────────────────────────────────────
set exrc
set secure
set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal
" ── Format Options ──────────────────────────────────────────────────────────
augroup ChopstickFormatOptions
autocmd!
autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions+=j
augroup END
augroup ChopstickPaste
autocmd!
autocmd InsertLeave * set nopaste
augroup END
set timeoutlen=500

41
modules/editing.vim Normal file
View file

@ -0,0 +1,41 @@
" editing.vim — EasyMotion, yank highlight, search auto-clear, undotree
" ── EasyMotion ──────────────────────────────────────────────────────────────
let g:EasyMotion_do_mapping = 0
let g:EasyMotion_smartcase = 1
if exists('g:plugs["vim-easymotion"]')
nmap s <Plug>(easymotion-overwin-f2)
nmap <Leader>j <Plug>(easymotion-j)
nmap <Leader>k <Plug>(easymotion-k)
endif
" ── UndoTree ────────────────────────────────────────────────────────────────
if exists('g:plugs["undotree"]')
nnoremap <F5> :UndotreeToggle<CR>
nnoremap <leader>u :UndotreeToggle<CR>
endif
" ── Yank Highlight ──────────────────────────────────────────────────────────
if exists('##TextYankPost') && has('timers')
function! s:YankHighlight() abort
if v:event.operator !=# 'y' | return | endif
let l:m = matchadd('IncSearch',
\ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1))
call timer_start(150, {-> matchdelete(l:m)})
endfunction
augroup ChopstickYankHL
autocmd!
autocmd TextYankPost * call s:YankHighlight()
augroup END
endif
" ── Auto-Clear Search Highlight ─────────────────────────────────────────────
augroup ChopstickSearchHL
autocmd!
autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif
augroup END

7
modules/env.vim Normal file
View file

@ -0,0 +1,7 @@
" env.vim — environment detection (must load first)
set nocompatible
let g:is_tty = empty($TERM) || $TERM ==# 'dumb' || $TERM =~# 'linux'
\ || $TERM =~# 'screen' || &term =~# 'builtin'
let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit')

25
modules/git.vim Normal file
View file

@ -0,0 +1,25 @@
" git.vim — Fugitive mappings, GitGutter config, conflict navigation
" ── GitGutter ───────────────────────────────────────────────────────────────
let g:gitgutter_sign_added = '+'
let g:gitgutter_sign_modified = '~'
let g:gitgutter_sign_removed = '-'
let g:gitgutter_sign_removed_first_line = '^'
let g:gitgutter_sign_modified_removed = '~'
" ── Fugitive ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-fugitive"]')
nnoremap <leader>gs :Git status<CR>
nnoremap <leader>gc :Git commit<CR>
nnoremap <leader>gp :Git push<CR>
nnoremap <leader>gl :Git pull<CR>
nnoremap <leader>gd :Gdiffsplit<CR>
nnoremap <leader>gb :Git blame<CR>
endif
" ── Conflict Navigation ────────────────────────────────────────────────────
nnoremap <silent> ]x /^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>
nnoremap <silent> [x ?^\(<<<<<<<\\|=======\\|>>>>>>>\)<CR>

52
modules/languages.vim Normal file
View file

@ -0,0 +1,52 @@
" languages.vim — vim-go config, per-filetype autocmds
" ── vim-go (syntax only — vim-lsp handles intelligence) ─────────────────────
let g:go_gopls_enabled = 0
let g:go_code_completion_enabled = 0
let g:go_fmt_autosave = 0
let g:go_imports_autosave = 0
let g:go_highlight_types = 1
let g:go_highlight_fields = 1
let g:go_highlight_functions = 1
let g:go_highlight_function_calls = 1
" ── Filetype Detection ──────────────────────────────────────────────────────
augroup ChopstickFiletype
autocmd!
autocmd BufReadPost *
\ if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
autocmd BufNewFile,BufRead *.json setlocal filetype=json
autocmd BufNewFile,BufRead *.md setlocal filetype=markdown
autocmd BufNewFile,BufRead *.jsx setlocal filetype=javascript.jsx
autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx
autocmd BufNewFile,BufRead Dockerfile* setlocal filetype=dockerfile
autocmd FileType python
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1
autocmd FileType javascript,typescript
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1
autocmd FileType go
\ setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1
autocmd FileType rust
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1
autocmd FileType c,cpp
\ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=80 colorcolumn=+1
autocmd FileType html,css
\ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType yaml
\ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType markdown
\ setlocal wrap linebreak spell textwidth=0 colorcolumn=0 conceallevel=2
autocmd FileType sh
\ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1
autocmd FileType make
\ setlocal noexpandtab shiftwidth=8 tabstop=8
autocmd FileType json
\ setlocal expandtab shiftwidth=2 tabstop=2
autocmd FileType dockerfile
\ setlocal expandtab shiftwidth=2 tabstop=2
augroup END

51
modules/lint.vim Normal file
View file

@ -0,0 +1,51 @@
" lint.vim — ALE async linting and format-on-save
let g:ale_disable_lsp = 1
let g:ale_linters = {
\ 'python': ['flake8', 'pylint'],
\ 'javascript': ['eslint'],
\ 'typescript': ['eslint'],
\ 'go': ['staticcheck'],
\ 'rust': ['cargo'],
\ 'c': ['cc'],
\ 'sh': ['shellcheck'],
\ 'yaml': ['yamllint'],
\ 'dockerfile': ['hadolint'],
\ 'css': ['stylelint'],
\ 'scss': ['stylelint'],
\ 'markdown': ['markdownlint'],
\ 'sql': ['sqlfluff'],
\}
let g:ale_fixers = {
\ '*': ['remove_trailing_lines', 'trim_whitespace'],
\ 'python': ['black', 'isort'],
\ 'javascript': ['prettier', 'eslint'],
\ 'typescript': ['prettier', 'eslint'],
\ 'go': ['goimports'],
\ 'rust': ['rustfmt'],
\ 'c': ['clang-format'],
\ 'json': ['prettier'],
\ 'yaml': ['prettier'],
\ 'html': ['prettier'],
\ 'css': ['prettier'],
\ 'scss': ['prettier'],
\ 'less': ['prettier'],
\ 'markdown': ['prettier'],
\ 'sql': ['sqlfluff'],
\}
let g:ale_fix_on_save = 1
let g:ale_python_isort_options = '--profile black'
let g:ale_sign_error = 'X'
let g:ale_sign_warning = '!'
let g:ale_lint_on_text_changed = 'normal'
let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1
if exists('g:plugs["ale"]')
nnoremap <silent> [e :ALEPrevious<cr>
nnoremap <silent> ]e :ALENext<cr>
nnoremap <silent> <leader>aD :ALEDetail<cr>
endif

74
modules/lsp.vim Normal file
View file

@ -0,0 +1,74 @@
" lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps
let g:lsp_settings_filetype_python = ['pylsp']
let g:lsp_settings_filetype_go = ['gopls']
let g:lsp_settings_filetype_rust = ['rust-analyzer']
let g:lsp_settings_filetype_typescript = ['typescript-language-server']
let g:lsp_settings_filetype_javascript = ['typescript-language-server']
let g:lsp_settings_filetype_c = ['clangd']
let g:lsp_settings_filetype_sh = ['bash-language-server']
let g:lsp_settings_filetype_html = ['vscode-html-language-server']
let g:lsp_settings_filetype_css = ['vscode-css-language-server']
let g:lsp_settings_filetype_scss = ['vscode-css-language-server']
let g:lsp_settings_filetype_json = ['vscode-json-language-server']
let g:lsp_settings_filetype_yaml = ['yaml-language-server']
let g:lsp_settings_filetype_markdown = ['marksman']
let g:lsp_settings_filetype_sql = ['sqls']
let g:lsp_diagnostics_virtual_text_enabled = !g:is_tty
let g:lsp_diagnostics_highlights_enabled = !g:is_tty
let g:lsp_document_highlight_enabled = !g:is_tty
let g:lsp_signs_enabled = 1
let g:lsp_diagnostics_echo_cursor = 1
let g:lsp_completion_documentation_enabled = 1
let g:lsp_signs_error = {'text': 'X'}
let g:lsp_signs_warning = {'text': '!'}
let g:lsp_signs_information = {'text': 'i'}
let g:lsp_signs_hint = {'text': '>'}
" ── Completion ──────────────────────────────────────────────────────────────
if has('patch-8.1.1517')
set completeopt=menuone,noinsert,noselect,popup
else
set completeopt=menuone,noinsert,noselect
endif
set pumheight=15
let g:asyncomplete_auto_popup = 1
let g:asyncomplete_auto_completeopt = 0
let g:asyncomplete_popup_delay = 50
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"
inoremap <expr> <CR> pumvisible() ? asyncomplete#close_popup() : "\<CR>"
" ── Buffer Keymaps ──────────────────────────────────────────────────────────
function! s:on_lsp_buffer_enabled() abort
setlocal omnifunc=lsp#complete
setlocal signcolumn=yes
nmap <buffer> gd <plug>(lsp-definition)
nmap <buffer> gy <plug>(lsp-type-definition)
nmap <buffer> gi <plug>(lsp-implementation)
nmap <buffer> gr <plug>(lsp-references)
nmap <buffer> [g <plug>(lsp-previous-diagnostic)
nmap <buffer> ]g <plug>(lsp-next-diagnostic)
nmap <buffer> K <plug>(lsp-hover)
nmap <buffer> <leader>rn <plug>(lsp-rename)
nmap <buffer> <leader>ca <plug>(lsp-code-action)
nmap <buffer> <leader>f <plug>(lsp-document-format)
xmap <buffer> <leader>f <plug>(lsp-document-range-format)
nmap <buffer> <leader>o <plug>(lsp-document-symbol-search)
nmap <buffer> <leader>ws <plug>(lsp-workspace-symbol-search)
nmap <buffer> <leader>cD <plug>(lsp-document-diagnostics)
endfunction
augroup lsp_install
autocmd!
autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

98
modules/navigation.vim Normal file
View file

@ -0,0 +1,98 @@
" navigation.vim — FZF, netrw, buffer/window management, terminal
" ── netrw (built-in file browser) ───────────────────────────────────────────
let g:netrw_liststyle = 3
let g:netrw_banner = 0
let g:netrw_browse_split = 0
let g:netrw_winsize = 25
let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+'
let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store'
nnoremap <leader>e :Explore<CR>
nnoremap <leader>E :Vexplore<CR>
" ── FZF ─────────────────────────────────────────────────────────────────────
function! s:SmartFiles() abort
if isdirectory('.git') || finddir('.git', '.;') !=# ''
GFiles
else
Files
endif
endfunction
if exists('g:plugs["fzf.vim"]')
nnoremap <C-p> :call <SID>SmartFiles()<CR>
nnoremap <leader>b :Buffers<CR>
nnoremap <leader>rg :Rg<CR>
nnoremap <leader>rG :RgWord<CR>
nnoremap <leader>rt :Tags<CR>
nnoremap <leader>gF :GFiles<CR>
nnoremap <leader>fh :History<CR>
nnoremap <leader>fc :Commands<CR>
nnoremap <leader>fm :Marks<CR>
nnoremap <leader>fl :BLines<CR>
nnoremap <leader>fL :Lines<CR>
nnoremap <leader>f/ :History/<CR>
nnoremap <leader>f: :History:<CR>
endif
let g:fzf_layout = { 'down': '40%' }
if g:is_tty
let g:fzf_preview_window = []
else
let g:fzf_preview_window = ['right:50%', 'ctrl-/']
endif
if g:is_tty
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', <bang>0)
else
command! -bang -nargs=* Rg
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -- '
\ .shellescape(<q-args>), 1, fzf#vim#with_preview(), <bang>0)
command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), <bang>0)
endif
if g:is_tty
command! -bang -nargs=* RgWord
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, <bang>0)
else
command! -bang -nargs=* RgWord
\ call fzf#vim#grep(
\ 'rg --column --line-number --no-heading --color=always --smart-case -F -- '
\ .shellescape(expand('<cword>')), 1, fzf#vim#with_preview(), <bang>0)
endif
" ── Window Maximize Toggle ──────────────────────────────────────────────────
function! s:ToggleMaximize() abort
if exists('t:maximize_session')
execute t:maximize_session
unlet t:maximize_session
else
let t:maximize_session = winrestcmd()
resize | vertical resize
endif
endfunction
nnoremap <silent> <leader>z :call <SID>ToggleMaximize()<CR>
" ── Terminal ────────────────────────────────────────────────────────────────
if has('terminal')
nnoremap <leader>tv :terminal<CR>
nnoremap <leader>th :terminal ++rows=10<CR>
tnoremap <Esc><Esc> <C-\><C-n>
tnoremap <C-h> <C-\><C-n><C-w>h
tnoremap <C-j> <C-\><C-n><C-w>j
tnoremap <C-k> <C-\><C-n><C-w>k
tnoremap <C-l> <C-\><C-n><C-w>l
endif

65
modules/plugins.vim Normal file
View file

@ -0,0 +1,65 @@
" plugins.vim — vim-plug declarations
let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim'
if empty(glob(data_dir . '/autoload/plug.vim'))
silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs '
\ . 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
augroup PlugBootstrap
autocmd!
autocmd VimEnter * PlugInstall --sync | source $MYVIMRC
augroup END
endif
call plug#begin('~/.vim/plugged')
" ── Navigation & Search ──────────────────────────────────────────────────────
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
" ── Git ──────────────────────────────────────────────────────────────────────
Plug 'tpope/vim-fugitive'
Plug 'airblade/vim-gitgutter'
" ── Editing ──────────────────────────────────────────────────────────────────
Plug 'tpope/vim-surround'
Plug 'tpope/vim-commentary'
Plug 'tpope/vim-repeat'
Plug 'tpope/vim-unimpaired'
Plug 'tpope/vim-sleuth'
Plug 'wellle/targets.vim'
Plug 'jiangmiao/auto-pairs'
Plug 'easymotion/vim-easymotion'
" ── Linting & Formatting ────────────────────────────────────────────────────
Plug 'dense-analysis/ale'
" ── LSP + Completion (no Node.js required) ──────────────────────────────────
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
" ── Language Syntax ──────────────────────────────────────────────────────────
Plug 'pangloss/vim-javascript'
Plug 'HerringtonDarkholme/yats.vim'
Plug 'preservim/vim-markdown'
Plug 'fatih/vim-go'
" ── Markdown Preview & Writing ───────────────────────────────────────────────
Plug 'previm/previm'
Plug 'junegunn/goyo.vim'
Plug 'junegunn/limelight.vim'
" ── UI ───────────────────────────────────────────────────────────────────────
Plug 'mbbill/undotree'
Plug 'mhinz/vim-startify'
Plug 'lifepillar/vim-solarized8'
if !g:is_tty
Plug 'Yggdroot/indentLine'
endif
" ── Session & Navigation ────────────────────────────────────────────────────
Plug 'tpope/vim-obsession'
Plug 'christoomey/vim-tmux-navigator'
call plug#end()

272
modules/tools.vim Normal file
View file

@ -0,0 +1,272 @@
" tools.vim — cheat sheet, run file, sudo save, quickfix, helpers
" ── Helper Functions ────────────────────────────────────────────────────────
function! HasPaste()
if &paste | return 'PASTE MODE ' | endif
return ''
endfunction
command! Bclose call <SID>BufcloseCloseIt()
function! <SID>BufcloseCloseIt()
let l:currentBufNum = bufnr("%")
let l:alternateBufNum = bufnr("#")
if buflisted(l:alternateBufNum)
buffer #
else
bnext
endif
if bufnr("%") == l:currentBufNum
new
endif
if buflisted(l:currentBufNum)
execute("bdelete! " . l:currentBufNum)
endif
endfunction
fun! CleanExtraSpaces()
let save_cursor = getpos(".")
let old_query = getreg('/')
silent! %s/\s\+$//e
call setpos('.', save_cursor)
call setreg('/', old_query)
endfun
function! ToggleNumber()
if(&relativenumber == 1)
set norelativenumber
set number
else
set relativenumber
endif
endfunc
" ── Additional Utilities ────────────────────────────────────────────────────
nnoremap <leader>F gg=G``
nnoremap <leader>wa :wa<CR>
nnoremap <silent> <Leader>= :exe "resize " . (winheight(0) * 3/2)<CR>
nnoremap <silent> <Leader>- :exe "resize " . (winheight(0) * 2/3)<CR>
nnoremap <silent> <Leader>+ :exe "vertical resize " . (winwidth(0) * 3/2)<CR>
nnoremap <silent> <Leader>_ :exe "vertical resize " . (winwidth(0) * 2/3)<CR>
nnoremap <leader><leader> <c-^>
nnoremap <leader>W :%s/\s\+$//<CR>:let @/=''<CR>
nnoremap <leader>so :if &filetype ==# 'vim' <Bar> source % <Bar> echo "Sourced " . expand('%') <Bar> else <Bar> echo "Not a vim file" <Bar> endif<CR>
nnoremap <leader>ev :edit $MYVIMRC<CR>
nnoremap <leader>sv :source $MYVIMRC<CR>:echo "vimrc reloaded"<CR>
nnoremap <leader>* :%s/\<<C-r><C-w>\>//g<Left><Left>
if has('clipboard')
nnoremap <leader>cp :let @+ = expand("%:p")<CR>:echo "Copied: " . expand("%:p")<CR>
nnoremap <leader>cf :let @+ = expand("%:t")<CR>:echo "Copied: " . expand("%:t")<CR>
endif
nnoremap <leader>ms :e ~/buffer.md<cr>
" ── Auto-Create Directories ─────────────────────────────────────────────────
function! s:MkNonExDir(file, buf)
if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/'
let dir = fnamemodify(a:file, ':h')
if !isdirectory(dir)
call mkdir(dir, 'p')
endif
endif
endfunction
augroup BWCCreateDir
autocmd!
autocmd BufWritePre *
\ if !empty(expand('<afile>')) |
\ call s:MkNonExDir(expand('<afile>'), +expand('<abuf>')) |
\ endif
augroup END
" ── Large File Handling (>10 MB) ────────────────────────────────────────────
let g:LargeFile = 1024 * 1024 * 10
augroup LargeFile
autocmd!
autocmd BufReadPre *
\ if !empty(expand('<afile>')) |
\ let f = getfsize(expand('<afile>')) |
\ if f > g:LargeFile || f == -2 | call LargeFileSettings() | endif |
\ endif
augroup END
function! LargeFileSettings()
setlocal bufhidden=unload
setlocal undolevels=-1
setlocal noswapfile
setlocal syntax=
let b:ale_enabled = 0
echo "Large file (>10 MB): syntax, undo, and linting disabled."
endfunction
if g:is_tty
augroup ChopstickTTYLargeFile
autocmd!
autocmd BufReadPre *
\ if !empty(expand('<afile>')) && getfsize(expand('<afile>')) > 512000 |
\ setlocal syntax= |
\ endif
augroup END
if !exists("g:tty_message_shown")
augroup TTYMessage
autocmd!
autocmd VimEnter * echom "TTY mode — visual features disabled"
augroup END
let g:tty_message_shown = 1
endif
endif
" ── Run Current File (,cr) ──────────────────────────────────────────────────
function! s:RunFile() abort
write
let l:ft = &filetype
let l:file = shellescape(expand('%:p'))
if l:ft ==# 'python' | execute '!python3 ' . l:file
elseif l:ft ==# 'javascript' | execute '!node ' . l:file
elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file
elseif l:ft ==# 'go' | execute '!go run ' . l:file
elseif l:ft ==# 'rust' | execute '!cargo run'
elseif l:ft ==# 'sh' | execute '!bash ' . l:file
elseif l:ft ==# 'c' | execute '!gcc -o /tmp/a.out ' . l:file . ' && /tmp/a.out'
elseif l:ft ==# 'lua' | execute '!lua ' . l:file
elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file
elseif l:ft ==# 'perl' | execute '!perl ' . l:file
else | echo 'No runner for filetype: ' . l:ft
endif
endfunction
nnoremap <leader>cr :call <SID>RunFile()<CR>
" ── Sudo Save ───────────────────────────────────────────────────────────────
cnoremap w!! w !sudo tee > /dev/null %
" ── QuickFix Improvements ───────────────────────────────────────────────────
augroup ChopstickQF
autocmd!
autocmd QuickFixCmdPost [^l]* cwindow
autocmd QuickFixCmdPost l* lwindow
augroup END
nnoremap <silent> ]q :cnext<CR>
nnoremap <silent> [q :cprev<CR>
" ── Debug Helpers ───────────────────────────────────────────────────────────
nnoremap <leader>sh :call <SID>SynStack()<CR>
function! <SID>SynStack()
if !exists("*synstack") | return | endif
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
endfunc
" ── Cheat Sheet (,?) ────────────────────────────────────────────────────────
function! s:CheatSheet() abort
let l:name = '__ChopsticksCheatSheet__'
if bufwinnr(l:name) > 0
execute bufwinnr(l:name) . 'wincmd w'
return
endif
execute 'botright new ' . l:name
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile
call setline(1, [
\ '=== chopsticks — Quick Reference ===',
\ '',
\ 'SURVIVAL',
\ ' Esc / jk Exit insert or visual mode',
\ ' :q! + Enter Quit without saving',
\ ' ,x Save+quit ,w Save Ctrl+s Save (any mode)',
\ ' :w!! Sudo save (when you forgot to open as root)',
\ '',
\ 'FILES & SEARCH',
\ ' Ctrl+p Fuzzy find file (git-aware)',
\ ' ,e / ,E File browser / vertical split',
\ ' ,b Search open buffers',
\ ' ,rg Search project contents (ripgrep)',
\ ' ,rG Ripgrep word under cursor',
\ ' ,fh Recent files history',
\ ' ,fl / ,fL Search lines in buffer / all buffers',
\ ' ,fc Commands | ,fm Marks',
\ ' ,f/ / ,f: Search / command history',
\ ' ,, Switch to last file (Ctrl+^)',
\ '',
\ 'CODE INTELLIGENCE (vim-lsp)',
\ ' gd Definition gy Type def gi Impl gr Refs',
\ ' K Hover documentation',
\ ' [g / ]g Prev / next LSP diagnostic',
\ ' [e / ]e Prev / next ALE error',
\ ' ,ca Code action ,rn Rename ,f Format',
\ ' ,o File outline ,ws Workspace symbols',
\ ' ,cr Run current file',
\ '',
\ 'MARKDOWN & WRITING',
\ ' ,mp Live browser preview (previm)',
\ ' ,mt Table of contents',
\ ' ,zen Zen mode (Goyo + Limelight)',
\ ' zr / zm Unfold / fold all headings',
\ '',
\ 'EDITING',
\ ' gc Toggle comment (visual mode too)',
\ ' s + 2 chars EasyMotion jump anywhere',
\ ' ,u / F5 Undo tree',
\ ' ,y / ,Y Yank to system clipboard',
\ ' Alt+j / Alt+k Move line down / up',
\ ' ,F Re-indent file ,W Strip trailing whitespace',
\ ' ,* Search and replace word under cursor',
\ '',
\ 'GIT',
\ ' ,gs Status ,gd Diff ,gb Blame',
\ ' ,gc Commit ,gp Push ,gl Pull',
\ ' [x / ]x Navigate git conflict markers',
\ '',
\ 'WINDOWS & PANES',
\ ' Ctrl+h/j/k/l Navigate splits and tmux panes',
\ ' ,h / ,l Prev / next buffer ,bd Close buffer',
\ ' ,z Maximize / restore current window',
\ ' ,tv / ,th Terminal (vertical / horizontal)',
\ ' Esc Esc Exit terminal mode',
\ ' ,= / ,- Resize height ,+ / ,_ Resize width',
\ '',
\ 'QUICKFIX',
\ ' ,qo / ,qc Open / close quickfix',
\ ' ]q / [q Next / prev quickfix entry',
\ '',
\ 'UTILITIES',
\ ' ,ev / ,sv Edit / reload ~/.vimrc',
\ ' ,cp / ,cf Copy file path / filename to clipboard',
\ ' ,ms Scratch buffer ,cd CD to file dir',
\ ' ,ss Toggle spell ,so Source current vim file',
\ ' F2 Paste F3 Line# F4 Relative# F6 Invisible',
\ '',
\ '(press q to close)',
\ ])
setlocal nomodifiable readonly
nnoremap <buffer> <silent> q :bd<CR>
endfunction
nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
" ── Interactive Tutorial ────────────────────────────────────────────────────
function! s:ChopsticksLearn() abort
let l:tutor = g:chopsticks_dir . '/tutor/chopsticks.tutor'
if !filereadable(l:tutor)
echo "Tutorial not found: " . l:tutor
return
endif
execute 'edit ' . fnameescape(l:tutor)
setlocal nomodifiable readonly
setlocal buftype=nofile bufhidden=wipe
setlocal filetype=text
setlocal wrap linebreak
endfunction
command! ChopsticksLearn call s:ChopsticksLearn()

164
modules/ui.vim Normal file
View file

@ -0,0 +1,164 @@
" ui.vim — colorscheme, statusline, startify, indentline
" ── Colorscheme (Solarized Dark — matches tmux palette) ────────────────────
if g:has_true_color && has('termguicolors') && !g:is_tty
let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
set termguicolors
endif
set background=dark
if !g:is_tty
try
colorscheme solarized8
catch
colorscheme default
endtry
else
colorscheme default
endif
if has("gui_running")
if has("gui_gtk2") || has("gui_gtk3")
set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12
elseif has("gui_win32")
set guifont=Consolas:h11:cANSI
endif
endif
" ── IndentLine (non-TTY only) ───────────────────────────────────────────────
if !g:is_tty && exists('g:plugs["indentLine"]')
let g:indentLine_char = '|'
let g:indentLine_first_char = '|'
let g:indentLine_showFirstIndentLevel = 1
let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'markdown']
let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile']
let g:indentLine_setConceal = 2
let g:indentLine_concealcursor = ''
endif
" ── Startify ────────────────────────────────────────────────────────────────
if exists('g:plugs["vim-startify"]')
let g:startify_custom_header = [
\ ' ██████╗██╗ ██╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗███████╗',
\ ' ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝',
\ ' ██║ ███████║██║ ██║██████╔╝███████╗ ██║ ██║██║ █████╔╝ ███████╗',
\ ' ██║ ██╔══██║██║ ██║██╔═══╝ ╚════██║ ██║ ██║██║ ██╔═██╗ ╚════██║',
\ ' ╚██████╗██║ ██║╚██████╔╝██║ ███████║ ██║ ██║╚██████╗██║ ██╗███████║',
\ ' ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝',
\ '',
\ ]
let g:startify_lists = [
\ { 'type': 'sessions', 'header': [' Sessions'] },
\ { 'type': 'files', 'header': [' Recent Files'] },
\ { 'type': 'dir', 'header': [' Current Dir'] },
\ { 'type': 'bookmarks', 'header': [' Bookmarks'] },
\ ]
let g:startify_bookmarks = [{'v': '~/.vimrc'}]
if filereadable(expand('~/.zshrc'))
call add(g:startify_bookmarks, {'z': '~/.zshrc'})
endif
if filereadable(expand('~/.bashrc'))
call add(g:startify_bookmarks, {'b': '~/.bashrc'})
endif
if filereadable(expand('~/.config/fish/config.fish'))
call add(g:startify_bookmarks, {'f': '~/.config/fish/config.fish'})
endif
let g:startify_session_persistence = 1
let g:startify_session_autoload = 1
let g:startify_change_to_vcs_root = 1
let g:startify_fortune_use_unicode = 0
let g:startify_enable_special = 0
let g:startify_files_number = 8
let g:startify_padding_left = 4
if !g:is_tty
augroup ChopstickStartup
autocmd!
autocmd StdinReadPre * let s:std_in = 1
autocmd VimEnter *
\ if argc() == 1 && isdirectory(argv()[0]) && !exists('s:std_in') |
\ exe 'cd ' . fnameescape(argv()[0]) |
\ if exists(':Startify') == 2 | Startify | else | enew | endif |
\ endif
augroup END
endif
endif
" ── Status Line (native — Solarized palette, seamless with tmux bar) ───────
set laststatus=2
set noshowmode
function! s:SLDefineColors() abort
hi SLNormal ctermbg=136 ctermfg=234 cterm=bold guibg=#b58900 guifg=#002b36 gui=bold
hi SLInsert ctermbg=33 ctermfg=234 cterm=bold guibg=#268bd2 guifg=#002b36 gui=bold
hi SLVisual ctermbg=125 ctermfg=234 cterm=bold guibg=#d33682 guifg=#002b36 gui=bold
hi SLReplace ctermbg=160 ctermfg=234 cterm=bold guibg=#dc322f guifg=#002b36 gui=bold
hi SLCommand ctermbg=37 ctermfg=234 cterm=bold guibg=#2aa198 guifg=#002b36 gui=bold
hi SLBody ctermbg=235 ctermfg=245 cterm=none guibg=#073642 guifg=#93a1a1
hi SLFlag ctermbg=235 ctermfg=136 cterm=none guibg=#073642 guifg=#b58900
hi SLRight ctermbg=235 ctermfg=240 cterm=none guibg=#073642 guifg=#586e75
hi SLGit ctermbg=235 ctermfg=37 cterm=none guibg=#073642 guifg=#2aa198
hi SLFtype ctermbg=235 ctermfg=244 cterm=none guibg=#073642 guifg=#839496
endfunction
augroup SLColors
autocmd!
autocmd ColorScheme * call s:SLDefineColors()
augroup END
call s:SLDefineColors()
function! SLMode() abort
let l:m = mode()
if l:m ==# 'n' | return [' N ', 'SLNormal' ]
elseif l:m ==# 'i' | return [' I ', 'SLInsert' ]
elseif l:m =~# '[vV]' || l:m ==# "\<C-v>" | return [' V ', 'SLVisual' ]
elseif l:m ==# 'R' | return [' R ', 'SLReplace']
elseif l:m ==# 'c' | return [' C ', 'SLCommand']
elseif l:m ==# 't' | return [' T ', 'SLInsert' ]
else | return [' ' . l:m . ' ', 'SLNormal']
endif
endfunction
function! SLGit() abort
if !exists('*FugitiveHead') | return '' | endif
let l:b = FugitiveHead()
return empty(l:b) ? '' : ' ' . l:b . ' '
endfunction
function! SLAle() abort
if !exists('*ale#statusline#Count') | return '' | endif
let l:c = ale#statusline#Count(bufnr(''))
let l:e = l:c.error + l:c.style_error
let l:w = l:c.warning + l:c.style_warning
if l:e == 0 && l:w == 0 | return '' | endif
return printf(' E:%d W:%d ', l:e, l:w)
endfunction
function! SLBuild() abort
let [l:label, l:hl] = SLMode()
let l:s = '%#' . l:hl . '#' . l:label
let l:s .= '%#SLBody# %f '
let l:s .= '%#SLFlag#%m%r'
let l:s .= '%#SLBody#%='
let l:s .= '%#SLFlag#' . SLAle()
let l:s .= '%#SLGit#' . SLGit()
let l:s .= '%#SLFtype# %y '
let l:s .= '%#SLRight# %l:%c %P '
return l:s
endfunction
set statusline=%!SLBuild()
if g:is_tty
set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%)
endif

56
modules/writing.vim Normal file
View file

@ -0,0 +1,56 @@
" writing.vim — vim-markdown, previm, goyo + limelight zen mode
" ── vim-markdown ────────────────────────────────────────────────────────────
let g:vim_markdown_conceal = 1
let g:vim_markdown_conceal_code_blocks = 0
let g:vim_markdown_folding_disabled = 0
let g:vim_markdown_folding_level = 2
let g:vim_markdown_frontmatter = 1
let g:vim_markdown_toml_frontmatter = 1
let g:vim_markdown_json_frontmatter = 1
let g:vim_markdown_follow_anchor = 1
let g:vim_markdown_new_list_item_indent = 2
let g:vim_markdown_strikethrough = 1
if exists('g:plugs["vim-markdown"]')
nnoremap <leader>mt :Toc<CR>
endif
" ── previm (Markdown browser preview) ───────────────────────────────────────
if has('macunix')
let g:previm_open_cmd = '/usr/bin/open'
elseif executable('xdg-open')
let g:previm_open_cmd = 'xdg-open'
endif
if exists('g:plugs["previm"]')
nnoremap <leader>mp :PrevimOpen<CR>
endif
let g:previm_enable_realtime = 1
" ── Goyo + Limelight (zen mode) ────────────────────────────────────────────
if exists('g:plugs["goyo.vim"]')
let g:goyo_width = 80
let g:goyo_height = '85%'
nnoremap <leader>zen :Goyo<CR>
function! s:goyo_enter()
if exists('g:plugs["limelight.vim"]') | Limelight | endif
set wrap linebreak scrolloff=999
endfunction
function! s:goyo_leave()
if exists('g:plugs["limelight.vim"]') | Limelight! | endif
set nowrap nolinebreak scrolloff=10
endfunction
augroup ChopstickGoyo
autocmd!
autocmd User GoyoEnter nested call s:goyo_enter()
autocmd User GoyoLeave nested call s:goyo_leave()
augroup END
endif
let g:limelight_conceal_ctermfg = 240
let g:limelight_conceal_guifg = '#586e75'

269
tutor/chopsticks.tutor Normal file
View file

@ -0,0 +1,269 @@
================================================================================
= C H O P S T I C K S I N T E R A C T I V E T U T O R =
================================================================================
This tutorial teaches the key features of the chopsticks Vim configuration.
It assumes you already know basic Vim (if not, run :Tutor first).
Leader key is , (comma) throughout this tutorial.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 1: SURVIVAL BASICS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These four commands get you out of any situation:
1. Press Esc or type jk to return to Normal mode
2. Type ,w to save the current file
3. Type ,x to save and quit
4. Type ,q to quit (without saving)
TIP: You can also press Ctrl+s to save from ANY mode (normal, insert,
or visual). Try it now — press i to enter insert mode, then Ctrl+s
to save without leaving insert mode.
When completely lost, press ,? to open the cheat sheet with every
keybinding. Press q to close it.
>>> Try it now: press ,? then read through the sheet, then press q
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 2: FINDING FILES
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
chopsticks uses FZF (fuzzy finder) for fast navigation.
Ctrl+p Fuzzy find files (git-aware — ignores .gitignore'd files)
,b Search open buffers
,fh Recent file history
,rg Search file contents with ripgrep
,rG Search for the word under your cursor
>>> Try it now: press Ctrl+p and start typing a filename
You can also browse files with the built-in file browser:
,e Open netrw file browser in current window
,E Open netrw in a vertical split
Inside netrw:
Enter Open file
- Go up one directory
% Create new file
d Create new directory
gh Toggle hidden files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 3: CODE INTELLIGENCE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
chopsticks uses vim-lsp for code intelligence — no Node.js required.
First, install a language server for your filetype:
:LspInstallServer (auto-detects the language)
:LspStatus (check if it's running)
Once a server is running, these keys become available:
gd Go to definition
gy Go to type definition
gi Go to implementation
gr List all references
K Show hover documentation
[g / ]g Jump to previous / next diagnostic
,rn Rename symbol under cursor
,ca Code action (auto-fix)
,f Format buffer (or selection in visual mode)
,o File outline (symbols)
>>> Open a source file and try: gd on a function call, then Ctrl+o
to jump back
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 4: EDITING POWER
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
These editing features make chopsticks faster than vanilla Vim.
EASYMOTION — jump anywhere on screen:
Press s then type two characters from your target location.
Matching positions light up — press the highlighted letter to jump.
>>> Try it now: press s then type two letters you can see on screen
MOVE LINES — rearrange code without cut/paste:
Alt+j Move current line down
Alt+k Move current line up
(Works in visual mode too — select lines first with V )
SURROUND — change surrounding characters:
cs"' Change surrounding " to '
cs'<div> Change surrounding ' to <div>...</div>
ds( Delete surrounding parentheses
ysiw" Surround word with "
COMMENT — toggle comments:
gc Toggle comment (works in visual mode on selections)
gcc Toggle comment on current line
VISUAL FEEDBACK:
- Yanked text flashes briefly so you know what was copied
- Search highlights auto-clear after you stop moving
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 5: GIT WORKFLOW
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
All git operations through vim-fugitive:
,gs Git status (press s to stage, press cc to commit)
,gd Diff current file (side-by-side split)
,gb Git blame (who changed each line)
,gc Git commit
,gp Git push
,gl Git pull
GitGutter shows changes in the sign column (left margin):
+ Added line
~ Modified line
- Deleted line
Navigate git conflict markers:
]x Jump to next conflict marker
[x Jump to previous conflict marker
>>> Try ,gs in a git repository to see the status view
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 6: MARKDOWN & WRITING
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Markdown files get special treatment automatically:
- Word wrap enabled
- Spell checking enabled
- Syntax concealment (bold renders as bold, headings hide # markers)
- Folding by heading level
Key bindings for markdown:
,mp Open live browser preview (auto-refreshes as you type)
,mt Table of contents in a side window
,zen Enter zen mode — Goyo + Limelight
(distraction-free writing, only current paragraph highlighted)
zr Unfold all headings
zm Fold all headings
>>> Open a .md file and try ,zen to enter zen mode.
Press ,zen again to leave.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 7: WINDOW MANAGEMENT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Navigate between splits (works with tmux panes too):
Ctrl+h Move to left split
Ctrl+j Move to split below
Ctrl+k Move to split above
Ctrl+l Move to right split
Buffer navigation:
,h Previous buffer
,l Next buffer
,bd Close buffer (keeps window layout)
,, Switch to last file (toggle between two files)
Window tricks:
,z Maximize current window (press again to restore)
,= / ,- Resize window height (bigger / smaller)
,+ / ,_ Resize window width (bigger / smaller)
Terminal:
,tv Open terminal (vertical split)
,th Open terminal (horizontal, 10 rows)
Esc Esc Exit terminal mode back to normal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 8: LINTING & FORMATTING
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ALE runs linters and formatters automatically:
- Linting happens as you type (normal mode) and on save
- Format-on-save is enabled by default (black, prettier, gofmt, etc.)
Navigate errors:
[e / ]e Previous / next ALE error
,aD Show error detail in a popup
Supported out of the box:
Python: flake8 + pylint (lint), black + isort (format)
JS/TS: eslint (lint), prettier + eslint (format)
Go: staticcheck (lint), goimports (format)
Rust: cargo (lint), rustfmt (format)
Shell: shellcheck (lint)
And more: yaml, dockerfile, css, markdown, sql
>>> Write some intentionally bad code and watch the sign column
light up with X (error) and ! (warning) markers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 9: RUNNING CODE
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Press ,cr to run the current file. It auto-detects the language:
Python → python3 file.py
JavaScript → node file.js
TypeScript → npx ts-node file.ts
Go → go run file.go
Rust → cargo run
Shell → bash file.sh
C → gcc + run
Lua, Ruby, Perl also supported
>>> Create a test file (e.g. test.py with: print("hello"))
and press ,cr to run it
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lesson 10: DAILY TIPS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SEARCH AND REPLACE:
,* Replace word under cursor throughout the file
// In visual mode, search for selected text
USEFUL SHORTCUTS:
Y Yank to end of line (consistent with D and C)
,y / ,Y Yank to system clipboard
,F Re-indent entire file
,W Strip all trailing whitespace
:w!! Sudo save (when you forgot to open as root)
,ev Edit your vimrc
,sv Reload your vimrc
SESSIONS:
:Obsess Start recording session (auto-restores next time)
:Obsess! Stop recording
The startup screen (Startify) shows recent files, sessions, and bookmarks.
>>> Press ,? one more time to review the full cheat sheet.
You now know the essential chopsticks features. Happy editing!
================================================================================