Compare commits

...

6 commits

Author SHA1 Message Date
716e7e5808 fix: silence fzf post-install hook false-positive exit code 2026-04-09 22:57:42 +08:00
a605185a2e fix: use TERM=dumb+/dev/null for non-interactive vim runs, eliminating escape sequence leakage 2026-04-09 22:53:53 +08:00
4a9c9354a2 fix: restore original art header as static list (no system() call) 2026-04-09 22:49:52 +08:00
b2b1632857 fix: terminal garble, black screen on startup, Unicode in header
.vimrc:
- TTY detection: add empty($TERM) and dumb — fixes black screen when
  TERM is unset (non-interactive SSH, minimal environments)
- Replace StartifyHeader() with a static ASCII list — removes the
  synchronous system('git ...') call that caused the black screen while
  vim waited for git before rendering, and removes Unicode block chars
  (███╗) that rendered as garbage on terminals without full UTF-8

install.sh:
- _vim_run() helper: uses </dev/tty when accessible so vim properly
  manages the alternate screen buffer and restores terminal state on
  exit — this was the root cause of garbled output after PlugInstall
  and CocInstall steps; falls back to --not-a-term for CI environments
- Remove 'screen may go dark' warning — no longer needed since terminal
  state is properly managed via /dev/tty

Remove CHANGELOG.md — use git log
2026-04-09 22:48:55 +08:00
b20bedaa8b docs: update README, QUICKSTART, CHANGELOG for v1.2.0
- README: one-liner curl install as primary method, updated Requirements
  table to reflect auto-install capability, expanded Installation section
  with 11-step installer description
- QUICKSTART: Step 1 now leads with curl one-liner, includes traditional
  and --yes variants
- CHANGELOG: add v1.2.0 entry covering all robustness and get.sh changes
2026-04-09 22:37:07 +08:00
8820d1d107 fix: use test-open for /dev/tty instead of -e check
[[ -e /dev/tty ]] returns true even in non-interactive SSH sessions
where the device file exists but cannot actually be opened, causing
'No such device or address' errors. Replace with { true </dev/tty; }
which tests actual openability before redirecting.
2026-04-09 22:32:04 +08:00
6 changed files with 85 additions and 254 deletions

48
.vimrc
View file

