Vim Tips Wiki
Line 11: Line 11:
 
The following script can be placed in your [[vimrc]].
 
The following script can be placed in your [[vimrc]].
 
<pre>
 
<pre>
let g:AlreadyChecked=0 "So startinsert won't trigger autocmd
+
let g:AlreadyChecked=0 "Prevents startinsert from triggering autocmd
 
function! WaitForKey(prev)
 
function! WaitForKey(prev)
 
let input=nr2char(getchar()) "wait for keystroke
echo "-- AUTOCAP --"
 
  +
if a:prev=~'\s' "blank space
let input=nr2char(getchar())
 
 
exe "noautocmd normal! a" . toupper(input)
if a:prev=~'\s'
 
 
elseif a:prev=~"\r" "return
exe "normal! a" . toupper(input)
 
 
exe "noautocmd normal! i" . toupper(input)
elseif a:prev=~"\r"
 
exe "normal! i" . toupper(input)
 
 
else
 
else
exe "normal! a" . input
+
exe "noautocmd normal! a" . input
 
endif
 
endif
 
redraw
 
redraw
if input=~'[.?!]\|\s\|\r' "punctuation or spaces
+
if input=~'[.?!\r[:blank:]]' "punctuations, return, spaces
call WaitForKey(input)
+
call WaitForKey(input) "recurse
 
else
 
else
normal! l
 
 
let g:AlreadyChecked=1
 
let g:AlreadyChecked=1
if col(".")==col("$")-1 "if at end of line
+
let fromend=col("$")-col(".")
  +
if a:prev=~'\r' && (input=='' || input=~'\e')
startinsert! "normal A
 
 
noautocmd normal! x
 
else
 
else
startinsert "normal i
+
noautocmd normal! f_x
  +
endif
  +
if fromend==2 "cursor was 2 from eol, eg, 'tex[t]_'
  +
startinsert! "append to line ('A')
 
else
 
startinsert "insert ('i')
 
endif
 
endif
 
endif
 
endif
 
endfu
 
endfu
  +
function! CheckThisLine()
 
  +
if col(".")==1 "Check for '[t]ext.text'
function! CheckCap() "Capitalize depending on preceding chars
 
  +
noautocmd normal! i_
if g:AlreadyChecked
 
  +
redraw
let g:AlreadyChecked=0
 
elseif col(".")==1
 
 
call WaitForKey("\r")
 
call WaitForKey("\r")
  +
else "Check for 'text.[t]ext'
else
 
let trunc=strpart(getline("."),0,col("."))
+
let trunc=strpart(getline("."),0,col(".")-1)
if trunc=~'[?!.]\s*$' "for example, "abcd? "
+
if trunc=~'[?!.]\s*$\|^\s*$'
  +
if col(".")==col("$") "eol special case
  +
noautocmd normal! A_
  +
else
  +
noautocmd normal! i_
  +
endif
  +
noautocmd normal! h
  +
redraw
 
call WaitForKey(trunc[len(trunc)-1])
 
call WaitForKey(trunc[len(trunc)-1])
 
endif
 
endif
 
endif
 
endif
 
endfu
 
endfu
 
inoremap <silent> . ._<C-C>h:call WaitForKey(".")<CR>
 
inoremap <silent> . .<Esc>:call WaitForKey(".")<CR>
+
inoremap <silent> ? ?_<C-C>h:call WaitForKey("?")<CR>
inoremap <silent> ? ?<Esc>:call WaitForKey("?")<CR>
+
inoremap <silent> ! !_<C-C>h:call WaitForKey("!")<CR>
inoremap <silent> ! !<Esc>:call WaitForKey("!")<CR>
+
inoremap <silent> <CR> <CR>_<C-C>:call WaitForKey("<C-V><C-M>")<CR>
  +
autocmd InsertEnter * if !g:AlreadyChecked | call CheckThisLine() | endif
inoremap <silent> <CR> <CR><Esc>:call WaitForKey("<C-V><C-M>")<CR>
 
autocmd InsertEnter * redraw | call CheckCap()
+
autocmd InsertLeave * let g:AlreadyChecked = 0
 
</pre>
 
</pre>
   

Revision as of 16:36, 6 March 2012

Autocapitalizing the start of every sentence, a common function in cell phones, is unexpectedly nontrivial. 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 ., ?, !, etc and to catch subsequent keypresses with getchar().

The key Vim commands here are:

  • getchar – wait for a single keypress, return the numeric key code
  • startinsert – start insert mode after the interrupt as to not disrupt flow
  • autocmd InsertEnter – trigger capitalization routine when user enters input mode

The basic idea is fairly simple, but some logical manipulations are required to handle the beginnings and the endings of lines because of how exiting from input mode works (cursor sometimes moves back one character). Some further complexity arises from ensuring the input loop isn't triggered multiple times.

Script

The following script can be placed in your vimrc.

let g:AlreadyChecked=0 "Prevents startinsert from triggering autocmd
function! WaitForKey(prev)
	let input=nr2char(getchar()) "wait for keystroke
	if a:prev=~'\s'              "blank space
		exe "noautocmd normal! a" . toupper(input)
	elseif a:prev=~"\r"          "return
		exe "noautocmd normal! i" . toupper(input)
	else 
		exe "noautocmd normal! a" . input
	endif
	redraw
	if input=~'[.?!\r[:blank:]]' "punctuations, return, spaces
		call WaitForKey(input)   "recurse
	else
		let g:AlreadyChecked=1
		let fromend=col("$")-col(".")
		if a:prev=~'\r' && (input=='' || input=~'\e')
			noautocmd normal! x
		else
			noautocmd normal! f_x
		endif
		if fromend==2  "cursor was 2 from eol, eg, 'tex[t]_'
			startinsert!         "append to line ('A')
		else
			startinsert          "insert ('i')
		endif
	endif
endfu
function! CheckThisLine()
	if col(".")==1               "Check for '[t]ext.text'
		noautocmd normal! i_
		redraw
		call WaitForKey("\r")
	else                         "Check for 'text.[t]ext'
		let trunc=strpart(getline("."),0,col(".")-1)
		if trunc=~'[?!.]\s*$\|^\s*$'
			if col(".")==col("$") "eol special case
				noautocmd normal! A_
			else
				noautocmd normal! i_
			endif
			noautocmd normal! h
			redraw
			call WaitForKey(trunc[len(trunc)-1])
		endif
	endif
endfu
inoremap <silent> . ._<C-C>h:call WaitForKey(".")<CR>
inoremap <silent> ? ?_<C-C>h:call WaitForKey("?")<CR>
inoremap <silent> ! !_<C-C>h:call WaitForKey("!")<CR>
inoremap <silent> <CR> <CR>_<C-C>:call WaitForKey("<C-V><C-M>")<CR>
autocmd InsertEnter * if !g:AlreadyChecked | call CheckThisLine() | endif
autocmd InsertLeave * let g:AlreadyChecked = 0 

Comments

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