fix: remaining usability and design improvements

.vimrc:
- Remove path+=** (hangs on large repos with node_modules)
- Add plugin guards for FZF, Git, ALE, Markdown, EasyMotion, UndoTree
  mappings (prevent E492 when plugins not loaded)
- Replace SmartFiles system() subprocess with isdirectory/finddir
  (no more synchronous fork on every Ctrl+p)
- Remove duplicate SLDefineColors VimEnter call (ColorScheme alone suffices)
- Convert all remaining nmap/map to nnoremap/noremap (eliminate recursive
  mapping risk and visual/operator-pending mode contamination)
- Expand cheat sheet with spell checking, utilities, window resize,
  F-key toggles, EasyMotion line motions, buffer management

get.sh:
- Support NO_COLOR and non-TTY output
- Show version tag or commit hash after clone/pull

QUICKSTART.md:
- Warn about first-launch plugin install (30-60s wait)
- Note that some language servers need Node.js
This commit is contained in:
m1ngsama 2026-04-15 10:15:07 +08:00
parent 233ceabf53
commit 5604eddbdb
3 changed files with 86 additions and 42 deletions

110
.vimrc
View file

@ -44,7 +44,7 @@ set wildmode=list:longest
set wildignorecase set wildignorecase
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx
set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/* set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/*
set path+=** " path+=** removed: hangs on large repos (node_modules); use Ctrl+p (FZF) instead
set mouse=a set mouse=a
set encoding=utf-8 set encoding=utf-8
set foldmethod=indent set foldmethod=indent
@ -214,9 +214,9 @@ set smartindent
let mapleader = "," let mapleader = ","
" Saving / quitting " Saving / quitting
nmap <leader>w :w!<cr> nnoremap <leader>w :w!<cr>
nmap <leader>q :q<cr> nnoremap <leader>q :q<cr>
nmap <leader>x :x<cr> nnoremap <leader>x :x<cr>
" Clear search highlight " Clear search highlight
nnoremap <silent> <leader><cr> :noh<cr> nnoremap <silent> <leader><cr> :noh<cr>
@ -235,7 +235,7 @@ nnoremap <leader>tm :tabmove
nnoremap <leader>t<leader> :tabnext<cr> nnoremap <leader>t<leader> :tabnext<cr>
let g:lasttab = 1 let g:lasttab = 1
nmap <Leader>tl :exe "tabn ".g:lasttab<CR> nnoremap <Leader>tl :exe "tabn ".g:lasttab<CR>
augroup ChopstickTabHistory augroup ChopstickTabHistory
autocmd! autocmd!
autocmd TabLeave * let g:lasttab = tabpagenr() autocmd TabLeave * let g:lasttab = tabpagenr()
@ -248,8 +248,8 @@ nnoremap <leader>cd :lcd %:p:h<cr>:pwd<cr>
nnoremap <leader>e :Explore<CR> nnoremap <leader>e :Explore<CR>
nnoremap <leader>E :Vexplore<CR> nnoremap <leader>E :Vexplore<CR>
" Remap 0 to first non-blank " Remap 0 to first non-blank (all modes intentional)
map 0 ^ noremap 0 ^
" Reselect last paste " Reselect last paste
nnoremap gV `[v`] nnoremap gV `[v`]
@ -354,19 +354,21 @@ let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store'
" Ctrl+p: git-aware file search (GFiles inside repo, Files outside) " Ctrl+p: git-aware file search (GFiles inside repo, Files outside)
function! s:SmartFiles() abort function! s:SmartFiles() abort
if !empty(system('git rev-parse --show-toplevel 2>/dev/null')) if isdirectory('.git') || finddir('.git', '.;') !=# ''
GFiles GFiles
else else
Files Files
endif endif
endfunction endfunction
map <C-p> :call <SID>SmartFiles()<CR> if exists('g:plugs["fzf.vim"]')
map <leader>b :Buffers<CR> nnoremap <C-p> :call <SID>SmartFiles()<CR>
map <leader>rg :Rg<CR> nnoremap <leader>b :Buffers<CR>
map <leader>rG :RgWord<CR> nnoremap <leader>rg :Rg<CR>
map <leader>rt :Tags<CR> nnoremap <leader>rG :RgWord<CR>
map <leader>gF :GFiles<CR> nnoremap <leader>rt :Tags<CR>
nnoremap <leader>gF :GFiles<CR>
endif
let g:fzf_layout = { 'down': '40%' } let g:fzf_layout = { 'down': '40%' }
@ -458,9 +460,11 @@ let g:ale_lint_on_text_changed = 'normal'
let g:ale_lint_on_insert_leave = 1 let g:ale_lint_on_insert_leave = 1
let g:ale_lint_on_enter = 1 let g:ale_lint_on_enter = 1
nmap <silent> [e :ALEPrevious<cr> if exists('g:plugs["ale"]')
nmap <silent> ]e :ALENext<cr> nnoremap <silent> [e :ALEPrevious<cr>
nmap <silent> <leader>aD :ALEDetail<cr> nnoremap <silent> ]e :ALENext<cr>
nnoremap <silent> <leader>aD :ALEDetail<cr>
endif
" ============================================================================ " ============================================================================
" => vim-go " => vim-go
@ -573,15 +577,19 @@ let g:vim_markdown_follow_anchor = 1
let g:vim_markdown_new_list_item_indent = 2 let g:vim_markdown_new_list_item_indent = 2
let g:vim_markdown_strikethrough = 1 let g:vim_markdown_strikethrough = 1
" Table of contents in quickfix " Table of contents (side window)
nnoremap <leader>mt :Toc<CR> if exists('g:plugs["vim-markdown"]')
nnoremap <leader>mt :Toc<CR>
endif
" ============================================================================ " ============================================================================
" => previm (Markdown browser preview) " => previm (Markdown browser preview)
" ============================================================================ " ============================================================================
" <leader>mp open live-reloading preview in browser " <leader>mp open live-reloading preview in browser
nnoremap <leader>mp :PrevimOpen<CR> if exists('g:plugs["previm"]')
nnoremap <leader>mp :PrevimOpen<CR>
endif
let g:previm_enable_realtime = 1 let g:previm_enable_realtime = 1
" ============================================================================ " ============================================================================
@ -591,19 +599,22 @@ let g:previm_enable_realtime = 1
let g:EasyMotion_do_mapping = 0 let g:EasyMotion_do_mapping = 0
let g:EasyMotion_smartcase = 1 let g:EasyMotion_smartcase = 1
" s + two chars: jump anywhere on screen if exists('g:plugs["vim-easymotion"]')
nmap s <Plug>(easymotion-overwin-f2) " s + two chars: jump anywhere on screen
nmap s <Plug>(easymotion-overwin-f2)
" Line motions " Line motions
nmap <Leader>j <Plug>(easymotion-j) nmap <Leader>j <Plug>(easymotion-j)
nmap <Leader>k <Plug>(easymotion-k) nmap <Leader>k <Plug>(easymotion-k)
endif
" ============================================================================ " ============================================================================
" => UndoTree " => UndoTree
" ============================================================================ " ============================================================================
nnoremap <F5> :UndotreeToggle<CR> if exists('g:plugs["undotree"]')
nnoremap <leader>u :UndotreeToggle<CR> nnoremap <F5> :UndotreeToggle<CR>
nnoremap <leader>u :UndotreeToggle<CR>
endif
" ============================================================================ " ============================================================================
" => IndentLine (non-TTY only) " => IndentLine (non-TTY only)
@ -713,7 +724,6 @@ endfunction
augroup SLColors augroup SLColors
autocmd! autocmd!
autocmd ColorScheme * call s:SLDefineColors() autocmd ColorScheme * call s:SLDefineColors()
autocmd VimEnter * call s:SLDefineColors()
augroup END augroup END
" Current mode → [label, highlight-group] " Current mode → [label, highlight-group]
@ -949,12 +959,14 @@ augroup END
" => Git Shortcuts " => Git Shortcuts
" ============================================================================ " ============================================================================
nnoremap <leader>gs :Git status<CR> if exists('g:plugs["vim-fugitive"]')
nnoremap <leader>gc :Git commit<CR> nnoremap <leader>gs :Git status<CR>
nnoremap <leader>gp :Git push<CR> nnoremap <leader>gc :Git commit<CR>
nnoremap <leader>gl :Git pull<CR> nnoremap <leader>gp :Git push<CR>
nnoremap <leader>gd :Gdiffsplit<CR> nnoremap <leader>gl :Git pull<CR>
nnoremap <leader>gb :Git blame<CR> nnoremap <leader>gd :Gdiffsplit<CR>
nnoremap <leader>gb :Git blame<CR>
endif
" ============================================================================ " ============================================================================
" => Terminal Integration " => Terminal Integration
@ -1073,9 +1085,20 @@ function! s:CheatSheet() abort
\ 'EDITING', \ 'EDITING',
\ ' gc Toggle comment (visual mode too)', \ ' gc Toggle comment (visual mode too)',
\ ' s + 2 chars EasyMotion — jump anywhere on screen', \ ' s + 2 chars EasyMotion — jump anywhere on screen',
\ ' ,u Undo tree (visual branch history)', \ ' ,j / ,k EasyMotion — line motions',
\ ' ,u / F5 Undo tree (visual branch history)',
\ ' ,y / ,Y Yank / yank line to system clipboard', \ ' ,y / ,Y Yank / yank line to system clipboard',
\ ' ,p / ,P Paste from system clipboard (after / before)', \ ' ,p / ,P Paste from system clipboard',
\ ' Alt+j / Alt+k Move line down / up (also visual mode)',
\ ' ,F Re-indent entire file',
\ ' ,W Strip trailing whitespace',
\ ' ,* Search and replace word under cursor',
\ '',
\ 'SPELLING',
\ ' ,ss Toggle spell checking',
\ ' ,sn / ,sp Next / prev misspelling',
\ ' ,sa Add word to dictionary',
\ ' ,s? Suggest corrections',
\ '', \ '',
\ 'GIT', \ 'GIT',
\ ' ,gs Status ,gd Diff ,gb Blame', \ ' ,gs Status ,gd Diff ,gb Blame',
@ -1084,8 +1107,19 @@ function! s:CheatSheet() abort
\ 'WINDOWS & PANES', \ 'WINDOWS & PANES',
\ ' Ctrl+h/j/k/l Navigate Vim splits and tmux panes', \ ' Ctrl+h/j/k/l Navigate Vim splits and tmux panes',
\ ' ,h / ,l Prev / next buffer', \ ' ,h / ,l Prev / next buffer',
\ ' ,bd Close buffer (keep layout)',
\ ' ,tn / ,tc New tab / close tab | ,tl Last tab',
\ ' ,tv / ,th Open terminal (vertical / horizontal)', \ ' ,tv / ,th Open terminal (vertical / horizontal)',
\ ' Esc Esc Exit terminal mode', \ ' Esc Esc Exit terminal mode',
\ ' ,= / ,- Resize height (grow / shrink)',
\ ' ,+ / ,_ Resize width (grow / shrink)',
\ '',
\ 'UTILITIES',
\ ' ,ev / ,sv Edit / reload ~/.vimrc',
\ ' ,cp / ,cf Copy file path / filename to clipboard',
\ ' ,ms Open scratch markdown buffer',
\ ' ,cd Change CWD to current file directory',
\ ' F2 Paste F3 Line# F4 Relative# F6 Invisible',
\ '', \ '',
\ 'SESSION', \ 'SESSION',
\ ' :Obsess Start tracking session', \ ' :Obsess Start tracking session',
@ -1103,7 +1137,7 @@ nnoremap <silent> <leader>? :call <SID>CheatSheet()<CR>
" ============================================================================ " ============================================================================
" Show syntax highlight stack for word under cursor " Show syntax highlight stack for word under cursor
nmap <leader>sh :call <SID>SynStack()<CR> nnoremap <leader>sh :call <SID>SynStack()<CR>
function! <SID>SynStack() function! <SID>SynStack()
if !exists("*synstack") | return | endif if !exists("*synstack") | return | endif
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')

View file

@ -56,6 +56,10 @@ vim . # startup dashboard, current directory listed
vim myfile # edit a specific file vim myfile # edit a specific file
``` ```
> **First launch:** Vim will automatically install plugins on the first open
> (takes 3060 seconds depending on network). This is normal — wait for it
> to finish, then restart Vim.
--- ---
## Step 3: Set Up LSP ## Step 3: Set Up LSP
@ -67,7 +71,9 @@ Open a source file, then run:
``` ```
This auto-detects the filetype and installs the correct language server. This auto-detects the filetype and installs the correct language server.
No Node.js required — vim-lsp runs on pure VimScript. vim-lsp itself runs on pure VimScript — no Node.js required. However,
some language servers (JS/TS, HTML, CSS, JSON, YAML) are npm packages
that need Node.js to run. Python, Go, and Rust servers don't need it.
Check status: Check status:
```vim ```vim

10
get.sh
View file

@ -10,7 +10,11 @@ set -eo pipefail
REPO="https://github.com/m1ngsama/chopsticks.git" REPO="https://github.com/m1ngsama/chopsticks.git"
DEST="$HOME/.vim" DEST="$HOME/.vim"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m' if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
else
GREEN=''; YELLOW=''; RED=''; BOLD=''; NC=''
fi
ok() { echo -e "${GREEN}[OK]${NC} $1"; } ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; }
die() { echo -e "${RED}[FATAL]${NC} $1" >&2; exit 1; } die() { echo -e "${RED}[FATAL]${NC} $1" >&2; exit 1; }
@ -42,7 +46,7 @@ if [[ -d "$DEST/.git" ]]; then
warn "$DEST already exists — pulling latest changes" warn "$DEST already exists — pulling latest changes"
git -C "$DEST" pull --ff-only origin main 2>/dev/null || \ git -C "$DEST" pull --ff-only origin main 2>/dev/null || \
warn "Could not pull latest — using existing version (run: git -C ~/.vim pull)" warn "Could not pull latest — using existing version (run: git -C ~/.vim pull)"
ok "Repository updated" ok "Repository updated ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))"
elif [[ -d "$DEST" ]]; then elif [[ -d "$DEST" ]]; then
die "$HOME/.vim exists but is not a chopsticks git repo. die "$HOME/.vim exists but is not a chopsticks git repo.
Back it up first: mv ~/.vim ~/.vim.bak Back it up first: mv ~/.vim ~/.vim.bak
@ -50,7 +54,7 @@ elif [[ -d "$DEST" ]]; then
else else
git clone --depth=1 "$REPO" "$DEST" || \ git clone --depth=1 "$REPO" "$DEST" || \
die "Clone failed — check your network connection" die "Clone failed — check your network connection"
ok "Cloned to $DEST" ok "Cloned to $DEST ($(git -C "$DEST" describe --tags 2>/dev/null || git -C "$DEST" rev-parse --short HEAD))"
fi fi
# ── Run installer ───────────────────────────────────────────────────────────── # ── Run installer ─────────────────────────────────────────────────────────────