1

Is there a succinct way to separate folds, open or closed, by the same number of newlines throughout the buffer? I'm using fold by indent. The goal is two visible blank lines between each fold (actually, after the end of a fold, since folds will abut non-folds). So if there is one blank line it adds another, and if there are three or more blank lines, it removes blank lines down to two. I would like these as actual inserted or removed characters as opposed to mere visual updates. Enforcing this automatically would be nice but I'm fine having it mapped to a command for manual reformatting. Spaces between a final fold and the EOF are nice but optional.

Before, with non-uniform spacing:

HEADER 1
--------
- closed fold -----------------------------------------------------------
HEADER 2
--------
  opened fold
  opened fold
HEADER 3
--------
- closed fold -----------------------------------------------------------



HEADER 4
--------
  opened fold
  opened fold
  opened fold



HEADER 5
--------
- closed fold -----------------------------------------------------------
~ EOF

After, with uniform spacing:

HEADER 1
--------
- closed fold -----------------------------------------------------------


HEADER 2
--------
  opened fold
  opened fold


HEADER 3
--------
- closed fold -----------------------------------------------------------


HEADER 4
--------
  opened fold
  opened fold
  opened fold


HEADER 5
--------
- closed fold -----------------------------------------------------------


~ EOF
2
  • Sorry, could you put a sample text and expected output. I'm not quite understanding your question. Commented Aug 27, 2018 at 23:31
  • added sample i/o Commented Aug 28, 2018 at 12:16

2 Answers 2

1

An easy way to append lines is by using the append() function, that takes the a line number and a list of lines to append. As append() also accepts the string '.' as a synonym for the current line, a simple map to insert two blank lines is:

" Ctrl-L can be changed according to your preferences
nnoremap <C-L> :call append('.', ['', ''])<CR>

As for the folding, you can set 'foldmethod' to "expr" and define your own fold expression to suit your specific problem. In this case, you could do something like this:

set foldminlines=0      " Allows folding a single line
set foldmethod=expr     " Use custom expression to define foldsj
set foldexpr=FoldAfterTwoEmptyLines(v:lnum) " The expression

function! FoldAfterTwoEmptyLines(lnum) abort
    if getline(a:lnum - 1) =~# '^-\+$'
        " If the line is just a series of ------
        return '>1'     " Starts fold with depth 1
    elseif getline(a:lnum) =~# '\S'
        " If the line has contents
        if getline(a:lnum + 1) =~# '^\s*$' && getline(a:lnum + 2) =~# '^\s*$'
            " If the next two lines are empty
            return '<1' " Ends fold with depth 1
        else
            return '-1' " Use fold of surroundings
        endif
    else
        return '0'      " No fold
    endif
endfunction
1

Ad-hoc macro solution

I couldn't be bothered doing maths, so I implemented this with normal editing commands in a recursive macro:

gg2zjqqqqqzko<CR><CR><Esc>d/^.<CR>zjzj@qq@q

Read on to see how this works, or skip to the bottom for a mapping you can paste into your .vimrc.

How it works

  1. First move to the top of the file:

    gg
    
  2. Move to the start of the second fold:

    2zj
    
  3. Start recording a recursive macro:

    qqqqq
    
  4. Move to the end of the previous fold:

    zk
    
  5. Add three blank lines:

    o<CR><CR><Esc>
    
  6. Delete the third blank line, and any others that may already have existed before the header:

    d/^.<CR>
    
  7. Move down to the third fold (N.B. We cannot use zj here because we need the failed movement to terminate the recursive macro. If you run 2zj when there is only one more fold, the movement still succeeds: it just moves down a single fold):

    zjzj
    
  8. Replay the macro (Currently, because of step 3, register "q is empty, so this has no effect):

    @q
    
  9. End recording and play the recursive macro:

    q@q
    

This works for the sample given, but might need some tweaks for a robust solution. (e.g. What if the first fold starts on the first line of the file? What do you want it to do for nested folds? etc.)

A Proper Solution

Of course, you don't want to have to keep this permanently in one of your registers, so let's wrap it in a function and create a mapping to invoke it:

function! SpaceFolds() abort
  " Save the current window view state
  let saved_view = winsaveview()

  " Save the current contents of register q
  let register_contents = @q

  " Run the preamble commands
  normal! gg2zj

  " Set up the macro
  let @q = "zko\<CR>\<CR>\<Esc>d/^.\<CR>zjzj@q"

  " Run it
  normal! @q

  " Restore register q and window state
  let @q = register_contents
  call winrestview(saved_view)
endfunction

nnoremap zs :call SpaceFolds()<CR>
2
  • This worked on the sample for me but failed (inexplicably) on a larger file. Commented Aug 30, 2018 at 12:28
  • @kevinlawler In what way did it fail?
    – Rich
    Commented Aug 30, 2018 at 12:35

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.