Muchin
Muchin

Reputation: 4997

Vim Markdown Folding?

I just realized that VIM 7.3 has built-in support for highlighting Markdown files. Excellent. However, it doesn't fold on the headings.

Can any offer suggestions on how to get this working?


Alternatively, I'm using Markdown only as a way to get simple structured text. If there is a better alternative format, please also suggest. But not sure I dig TVO or VimOutliner.

Upvotes: 39

Views: 25386

Answers (13)

Sanghyun Lee
Sanghyun Lee

Reputation: 23002

let g:markdown_folding = 1

You can enable markdown folding feature by adding this in your .vimrc if you are using the latest version of Vim - no need to be the latest, but I don't know the exact version.

For some reason it's not documented in the README but you can find the related code in the repository.

FYI, if you don't want the sections closed when you open a file, refer to this SO thread. I think adding this would be the best way but you may have a different preference.

set nofoldenable

Update

Unfortunately, I stopped using markdown_folding since it makes things slow (Github issue).

Upvotes: 32

Patrick Oscity
Patrick Oscity

Reputation: 54684

Here's what I came up with as a combination of many of the other answers here. I found that most of them, including the builtin g:markdown_folding, do not properly handle code blocks that contain # characters as part of comments. I based this on matching the syntax IDs, which also handles <h1-6> tags properly.

" ~/.vim/ftplugin/markdown.vim

function MarkdownLevel(lnum)
  for synID in synstack(a:lnum, 1)
    let name = synIDattr(synID, "name")
    if     name == 'htmlH1' | return ">1"
    elseif name == 'htmlH2' | return ">2"
    elseif name == 'htmlH3' | return ">3"
    elseif name == 'htmlH4' | return ">4"
    elseif name == 'htmlH5' | return ">5"
    elseif name == 'htmlH6' | return ">6"
    endif
  endfor
  return "="
endfunction

setlocal foldexpr=MarkdownLevel(v:lnum)
setlocal foldmethod=expr
setlocal foldlevel=1

Upvotes: 0

Xopi Garc&#237;a
Xopi Garc&#237;a

Reputation: 386

Working from @Omar comment to this answer, I coded a fold method to languages which comment with //, like JS. Add following to ~/.vimrc:

autocmd FileType javascript setlocal foldmethod=expr foldcolumn=6 
autocmd FileType javascript setlocal foldexpr=JSFolds()

" Level of a folding:
    "// #: level 1
    "// ##: level 2
    "// ###: level 3
function! JSFolds()
    " Option 1: // and no space and hashes:
        "if getline(v:lnum) =~ '^//#'
    " Option 2: // and 1 space and hashes:
        "if getline(v:lnum) =~ '^//\+ #'
    " Option 3: spaces/tabs/nothing  and // and 1 space and hashes:
    if getline(v:lnum) =~ '^\s*//\+ #'
    " Option 4: anything and // and 1 space and hashes:
    " DANEGROUS! Potential conflict with code. E.g. print("// # Title");
    " if getline(v:lnum) =~ '//\+ #'

        " Number of hashs # in line that success previous condition (if)
        " determine the fold level
       let repeatHash = len(matchstr(getline(v:lnum), '#\+'))
       return ">" . repeatHash
    endif
    return "=" 
endfunction

Examples. Note on the left the fold levels ("|" and "-"):

-     // # ** Fold style recommended **
-     // #       1  easy case
|-    // ##      2  easy case
||-   // ###     3  easy case
|||   //            Comment inside level 3
|||-  // ####    4  easy case
||||  //            Comment inside level 4
|-        // ##  2  easy case (indents are OK with Option 3)
||    /####     error (JS comment needs 2 slashes)
||  
-     // # ** Fold of just 1 line **
|--   // ###    3  easy case
||-   // ###    =  same fold level as previous line, thus previous line folds just itself ?!? (not concerns this fold function) 
|||   
-     // # ** Space needed before, BUT not needed after hash/-es **
|-    // ##Fold Level   changed Because no space after hashes is OK:    '// # ' vs '// #NoSpace'. NoSpace could even be a return carriage (enter). 
||    //## Fold Level Unchanged Because no space after pair of slashes: '// #' vs '//#'
||    //     ##txt    Unchanged Because too much space after slashes
||    //     ## txt   Unchanged Because too much space after slashes
||    
-     // # ** Odds vs Even slashes **
-     /// #     1  overrides typo 3 slash instead of just 2 (/// vs //)
-     ///// #   1  overrides typo 5 slash instead of just 4 (///// vs ////). Read Recommenting Comments notes.
|-    // ## ** As long as the pattern is at least '// # ', further previous slashes are ok  **
-     // #            1  easy case
|--   // ###          3  ok (and recommended fold style)
||-   ///// ###       3  ok (recommented + typo)
||-   ////// ###      3  ok (re-recommented)
||-   /// ###         3  ok (typo)
||-   //// ###        3  ok (recommented)
||-   ///////// ###   3  ok (who cares? it works!)
|||   
-     // # ** Recommenting Comments **
-     // #      1  easy case
|     //           Comment inside level 1
-     //// #    1  recommented a comment
|     ////         Comment inside level 1
-     ///// #   1  re-re-recomment
|     /////        Comment inside level 1
|     
-     // # ** Recommenting Comments adding text **
|--   // ### //// #    3  changing fold level on purpose of a recommented a comment
|||   //               Comment inside level 3
|||   // text  // ##         2  (recommented a comment adding text)
|||   // text#text  // ##    2  right   {recommented a comment adding initial text, as long as this text has no hash just after '// ' (2*slash + space) would be ok }
-     // #text#text  // ##   2  wrongly {recommented a comment adding initial text, as long as this text has no hash just after '// ' (2*slash + space) would be ok }
-     // # changeFoldIntentionally // ##     1  clear intention to change fold level of comments
-     // #changeFoldIntentionally // ##      1  clear intention to change fold level of comments (previousi example, with space after hash would be clearer)
|--   // ### changeFoldIntentionally // ##   3  clear intention to change fold level of comments
|||   

