diff --git a/.vimrc b/.vimrc index 1fb7e2c..d3b0f2c 100644 --- a/.vimrc +++ b/.vimrc @@ -96,7 +96,10 @@ 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' - autocmd VimEnter * PlugInstall --sync | source $MYVIMRC + augroup PlugBootstrap + autocmd! + autocmd VimEnter * PlugInstall --sync | source $MYVIMRC + augroup END endif call plug#begin('~/.vim/plugged') @@ -358,7 +361,7 @@ endfunction map :call SmartFiles() map b :Buffers map rg :Rg -map rG :Rg -F +map rG :RgWord map rt :Tags map gF :GFiles @@ -384,6 +387,19 @@ else command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), 0) endif +" RgWord: fixed-string search for word under cursor (flags before --) +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('')), 1, 0) +else + command! -bang -nargs=* RgWord + \ call fzf#vim#grep( + \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' + \ .shellescape(expand('')), 1, fzf#vim#with_preview(), 0) +endif + " ============================================================================ " => GitGutter " ============================================================================ @@ -450,8 +466,8 @@ nmap aD :ALEDetail " vim-lsp (gopls) handles all Go intelligence; disable vim-go's own LSP layer let g:go_gopls_enabled = 0 let g:go_code_completion_enabled = 0 -let g:go_def_mode = 'gopls' -let g:go_info_mode = 'gopls' +let g:go_def_mode = 'godef' +let g:go_info_mode = 'godef' let g:go_fmt_autosave = 0 " ALE handles format-on-save let g:go_imports_autosave = 0 let g:go_highlight_types = 1 @@ -903,7 +919,7 @@ nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p")< nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") " Scratch markdown buffer -map m :e ~/buffer.md +nnoremap ms :e ~/buffer.md " Auto-create parent directories on save function! s:MkNonExDir(file, buf) @@ -930,7 +946,7 @@ nnoremap gs :Git status nnoremap gc :Git commit nnoremap gp :Git push nnoremap gl :Git pull -nnoremap gd :Gdiff +nnoremap gd :Gdiffsplit nnoremap gb :Git blame " ============================================================================ @@ -967,7 +983,7 @@ function! LargeFileSettings() setlocal undolevels=-1 setlocal eventignore+=FileType setlocal noswapfile - setlocal syntax=OFF + setlocal syntax= let b:ale_enabled = 0 echo "Large file (>10 MB): syntax, undo, and linting disabled." endfunction @@ -977,7 +993,7 @@ if g:is_tty autocmd! autocmd BufReadPre * \ if !empty(expand('')) && getfsize(expand('')) > 512000 | - \ setlocal syntax=OFF | + \ setlocal syntax= | \ endif augroup END diff --git a/QUICKSTART.md b/QUICKSTART.md index 47996de..3390b8c 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -142,7 +142,7 @@ K Show documentation | Key | Action | |-----|--------| | `,mp` | Open live preview in browser | -| `,mt` | Table of contents | +| `,mt` | Table of contents (side window) | | `zr` / `zm` | Unfold / fold all headings | Formatting in the buffer is live: `**bold**` renders as bold, diff --git a/README.md b/README.md index a2207b4..0f9e938 100644 --- a/README.md +++ b/README.md @@ -86,11 +86,10 @@ cd ~/.vim && ./install.sh 3. Backs up existing `~/.vimrc`, then symlinks `~/.vimrc → ~/.vim/.vimrc` 4. Installs vim-plug and runs `:PlugInstall` 5. Offers to install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman) -6. Offers to install Node.js via nvm (for npm formatters — optional) -7. Offers to install npm formatters (prettier, eslint, etc.) -8. Offers to install Python formatters/linters (black, isort, flake8, etc.) -9. Offers to install Go tools (gopls, goimports, staticcheck) -10. Offers to append vim-tmux-navigator bindings to `~/.tmux.conf` +6. Offers to install npm formatters (prettier, eslint, etc.) — requires Node.js +7. Offers to install Python formatters/linters (black, isort, flake8, etc.) +8. Offers to install Go tools (gopls, goimports, staticcheck) +9. Offers to append vim-tmux-navigator bindings to `~/.tmux.conf` **Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf). @@ -150,10 +149,12 @@ Press `,?` at any time to open the built-in cheat sheet. | `,E` | Open netrw in vertical split | | `,b` | Search open buffers (FZF) | | `,rg` | Project-wide search (ripgrep + FZF) | -| `,rG` | Ripgrep for word under cursor | +| `,rG` | Ripgrep word under cursor (fixed-string) | | `,,` | Switch to last file | | `,l` / `,h` | Next / previous buffer | | `,bd` | Close current buffer (preserves window layout) | +| `,wa` | Save all open buffers | +| `,cd` | Change working directory to current file's directory | ### Code Intelligence (vim-lsp) @@ -179,7 +180,7 @@ Press `,?` at any time to open the built-in cheat sheet. | Key | Action | |-----|--------| | `,mp` | Open live preview in browser (previm) | -| `,mt` | Table of contents | +| `,mt` | Table of contents (side window) | | `zr` / `zm` | Unfold / fold all headings | ### Git (vim-fugitive) @@ -253,7 +254,7 @@ No Node.js required. Uses `open` (macOS) or `xdg-open` (Linux). ### Table of contents ```vim -,mt " open TOC in a quickfix window — press Enter to jump to heading +,mt " open TOC in a side window — press Enter to jump to heading ``` --- @@ -307,7 +308,7 @@ bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R' ### TTY / SSH Support -Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode: +Detected automatically when `$TERM` is unset, `dumb`, `linux`, `screen`, or contains `builtin`. In TTY mode: - True colour and cursorline disabled - FZF preview windows disabled diff --git a/coc-settings.json b/coc-settings.json index 6b22baf..8f1c114 100644 --- a/coc-settings.json +++ b/coc-settings.json @@ -6,8 +6,5 @@ "filetypes": ["markdown"], "rootPatterns": [".git", ".marksman.toml"] } - }, - "coc.preferences.formatOnSaveFiletypes": [ - "markdown" - ] + } } diff --git a/install.sh b/install.sh index aaecf59..1fec44b 100755 --- a/install.sh +++ b/install.sh @@ -72,10 +72,11 @@ safe_download() { # ── Cross-platform package install helper ───────────────────────────────────── pkg_install() { local brew_pkg="${1:-}" apt_pkg="${2:-}" pac_pkg="${3:-}" dnf_pkg="${4:-}" - if [[ $HAS_BREW -eq 1 && -n "$brew_pkg" ]]; then brew install "$brew_pkg" >/dev/null 2>&1 + if [[ $OS == "macos" && $HAS_BREW -eq 1 && -n "$brew_pkg" ]]; then brew install "$brew_pkg" >/dev/null 2>&1 elif [[ $HAS_APT -eq 1 && -n "$apt_pkg" && $HAS_SUDO -eq 1 ]]; then sudo apt-get install -y "$apt_pkg" >/dev/null 2>&1 elif [[ $HAS_PACMAN -eq 1 && -n "$pac_pkg" && $HAS_SUDO -eq 1 ]]; then sudo pacman -S --noconfirm "$pac_pkg" >/dev/null 2>&1 elif [[ $HAS_DNF -eq 1 && -n "$dnf_pkg" && $HAS_SUDO -eq 1 ]]; then sudo dnf install -y "$dnf_pkg" >/dev/null 2>&1 + elif [[ $HAS_BREW -eq 1 && -n "$brew_pkg" ]]; then brew install "$brew_pkg" >/dev/null 2>&1 else return 1 fi } @@ -164,11 +165,14 @@ _menu_checkbox() { local _key _esc _i tput civis 2>/dev/null # hide cursor - _menu_draw + local _first=1 while true; do - tput cuu "$_lines" 2>/dev/null # move back to top of menu + if [[ $_first -eq 0 ]]; then + tput cuu "$_lines" 2>/dev/null # move back to top of menu + fi _menu_draw + _first=0 IFS= read -r -s -n1 _key /dev/null fi } +if [[ -d "$HOME/.vim/plugged" ]] && [[ -n "$(ls -A "$HOME/.vim/plugged" 2>/dev/null)" ]]; then + warn "PlugClean: removing plugins not listed in .vimrc from ~/.vim/plugged" +fi _vim_run +'PlugClean!' +qall || true # remove plugins no longer in vimrc; ignore exit code (none expected) _vim_run +'PlugInstall --sync' +qall || true # fzf post-install hook may exit non-zero; harmless @@ -558,6 +565,10 @@ _do_binary_apt() { if command -v "$check" >/dev/null 2>&1; then ok "$name (already installed)"; return fi + if [[ $HAS_SUDO -ne 1 ]]; then + fail "$name — sudo not available, cannot install to /usr/local/bin" + FAILED+=("$name"); return + fi if safe_download "$url" "$tmp"; then chmod +x "$tmp" && sudo mv "$tmp" /usr/local/bin/"$check" ok "$name"; INSTALLED+=("$name") @@ -583,20 +594,38 @@ elif [[ $HAS_APT -eq 1 ]]; then _do_sys "shellcheck" shellcheck "$_I_SHELLCHECK" "" shellcheck "" "" # hadolint: no apt package — binary from GitHub releases - HARCH=$(arch_github) - HVER=$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \ - | grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || HVER="" - _do_binary_apt "hadolint" hadolint "$_I_HADOLINT" \ - "https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \ - /tmp/chopsticks-hadolint + if [[ $_I_HADOLINT -ge 0 ]] && _selected "$_I_HADOLINT"; then + HARCH=$(arch_github) + HVER=$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || HVER="" + if [[ -z "$HVER" ]]; then + fail "hadolint — could not determine latest release version" + FAILED+=("hadolint") + else + _do_binary_apt "hadolint" hadolint "$_I_HADOLINT" \ + "https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \ + /tmp/chopsticks-hadolint + fi + else + skip "hadolint"; SKIPPED+=("hadolint") + fi # marksman: no apt package — binary from GitHub releases - MARCH=$(arch_linux_x64) - MVER=$(curl -fsSL https://api.github.com/repos/artempyanykh/marksman/releases/latest \ - | grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || MVER="" - _do_binary_apt "marksman" marksman "$_I_MARKSMAN" \ - "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \ - /tmp/chopsticks-marksman + if [[ $_I_MARKSMAN -ge 0 ]] && _selected "$_I_MARKSMAN"; then + MARCH=$(arch_linux_x64) + MVER=$(curl -fsSL https://api.github.com/repos/artempyanykh/marksman/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || MVER="" + if [[ -z "$MVER" ]]; then + fail "marksman — could not determine latest release version" + FAILED+=("marksman") + else + _do_binary_apt "marksman" marksman "$_I_MARKSMAN" \ + "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \ + /tmp/chopsticks-marksman + fi + else + skip "marksman"; SKIPPED+=("marksman") + fi elif [[ $HAS_PACMAN -eq 1 ]]; then _do_sys "ripgrep" rg "$_I_RIPGREP" "" "" ripgrep "" @@ -610,16 +639,22 @@ elif [[ $HAS_DNF -eq 1 ]]; then _do_sys "ripgrep" rg "$_I_RIPGREP" "" "" "" ripgrep _do_sys "fzf" fzf "$_I_FZF" "" "" "" fzf _do_sys "shellcheck" shellcheck "$_I_SHELLCHECK" "" "" "" ShellCheck - if [[ $_I_CTAGS -ge 0 ]] && _selected "$_I_CTAGS"; then - skip "universal-ctags — Fedora: install manually: sudo dnf install ctags" + if [[ $_I_CTAGS -ge 0 ]]; then + if _selected "$_I_CTAGS"; then + skip "universal-ctags — Fedora: install manually: sudo dnf install ctags" + fi SKIPPED+=("universal-ctags") fi - if [[ $_I_HADOLINT -ge 0 ]] && _selected "$_I_HADOLINT"; then - skip "hadolint — Fedora: install manually: https://github.com/hadolint/hadolint/releases" + if [[ $_I_HADOLINT -ge 0 ]]; then + if _selected "$_I_HADOLINT"; then + skip "hadolint — Fedora: install manually: https://github.com/hadolint/hadolint/releases" + fi SKIPPED+=("hadolint") fi - if [[ $_I_MARKSMAN -ge 0 ]] && _selected "$_I_MARKSMAN"; then - skip "marksman — Fedora: install manually: https://github.com/artempyanykh/marksman/releases" + if [[ $_I_MARKSMAN -ge 0 ]]; then + if _selected "$_I_MARKSMAN"; then + skip "marksman — Fedora: install manually: https://github.com/artempyanykh/marksman/releases" + fi SKIPPED+=("marksman") fi else