diff --git a/.vimrc b/.vimrc index b47c76a..98c2343 100644 --- a/.vimrc +++ b/.vimrc @@ -2,1267 +2,36 @@ " 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 -" ============================================================================ - -filetype on -filetype plugin on -filetype indent on -syntax on - -set number -set relativenumber - -if !g:is_tty - set cursorline -endif - -set nobackup -set scrolloff=10 -set nowrap -set incsearch -set ignorecase -set smartcase -set showcmd -set showmode -set hlsearch -set history=1000 -set wildmenu -set wildmode=list:longest -set wildignorecase -set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx -set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/* -" path+=** removed: hangs on large repos (node_modules); use Ctrl+p (FZF) instead -set mouse=a -set encoding=utf-8 -set foldmethod=indent -set foldlevel=99 -set splitbelow -set splitright -set backspace=indent,eol,start -set nrformats-=octal -set autoread -set cmdheight=1 -set hidden -set whichwrap+=<,>,h,l -set magic -set showmatch -set mat=2 -set noerrorbells -set novisualbell -set t_vb= -set ttimeout -set ttimeoutlen=10 - -if $COLORTERM ==# 'gnome-terminal' - set t_Co=256 -endif - -if has("gui_running") - set guioptions-=T - set guioptions-=e - set t_Co=256 - set guitablabel=%M\ %t -endif - -set display+=lastline -set ffs=unix,dos,mac -set nowb -set noswapfile - -if has('persistent_undo') - set undofile - let &undodir = expand('~/.vim/.undo') - silent! call mkdir(&undodir, 'p', 0700) -endif - -" ============================================================================ -" => 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' - augroup PlugBootstrap - autocmd! - autocmd VimEnter * PlugInstall --sync | source $MYVIMRC - augroup END -endif - -call plug#begin('~/.vim/plugged') - -" ── Navigation & Search ─────────────────────────────────────────────────────── -Plug 'junegunn/fzf', { 'do': { -> fzf#install() } } -Plug 'junegunn/fzf.vim' - -" ── Git ─────────────────────────────────────────────────────────────────────── -Plug 'tpope/vim-fugitive' -Plug 'airblade/vim-gitgutter' - -" ── Editing ─────────────────────────────────────────────────────────────────── -Plug 'tpope/vim-surround' -Plug 'tpope/vim-commentary' -Plug 'tpope/vim-repeat' -Plug 'tpope/vim-unimpaired' -Plug 'tpope/vim-sleuth' -Plug 'wellle/targets.vim' -Plug 'jiangmiao/auto-pairs' -Plug 'easymotion/vim-easymotion' - -" ── Linting & Formatting ────────────────────────────────────────────────────── -Plug 'dense-analysis/ale' - -" ── LSP + Completion (no Node.js required) ──────────────────────────────────── -Plug 'prabirshrestha/vim-lsp' -Plug 'mattn/vim-lsp-settings' -Plug 'prabirshrestha/asyncomplete.vim' -Plug 'prabirshrestha/asyncomplete-lsp.vim' - -" ── Language Syntax ─────────────────────────────────────────────────────────── -Plug 'pangloss/vim-javascript' -Plug 'HerringtonDarkholme/yats.vim' -Plug 'preservim/vim-markdown' -Plug 'fatih/vim-go' - -" ── Markdown Preview & Writing ──────────────────────────────────────────────── -Plug 'previm/previm' -Plug 'junegunn/goyo.vim' -Plug 'junegunn/limelight.vim' - -" ── UI ──────────────────────────────────────────────────────────────────────── -Plug 'mbbill/undotree' -Plug 'mhinz/vim-startify' -Plug 'lifepillar/vim-solarized8' -if !g:is_tty - Plug 'Yggdroot/indentLine' -endif - -" ── Session ─────────────────────────────────────────────────────────────────── -Plug 'tpope/vim-obsession' - -" ── tmux ────────────────────────────────────────────────────────────────────── -Plug 'christoomey/vim-tmux-navigator' - -call plug#end() - -" ============================================================================ -" => Colors (Solarized Dark — matches tmux palette) -" ============================================================================ - -if g:has_true_color && has('termguicolors') && !g:is_tty - let &t_8f = "\[38;2;%lu;%lu;%lum" - let &t_8b = "\[48;2;%lu;%lu;%lum" - set termguicolors -endif - -set background=dark - -if !g:is_tty - try - colorscheme solarized8 - catch - colorscheme default - endtry -else - colorscheme default -endif - -if has("gui_running") - if has("gui_gtk2") || has("gui_gtk3") - set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12 - elseif has("gui_win32") - set guifont=Consolas:h11:cANSI - endif -endif - -" ============================================================================ -" => Text, Tab and Indent -" ============================================================================ - -if g:is_tty - set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_ -else - set listchars=tab:→\ ,trail:·,extends:▸,precedes:◂,nbsp:· -endif - -set expandtab -set smarttab -set shiftwidth=4 -set tabstop=4 -set lbr -set tw=0 -set autoindent -set smartindent - -" ============================================================================ -" => Key Mappings -" ============================================================================ - -let mapleader = "," - -" Saving / quitting -nnoremap w :w! -nnoremap q :q -nnoremap x :x - -" Clear search highlight -nnoremap :noh - -" Buffer navigation -nnoremap bd :Bclose -nnoremap ba :bufdo bd -nnoremap l :bnext -nnoremap h :bprevious - -" Tab management -nnoremap tn :tabnew -nnoremap to :tabonly -nnoremap tc :tabclose -nnoremap tm :tabmove -nnoremap t :tabnext - -let g:lasttab = 1 -nnoremap tl :exe "tabn ".g:lasttab -augroup ChopstickTabHistory - autocmd! - autocmd TabLeave * let g:lasttab = tabpagenr() -augroup END - -nnoremap te :tabedit =expand("%:p:h")/ -nnoremap cd :lcd %:p:h:pwd - -" File browser (netrw — built-in, no plugins) -nnoremap e :Explore -nnoremap E :Vexplore - -" Remap 0 to first non-blank -nnoremap 0 ^ - -" Reselect last paste -nnoremap gV `[v`] - -" Command-line history -cnoremap -cnoremap - -" Move lines (Alt+j / Alt+k in both normal and visual mode) -nnoremap :m .+1== -nnoremap :m .-2== -vnoremap :m '>+1gv=gv -vnoremap :m '<-2gv=gv - -" Spell checking -nnoremap ss :setlocal spell! -nnoremap sn ]s -nnoremap sp [s -nnoremap sa zg -nnoremap s? z= - -" Toggle modes -set pastetoggle= -nnoremap :set invnumber -nnoremap :set invrelativenumber -nnoremap :set list! - -" Folding -nnoremap za - -" Consistency with D and C -nnoremap Y y$ -nnoremap Q - -" Ergonomic escape -inoremap jk - -" Indent keeps visual selection -vnoremap < >gv - -" Search: centre result on screen -nnoremap n nzzzv -nnoremap N Nzzzv - -" Search for visual selection -vnoremap // y/\V=escape(@",'/\') - -" Save from any mode -nnoremap :w -inoremap :w - -" Scroll keeping cursor centred -nnoremap zz -nnoremap zz - -" System clipboard -if has('clipboard') - nnoremap y "+y - vnoremap y "+y - nnoremap Y "+Y - nnoremap p "+p - nnoremap P "+P -endif - -" Quickfix -nnoremap qo :copen -nnoremap qc :cclose - -" Auto-equalise splits on window resize -augroup ChopstickResize - autocmd! - autocmd VimResized * wincmd = -augroup END - -" ============================================================================ -" => 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) +" Modular layout — each file in modules/ is self-contained: +" env.vim Environment detection (TTY, truecolor) +" plugins.vim vim-plug bootstrap + all 30 plugin declarations +" core.vim General settings, keymaps, performance, indentation +" ui.vim Colorscheme, statusline, startify, indentline +" editing.vim EasyMotion, yank highlight, search auto-clear +" navigation.vim FZF, netrw, window management, terminal +" lsp.vim vim-lsp + asyncomplete configuration +" lint.vim ALE linting and format-on-save +" git.vim Fugitive, GitGutter, conflict navigation +" writing.vim Markdown, previm, goyo + limelight zen mode +" languages.vim vim-go config, per-filetype settings +" tools.vim Cheat sheet, run file, sudo save, helpers -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' +let g:chopsticks_dir = fnamemodify(resolve(expand('')), ':h') -" ============================================================================ -" => FZF -" ============================================================================ - -" Ctrl+p: git-aware file search (GFiles inside repo, Files outside) -function! s:SmartFiles() abort - if isdirectory('.git') || finddir('.git', '.;') !=# '' - GFiles - else - Files - endif +function! s:load(mod) abort + execute 'source ' . g:chopsticks_dir . '/modules/' . a:mod . '.vim' endfunction -if exists('g:plugs["fzf.vim"]') - nnoremap :call SmartFiles() - nnoremap b :Buffers - nnoremap rg :Rg - nnoremap rG :RgWord - nnoremap rt :Tags - nnoremap gF :GFiles - nnoremap fh :History - nnoremap fc :Commands - nnoremap fm :Marks - nnoremap fl :BLines - nnoremap fL :Lines - nnoremap f/ :History/ - nnoremap f: :History: -endif - -let g:fzf_layout = { 'down': '40%' } - -if g:is_tty - let g:fzf_preview_window = [] -else - let g:fzf_preview_window = ['right:50%', 'ctrl-/'] -endif - -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) - 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) - command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), 0) -endif - -" RgWord: fixed-string search for word under cursor (flags before --) -if g:is_tty - command! -bang -nargs=* RgWord - \ call fzf#vim#grep( - \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' - \ .shellescape(expand('')), 1, 0) -else - command! -bang -nargs=* RgWord - \ call fzf#vim#grep( - \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' - \ .shellescape(expand('')), 1, fzf#vim#with_preview(), 0) -endif - -" ============================================================================ -" => GitGutter -" ============================================================================ - -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 (async linting + format-on-save) -" ============================================================================ - -let g:ale_disable_lsp = 1 - -let g:ale_linters = { -\ 'python': ['flake8', 'pylint'], -\ 'javascript': ['eslint'], -\ 'typescript': ['eslint'], -\ 'go': ['staticcheck'], -\ 'rust': ['cargo'], -\ 'c': ['cc'], -\ 'sh': ['shellcheck'], -\ 'yaml': ['yamllint'], -\ 'dockerfile': ['hadolint'], -\ 'css': ['stylelint'], -\ 'scss': ['stylelint'], -\ 'markdown': ['markdownlint'], -\ 'sql': ['sqlfluff'], -\} - -let g:ale_fixers = { -\ '*': ['remove_trailing_lines', 'trim_whitespace'], -\ 'python': ['black', 'isort'], -\ 'javascript': ['prettier', 'eslint'], -\ 'typescript': ['prettier', 'eslint'], -\ 'go': ['goimports'], -\ 'rust': ['rustfmt'], -\ 'c': ['clang-format'], -\ 'json': ['prettier'], -\ 'yaml': ['prettier'], -\ 'html': ['prettier'], -\ 'css': ['prettier'], -\ 'scss': ['prettier'], -\ 'less': ['prettier'], -\ 'markdown': ['prettier'], -\ 'sql': ['sqlfluff'], -\} - -let g:ale_fix_on_save = 1 -let g:ale_python_isort_options = '--profile black' -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 - -if exists('g:plugs["ale"]') - nnoremap [e :ALEPrevious - nnoremap ]e :ALENext - nnoremap aD :ALEDetail -endif - -" ============================================================================ -" => vim-go -" ============================================================================ - -" vim-lsp (gopls) handles all Go intelligence; vim-go is for syntax only -let g:go_gopls_enabled = 0 -let g:go_code_completion_enabled = 0 -let g:go_fmt_autosave = 0 -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 - -" ============================================================================ -" => vim-lsp (primary LSP backend — pure VimScript, no Node.js) -" ============================================================================ - -let g:lsp_settings_filetype_python = ['pylsp'] -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': '>'} - -if has('patch-8.1.1517') - set completeopt=menuone,noinsert,noselect,popup -else - set completeopt=menuone,noinsert,noselect -endif -set pumheight=15 -let g:asyncomplete_auto_popup = 1 -let g:asyncomplete_auto_completeopt = 0 -let g:asyncomplete_popup_delay = 50 - -" 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 (side window) -if exists('g:plugs["vim-markdown"]') - nnoremap mt :Toc -endif - -" ============================================================================ -" => previm (Markdown browser preview) -" ============================================================================ - -" mp open live-reloading preview in browser -if has('macunix') - let g:previm_open_cmd = '/usr/bin/open' -elseif executable('xdg-open') - let g:previm_open_cmd = 'xdg-open' -endif -if exists('g:plugs["previm"]') - nnoremap mp :PrevimOpen -endif -let g:previm_enable_realtime = 1 - -" ============================================================================ -" => Goyo + Limelight (zen mode for focused writing) -" ============================================================================ - -if exists('g:plugs["goyo.vim"]') - let g:goyo_width = 80 - let g:goyo_height = '85%' - nnoremap zen :Goyo - - function! s:goyo_enter() - if exists('g:plugs["limelight.vim"]') | Limelight | endif - set wrap linebreak scrolloff=999 - endfunction - function! s:goyo_leave() - if exists('g:plugs["limelight.vim"]') | Limelight! | endif - set nowrap nolinebreak scrolloff=10 - endfunction - - augroup ChopstickGoyo - autocmd! - autocmd User GoyoEnter nested call s:goyo_enter() - autocmd User GoyoLeave nested call s:goyo_leave() - augroup END -endif - -let g:limelight_conceal_ctermfg = 240 -let g:limelight_conceal_guifg = '#586e75' - -" ============================================================================ -" => EasyMotion -" ============================================================================ - -let g:EasyMotion_do_mapping = 0 -let g:EasyMotion_smartcase = 1 - -if exists('g:plugs["vim-easymotion"]') - " s + two chars: jump anywhere on screen - nmap s (easymotion-overwin-f2) - " Line motions - nmap j (easymotion-j) - nmap k (easymotion-k) -endif - -" ============================================================================ -" => UndoTree -" ============================================================================ - -if exists('g:plugs["undotree"]') - nnoremap :UndotreeToggle - nnoremap u :UndotreeToggle -endif - -" ============================================================================ -" => IndentLine (non-TTY only) -" ============================================================================ - -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 - -" ============================================================================ -" => Startify (startup screen + session management) -" ============================================================================ - -if exists('g:plugs["vim-startify"]') - let g:startify_custom_header = [ - \ ' ██████╗██╗ ██╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗███████╗', - \ ' ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝', - \ ' ██║ ███████║██║ ██║██████╔╝███████╗ ██║ ██║██║ █████╔╝ ███████╗', - \ ' ██║ ██╔══██║██║ ██║██╔═══╝ ╚════██║ ██║ ██║██║ ██╔═██╗ ╚════██║', - \ ' ╚██████╗██║ ██║╚██████╔╝██║ ███████║ ██║ ██║╚██████╗██║ ██╗███████║', - \ ' ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝', - \ '', - \ ] - - let g:startify_lists = [ - \ { 'type': 'sessions', 'header': [' Sessions'] }, - \ { 'type': 'files', 'header': [' Recent Files'] }, - \ { 'type': 'dir', 'header': [' Current Dir'] }, - \ { 'type': 'bookmarks', 'header': [' Bookmarks'] }, - \ ] - - let g:startify_bookmarks = [{'v': '~/.vimrc'}] - if filereadable(expand('~/.zshrc')) - call add(g:startify_bookmarks, {'z': '~/.zshrc'}) - endif - if filereadable(expand('~/.bashrc')) - call add(g:startify_bookmarks, {'b': '~/.bashrc'}) - endif - if filereadable(expand('~/.config/fish/config.fish')) - call add(g:startify_bookmarks, {'f': '~/.config/fish/config.fish'}) - endif - - 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 - - " 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 - -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() -augroup END -call s:SLDefineColors() - -" 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 - -" ALE error/warning count -function! SLAle() abort - if !exists('*ale#statusline#Count') | return '' | endif - let l:c = ale#statusline#Count(bufnr('')) - let l:e = l:c.error + l:c.style_error - let l:w = l:c.warning + l:c.style_warning - if l:e == 0 && l:w == 0 | return '' | endif - return printf(' E:%d W:%d ', l:e, l:w) -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 .= '%#SLFlag#' . SLAle() - 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 -" ============================================================================ - -function! HasPaste() - if &paste | return 'PASTE MODE ' | endif - return '' -endfunction - -" Close buffer without closing the window -command! Bclose call BufcloseCloseIt() -function! BufcloseCloseIt() - 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) - endif -endfunction - -" Strip trailing whitespace without moving cursor -fun! CleanExtraSpaces() - let save_cursor = getpos(".") - let old_query = getreg('/') - silent! %s/\s\+$//e - call setpos('.', save_cursor) - call setreg('/', old_query) -endfun - -" Toggle between absolute and relative numbers -function! ToggleNumber() - if(&relativenumber == 1) - set norelativenumber - set number - else - set relativenumber - endif -endfunc - -" ============================================================================ -" => Auto Commands -" ============================================================================ - -" Suppress comment continuation on Enter / o / O -augroup ChopstickFormatOptions - autocmd! - autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions+=j -augroup END - -" Auto-disable paste mode on leaving insert -augroup ChopstickPaste - autocmd! - autocmd InsertLeave * set nopaste -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 -" ============================================================================ - -set synmaxcol=200 -set ttyfast -set lazyredraw -set complete-=i -set updatetime=300 -set shortmess+=c - -if g:is_tty - set signcolumn=auto - set synmaxcol=120 -else - if has("patch-8.1.1564") - set signcolumn=number - else - set signcolumn=yes - endif -endif - -" ============================================================================ -" => Project-Local Config -" ============================================================================ - -set exrc -set secure -set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal - -" ============================================================================ -" => Additional Utilities -" ============================================================================ - -" Quick re-indent entire file -nnoremap F gg=G`` - -" Save all open buffers -nnoremap wa :wa - -" 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 -nnoremap - -" Strip trailing whitespace (manual, no auto-save) -nnoremap W :%s/\s\+$//:let @/='' - -" Source helpers -nnoremap so :if &filetype ==# 'vim' source % echo "Sourced " . expand('%') else echo "Not a vim file" endif -nnoremap ev :edit $MYVIMRC -nnoremap sv :source $MYVIMRC:echo "vimrc reloaded" - -" Search and replace word under cursor -nnoremap * :%s/\<\>//g - -" Copy path / filename to clipboard -if has('clipboard') - nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") - nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") -endif - -" Scratch markdown buffer -nnoremap ms :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 !isdirectory(dir) - call mkdir(dir, 'p') - endif - endif -endfunction -augroup BWCCreateDir - autocmd! - autocmd BufWritePre * - \ if !empty(expand('')) | - \ call s:MkNonExDir(expand(''), +expand('')) | - \ endif -augroup END - -" ============================================================================ -" => Git Shortcuts -" ============================================================================ - -if exists('g:plugs["vim-fugitive"]') - nnoremap gs :Git status - nnoremap gc :Git commit - nnoremap gp :Git push - nnoremap gl :Git pull - nnoremap gd :Gdiffsplit - nnoremap gb :Git blame -endif - -" ============================================================================ -" => Terminal Integration -" ============================================================================ - -if has('terminal') - nnoremap tv :terminal - nnoremap th :terminal ++rows=10 - " Double-Esc exits terminal mode (single Esc passes through to the program) - tnoremap - tnoremap h - tnoremap j - tnoremap k - tnoremap l -endif - -" ============================================================================ -" => Large File Handling (>10 MB) -" ============================================================================ - -let g:LargeFile = 1024 * 1024 * 10 -augroup LargeFile - autocmd! - autocmd BufReadPre * - \ if !empty(expand('')) | - \ let f = getfsize(expand('')) | - \ if f > g:LargeFile || f == -2 | call LargeFileSettings() | endif | - \ endif -augroup END - -function! LargeFileSettings() - setlocal bufhidden=unload - setlocal undolevels=-1 - setlocal noswapfile - setlocal syntax= - let b:ale_enabled = 0 - echo "Large file (>10 MB): syntax, undo, and linting disabled." -endfunction - -if g:is_tty - augroup ChopstickTTYLargeFile - autocmd! - autocmd BufReadPre * - \ if !empty(expand('')) && getfsize(expand('')) > 512000 | - \ setlocal syntax= | - \ endif - augroup END - - 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 - -" ============================================================================ -" => Cheat Sheet (,?) -" ============================================================================ - -set timeoutlen=500 - -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 ===', - \ '', - \ 'SURVIVAL', - \ ' Esc / jk Exit insert or visual mode', - \ ' :q! + Enter Quit without saving', - \ ' ,x Save+quit ,w Save Ctrl+s Save (any mode)', - \ ' :w!! Sudo save (when you forgot to open as root)', - \ '', - \ 'FILES & SEARCH', - \ ' Ctrl+p Fuzzy find file (git-aware)', - \ ' ,e / ,E File browser / vertical split', - \ ' ,b Search open buffers', - \ ' ,rg Search project contents (ripgrep)', - \ ' ,rG Ripgrep word under cursor', - \ ' ,fh Recent files history', - \ ' ,fl / ,fL Search lines in buffer / all buffers', - \ ' ,fc Commands | ,fm Marks', - \ ' ,f/ / ,f: Search / command history', - \ ' ,, Switch to last file (Ctrl+^)', - \ '', - \ 'CODE INTELLIGENCE (vim-lsp)', - \ ' gd Definition gy Type def gi Impl gr Refs', - \ ' K Hover documentation', - \ ' [g / ]g Prev / next LSP diagnostic', - \ ' [e / ]e Prev / next ALE error', - \ ' ,ca Code action ,rn Rename ,f Format', - \ ' ,o File outline ,ws Workspace symbols', - \ ' ,cr Run current file', - \ '', - \ 'MARKDOWN & WRITING', - \ ' ,mp Live browser preview (previm)', - \ ' ,mt Table of contents', - \ ' ,zen Zen mode (Goyo + Limelight)', - \ ' zr / zm Unfold / fold all headings', - \ '', - \ 'EDITING', - \ ' gc Toggle comment (visual mode too)', - \ ' s + 2 chars EasyMotion jump anywhere', - \ ' ,u / F5 Undo tree', - \ ' ,y / ,Y Yank to system clipboard', - \ ' Alt+j / Alt+k Move line down / up', - \ ' ,F Re-indent file ,W Strip trailing whitespace', - \ ' ,* Search and replace word under cursor', - \ '', - \ 'GIT', - \ ' ,gs Status ,gd Diff ,gb Blame', - \ ' ,gc Commit ,gp Push ,gl Pull', - \ ' [x / ]x Navigate git conflict markers', - \ '', - \ 'WINDOWS & PANES', - \ ' Ctrl+h/j/k/l Navigate splits and tmux panes', - \ ' ,h / ,l Prev / next buffer ,bd Close buffer', - \ ' ,z Maximize / restore current window', - \ ' ,tv / ,th Terminal (vertical / horizontal)', - \ ' Esc Esc Exit terminal mode', - \ ' ,= / ,- Resize height ,+ / ,_ Resize width', - \ '', - \ 'QUICKFIX', - \ ' ,qo / ,qc Open / close quickfix', - \ ' ]q / [q Next / prev quickfix entry', - \ '', - \ 'UTILITIES', - \ ' ,ev / ,sv Edit / reload ~/.vimrc', - \ ' ,cp / ,cf Copy file path / filename to clipboard', - \ ' ,ms Scratch buffer ,cd CD to file dir', - \ ' ,ss Toggle spell ,so Source current vim file', - \ ' F2 Paste F3 Line# F4 Relative# F6 Invisible', - \ '', - \ '(press q to close)', - \ ]) - setlocal nomodifiable readonly - nnoremap q :bd -endfunction -nnoremap ? :call CheatSheet() - -" ============================================================================ -" => Yank Highlight (flash yanked region for visual feedback) -" ============================================================================ - -if exists('##TextYankPost') && has('timers') - function! s:YankHighlight() abort - if v:event.operator !=# 'y' | return | endif - let l:m = matchadd('IncSearch', - \ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1)) - call timer_start(150, {-> matchdelete(l:m)}) - endfunction - augroup ChopstickYankHL - autocmd! - autocmd TextYankPost * call s:YankHighlight() - augroup END -endif - -" ============================================================================ -" => Auto-Clear Search Highlight (clear after idle) -" ============================================================================ - -augroup ChopstickSearchHL - autocmd! - autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif -augroup END - -" ============================================================================ -" => Run Current File (,cr) -" ============================================================================ - -function! s:RunFile() abort - write - let l:ft = &filetype - let l:file = shellescape(expand('%:p')) - if l:ft ==# 'python' | execute '!python3 ' . l:file - elseif l:ft ==# 'javascript' | execute '!node ' . l:file - elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file - elseif l:ft ==# 'go' | execute '!go run ' . l:file - elseif l:ft ==# 'rust' | execute '!cargo run' - elseif l:ft ==# 'sh' | execute '!bash ' . l:file - elseif l:ft ==# 'c' | execute '!gcc -o /tmp/a.out ' . l:file . ' && /tmp/a.out' - elseif l:ft ==# 'lua' | execute '!lua ' . l:file - elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file - elseif l:ft ==# 'perl' | execute '!perl ' . l:file - else | echo 'No runner for filetype: ' . l:ft - endif -endfunction -nnoremap cr :call RunFile() - -" ============================================================================ -" => Git Conflict Navigation ([x / ]x) -" ============================================================================ - -nnoremap ]x /^\(<<<<<<<\\|=======\\|>>>>>>>\) -nnoremap [x ?^\(<<<<<<<\\|=======\\|>>>>>>>\) - -" ============================================================================ -" => Window Maximize Toggle (,z) -" ============================================================================ - -function! s:ToggleMaximize() abort - if exists('t:maximize_session') - execute t:maximize_session - unlet t:maximize_session - else - let t:maximize_session = winrestcmd() - resize | vertical resize - endif -endfunction -nnoremap z :call ToggleMaximize() - -" ============================================================================ -" => Sudo Save (:w!! when you forget to open as root) -" ============================================================================ - -cnoremap w!! w !sudo tee > /dev/null % - -" ============================================================================ -" => QuickFix Improvements -" ============================================================================ - -augroup ChopstickQF - autocmd! - autocmd QuickFixCmdPost [^l]* cwindow - autocmd QuickFixCmdPost l* lwindow -augroup END - -nnoremap ]q :cnext -nnoremap [q :cprev - -" ============================================================================ -" => Debug Helpers -" ============================================================================ - -nnoremap sh :call SynStack() -function! SynStack() - if !exists("*synstack") | return | endif - echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') -endfunc - -" ============================================================================ -" End of Configuration -" ============================================================================ +call s:load('env') +call s:load('plugins') +call s:load('core') +call s:load('ui') +call s:load('editing') +call s:load('navigation') +call s:load('lsp') +call s:load('lint') +call s:load('git') +call s:load('writing') +call s:load('languages') +call s:load('tools') diff --git a/QUICKSTART.md b/QUICKSTART.md index 30fbcfc..4de1bbe 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -26,6 +26,7 @@ Vim is **modal** — the keyboard behaves differently depending on which mode yo | `,w` or `Ctrl+s` | Save | Once in Normal mode, press `,?` to open the cheat sheet. +Run `:ChopsticksLearn` for a full interactive tutorial. --- diff --git a/README.md b/README.md index 1d548ab..e8d67cf 100644 --- a/README.md +++ b/README.md @@ -260,10 +260,55 @@ let g:ale_python_black_options = '--line-length=100' --- +## Learn + +New to chopsticks? Run the interactive tutorial: + +```vim +:ChopsticksLearn +``` + +It walks you through every feature — file finding, LSP, git, zen mode, +and more — with exercises you can try in real time. (For Vim basics, run +`:Tutor` first.) + +--- + +## Architecture + +chopsticks follows the Unix philosophy: each concern lives in its own file. + +``` +~/.vim/ +├── .vimrc ← thin loader (sources modules in order) +├── modules/ +│ ├── env.vim ← environment detection (TTY, truecolor) +│ ├── plugins.vim ← vim-plug bootstrap + 30 plugin declarations +│ ├── core.vim ← general settings, keymaps, performance +│ ├── ui.vim ← colorscheme, statusline, startify +│ ├── editing.vim ← EasyMotion, yank highlight, search auto-clear +│ ├── navigation.vim ← FZF, netrw, window management, terminal +│ ├── lsp.vim ← vim-lsp + asyncomplete +│ ├── lint.vim ← ALE linting and format-on-save +│ ├── git.vim ← Fugitive, GitGutter, conflict navigation +│ ├── writing.vim ← Markdown, previm, goyo + limelight +│ ├── languages.vim ← vim-go, per-filetype settings +│ └── tools.vim ← cheat sheet, run file, helpers +└── tutor/ + └── chopsticks.tutor ← interactive tutorial +``` + +Each module is self-contained. Want to disable git integration? Remove +`call s:load('git')` from `.vimrc`. Want to add your own module? Create +`modules/mine.vim` and add `call s:load('mine')`. + +--- + ## Customization -Edit `~/.vimrc` directly (`,ev` opens it, `,sv` reloads). Per-project overrides -go in a `.vimrc` at your project root. +Edit `~/.vimrc` directly (`,ev` opens it, `,sv` reloads), or edit individual +modules under `modules/`. Per-project overrides go in a `.vimrc` at your +project root. --- diff --git a/modules/core.vim b/modules/core.vim new file mode 100644 index 0000000..56cfb1c --- /dev/null +++ b/modules/core.vim @@ -0,0 +1,220 @@ +" core.vim — general settings, basic keymaps, performance, indentation + +filetype on +filetype plugin on +filetype indent on +syntax on + +set number +set relativenumber + +if !g:is_tty + set cursorline +endif + +set nobackup +set scrolloff=10 +set nowrap +set incsearch +set ignorecase +set smartcase +set showcmd +set showmode +set hlsearch +set history=1000 +set wildmenu +set wildmode=list:longest +set wildignorecase +set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx +set wildignore+=*/node_modules/*,*/.git/*,*/__pycache__/*,*/dist/*,*/build/* +set mouse=a +set encoding=utf-8 +set foldmethod=indent +set foldlevel=99 +set splitbelow +set splitright +set backspace=indent,eol,start +set nrformats-=octal +set autoread +set cmdheight=1 +set hidden +set whichwrap+=<,>,h,l +set magic +set showmatch +set mat=2 +set noerrorbells +set novisualbell +set t_vb= +set ttimeout +set ttimeoutlen=10 + +if $COLORTERM ==# 'gnome-terminal' + set t_Co=256 +endif + +if has("gui_running") + set guioptions-=T + set guioptions-=e + set t_Co=256 + set guitablabel=%M\ %t +endif + +set display+=lastline +set ffs=unix,dos,mac +set nowb +set noswapfile + +if has('persistent_undo') + set undofile + let &undodir = expand('~/.vim/.undo') + silent! call mkdir(&undodir, 'p', 0700) +endif + +" ── Text, Tab and Indent ──────────────────────────────────────────────────── + +if g:is_tty + set listchars=tab:>-,trail:.,extends:>,precedes:<,nbsp:_ +else + set listchars=tab:→\ ,trail:·,extends:▸,precedes:◂,nbsp:· +endif + +set expandtab +set smarttab +set shiftwidth=4 +set tabstop=4 +set lbr +set tw=0 +set autoindent +set smartindent + +" ── Leader ────────────────────────────────────────────────────────────────── + +let mapleader = "," + +" ── Basic Keymaps ─────────────────────────────────────────────────────────── + +nnoremap w :w! +nnoremap q :q +nnoremap x :x + +nnoremap :noh + +nnoremap bd :Bclose +nnoremap ba :bufdo bd +nnoremap l :bnext +nnoremap h :bprevious + +nnoremap tn :tabnew +nnoremap to :tabonly +nnoremap tc :tabclose +nnoremap tm :tabmove +nnoremap t :tabnext + +let g:lasttab = 1 +nnoremap tl :exe "tabn ".g:lasttab +augroup ChopstickTabHistory + autocmd! + autocmd TabLeave * let g:lasttab = tabpagenr() +augroup END + +nnoremap te :tabedit =expand("%:p:h")/ +nnoremap cd :lcd %:p:h:pwd + +nnoremap 0 ^ +nnoremap gV `[v`] + +cnoremap +cnoremap + +nnoremap :m .+1== +nnoremap :m .-2== +vnoremap :m '>+1gv=gv +vnoremap :m '<-2gv=gv + +nnoremap ss :setlocal spell! +nnoremap sn ]s +nnoremap sp [s +nnoremap sa zg +nnoremap s? z= + +set pastetoggle= +nnoremap :set invnumber +nnoremap :set invrelativenumber +nnoremap :set list! + +nnoremap za + +nnoremap Y y$ +nnoremap Q + +inoremap jk + +vnoremap < >gv + +nnoremap n nzzzv +nnoremap N Nzzzv + +vnoremap // y/\V=escape(@",'/\') + +nnoremap :w +inoremap :w + +nnoremap zz +nnoremap zz + +if has('clipboard') + nnoremap y "+y + vnoremap y "+y + nnoremap Y "+Y + nnoremap p "+p + nnoremap P "+P +endif + +nnoremap qo :copen +nnoremap qc :cclose + +augroup ChopstickResize + autocmd! + autocmd VimResized * wincmd = +augroup END + +" ── Performance ───────────────────────────────────────────────────────────── + +set synmaxcol=200 +set ttyfast +set lazyredraw +set complete-=i +set updatetime=300 +set shortmess+=c + +if g:is_tty + set signcolumn=auto + set synmaxcol=120 +else + if has("patch-8.1.1564") + set signcolumn=number + else + set signcolumn=yes + endif +endif + +" ── Project-Local Config ──────────────────────────────────────────────────── + +set exrc +set secure +set sessionoptions=blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal + +" ── Format Options ────────────────────────────────────────────────────────── + +augroup ChopstickFormatOptions + autocmd! + autocmd FileType * setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions+=j +augroup END + +augroup ChopstickPaste + autocmd! + autocmd InsertLeave * set nopaste +augroup END + +set timeoutlen=500 diff --git a/modules/editing.vim b/modules/editing.vim new file mode 100644 index 0000000..e49cc52 --- /dev/null +++ b/modules/editing.vim @@ -0,0 +1,41 @@ +" editing.vim — EasyMotion, yank highlight, search auto-clear, undotree + +" ── EasyMotion ────────────────────────────────────────────────────────────── + +let g:EasyMotion_do_mapping = 0 +let g:EasyMotion_smartcase = 1 + +if exists('g:plugs["vim-easymotion"]') + nmap s (easymotion-overwin-f2) + nmap j (easymotion-j) + nmap k (easymotion-k) +endif + +" ── UndoTree ──────────────────────────────────────────────────────────────── + +if exists('g:plugs["undotree"]') + nnoremap :UndotreeToggle + nnoremap u :UndotreeToggle +endif + +" ── Yank Highlight ────────────────────────────────────────────────────────── + +if exists('##TextYankPost') && has('timers') + function! s:YankHighlight() abort + if v:event.operator !=# 'y' | return | endif + let l:m = matchadd('IncSearch', + \ printf('\%%>%dl\%%<%dl', line("'[") - 1, line("']") + 1)) + call timer_start(150, {-> matchdelete(l:m)}) + endfunction + augroup ChopstickYankHL + autocmd! + autocmd TextYankPost * call s:YankHighlight() + augroup END +endif + +" ── Auto-Clear Search Highlight ───────────────────────────────────────────── + +augroup ChopstickSearchHL + autocmd! + autocmd CursorHold * if get(v:, 'hlsearch', 0) | let v:hlsearch = 0 | endif +augroup END diff --git a/modules/env.vim b/modules/env.vim new file mode 100644 index 0000000..7357c89 --- /dev/null +++ b/modules/env.vim @@ -0,0 +1,7 @@ +" env.vim — environment detection (must load 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') diff --git a/modules/git.vim b/modules/git.vim new file mode 100644 index 0000000..88077b2 --- /dev/null +++ b/modules/git.vim @@ -0,0 +1,25 @@ +" git.vim — Fugitive mappings, GitGutter config, conflict navigation + +" ── GitGutter ─────────────────────────────────────────────────────────────── + +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 = '~' + +" ── Fugitive ──────────────────────────────────────────────────────────────── + +if exists('g:plugs["vim-fugitive"]') + nnoremap gs :Git status + nnoremap gc :Git commit + nnoremap gp :Git push + nnoremap gl :Git pull + nnoremap gd :Gdiffsplit + nnoremap gb :Git blame +endif + +" ── Conflict Navigation ──────────────────────────────────────────────────── + +nnoremap ]x /^\(<<<<<<<\\|=======\\|>>>>>>>\) +nnoremap [x ?^\(<<<<<<<\\|=======\\|>>>>>>>\) diff --git a/modules/languages.vim b/modules/languages.vim new file mode 100644 index 0000000..89db0ba --- /dev/null +++ b/modules/languages.vim @@ -0,0 +1,52 @@ +" languages.vim — vim-go config, per-filetype autocmds + +" ── vim-go (syntax only — vim-lsp handles intelligence) ───────────────────── + +let g:go_gopls_enabled = 0 +let g:go_code_completion_enabled = 0 +let g:go_fmt_autosave = 0 +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 + +" ── Filetype Detection ────────────────────────────────────────────────────── + +augroup ChopstickFiletype + autocmd! + + autocmd BufReadPost * + \ if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif + + 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 + + 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 diff --git a/modules/lint.vim b/modules/lint.vim new file mode 100644 index 0000000..681455b --- /dev/null +++ b/modules/lint.vim @@ -0,0 +1,51 @@ +" lint.vim — ALE async linting and format-on-save + +let g:ale_disable_lsp = 1 + +let g:ale_linters = { +\ 'python': ['flake8', 'pylint'], +\ 'javascript': ['eslint'], +\ 'typescript': ['eslint'], +\ 'go': ['staticcheck'], +\ 'rust': ['cargo'], +\ 'c': ['cc'], +\ 'sh': ['shellcheck'], +\ 'yaml': ['yamllint'], +\ 'dockerfile': ['hadolint'], +\ 'css': ['stylelint'], +\ 'scss': ['stylelint'], +\ 'markdown': ['markdownlint'], +\ 'sql': ['sqlfluff'], +\} + +let g:ale_fixers = { +\ '*': ['remove_trailing_lines', 'trim_whitespace'], +\ 'python': ['black', 'isort'], +\ 'javascript': ['prettier', 'eslint'], +\ 'typescript': ['prettier', 'eslint'], +\ 'go': ['goimports'], +\ 'rust': ['rustfmt'], +\ 'c': ['clang-format'], +\ 'json': ['prettier'], +\ 'yaml': ['prettier'], +\ 'html': ['prettier'], +\ 'css': ['prettier'], +\ 'scss': ['prettier'], +\ 'less': ['prettier'], +\ 'markdown': ['prettier'], +\ 'sql': ['sqlfluff'], +\} + +let g:ale_fix_on_save = 1 +let g:ale_python_isort_options = '--profile black' +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 + +if exists('g:plugs["ale"]') + nnoremap [e :ALEPrevious + nnoremap ]e :ALENext + nnoremap aD :ALEDetail +endif diff --git a/modules/lsp.vim b/modules/lsp.vim new file mode 100644 index 0000000..15d3af3 --- /dev/null +++ b/modules/lsp.vim @@ -0,0 +1,74 @@ +" lsp.vim — vim-lsp settings, asyncomplete, LSP buffer keymaps + +let g:lsp_settings_filetype_python = ['pylsp'] +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': '>'} + +" ── Completion ────────────────────────────────────────────────────────────── + +if has('patch-8.1.1517') + set completeopt=menuone,noinsert,noselect,popup +else + set completeopt=menuone,noinsert,noselect +endif +set pumheight=15 +let g:asyncomplete_auto_popup = 1 +let g:asyncomplete_auto_completeopt = 0 +let g:asyncomplete_popup_delay = 50 + +inoremap pumvisible() ? "\" : "\" +inoremap pumvisible() ? "\" : "\" +inoremap pumvisible() ? asyncomplete#close_popup() : "\" + +" ── Buffer Keymaps ────────────────────────────────────────────────────────── + +function! s:on_lsp_buffer_enabled() abort + setlocal omnifunc=lsp#complete + setlocal signcolumn=yes + + 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) + + nmap K (lsp-hover) + + nmap rn (lsp-rename) + nmap ca (lsp-code-action) + nmap f (lsp-document-format) + xmap f (lsp-document-range-format) + + 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 diff --git a/modules/navigation.vim b/modules/navigation.vim new file mode 100644 index 0000000..9fc8d7d --- /dev/null +++ b/modules/navigation.vim @@ -0,0 +1,98 @@ +" navigation.vim — FZF, netrw, buffer/window management, terminal + +" ── netrw (built-in file browser) ─────────────────────────────────────────── + +let g:netrw_liststyle = 3 +let g:netrw_banner = 0 +let g:netrw_browse_split = 0 +let g:netrw_winsize = 25 +let g:netrw_list_hide = '\(^\|\s\s\)\zs\.\S\+' +let g:netrw_list_hide .= ',\.pyc$,node_modules,\.git,__pycache__,\.DS_Store' + +nnoremap e :Explore +nnoremap E :Vexplore + +" ── FZF ───────────────────────────────────────────────────────────────────── + +function! s:SmartFiles() abort + if isdirectory('.git') || finddir('.git', '.;') !=# '' + GFiles + else + Files + endif +endfunction + +if exists('g:plugs["fzf.vim"]') + nnoremap :call SmartFiles() + nnoremap b :Buffers + nnoremap rg :Rg + nnoremap rG :RgWord + nnoremap rt :Tags + nnoremap gF :GFiles + nnoremap fh :History + nnoremap fc :Commands + nnoremap fm :Marks + nnoremap fl :BLines + nnoremap fL :Lines + nnoremap f/ :History/ + nnoremap f: :History: +endif + +let g:fzf_layout = { 'down': '40%' } + +if g:is_tty + let g:fzf_preview_window = [] +else + let g:fzf_preview_window = ['right:50%', 'ctrl-/'] +endif + +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) + 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) + command! -bang GFiles call fzf#vim#gitfiles('', fzf#vim#with_preview(), 0) +endif + +if g:is_tty + command! -bang -nargs=* RgWord + \ call fzf#vim#grep( + \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' + \ .shellescape(expand('')), 1, 0) +else + command! -bang -nargs=* RgWord + \ call fzf#vim#grep( + \ 'rg --column --line-number --no-heading --color=always --smart-case -F -- ' + \ .shellescape(expand('')), 1, fzf#vim#with_preview(), 0) +endif + +" ── Window Maximize Toggle ────────────────────────────────────────────────── + +function! s:ToggleMaximize() abort + if exists('t:maximize_session') + execute t:maximize_session + unlet t:maximize_session + else + let t:maximize_session = winrestcmd() + resize | vertical resize + endif +endfunction +nnoremap z :call ToggleMaximize() + +" ── Terminal ──────────────────────────────────────────────────────────────── + +if has('terminal') + nnoremap tv :terminal + nnoremap th :terminal ++rows=10 + tnoremap + tnoremap h + tnoremap j + tnoremap k + tnoremap l +endif diff --git a/modules/plugins.vim b/modules/plugins.vim new file mode 100644 index 0000000..9cc1030 --- /dev/null +++ b/modules/plugins.vim @@ -0,0 +1,65 @@ +" plugins.vim — vim-plug declarations + +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' + augroup PlugBootstrap + autocmd! + autocmd VimEnter * PlugInstall --sync | source $MYVIMRC + augroup END +endif + +call plug#begin('~/.vim/plugged') + +" ── Navigation & Search ────────────────────────────────────────────────────── +Plug 'junegunn/fzf', { 'do': { -> fzf#install() } } +Plug 'junegunn/fzf.vim' + +" ── Git ────────────────────────────────────────────────────────────────────── +Plug 'tpope/vim-fugitive' +Plug 'airblade/vim-gitgutter' + +" ── Editing ────────────────────────────────────────────────────────────────── +Plug 'tpope/vim-surround' +Plug 'tpope/vim-commentary' +Plug 'tpope/vim-repeat' +Plug 'tpope/vim-unimpaired' +Plug 'tpope/vim-sleuth' +Plug 'wellle/targets.vim' +Plug 'jiangmiao/auto-pairs' +Plug 'easymotion/vim-easymotion' + +" ── Linting & Formatting ──────────────────────────────────────────────────── +Plug 'dense-analysis/ale' + +" ── LSP + Completion (no Node.js required) ────────────────────────────────── +Plug 'prabirshrestha/vim-lsp' +Plug 'mattn/vim-lsp-settings' +Plug 'prabirshrestha/asyncomplete.vim' +Plug 'prabirshrestha/asyncomplete-lsp.vim' + +" ── Language Syntax ────────────────────────────────────────────────────────── +Plug 'pangloss/vim-javascript' +Plug 'HerringtonDarkholme/yats.vim' +Plug 'preservim/vim-markdown' +Plug 'fatih/vim-go' + +" ── Markdown Preview & Writing ─────────────────────────────────────────────── +Plug 'previm/previm' +Plug 'junegunn/goyo.vim' +Plug 'junegunn/limelight.vim' + +" ── UI ─────────────────────────────────────────────────────────────────────── +Plug 'mbbill/undotree' +Plug 'mhinz/vim-startify' +Plug 'lifepillar/vim-solarized8' +if !g:is_tty + Plug 'Yggdroot/indentLine' +endif + +" ── Session & Navigation ──────────────────────────────────────────────────── +Plug 'tpope/vim-obsession' +Plug 'christoomey/vim-tmux-navigator' + +call plug#end() diff --git a/modules/tools.vim b/modules/tools.vim new file mode 100644 index 0000000..dd829ef --- /dev/null +++ b/modules/tools.vim @@ -0,0 +1,272 @@ +" tools.vim — cheat sheet, run file, sudo save, quickfix, helpers + +" ── Helper Functions ──────────────────────────────────────────────────────── + +function! HasPaste() + if &paste | return 'PASTE MODE ' | endif + return '' +endfunction + +command! Bclose call BufcloseCloseIt() +function! BufcloseCloseIt() + 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) + endif +endfunction + +fun! CleanExtraSpaces() + let save_cursor = getpos(".") + let old_query = getreg('/') + silent! %s/\s\+$//e + call setpos('.', save_cursor) + call setreg('/', old_query) +endfun + +function! ToggleNumber() + if(&relativenumber == 1) + set norelativenumber + set number + else + set relativenumber + endif +endfunc + +" ── Additional Utilities ──────────────────────────────────────────────────── + +nnoremap F gg=G`` +nnoremap wa :wa + +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) + +nnoremap + +nnoremap W :%s/\s\+$//:let @/='' + +nnoremap so :if &filetype ==# 'vim' source % echo "Sourced " . expand('%') else echo "Not a vim file" endif +nnoremap ev :edit $MYVIMRC +nnoremap sv :source $MYVIMRC:echo "vimrc reloaded" + +nnoremap * :%s/\<\>//g + +if has('clipboard') + nnoremap cp :let @+ = expand("%:p"):echo "Copied: " . expand("%:p") + nnoremap cf :let @+ = expand("%:t"):echo "Copied: " . expand("%:t") +endif + +nnoremap ms :e ~/buffer.md + +" ── Auto-Create Directories ───────────────────────────────────────────────── + +function! s:MkNonExDir(file, buf) + if empty(getbufvar(a:buf, '&buftype')) && a:file !~# '\v^\w+\:\/' + let dir = fnamemodify(a:file, ':h') + if !isdirectory(dir) + call mkdir(dir, 'p') + endif + endif +endfunction +augroup BWCCreateDir + autocmd! + autocmd BufWritePre * + \ if !empty(expand('')) | + \ call s:MkNonExDir(expand(''), +expand('')) | + \ endif +augroup END + +" ── Large File Handling (>10 MB) ──────────────────────────────────────────── + +let g:LargeFile = 1024 * 1024 * 10 +augroup LargeFile + autocmd! + autocmd BufReadPre * + \ if !empty(expand('')) | + \ let f = getfsize(expand('')) | + \ if f > g:LargeFile || f == -2 | call LargeFileSettings() | endif | + \ endif +augroup END + +function! LargeFileSettings() + setlocal bufhidden=unload + setlocal undolevels=-1 + setlocal noswapfile + setlocal syntax= + let b:ale_enabled = 0 + echo "Large file (>10 MB): syntax, undo, and linting disabled." +endfunction + +if g:is_tty + augroup ChopstickTTYLargeFile + autocmd! + autocmd BufReadPre * + \ if !empty(expand('')) && getfsize(expand('')) > 512000 | + \ setlocal syntax= | + \ endif + augroup END + + 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 + +" ── Run Current File (,cr) ────────────────────────────────────────────────── + +function! s:RunFile() abort + write + let l:ft = &filetype + let l:file = shellescape(expand('%:p')) + if l:ft ==# 'python' | execute '!python3 ' . l:file + elseif l:ft ==# 'javascript' | execute '!node ' . l:file + elseif l:ft ==# 'typescript' | execute '!npx ts-node ' . l:file + elseif l:ft ==# 'go' | execute '!go run ' . l:file + elseif l:ft ==# 'rust' | execute '!cargo run' + elseif l:ft ==# 'sh' | execute '!bash ' . l:file + elseif l:ft ==# 'c' | execute '!gcc -o /tmp/a.out ' . l:file . ' && /tmp/a.out' + elseif l:ft ==# 'lua' | execute '!lua ' . l:file + elseif l:ft ==# 'ruby' | execute '!ruby ' . l:file + elseif l:ft ==# 'perl' | execute '!perl ' . l:file + else | echo 'No runner for filetype: ' . l:ft + endif +endfunction +nnoremap cr :call RunFile() + +" ── Sudo Save ─────────────────────────────────────────────────────────────── + +cnoremap w!! w !sudo tee > /dev/null % + +" ── QuickFix Improvements ─────────────────────────────────────────────────── + +augroup ChopstickQF + autocmd! + autocmd QuickFixCmdPost [^l]* cwindow + autocmd QuickFixCmdPost l* lwindow +augroup END + +nnoremap ]q :cnext +nnoremap [q :cprev + +" ── Debug Helpers ─────────────────────────────────────────────────────────── + +nnoremap sh :call SynStack() +function! SynStack() + if !exists("*synstack") | return | endif + echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")') +endfunc + +" ── Cheat Sheet (,?) ──────────────────────────────────────────────────────── + +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 ===', + \ '', + \ 'SURVIVAL', + \ ' Esc / jk Exit insert or visual mode', + \ ' :q! + Enter Quit without saving', + \ ' ,x Save+quit ,w Save Ctrl+s Save (any mode)', + \ ' :w!! Sudo save (when you forgot to open as root)', + \ '', + \ 'FILES & SEARCH', + \ ' Ctrl+p Fuzzy find file (git-aware)', + \ ' ,e / ,E File browser / vertical split', + \ ' ,b Search open buffers', + \ ' ,rg Search project contents (ripgrep)', + \ ' ,rG Ripgrep word under cursor', + \ ' ,fh Recent files history', + \ ' ,fl / ,fL Search lines in buffer / all buffers', + \ ' ,fc Commands | ,fm Marks', + \ ' ,f/ / ,f: Search / command history', + \ ' ,, Switch to last file (Ctrl+^)', + \ '', + \ 'CODE INTELLIGENCE (vim-lsp)', + \ ' gd Definition gy Type def gi Impl gr Refs', + \ ' K Hover documentation', + \ ' [g / ]g Prev / next LSP diagnostic', + \ ' [e / ]e Prev / next ALE error', + \ ' ,ca Code action ,rn Rename ,f Format', + \ ' ,o File outline ,ws Workspace symbols', + \ ' ,cr Run current file', + \ '', + \ 'MARKDOWN & WRITING', + \ ' ,mp Live browser preview (previm)', + \ ' ,mt Table of contents', + \ ' ,zen Zen mode (Goyo + Limelight)', + \ ' zr / zm Unfold / fold all headings', + \ '', + \ 'EDITING', + \ ' gc Toggle comment (visual mode too)', + \ ' s + 2 chars EasyMotion jump anywhere', + \ ' ,u / F5 Undo tree', + \ ' ,y / ,Y Yank to system clipboard', + \ ' Alt+j / Alt+k Move line down / up', + \ ' ,F Re-indent file ,W Strip trailing whitespace', + \ ' ,* Search and replace word under cursor', + \ '', + \ 'GIT', + \ ' ,gs Status ,gd Diff ,gb Blame', + \ ' ,gc Commit ,gp Push ,gl Pull', + \ ' [x / ]x Navigate git conflict markers', + \ '', + \ 'WINDOWS & PANES', + \ ' Ctrl+h/j/k/l Navigate splits and tmux panes', + \ ' ,h / ,l Prev / next buffer ,bd Close buffer', + \ ' ,z Maximize / restore current window', + \ ' ,tv / ,th Terminal (vertical / horizontal)', + \ ' Esc Esc Exit terminal mode', + \ ' ,= / ,- Resize height ,+ / ,_ Resize width', + \ '', + \ 'QUICKFIX', + \ ' ,qo / ,qc Open / close quickfix', + \ ' ]q / [q Next / prev quickfix entry', + \ '', + \ 'UTILITIES', + \ ' ,ev / ,sv Edit / reload ~/.vimrc', + \ ' ,cp / ,cf Copy file path / filename to clipboard', + \ ' ,ms Scratch buffer ,cd CD to file dir', + \ ' ,ss Toggle spell ,so Source current vim file', + \ ' F2 Paste F3 Line# F4 Relative# F6 Invisible', + \ '', + \ '(press q to close)', + \ ]) + setlocal nomodifiable readonly + nnoremap q :bd +endfunction +nnoremap ? :call CheatSheet() + +" ── Interactive Tutorial ──────────────────────────────────────────────────── + +function! s:ChopsticksLearn() abort + let l:tutor = g:chopsticks_dir . '/tutor/chopsticks.tutor' + if !filereadable(l:tutor) + echo "Tutorial not found: " . l:tutor + return + endif + execute 'edit ' . fnameescape(l:tutor) + setlocal nomodifiable readonly + setlocal buftype=nofile bufhidden=wipe + setlocal filetype=text + setlocal wrap linebreak +endfunction +command! ChopsticksLearn call s:ChopsticksLearn() diff --git a/modules/ui.vim b/modules/ui.vim new file mode 100644 index 0000000..2540323 --- /dev/null +++ b/modules/ui.vim @@ -0,0 +1,164 @@ +" ui.vim — colorscheme, statusline, startify, indentline + +" ── Colorscheme (Solarized Dark — matches tmux palette) ──────────────────── + +if g:has_true_color && has('termguicolors') && !g:is_tty + let &t_8f = "\[38;2;%lu;%lu;%lum" + let &t_8b = "\[48;2;%lu;%lu;%lum" + set termguicolors +endif + +set background=dark + +if !g:is_tty + try + colorscheme solarized8 + catch + colorscheme default + endtry +else + colorscheme default +endif + +if has("gui_running") + if has("gui_gtk2") || has("gui_gtk3") + set guifont=Hack\ 12,Source\ Code\ Pro\ 12,Monospace\ 12 + elseif has("gui_win32") + set guifont=Consolas:h11:cANSI + endif +endif + +" ── IndentLine (non-TTY only) ─────────────────────────────────────────────── + +if !g:is_tty && exists('g:plugs["indentLine"]') + let g:indentLine_char = '|' + let g:indentLine_first_char = '|' + let g:indentLine_showFirstIndentLevel = 1 + let g:indentLine_fileTypeExclude = ['text', 'help', 'startify', 'markdown'] + let g:indentLine_bufTypeExclude = ['help', 'terminal', 'nofile'] + let g:indentLine_setConceal = 2 + let g:indentLine_concealcursor = '' +endif + +" ── Startify ──────────────────────────────────────────────────────────────── + +if exists('g:plugs["vim-startify"]') + let g:startify_custom_header = [ + \ ' ██████╗██╗ ██╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██████╗██╗ ██╗███████╗', + \ ' ██╔════╝██║ ██║██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝', + \ ' ██║ ███████║██║ ██║██████╔╝███████╗ ██║ ██║██║ █████╔╝ ███████╗', + \ ' ██║ ██╔══██║██║ ██║██╔═══╝ ╚════██║ ██║ ██║██║ ██╔═██╗ ╚════██║', + \ ' ╚██████╗██║ ██║╚██████╔╝██║ ███████║ ██║ ██║╚██████╗██║ ██╗███████║', + \ ' ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝', + \ '', + \ ] + + let g:startify_lists = [ + \ { 'type': 'sessions', 'header': [' Sessions'] }, + \ { 'type': 'files', 'header': [' Recent Files'] }, + \ { 'type': 'dir', 'header': [' Current Dir'] }, + \ { 'type': 'bookmarks', 'header': [' Bookmarks'] }, + \ ] + + let g:startify_bookmarks = [{'v': '~/.vimrc'}] + if filereadable(expand('~/.zshrc')) + call add(g:startify_bookmarks, {'z': '~/.zshrc'}) + endif + if filereadable(expand('~/.bashrc')) + call add(g:startify_bookmarks, {'b': '~/.bashrc'}) + endif + if filereadable(expand('~/.config/fish/config.fish')) + call add(g:startify_bookmarks, {'f': '~/.config/fish/config.fish'}) + endif + + 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 + + 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 + +endif + +" ── Status Line (native — Solarized palette, seamless with tmux bar) ─────── + +set laststatus=2 +set noshowmode + +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() +augroup END +call s:SLDefineColors() + +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 + +function! SLGit() abort + if !exists('*FugitiveHead') | return '' | endif + let l:b = FugitiveHead() + return empty(l:b) ? '' : ' ' . l:b . ' ' +endfunction + +function! SLAle() abort + if !exists('*ale#statusline#Count') | return '' | endif + let l:c = ale#statusline#Count(bufnr('')) + let l:e = l:c.error + l:c.style_error + let l:w = l:c.warning + l:c.style_warning + if l:e == 0 && l:w == 0 | return '' | endif + return printf(' E:%d W:%d ', l:e, l:w) +endfunction + +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 .= '%#SLFlag#' . SLAle() + let l:s .= '%#SLGit#' . SLGit() + let l:s .= '%#SLFtype# %y ' + let l:s .= '%#SLRight# %l:%c %P ' + return l:s +endfunction + +set statusline=%!SLBuild() + +if g:is_tty + set statusline=%f\ %h%w%m%r\ %=%(%l,%c%V\ %=\ %P%) +endif diff --git a/modules/writing.vim b/modules/writing.vim new file mode 100644 index 0000000..732407f --- /dev/null +++ b/modules/writing.vim @@ -0,0 +1,56 @@ +" writing.vim — vim-markdown, previm, goyo + limelight zen mode + +" ── vim-markdown ──────────────────────────────────────────────────────────── + +let g:vim_markdown_conceal = 1 +let g:vim_markdown_conceal_code_blocks = 0 +let g:vim_markdown_folding_disabled = 0 +let g:vim_markdown_folding_level = 2 +let g:vim_markdown_frontmatter = 1 +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 + +if exists('g:plugs["vim-markdown"]') + nnoremap mt :Toc +endif + +" ── previm (Markdown browser preview) ─────────────────────────────────────── + +if has('macunix') + let g:previm_open_cmd = '/usr/bin/open' +elseif executable('xdg-open') + let g:previm_open_cmd = 'xdg-open' +endif +if exists('g:plugs["previm"]') + nnoremap mp :PrevimOpen +endif +let g:previm_enable_realtime = 1 + +" ── Goyo + Limelight (zen mode) ──────────────────────────────────────────── + +if exists('g:plugs["goyo.vim"]') + let g:goyo_width = 80 + let g:goyo_height = '85%' + nnoremap zen :Goyo + + function! s:goyo_enter() + if exists('g:plugs["limelight.vim"]') | Limelight | endif + set wrap linebreak scrolloff=999 + endfunction + function! s:goyo_leave() + if exists('g:plugs["limelight.vim"]') | Limelight! | endif + set nowrap nolinebreak scrolloff=10 + endfunction + + augroup ChopstickGoyo + autocmd! + autocmd User GoyoEnter nested call s:goyo_enter() + autocmd User GoyoLeave nested call s:goyo_leave() + augroup END +endif + +let g:limelight_conceal_ctermfg = 240 +let g:limelight_conceal_guifg = '#586e75' diff --git a/tutor/chopsticks.tutor b/tutor/chopsticks.tutor new file mode 100644 index 0000000..c9289b1 --- /dev/null +++ b/tutor/chopsticks.tutor @@ -0,0 +1,269 @@ +================================================================================ += C H O P S T I C K S I N T E R A C T I V E T U T O R = +================================================================================ + +This tutorial teaches the key features of the chopsticks Vim configuration. +It assumes you already know basic Vim (if not, run :Tutor first). + +Leader key is , (comma) throughout this tutorial. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 1: SURVIVAL BASICS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These four commands get you out of any situation: + + 1. Press Esc or type jk to return to Normal mode + 2. Type ,w to save the current file + 3. Type ,x to save and quit + 4. Type ,q to quit (without saving) + +TIP: You can also press Ctrl+s to save from ANY mode (normal, insert, + or visual). Try it now — press i to enter insert mode, then Ctrl+s + to save without leaving insert mode. + +When completely lost, press ,? to open the cheat sheet with every +keybinding. Press q to close it. + + >>> Try it now: press ,? then read through the sheet, then press q + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 2: FINDING FILES +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +chopsticks uses FZF (fuzzy finder) for fast navigation. + + Ctrl+p Fuzzy find files (git-aware — ignores .gitignore'd files) + ,b Search open buffers + ,fh Recent file history + ,rg Search file contents with ripgrep + ,rG Search for the word under your cursor + + >>> Try it now: press Ctrl+p and start typing a filename + +You can also browse files with the built-in file browser: + + ,e Open netrw file browser in current window + ,E Open netrw in a vertical split + +Inside netrw: + Enter Open file + - Go up one directory + % Create new file + d Create new directory + gh Toggle hidden files + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 3: CODE INTELLIGENCE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +chopsticks uses vim-lsp for code intelligence — no Node.js required. + +First, install a language server for your filetype: + + :LspInstallServer (auto-detects the language) + :LspStatus (check if it's running) + +Once a server is running, these keys become available: + + gd Go to definition + gy Go to type definition + gi Go to implementation + gr List all references + K Show hover documentation + + [g / ]g Jump to previous / next diagnostic + ,rn Rename symbol under cursor + ,ca Code action (auto-fix) + ,f Format buffer (or selection in visual mode) + ,o File outline (symbols) + + >>> Open a source file and try: gd on a function call, then Ctrl+o + to jump back + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 4: EDITING POWER +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These editing features make chopsticks faster than vanilla Vim. + +EASYMOTION — jump anywhere on screen: + Press s then type two characters from your target location. + Matching positions light up — press the highlighted letter to jump. + + >>> Try it now: press s then type two letters you can see on screen + +MOVE LINES — rearrange code without cut/paste: + Alt+j Move current line down + Alt+k Move current line up + (Works in visual mode too — select lines first with V ) + +SURROUND — change surrounding characters: + cs"' Change surrounding " to ' + cs'
Change surrounding ' to
...
+ ds( Delete surrounding parentheses + ysiw" Surround word with " + +COMMENT — toggle comments: + gc Toggle comment (works in visual mode on selections) + gcc Toggle comment on current line + +VISUAL FEEDBACK: + - Yanked text flashes briefly so you know what was copied + - Search highlights auto-clear after you stop moving + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 5: GIT WORKFLOW +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All git operations through vim-fugitive: + + ,gs Git status (press s to stage, press cc to commit) + ,gd Diff current file (side-by-side split) + ,gb Git blame (who changed each line) + ,gc Git commit + ,gp Git push + ,gl Git pull + +GitGutter shows changes in the sign column (left margin): + + Added line + ~ Modified line + - Deleted line + +Navigate git conflict markers: + ]x Jump to next conflict marker + [x Jump to previous conflict marker + + >>> Try ,gs in a git repository to see the status view + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 6: MARKDOWN & WRITING +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Markdown files get special treatment automatically: + - Word wrap enabled + - Spell checking enabled + - Syntax concealment (bold renders as bold, headings hide # markers) + - Folding by heading level + +Key bindings for markdown: + + ,mp Open live browser preview (auto-refreshes as you type) + ,mt Table of contents in a side window + ,zen Enter zen mode — Goyo + Limelight + (distraction-free writing, only current paragraph highlighted) + zr Unfold all headings + zm Fold all headings + + >>> Open a .md file and try ,zen to enter zen mode. + Press ,zen again to leave. + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 7: WINDOW MANAGEMENT +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Navigate between splits (works with tmux panes too): + + Ctrl+h Move to left split + Ctrl+j Move to split below + Ctrl+k Move to split above + Ctrl+l Move to right split + +Buffer navigation: + + ,h Previous buffer + ,l Next buffer + ,bd Close buffer (keeps window layout) + ,, Switch to last file (toggle between two files) + +Window tricks: + + ,z Maximize current window (press again to restore) + ,= / ,- Resize window height (bigger / smaller) + ,+ / ,_ Resize window width (bigger / smaller) + +Terminal: + + ,tv Open terminal (vertical split) + ,th Open terminal (horizontal, 10 rows) + Esc Esc Exit terminal mode back to normal + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 8: LINTING & FORMATTING +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +ALE runs linters and formatters automatically: + + - Linting happens as you type (normal mode) and on save + - Format-on-save is enabled by default (black, prettier, gofmt, etc.) + +Navigate errors: + + [e / ]e Previous / next ALE error + ,aD Show error detail in a popup + +Supported out of the box: + Python: flake8 + pylint (lint), black + isort (format) + JS/TS: eslint (lint), prettier + eslint (format) + Go: staticcheck (lint), goimports (format) + Rust: cargo (lint), rustfmt (format) + Shell: shellcheck (lint) + And more: yaml, dockerfile, css, markdown, sql + + >>> Write some intentionally bad code and watch the sign column + light up with X (error) and ! (warning) markers + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 9: RUNNING CODE +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Press ,cr to run the current file. It auto-detects the language: + + Python → python3 file.py + JavaScript → node file.js + TypeScript → npx ts-node file.ts + Go → go run file.go + Rust → cargo run + Shell → bash file.sh + C → gcc + run + Lua, Ruby, Perl also supported + + >>> Create a test file (e.g. test.py with: print("hello")) + and press ,cr to run it + + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Lesson 10: DAILY TIPS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +SEARCH AND REPLACE: + ,* Replace word under cursor throughout the file + // In visual mode, search for selected text + +USEFUL SHORTCUTS: + Y Yank to end of line (consistent with D and C) + ,y / ,Y Yank to system clipboard + ,F Re-indent entire file + ,W Strip all trailing whitespace + :w!! Sudo save (when you forgot to open as root) + ,ev Edit your vimrc + ,sv Reload your vimrc + +SESSIONS: + :Obsess Start recording session (auto-restores next time) + :Obsess! Stop recording + +The startup screen (Startify) shows recent files, sessions, and bookmarks. + + >>> Press ,? one more time to review the full cheat sheet. + You now know the essential chopsticks features. Happy editing! + +================================================================================