16

I'm a fan of the way that Atom and Sublime Text handle line folding, where the first line of each fold is visible (complete with syntax highlighting), and a marker is appended to the end of the line that indicates the fold.

See the screenshot below comparing Vim's indent folding (top) versus Atom's (bottom):Vim vs. Atom code folding

Vim dedicates two lines for each fold. The first line serves as a heading, and the second line describes some information about the fold (number of lines and text inside the fold).

Atom only uses one line, and uses a small marker at the end of the line to indicate the fold, along with color added to the line numbers on the left. Atom's folding style uses less screen real estate but still communicates all the information I really need.

I'm partial to the Atom folding style. It seems cleaner and more consistent, in my opinion, particularly when listing multiple methods or attributes in a row (as in the above screenshot).

Is there a way to roughly approximate Atom's folding style in Vim?

3
  • 1
    I think it's because your folds don't actually begin with with the line containing the opening brackets. Vim only uses the fold text on the line where the fold begins. See, for example, how brace placement affects it in C: imgur.com/3h70dPf,wfCLPm7
    – muru
    Feb 24, 2015 at 6:07
  • But this is Python, no braces involved (from __future__ import braces)... How did you configure folding? And can you paste this code snippet (or another that demonstrates the problem)? Vim folds to a single line, but as muru mentioned, your folds start a line late. Feb 24, 2015 at 8:16
  • @Carpetsmoker the braces are only for illustration of where the fold starts. The same effect can be seen on, say, Haskell.
    – muru
    Feb 24, 2015 at 8:46

2 Answers 2

14

Two lines vs. one line

In Vim, all lines within a fold will be collapsed to a single line, and the 'foldtext' option then determines the synopsis of those lines (usually dashes, the number of folded lines, and content from the first (or all) lines).

In your example, in Vim only the {...} parameter block itself is folded; the line above (that uses the parameters) isn't part of the fold. In contrast, in the other editor, that line using the parameters belongs to the fold, and its foldtext (to use Vim terminology) is the contents of that line plus the appended marker.

Adapting

In Vim, folds can be generated via different means (see :help fold-methods), and depends on the 'filetype' of the buffer. The difficulty of including the above line in the fold depends on that; check with :setlocal foldmethod?. With indent folding, there's nothing you can influence; you have to switch to another method. For syntax folding, that would mean adapting the syntax definitions, and could be very tricky. You'll have better luck if an expression is used, but it would still mean understanding and changing the logic. (It's unlikely that the filetype plugin author provided a configuration to influence this.)

Syntax highlighting of the folded line

That unfortunately isn't possible at all. Vim always uses the Folded highlight group for the entire folded line. You can tweak that via :highlight commands in your ~/.vimrc, but the individual differentiation of syntax will be lost.

7

Vim already displays folds as a single line. However, in Vim's indent folding, all the lines that have the same indent are included in a fold. So in your screenshot, the lines you are referring to as "headers" (e.g., the one starting collection_base_url) are not within the folds.

You can achieve something similar to Atom's folding by using Vim's foldexpr foldmethod:

" Finds the indent of a line. The indent of a blank line is the indent of the
" first non-blank line above it.
function! FindIndent(line_number, indent_width)
    " Regular expression for a "blank" line
    let regexp_blank = "^\s*$"

    let non_blank_line = a:line_number
    while non_blank_line > 0 && getline(non_blank_line) =~ regexp_blank
        let non_blank_line = non_blank_line - 1
    endwhile
    return indent(non_blank_line) / a:indent_width
endfunction

" 'foldexpr' for Atom-style indent folding
function! AtomStyleFolding(line_number)
    let indent_width = &shiftwidth

    " Find current indent
    let indent = FindIndent(a:line_number, indent_width)

    " Now find the indent of the next line
    let indent_below = FindIndent(a:line_number + 1, indent_width)

    " Calculate indent level
    if indent_below > indent
        return indent_below
    elseif indent_below < indent
        return "<" . indent
    else
        return indent
    endif
endfunction

set foldexpr=AtomStyleFolding(v:lnum)
set foldmethod=expr

This defines a fold expression (see :help fold-expr) as follows:

  • For lines immediately preceding indented lines, it returns the indentation of the block that follows.
  • For indented lines, it returns the indentation. (Divided by the shiftwidth so each level of indentation increments the returned value by 1)
  • For lines at the end of a block of indented lines, it returns a string "<N", where N is set to the indent. This tells Vim that a fold of level N finishes on that line.

Update

@alxndr asks in the comments whether it's possible to extend this to include Ruby's outdented ends within the folds. You could start by replacing the " Calculate indent level section with the following:

if indent_below > indent
    return indent_below
elseif getline(a:line_number) =~ '^\s*end\s*$'
    return "<" . (indent + 1)
else
    return indent
endif

As it is, this isn't the most robust solution (e.g. it will fail if the end statement is on the same line, after a semi-colon). You could tweak the regular-expression and surrounding code to address this, but as you're now within the realms of parsing the actual syntax of the file, I suspect things could get a lot more complicated before you have a good solution.

5
  • 1
    Great approach. Could this answer be generalized to also handle say Ruby, with its outdented end to finish a block?
    – alxndr
    Feb 24, 2015 at 18:29
  • 3
    @alxndr I've edited my answer with some discussion.
    – Rich
    Jul 15, 2016 at 13:07
  • @Rich: I also find Sublime text fold that only show a mark ... at end of line as symbol of a folded text block very usefull and make less distraction than have to highlight the whole first line of text block as in vim. I tried your fold above with default fold behavior of vim, simple doesn't work. How to have fold that give a mark at the end first line of text block with python? Should I open a new question? I just test again your fold, it's indeed work very well with indent line. super Thanks.
    – Tuyen Pham
    Nov 5, 2018 at 5:18
  • 2
    This is amazing! I've been looking for something that does org-mode like folding and this helps a lot!
    – priomsrb
    Apr 2, 2019 at 2:09
  • Unfortunately, this won't work in cases when the indentlevel decreases by more than 1. Here vim would make one top level ("a") fold, although there are two top-level "nodes": i.postimg.cc/cLmhR8n1/image.png Aug 19, 2022 at 22:53

Your Answer

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

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