PD: totally open to critics and improvements of the code. Actually I'm a beginner with vimscript.

Upvotes: 0

jpgeek
jpgeek

Reputation: 5281

As of Vim 8 it is included by default (via Tim Pope's markdown plugin). Just add this to .vimrc:

let g:markdown_folding=1

To make sure you have this plugin loaded you can run

:showscripts

and look for

vim80/syntax/markdown.vim

Upvotes: 1

studgeek
studgeek

Reputation: 14920

VOoM : Vim two-pane outliner is worth checking it out.

Not only does it provide basic folding, but it also provides outline navigation via a 2nd outline view pane (similar to document map in MS Word). And it supports a large number of markup languages including others mentioned in other answers - Markdown, viki, reStructuredText, vimwiki, org, and many others.

For more info see the screenshots and the help page.

Upvotes: 1

joharr
joharr

Reputation: 535

Based on Jeromy & Omar's suggestions, I came up with this (for my vimrc) to automatically and unambiguously fold my DokuWiki files (in which top level header is marked by ====== at start of line, down to fourth level header marked by ===):

function! DWTitleLevel()
    let j = len(matchstr(getline(v:lnum), '^=\+'))
    if     j =~ 6 | return ">1"
    elseif j =~ 5 | return ">2"
    elseif j =~ 4 | return ">3"
    elseif j =~ 3 | return ">4"
    endif
endfunction

'^=+' means match from the start of the line any number of contiguous '='s

Then this in a vim modeline makes it work nicely for a DokuWiki file:

foldmethod=expr foldexpr=DWTitleLevel() foldcolumn=5

And for Markdown, I needed to write Omar's code like this:

if empty(j) | return "=" | else | return ">".len(j) | endif

Upvotes: 2

weddingcakes
weddingcakes

Reputation: 653

I'm guessing you don't watch VimCasts. The guy who makes that made a pugin for just this. Here it is: https://github.com/nelstrom/vim-markdown-folding

Upvotes: 2

cutemachine
cutemachine

Reputation: 6210

There is an app a plugin for that on GitHub.

vim-markdown-folding

When you are editing Markdown files with Vim, you probably also want to install Tim Pope's Markdown plugin.

vim-markdown

Upvotes: 5

blueyed
blueyed

Reputation: 27858

There is a vim-markdown plugin at https://github.com/plasticboy/vim-markdown .

The code related to folding from there appears to be:

" fold region for headings
syn region mkdHeaderFold
    \ start="^\s*\z(#\+\)"
    \ skip="^\s*\z1#\+"
    \ end="^\(\s*#\)\@="
    \ fold contains=TOP

" fold region for lists
syn region mkdListFold
    \ start="^\z(\s*\)\*\z(\s*\)"
    \ skip="^\z1 \z2\s*[^#]"
    \ end="^\(.\)\@="
    \ fold contains=TOP

syn sync fromstart
setlocal foldmethod=syntax

Upvotes: 8

Jeromy Anglim
Jeromy Anglim

Reputation: 34907

When I use markdown I only use the hash-style headings with space separating hashes and text. This makes the folding task a lot simpler.

I'm pretty new to Vim, so use the following at your own risk. I added the following code to my vimrc and it folds headings based on number of hashes, and it retains the syntax colouring.

function! MarkdownLevel()
    if getline(v:lnum) =~ '^# .*$'
        return ">1"
    endif
    if getline(v:lnum) =~ '^## .*$'
        return ">2"
    endif
    if getline(v:lnum) =~ '^### .*$'
        return ">3"
    endif
    if getline(v:lnum) =~ '^#### .*$'
        return ">4"
    endif
    if getline(v:lnum) =~ '^##### .*$'
        return ">5"
    endif
    if getline(v:lnum) =~ '^###### .*$'
        return ">6"
    endif
    return "=" 
endfunction
au BufEnter *.md setlocal foldexpr=MarkdownLevel()  
au BufEnter *.md setlocal foldmethod=expr     

Upvotes: 39

ematsen
ematsen

Reputation: 134

I had the same question, and played around with Jander's nice solution. The only problem is that by defining folding using syntax, you lose any Markdown syntax highlighting.

Given that you might be interested in alternate markups, I would suggest using reStructuredText, and the amazing Vst vim extension. It does folding very nicely. Rst is much more powerful than Markdown.

Upvotes: 7

Jander
Jander

Reputation: 5627

Here is a try at a recursive header folding rule. It doesn't include the underline style of Markdown header, but I'm guessing those would be awkward for your purposes anyway.

Put the following code into your .vimrc:

au FileType markdown syn region myMkdHeaderFold
        \ start="\v^\s*\z(\#{1,6})"
        \ skip="\v(\n\s*\z1\#)\@="
        \ end="\v\n(\s*\#)\@="ms=s-1,me=s-1
        \ fold contains=myMkdHeaderFold

au FileType markdown syn sync fromstart
au FileType markdown set foldmethod=syntax

Upvotes: 7

kfl62
kfl62

Reputation: 2514

The only way how I get folding to work in markdown, was't very elegant, :set fdm=marker and use html comment tag

 <!-- My folding {{{1 -->

more help :help folding

Upvotes: 4

Related Questions