Vim Tips Wiki
(→‎Comments: Explaining the problem with auto-indented lines)
Line 51: Line 51:
 
* In the new line, the cursor appears to be at the correct indent preceded by whitespace.
 
* In the new line, the cursor appears to be at the correct indent preceded by whitespace.
   
In reality, the new line in the buffer is actually empty (there is no spoon.. sorry, whitespace). Thus, when a script asks for the line contents, it simply receives the empty string "" instead of a string of only whitespace, representing the indent. In the script above, this produces the following behaviour.
+
In reality, the new line in the buffer is actually empty (there is no spoon.. sorry, whitespace). Thus, when a script asks for the line contents, it simply receives the empty string "" and not a string containing the indent whitespace. In the script above, this produces the following behaviour.
   
 
Say the user is editing a Python file, and the cursor is placed where the marker | is (not actually a part of the buffer):
 
Say the user is editing a Python file, and the cursor is placed where the marker | is (not actually a part of the buffer):

Revision as of 11:51, 27 May 2008

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 May 17, 2008 · complexity basic · author Arnar · version 7.0

It bothered me a bit that Ctrl-D in insert mode would just hop on multiples of shiftwidth when editing files with complex (and creative) indentation such as Haskell source files. I replaced <Ctrl-D> with the following Python function that scans the lines above the current one, finds the closest line that has strictly smaller indent and dedents the current one to that level.

" Handy function to search previous lines for indent levels and
" use those instead of multiples of shiftwidth.
function! DedentToPrevious()
python << EOF
import vim
tabsize = int(vim.eval("&ts"))
l = vim.current.line
rest = l.lstrip()
indent = l[:len(l)-len(rest)]
if indent != "":
    cur_size = len(indent.replace("\t", " "*tabsize))
    idx = vim.current.window.cursor[0]-2
    while idx >= 0:
        ll = vim.current.buffer[idx]
        indent = ll[:len(ll)-len(ll.lstrip())]
        if len(indent.replace("\t", " "*tabsize)) < cur_size:
            vim.current.line = indent+rest
            break
        idx -= 1
EOF
endfunction

" replace the <C-D> in insert mode with the above function
imap <C-d> <C-o>:call DedentToPrevious()<cr>

Tips for improvement

If you are feeling adventurous, you could make a modified version that takes over the backspace key. I'd recommend using

vim.current.window.cursor

to detect if the cursor is right between the indent whitespace and line contents (if any) and only use the above behavior in that case. All other cursor positions would need to have the normal backspace behavior.

Comments

 TO DO 
When editing files in some kind of auto-indent (set ai, set cin etc.), vim behaves like this to the user:

  • End a line by pressing <return>
  • Vim will determine the indent of the next line, either by using the previous line's indent or by calling a language specific indent script.
  • In the new line, the cursor appears to be at the correct indent preceded by whitespace.

In reality, the new line in the buffer is actually empty (there is no spoon.. sorry, whitespace). Thus, when a script asks for the line contents, it simply receives the empty string "" and not a string containing the indent whitespace. In the script above, this produces the following behaviour.

Say the user is editing a Python file, and the cursor is placed where the marker | is (not actually a part of the buffer):

def some function:
    if condition:
        do_something()|

Now the user presses return and sees this:

def some function:
    if condition:
        do_something()
        |

The user wants to reduce the indent by one fold, so instead of pressing backspace four times, she presses C-D. The script above kicks in and tries to determine the current line's indent. The user expects it to find eight spaces, triggering the search of above lines finding that the "one-less" indent is four spaces -- but what really happens is that the current line indent is computed as zero and the script sets that as the current indent, giving the user this:

def some function:
    if condition:
        do_something()
|

instead of the expected:

def some function:
    if condition:
        do_something()
    |

Now, there is an ugly hack to solve this. After creating a new line, if the user types some character, say a space, and then deletes it immediately with backspace, the "fake" indent is turned into actual indent and C-D behaves as expected. The script above could detect this "empty-line" condition and simply emulate that key sequence (space, backspace) before continuing.