Vim Tips Wiki
Register
Advertisement
Tip 1574 Printable Monobook Previous Next

created 2008 · complexity basic · author Sightless · version 7.0


Often when you enter a blank line by pressing Enter twice in insert mode, Vim removes the indent from the blank line and your cursor moves back to column 1. Likewise, if you position the cursor on a blank line and press letter o to open a new line below it, Vim does not open that line with any indent. Often, though, you want to use the same indent as the preceding non-blank line.

There are two ways to achieve this. If you can tolerate lines in your files that contain only whitespace, you can simply put this in your vimrc:

:inoremap <CR> <CR>x<BS>

Vim doesn't delete the indent if you type something, even if you delete that something, and this mapping simulates your typing something even if you didn't, and deletes it before proceeding to the next line! This approach is not recommended, though, as it is generally considered poor practice to have lines that contain only whitespace.

Another approach is to save the following in a .vim file in your ~/.vim/plugin (or $HOME/vimfiles/plugin on Windows) directory, or add it to your vimrc:

function! IndentIgnoringBlanks(child)
  let lnum = v:lnum
  while v:lnum > 1 && getline(v:lnum-1) == ""
    normal k
    let v:lnum = v:lnum - 1
  endwhile
  if a:child == ""
    if ! &l:autoindent
      return 0
    elseif &l:cindent
      return cindent(v:lnum)
    endif
  else
    exec "let indent=".a:child
    if indent != -1
      return indent
    endif
  endif
  if v:lnum == lnum && lnum != 1
    return -1
  endif
  let next = nextnonblank(lnum)
  if next == lnum
    return -1
  endif
  if next != 0 && next-lnum <= lnum-v:lnum
    return indent(next)
  else
    return indent(v:lnum-1)
  endif
endfunction
command! -bar IndentIgnoringBlanks
            \ if match(&l:indentexpr,'IndentIgnoringBlanks') == -1 |
            \   if &l:indentexpr == '' |
            \     let b:blanks_indentkeys = &l:indentkeys |
            \     if &l:cindent |
            \       let &l:indentkeys = &l:cinkeys |
            \     else |
            \       setlocal indentkeys=!^F,o,O |
            \     endif |
            \   endif |
            \   let b:blanks_indentexpr = &l:indentexpr |
            \   let &l:indentexpr = "IndentIgnoringBlanks('".
            \   substitute(&l:indentexpr,"'","''","g")."')" |
            \ endif
command! -bar IndentNormally
            \ if exists('b:blanks_indentexpr') |
            \   let &l:indentexpr = b:blanks_indentexpr |
            \ endif |
            \ if exists('b:blanks_indentkeys') |
            \   let &l:indentkeys = b:blanks_indentkeys |
            \ endif
augroup IndentIgnoringBlanks
  au!
  au FileType * IndentIgnoringBlanks
augroup END

You can change the * in the au command at the bottom to make it apply to only the filetypes you want, or put the part after the * in after scripts instead of with the above. :help after-directory

It works by 'wrapping' your indentexpr in another. The script sets the indentexpr to its own function, which adjusts the cursor position and variables as if you were inserting directly after the last non-blank line, not after the blank line, and then evaluates the original indentexpr.

Comments[]

See :help prevnonblank(). It should greatly simplify your method.

Regarding :help prevnonblank(): Rather than simplifying it, this function would make the script more complicated; what we need is not a non-blank line but a line preceded by a non-blank line; we also need the cursor moved. Sightless 23:26, 12 February 2009 (UTC)

I don't understand. Don't you just want to set the current indent to be the same as the indent level of the previous line that isn't blank? This is easily done using prevnonblank, with an example given in the help: "let ind = indent(prevnonblank(v:lnum - 1))". Won't this work? Am I missing something? --Fritzophrenic 19:45, 16 February 2009 (UTC)

Yeah, it's not that simple. What we want to do primarily is trick the syntax-specific indentexpr so it thinks it's opening a line below a correctly indented line, not below a blank line, and leave it to calculate whatever complicated indent change it wants to. Sightless 15:04, 17 March 2009 (UTC)

The script seems fairly solid. It should now behave the same as Vim when there are no blank lines involved. When blank lines are involved, and 'autoindent' would otherwise be in effect, the script takes the indent from whichever non-blank line is closest. This will usually be what the user wants. It can't second-guess indent expressions' intentions, though, so it may be slightly out in some cases. Sightless 15:04, 17 March 2009 (UTC)

This approach is not recommended, though, as it is generally considered poor practice to have lines that contain only whitespace.

The thing is, using indented blank lines is useful. One example is diff'ing files often result to much cleaner and understandable diffs if blank lines are indented instead of empty. The main problem I have with the current system is that it makes trailing whitespace removal a mandatory and always-active part of the indenting system. An option would be good, but maybe the best thing would be to remove the option from the indenting and create a trailing-spaces removal system that allows to remove whitespace automatically all the time, and not in some specific conditions (i.e. blank lines if the blank line has not been modified). 23:54, October 15, 2011 (UTC)

As a remark, there are programming languages where whitespace/indenting has a meaning, the most known example being Python. There, Sightless' script is a very good help to keep the indenting right.
Advertisement