" " Tom Ryder (tejr)'s Literate Vimrc " ================================= " " " " This is an attempt at something like a 'literate vimrc', in the tradition of " Donald Knuth's "literate programming". " " " " It's a long file, and comments abound. If this bothers you, you can do " something like this to strip out all the blank lines and lines with only " comments: " " :v/^\s*[^"]/d " " This file should be saved as "vimrc" in the user runtime directory. On " Unix-like operating systems, this is ~/.vim; on Windows, it's ~/vimfiles. " It requires Vim 7.0 or newer with +eval, not running in &compatible mode. " The vimrc stub at ~/.vimrc on Unix or ~/_vimrc on Windows checks that these " conditions are met before loading this file. " " > And I was lifted up in heart, and thought " > Of all my late-shown prowess in the lists, " > How my strong lance had beaten down the knights, " > So many and famous names; and never yet " > Had heaven appeared so blue, nor earth so green, " > For all my blood danced in me, and I knew " > That I should light upon the Holy Grail. " > --Tennyson " " This file contains a few Unicode characters, and the Vint Vim script linter " wants me to declare that, so I'll do so. The :help for :scriptencoding says " that I should do that after 'encoding' is set, so we'll do that now. " " On Unix, I keep LANG defined in my environment, and it's almost always set " to a multibyte (UTF-8) locale. This informs Vim's choice of internal " character encoding, but the default for the 'encoding' option is latin1, " which is seldom what I want, and if I do want it, I'll specify it with LANG " or possibly a manual :set command. UTF-8 makes much more sense as a default " encoding if Vim can't glean what I want from LANG. " if !exists('$LANG') set encoding=utf-8 endif scriptencoding utf-8 " With encoding handled, the next thing we'll do is set an environment " variable MYVIM for the user runtime directory, if such a variable does not " already exist in the environment, and there's a value in 'runtimepath' from " which to glean a useable path. We'll use the path nominated in the MYVIM " variable as the root of our 'backupdir', 'directory', 'undodir', and " 'viminfofile' caches, and anywhere else we need a sensible writeable " location for Vim-related files. " " I think the absence of a variable like this is a glaring omission from Vim. " We have VIM, VIMRUNTIME, and MYVIMRC, so why is there not an environment " variable for the user's Vim runtime directory? It is a mystery. " " We'll use the first path specified in 'runtimepath' as a default value. " This is similar to what Vim does internally for the location of the spelling " database files in the absence of a setting for 'spellfile'. " " Splitting the values of a comma-separated option like 'runtimepath' " correctly, is a bit more complicated than it seems. Its list separator is " more accurately defined as a comma that is not preceded by a backslash, and " which is followed by any number of spaces and/or further commas. " " The pattern required for the split breaks down like this: " " \\ Literal backslash " \@ 0 let $MYVIM = runtimepath[0] endif " We need to check the MYVIM environment variable's value to ensure it's not " going to cause problems for the rest of this file. " " Firstly, if the path specified in the MYVIM environment variable contains " a comma, its use in comma-separated option values will confuse Vim into " thinking more than one directory is being specified, per normal :set " semantics. It's possible to work around this with some careful escaping, " either at :set time with an :execute abstraction or with a separate " environment variable for that particular context, but it's not really worth " the extra complexity for such a niche situation. " " Secondly, some versions of Vim prior to v7.2.0 exhibit bizarre behaviour " with escaping with the backslash character on the command line, so on these " older versions of Vim, forbid that character. I haven't found the exact " patch level that this was fixed yet, nor the true reason for the bug. " " If either of these conditions are meant, throw an error and blank the MYVIM " variable so that nothing uses it. " if $MYVIM =~# ',' echoerr 'Illegal comma in user runtime path' let $MYVIM = '' elseif $MYVIM =~# '\\' && v:version < 702 echoerr 'Illegal backslash in user runtime path on Vim < v7.2' let $MYVIM = '' endif " If the MYVIM environment variable was set outside Vim, it may not correspond " to the first element of the default &runtimepath. If this is the case, " we'll slot it in, having already checked it for troublesome characters. " if $MYVIM !=# '' \ && len(runtimepath) > 0 \ && $MYVIM !=# runtimepath[0] set runtimepath^=$MYVIM endif " We're going to be creating a few directories, and the code to do so in " a compatible way is surprisingly verbose, because we need to check the " mkdir() function is actually available, and also whether the directory " concerned already exists, even if we specify the special 'p' value for its " optional {path} argument. " " This is because the meaning of mkdir(..., 'p') is not the same as `mkdir -p` " in shell script, or at least, it isn't in versions of Vim before v8.0.1708. " Even with the magic 'p' sauce, these versions throw errors if the directory " already exists, despite what someone familiar with `mkdir -p`'s behaviour in " shell script might expect. " " So, let's wrap all that nonsense in a script-local function, and then " abstract that away too with a user command, to keep the semantics of the " :set operations nice and clean. We'll make all the directories we create " have restrictive permissions, too, with a {prot} argument of 0700 for the " final one, since every directory we want to create in this file should be " locked down in this way. " function s:EnsureDir(path) abort let path = expand(a:path) return isdirectory(path) \ || exists('*mkdir') && mkdir(path, 'p', 0700) endfunction " Now we define the :EnsureDir command for user-level access to the " s:EnsureDir() function. We set the tab completion to provide directory " names as candidates, and specify that there must be only one argument, which " we'll provide as a quoted parameter to the function. " command! -complete=dir -nargs=1 EnsureDir \ call s:EnsureDir() " Now that we have a clean means to create directories if they don't already " exist, let's apply it for the first time, in making sure that the MYVIM " directory exists, if it's been set. " if $MYVIM !=# '' EnsureDir $MYVIM endif " Create a 'vimrc' automatic command hook group, if it doesn't already exist, " and clear away any automatic command hooks already defined within it if it " does. This way, we don't end up collecting multiple copies of the hooks " configured in the rest of this file if it's reloaded. I don't want to make " the augroup span the entire file, though. " augroup vimrc autocmd! augroup END " If this file or the vimrc stub that calls it is written to by Vim, we'd like " to reload the stub vimrc and thereby the main vimrc, so that our changes " apply immediately in the current editing session. This often makes broken " changes immediately apparent. " autocmd vimrc BufWritePost $MYVIMRC \ source $MYVIMRC if $MYVIMRC !=# '' autocmd vimrc BufWritePost $MYVIM/vimrc \ doautocmd vimrc BufWritePost $MYVIMRC endif " Similarly, if this file or the vimrc stub that calls it is sourced, whether " because of the above hook, or the R mapping prescribed later in this " file, add a hook that re-runs filetype detection and thereby ftplugin " loading. This is chiefly so that any global options set in this file don't " trample over needed buffer-local settings. " " We'll abstract this away a bit behind a new user command named " FileTypeReload, which just re-runs BufRead events for filetype detection if " they've been loaded. " command! FileTypeReload \ if exists('did_load_filetypes') \| doautocmd filetypedetect BufRead \|endif " Now we'll use that new :FileTypeReload command as part of an automatic " command hook that runs whenever this vimrc is sourced. " " If there's stuff in any of your filetype plugins that doesn't cope well with " being reloaded, and just assumes a single BufRead event, it might be " necessary to rewrite those parts to be idempotent, or to add load guards " around them so that they only run once. " " Note that we reload the stub ~/.vimrc or ~/_vimrc file when either it or " this main file is saved, using :doautocmd abstraction. Note also that the " SourceCmd event wasn't added until Vim 7.0.187, so we need to check it " exists first. " if exists('##SourceCmd') autocmd vimrc SourceCmd $MYVIMRC \ source | FileTypeReload if $MYVIM !=# '' autocmd vimrc SourceCmd $MYVIM/vimrc \ doautocmd vimrc SourceCmd $MYVIMRC endif endif " Keep the viminfo file in a cache subdirectory of the user runtime directory, " creating that subdirectory first if necessary. " " Using this non-default location for viminfo has the nice benefit of " preventing command and search history from getting clobbered when something " runs Vim without using this vimrc, because it writes its history to the " default viminfo path instead. It also means that everything Vim-related in " the user's home directory should be encapsulated in the one ~/.vim or " ~/vimfiles directory. " " The normal method of specifying the path to the viminfo file used here is an " addendum to the 'viminfo' option, which works OK. Vim v8.1.716 introduced " a nicer way to set it with a 'viminfofile' option, but there's no particular " reason to use it until it's in a few more stable versions. " if $MYVIM !=# '' EnsureDir $MYVIM/cache set viminfo+=n$MYVIM/cache/viminfo endif " Speaking of recorded data in viminfo files, the command and search history " count default limit of 50 is pretty restrictive. Because I don't think I'm " ever likely to be in a situation where remembering several thousand Vim " commands and search patterns is going to severely tax memory, let alone hard " disk space, I'd rather it were much higher, as it's sometimes really handy " to dig up commands from some time ago. The maximum value for this option is " documented as 10000, so let's just use that and see if anything breaks. " set history=10000 " Next, we'll modernise a little in adjusting some options with old " language-specific defaults. " " Traditional vi was often used for development in the C programming language. " The default values for a lot of Vim's options still reflect this common use " pattern. In this case, the 'comments' and 'commentstring' options reflect " the C syntax for comments: " " /* " * This is an ANSI C comment. " */ " " Similarly, the 'define' and 'include' options default to C preprocessor " directives: " " #define FOO "bar" " " #include "baz.h" " " Times change, however, and I don't get to work with C nearly as much as I'd " like. The defaults for these options no longer make sense, and so we blank " them, compelling filetype plugins to set them as they need instead. " set comments= commentstring= define= include= " The default value for the 'path' option is similar, in that it has an aged " default; this option specifies directories in which project files and " includes can be unearthed by navigation commands like 'gf'. Specifically, " its default value comprises /usr/include, which is another C default. Let's " get rid of that, too. " set path-=/usr/include " Next, we'll adjust the global indentation settings. In general and as " a default, I prefer spaces to tabs, and I like to use four of them, for " a more distinct visual structure. Should you happen to disagree with this, " I cordially invite you to fite me irl. " " " " Filetype indent plugins will often refine these settings for individual " buffers. For example, 'expandtab' is not appropriate for Makefiles, nor for " the Go programming language. For another, two-space indents are more " traditional for Vim script. " set autoindent " Use indent of previous line on new lines set expandtab " Insert spaces when tab key is pressed in insert mode set shiftwidth=4 " Indent command like < and > use four-space indents " Apply 'softtabstop' option to make a tab key press in insert mode insert the " same number of spaces as defined by the indent depth in 'shiftwidth'. If " Vim is new enough to support it (v7.3.693), apply a negative value to do " this dynamically if 'shiftwidth' changes. " if v:version > 730 || v:version == 730 && has('patch693') set softtabstop=-1 else let &softtabstop = &shiftwidth endif " Enable automatic backups of most file buffers. In practice, I don't need " these backups very much if I'm using version control sensibly, but they have " still saved my bacon a few times. We're not done here yet, though; it " requires some fine-tuning. " set backup " Try to keep the aforementioned backup files in a dedicated cache directory, " to stop them proliferating next to their prime locations and getting " committed to version control repositories. Create said directory if needed, " too, with restrictive permissions. " " If Vim is new enough (v8.1.251), add two trailing slashes to the path we're " inserting, which prompts Vim to incorporate the full escaped path in the " backup filename, avoiding collisions. " " As a historical note, other similar directory path list options supported " this trailing slashes hint for a long time before 'backupdir' caught up to " them. The 'directory' option for swap files has supported it at least as " far back as v5.8.0 (2001), and 'undodir' appears to have supported it since " its creation in v7.2.438. Even though the :help for 'backupdir' didn't say " so, people assumed it would work the same way, when in fact Vim simply " ignored it until v8.1.251. " " I don't want to add the slashes to the option value in older versions of Vim " where they don't do anything, so I check the version to see if there's any " point adding them. " " It's all so awkward. Surely options named something like 'backupfullpath', " 'swapfilefullpath', and 'undofullpath' would have been clearer. " if $MYVIM !=# '' EnsureDir $MYVIM/cache/backup if has('patch-8.1.251') set backupdir^=$MYVIM/cache/backup// else set backupdir^=$MYVIM/cache/backup endif endif " Files in certain directories on Unix-compatible filesystems should not be " backed up for reasons of privacy, or an intentional ephemerality, or both. " This is particularly important if editing temporary files created by " sudoedit(8). On Unix-like systems, we here add a few paths to the default " value of 'backupskip' in order to prevent the creation of such undesired " backup files. " if has('unix') " Vim doesn't seem to check patterns added to 'backupskip' for uniqueness, " so adding them repeatedly if this file is reloaded results in duplicates, " due to the absence of the P_NODUP flag for its definition in src/option.c. " This is likely a bug in Vim. For the moment, to work around the problem, " we reset the path back to its default first. " set backupskip& " * /dev/shm: RAM disk, default path for password-store's temporary files " * /usr/tmp: Hard-coded path for sudoedit(8) [1/2] " * /var/tmp: Hard-coded path for sudoedit(8) [2/2] " set backupskip^=/dev/shm/*,/usr/tmp/*,/var/tmp/* endif " Relax traditional vi's harsh standards over what regions of the buffer can " be removed with backspace in insert mode. While this admittedly allows bad " habits to continue, since insert mode by definition is not really intended " for deleting text, I feel the convenience outweighs that in this case. " set backspace+=eol " Line breaks set backspace+=indent " Leading whitespace characters created by 'autoindent' set backspace+=start " Text before the start of the current insertion " When soft-wrapping text with the 'wrap' option on, which is off by default, " break the lines between words, rather than within them; it's much easier to " read. " set linebreak " Similarly, show that the screen line is a trailing part of a wrapped line by " prefixing it with an ellipsis. If we have a multi-byte encoding, use U+2026 " HORIZONTAL ELLIPSIS to save a couple of columns, but otherwise three periods " will do just fine. " if has('multi_byte_encoding') set showbreak=… else set showbreak=... endif " The visual structure of code provided by indents breaks down if a lot of the " lines wrap. Ideally, most if not all lines would be kept below 80 " characters, but in cases where this isn't possible, soft-wrapping longer " lines when 'wrap' is on so that the indent is preserved in the following " line mitigates this breakdown somewhat. " " With this set, it's particularly important to have 'showbreak' set to " something, above, otherwise without line numbers it's hard to tell what's " a logical line and what's not. " " This option wasn't added until v7.4.338, so we need to check it exists " before we set it. " if exists('+breakindent') set breakindent endif " Rather than rejecting operations like :write or :saveas when 'readonly' is " set, and other situations in which data might be lost or I'm acting against " an option, Vim should give me a prompt to allow me to confirm that I know " what I'm doing. " set confirm " Keep swap files for file buffers in a dedicated directory, rather than the " default of writing them to the same directory as the buffer file. Add two " trailing slashes to the path to prompt Vim to use the full escaped path in " its name, in order to avoid filename collisions. Create that path if " needed, too. " if $MYVIM !=# '' EnsureDir $MYVIM/cache/swap set directory^=$MYVIM/cache/swap// endif " If Vim receives an Escape key code in insert mode, it shouldn't wait to see " if it's going to be followed by another key code, despite this being how the " function keys and Meta/Alt modifier are implemented for many terminal types. " Otherwise, if I press Escape, there's an annoying delay before 'showmode' " stops showing "--INSERT--". " " This breaks the function keys and the Meta/Alt modifier in insert mode in " most or maybe all of the terminals I use, but I don't want those keys in " insert mode anyway. It all works fine in the GUI, of course. " " There's no such option as 'esckeys' in Neovim, which I gather has completely " overhauled its method of keyboard event handling, so we need to check " whether the option exists before we try to set it. " if exists('+esckeys') set noesckeys endif " By default, I prefer that figuring out where a region of text to fold away " should be done by the indent level of its lines, since I tend to be careful " about my indentation even in languages where it has no structure " significance. " set foldmethod=indent " That said, I don't want any folding to actually take place unless " I specifically ask for it. " " I think of a Vim window with a file buffer loaded as a two-dimensional " planar view of the file, so that moving down one screen line means moving " down one buffer line, at least when 'wrap' is unset. Folds break that " mental model, and so I usually enable them explicitly only when I'm " struggling to grasp some in-depth code with very long functions or loops. " " Therefore, we set the depth level at which folds should automatically start " as closed to a rather high number, per the documentation's recommendations. " set foldlevelstart=99 " Automatic text wrapping options using flags in the 'formatoptions' option " begin here. I allow filetypes to set 't' and 'c' to configure whether text " or comments should be wrapped, and so I don't mess with either of those " flags here. " If a line is already longer than 'textwidth' would otherwise limit when " editing of that line begins in insert mode, don't suddenly automatically " wrap it; I'll break it apart myself with a command like 'gq'. " set formatoptions+=l " Don't wrap a line in such a way that a single-letter word like "I" or "a" is " at the end of it. Typographically, as far as I can tell, this seems to be " a stylistic preference rather than a rule like avoiding "widow" and "orphan" " lines in typesetting. I think it generally looks better to have the short " word start the line. " set formatoptions+=1 " If the filetype plugins have correctly described what the comment syntax for " the buffer's language looks like, it makes sense to use that to figure out " how to join lines within comments without redundant comment leaders cropping " up. For example, with this set, in Vim, joining lines in this very comment " with 'J' would remove the leading '"' characters that denote a comment. " " This option flag wasn't added until v7.3.541. Because we can't test for the " availability of option flags directly, we resort to a version number check " before attempting to add the flag. I don't like using :silent! to suppress " errors for this sort of thing when I can reasonably avoid it, even if it's " somewhat more verbose. " if v:version > 730 || v:version == 730 && has('patch541') set formatoptions+=j endif " A momentary digression here into the doldrums of 'cpoptions'--after " staunchly opposing it for years, I have converted to two-spacing. You can " blame Steve Losh: " " " " Consequently, we specify that sentence objects for the purposes of the 's' " text object, the '(' and ')' sentence motions, and formatting with the 'gq' " command must be separated by *two* spaces. One space does not suffice. " " My defection to the two-spacers is also the reason I now leave 'joinspaces' " set, per its default, so that two spaces are inserted when consecutive " sentences separated by a line break are joined onto one line by the 'J' " command. " set cpoptions+=J " Separating sentences with two spaces has an advantage in making a clear " distinction between two different types of periods: periods that abbreviate " longer words, as in "Mr. Moolenaar", and periods that terminate sentences, " like this one. " " If we're using two-period spacing for sentences, Vim can interpret the " different spacing to distinguish between the two types, and can thereby " avoid breaking a line just after an abbreviating period. For example, the " two words in "Mr. Moolenaar" should never be split apart, preventing " confusion on the reader's part lest the word "Mr." look too much like the " end of a sentence, and also preserving the semantics of that same period for " subsequent reformats; its single-space won't get lost. " " So, getting back to our 'formatoptions' settings, that is what the 'p' flag " does. I wrote the patch that added it, after becoming envious of an " analogous feature during an ill-fated foray into GNU Emacs usage. " " " if has('patch-8.1.728') set formatoptions+=p endif " In an effort to avoid loading unnecessary files, we add a flag to the " 'guioptions' option to prevent the menu.vim runtime file from being loaded. " It doesn't do any harm, but I never use it, and it's easy to turn it off. " " The documentation for this flag in `:help 'go-M'` includes a note saying the " flag should be set here, rather that in the GUI-specific gvimrc file, as one " might otherwise think. " if has('gui_running') set guioptions+=M endif " By default, Vim doesn't allow a file buffer to have unsaved changes if it's " not displayed in a window. Setting this option removes that restriction so " that buffers can remain in a modified state while not actually displayed " anywhere. " " This option is set in almost every vimrc I read; it's so pervasive that " I sometimes see comments expressing astonishment or annoyance that it isn't " set by default. However, I didn't actually need this option for several " years of Vim usage, because I instinctively close windows onto buffers only " after the buffers within them were saved anyway. " " However, the option really is required for batch operations performed with " commands like :argdo or :bufdo, because Vim won't otherwise tolerate unsaved " changes to a litany of undisplayed buffers. After I started using such " command maps a bit more often, I realised I finally had a reason to turn " this on permanently. " set hidden " Do highlight matches for completed searches in the text, but clear that " highlighting away when this vimrc is reload. Later on in this file, CTRL-L " in normal mode is remapped to tack on a :nohlsearch as well. " set hlsearch nohlsearch " Highlight search matches in my text while I'm still typing my pattern, " including scrolling the screen to show the first such match if necessary. " This can be somewhat jarring, particularly when the cursor ends up scrolling " a long way from home in a large file, but I think the benefits of being able " to see instances of what I'm trying to match as I try to match it do " outweigh that discomfort. " set incsearch " If there's only one window, I don't need a status line to appear beneath it. " I very often edit only a few files in one window in a Vim session. I like " the initial screen just being empty save for the trademark tildes. It gives " me an extra screen line, too. It's a reflex for me to press CTRL-G in " normal mode if I need to see the buffer name. " " This value reflects the Vim default, but Neovim changed its default to '2' " for an 'always-on' status line, so we'll explicitly set it to the default " here in case we're using Neovim. " set laststatus=1 " Don't waste cycles and bandwidth redrawing the screen during execution of " aggregate commands in e.g. macros. I think this does amount to the " occasional :redraw needing to be in a script, but it's not too bad, and last " I checked it really does speed things up, especially for operations on " really big data sets. " set lazyredraw " Define meta-characters to show in place of characters that are otherwise " invisible, or line wrapping attributes when the 'list' option is enabled. " " We need to reset the option to its default value first, because at the time " of writing at least, Neovim v0.3.5 doesn't check these for uniqueness, " resulting in duplicates if this file is reloaded. 'backupskip' has similar " problems in the original Vim v8.1.1487 and earlier. " set listchars&vi " These 'list' characters all correspond to invisible or indistinguishable " characters. We leave the default eol:$ in place to show newlines, and add " a few more. " set listchars+=tab:>- " Tab characters, preserve width with hyphens set listchars+=trail:- " Trailing spaces set listchars+=nbsp:+ " Non-breaking spaces " The next pair of 'list' characters are arguably somewhat misplaced, in that " they don't really represent invisible characters in the same way as the " others, but are hints for the presence of other characters unwrapped lines " that are wider than the screen. They're very useful, though. " " If the current encoding supports it, use these non-ASCII characters for the " markers, as they're visually distinctive: " " extends: Signals presence of unwrapped text to screen right " » U+00BB RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK " precedes: Signals presence of unwrapped text to screen left " « U+00BB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK " " Failing that, '<' and '>' will still do the trick. " if has('multi_byte_encoding') set listchars+=extends:» listchars+=precedes:« else set listchars+=extends:> listchars+=precedes:< endif " Don't let your editor's options be configured by content in arbitrary files! " Down with modelines! Purge them from your files! " " I think that modelines are Vim's worst misfeature, and that 'nomodeline' " should be the default. It's enabled pretty bad security vulnerabilities " over the years, and it's a lot more effective to use filetype detection, " other automatic command hooks, or systems like .editorconfig to set " variables specifically for a buffer or project. " set nomodeline " The only octal numbers I can think of that I ever even encounter are Unix " permissions masks, and I'd never use CTRL-A or CTRL-X to increment them. " Numbers with leading zeroes are far more likely to be decimals. " set nrformats-=octal " I like to leave the last line of the screen blank unless something is " actually happening in it, so I have grown to like the Vim default of " 'noruler'. CTRL-G shows me everything I need to know, and is " near-instinctive now. " " Rude system vimrc files tend to switch this back on, though, and Neovim has " it on by default, so we will often need to put it back to normal, as we do " here. " set noruler " Sessions preserving buffer and window layout are great for more complex and " longer-term projects like books, but they don't play together well with " plugins and filetype plugins. Restoring the same settings from both " reloaded plugins and from the session causes screeds of errors. Adjusting " session behaviour to stop it trying to restore quite so much makes them " useable. " set sessionoptions-=localoptions " No buffer options or mappings set sessionoptions-=options " No global options or mappings " The 'I' flag for the 'shortmess' option prevents the display of the Vim " startup screen with version information, :help hints, and donation " suggestion. After I registered Vim and donated to Uganda per the screen's " plea, I didn't feel bad about turning this off anymore. Even with this " setting in place, I wouldn't normally see it too often anyway, as I seldom " start Vim with no file arguments. " " I haven't felt the need to mess with the other flags in this option. " I don't have any problems with spurious Enter prompts, which seems to be " the main reason people pile it full of letters. " set shortmess+=I " Limit the number of characters per line that syntax highlighting will " attempt to match. This is as much an effort to encourage me to break long " lines and do hard wrapping correctly as it is for efficiency. " set synmaxcol=500 " Vim has an internal list of terminal types that support using smoother " terminal redrawing, and for which 'ttyfast' is normally set, described in " `:help 'ttyfast'`. That list includes most of the terminals I use, but " there are a couple more for which the 'ttyfast' option should apply: the " windows terminal emulator PuTTY, and the terminal multiplexer tmux, both of " which I use heavily. " if &term =~# '^putty\|^tmux' set ttyfast endif " We really don't want a mouse; while I use it a lot for cut and paste in X, " at the terminal application level, it just gets in the way. Mouse events " should be exclusively handled by the terminal emulator application, so Vim " shouldn't try to give me terminal mouse support, even if it would work. " " The manual suggests that disabling this should be done by clearing 't_RV', " but that didn't actually seem to work when I tried it. " " We have to check for the existence of the option first, as it doesn't exist " in Neovim. " if exists('+ttymouse') set ttymouse= endif " Keep tracked undo history for files permanently, in a dedicated cache " directory, so that the u/:undo and CTRL-R/:redo commands will work between " Vim invocations. " " Support for persistent undo file caches was not added until v7.2.438, so we " need to check for the feature's presence before we enable it. " if has('persistent_undo') " This has the same structure as 'backupdir' and 'directory'; if we have " a user runtime directory, create a sub-subdirectory within it dedicated to " the undo files cache. Note also the trailing double-slash as a signal to " Vim to use the full path of the original file in its undo file cache's " name. " if $MYVIM !=# '' EnsureDir $MYVIM/cache/undo set undodir^=$MYVIM/cache/undo// endif " Turn the persistent undo features on, regardless of whether we have " a cache directory for them as a result of the logic above. The files " might sprinkle around the filesystem annoyingly, but that's still better " than losing the history completely. " set undofile endif " While using virtual block mode, allow me to navigate to any column of the " buffer window; don't confine the boundaries of the block to the coordinates " of characters that actually exist in the buffer text. While working with " formatted columnar data with this off is generally OK, it's a hassle for " more subtle applications of visual block mode. " set virtualedit+=block " I can't recall a time that Vim's error beeping or flashing was actually " useful to me, and so we turn it off in the manner that the manual instructs " in `:help 'visualbell'`. This enables visual rather than audio error bells, " but in the same breath blanks the terminal attribute that would be used to " trigger such screen blinking, indirectly disabling the bell altogether. " " I thought at first that the newer 'belloff' and/or 'errorbells' options " would be a more intuitive way to keep Vim quiet, but the last time I checked " that they didn't actually appear to work as comprehensively as this older " method does. " " Interestingly, the :help says that this setting has to be repeated in the " gvimrc file for GUI Vim, so you'll find this exact same command issued again " in there. " set visualbell t_vb= " When Ex command line completion is started with Tab, list valid completions " and complete the command line to the longest common substring, just as Bash " does, with just the one keypress. " " The default value of 'full' for the 'wildmode' option puts the full " completion onto the line immediately, which I tolerate for insert mode " completion but don't really like on the Ex command line. Instead, I arrange " for that with a second keypress if I ever want it, which isn't often. I did " without using it at all for years. " set wildmenu set wildmode=list:longest,full " Define a list of patterns for the 'wildignore' option. Files and " directories with names matching any of these patterns won't be presented as " candidates for tab completion on the command line. " " To make this list, I went right through my home directory with " a `find`-toothed comb, counted the occurrences of every extension, forced " down to lowercase; and then manually selected the ones that I was confident " would seldom contain plain text. " " This does the trick with POSIX-compatible shell tools, giving you patterns " for the top 50 extensions: " " $ find ~ -type f -name '*.*' | " awk -F. '{exts[tolower($NF)]++} " END {for(ext in exts)print exts[ext], "*." ext}' | " sort -k1,1nr | " sed 50q " " I turned out to have rather a lot of .html and .vim files. " " It's tempting to put the list of patterns here into a separate file--or at " least into a more readily editable intermediate list variable--rather than " the minor maintenance hassle it presently constitutes in this compact form. " I'm not sure whether I'll do that just yet. " set wildignore=*~,#*#,*.7z,.DS_Store,.git,.hg,.svn,*.a,*.adf,*.asc,*.au,*.aup \,*.avi,*.bin,*.bmp,*.bz2,*.class,*.db,*.dbm,*.djvu,*.docx,*.exe \,*.filepart,*.flac,*.gd2,*.gif,*.gifv,*.gmo,*.gpg,*.gz,*.hdf,*.ico \,*.iso,*.jar,*.jpeg,*.jpg,*.m4a,*.mid,*.mp3,*.mp4,*.o,*.odp,*.ods,*.odt \,*.ogg,*.ogv,*.opus,*.pbm,*.pdf,*.png,*.ppt,*.psd,*.pyc,*.rar,*.rm \,*.s3m,*.sdbm,*.sqlite,*.swf,*.swp,*.tar,*.tga,*.ttf,*.wav,*.webm,*.xbm \,*.xcf,*.xls,*.xlsx,*.xpm,*.xz,*.zip " Allow me to be lazy and type a path to complete on the Ex command line in " all-lowercase, and transform the consequent completion to match the " appropriate case, like the Readline setting completion-ignore-case can be " used for GNU Bash. " " As far as I can tell, despite its name, the 'wildignore' case option doesn't " have anything to do with the 'wildignore' option, and so files that would " match any of those patterns only with case insensitivity implied will still " be candidates for completion. " " The option wasn't added until v7.3.72, so we need to check it exists before " we try to set it. " if exists('+wildignorecase') set wildignorecase endif " For word completion in insert mode with CTRL-X CTRL-K, or if 'complete' " includes the 'k' flag, the 'dictionary' option specifies the path to the " system word list. This makes the dictionary completion work consistently, " even if 'spell' isn't set at the time to coax it into using 'spellfile'. " " It's not an error if the system directory file added first doesn't exist; " it's just a common location that often yields a workable word list, and does " so on all of my main machines. " " At some point, I may end up having to set this option along with 'spellfile' " a bit more intelligently to ensure that spell checking and dictionary " function consistently, and with reference to the same resources. For the " moment, I've just added another entry referring to a directory in the user " runtime directory, but I don't have anything distinct to put there yet. " " In much the same way, we add an expected path to a thesaurus, for completion " with CTRL-X CTRL-T in insert mode, or with 't' added to 'completeopt'. The " thesaurus data isn't installed as part of the default `install-vim` target " in tejr's dotfiles, but it can be retrieved and installed with " `install-vim-thesaurus`. " " I got the thesaurus itself from the link in the :help for 'thesaurus' in " v8.1.1487. It's from WordNet and MyThes-1. I maintain a mirror on my own " website that the Makefile recipe attempts to retrieve. I had to remove the " first two metadata lines from thesaurus.txt, as Vim appeared to interpret " them as part of the body data. " " The checks for appending the 'dictionary' and 'thesaurus' paths in MYVIM " need to be stricter than the ones for 'backupdir', because the P_NDNAME " property is assigned to them, which enforces a character blacklist in the " option value. We check for the same set of blacklist characters here, and " if the MYVIM path offends, we just skip the setting entirely, rather than " throwing cryptic errors at the user. None of them are particularly wise " characters to have in paths, anyway, legal though they may be on Unix " filesystems. " set dictionary^=/usr/share/dict/words if $MYVIM !=# '' && $MYVIM !~# '[*?[|;&<>\r\n]' set dictionary^=$MYVIM/ref/dictionary.txt set thesaurus^=$MYVIM/ref/thesaurus.txt endif " Use all of the filetype detection, plugin, and indent support available. " I define my own filetype.vim and scripts.vim files for filetype detection, " in a similar but not identical form to the stock runtime files. I also " define my own ftplugin and indent files for some types, sometimes replacing " and sometimes supplementing the runtime files. " filetype plugin indent on " Enable syntax highlighting, but only if it's not already on, to save " reloading the syntax files unnecessarily. " " " " For several months in 2018, as an experiment, I tried using terminals with " no colour at all, imitating a phenomenally productive BSD purist co-worker " who abhorred colour in any form on his terminals. He only drank black " coffee, too. If you're reading this: Hello, bdh! " " That experiment was instructive and interesting, and I found I had been " leaning on colour information in some surprising ways. However, some months " later, I found I still missed my colours, and so I went back to my " Kodachrome roots, and didn't pine at all for that monochrome world. " " The thing I most like about syntax highlighting is detecting runaway " strings, which generally works in even the most threadbare language syntax " highlighting definitions. I kept missing such errors when I didn't have the " colours. I don't have high standards for it otherwise, except maybe for " shell script. " if !exists('syntax_on') syntax enable endif " We'll have Vim try to use my 'sahara' fork of the 'desert256' colour scheme, " and if it manages to do so without errors, turn on the 'cursorline' feature, " since the scheme configures it and 'cursorcolumn' to be a very dark grey " that doesn't stand out too much against a black background. Aside from the " aforementioned experiment with monochrome terminals, I exclusively use dark " backgrounds. " " If we fail to load the colour scheme, for whatever reason, suppress the " error, and reset the syntax highlighting, 'background', and 'cursorline' for " dark-background default colours. I used it for years; it looks and works " just fine. " " There's also a very simple grayscale colour scheme I occasionally use " instead called 'juvenile', which is included as a Git submodule with this " dotfiles distribution. " try colorscheme sahara set cursorline catch syntax reset set background=dark set nocursorline endtry " My mapping definitions begin here. I have some general personal rules for " approaches to mappings: " " * Use the configured Leader key as a prefix for mappings as much as " possible. " " * Use only the configured LocalLeader key as a prefix for mappings that are " defined as local to a buffer, which for me are almost always based on " &filetype and set up by ftplugin files. " " * If a normal mode map would make sense in visual mode, take the time to " configure that too. Use :xmap and its analogues rather :vmap to avoid " defining unusable select-mode mappings, even though I never actually use " selection mode directly. " " * Avoid mapping in insert mode; let characters be literal to the greatest " extent possible, and avoid "doing more" in insert mode besides merely " inserting text as it's typed. " " * Avoid chording with Ctrl in favour of leader keys. " " * Never use Alt/Meta chording; the terminal support for them is just too " confusing and flaky. " " * Don't suppress display of mapped commands for no reason; it's OK to show " the user the command that's being run under the hood. Do avoid HIT-ENTER " prompts, though. " " * Avoid shadowing any of Vim's existing functionality. If possible, extend " or supplement what Vim does, rather than replacing it. " " We'll start with the non-leader mappings. Ideally there shouldn't be too " many of these. " " I like using the space bar to scroll down a page, so I can lazily tap it to " read documents, and I find its default behaviour of moving right one " character to be useless. " " I also have a custom plugin named scroll_next.vim that issues :next to have " it move to the next file in the arglist if the bottom line of the buffer is " visible, for reading multiple buffers. " " " " However, I only want that functionality mapped if the required plugin is " actually going to load, so I check that it's available and that the " 'loadplugin' option is set before using its provided map target, because if " it doesn't it will kill the space key. it kills the space key. If the " plugin doesn't look like it's going to load, I just bind Space to do the " same thing as PageDown. " " Either way, the downside of this arrangement is it's an easy key to hit " accidentally. I'm keeping it for the moment, though. " " I always wanted you to go into space, man. " if globpath(&runtimepath, 'plugin/scroll_next.vim') !=# '' \ && &loadplugins nmap \ (ScrollNext) else nnoremap \ endif " I hate CTRL-C in insert mode, which ends the insert session without firing " the InsertLeave event for automatic command hooks. It seems worse than " useless; why would you want that? It breaks plugins that hinge on mirrored " functionality between the InsertEnter and InsertLeave automatic command " events, and doesn't otherwise do anything different from Escape. Terrible! " " Instead, I apply a custom plugin named insert_cancel.vim to make it cancel " the current insert operation; that is, if the buffer has changed at all " since the start of the insert operation, pressing CTRL-C will reverse it, " while ending insert mode and firing InsertLeave as normal. This makes way " more sense to me, and I use it all the time now. " " " " You might think on a first look, as I did, that a plugin is overkill, and " that a mapping like this would be all that's required: " " :inoremap u " " Indeed, it *mostly* works, but there are some subtle problems with it. The " primary issue is that if you didn't make any changes during the insert mode " session that you're terminating, it *still* reverses the previous change, " which will be something else entirely that you probably *didn't* mean to be " undone. The plugin's way of working around this and the other shortcomings " of the simple mapping above is not too much more complicated, but it was not " easy to figure out. " " At any rate, as with the space bar's leverage of the scroll_next.vim plugin " above, we only want to establish the mapping if we can expect the plugin to " load, so test that it exists with the expected name and that 'loadplugins' " is set. " " If the plugin isn't available, I just abandon CTRL-C to continue its " uselessness. " if globpath(&runtimepath, 'plugin/insert_cancel.vim') !=# '' \ && &loadplugins imap \ (InsertCancel) endif " I often don't remember or can't guess digraph codes very well, and want to " look up how to compose a specific character that I can name, at least in " part. The table in `:help digraph-table` is what to use for that situation, " and it solves the problem, but the overhead of repeated lookups therein was " just a little bit high. " " Steve Losh has a solution I liked where a double-tap of CTRL-K in insert " mode brought up a help window with the table, which could then be searched " as normal: " " " " I took it one step further with a custom plugin digraph_search.vim that " parses the digraph table and runs a simple text search of its names using " a string provided by the user. For example, searching for ACUTE yields: " " > Digraphs matching ACUTE: " > ´ '' ACUTE ACCENT " > Á A' LATIN CAPITAL LETTER A WITH ACUTE " > É E' LATIN CAPITAL LETTER E WITH ACUTE " > Í I' LATIN CAPITAL LETTER I WITH ACUTE " > ... etc ... " " " " This leaves you in insert mode, ready to hit CTRL-K one more time and then " type the digraph that you've hopefully found. " " Since a double-tap of CTRL-K does nothing in default Vim, we don't bother " checking that the plugin's available before we map to it; it'll just quietly " do nothing. " imap \ (DigraphSearch) " Stack Ctrl-L to clear search highlight, make it work in insert mode too nnoremap \ :nohlsearch vnoremap \ :nohlsearchgv inoremap \ :nohlsearch " Remap normal/visual & and g& to preserve substitution flags nnoremap & \ :&& xnoremap & \ :&& nnoremap g& \ :%&& " Map g: as a 'colon operator' nmap g: \ (ColonOperator) " Cycle through argument list nnoremap [a \ :previous nnoremap ]a \ :next " Cycle through buffers nnoremap [b \ :bprevious nnoremap ]b \ :bnext " Cycle through quickfix list items nnoremap [c \ :cprevious nnoremap ]c \ :cnext " Cycle through location list items nnoremap [l \ :lprevious nnoremap ]l \ :lnext " Insert blank lines around current line nmap [ \ (PutBlankLinesAbove) nmap ] \ (PutBlankLinesBelow) " Set leader keys let mapleader = '\' let maplocalleader = ',' " Leader,a toggles 'formatoptions' 'a' flag using a plugin nnoremap a \ :ToggleFlagLocal formatoptions a " Leader,b toggles settings friendly to copying and pasting nmap b \ (CopyLinebreakToggle) " Leader,c toggles 'cursorline'; no visual mode map as it doesn't work nnoremap c \ :setlocal cursorline! cursorline? " Leader,C toggles 'cursorcolumn'; works in visual mode nnoremap C \ :setlocal cursorcolumn! cursorcolumn? xnoremap C \ :setlocal cursorcolumn! cursorcolumn?gv " Leader,d inserts the local date (POSIX date) nnoremap d \ :read !date " Leader,D inserts the UTC date (POSIX date) nnoremap D \ :read !date -u " Leader,e forces a buffer to be editable nnoremap e \ :setlocal modifiable noreadonly " Leader,f shows the current 'formatoptions' at a glance nnoremap f \ :setlocal formatoptions? " Leader,F reloads filetype plugins nnoremap F \ :FileTypeReload " Leader,g shows the current file's fully expanded path nnoremap g \ :echo expand('%:p') " Leader,G changes directory to the current file's location nnoremap G \ :cd %:hpwd " Leader,h toggles highlighting search results nnoremap h \ :set hlsearch! hlsearch? " Leader,H shows command history nnoremap H \ :history : " Leader,i toggles showing matches as I enter my pattern nnoremap i \ :set incsearch! incsearch? " Leader,j jumps to buffers ("jetpack") nnoremap j \ :buffers:buffer " Leader,k shows my marks nnoremap k \ :marks " Leader,l toggles showing tab, end-of-line, and trailing white space nnoremap l \ :setlocal list! list? xnoremap l \ :setlocal list! list?gv " Leader,L toggles 'colorcolumn' showing 'textwidth' nnoremap L \ :ToggleFlagLocal colorcolumn +1 xnoremap L \ :ToggleFlagLocal colorcolumn +1gv " Leader,m shows normal maps nnoremap m \ :map " Leader,M shows buffer-local normal maps nnoremap M \ :map " Leader,n toggles line number display nnoremap n \ :setlocal number! number? xnoremap n \ :setlocal number! number?gv " Leader,N toggles position display in bottom right nnoremap N \ :set ruler! ruler? xnoremap N \ :set ruler! ruler?gv " Leader,o opens a line below in paste mode nmap o \ (PasteOpenBelow) " Leader,O opens a line above in paste mode nmap O \ (PasteOpenAbove) " Leader,p toggles paste mode nnoremap p \ :set paste! paste? " Leader,P creates the path to the current file nnoremap P \ :call mkdir(expand('%:h'), 'p') " Leader,q formats the current paragraph nnoremap q \ gqap " Leader,r acts as a replacement operator nmap r \ (ReplaceOperator) xmap r \ (ReplaceOperator) " Leader,R reloads ~/.vimrc nnoremap R \ :source $MYVIMRC " Leader,s toggles spell checking nnoremap s \ :setlocal spell! spell? " Leader,S shows loaded scripts nnoremap S \ :scriptnames " Leader,t shows current filetype nnoremap t \ :setlocal filetype? " Leader,T clears filetype nnoremap T \ :setlocal filetype= " Leader,u sets US English spelling (compare Leader,z) nnoremap u \ :setlocal spelllang=en_us " Leader,v shows all global variables nnoremap v \ :let g: v: " Leader,V shows all local variables nnoremap V \ :let b: t: w: " Leader,w toggles wrapping nnoremap w \ :setlocal wrap! wrap? xnoremap w \ :setlocal wrap! wrap?gv " Leader,x strips trailing whitespace via a custom plugin nnoremap x \ :StripTrailingWhitespace xnoremap x \ :StripTrailingWhitespace " Leader,X squeezes repeated blank lines via a custom plugin nnoremap X \ :SqueezeRepeatBlanks xnoremap X \ :SqueezeRepeatBlanks " Leader,y shows all registers nnoremap y \ :registers " Leader,z sets NZ English spelling (compare Leader,u) nnoremap z \ :setlocal spelllang=en_nz " Leader,= runs the whole buffer through =, preserving position nnoremap = \ :KeepPosition normal! 1G=G " Leader,+ runs the whole buffer through gq, preserving position nnoremap + \ :KeepPosition normal! 1GgqG " Leader,. runs the configured make program into the location list nnoremap . \ :lmake! " Leader,< and Leader,> adjust indent of last edit; good for pasting nnoremap \ :'[,'] nnoremap > \ :'[,']> " Leader,_ uses last changed or yanked text as an object onoremap _ \ :normal! `[v`] " Leader,% uses entire buffer as an object onoremap % \ :normal! 1GVG " Leader,{ and Leader,} move to lines with non-space chars before current column map { \ (VerticalRegionUp) sunmap { map } \ (VerticalRegionDown) sunmap } " Leader,/ types :vimgrep for me ready to enter a search pattern nnoremap / \ :vimgrep /\c/j ** " Leader,? types :lhelpgrep for me ready to enter a search pattern nnoremap ? \ :lhelpgrep \c " Leader,* escapes regex metacharacters nmap * \ (RegexEscape) xmap * \ (RegexEscape) " Leader,\ jumps to the last edit position mark, like g;, but works as a motion " "Now, where was I?" (tap-tap) nnoremap \ \ `" xnoremap \ \ `" " Leader,DEL deletes the current buffer nnoremap \ :bdelete " Leader,INS edits a new buffer nnoremap \ :enew " Leader,TAB toggles 'autoindent' nnoremap \ :setlocal autoindent! autoindent? " Some useful abbreviations inoreabbrev tr@ tom@sanctum.geek.nz inoreabbrev tr/ " Things I almsot always type wrnog inoreabbrev almsot almost inoreabbrev wrnog wrong inoreabbrev Fielding Feilding inoreabbrev THe The inoreabbrev THere There