The following is a stripped down version of my folding function for LaTeX documents:

setlocal foldmethod=expr
setlocal foldexpr=LaTeXFoldsExpr(v:lnum)

" Sections to be folded
let g:LaTeXFolds_fold_sections = [
  \ 'part',
  \ 'chapter',
  \ 'section',
  \ 'subsection',
  \ 'subsubsection'
  \ ]

" Join all section names in a single regex
let s:sections_regex = '^\s*\\\(' . join(g:LaTeXFolds_fold_sections, '\|') . '\){\(.*\)}\s*$'

function! s:find_sections()
  " This function finds which sections in g:LaTeXFolds_fold_sections are
  " actually present in the document, and returns a dictionary where the keys
  " are the section names and the values are their foldlevel in the document.

  let fold_levels = {}

  let level = 1
  for section in g:LaTeXFolds_fold_sections
    let i = 1
    while i <= line('$')
      if getline(i) =~# '^\s*\\' . section . '{.*'
        let fold_levels[section] = level
        let level += 1
      let i += 1

  return fold_levels

" s:find_sections() returns a dictionary where the keys are the section names
" and the values are their foldlevel in the document.
let s:fold_levels = s:find_sections()

function! LaTeXFoldsExpr(lnum)
  let line = getline(a:lnum)

  " If the line is blank return the fold level of the previous or the next
  " line, whichever is the lowest.
  if line =~ '^\s*$' | return '-1' | endif

  " Let \begin{document} and \end{document} remain unfolded
  if line =~# '^\s*\\\(begin\|end\)\s*{document}' | return '0' | endif

  " If this is a 'regular' line, return the fold level of the previous line
  if line !~# s:sections_regex | return '=' | endif

  " If I get here it means the line contains a sectioning command. Find which
  " one it is and return its fold level.
  let line_section = substitute(line, s:sections_regex, '\1', '')
  return '>' . s:fold_levels[line_section]

now condider the following test document



\chapter{A chapter}

\subsection{A subsection after a chapter, no section in between}

Foo bar baz.

\section{A section}

Foo bar baz.

\section{Another section}

Foo bar baz.

\chapter{Another chapter}

Foo bar baz.


I have 3 questions:

  1. line 7 of the test document contains a subsection (fold level = 3) right after a chapter (fold level = 1), with no sections in between. Now when I first open the document, this is what I get:

enter image description here

you can see that the fold text of that line contains 3 dashes, same as lines that contain sections. That indicates that neovim thinks that is a second level fold, instead of a third level one, as it should be.

Indeed, once I toggle the fold with za, I don't get to see its contents, instead the fold is now recognized as a level 3 fold, and another dash appears (I'm not going to post another screenshot, it's easy to check that that's what happens).

Once that fold is expanded for the first time it then behaves as expected. I suspect that is a bug, correct?

  1. I return -1 on every blank line. That means the fold level of that line is not yet defined, and it will be set to the fold level of the line before it or after it, whichever is smallest.

If that's what's happening, then why does line 14 have a foldlevel of 1, when lines 13 and 15 both have a fold level of 2 (to get the foldlevel of a line simply run :echo foldlevel(14))?

  1. line 18, which is also blank, is assigned the foldlevel of 1, which is correct since the following line, line 19, starts a new chapter, i.e. a level 1 fold. But then why is it that the fold associated with the first chapter doesn't include it?

enter image description here

Shouldn't lines 5-18 all be in the same level 1 fold? I suspect what is happening is line 18 is in its own level 1 fold, and the fold associated with the first chapter goes from line 5 to 17.

  • This is a very interesting question! The reason why the -1 lines are kept at a lower level is because you have >2 on the "section" lines, which indicates that it starts a fold at that position. (So I can see the logic in line 14 having a fold level of 1.) But, by that logic, line 18 should have a fold level of 0 and I can't really explain why it has a fold level of 1 (and yet it doesn't belong to the fold above it.) I think this will require quite some digging into the source code to understand, I plan to do so when I find time. – filbranden Aug 26 '20 at 20:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.