fix: correct non-TTY vim-plug install; add real plugin count check

- _vim_run non-TTY branch: remove </dev/null (caused "Error reading
  input, exiting" before downloads finished → 0 plugins installed) and
  >/dev/null (broke async job callbacks → partial install of ~13/26).
  Now only redirects stderr; stdin inherits from caller so vim's event
  loop runs to completion.
- Add post-install verification: count ~/.vim/plugged entries and die
  if empty, replacing the unconditional "ok Plugins installed" that
  masked failures silently.
- .vimrc, install.sh, QUICKSTART.md, README.md: carry forward the full
  v1.2 rewrite (checkbox module selection, solarized palette, LSP
  stack) that accumulated in the previous session.

Tested: macOS (27 plugins, exit 0) and Arch Linux SSH (26 plugins,
exit 0). shellcheck: zero warnings.
This commit is contained in:
m1ngsama 2026-04-10 18:43:52 +08:00
parent 716e7e5808
commit 0b3f631e98
4 changed files with 1363 additions and 1887 deletions

1521
.vimrc

File diff suppressed because it is too large Load diff

View file

@ -1,63 +1,47 @@
# Quick Start
Five minutes from zero to a working Vim engineering environment.
> **New to Vim?** Read Step 0 first — it takes 2 minutes and prevents the most
> common beginner frustration. Already know how Vim modes work? [Skip to Step 1](#step-1-install).
Five minutes from zero to a working Vim environment.
---
## Step 0: Vim Basics
## Step 0: Vim Basics (2 minutes)
> **When confused, press `Esc` until things feel normal again — then keep reading.**
> **When confused, press `Esc` until things feel normal again.**
Vim is **modal**: the keyboard behaves differently depending on which mode you are in.
Most people get stuck because they try to type text while in Normal mode.
Vim is **modal** — the keyboard behaves differently depending on which mode you are in.
### The Three Modes
| Mode | Purpose | Enter | Leave |
|------|---------|-------|-------|
| **Normal** | Navigate and run commands | default on startup | — |
| **Insert** | Type text | `i` before / `a` after / `o` new line | `Esc` or `jk` |
| **Visual** | Select text | `v` char / `V` line | `Esc` |
| Mode | Purpose | How to enter | How to leave |
|------|---------|--------------|--------------|
| **Normal** | Navigate and run commands | Startup default | — (you're already here) |
| **Insert** | Type text | `i` before cursor, `a` after, `o` new line below | `Esc` or `jk` |
| **Visual** | Select text | `v` char-by-char, `V` whole lines | `Esc` |
### 4 Survival Commands
Learn these before anything else. They will get you out of every stuck situation.
### 4 commands that get you out of any jam
| Command | Action |
|---------|--------|
| `Esc` or `jk` | Exit insert/visual mode — return to Normal |
| `:q!` then `Enter` | Force quit without saving (emergency exit) |
| `Esc` or `jk` | Exit insert / visual mode → Normal |
| `:q!` then `Enter` | Force quit without saving |
| `,x` | Save and quit |
| `,w` or `Ctrl+s` | Save the file |
| `,w` or `Ctrl+s` | Save |
Once in Normal mode, press `,?` to open a cheat sheet covering everything else.
Once in Normal mode, press `,?` to open the cheat sheet.
---
## 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:**
Traditional:
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
cd ~/.vim && ./install.sh
```
**Non-interactive (CI / server / scripting):**
Non-interactive / CI:
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
@ -67,37 +51,14 @@ curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | b
## Step 2: Open Vim
```bash
vim
```
The startup screen (vim-startify) shows recent files and sessions.
Press `Ctrl+p` to find a file, or just type a path.
To open a project:
```bash
vim . # NERDTree on left, Startify on right
vim myfile # opens file directly
vim # startup dashboard (recent files + sessions)
vim . # startup dashboard, current directory listed
vim myfile # edit a specific file
```
---
## Step 3: Set Up LSP (pick your path)
### Path A: With Node.js (CoC — full LSP)
```bash
node --version # must be >= 14.14
```
Inside Vim, install language servers for your stack:
```vim
:CocInstall coc-pyright coc-tsserver coc-go coc-rust-analyzer
```
Or let `install.sh` do it — it asks during setup.
### Path B: Without Node.js (vim-lsp — no dependencies)
## Step 3: Set Up LSP
Open a source file, then run:
@ -105,25 +66,38 @@ Open a source file, then run:
:LspInstallServer
```
This auto-detects and installs the correct language server for the current filetype.
This auto-detects the filetype and installs the correct language server.
No Node.js required — vim-lsp runs on pure VimScript.
Check status:
```vim
:LspStatus
```
**Markdown LSP** (`marksman`) needs a standalone binary:
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# install.sh handles this automatically
```
---
## The 12 Keys That Matter
## The Keys That Matter
```
, (pause 500ms) Show all keybindings (which-key)
,? Open cheat sheet inside Vim
Esc / jk Exit insert mode → Normal (memorize this)
Ctrl+s Save (works in normal and insert mode)
,? Open cheat sheet (all bindings in one place)
Esc / jk Exit insert mode → Normal
Ctrl+s Save
Ctrl+p Fuzzy find file
Ctrl+n Toggle file tree
,e File browser (netrw)
gd Go to definition
K Show documentation
[g / ]g Prev / next LSP diagnostic
,rn Rename symbol
,rG Search word under cursor (ripgrep)
,gs Git status
,mp Markdown live preview in browser
,w / ,x Save / Save+quit
```
@ -138,8 +112,8 @@ K Show documentation
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | List all references |
| `K` | Show docs for symbol under cursor |
| `gr` | List references |
| `K` | Docs for symbol under cursor |
| `Ctrl+o` | Jump back |
| `Ctrl+i` | Jump forward |
@ -152,23 +126,35 @@ K Show documentation
| `gc` | Toggle comment (visual mode too) |
| `cs"'` | Change surrounding `"` to `'` |
| `ds(` | Delete surrounding `(` |
| `s`+2ch | EasyMotion: jump anywhere |
| `s` + 2 chars | EasyMotion: jump anywhere |
### Manage Errors
| Key | Action |
|-----|--------|
| `]g` | Jump to next diagnostic |
| `]g` | Jump to next LSP diagnostic |
| `[g` | Jump to previous diagnostic |
| `K` | Read the error message |
| `,ca` | Apply code action / auto-fix |
### Markdown
| Key | Action |
|-----|--------|
| `,mp` | Open live preview in browser |
| `,mt` | Table of contents |
| `zr` / `zm` | Unfold / fold all headings |
Formatting in the buffer is live: `**bold**` renders as bold,
headings hide their `#` markers. Raw syntax reappears when
the cursor enters that line.
### Git Workflow
```
,gs git status (stage with 's', commit with 'cc')
,gd diff current file
,gb blame current file
,gb blame
,gc commit
,gp push
,gl pull
@ -176,82 +162,18 @@ K Show documentation
---
## Language Workflows
### Python
```bash
# tools installed by install.sh; or manually:
pip install black flake8 pylint isort
```
Auto-formats with `black` + `isort` on save. Lint errors show as `X`/`!` in the sign column.
### JavaScript / TypeScript
```bash
npm install -g prettier eslint typescript
```
Auto-formats with `prettier` on save.
### Go
```bash
# tools installed by install.sh; or manually:
go install golang.org/x/tools/gopls@latest
go install golang.org/x/tools/cmd/goimports@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
```
`gofmt` + `goimports` run on save automatically.
### Markdown
Install `marksman` for LSP support (completions, link checking):
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh (handles it automatically)
```
---
## Customize
Edit config live:
```vim
,ev " opens ~/.vimrc in Vim
,sv " reloads config without restarting
```
Per-project settings: create `.vimrc` in your project root.
```vim
" project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
Change color scheme in `~/.vimrc`:
```vim
colorscheme dracula " or: gruvbox, solarized, onedark
```
---
## Quick Reference Card
```
BASICS (learn these first)
BASICS
Esc / jk Exit insert mode → Normal
Ctrl+s Save (normal + insert mode)
:q! + Enter Emergency quit without saving
Ctrl+s Save
:q! + Enter Emergency quit
,? Open cheat sheet
FILES
Ctrl+n File tree toggle
Ctrl+p Fuzzy find file (git-aware)
,e File browser (netrw)
,b Search open buffers
,rg Search file contents (ripgrep)
,rG Ripgrep word under cursor
@ -260,13 +182,16 @@ FILES
,, Switch to last file
CODE
gd Go to definition
gd Definition | gy Type def | gi Impl | gr References
K Show documentation
[g / ]g Prev/next LSP diagnostic
[e / ]e Prev/next ALE error
[g / ]g Prev / next LSP diagnostic
[e / ]e Prev / next ALE error
,rn Rename symbol
,ca Code action / auto-fix
,f Format selection | ,F Format whole file
,ca Code action
,f Format buffer / selection
MARKDOWN
,mp Live preview | ,mt Table of contents
GIT
,gs Status | ,gd Diff | ,gb Blame
@ -275,19 +200,14 @@ GIT
WINDOWS / PANES
Ctrl+h/j/k/l Move between Vim windows or tmux panes
,h / ,l Prev / next buffer
,tv Open terminal (vertical)
,th Open terminal (horizontal)
Esc Exit terminal mode
,u Undo tree | ,tt Tag browser
,tv / ,th Terminal (vertical / horizontal)
Esc Esc Exit terminal mode
,u Undo tree
SEARCH & REPLACE
/text Search forward | ?text backward
// Search for visually selected text
SEARCH
/text Forward | ?text Backward | n next | N prev
// Search visually selected text
,* Replace word under cursor (file-wide)
CLIPBOARD
,y / ,Y Yank / yank line to system clipboard
,p / ,P Paste from system clipboard (after / before)
```
---

554
README.md
View file

@ -1,7 +1,7 @@
# chopsticks
> A batteries-included Vim configuration for full-stack engineering.
> Tiered LSP · 14 languages · TTY-aware · Zero icon fonts · One-command install.
> Flowing vim for any machine — SSH servers included.
> Solarized · vim-lsp (no Node.js) · Markdown-first · One-command install.
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](LICENSE)
[![Vim 8.0+](https://img.shields.io/badge/Vim-8.0%2B-brightgreen?style=flat-square)](https://www.vim.org/)
@ -9,18 +9,11 @@
[![Release](https://img.shields.io/github/v/release/m1ngsama/chopsticks?style=flat-square&label=release&color=orange)](https://github.com/m1ngsama/chopsticks/releases)
[![Last Commit](https://img.shields.io/github/last-commit/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/commits/main)
[![Stars](https://img.shields.io/github/stars/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/stargazers)
[![Issues](https://img.shields.io/github/issues/m1ngsama/chopsticks?style=flat-square)](https://github.com/m1ngsama/chopsticks/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/m1ngsama/chopsticks/pulls)
[![Plugins](https://img.shields.io/badge/plugins-30%2B-blueviolet?style=flat-square)](#plugins)
[![Languages](https://img.shields.io/badge/languages-14-informational?style=flat-square)](#language-support)
```bash
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 —
> a 2-minute intro to modes and the 4 commands that get you out of any jam.
---
## Contents
@ -28,14 +21,13 @@ curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | b
- [Design Principles](#design-principles)
- [Requirements](#requirements)
- [Installation](#installation)
- [LSP: Tiered Backend](#lsp-tiered-backend)
- [LSP](#lsp)
- [Key Mappings](#key-mappings)
- [Markdown](#markdown)
- [Features](#features)
- [Language Support](#language-support)
- [Plugins](#plugins)
- [Customization](#customization)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
---
@ -43,51 +35,44 @@ curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | b
| Principle | What it means |
|-----------|--------------|
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII everywhere |
| **Tiered LSP** | CoC (full) when Node.js is available; vim-lsp (pure VimScript) otherwise |
| **TTY-aware** | Automatically detects SSH/console environments and degrades gracefully |
| **Engineering-first** | Git workflow, persistent sessions, project-local config, large-file safety |
| **Batteries included** | `install.sh` handles vim-plug, plugins, system tools, and language servers |
| **Flowing writing** | Every plugin earns its place by reducing interruptions to thought |
| **No Node.js** | vim-lsp runs on pure VimScript — works on any machine, including SSH servers |
| **Solarized** | One palette, everywhere — vim statusline matches tmux bar exactly |
| **TTY-aware** | SSH and console environments degrade gracefully without breaking |
| **KISS** | No icon fonts, no Nerd Font glyphs — plain ASCII throughout |
---
## Requirements
| Tool | Minimum | Role |
|------|---------|------|
| 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 |
| Tool | Role |
|------|------|
| Vim 8.0+ | Required — `install.sh` installs it if missing |
| git | Required |
| curl | Required |
| ripgrep | Recommended — enables `,rg` project search |
| fzf | Recommended — enables `Ctrl+p` fuzzy finder |
| Node.js | Optional — enables npm formatters (prettier, eslint) |
`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.
`install.sh` detects your environment and installs missing dependencies.
---
## Installation
### One command (recommended)
### One command
```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:
Non-interactive / CI:
```bash
curl -fsSL https://raw.githubusercontent.com/m1ngsama/chopsticks/main/get.sh | bash -s -- --yes
```
### Traditional (git clone)
### Git clone
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
@ -96,250 +81,196 @@ cd ~/.vim && ./install.sh
### What the installer does
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`
1. Detects OS and package manager
2. Verifies or installs `curl`, `git`, `vim`
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`
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch Linux (pacman), Fedora (dnf).
### Manual
```bash
git clone https://github.com/m1ngsama/chopsticks.git ~/.vim
ln -sf ~/.vim/.vimrc ~/.vimrc
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
vim +PlugInstall +qall </dev/null
```
**Supported platforms:** macOS (Homebrew), Debian/Ubuntu (apt), Arch (pacman), Fedora (dnf).
---
## LSP: Tiered Backend
## LSP
Code intelligence is provided by one of two backends, chosen automatically at startup:
Code intelligence is provided by **vim-lsp** — a pure VimScript LSP client with no
Node.js dependency. It works on any machine, including servers accessed via SSH.
| Condition | Backend | Capabilities |
|-----------|---------|-------------|
| Vim 8.0.1453+ **and** Node.js 14.14+ | **CoC** | Full LSP, snippets, extension ecosystem |
| Vim 8.0+ (no Node.js) | **vim-lsp** | LSP via language server binaries, asyncomplete |
| Any Vim | **ALE** | Async linting + auto-fix (always active, both backends) |
Both CoC and vim-lsp expose the same key mappings so switching backends is transparent.
### With Node.js (CoC)
Install language server extensions from inside Vim — or let `install.sh` do it automatically:
Install a language server for the current file:
```vim
:CocInstall coc-pyright " Python
:CocInstall coc-tsserver " JavaScript / TypeScript
:CocInstall coc-go " Go
:CocInstall coc-rust-analyzer " Rust
:CocInstall coc-json coc-yaml " JSON, YAML
:CocInstall coc-html coc-css " HTML, CSS/SCSS
:CocInstall coc-sh " Shell
:CocInstall coc-sql " SQL
:LspInstallServer " auto-detects filetype and installs the correct server
:LspStatus " check server status
```
**Markdown LSP** uses `marksman` as an external binary (not a CoC extension):
Supported languages and their servers:
| Language | Server |
|----------|--------|
| Python | pylsp / pyright |
| JavaScript / TypeScript | typescript-language-server |
| Go | gopls |
| Rust | rust-analyzer |
| C / C++ | clangd |
| Shell | bash-language-server |
| HTML | vscode-html-language-server |
| CSS / SCSS | vscode-css-language-server |
| JSON | vscode-json-language-server |
| YAML | yaml-language-server |
| Markdown | marksman |
| SQL | sqls |
**Markdown LSP** requires `marksman` as a standalone binary:
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
# or: install.sh handles it automatically
```
### Without Node.js (vim-lsp)
Open a source file, then run:
```vim
:LspInstallServer " detects filetype and installs the correct server
:LspStatus " check server status
```
Supported: Python, Go, Rust, TypeScript/JavaScript, Shell, HTML, CSS/SCSS, JSON, YAML, Markdown, SQL.
---
## Key Mappings
**Leader key:** `,` (comma)
Press `,` and wait 500 ms to see an interactive guide to all bindings (vim-which-key).
Press `,?` to open the built-in cheat sheet at any time.
### Survival
| Key | Action |
|-----|--------|
| `jk` | Exit insert mode → Normal (ergonomic Escape) |
| `Esc` | Exit insert / visual mode (standard) |
| `Ctrl+s` | Save file (normal and insert mode) |
| `,w` | Save file |
| `,x` | Save and quit |
| `,q` | Quit |
| `,?` | Open cheat sheet |
> **`Ctrl+s` note:** some terminals freeze on `Ctrl+s` (XON/XOFF). Add `stty -ixon`
> to your `~/.bashrc` / `~/.zshrc` to disable this permanently.
Press `,?` at any time to open the built-in cheat sheet.
### Files and Buffers
| Key | Action |
|-----|--------|
| `Ctrl+p` | Fuzzy file search — git-aware (FZF) |
| `Ctrl+n` | Toggle file tree (NERDTree) |
| `,n` | Reveal current file in NERDTree |
| `,e` | Open netrw file browser |
| `,E` | Open netrw in vertical split |
| `,b` | Search open buffers (FZF) |
| `,rg` | Project-wide search (ripgrep + FZF) |
| `,rG` | Ripgrep for word under cursor (literal match) |
| `,rt` | Search tags (FZF) |
| `,gF` | Search git-tracked files (FZF) |
| `,l` | Next buffer |
| `,h` | Previous buffer |
| `,bd` | Close current buffer (preserves window layout) |
| `,rG` | Ripgrep for word under cursor |
| `,,` | Switch to last file |
| `,l` / `,h` | Next / previous buffer |
| `,bd` | Close current buffer (preserves window layout) |
### Windows, Tabs, and tmux
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate between Vim splits **and** tmux panes |
| `,=` | Increase window height |
| `,-` | Decrease window height |
| `,+` | Increase window width |
| `,_` | Decrease window width |
| `,tn` | New tab |
| `,tc` | Close tab |
| `,tl` | Toggle to last tab |
| `,tv` | Open terminal (vertical split) |
| `,th` | Open terminal (horizontal split) |
| `Esc Esc` | Exit terminal mode |
### Code Intelligence (CoC / vim-lsp)
### Code Intelligence (vim-lsp)
| Key | Action |
|-----|--------|
| `gd` | Go to definition |
| `gy` | Go to type definition |
| `gi` | Go to implementation |
| `gr` | Show all references |
| `gr` | Show references |
| `K` | Hover documentation |
| `[g` | Previous diagnostic |
| `]g` | Next diagnostic |
| `[g` / `]g` | Previous / next LSP diagnostic |
| `[e` / `]e` | Previous / next ALE error |
| `,rn` | Rename symbol |
| `,f` | Format selection |
| `,F` | Format whole file |
| `,ca` | Code action (cursor position) |
| `,f` | Format buffer / selection |
| `,ca` | Code action |
| `,o` | File outline (symbols) |
| `,ws` | Workspace symbols |
| `,cD` | Diagnostics list |
| `,qf` | Quick-fix current line (CoC) |
| `Tab` | Next completion item |
| `Shift+Tab` | Previous completion item |
| `Tab` / `Shift+Tab` | Navigate completion popup |
| `Enter` | Confirm completion |
Text objects (CoC): `if`/`af` (function inner/around), `ic`/`ac` (class inner/around).
### Linting (ALE — always active)
### Markdown
| Key | Action |
|-----|--------|
| `[e` | Previous error / warning |
| `]e` | Next error / warning |
| `,aD` | Show error detail |
| `,ad` | Full diagnostics list |
Signs in the gutter: `X` = error, `!` = warning.
| `,mp` | Open live preview in browser (previm) |
| `,mt` | Table of contents |
| `zr` / `zm` | Unfold / fold all headings |
### Git (vim-fugitive)
| Key | Action |
|-----|--------|
| `,gs` | Git status (stage with `s`, commit with `cc`) |
| `,gs` | Git status |
| `,gc` | Git commit |
| `,gp` | Git push |
| `,gl` | Git pull |
| `,gd` | Git diff |
| `,gb` | Git blame |
### Search and Replace
| Key | Action |
|-----|--------|
| `n` / `N` | Next / previous match (cursor centered) |
| `//` | Search for visually selected text |
| `,*` | Search and replace word under cursor (file-wide) |
| `,rG` | Ripgrep word under cursor across project |
| `,<CR>` | Clear search highlight |
### Clipboard
| Key | Action |
|-----|--------|
| `,y` | Yank to system clipboard |
| `,Y` | Yank line to system clipboard |
| `,p` | Paste from system clipboard (after cursor) |
| `,P` | Paste from system clipboard (before cursor) |
### Editing and Navigation
### Editing
| Key | Action |
|-----|--------|
| `s` + 2 chars | EasyMotion — jump anywhere on screen |
| `gc` | Toggle comment (visual mode too) |
| `Space` | Toggle code fold |
| `Y` | Yank to end of line (consistent with `D`, `C`) |
| `Ctrl+d/u` | Half-page scroll (cursor stays centered) |
| `>` / `<` | Indent / dedent (keeps visual selection) |
| `Alt+j/k` | Move current line down / up |
| `0` | Jump to first non-blank character |
| `[q` / `]q` | Previous / next quickfix entry (vim-unimpaired) |
| `,u` | Toggle undo tree (visual branch history) |
| `,tt` | Toggle tagbar (code structure) |
| `F2` | Toggle paste mode |
| `F3` | Toggle absolute line numbers |
| `F4` | Toggle relative line numbers |
| `Y` | Yank to end of line |
| `Ctrl+d` / `Ctrl+u` | Half-page scroll, cursor centred |
| `Alt+j` / `Alt+k` | Move line down / up |
| `,u` | Undo tree (visual branch history) |
### Config and Utilities
### Survival
| Key | Action |
|-----|--------|
| `,ev` | Edit `~/.vimrc` |
| `,sv` | Reload `~/.vimrc` |
| `,wa` | Save all open buffers |
| `,wd` | Change working directory to current file's location |
| `,cp` | Copy absolute file path to clipboard |
| `,cf` | Copy filename to clipboard |
| `,qo` / `,qc` | Open / close quickfix list |
| `jk` | Exit insert mode |
| `Esc` | Exit insert / visual mode |
| `Ctrl+s` | Save |
| `,w` | Save |
| `,x` | Save and quit |
| `,q` | Quit |
| `,?` | Open cheat sheet |
### Windows, Tabs, tmux
| Key | Action |
|-----|--------|
| `Ctrl+h/j/k/l` | Navigate Vim splits **and** tmux panes |
| `,tv` / `,th` | Open terminal (vertical / horizontal split) |
| `Esc Esc` | Exit terminal mode |
| `,tn` / `,tc` | New tab / close tab |
| `,tl` | Toggle to last tab |
---
## Markdown
chopsticks treats Markdown as a first-class language.
### In-buffer rendering (concealment)
`vim-markdown` hides syntax markers and renders formatting inline:
- `**bold**` displays as bold text
- `# Heading` hides the `#` characters
- Tables align automatically
The raw syntax reappears when the cursor enters that line.
### Live browser preview (previm)
```vim
,mp " open rendered preview in browser — updates on every save
```
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
```
---
## Features
### Startup Dashboard
### Statusline
Running `vim` (no arguments) opens a full-screen Startify dashboard showing recent
files, sessions, and bookmarks. Running `vim .` opens NERDTree on the left with
the dashboard on the right.
A native, hand-written statusline using the Solarized palette:
### Keybinding Guide
```
N ~/.vimrc [+] main [vim] 42:7 68%
```
Press `,` and pause for 500 ms. A popup (vim-which-key) lists all leader bindings
organized into groups. No need to memorize everything upfront.
### Built-in Cheat Sheet
Press `,?` to open an inline reference covering modes, survival commands, search,
code intelligence, git, and clipboard — without leaving Vim.
- Mode block changes colour by mode (Normal=yellow, Insert=blue, Visual=magenta, Replace=red)
- Git branch via vim-fugitive
- Background matches tmux status bar for a seamless bottom band
### Session Management
@ -348,29 +279,23 @@ code intelligence, git, and clipboard — without leaving Vim.
:Obsess! " stop tracking
```
Sessions are stored in `~/.vim/sessions/` and automatically restored by vim-prosession
the next time you open Vim in the same directory.
Sessions auto-restore when you open Vim in the same directory.
### Project-Local Config
Drop a `.vimrc` in any project root to override settings for that project:
Drop a `.vimrc` in any project root to override settings:
```vim
" project/.vimrc
" my-project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=100'
```
Loaded automatically via `set exrc`. Restricted to safe options via `set secure`.
### tmux Integration
`Ctrl+h/j/k/l` navigates seamlessly between Vim splits and tmux panes — no prefix
key, no mode switch.
`Ctrl+h/j/k/l` navigates seamlessly between Vim splits and tmux panes.
**Vim side:** handled by vim-tmux-navigator (installed automatically).
**tmux side:** add to `~/.tmux.conf` (or let `install.sh` append it):
Add to `~/.tmux.conf` (or let `install.sh` append it):
```tmux
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'"
@ -380,123 +305,79 @@ bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k' 'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l' 'select-pane -R'
```
Then reload: `tmux source-file ~/.tmux.conf`
### TTY / SSH Support
> **Note:** the `C-l` binding replaces the terminal's screen-clear shortcut inside
> tmux. To restore it, add `bind C-l send-keys 'C-l'` — then use `prefix + C-l`.
Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode:
- True colour and cursorline disabled
- FZF preview windows disabled
- IndentLine guides disabled
- Simplified statusline (no colour)
- Syntax column limit reduced to 120 characters
### Large File Handling
Files over 10 MB automatically disable syntax highlighting, undo history, and
linting to prevent Vim from stalling.
### TTY / Console Support
Detected automatically when `$TERM` is `linux` or `screen`. In TTY mode:
- True color and cursorline disabled
- Powerline separators replaced with plain ASCII
- FZF preview windows disabled
- IndentLine guides disabled
- Syntax column limit reduced to 120 characters
- Simpler built-in status line used instead of airline
---
## Language Support
| Language | Indent | Formatter | Linter | LSP |
|----------|--------|-----------|--------|-----|
| Python | 4 sp | black, isort | flake8, pylint | coc-pyright |
| JavaScript | 2 sp | prettier | eslint | coc-tsserver |
| TypeScript | 2 sp | prettier | eslint, tsserver | coc-tsserver |
| Go | tab | gofmt, goimports | staticcheck | coc-go |
| Rust | 4 sp | rustfmt | cargo | coc-rust-analyzer |
| Shell | 2 sp | — | shellcheck | coc-sh |
| YAML | 2 sp | prettier | yamllint | coc-yaml |
| HTML | 2 sp | prettier | — | coc-html |
| CSS / SCSS | 2 sp | prettier | stylelint | coc-css |
| Less | 2 sp | prettier | — | — |
| JSON | 2 sp | prettier | — | coc-json |
| Markdown | 2 sp | prettier | markdownlint | marksman |
| SQL | 4 sp | sqlfluff | sqlfluff | — |
| Dockerfile | 2 sp | — | hadolint | — |
`install.sh` installs all formatters and linters automatically.
ALE runs them asynchronously; format-on-save is active for all supported languages.
linting to prevent stalling.
---
## Plugins
### Navigation
- **NERDTree** — file tree explorer
- **fzf + fzf.vim** — fuzzy finder for files, buffers, tags, and ripgrep
- **fzf + fzf.vim** — fuzzy finder for files, buffers, tags, ripgrep
### Git
- **vim-fugitive** — full Git integration inside Vim
- **vim-fugitive** — full Git integration
- **vim-gitgutter** — diff signs in the sign column
### LSP and Completion
- **coc.nvim** — full LSP + completion via Node.js (recommended)
- **vim-lsp** — pure VimScript LSP client (Node.js-free fallback)
- **vim-lsp-settings** — auto-configures language servers for vim-lsp
- **asyncomplete.vim** — async completion engine (vim-lsp mode)
- **vim-lsp** — pure VimScript LSP client
- **vim-lsp-settings** — auto-configures language servers
- **asyncomplete.vim** — async completion engine
- **asyncomplete-lsp.vim** — LSP completion source
### Linting
- **ALE** — asynchronous lint engine, always active regardless of LSP backend
### Linting and Formatting
- **ALE** — async linting and format-on-save
### UI
- **vim-airline** — status line and tabline
- **vim-startify** — startup dashboard with session management
- **vim-which-key** — keybinding hint popup on leader pause
- **indentLine** — indent guide lines (non-TTY only)
- **undotree** — visual undo branch history
- **tagbar** — code structure sidebar via ctags
### Markdown
- **vim-markdown** — folding, concealment, table alignment
- **previm** — live browser preview
### Language Syntax
- **vim-javascript** — enhanced JS syntax
- **yats.vim** — TypeScript syntax
- **vim-go** — Go syntax and tooling
### Editing
- **vim-surround** — change surrounding quotes, brackets, and tags
- **vim-surround** — change/delete/add surroundings
- **vim-commentary**`gc` to toggle comments
- **auto-pairs** — auto-close brackets and quotes
- **vim-easymotion** — jump anywhere on screen with 2 keystrokes (`s`)
- **vim-unimpaired** — bracket shortcut pairs (`[q`/`]q`, etc.)
- **vim-repeat** — repeat plugin maps with `.`
- **vim-unimpaired** — bracket shortcut pairs
- **targets.vim** — additional text objects
- **vim-snippets** — snippet library (used with CoC)
- **vim-tmux-navigator** — seamless `Ctrl+h/j/k/l` across Vim and tmux
- **auto-pairs** — auto-close brackets and quotes
- **vim-easymotion** — `s` + 2 chars to jump anywhere
### Language Packs
- **vim-polyglot** — syntax for 100+ languages
- **vim-go** — Go syntax and tooling (LSP handled by coc-go)
### UI
- **vim-colors-solarized** — color scheme
- **undotree** — visual undo branch history
- **vim-startify** — startup dashboard and session list
- **indentLine** — indent guides (non-TTY)
### Session
- **vim-obsession** — continuous session saving
- **vim-prosession** — project-level session management
### Color Schemes
- **gruvbox** (default), **dracula**, **solarized**, **onedark**
### Session and Navigation
- **vim-obsession** — session tracking
- **vim-tmux-navigator** — seamless Vim/tmux pane navigation
---
## Customization
### Change the color scheme
In `~/.vimrc`, find and update the `colorscheme` line:
```vim
colorscheme dracula " options: gruvbox, solarized, onedark
```
True color is enabled automatically when `$COLORTERM=truecolor`. Falls back to
256-color, then 16-color in TTY.
### Per-project overrides
Create `.vimrc` in your project root. Anything placed here overrides the global
config for that directory:
Create `.vimrc` in your project root:
```vim
" my-project/.vimrc
" project/.vimrc
set shiftwidth=2
let g:ale_python_black_options = '--line-length=120'
```
@ -512,100 +393,41 @@ Edit `~/.vimrc` directly (`,ev` opens it from inside Vim). Reload with `,sv`.
**Plugins not loading**
```vim
:PlugInstall " install any missing plugins
:PlugInstall " install missing plugins
:PlugUpdate " update all plugins
```
**CoC not working**
```bash
node --version # must be 14.14+
```
Inside Vim: `:CocInfo` for diagnostics, `:CocInstall <extension>` to add a language server.
**vim-lsp server not starting**
**LSP server not starting**
```vim
:LspInstallServer " install the correct server for the current filetype
:LspInstallServer " install server for current filetype
:LspStatus " check server status
```
**Markdown LSP not starting**
**Markdown preview not opening**
`marksman` must be installed as a standalone binary (not a CoC extension):
```bash
brew install marksman # macOS
sudo pacman -S marksman # Arch
# or: ./install.sh handles it automatically
```
`previm` uses `open` (macOS) or `xdg-open` (Linux). Make sure a browser is
set as the default handler for HTML files.
**Colors look wrong**
```bash
export TERM=xterm-256color # add to ~/.bashrc or ~/.zshrc
export COLORTERM=truecolor # for true colour
```
For true color: `export COLORTERM=truecolor`.
**ALE linters not found**
```bash
which flake8 black prettier eslint # verify tools are on PATH
```
If tools were installed with `pip install --user` or `npm install -g`, make sure
the respective bin directories are on `$PATH`.
**`Ctrl+s` freezes the terminal**
Add `stty -ixon` to your `~/.bashrc`, `~/.zshrc`, or `~/.config/fish/config.fish`.
This disables XON/XOFF flow control permanently.
---
## Contributing
Bug reports and pull requests are welcome. Please follow these guidelines:
### Reporting a bug
1. Search [existing issues](https://github.com/m1ngsama/chopsticks/issues) before opening a new one.
2. Include your Vim version (`vim --version`), OS, and a minimal reproduction.
3. If the bug is plugin-specific, check whether it reproduces with a minimal config
(`vim -u NONE`) or only with chopsticks loaded.
### Proposing a change
1. Open an issue first to discuss the change, especially for non-trivial additions.
2. Keep the scope focused — one feature or fix per PR.
3. Follow existing conventions: augroups for autocmds, TTY guards for visual features,
conditional plugin loading where appropriate.
4. Update `CHANGELOG.md` with a summary of the change.
### Scope
Chopsticks is an opinionated configuration. Changes should align with the design
principles above — in particular, KISS (no icon fonts, minimal dependencies) and
TTY-compatibility. Neovim-only features and Lua configs are out of scope.
---
## Acknowledgements
Inspired by [amix/vimrc](https://github.com/amix/vimrc).
Built with [vim-plug](https://github.com/junegunn/vim-plug),
[coc.nvim](https://github.com/neoclide/coc.nvim),
[vim-lsp](https://github.com/prabirshrestha/vim-lsp),
and the broader Vim plugin community.
---
## Changelog
See [CHANGELOG.md](CHANGELOG.md).
## License
[MIT](LICENSE) © m1ng

View file

@ -16,6 +16,7 @@ YELLOW='\033[1;33m'
RED='\033[0;31m'
BOLD='\033[1m'
CYAN='\033[0;36m'
DIM='\033[2m'
NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
@ -29,13 +30,11 @@ die() { echo -e "${RED}[FATAL]${NC} $1" >&2
step() { echo -e "\n${BOLD}==> $1${NC}"; }
info() { echo " $1"; }
# Track results for summary
INSTALLED=()
SKIPPED=()
FAILED=()
# Ask yes/no; returns 0 for yes
# Reads from /dev/tty so interactive prompts work even under: curl | bash
# Ask yes/no; reads from /dev/tty so it works under: curl | bash
ask() {
[[ $AUTO_YES -eq 1 ]] && return 0
if [[ -t 0 ]]; then
@ -43,7 +42,6 @@ ask() {
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)
echo "$1 [y/N] N"
return 1
fi
@ -52,26 +50,19 @@ ask() {
# ── Error trap ────────────────────────────────────────────────────────────────
on_error() {
local line="${BASH_LINENO[0]}"
echo -e "\n${RED}[FATAL]${NC} Unexpected error at line $line." >&2
echo -e "\n${RED}[FATAL]${NC} Unexpected error at line ${BASH_LINENO[0]}." >&2
echo " To get a full debug log:" >&2
echo " ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2
echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2
}
trap on_error ERR
# Cleanup temp files on exit
trap 'rm -f /tmp/chopsticks-hadolint /tmp/chopsticks-marksman 2>/dev/null' EXIT
# ── Safe download helper ──────────────────────────────────────────────────────
# safe_download <url> <dest>
# Returns 1 if download fails or file is empty / HTML error page
safe_download() {
local url="$1" dest="$2"
curl -fsSL --connect-timeout 15 --retry 3 "$url" -o "$dest" 2>/dev/null || return 1
# Reject empty files
[[ -s "$dest" ]] || { rm -f "$dest"; return 1; }
# Reject HTML error pages (GitHub 404, rate limits, etc.)
if head -c 200 "$dest" 2>/dev/null | grep -qi "<!DOCTYPE\|<html"; then
rm -f "$dest"; return 1
fi
@ -79,7 +70,6 @@ safe_download() {
}
# ── Cross-platform package install helper ─────────────────────────────────────
# pkg_install <brew> <apt> <pacman> <dnf> (pass "" to skip that pkg manager)
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
@ -90,25 +80,122 @@ pkg_install() {
fi
}
# ── CPU architecture normalizer ───────────────────────────────────────────────
# Normalize uname -m to the naming convention used by GitHub releases
# ── CPU architecture helpers ──────────────────────────────────────────────────
arch_github() {
case "$(uname -m)" in
x86_64) echo "x86_64" ;;
aarch64|arm64) echo "arm64" ;;
armv7l) echo "armv7" ;;
*) echo "$(uname -m)" ;;
*) uname -m ;;
esac
}
arch_linux_x64() {
# Returns x64 or arm64 style (used by marksman)
case "$(uname -m)" in
x86_64) echo "x64" ;;
aarch64|arm64) echo "arm64" ;;
*) echo "$(uname -m)" ;;
*) uname -m ;;
esac
}
# ============================================================================
# Checkbox selection menu
# ============================================================================
#
# Usage:
# _menu_checkbox "Title" "label|description|default(0/1)" ...
#
# Result: global MENU_SEL array — MENU_SEL[i]=1 means item i was selected.
# Globals _MENU_LABELS / _MENU_DESCS remain populated after return.
#
# In --yes mode or non-TTY: uses defaults silently.
# Controls: ↑↓ navigate · Space toggle · a all · n none · Enter confirm
# ─────────────────────────────────────────────────────────────────────────────
_MENU_LABELS=()
_MENU_DESCS=()
_MENU_SELS=()
_MENU_N=0
_MENU_TITLE=""
_MENU_CUR=0
MENU_SEL=()
_menu_draw() {
local i mark
printf "\033[2K${BOLD}%s${NC}\n" "$_MENU_TITLE"
printf "\033[2K %b%b\n" "$DIM" "↑/↓ move Space toggle a all n none Enter confirm${NC}"
printf "\033[2K\n"
for ((i = 0; i < _MENU_N; i++)); do
if [[ ${_MENU_SELS[$i]} -eq 1 ]]; then
mark="${GREEN}${NC}"
else
mark=" "
fi
if [[ $i -eq $_MENU_CUR ]]; then
printf "\033[2K ${BOLD}▶ [%b] %s${NC}\n" "$mark" "${_MENU_LABELS[$i]}"
else
printf "\033[2K [%b] %s\n" "$mark" "${_MENU_LABELS[$i]}"
fi
printf "\033[2K ${CYAN}%s${NC}\n" "${_MENU_DESCS[$i]}"
done
}
_menu_checkbox() {
_MENU_TITLE="$1"; shift
_MENU_LABELS=(); _MENU_DESCS=(); _MENU_SELS=()
_MENU_N=0; _MENU_CUR=0
while [[ $# -gt 0 ]]; do
IFS='|' read -r \
"_MENU_LABELS[$_MENU_N]" \
"_MENU_DESCS[$_MENU_N]" \
"_MENU_SELS[$_MENU_N]" <<< "$1"
shift; : $(( _MENU_N++ ))
done
# Non-interactive or --yes: use defaults, no UI
if [[ $AUTO_YES -eq 1 ]] || ! { true </dev/tty; } 2>/dev/null; then
MENU_SEL=("${_MENU_SELS[@]}")
[[ $AUTO_YES -eq 1 ]] && info "(--yes mode: using all defaults)"
return
fi
# Lines printed per _menu_draw call: 3 header + 2 per item
local _lines=$(( 3 + 2 * _MENU_N ))
local _key _esc _i
tput civis 2>/dev/null # hide cursor
_menu_draw
while true; do
tput cuu "$_lines" 2>/dev/null # move back to top of menu
_menu_draw
IFS= read -r -s -n1 _key </dev/tty
if [[ $_key == $'\x1b' ]]; then
IFS= read -r -s -n2 _esc </dev/tty
case "$_esc" in
'[A') ((_MENU_CUR > 0)) && ((_MENU_CUR--)) ;;
'[B') ((_MENU_CUR < _MENU_N - 1)) && ((_MENU_CUR++)) ;;
esac
elif [[ $_key == ' ' ]]; then
_MENU_SELS[_MENU_CUR]=$(( 1 - _MENU_SELS[_MENU_CUR] ))
elif [[ $_key == 'a' || $_key == 'A' ]]; then
for ((_i = 0; _i < _MENU_N; _i++)); do _MENU_SELS[_i]=1; done
elif [[ $_key == 'n' || $_key == 'N' ]]; then
for ((_i = 0; _i < _MENU_N; _i++)); do _MENU_SELS[_i]=0; done
elif [[ -z $_key ]]; then # Enter
break
fi
done
tput cnorm 2>/dev/null # restore cursor
echo ""
MENU_SEL=("${_MENU_SELS[@]}")
}
# Helper: was menu item at index $1 selected?
_selected() { [[ ${MENU_SEL[${1:-999}]:-0} -eq 1 ]]; }
echo -e "${BOLD}chopsticks — Vim Configuration Installer${NC}"
echo "----------------------------------------"
@ -119,14 +206,10 @@ echo "----------------------------------------"
step "Detecting environment"
OS="unknown"
if [[ "$OSTYPE" == darwin* ]]; then
OS="macos"
elif [[ -f /etc/debian_version ]]; then
OS="debian"
elif [[ -f /etc/fedora-release ]]; then
OS="fedora"
elif [[ -f /etc/arch-release ]]; then
OS="arch"
if [[ "$OSTYPE" == darwin* ]]; then OS="macos"
elif [[ -f /etc/debian_version ]]; then OS="debian"
elif [[ -f /etc/fedora-release ]]; then OS="fedora"
elif [[ -f /etc/arch-release ]]; then OS="arch"
fi
ok "OS: $OS"
@ -135,44 +218,38 @@ 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
# ── sudo ─────────────────────────────────────────────────────────────────────
# sudo
HAS_SUDO=0
if [[ $OS == "macos" ]]; then
# brew handles its own privilege escalation; no sudo needed for system tools
HAS_SUDO=1
HAS_SUDO=1 # brew handles its own privilege escalation
elif sudo -n true 2>/dev/null; then
HAS_SUDO=1
ok "sudo: available (passwordless)"
HAS_SUDO=1; ok "sudo: available (passwordless)"
elif [[ $AUTO_YES -eq 1 ]]; then
warn "sudo requires a password but running non-interactively (--yes)"
warn "System package installations will be skipped"
else
# Prompt once for password now so later sudo calls don't interrupt flow
warn "Some steps require sudo. Authenticating now..."
if sudo true; then
HAS_SUDO=1
ok "sudo: authenticated"
HAS_SUDO=1; ok "sudo: authenticated"
else
warn "sudo not available — system package installations will be skipped"
fi
fi
# ── Network ──────────────────────────────────────────────────────────────────
# Network
if curl -fsSL --connect-timeout 5 https://github.com -o /dev/null 2>/dev/null; then
ok "Network: github.com reachable"
else
warn "Network: cannot reach github.com — plugin and binary downloads may fail"
warn "Check your internet connection or proxy settings before continuing"
fi
# ── Homebrew (macOS) ─────────────────────────────────────────────────────────
# Homebrew (macOS)
if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; then
warn "Homebrew not found — it is the recommended package manager for macOS"
if ask "Install Homebrew now? (strongly recommended — required for system tools)"; then
info "This may take a few minutes and will prompt for your password..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || \
die "Homebrew installation failed. Install manually: https://brew.sh"
# Source brew for Apple Silicon and Intel paths
for brew_path in /opt/homebrew/bin/brew /usr/local/bin/brew; do
[[ -x "$brew_path" ]] && eval "$("$brew_path" shellenv)" && break
done
@ -182,7 +259,7 @@ if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; then
fi
fi
# ── curl ─────────────────────────────────────────────────────────────────────
# curl
if ! command -v curl >/dev/null 2>&1; then
warn "curl not found — required to download plugins and tools"
if pkg_install curl curl curl curl 2>/dev/null; then
@ -196,7 +273,7 @@ if ! command -v curl >/dev/null 2>&1; then
fi
fi
# ── git ──────────────────────────────────────────────────────────────────────
# git
if ! command -v git >/dev/null 2>&1; then
warn "git not found — required for vim-plug to install plugins"
if pkg_install git git git git 2>/dev/null; then
@ -210,9 +287,8 @@ if ! command -v git >/dev/null 2>&1; then
fi
fi
# ── vim ──────────────────────────────────────────────────────────────────────
# vim
[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR — is this the chopsticks repo?"
if ! command -v vim >/dev/null 2>&1; then
warn "vim not found — attempting to install"
if pkg_install vim vim vim vim 2>/dev/null; then
@ -225,70 +301,25 @@ if ! command -v vim >/dev/null 2>&1; then
macOS: brew install vim"
fi
fi
VIM_VERSION=$(vim --version | head -n1)
ok "Found: $VIM_VERSION"
ok "Found: $(vim --version | head -n1)"
vim --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \
warn "Vim 8.0+ recommended for full async/LSP support — some features may not work"
# ── Node.js ──────────────────────────────────────────────────────────────────
# Node.js (optional — vim-lsp needs no Node.js; only npm formatters do)
HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1
if [[ $HAS_NODE -eq 0 ]]; then
warn "Node.js not found — CoC LSP and npm-based formatters will be unavailable"
info "Without Node.js, the config falls back to vim-lsp (pure VimScript)."
info ""
info "Install options:"
info " nvm (recommended): curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/install.sh | bash"
info " macOS: brew install node"
info " Ubuntu/Debian: curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -"
info " sudo apt-get install -y nodejs"
info " Arch: sudo pacman -S nodejs npm"
info ""
if ask "Install Node.js via nvm now? (recommended — manages multiple Node versions)"; then
info "Fetching latest nvm release..."
NVM_VER=$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || NVM_VER="v0.40.1"
[[ -z "$NVM_VER" ]] && NVM_VER="v0.40.1"
info "Installing nvm $NVM_VER + Node.js LTS..."
if curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VER}/install.sh" | bash >/dev/null 2>&1; then
export NVM_DIR="$HOME/.nvm"
# shellcheck disable=SC1091
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
if command -v nvm >/dev/null 2>&1; then
nvm install --lts >/dev/null 2>&1 && nvm use --lts >/dev/null 2>&1 || true
command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) installed via nvm"
fi
fi
if [[ $HAS_NODE -eq 0 ]]; then
warn "nvm install failed — CoC and npm tools will be skipped"
warn "After manually installing Node.js, re-run: ./install.sh"
fi
else
skip "Node.js — config will use vim-lsp fallback (no Node.js required)"
fi
else
if [[ $HAS_NODE -eq 1 ]]; then
ok "Node.js $(node --version) found"
else
warn "Node.js not found — npm formatters (prettier, eslint) will be unavailable"
warn "LSP still works without Node.js. To add formatters later: brew install node"
fi
# ── Python3 ──────────────────────────────────────────────────────────────────
# Python3 / pip3
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
if [[ $HAS_PYTHON -eq 0 ]]; then
warn "python3 not found — Python formatters/linters will be unavailable"
if ask "Install Python 3?"; then
if pkg_install python3 python3 python3 python3 2>/dev/null; then
command -v python3 >/dev/null 2>&1 && HAS_PYTHON=1 && ok "Python3 installed"
else
warn "Python3 install failed — Python tools will be skipped"
fi
else
skip "Python3"
fi
fi
# Bootstrap pip3 when python3 exists but pip3 is absent (common on Ubuntu minimal)
if [[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]]; then
warn "python3 found but pip3 missing — attempting bootstrap"
@ -299,11 +330,10 @@ if [[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]]; then
warn "pip3 bootstrap failed — Python tools will be skipped"
fi
fi
[[ $HAS_PIP -eq 1 ]] && ok "Python/pip3 found"
[[ $HAS_PYTHON -eq 1 && $HAS_PIP -eq 0 ]] && warn "pip3 not available — Python tools will be skipped"
# ── Go ───────────────────────────────────────────────────────────────────────
# Go
HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1
[[ $HAS_GO -eq 1 ]] && ok "Go $(go version | awk '{print $3}') found"
[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped (see https://go.dev/dl/)"
@ -316,22 +346,29 @@ step "Setting up symlinks"
if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then
TS=$(date +%Y%m%d_%H%M%S)
warn "Backing up existing ~/.vimrc → ~/.vimrc.backup.$TS"
warn "Backing up existing ~/.vimrc → $HOME/.vimrc.backup.$TS"
mv "$HOME/.vimrc" "$HOME/.vimrc.backup.$TS"
fi
ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc"
# Verify symlink
[[ -L "$HOME/.vimrc" ]] && ok "~/.vimrc → $SCRIPT_DIR/.vimrc" || die "Failed to create ~/.vimrc symlink"
if [[ -L "$HOME/.vimrc" ]]; then
ok "$HOME/.vimrc → $SCRIPT_DIR/.vimrc"
else
die "Failed to create ~/.vimrc symlink"
fi
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 → ~/.vim/coc-settings.json.backup.$TS"
warn "Backing up existing coc-settings.json → $COC_CFG.backup.$TS"
mv "$COC_CFG" "$COC_CFG.backup.$TS"
fi
ln -sf "$SCRIPT_DIR/coc-settings.json" "$COC_CFG"
[[ -L "$COC_CFG" ]] && ok "~/.vim/coc-settings.json → $SCRIPT_DIR/coc-settings.json" || warn "coc-settings.json symlink failed (non-fatal)"
if [[ -L "$COC_CFG" ]]; then
ok "$HOME/.vim/coc-settings.json → $SCRIPT_DIR/coc-settings.json"
else
warn "coc-settings.json symlink failed (non-fatal)"
fi
# ============================================================================
# 3. vim-plug + Plugins
@ -342,13 +379,16 @@ step "Installing vim-plug"
VIM_PLUG="$HOME/.vim/autoload/plug.vim"
if [ ! -f "$VIM_PLUG" ]; then
mkdir -p "$HOME/.vim/autoload"
if safe_download "https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" "$VIM_PLUG"; then
if safe_download \
"https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim" \
"$VIM_PLUG"; then
ok "vim-plug downloaded"
else
# Fallback: git clone
warn "curl download failed — trying git clone fallback"
if git clone --depth=1 https://github.com/junegunn/vim-plug.git /tmp/vim-plug-src 2>/dev/null; then
cp /tmp/vim-plug-src/plug.vim "$VIM_PLUG" && rm -rf /tmp/vim-plug-src
if git clone --depth=1 https://github.com/junegunn/vim-plug.git \
/tmp/vim-plug-src 2>/dev/null; then
cp /tmp/vim-plug-src/plug.vim "$VIM_PLUG"
rm -rf /tmp/vim-plug-src
ok "vim-plug installed (via git)"
else
die "vim-plug installation failed. Check your network connection and try again."
@ -360,156 +400,250 @@ else
fi
step "Installing Vim plugins"
# 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
# Interactive terminal: vim uses alternate screen; user sees progress
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
# No TTY (SSH batch, CI): do NOT redirect stdin (causes "Error reading input" exit)
# or stdout (breaks async job callbacks — partial install).
# Redirect only stderr; escape sequences appear on stdout but installation succeeds.
vim --not-a-term "$@" 2>/dev/null
fi
}
_vim_run +PlugInstall +qall || true # post-install hooks (e.g. fzf) may exit non-zero; harmless
ok "Plugins installed"
_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
_plug_count=$(ls -1 "$HOME/.vim/plugged" 2>/dev/null | wc -l | tr -d ' ')
if [[ $_plug_count -eq 0 ]]; then
die "Plugin installation failed — ~/.vim/plugged is empty. Check network and retry."
fi
ok "Plugins installed ($_plug_count)"
# ============================================================================
# 4. System Tools
# 4. Module Selection
# ============================================================================
step "Select optional components"
_ITEMS=()
_idx=0
# Index map (-1 = not in menu / unavailable)
_I_RIPGREP=-1; _I_FZF=-1; _I_CTAGS=-1; _I_SHELLCHECK=-1
_I_HADOLINT=-1; _I_MARKSMAN=-1
_I_NPM=-1; _I_PYTHON=-1; _I_GO=-1; _I_TMUX=-1
# Is any package manager available?
HAS_PKG_MGR=0
if [[ $HAS_BREW -eq 1 ]] || \
{ [[ $HAS_APT -eq 1 || $HAS_PACMAN -eq 1 || $HAS_DNF -eq 1 ]] && [[ $HAS_SUDO -eq 1 ]]; }; then
HAS_PKG_MGR=1
fi
# ── System tools ─────────────────────────────────────────────────────────────
if [[ $HAS_PKG_MGR -eq 1 ]]; then
_I_RIPGREP=$_idx
_ITEMS+=("ripgrep|,rg / ,rG project-wide search · powers Ctrl+p preview|1")
: $(( _idx++ ))
_I_FZF=$_idx
_ITEMS+=("fzf|Ctrl+p fuzzy file search · ,b buffers · ,rt tag search|1")
: $(( _idx++ ))
_I_CTAGS=$_idx
_ITEMS+=("universal-ctags|code symbol index (backing engine for ,rt tag jumps)|1")
: $(( _idx++ ))
_I_SHELLCHECK=$_idx
_ITEMS+=("shellcheck|shell script static analysis (ALE integration, on-save)|1")
: $(( _idx++ ))
_I_HADOLINT=$_idx
_ITEMS+=("hadolint|Dockerfile linting (ALE integration, on-save)|1")
: $(( _idx++ ))
_I_MARKSMAN=$_idx
_ITEMS+=("marksman|Markdown LSP — completion · go-to-definition · live diagnostics|1")
: $(( _idx++ ))
else
warn "No package manager available — system tools skipped"
fi
# ── npm tools ────────────────────────────────────────────────────────────────
if [[ $HAS_NODE -eq 1 ]]; then
_I_NPM=$_idx
_ITEMS+=("npm formatter suite|prettier / eslint / markdownlint / stylelint / tsc — ALE fix-on-save|1")
: $(( _idx++ ))
fi
# ── Python tools ─────────────────────────────────────────────────────────────
if [[ $HAS_PIP -eq 1 ]]; then
_I_PYTHON=$_idx
_ITEMS+=("Python tool suite|black / isort / flake8 / pylint / yamllint / sqlfluff — ALE fix-on-save|1")
: $(( _idx++ ))
fi
# ── Go tools ─────────────────────────────────────────────────────────────────
if [[ $HAS_GO -eq 1 ]]; then
_I_GO=$_idx
_ITEMS+=("Go tool suite|gopls (LSP) / goimports / staticcheck — completion · format · analysis|1")
: $(( _idx++ ))
fi
# ── tmux ─────────────────────────────────────────────────────────────────────
if command -v tmux >/dev/null 2>&1; then
if ! grep -q 'vim-tmux-navigator' "$HOME/.tmux.conf" 2>/dev/null; then
_I_TMUX=$_idx
_ITEMS+=("tmux integration|seamless Ctrl+h/j/k/l navigation between vim and tmux panes|1")
: $(( _idx++ ))
else
ok "tmux integration (vim-tmux-navigator already configured)"
fi
fi
if [[ ${#_ITEMS[@]} -gt 0 ]]; then
_menu_checkbox "Select components to install:" "${_ITEMS[@]}"
echo -e "${BOLD}Install plan:${NC}"
for ((_i = 0; _i < _MENU_N; _i++)); do
if [[ ${MENU_SEL[$_i]:-0} -eq 1 ]]; then
echo -e " ${GREEN}${NC} ${_MENU_LABELS[$_i]}"
else
echo -e " ${DIM}${NC} ${_MENU_LABELS[$_i]}"
fi
done
echo ""
else
warn "No optional components available for this environment"
MENU_SEL=()
fi
# ============================================================================
# 5. System Tools
# ============================================================================
step "System tools"
if [[ $OS == "macos" && $HAS_BREW -eq 0 ]]; then
skip "system tools (Homebrew not available — install brew first, then re-run)"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
elif ask "Install system tools (ripgrep, fzf, ctags, shellcheck, hadolint, marksman)?"; then
if [[ $HAS_PKG_MGR -eq 0 ]]; then
skip "system tools (no package manager available)"
SKIPPED+=("ripgrep" "fzf" "universal-ctags" "shellcheck" "hadolint" "marksman")
else
install_sys() {
local name="$1" check="$2"; shift 2
if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed)"
return
# _do_sys <name> <cmd_check> <idx> <brew_pkg> <apt_pkg> <pac_pkg> [dnf_pkg]
_do_sys() {
local name="$1" check="$2" idx="$3"
local brew_p="${4:-}" apt_p="${5:-}" pac_p="${6:-}" dnf_p="${7:-}"
if [[ $idx -lt 0 ]] || ! _selected "$idx"; then
skip "$name"; SKIPPED+=("$name"); return
fi
local installed=0
for cmd in "$@"; do
if eval "$cmd" >/dev/null 2>&1; then installed=1; break; fi
done
if [[ $installed -eq 1 ]]; then
if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed)"; return
fi
if pkg_install "$brew_p" "$apt_p" "$pac_p" "$dnf_p"; then
ok "$name"; INSTALLED+=("$name")
else
fail "$name — could not install automatically (install manually)"
FAILED+=("$name")
fi
}
}
if [[ $OS == "macos" ]]; then
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 "hadolint" hadolint "brew install hadolint"
install_sys "marksman" marksman "brew install marksman"
elif [[ $HAS_APT -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping apt system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
# _do_binary_apt: for tools with no apt/dnf package — download binary from GitHub
_do_binary_apt() {
local name="$1" check="$2" idx="$3" url="$4" tmp="$5"
if [[ $idx -lt 0 ]] || ! _selected "$idx"; then
skip "$name"; SKIPPED+=("$name"); return
fi
if command -v "$check" >/dev/null 2>&1; then
ok "$name (already installed)"; return
fi
if safe_download "$url" "$tmp"; then
chmod +x "$tmp" && sudo mv "$tmp" /usr/local/bin/"$check"
ok "$name"; INSTALLED+=("$name")
else
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"
fail "$name — binary download failed (install manually)"
FAILED+=("$name")
fi
}
# hadolint: no apt package — download binary from GitHub releases
if command -v hadolint >/dev/null 2>&1; then
ok "hadolint (already installed)"
else
if [[ $OS == "macos" ]]; then
_do_sys "ripgrep" rg "$_I_RIPGREP" ripgrep "" "" ""
_do_sys "fzf" fzf "$_I_FZF" fzf "" "" ""
_do_sys "universal-ctags" ctags "$_I_CTAGS" universal-ctags "" "" ""
_do_sys "shellcheck" shellcheck "$_I_SHELLCHECK" shellcheck "" "" ""
_do_sys "hadolint" hadolint "$_I_HADOLINT" hadolint "" "" ""
_do_sys "marksman" marksman "$_I_MARKSMAN" marksman "" "" ""
elif [[ $HAS_APT -eq 1 ]]; then
[[ $HAS_SUDO -eq 1 ]] && sudo apt-get update -qq
_do_sys "ripgrep" rg "$_I_RIPGREP" "" ripgrep "" ""
_do_sys "fzf" fzf "$_I_FZF" "" fzf "" ""
_do_sys "universal-ctags" ctags "$_I_CTAGS" "" universal-ctags "" ""
_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=""
if [[ -n "$HVER" ]] && safe_download \
_do_binary_apt "hadolint" hadolint "$_I_HADOLINT" \
"https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \
/tmp/chopsticks-hadolint; then
chmod +x /tmp/chopsticks-hadolint && sudo mv /tmp/chopsticks-hadolint /usr/local/bin/hadolint
ok "hadolint"; INSTALLED+=("hadolint")
else
fail "hadolint — download failed (install manually: https://github.com/hadolint/hadolint/releases)"
FAILED+=("hadolint")
fi
fi
/tmp/chopsticks-hadolint
# marksman: no apt package — download binary from GitHub releases
if command -v marksman >/dev/null 2>&1; then
ok "marksman (already installed)"
else
# 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=""
if [[ -n "$MVER" ]] && safe_download \
_do_binary_apt "marksman" marksman "$_I_MARKSMAN" \
"https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \
/tmp/chopsticks-marksman; then
chmod +x /tmp/chopsticks-marksman && sudo mv /tmp/chopsticks-marksman /usr/local/bin/marksman
ok "marksman"; INSTALLED+=("marksman")
else
fail "marksman — download failed (install manually: https://github.com/artempyanykh/marksman/releases)"
FAILED+=("marksman")
fi
fi
fi
/tmp/chopsticks-marksman
elif [[ $HAS_PACMAN -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping pacman system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
else
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"
fi
elif [[ $HAS_PACMAN -eq 1 ]]; then
_do_sys "ripgrep" rg "$_I_RIPGREP" "" "" ripgrep ""
_do_sys "fzf" fzf "$_I_FZF" "" "" fzf ""
_do_sys "universal-ctags" ctags "$_I_CTAGS" "" "" ctags ""
_do_sys "shellcheck" shellcheck "$_I_SHELLCHECK" "" "" shellcheck ""
_do_sys "hadolint" hadolint "$_I_HADOLINT" "" "" hadolint ""
_do_sys "marksman" marksman "$_I_MARKSMAN" "" "" marksman ""
elif [[ $HAS_DNF -eq 1 ]]; then
if [[ $HAS_SUDO -eq 0 ]]; then
warn "No sudo — skipping dnf system tools"
SKIPPED+=("ripgrep" "fzf" "shellcheck" "ctags" "hadolint" "marksman")
else
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"
skip "universal-ctags — install manually: sudo dnf install ctags"
SKIPPED+=("ctags")
skip "hadolint — install manually: https://github.com/hadolint/hadolint/releases"
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"
SKIPPED+=("universal-ctags")
fi
if [[ $_I_HADOLINT -ge 0 ]] && _selected "$_I_HADOLINT"; then
skip "hadolint — Fedora: install manually: https://github.com/hadolint/hadolint/releases"
SKIPPED+=("hadolint")
skip "marksman — install manually: https://github.com/artempyanykh/marksman/releases"
fi
if [[ $_I_MARKSMAN -ge 0 ]] && _selected "$_I_MARKSMAN"; then
skip "marksman — Fedora: install manually: https://github.com/artempyanykh/marksman/releases"
SKIPPED+=("marksman")
fi
else
warn "Unknown distro — skipping system tools (install manually)"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
fi
else
skip "system tools"
SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman")
warn "Unknown distro — skipping system tools (install manually)"
SKIPPED+=("ripgrep" "fzf" "universal-ctags" "shellcheck" "hadolint" "marksman")
fi
fi # end HAS_PKG_MGR
# ============================================================================
# 5. npm tools
# 6. npm Tools
# ============================================================================
step "npm tools (formatters + linters)"
if [[ $HAS_NODE -eq 1 ]]; then
if ask "Install npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript)?"; then
if [[ $HAS_NODE -eq 0 ]]; then
skip "npm tools (Node.js not installed)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
elif [[ $_I_NPM -lt 0 ]] || ! _selected "$_I_NPM"; then
skip "npm formatter suite (skipped by user)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
else
npm_install() {
local pkg="$1"; local check="${2:-$1}"
local pkg="$1" check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"; return
fi
@ -525,25 +659,23 @@ if [[ $HAS_NODE -eq 1 ]]; then
npm_install stylelint-config-standard
npm_install eslint
npm_install typescript tsc
else
skip "npm tools"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
fi
else
skip "npm tools (Node.js not installed)"
SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript")
fi
# ============================================================================
# 6. Python tools
# 7. Python Tools
# ============================================================================
step "Python tools (formatters + linters)"
if [[ $HAS_PIP -eq 1 ]]; then
if ask "Install Python tools (black, isort, flake8, pylint, yamllint, sqlfluff)?"; then
if [[ $HAS_PIP -eq 0 ]]; then
skip "Python tools (pip3 not installed)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
elif [[ $_I_PYTHON -lt 0 ]] || ! _selected "$_I_PYTHON"; then
skip "Python tool suite (skipped by user)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
else
pip_install() {
local pkg="$1"; local check="${2:-$1}"
local pkg="$1" check="${2:-$1}"
if command -v "$check" >/dev/null 2>&1; then
ok "$pkg (already installed)"; return
fi
@ -560,23 +692,21 @@ if [[ $HAS_PIP -eq 1 ]]; then
pip_install pylint
pip_install yamllint
pip_install sqlfluff
else
skip "Python tools"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
fi
else
skip "Python tools (pip3 not installed)"
SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff")
fi
# ============================================================================
# 7. Go tools
# 8. Go Tools
# ============================================================================
step "Go tools"
if [[ $HAS_GO -eq 1 ]]; then
if ask "Install Go tools (gopls, goimports, staticcheck)?"; then
if [[ $HAS_GO -eq 0 ]]; then
skip "Go tools (go not installed — see https://go.dev/dl/)"
SKIPPED+=("gopls" "goimports" "staticcheck")
elif [[ $_I_GO -lt 0 ]] || ! _selected "$_I_GO"; then
skip "Go tool suite (skipped by user)"
SKIPPED+=("gopls" "goimports" "staticcheck")
else
GOBIN="$(go env GOPATH)/bin"
export PATH="$PATH:$GOBIN"
@ -597,26 +727,24 @@ if [[ $HAS_GO -eq 1 ]]; then
echo "$PATH" | grep -q "$GOBIN" || \
warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\""
else
skip "Go tools"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
else
skip "Go tools (go not installed — see https://go.dev/dl/)"
SKIPPED+=("gopls" "goimports" "staticcheck")
fi
# ============================================================================
# 8. tmux: vim-tmux-navigator integration
# 9. tmux: vim-tmux-navigator integration
# ============================================================================
step "tmux: vim-tmux-navigator integration"
if command -v tmux >/dev/null 2>&1; then
if ! command -v tmux >/dev/null 2>&1; then
skip "tmux not found — skipping navigator config"
SKIPPED+=("tmux-navigator-config")
elif [[ $_I_TMUX -lt 0 ]]; then
: # already configured — noted earlier
elif ! _selected "$_I_TMUX"; then
skip "tmux navigator config (skipped by user)"
SKIPPED+=("tmux-navigator-config")
else
TMUX_CONF="$HOME/.tmux.conf"
if grep -q 'vim-tmux-navigator' "$TMUX_CONF" 2>/dev/null; then
ok "vim-tmux-navigator bindings already present in ~/.tmux.conf"
elif ask "Append vim-tmux-navigator bindings to ~/.tmux.conf (enables seamless Ctrl+h/j/k/l across vim and tmux)?"; then
cat >> "$TMUX_CONF" << 'TMUXEOF'
# vim-tmux-navigator: seamless Ctrl+h/j/k/l navigation between vim and tmux
@ -631,34 +759,21 @@ TMUXEOF
warn "Note: C-l now navigates panes instead of clearing the screen."
warn " To restore clear: add 'bind C-l send-keys C-l' to ~/.tmux.conf"
INSTALLED+=("tmux-navigator-config")
else
skip "tmux navigator config"
SKIPPED+=("tmux-navigator-config")
fi
else
skip "tmux not found — skipping navigator config"
SKIPPED+=("tmux-navigator-config")
fi
# ============================================================================
# 9. CoC language server extensions
# 10. LSP language servers
# ============================================================================
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 — this may take 1-3 minutes)"
# Note: coc-marksman doesn't exist on npm — markdown LSP is handled via coc-settings.json
_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"
info "Install later with :CocInstall <name> inside Vim"
fi
else
warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim for each language)"
fi
step "LSP language servers"
info "vim-lsp installs language servers on demand — no action needed here."
info ""
info "To install a server: open a source file in Vim and run:"
info " :LspInstallServer"
info ""
info "Supported: Python, JS/TS, Go, Rust, C/C++, Shell, HTML, CSS, JSON, YAML, Markdown, SQL"
info ""
info "For Markdown LSP (marksman), the installer already handled it above."
# ============================================================================
# Summary
@ -689,15 +804,15 @@ echo -e "${BOLD}---------------------------------------${NC}"
echo -e "${BOLD} You're ready. Open Vim with:${NC}"
echo -e "${BOLD}---------------------------------------${NC}"
echo -e " ${CYAN}vim${NC} Launch startup dashboard"
echo -e " ${CYAN}vim .${NC} Open file tree + dashboard"
echo -e " ${CYAN}vim .${NC} Open dashboard in current directory"
echo -e " ${CYAN}vim myfile${NC} Edit a specific file"
echo ""
echo -e "${BOLD} Survival Guide (first-time Vim users)${NC}"
echo -e "${BOLD} First steps inside Vim${NC}"
echo -e " ${CYAN}Esc${NC} or ${CYAN}jk${NC} Exit insert mode → back to Normal"
echo -e " ${CYAN}:q!${NC} + Enter Emergency quit without saving"
echo -e " ${CYAN},x${NC} Save and quit"
echo -e " ${CYAN},?${NC} Open cheat sheet inside Vim"
echo -e " ${CYAN},${NC} + pause Interactive keybinding guide"
echo -e " ${CYAN},?${NC} Open cheat sheet"
echo -e " ${CYAN}:LspInstallServer${NC} Install LSP for current filetype"
echo ""
echo -e "${YELLOW}[!]${NC} Ctrl+s is mapped to save in Vim."
echo " If it freezes your terminal, add this to ~/.bashrc or ~/.zshrc:"