Vim Tips Wiki

Proposed tip Please edit this page to improve it, or add your comments below (do not use the discussion page).

Please use new tips to discuss whether this page should be a permanent tip, or whether it should be merged to an existing tip.
created March 3, 2012 · complexity basic · version 7.0

Autocapitalizing the start of every sentence, a common function in cell phones, is not as trivial as one would think. Approaches such as mapping ". a" to ". A" in insert mode, using abbreviate, do not work, produce unexpected delay, or require extra keypresses. The strategy here is to interrupt insert mode on certain key presses, such as ., ?, !, as well as commands that start insert mode (a, A, i, o, etc.) and to catch subsequent keypresses with getchar().

Note that his script will load the relevant key mappings, via an autocommand, only when a file ending with *.txt is being edited.

The Vim cuteness here consists of:

  • getchar – wait for a single keypress, return the numeric key code. One side effect of this function is that pressing Esc will always produce a brief delay, since the function sees Esc as a modifier. To skip this delay, you can perhaps modify the script slightly to also accept, say, "@" as cancel.
  • Ctrl-R= - In insert mode, pressing Ctr-R and then = allows one to execute a function, insert the result, and then return to insert mode. This trick is used to return to insert mode. An alternative would be to use "startinsert", but one nice effect of the Ctrl-R method is that abbreviations (eg, "ab im I'm") still work! (Vim regards using a normal command and then starting insert as an interruption and won't trigger abbreviations. However, using Ctrl-R to call a function that executes normal commands is an odd loophole.)
  • "/<Del>" - It is possible using Ctrl-R to "insert" a delete command. One way this is helpful is that one can avoid various end-of-line complications by inserting this buffer character, "_".


The following script can be placed in your vimrc. (Pressing escape cancels capitalize)

fun! CapWait(prev)
	let next=nr2char(getchar())
	if next=~'[.?!\r\n[:blank:]()]'
		exe 'normal! i' . next . "\<Right>"
		return CapWait(next)
	elseif next=~'[\e]'
		return "\<del>"
	elseif a:prev=~'[\r\n[:blank:]]'
		return toupper(next) . "\<del>"
		return next . "\<del>"
fun! CapHere()
	let trunc = getline(".")[0:col(".")-2] 
	return col(".")==1 ? CapWait("\r")
	\ : trunc=~'[?!.]\s*$\|^\s*$' ? CapWait(trunc[-1:-1]) : "\<del>"
fun! InitTextFile()
	im <buffer> <silent> . ._<Left><C-R>=CapWait('.')<CR>
	im <buffer> <silent> ? ?_<Left><C-R>=CapWait('?')<CR>
	im <buffer> <silent> ! !_<Left><C-R>=CapWait('!')<CR>
	im <buffer> <silent> <CR> <CR>_<Left><C-R>=CapWait("\r")<R>
	im <buffer> <silent> <NL> <NL>_<Left><C-R>=CapWait("\n")<CR>
	nm <buffer> <silent> O O_<Left><C-R>=CapWait("\r")<CR>
	nm <buffer> <silent> o o_<Left><C-R>=CapWait("\r")<CR>
	nm <buffer> <silent> a a_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> A $a_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> i i_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> I I_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> s s_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> cc cc_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> cw cw_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> R R_<Left><C-R>=CapHere()<CR>
	nm <buffer> <silent> C C_<Left><C-R>=CapHere()<CR>
au BufNewFile,BufRead *.txt call InitTextFile()


Amazing. I have not read the code, but I guess it would always be active? Wouldn't you want a way to turn it on/off? JohnBeckett 01:00, March 4, 2012 (UTC)

Yeah, you're right, it's always active, and a future version should probably include an easy way to unmap and remove the autocommands, although that might increase the complexity even more. I use my vim on my cell phone a lot, and I'm not really sure how common that is and how useful people may find this, but it might be interesting to some as a way to use getchar() to create a "new mode" or a more interactive vim. -- q335r49

I agree with this.