diff --git a/install.sh b/install.sh index 2316279..f025f5a 100755 --- a/install.sh +++ b/install.sh @@ -1,54 +1,109 @@ #!/usr/bin/env bash # install.sh - chopsticks vim configuration installer -# Usage: cd ~/.vim && ./install.sh +# Usage: cd /path/to/chopsticks && ./install.sh [--yes] +# +# --yes non-interactive: install all optional components automatically set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AUTO_YES=0 +[[ "${1:-}" == "--yes" ]] && AUTO_YES=1 + GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BOLD='\033[1m' +CYAN='\033[0;36m' NC='\033[0m' -ok() { echo -e "${GREEN}[OK]${NC} $1"; } -warn() { echo -e "${YELLOW}[!]${NC} $1"; } -die() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; } -step() { echo -e "\n${BOLD}==> $1${NC}"; } +ok() { echo -e "${GREEN}[OK]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +skip() { echo -e "${CYAN}[--]${NC} $1"; } +fail() { echo -e "${RED}[ERR]${NC} $1"; } +die() { echo -e "${RED}[ERR]${NC} $1" >&2; exit 1; } +step() { echo -e "\n${BOLD}==> $1${NC}"; } -echo -e "${BOLD}chopsticks - Vim Configuration Installer${NC}" +# Track results for summary +INSTALLED=() +SKIPPED=() +FAILED=() + +# Ask yes/no; returns 0 for yes +ask() { + [[ $AUTO_YES -eq 1 ]] && return 0 + read -r -p "$1 [y/N] " reply + [[ "$reply" =~ ^[Yy]$ ]] +} + +# Try to install a single binary tool via a given command +# Usage: try_install +try_install() { + local name="$1"; local check="$2"; shift 2 + if command -v "$check" >/dev/null 2>&1; then + ok "$name (already installed: $(command -v "$check"))" + return 0 + fi + if "$@" >/dev/null 2>&1; then + ok "$name" + INSTALLED+=("$name") + else + fail "$name — install failed (run manually: $*)" + FAILED+=("$name") + fi +} + +echo -e "${BOLD}chopsticks — Vim Configuration Installer${NC}" echo "----------------------------------------" -# --- Preflight checks --- +# ============================================================================ +# Preflight +# ============================================================================ step "Checking environment" -[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR. Run from the cloned repo: cd ~/.vim && ./install.sh" +[ -f "$SCRIPT_DIR/.vimrc" ] || die ".vimrc not found in $SCRIPT_DIR" -command -v vim >/dev/null 2>&1 || die "vim not found. Install it first: - Ubuntu/Debian: sudo apt install vim - Fedora: sudo dnf install vim - macOS: brew install vim" +command -v vim >/dev/null 2>&1 || die "vim not found. + Ubuntu/Debian: sudo apt install vim + Fedora: sudo dnf install vim + macOS: brew install vim" VIM_VERSION=$(vim --version | head -n1) ok "Found $VIM_VERSION" -# Check Vim version >= 8.0 vim --version | grep -q 'Vi IMproved 8\|Vi IMproved 9' || \ - warn "Vim 8.0+ recommended for full LSP support. Some features may be missing." + warn "Vim 8.0+ recommended for full LSP support." -# Detect Node.js for CoC -HAS_NODE=0 -if command -v node >/dev/null 2>&1; then - NODE_VER=$(node --version) - ok "Node.js $NODE_VER detected - CoC LSP will be available" - HAS_NODE=1 -else - warn "Node.js not found - will use vim-lsp (no Node.js required)" - echo " Install Node.js later to enable CoC: https://nodejs.org/en/download" +# Detect OS +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" fi +ok "OS: $OS" -# --- Symlink --- +# Detect package managers +HAS_BREW=0; command -v brew >/dev/null 2>&1 && HAS_BREW=1 +HAS_APT=0; command -v apt >/dev/null 2>&1 && HAS_APT=1 +HAS_DNF=0; command -v dnf >/dev/null 2>&1 && HAS_DNF=1 +HAS_PACMAN=0; command -v pacman >/dev/null 2>&1 && HAS_PACMAN=1 +HAS_NODE=0; command -v node >/dev/null 2>&1 && HAS_NODE=1 && ok "Node.js $(node --version) detected" +HAS_PIP=0; command -v pip3 >/dev/null 2>&1 && HAS_PIP=1 && ok "Python/pip3 detected" +HAS_GO=0; command -v go >/dev/null 2>&1 && HAS_GO=1 && ok "Go $(go version | awk '{print $3}') detected" + +[[ $HAS_NODE -eq 0 ]] && warn "Node.js not found — JS/TS/Markdown npm tools will be skipped" +[[ $HAS_PIP -eq 0 ]] && warn "pip3 not found — Python tools will be skipped" +[[ $HAS_GO -eq 0 ]] && warn "Go not found — Go tools will be skipped" + +# ============================================================================ +# Symlink +# ============================================================================ step "Setting up ~/.vimrc symlink" @@ -59,14 +114,11 @@ if [ -f "$HOME/.vimrc" ] && [ ! -L "$HOME/.vimrc" ]; then fi ln -sf "$SCRIPT_DIR/.vimrc" "$HOME/.vimrc" +ok "~/.vimrc -> $SCRIPT_DIR/.vimrc" -if [ "$(readlink "$HOME/.vimrc")" = "$SCRIPT_DIR/.vimrc" ]; then - ok "~/.vimrc -> $SCRIPT_DIR/.vimrc" -else - die "Symlink verification failed" -fi - -# --- vim-plug --- +# ============================================================================ +# vim-plug + plugins +# ============================================================================ step "Installing vim-plug" @@ -79,63 +131,241 @@ else ok "vim-plug already present" fi -# --- Plugins --- - step "Installing Vim plugins" - vim +PlugInstall +qall ok "Plugins installed" -# --- Optional: CoC language servers --- +# ============================================================================ +# System tools (ripgrep, fzf, ctags, shellcheck, marksman) +# ============================================================================ -echo "" -if [ "$HAS_NODE" -eq 1 ]; then - read -p "Install CoC language servers for common languages? [y/N] " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - step "Installing CoC language servers" - vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-marksman coc-sql' +qall - ok "CoC language servers installed" +step "System tools" + +if ask "Install system tools (ripgrep, fzf, ctags, shellcheck, marksman)?"; then + install_sys() { + local name="$1"; local check="$2"; shift 2 + if command -v "$check" >/dev/null 2>&1; then + ok "$name (already installed)" + 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 + ok "$name" + INSTALLED+=("$name") + else + fail "$name — could not install automatically" + FAILED+=("$name") + fi + } + + if [[ $OS == macos ]]; then + command -v brew >/dev/null 2>&1 || { warn "brew not found — skipping system tools"; } + install_sys "ripgrep" rg "brew install ripgrep" + install_sys "fzf" fzf "brew install fzf" + install_sys "universal-ctags" ctags "brew install universal-ctags" + install_sys "shellcheck" shellcheck "brew install shellcheck" + install_sys "marksman" marksman "brew install marksman" + elif [[ $HAS_APT -eq 1 ]]; then + sudo apt-get update -qq + install_sys "ripgrep" rg "sudo apt-get install -y ripgrep" + install_sys "fzf" fzf "sudo apt-get install -y fzf" + install_sys "universal-ctags" ctags "sudo apt-get install -y universal-ctags" + install_sys "shellcheck" shellcheck "sudo apt-get install -y shellcheck" + # marksman: no apt package, download binary + if ! command -v marksman >/dev/null 2>&1; then + ARCH=$(uname -m) + [[ "$ARCH" == "x86_64" ]] && MARCH="x64" || MARCH="arm64" + MVER=$(curl -s https://api.github.com/repos/artempyanykh/marksman/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4) + if [[ -n "$MVER" ]]; then + curl -fsSL "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \ + -o /tmp/marksman && chmod +x /tmp/marksman && sudo mv /tmp/marksman /usr/local/bin/marksman + ok "marksman" + INSTALLED+=("marksman") + else + warn "marksman: could not detect latest release, install manually" + SKIPPED+=("marksman") + fi + else + ok "marksman (already installed)" + fi + elif [[ $HAS_DNF -eq 1 ]]; then + install_sys "ripgrep" rg "sudo dnf install -y ripgrep" + install_sys "fzf" fzf "sudo dnf install -y fzf" + install_sys "shellcheck" shellcheck "sudo dnf install -y ShellCheck" + skip "universal-ctags — install manually: sudo dnf install ctags" + SKIPPED+=("ctags") + skip "marksman — install manually from https://github.com/artempyanykh/marksman/releases" + SKIPPED+=("marksman") + else + warn "Unknown Linux distro — skipping system tools (install manually)" + SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "marksman") fi else - echo " To enable LSP without Node.js:" - echo " 1. Open a source file in Vim" - echo " 2. Run :LspInstallServer" - echo " 3. vim-lsp-settings will auto-install the right language server" + skip "system tools" + SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "marksman") fi -# --- Done --- +# ============================================================================ +# npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript) +# ============================================================================ + +step "npm tools (formatters + linters)" + +if [[ $HAS_NODE -eq 1 ]]; then + if ask "Install npm tools (prettier, markdownlint-cli, stylelint, eslint, typescript)?"; then + npm_install() { + local pkg="$1"; local check="${2:-$1}" + if command -v "$check" >/dev/null 2>&1; then + ok "$pkg (already installed)" + return + fi + if npm install -g "$pkg" >/dev/null 2>&1; then + ok "$pkg" + INSTALLED+=("$pkg") + else + fail "$pkg" + FAILED+=("$pkg") + fi + } + npm_install prettier + npm_install markdownlint-cli markdownlint + npm_install stylelint + npm_install stylelint-config-standard + npm_install eslint + npm_install typescript tsc + npm_install sqlfmt + else + skip "npm tools" + SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") + fi +else + skip "npm tools (Node.js not installed)" + SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") +fi + +# ============================================================================ +# pip tools (black, isort, flake8, pylint, sqlfluff) +# ============================================================================ + +step "Python tools (formatters + linters)" + +if [[ $HAS_PIP -eq 1 ]]; then + if ask "Install Python tools (black, isort, flake8, pylint, sqlfluff)?"; then + pip_install() { + local pkg="$1"; local check="${2:-$1}" + if command -v "$check" >/dev/null 2>&1; then + ok "$pkg (already installed)" + return + fi + if pip3 install --quiet "$pkg" 2>/dev/null || \ + pip3 install --quiet --break-system-packages "$pkg" 2>/dev/null; then + ok "$pkg" + INSTALLED+=("$pkg") + else + fail "$pkg" + FAILED+=("$pkg") + fi + } + pip_install black + pip_install isort + pip_install flake8 + pip_install pylint + pip_install sqlfluff + else + skip "Python tools" + SKIPPED+=("black" "isort" "flake8" "pylint" "sqlfluff") + fi +else + skip "Python tools (pip3 not installed)" + SKIPPED+=("black" "isort" "flake8" "pylint" "sqlfluff") +fi + +# ============================================================================ +# Go tools (gopls, goimports) +# ============================================================================ + +step "Go tools" + +if [[ $HAS_GO -eq 1 ]]; then + if ask "Install Go tools (gopls, goimports)?"; then + # Go installs binaries to $(go env GOPATH)/bin — add to PATH for this session + GOBIN="$(go env GOPATH)/bin" + export PATH="$PATH:$GOBIN" + + go_install() { + local name="$1"; local pkg="$2"; local check="$3" + if command -v "$check" >/dev/null 2>&1 || [[ -x "$GOBIN/$check" ]]; then + ok "$name (already installed)" + return + fi + if go install "$pkg" >/dev/null 2>&1; then + ok "$name" + INSTALLED+=("$name") + else + fail "$name" + FAILED+=("$name") + fi + } + go_install gopls "golang.org/x/tools/gopls@latest" gopls + go_install goimports "golang.org/x/tools/cmd/goimports@latest" goimports + + # Remind user to add GOPATH/bin to their shell profile + if ! echo "$PATH" | grep -q "$GOBIN"; then + warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\"" + fi + else + skip "Go tools" + SKIPPED+=("gopls" "goimports") + fi +else + skip "Go tools (go not installed)" + SKIPPED+=("gopls" "goimports") +fi + +# ============================================================================ +# CoC language server extensions +# ============================================================================ + +step "CoC language server extensions" + +if [[ $HAS_NODE -eq 1 ]]; then + if ask "Install CoC language servers (LSP for all configured languages)?"; then + vim +'CocInstall -sync coc-json coc-tsserver coc-pyright coc-sh coc-html coc-css coc-yaml coc-go coc-rust-analyzer coc-marksman coc-sql' +qall + ok "CoC language servers installed" + else + skip "CoC language servers" + echo " Install later with :CocInstall inside Vim" + fi +else + warn "Node.js not found — using vim-lsp fallback (run :LspInstallServer inside Vim)" +fi + +# ============================================================================ +# Summary +# ============================================================================ echo "" -echo -e "${BOLD}========================================${NC}" +echo -e "${BOLD}=======================================${NC}" echo -e "${GREEN}Installation complete.${NC}" -echo -e "${BOLD}========================================${NC}" +echo -e "${BOLD}=======================================${NC}" + +if [[ ${#INSTALLED[@]} -gt 0 ]]; then + echo -e "\n${GREEN}Installed:${NC}" + for t in "${INSTALLED[@]}"; do echo " + $t"; done +fi +if [[ ${#SKIPPED[@]} -gt 0 ]]; then + echo -e "\n${CYAN}Skipped:${NC}" + for t in "${SKIPPED[@]}"; do echo " - $t"; done +fi +if [[ ${#FAILED[@]} -gt 0 ]]; then + echo -e "\n${RED}Failed (install manually):${NC}" + for t in "${FAILED[@]}"; do echo " ! $t"; done +fi + echo "" -echo "Optional tools (install for best experience):" -echo "" -echo " ripgrep (,rg project search)" -echo " Ubuntu: sudo apt install ripgrep" -echo " macOS: brew install ripgrep" -echo "" -echo " fzf (Ctrl+p file search)" -echo " Ubuntu: sudo apt install fzf" -echo " macOS: brew install fzf" -echo "" -echo " ctags (F8 tag browser)" -echo " Ubuntu: sudo apt install universal-ctags" -echo " macOS: brew install universal-ctags" -echo "" -echo " Language linters and formatters:" -echo " Python: pip install black flake8 pylint isort" -echo " JS/TS: npm install -g prettier eslint typescript" -echo " Go: go install golang.org/x/tools/gopls@latest" -echo " Shell: sudo apt install shellcheck # or: brew install shellcheck" -echo " CSS/SCSS: npm install -g stylelint stylelint-config-standard" -echo " Markdown: npm install -g markdownlint-cli" -echo " SQL: pip install sqlfluff | npm install -g sqlfmt" -echo " Markdown LS: brew install marksman # or: https://github.com/artempyanykh/marksman" -echo "" -echo "Getting started:" -echo " See QUICKSTART.md for the 5-minute guide" -echo " Run 'vim' and press ',' then wait 500ms for keybinding hints" +echo "Run 'vim' and press ',' then wait 500ms for keybinding hints." echo ""