├── truly-cross-platform ├── syscall_vimrc ├── no_syscall_vimrc ├── exec_vimrc ├── command.txt ├── exec_startup.txt ├── syscall_startup.txt └── no_syscall_startup.txt ├── AUTHORS ├── default_diff.png ├── ruby_default.png ├── histogram_diff.png ├── ruby_indent_heuristics.png ├── tejr-from-vimrc-to-vim.zip ├── justone-one-vim-session.png ├── shlomif-if_pyth-cookbook.md ├── LICENSE ├── README.md ├── robertmeta-the-three-percent.md ├── igemnace-road-to-integration.md ├── romainl-death-by-a-thousand-files.md ├── topics.md ├── chrisbra-diffmode.md ├── justone-one-vim.md ├── justinmk-exmode.md ├── swalladge-vim-and-git.md ├── george-b-formatting-lists-with-vim.md ├── djmoch-tag-vims-it.md ├── gagbo-truly-cross-platform.md ├── tejr-from-vimrc-to-vim.md └── markzen-mappings.md /truly-cross-platform/syscall_vimrc: -------------------------------------------------------------------------------- 1 | let g:string_date = system('date') 2 | -------------------------------------------------------------------------------- /truly-cross-platform/no_syscall_vimrc: -------------------------------------------------------------------------------- 1 | let g:string_date = 'dummy_string' 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Robert Melton (robertmeta) 2 | Tom Ryder (tejr) 3 | Gerry Agbobada (gagbo) 4 | -------------------------------------------------------------------------------- /default_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/default_diff.png -------------------------------------------------------------------------------- /ruby_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/ruby_default.png -------------------------------------------------------------------------------- /histogram_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/histogram_diff.png -------------------------------------------------------------------------------- /truly-cross-platform/exec_vimrc: -------------------------------------------------------------------------------- 1 | if executable('rg') 2 | let g:string_date = 'dummy string' 3 | endif 4 | -------------------------------------------------------------------------------- /ruby_indent_heuristics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/ruby_indent_heuristics.png -------------------------------------------------------------------------------- /tejr-from-vimrc-to-vim.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/tejr-from-vimrc-to-vim.zip -------------------------------------------------------------------------------- /justone-one-vim-session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vim-advent-calendar/ideas/HEAD/justone-one-vim-session.png -------------------------------------------------------------------------------- /truly-cross-platform/command.txt: -------------------------------------------------------------------------------- 1 | vim --noplugin -u {syscall_vimrc, NONE} --startuptime {startup_syscall,startup_no_syscall} 2 | -------------------------------------------------------------------------------- /shlomif-if_pyth-cookbook.md: -------------------------------------------------------------------------------- 1 | # if_pyth (= the python interface) cookbook 2 | 3 | Content to cover: 4 | 5 | - Why if_pyth 6 | - python (or perl or whatever) are often better than vimscript 7 | - pypi / pip 8 | - write extensions in C/etc. 9 | - Processing/filtering each line in a range of lines 10 | - Processing an entire subrange / selection of lines at once 11 | - Accessing vimscript variables and registers 12 | - Writing vimscript functions in python. 13 | 14 | Ideas/comments/feedback appreciated :) 15 | -------------------------------------------------------------------------------- /truly-cross-platform/exec_startup.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | times in msec 4 | clock self+sourced self: sourced script 5 | clock elapsed: other lines 6 | 7 | 000.009 000.009: --- VIM STARTING --- 8 | 000.127 000.118: Allocated generic buffers 9 | 000.233 000.106: locale set 10 | 000.242 000.009: window checked 11 | 001.037 000.795: inits 1 12 | 001.087 000.050: parsing arguments 13 | 001.089 000.002: expanding arguments 14 | 001.108 000.019: shell init 15 | 001.612 000.504: Termcap init 16 | 001.646 000.034: inits 2 17 | 001.793 000.147: init highlight 18 | 001.991 000.136 000.136: sourcing exec_vimrc 19 | 002.002 000.073: sourcing vimrc file(s) 20 | 002.020 000.018: inits 3 21 | 002.050 000.030: setting raw mode 22 | 002.055 000.005: start termcap 23 | 002.073 000.018: clearing screen 24 | 002.841 000.768: opening buffers 25 | 002.846 000.005: BufEnter autocommands 26 | 002.847 000.001: editing files in windows 27 | 002.857 000.010: VimEnter autocommands 28 | 002.858 000.001: before starting main loop 29 | 003.378 000.520: first screen update 30 | 003.383 000.005: --- VIM STARTED --- 31 | -------------------------------------------------------------------------------- /truly-cross-platform/syscall_startup.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | times in msec 4 | clock self+sourced self: sourced script 5 | clock elapsed: other lines 6 | 7 | 000.017 000.017: --- VIM STARTING --- 8 | 000.346 000.329: Allocated generic buffers 9 | 000.567 000.221: locale set 10 | 000.581 000.014: window checked 11 | 001.957 001.376: inits 1 12 | 002.045 000.088: parsing arguments 13 | 002.050 000.005: expanding arguments 14 | 002.101 000.051: shell init 15 | 003.144 001.043: Termcap init 16 | 003.194 000.050: inits 2 17 | 003.593 000.399: init highlight 18 | 092.540 088.869 088.869: sourcing bad_vimrc 19 | 092.546 000.084: sourcing vimrc file(s) 20 | 092.558 000.012: inits 3 21 | 092.583 000.025: setting raw mode 22 | 092.589 000.006: start termcap 23 | 092.627 000.038: clearing screen 24 | 093.078 000.451: opening buffers 25 | 093.081 000.003: BufEnter autocommands 26 | 093.082 000.001: editing files in windows 27 | 093.089 000.007: VimEnter autocommands 28 | 093.090 000.001: before starting main loop 29 | 093.495 000.405: first screen update 30 | 093.497 000.002: --- VIM STARTED --- 31 | -------------------------------------------------------------------------------- /truly-cross-platform/no_syscall_startup.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | times in msec 4 | clock self+sourced self: sourced script 5 | clock elapsed: other lines 6 | 7 | 000.005 000.005: --- VIM STARTING --- 8 | 000.085 000.080: Allocated generic buffers 9 | 000.145 000.060: locale set 10 | 000.150 000.005: window checked 11 | 000.606 000.456: inits 1 12 | 000.636 000.030: parsing arguments 13 | 000.637 000.001: expanding arguments 14 | 000.651 000.014: shell init 15 | 000.994 000.343: Termcap init 16 | 001.018 000.024: inits 2 17 | 001.188 000.170: init highlight 18 | 001.246 000.021 000.021: sourcing no_syscall_vimrc 19 | 001.250 000.041: sourcing vimrc file(s) 20 | 001.260 000.010: inits 3 21 | 001.274 000.014: setting raw mode 22 | 001.278 000.004: start termcap 23 | 001.296 000.018: clearing screen 24 | 001.818 000.522: opening buffers 25 | 001.822 000.004: BufEnter autocommands 26 | 001.824 000.002: editing files in windows 27 | 001.833 000.009: VimEnter autocommands 28 | 001.834 000.001: before starting main loop 29 | 002.366 000.532: first screen update 30 | 002.369 000.003: --- VIM STARTED --- 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 AUTHORS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vim Advent Ideas 2 | 3 | Please claim a topic in the topics.md! If you want to start writing 4 | more seriously, follow the format: 5 | 6 | - romainl-vim-for-fun-and-profit.md 7 | - robertmeta-the-three-percent.md 8 | 9 | in the root of ideas to start sketching stuff down or if you want 10 | feedback. 11 | 12 | ## Deadlines 13 | 14 | The schedule is fairly laid back: 15 | 16 | - Sept 1st: at least 22 contributors signed up, hopefully a bit more because life happens 17 | - Oct 1st: finalization of topics (no overlap) and light sketch of article 18 | - Nov 1st: significant work shown (article content) and starts of integration into site 19 | - Nov 15th: final versions checked in / handed over with proper license included 20 | - Nov 20th: domain goes live 21 | - Dec 1st: first article goes live 22 | 23 | Taken from [Reddit](https://reddit.com/r/vim/comments/94o8al/vim_advent_calendar_call_for_authors/). 24 | 25 | # LICENSES 26 | 27 | - Techincal content will be under an MIT license, source code, etc 28 | - Article content will be contributed under a CC 4.0 license, please 29 | add a CC 4.0 link to the bottom of your article: 30 | https://creativecommons.org/licenses/by/4.0/. 31 | -------------------------------------------------------------------------------- /robertmeta-the-three-percent.md: -------------------------------------------------------------------------------- 1 | The 3% You Need To Know 2 | ======================================================================== 3 | Vim is an amazing editor, and you should take the time to dive deep and 4 | learn a lot of the features. This is the 3% of Vim I think you should 5 | having in your toolbox. This isn't an ideal learning path, this won't 6 | start from the beginning, it won't end at the end. It is just the 7 | features I find most useful. 8 | 9 | :helpgrep (and :help) 10 | ---------------------------------------------------------------------- 11 | Vim has astounding documentation. 3500+ pages of it. Including a 341+ 12 | page user manual (written to be read cover to cover). 13 | 14 | > an undocumented feature is a useless feature 15 | 16 | :g and norm 17 | ---------------------------------------------------------------------- 18 | 19 | :s 20 | --------------------------------------------------------------------- 21 | 22 | bufdo, argdo, dofile, doline 23 | -------------------------------------------------------------------- 24 | 25 | jumps and lists 26 | ------------------------------------------------------------------- 27 | 28 | undotree (or by time) 29 | ------------------------------------------------------------------- 30 | 31 | macros 32 | ------------------------------------------------------------------- 33 | 34 | registers 35 | ------------------------------------------------------------------- 36 | -------------------------------------------------------------------------------- /igemnace-road-to-integration.md: -------------------------------------------------------------------------------- 1 | The Road to Integration 2 | ======================================================================= 3 | 4 | Topic thesis (WIP, from topics.md): 5 | 6 | Dev workflows vary from person to person, although they do share some 7 | common tasks such as kicking off build processes, linting, formatting, 8 | testing, running code, etc. This article will talk about integrating 9 | these tasks into Vim. 10 | 11 | Personal notes: 12 | 13 | - Re: title: obviously still WIP since I still have to write the whole 14 | article, but I like how "Road to Integration" implies that instead of 15 | supplying pre-packaged solutions (which is what IDEs do better), the 16 | article will instead help the reader build his own tailor-fit 17 | customizations to match his unique workflow better. 18 | - What to cover -- the major points: 19 | - Build processes, so `:make` and related settings 20 | - Linting. Should fall under `:make`, but it might do well to point 21 | out that the feature can be used for more than just literally `make` 22 | and other build runners. Might still need new ideas, since there's 23 | already material out there, e.g. [vimgor's why you don't need 24 | Syntastic][1] 25 | - Formatting, so `gq`, `=`, and related settings 26 | - Running code. Quickly running through an interpreter is fine. Might 27 | even be able to still take advantage of `:make` for this. 28 | Interacting with REPLs is trickier. Might merit plugin suggestions 29 | here since I use one to interact with tmux panes (as well as taking 30 | advantage of file watchers). I'll also need to do research re: how 31 | to take advantage of `:terminal` for this. 32 | - Debugging? I'll need to do a *lot* of research if I include this, 33 | since I don't debug from within Vim myself. `:terminal` was followed 34 | closely by a `termdebug` package, so this merits some thought. 35 | - Completion? Still unsure if this should be included, but I'm 36 | currently leaning towards "No". Some other topics like LSP and such 37 | are more suited, and leaving this out conveniently gives me a more 38 | focused "`:make` and quickfix to the fullest" theme. 39 | - Search? Same boat as completion above 40 | - Re: "Jump to definition". I'd definitely leave this one out, since a 41 | whole article is already being devoted to tags. 42 | - Bonus minor ideas: 43 | - Fugitive's `:Gmerge`, which I find extra useful after having `git 44 | merge` result in conflicts, since it loads up the conflicts in the 45 | quickfix. 46 | - Client/server feature. A combination of this feature, a short 47 | script, and some cool tooling bundled with React Native allows me to 48 | tap on errors in the stack trace on my test device and have the 49 | relevant file pop up in my current running Vim on my dev machine. 50 | Definitely bumped down to "minor" status for being very specific in 51 | its use case, but might make for a great point to discuss, 52 | especially in an "integrating Vim with the dev environment" article. 53 | 54 | [1]: https://gist.github.com/ajh17/a8f5f194079818b99199 55 | -------------------------------------------------------------------------------- /romainl-death-by-a-thousand-files.md: -------------------------------------------------------------------------------- 1 | # Death by a thousand files 2 | 3 | > A guy told me one time, "Don't let yourself get attached to anything you are not willing to walk out on in 30 seconds flat if you feel the heat around the corner." - Neil McCauley (1995) 4 | 5 | ## Moving with intent 6 | 7 | Right after we learn Vim's most mundane commands (for writing, quitting, etc.) the most immediately useful features are motions: `w` for "beginning of next word", `B` for "beginning of current or previous WORD", etc. and the grammar that allows us to combine them with various operators to form whatever sentence we need for the task at hand. 8 | 9 | Taking `w` as an example; that motion is demonstrated in *every* beginner-level tutorial and typically used dozens of times a day by almost every vimmer but, however useful it might be, it can easily be overused by mashing it until you arrive at or near your destination. 10 | 11 | Repeating intermediary jumps with `w` is only marginally better than repeating `` or `l`. Sure it *may* take fewer keystrokes but we still go through an undefined number of intermediary steps that are far removed from our actual goal which, more often than not, is "go there" rather than "go there, there, there, there, and finally *there*". 12 | 13 | Zooming out a bit, it's easy to see that we tend to go through those intermediary steps *a lot*: 14 | 15 | * at the line level, by repeating short jumps, 16 | * at the block level by jumping to its beginning or end, 17 | * at the screen level by jumping from block to block, 18 | * at the buffer level by jumping from screen to screen, 19 | * and at the project level by jumping to a file/buffer in order to jump to a symbol. 20 | 21 | What if we actually "went *there*" directly instead? 22 | 23 | At the line, block, and screen levels, incremental search helps tremendously. We have our eyes on `getUserName` and we simply type `/` or `?`, followed by as much of our target as necessary to land the cursor on it. 24 | 25 | At the buffer level, commands like `:g/pattern/#` or `:ilist /pattern` help a lot by letting us choose our destination from a list rather than going through each item. There's also `gd` to jump directly to the first instance of the word under the cursor. 26 | 27 | At the project level… it gets a little too complicated. 28 | 29 | Directories, files, paragraphs, lines, words, numbers, signs, etc. The way computers have been designed for the last 40+ years make us think of those things as… *things*. Physical objects with which we interact as if they were *real*. We "cut" them as if they were tomatoes, "navigate" from one to another as if they were harbors, etc. 30 | 31 | But how necessary are those metaphors for thinking about our program and our data flow? How useful is it to map *this* step in the processing of a video to *that* file? Why should we care about paths, directories, or file names at all? Why do we have to juggle with so many pieces of information when we already have the exact name of what we want to see next right under our nose/cursor? 32 | 33 | ## Source code as a hierarchy 34 | 35 | Because the file system is usually exposed to us as a hierarchy of (hierarchies of) directories and files, programmers naturally store the symbols and instructions that make their programs into files, themselves stored within directories and subdirectories. Just like our forefathers did with punch cards in file cabinets. Some languages or frameworks may even enforce a "standard" directory structure in the hope of making projects easier to explore and grok, by people and machines alike. There's nothing inherently wrong with any of that, of course: we are just using a convenient metaphor and things are well categorized and organized, right? 36 | 37 | But there is a fundamental limitation with that approach: it is completely at odds with how we think when coding. 38 | 39 | * Moving stuff around can and do cause undesirable effects across the whole project. 40 | 41 | 42 | 43 | * There's only one way to get from point A to point B. 44 | * One thing can't be in two places. 45 | * Two things can have the same name but be in different locations. 46 | 47 | * We need to maintain a rather complex map of our source code 48 | 49 | 50 | 51 | ## Source code as a network 52 | 53 | ## Symbols, not files 54 | 55 | [//]: # ( Vim: set spell spelllang=en: ) 56 | -------------------------------------------------------------------------------- /topics.md: -------------------------------------------------------------------------------- 1 | Topic Ideas 2 | ======================================================================== 3 | This is just a list of ideas and sort of a "claim sheet". Feel free to 4 | list your idea here, list an idea AND claim it, or claim an existing 5 | idea. We don't want duplication in our small 24(ish) article run. 6 | 7 | Additionally, even if you aren't doing a topic, feel free to write some 8 | notes under it that you think are relevant / interesting. 9 | 10 | Don't Use Vim 11 | ----------------------------------------------------------------------- 12 | CLAIMED BY: 13 | 14 | This would be a talked about reasons not to use Vim, reasons to use an 15 | alternative, etc. 16 | 17 | Make your setup truly cross-platform 18 | ----------------------------------------------------------------------- 19 | CLAIMED BY: gagbo 20 | 21 | Tips to be able to clone your version-controlled `.vim` directory on 22 | any machine you use without having to worry about environment specific 23 | configuration (or even using vim vs neovim) 24 | 25 | see gagbo-truly-cross-platform.md. 26 | 27 | The ideal learning path 28 | ----------------------------------------------------------------------- 29 | CLAIMED BY: 30 | 31 | ... 32 | 33 | The 3% of Vim you need, skip the rest 34 | ----------------------------------------------------------------------- 35 | CLAIMED BY: robertmeta 36 | 37 | see robertmeta-the-three-percent.md. 38 | 39 | How to integrate Vim with its environment 40 | ----------------------------------------------------------------------- 41 | CLAIMED BY: 42 | 43 | ... 44 | 45 | How to integrate Vim with your dev environment 46 | ----------------------------------------------------------------------- 47 | CLAIMED BY: igemnace 48 | 49 | Dev workflows vary from person to person, although they do share some 50 | common tasks such as kicking off build processes, linting, formatting, 51 | testing, running code, etc. This article will talk about integrating 52 | these tasks into Vim. 53 | 54 | See igemnace-road-to-integration.md. 55 | 56 | ... 57 | 58 | Using Vim for weird things (music, statistical analysis, painting...) 59 | ----------------------------------------------------------------------- 60 | CLAIMED BY: 61 | 62 | ... 63 | 64 | When to look for a plugin 65 | ----------------------------------------------------------------------- 66 | CLAIMED BY: 67 | 68 | ... 69 | 70 | What to look for in a plugin 71 | ----------------------------------------------------------------------- 72 | CLAIMED BY: 73 | 74 | ... 75 | 76 | Editing best practices 77 | ----------------------------------------------------------------------- 78 | CLAIMED BY: 79 | 80 | ... 81 | 82 | Vimscript is not that bad/hard 83 | ----------------------------------------------------------------------- 84 | CLAIMED BY: 85 | 86 | ... 87 | 88 | Debunking common misconceptions 89 | ----------------------------------------------------------------------- 90 | CLAIMED BY: 91 | 92 | ... 93 | 94 | Listing valuable resources (and explaining why they are valuable) 95 | ----------------------------------------------------------------------- 96 | CLAIMED BY: 97 | 98 | ... 99 | 100 | Everything you never thought you wanted to know about tags 101 | ----------------------------------------------------------------------- 102 | CLAIMED BY: djmoch 103 | 104 | - Introduction to tags in Vim 105 | - Introduction to exuberant ctags 106 | - Manually generating tags files 107 | - Automatically generating tags files (git hooks, plugins, etc.) 108 | - Tag completion (or, why you don't need jedi plugins) 109 | 110 | Let me talk to you about my convoluted way to become a Vimmer 111 | ----------------------------------------------------------------------- 112 | CLAIMED BY: 113 | 114 | ... 115 | 116 | State of the nation 117 | ----------------------------------------------------------------------- 118 | CLAIMED BY: 119 | 120 | There are very few people I would be comfortable with writing this, 121 | justinmk, Bram, a handful of others who are deep enough in the list 122 | the project. 123 | 124 | Long-term trends in plugin design (async, lsp, etc.) 125 | ----------------------------------------------------------------------- 126 | CLAIMED BY: 127 | 128 | ... 129 | 130 | LSP and why it matters 131 | ----------------------------------------------------------------------- 132 | CLAIMED BY: 133 | 134 | ... 135 | 136 | A view at operators from a linguistics point of view 137 | ----------------------------------------------------------------------- 138 | CLAIMED BY: 139 | 140 | ... 141 | 142 | History behind $DESIGN_CHOICE 143 | ----------------------------------------------------------------------- 144 | CLAIMED BY: 145 | 146 | ... 147 | 148 | A vimrc explained 149 | ----------------------------------------------------------------------- 150 | CLAIMED BY: 151 | 152 | Explain a vimrc completely, make it have lots of useful or interesting 153 | tidbits in it. 154 | 155 | Bulk Refactoring using bufdo, argdo (maybe doline, dofile) 156 | ----------------------------------------------------------------------- 157 | CLAIMED BY: 158 | 159 | ... 160 | 161 | Why you don't need more than one cursor 162 | ----------------------------------------------------------------------- 163 | CLAIMED BY: 164 | 165 | Lots of editors are making multiple cursors are core feature, explain 166 | why vim doesn't need them. 167 | 168 | The insane power of vim's regexps 169 | ----------------------------------------------------------------------- 170 | CLAIMED BY: 171 | 172 | ... 173 | 174 | The power of vim's built-in lists 175 | ----------------------------------------------------------------------- 176 | CLAIMED BY: 177 | 178 | ... 179 | 180 | From zero to sixty with ${PLUGIN} 181 | ----------------------------------------------------------------------- 182 | CLAIMED BY: 183 | 184 | ... 185 | 186 | One Vim, why you should use just one instance 187 | ----------------------------------------------------------------------- 188 | CLAIMED BY: justone 189 | 190 | See justone-one-vim.md 191 | 192 | Symbols, not files 193 | ----------------------------------------------------------------------- 194 | CLAIMED BY: romainl 195 | 196 | See romainl-death-by-a-thousand-files.md 197 | 198 | Formating with Vim 199 | ----------------------------------------------------------------------- 200 | CLAIMED BY: george-b 201 | 202 | See george-b-formatting-with-vim.md 203 | 204 | From .vimrc to .vim 205 | ----------------------------------------------------------------------- 206 | CLAIMED BY: tejr 207 | 208 | See tejr-from-vimrc-to-vim.md 209 | 210 | Vim History and Evolution 211 | ----------------------------------------------------------------------- 212 | CLAIMED BY: rossijonas 213 | 214 | - I'm thinking about telling the story behind vim's creation and the evolution, inspired by [this article](https://thenewstack.io/a-look-at-vim-a-text-editor-for-the-ages/) from David Cassel, and [this video](https://www.youtube.com/watch?v=ayc_qpB-93o) from Bram. 215 | 216 | Colorschemes Best Practices 217 | ------------------------------------------------------------------------ 218 | CLAIMED BY: legendre6891 219 | 220 | - I'm thinking about writing a short HOWTO on creating colorschemes, focusing on best practices 221 | and techniques to ensure the colorschemes works across terminals. 222 | 223 | if_pyth (= the python interface) cookbook 224 | ------------------------------------------------------------------------ 225 | CLAIMED BY: shlomif 226 | 227 | - Some common tasks and how to achieve them. 228 | 229 | Vim and Git 230 | ------------------------------------------------------------------------ 231 | CLAIMED BY: swalladge 232 | 233 | See swalladge-vim-and-git.md 234 | -------------------------------------------------------------------------------- /chrisbra-diffmode.md: -------------------------------------------------------------------------------- 1 | # The power of diff 2 | 3 | Lot of people use vimdiff to understand and handle diffs in console 4 | mode. While there exists more specialized tools for comparing files, 5 | vimdiff has always worked good enough for me. 6 | 7 | ## The inefficiency of the external diff 8 | 9 | However, Vims diff mode was seriously lacking. This was basically, 10 | because it needed to write down temporary files, shell out and run a 11 | manual diff command and parse the result back and as one can imagine, 12 | this could be slow and was seriously inefficient. 13 | 14 | Additionally, this required to have a diff binary available that was 15 | able to create [ed like style diffs][0], so one could not even fall-back 16 | to using git-diff (which is considered to have one of the best tested 17 | diff libraries and allows to select different algorithms) for creating 18 | those diffs. This lead to the creation of vimscript [plugins][1], that 19 | would internally translate a unified diff back into an ed-like diff. Of 20 | course this would add an extra performance penalty. 21 | 22 | In Windows land, this was also a pain, since Vim had to be distributed with 23 | an extra diff binary, since Windows does not come with it out of the box 24 | and one would notice the expansive diff call by an ugly command line 25 | window flashing by whenever the diff needed to be updated. 26 | 27 | Also, since the whole generation of diffs was so ugly, Vim would not 28 | always refresh the diff on each and every modification to not slow down 29 | too much causing inaccurate diffs every once in a while. 30 | 31 | And finally, before shelling out for the external diff command, Vim 32 | would **every time** check, that a diff is actually available by running 33 | a hard coded diff against "line1" and "line2" buffer. 34 | 35 | ## Bundling an internal diff library with Vim 36 | 37 | This problem was well known and can still be found in the well known 38 | todo.txt file (`:h todo.txt`, search for diff). One problem why it 39 | wasn't done earlier, was that there did not exist a good documented and 40 | simple to use C library that could be used by Vim. 41 | 42 | So I started working on how to improve this [situation][2] and decided 43 | to go with the xdiff library which the git developers finally settled to 44 | use. They basically had the same problem when the git vcs system was 45 | developed by Linus Torvalds. Back at the days around 2006 they decided 46 | to ship git with the [libxdiff][3] library, which over time got heavily 47 | modified to fit better the needs of git. 48 | 49 | The advantage of using the same library for Vim is, that for one, the 50 | library has been tested and proven to be working well over the last 51 | 12 years. In addition, is has been tweaked and several new diff 52 | algorithms have been added, like the [patience diff algorithm][4] and 53 | [histogram diff algorithm][5] and the [indent-heuristics][6]. 54 | 55 | So with [Patch 8.1.360][7] the xdiff code from git has been finally 56 | merged into Vim and allows for a much smoother and more efficient diff 57 | experience in Vim. In addition, the internal diff algorithm has been 58 | made the default, but one can still switch to the old external 59 | algorithm, using 60 | 61 | :set diffopt-=internal 62 | 63 | Also, Vim can now read and understand the [unified diff format][8] 64 | (which seems to be the standard format nowadays), so even when the 65 | bundled the diff library does not work well enough, one does not need to 66 | translate the output back into a ed like diff format anymore. 67 | 68 | ## Some screenshots 69 | 70 | By default, the diff library uses the myers algorithm (also known as 71 | [longest common subsequence problem][9]). However, in certain 72 | circumstances, one might want to use a different algorithm. One famous 73 | example is for the patience algorithm. 74 | 75 | ### The patience algorithm 76 | 77 | Say you have the following file1: 78 | 79 | ```c 80 | #include 81 | 82 | // Frobs foo heartily 83 | int frobnitz(int foo) 84 | { 85 | int i; 86 | for(i = 0; i < 10; i++) 87 | { 88 | printf("Your answer is: "); 89 | printf("%d\n", foo); 90 | } 91 | } 92 | 93 | int fact(int n) 94 | { 95 | if(n > 1) 96 | { 97 | return fact(n-1) * n; 98 | } 99 | return 1; 100 | } 101 | 102 | int main(int argc, char **argv) 103 | { 104 | frobnitz(fact(10)); 105 | } 106 | ``` 107 | 108 | In addition you have the following changed file2 (e.g. from a later revision): 109 | ```c 110 | #include 111 | 112 | int fib(int n) 113 | { 114 | if(n > 2) 115 | { 116 | return fib(n-1) + fib(n-2); 117 | } 118 | return 1; 119 | } 120 | 121 | // Frobs foo heartily 122 | int frobnitz(int foo) 123 | { 124 | int i; 125 | for(i = 0; i < 10; i++) 126 | { 127 | printf("%d\n", foo); 128 | } 129 | } 130 | 131 | int main(int argc, char **argv) 132 | { 133 | frobnitz(fib(10)); 134 | } 135 | ``` 136 | 137 | The default diff, running `vimdiff file1 file2` produced would then look like this: 138 | ![default diff](https://raw.githubusercontent.com/chrisbra/vim-diff-enhanced/master/default_diff.png) 139 | 140 | However, now you can simply do `:set diffopt+=algorithm:patience` the 141 | diff would change to the following: 142 | ![patience diff](https://raw.githubusercontent.com/chrisbra/vim-diff-enhanced/master/histogram_diff.png) 143 | 144 | Pretty nice, isn't it? 145 | Here is a screencast: 146 | 147 | 148 | ### The indent heuristics 149 | 150 | Here is an example where the indent heuristics might come handy. Say you have the following file: 151 | 152 | ```ruby 153 | 154 | def finalize(values) 155 | 156 | values.each do |v| 157 | v.finalize 158 | end 159 | ``` 160 | 161 | And later the file has been changed to the following: 162 | 163 | ```ruby 164 | 165 | def finalize(values) 166 | 167 | values.each do |v| 168 | v.prepare 169 | end 170 | 171 | values.each do |v| 172 | v.finalize 173 | end 174 | ``` 175 | 176 | The default diff, running `vimdiff file1.rb file2.rb` produced would then look like this: 177 | ![default ruby diff](https://i.imgur.com/nCUkO7D.png) 178 | Now, type `:set diffopt+=indent-heuristic` and see how the diff would change to the following: 179 | ![indent-heuristic diff](https://i.imgur.com/HQ4bDHC.png) 180 | 181 | Now one can clearly see, what part has been added. 182 | 183 | That is pretty neat. 184 | 185 | This is also available as Screencast: 186 | 187 | 188 | ## What is next 189 | 190 | Having included the xdiff library this does not mean, improving the diff 191 | mode stops. There have been additional patches that fixed small bugs as 192 | well as improved the diff mode further. For example the DiffUpdate 193 | autocommand has been included in [Patch 8.1.397][10] which allows to run 194 | commands once the diff mode has been updated. 195 | 196 | In addition, there are already requests to provide a VimScript API for 197 | creating diffs or update the diff more often. It should also be possible 198 | to create better inline diffs. 199 | 200 | That hasn't been done yet, but I am sure some of those improvements will 201 | be developed in the future. 202 | 203 | 204 | [0]: https://en.wikipedia.org/wiki/Diff#Edit_script 205 | [1]: https://github.com/chrisbra/vim-diff-enhanced 206 | [2]: https://github.com/vim/vim/pull/2732 207 | [3]: http://www.xmailserver.org/xdiff-lib.html 208 | [4]: https://bramcohen.livejournal.com/73318.html 209 | [5]: https://stackoverflow.com/a/32367597/789222 210 | [6]: https://hackernoon.com/whats-new-in-git-2-11-64860aea6c4f#892c 211 | [7]: https://github.com/vim/vim/commit/e828b7621cf9065a3582be0c4dd1e0e846e335bf 212 | [8]: https://en.wikipedia.org/wiki/Diff#Unified_format 213 | [9]: https://en.wikipedia.org/wiki/Longest_common_subsequence_problem 214 | [10]: https://github.com/vim/vim/releases/tag/v8.1.0397 215 | 216 | * Name: Christian Brabandt 217 | * Email: 218 | * GitHub: chrisbra 219 | * Picture: https://i.imgur.com/HjSMLD9.png 220 | -------------------------------------------------------------------------------- /justone-one-vim.md: -------------------------------------------------------------------------------- 1 | # One Vim: managing multiple files with ease 2 | 3 | Vim is a powerful editor. But like many powerful things, sometimes it can be used in less-than-powerful ways. One of those weaker ways is to only edit one file at a time, bouncing back and forth between editor and the shell or file explorer. Vim can easily handle multiple files at the same time, and this post will show you some of the built-in commands that are available to help out. 4 | 5 | Editing multiple files simultaneously has many benefits. 6 | 7 | First, it dispenses with the overhead of startup time and finding the right place in the file. This usually isn't a lot of time, but it can add up as you flip between different files. 8 | 9 | The second benefit has to do with the locality and impact of your edits. When you start a task that involves editing a single file, it's likely that you will need to edit other files as well. They could be a reference, or a place to copy information from. When working in software, it's common to move lines of code between files when doing a refactor. Non software tasks benefit from this as well. For instance, when editing a file in `/etc/cron.d/`, it might be useful to check a neighboring file to see how its cron expression is constructed. The point is that rarely do files truly stand alone. 10 | 11 | A third benefit is that once you have the three or four (or twenty) files open, you can leverage Vim's built-in session saving ability to take that entire set up and save it for later. This is particularly useful when switching between tasks in a software project. You can save the editing session for one branch while that code is in review, and start an entirely different task with its own constellation of files. Then, when you need to make changes in response to the review, you can load up the previous session and get right back to where you were. 12 | 13 | There are other benefits as you progress further down the road, but these are a good place to start. 14 | 15 | # Structure 16 | 17 | This post is written so that you can stop at any time. You do not need to read and implement the entire content in order to receive any benefit. On the contrary, the best way to incorporate these (or any tutorial/learning tasks) is to take an incremental approach. Choose a small amount of information or technique that you can integrate into your workflow, and then use it for a while. Once it has become integrated, move on to the next section and integrate again. 18 | 19 | Don't be frustrated when this information feels like it slows you down. Remember the phrase "Slow is smooth, and smooth is fast". Adding a second file to your current Vim session will be slower than quitting your existing session, but overall your productivity will increase. Take your time and do the motions correctly. As your muscle memory grows, you will be able to add more files so quickly that you will outpace any former technique. 20 | 21 | # Preparation 22 | 23 | To give you a known playground, I've prepared a git repository, that you can get one of two ways: 24 | 25 | 1. Clone it: `git clone https://github.com/justone/2018-one-vim-playground.git`. 26 | 2. Download it: `curl -L https://github.com/justone/2018-one-vim-playground/archive/master.tar.gz | tar -xzvf -` 27 | 28 | Either way, you should have a directory in which to try out the following. 29 | 30 | # From one to more 31 | 32 | The first step is opening just one more file. First, open a single file in Vim (`vim file1`), and then run the following: 33 | 34 | 1. Open a second file in a horizontal split: `:split file2`. 35 | 2. Open a third file in a vertical split: `:vsplit file3`. 36 | 3. Save our state with `:mksession! session.vim` 37 | 38 | The `mksession!` command saved the state of your windows (and many other things) into a file, so you can always return to the layout you have now by quitting vim and running `vim -S session.vim` (or running `:bufdo bdel | source session.vim` to reuse your current vim instance). 39 | 40 | Your Vim session should now look like this: 41 | 42 | ![split session](justone-one-vim-session.png) 43 | 44 | Now that we have multiple files open, the next task is navigation. Vim helpfully supplies the following: 45 | 46 | ``` 47 | CTRL-W h - move to the left 48 | CTRL-W l - move to the right 49 | CTRL-W j - move below 50 | CTRL-W k - move above 51 | ``` 52 | 53 | These map directly to the standard movement keys, and have similar effect. Experiment by moving around into each buffer. 54 | 55 | Next up is moving windows around. The most common way to move windows around is with one of these commands: 56 | 57 | ``` 58 | C-w H - move all the way to the left, full height 59 | C-w L - move all the way to the right, full height 60 | C-w J - move all the way down, full width 61 | C-w K - move all the way up, full width 62 | ``` 63 | 64 | Try moving each command in each buffer to get used to what it does to the layout. Try to move buffers around and then move them back to their original configuration. At any point in time, you can reset to get back to the original layout using the commands from the previous section. 65 | 66 | Further reading in the Vim documentation: 67 | * [:help opening-window](http://vimdoc.sourceforge.net/htmldoc/windows.html#opening-window) 68 | * [:help window-move-cursor](http://vimdoc.sourceforge.net/htmldoc/windows.html#window-move-cursor) 69 | * [:help window-moving](http://vimdoc.sourceforge.net/htmldoc/windows.html#window-moving) 70 | 71 | # Tabs 72 | 73 | Tabs in Vim are a great way to group together associated buffers. For instance, one tab for database code, one for the web routes, and another for the frontend code. 74 | 75 | Take the three window session from above (`vim -S session.vim`) and run: 76 | 77 | 1. Open a fourth file in a new tab: `:tabe file4` 78 | 2. Go back to the previous tab: `gT` 79 | 3. Push a file to a new tab: `CTRL-w T` 80 | 81 | # Editing neighbors 82 | 83 | Many times it is useful to open files that are nearby the current file, since many times like files are grouped together in the same directory. 84 | 85 | The best way to accomplish this is to use a set of mappings from [this episode of vimcasts](http://vimcasts.org/episodes/the-edit-command/). To load these up, load the `nearby.vim` file into your session by running `:source nearby.vim`. 86 | 87 | Then, in the root of the playground repository, run `vim` and: 88 | 89 | 1. Open a fifth file in a new tab: `:edit deep/file/path/file5` 90 | 2. Bring up an edit prompt for nearby files by pressing `,es`. 91 | 3. Enter `file6` and the new file will be opened in a horizontal split. 92 | 93 | Experiment with the other "nearby" mappings by using `,ew`, `,ev`, and `,et` to open files. 94 | 95 | The nearby mappings utilize a few advanced Vim features: 96 | * [:help expand()](http://vimdoc.sourceforge.net/htmldoc/eval.html#expand()) 97 | * [:help c_CTRL-R_=](http://vimdoc.sourceforge.net/htmldoc/cmdline.html#c_CTRL-R_=) 98 | 99 | # Cleanup 100 | 101 | Up until this point, everything has been about adding more buffers to your session. If you simply run `:q` in any open buffer, only the window is removed. The buffer remains in memory. Most of the time, this is ok, but for very long running sessions with many (or large) files, buffer management is key. The best way to remove a buffer from your session is via the `:bd` command. 102 | 103 | Take the session from above (`vim -S session.vim`) and run: 104 | 105 | 1. List the loaded buffers: `:ls` 106 | 2. Delete a buffer: `:bd file1` or `:bd` 107 | 3. List buffers again to verify that the buffer is gone: `:ls` 108 | 109 | # Helpful plugins 110 | 111 | I've come across many plugins over the years that have helped me in managing my buffers. Here are a few: 112 | 113 | * [bufexplorer](https://github.com/jlanzarotta/bufexplorer) - This will help with listing, opening, and removing buffers. 114 | * [bufkill](https://github.com/qpkorr/vim-bufkill) - This provides a `:BD` command that will do the same thing `:bd` does, but retain your window layout. 115 | * [ctrlp](https://github.com/kien/ctrlp.vim) - This helps find files using fuzzy matching. It's most-recently-used functionality is particularly useful. 116 | * [obsession](https://github.com/tpope/vim-obsession) - This extends the built-in session handling to continually update the session file as you edit. 117 | 118 | # Conclusion 119 | 120 | I hope this guide has been helpful. There are many other plugins and mappings that can help, but getting the built-in basics down will get you quite far. 121 | 122 | Enjoy. 123 | -------------------------------------------------------------------------------- /justinmk-exmode.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | date: 2018-12-01 4 | last_modified_at: 2018-12-01 5 | title: Vim's social life 6 | author: 7 | name: Justin M. Keyes 8 | email: justinkz@gmail.com 9 | github: justinmk 10 | twitter: justinmk 11 | site: http://justinmk.github.io/ 12 | --- 13 | 14 | Vim is a shell command, and its fast startup supports that use-case: shell 15 | tasks, whether ad-hoc (interactive) or orchestrated (pipeline, script), are 16 | cheap and thus frequent. 17 | 18 | Yet Vim's startup story is relatively unpolished. Shell tools are expected to 19 | consume standard input ("stdin") and emit to standard output ("stdout")—but 20 | Vim supports this awkwardly, at best. The endeavor is never mentioned in Vim 21 | tutorials (including the "Unix as IDE" 22 | [hymnals](https://news.ycombinator.com/item?id=12653028)), and puzzled out of 23 | Vim's documentation only by careful inspection. 24 | 25 | Vim is positioned as a script host (VimL, `if_python`, …) , but not as 26 | a participant. Yet Vim is a terminal tool, and terminal users expect their 27 | tools to compose. Like this: 28 | 29 | # Does not work! 30 | printf 'a\nb\nc\nb\n' | vim +'g/b/norm gUUixx' +2 +'norm yy2p' | tr x z 31 | 32 | Why doesn't that work? What can we do instead? 33 | 34 | 35 | Let's talk about -s-ex 36 | ---------------------- 37 | 38 | The goal is to penetrate Vim with input, manipulate it non-interactively, and 39 | produce output consumable by other shell tools. 40 | 41 | Sending text input to Vim requires the explicit `-` file. 42 | 43 | echo foo | vim - 44 | 45 | Working non-interactively is less obvious. Vim's 46 | [testsuite](https://github.com/vim/vim/tree/e751a5f531c1ceb58dacc7c280fdaae0df2c71c7/src/testdir) 47 | does something like this: 48 | 49 | vim -es -u NONE -U NONE -i NONE --noplugin -c ... -c "qall!" 50 | 51 | But what is `-es`? Not merely the combination of `-e` and `-s`, it is a special 52 | "silent mode" described at `:help -s-ex`: 53 | 54 | Switches off most prompts. 55 | ... 56 | The output of these commands is displayed (to stdout): 57 | :print 58 | ... 59 | Initializations are skipped. 60 | 61 | So `-es` does not draw the UI, and we can emit text to stdout using `:print`. 62 | 63 | echo foo | vim - -es +'%p' +'qa!' 64 | Vim: Reading from stdin... 65 | foo 66 | 67 | `:%p` prints the entire buffer and `:qa!` ensures that Vim quits. In Vim 68 | version 8, that "Vim: Reading from stdin..." message can be avoided with 69 | `--not-a-term`. 70 | 71 | Note that `-es` and `-se` are not equivalent, the Vim 72 | [parser](https://github.com/vim/vim/blob/d47d52232bf21036c5c89081458be7eaf2630d24/src/main.c#L2156) 73 | quite literally expects `-e` to precede `-s`: 74 | 75 | echo foo | vim - -se +'%p' +'qa!' 76 | Garbage after option argument: "-se" 77 | 78 | A similar order-sensitivity befalls the `-` file argument: `vim - -es` behaves 79 | differently than `vim -es -`! The former consumes stdin as text, while the 80 | latter activates stdin as Ex commands. 81 | 82 | If you run into trouble, use `-V1` to reveal why `-e` isn't working: 83 | 84 | printf 'foo\n' | vim -es 85 | # No output. Non-zero error code. 86 | echo $? 87 | 1 88 | 89 | printf 'foo\n' | vim -es -V1 90 | Entering Ex mode. Type "visual" to go to Normal mode. 91 | :foo 92 | E492: Not an editor command: foo 93 | 94 | So now we can light up our tinsel: 95 | 96 | printf 'a\nb\nc\nb\n' | vim - -es --not-a-term +'g/b/norm gUUixx' +2 +'norm yy2p' '+%p' '+qa!' | tr x z 97 | a 98 | zzB 99 | zzB 100 | zzB 101 | c 102 | zzB 103 | 104 | Yay! We did it. Wait, you're going home already...? 105 | 106 | 107 | Ugly sweater party 108 | ------------------ 109 | 110 | Apparently Vim thought this was an ugly sweater party. Vim's sweater has `-` 111 | and `Reading from stdin...` and forty-four `--help` options. Let's learn more 112 | about Vim before seating it next to Grandpa vi. 113 | 114 | Input at startup can take these forms: 115 | 116 | - user ("keyboard") input 117 | - Ex commands 118 | - text 119 | 120 | By default, even in non-interactive mode (`-es`) Vim treats input as 121 | "commands". That's a tradition from Grandpa vi. Note that "commands" in vi 122 | parlance means general _user-input_ (starting from Normal-mode). 123 | 124 | With `-e` (and `-es`) Vim treats input as Ex commands: those entered at the `:` 125 | prompt, or "statements" in Vim script. 126 | 127 | printf "put ='foo'\n%%s/o/X\n%%print\n" | vim -es 128 | 129 | fXo 130 | 131 | Finally the `-` file tells Vim to slurp input as plain text into a buffer. Then 132 | commands can be given with `-c` or `--cmd`. 133 | 134 | There's another thing I want to announce at the dinner table: if you specify 135 | the `-` file, then other _file arguments_ are not allowed. `:help vim-arguments` 136 | characterizes these independent "editing ways": 137 | 138 | > Exactly one out of the following five items may be used to choose how to start editing: 139 | 140 | Should you try to invoke multiple "editing ways", Vim will leave the table. For 141 | example, asking Vim to read both `-` and `a.txt` is asking too much: 142 | 143 | echo foo | vim - -es --not-a-term '+%p' a.txt 144 | Too many edit arguments: "a.txt" 145 | 146 | 147 | Santa POS is coming to town 148 | --------------------------- 149 | 150 | Gathering from the 151 | [POSIX vi specification](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html) 152 | we find these directives regarding non-interactive ("not a terminal") cases: 153 | 154 | > Historically, vi exited immediately if the standard input was **not a terminal**. ... 155 | > 156 | > If standard input is **not a terminal** device, the results are undefined. The 157 | > standard input consists of a series of commands and input text, ... 158 | > 159 | > If a read from the standard input returns an error, or if the editor detects 160 | > an end-of-file condition from the standard input, it shall be equivalent to 161 | > a SIGHUP 162 | 163 | - Input is always interpreted as user input, even if non-interactive. 164 | - When stdout is not a terminal, Vim may behave as it likes ("undefined"). 165 | - EOF (that is, closed input stream) means quit. 166 | 167 | POSIX conspicuously omits the `-e` and `-s` startup options (and `-es`, 168 | which is not merely the composition of the two!). 169 | [Traditional vi](http://ex-vi.sourceforge.net/vi.html) lacks `-e`, but 170 | [nvi](https://www.freebsd.org/cgi/man.cgi?query=nvi) implements both and calls 171 | out POSIX's incongruence with "historical ex/vi practice". 172 | 173 | We've uncovered the origin of Vim's eagerness to consume stdin as something 174 | live (commands) instead of something inert (text): 'twas always done that way. 175 | 176 | echo foo | vim 177 | Vim: Warning: Input is not from a terminal 178 | Vim: Error reading input, exiting... 179 | Vim: Finished. 180 | 181 | Vim must warn about stdin-as-commands because (1) it's potentially destructive 182 | and (2) it's almost always accidental (does anyone actually use this feature?). 183 | 184 | Vim exits after stdin EOF, as prescribed by POSIX. (Party trick: convince it to 185 | keep running by sending the input to `-s`: `vim -s <(echo ifoo)`.) 186 | 187 | POSIX does not mention `-E`, a variant of `-e` ignored by Vim's own testsuite. 188 | `-e` invokes 189 | [getexmodeline](https://github.com/vim/vim/blob/d47d52232bf21036c5c89081458be7eaf2630d24/src/ex_getln.c#L2731) 190 | whereas `-E` invokes 191 | [getexline](https://github.com/vim/vim/blob/d47d52232bf21036c5c89081458be7eaf2630d24/src/ex_getln.c#L2713). 192 | The distinction is not useful, and in Nvim they both invoke `getexline`. 193 | 194 | 195 | ## Under the tree: Neovim 196 | 197 | [Neovim](https://neovim.io/) version 0.3.1 features some 198 | [improvements](https://github.com/neovim/neovim/pull/7679) to the workflow 199 | described above. The [documentation](https://neovim.io/doc/user/starting.html#-es) 200 | and manpage were rewritten. 201 | 202 | Nvim now treats non-terminal stdin as plain text _by default_ (the explicit `-` 203 | file is not needed): 204 | 205 | echo foo | nvim 206 | 207 | That means Nvim never pauses for two seconds to display a warning (because 208 | stdin is not executed). Nvim also allows multiple "editing ways": 209 | 210 | echo foo | nvim file1.txt file2.txt 211 | 212 | It also works with `-Es` (but not `-es`), and it exits automatically (no 213 | `+'qa!'` needed): 214 | 215 | echo foo | nvim -Es +"%p" 216 | 217 | If you ever _want_ to execute stdin-as-commands, use `-s -`: 218 | 219 | echo ifoo | nvim -s - 220 | 221 | With these improvements it's now possible to use `nvim -es` as one might use 222 | `python -c` or `perl -e`. For example, I use it in my `.bashrc` to configure 223 | the shell depending on the Nvim version: 224 | 225 | if nvim -es +'if has("nvim-0.3.2")|0cq|else|1cq|endif' ; then 226 | ... 227 | fi 228 | 229 | 230 | ## Eggnog 231 | 232 | The mechanisms and ergonomics for delivering data to Vim at invocation-time are 233 | essentially unchanged from vi—owing, yes, to deference to 234 | [POSIX vi](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html) 235 | and our old friend backwards compatibility—but perhaps primarly to intertia. 236 | 237 | It turns out that very few people actually care about the traditional behavior: 238 | the precise behavior of `-es`, for example, was broken in Nvim for years but no 239 | one complained. And Vim's own codebase (including testsuite) does not use `-E`. 240 | If no one uses a feature, it might be ok to change it. 241 | 242 | Merry Textmas! This holiday, when you're with your loved ones, think of Vim. 243 | -------------------------------------------------------------------------------- /swalladge-vim-and-git.md: -------------------------------------------------------------------------------- 1 | # Vim ∪ Git 2 | 3 | 4 | Vim and Git are both highly complex, configurable developer tools. Developers 5 | who use Vim are likely to also need to use Git frequently. This article attempts 6 | to explore how these two tools can interact in many ways. 7 | 8 | First off, I'm not going to prescribe any particular workflows, or argue for or 9 | against a particular method. There are just too many options and you are 10 | encouraged to develop your own workflow. 11 | 12 | Vim and Git are two separate tools and can of course be used that way. However, 13 | it can be useful, time-saving, and convenient to integrate the two. 14 | 15 | 16 | ## The Git perspective 17 | 18 | From the perspective of Git there are several opportunities to utilize Vim: 19 | 20 | - editing commit/tag messages 21 | - resolving merge conflicts (yay!) 22 | - interactive rebasing 23 | 24 | ### Editing commit messages 25 | 26 | If you have the `EDITOR` environment variable already set to Vim, Git should 27 | automatically use Vim to edit messages. If environment variables aren't to be 28 | relied on, the `core.editor` git config can be set: 29 | 30 | ``` 31 | [core] 32 | editor = "vim" 33 | ``` 34 | 35 | When editing a commit message in Vim and wish to abort, you can use the `:cq` 36 | command to exit with a non-zero status. This will cause Git to abort the commit, 37 | even if you had begun writing a message. 38 | 39 | Additionally, be sure to use `git commit -v` or set `commit.verbose = true` in 40 | your gitconfig file to have the full patch shown in Vim when editing the commit 41 | message. 42 | 43 | 44 | ### Resolving merge conflicts 45 | 46 | Before resorting to external diff tools to merge, consider using Vim's excellent 47 | diff mode to help! Vim can be configured to be used as Git's `mergetool`, so 48 | it can be automatically launched with the correct configuration and files ready 49 | to perform the merge when you run `git mergetool`. There are two main methods 50 | which I've used: vanilla vim (or neovim) in diffmode, and vim-fugitive's 51 | `Gdiff`. Example configuration below (my setup, neovim): 52 | 53 | ``` 54 | [merge] 55 | tool = nvimdiff4 56 | # if not using a tool name with builtin support, must supply mergetool cmd 57 | # as below 58 | 59 | [mergetool "nvimdiff4"] 60 | cmd = nvim -d $LOCAL $BASE $REMOTE $MERGED -c '$wincmd w' -c 'wincmd J' 61 | 62 | [mergetool "nfugitive"] 63 | cmd = nvim -f -c "Gdiff" "$MERGED" 64 | ``` 65 | 66 | The first method requires no plugins. It is simply Vim (Neovim) launched in 67 | diffmode with the 4 files Git provides in environment variables. The wincmds 68 | executed move the working file to full-width along the lower half of the window. 69 | 70 | The second method uses the vim-fugitive plugin to set up the layout. It still 71 | uses Vim's diffmode, but only shows 3 files (local, remote, and index). I think 72 | it provides some other niceties too (TODO: I've forgotten what because i usually 73 | use vanilla). 74 | 75 | To help with choosing which buffer to `do` or `dp` from/to, I like to include 76 | the buffer number in the status line. The item `%n` will do that. See `:h 'statusline'` for more. 77 | 78 | 79 | Likewise, git `difftool` can be set to use Vim too: 80 | 81 | ``` 82 | [diff] 83 | tool = nvimdiff2 84 | 85 | [difftool "nvimdiff2"] 86 | cmd = nvim -d $LOCAL $REMOTE 87 | ``` 88 | 89 | 90 | ### Interactive rebasing 91 | 92 | TODO 93 | 94 | 95 | ## The Vim perspective 96 | 97 | Ok, so the flip side: how can Git be integrated into Vim? 98 | 99 | Note: I personally use Neovim, but practically everything will work the same in 100 | Vim. Assume "Vim" refers to both Neovim and Vim unless a distinction is 101 | explicitly made. 102 | 103 | ### Vanilla 104 | 105 | Let's begin with useful things you can do with no plugins required. 106 | 107 | #### `:!git` 108 | 109 | One way is to shell out directly to Git to run a command. Be aware that 110 | interactive stdin is impossible currently in Neovim so any commands that prompt 111 | for input will fail. 112 | 113 | ``` 114 | :!git stash 115 | ``` 116 | 117 | #### `:terminal` 118 | 119 | Neovim, and more recently Vim, have embedded terminals that can be launched with 120 | the `:terminal` command. This enables having a complete shell (and Git command 121 | line tools) within Vim. Very useful if using Gvim, or don't want to suspend/quit 122 | Vim to get back to a shell. 123 | 124 | #### 125 | 126 | TODO others 127 | 128 | 129 | ### Configuration 130 | 131 | `autoread` and `checktime` are useful if you leave Vim open while checking out 132 | different commits and don't want to deal with Vim complaining that a file has 133 | changed on disk. 134 | 135 | ``` 136 | set autoread 137 | set checktime 138 | ``` 139 | 140 | 141 | 142 | ### Plugins 143 | 144 | 145 | #### Vim-Fugitive 146 | 147 | **The** Git plugin. There are many articles about Fugitive so I won't go into it 148 | in much detail. Basically, it provides many Vim commands for working with the 149 | repo, a diffing setup for staging changes, interactive status window, etc. I 150 | recommend the 151 | 152 | 153 | #### GitGutter/Signify 154 | 155 | [GitGutter][gitgutter] and [Signify][signify] are both plugins whose job is to 156 | show diff information in the sign column. GitGutter is tailored for Git, and 157 | provides some useful extras, such as undoing hunks. Signify has historically 158 | been faster, but recent refactoring efforts in GitGutter has levelled the 159 | playing field. I'd recommend GitGutter for its extra features if you only use 160 | Git, or Signify if you also use other VCSs. 161 | 162 | 163 | #### GV 164 | 165 | [GV][gv] is an excellent Git browser for Vim. TODO: more info 166 | 167 | 168 | #### Vimagit 169 | 170 | [Vimagit][vimagit] is an attempt to bring some of Emacs' famous [Magit][magit] 171 | to Vim. It provides a single buffer where you can launch Git commands, stage 172 | hunks/files, and commit. Its interface is very nice (imho), but unfortunately 173 | it suffers from low performance. Worth checking out but is perhaps not mature 174 | enough to support efficient workflows yet. 175 | 176 | 177 | #### Committia 178 | 179 | [Committia][committia] Committia is a simple plugin to help make editing commit 180 | messages smoother. 181 | 182 | This is separate to other plugins like fugitive. If you are already in vim, This 183 | is separate to other plugins like fugitive. If you are already in vim, then 184 | fugitive will give a more advanced then fugitive will give a more advanced 185 | commit workflow. However, committia is useful when you are working on the 186 | command line and just want to commit with `git commit ...`. In this case, your 187 | configured editor will launch with the `COMMIT_EDITMSG` file to edit the commit 188 | message. If you `git commit -v ...`, then you already get the diff view, but 189 | committia adds some niceties: 190 | TODO: clean up this section 191 | 192 | 193 | ### Tig 194 | 195 | [Tig][tig] is an awesome cli Git browser. [tig-explorer][tig-explorer] is a neat 196 | plugin that opens Tig in a Vim terminal buffer. 197 | 198 | TODO more explanation 199 | 200 | 201 | ### Branch manager 202 | 203 | I recently discovered some cool plugins for managing Git branches (and related 204 | tasks). [Twiggy][twiggy] and [Merginal][merginal] 205 | 206 | 207 | 208 | 209 | ## Github specific goodies 210 | 211 | Github is one of the most popular Git repository hosting services, and it 212 | follows that there are some helpful Vim plugins for further Git repository 213 | integration if hosted on Github. 214 | 215 | [rhubarb][rhubarb] is a Vim plugin by tpope that enables opening a Git object on 216 | Github in the browser. It's very convenient when you are working in Vim on a 217 | local repository and wish to refer someone to a particular line or file. 218 | 219 | To open the current file: 220 | 221 | ``` 222 | :Gbrowse 223 | ``` 224 | 225 | To link to a range of lines, supply a range for the function. For example, visual 226 | select some lines and run: 227 | 228 | ``` 229 | :'<,'>Gbrowse 230 | ``` 231 | 232 | Finally, any git object can be given as an argument to the function: 233 | 234 | ``` 235 | :Gbrowse a75830f 236 | ``` 237 | 238 | Rhubarb also contains an autocompleter for Github issues when editing commit/tag 239 | messages. 240 | 241 | 242 | 243 | ## My workflow 244 | 245 | So what about my workflow, you may ask. Well I cnsider my workflow to be very 246 | scattered. I use whichever method of performing a Git action as happesn to be 247 | most convenient at the time. Generally I use Tmux and have a pane/window open 248 | that I can switch to and run Git commands. I find GitGutter great in vim to see 249 | which lines have been changed and not staged. I do have a lot of Git plugins 250 | installed, and attempting to transition to using them more often. When I say I 251 | use the most convenient command, sometimes the most convenient is a command that 252 | is slower but less taxing on the memory. For example, often I will open a new 253 | tmux window and use `git add --patch` from the cli rather than Fugitive's 254 | `:Gdiff` simply because I'm more familiar with the former. 255 | 256 | Something I can't stress enough is the importance of learning a tool and forcing 257 | yourself to use that tool everywhere possible until it reaches muscle memory if 258 | it's to actually be an improvement to your workflow. I've found that it's no 259 | point having convenient plugins installed if I end up forgetting about them and 260 | using the vanilla methods instead. 261 | 262 | 263 | ## Conclusion 264 | 265 | TODO 266 | 267 | --- 268 | 269 | Contact details: 270 | 271 | - name: Samuel Walladge 272 | - email: samuel@swalladge.id.au 273 | - GitHub: swalladge 274 | - Twitter: @srwalladge 275 | - irc (freenode): swalladge 276 | - website: swalladge.id.au 277 | 278 | 279 | [committia]: https://github.com/rhysd/committia.vim 280 | [gitgutter]: https://github.com/airblade/vim-gitgutter 281 | [gv]: https://github.com/junegunn/gv.vim 282 | [magit]: https://magit.vc/ 283 | [merginal]: https://github.com/idanarye/vim-merginal 284 | [rhubarb]: https://github.com/tpop/vim-rhubarb 285 | [signify]: https://github.com/mhinz/vim-signify 286 | [tig-exploror]: https://github.com/iberianpig/tig-explorer.vim 287 | [tig]: https://github.com/jonas/tig 288 | [twiggy]: https://github.com/sodapopcan/vim-twiggy 289 | [vimagit]: https://github.com/jreybert/vimagit 290 | -------------------------------------------------------------------------------- /george-b-formatting-lists-with-vim.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Formatting lists with Vim" 3 | publishDate: 2018-12-06 4 | date: 2018-12-06 5 | draft: false 6 | description: "Understanding how Vim understands lists" 7 | slug: "formatting-lists-with-vim" 8 | author: 9 | name: "George Brown" 10 | email: "321.george@gmail.com" 11 | github: "https://github.com/george-b" 12 | --- 13 | 14 | ## Use of formatoptions and the fo-table 15 | 16 | We tell Vim how formatting should be carried out by setting `formatoptions`. This is set to a string of characters who's meaning is described in `:help fo-table`. Rather than repeating what's in the help it's sufficient to say the default is `formatoptions=tcq`, which may lead users to think that invoking Vim's formatting with `gw` from normal mode simply wraps text at whatever `textwidth` is set to. But we can actually do a lot more. 17 | 18 | The focus of this article is how we can alter the formatting behaviour when we have the `n` option present in `formatoptions`. This allows formatting operations to recognize lists, thereby avoiding joining distinct items as if they were a single paragraph. 19 | 20 | ## Understanding formatlistpat 21 | 22 | Succinctly described in the documentation. 23 | 24 | ``` 25 | The default recognizes a number, followed by an optional punctuation 26 | character and white space. 27 | ``` 28 | 29 | This translates as the regex string of `^\s*\d\+[\]:.)}\t ]\s*`. 30 | 31 | ``` 32 | ^ " From the start of line 33 | \s* " Zero or more whitespace characters 34 | \d\+ " One or more digits 35 | [ " Start a class 36 | \]:.)}\t " A closing bracket, colon, full stop, closing parenthesis, closing curly brace, tab, or space 37 | ] " End a class 38 | \s* " Zero or more whitespace characters 39 | ``` 40 | 41 | Here is an example of a list that can be formatted when passing over it when executing `gwip`. 42 | 43 | ``` 44 | 1. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 45 | 2. Donec feugiat a quam id faucibus. 46 | 3. Sed maximus efficitur commodo. 47 | 4. Sed et euismod ex. 48 | 5. Sed at libero placerat, pretium sem sit amet, mattis dolor. 49 | ``` 50 | 51 | However something like the following won't be recognized. 52 | 53 | ``` 54 | a. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 55 | b. Donec feugiat a quam id faucibus. 56 | c. Sed maximus efficitur commodo. 57 | d. Sed et euismod ex. 58 | e. Sed at libero placerat, pretium sem sit amet, mattis dolor. 59 | ``` 60 | 61 | ## Expanding formatlistpat 62 | 63 | An item of particular importance when setting `formatlistpat`, though not exclusively so, is that we will need to enter more backslashes than you may expect. This is because when setting string options in Vim backslashes need to be escaped (covered in `:help option-backslash`). As an example let's see how we would set `formatlistpat` to its default value of `^\s*\d\+[\]:.)}\t ]\s*`. 64 | 65 | ``` 66 | set formatlistpat=^\\s*\\d\\+[\\]:.)}\\t\ ]\\s* 67 | ``` 68 | 69 | Now to use the example of the alphabetical listing used earlier how may we change `formatlistpat` to recognise this as a list? Changing the matching of a digit to a group matching a digit or an alphabetic character seems like a simple solution. We could so so with the following (note the three backslashes that precede the pipe). 70 | 71 | ``` 72 | set formatlistpat=^\\s*\\(\\d\\\|\\a\\)\\+[\\]:.)}\\t\ ]\\s* 73 | ``` 74 | 75 | However this will end up matching too many structures of text. The punctuation after digits or alphabetic characters also includes a space. So this will match prose-like paragraphs. We could remove the space in the class like so to avoid this like so. 76 | 77 | ``` 78 | set formatlistpat=^\\s*\\(\\d\\\|\\a\\)\\+[\\]:.)}\\t\]\\s* 79 | ``` 80 | 81 | Though as one may expect by removing something from the default setting we've broken the matching of lists that are prefixed by numbers alone. Rather than continuing example by example I'll stop here, an example of what I consider to be comprehensive for my personal needs will follow in the final section. 82 | 83 | ## Some filetypes will set formatlistpat 84 | 85 | As a note Vim will set `formatlistpat` for some filetpyes, at the time of 86 | writing this consists of the following. 87 | 88 | ``` 89 | runtime/ftplugin/rmd.vim:: setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^\\s*[-*+]\\s\\+ 90 | runtime/ftplugin/rrst.vim: setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^\\s*[-*+]\\s\\+ 91 | runtime/ftplugin/markdown.vim: setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^[-*+]\\s\\+\\\|^\\[^\\ze[^\\]]\\+\\]: 92 | ``` 93 | 94 | This is just something to bear in mind if you find `formatlistpat` set to something unexpected or if you had been wondering how formatting of lists in your markdown files had "just worked" for example. 95 | 96 | # Other settings that can interfere with formatlistpat 97 | 98 | Vim has a notion of comments which can lead to formatting not working as you may expect. For example if we were to open a file named "list.txt" with the following content. 99 | 100 | ``` 101 | * Lorem ipsum dolor sit amet, consectetur adipiscing elit. 102 | * Donec feugiat a quam id faucibus. 103 | * Sed maximus efficitur commodo. 104 | * Sed et euismod ex. 105 | * Sed at libero placerat, pretium sem sit amet, mattis dolor. 106 | ``` 107 | 108 | Executing `gwip` does *not* join the lines. As we have already covered the default value for `formatlistpat` expects list items to at least be denoted with a digit, so what's happening here? From the previous section about filetypes it wouldn't seem that `formatlistpat` is being modified for this example. We can check this and see that sure enough it's the default value. 109 | 110 | ``` 111 | :set formatlistpat? 112 | formatlistpat=^\s*\d\+[\]:.)}\t ]\s* 113 | ``` 114 | 115 | So why do we have this behaviour? Is Vim recognising this as a list by some other means? Yes, sort of. Vim's understanding of a comment also extends to lists and is not defined by `formatlistpat`. Specifically in this example of a file with the "text" `filetype` we have the following. 116 | 117 | ``` 118 | :verbose set comments? 119 | comments=fb:-,fb:* 120 | Last set from /usr/local/Cellar/vim/8.1.0450/share/vim/vim81/ftplugin/text.vim line 16 121 | ``` 122 | 123 | The various setting for `comments` is of course covered in Vim's help (see `:help format-comments`) but let's breakdown the abovesees 124 | 125 | ``` 126 | f " Flag denoting only the first line has this string 127 | b " Flag denoting whitespace is required after the string 128 | : " Delimiter denoting the end of flags 129 | - " The string literal - 130 | , " Delimieter denoting ending of a setting 131 | f " Flag denoting only the first line has this string 132 | b " Flag denoting whitespace is required after the string 133 | : " Delimiter denoting the end of flags 134 | * " String literal * 135 | ``` 136 | 137 | The usage of the `f` flag may seem slightly confusing given the prior example. But becomes clearer if we try and format a line that exceeds `textwidth`, here we are again in a buffer with the `filetype` of "text". 138 | 139 | ``` 140 | * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec feugiat a quam id faucibus. 141 | ``` 142 | 143 | Executing `gwip` will yield the following. 144 | 145 | ``` 146 | * Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec feugiat a quam 147 | id faucibus. 148 | ``` 149 | 150 | Here we see the second line is not prefixed with an asterisk, this action is in line with the description of the `f` flag in `comments`. So going back to our earlier example Vim is not re-flowing the paragraph as it treats each line as an individual list item under the notion of being a "comment". 151 | 152 | ## My personal customizations for notes 153 | 154 | I often open Vim to write up emails and replies for ticketing systems. In such instances Vim has no `filetype` set. As such in order to associate various setting with such a buffer I give it a pseudo filetype. 155 | 156 | ``` 157 | function! EmptyBuffer() 158 | if @% == "" 159 | setfiletype txt 160 | endif 161 | endfunction 162 | ``` 163 | 164 | I simply call the function above upon entering Vim with an autocmd. 165 | 166 | ``` 167 | autocmd vimrc BufEnter * call EmptyBuffer() 168 | ``` 169 | 170 | I also set this pseudo filetype for any "blank" buffers I pull up, here the autocmd is making use of `setfiletpye` to only set the fileftype if it has not otherwise been set. 171 | 172 | ``` 173 | autocmd vimrc BufRead,BufNewFile * setfiletype txt 174 | ``` 175 | 176 | I then have a function which I call when the pseudo filetpye of "txt" has been set, the below is a truncated version as to only illustrate formatting settings. 177 | 178 | ``` 179 | function! PlainText() 180 | setlocal comments= 181 | endfunction 182 | 183 | autocmd vimrc FileType txt call PlainText() 184 | ``` 185 | 186 | Why am I setting `comments` to an empty value? Well as described in the help it like many other items in Vim defaults to the assumption you are working with something resembling C code. As such this can cause issues if we mathc lists that use asterisks in `formatlistpat`. 187 | 188 | As a note for the more savvy reader you may be thinking why not make this "pseudo filetype" a fully fledged filetpye with it's own files somewhere in my `runtimepath` with it's own "after" directory for this? This is simply a personal preference, I prefer a more monolithic organization of my config. 189 | 190 | And finally my `formatlistpat`. 191 | 192 | ``` 193 | set formatlistpat=^\\s* " Optional leading whitespace 194 | set formatlistpat+=[ " Start class 195 | set formatlistpat+=\\[({]\\? " | Optionally match opening punctuation 196 | set formatlistpat+=\\( " | Start group 197 | set formatlistpat+=[0-9]\\+ " | | Numbers 198 | set formatlistpat+=\\\| " | | or 199 | set formatlistpat+=[a-zA-Z]\\+ " | | Letters 200 | set formatlistpat+=\\) " | End group 201 | set formatlistpat+=[\\]:.)} " | Closing punctuation 202 | set formatlistpat+=] " End class 203 | set formatlistpat+=\\s\\+ " One or more spaces 204 | set formatlistpat+=\\\| " or 205 | set formatlistpat+=^\\s*[-–+o*•]\\s\\+ " Bullet points 206 | ``` 207 | 208 | This handles a broader range of lists. 209 | 210 | ``` 211 | 1. Typical item the default handles 212 | a. An item with an alaphabetic character and punctuation 213 | (2) An item with punctuation preceding and following it 214 | • An item consiting of leading punctuation 215 | ``` 216 | 217 | I hope this has shown how to extend Vim's `formatlistpat` and how other settings interplay with it. 218 | -------------------------------------------------------------------------------- /djmoch-tag-vims-it.md: -------------------------------------------------------------------------------- 1 | # You Should Be Using Tags In Vim 2 | 3 | > I love you; you complete me. 4 | > - Dr. Evil 5 | 6 | I first came to Vim by recommendation. I was looking for a good Python 7 | IDE (at the time I was new to the language) and one recommendation was 8 | to use Vim with a variety of plugins added on top. That Vim could do a 9 | lot of the things I thought only an IDE could do came as a bit of a 10 | shock. I spent a summer as an intern using Emacs at a Unix terminal, but 11 | didn't have enough curiosity at the time to use it any differently from 12 | `notepad.exe`. I spent that summer wishing I had automatic features for 13 | completion, indentation, and all the things that made me appreciate 14 | the IDE's I used in college. How naive I was! 15 | 16 | So how was I directed to achieve powerful programming completions in 17 | Vim? By the use of a plugin called YouCompleteMe. My experience with it 18 | was okay, at least to start with. It took a while to install and get 19 | working, but that didn't bother me at the time since I was just playing 20 | around with it at home and the stakes were low if it suddenly broke. I 21 | did notice it slowed Vim down. Like a lot. But that was mainly when it 22 | was starting up and I didn't know enough to find it frustrating. 23 | Probably the first thing that really bothered me about the plugin was 24 | that the embedded Jedi language server used more memory than Vim itself. 25 | The other recommended plugins were similarly laggy, and I eventually 26 | went in search of something better. 27 | 28 | What I found was Vim itself. 29 | 30 | Did you know that Vim has built-in facilities for completions? It works 31 | admirably well out of the box too, but with a little bit of additional 32 | setup it can be great. Let's take a look at what Vim has on offer 33 | regarding completion and see what it takes to fully leverage it. 34 | 35 | ## Completion in Vim 36 | 37 | Completion in Vim is powerful, but not necessarily straightforward. Read 38 | [`:h ins-completion`][ic] and you'll see what I mean. 39 | 40 | Completion can be done for: 41 | 42 | 1. Whole lines |i_CTRL-X_CTRL-L| 43 | 2. keywords in the current file |i_CTRL-X_CTRL-N| 44 | 3. keywords in 'dictionary' |i_CTRL-X_CTRL-K| 45 | 4. keywords in 'thesaurus', thesaurus-style |i_CTRL-X_CTRL-T| 46 | 5. keywords in the current and included files |i_CTRL-X_CTRL-I| 47 | 6. tags |i_CTRL-X_CTRL-]| 48 | 7. file names |i_CTRL-X_CTRL-F| 49 | 8. definitions or macros |i_CTRL-X_CTRL-D| 50 | 9. Vim command-line |i_CTRL-X_CTRL-V| 51 | 10. User defined completion |i_CTRL-X_CTRL-U| 52 | 11. omni completion |i_CTRL-X_CTRL-O| 53 | 12. Spelling suggestions |i_CTRL-X_s| 54 | 13. keywords in 'complete' |i_CTRL-N| |i_CTRL-P| 55 | 56 | Vim is smart enough to pull completion data from a variety of sources, 57 | but in turn expects users to know which source will provide the best 58 | answer and to invoke the correct keymap to draw the desired completions. 59 | It's not a huge hurdle in terms of a learning curve, but it's not as 60 | simple as hitting tab either. 61 | 62 | The first thing one should do when trying to learn Vim's completion 63 | system is to disable any completion plugins and learn these keymaps. 64 | Getting comfortable with them will also help you learn and remember 65 | where Vim can pull completion information from. You should also read [`:h 66 | 'completefunc'`][cf] and [`:h 'complete'`][co] for more information on 67 | user-defined completion and the `complete` option. 68 | 69 | Now that we have a cursory understanding of completion in Vim, let's 70 | take a deeper look at tags and how they figure in to completion. 71 | 72 | ## Introduction to `tags` in Vim 73 | 74 | One source of completion in Vim is tag completion, which pulls from a 75 | special file called–appropriately—a tag file. Tag files are collections 76 | of identifiers (e.g., function names) that are compiled into a single 77 | file along with references to their location in a source tree. Vim is 78 | capable a using a (properly formatted) tags file for a variety of use 79 | cases, among them navigating your source code a la Visual Studio and 80 | completion. 81 | 82 | By default Vim doesn't do anything with a tags file except read it. See 83 | [`:h 'tags'`][to] to learn how to configure where Vim looks for tags 84 | files. Vimdoc also contains a very good [introduction][ti] to tags more 85 | generally, so I won't spend any more space here introducing them. Let's 86 | move on and take a look at how we generate tags files. 87 | 88 | ## Introduction to `ctags` 89 | 90 | Tags files solve the problem of navigating and completing code in a 91 | given project, but they also create a problem: How do we create the tags 92 | file, and how do we keep it up-to-date? It would be a pain to manually 93 | maintain the tags file even for a small project; it would be all but 94 | impossible to do it for a large project like the Linux kernel. Luckily 95 | no one has to maintain a tags file. There are plenty of utilities to do 96 | that for you, usually bearing the name ctags, or some variant. One very 97 | popular choice is called [Exuberant Ctags][ec], which has the virtue of 98 | being extendable via regular expressions placed into a `.ctags` file, 99 | but the drawback of not having been updated since 2009. Another 100 | increasingly popular option is [Universal Ctags][uc], which functions as 101 | a drop-in replacement for Exuberant Ctags and is actively maintained. 102 | I've had good luck with both. 103 | 104 | Tags files and the tools that generate them have a long history 105 | alongside advanced text editors. The history-of-computing nerd in me 106 | likes knowing that I'm using the same tool programmers have used since 107 | the early days of BSD Unix. It's also a testament to how strong of a 108 | solution they provide that folks are still using them 40 years later. 109 | 110 | ## Generating `tags` Files 111 | 112 | ### Manually 113 | 114 | When we speak of manually generating tags files, we're talking about 115 | using any one of the aforementioned tags utilities to generate the tags 116 | file. If you're the type of person who takes pleasure in at least 117 | understanding how to do things from the command line, you should consult 118 | the manual page for your selected tags utility. Take special note of the 119 | flags necessary to recurse through all of the subdirectories if you want 120 | to generate a tags file for an entire project in one shot. 121 | 122 | ### Automatically 123 | 124 | You can always use your ctags utility of generate your tags files from 125 | the command line, but that's a heck of a lot of back and forth between 126 | your text editor and your shell, and I doubt anyone who tries to do that 127 | will enjoy the experience for long. So let's look at ways to generate 128 | them automatically. 129 | 130 | If you only ever see yourself using tags files with Vim, then maybe a 131 | plugin will interest you. I used [Gutentags][gt] for a long time, and 132 | found it "just works" as advertised. It has sane defaults, but lots of 133 | opportunities to customize its behavior, which you'll see if you visit 134 | the above link. 135 | 136 | In spite of that, I ended up moving in a different direction with 137 | managing my tags files. There were several reasons, but the main one is 138 | that I like to think of tags files a separate from Vim, something the 139 | text editor consumes without having to manage. It's an opinionated view 140 | of things, but I increasingly didn't like to configure my text editor to 141 | manage my tags files. So I went in search of another method, and what I 142 | found was the [Tim Pope][tp] method, which I've since implemented 143 | myself. Rather than using Vim itself to manage tags files, this method 144 | uses local [Git hooks][gh] to rebuild the tags whenever any of a handful of 145 | common git operations are performed. The result is a system that also 146 | just works, but does so in a half-a-dozen lines of shell script rather 147 | than a few _hundred_ lines of Vimscript. Gotta keep that Vim config 148 | tight. 149 | 150 | As a bonus, if you already use Tim Pope's [Fugitive git 151 | plugin][fugitive] (and you should), this method handily places your tags 152 | file where that plugin tells Vim to look for it—in the `.git` folder. 153 | Of course the shell-script approach is infinitely configurable, so you 154 | can ultimately place the tags file wherever you want. One could also 155 | tailor this for other distributed SCM tools (e.g., Mercurial). 156 | 157 | ## Tying It All Together 158 | 159 | That brings us back to where we started, to the issue of code completion 160 | in Vim. Yes, Vim does offer native code completion (completing from tags 161 | is done with `C-x, C-]` in insert mode). No, it's probably not as 162 | powerful as what you could get with something like a Jedi plugin a la 163 | YouCompleteMe, but I've found it satisfies my needs more often than not, 164 | with `:grep` (or my own [`:GrepJob`][mj]) filling the gap nicely in a 165 | more native fashion. 166 | 167 | There's more you can do here too. For instance, if you find yourself 168 | instinctively reaching for the tab key in order to complete a word, 169 | there is [VimCompletesMe][vcm], which takes advantage of all of Vim's 170 | built-in completions through the clever use of an [omni completion 171 | function][oc]. It works, but users do give up some control over selecting 172 | what data source Vim uses for a particular completion. I used this 173 | plugin for a while after I gave up on YouCompleteMe, but ultimately 174 | removed it because it effectively made the TAB key ambiguous in insert 175 | mode. Sometimes I wanted to insert an actual TAB character, but got a 176 | completion instead. 177 | 178 | With all of this in place, it's natural to ask whether a language server 179 | is even necessary with Vim. I don't intend here to suggest an answer to 180 | that question, but I will say that many of the solutions to-date for 181 | language server integration in Vim have seemed like more trouble than 182 | they're worth. That said, with the advent of Vim 8 and its asynchronous 183 | capabilities, there is headroom for these solutions to improve, and I 184 | expect the best among them to become more compelling in the near future. 185 | 186 | I do not recommend Vim because it can be your IDE in the terminal. That 187 | said, Vim is a very powerful tool and if you invest the time to learn 188 | how it works it will take you very far. In other words, use Vim for all 189 | it's worth _before_ looking for a plugin to help you out. Anyone who 190 | (like me) jumps right to installing a bunch of plugins—whether in a 191 | spree of grabbing anything that looks interesting or just to copy 192 | someone else's configuration—will likely end up with an unmaintainable 193 | mess of a tool that doesn't work consistently, may not work at all, or 194 | works about as slow as the IDE you wanted to break free of. 195 | 196 | ## Meta: Contact Information 197 | 198 | - Real name: Daniel Moch 199 | - Email: 200 | - GitHub account: [djmoch][github] 201 | - Twitter handle: [@\_djmoch][twitter] 202 | - IRC handle: djmoch 203 | - Personal site: 204 | 205 | ## Meta: License 206 | 207 | This work is licensed under a [Creative Commons 208 | Attribution-NonCommercial-ShareAlike 4.0 International License][license]. 209 | Permissions beyond the scope of this license may be available by 210 | contacting the author. 211 | 212 | [ic]: http://vimdoc.sourceforge.net/htmldoc/insert.html#ins-completion 213 | [ec]: http://ctags.sourceforge.net/ 214 | [uc]: https://ctags.io/ 215 | [gt]: https://bolt80.com/gutentags/ 216 | [tp]: https://tbaggery.com/2011/08/08/effortless-ctags-with-git.html 217 | [gh]: https://git-scm.com/docs/githooks 218 | [fugitive]: https://github.com/tpope/vim-fugitive 219 | [mj]: https://git.danielmoch.com/vim-makejob.git 220 | [vcm]: https://github.com/ajh17/VimCompletesMe 221 | [oc]: http://vimdoc.sourceforge.net/htmldoc/options.html#'omnifunc' 222 | [github]: https://github.com/djmoch 223 | [twitter]: https://twitter.com/_djmoch 224 | [cf]: http://vimdoc.sourceforge.net/htmldoc/options.html#'completefunc' 225 | [co]: http://vimdoc.sourceforge.net/htmldoc/options.html#'complete' 226 | [to]: http://vimdoc.sourceforge.net/htmldoc/options.html#'tags' 227 | [ti]: http://vimdoc.sourceforge.net/htmldoc/usr_29.html#29.1 228 | [license]: https://creativecommons.org/licenses/by-nc-sa/4.0/ 229 | -------------------------------------------------------------------------------- /gagbo-truly-cross-platform.md: -------------------------------------------------------------------------------- 1 | Make your setup truly cross-platform 2 | =============================================================================== 3 | Once you have taken the time to setup Vim exactly the way you want, you might 4 | still encounter configuration issues. My whole `~/.vim` folder is under version 5 | control so I can just clone it on any computer I want my familiar Vim 6 | experience. 7 | 8 | But I use Windows computers and Linux computers. Even within the same 9 | environment, I might have different dependencies installed for the plugins I 10 | try to import. Also, from time to time I like to use Neovim too, to try out a 11 | few unique features. 12 | 13 | All the tips in this article gravitate around being able to use if statements in 14 | your startup scripts. 15 | 16 | 17 | Environment specific settings 18 | ------------------------------------------------------------------------------- 19 | To make environment specific settings we need to know the environment we are 20 | running on. 21 | We can ask vim directly if it was compiled for Windows, otherwise a system call 22 | to `uname` will give the running environment. The function below returns a string 23 | containing the value of the running environment. 24 | 25 | ```vim 26 | function! whichEnv() abort 27 | if has('win64') || has('win32') || has('win16') 28 | return 'WINDOWS' 29 | else 30 | return toupper(substitute(system('uname'), '\n', '', '')) 31 | endif 32 | endfunction 33 | 34 | """"""""""""""""""""""""""""" 35 | " Later use in another file " 36 | """"""""""""""""""""""""""""" 37 | if (whichEnv() =~# 'WINDOWS') 38 | " Enable Windows specific settings/plugins 39 | else if (whichEnv() =~# 'LINUX') 40 | " Enable Linux specific settings/plugins 41 | else if (whichEnv() =~# 'DARWIN') 42 | " Enable MacOS specific settings/plugins 43 | else 44 | " Other cases I can't think of like MINGW 45 | endif 46 | ``` 47 | 48 | Note that vim also has so-called *features* (the arguments in the `has` 49 | function) for checking respectively `'osx'` and '`osx_darwin`', but after having 50 | spoken with a MacOS user (I don't use it personnally), there seems to be cases 51 | where using these flags for MacOS detection is not working as 52 | [one would guess](https://github.com/vim-advent-calendar/ideas/pull/12#issuecomment-435005197). 53 | 54 | Intermission : external shell calls optimizations 55 | ------------------------------------------------------------------------------- 56 | External shell calls are costly, mostly because of the context switching they 57 | require. They are not very long, but one of the big advantages of vim is the 58 | very quick startup time, and having too many external calls in your startup 59 | will definitely have consequences on your experience. 60 | 61 | To illustrate this, let's compare the startup time of Vim with a one-liner 62 | vimrc, where : 63 | - one case has `let g:dummy_string = 'dummy string'` (no external shell call), 64 | - the other case has `let g:dummy_string = system('date')` (one external shell 65 | call) 66 | 67 | The command I used to compare the startup times was `vim --noplugin -u 68 | one_liner_vimrc --startuptime startup.txt`, and here is the final result : 69 | - no external call : `002.369 000.003: --- VIM STARTED ---` 70 | - external call : `093.497 000.002: --- VIM STARTED ---` 71 | 72 | The 90 ms difference in startup time is entirely due to the external system call 73 | (see `:h startuptime` for help on the syntax) : 74 | ``` 75 | # No external call 76 | 001.246 000.021 000.021: sourcing no_externalcall_vimrc 77 | 78 | # External call 79 | 092.540 088.869 088.869: sourcing externalcall_vimrc 80 | ``` 81 | 82 | Caching the results from any external system call as 83 | much as possible is important when crafting flexible `.vim/` startup scripts. 84 | Here is the final function I use (and call in my other scripts) to know my 85 | environment. The result of `system` is stored in a global variable and used 86 | in all scripts. 87 | 88 | ```vim 89 | """"""""""""""""""""""""""""" 90 | " vimrc " 91 | """"""""""""""""""""""""""""" 92 | 93 | " The function needs to be near the top since it has to be known for later use 94 | 95 | " Sets only once the value of g:env to the running environment 96 | " from romainl 97 | " https://gist.github.com/romainl/4df4cde3498fada91032858d7af213c2 98 | function! Config_setEnv() abort 99 | if exists('g:env') 100 | return 101 | endif 102 | if has('win64') || has('win32') || has('win16') 103 | let g:env = 'WINDOWS' 104 | else 105 | let g:env = toupper(substitute(system('uname'), '\n', '', '')) 106 | endif 107 | endfunction 108 | 109 | """"""""""""""""""""""""""""" 110 | " Later use in another file " 111 | """"""""""""""""""""""""""""" 112 | 113 | " I can call this function before every environment specific block with the 114 | " early return branch. 115 | call Config_setEnv() 116 | if (g:Env =~# 'WINDOWS') 117 | " Enable Windows specific settings/plugins 118 | else if (g:Env =~# 'LINUX') 119 | " Enable Linux specific settings/plugins 120 | else if (g:Env =~# 'DARWIN') 121 | " Enable MacOS specific settings/plugins 122 | else 123 | " Other cases I can't think of like MINGW 124 | endif 125 | ``` 126 | 127 | Host specific settings 128 | ------------------------------------------------------------------------------- 129 | We can use exactly the same method for host specific settings. 130 | Linux provides [hostname](https://linux.die.net/man/1/hostname) so we can use 131 | the same function as before, replacing only the `toupper...` line with 132 | `system('hostname')`, and storing it in another `g:` variable like `g:Hostname`. 133 | 134 | This method should also work on Windows, since it also provides 135 | [hostname](https://ss64.com/nt/hostname.html), but I have not tested it yet. 136 | 137 | In order to show 138 | how this can be useful I will have to present a little my work environment. 139 | I am currently a PhD student in computational mechanics, which 140 | is one heavy user of High Performance Computing 141 | ([HPC](https://en.wikipedia.org/w/index.php?title=High-performance_computing&redirect=no) 142 | : the laboratory has a Linux-powered 143 | [cluster](https://en.wikipedia.org/wiki/Computer_cluster) on which all the heavy 144 | simulations are run. The software we use is written in C++ and we built a DSL to 145 | communicate input parameters through plain-text files to the software. 146 | 147 | This means I need to edit text from multiple places on 148 | multiple machines for my work : 149 | 150 | - I might want to edit files directly on my office Windows machine. This machine 151 | is a little beefy and I can use it to test locally developments which need 152 | more computational power to be run. 153 | - I might want to edit files stored on the cluster from my office Windows 154 | machine (with ssh). This is useful to work on the code and/or launch tests 155 | directly in the correct environment. 156 | - I might want to edit files stored on the cluster from my laptop running Linux 157 | (with ssh). This is useful when I want to change quickly simulation 158 | parameters. 159 | - I might want to edit files directly on my laptop. This is where I work on 160 | code the most. 161 | 162 | Therefore, I use Vim to edit text in 2 Linux environments (my laptop and the 163 | front node of the cluster), but I do have specific issues related to the way I 164 | access the files (either "natively" or through ssh using a Windows client are 165 | the two extremes). So in the following snippet, I use the "host specific" method 166 | to disable X server connection when working on the cluster (Putty used to try to 167 | connect and wait for a timeout, leading to startup times of 3-5 *seconds*), and 168 | to add the [FZF](https://github.com/junegunn/fzf#as-vim-plugin) directory to the 169 | runtimepath, since I had to install fzf in my `$HOME` directory on this machine. 170 | 171 | ```vim 172 | """"""""""""""""""""""""""""" 173 | " vimrc " 174 | """"""""""""""""""""""""""""" 175 | " Sets only once the value of g:host to the output of hostname 176 | function! Config_setHost() abort 177 | if exists('g:Hostname') 178 | return 179 | endif 180 | let g:host = system('hostname') 181 | endfunction 182 | 183 | call Config_setHost() 184 | if g:Hostname =~? 'front' 185 | set clipboard=exclude:.* 186 | set runtimepath+=~/.fzf 187 | endif 188 | ``` 189 | 190 | Host specific settings are good when you know you're only cloning your `~/.vim` 191 | directory in a few computers on which you know what is installed. Using this to 192 | differentiate between 50 hosts means you will need very long if statements which 193 | get quickly hard to read. 194 | 195 | I still find this useful for clipboard handling or other purely host-specific 196 | settings. 197 | 198 | **Security note** : as you can see in the last snippet, you have to put the 199 | hostname in your configuration in order to make these specific settings. This 200 | information will then be available to anyone who can see your configuration 201 | files on the internet. If this is an issue (especially regarding version 202 | control on online git repositories), I think the best thing to do is to : 203 | - keep these if statements in separate `.vim` files, 204 | - Move these `.vim` files in a specific folder under `~/.vim` like 205 | `host_settings` 206 | - `runtime` the settings in the version controlled file. 207 | 208 | Eventually the configuration looks like this : 209 | ```vim 210 | """""""""""""""""""""""""""""" 211 | " vimrc (version-controlled) " 212 | """""""""""""""""""""""""""""" 213 | " Sets only once the value of g:host to the output of hostname 214 | function! Config_setHost() abort 215 | if exists('g:Hostname') 216 | return 217 | endif 218 | let g:host = system('hostname') 219 | endfunction 220 | 221 | call Config_setHost() 222 | runtime host_settings/34.vim 223 | 224 | """"""""""""""""""""""""""""" 225 | " .gitignore " 226 | """"""""""""""""""""""""""""" 227 | # Ignore the host_settings folder in version control 228 | host_settings/ 229 | 230 | " All files below are now private 231 | """"""""""""""""""""""""""""" 232 | " host_settings/34.vim " 233 | """"""""""""""""""""""""""""" 234 | if g:Hostname =~? 'front' 235 | set clipboard=exclude:.* 236 | set runtimepath+=~/.fzf 237 | endif 238 | 239 | 240 | ``` 241 | 242 | If all snippets can be run at the same location, you can even use globs to hide 243 | even file names : 244 | ```vim 245 | """""""""""""""""""""""""""""" 246 | " vimrc (version-controlled) " 247 | """""""""""""""""""""""""""""" 248 | " Sets only once the value of g:host to the output of hostname 249 | function! Config_setHost() abort 250 | if exists('g:Hostname') 251 | return 252 | endif 253 | let g:host = system('hostname') 254 | endfunction 255 | 256 | call Config_setHost() 257 | runtime! host_settings/*.vim " Beware of the '!', it is necessary 258 | 259 | """"""""""""""""""""""""""""" 260 | " .gitignore " 261 | """"""""""""""""""""""""""""" 262 | # Ignore the host_settings folder in version control 263 | host_settings/ 264 | 265 | " All files below are now private 266 | """""""""""""""""""""""""""""""""""""""" 267 | " host_settings/clipboard_settings.vim " 268 | """""""""""""""""""""""""""""""""""""""" 269 | if g:Hostname =~? 'front' 270 | set clipboard=exclude:.* 271 | endif 272 | 273 | """""""""""""""""""""""""""""""""" 274 | " host_settings/fzf_settings.vim " 275 | """""""""""""""""""""""""""""""""" 276 | if g:Hostname =~? 'front' 277 | set runtimepath+=~/.fzf 278 | endif 279 | 280 | ``` 281 | 282 | Dependencies specific settings 283 | ------------------------------------------------------------------------------- 284 | Even on the same environment, dependencies might not be fulfilled on all the 285 | target machines. These dependencies can be separated into 2 categories, Vim's 286 | *features* and external *dependencies*. The difference I make between these 2 287 | is that *features* are defined at compilation time in Vim and that 288 | *dependencies* are external to Vim's compilation. 289 | 290 | Vim keeps track of its own feature set, defined at compile time. Therefore you 291 | can directly use vimscript to know if Vim has a feature or not, using the 292 | `has()` function. 293 | See `:h has()` for all the features you can test for 294 | directly within vim. 295 | ```vim 296 | if has('cscope') 297 | " Enable all the plugins or change settings to use cscope support 298 | endif 299 | ``` 300 | 301 | For external dependencies (like the linters you might want to set as `makeprg` 302 | or the LSP servers you want to start for a project), using `executable()` 303 | instead of using a call to [which](https://linux.die.net/man/1/which) is very 304 | important, as it is way faster than `system`. I ran the same test case as 305 | before with a vimrc which only include an `executable()` call : 306 | ```vim 307 | if executable('rg') 308 | let g:string_date = 'dummy string' 309 | " usually the line here is set grepprg=rg\ --vimgrep 310 | endif 311 | ``` 312 | 313 | and the results are almost the same as the *no external call* case : 314 | ``` 315 | # Important lines only 316 | 001.991 000.136 000.136: sourcing exec_vimrc 317 | 003.383 000.005: --- VIM STARTED --- 318 | ``` 319 | 320 | 321 | Bonus round : Vim 8+ and Neovim compatibility 322 | ------------------------------------------------------------------------------- 323 | Vim 8+ is important because I make heavy usage of the package feature for this 324 | adaptation. 325 | 326 | First step is to symlink the folders of course. We only want one copy of the 327 | `.vim` folder on the system, Vim does not care about `init.vim` and Neovim does 328 | not care about `vimrc` 329 | 330 | After a few updates I made in my plugins and/or colorschemes, I noticed I 331 | always had 2 files to change : `init.vim` and `vimrc`. I still want to keep the 332 | files different because there are a few settings which are actually specific to 333 | one software, but duplicating changes is a code smell. 334 | 335 | My solution is to use 336 | `runtime` heavily and externalize all the common parts of my old `vimrc`. 337 | `runtime` will look for files to source with the given name in your 338 | `runtimepath` and will source the first one it finds. Choosing a "unique" 339 | folder for the sourced files (like `settings`) allows to store them all with 340 | any name as long as they are in the `runtimepath`. I choose to leave them 341 | directly in the `~/.vim` folder to have them under version control of course : 342 | ``` 343 | $ ls ~/.vim 344 | ... some files 345 | init.vim 346 | vimrc 347 | settings/ 348 | ... other folders 349 | 350 | $ ls ~/.vim/settings 351 | colors.vim 352 | ale.vim 353 | ... other files 354 | 355 | ``` 356 | 357 | ```vim 358 | " In vimrc 359 | set autoindent " 'vim-specific' setting 360 | runtime settings/fold_fillchars.vim 361 | 362 | ``` 363 | 364 | 365 | `if has('nvim')` is exactly what you want to separate the 2 cases in your 366 | scripts. For example, to load plugins only for Vim or only for Neovim, you can 367 | put your optional plugins in `~/.vim/pack/vim_or_neovim/opt` and then use this 368 | kind of snippets : 369 | 370 | ```vim 371 | if has('nvim') 372 | " Load Neovim specific plugins 373 | packadd LanguageClient-neovim " This plugin is not Neovim specific anymore, 374 | " just here for the example 375 | else 376 | " Load Vim specific plugins 377 | packadd traces.vim 378 | endif 379 | ``` 380 | 381 | 382 | Results 383 | ------------------------------------------------------------------------------- 384 | You can see a few of those principles applied on my [current 385 | repo](https://framagit.org/gagbo/vim-setup). Be warned that it is still a little 386 | bit messy, because tidying all the files and plugins is very low priority on my 387 | TODO list. And also because writing this post made me verify and learn new 388 | things about how to further smooth my truly cross platform setup. 389 | 390 | 391 | Gerry 392 | 393 | This article is licensed under 394 | [Creative Commons v4.0](https://creativecommons.org/licenses/by/4.0/) 395 | -------------------------------------------------------------------------------- /tejr-from-vimrc-to-vim.md: -------------------------------------------------------------------------------- 1 | From `.vimrc` to `.vim` 2 | ======================= 3 | 4 | > You can’t have everything. Where would you put it? 5 | > 6 | > –Steven Wright 7 | 8 | Attack of the 5,000-line vimrc 9 | ------------------------------ 10 | 11 | Vim is an endlessly configurable and extensible editor, with a culture of users 12 | sharing configuration for their [`~/.vimrc` or `~/.vim/vimrc`][rc] startup 13 | files. These files tend to expand over time. New users start by setting only a 14 | few global defaults for options like [`'expandtab'`][et] and [`'wrap'`][wr], 15 | and then add custom mappings, functions, filetype-specific logic, and 16 | third-party plugins, often under an ever-shifting mantle of plugin managers. 17 | Their vimrc files grow not only larger, but more intricate and complex. 18 | 19 | If you have one of these longer files, you’re in good company. Damian Conway, a 20 | Vim guru specializing in Perl, published [a vimrc with 1,855 lines][dc], and at 21 | the time of writing, [Steve Losh’s is a whopping 3,160 lines][sl]. I’m sure you 22 | can find vimrc files that are even longer—perhaps yours already is. 23 | 24 | The issue with very long vimrc files isn’t the sheer amount of 25 | configuration—after all, all of that power is there for a reason. However, if 26 | you’ve been programming for a while, you’ll know from experience that it’s best 27 | to avoid very large files with code that does many disparate things, because it 28 | makes code hard to find, manage, and understand. Vim configuration is no 29 | exception. Instead of a single large configuration file, there’s a case to be 30 | made for having a set of smaller, well-organized files. Those files go in 31 | `~/.vim`. 32 | 33 | Creating a directory hierarchy in `~/.vim` to replace your large vimrc keeps 34 | your configuration manageable. It improves efficiency by loading code only when 35 | needed. It becomes clearer from a file’s position in the hierarchy what its 36 | purpose is. It also makes it easier to *package* configuration for others to 37 | use. 38 | 39 | Your own personal `$VIMRUNTIME` 40 | ------------------------------- 41 | 42 | Most of the benefit of an organized `~/.vim` directory comes from leaning on 43 | Vim’s built-in behavior, which gives you a lot of control over how 44 | configuration files are loaded. 45 | 46 | Let’s start by looking at the structure of the runtime files that come with Vim 47 | itself. You can find the path for this directory in the `$VIMRUNTIME` variable: 48 | 49 | :echo $VIMRUNTIME 50 | 51 | If you’re using the version of Vim that was packaged with your operating 52 | system, it will very likely be something like `/usr/share/vim/vim81`. 53 | 54 | Let’s take a look at the contents of that directory: 55 | 56 | $ ls /usr/share/vim/vim81 57 | autoload/ bugreport.vim colors/ compiler/ 58 | defaults.vim delmenu.vim doc/ evim.vim 59 | filetype.vim ftoff.vim ftplugin/ ftplugin.vim 60 | ftplugof.vim gvimrc_example.vim indent/ indent.vim 61 | indoff.vim keymap/ lang/ macros/ 62 | menu.vim mswin.vim optwin.vim pack/ 63 | plugin/ print/ rgb.txt scripts.vim 64 | spell/ synmenu.vim syntax/ tools/ 65 | tutor/ vimrc_example.vim 66 | 67 | A quick-and-dirty count in the shell shows us there are 1,674 files in this 68 | directory tree: 69 | 70 | $ find /usr/share/vim/vim81 -type f | wc -l 71 | 1674 72 | 73 | Of those, 1,335 are `.vim` files: 74 | 75 | $ find /usr/share/vim/vim81 -type f -name \*.vim | wc -l 76 | 1335 77 | 78 | All of these are just plain Vim script files, like your vimrc. Their location 79 | within this directory determines when they are loaded. Only a few of them are 80 | loaded on Vim startup. That’s well over a thousand files, ready to be loaded 81 | *only when relevant*. We should take a hint from Bram on that! 82 | 83 | If we look at the value of the [`'runtimepath'`][ro] option in Vim, we can see 84 | a few other paths: 85 | 86 | :set runtimepath? 87 | runtimepath=~/.vim,/usr/share/vim/vim81,…,~/.vim/after 88 | 89 | The very first entry of `'runtimepath'` is `~/.vim`, and that’s where you can 90 | build a structure mimicking that of `$VIMRUNTIME`. This is your *personal* Vim 91 | runtime directory. 92 | 93 | Lose the `:source`, Luke 94 | ------------------------ 95 | 96 | If you’ve worked with Vim script for a while, you probably know how to use the 97 | [`:source`][sc] command to read and execute commands from a file. For example, 98 | if you had the aim of loading a separate file with something like mapping 99 | definitions in it during Vim startup, you might have put a line like this in 100 | your vimrc: 101 | 102 | source ~/.vim/mappings.vim 103 | 104 | Vim has another command named [`:runtime`][rt] that also sources Vim script 105 | files, but with very different behaviour—it’s the counterpart to the file 106 | layout of the `'runtimepath'` directories we just inspected. 107 | 108 | The `:runtime` command takes one or more relative paths as arguments. It 109 | iterates through each of the directories in the `'runtimepath'` option, and 110 | finds and sources files that match those relative paths in each one. 111 | 112 | As an example, consider these commands: 113 | 114 | set runtimepath=~/.vim,/usr/share/vim/vim81 115 | runtime syntax/c.vim 116 | 117 | If a file named `~/.vim/syntax/c.vim` exists, the `:runtime` command will find 118 | it and source it. If such a file does not exist, `:runtime` checks for 119 | `/usr/share/vim/vim81/syntax/c.vim` next, and then sources that if it exists. 120 | 121 | Note that we didn’t include the leading `~/.vim` path in the pattern for 122 | `:runtime`—it’s a *relative* path. 123 | 124 | With an exclamation mark added, `:runtime!` sources *all* files from *all* 125 | `'runtimepath'` directories that match the pattern. It doesn’t stop after the 126 | first one. 127 | 128 | runtime! syntax/c.vim 129 | 130 | This works for multiple patterns, too: 131 | 132 | runtime! syntax/c.vim syntax/cpp.vim 133 | 134 | We can include `*` and `?` **globs** for pattern matching: 135 | 136 | runtime! */maps.vim 137 | 138 | This sources files named `~/.vim/foo/maps.vim` and also 139 | `/usr/share/vim/vim81/bar/maps.vim`, if such files exist. 140 | 141 | Patterns can match filenames as well as directories: 142 | 143 | runtime! maps/*.vim 144 | 145 | This sources any and all `.vim` files in `~/.vim/maps` and in 146 | `/usr/share/vim/vim81/maps`, if such directories exist. 147 | 148 | We can even use a [double asterisk][ss], which represents a path with an 149 | arbitrary number of directory path elements, up to 100 levels deep: 150 | 151 | runtime! **/maps.vim 152 | 153 | With this command, a file with a deep path like 154 | `~/.vim/foo/bar/baz/quux/maps.vim` would still be found and loaded if the 155 | pattern as a whole matched. 156 | 157 | Unlike `:source`, `:runtime` doesn’t raise errors if it can’t find any matching 158 | files. If the absence of a file matching a particular runtime path is not an 159 | error condition, this allows you to avoid boilerplate checks for its existence: 160 | 161 | " Source extra commands for this system, if file exists 162 | runtime local.vim 163 | 164 | Knowing how to use `:runtime` is helpful all by itself. However, it gets more 165 | interesting when you realise that much of Vim’s startup process is just thin 166 | wrappers around `:runtime` commands; so are some of its other commands, 167 | including [`:filetype`][ft]. You can apply this as you translate your vimrc 168 | file into a runtime directory, in order to run your own code before, instead 169 | of, or after Vim’s bundled runtime code, in order to disable, replace, modify, 170 | or extend it. 171 | 172 | Turn on, `plugin`, drop out 173 | --------------------------- 174 | 175 | You can start the process of breaking up your vimrc file by looking for blocks 176 | of code that have expanded beyond simple configuration, and can be grouped 177 | together meaningfully. You can extract these into self-contained files in the 178 | `plugin` subdirectory. 179 | 180 | As an example, if you read others’ vimrc files, you will often see approaches 181 | to solving the problem of conveniently removing trailing whitespace at line 182 | endings. Here’s one approach, in a function from the [Vim Tips wiki][vs]: 183 | 184 | function StripTrailingWhitespace() 185 | if !&binary && &filetype != 'diff' 186 | normal mz 187 | normal Hmy 188 | %s/\s\+$//e 189 | normal 'yz 190 | normal `z 191 | endif 192 | endfunction 193 | 194 | This kind of function is usually followed by a mapping to call it: 195 | 196 | nnoremap x :call StripTrailingWhitespace() 197 | 198 | This function doesn’t need to be loaded every time vimrc is sourced. Once 199 | defined, it can just sit there, ready for calling when appropriate. In fact, 200 | Vim throws an error if a vimrc with this function is reloaded. We could fix 201 | that by declaring the function with `function!`, but there’s another way: 202 | instead of putting the function definition in `~/.vimrc`, we can drop it into a 203 | `.vim` file in `~/.vim/plugin`. We’ll use 204 | `~/.vim/plugin/strip_trailing_whitespace.vim`. 205 | 206 | Once this file is created, we can restart Vim, and then confirm our plugin has 207 | been loaded by checking its path is in the output of [`:scriptnames`][sn]: 208 | 209 | :scriptnames 210 | ... 211 | 10: ~/.vim/plugin/strip_trailing_whitespace.vim 212 | ... 213 | 214 | Note that the `x` mapping left in the vimrc still works, despite being 215 | set *before* the function it calls was defined. 216 | 217 | ### What’s a plugin, anyway? 218 | 219 | Why should we put the script in `~/.vim/plugin`? You might object that our 220 | example isn’t a *real* plugin; it’s just a single function. However, that’s not 221 | a meaningful distinction to Vim. At startup, it sources any and all `.vim` 222 | files in the `plugins` subdirectory of each of the directories in 223 | `'runtimepath'`. It makes no difference what those files actually contain. A 224 | set of related abbreviations? Custom commands? Code dependent on one particular 225 | machine or operating system? Sure, why not? 226 | 227 | ### Plugin subdirectories 228 | 229 | Similarly, because `*.vim` files are loaded from the `plugin` directory 230 | *recursively*, you can organize them in subdirectories if you want to: 231 | 232 | ~/.vim/plugin/insert/cancel.vim 233 | ~/.vim/plugin/insert/suspend.vim 234 | ~/.vim/plugin/visual/region.vim 235 | ~/.vim/plugin/whitespace/squeeze.vim 236 | ~/.vim/plugin/whitespace/trim.vim 237 | 238 | The names of the subdirectories aren’t significant; Vim will search them all. 239 | Remember how we mentioned Vim’s thinly-veiled `:runtime` wrappers? This is one 240 | of them. A clue here is in the command that [`:help load-plugins`][lp] suggests 241 | as an analogue to what Vim does internally at this step: 242 | 243 | :runtime! plugin/**/*.vim 244 | 245 | ### Local script scope 246 | 247 | Putting blocks of code like this in distinct files in `~/.vim/plugin` has some 248 | other advantages. One of these is Vim’s [script-variable][sv] **scoping** for 249 | functions and variables that are only needed within the script: 250 | 251 | let s:myvar = 'value' 252 | function s:Myfunc() 253 | ... 254 | endfunction 255 | 256 | This applies a unique prefix to all of your function names and variable names 257 | at the time the file is sourced. That means you don’t have to worry about 258 | trampling on any other variables defined elsewhere in your configuration. 259 | 260 | There are some caveats here for defining mappings; make sure you read `:help 261 | script-variable` carefully to make sure you understand how to use `` 262 | prefixes. 263 | 264 | ### Short-circuiting and load guards 265 | 266 | Another advantage of separate script files is the ability to **short-circuit** 267 | a script, to prevent it from loading if it’s not appropriate to do so. This is 268 | done by checking at the start of the script whether the rest of it should be 269 | loaded, and skipping it with [`:finish`][fn] if it shouldn’t. 270 | 271 | You can use this to check options like [`'compatible'`][co], the Vim version 272 | number, the availability of a feature, or whether the plugin has already been 273 | loaded: 274 | 275 | if &compatible 276 | \ || v:version < 700 277 | \ || has('folding') 278 | \ || exists('g:loaded_myplugin') 279 | finish 280 | endif 281 | let g:loaded_myplugin = 1 282 | 283 | This way, you don’t have to wrap all your feature-dependent code in clumsy 284 | [`:if`][if] blocks. 285 | 286 | ### The question of mappings 287 | 288 | Should you include a mapping that uses a defined function in the plugin itself? 289 | It’s up to you, but the author likes to think of vimrc files as where 290 | user-level preferences go, and plugins where the code they call should go. 291 | Key choices for mappings are personal, and fall into the former category. 292 | 293 | If you want to keep some abstraction between what the plugin does and how it’s 294 | called, you can use [`` prefix][pp] mappings to expose an interface from 295 | the plugin file: 296 | 297 | function s:StripTrailingWhitespace() 298 | ... 299 | endfunction 300 | nnoremap StripTrailingWhitespace 301 | \ :call StripTrailingWhitespace() 302 | 303 | You can then put your choice of mapping for that target in your vimrc: 304 | 305 | nmap x StripTrailingWhitespace 306 | 307 | If someone else wants to use your plugin, this makes choosing their own 308 | mappings for it more straightforward. There’s more general advice about good 309 | mapping practices in writing fully-fledged plugin files in [`:help 310 | write-plugin`][wp]. 311 | 312 | Not really my `:filetype` 313 | ------------------------- 314 | 315 | Another pattern in big vimrc files is setting options only for buffers of a 316 | certain filetype. For example, this line of code is intended to set the 317 | [`'spell'`][sp] option, to highlight possible spelling errors in the text, but 318 | *only* for [`mail` filetype][mf] buffers: 319 | 320 | autocmd FileType mail setlocal spell 321 | 322 | The first thing to note here is that this should be surrounded in a 323 | self-clearing [`augroup`][ag], so that reloading it doesn’t make multiple 324 | definitions for the same hook: 325 | 326 | augroup ftmail 327 | autocmd! 328 | autocmd FileType mail setlocal spell 329 | augroup END 330 | 331 | This is annoying, but there’s a way to avoid this boilerplate. 332 | 333 | The second thing to note about our `autocmd` is that it’s set every time vimrc 334 | is loaded, *regardless* of whether a mail file is actually edited in that 335 | session. It therefore makes more sense to put this into a [filetype plugin][fp] 336 | or **ftplugin**, so that it’s only loaded when relevant. 337 | 338 | The `autocmd` hooks that set a buffer’s filetype are defined in 339 | `$VIMRUNTIME/filetype.vim`. They apply heuristics to guess and then set the 340 | type of a buffer, and then Vim runs any appropriate filetype plugins 341 | afterwards: files in `'runtimepath'` directories named `ftplugin/FILETYPE.vim` 342 | will be sourced. 343 | 344 | This means there’s no need for the `autocmd` hooks around our `'spell'` 345 | setting. We already have hooks for changes of filetype available to us, and we 346 | can just put this single line in `~/.vim/ftplugin/mail.vim` to use them: 347 | 348 | setlocal spell 349 | 350 | With this done, upon editing a new `mail` buffer, we can check `:scriptnames` 351 | to confirm that our filetype plugin was loaded: 352 | 353 | :set filetype=mail 354 | :scriptnames 355 | ... 356 | 20: ~/.vim/ftplugin/mail.vim 357 | ... 358 | :set spell? 359 | spell 360 | 361 | This is better, but we can improve it further. 362 | 363 | ### Loading filetype configuration afterwards 364 | 365 | Rather than putting our `'spell'` setting in `~/.vim/ftplugin/mail.vim`, we can 366 | put it in `~/.vim/after/ftplugin/mail.vim`—note the extra directory named 367 | `after` in the path. 368 | 369 | Files in the [`after` runtime directory][ad] are loaded *after* the analogous 370 | runtime files included in Vim. Using this path, we can ensure that our option 371 | is set *after* the `mail` filetype plugin in `$VIMRUNTIME/ftplugin/mail.vim` 372 | has been sourced. This is how to *override* something a filetype plugin does if 373 | you don’t like it. 374 | 375 | ### Breaking up filetype plugins 376 | 377 | If you need to make this even more granular, you can also put files in 378 | *subdirectories* named after the filetype: 379 | 380 | ~/.vim/after/ftplugin/mail/spell.vim 381 | ~/.vim/after/ftplugin/mail/quote.vim 382 | 383 | The filetype followed by an underscore and then a script name works, too: 384 | 385 | ~/.vim/after/ftplugin/mail_spell.vim 386 | ~/.vim/after/ftplugin/mail_quote.vim 387 | 388 | You may have guessed by now that filetype switching is yet another `:runtime` 389 | wrapper. Switching to a filetype of `mail` effectively runs this command: 390 | 391 | :runtime! ftplugin/mail.vim ftplugin/mail_*.vim ftplugin/mail/*.vim 392 | 393 | ### Undoing filetype settings 394 | 395 | If the filetype of a buffer changes, we should *reverse* any local 396 | configuration we applied. We can do this with the [`b:undo_ftplugin`][uf] 397 | variable, which contains a list of pipe-separated (`|`) commands. When a 398 | buffer’s filetype changes, the commands *undo* the buffer-specific settings for 399 | the previous filetype, ready for the new filetype’s plugins to be loaded. 400 | 401 | After each filetype plugin setting we make, we should append corresponding 402 | commands to reverse that change to `b:undo_ftplugin`. For our `'spell'` 403 | example, we’d do this: 404 | 405 | setlocal spell 406 | let b:undo_ftplugin .= '|setlocal spell<' 407 | 408 | The `spell<` syntax used here, with a trailing left angle bracket, specifies 409 | that the local value of `'spell'` should be restored to match the global value 410 | of `'spell'` when the `mail` filetype is unloaded. 411 | 412 | After setting a filetype, we can check the `b:undo_ftplugin` variable’s value 413 | with [`:let`][lt]: 414 | 415 | :set filetype=mail 416 | :let b:undo_ftplugin 417 | b:undo_ftplugin setl modeline< tw< fo< comments<|setlocal spell< 418 | 419 | ### The difference with indent 420 | 421 | Filetype-specific code related to indentation goes in a different location 422 | again: `~/.vim/indent/FILETYPE.vim` or `~/.vim/after/indent/FILETYPE.vim`. 423 | Those files are sourced if you include the word `indent` in your `vimrc`’s 424 | `:filetype` call. You should use this layout for files that change 425 | [`'autoindent'`][ai] or [`'indentexpr'`][ie] settings, for example. 426 | 427 | You can put indent settings in your filetype plugin if you want to, but 428 | remember that we’re trying to find the *right place* for things. Doing it this 429 | way keeps your indentation settings separate from all other filetype-specific 430 | settings. This gives users an easy way to load only what they want, using 431 | appropriate arguments to their vimrc’s `:filetype` call. 432 | 433 | ### Detecting filetypes 434 | 435 | As a final note for filetype-dependent logic, if you have any hooks to *set* a 436 | buffer’s filetype in the first place, based on its filename or contents, those 437 | go in the [`ftdetect`][fd] directory. You might put this in 438 | `~/.vim/ftdetect/irssilog`, for example: 439 | 440 | autocmd BufNewFile,BufRead */irc/*.log setfiletype irssilog 441 | 442 | Putting the hooks in `~/.vim/ftdetect` means they are sourced as part of the 443 | `filetypedetect` `augroup` defined in `filetype.vim`. This is another context 444 | in which you don’t need to surround `autocmd` definitions in a self-clearing 445 | `augroup`, because it’s already been done for you. 446 | 447 | Be water, my friend 448 | ------------------- 449 | 450 | All of the above is just the beginning. We haven’t even touched on 451 | [lazy-loading functions][al] for speed with definitions in `~/.vim/autoload`, 452 | or custom [`:compiler`][cm] definitions for setting [`'makeprg'`][mp] and 453 | [`'errorformat'`][ef] in `~/.vim/compiler`. These are yet more examples of Vim 454 | functionality that wraps around `:runtime` loading. 455 | 456 | While Vim gives you a lot of flexibility in configuring and customizing, there 457 | is definitely a **Way of Vim** for the timely loading of relevant 458 | configuration, and if you learn a little about how it works, you’ll fight with 459 | your editor that much less. If this seems stringent to you, think back to when 460 | you first learned Vim. Do you remember how strange using the HJKL keys for 461 | movement seemed, before it made sense? Do you remember how you wanted to stay 462 | in insert mode all the time, before normal mode made sense? 463 | 464 | Working within the Vim runtime file structure instead of ignoring it or 465 | fighting with it makes your `~/.vim` directory into a refined toolbox, with a 466 | place for everything, and everything in its place. It’s well worth the effort! 467 | 468 | If you’d like to see an example of how this layout can end up looking when you 469 | make it work for you, [the author’s personal `~/.vim` directory is available 470 | for download][pv]. 471 | 472 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 473 | 474 | [ad]: https://vimhelp.appspot.com/options.txt.html#after-directory 475 | [ag]: https://vimhelp.appspot.com/autocmd.txt.html#%3Aaugroup 476 | [ai]: https://vimhelp.appspot.com/options.txt.html#%27autoindent%27 477 | [al]: https://vimhelp.appspot.com/eval.txt.html#autoload 478 | [cm]: https://vimhelp.appspot.com/quickfix.txt.html#%3Acompiler 479 | [co]: https://vimhelp.appspot.com/options.txt.html#%27compatible%27 480 | [dc]: https://github.com/thoughtstream/Damian-Conway-s-Vim-Setup/blob/cbe1fb5b5505e17bd7709669168c367903d94cd4/.vimrc 481 | [ef]: https://vimhelp.appspot.com/options.txt.html#%27errorformat%27 482 | [et]: https://vimhelp.appspot.com/options.txt.html#%27expandtab%27 483 | [fd]: https://vimhelp.appspot.com/filetype.txt.html#ftdetect 484 | [fn]: https://vimhelp.appspot.com/repeat.txt.html#%3Afinish 485 | [fp]: https://vimhelp.appspot.com/usr_05.txt.html#ftplugins 486 | [ft]: https://vimhelp.appspot.com/filetype.txt.html#%3Afiletype 487 | [ie]: https://vimhelp.appspot.com/options.txt.html#%27indentexpr%27 488 | [if]: https://vimhelp.appspot.com/eval.txt.html#%3Aif 489 | [lp]: https://vimhelp.appspot.com/starting.txt.html#load-plugins 490 | [lt]: https://vimhelp.appspot.com/eval.txt.html#%3Alet 491 | [mf]: https://vimhelp.appspot.com/syntax.txt.html#mail%2Evim 492 | [mp]: https://vimhelp.appspot.com/options.txt.html#%27makeprg%27 493 | [pp]: https://vimhelp.appspot.com/usr_41.txt.html#using-%3CPlug%3E 494 | [pv]: tejr-from-vimrc-to-vim.zip 495 | [rc]: https://vimhelp.appspot.com/usr_05.txt.html#vimrc-intro 496 | [ro]: https://vimhelp.appspot.com/options.txt.html#%27runtimepath%27 497 | [rt]: https://vimhelp.appspot.com/repeat.txt.html#%3Aruntime 498 | [sc]: https://vimhelp.appspot.com/repeat.txt.html#%3Asource 499 | [sl]: https://bitbucket.org/sjl/dotfiles/src/e2a961f1d037e53ea2809885a65feba66a9aa03e/vim/vimrc?at=default&fileviewer=file-view-default 500 | [sn]: https://vimhelp.appspot.com/repeat.txt.html#%3Ascriptnames 501 | [sp]: https://vimhelp.appspot.com/options.txt.html#%27spell%27 502 | [ss]: https://vimhelp.appspot.com/editing.txt.html#starstar-wildcard 503 | [sv]: https://vimhelp.appspot.com/eval.txt.html#script-variable 504 | [uf]: https://vimhelp.appspot.com/usr_41.txt.html#undo_ftplugin 505 | [wp]: https://vimhelp.appspot.com/usr_41.txt.html#write-plugin 506 | [wr]: https://vimhelp.appspot.com/options.txt.html#%27wrap%27 507 | [vs]: http://vim.wikia.com/wiki/Remove_unwanted_spaces 508 | -------------------------------------------------------------------------------- /markzen-mappings.md: -------------------------------------------------------------------------------- 1 | # Mappings 2 | 3 | ## On The Genealogy of Modality 4 | 5 | What makes Vim so different from other editors is, arguably, its rich set of 6 | motion commands. Once you can move around with precision, you not only gain 7 | time, as you do not need to reach for the mouse, but you can also turn pretty 8 | much every modification into tiny "programs", either to accomplish ad hoc tasks 9 | via recorded macros, or to address more general cases, thanks to commands, 10 | functions--and mappings. 11 | 12 | A mapping is the binding of a key or a sequence of keys, called the *left-hand 13 | side (LHS)*, to a series of keystrokes, the *right-hand side (RHS)*. The RHS is 14 | similar to a macro recorded with [`q`][q]; contrary to some other tools or 15 | editors, mappings do not reach for some 'core' functions or commands, like 16 | hypothetical `next-word` or `delete-char`, that the default bindings would just 17 | call. In Vim, motions and modification commands are so central that it would not 18 | make much sense to decouple them from their standard keys, since they are the 19 | building blocks of what we could call the "Vim editing programming language". 20 | For instance, `dw` is like a program statement that deletes chars from cursor to 21 | the beginning of next word; it's a piece of code every Vim user will understand, 22 | like an 'if' statement in C or in another programming language. Thus, in Vim the 23 | media (the key) is definitely the message (the function to execute). 24 | 25 | Obviously, since alphanumeric keys are used to write "editing programs", there is 26 | a need to separate "programming mode" where the user hits `dw` to delete a word, 27 | from "insertion mode", where the user just wants to type text and not execute 28 | programs. That is why Vim is a *modal editor*, contrary to, say, Emacs. Once you 29 | go the modal road, it makes sense to define [extra modes][em] for things like 30 | visual selection or the command-line. Again, mappings make it possible to 31 | automate tasks for each of these modes. 32 | 33 | So, if Vim is programming, custom mappings are no less than its user-defined 34 | functions. 35 | 36 | ## Maps. Very dangerous... You Go First 37 | 38 | The general syntax to create mappings is: 39 | 40 | {map-cmd} [modifiers] {lhs} {rhs} 41 | 42 | '{map-cmd}' is one of the several mapping-defining Ex commands we will examine 43 | shortly. '[modifiers]' is an optional list of modifiers. '{lhs}' is the key 44 | sequence that will trigger the mapping: if it contains a literal blank (space, 45 | tab or linefeed), it must be escaped. '{rhs}' is the key sequence that will be 46 | triggered, as though the user typed these keys directly (for the most 47 | part--there are a few differences). 48 | 49 | ### Mad Maps Beyond Thousand Modes 50 | 51 | Each Vim mode has its own [mapping-defining commands][mc]: 52 | 53 | * `nmap` for normal mode 54 | * `imap` for insert mode 55 | * `vmap` for visual mode and select mode 56 | * `xmap` for visual mode only 57 | * `cmap` for command-line mode 58 | * `omap` for operator-pending mode 59 | * `tmap` for terminal mode 60 | 61 | Most are self-explanatory. `vmap` defines mappings both for visual mode and 62 | select mode; if you do not know what [select mode][sm] is, it is similar to 63 | visual mode, but hitting a printable key like alphanumerics will replace the 64 | visual selection with that key and switch to insert mode. Select mode is not 65 | often used, so many Vim users do as though it did not exist and use `vmap` to 66 | define visual mode mappings. However, some plugins can take advantage of it, for 67 | instance snippet plugins that expand shortcuts into text or code with parts that 68 | the user may want to change, like some default variable name. Selecting the 69 | variable in select mode enables the user to type the new name "over" the visual 70 | selection, or to hit some control char (eg. ``) to go to the next 71 | occurrence. Unfortunately, if the user made some mappings with `vmap`, this can 72 | interfere with that process, if there are mappings starting with a printable 73 | char like `` or `,`. Thus, it is best to use `xmap` to define mappings 74 | for visual mode (and `smap` for select mode if you ever need it). 75 | 76 | `omap` is for [operator-pending mode][om], ie. the mode where extra key(s) are 77 | expected, typically to define the selection where the current command will 78 | operate. This is the mode you get when you hit `y`, `c` or `d` and Vim waits for 79 | the user to define what will be yanked, changed or deleted. This command 80 | enables the user to create custom text objects of sorts. 81 | 82 | `tmap` lets users define mappings for the new [_terminal mode_][te] of Vim, 83 | available inside a buffer containing a terminal. This requires Vim 8 or the 84 | backported patches. 85 | 86 | Finally, `map` is the original mapping command from vi, and in Vim it 87 | simultaneously defines a mapping for normal, visual and operator-pending mode. 88 | `map!` (also from vi) defines a mapping for insert and command-line mode. The 89 | mode-specific versions (`nmap`, `imap` etc.) are generally preferred in Vim. 90 | 91 | ### GG no RE 92 | 93 | Say you want to extend the `` key in normal mode, so that in addition to 94 | its standard function (going to the first non-blank of the next line), it also 95 | temporarily turns off search highlighting. You come up with the following 96 | command: 97 | 98 | nmap :nohls 99 | 100 | It calls the [`nohls`][nh] Ex command on the command-line, runs it, and then 101 | finally sends a `` to go to the next line. You execute this nmap 102 | command, hit ``, and... Vim seems to hang (hit `` to interrupt). 103 | What gives? 104 | 105 | The `:nohls` part runs just fine, but the issue is the last ``: 106 | remember, the RHS of a mapping runs as though the user typed it. So, when you 107 | hit `` to run the mapping, the mapping will also "hit" `` at the 108 | end--and you get an infinite recursion! 109 | 110 | nmap :nohls 111 | " ^ | 112 | " `---[recursive call]---' 113 | 114 | What you meant, of course, was to execute the core function of the unmapped 115 | ``, not to run the mapping again. In other words, you do not want 116 | mappings to apply in the RHS. That is exactly what the [_noremap_][nr] version 117 | of mapping-defining commands do. Try: 118 | 119 | nnoremap :nohls 120 | " ^ [Built-in ] 121 | 122 | Bingo, everything works as intended. 123 | 124 | Each mapping-creating command has its noremap version: 125 | 126 | * nnoremap 127 | * inoremap 128 | * xnoremap 129 | * cnoremap 130 | * onoremap 131 | * etc. 132 | 133 | As a rule of thumb, use the noremap versions unless you actually need to run 134 | mappings in the RHS. 135 | 136 | ### Modifiers: The Bestiary 137 | 138 | A few modifiers are available to tweak the created mapping; they all have the 139 | form ``, between angle brackets. Here are the main ones; consult the 140 | [documentation][ma] for the whole list. 141 | 142 | #### Silence, RHSling 143 | 144 | [``][si] is one of the most used modifiers. As its name suggests, it 145 | turns off echo area visual feedback for the duration of the mapping execution. 146 | This is especially useful if you run Ex commands in the RHS that you do not want 147 | to expose to the user. For instance: 148 | 149 | xnoremap p p:if v:register == '"'let @@=@0endif 150 | 151 | This extends `p` in visual mode (note the _noremap_), so that if you paste from 152 | the unnamed (ie. default) register, that register content is not replaced by 153 | the visual selection you just pasted over--which is the default behavior. This 154 | enables the user to yank some text and paste it over several places in a row, 155 | without using a named register (eg. `"ay`, `"ap` etc.). 156 | 157 | In the previous mapping, it would be annoying to see the `:if v:register...` 158 | command-line each time you pasted in visual mode; thus, the `` modifier 159 | was added, and nothing is displayed when the mapping runs. As for the rest of 160 | the mapping, the `v:register` special variable contains the name of the 161 | register, if any, that was specified for the current mapping when the user typed 162 | something like `"ap`. The statement `let @@=@0` re-assigns the value of the `@0` 163 | register, which contains the last yanked text, to the unnamed register (`@@`) 164 | once the paste is done. `` stands for the `|` character that separates Ex 165 | commands on the command-line, and `` is another way to get a carriage 166 | return, along with `` or ``. 167 | 168 | #### Don't Map For Me Next Door Neighbor 169 | 170 | [``][bu] makes the new mapping buffer-local, ie. it will be defined only 171 | for the buffer that was current when the mapping was created. It is useful in 172 | filetype-specific settings, eg. in `/.vim/after/ftplugin/help.vim`: 173 | 174 | nnoremap zl 175 | \ :call search('[^ ]\+\''[A-Za-z0-9_-]\{2,}''') 176 | 177 | A buffer-local mapping on `zl` is created on buffers with the `help` filetype, 178 | ie. help buffers created with `:help`. The mapping jumps to the next tag in the 179 | current buffer. Obviously, this only makes sense in help buffers, so we should 180 | not make this mapping global. Again, note the `` modifier, since we 181 | do not want to show this RHS on the command-line each time we use the mapping. 182 | 183 | Here is the backward-jumping version of the mapping: 184 | 185 | nnoremap zh 186 | \ :call search('[^ ]\+\''[A-Za-z0-9_-]\{2,}''','b') 187 | 188 | #### Unique: Is This Seat Taken? 189 | 190 | [``][un] prevents the clobbering of an existing mapping on the same LHS. 191 | This can be useful for plugin authors, who want to offer default mappings but 192 | are still careful not to override the users' own mappings: 193 | 194 | nnoremap a :call ThisPluginFunction() 195 | 196 | If there already is a mapping on `a`, then the `nnoremap` above 197 | will fail (the error can be silenced with `silent! nnoremap ...`). 198 | `` is expanded to some user-defined key, and is typically used for 199 | buffer-local settings. 200 | 201 | #### Expr-esso: What Else To Eval 202 | 203 | [``][ex] changes the meaning of the RHS: it is no longer a sequence of 204 | keys to run like a macro, but an _expression_ that will be evaluated each time 205 | the mapping is triggered, and the result of that evaluation will be a string 206 | containing the key sequence to run. So this is a level of indirection to make 207 | things more dynamic; typically, it contains some conditional to either run one 208 | sequence or the other. Here is an example: 209 | 210 | inoremap jk pumvisible() ? "" : "" 211 | 212 | This is the "classic" `jk` to exit insert mode, with a twist: if the pop-up menu 213 | is visible (this is the menu showing completion candidates), then it will close 214 | it with [``][pe] instead of exiting insert mode with ``. The whole RHS 215 | is a single expression, here, a ternary conditional, and when you hit `jk`, the 216 | expression is evaluated. The [`pumvisible()`][pv] function is called, returning 217 | true or false depending on whether the pop-up menu is visible, and if it is then 218 | the expression evaluates to ``, otherwise it evaluates to ``. The 219 | result of that evaluation becomes the final RHS. 220 | 221 | Here is another example: 222 | 223 | onoremap il ':norm! `['.strpart(getregtype(), 0, 1).'`]' 224 | 225 | This is an operator-pending mapping, that selects the last piece of changed text 226 | (most often, some pasted text), in the same visual mode (char, line or block) as 227 | that of the used register. It can be used to indent some pasted lines, with 228 | `>il`. The `` modifier is necessary to evaluate the `strpart(...)` part of 229 | the RHS; it is then concatenated to the leading and trailing literal strings, to 230 | form the final RHS. 231 | 232 | As for the rest of the mapping: the `` clears the command-line, since in 233 | some circumstances Vim can fill it automatically with a line range, for instance 234 | if `:` is hit from visual mode, or if a count is given to it (either directly, 235 | or from a mapping, eg. if we hit something like `3il`). [`norm`][no] will 236 | execute normal mode commands, and the `!` says not to use mappings in those, 237 | like `noremap`. The [`[` and `]`][bk] marks are automatically set on both ends 238 | of the last changed text, so the first backtick goes to one end, then the right 239 | visual mode is set by the `strpart(...)` part, and the second backtick goes to 240 | the other end of the changed text. Making a visual selection from an 241 | operator-pending mapping defines the object of the current command, eg. `y`, `c` 242 | or `d`. 243 | 244 | ### Escaping and Notation 245 | 246 | #### Better Get Used To These Bars, Kid 247 | 248 | Mapping-defining commands are standard Ex commands, so they can be separated by 249 | the [`|`][ba] character. What do you think the following command will do? 250 | 251 | nnoremap :echo "foo"|echo "bar" 252 | 253 | This command actually contains two parts: first, the `nnoremap` Ex command, then 254 | the `echo "bar"` one. So, it will map `` to `:echo "foo"`, _then_ it will 255 | run `echo "bar"` just once, when the `` mapping is defined. This is probably 256 | not what the user intended! 257 | 258 | In order to map `` to `echo "foo"|echo "bar"`, the `|` character must be 259 | escaped, either with `\|` or with the Vim notation `` (in five characters). 260 | The following two commands do the same thing: 261 | 262 | " same thing 263 | nnoremap :echo "foo"\|echo "bar" 264 | nnoremap :echo "foo"echo "bar" 265 | 266 | Pick your favorite method. Also, do not forget to add a trailing `` to those 267 | mappings if you want to run the command-line when calling the mappings; 268 | otherwise, they will stay there on the command-line, for the user to modify, run 269 | or cancel. 270 | 271 | #### We Don't Need No True Control 272 | 273 | You can use literal control chars in your mappings, by hitting [``][cv] 274 | followed by the control char in insert or command-line mode. For instance, 275 | pressing `` then the tab key will insert a literal tab char (you can use 276 | `:set list` to show them). However, it is not very convenient to work with 277 | literal control chars: the graphic representation can be confusing (eg. `^[` for 278 | the Escape key), and it can insert a terminal-dependent sequence, like `^[OP`, 279 | instead of the generic character (here, the F1 key). 280 | 281 | The best practice is to use the Vim notation like we did so far, eg. ``, in 282 | five chars (`<` + `C` + `-` + `x` + `>`), for Control+x. All keys have a 283 | notation, eg. ``, `` or ``; check out the [documentation][vn] 284 | for the complete list. 285 | 286 | Vim will expand Vim notation in mappings commands as long as the `<` flag does 287 | not appear in [`'cpoptions'`][cp] (it does not by default). You can also include 288 | the Vim notation in strings by prepending the notation with a backslash, eg. 289 | `"\"` is a string that will contain a single literal tab when the code is 290 | evaluated. 291 | 292 | #### And All The Keys That Lead You There Were Mappings 293 | 294 | As a convenience, Vim provides two customizable Vim notation expansions that you 295 | can use in your mappings: [``][le] and [``][ll]. You can 296 | set their value via the `mapleader` and `maplocalleader` global variables, eg.: 297 | 298 | let mapleader = "\" 299 | let maplocalleader = "&" 300 | 301 | You can then use it like this: 302 | 303 | nnoremap w :w 304 | 305 | Now hitting `w` will save the current buffer. 306 | 307 | It is called "leader" as it is generally the first key of a group of mappings. A 308 | common setting is to undefine the original behavior of that key, so that it does 309 | not interfer or trigger inadvertently: 310 | 311 | nnoremap 312 | 313 | Leader and LocalLeader let the user change several mappings at one fell swoop 314 | just by changing the value of the `mapleader` and `maplocalleader` variables, 315 | but this is not something that occurs very often. Also, note that the mechanism 316 | is a mere convenience: `` is expanded in mapping commands, but not 317 | elsewhere. This does *NOT* echo a space: 318 | 319 | " No expansion: echoes `` in eight chars 320 | :echo "\" 321 | 322 | And if you show a mapping containing Leader, you will see its expansion (the 323 | `` character): 324 | 325 | :nmap w 326 | 327 | So all in all, there is not a whole lot to gain with Leader and LocalLeader, 328 | but they at least show intent, and it can make it easier to search through your 329 | Vim files for mappings. 330 | 331 | ## Vimtorinox Knife 332 | 333 | Here are some more tools to use for your own mappings. 334 | 335 | ### Follow The Yellow Power Cord 336 | 337 | In a nutshell, [``][pg] is a Vim notation for some special key sequence 338 | _that the user cannot type_. What use is it? 339 | 340 | Imagine you are a plugin author and you have a complicated mapping whose LHS 341 | must be customizable by the user. The first option is to instruct them in the 342 | documentation to copy that complicated mapping in their vimrc and just change 343 | the LHS to their liking. This will work, but the user will have to deal with the 344 | internals of your plugin, which is not ideal. And if you want to change the RHS 345 | in a later version, your users will have to update their own version of the 346 | mapping. 347 | 348 | The second option is to make an indirection: create a mapping to your 349 | internal RHS from a simple, intermediate LHS, and expose that LHS in your 350 | documentation. The user will then be able to map his own LHS to your simple LHS, 351 | and your implementation details will not be exposed. An example will make this 352 | clear: 353 | 354 | " ]&MyPluginIndentLine is an intermediate, "pivot" LHS/RHS 355 | nnoremap ]&MyPluginIndentLine ...internal RHS... 356 | nmap f ]&MyPluginIndentLine 357 | 358 | Now, if the user want to change the default mapping from `f` to 359 | something else, say, ``, they will just need to add this in their 360 | vimrc: 361 | 362 | nmap ]&MyPluginIndentLine 363 | 364 | Then, if you later modify the internal RHS part, your users will not have to 365 | change anything. Note that all mappings except the one to the internal RHS are 366 | _not_ of the noremap family, since we do want all mappings to chain together; 367 | `nnoremap` in any of those would break the chain. 368 | 369 | This setting would work, but there is a flaw: the intermediate LHS, namely 370 | `]&MyPluginIndentLine`, interfers with normal usage. We paid attention to use a 371 | leading sequence of `]&` that is not mapped by default, but the user might well 372 | have created a mapping on that very sequence--and now, each time they will hit 373 | `]&`, there will be a slight delay while Vim waits for the duration of the 374 | timeout to see if it should run `]&` or `]&MyPluginIndentLine`. 375 | 376 | That is where `` comes in handy: since it is not a sequence made from 377 | normal keys, it gets totally out of the way of other mappings that do not use 378 | `` themselves. To use it, just replace the arbitrary `]&` prefix above 379 | with ``: 380 | 381 | nnoremap MyPluginIndentLine ...internal RHS... 382 | nmap f MyPluginIndentLine 383 | 384 | And in the user's vimrc: 385 | 386 | nmap MyPluginIndentLine 387 | 388 | Note that the RHS comprises, firstly, the expansion of `` (we do not need 389 | to know what it is exactly, though you can get an idea with `:echo "\"`), 390 | followed by all the individual letters of "MyPluginIndentLine". This is not some 391 | special command-line or function-invoking mode! Therefore, conflicts could 392 | theoretically arise. Suppose we write a snippet plugin with these two mappings: 393 | 394 | nnoremap MyPluginFor ...internal RHS 1... 395 | nnoremap MyPluginFori ...internal RHS 2... 396 | 397 | The first mapping might insert some for-loop snippet, and the second one could 398 | insert a for-loop variant that uses a variable called 'i'. 399 | 400 | So far so good, but now a user finds it convenient to go into insert mode right 401 | after inserting the first for-loop variant, and they want to make a mapping for 402 | it: 403 | 404 | " Intent: call MyPluginFor and hit 'i' to go into insert mode 405 | nmap i MyPluginFori 406 | 407 | You see the problem: the mapping will inadvertently call the wrong mapping from 408 | our snippet plugin! 409 | 410 | Even though those cases are rare, it is common practice to avoid them altogether 411 | by surrounding the part after `` in braces: 412 | 413 | nmap f (MyPluginFor) 414 | nmap (MyPluginFori) 415 | 416 | In user's vimrc: 417 | 418 | " Intent: call (MyPluginFor) and hit 'i' to go into insert mode 419 | nmap i (MyPluginFor)i 420 | 421 | Problem solved. 422 | 423 | ### Situation Under Control-R 424 | 425 | [``][cr] inserts the content of a register from insert and command-line 426 | mode. It is notably useful in visual mode, when the mapping needs to work with 427 | the selection eg.: 428 | 429 | xnoremap gf y:pedit " 430 | 431 | This will open the filename expected in the visual selection into the preview 432 | window. Doubling the `` inserts the content literally, in case there were 433 | some control characters in the filename that might be interpreted by Vim. 434 | 435 | The expression register can also be used, opening some interesting 436 | possibilities: 437 | 438 | inoremap [=strftime("%d/%b/%y %H:%M")] 439 | 440 | That mapping will insert the current date an time between brackets. 441 | 442 | Another example, from command-line mode: 443 | 444 | cnoremap _ =split(histget('cmd', -1))[-1] 445 | 446 | This will insert the last space-separated word from the last command-line, as 447 | `` in Bash. 448 | 449 | `` can also insert text present under the current cursor position, when 450 | followed by some control characters. Here is an example with ``, which 451 | inserts the filename under the cursor: 452 | 453 | nnoremap gf :pedit 454 | 455 | This is the normal mode version of the preview mapping we saw above (the 456 | filename recognition will depend on the [`'isfname'`][if] option). Note that on 457 | the command-line, for a command where a filename is expected (like `:e`), you 458 | can also use a few special Vim notations to similar effects, eg. `` will 459 | insert on the command-line the filename under the cursor, and `` will 460 | insert the current word. If a filename is not expected, you can always use 461 | [`expand()`][ed] like this: 462 | 463 | nnoremap c :let @/=expand('')cgn 464 | 465 | This mapping sets the last search pattern to the word under the cursor, and 466 | changes it with the `cgn` sequence--making the whole thing conveniently 467 | repeatable with `.` to apply the same replacement to some following occurrences. 468 | 469 | ### The Mushroom Register: @= 470 | 471 | The [`@`][at] key executes the content of a register, and once again the 472 | expression register offers a good deal of flexibility. As an example, consider 473 | the [``][ca] normal mode command, that increases the number under the 474 | cursor or the closest number on its right, on the same line, if any. A common 475 | annoyance is words like `file-1.txt`: hitting `` will turn it to 476 | `file0.txt`, to the surprise of many users, as Vim assumes the next number is 477 | '-1', not '1'. Let's write a mapping to change this behavior. 478 | 479 | function! Increment() abort 480 | call search('\d\@" 485 | return '' 486 | endfun 487 | 488 | The `Increment()` function finds the sequence of digits under the cursor or 489 | following it, then selects it in visual mode, and finally runs `` on it. 490 | The visual mode version of `` is a relatively recent addition, so Vim 8 or 491 | a late Vim 7 version is required. Now, let's remap the normal mode `` to 492 | our function: 493 | 494 | nnoremap @=Increment() 495 | 496 | The effect is to execute the `Increment()` function in the expression register, 497 | which as we saw increases the next number ignoring leading minuses and returns 498 | the empty string--leaving nothing to do for the `@` command, since the job is 499 | already done. 500 | 501 | At first glance, this might just look like a fancy alternative to `:call 502 | Increment()`. There is a nice bonus to it, though: our mapping now accepts a 503 | _count_, so that we can type `3` to add three to the next number. This is 504 | not something we could do with the `:call` version, at least not without adding 505 | more code to deal with the count. 506 | 507 | ### Feeding Frenzy 508 | 509 | The built-in [`feedkeys()`][fk] function inserts keys into the internal Vim 510 | buffer containing all keys left to execute, either typed by the user or coming 511 | from mappings. This can sound somewhat low-level, but it is a very useful tool. 512 | 513 | nnoremap :call feedkeys(nr2char(getchar()),'nt') 514 | 515 | This mapping waits for a key after hitting `` and executes it, ignoring any 516 | mapping for that key -- a kind of "just-once-noremap". `getchar()` is first 517 | executed: it waits for the user to hit a key, and returns its keycode. 518 | `nr2char()` converts that keycode into a character, and `feedkeys()` puts that 519 | key into the Vim internal buffer; the 'nt' options says not to use mappings, and 520 | to process the key as though the user typed it. Even though it remaps the useful 521 | `` built-in, it instantly makes it available again on ``. 522 | 523 | Here's a longer example (inspired from igemnace on #vim): 524 | 525 | function! QuickBuffer(pattern) abort 526 | if empty(a:pattern) 527 | call feedkeys(":B \") 528 | return 529 | elseif a:pattern is '*' 530 | call feedkeys(":ls!\:B ") 531 | return 532 | elseif a:pattern =~ '^\d\+$' 533 | execute 'buffer' a:pattern 534 | return 535 | endif 536 | let l:globbed = '*' . join(split(a:pattern, ' '), '*') . '*' 537 | try 538 | execute 'buffer' l:globbed 539 | catch 540 | call feedkeys(':B ' . l:globbed . "\\B " . a:pattern) 541 | endtry 542 | endfun 543 | 544 | command! -nargs=* -complete=buffer B call QuickBuffer() 545 | 546 | nnoremap b :B 547 | 548 | Hitting `b` will run the user-defined Ex command `B`, which will in turn 549 | call the `QuickBuffer()` function. When the latter is called without argument, 550 | it will run `feedkeys(":B \")`, with the effect of listing the completion 551 | options of the `B` command -- that is, showing the list of buffers, thanks to 552 | the `-complete=buffer` option of `B`. The `:B` is still on the command-line, so 553 | now the user can pick its choice by entering a part of the wanted buffer 554 | filename. All the conditionals of the `QuickBuffer()` function will be skipped, 555 | and the [`buffer`][bf] Ex command inside the try block will be run on the 556 | argument with leading and trailing wildcards automatically added. If there is a 557 | single match, the buffer will be displayed and the function ends. If there is no 558 | match or more than one match, the choices will be shown and the `:B` will be put 559 | back on the commnd-line (in the 'catch' block). 560 | 561 | The first `elseif` allows for `:B *` to show a full `:ls!` listing, with hidden 562 | buffers. The second `elseif` lets the user select a buffer by number, eg. 563 | `:B 2`, skipping all wildcards addition. 564 | 565 | ## Lazy And Gentlemen, Let's Jump To The Conclusion 566 | 567 | While a few mappings into your vimrc are quick to process, a larger amount of 568 | them can take its toll on the overall startup time. Quite often, a group of 569 | related mappings share a common prefix, eg. `x`; these mappings can deal 570 | with some specific task, tool or plugin -- something that you might not use 571 | every time you run Vim. In other words, they stand out as prime candidates for 572 | _lazy loading_, and that is what we will do in this final example. 573 | 574 | [vim-flattery](https://github.com/fcpg/vim-flattery) is a plugin of mine 575 | (shameless ``!) that overrides the `f` key so as to provide new targets on 576 | the alpha characters: for instance, `fu` will jump to the next uppercase letter 577 | on the current line, instead of jumping to the next 'u' letter. Not all letters 578 | are overridden though, and the user can also choose which ones they want; for 579 | the others, the key falls back to the default `f` built-in. 580 | 581 | The design choice was to create a `` mapping for each new target provided 582 | by the plugin. This makes things easy to customize for the user, but it also 583 | means creating quite a few mappings, all duplicated for `f` and `t`. 584 | Lazy-loading them could definitely save some time during startup. 585 | 586 | The initialization goes like this: 587 | 588 | " in plugin/flattery.vim 589 | if get(g:, 'flattery_autoload', 1) 590 | for op in [s:flattery_f_map, s:flattery_t_map] 591 | for cmd in ['nm', 'xm', 'om'] 592 | exe cmd '' op 593 | \ 'FlatteryLoad("'.op.'")' 594 | exe cmd '' '(flattery)'.op 595 | \ op 596 | endfor 597 | endfor 598 | else 599 | call flattery#SetPlugMaps() 600 | call flattery#SetUserMaps() 601 | endif 602 | 603 | If the `g:flattery_autoload` variable is true or does not exist, this code will 604 | create a mapping on `s:flattery_f_map` and `s:flattery_t_map` (script-local 605 | variables containing `"f"` and `"t"` by default) to some `FlatteryLoad()` 606 | function. This is similar to this: 607 | 608 | nmap f FlatteryLoad("f") 609 | nmap t FlatteryLoad("t") 610 | 611 | nmap (flattery)f f 612 | nmap (flattery)t t 613 | 614 | This is done for normal, visual and operator-pending mode. The `` mappings 615 | make it possible for the user to map them to what they want without setting 616 | variables, and still benefit from lazy loading if needed. 617 | 618 | Here is the `FlatteryLoad()` function: 619 | 620 | " in plugin/flattery.vim 621 | function! FlatteryLoad(o) abort 622 | call flattery#SetPlugMaps() 623 | call flattery#SetUserMaps() 624 | for op in [s:flattery_f_map, s:flattery_t_map] 625 | for cmd in ['nun', 'xu', 'ou'] 626 | exe cmd op 627 | endfor 628 | endfor 629 | return "\(flattery)".a:o 630 | endfun 631 | 632 | It calls the autoloaded `flattery#SetPlugMaps()` and `flattery#SetUserMaps()` 633 | functions, which sets all the plugin mappings starting with `f` and `t` eg. 634 | `fa`, `fb`, `fu` etc. Then, it unmaps the initial "lazy-loader" mappings (those 635 | who called this very function) for all modes, as the loading has just been done. 636 | Finally, it returns a string containing a `` mapping that will be 637 | processed as an RHS, since the mapping that called the `FlatteryLoad()` function 638 | had the `` modifier. Consequently, the intended mapping will be executed. 639 | 640 | With some effort, that mechanism can be made generic, and it can also load 641 | plugins on demand, for instance with the Vim 8 package management. That is how 642 | my current setting works, and it might be the topic of a following article. 643 | 644 | Until then, merry xmaps to all! 645 | 646 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 647 | 648 | [q]: http://vimhelp.appspot.com/repeat.txt.html#q 649 | [em]: http://vimhelp.appspot.com/intro.txt.html#vim-modes 650 | [mc]: http://vimhelp.appspot.com/map.txt.html#%3Amap-commands 651 | [sm]: http://vimhelp.appspot.com/visual.txt.html#Select-mode 652 | [om]: http://vimhelp.appspot.com/intro.txt.html#Operator-pending 653 | [te]: http://vimhelp.appspot.com/terminal.txt.html#terminal 654 | [nh]: http://vimhelp.appspot.com/pattern.txt.html#%3Anohlsearch 655 | [nr]: http://vimhelp.appspot.com/map.txt.html#%3Anore 656 | [ma]: http://vimhelp.appspot.com/map.txt.html#%3Amap-arguments 657 | [si]: http://vimhelp.appspot.com/map.txt.html#%3Amap-silent 658 | [bu]: http://vimhelp.appspot.com/map.txt.html#%3Amap-local 659 | [un]: http://vimhelp.appspot.com/map.txt.html#%3Amap-%3Cunique%3E 660 | [ex]: http://vimhelp.appspot.com/map.txt.html#%3Amap-expression 661 | [pe]: http://vimhelp.appspot.com/map.txt.html#popupmenu-keys 662 | [pv]: http://vimhelp.appspot.com/eval.txt.html#pumvisible%28%29 663 | [no]: http://vimhelp.appspot.com/various.txt.html#%3Anorm 664 | [bk]: http://vimhelp.appspot.com/motion.txt.html#%27%5B 665 | [ba]: http://vimhelp.appspot.com/cmdline.txt.html#%3Abar 666 | [cv]: http://vimhelp.appspot.com/insert.txt.html#i_CTRL-V 667 | [vn]: http://vimhelp.appspot.com/intro.txt.html#key-notation 668 | [cp]: http://vimhelp.appspot.com/options.txt.html#%27cpo%27 669 | [le]: http://vimhelp.appspot.com/map.txt.html#mapleader 670 | [ll]: http://vimhelp.appspot.com/map.txt.html#maplocalleader 671 | [pg]: http://vimhelp.appspot.com/map.txt.html#%3CPlug%3E 672 | [cr]: http://vimhelp.appspot.com/cmdline.txt.html#c_%3CC-R%3E 673 | [if]: http://vimhelp.appspot.com/options.txt.html#%27isf%27 674 | [ed]: http://vimhelp.appspot.com/eval.txt.html#expand%28%29 675 | [at]: http://vimhelp.appspot.com/repeat.txt.html#%40 676 | [ca]: http://vimhelp.appspot.com/change.txt.html#CTRL-A 677 | [fk]: http://vimhelp.appspot.com/eval.txt.html#feedkeys%28%29 678 | [bf]: http://vimhelp.appspot.com/windows.txt.html#%3Abuffer 679 | 680 | * Name: Markzen 681 | * Email: gh nick at proton mail 682 | * GitHub: fcpg 683 | 684 | --------------------------------------------------------------------------------