dotfiles/.vimrc

1731 lines
69 KiB
VimL

"-----------------------------------------------------------------------------------------------------------------------
" 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 = "\<Esc>[38;2;%lu;%lu;%lum"
let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
endif
fu! PrintError(msg) abort
exec 'normal! \<Esc>'
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=<n>.
" 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
let g:campo_custom_ctags_args = ""
" 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 '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 'ctrlpvim/ctrlp.vim', { 'commit': '7c972cb' } " (prev stable: d93d978) Fuzzy file, buffer, mru, tag, etc finder.
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=<dir path for swap files>
" set backupdir=<dir path for backup files>
" if has('persistent_undo')
" set undodir=<dir path for undo files>
" 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=<path>
"
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 <tab> inserts in the command bar to autocomplete, e.g. see <leader>e
set wildcharm=<tab>
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 <up> <nop>
noremap <down> <nop>
noremap <left> <nop>
noremap <right> <nop>
inoremap <up> <nop>
inoremap <down> <nop>
inoremap <left> <nop>
inoremap <right> <nop>
"/////////////////////////////////////////////////
" 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. <leader>s and <leader>sg will
" introduce a timeout delay, which will be noticed when you're trying to use
" <leader>s. If you type <leader>sg then it'll be handled immediately, unless
" there is a third mapping with that initial sequence, e.g. <leader>sgn.
"
" You can adjust the timeout duration by modidying the timeoutlen option.
"
" You can see when a keysequence is mapped to by entering :map <sequence>
" 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 <Esc>
" Suspend vim process and return to the shell. Can return to vim with `fg`.
nnoremap <leader>z <c-z>
" Open the vimrc file for editing / reload vimrc file.
nnoremap <silent> <leader>ev :vsp $MYVIMRC<cr>
nnoremap <silent> <leader>pv :vsp ~/.vimrc.private<cr>
nnoremap <silent> <leader>rv :source $MYVIMRC<cr>
" 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 <leader>t for opening my todo file.
"if exists(':terminal')
" noremap <leader>t :terminal<cr>
" tnoremap <leader>te <C-\><C-n>
" tnoremap <A-h> <C-\><C-n><C-w>h
" tnoremap <A-j> <C-\><C-n><C-w>j
" tnoremap <A-k> <C-\><C-n><C-w>k
" tnoremap <A-l> <C-\><C-n><C-w>l
" nnoremap <A-h> <C-w>h
" nnoremap <A-j> <C-w>j
" nnoremap <A-k> <C-w>k
" nnoremap <A-l> <C-w>l
"endif
" Jump to other buffers.
noremap <c-k> <c-w><Up>
noremap <c-j> <c-w><Down>
noremap <c-l> <c-w><Right>
noremap <c-h> <c-w><Left>
" Make it easier to jump around the command line. The default behaviour is
" using the arrow keys with or without shift.
:cnoremap <C-J> <S-Left>
:cnoremap <C-K> <S-Right>
" Window splitting - couldn't figure out how to remap <c-w>v & <c-w>n to <c-m>
" & <c-n>
noremap <leader>m :vsplit<cr>
noremap <leader>mm :split<cr>
" 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 <leader>a and then do your yank, delete or
" paste. Subsequent yanks into <leader>a will overwrite existing data. You can
" append to the register by using <leader>aa
"
" This overwrites the contents of a.
noremap <leader>a "a
" This appends to a.
noremap <leader>aa "A
"-----------------------------------------------------
" Backward replace word including cursor character.
noremap <leader>d cvb
" Allow fast pasting by accessing the system clipboard register.
noremap <leader>p "+p
" Likely won't need to use this if pasting with <leader>p, but just in case here ya go.
noremap <leader>pp :set paste! paste?<cr>
" Toggle line numbers.
nnoremap <leader>o :set number! number?<cr>
" Show spell checking.
" You can add new entries to the dict by moving the cursor over the word and pressing `zg`.
noremap <leader>j :exec &spell==&spell? "se spell! spelllang=en_us" : "se spell!"<cr>
noremap <leader>= z=
" Clear the search buffer (highlighting) when hitting return.
nnoremap <cr> :nohlsearch<cr>
" Switch to the previous file.
nnoremap <leader><leader> <c-^>
" Replace currently selected text with default register without yanking it.
vnoremap p "_dP
" Switch between C++ source and header files.
noremap <leader>v :e %:p:s,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp,<CR>
noremap <leader>vv :e %:p:s,.h$,.X123X,:s,.c$,.h,:s,.X123X$,.c,<CR>
"noremap <leader>vvv :e %:p:s,.h$,.X123X,:s,.cc$,.h,:s,.X123X$,.cc,<CR>
" Replace all instances of the highlighted text with whatever you enter.
nnoremap <c-g> :%s///g<left><left>
"##################################################################################
" 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 we're editing a text file. This won't be an exhaustive
" filter. We can restrict what goes into the tag file
" First determine if we're in a root drive directory. If we are then
" we bail 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
" 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 <leader>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 <expr> %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.
" <tab> will autocomplete the expansion here because we set wildcharm to <tab>.
nnoremap <leader>e :e %:p:h/<tab>
nnoremap <leader>w :call WriteCurrentFileAndCreateCtags()<cr>
nnoremap <leader>x :call WriteCurrentFileAndCreateCtagsThenQuit()<cr>
nnoremap <leader>q :q<cr>
call Cabbrev('Q', 'q')
call Cabbrev('Qa', 'qa')
command! Qa qall
" Disable Ex mode.
noremap Q <Nop>
"##################################################################################
" MULTIPURPOSE TAB KEY
"##################################################################################
fu! InsertTabWrapper()
let l:col = col('.') - 1
if !l:col || getline('.')[l:col - 1] !~ '\k'
return "\<tab>"
else
return "\<c-p>"
endif
endfu
inoremap <tab> <c-r>=InsertTabWrapper()<cr>
inoremap <s-tab> <c-n>
"|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
" #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 <F12> :TagbarToggle<cr>
" 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 <leader>hh :GitGutterToggle<cr>
nmap <leader>ha <Plug>(GitGutterStageHunk)
nmap [h <Plug>(GitGutterNextHunk)
nmap ]h <Plug>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 <leader>g :call CtrlP_Search('')<cr> " Search in current directory
noremap <leader>gg :call CtrlP_JaiSearch('')<cr> " Search in Jai directory
noremap <leader>gm :call CtrlP_JaiSearch('modules')<cr> " Search in Jai modules
noremap <leader>gh :call CtrlP_JaiSearch('how_to')<cr> " Search in Jai how_to
noremap <leader>ge :call CtrlP_JaiSearch('examples')<cr> " Search in Jai examples
let g:ctrlp_map = '<leader>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()
"##################################################################################
" GIT
"##################################################################################
noremap <leader>gb :Git blame -w<cr>
" Ignore whitespace changes; follow renames and copies.
command! -bar -bang -nargs=* Blame :Git blame<bang> -wCM <args>
command! -bar -bang -nargs=* Gblame :Git blame<bang> -wCM <args>
"##################################################################################
" 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 <leader>l :call ToggleLightDarkTheme()<cr>
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
"##################################################################################
" 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 <leader>ce :call ShowErrorAtCursor()<cr>
"/////////////////////////////////////////////////
" 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
fu! Build(optimized=0, silent=0)
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 = 1
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") == 1
let l:cmd = "jai ". l:current_dir . "/build.jai"
elseif filereadable(l:current_dir . "/first.jai") == 1
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") == 1
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
let l:cmd .= './build* '
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
" If there's a local modules/ directory then we'll import it.
if isdirectory(l:current_dir . "/modules")
let l:cmd .= " -import_dir modules"
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
let l:file_exe_name = expand('%: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(expand('%:p:h') . '/' . l:file_exe_name)
exec 'AsyncRun! ' . expand('%: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 = expand('%: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
if tolower(expand('%:e')) == "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
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.
let l:current_path = l:search_path
while 1
" Run script has higher priority over an exe named after the current file.
let l:search = l:current_path . 'run'
if filereadable(l:search)
exec "AsyncRun! " . l:search
return 1
endif
" filename exe
let l:search = l:current_path . a:file_exe_name
if filereadable(l:search)
exec "AsyncRun! " . l:search
return 1
endif
" bin/filename exe
let l:search = l:current_path . 'bin/' . a:file_exe_name
if filereadable(l:search)
exec "AsyncRun! " . l:search
return 1
endif
" run_tree/filename exe
let l:search = l:current_path . 'run_tree/' . a:file_exe_name
if filereadable(l:search)
exec "AsyncRun! " . l:search
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 <leader>bc :call ToggleBuildResults()<cr>
noremap <F11> :call ToggleBuildResults()<cr>
" Hide build results and clear errors
noremap <F10> :call HideBuildResultsAndClearErrors()<cr>
" Execute build script and display results.
nnoremap <silent><leader>b :call Build(0)<cr>
nnoremap <silent><F8> :call Build(0)<cr>
" Execute optimized build script
nnoremap <leader>bb :call Build(1)<cr>
" Execute run script
nnoremap <silent><leader>br :call RunProgram()<cr>
nnoremap <silent><F9> :call RunProgram()<cr>
nnoremap <leader>bs :AsyncStop<cr>
"Go to next build error
nnoremap <F7> :cn<CR>
nnoremap <C-n> :cn<CR>
"Go to previous build error
nnoremap <F6> :cp<CR>
nnoremap <C-p> :cp<CR>
"##################################################################################
" 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
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 <leader>s :call Search( '.', g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sd :call SearchExt('.', g:campo_custom_search_args, 1)<cr>
" Case sensitive:
nnoremap <leader>ss :call Search( '.', g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssd :call SearchExt('.', g:campo_custom_search_args, 0)<cr>
"/////////////////////////////////////////////////
" SEARCH IN DIRECTORY CONTAINING THE ACTIVE FILE
"/////////////////////////////////////////////////
" Case insensitive:
nnoremap <leader>sf :call Search( expand('%:p:h'), g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sdf :call SearchExt(expand('%:p:h'), g:campo_custom_search_args, 1)<cr>
" Case sensitive:
nnoremap <leader>ssf :call Search( expand('%:p:h'), g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssdf :call SearchExt(expand('%:p:h'), g:campo_custom_search_args, 0)<cr>
"/////////////////////////////////////////////////
" SEARCH IN JAI FOLDERS
"/////////////////////////////////////////////////
" Case insensitive:
"
" ROOT
nnoremap <leader>sg :call Search( g:campo_jai_path, g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sdg :call SearchExt(g:campo_jai_path, g:campo_custom_search_args, 1)<cr>
" MODULES
nnoremap <leader>sm :call Search( g:campo_jai_path.'/modules', g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sdm :call SearchExt(g:campo_jai_path.'/modules', g:campo_custom_search_args, 1)<cr>
" HOW TO
nnoremap <leader>sh :call Search( g:campo_jai_path.'/how_to', g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sdh :call SearchExt(g:campo_jai_path.'/how_to', g:campo_custom_search_args, 1)<cr>
" EXAMPLES
nnoremap <leader>se :call Search( g:campo_jai_path.'/examples', g:campo_custom_search_args, 1)<cr>
nnoremap <leader>sde :call SearchExt(g:campo_jai_path.'/examples', g:campo_custom_search_args, 1)<cr>
" Case sensitive:
"
" ROOT
nnoremap <leader>ssg :call Search( g:campo_jai_path, g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssdg :call SearchExt(g:campo_jai_path, g:campo_custom_search_args, 0)<cr>
" MODULES
nnoremap <leader>ssm :call Search( g:campo_jai_path.'/modules', g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssdm :call SearchExt(g:campo_jai_path.'/modules', g:campo_custom_search_args, 0)<cr>
" HOW TO
nnoremap <leader>ssh :call Search( g:campo_jai_path.'/how_to', g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssdh :call SearchExt(g:campo_jai_path.'/how_to', g:campo_custom_search_args, 0)<cr>
" EXAMPLES
nnoremap <leader>sse :call Search( g:campo_jai_path.'/examples', g:campo_custom_search_args, 0)<cr>
nnoremap <leader>ssde :call SearchExt(g:campo_jai_path.'/examples', g:campo_custom_search_args, 0)<cr>
" 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 <expr> o (&buftype is# "quickfix" ? "<CR>\|:lopen<CR>" : "o")
nnoremap <expr> p (&buftype is# "quickfix" ? "<CR>\|:copen<CR>" : "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 <leader>r :call GlobalReplaceIt(0)<cr>
noremap <leader>rr :call GlobalReplaceIt(1)<cr>
"##################################################################################
" 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 <leader>n :call RenameFile()<cr>
"##################################################################################
" 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 <leader>cc :call CenterPane()<cr>
nnoremap <leader>cd :call RemoveCenterPane()<cr>
"##################################################################################
" 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 <leader>c :call ToggleSimpleView()<cr>
"-----------------------------------------------------------------------------------------------------------------------
let g:campo_vimrc_initialized = 1