From 3daf725ad8dab0e92c80987d0fc2ddb934748676 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 29 Mar 2026 16:36:43 +0800 Subject: [PATCH] audit: wrap loose autocmds in augroups, fix install.sh robustness (#13) .vimrc: - Wrap all loose autocmds in named augroups with autocmd! (prevents doubling on :source $MYVIMRC): ChopstickTabHistory, ChopstickResize, ChopstickStdin, CocHighlight, ChopstickCleanup, ChopstickFiletype, ChopstickTTYLargeFile, ChopstickWhichKey, ChopstickStartify install.sh: - Add Arch Linux (pacman) branch for system tools - Add hadolint to system tools (brew/apt binary download/pacman) - Add staticcheck to Go tools - Add yamllint to pip tools - Remove sqlfmt from npm (SQL unified to sqlfluff via pip) - Remove coc-marksman (package does not exist on npm) - Add coc-settings.json symlink step with backup - Add pip3 bootstrap when python3 present but pip3 absent - Fix PlugInstall and CocInstall to use t :tabnext " Let 'tl' toggle between this and the last accessed tab let g:lasttab = 1 nmap tl :exe "tabn ".g:lasttab -au TabLeave * let g:lasttab = tabpagenr() +augroup ChopstickTabHistory + autocmd! + autocmd TabLeave * let g:lasttab = tabpagenr() +augroup END " Opens a new tab with the current buffer's path map te :tabedit =expand("%:p:h")/ @@ -411,7 +422,10 @@ nnoremap qo :copen nnoremap qc :cclose " Auto-equalize splits when terminal window is resized -autocmd VimResized * wincmd = +augroup ChopstickResize + autocmd! + autocmd VimResized * wincmd = +augroup END " ============================================================================ " => Plugin Settings @@ -437,7 +451,10 @@ let NERDTreeIgnore=['\.pyc$', '\~$', '\.swp$', '\.git$', '\.DS_Store', 'node_mod let NERDTreeWinSize=35 " Track stdin reads so startup autocmds can skip pipe/heredoc input -autocmd StdinReadPre * let s:std_in=1 +augroup ChopstickStdin + autocmd! + autocmd StdinReadPre * let s:std_in=1 +augroup END " Startup layout (non-TTY only — keeps TTY startup instant) if !g:is_tty @@ -538,7 +555,7 @@ let g:ale_linters = { \ 'python': ['flake8', 'pylint'], \ 'javascript': ['eslint'], \ 'typescript': ['eslint', 'tsserver'], -\ 'go': ['gopls', 'golint'], +\ 'go': ['gopls', 'staticcheck'], \ 'rust': ['cargo'], \ 'sh': ['shellcheck'], \ 'yaml': ['yamllint'], @@ -563,7 +580,7 @@ let g:ale_fixers = { \ 'scss': ['prettier'], \ 'less': ['prettier'], \ 'markdown': ['prettier'], -\ 'sql': ['sqlfmt'], +\ 'sql': ['sqlfluff'], \} " Don't fix on save if LSP is handling formatting (avoids double-format) @@ -577,10 +594,11 @@ let g:ale_lint_on_enter = 0 " --- vim-go: disable built-in LSP/gopls — CoC (coc-go) handles all Go intelligence --- " vim-go's gopls conflicts with coc-go and causes E495 errors on startup " (BufWinEnter afile expand fails for non-file buffers like NERDTree/Startify) -let g:go_gopls_enabled = 0 " disable vim-go's own gopls client +let g:go_gopls_enabled = 0 " disable vim-go's own gopls — coc-go handles LSP let g:go_code_completion_enabled = 0 " let CoC handle completion -let g:go_def_mode = 'gopls' " fallback if CoC unavailable (won't start without g:go_gopls_enabled) -let g:go_info_mode = 'gopls' +" Use godef as fallback for jump-to-def when CoC unavailable; gopls+disabled = error +let g:go_def_mode = g:use_coc ? 'gopls' : 'godef' +let g:go_info_mode = g:use_coc ? 'gopls' : 'godef' let g:go_fmt_autosave = 0 " CoC/ALE handle format-on-save let g:go_imports_autosave = 0 let g:go_highlight_types = 1 " keep syntax features @@ -655,7 +673,10 @@ if g:use_coc endfunction " Highlight symbol and its references on cursor hold - autocmd CursorHold * silent call CocActionAsync('highlight') + augroup CocHighlight + autocmd! + autocmd CursorHold * silent call CocActionAsync('highlight') + augroup END " Symbol renaming nmap rn (coc-rename) @@ -832,54 +853,58 @@ fun! CleanExtraSpaces() call setreg('/', old_query) endfun -if has("autocmd") +augroup ChopstickCleanup + autocmd! " Run for real files only; skip special buffers (NERDTree, Startify, terminal, etc.) autocmd BufWritePre * if empty(&buftype) && !empty(expand('')) | call CleanExtraSpaces() | endif -endif +augroup END " ============================================================================ " => Auto Commands " ============================================================================ -" Return to last edit position when opening files -autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif +augroup ChopstickFiletype + autocmd! + " Return to last edit position when opening files + autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif -" Set specific file types -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 + " Set specific file types + 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 -" Python specific settings -autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 colorcolumn=88 + " Python specific settings + autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 colorcolumn=88 -" JavaScript specific settings -autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 + " JavaScript specific settings + autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 -" Go specific settings -autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4 + " Go specific settings + autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4 -" HTML/CSS specific settings -autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2 + " HTML/CSS specific settings + autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2 -" YAML specific settings -autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2 + " YAML specific settings + autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2 -" Markdown specific settings -autocmd FileType markdown setlocal wrap linebreak spell tw=0 + " Markdown specific settings + autocmd FileType markdown setlocal wrap linebreak spell tw=0 -" Shell script settings -autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2 + " Shell script settings + autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2 -" Makefile settings (must use tabs) -autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8 + " Makefile settings (must use tabs) + autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8 -" JSON specific settings -autocmd FileType json setlocal expandtab shiftwidth=2 tabstop=2 + " JSON specific settings + autocmd FileType json setlocal expandtab shiftwidth=2 tabstop=2 -" Docker specific settings -autocmd BufNewFile,BufRead Dockerfile* setlocal filetype=dockerfile -autocmd FileType dockerfile setlocal expandtab shiftwidth=2 tabstop=2 + " Docker specific settings + autocmd BufNewFile,BufRead Dockerfile* setlocal filetype=dockerfile + autocmd FileType dockerfile setlocal expandtab shiftwidth=2 tabstop=2 +augroup END " ============================================================================ " => Status Line @@ -1071,7 +1096,10 @@ endfunction " Additional optimizations for TTY/basic terminals if g:is_tty " Disable syntax highlighting for very large files in TTY - autocmd BufReadPre * if !empty(expand('')) && getfsize(expand('')) > 512000 | setlocal syntax=OFF | endif + augroup ChopstickTTYLargeFile + autocmd! + autocmd BufReadPre * if !empty(expand('')) && getfsize(expand('')) > 512000 | setlocal syntax=OFF | endif + augroup END " Simpler status line for TTY set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%) @@ -1101,7 +1129,10 @@ set timeoutlen=500 if exists('g:plugs["vim-which-key"]') " Register after plugins are loaded (autoload functions available at VimEnter) - autocmd VimEnter * call which_key#register(',', 'g:which_key_map') + augroup ChopstickWhichKey + autocmd! + autocmd VimEnter * call which_key#register(',', 'g:which_key_map') + augroup END nnoremap :WhichKey ',' vnoremap :WhichKeyVisual ',' @@ -1262,7 +1293,10 @@ if exists('g:plugs["vim-startify"]') let g:startify_files_number = 10 " Required for NERDTree compatibility (prevents buftype conflicts) - autocmd User Startified setlocal buftype= + augroup ChopstickStartify + autocmd! + autocmd User Startified setlocal buftype= + augroup END endif " ============================================================================ diff --git a/coc-settings.json b/coc-settings.json new file mode 100644 index 0000000..6b22baf --- /dev/null +++ b/coc-settings.json @@ -0,0 +1,13 @@ +{ + "languageserver": { + "marksman": { + "command": "marksman", + "args": ["server"], + "filetypes": ["markdown"], + "rootPatterns": [".git", ".marksman.toml"] + } + }, + "coc.preferences.formatOnSaveFiletypes": [ + "markdown" + ] +} diff --git a/install.sh b/install.sh index f025f5a..0217028 100755 --- a/install.sh +++ b/install.sh @@ -93,13 +93,28 @@ HAS_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=1 HAS_APT=0; command -v apt >/dev/null 2>&1 && HAS_APT=1 HAS_DNF=0; command -v dnf >/dev/null 2>&1 && HAS_DNF=1 HAS_PACMAN=0; command -v pacman >/dev/null 2>&1 && HAS_PACMAN=1 -HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) detected" -HAS_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "Python/pip3 detected" -HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1 && ok "Go $(go version | awk '{print $3}') detected" +HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) detected" +HAS_PYTHON=0; command -v python3 >/dev/null 2>&1 && HAS_PYTHON=1 +HAS_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 +HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1 && ok "Go $(go version | awk '{print $3}') detected" -[[ $HAS_NODE -eq 0 ]] && warn "Node.js not found — JS/TS/Markdown npm tools will be skipped" -[[ $HAS_PIP -eq 0 ]] && warn "pip3 not found — Python tools will be skipped" -[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped" +# Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal images) +if [[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]]; then + warn "python3 found but pip3 missing — attempting bootstrap" + if python3 -m ensurepip --upgrade >/dev/null 2>&1 || \ + (command -v apt-get >/dev/null 2>&1 && sudo apt-get install -y python3-pip >/dev/null 2>&1) || \ + (command -v pacman >/dev/null 2>&1 && sudo pacman -S --noconfirm python-pip >/dev/null 2>&1) || \ + (command -v dnf >/dev/null 2>&1 && sudo dnf install -y python3-pip >/dev/null 2>&1); then + command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "pip3 bootstrapped" + else + warn "pip3 bootstrap failed — Python tools will be skipped" + fi +fi + +[[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 detected" +[[ $HAS_NODE -eq 0 ]] && warn "Node.js not found — JS/TS/Markdown npm tools will be skipped" +[[ $HAS_PIP -eq 0 ]] && warn "pip3 not found — Python tools will be skipped" +[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped" # ============================================================================ # Symlink @@ -116,6 +131,17 @@ fi ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc" ok "~/.vimrc -> $SCRIPT_DIR/.vimrc" +# CoC settings (marksman markdown LSP + format-on-save config) +mkdir -p "$HOME/.vim" +COC_CFG="$HOME/.vim/coc-settings.json" +if [ -f "$COC_CFG" ] && [ ! -L "$COC_CFG" ]; then + TS=$(date +%Y%m%d_%H%M%S) + warn "Backing up existing coc-settings.json to ~/.vim/coc-settings.json.backup.$TS" + mv "$COC_CFG" "$COC_CFG.backup.$TS" +fi +ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG" +ok "~/.vim/coc-settings.json -> $SCRIPT_DIR/coc-settings.json" + # ============================================================================ # vim-plug + plugins # ============================================================================ @@ -132,7 +158,8 @@ else fi step "Installing Vim plugins" -vim +PlugInstall +qall +# /dev/null 2>&1; then @@ -163,17 +190,36 @@ if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, marksman)?"; then if [[ $OS == macos ]]; then command -v brew >/dev/null 2>&1 || { warn "brew not found — skipping system tools"; } - install_sys "ripgrep" rg "brew install ripgrep" - install_sys "fzf" fzf "brew install fzf" - install_sys "universal-ctags" ctags "brew install universal-ctags" + install_sys "ripgrep" rg "brew install ripgrep" + install_sys "fzf" fzf "brew install fzf" + install_sys "universal-ctags" ctags "brew install universal-ctags" install_sys "shellcheck" shellcheck "brew install shellcheck" - install_sys "marksman" marksman "brew install marksman" + install_sys "hadolint" hadolint "brew install hadolint" + install_sys "marksman" marksman "brew install marksman" elif [[ $HAS_APT -eq 1 ]]; then sudo apt-get update -qq install_sys "ripgrep" rg "sudo apt-get install -y ripgrep" install_sys "fzf" fzf "sudo apt-get install -y fzf" install_sys "universal-ctags" ctags "sudo apt-get install -y universal-ctags" - install_sys "shellcheck" shellcheck "sudo apt-get install -y shellcheck" + install_sys "shellcheck" shellcheck "sudo apt-get install -y shellcheck" + # hadolint: no apt package, download binary + if ! command -v hadolint >/dev/null 2>&1; then + ARCH=$(uname -m) + [[ "$ARCH" == "x86_64" ]] && HARCH="x86_64" || HARCH="arm64" + HVER=$(curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4) + if [[ -n "$HVER" ]]; then + curl -fsSL "https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \ + -o /tmp/hadolint && chmod +x /tmp/hadolint && sudo mv /tmp/hadolint /usr/local/bin/hadolint + ok "hadolint" + INSTALLED+=("hadolint") + else + warn "hadolint: could not detect latest release, install manually" + SKIPPED+=("hadolint") + fi + else + ok "hadolint (already installed)" + fi # marksman: no apt package, download binary if ! command -v marksman >/dev/null 2>&1; then ARCH=$(uname -m) @@ -192,21 +238,30 @@ if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, marksman)?"; then else ok "marksman (already installed)" fi + elif [[ $HAS_PACMAN -eq 1 ]]; then + install_sys "ripgrep" rg "sudo pacman -S --noconfirm ripgrep" + install_sys "fzf" fzf "sudo pacman -S --noconfirm fzf" + install_sys "universal-ctags" ctags "sudo pacman -S --noconfirm ctags" + install_sys "shellcheck" shellcheck "sudo pacman -S --noconfirm shellcheck" + install_sys "hadolint" hadolint "sudo pacman -S --noconfirm hadolint" + install_sys "marksman" marksman "sudo pacman -S --noconfirm marksman" elif [[ $HAS_DNF -eq 1 ]]; then install_sys "ripgrep" rg "sudo dnf install -y ripgrep" install_sys "fzf" fzf "sudo dnf install -y fzf" - install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck" + install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck" skip "universal-ctags — install manually: sudo dnf install ctags" SKIPPED+=("ctags") + skip "hadolint — install manually from https://github.com/hadolint/hadolint/releases" + SKIPPED+=("hadolint") skip "marksman — install manually from https://github.com/artempyanykh/marksman/releases" SKIPPED+=("marksman") else warn "Unknown Linux distro — skipping system tools (install manually)" - SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "marksman") + SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") fi else skip "system tools" - SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "marksman") + SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") fi # ============================================================================ @@ -237,7 +292,6 @@ if [[ $HAS_NODE -eq 1 ]]; then npm_install stylelint-config-standard npm_install eslint npm_install typescript tsc - npm_install sqlfmt else skip "npm tools" SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") @@ -254,7 +308,7 @@ fi step "Python tools (formatters + linters)" if [[ $HAS_PIP -eq 1 ]]; then - if ask "Install Python tools (black, isort, flake8, pylint, sqlfluff)?"; then + if ask "Install Python tools (black, isort, flake8, pylint, yamllint, sqlfluff)?"; then pip_install() { local pkg="$1"; local check="${2:-$1}" if command -v "$check" >/dev/null 2>&1; then @@ -274,24 +328,25 @@ if [[ $HAS_PIP -eq 1 ]]; then pip_install isort pip_install flake8 pip_install pylint + pip_install yamllint pip_install sqlfluff else skip "Python tools" - SKIPPED+=("black" "isort" "flake8" "pylint" "sqlfluff") + SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff") fi else skip "Python tools (pip3 not installed)" - SKIPPED+=("black" "isort" "flake8" "pylint" "sqlfluff") + SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff") fi # ============================================================================ -# Go tools (gopls, goimports) +# Go tools (gopls, goimports, staticcheck) # ============================================================================ step "Go tools" if [[ $HAS_GO -eq 1 ]]; then - if ask "Install Go tools (gopls, goimports)?"; then + if ask "Install Go tools (gopls, goimports, staticcheck)?"; then # Go installs binaries to $(go env GOPATH)/bin — add to PATH for this session GOBIN="$(go env GOPATH)/bin" export PATH="$PATH:$GOBIN" @@ -310,8 +365,9 @@ if [[ $HAS_GO -eq 1 ]]; then FAILED+=("$name") fi } - go_install gopls "golang.org/x/tools/gopls@latest" gopls - go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports + go_install gopls "golang.org/x/tools/gopls@latest" gopls + go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports + go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck # Remind user to add GOPATH/bin to their shell profile if ! echo "$PATH" | grep -q "$GOBIN"; then @@ -319,11 +375,11 @@ if [[ $HAS_GO -eq 1 ]]; then fi else skip "Go tools" - SKIPPED+=("gopls" "goimports") + SKIPPED+=("gopls" "goimports" "staticcheck") fi else skip "Go tools (go not installed)" - SKIPPED+=("gopls" "goimports") + SKIPPED+=("gopls" "goimports" "staticcheck") fi # ============================================================================ @@ -334,7 +390,8 @@ step "CoC language server extensions" if [[ $HAS_NODE -eq 1 ]]; then if ask "Install CoC language servers (LSP for all configured languages)?"; then - vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-marksman coc-sql' +qall + # Note: coc-marksman doesn't exist on npm — markdown LSP is handled via coc-settings.json + vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-sql' +qall