diff --git a/.config/git/ignore b/.config/git/ignore index 5e96e2b..cbfb1af 100644 --- a/.config/git/ignore +++ b/.config/git/ignore @@ -12,3 +12,24 @@ __pycache__/ .venv/ venv/ .env + +# Vim +# Source: https://github.com/github/gitignore/blob/main/Global/Vim.gitignore +# +# Swap +[._]*.s[a-v][a-z] +!*.svg +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] +# Session +Session.vim +Sessionx.vim +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ diff --git a/.config/shell/aliases b/.config/shell/aliases index cf6fe17..03c42ea 100644 --- a/.config/shell/aliases +++ b/.config/shell/aliases @@ -4,6 +4,12 @@ +# Make non-interactive `bash` expand aliases as well (e.g., ":!cmd" in `vim`) +if [ -n "${BASH_VERSION:-}" ]; then + shopt -s expand_aliases +fi + + forget() { # the current history and restore $HISTFILE from disk if [ -n "$BASH_VERSION" ]; then @@ -123,3 +129,12 @@ if _command_exists pyenv; then alias pywhich='pyenv which' fi + + + +if _command_exists vim; then + + alias v='vim' + _command_exists sudoedit && alias sv='sudoedit' + +fi diff --git a/.config/shell/env b/.config/shell/env index 0e30405..9397210 100644 --- a/.config/shell/env +++ b/.config/shell/env @@ -37,7 +37,13 @@ fi [ -t 0 ] && export GPG_TTY=$(tty) -export EDITOR=nano +if _command_exists vim; then + export EDITOR=vim +elif _command_exists vi; then + export EDITOR=vi +else + export EDITOR=nano +fi export PAGER=less @@ -68,3 +74,4 @@ export PYENV_ROOT="$XDG_DATA_HOME/pyenv" export PYTHONPYCACHEPREFIX="$XDG_CACHE_HOME/python" export PYTHONSTARTUP="$XDG_CONFIG_HOME/python/startup.py" export PYTHON_HISTORY="$XDG_STATE_HOME/python/history" +export VIMINIT='let $MYVIMRC="'"$XDG_CONFIG_HOME"'/vim/vimrc" | source $MYVIMRC' diff --git a/.config/shell/welcome b/.config/shell/welcome index 3040b9c..539d5d2 100644 --- a/.config/shell/welcome +++ b/.config/shell/welcome @@ -5,6 +5,7 @@ # ... except: [ -t 1 ] || return # If stdout is not a terminal +[ "${SHLVL:-1}" -gt 1 ] && return # In nested shells (e.g., ":terminal" in `vi`) [ "${TERM:-}" = "dumb" ] && return # For tools opening shells (e.g., `emacs`) diff --git a/.config/vim/README.md b/.config/vim/README.md new file mode 100644 index 0000000..ad71068 --- /dev/null +++ b/.config/vim/README.md @@ -0,0 +1,11 @@ +# `vim`-related Configurations + +This folder contains + the [`vimrc`](./vimrc), + loaded via `$VIMINIT` set in [`~/.config/shell/env`](../shell/env). + +[`after/`](./after/) holds + [filetype](./after/ftplugin/)- and + [syntax](./after/syntax/)-specific overrides. + +[`spell/`](./spell/) is a placeholder for downloaded and untracked spell files. diff --git a/.config/vim/after/ftplugin/python.vim b/.config/vim/after/ftplugin/python.vim new file mode 100644 index 0000000..3b867e3 --- /dev/null +++ b/.config/vim/after/ftplugin/python.vim @@ -0,0 +1,100 @@ +" Set the search path to the folder of the current file and its sub-folders +setlocal path=.,** + +" Exclude Python's compiled files from searches +setlocal wildignore=*/__pycache__/*,*.pyc + + +" Format the current file with `black` + +function! s:RunBlack() + update + let l:out = system('black ' . shellescape(expand('%'))) + if v:shell_error + echohl ErrorMsg | echo split(l:out, '\n')[0] | echohl NONE + else + let s:autoread = &autoread + set autoread + silent! checktime + let &autoread = s:autoread + endif +endfunction + +nnoremap b :call RunBlack() + + +" Resolve Python imports to files, +" so that `gf` jumps to the module under the cursor +" +" Examples, with the cursor on the marked word: +" - `from lalib.elements import galois` => lalib/elements/galois.py +" - `from lalib.elements import ...` => lalib/elements/__init__.py +" - `import lalib.config` => lalib/config.py +" +if !exists('*s:PyGoToFile') + + " Build the dotted module path for the word under the cursor + function! s:PyModuleUnderCursor() + let l:word = expand('') + let l:from = matchstr(getline('.'), '^\s*from\s\+\zs[A-Za-z0-9_.]\+') + let l:imp = matchstr(getline('.'), '^\s*import\s\+\zs[A-Za-z0-9_.]\+') + let l:base = !empty(l:from) ? l:from : l:imp + if empty(l:base) + return '' + endif + let l:parts = split(l:base, '\.') + let l:idx = index(l:parts, l:word) + if l:idx >= 0 " Cursor within the dotted prefix => Truncate there + return join(l:parts[0:l:idx], '/') + elseif !empty(l:from) " Cursor on an imported name => Append it + return join(l:parts, '/') . '/' . l:word + endif + return '' + endfunction + + function! s:PyFindFile(mod) + for l:candidate in [a:mod . '.py', a:mod . '/__init__.py'] + let l:found = findfile(l:candidate) " search the `path` + if !empty(l:found) + return l:found + endif + endfor + return '' + endfunction + + function! s:PyGoToFile() + let l:mod = s:PyModuleUnderCursor() + let l:file = empty(l:mod) ? '' : s:PyFindFile(l:mod) + if empty(l:file) + echohl ErrorMsg | echo 'No module file for: ' . l:mod | echohl NONE + return + endif + execute 'edit ' . fnameescape(l:file) + endfunction + +endif + +" jumps back +nnoremap gf :call PyGoToFile() + +" Find `def`s and `class`es with [d and [D +setlocal define=^\\s*\\<\\(def\\|class\\)\\> + + +" Indentation settings come from ~/.config/vim/vimrc, and +" `smartindent` is left off as it interferes with VIM's Python indent script + + +" Auto-wrap lines after 88 characters, which is PEP8's limit plus 10%, +" a more relaxed boundary which occasionally may be used; +" nevertheless, we highlight anything beyond 80 as error +setlocal textwidth=88 + + +" Make column 80 red to indicate PEP8's maximum allowed line length of 79 +setlocal colorcolumn=80 + + +" Show line numbers by default for .py files +let b:show_numbers=1 +call ShowLineNumbers() diff --git a/.config/vim/after/syntax/python.vim b/.config/vim/after/syntax/python.vim new file mode 100644 index 0000000..7297dde --- /dev/null +++ b/.config/vim/after/syntax/python.vim @@ -0,0 +1,3 @@ +" Give every character beyond 80 columns a red background +syntax match pythonOverLength /\%>80v.\+/ containedin=ALL +highlight link pythonOverLength ErrorMsg diff --git a/.config/vim/spell/.gitkeep b/.config/vim/spell/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.config/vim/vimrc b/.config/vim/vimrc new file mode 100644 index 0000000..6dff999 --- /dev/null +++ b/.config/vim/vimrc @@ -0,0 +1,451 @@ +" Use `vim` (i.e., "improved") mode +set nocompatible + +" Ensure we work with Unicode +set encoding=utf-8 +scriptencoding utf-8 + +" Disable the startup message +set shortmess+=I + +" Number of remembered undo steps +set undolevels=1000 + + +" Silence every bell event, +" then re-enable the two we want to see +set belloff=all +set belloff-=error +set belloff-=cursor + +" Flash instead of beep +set visualbell + + +" Ensure the XDG variables exist even when `vim` is started +" outside a login shell (e.g., from a desktop launcher) +let $XDG_CONFIG_HOME = get(environ(), 'XDG_CONFIG_HOME', $HOME . '/.config') +let $XDG_STATE_HOME = get(environ(), 'XDG_STATE_HOME', $HOME . '/.local/state') + +set runtimepath^=$XDG_CONFIG_HOME/vim +set runtimepath+=$XDG_CONFIG_HOME/vim/after + +" Set environment variables for convenient usage +let $RC = expand('$XDG_CONFIG_HOME/vim/vimrc') +let $RTP=split(&runtimepath, ',')[0] + + +" Make :!cmd use `bash` and load the full shell setup +" (~/.profile defines helpers, then sources ~/.config/shell/{env,aliases}) +set shell=/bin/bash +let $BASH_ENV = expand("$HOME/.profile") + + +" Detect the file's type and load the corresponding plugin and indent files +filetype plugin indent on + +" Enable syntax highlighting +syntax on + + +" Configure tab behavior +set expandtab " Insert spaces instead of tab characters +set tabstop=4 " Width of an existing tab character +set shiftwidth=4 " Width of one >> or << indent step +set softtabstop=4 " Width inserted/removed by and +set shiftround " Round indents to a multiple of shiftwidth +set autoindent " Keep the previous line's indent on a new line +vnoremap >gv +vnoremap