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/readline/inputrc b/.config/readline/inputrc new file mode 100644 index 0000000..e602c7d --- /dev/null +++ b/.config/readline/inputrc @@ -0,0 +1,14 @@ +# Enable vi-style line editing for all readline apps +set editing-mode vi + +# Indicate the current mode via the cursor shape +set show-mode-in-prompt on +set vi-cmd-mode-string "\1\e[2 q\2" # steady block +set vi-ins-mode-string "\1\e[5 q\2" # blinking bar + +# Ctrl-L clears the screen in command mode only +# => Bind in insert mode as well +set keymap vi-insert +"\C-l": clear-screen + +set keymap vi diff --git a/.config/shell/aliases b/.config/shell/aliases index 4d662da..6fc294d 100644 --- a/.config/shell/aliases +++ b/.config/shell/aliases @@ -3,6 +3,22 @@ # Aliases used in all kinds of shells +# Make this file work standalone in non-interactive `bash` (e.g., ":!cmd" in `vim`) + +if [ -n "${BASH_VERSION:-}" ]; then + shopt -s expand_aliases +fi + +if ! command -v _command_exists >/dev/null 2>&1; then + # This utility is supposed to be defined only in ~/.profile but copied here + # to make `$BASH_ENV` work in `vim`, which sources this file only + _command_exists() { + command -v "$1" 1>/dev/null 2>&1 + } +fi + + + # Manage the bare `git` repository in ~/ holding the dotfiles alias dotfiles='git --git-dir=$DOTFILES_DIR --work-tree=$HOME' @@ -164,3 +180,11 @@ if _command_exists pyenv; then alias pyvers='pyenv versions --skip-aliases' 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 4b4ab2f..9b90fbb 100644 --- a/.config/shell/env +++ b/.config/shell/env @@ -26,6 +26,13 @@ export DOTFILES_DIR="$XDG_DATA_HOME/dotfiles" # also set in ~/.local/bin/dotfil [ -t 0 ] && export GPG_TTY=$(tty) +if _command_exists vim; then + export EDITOR=vim + export SUDO_EDITOR="$EDITOR" + export VISUAL="$EDITOR" +fi + + export PAGER=less export LESS="--chop-long-lines --ignore-case --LONG-PROMPT --no-init --raw-control-chars --status-column --quit-if-one-screen" @@ -47,9 +54,11 @@ unset _bat # Move common tools' config and cache files into XDG directories export BAT_CONFIG_PATH="$XDG_CONFIG_HOME/bat/config" +export INPUTRC="$XDG_CONFIG_HOME/readline/inputrc" export LESSHISTFILE="$XDG_STATE_HOME/less/history" export PSQLRC="$XDG_CONFIG_HOME/psql/psqlrc" 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/vim/after/ftplugin/python.vim b/.config/vim/after/ftplugin/python.vim new file mode 100644 index 0000000..f658c02 --- /dev/null +++ b/.config/vim/after/ftplugin/python.vim @@ -0,0 +1,99 @@ +" 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 +setlocal textwidth=88 + + +" Make column 80 red to indicate PEP8's maximum allowed line length +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..fd393d8 --- /dev/null +++ b/.config/vim/vimrc @@ -0,0 +1,440 @@ +" Use VIM improved mode +set nocompatible + +" Ensure we work with Unicode +set encoding=utf-8 +scriptencoding utf-8 + +" Disable VIM's startup message +set shortmess+=I + +" Number of remembered undo steps +set undolevels=1000 + + +" 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 shell aliases +set shell=/bin/bash +let $BASH_ENV = expand("$XDG_CONFIG_HOME/shell/aliases") + + +" 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