diff --git a/.vimrc b/.vimrc index e56c3ce..1fb7e2c 100644 --- a/.vimrc +++ b/.vimrc @@ -1,141 +1,75 @@ " ============================================================================ -" Vim Configuration - The Ultimate vimrc -" Inspired by the best practices from the Vim community +" chopsticks — vim configuration +" Philosophy: flowing writing on any machine. No Node.js. Solarized palette. " ============================================================================ +" ============================================================================ +" => Environment Detection (must run first) +" ============================================================================ + +set nocompatible + +let g:is_tty = empty($TERM) || $TERM ==# 'dumb' || $TERM =~# 'linux' + \ || $TERM =~# 'screen' || &term =~# 'builtin' +let g:has_true_color = ($COLORTERM ==# 'truecolor' || $COLORTERM ==# '24bit') + " ============================================================================ " => General Settings " ============================================================================ -" Disable compatibility with vi which can cause unexpected issues -set nocompatible - -" Detect terminal type and capabilities (must be early for conditional configs) -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 filetype on - -" Enable plugins and load plugin for the detected file type filetype plugin on - -" Load an indent file for the detected file type filetype indent on - -" Turn syntax highlighting on syntax on -" Add numbers to each line on the left-hand side set number - -" Show relative line numbers set relativenumber -" Highlight cursor line (disabled in TTY for performance) if !g:is_tty set cursorline endif -" Do not save backup files set nobackup - -" Do not let cursor scroll below or above N number of lines when scrolling set scrolloff=10 - -" Do not wrap lines. Allow long lines to extend as far as the line goes set nowrap - -" While searching though a file incrementally highlight matching characters as you type set incsearch - -" Ignore capital letters during search set ignorecase - -" Override the ignorecase option if searching for capital letters set smartcase - -" Show partial command you type in the last line of the screen set showcmd - -" Show the mode you are on the last line set showmode - -" Use highlighting when doing a search set hlsearch - -" Set the commands to save in history default number is 20 set history=1000 - -" Enable auto completion menu after pressing TAB set wildmenu - -" Make wildmenu behave like similar to Bash completion set wildmode=list:longest - -" Case-insensitive filename completion in wildmenu (spf13, YADR) set wildignorecase - -" Files and directories to exclude from wildmenu and :find set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/* - -" Recursive :find across the project tree (works with wildignore above) set path+=** - -" Enable mouse support set mouse=a - -" Set encoding set encoding=utf-8 - -" Enable folding set foldmethod=indent set foldlevel=99 - -" Split window settings set splitbelow set splitright - -" Better backspace behavior set backspace=indent,eol,start - -" Auto read when file is changed from outside set autoread - -" Height of the command bar set cmdheight=1 - -" A buffer becomes hidden when it is abandoned set hid - -" Configure backspace so it acts as it should act (enhanced from earlier basic setting) set whichwrap+=<,>,h,l - -" For regular expressions turn magic on set magic - -" Show matching brackets and how many tenths of a second to blink set showmatch set mat=2 - -" No annoying sound on errors set noerrorbells set novisualbell set t_vb= set tm=500 - -" Separate timeout for keycodes (arrow keys, Esc) vs leader sequences -" ttimeoutlen=10 eliminates the ~500ms ESC lag in terminal Vim (vim-sensible) set ttimeout set ttimeoutlen=10 -" Enable 256 colors palette in Gnome Terminal -if $COLORTERM == 'gnome-terminal' +if $COLORTERM ==# 'gnome-terminal' set t_Co=256 endif -" Set extra options when running in GUI mode if has("gui_running") set guioptions-=T set guioptions-=e @@ -143,16 +77,11 @@ if has("gui_running") set guitablabel=%M\ %t endif -" Show last line partially instead of replacing it with @@@ (vim-sensible) set display+=lastline - -" Use Unix as the standard file type set ffs=unix,dos,mac - set nowb set noswapfile -" Persistent undo across sessions if has('persistent_undo') set undofile let &undodir = expand('~/.vim/.undo') @@ -160,136 +89,91 @@ if has('persistent_undo') endif " ============================================================================ -" => Vim-Plug Plugin Manager +" => vim-plug " ============================================================================ -" Auto-install vim-plug let data_dir = has('nvim') ? stdpath('data') . '/site' : '~/.vim' if empty(glob(data_dir . '/autoload/plug.vim')) - silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim' - autocmd VimEnter * PlugInstall --sync | source $MYVIMRC + silent execute '!curl -fLo '.data_dir.'/autoload/plug.vim --create-dirs ' + \ . 'https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim' + autocmd VimEnter * PlugInstall --sync | source $MYVIMRC endif -" Plugin list call plug#begin('~/.vim/plugged') -" ===== File Navigation & Search ===== -Plug 'preservim/nerdtree' " File explorer +" ── Navigation & Search ─────────────────────────────────────────────────────── Plug 'junegunn/fzf', { 'do': { -> fzf#install() } } -Plug 'junegunn/fzf.vim' " Fuzzy finder +Plug 'junegunn/fzf.vim' -" ===== Git Integration ===== -Plug 'tpope/vim-fugitive' " Git wrapper -Plug 'airblade/vim-gitgutter' " Show git diff in gutter +" ── Git ─────────────────────────────────────────────────────────────────────── +Plug 'tpope/vim-fugitive' +Plug 'airblade/vim-gitgutter' -" ===== Status Line & UI ===== -Plug 'vim-airline/vim-airline' " Status bar -Plug 'vim-airline/vim-airline-themes' " Airline themes +" ── Editing ─────────────────────────────────────────────────────────────────── +Plug 'tpope/vim-surround' +Plug 'tpope/vim-commentary' +Plug 'tpope/vim-repeat' +Plug 'tpope/vim-unimpaired' +Plug 'wellle/targets.vim' +Plug 'jiangmiao/auto-pairs' +Plug 'easymotion/vim-easymotion' -" ===== Code Editing & Completion ===== -Plug 'tpope/vim-surround' " Surround text objects -Plug 'tpope/vim-commentary' " Comment stuff out -Plug 'tpope/vim-repeat' " Repeat plugin maps -Plug 'jiangmiao/auto-pairs' " Auto close brackets -Plug 'dense-analysis/ale' " Async linting engine +" ── Linting & Formatting ────────────────────────────────────────────────────── +Plug 'dense-analysis/ale' -" ===== Language Support ===== -Plug 'sheerun/vim-polyglot' " Language pack -Plug 'fatih/vim-go' " Go support (run :GoUpdateBinaries manually if needed) +" ── LSP + Completion (no Node.js required) ──────────────────────────────────── +Plug 'prabirshrestha/vim-lsp' +Plug 'mattn/vim-lsp-settings' +Plug 'prabirshrestha/asyncomplete.vim' +Plug 'prabirshrestha/asyncomplete-lsp.vim' -" ===== Color Schemes ===== -Plug 'morhetz/gruvbox' " Gruvbox theme -Plug 'dracula/vim', { 'as': 'dracula' } " Dracula theme -Plug 'altercation/vim-colors-solarized' " Solarized theme -Plug 'joshdick/onedark.vim' " One Dark theme +" ── Language Syntax ─────────────────────────────────────────────────────────── +Plug 'pangloss/vim-javascript' +Plug 'HerringtonDarkholme/yats.vim' +Plug 'preservim/vim-markdown' +Plug 'fatih/vim-go' -" ===== Productivity ===== -Plug 'mbbill/undotree' " Undo history visualizer -Plug 'preservim/tagbar' " Tag browser -Plug 'easymotion/vim-easymotion' " Easy motion +" ── Markdown Preview ────────────────────────────────────────────────────────── +Plug 'previm/previm' -" ===== Code Intelligence (CoC: requires Vim 8.0.1453+ and Node.js) ===== -if (has('nvim') || has('patch-8.0.1453')) && executable('node') - Plug 'neoclide/coc.nvim', {'branch': 'release'} " Full LSP + completion via Node.js -endif - -" ===== Session Management ===== -Plug 'tpope/vim-obsession' " Continuous session save -Plug 'dhruvasagar/vim-prosession' " Better session management - -" ===== Additional Utilities ===== -Plug 'tpope/vim-unimpaired' " Handy bracket mappings -Plug 'wellle/targets.vim' " Additional text objects -Plug 'honza/vim-snippets' " Snippet collection -Plug 'christoomey/vim-tmux-navigator' " Seamless vim/tmux pane navigation - -" ===== Native LSP (vim-lsp: works without Node.js, Vim 8.0+ only) ===== -" Used as fallback when CoC/Node.js is unavailable -if !((has('nvim') || has('patch-8.0.1453')) && executable('node')) - Plug 'prabirshrestha/vim-lsp' " Pure VimScript LSP client - Plug 'mattn/vim-lsp-settings' " Auto-configure language servers - Plug 'prabirshrestha/asyncomplete.vim' " Async completion framework - Plug 'prabirshrestha/asyncomplete-lsp.vim' " LSP completion source for asyncomplete -endif - -" ===== Enhanced UI Experience ===== -Plug 'mhinz/vim-startify' " Startup screen with recent files -Plug 'liuchengxu/vim-which-key' " Show keybindings on leader pause +" ── UI ──────────────────────────────────────────────────────────────────────── +Plug 'mbbill/undotree' +Plug 'mhinz/vim-startify' +Plug 'altercation/vim-colors-solarized' if !g:is_tty - Plug 'Yggdroot/indentLine' " Indent guide lines + Plug 'Yggdroot/indentLine' endif +" ── Session ─────────────────────────────────────────────────────────────────── +Plug 'tpope/vim-obsession' + +" ── tmux ────────────────────────────────────────────────────────────────────── +Plug 'christoomey/vim-tmux-navigator' + call plug#end() " ============================================================================ -" => LSP Backend Detection & Completion Settings +" => Colors (Solarized Dark — matches tmux palette) " ============================================================================ -" Detect which LSP backend is active -" Priority: CoC (full-featured, needs Node.js) > vim-lsp (lightweight fallback) -let g:use_coc = (has('nvim') || has('patch-8.0.1453')) && executable('node') && exists('g:plugs["coc.nvim"]') -let g:use_vimlsp = !g:use_coc && has('patch-8.0.0') && exists('g:plugs["vim-lsp"]') - -" Suppress coc.nvim's blocking startup warning on Vim < 9.0.0438 -" (the guard above already prevents coc from loading, but the warning -" fires from the plugin file itself if coc.nvim is in runtimepath) -if !g:use_coc - let g:coc_start_at_startup = 0 - let g:coc_disable_startup_warning = 1 -endif - -" Limit popup menu height (applies to all completion) -set pumheight=15 - -" ============================================================================ -" => Colors and Fonts -" ============================================================================ - -" Enable true colors support only if terminal supports it if g:has_true_color && has('termguicolors') && !g:is_tty set termguicolors endif -" Set colorscheme with proper fallbacks +set background=dark + if &t_Co >= 256 && !g:is_tty - " 256-color terminals try - colorscheme gruvbox - set background=dark + " 256-color approximation — works on any terminal, no palette setup needed + let g:solarized_termcolors = 256 + colorscheme solarized catch - try - colorscheme desert - catch - colorscheme default - endtry + colorscheme default endtry else - " Basic 16-color terminals (TTY, console) colorscheme default - set background=dark endif -" Set font for GUI if has("gui_running") if has("gui_gtk2") || has("gui_gtk3") set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12 @@ -299,31 +183,21 @@ if has("gui_running") endif " ============================================================================ -" => Text, Tab and Indent Related +" => Text, Tab and Indent " ============================================================================ -" Visible whitespace characters (toggled with ) -" TTY: ASCII equivalents; GUI/modern terminal: Unicode symbols if g:is_tty set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_ else set listchars=tab:→\ ,trail:·,extends:▸,precedes:◂,nbsp:· endif -" Use spaces instead of tabs set expandtab - -" Be smart when using tabs set smarttab - -" 1 tab == 4 spaces set shiftwidth=4 set tabstop=4 - -" Linebreak on 500 characters set lbr set tw=500 - set autoindent set smartindent @@ -331,44 +205,29 @@ set smartindent " => Key Mappings " ============================================================================ -" Set leader key to comma let mapleader = "," -" Fast saving +" Saving / quitting nmap w :w! - -" Fast quitting nmap q :q - -" Fast save and quit nmap x :x -" Disable highlight when is pressed +" Clear search highlight map :noh -" Window navigation — owned by vim-tmux-navigator plugin (Ctrl+h/j/k/l works -" seamlessly across Vim splits and tmux panes; no manual maps needed here) - -" Close the current buffer (Bclose preserves window layout) +" Buffer navigation map bd :Bclose - -" Close all the buffers map ba :bufdo bd +map l :bnext +map h :bprevious -" Next buffer -map l :bnext - -" Previous buffer -map h :bprevious - -" Useful mappings for managing tabs +" Tab management map tn :tabnew map to :tabonly map tc :tabclose map tm :tabmove map t :tabnext -" Let 'tl' toggle between this and the last accessed tab let g:lasttab = 1 nmap tl :exe "tabn ".g:lasttab augroup ChopstickTabHistory @@ -376,88 +235,72 @@ augroup ChopstickTabHistory autocmd TabLeave * let g:lasttab = tabpagenr() augroup END -" Opens a new tab with the current buffer's path map te :tabedit =expand("%:p:h")/ - -" Change window-local CWD to current file's directory (lcd = local, safer than cd) map cd :lcd %:p:h:pwd -" Open built-in file browser (works on any Vim, no plugins needed — tpope) +" File browser (netrw — built-in, no plugins) nnoremap e :Explore +nnoremap E :Vexplore -" Remap VIM 0 to first non-blank character +" Remap 0 to first non-blank map 0 ^ -" Reselect last pasted text (gV = visual select last paste) — spf13, YADR +" Reselect last paste nnoremap gV `[v`] -" Command-line history navigation with Ctrl+p/n (amix, spf13) +" Command-line history cnoremap cnoremap -" Move a line of text using ALT+[jk] (normal mode) +" Move lines nmap mz:m+`z nmap mz:m-2`z - -" Move selected lines up/down and re-indent (visual mode) — ThePrimeagen -" Overrides visual-J (join) and visual-K (keywordprg) — use normal mode for those vnoremap J :m '>+1gv=gv vnoremap K :m '<-2gv=gv -" Pressing ,ss will toggle and untoggle spell checking +" Spell checking map ss :setlocal spell! - -" Shortcuts using map sn ]s map sp [s map sa zg map s? z= -" Toggle paste mode +" Toggle modes set pastetoggle= - -" Toggle line numbers nnoremap :set invnumber - -" Toggle relative line numbers nnoremap :set invrelativenumber - -" Toggle visible whitespace (tabs, trailing spaces, non-breaking spaces) nnoremap :set list! -" Enable folding with the spacebar +" Folding nnoremap za -" Y yanks to end of line (consistent with D, C) +" Consistency with D and C nnoremap Y y$ - -" Disable accidental Ex mode nnoremap Q -" Exit insert mode without reaching for Escape (community standard) +" Ergonomic escape inoremap jk -" Keep visual selection after indent +" Indent keeps visual selection vnoremap < >gv -" Center cursor when jumping through search results +" Search: centre result on screen nnoremap n nzzzv nnoremap N Nzzzv -" Search for visually selected text with // (hit // in visual mode) +" Search for visual selection vnoremap // y/\V=escape(@",'/\') -" to save in normal and insert mode -" (for terminals: add 'stty -ixon' to your shell rc to disable XON/XOFF) +" Save from any mode nnoremap :w inoremap :wa -" Center cursor after half-page scroll +" Scroll keeping cursor centred nnoremap zz nnoremap zz -" System clipboard yank/paste (conditional: requires clipboard provider) +" System clipboard if has('clipboard') nnoremap y "+y vnoremap y "+y @@ -466,64 +309,44 @@ if has('clipboard') nnoremap P "+P endif -" Quickfix list shortcuts ([q/]q from vim-unimpaired handles navigation) +" Quickfix nnoremap qo :copen nnoremap qc :cclose -" Auto-equalize splits when terminal window is resized +" Auto-equalise splits on window resize augroup ChopstickResize autocmd! autocmd VimResized * wincmd = augroup END " ============================================================================ -" => Plugin Settings +" => netrw (built-in file browser — replaces NERDTree) +" ============================================================================ +" +" e open netrw in current window (:Explore) +" E open netrw in a vertical split (:Vexplore) +" Inside netrw: +" Enter / o open file +" - go up one directory +" % create new file +" d create new directory +" D delete file/directory +" R rename +" gh toggle hidden files +" i cycle list style (1=thin, 2=long, 3=tree, 4=wide) + +let g:netrw_liststyle = 3 " tree view by default +let g:netrw_banner = 0 " no banner +let g:netrw_browse_split = 0 " open in same window +let g:netrw_winsize = 25 " 25% width when split +let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+' +let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store' + +" ============================================================================ +" => FZF " ============================================================================ -" --- NERDTree --- -map :NERDTreeToggle -map n :NERDTreeFind - -" Close vim if the only window left open is a NERDTree -augroup NERDTreeAutoClose - autocmd! - autocmd BufEnter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif -augroup END - -" Show hidden files -let NERDTreeShowHidden=1 - -" Ignore files in NERDTree -let NERDTreeIgnore=['\.pyc$', '\~$', '\.swp$', '\.git$', '\.DS_Store', 'node_modules', '__pycache__', '\.egg-info$'] - -" NERDTree window size -let NERDTreeWinSize=35 - -" Track stdin reads so startup autocmds can skip pipe/heredoc input -augroup ChopstickStdin - autocmd! - autocmd StdinReadPre * let s:std_in=1 -augroup END - -" Startup layout (non-TTY only — keeps TTY startup instant) -if !g:is_tty - augroup ChopstickStartup - autocmd! - " vim → NERDTree on left + Startify (or blank buffer) on right - autocmd VimEnter * - \ if argc() == 1 && isdirectory(argv()[0]) && !exists('s:std_in') | - \ exe 'NERDTree ' . fnameescape(argv()[0]) | - \ exe 'cd ' . fnameescape(argv()[0]) | - \ wincmd p | - \ if exists(':Startify') == 2 | Startify | else | enew | endif | - \ endif - " vim (no args) → Startify fullscreen dashboard, no auto NERDTree - " Use Ctrl+n to open NERDTree when needed - augroup END -endif - -" --- FZF --- -" Smart file search: use GFiles (respects .gitignore) inside git repos, Files elsewhere +" Ctrl+p: git-aware file search (GFiles inside repo, Files outside) function! s:SmartFiles() abort if !empty(system('git rev-parse --show-toplevel 2>/dev/null')) GFiles @@ -531,78 +354,57 @@ function! s:SmartFiles() abort Files endif endfunction -map :call SmartFiles() + +map :call SmartFiles() map b :Buffers map rg :Rg map rG :Rg -F map rt :Tags map gF :GFiles -" FZF customization for better project search let g:fzf_layout = { 'down': '40%' } -" Disable preview in TTY for better performance if g:is_tty let g:fzf_preview_window = [] else let g:fzf_preview_window = ['right:50%', 'ctrl-/'] endif -" Advanced FZF commands -" Conditionally enable preview based on terminal type if g:is_tty command! -bang -nargs=* Rg - \ call fzf#vim#grep( - \ 'rg --column --line-number --no-heading --color=always --smart-case -- '.shellescape(), 1, - \ 0) - + \ call fzf#vim#grep( + \ 'rg --column --line-number --no-heading --color=always --smart-case -- ' + \ .shellescape(), 1, 0) command! -bang GFiles call fzf#vim#gitfiles('', 0) else command! -bang -nargs=* Rg - \ call fzf#vim#grep( - \ 'rg --column --line-number --no-heading --color=always --smart-case -- '.shellescape(), 1, - \ fzf#vim#with_preview(), 0) - + \ call fzf#vim#grep( + \ 'rg --column --line-number --no-heading --color=always --smart-case -- ' + \ .shellescape(), 1, fzf#vim#with_preview(), 0) command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), 0) endif +" ============================================================================ +" => GitGutter +" ============================================================================ -" --- Airline --- -" Disable powerline fonts in TTY for compatibility -if g:is_tty - let g:airline_powerline_fonts = 0 - let g:airline_left_sep = '' - let g:airline_right_sep = '' - let g:airline#extensions#tabline#left_sep = ' ' - let g:airline#extensions#tabline#left_alt_sep = '|' -else - let g:airline_powerline_fonts = 1 -endif - -let g:airline#extensions#tabline#enabled = 1 -let g:airline#extensions#tabline#formatter = 'unique_tail' - -" Set theme based on terminal capabilities -if &t_Co >= 256 && !g:is_tty - let g:airline_theme='gruvbox' -else - let g:airline_theme='dark' -endif - -" --- GitGutter --- -let g:gitgutter_sign_added = '+' -let g:gitgutter_sign_modified = '~' -let g:gitgutter_sign_removed = '-' +let g:gitgutter_sign_added = '+' +let g:gitgutter_sign_modified = '~' +let g:gitgutter_sign_removed = '-' let g:gitgutter_sign_removed_first_line = '^' let g:gitgutter_sign_modified_removed = '~' -" --- ALE (Asynchronous Lint Engine) --- +" ============================================================================ +" => ALE (async linting + format-on-save) +" ============================================================================ + let g:ale_linters = { \ 'python': ['flake8', 'pylint'], \ 'javascript': ['eslint'], \ 'typescript': ['eslint', 'tsserver'], \ 'go': ['gopls', 'staticcheck'], \ 'rust': ['cargo'], +\ 'c': ['cc'], \ 'sh': ['shellcheck'], \ 'yaml': ['yamllint'], \ 'dockerfile': ['hadolint'], @@ -619,6 +421,7 @@ let g:ale_fixers = { \ 'typescript': ['prettier', 'eslint'], \ 'go': ['gofmt', 'goimports'], \ 'rust': ['rustfmt'], +\ 'c': ['clang-format'], \ 'json': ['prettier'], \ 'yaml': ['prettier'], \ 'html': ['prettier'], @@ -629,365 +432,339 @@ let g:ale_fixers = { \ 'sql': ['sqlfluff'], \} -" Don't fix on save if LSP is handling formatting (avoids double-format) -let g:ale_fix_on_save = !g:use_vimlsp -let g:ale_sign_error = 'X' -let g:ale_sign_warning = '!' +let g:ale_fix_on_save = 1 +let g:ale_sign_error = 'X' +let g:ale_sign_warning = '!' let g:ale_lint_on_text_changed = 'normal' let g:ale_lint_on_insert_leave = 1 -let g:ale_lint_on_enter = 1 +let g:ale_lint_on_enter = 1 -" --- vim-go: disable built-in LSP/gopls — CoC (coc-go) handles all Go intelligence --- -" vim-go's gopls conflicts with coc-go and causes E495 errors on startup -" (BufWinEnter afile expand fails for non-file buffers like NERDTree/Startify) -let g:go_gopls_enabled = 0 " disable vim-go's own gopls — coc-go handles LSP -let g:go_code_completion_enabled = 0 " let CoC handle completion -" Use godef as fallback for jump-to-def when CoC unavailable; gopls+disabled = error -let g:go_def_mode = g:use_coc ? 'gopls' : 'godef' -let g:go_info_mode = g:use_coc ? 'gopls' : 'godef' -let g:go_fmt_autosave = 0 " CoC/ALE handle format-on-save -let g:go_imports_autosave = 0 -let g:go_highlight_types = 1 " keep syntax features -let g:go_highlight_fields = 1 -let g:go_highlight_functions = 1 -let g:go_highlight_function_calls = 1 - -" Navigate between errors: [e/]e (unimpaired convention: [ = prev, ] = next) nmap [e :ALEPrevious nmap ]e :ALENext nmap aD :ALEDetail -" --- Tagbar --- -nmap :TagbarToggle -nmap tt :TagbarToggle +" ============================================================================ +" => vim-go +" ============================================================================ -" --- UndoTree --- -nnoremap :UndotreeToggle -nnoremap u :UndotreeToggle +" vim-lsp (gopls) handles all Go intelligence; disable vim-go's own LSP layer +let g:go_gopls_enabled = 0 +let g:go_code_completion_enabled = 0 +let g:go_def_mode = 'gopls' +let g:go_info_mode = 'gopls' +let g:go_fmt_autosave = 0 " ALE handles format-on-save +let g:go_imports_autosave = 0 +let g:go_highlight_types = 1 +let g:go_highlight_fields = 1 +let g:go_highlight_functions = 1 +let g:go_highlight_function_calls = 1 -" --- EasyMotion --- -let g:EasyMotion_do_mapping = 0 " Disable default mappings +" ============================================================================ +" => vim-lsp (primary LSP backend — pure VimScript, no Node.js) +" ============================================================================ -" Jump to anywhere you want with minimal keystrokes +let g:lsp_settings_filetype_python = ['pylsp', 'pyright-langserver'] +let g:lsp_settings_filetype_go = ['gopls'] +let g:lsp_settings_filetype_rust = ['rust-analyzer'] +let g:lsp_settings_filetype_typescript = ['typescript-language-server'] +let g:lsp_settings_filetype_javascript = ['typescript-language-server'] +let g:lsp_settings_filetype_c = ['clangd'] +let g:lsp_settings_filetype_sh = ['bash-language-server'] +let g:lsp_settings_filetype_html = ['vscode-html-language-server'] +let g:lsp_settings_filetype_css = ['vscode-css-language-server'] +let g:lsp_settings_filetype_scss = ['vscode-css-language-server'] +let g:lsp_settings_filetype_json = ['vscode-json-language-server'] +let g:lsp_settings_filetype_yaml = ['yaml-language-server'] +let g:lsp_settings_filetype_markdown = ['marksman'] +let g:lsp_settings_filetype_sql = ['sqls'] + +let g:lsp_diagnostics_virtual_text_enabled = !g:is_tty +let g:lsp_diagnostics_highlights_enabled = !g:is_tty +let g:lsp_document_highlight_enabled = !g:is_tty +let g:lsp_signs_enabled = 1 +let g:lsp_diagnostics_echo_cursor = 1 +let g:lsp_completion_documentation_enabled = 1 + +let g:lsp_signs_error = {'text': 'X'} +let g:lsp_signs_warning = {'text': '!'} +let g:lsp_signs_information = {'text': 'i'} +let g:lsp_signs_hint = {'text': '>'} + +set completeopt=menuone,noinsert,noselect +set pumheight=15 +let g:asyncomplete_auto_popup = 1 +let g:asyncomplete_auto_completeopt = 1 +let g:asyncomplete_popup_delay = 200 + +" Completion popup navigation +inoremap pumvisible() ? "\" : "\" +inoremap pumvisible() ? "\" : "\" +inoremap pumvisible() ? asyncomplete#close_popup() : "\" + +function! s:on_lsp_buffer_enabled() abort + setlocal omnifunc=lsp#complete + setlocal signcolumn=yes + + " Navigation + nmap gd (lsp-definition) + nmap gy (lsp-type-definition) + nmap gi (lsp-implementation) + nmap gr (lsp-references) + nmap [g (lsp-previous-diagnostic) + nmap ]g (lsp-next-diagnostic) + + " Documentation + nmap K (lsp-hover) + + " Refactoring + nmap rn (lsp-rename) + nmap ca (lsp-code-action) + nmap f (lsp-document-format) + xmap f (lsp-document-range-format) + + " Workspace + nmap o (lsp-document-symbol-search) + nmap ws (lsp-workspace-symbol-search) + nmap cD (lsp-document-diagnostics) +endfunction + +augroup lsp_install + autocmd! + autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() +augroup END + +" ============================================================================ +" => vim-markdown +" ============================================================================ + +" Concealment: hides syntax markers, renders formatting inline +" (cursor moving to a line temporarily reveals the raw syntax) +let g:vim_markdown_conceal = 1 +let g:vim_markdown_conceal_code_blocks = 0 " keep fenced code readable +let g:vim_markdown_folding_disabled = 0 +let g:vim_markdown_folding_level = 2 +let g:vim_markdown_frontmatter = 1 " YAML front matter +let g:vim_markdown_toml_frontmatter = 1 +let g:vim_markdown_json_frontmatter = 1 +let g:vim_markdown_follow_anchor = 1 +let g:vim_markdown_new_list_item_indent = 2 +let g:vim_markdown_strikethrough = 1 + +" Table of contents in quickfix +nnoremap mt :Toc + +" ============================================================================ +" => previm (Markdown browser preview) +" ============================================================================ + +" mp open live-reloading preview in browser +nnoremap mp :PrevimOpen +let g:previm_enable_realtime = 1 + +" ============================================================================ +" => EasyMotion +" ============================================================================ + +let g:EasyMotion_do_mapping = 0 +let g:EasyMotion_smartcase = 1 + +" s + two chars: jump anywhere on screen nmap s (easymotion-overwin-f2) -" Turn on case-insensitive feature -let g:EasyMotion_smartcase = 1 - -" JK motions: Line motions +" J/K: line motions map j (easymotion-j) map k (easymotion-k) -" --- CoC (Conquer of Completion) - Full LSP via Node.js --- -if g:use_coc - " Tab for trigger completion / navigate popup - inoremap - \ coc#pum#visible() ? coc#pum#next(1) : - \ CheckBackspace() ? "\" : - \ coc#refresh() - inoremap coc#pum#visible() ? coc#pum#prev(1) : "\" +" ============================================================================ +" => UndoTree +" ============================================================================ - " CR to confirm selected completion item - inoremap coc#pum#visible() ? coc#pum#confirm() - \: "\u\\=coc#on_enter()\" +nnoremap :UndotreeToggle +nnoremap u :UndotreeToggle - function! CheckBackspace() abort - let col = col('.') - 1 - return !col || getline('.')[col - 1] =~# '\s' - endfunction +" ============================================================================ +" => IndentLine (non-TTY only) +" ============================================================================ - " to trigger completion manually - inoremap coc#refresh() - - " Diagnostic navigation - nmap [g (coc-diagnostic-prev) - nmap ]g (coc-diagnostic-next) - nmap ad :CocDiagnostics - - " GoTo code navigation - nmap gd (coc-definition) - nmap gy (coc-type-definition) - nmap gi (coc-implementation) - nmap gr (coc-references) - - " Hover documentation - nnoremap K :call ShowDocumentation() - function! ShowDocumentation() - if CocAction('hasProvider', 'hover') - call CocActionAsync('doHover') - else - call feedkeys('K', 'in') - endif - endfunction - - " Highlight symbol and its references on cursor hold - augroup CocHighlight - autocmd! - autocmd CursorHold * silent call CocActionAsync('highlight') - augroup END - - " Symbol renaming - nmap rn (coc-rename) - - " Format selected code - xmap f (coc-format-selected) - nmap f (coc-format-selected) - - " Code actions (cursor, file, selected range) - nmap ca (coc-codeaction-cursor) - nmap cA (coc-codeaction-source) - xmap ca (coc-codeaction-selected) - - " Apply auto-fix for current line - nmap qf (coc-fix-current) - - " Run code lens action on current line - nmap cl (coc-codelens-action) - - " Workspace symbols and outline - nnoremap ws :CocList -I symbols - nnoremap o :CocList outline - - " Search recently used commands - nnoremap cc :CocList commands - - " Resume latest CoC list - nnoremap cr :CocListResume - - " Show all diagnostics - nnoremap cD :CocList diagnostics - - " Text object for function/class (requires language server support) - xmap if (coc-funcobj-i) - omap if (coc-funcobj-i) - xmap af (coc-funcobj-a) - omap af (coc-funcobj-a) - xmap ic (coc-classobj-i) - omap ic (coc-classobj-i) - xmap ac (coc-classobj-a) - omap ac (coc-classobj-a) - - " Scroll float windows - if has('nvim-0.4.0') || has('patch-8.2.0750') - nnoremap coc#float#has_scroll() ? coc#float#scroll(1) : "\" - nnoremap coc#float#has_scroll() ? coc#float#scroll(0) : "\" - inoremap coc#float#has_scroll() ? "\=coc#float#scroll(1)\" : "\" - inoremap coc#float#has_scroll() ? "\=coc#float#scroll(0)\" : "\" - endif - - " Status line integration - set statusline^=%{coc#status()}%{get(b:,'coc_current_function','')} +if !g:is_tty && exists('g:plugs["indentLine"]') + let g:indentLine_char = '|' + let g:indentLine_first_char = '|' + let g:indentLine_showFirstIndentLevel = 1 + " Exclude filetypes where concealment causes display issues + let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'markdown'] + let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile'] + " Let indentLine manage conceallevel (reverts on excluded types) + let g:indentLine_setConceal = 2 + let g:indentLine_concealcursor = '' endif -" --- vim-lsp (Native VimScript LSP - fallback when Node.js unavailable) --- -if g:use_vimlsp - " Auto-configure language servers via vim-lsp-settings - let g:lsp_settings_filetype_python = ['pylsp', 'pyright-langserver'] - let g:lsp_settings_filetype_go = ['gopls'] - let g:lsp_settings_filetype_rust = ['rust-analyzer'] - let g:lsp_settings_filetype_typescript = ['typescript-language-server'] - let g:lsp_settings_filetype_javascript = ['typescript-language-server'] - let g:lsp_settings_filetype_sh = ['bash-language-server'] - let g:lsp_settings_filetype_html = ['vscode-html-language-server'] - let g:lsp_settings_filetype_css = ['vscode-css-language-server'] - let g:lsp_settings_filetype_scss = ['vscode-css-language-server'] - let g:lsp_settings_filetype_json = ['vscode-json-language-server'] - let g:lsp_settings_filetype_yaml = ['yaml-language-server'] - let g:lsp_settings_filetype_markdown = ['marksman'] - let g:lsp_settings_filetype_sql = ['sqls'] +" ============================================================================ +" => Startify (startup screen + session management) +" ============================================================================ - " Performance: disable virtual text diagnostics in TTY - let g:lsp_diagnostics_virtual_text_enabled = !g:is_tty - let g:lsp_diagnostics_highlights_enabled = !g:is_tty - let g:lsp_document_highlight_enabled = !g:is_tty - let g:lsp_signs_enabled = 1 - let g:lsp_diagnostics_echo_cursor = 1 - let g:lsp_completion_documentation_enabled = 1 +if exists('g:plugs["vim-startify"]') + let g:startify_custom_header = [ + \ ' ███╗ ███╗ ██╗███╗ ██╗ ██████╗ ███████╗ █████╗ ███╗ ███╗ █████╗ ', + \ ' ████╗ ████║███║████╗ ██║██╔════╝ ██╔════╝██╔══██╗████╗ ████║██╔══██╗ ', + \ ' ██╔████╔██║╚██║██╔██╗ ██║██║ ███╗███████╗███████║██╔████╔██║███████║ ', + \ ' ██║╚██╔╝██║ ██║██║╚██╗██║██║ ██║╚════██║██╔══██║██║╚██╔╝██║██╔══██║ ', + \ ' ██║ ╚═╝ ██║ ██║██║ ╚████║╚██████╔╝███████║██║ ██║██║ ╚═╝ ██║██║ ██║ ', + \ ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', + \ '', + \ ] - " Diagnostic signs (ASCII, KISS) - let g:lsp_signs_error = {'text': 'X'} - let g:lsp_signs_warning = {'text': '!'} - let g:lsp_signs_information = {'text': 'i'} - let g:lsp_signs_hint = {'text': '>'} + let g:startify_lists = [ + \ { 'type': 'sessions', 'header': [' Sessions'] }, + \ { 'type': 'files', 'header': [' Recent Files'] }, + \ { 'type': 'dir', 'header': [' Current Dir'] }, + \ { 'type': 'bookmarks', 'header': [' Bookmarks'] }, + \ ] - " asyncomplete manages completeopt for vim-lsp mode - set completeopt=menuone,noinsert,noselect - let g:asyncomplete_auto_popup = 1 - let g:asyncomplete_auto_completeopt = 1 - let g:asyncomplete_popup_delay = 200 + let g:startify_bookmarks = [ + \ {'v': '~/.vimrc'}, + \ {'z': '~/.zshrc'}, + \ {'b': '~/.bashrc'}, + \ ] - " Tab to navigate completion popup (mirrors CoC behavior) - inoremap pumvisible() ? "\" : "\" - inoremap pumvisible() ? "\" : "\" - inoremap pumvisible() ? asyncomplete#close_popup() : "\" + let g:startify_session_persistence = 1 + let g:startify_session_autoload = 1 + let g:startify_change_to_vcs_root = 1 + let g:startify_fortune_use_unicode = 0 + let g:startify_enable_special = 0 + let g:startify_files_number = 8 + let g:startify_padding_left = 4 - " Key mappings (mirror CoC's gd/gy/gi/gr/K/rn layout) - function! s:on_lsp_buffer_enabled() abort - setlocal omnifunc=lsp#complete - setlocal signcolumn=yes + " vim : cd to it and show Startify (no auto file-tree) + if !g:is_tty + augroup ChopstickStartup + autocmd! + autocmd StdinReadPre * let s:std_in = 1 + autocmd VimEnter * + \ if argc() == 1 && isdirectory(argv()[0]) && !exists('s:std_in') | + \ exe 'cd ' . fnameescape(argv()[0]) | + \ if exists(':Startify') == 2 | Startify | else | enew | endif | + \ endif + augroup END + endif - " Navigation - nmap gd (lsp-definition) - nmap gy (lsp-type-definition) - nmap gi (lsp-implementation) - nmap gr (lsp-references) - nmap [g (lsp-previous-diagnostic) - nmap ]g (lsp-next-diagnostic) - - " Hover documentation - nmap K (lsp-hover) - - " Refactoring - nmap rn (lsp-rename) - nmap ca (lsp-code-action) - nmap f (lsp-document-format) - - " Workspace - nmap ws (lsp-workspace-symbol-search) - nmap o (lsp-document-symbol-search) - nmap cD (lsp-document-diagnostics) - - " Enable auto-format on save for filetypes with reliable LSP formatters - if index(['python', 'go', 'rust', 'typescript', 'javascript', 'sh'], &filetype) >= 0 - autocmd BufWritePre LspDocumentFormatSync - endif - endfunction - - augroup lsp_install + augroup ChopstickStartify autocmd! - autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled() + autocmd User Startified setlocal buftype= augroup END endif +" ============================================================================ +" => Status Line (native — Solarized palette, seamless with tmux bar) +" ============================================================================ +" +" Palette reference (Solarized 256 approximations): +" base03=234 base02=235 base01=240 base0=244 base1=245 +" yellow=136 blue=33 cyan=37 green=64 red=160 +" orange=166 magenta=125 +" +" The statusline bg (base02=235 / #073642) intentionally matches the tmux +" status bar bg (#002b36 ≈ base03), so the two bars read as one continuous +" band at the bottom of the screen. + +set laststatus=2 +set noshowmode " mode is shown in the statusline block, not the echo line + +function! s:SLDefineColors() abort + hi SLNormal ctermbg=136 ctermfg=234 cterm=bold guibg=#b58900 guifg=#002b36 gui=bold + hi SLInsert ctermbg=33 ctermfg=234 cterm=bold guibg=#268bd2 guifg=#002b36 gui=bold + hi SLVisual ctermbg=125 ctermfg=234 cterm=bold guibg=#d33682 guifg=#002b36 gui=bold + hi SLReplace ctermbg=160 ctermfg=234 cterm=bold guibg=#dc322f guifg=#002b36 gui=bold + hi SLCommand ctermbg=37 ctermfg=234 cterm=bold guibg=#2aa198 guifg=#002b36 gui=bold + hi SLBody ctermbg=235 ctermfg=245 cterm=none guibg=#073642 guifg=#93a1a1 + hi SLFlag ctermbg=235 ctermfg=136 cterm=none guibg=#073642 guifg=#b58900 + hi SLRight ctermbg=235 ctermfg=240 cterm=none guibg=#073642 guifg=#586e75 + hi SLGit ctermbg=235 ctermfg=37 cterm=none guibg=#073642 guifg=#2aa198 + hi SLFtype ctermbg=235 ctermfg=244 cterm=none guibg=#073642 guifg=#839496 +endfunction + +augroup SLColors + autocmd! + autocmd ColorScheme * call s:SLDefineColors() + autocmd VimEnter * call s:SLDefineColors() +augroup END + +" Current mode → [label, highlight-group] +function! SLMode() abort + let l:m = mode() + if l:m ==# 'n' | return [' N ', 'SLNormal' ] + elseif l:m ==# 'i' | return [' I ', 'SLInsert' ] + elseif l:m =~# '[vV]' || l:m ==# "\" | return [' V ', 'SLVisual' ] + elseif l:m ==# 'R' | return [' R ', 'SLReplace'] + elseif l:m ==# 'c' | return [' C ', 'SLCommand'] + elseif l:m ==# 't' | return [' T ', 'SLInsert' ] + else | return [' ' . l:m . ' ', 'SLNormal'] + endif +endfunction + +" Git branch via vim-fugitive (already installed — zero extra cost) +function! SLGit() abort + if !exists('*FugitiveHead') | return '' | endif + let l:b = FugitiveHead() + return empty(l:b) ? '' : ' ' . l:b . ' ' +endfunction + +" Assemble the statusline on every redraw +function! SLBuild() abort + let [l:label, l:hl] = SLMode() + let l:s = '%#' . l:hl . '#' . l:label + let l:s .= '%#SLBody# %f ' + let l:s .= '%#SLFlag#%m%r' + let l:s .= '%#SLBody#%=' + let l:s .= '%#SLGit#' . SLGit() + let l:s .= '%#SLFtype# %y ' + let l:s .= '%#SLRight# %l:%c %P ' + return l:s +endfunction + +set statusline=%!SLBuild() + +" TTY: simpler fallback (no colour, no function call overhead) +if g:is_tty + set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%) +endif + " ============================================================================ " => Helper Functions " ============================================================================ -" Returns true if paste mode is enabled function! HasPaste() - if &paste - return 'PASTE MODE ' - endif + if &paste | return 'PASTE MODE ' | endif return '' endfunction -" Don't close window, when deleting a buffer +" Close buffer without closing the window command! Bclose call BufcloseCloseIt() function! BufcloseCloseIt() - let l:currentBufNum = bufnr("%") + let l:currentBufNum = bufnr("%") let l:alternateBufNum = bufnr("#") - if buflisted(l:alternateBufNum) buffer # else bnext endif - if bufnr("%") == l:currentBufNum new endif - if buflisted(l:currentBufNum) - execute("bdelete! ".l:currentBufNum) + execute("bdelete! " . l:currentBufNum) endif endfunction -" Delete trailing white space on save +" Strip trailing whitespace without moving cursor fun! CleanExtraSpaces() let save_cursor = getpos(".") - let old_query = getreg('/') + let old_query = getreg('/') silent! %s/\s\+$//e call setpos('.', save_cursor) call setreg('/', old_query) endfun -" Disable auto-insertion of comment leaders when pressing Enter or o/O -" Must run at BufEnter because filetype plugins reset formatoptions per buffer -augroup ChopstickFormatOptions - autocmd! - autocmd BufEnter * setlocal formatoptions-=c formatoptions-=r formatoptions-=o -augroup END - -" Auto-disable paste mode when leaving insert mode (prevents broken indentation) -augroup ChopstickPaste - autocmd! - autocmd InsertLeave * set nopaste -augroup END - -augroup ChopstickCleanup - autocmd! - " Run for real files only; skip special buffers (NERDTree, Startify, terminal, etc.) - autocmd BufWritePre * if empty(&buftype) && !empty(expand('')) | call CleanExtraSpaces() | endif -augroup END - -" ============================================================================ -" => Auto Commands -" ============================================================================ - -augroup ChopstickFiletype - autocmd! - " Return to last edit position when opening files - autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif - - " Set specific file types - autocmd BufNewFile,BufRead *.json setlocal filetype=json - autocmd BufNewFile,BufRead *.md setlocal filetype=markdown - autocmd BufNewFile,BufRead *.jsx setlocal filetype=javascript.jsx - autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx - - " Python specific settings - autocmd FileType python setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1 - - " JavaScript / TypeScript specific settings - autocmd FileType javascript,typescript setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1 - - " Go specific settings (standard: no textwidth limit, but 120 is common) - autocmd FileType go setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1 - - " Rust specific settings - autocmd FileType rust setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1 - - " HTML/CSS specific settings - autocmd FileType html,css setlocal expandtab shiftwidth=2 tabstop=2 - - " YAML specific settings - autocmd FileType yaml setlocal expandtab shiftwidth=2 tabstop=2 - - " Markdown specific settings (no line-length limit; wrap at window edge) - autocmd FileType markdown setlocal wrap linebreak spell textwidth=0 colorcolumn=0 - - " Shell script settings - autocmd FileType sh setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1 - - " Makefile settings (must use tabs) - autocmd FileType make setlocal noexpandtab shiftwidth=8 tabstop=8 - - " JSON specific settings - autocmd FileType json setlocal expandtab shiftwidth=2 tabstop=2 - - " Docker specific settings - autocmd BufNewFile,BufRead Dockerfile* setlocal filetype=dockerfile - autocmd FileType dockerfile setlocal expandtab shiftwidth=2 tabstop=2 -augroup END - -" ============================================================================ -" => Status Line -" ============================================================================ - -" Always show the status line -set laststatus=2 - -" Format the status line (if not using airline) -" set statusline=\ %{HasPaste()}%F%m%r%h\ %w\ \ CWD:\ %r%{getcwd()}%h\ \ \ Line:\ %l\ \ Column:\ %c - -" ============================================================================ -" => Misc -" ============================================================================ - -" Quickly open a markdown buffer for scribble -map m :e ~/buffer.md - -" Toggle between number and relativenumber +" Toggle between absolute and relative numbers function! ToggleNumber() if(&relativenumber == 1) set norelativenumber @@ -998,26 +775,83 @@ function! ToggleNumber() endfunc " ============================================================================ -" => Performance Optimization +" => Auto Commands +" ============================================================================ + +" Suppress comment continuation on Enter / o / O +augroup ChopstickFormatOptions + autocmd! + autocmd BufEnter * setlocal formatoptions-=c formatoptions-=r formatoptions-=o +augroup END + +" Auto-disable paste mode on leaving insert +augroup ChopstickPaste + autocmd! + autocmd InsertLeave * set nopaste +augroup END + +" Strip trailing whitespace on save (real files only) +augroup ChopstickCleanup + autocmd! + autocmd BufWritePre * + \ if empty(&buftype) && !empty(expand('')) | call CleanExtraSpaces() | endif +augroup END + +augroup ChopstickFiletype + autocmd! + + " Restore cursor to last known position + autocmd BufReadPost * + \ if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif + + " Filetype detection for common extensions + autocmd BufNewFile,BufRead *.json setlocal filetype=json + autocmd BufNewFile,BufRead *.md setlocal filetype=markdown + autocmd BufNewFile,BufRead *.jsx setlocal filetype=javascript.jsx + autocmd BufNewFile,BufRead *.tsx setlocal filetype=typescript.tsx + autocmd BufNewFile,BufRead Dockerfile* setlocal filetype=dockerfile + + " Per-filetype formatting + autocmd FileType python + \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=88 colorcolumn=+1 + autocmd FileType javascript,typescript + \ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=100 colorcolumn=+1 + autocmd FileType go + \ setlocal noexpandtab shiftwidth=4 tabstop=4 textwidth=120 colorcolumn=+1 + autocmd FileType rust + \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=100 colorcolumn=+1 + autocmd FileType c,cpp + \ setlocal expandtab shiftwidth=4 tabstop=4 textwidth=80 colorcolumn=+1 + autocmd FileType html,css + \ setlocal expandtab shiftwidth=2 tabstop=2 + autocmd FileType yaml + \ setlocal expandtab shiftwidth=2 tabstop=2 + autocmd FileType markdown + \ setlocal wrap linebreak spell textwidth=0 colorcolumn=0 conceallevel=2 + autocmd FileType sh + \ setlocal expandtab shiftwidth=2 tabstop=2 textwidth=80 colorcolumn=+1 + autocmd FileType make + \ setlocal noexpandtab shiftwidth=8 tabstop=8 + autocmd FileType json + \ setlocal expandtab shiftwidth=2 tabstop=2 + autocmd FileType dockerfile + \ setlocal expandtab shiftwidth=2 tabstop=2 +augroup END + +" ============================================================================ +" => Performance " ============================================================================ -" Optimize for large files set synmaxcol=200 set ttyfast - -" Don't scan included files for completion — makes Ctrl+n/p much faster (vim-sensible) -set complete-=i - -" Reduce updatetime for better user experience +set complete-=i " don't scan included files — makes Ctrl+n/p much faster set updatetime=300 - -" Don't pass messages to |ins-completion-menu| set shortmess+=c -" Always show the signcolumn (simplified for TTY) if g:is_tty - " In TTY, only show signcolumn when there are signs set signcolumn=auto + set synmaxcol=120 + set lazyredraw else if has("patch-8.1.1564") set signcolumn=number @@ -1027,60 +861,54 @@ else endif " ============================================================================ -" => Project-Specific Settings +" => Project-Local Config " ============================================================================ -" Load project-specific vimrc if it exists -" This allows per-project customization set exrc set secure - -" Session options: exclude 'options' (stale plugin settings) and 'globals'; -" include terminal buffers — compatible with vim-obsession/vim-prosession set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal " ============================================================================ -" => Additional Engineering Utilities +" => Additional Utilities " ============================================================================ -" Quick format entire file +" Quick re-indent entire file nnoremap F gg=G`` -" Quick save all buffers +" Save all open buffers nnoremap wa :wa -" Easier window resizing -nnoremap = :exe "resize " . (winheight(0) * 3/2) -nnoremap - :exe "resize " . (winheight(0) * 2/3) -nnoremap + :exe "vertical resize " . (winwidth(0) * 3/2) -nnoremap _ :exe "vertical resize " . (winwidth(0) * 2/3) +" Window resizing +nnoremap = :exe "resize " . (winheight(0) * 3/2) +nnoremap - :exe "resize " . (winheight(0) * 2/3) +nnoremap + :exe "vertical resize " . (winwidth(0) * 3/2) +nnoremap _ :exe "vertical resize " . (winwidth(0) * 2/3) -" Quick switch between last two files +" Quick-switch between last two files nnoremap -" Clear whitespace on empty lines +" Strip trailing whitespace (manual, no auto-save) nnoremap W :%s/\s\+$//:let @/='' -" Source current file +" Source helpers nnoremap so :source % - -" Edit vimrc quickly nnoremap ev :edit $MYVIMRC - -" Reload vimrc with confirmation echo nnoremap sv :source $MYVIMRC:echo "vimrc reloaded" " Search and replace word under cursor nnoremap * :%s/\<\>//g -" Copy file path to clipboard -nnoremap cp :let @+ = expand("%:p"):echo "Copied path: " . expand("%:p") -nnoremap cf :let @+ = expand("%:t"):echo "Copied filename: " . expand("%:t") +" Copy path / filename to clipboard +nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") +nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") -" Create parent directories on save if they don't exist +" Scratch markdown buffer +map m :e ~/buffer.md + +" Auto-create parent directories on save function! s:MkNonExDir(file, buf) - if empty(getbufvar(a:buf, '&buftype')) && a:file!~#'\v^\w+\:\/' - let dir=fnamemodify(a:file, ':h') + if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/' + let dir = fnamemodify(a:file, ':h') if !isdirectory(dir) call mkdir(dir, 'p') endif @@ -1088,107 +916,16 @@ function! s:MkNonExDir(file, buf) endfunction augroup BWCCreateDir autocmd! - " Guard: is empty for special buffers (NERDTree, Startify, etc.) - autocmd BufWritePre * if !empty(expand('')) | call s:MkNonExDir(expand(''), +expand('')) | endif + autocmd BufWritePre * + \ if !empty(expand('')) | + \ call s:MkNonExDir(expand(''), +expand('')) | + \ endif augroup END -" In-Vim quick reference cheat sheet — ,? opens it, q closes it -function! s:CheatSheet() abort - let l:name = '__ChopsticksCheatSheet__' - let l:winnr = bufwinnr(l:name) - if l:winnr > 0 - execute l:winnr . 'wincmd w' - return - endif - execute 'botright new ' . l:name - setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile - call setline(1, [ - \ '=== chopsticks — Quick Reference ===', - \ '', - \ 'MODES (Vim is modal — the most important concept)', - \ ' Normal Default state. Navigate and run commands.', - \ ' Insert Type text. Enter: i/a/o Leave: Esc or jk', - \ ' Visual Select text. Enter: v/V Leave: Esc', - \ '', - \ 'SURVIVAL (learn these 4 first)', - \ ' Esc / jk Exit insert or visual mode — back to Normal', - \ ' :q! + Enter Quit without saving (emergency exit)', - \ ' ,x Save and quit', - \ ' ,w Save file | Ctrl+s Save (normal + insert)', - \ '', - \ 'NAVIGATION', - \ ' h j k l Left / Down / Up / Right', - \ ' Ctrl+p Fuzzy find file | Ctrl+n File tree', - \ ' Ctrl+o/i Jump back / forward in history', - \ ' ,, Switch to last file', - \ '', - \ 'SEARCH', - \ ' /text Search forward | n next | N prev', - \ ' // Search for visually selected text', - \ ' ,rg Search project contents (ripgrep)', - \ ' ,rG Ripgrep word under cursor', - \ ' ,* Replace word under cursor (file-wide)', - \ '', - \ 'CODE INTELLIGENCE', - \ ' gd Go to definition', - \ ' K Hover documentation', - \ ' [g / ]g Prev / next diagnostic (LSP)', - \ ' [e / ]e Prev / next ALE error', - \ ' ,ca Code action / auto-fix', - \ ' ,rn Rename symbol', - \ ' ,f Format selection | ,F Format whole file', - \ '', - \ 'EDITING', - \ ' i / a / o Insert before / after / on new line', - \ ' gc Toggle comment (works in visual too)', - \ ' u / Ctrl+r Undo / Redo', - \ ' ,p / ,P Paste from system clipboard', - \ ' ,y / ,Y Yank / line-yank to system clipboard', - \ ' s + 2 chars EasyMotion jump anywhere on screen', - \ '', - \ 'GIT', - \ ' ,gs Git status | ,gd Diff | ,gb Blame', - \ ' ,gc Commit | ,gp Push | ,gl Pull', - \ '', - \ 'WINDOWS / PANES', - \ ' Ctrl+h/j/k/l Move between Vim windows or tmux panes', - \ ' ,h / ,l Prev / next buffer', - \ ' ,tv / ,th Open terminal (vertical / horizontal)', - \ '', - \ 'TOOLS', - \ ' ,u Undo tree (visual history)', - \ ' ,tt Tagbar (code structure)', - \ ' ,o File outline (LSP symbols)', - \ '', - \ 'TIP: When confused, press Esc. Then press , and wait 500ms', - \ ' for an interactive guide to ALL keybindings.', - \ ' Re-open this sheet with ,?', - \ '', - \ '(press q to close)', - \ ]) - setlocal nomodifiable readonly - nnoremap q :bd -endfunction -nnoremap ? :call CheatSheet() - " ============================================================================ -" => Debugging Helpers +" => Git Shortcuts " ============================================================================ -" Show syntax highlighting groups for word under cursor -nmap sh :call SynStack() -function! SynStack() - if !exists("*synstack") - return - endif - echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') -endfunc - -" ============================================================================ -" => Git Workflow Enhancements -" ============================================================================ - -" Git shortcuts nnoremap gs :Git status nnoremap gc :Git commit nnoremap gp :Git push @@ -1200,14 +937,10 @@ nnoremap gb :Git blame " => Terminal Integration " ============================================================================ -" Better terminal navigation if has('terminal') - " Open terminal in split nnoremap tv :terminal nnoremap th :terminal ++rows=10 - - " Terminal mode mappings - " Double-Esc to exit terminal mode (single Esc passes through to the running program) + " Double-Esc exits terminal mode (single Esc passes through to the program) tnoremap tnoremap h tnoremap j @@ -1216,15 +949,17 @@ if has('terminal') endif " ============================================================================ -" => Large File Handling +" => Large File Handling (>10 MB) " ============================================================================ -" Disable syntax highlighting and other features for large files (>10MB) let g:LargeFile = 1024 * 1024 * 10 augroup LargeFile autocmd! - " Guard: is empty for special buffers - autocmd BufReadPre * if !empty(expand('')) | let f=getfsize(expand('')) | if f > g:LargeFile || f == -2 | call LargeFileSettings() | endif | endif + autocmd BufReadPre * + \ if !empty(expand('')) | + \ let f = getfsize(expand('')) | + \ if f > g:LargeFile || f == -2 | call LargeFileSettings() | endif | + \ endif augroup END function! LargeFileSettings() @@ -1234,239 +969,123 @@ function! LargeFileSettings() setlocal noswapfile setlocal syntax=OFF let b:ale_enabled = 0 - echo "Large file (>10MB): syntax, undo, and linting disabled for performance." + echo "Large file (>10 MB): syntax, undo, and linting disabled." endfunction -" ============================================================================ -" => TTY and Basic Terminal Optimizations -" ============================================================================ - -" Additional optimizations for TTY/basic terminals if g:is_tty - " Disable syntax highlighting for very large files in TTY augroup ChopstickTTYLargeFile autocmd! - autocmd BufReadPre * if !empty(expand('')) && getfsize(expand('')) > 512000 | setlocal syntax=OFF | endif + autocmd BufReadPre * + \ if !empty(expand('')) && getfsize(expand('')) > 512000 | + \ setlocal syntax=OFF | + \ endif augroup END - " Simpler status line for TTY - set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%) - - " Reduce syntax highlighting complexity in TTY (global is 200, lower here) - set synmaxcol=120 - - " lazyredraw is safe in TTY; avoid globally as it causes CoC float flicker - set lazyredraw -endif - -" Provide helpful message on first run in TTY -if g:is_tty && !exists("g:tty_message_shown") - augroup TTYMessage - autocmd! - autocmd VimEnter * echom "Running in TTY mode - some features disabled for performance" - augroup END - let g:tty_message_shown = 1 + if !exists("g:tty_message_shown") + augroup TTYMessage + autocmd! + autocmd VimEnter * echom "TTY mode — visual features disabled" + augroup END + let g:tty_message_shown = 1 + endif endif " ============================================================================ -" => Which-Key: Keybinding Hints +" => Cheat Sheet (,?) " ============================================================================ -" Show available bindings after with a 500ms pause set timeoutlen=500 -if exists('g:plugs["vim-which-key"]') - " Register after plugins are loaded (autoload functions available at VimEnter) - augroup ChopstickWhichKey - autocmd! - autocmd VimEnter * call which_key#register(',', 'g:which_key_map') - augroup END - - nnoremap :WhichKey ',' - vnoremap :WhichKeyVisual ',' - - " Top-level single-key bindings (w and q also have sub-groups below) - let g:which_key_map = {} - let g:which_key_map['x'] = 'save-and-quit' - let g:which_key_map['F'] = 'format-file' - let g:which_key_map['W'] = 'strip-trailing-whitespace' - let g:which_key_map['m'] = 'scratch-markdown' - let g:which_key_map['n'] = 'nerdtree-find' - let g:which_key_map['o'] = 'outline' - let g:which_key_map['b'] = 'buffers' - let g:which_key_map['h'] = 'prev-buffer' - let g:which_key_map['l'] = 'next-buffer' - let g:which_key_map['*'] = 'search-replace-word' - let g:which_key_map[','] = 'last-file' - let g:which_key_map['y'] = 'clipboard-yank' - let g:which_key_map['Y'] = 'clipboard-yank-line' - let g:which_key_map['p'] = 'clipboard-paste-after' - let g:which_key_map['P'] = 'clipboard-paste-before' - let g:which_key_map['u'] = 'undotree-toggle' - let g:which_key_map['?'] = 'cheat-sheet' - - " [a]LE lint group ([e/]e navigate; aD for detail; ad for diagnostics) - let g:which_key_map['a'] = { - \ 'name': '+ale-lint', - \ 'D': 'ale-detail', - \ 'd': 'diagnostics', - \ } - - " [c]ode / [c]opy group - let g:which_key_map['c'] = { - \ 'name': '+code/copy', - \ 'a': 'code-action-cursor', - \ 'A': 'code-action-source', - \ 'c': 'coc-commands', - \ 'D': 'diagnostics-list', - \ 'l': 'code-lens', - \ 'r': 'coc-list-resume', - \ 'p': 'copy-filepath', - \ 'f': 'copy-filename', - \ } - - " [e]dit / [e]xplore group - let g:which_key_map['e'] = { - \ 'name': '+edit/explore', - \ 'v': 'edit-vimrc', - \ } - - " [g]it group - let g:which_key_map['g'] = { - \ 'name': '+git', - \ 's': 'status', - \ 'c': 'commit', - \ 'p': 'push', - \ 'l': 'pull', - \ 'd': 'diff', - \ 'b': 'blame', - \ 'F': 'git-files-fzf', - \ } - - " [q]uickfix group (also: q = fast quit) - let g:which_key_map['q'] = { - \ 'name': '+quickfix', - \ 'f': 'lsp-fix-current', - \ 'o': 'open-quickfix', - \ 'c': 'close-quickfix', - \ } - - " [r]efactor / [r]ipgrep / [r]eplace group - let g:which_key_map['r'] = { - \ 'name': '+search/refactor', - \ 'n': 'rename', - \ 'g': 'ripgrep', - \ 'G': 'ripgrep-word-under-cursor', - \ 't': 'tags-search', - \ } - - " [s]pell / [s]ource group - let g:which_key_map['s'] = { - \ 'name': '+spell/source', - \ 's': 'toggle-spell', - \ 'n': 'next-spell', - \ 'p': 'prev-spell', - \ 'a': 'add-word', - \ '?': 'suggest', - \ 'v': 'source-vimrc', - \ 'o': 'source-file', - \ 'h': 'syntax-highlight-stack', - \ } - - " [t]ab / [t]erminal group - let g:which_key_map['t'] = { - \ 'name': '+tab/terminal/tagbar', - \ 'n': 'new-tab', - \ 'o': 'tab-only', - \ 'c': 'close-tab', - \ 'm': 'move-tab', - \ 'l': 'last-tab', - \ 'e': 'edit-in-tab', - \ 'v': 'terminal-vertical', - \ 'h': 'terminal-horizontal', - \ 't': 'tagbar-toggle', - \ } - - " [w]orkspace / [w]indow / save group (also: w = fast save) - let g:which_key_map['w'] = { - \ 'name': '+save/window', - \ 'a': 'save-all', - \ 's': 'workspace-symbols', - \ } - - " [c]hange-dir (standalone — cd changes window-local CWD) - let g:which_key_map['cd'] = 'change-local-dir' -endif - -" ============================================================================ -" => Startify: Startup Screen -" ============================================================================ - -if exists('g:plugs["vim-startify"]') - " Static header — no system() calls so vim renders instantly on startup - let g:startify_custom_header = [ - \ ' ███╗ ███╗ ██╗███╗ ██╗ ██████╗ ███████╗ █████╗ ███╗ ███╗ █████╗ ', - \ ' ████╗ ████║███║████╗ ██║██╔════╝ ██╔════╝██╔══██╗████╗ ████║██╔══██╗ ', - \ ' ██╔████╔██║╚██║██╔██╗ ██║██║ ███╗███████╗███████║██╔████╔██║███████║ ', - \ ' ██║╚██╔╝██║ ██║██║╚██╗██║██║ ██║╚════██║██╔══██║██║╚██╔╝██║██╔══██║ ', - \ ' ██║ ╚═╝ ██║ ██║██║ ╚████║╚██████╔╝███████║██║ ██║██║ ╚═╝ ██║██║ ██║ ', - \ ' ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝', +function! s:CheatSheet() abort + let l:name = '__ChopsticksCheatSheet__' + if bufwinnr(l:name) > 0 + execute bufwinnr(l:name) . 'wincmd w' + return + endif + execute 'botright new ' . l:name + setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile + call setline(1, [ + \ '=== chopsticks — Quick Reference ===', \ '', - \ ] - - " Sessions first, then recent files — mirrors dashboard plugin ordering - let g:startify_lists = [ - \ { 'type': 'sessions', 'header': [' Sessions'] }, - \ { 'type': 'files', 'header': [' Recent Files'] }, - \ { 'type': 'dir', 'header': [' Current Dir'] }, - \ { 'type': 'bookmarks', 'header': [' Bookmarks'] }, - \ ] - - " Quick-access bookmarks for common config files - let g:startify_bookmarks = [ - \ {'v': '~/.vimrc'}, - \ {'z': '~/.zshrc'}, - \ {'b': '~/.bashrc'}, - \ ] - - " Session integration - let g:startify_session_persistence = 1 " Auto-save session on quit - let g:startify_session_autoload = 1 " Auto-load Session.vim if present - let g:startify_change_to_vcs_root = 1 " cd to git root on open - let g:startify_fortune_use_unicode = 0 " ASCII only (KISS) - let g:startify_enable_special = 0 " Hide / clutter - - " Show more recent files - let g:startify_files_number = 8 - - " Left padding for list items (gives visual breathing room) - let g:startify_padding_left = 4 - - " Required for NERDTree compatibility (prevents buftype conflicts) - augroup ChopstickStartify - autocmd! - autocmd User Startified setlocal buftype= - augroup END -endif + \ 'MODES', + \ ' Normal Default. Navigate and run commands.', + \ ' Insert Type text. Enter: i/a/o Leave: Esc or jk', + \ ' Visual Select. Enter: v/V Leave: Esc', + \ '', + \ 'SURVIVAL', + \ ' Esc / jk Exit insert or visual mode', + \ ' :q! + Enter Quit without saving', + \ ' ,x Save and quit | ,w Save', + \ ' Ctrl+s Save (normal + insert mode)', + \ '', + \ 'FILES & NAVIGATION', + \ ' Ctrl+p Fuzzy find file (git-aware, FZF)', + \ ' ,e Open netrw file browser', + \ ' ,E Open netrw in vertical split', + \ ' ,b Search open buffers (FZF)', + \ ' ,rg Search project contents (ripgrep)', + \ ' ,rG Ripgrep word under cursor', + \ ' ,, Switch to last file (Ctrl+^)', + \ ' Ctrl+o / i Jump back / forward in history', + \ '', + \ 'CODE INTELLIGENCE (vim-lsp)', + \ ' gd Go to definition', + \ ' gy Go to type definition', + \ ' gi Go to implementation', + \ ' gr Show references', + \ ' K Hover documentation', + \ ' [g / ]g Prev / next LSP diagnostic', + \ ' [e / ]e Prev / next ALE error', + \ ' ,ca Code action', + \ ' ,rn Rename symbol', + \ ' ,f Format buffer (or visual selection)', + \ ' ,o File outline (symbols)', + \ ' ,ws Workspace symbols', + \ '', + \ 'MARKDOWN', + \ ' ,mp Open live browser preview (previm)', + \ ' ,mt Table of contents', + \ ' zr Unfold all headings', + \ ' zm Fold all headings', + \ '', + \ 'EDITING', + \ ' gc Toggle comment (visual mode too)', + \ ' s + 2 chars EasyMotion — jump anywhere on screen', + \ ' ,u Undo tree (visual branch history)', + \ ' ,y / ,Y Yank / yank line to system clipboard', + \ ' ,p / ,P Paste from system clipboard (after / before)', + \ '', + \ 'GIT', + \ ' ,gs Status ,gd Diff ,gb Blame', + \ ' ,gc Commit ,gp Push ,gl Pull', + \ '', + \ 'WINDOWS & PANES', + \ ' Ctrl+h/j/k/l Navigate Vim splits and tmux panes', + \ ' ,h / ,l Prev / next buffer', + \ ' ,tv / ,th Open terminal (vertical / horizontal)', + \ ' Esc Esc Exit terminal mode', + \ '', + \ 'SESSION', + \ ' :Obsess Start tracking session', + \ ' :Obsess! Stop tracking', + \ '', + \ '(press q to close)', + \ ]) + setlocal nomodifiable readonly + nnoremap q :bd +endfunction +nnoremap ? :call CheatSheet() " ============================================================================ -" => IndentLine: Indent Guide Lines (non-TTY only) +" => Debug Helpers " ============================================================================ -if !g:is_tty && exists('g:plugs["indentLine"]') - " Use simple ASCII bar as indent guide - let g:indentLine_char = '|' - let g:indentLine_first_char = '|' - let g:indentLine_showFirstIndentLevel = 1 - - " Disable in certain filetypes where it causes issues - let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'nerdtree', 'markdown'] - let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile'] - - " Conceal level settings (prevent hiding quotes in JSON/markdown) - let g:indentLine_setConceal = 2 - let g:indentLine_concealcursor = '' -endif +" Show syntax highlight stack for word under cursor +nmap sh :call SynStack() +function! SynStack() + if !exists("*synstack") | return | endif + echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') +endfunc " ============================================================================ " End of Configuration diff --git a/QUICKSTART.md b/QUICKSTART.md index d54ba7c..47996de 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -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,26 +66,39 @@ 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) -Ctrl+p Fuzzy find file -Ctrl+n Toggle file tree -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 -,w / ,x Save / Save+quit +,? Open cheat sheet (all bindings in one place) +Esc / jk Exit insert mode → Normal +Ctrl+s Save +Ctrl+p Fuzzy find file +,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,97 +162,36 @@ 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 - ,w Save | ,q Quit | ,x Save+quit + ,w Save | ,q Quit | ,x Save+quit ,wa Save all buffers ,, Switch to last file CODE - gd Go to definition - K Show documentation - [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 + gd Definition | gy Type def | gi Impl | gr References + K Show documentation + [g / ]g Prev / next LSP diagnostic + [e / ]e Prev / next ALE error + ,rn Rename symbol + ,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 - ,* Replace word under cursor (file-wide) - -CLIPBOARD - ,y / ,Y Yank / yank line to system clipboard - ,p / ,P Paste from system clipboard (after / before) +SEARCH + /text Forward | ?text Backward | n next | N prev + // Search visually selected text + ,* Replace word under cursor (file-wide) ``` --- diff --git a/README.md b/README.md index c96800e..a2207b4 100644 --- a/README.md +++ b/README.md @@ -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,77 +81,57 @@ 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 **`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 | -| `,` | 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 ` 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 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 +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 diff --git a/install.sh b/install.sh index 6941bba..aaecf59 100755 --- a/install.sh +++ b/install.sh @@ -16,26 +16,25 @@ 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"; } -warn() { echo -e "${YELLOW}[!]${NC} $1"; } -skip() { echo -e "${CYAN}[--]${NC} $1"; } -fail() { echo -e "${RED}[ERR]${NC} $1"; } -die() { echo -e "${RED}[FATAL]${NC} $1" >&2 - echo " Retry with: ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2 - echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2 - exit 1; } -step() { echo -e "\n${BOLD}==> $1${NC}"; } -info() { echo " $1"; } +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}[FATAL]${NC} $1" >&2 + echo " Retry with: ./install.sh 2>&1 | tee /tmp/chopsticks-install.log" >&2 + echo " Report issues: https://github.com/m1ngsama/chopsticks/issues" >&2 + exit 1; } +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/null; then read -r -p "$1 [y/N] " reply &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 -# 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 " (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)" ;; + x86_64) echo "x86_64" ;; + aarch64|arm64) echo "arm64" ;; + armv7l) echo "armv7" ;; + *) 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)" ;; + x86_64) echo "x64" ;; + aarch64|arm64) echo "arm64" ;; + *) 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/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 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,264 +400,352 @@ 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/null; then - # Interactive terminal: let vim manage the alternate screen properly + # Interactive terminal: vim uses alternate screen; user sees progress vim "$@" /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 - - install_sys() { - local name="$1" 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 (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") - 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" - - # hadolint: no apt package — download binary from GitHub releases - if command -v hadolint >/dev/null 2>&1; then - ok "hadolint (already installed)" - else - 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 \ - "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 - - # marksman: no apt package — download binary from GitHub releases - if command -v marksman >/dev/null 2>&1; then - ok "marksman (already installed)" - else - 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 \ - "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 - - 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_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" - SKIPPED+=("hadolint") - skip "marksman — 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 - +if [[ $HAS_PKG_MGR -eq 0 ]]; then + skip "system tools (no package manager available)" + SKIPPED+=("ripgrep" "fzf" "universal-ctags" "shellcheck" "hadolint" "marksman") else - skip "system tools" - SKIPPED+=("ripgrep" "fzf" "ctags" "shellcheck" "hadolint" "marksman") + +# _do_sys [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 + 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 +} + +# _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 + fail "$name — binary download failed (install manually)" + FAILED+=("$name") + fi +} + +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="" + _do_binary_apt "hadolint" hadolint "$_I_HADOLINT" \ + "https://github.com/hadolint/hadolint/releases/download/${HVER}/hadolint-Linux-${HARCH}" \ + /tmp/chopsticks-hadolint + + # marksman: no apt package — binary from GitHub releases + MARCH=$(arch_linux_x64) + MVER=$(curl -fsSL https://api.github.com/repos/artempyanykh/marksman/releases/latest \ + | grep '"tag_name"' | cut -d'"' -f4 2>/dev/null) || MVER="" + _do_binary_apt "marksman" marksman "$_I_MARKSMAN" \ + "https://github.com/artempyanykh/marksman/releases/download/${MVER}/marksman-linux-${MARCH}" \ + /tmp/chopsticks-marksman + +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 + _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") + 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" "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 - 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 - else - skip "npm tools" - SKIPPED+=("prettier" "markdownlint-cli" "stylelint" "eslint" "typescript") - fi -else +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" 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 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 - 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 yamllint - pip_install sqlfluff - else - skip "Python tools" - SKIPPED+=("black" "isort" "flake8" "pylint" "yamllint" "sqlfluff") - fi -else +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" 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 yamllint + pip_install 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 - GOBIN="$(go env GOPATH)/bin" - export PATH="$PATH:$GOBIN" - - go_install() { - local name="$1" pkg="$2" 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 - go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck - - 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 +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" + + go_install() { + local name="$1" pkg="$2" 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 + go_install staticcheck "honnef.co/go/tools/cmd/staticcheck@latest" staticcheck + + echo "$PATH" | grep -q "$GOBIN" || \ + warn "Add Go binaries to PATH: export PATH=\"\$PATH:$GOBIN\"" 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' + cat >> "$TMUX_CONF" << 'TMUXEOF' # vim-tmux-navigator: seamless Ctrl+h/j/k/l navigation between vim and tmux is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +(\S+\/)?g?(view|n?vim?x?)(diff)?$'" @@ -626,39 +754,26 @@ bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j' 'select-pane -D' 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' TMUXEOF - ok "vim-tmux-navigator bindings appended to ~/.tmux.conf" - warn "Reload tmux config now: tmux source-file ~/.tmux.conf" - 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") + ok "vim-tmux-navigator bindings appended to ~/.tmux.conf" + warn "Reload tmux config now: tmux source-file ~/.tmux.conf" + 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") 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 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:"