Continuing in the same theme as my last post, I am going to write about another awesome Vim tip I found recently. That is: automatic capitalisation of the first letter of each sentence.
Many word processors already include this. For some reason, Vim and other text editors don't usually have the option. I don't know why. I feel that a lot of unnecessary key-strokes are wasted on the shift keys. It also stresses the little fingers and twists the wrists.
There is surprisingly little information available about this. The only decent thing I've ever found has been this superuser question/answer. There are two answers, both of which I like:
for char in split('abcdefghijklmnopqrstuvwxyz', '\zs') exe printf("inoremap <expr> %s search('[.!?]\\_s\\+\\%%#', 'bcnw') ? '%s' : '%s'", char, toupper(char), char) endfor
The above script creates insert mode mappings for every lower-case letter. It uses the
<expr> option to evaluate an expression instead of mapping directly to another key. That expression then searches backwards to check for certain punctuation. It then returns either the original character or the upper-case version of the character if a match was found.
I like it but I prefer the second answer:
augroup SENTENCES au! autocmd InsertCharPre * if search('\v(%^|[.!?]\_s)\_s*%#', 'bcnw') != 0 | let v:char = toupper(v:char) | endif augroup END
Here, instead of creating mappings, an auto-command is used. It "listens" for characters to be inserted and intercepts them. It searches backwards for punctuation and converts the character to be inserted (
v:char) to upper-case. The
au! stuff is just for grouping and isn't terribly important.
If you want to bypass the script(s) and force a lower-case letter to be inserted, you can press Ctrl+V followed by the letter.
I'll be the first to admit that my Vimscript isn't the best. Nevertheless, I still wanted to change a few things based on the way I type. I wanted to add a few more contexts in which letters get capitalised:
- Markdown list items
- YAML front matter titles
- New paragraphs (which happen to come after something that doesn't end in a punctuation mark)
Here is my tweaked script:
augroup SENTENCES au! autocmd InsertCharPre * if search('\v(%^|[.!?]\_s+|\_^\-\s|\_^title\:\s|\n\n)%#', 'bcnw') != 0 | let v:char = toupper(v:char) | endif augroup END
It can be tricky to figure out what's going on in a regular expression like that. I think the Vim documentation on patterns is pretty helpful. Here is a very brief summary of what's going on:
\vmeans that the following pattern is "very magic" so not as many characters need to be escaped
%^is the start of the file
[.!?]\_s+is a punctuation mark followed by some whitespace which may or may not include newlines
\_^\-\sis a dash at the start of a line followed by a whitespace character
\_^title\:\sis "title: " at the start of a line
\n\nmatches two newlines
%#matches the cursor position
'b'option means search backwards
[Edit on 2014-09-29: I somehow forgot to add Markdown headers to the list of capitalisation contexts. I will leave that as an exercise for the reader.]
Putting it all together
Combining this with the rest of my configuration options gives the following section of my
func! WordProcessorMode() " Load Markdown syntax highlighting but with custom hashtag support set filetype=mkd syn match htmlBoldItalic "#[-_a-zA-Z0-9]\+" " Other options set nonumber set wrap set linebreak set breakat=\ set display=lastline set tabstop=4 set softtabstop=4 set shiftwidth=4 set formatoptions= set spell spelllang=en_gb source ~/.vim/abbreviations.vim " Auto-capitalize script augroup SENTENCES au! autocmd InsertCharPre * if search('\v(%^|[.!?]\_s+|\_^\-\s|\_^title\:\s|\n\n)%#', 'bcnw') != 0 | let v:char = toupper(v:char) | endif augroup END endfu com! WP call WordProcessorMode() au BufNewFile,BufRead *.mkd call WordProcessorMode() au BufNewFile,BufRead *.md call WordProcessorMode() au BufNewFile,BufRead diary-*.txt call WordProcessorMode()