mihai
mihai

Reputation: 38543

How to preserve indent after pressing Esc in Vim

I have set autoindent
I go to a line, press A and <CR> which gets me to the next line and inserts an indent. However if I press Esc the cursor jumps to the beginning of the line and the indent is gone.
I have to go about and press tabs to get to the right place again.

I know the help says:

If you do not type anything on the new line except <BS> or CTRL-D and then type
<Esc>, CTRL-O or <CR>, the indent is deleted again.

Is there a way to disable this, or at least a workaround?

Upvotes: 21

Views: 2318

Answers (8)

matt0089
matt0089

Reputation: 471

Thanks to @EdanMaor for the S and cc suggestions, they are helpful. Something that started to make this bearable for me was the <C-f> (control-f) binding in insert mode, which performs the equivalent of == (but with the convenience of insert mode). It's set by default in the indentkeys option.

So if you go into insert mode, start writing and see indent is 0 (cursor indicated by | char):

function main() {
        // ...
        
        // ... deeply nested function...
        function something() {
          console.log("ok");
// Star|ted writing a comment
          console.log("after");
        }
        // ...
}

you can hit <C-f> and it will indent on the spot, even if your cursor is in the middle of the line:

function main() {
        // ...
        
        // ... deeply nested function...
        function something() {
          console.log("ok");
          // Star|ted writing a comment
          console.log("after");
        }
        // ...
}

I experimented with this mapping to make it more automatic:

:nnoremap I I<c-f>

It automatically indents the line whenever you insert at the beginning of the line. I'm sure there are ways to make it more seamless.


For the issues with python files, for me it was due to the default python plugin (running neovim v0.9.1). It runs the python#GetIndent() function when pressing == or <C-f>, which doesn't pick up the previous indent you would expect.

I added these few lines to the end of the python#GetIndent() function (right before the default return of -1). It seems to fix the issue, but I'm sure it's broken in some way.

  if a:lnum - plnum < 3
    if getline(a:lnum) =~ '^\s*def\>'
    " dedent if current line is a def (maybe class too?)
        return max([indent(plnum) - shiftwidth(), 0])
    else
    " keep same indent as previous line
        return indent(plnum)
    endif
  endif

The python#GetIndent function is defined in $VIMRUNTIME/autoload/python.vim. If you can't edit it, you can copy the whole file into ~/.config/nvim/after/plugin/python.vim for neovim, and ~/.vim/after/plugin/python.vim for vim, then do the edits there.


References:

  • :h i_ctrl-f
  • :h 'indentkeys'
  • :h 'indentexpr'
  • https://learnvim.irian.to/customize/vim_runtime
    Nice description of how vim indent plugins are implemented. When a python file is detected, vim automatically runs $VIMRUNTIME/indent/python.vim, which refers to a function in $VIMRUNTIME/autoload/python.vim

Upvotes: 1

jojo
jojo

Reputation: 315

consider I use 'o' to start a newline. I add below config in _vimrc(notice I have ':set autoindent')

" ugly hack to start newline and keep indent
nnoremap o ox<BS>
nnoremap O Ox<BS>

Upvotes: 4

mihai
mihai

Reputation: 38543

Alright, I figured this out.

Based on Edan Maor's answer, S or cc should enter insert mode with the proper level of indentation.
...except when it doesn't :)

This works under two circumstances.

  • when cindent is set, it will insert indent based on C formatting rules
    This may prove annoying when editing non C-like files.
  • when indentexpr is set.

I found that the best solution is to have this is my .vimrc

set autoindent
set indentexpr=GetIndent()

function GetIndent()
   let lnum = prevnonblank(v:lnum - 1)
   let ind = indent(lnum)
   return ind
endfunction

Now when I press S or cc, it will insert the same indent as on the previous non-blank line.

Upvotes: 4

Lucia
Lucia

Reputation: 13571

I had want to achieve the same effect, but because I want the plugin showing indent to work properly. This is my workaround: I've found that <enter> in normal mode is almost useless. It only moves the cursor one line down, which could be achieved by j.

So I added this in my .vimrc:

nmap <cr> o.<c-h><esc>

Whenever I need a blank line for its indentation, I'd use <enter> instead.

Upvotes: 0

whoknows
whoknows

Reputation: 78

It may be worth noting that with proper plugins S and cc seem to work properly again. It is most likely python-mode that is fixing this.

https://github.com/klen/python-mode

Upvotes: 1

Edan Maor
Edan Maor

Reputation: 10052

I had this exact problem until two days ago.

There is no way to disable this, but luckily, you don't need to, because instead:

Enter insert mode with S or cc. Entering insert mode again with S will enter insert mode with the proper level of indentation, making the fact that Vim deleted the indents unimportant.

Note: I found that this trick worked for me most places. But for some reason, it did not work with Python files. I'm guessing it's something to do with the Python filetype messing with its own indentation functions, or something along those lines.

Edit:

Another trick, you can define cpoptions in a way that, if you're on a line with an indent and move the cursor, it will preserve the indent. This won't solve your problem of hitting Esc right away, but it's a related issue that might also be bothering you.

Upvotes: 9

aliva
aliva

Reputation: 5720

type your text then press == in normal mode in that line

Upvotes: 4

David.Chu.ca
David.Chu.ca

Reputation: 38654

A simple way is to press '.' (or any char), escape, then press x to remove the char. The indent should be preserved.

Upvotes: 4

Related Questions