4

I am looking for a transformation that would change this text:

    text
        indented
        text
            double
            indented
            text
        indented
        again

to:

text
    indented
    text
        double
        indented
        text
    indented
    again

But then wouldn't change anything if applied again.

I a nutshell, I need to keep the minimal indentation level without loosing the relative indentations of the lines. So just drop the indentations that run all along the selection.

Is there a clever way to do so ?

2
  • How do you indent your files (a tab character, 4 spaces...) ? How do you imagine the solution (a mapping used in normal mode for the whole file, in visual mode while a portion of the file is selected...) ?
    – saginaw
    Nov 20, 2015 at 10:04
  • @saginaw There are either only spaces or only tab characters (otherwise the problem might have no solution). I actually need to apply this to whole files, but I guess it'd be easily usable on visually selected portions as well. The algorithm looks simple: given the amount of whitespace at the beginning of each line, stored in a vector v, say, subtract from v the minimum of v so that its new minimum is now zero.
    – iago-lito
    Nov 20, 2015 at 11:06

2 Answers 2

3

Delete the text linewise and then paste with ]p with your cursor from an empty line.

dG]p

Here ]p is doing all the hard work. ]p will paste the text, but will adjust the indent to the current line.

For more help see:

:h ]p
3
  • Nice! This is definitely more elegant ;)
    – iago-lito
    Nov 20, 2015 at 16:16
  • Or just d]P if you have selected the lines you want in visual mode.
    – xenia
    Nov 20, 2015 at 16:32
  • @Loovjo If you are in visual mode make sure you use V (Visual Line) so that the text is deleted line-wise. Alternatively you can use Tim Pope's unimpaired.vim which changes ]p and friends to always paste line-wise. Nov 20, 2015 at 16:52
1

The following code add a mapping <leader>s that should do what you want on the specific file you provided, if you use spaces to indent.

nnoremap <leader>s :<c-u>call UnindentSpaces()<cr>

function! UnindentSpaces()

    function! GlobalUnindent()

        let line = getline('.')
        let p = match(line, '\S') - s:n
        s;^\s*;\=repeat(' ', p);

    endfunction

    let line = getline(1)
    let s:n = match(line, '\S')
    if s:n > 0
        g/^/call GlobalUnindent()
    endif

endfunction

Conditions in which I tested this code :

set expandtab
set tabstop=4 softtabstop=4 shiftwidth=4

If you use a tab character to indent, then the following code should be used instead (<leader>t) :

nnoremap <leader>t :<c-u>call UnindentTabs()<cr>

function! UnindentTabs()

    function! GlobalUnindent()

        let line = getline('.')
        let p = match(line, '\S') - s:n
        s;^\s*;\=repeat("\t", p);

    endfunction

    let line = getline(1)
    let s:n = match(line, '\S')
    if s:n > 0
        g/^/call GlobalUnindent()
    endif

endfunction

Conditions in which I tested this code :

set noexpandtab
set tabstop=4 softtabstop=4 shiftwidth=4

Tell me if it works for you, otherwise I'll try to modify it if I can.


Edit : if you can rely on the first character of the first line to detect whether the file use spaces or tabs for indentation, then I think you can do it with only one mapping (<leader>u) and one function which will cover both cases (tabs vs spaces) :

nnoremap <leader>u :<c-u>call Unindent()<cr>

function! Unindent()

    function! GlobalUnindent()

        let line = getline('.')
        let p = match(line, '\S') - s:n
        s;^\s*;\=repeat(s:indentchar, p);

    endfunction

    let line = getline(1)
    let s:indentchar = matchstr(line, '\%1c.')
    let s:n = match(line, '\S')

    if s:n > 0
        g/^/call GlobalUnindent()
    endif

endfunction
4
  • Thanks. Unfortunately, I think it removes one extra level and thus looses the information. I am sorry this script uses syntaxes I was even unaware of, so I can't read it and spot the mistake. I'll have a look at it after I delved into vim's doc.
    – iago-lito
    Nov 20, 2015 at 11:28
  • Could you provide a sample text on which the code removes one extra level ? I've tested it on the sample text you gave in your first post and it seems to work, unless I misunderstood something ? Or maybe I have different settings ? My indentation settings are : set expandtab, set tabstop=4, set shiftwidth=4, set softtabstop=4. Also, I've edited the code so that you don't have to repeat the mapping in case there are more than one extra indentation level on the first line.
    – saginaw
    Nov 20, 2015 at 11:45
  • I was missing the tabstop one ! It's working perfectly now. In order words, you nailed it. Cheers :)
    – iago-lito
    Nov 20, 2015 at 12:01
  • I'm really glad it helped you ! I've edited the answer, if you can rely on the first character of the first line to detect whether the file uses spaces or tabs for indentation, then I think you can do it with only one function and one mapping which will cover both cases.
    – saginaw
    Nov 20, 2015 at 12:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.