Vim Tips Wiki
Tip 315 Printable Monobook Previous Next

created 2002 · complexity advanced · version 7.0

With "smart home", pressing the Home key moves the cursor to the first nonblank character on the line, or, if already at that position, to the start of the line.

Simple mappings[]

The following lines (in your vimrc) implement smart home for normal, visual, operator pending, and insert modes.

noremap <expr> <silent> <Home> col('.') == match(getline('.'),'\S')+1 ? '0' : '^'
imap <silent> <Home> <C-O><Home>

The first line is an expression mapping for normal, visual, and operator pending modes. The right-hand side of the mapping is an expression that is evaluated each time Home is pressed. The expression gives a string, and the result is as if the characters in the string had been pressed. The expression compares col('.') (the cursor column position, where 1 is the first column) and match(getline('.'),'\S')+1 (the index of the first non-whitespace character in the current line; 1 is added because the index starts at 0). If both sides of "==" are equal, the result is '0' (move to start of line); otherwise it is '^' (move to first nonblank character).

In insert mode, the second line applies and if Home is pressed the result is Ctrl-O followed by Home. In insert mode, pressing Ctrl-O executes what follows as a normal-mode command, so the Home which follows invokes the normal mode mapping.

The simple mappings have two deficiencies:

  • If there are no non-whitespace characters, match() returns −1 which becomes 0 after adding 1, and that value never equals the result from col('.'). Therefore the mapping always performs ^.
  • The mapping does not account for the case where the 'wrap' option is on and long lines are wrapped. In that case, pressing Home will jump to the first nonblank character or the start of the line, whereas moving to the start of the screen line may be wanted.

More features[]

If lines are not wrapped, the following operates in the same manner as the above simple mappings, except that it works correctly on a line which consists only of whitespace characters.

If options 'wrap' and 'linebreak' are on, long lines will wrap and wrapped lines will start with a non-whitespace character. In that case, if the cursor is on a character in the middle of a wrapped line, pressing Home will move to the start of that screen line (not the start of the line of text in the buffer). Pressing Home again will move to the first nonblank character of the line in the buffer (on a different screen line). Pressing Home a third time will move to the start of the line.

function! SmartHome()
  let first_nonblank = match(getline('.'), '\S') + 1
  if first_nonblank == 0
    return col('.') + 1 >= col('$') ? '0' : '^'
  if col('.') == first_nonblank
    return '0'  " if at first nonblank, go to start line
  return &wrap && wincol() > 1 ? 'g^' : '^'
noremap <expr> <silent> <Home> SmartHome()
imap <silent> <Home> <C-O><Home>

See also[]

  • homeLikeVC++.vim provides smart Home and smart End using @= rather than <expr>, which should work on versions prior to Vim 7


The following provides smart Home and smart End and should work properly in normal mode, visual mode, insert mode, select mode, and operator-pending mode:

noremap <expr> <Home> (col('.') == matchend(getline('.'), '^\s*')+1 ? '0' : '^')
noremap <expr> <End> (col('.') == match(getline('.'), '\s*$') ? '$' : 'g_')
vnoremap <expr> <End> (col('.') == match(getline('.'), '\s*$') ? '$h' : 'g_')
imap <Home> <C-o><Home>
imap <End> <C-o><End>

Would be nice for the "more features" section (triple-use home key) to be expanded to include the same functionality for the End key 21:21, November 19, 2015 (UTC)