"----------------------------------------------------------------------------------------------------------------------- " The config is chopped up into sections. Search for these headings to quickly jump to a particular section. " " #0 GLOBALS " #1 PLUGINS " #2 BASE CONFIG " #3 CUSTOM AUTOCMDS " #4 KEY MAPPINGS " #5 PLUGIN CONFIGS " #6 VISUALS (COLORS, HIGHLIGHTING) " #7 CUSTOM FUNCTIONS & COMMANDS " " @incomplete Add setup steps (plugins, cache setup, search tool, etc). "----------------------------------------------------------------------------------------------------------------------- if !exists("g:campo_vimrc_initialized") let g:campo_vimrc_initialized = 0 " Will be set to 1 at the end of the file. Can be used to avoid changes on subsequent vimrc reloads. endif scriptencoding utf-8 " @note If the file contains a BOM then vim will automatically set `bomb` for the buffer so that the BOM is written out again. set encoding=utf-8 fileencoding=utf-8 fileencodings=ucs-bom,utf8,prc set nocompatible filetype off " Store the current system name so that we can conditionally set configs for different platforms. let s:uname = system("echo -n \"$(uname)\"") let g:vim_dir = $HOME . "/.vim" let mapleader="," fu! IsWindows() if s:uname =~? "mingw" || s:uname =~? "msys" return 1 endif if has("win64") || has("win32") || has("win16") return 1 endif return 0 endfu if has('termguicolors') set termguicolors " Set Vim-specific sequences for RGB colors. let &t_8f = "\[38;2;%lu;%lu;%lum" let &t_8b = "\[48;2;%lu;%lu;%lum" endif fu! PrintError(msg) abort exec 'normal! \' echohl ErrorMsg echomsg a:msg echohl None endfu "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #0 GLOBALS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "----------------------------------------------------------------------------------------------------------------------- " @note The following globals can be used to customize various functions in " this file. The easiest way to set them is in ~/.vimrc.private or an .lvimrc " file in the root folder that you want it applied to. " " Some variables cannot be customized in an .lvimrc because their value is " used by settings in this file when it's sourced. These have been flagged " with the note :unsupported-in-lvimrc. " " Also take note that an .lvimrc has precedence because it's loaded after this " and the private vimrc. "----------------------------------------------------------------------------------------------------------------------- " @note :unsupported-in-lvimrc let g:campo_max_line_length = 120 " Display a vertical bar at x=. " Set the row height of the quickfix pane, which is used to display results " from various plugins (like ctrlp, ripgrep, compilation errors, etc), in rows let g:quickfix_pane_height = 20 "################################################################################## " COLORS "################################################################################## let g:campo_light_dark_mode = 'dark' " Start vim with the dark theme. Set to 'light' for the light theme. let g:campo_dark_theme = 'campo-dark-simple' "'campo-dark-blue' let g:campo_light_theme = 'campo-light-simple' "'campo-light' "################################################################################## " FORMATTING "################################################################################## " When set to 1, all files will be stripped of trailing whitespace when the " file is saved. Set to 0 to disable. You can customize which files are " ignored or always stripped; see below. let g:campo_strip_trailing_whitespace = 1 " When set to 1, the whitespace stripping force/ignore file filters below will " be expected to have full paths to the files, otherwise just the filename is " required. let g:campo_use_full_paths_for_whitespace_stripping_file_filters = 1 " If g:campo_strip_trailing_whitespace is 1 then you can stop stripping in " specific directories by setting this to a list of full directory paths. " This has no effect when g:campo_strip_trailing_whitespace is 0. " " e.g. let g:campo_directories_to_ignore_when_stripping_trailing_whitespace = ['/z/modules', '/d/build'] let g:campo_directories_to_ignore_when_stripping_trailing_whitespace = [] " If g:campo_strip_trailing_whitespace is 1 then you can stop stripping in " specific files by setting this to a list of full file paths. " This has no effect when g:campo_strip_trailing_whitespace is 0. " " e.g. let g:campo_files_to_ignore_when_stripping_trailing_whitespace = ['/z/modules/test.h', '/d/build/config.h'] let g:campo_files_to_ignore_when_stripping_trailing_whitespace = [] " If g:campo_strip_trailing_whitespace is 0 then you can force whitespace " stripping in specific directories by setting this to a list of full paths. " This has no effect when g:campo_strip_trailing_whitespace is 1. " " e.g. let g:campo_directories_to_force_stripping_trailing_whitespace = ['/z/modules '/d/build'] let g:campo_directories_to_force_stripping_trailing_whitespace = [] " If g:campo_strip_trailing_whitespace is 0 then you can force whitespace " stripping in specific files by setting this to a list of full file paths. " This has no effect when g:campo_strip_trailing_whitespace is 1. " " e.g. let g:campo_files_to_force_stripping_trailing_whitespace = ['/z/modules/test.h', '/d/build/config.h'] let g:campo_files_to_force_stripping_trailing_whitespace = [] "################################################################################## " SEARCH "################################################################################## " This is included in the ripgrep args. You can use this to do things like " ignore folders in your project or limit the search to specific file types. " For example, if you want to ignore the 3rd_party dir and only search C files " (remove the backslash from the first quote as that's just here to escape it " in this comment string) " let g:campo_custom_search_args = \"-g \"!3rd_party/*\" -tc" let g:campo_custom_search_args = "" "################################################################################## " CTAGS "################################################################################## " I use the ctags executable from https://github.com/universal-ctags/ctags-win32/releases " " Be sure to check out the ctags plugin config in section #5 for additional API functions. " If != 0 then ctag generation will always happen on a file save, otherwise " it'll only be triggered if the file being saved has an extension in the " g:campo_extensions_that_run_ctags list. let g:campo_force_ctags_regardless_of_extension = 0 " If one of these file types are saved then the ctags creation function will be called. " You can append to this list in another config like so: " let g:campo_extensions_that_run_ctags = g:campo_extensions_that_run_ctags + ['foo', 'bar'] let g:campo_extensions_that_run_ctags = ['c','cpp','h','hpp','inc','cs','py','asm','ex','exs'] " Default files and directories that ctags should ignore when doing a " recursive crawl. " @note The CreateCtags function will always ignore .git and node_modules " regardless of this variable's value. let g:campo_ctags_exclude = ['*.txt', '*.config', '.cache', 'run_tree'] " This is included in the ctags autocmd args. You can use this to customize " how ctags are built. " Examples: " * Recursive ctag generation with `let g:campo_custom_ctags_args = '-R'` " * Create tags for specific langauges: `let g:campo_custom_ctags_args = '--languages=C,C++,Elixir' " * You can see a list of languages with `ctags --list-languages` " * For C# you have to escape the ampersand like so: `--languages=C\\#` " * Exclude directories using g:campo_ctags_exclude " " We'll do it recursively by default. let g:campo_custom_ctags_args = "-R" " Set extra paths to use when searching for ctags files. By default the current " directory is always checked. You can use this to combine tag lookups from " different projects, e.g. set it to the Jai directory and you can look up " current project tags and Jai module tags (the latter isn't needed if you " have Jai module tags in your local file, which can be generated using the " ctags module at compile time). Related, if you're generating jai ctags and " the editor isn't finding module references then check if the current " directory is set to where the tags file exists. I've been caught up by this " because I have a build.jai in the root and my code in a src/ folder, so the " tags file gets created in the root and won't be seen if I've cd'd into src/ " when editing code. " " This destructively overwrites the tags option value. " " Call this from a .vimrc.private or .lvimrc file, e.g. " call g:SetExtraCtagsPaths([g:campo_jai_path.'/tags']) " " You can see what your ctags search list is set to in the editor with :echo &tags fu! g:SetExtraCtagsPaths(paths_array) let l:list = './tags,tags' " This is the default tags list set by vim. for path in a:paths_array let l:list .= ',' . path endfor let &tags=l:list endfu "################################################################################## " JAI "################################################################################## " Set to your Jai install path. Used for various commands, like for example " searching the modules and how_to directories with CtrlP let g:campo_jai_path = '' " Args to include when compiling a Jai file. let g:campo_jai_compiler_args = '' let g:campo_jai_metaprogram_args = '' "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #1 PLUGINS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| call plug#begin('~/.vim/plugged') " I've locked the plugins to stable commits. No need to upgrade them unless there are bug fixes. """"""""""""""""""" " # MISC """"""""""""""""""" Plug 'sir-pinecone/errormarker.vim' " Build error highlighting (requires skywind3000/asyncrun.vim). Plug 'sir-pinecone/vim-ripgrep' " Fast grep-like search. Requires ripgrep; install Rust package: `cargo install ripgrep`. Plug 'sir-pinecone/vim-qargs', " For the GlobalReplaceIt function (i.e. search and replace). Plug 'sir-pinecone/AnsiEsc.vim' " Ansi escape sequences concealed, but highlighted as specified. Plug 'sir-pinecone/ctrlp.vim', " Fuzzy file, buffer, mru, tag, etc finder. Plug 'sir-pinecone/ctrlp-py-matcher' " Significantly speed up ctrlp's fuzzy matcher. Plug 'vim-airline/vim-airline', { 'commit': 'c7460aa' } " Enhanced status/tabline. Plug 'embear/vim-localvimrc', { 'commit': '0206f5f' } " Add a .lvimrc to a folder to override .vimrc config. Plug 'tpope/vim-fugitive', { 'commit': '46eaf89' } " Git wrapper (I particularly like :Gblame, which I've wrapped as :Blame) Plug 'tpope/tpope-vim-abolish', { 'commit': 'dcbfe06' } " Search for, substitute, and abbreviate multiple variants of a word. Add to `after/plugin/abolish.vim` Plug 'tpope/vim-obsession', { 'commit': 'fe9d3e1' } " @flagged for removal. Continuously updated session files (tracks window positions, open folds, etc). Plug 'itchyny/vim-cursorword', { 'commit': '74a97c4' } " Underlines all instances of the symbol under the cursor. Requires a ctags file. Plug 'skywind3000/asyncrun.vim', { 'commit': '61cc308' } " (prev stable: 58d23e7) Async commands. Plug 'airblade/vim-gitgutter', { 'commit': 'fe0e8a2' } " Displays a git diff in the vim gutter and allows staging/unstaging of hunks. Plug 'majutsushi/tagbar', { 'commit': '5d6990e' } " Generates ctags on-demand and shows the current file's symbols. Doesn't support existing ctag files, so no Jai support. Plug 'tommcdo/vim-lion', { 'commit': 'ce46593' } " For text alignment, use gl= and gL= Plug 'editorconfig/editorconfig-vim', { 'commit': '95cb75e' } " Adds support for .editorconfig files. """"""""""""""""""" " # SYNTAX """"""""""""""""""" Plug 'sir-pinecone/jai.vim' " Jai Plug 'sir-pinecone/fasm.vim' " Flat Assembler Plug 'bfrg/vim-cpp-modern', { 'commit': 'cc7019b' } " C/C++ Plug 'elixir-editors/vim-elixir', { 'commit': '6dd03f8' } " Elixir Plug 'pprovost/vim-ps1', { 'commit': '308aac5' } " PowerShell Plug 'tpope/vim-markdown', { 'commit': 'f2b82b7' } " Markdown "Plug 'vim-ruby/vim-ruby' { 'commit': 'f06f069' } " Ruby """"""""""""""""""" " # THEMES """"""""""""""""""" Plug 'vim-airline/vim-airline-themes', { 'commit': '04fa4fc' } Plug 'dracula/vim', { 'commit': '6495b4f', 'as': 'dracula' } call plug#end() filetype plugin indent on "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #2 BASE CONFIG "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| set hidden set history=10000 set expandtab set tabstop=4 set shiftwidth=4 set softtabstop=4 set autoindent set laststatus=2 set showcmd " Display incomplete commands. set showmatch set incsearch " Highlight matches as you type. set hlsearch " Highlight matches. set dictionary+=/usr/share/dict/words "set clipboard=unnamed " Yank and paste with the system clipboard. set number set ignorecase smartcase " Make searches case-sensitive only if they contain upper-case characters. set visualbell " No bell sounds. set ttyfast set cmdheight=2 set switchbuf=useopen,split set numberwidth=5 set showtabline=2 set winwidth=79 " Use abbreviations. set shortmess=a " Remove gvim Menubar and Toolbar "set guioptions -=m "set guioptions -=T " @warning: This must come AFTER `set ignorecase smartcase` otherwise vim spews out a ton of errors. No idea why! if IsWindows() " Just assume we don't have a zsh shell set shell=bash else "set shell=zsh set shell=bash endif set t_ti= t_te= " Prevent Vim from clobbering the scrollback buffer. See http://www.shallowsky.com/linux/noaltscreen.html set scrolloff=3 " keep more context when scrolling off the end of a buffer set cursorline set cursorcolumn " Store swap, backup and undo files in a central spot. I have my settings in a " `vimrc.private` so that my drive paths aren't in this config. If you want to " set them here then add: " " set directory= " set backupdir= " if has('persistent_undo') " set undodir= " endif " " And make sure those directories exist before opening vim. " set backup set backupcopy=yes augroup backupCmds autocmd! autocmd BufWritePre * let &bex = '.' . strftime("%Y-%m-%d-%T") . '.bak' augroup END set writebackup " Make buckup before overwriting the current buffer. " Keep undo history across sessions by storing it in a file. The undo save " location is set in the vimrc.private file. You can also set it here with: " set undodir= " set undolevels=1000 " Allow undo when going back into a closed file set undoreload=10000 if has('persistent_undo') set undofile endif set backspace=indent,eol,start " Allow backspacing over everything in insert mode. set complete+=kspell " Spell checking autocomplete. set complete-=i " Don't scan all included files since it's really slow. set termguicolors " Disabled this because it clears my custom syntax highlights (see campoSyntax augroup) " when the vimrc file is resourced. I guess I don't need to set this to have " syntax highlighting. It's probably being enabled by a plugin. "set syntax on set wildmenu set wildmode=longest,list,full set wildignore+=*/log/*,*.so,*.swp,*.zip,*/rdoc/* " Allow inserts in the command bar to autocomplete, e.g. see e set wildcharm= if executable('rg') set grepprg=rg\ --vimgrep\ --hidden " Requires ripgrep to be installed. endif " Show trailing tabs and whitespace. set listchars=tab:»\ ,trail:·,extends:>,precedes:<,nbsp:+ set timeoutlen=250 ttimeoutlen=0 " Don't set it too low otherwise you won't be able to type use multi-key sequences. " Keeping this a bit low to make git-gutter updates faster. The original value " was 4000. I tried 250 and eventually started seeing some plugin errors " related to paren formatting. 800 seems to be the sweet spot. set updatetime=800 " Fix vim's background colour erase - http://snk.tuxfamily.org/log/vim-256color-bce.html if &term =~ '256color' " Disable Background Color Erase (BCE) so that color schemes " work properly when Vim is used inside tmux and GNU screen. " See also http://snk.tuxfamily.org/log/vim-256color-bce.html set t_ut= endif " Status line set statusline=%<%f\ (%{&ft})\ %-4(%m%)%=%-19(%3l,%02c%03V%) " Disable arrow keys. noremap noremap noremap noremap inoremap inoremap inoremap inoremap "///////////////////////////////////////////////// " LOAD PRIVATE VIMRC "///////////////////////////////////////////////// " This should be done after above base settings that don't use the global " campo variables. " " You can further customize things in a private vimrc. I use this for things " that I don't want included in my public dotfiles repo such as temp file settings. if filereadable($HOME . "/.vimrc.private") source $HOME/.vimrc.private endif " Settings that use the global campo variables: let &colorcolumn=g:campo_max_line_length "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #3 CUSTOM AUTOCMDS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| augroup campoCmds " Clear all autocmds in the group. autocmd! " Automatically wrap at N characters. autocmd FileType gitcommit setlocal colorcolumn=72 " @flagged for removal. It's a bit annoying. autocmd BufRead,BufNewFile *.{md,txt,plan} exec "setlocal textwidth=".g:campo_max_line_length " Enable spell checking by default. autocmd FileType gitcommit,markdown,text setlocal spell " Jump to last cursor position unless it's invalid or in an event handler. autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g`\"" | endif " Properly indent schemes (scheme, racket, etc). autocmd BufRead,BufNewFile *.{lisp,scm,rkt} setlocal equalprg=scmindent.vim.rkt " Elixir indent autocmd FileType elixir setlocal tabstop=2 | setlocal shiftwidth=2 | setlocal softtabstop=2 " Fasm indent; uses the fedorenchik/fasm.vim plugin. autocmd BufReadPre *.asm let g:asmsyntax = "fasm" " Auto reload VIM when settings changed. " Need to use silent! now because the save commands call a custom function " and without the silent we would see an error after sourcing saying that " we can't redefine the function that initiated the save. :ReloadVimrcError " " @fixme Reload lvimrc after sourcing this file on a save. I tried calling " a function that does the source and a call to lvimrc's API but got an " error complaining that the function cannot be created while it's in use. autocmd BufWritePost $MYVIMRC silent! source $MYVIMRC autocmd BufWritePost *.vim silent! source $MYVIMRC autocmd BufWritePost ~/.vimrc.private silent! source $MYVIMRC autocmd BufWritePost ~/.vimrc_templates.private silent! source $MYVIMRC " Remove trailing whitespace when saving any file. fu! StripTrailingWhitespaces() if g:campo_strip_trailing_whitespace == 1 if len(g:campo_directories_to_ignore_when_stripping_trailing_whitespace) for path in g:campo_directories_to_ignore_when_stripping_trailing_whitespace if path == expand('%:p:h') return endif endfor endif if len(g:campo_files_to_ignore_when_stripping_trailing_whitespace) for filename in g:campo_files_to_ignore_when_stripping_trailing_whitespace if (g:campo_use_full_paths_for_whitespace_stripping_file_filters == 1 && filename == expand('%:p')) || (g:campo_use_full_paths_for_whitespace_stripping_file_filters == 0 && filename == expand('%:t')) return endif endfor endif else let l:found_match = 0 if len(g:campo_directories_to_force_stripping_trailing_whitespace) for path in g:campo_directories_to_force_stripping_trailing_whitespace if path == expand('%:p:h') let l:found_match = 1 break endif endfor endif if l:found_match == 0 && len(g:campo_files_to_force_stripping_trailing_whitespace) for filename in g:campo_files_to_force_stripping_trailing_whitespace if (g:campo_use_full_paths_for_whitespace_stripping_file_filters == 1 && filename == expand('%:p')) || (g:campo_use_full_paths_for_whitespace_stripping_file_filters == 0 && filename == expand('%:t')) let l:found_match = 1 break endif endfor endif if l:found_match == 0 return endif endif let l = line(".") let c = col(".") %s/\s\+$//e call cursor(l, c) endfun autocmd BufWritePre * call StripTrailingWhitespaces() augroup END "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #4 KEY MAPPINGS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " Note: if there are two+ mappings that start with the same sequence then vim " will introduce a delay before the mapping is handled in order to wait for " the entire sequence to be typed. e.g. s and sg will " introduce a timeout delay, which will be noticed when you're trying to use " s. If you type sg then it'll be handled immediately, unless " there is a third mapping with that initial sequence, e.g. sgn. " " You can adjust the timeout duration by modidying the timeoutlen option. " " You can see when a keysequence is mapped to by entering :map " e.g. :map ,s " This is helpful when you are experiencing a timeout but you only have one " mapping defined in your vimrc. It's likely that a plugin added a similar " mapping sequence. " " The difference between map and noremap is that the former does recursive " expansion and the latter doesn't. The expansion means that if the mapped key " sequence contains any mappings then those mappings will be expanded as well. " This can lead to issues and confusion, so it's best to use noremap unless " you really have a reason not to. "################################################################################## " MISC "################################################################################## " Lowercase the e (have a habit of making it uppercase). :ca E e " Bail out of insert mode by double-pressing 'j'. " Disabling because it's annoying and I now have a keyboard that I can use " through the Steam Link that lets me map caps to ctrl, so I no longer need " this. " imap jj " Suspend vim process and return to the shell. Can return to vim with `fg`. nnoremap z " Open the vimrc file for editing / reload vimrc file. nnoremap ev :vsp $MYVIMRC nnoremap pv :vsp ~/.vimrc.private nnoremap rv :source $MYVIMRC " Type %/ in the command bar to have it replaced with the current buffer's " path if the file is on disk. One thing I noticed is that you have to type " the full %/ quickly otherwise it won't replace it. cmap %/ %:p:h/ " Open a terminal within vim. Use `exit` to close it. " DISABLING because I don't use this and I want to use the t for opening my todo file. "if exists(':terminal') " noremap t :terminal " tnoremap te " tnoremap h " tnoremap j " tnoremap k " tnoremap l " nnoremap h " nnoremap j " nnoremap k " nnoremap l "endif " Jump to other buffers. noremap noremap noremap noremap " Make it easier to jump around the command line. The default behaviour is " using the arrow keys with or without shift. :cnoremap :cnoremap " Window splitting - couldn't figure out how to remap v & n to " & noremap m :vsplit noremap mm :split " Disable treating special characters as regex when doing searches as it's so " fucking annoying having to escape them, particularly periods and asterisk. " This is done by setting the 'very nomagic' setting \V. nnoremap / /\V "----------------------------------------------------- " 'a' register helpers " " Faster way to activate the 'a' named register. These registers can be used " for various operations like yanking (copying), deleting, and pasting text. " Each register is identified by a single character. For example, there are " named registers 'a' to 'z', where text yanked or deleted can be specifically " stored and later retrieved. " " First activate the register with a and then do your yank, delete or " paste. Subsequent yanks into a will overwrite existing data. You can " append to the register by using aa " " This overwrites the contents of a. noremap a "a " This appends to a. noremap aa "A "----------------------------------------------------- " Backward replace word including cursor character. noremap d cvb " Allow fast pasting by accessing the system clipboard register. noremap p "+p " Likely won't need to use this if pasting with p, but just in case here ya go. noremap pp :set paste! paste? " Toggle line numbers. nnoremap o :set number! number? " Show spell checking. " You can add new entries to the dict by moving the cursor over the word and pressing `zg`. noremap j :exec &spell==&spell? "se spell! spelllang=en_us" : "se spell!" noremap = z= " Clear the search buffer (highlighting) when hitting return. nnoremap :nohlsearch " Switch to the previous file. nnoremap " Replace currently selected text with default register without yanking it. vnoremap p "_dP " Switch between C++ source and header files. noremap v :e %:p:s,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp, noremap vv :e %:p:s,.h$,.X123X,:s,.c$,.h,:s,.X123X$,.c, "noremap vvv :e %:p:s,.h$,.X123X,:s,.cc$,.h,:s,.X123X$,.cc, " Replace all instances of the highlighted text with whatever you enter. nnoremap :%s///g "################################################################################## " SAVING AND QUITING "################################################################################## " I used to have a BufWritePost autocommand that ran the ctags generator but " it ends up running multiple times when saving multiple buffers at once. In " order to only call it once for a group of saves I've had to remap the " various save commands to a function call. fu! CreateCtags() " Only allow one instance of ctags to run in this directory at any given time. let l:lock_file = "ctags.lock" if filereadable(l:lock_file) || filereadable("newtags") " Don't print a warning because this will always show when saving multiple files at the same time with a :wa or :xa return endif let l:extension = tolower(expand('%:e')) if (g:campo_force_ctags_regardless_of_extension == 0) && (index(g:campo_extensions_that_run_ctags, l:extension) < 0) "echo "Skipping ctags generation" return endif " Abort if the file is in a root drive directory because we don't want to recurse across the entire drive! let l:path = expand('%:p:h') if IsRootDrive(l:path) call PrintError("Not going to run ctags because the file is in a root drive directory") return endif " Abort if the file is in the home directory for the same reason as above. if l:path == expand('$HOME') call PrintError("Not going to run ctags because the file is in the home directory") return endif " Always ignore .git and node_modules let g:campo_ctags_exclude = g:campo_ctags_exclude + ['.git', 'node_modules'] let l:exclude_list = "" for name in g:campo_ctags_exclude let l:exclude_list .= "--exclude=" . name . " " endfor " Include local variables for C-like languages. let l:ctags_cmd = 'ctags '.l:exclude_list.' '.g:campo_custom_ctags_args.' --c-types=+l --c++-types=+l -o newtags' " Add the filename to the ctags command if not running in recursive mode. let l:recursive = matchstr(g:campo_custom_ctags_args, '\(-R\s\)\|\(-R$\)\|\(--recurse=yes\)\|\(--recurse\s\)\|\(--recurse$\)') if l:recursive == '' let l:ctags_cmd .= ' ' . expand('%:t') echo "Creating non-recursive ctags" else echo "Creating recursive ctags" endif " The ampersand at the end is to make this run in the background. I had to group the " commands in parens to make the chained commands run in the background. let l:cmd = '!(touch '.l:lock_file.'; '.l:ctags_cmd.'; mv newtags tags &>/dev/null; rm '.l:lock_file.') &' silent! exec l:cmd endfun " @note We have an autocmd that reloads the vimrc files after they're saved. " These write functions below will not be reloaded because they initiate the " save. So if you make changes to them then you need to manually reload this " file using rv or whatever. :ReloadVimrcError fu! WriteCurrentFileAndCreateCtags() write! call CreateCtags() endfu fu! WriteCurrentFileAndCreateCtagsThenQuit() write! call CreateCtags() quit endfu " @fixme Sometimes a :wa that saves multiple files causes vim to hang and use a lot of CPU. fu! WriteAllModifiedFilesAndCreateCtags() wall! call CreateCtags() endfu " Create a command abbreviation that only applies when it's at the beginning " of a ex command. If you were to do a normal cnoreabbrev or cabbrev then the " subsitution can happen anywhere in the command line. It was mostly affecting " my text search; I'd type '/w' and it would be replaced with the call to save " and create ctags. fu! Cabbrev(key, value) exe printf('cabbrev %s (getcmdtype() == ":" && getcmdpos() <= %d) ? %s : %s', \ a:key, len(a:key)+1, string(a:value), string(a:key)) endfu call Cabbrev('w', 'call WriteCurrentFileAndCreateCtags()') call Cabbrev('W', 'call WriteCurrentFileAndCreateCtags()') call Cabbrev('wa', 'call WriteAllModifiedFilesAndCreateCtags()') call Cabbrev('Wa', 'call WriteAllModifiedFilesAndCreateCtags()') call Cabbrev('WA', 'call WriteAllModifiedFilesAndCreateCtags()') call Cabbrev('wq', 'call WriteCurrentFileAndCreateCtagsThenQuit()') call Cabbrev('Wq', 'call WriteCurrentFileAndCreateCtagsThenQuit()') call Cabbrev('WQ', 'call WriteCurrentFileAndCreateCtagsThenQuit()') " Faster way to open a file in the same directory. " will autocomplete the expansion here because we set wildcharm to . nnoremap e :e %:p:h/ " Jai folders nnoremap ee :e =g:campo_jai_path/ nnoremap em :e =g:campo_jai_path/modules/ nnoremap eh :e =g:campo_jai_path/how_to/ nnoremap w :call WriteCurrentFileAndCreateCtags() nnoremap x :call WriteCurrentFileAndCreateCtagsThenQuit() nnoremap q :q call Cabbrev('Q', 'q') call Cabbrev('Qa', 'qa') command! Qa qall " Disable Ex mode. noremap Q "################################################################################## " MULTIPURPOSE TAB KEY "################################################################################## fu! InsertTabWrapper() let l:col = col('.') - 1 if !l:col || getline('.')[l:col - 1] !~ '\k' return "\" else return "\" endif endfu inoremap =InsertTabWrapper() inoremap "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #5 PLUGIN CONFIGS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "################################################################################## " LOCAL VIMRC "################################################################################## let g:localvimrc_sandbox = 0 let g:localvimrc_ask = 0 "################################################################################## " EDITOR CONFIG "################################################################################## let g:EditorConfig_exclude_patterns = ['fugitive://.*'] "################################################################################## " LION (TEXT ALIGNMENT) "################################################################################## let b:lion_squeeze_spaces = 1 "################################################################################## " TAGBAR "################################################################################## noremap :TagbarToggle " Sort tags by their order in the source file. Press 's' to sort them alphabetically. let g:tagbar_sort = 0 "################################################################################## " GITGUTTER "################################################################################## let g:gitgutter_enabled = 1 let g:gitgutter_highlight_lines = 0 nnoremap hh :GitGutterToggle nmap ha (GitGutterStageHunk) nmap [h (GitGutterNextHunk) nmap ]h GitGutterPrevHunk augroup gitGutterPluginCmds autocmd! " Update marks on save autocmd BufWritePost * GitGutter augroup END "################################################################################## " SYNTASTIC "################################################################################## " NOTE: there is a status line config in the status line section let g:syntastic_always_populate_loc_list = 1 let g:syntastic_auto_loc_list = 1 let g:syntastic_check_on_open = 0 let g:syntastic_check_on_wq = 0 " Customize Rust " https://github.com/rust-lang/rust.vim/issues/130 " Can remove once this Syntastic PR is merged https://github.com/rust-lang/rust.vim/pull/132 "let g:syntastic_rust_rustc_exe = 'cargo check' "let g:syntastic_rust_rustc_fname = '' "let g:syntastic_rust_checkers = ['rustc'] "################################################################################## " RIPGREP "################################################################################## let g:rg_highlight = 1 let g:rg_window_height = g:quickfix_pane_height "################################################################################## " CTRL-P "################################################################################## " keybindings: " " leader-f = open CtrlP in tag searching mode. " leader-g = open CtrlP in file searching mode. " ctrl-f = toggle search mode " enter = open result in a current buffer or in an already open buffer for that file. " ctrl-v = open result in a vertically-split buffer. " ctrl-h = open result in a horizontally-split buffer. " ctrl-t = open result in a new tab. " ctrl-j | ctrl-k = move up and down the search results. " ctrl-y = create file and open it. " ctrl-z = mark multiple file search results to open (I think you can only use ctrl-v or ctrl-x and not enter). " ctrl-o = ask how to open a file search result. " ctrl-o = ask how to open a file search result. " ctrl-p | ctrl-n = traverse search history. " CtrlP finds the tags file by using vim's 'tags' option value. I initially " tried changing the tags value to the working_path if one is provided. I made " a copy of the current tags value and attempted to restore it when CtrlP was " exited without an action or a tag action took place. I wasn't able to get " the tags restored because there's no vim event that gets triggered when a " tag is opened using the :tag comment. I tried a bunch of autocmds on buffer, " window and cmdline events but wasn't able to find anything that fired AFTER " the tag command. So we're instead just setting the tags list once for the " session and not bothering with changing it on the fly. See g:SetExtraCtagsPaths fu! CtrlP_Search(search_path) " If a:search_path is empty then ctrlp will use g:ctrlp_working_path_mode to determine the cwd to search in. execute 'CtrlP ' . a:search_path endfu fu! CtrlP_JaiSearch(jai_rel_search_path) if g:campo_jai_path == '' call PrintError("g:campo_jai_path isn't set!") return endif let l:path = g:campo_jai_path if a:jai_rel_search_path != '' let l:path .= '/' . a:jai_rel_search_path endif if !isdirectory(l:path) call PrintError("Directory ".l:path." doesn't exist.") return endif call CtrlP_Search(l:path) endfu " CtrlP File Searching noremap g :call CtrlP_Search('') " Search in current directory noremap gg :call CtrlP_JaiSearch('') " Search in Jai directory noremap gm :call CtrlP_JaiSearch('modules') " Search in Jai modules noremap gh :call CtrlP_JaiSearch('how_to') " Search in Jai how_to noremap ge :call CtrlP_JaiSearch('examples') " Search in Jai examples " @note we're using a modified version of ctrlp that removes duplicate tags " when using multiple tag files. See https://github.com/sir-pinecone/ctrlp.vim/commit/5cceab let g:ctrlp_map = 'f' let g:ctrlp_cmd = 'CtrlPTag' " Search tags by default. let g:ctrlp_by_filename = 1 " File search by filename as opposed to full path. let g:ctrlp_match_window = 'bottom,order:ttb,min:10,max:'.g:quickfix_pane_height.',results:'.g:quickfix_pane_height let g:ctrlp_use_caching = 1 let g:ctrlp_clear_cache_on_exit = 1 " No need to keep cache for now since I mostly work in git repos. Press F5 inside CtrlP to rebuild the cache. let g:ctrlp_working_path_mode = 'ra' " Search from nearest ancestor of the current file that contains .git OR directory of the current file unless it's a subdirectory of the cwd let g:ctrlp_switch_buffer = 'et' " If a file is already open, open it again in a new pane instead of switching to the existing pane let g:ctrlp_custom_ignore = '\v[\/]\.(git|hg|svn)$' let g:ctrlp_user_command = ['.git', 'cd %s && git ls-files -co --exclude-standard'] " If a git repo, use checked in files (ignore things in .gitignore); fallback to globpath() let g:ctrlp_match_func = { 'match': 'pymatcher#PyMatch' } "################################################################################## " GIT "################################################################################## noremap gb :Git blame -w " Ignore whitespace changes; follow renames and copies. command! -bar -bang -nargs=* Blame :Git blame -wCM command! -bar -bang -nargs=* Gblame :Git blame -wCM "################################################################################## " VIM-CLOJURE-STATIC "################################################################################## " Default let g:clojure_fuzzy_indent = 1 let g:clojure_align_multiline_strings = 1 let g:clojure_fuzzy_indent_patterns = ['^match', '^with', '^def', '^let'] let g:clojure_fuzzy_indent_blacklist = ['-fn$', '\v^with-%(meta|out-str|loading-context)$'] "################################################################################## " RUST.VIM "################################################################################## "let g:rustfmt_autosave = 1 " auto run rust formatter when saving "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #6 VISUALS (COLORS, HIGHLIGHTING) "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "########################################################################### " COLORS "########################################################################### " Custom notes highlights. These are used in my color schemes. " " @incomplete I can't get these to only match in comments. Not a big deal but " would be nice to fix. augroup campoSyntax autocmd! autocmd Syntax * syntax match MyAnnotatedNote /@\S\+/ containedin=.*Comment,vimCommentTitle display autocmd Syntax * syntax match MyTitle /#\+ .\+$/ containedin=.*Comment,vimCommentTitle display autocmd Syntax * syntax match MyNote /\v<(NOTE|IDEA|TODO):/ containedin=.*Comment,vimCommentTitle display autocmd Syntax * syntax match MyEmphasis /\v<(WARNING|IMPORTANT):/ containedin=.*Comment,vimCommentTitle display autocmd Syntax * syntax match MyBug /\v<(FIXME|BUG|DEPRECATED):/ containedin=.*Comment,vimCommentTitle display highlight link MyAnnotatedNote CampoAnnotatedNote highlight link MyTitle CampoTitle highlight link MyNote CampoNote highlight link MyEmphasis CampoEmphasis highlight link MyBug CampoBug augroup END " Toggle between light and dark themes. noremap l :call ToggleLightDarkTheme() let s:current_light_dark_mode = g:campo_light_dark_mode fu! ToggleLightDarkTheme() if s:current_light_dark_mode == 'light' call ChangeLightDarkMode('dark', 0) else call ChangeLightDarkMode('light', 0) endif endfu fu! ChangeLightDarkMode(mode, onlySetTheme) if a:mode == 'light' let s:theme = g:campo_light_theme exe 'colorscheme ' . s:theme set background=light else let s:theme = g:campo_dark_theme exe 'colorscheme ' . s:theme set background=dark endif let s:current_light_dark_mode = a:mode if !a:onlySetTheme exec 'AirlineTheme' a:mode endif endfu " Set the intial light/dark mode. if g:campo_light_dark_mode =~ 'light' call ChangeLightDarkMode('light', 1) else call ChangeLightDarkMode('dark', 1) endif " Open the current color scheme for editing. fu! EditColorScheme() let l:path = g:vim_dir . '/colors/' . s:theme . '.vim' if filereadable(l:path) execute 'vsplit ' . l:path else call PrintError("Failed to open " . l:path . " for editing.") endif endfu command -nargs=0 EditColorScheme call EditColorScheme() "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| " #7 CUSTOM FUNCTIONS & COMMANDS "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| "||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| fu! IsRootDrive(path) abort let l:path_without_slashes = substitute(a:path, "/", "", "g") return strchars(l:path_without_slashes) <= 1 endfu fu! IsPathContained(path1, path2) abort let l:normalized_path1 = substitute(fnamemodify(a:path1, ':p'), '\', '/', 'g') let l:normalized_path2 = substitute(fnamemodify(a:path2, ':p'), '\', '/', 'g') " Ensure paths end with a directory separator. if l:normalized_path1[-1:] != '/' let l:normalized_path1 .= '/' endif if l:normalized_path2[-1:] != '/' let l:normalized_path2 .= '/' endif echo l:normalized_path1 echo l:normalized_path2 return match(l:normalized_path1, '^' . escape(l:normalized_path2, '\')) != -1 endfu fu! CountChar(str, char) abort " Remove all characters that are not the target character. let l:filtered = substitute(a:str, '[^' . a:char . ']', '', 'g') return strlen(l:filtered) endfu fu! WindowsToUnixPath(str) abort let l:result = substitute(a:str, '\\', '/', 'g') return l:result endfu fu! ConvertQuickfixPathsToUnixSlashes() let l:qflist = getqflist() for i in range(len(l:qflist)) let l:bufnr = l:qflist[i]['bufnr'] if l:bufnr != -1 && bufexists(l:bufnr) let l:filename = bufname(l:bufnr) let l:filename = substitute(l:filename, '\\', '/', 'g') let l:qflist[i]['module'] = l:filename endif endfor call setqflist(l:qflist) endfu "################################################################################## " COMPILING CODE "################################################################################## " AsyncRun status line let g:airline_section_error = airline#section#create_right(['%{g:asyncrun_status}']) " "make" value is needed for asyncrun to work with the errormarker plugin " https://github.com/skywind3000/asyncrun.vim/wiki/FAQ#can-asyncrunvim-trigger-an-autocommand-quickfixcmdpost-to-get-some-plugin-like-errormaker-processing-the-content-in-quickfix- let g:asyncrun_auto = "make" " Error and warning gutter characters. let errormarker_errortext = "E" let errormarker_warningtext = "W" let errormarker_infotext = "I" " Gutter character colors. See color file for the group definition. let errormarker_errortextgroup = "BuildError" let errormarker_warningtextgroup = "BuildWarn" let errormarker_infotextgroup = "BuildInfo" " Colors for the marked lines. See color file for the group definition. let errormarker_errorgroup = "BuildError" let errormarker_warninggroup = "BuildWarn" let errormarker_infogroup = "BuildInfo" " I don't know how to map to a plugin command, so I'm wrapping it with a function...ugh. fu! ShowErrorAtCursor() " This is defined in errormarker.vim ErrorAtCursor endfu nnoremap ce :call ShowErrorAtCursor() "///////////////////////////////////////////////// " CUSTOM ERROR FORMATS "///////////////////////////////////////////////// " @note: You can debug the error parsing by running :ShowErrorEntries " This will print the valid entries. You'll know parsing is correct when the " entries have a type, line num, error message, etc. fu! ShowErrorEntries() " Prints out valid quickfix errors. redraw! for l:d in getqflist() if l:d.valid == 1 echomsg l:d endif endfor endfu command -nargs=0 ShowErrorEntries call ShowErrorEntries() " Jai " " Z:\path\main.jai:100,10: Error: Undeclared identifier 's1'. set errorformat=\\\ %#%f:%l\\,%c:\ %t%[A-z]%#:\ %m " Microsoft compiler: cl.exe " " Z:\path\main.cpp(2808): error C2220: the following warning is treated as an error set errorformat+=\\\ %#%f(%l):\ %#%t%[A-z]%#\ %[A-z]%#%n:\ %m " Microsoft MSBuild errors " " @note I got this off the Internet and haven't tested it yet. " " Z:\path\main.cpp(2808): error C2220: the following warning is treated as an error set errorformat+=\\\ %#%f(%l\\,%c):\ %m " Microsoft HLSL compiler: fxc.exe " " @note I got this off the Internet and haven't tested it yet. " @todo Add an example set errorformat+=\\\ %#%f(%l\\\,%c-%*[0-9]):\ %#%t%[A-z]%#\ %m "///////////////////////////////////////////////// " BUILD FUNCTIONS "///////////////////////////////////////////////// fu! HideBuildResultsAndClearErrors() RemoveErrorMarkers call asyncrun#quickfix_toggle(g:quickfix_pane_height, 0) endfu fu! HideAsyncResults() call asyncrun#quickfix_toggle(g:quickfix_pane_height, 0) endfu fu! ToggleBuildResults() call asyncrun#quickfix_toggle(g:quickfix_pane_height) endfu fu! StopRunTask() AsyncStop call HideAsyncResults() endfu " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram " @incomplete use the same path searching from RunProgram fu! Build(optimized=0, silent=0) abort let l:async_cmd = "AsyncRun! " if a:silent let l:async_cmd .= "-post=call\\ HideAsyncResults() " endif let l:is_jai = 0 let l:has_jai_build_file = 0 let l:has_jai_first_file = 0 let l:ext = tolower(expand('%:e')) let l:current_dir = expand('%:p:h') let l:one_dir_back = expand('%:p:h:h') let l:cmd = "" if l:ext == "jai" let l:is_jai = 1 " Check for a build file in the current directory or one directory back when one directory back isn't the root of a drive. " (e.g. we're in modules/ or src/, code/, etc) if filereadable(l:current_dir . "/build.jai") || filereadable(l:current_dir . "/first.jai") || ((l:one_dir_back != "/") && (filereadable(l:one_dir_back . "/build.jai") || filereadable(l:one_dir_back . "/first.jai"))) let l:has_jai_build_file = 1 if filereadable(l:current_dir . "/build.jai") let l:cmd = "jai ". l:current_dir . "/build.jai" elseif filereadable(l:current_dir . "/first.jai") let l:cmd = "jai ". l:current_dir . "/first.jai" let l:has_jai_first_file = 1 else " It's one directory back. We don't want to include '../' in " the cmd because then our reported paths in the program get " botched, e.g. path shown in an assert error. if filereadable(l:one_dir_back . "/build.jai") let l:cmd = "jai " . l:one_dir_back . "/build.jai" else let l:cmd = "jai " . l:one_dir_back . "/first.jai" let l:has_jai_first_file = 1 endif endif else let l:cmd = "jai % " endif else if filereadable("build") && !isdirectory("build") let l:cmd .= './build ' elseif filereadable("build.sh") let l:cmd .= './build.sh ' elseif filereadable("build.bat") let l:cmd .= './build.bat ' else let l:cmd .= './build* ' endif if a:optimized == 1 let l:cmd .= ' -o' endif endif if l:is_jai let l:cmd .= ' '.g:campo_jai_compiler_args let l:set_metaprogram_args = 0 if l:has_jai_build_file let l:filename = "build.jai" if l:has_jai_first_file let l:filename = "first.jai" endif if a:optimized == 1 echo "Compiling release " . l:filename " @note We pass 'release' as a user metaprogram arg for the " build file to parse in case it cares about that. -release is " a compiler arg that we also include because some build " scripts won't be looking at the user metaprogram args. " We also don't bother adding an import directory for local modules " because the build file should manage that sort of thing for us. let l:cmd .= " -release - -release" let l:set_metaprogram_args = 1 else echo "Compiling debug " . l:filename endif else if a:optimized == 1 echo "Compiling release " . expand('%:t') let l:cmd .= " -release" else echo "Compiling debug " . expand('%:t') endif endif if g:campo_jai_metaprogram_args != "" if l:set_metaprogram_args == 1 let l:cmd .= ' '.g:campo_jai_metaprogram_args else let l:cmd .= ' - '.g:campo_jai_metaprogram_args endif endif endif " I was originally passing -save=2 to AsyncRun! in order to save all " modified files (it just does a `silent! wall` call), but I want ctags to " be generated so we're handling the save ourselves. call WriteAllModifiedFilesAndCreateCtags() exec l:async_cmd . l:cmd endfu fu! RunProgram() abort if tolower(expand('%:e')) == "py" exec "AsyncRun! python %" return endif " Sometimes vim mixes unix and windows slashes so we need to normalize and " use this in future fnamemodify calls (can't use expand with a string " that doesn't contain wildcards). let l:full_path = WindowsToUnixPath(expand('%')) let l:file_exe_name = fnamemodify(l:full_path, ':t:r').'.exe' " Just the filename without the path " Easy case is the current file has an exe with the same name in the " same directory. We run that if found. if filereadable(fnamemodify(l:full_path, ':p:h') . '/' . l:file_exe_name) exec 'AsyncRun! ' . fnamemodify(l:full_path, ':p:h') . '/' . l:file_exe_name return endif " We start by looking for something to run relative to the current file's " path. If nothing is found then we start over looking in the current " working directory (the directory vim was opened in). let l:current_path = fnamemodify(l:full_path, ':p:h') if !RunProgramInDirectory(l:current_path, l:file_exe_name) let l:cwd = getcwd() if !RunProgramInDirectory(l:cwd, l:file_exe_name) call PrintError("No exe or run script found in current file path '" . l:current_path . "' or working directory '". l:cwd ."'") endif endif endfu fu! RunProgramInDirectory(starting_path, file_exe_name) abort let l:search_path = a:starting_path let l:ext = tolower(expand('%:e')) let l:also_search_for_cpp_run_script = 0 " Covers the case of having a run-cpp file in a jai project. if l:ext == "jai" " Check if we're editing inside a modules file and if so then get a " path that takes us outside of it. We'll use that path for finding an " exe or run script. let l:pos = match(l:search_path, "modules") if l:pos != -1 " e.g. if the current path is " /z/jai/examples/wasm/modules/Blah/blah.jai then this is /z/jai/examples/wasm/ let l:module_root_path = l:search_path[:l:pos-1] " We don't want to proceed if the path is in the jai compiler's modules folder. let l:jai_path = tolower(g:campo_jai_path) if l:jai_path[-1:] != '/' let l:jai_path .= '/' endif "echo 'modules root path: ' . l:module_root_path . ' | jai path: ' . l:jai_path if tolower(l:module_root_path) == l:jai_path "echo 'inside jai modules. Aborting run' return 0 endif let l:search_path = l:module_root_path endif elseif l:ext == "cpp" || l:ext == "c" || l:ext == "h" || l:ext == "inc" let l:also_search_for_cpp_run_script = 1 endif let l:search_path = fnamemodify(l:search_path, ':p') " Normalize the path just in case. " Search the current path for a run script, an exe with the current " filename, a bin/filename exe, a run_tree/filename exe - if nothing is " found then repeat one directory back when the current folder doesn't " contain a .git/ or it's not a root directory, e.g. /z/. " " If nothing is found after exhausting the search then we start over and " find the first exe with any name. fu! TryRun(path) abort if filereadable(a:path) exec "AsyncRun! " . a:path return 1 endif return 0 endfu let l:current_path = l:search_path while 1 " run script if l:also_search_for_cpp_run_script if TryRun(l:current_path . 'run-cpp') | return 1 | endif endif if TryRun(l:current_path . 'run') | return 1 | endif " run_tree/run script if l:also_search_for_cpp_run_script if TryRun(l:current_path . 'run_tree/run-cpp') | return 1 | endif endif if TryRun(l:current_path . 'run_tree/run') | return 1 | endif " filename exe if TryRun(l:current_path . a:file_exe_name) | return 1 | endif " bin/filename exe if TryRun(l:current_path. 'bin/' . a:file_exe_name) | return 1 | endif " run_tree/filename exe if TryRun(l:current_path. 'run_tree/' . a:file_exe_name) | return 1 | endif " Only go back a directory if the current path doesn't have a .git folder or we're not in a root drive path. if isdirectory(l:current_path . '.git') || IsRootDrive(l:current_path) break endif let l:current_path = fnamemodify(l:current_path."../", ':p') endwhile """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" " Start over but look for the first exe. The user will confirm if they " want to run it. " " @improve maybe provide the first 4 or 5 exes found and let them input " which one they want? fu! GetFirstExePath(path) let l:files = systemlist('ls '.a:path.'*.exe 2>/dev/null') if len(l:files) > 0 return l:files[0] endif return "" endfu let l:current_path = l:search_path let l:exe_to_confirm = "" while 1 let l:exe = GetFirstExePath(l:current_path) if l:exe != "" let l:exe_to_confirm = l:exe break endif " bin/filename exe let l:exe = GetFirstExePath(l:current_path."bin/") if l:exe != "" let l:exe_to_confirm = l:exe break endif " run_tree/filename exe let l:exe = GetFirstExePath(l:current_path."run_tree/") if l:exe != "" let l:exe_to_confirm = l:exe break endif " Only go back a directory if the current path doesn't have a .git folder or we're not in a root drive path. if isdirectory(l:current_path . '.git') || IsRootDrive(l:current_path) break endif let l:current_path = fnamemodify(l:current_path."../", ':p') endwhile if l:exe_to_confirm != "" let l:confirm = confirm("Found exe ".l:exe_to_confirm." - run this?", "&Yes\n&No") if l:confirm == 1 exec "AsyncRun! " . l:exe_to_confirm return 1 endif redraw! endif return 0 endfu " Show results window the moment the async job starts augroup asyncPluginCmds autocmd! autocmd User AsyncRunStart call asyncrun#quickfix_toggle(g:quickfix_pane_height, 1) augroup END " Toggle build results nnoremap bc :call ToggleBuildResults() noremap :call ToggleBuildResults() " Hide build results and clear errors noremap :call HideBuildResultsAndClearErrors() " Execute build script and display results. nnoremap b :call Build(0) nnoremap :call Build(0) " Execute optimized build script nnoremap bb :call Build(1) " Execute run script nnoremap br :call RunProgram() nnoremap :call RunProgram() nnoremap bs :AsyncStop "Go to next build error nnoremap :cn nnoremap :cn "Go to previous build error nnoremap :cp nnoremap :cp "################################################################################## " TEXT SEARCH "################################################################################## " Search using ripgrep (first install with Rust: cargo install ripgrep). fu! Search(path, search_args, case_insensitive=0) let l:helper = "Search '" . a:path . "' (" . (len(a:search_args) > 0 ? a:search_args . " | " : '') . (a:case_insensitive ? "INSENSITIVE" : "SENSITIVE") . "): " let l:term = input(l:helper) if empty(l:term) return endif " @note --pretty (i.e. colors) is not enabled in vim-ripgrep because the " quickfix window doesn't seem to parse the ansi color codes. let l:rg_args = "--column --line-number --no-heading --fixed-strings --no-ignore --hidden --follow --trim -g \"!tags\" -g \"!.git/\" -g \"!AppData/\" " . a:search_args if a:case_insensitive let l:rg_args .= " --ignore-case" endif " Some characters need to be escaped. let l:escaped_term = substitute(l:term, '[#%]', '\\\\\\&', 'g') let l:format = 'Rg ' . l:rg_args . ' ' . a:path . ' -e %s' let l:cmd = printf(l:format, shellescape(l:escaped_term)) exec l:cmd call ConvertQuickfixPathsToUnixSlashes() endfu fu! SearchExt(path, search_args, case_insensitive=0) call inputsave() let l:ext = input('Enter extension to search on (leave blank for files with no ext): ') call inputrestore() redraw! if empty(l:ext) let l:ext = "!*.*" else let l:ext = "*.".l:ext endif let l:args = a:search_args." -g \"".l:ext."\"" call Search(a:path, l:args, a:case_insensitive) endfu "///////////////////////////////////////////////// " SEARCH IN CURRENT WORKING DIRECTORY "///////////////////////////////////////////////// " Case insensitive: nnoremap s :call Search( '.', g:campo_custom_search_args, 1) nnoremap sd :call SearchExt('.', g:campo_custom_search_args, 1) " Case sensitive: nnoremap ss :call Search( '.', g:campo_custom_search_args, 0) nnoremap ssd :call SearchExt('.', g:campo_custom_search_args, 0) "///////////////////////////////////////////////// " SEARCH IN DIRECTORY CONTAINING THE ACTIVE FILE "///////////////////////////////////////////////// " Case insensitive: nnoremap sf :call Search( expand('%:p:h'), g:campo_custom_search_args, 1) nnoremap sdf :call SearchExt(expand('%:p:h'), g:campo_custom_search_args, 1) " Case sensitive: nnoremap ssf :call Search( expand('%:p:h'), g:campo_custom_search_args, 0) nnoremap ssdf :call SearchExt(expand('%:p:h'), g:campo_custom_search_args, 0) "///////////////////////////////////////////////// " SEARCH IN JAI FOLDERS "///////////////////////////////////////////////// " Case insensitive: " " ROOT nnoremap sg :call Search( g:campo_jai_path, g:campo_custom_search_args, 1) nnoremap sdg :call SearchExt(g:campo_jai_path, g:campo_custom_search_args, 1) " MODULES nnoremap sm :call Search( g:campo_jai_path.'/modules', g:campo_custom_search_args, 1) nnoremap sdm :call SearchExt(g:campo_jai_path.'/modules', g:campo_custom_search_args, 1) " HOW TO nnoremap sh :call Search( g:campo_jai_path.'/how_to', g:campo_custom_search_args, 1) nnoremap sdh :call SearchExt(g:campo_jai_path.'/how_to', g:campo_custom_search_args, 1) " EXAMPLES nnoremap se :call Search( g:campo_jai_path.'/examples', g:campo_custom_search_args, 1) nnoremap sde :call SearchExt(g:campo_jai_path.'/examples', g:campo_custom_search_args, 1) " Case sensitive: " " ROOT nnoremap ssg :call Search( g:campo_jai_path, g:campo_custom_search_args, 0) nnoremap ssdg :call SearchExt(g:campo_jai_path, g:campo_custom_search_args, 0) " MODULES nnoremap ssm :call Search( g:campo_jai_path.'/modules', g:campo_custom_search_args, 0) nnoremap ssdm :call SearchExt(g:campo_jai_path.'/modules', g:campo_custom_search_args, 0) " HOW TO nnoremap ssh :call Search( g:campo_jai_path.'/how_to', g:campo_custom_search_args, 0) nnoremap ssdh :call SearchExt(g:campo_jai_path.'/how_to', g:campo_custom_search_args, 0) " EXAMPLES nnoremap sse :call Search( g:campo_jai_path.'/examples', g:campo_custom_search_args, 0) nnoremap ssde :call SearchExt(g:campo_jai_path.'/examples', g:campo_custom_search_args, 0) " Navigation for the vim-ripgrep search results. " Hit o on a result line to open the file at that line. " Hit p on a result line to open the file at that line and return to the results pane. nnoremap o (&buftype is# "quickfix" ? "\|:lopen" : "o") nnoremap p (&buftype is# "quickfix" ? "\|:copen" : "p") "################################################################################## " TEXT SEARCH & REPLACE "################################################################################## " @warning I've stopped using this because it sometimes locks up vim and I " have to force exit the process then clean up the swap files. " " @improve Figure out what's causing vim to freeze and fix it. Seems to happen " when it quickly changes and saves a handful of buffers. " Replace text in a git repo's committed files. " The range identifier allows us to run this once when multiple lines are selected in a file. fu! GlobalReplaceIt(confirm_replacement) range if exists(':Ggrep') call inputsave() if a:confirm_replacement let l:term = input('Enter search term (w/ confirmation): ') else let l:term = input('Enter search term (no confirmation): ') endif call inputrestore() if empty(l:term) return endif call inputsave() let l:replacement = input('Enter replacement: ') call inputrestore() if empty(l:replacement) return endif if a:confirm_replacement let l:confirm = 'c' else let l:confirm = 'e' endif " Capture opened buffers and windows so that we can restore everything after running cdo. exec 'mksession! _replace_session.vim' " Search all committed files in the current directory " Ignoring binary files (-I) " Only including a matching filename once (--name-only) exec 'Ggrep --threads 4 -I --name-only' l:term '.' " cdo will run the command foreach file in the grep results. It opens " the file in a window so we immediately write the changes and then " wipe the buffer (closing the window). If we don't close it then vim " will run out of space when modifying a lot of files. This will " likely close buffers that were open before running the replace, but " we will restore them below from the saved session file. " Note that we don't run autocommands for optimization purposes! :noautocmd exec 'cdo' '%s/'.l:term.'/'.l:replacement.'/g'.l:confirm '| write | bwipe' " Restore the session. if filereadable('_replace_session.vim') silent! exec 'source _replace_session.vim | !rm _replace_session.vim &>/dev/null' endif call CreateCtags() else call PrintError("Unable to search since you're not in a git repo!") endif endfu noremap r :call GlobalReplaceIt(0) noremap rr :call GlobalReplaceIt(1) "################################################################################## " RENAME CURRENT FILE "################################################################################## fu! RenameFile() let l:old_name = expand('%') let l:new_name = input('New file name: ', expand('%'), 'file') if l:new_name != '' && l:new_name != l:old_name let l:confirm = confirm("Rename ".l:old_name." to ".l:new_name."?", "&Yes\n&No") if l:confirm == 1 exec 'saveas' l:new_name exec 'silent! !rm' l:old_name endif endif redraw! endfu noremap n :call RenameFile() "################################################################################## " CENTER THE BUFFER "################################################################################## fu! CenterPane() " Centers the current pane as the middle 2 of 4 imaginary columns should " be called in a window with a single pane. " Taken from https://dev.to/vinneycavallo/easily-center-content-in-vim lefta vnew wincmd w exec 'vertical resize' string(&columns * 0.65) endfu fu! RemoveCenterPane() wincmd w close endfu nnoremap cc :call CenterPane() nnoremap cd :call RemoveCenterPane() "################################################################################## " SIMPLE VIEW "################################################################################## " Applies a clean view of the current buffer by turning off line " numbers, trailing spaces, tabs and git diff markers in the gutter. fu! ToggleSimpleView() " I wasn't able to get this to apply to every copy of the same buffer, " e.g. you have a split view showing the same file; only the active one " will be affected, but they will share the same state! I tried many " different approaches (bufdo, looping over windows and buffers) and " nothing worked. I looked at the gitgutter plugin since it supports " toggling a buffer and it correctly modifies all instances, but the code " is complex and there's so much just to do a simple thing. Fuck it. It's " not worth the hassle and the inevitable hair pulling that I always " experience when trying to do non-trivial things with this garbage " scripting language. if ! exists("b:simple_view_enabled") let b:simple_view_enabled = 0 endif if b:simple_view_enabled == 0 let b:simple_view_enabled = 1 setlocal nonumber setlocal nolist exec 'GitGutterBufferDisable' else let b:simple_view_enabled = 0 setlocal number setlocal list exec 'GitGutterBufferEnable' endif endfu nnoremap c :call ToggleSimpleView() "----------------------------------------------------------------------------------------------------------------------- let g:campo_vimrc_initialized = 1