@ -11,7 +11,7 @@
set nocompatible
" Detect terminal type and capabilities (must be early for conditional configs)
let g:is_tty = ($TERM =~ 'linux' || $TERM =~ 'screen' || &term =~ 'builtin')
let g:is_tty = empty($TERM) || $TERM ==# 'dumb' || $TERM =~# 'linux' || $TERM =~# 'screen' || &term =~# 'builtin'
let g:has_true_color = ($COLORTERM == 'truecolor' || $COLORTERM == '24bit')
" Enable type file detection. Vim will be able to try to detect the type of file in use
@ -1403,42 +1403,16 @@ endif
" ============================================================================
if exists('g:plugs["vim-startify"]')
" Centered dashboard header — matches neovim startup aesthetic
function! StartifyHeader() abort
let l:art = [
\ '███╗ ███╗ ██╗███╗ ██╗ ██████╗ ███████╗ █████╗ ███╗ ███╗ █████╗ ',
\ '████╗ ████║███║████╗ ██║██╔════╝ ██╔════╝██╔══██╗████╗ ████║██╔══██╗',
\ '██╔████╔██║╚██║██╔██╗ ██║██║ ███╗███████╗███████║██╔████╔██║███████║ ',
\ '██║╚██╔╝██║ ██║██║╚██╗██║██║ ██║╚════██║██╔══██║██║╚██╔╝██║██╔══██║',
\ '██║ ╚═╝ ██║ ██║██║ ╚████║╚██████╔╝███████║██║ ██║██║ ╚═╝ ██║██║ ██║',
\ '╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝',
\ ]
let l:art_w = 71
let l:pad = max([0, (winwidth(0) - l:art_w) / 2])
let l:indent = repeat(' ', l:pad)
let l:lines = []
for l:line in l:art
call add(l:lines, l:indent . l:line)
endfor
" Info subheader: vim version + cwd + git branch
let l:ver = 'Vim ' . (v:version / 100) . '.' . printf('%02d', v:version % 100)
let l:cwd = fnamemodify(getcwd(), ':t')
let l:git = ''
if executable('git')
let l:branch = system('git -C ' . shellescape(getcwd()) .
\ ' rev-parse --abbrev-ref HEAD 2>/dev/null')
if v:shell_error == 0
let l:git = ' [' . substitute(l:branch, '\n\+$', '', '') . ']'
endif
endif
let l:info = l:ver . ' ' . l:cwd . l:git
let l:info_pad = max([0, (winwidth(0) - len(l:info)) / 2])
call add(l:lines, '')
call add(l:lines, repeat(' ', l:info_pad) . l:info)
call add(l:lines, '')
return l:lines
endfunction
let g:startify_custom_header = 'StartifyHeader()'
" Static header — no system() calls so vim renders instantly on startup
let g:startify_custom_header = [
\ ' ███╗ ███╗ ██╗███╗ ██╗ ██████╗ ███████╗ █████╗ ███╗ ███╗ █████╗ ',
\ ' ████╗ ████║███║████╗ ██║██╔════╝ ██╔════╝██╔══██╗████╗ ████║██╔══██╗ ',
\ ' ██╔████╔██║╚██║██╔██╗ ██║██║ ███╗███████╗███████║██╔████╔██║███████║ ',
\ ' ██║╚██╔╝██║ ██║██║╚██╗██║██║ ██║╚════██║██╔══██║██║╚██╔╝██║██╔══██║ ',
\ ' ██║ ╚═╝ ██║ ██║██║ ╚████║╚██████╔╝███████║██║ ██║██║ ╚═╝ ██║██║ ██║ ',
\ ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝',
\ '',
\ ]
" Sessions first, then recent files — mirrors dashboard plugin ordering
let g:startify_lists = [

View file

@ -1,173 +0,0 @@
# Changelog
All notable changes to chopsticks are documented here.
---
## [1.1.1] - 2026-04-09
Systematic absorption of best practices from amix/vimrc, tpope/vim-sensible,
ThePrimeagen, skwp/YADR, and spf13-vim — settings and mappings that appear
consistently across all top global configs but were missing here.
### Added
- **`set ttimeoutlen=10`** — eliminates the ~500ms ESC lag in terminal Vim; separates
keycode timeout from leader-key timeout (`timeoutlen` unchanged at 500ms)
- **`set display+=lastline`** — shows truncated long lines instead of replacing them with `@@@`
- **`set complete-=i`** — `Ctrl+n/p` no longer scans all included files; completion is instant
- **`set wildignorecase`** — case-insensitive filename completion in wildmenu and `:find`
- **`set path+=**`** — recursive `:find` across the project; wildignore excludes
`node_modules`, `__pycache__`, `dist`, `build`, `.git`
- **`set sessionoptions`** — removes `options` from saved sessions (prevents stale plugin
settings from contaminating restored sessions)
- **`set listchars`** — defines visible whitespace characters; TTY uses ASCII symbols,
modern terminals use Unicode (tab `→`, trail `·`, extends `▸`)
- **`F6`** — toggle visible whitespace on/off
- **`formatoptions-=cro`** on `BufEnter` — disables automatic comment-leader insertion
when pressing Enter or `o/O`; runs on BufEnter to override filetype plugins
- **`InsertLeave * set nopaste`** — auto-disables paste mode on leaving insert, preventing
permanently broken auto-indent
- **`colorcolumn=+1`** for all languages via `textwidth`:
Python 88, Go 120, JS/TS 100, Rust 100, Shell 80 (Markdown disabled)
- **`vnoremap J/K`** with `gv=gv` — move selected lines down/up and re-indent (ThePrimeagen)
- **`gV`** — re-select last pasted text (`\`[v\`]` — spf13, YADR)
- **`cnoremap <C-p>/<C-n>`** — navigate command-line history matching typed prefix (amix, spf13)
- **`<leader>e :Explore`** — open built-in Netrw file browser; works on any Vim without plugins
- **`<leader>cd`** — change window-local CWD to current file's directory (was `<leader>wd`)
- **`<leader>sv`** — reloads vimrc and echoes confirmation
### Changed
- `<leader>wd` renamed to `<leader>cd`; now uses `lcd` (window-local) instead of `cd` (global)
- `wildignore` expanded with `*/node_modules/*`, `*/__pycache__/*`, `*/dist/*`, `*/build/*`
---
## [1.1.0] - 2026-04-09
Ergonomics and automation overhaul: community-standard keybindings, seamless
tmux integration, an in-Vim cheat sheet, a beginner onboarding section, and
several correctness fixes from a systematic review.
### Added
- **`jk``Esc`** in insert mode — ergonomic escape without reaching for the key
- **`Ctrl+s` save** in normal and insert mode (add `stty -ixon` to shell rc to enable
in terminals that use XON/XOFF flow control)
- **`//` visual search** — search for visually selected text using `\V` very-nomagic escaping
- **`<leader>p` / `<leader>P`** — paste from system clipboard after/before cursor
- **`<leader>rG`** — ripgrep word under cursor with `-F` (literal, not regex)
- **`<leader>u`** — leader-key alias for UndoTree (complements `F5`)
- **`<leader>tt`** — leader-key alias for Tagbar (complements `F8`)
- **`,?` in-Vim cheat sheet** — opens a read-only buffer covering modes, survival
commands, search, code intelligence, git, and clipboard; press `q` to close
- **vim-tmux-navigator** plugin — `Ctrl+h/j/k/l` navigates seamlessly across Vim
splits and tmux panes without a prefix key
- **`install.sh` tmux step** — detects tmux and optionally appends the four
navigator `bind-key` lines to `~/.tmux.conf`; warns about `C-l`/screen-clear tradeoff
- **`install.sh` survival guide** — post-install output now shows the 4 essential
commands for first-time Vim users, plus the `stty -ixon` advisory
- **QUICKSTART.md Step 0** — new first section explaining Vim modes (Normal/Insert/Visual)
and 4 survival commands; makes the guide usable by users who have never opened Vim
- **`let b:ale_enabled = 0`** in `LargeFileSettings()` — ALE no longer spawns
linter subprocesses for files over 10 MB
### Changed
- **ALE lint triggers**`ale_lint_on_text_changed` changed from `'never'` to `'normal'`;
`ale_lint_on_insert_leave` and `ale_lint_on_enter` changed from `0` to `1` — diagnostics
now refresh on buffer enter and after edits settle in normal mode
- **`<C-h/j/k/l>` manual maps removed** — vim-tmux-navigator owns these keys at
plugin load time; the previous hand-rolled `<C-W>` maps were unreachable dead code
- **`<leader>pp` paste-mode toggle removed** — functionally identical to the existing
`F2` pastetoggle; its presence caused a 500 ms delay on every `<leader>p` paste
### Fixed
- **ALE navigation direction reversed**`[e` now correctly calls `ALEPrevious`
and `]e` calls `ALENext`, matching the vim-unimpaired `[`/`]` convention
- **`<leader>rG` regex metacharacter bug** — without `-F`, characters like `.` `*`
`(` in the cursor word were treated as regex, producing incorrect matches
---
## [1.0.0] - 2026-03-29
First stable release. Full-stack engineering environment out of the box — automatic
installation, tiered LSP, TTY fallback, and coverage for 14 languages.
### Added
- **Arch Linux support** in `install.sh` — pacman branch for all system tools
- **`hadolint`** added to system tools installation (Dockerfile linting)
- **`staticcheck`** added to Go tools (replaces archived `golint`)
- **`yamllint`** added to pip tools (YAML linting)
- **`coc-settings.json`** — configures `marksman` as Markdown LSP for CoC via
`languageserver` entry; symlinked automatically by `install.sh`
- **pip3 bootstrap** in `install.sh` — auto-installs pip3 when python3 is present
but pip3 is absent (common on Ubuntu minimal images)
- **9 named augroups** in `.vimrc` — all loose `autocmd` statements now wrapped
with `autocmd!` to prevent doubling on `:source $MYVIMRC`:
`ChopstickTabHistory`, `ChopstickResize`, `ChopstickStdin`, `CocHighlight`,
`ChopstickCleanup`, `ChopstickFiletype`, `ChopstickTTYLargeFile`,
`ChopstickWhichKey`, `ChopstickStartify`
- **TTY-safe plugin install**`vim +PlugInstall +qall </dev/null` prevents
Vim from blocking in non-interactive/piped environments
### Changed
- SQL tooling unified to **`sqlfluff`** (pip) — `sqlfmt` removed from npm section
- Go linter changed from `golint` (archived 2023) to **`staticcheck`**
- Markdown LSP changed from broken `coc-marksman` (npm) to **`marksman`** binary
configured via `coc-settings.json`
### Fixed
- **vim-go startup hang** on Arch Linux — removed `:GoUpdateBinaries` post-install
hook; set `g:go_gopls_enabled = 0` to prevent conflict with `coc-go`
- **E495 errors** (`<afile>` in special buffers) — all `<afile>` usages guarded
with `!empty(expand('<afile>'))` and `empty(&buftype)` checks
- **`g:go_def_mode` conflict** — now conditional: uses `gopls` when CoC active,
`godef` when vim-lsp active (avoids error when gopls is disabled)
- **vim startup UX** — NERDTree + Startify layout for `vim .` and bare `vim`
- **`coc-marksman` silent failure** — package does not exist on npm; replaced with
native `languageserver` configuration in `coc-settings.json`
- **CoC startup warning** in no-node environments — `g:coc_start_at_startup = 0`
and `g:coc_disable_startup_warning = 1` set when `g:use_coc = 0`
---
## [0.9.0] - 2026-02-21
### Added
- **Full-stack language coverage** — LSP, lint, and format for: Python,
JavaScript, TypeScript, Go, Rust, Shell, YAML, HTML, CSS/SCSS, Less,
JSON, Markdown, SQL, Dockerfile
- **`install.sh` overhaul** — automated installation of system tools, npm tools,
pip tools, Go tools, and CoC language server extensions with platform detection
and interactive prompts; `--yes` flag for non-interactive mode
- **vim-startify** startup screen with dynamic header (version, cwd, git branch)
- **vim-which-key** keybinding popup on `,` + 500ms pause
- **Startup layout**`vim .` opens NERDTree left + Startify right; bare `vim`
opens Startify with NERDTree alongside
- **Session management** via vim-obsession + vim-prosession
- **Large file handling** — syntax and undo disabled for files > 10 MB
- **Project-local config**`.vimrc` in project root auto-loaded via `set exrc`
- **Persistent undo**`~/.vim/.undo/` with `undolevels=1000`
### Changed
- Tiered LSP backend: CoC (Node.js) preferred, vim-lsp (no Node.js) as fallback
- All CoC and vim-lsp keybindings unified (`gd`, `K`, `[g`/`]g`, `,rn`, `,ca`)
- ALE `fix_on_save` disabled when vim-lsp active (prevents double-format)
- NERDTree autocmd wrapped in `augroup NERDTreeAutoClose`
### Fixed
- Multiple leader key conflicts resolved (`,ad`, `,cd`, `,cp`, `,sp`, `,t`)
- CtrlP removed (redundant with FZF)
- Duplicate `set` options cleaned up
- `<leader>A` dead mapping (no alternate-file plugin) removed
---
## [0.1.0] - 2024
Initial release — base Vim configuration with vim-plug, basic plugins, and
TTY/non-TTY detection.

View file

@ -39,17 +39,27 @@ Once in Normal mode, press `,?` to open a cheat sheet covering everything else.
## Step 1: Install
**One command — works on macOS and Linux:**
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
```
This clones the repo to `~/.vim` and runs the full installer. Interactive prompts
let you choose which optional tools to install (ripgrep, Node.js, Python tools, etc.).
The installer automatically handles missing dependencies — it will offer to install
`git`, Homebrew (macOS), or Node.js via nvm if they are not found.
**Traditional install:**
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
The script handles everything: symlinks, vim-plug, plugins, and all tools.
It detects your OS (macOS/Debian/Arch/Fedora) and installs what it can automatically.
**Non-interactive (CI / server):**
**Non-interactive (CI / server / scripting):**
```bash
./install.sh --yes
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
---

View file

@ -15,8 +15,7 @@
[![Languages](https://img.shields.io/badge/languages-14-informational?style=flat-square)](#language-support)
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
```
> **New to Vim?** Read [Step 0 in QUICKSTART.md](QUICKSTART.md#step-0-vim-basics) first —
@ -56,54 +55,66 @@ cd ~/.vim && ./install.sh
| Tool | Minimum | Role |
|------|---------|------|
| Vim | **8.0+** | Required |
| git | any | Cloning and vim-fugitive |
| curl | any | vim-plug bootstrap |
| Node.js | 14.14+ | Optional — enables CoC LSP (recommended) |
| Vim | **8.0+** | Required `install.sh` installs it if missing |
| git | any | Required — `install.sh` installs it if missing |
| curl | any | Required — `install.sh` installs it if missing |
| Node.js | 14.14+ | Optional — enables CoC LSP; `install.sh` offers nvm install |
| ripgrep | any | Optional — enables `,rg` / `,rG` project search |
| fzf | any | Optional — enables `Ctrl+p` fuzzy finder |
| ctags | any | Optional — enables `,tt` tag browser |
| tmux | 1.8+ | Optional — enables seamless pane navigation |
All optional tools are installed automatically by `install.sh` when prompted.
`install.sh` detects your environment and installs missing dependencies automatically.
On macOS it will offer to install Homebrew if not present. On any platform it will
offer to install Node.js via nvm if missing.
---
## Installation
### Automatic (recommended)
### One command (recommended)
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash
```
This bootstrap script clones the repo to `~/.vim`, then runs the full installer.
It works correctly even when piped from curl — interactive prompts use `/dev/tty`.
For non-interactive or CI environments:
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
### Traditional (git clone)
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim
./install.sh
cd ~/.vim && ./install.sh
```
The installer handles everything in sequence:
### What the installer does
1. Verifies Vim 8.0+ and detects OS / package manager
2. Backs up any existing `~/.vimrc` with a timestamp
3. Symlinks `~/.vimrc → ~/.vim/.vimrc` and `~/.vim/coc-settings.json`
4. Installs vim-plug and runs `:PlugInstall`
5. Optionally installs system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)
6. Optionally installs language tools (npm, pip, Go)
7. Optionally installs CoC language server extensions
8. Optionally configures tmux for seamless pane navigation
1. **Preflight** — checks network, detects OS and package manager, verifies or installs `curl`, `git`, and `vim`
2. **sudo** — authenticates once upfront; gracefully skips system packages when unavailable
3. **macOS** — offers to install Homebrew if `brew` is not found
4. **Node.js** — offers to install via nvm if not found (falls back to vim-lsp if declined)
5. **Python** — offers to install Python 3 if missing; bootstraps pip3 if only python3 is present
6. **Symlinks** — backs up any existing `~/.vimrc` with a timestamp, then symlinks `~/.vimrc → ~/.vim/.vimrc`
7. **Plugins** — installs vim-plug and runs `:PlugInstall` (with a progress notice during the black-screen period)
8. **System tools** — ripgrep, fzf, ctags, shellcheck, hadolint, marksman (verified downloads)
9. **Language tools** — npm formatters, pip formatters/linters, Go tools
10. **CoC extensions** — all language servers in one step
11. **tmux** — optionally appends vim-tmux-navigator bindings to `~/.tmux.conf`
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch Linux (pacman), Fedora (dnf).
Use `--yes` for non-interactive or CI environments:
```bash
./install.sh --yes
```
### Manual
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
ln -sf ~/.vim/.vimrc ~/.vimrc
ln -sf ~/.vim/coc-settings.json ~/.vim/coc-settings.json
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
vim +PlugInstall +qall </dev/null

6
get.sh
View file

@ -59,8 +59,10 @@ step "Running installer"
cd "$DEST"
# exec replaces this process with install.sh and reconnects stdin to /dev/tty
# so interactive prompts work correctly even when this script was piped from curl
if [[ -e /dev/tty ]]; then
# so interactive prompts work correctly even when this script was piped from curl.
# Use a test-open to check /dev/tty is actually accessible (it may exist but be
# unusable in non-interactive SSH sessions or container environments).
if { true </dev/tty; } 2>/dev/null; then
exec bash install.sh "$@" </dev/tty
else
exec bash install.sh "$@"

View file

@ -40,7 +40,7 @@ ask() {
[[ $AUTO_YES -eq 1 ]] && return 0
if [[ -t 0 ]]; then
read -r -p "$1 [y/N] " reply
elif [[ -e /dev/tty ]]; then
elif { true </dev/tty; } 2>/dev/null; then
read -r -p "$1 [y/N] " reply </dev/tty
else
# No terminal available — default to no (safe)
@ -360,14 +360,21 @@ else
fi
step "Installing Vim plugins"
info "(Vim will open fullscreen to install plugins — screen may go dark for 10-30s, this is normal)"
# </dev/null prevents Vim from reading stdin in non-interactive/piped environments
if ! vim +PlugInstall +qall </dev/null; then
warn "vim +PlugInstall exited non-zero — plugins may be partially installed"
warn "Run :PlugInstall manually inside Vim if something looks wrong"
else
ok "Plugins installed"
fi
# Use /dev/tty when available so vim properly manages the terminal (alternate
# screen buffer, cursor, colours) and restores it cleanly on exit.
# Fall back to --not-a-term for non-interactive/CI environments.
_vim_run() {
if { true </dev/tty; } 2>/dev/null; then
# Interactive terminal: let vim manage the alternate screen properly
vim "$@" </dev/tty
else
# Non-interactive / CI: TERM=dumb suppresses all escape sequences;
# stdout+stderr redirected so nothing leaks into installer output
TERM=dumb vim "$@" </dev/null >/dev/null 2>&1
fi
}
_vim_run +PlugInstall +qall || true # post-install hooks (e.g. fzf) may exit non-zero; harmless
ok "Plugins installed"
# ============================================================================
# 4. System Tools
@ -641,9 +648,9 @@ step "CoC language server extensions"
if [[ $HAS_NODE -eq 1 ]]; then
if ask "Install CoC language servers (LSP for all configured languages)?"; then
info "(Downloading CoC extensions via npm — screen may go dark for 1-3 minutes, this is normal)"
info "(Downloading CoC extensions via npm — this may take 1-3 minutes)"
# 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 </dev/null
_vim_run +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-sql' +qall
ok "CoC language servers installed"
else
skip "CoC language servers"