Vim Markdown Folding? - vim

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.

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

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).

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.

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

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

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

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

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

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

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.

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

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.

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

Related

How can I save in vim a file with the actual fold text? ("+-- 43 lines [...]")?

I am looking for a way to save to a new text file a file that is folded, with all the folds closed. In other words, just as I see it on the screen.
Is it possible?
(I will have to print the code later, and parts of it are irrelevant to my purposes; the folding mechanism would be ideal for this, my other alternative is manually adding "[X lines omitted]" to the saved text.)
Fold your text as needed and then use :TOhtml to convert to an HTML file. This will preserve your folding. If you need it as a plain text file, you can post-process e.g. with w3m, which renders HTML to text and allows to dump it to a text file.
Here is a custom :RenderClosedFolds command, which will modify the current buffer / range. It also attempts to maintain the Folded highlighting of the original folds.
":[range]RenderClosedFolds
" Replace all lines currently hidden inside closed folds
" with a single line representing 'foldtext'.
function! s:RenderClosedFolds()
if line('.') == foldclosed('.')
let l:result = foldtextresult('.')
call setline('.', l:result)
execute printf('syntax match renderedFold "\V\^%s\$" containedin=ALL keepend', escape(l:result, '"\'))
else
delete _
endif
endfunction
command! -bar -range=% RenderClosedFolds
\ highlight def link renderedFold Folded |
\ let g:ingocommands_IsEntireBuffer = (<line1> == 1 && <line2> == line('$')) |
\ if g:ingocommands_IsEntireBuffer | syntax clear renderedFold | endif |
\ let g:save_foldmethod = &l:foldmethod | setlocal foldmethod=manual |
\ execute '<line1>,<line2>folddoclosed call <SID>RenderClosedFolds()' |
\ if g:ingocommands_IsEntireBuffer | setlocal nofoldenable | endif |
\ let &l:foldmethod = g:save_foldmethod | unlet g:save_foldmethod g:ingocommands_IsEntireBuffer
I have once created a script, that saves all folds to a new buffer.

How to make use nerdcommenter to give extra space after #

I am a vim user and has nerdcommenter plugin, the problem is when I use <leader>c<space> to comment out code (also block of code), it prefix # right in front of the code, but pep8 style checker is complaining that I should have a space after the #
eg.
#string = 'abc'
but I want it to comment to:
# string = 'abc'
I found that adding the following to my .vimrc was helpful.
let NERDSpaceDelims=1
This adds the desired extra space for all languages
(see "NERDSpaceDelims" at https://github.com/scrooloose/nerdcommenter/blob/master/doc/NERD_commenter.txt)
It appears that the delimiters are hardcoded in the /plugin/NERD_commenter.vim file, starting on line 67. You should be able to change '#' to '# ' for the filetypes that you wish to modify.
UPDATE:
I found a more intended and more preferred way of accomplishing this. The plugin has code to handle what it calls CustomDelimiters. You can use something like this in your vimrc to accomplish the same thing as above in a more visible and transferable way:
let g:NERDCustomDelimiters = { 'py' : { 'left': '# ', 'leftAlt': '', 'rightAlt': '' }}
Not super tested but seems to work.
EDIT: I think they fixed the plugin so this works now without the code below: let g:NERDSpaceDelims = 1
augroup NERDCommenter_whitespace_defender
au!
" NOTE: g:NERDSpaceDelims can only be set to [0,1] according to :h NERDSpaceDelims
au BufEnter * if has_key(nerdcommenter#delimiterMap, &ft) |
\ let g:NERDSpaceDelims = (nerdcommenter#delimiterMap[&ft]['left'][-1:] =~ '\s') ? 0 : 1 |
\elseif &filetype ==? 'vim' |
\ let g:NERDSpaceDelims = 1 |
\endif
augroup END

Yanking all marked lines in vim

Often times when reviewing log files in vim, I'll highlight interesting lines using marks. At some point, I'd like to be able to copy all of the interesting lines (either all marked lines, or a list of marks) to either a register or another file (it doesn't really matter which; the goal is to facilitate writing a summary). I haven't been able to find any built in way to do this; is it possible in vim?
I suppose it's probably a fairly straightforward function; probably looking something like this, but my vimscript abilities are very weak:
for cur_mark in list_of_marks
goto mark
yank current line and append to register
Has anyone ever written anything similar that they can point me to?
Thanks
EDIT: I posted the accepted solution at https://github.com/mikeage/vim-yankmarks
As always, there are few things that are more motivating than asking for help. Here's what I came up with; feedback welcome.
function! Yankmark()
let save_cursor = getpos(".")
let n = 0
" I should really make this a parameter...
let marks_to_yank="abcdefghijklmnopqrstuvwxyz"
let nummarks = strlen(marks_to_yank)
" Clear the a register
let #a=''
while n < nummarks
let c = strpart(marks_to_yank, n, 1)
" Is the mark defined
if getpos("'".c)[2] != 0
" using g' instead of ' doesn't mess with the jumplist
exec "normal g'".c
normal "Ayy
endif
let n = n + 1
endwhile
call setpos('.', save_cursor)
endfunction
Mikeage had a great idea; here's a more refined version of his function turned into a command:
":YankMarks [{marks}] [{register}]
" Yank all marked (with [a-z] / {marks} marks) lines into
" the default register / {register} (in the order of the
" marks).
function! s:YankMarks( ... )
let l:marks = 'abcdefghijklmnopqrstuvwxyz'
let l:register = '"'
if a:0 > 2
echohl ErrorMsg
echomsg 'Too many arguments'
echohl None
return
elseif a:0 == 2
let l:marks = a:1
let l:register = a:2
elseif a:0 == 1
if len(a:1) == 1
let l:register = a:1
else
let l:marks = a:1
endif
endif
let l:lines = ''
let l:yankedMarks = ''
for l:mark in split(l:marks, '\zs')
let l:lnum = line("'" . l:mark)
if l:lnum > 0
let l:yankedMarks .= l:mark
let l:lines .= getline(l:lnum) . "\n"
endif
endfor
call setreg(l:register, l:lines, 'V')
echomsg printf('Yanked %d line%s from mark%s %s',
\ len(l:yankedMarks),
\ len(l:yankedMarks) == 1 ? '' : 's',
\ len(l:yankedMarks) == 1 ? '' : 's',
\ l:yankedMarks
\) . (l:register ==# '"' ? '' : ' into register ' . l:register)
endfunction
command! -bar -nargs=* YankMarks call <SID>YankMarks(<f-args>)
A different way of accomplishing this might be using the :global command. The global command takes the form :g/{pattern}/{cmd}. The command, {cmd}, will be executed on all lines matching {pattern}.
Append lines matching a pattern to a register:
:g/pattern/yank A
Append matching line to a log file:
:g/pattern/w >> file.log
Of course if you want to find line matching a mark you can match it in your pattern. The following pattern matches a line with mark m.
:g/\%'m/w >> file.log
To do something like this. (Note: I am using \v to turn on very magic)
:g/\v(%'a|%'b|%'m)/yank A
Of course if a pattern won't work you can do this by hand. Instead of marking the lines just build up the lines as you go. Just yank a line to an uppercase register to append.
"Ayy
Or do a write append with a range of a single line
:.w >> file.log
For more help see
:h :g
:h :w_a
:h /\%'m
:h /\v
You can do something like:
:redir #a
:silent marks XYZN
:redir END
"ap
That way the output of the :marks command will be redirected to the a register. Note, that it will only lists (in the above case) the X, Y, Z and N marks (as the arguments), and if there was an a register, it will be deleted/overwritten.
Also note, that it might not give the desired output, but gives you a starting point...
I like the solution from Mikeage, though I would probably solve this with the multiselect - Create multiple selections and operate plugin. This also has the benefit that you don't run out of marks.
With the plugin, you can select lines with <Leader>msa or :MSAdd. Finally, yank all lines with:
:let #a=''
:MSExecCmd yank A
If you use an upper-case register name when yanking into a specific register, Vim will append the yanked content instead of overwriting the register's value.
So, for example:
"ayy - yank current line to register a, overwriting
[move]
"Ayy - append this line to register a
[move]
"ap - paste all yanked material
See :help quotea for more details.

How to auto add more blank lines to bottom when editing?

like scrolloff, but it will reach bottom
I want it never reach bottom of the edit area although move the cursor to the bottom of text.
100 func coding
101 return coding
102 end
103 // bottom of file
~
~ auto added, can't reach
~
=============status bar===========
Something like this?
:autocmd CursorMoved * if line('.') == line('$') | call append('$', '') | endif
It will automatically append an empty line whenever the cursor reaches the last line.
IIUC, you could combine the comment by 244an and Ingo Karkat's answer to have vim always leave some empty lines at the bottom of its window. The following should get you part of the way:
autocmd CursorMoved *
\ if line('.') > line('$') - &scrolloff
\ && line('w0') + winheight(0) - 1 - &scrolloff < line('$')
\ | exec 'normal ^R'
\ | endif
Note that there are a couple of issues with this:
This also applies to the command window.
If you jump directly to the bottom of the file, new skips will be done until scrolloff lines are visible below the last line.

How to convert leading spaces to tabs?

Many people use spaces rather than tabs. I use both of them. Tabs at the beginning of line and spaces from the first non-whitespace character. No problem for starting new document and in case I have to modify one better adapt to using format. Still sometimes I need to fix the spaces issue anyway.
According to Search and replace I can just do :%s/spaces_for_tab/tab/g. It is simple and it will work for many cases. Anyway I want to refactor only spaces at the beginning of line.
This is more of a regex issue. To anchor at the beginning of the line, use the caret, e.g.
s/^ /\t/
Or do it using vim's builtin functionality:
:set tabstop=4 "four spaces will make up for one tab
:set noexpandtab "tell vim to keep tabs instead of inserting spaces
:retab "let vim handle your case
By the way, I too prefer tabs for indentation and spaces for alignment. Unfortunately, vim doesn't handle this well (and I don't know what other editors do), so I mostly use :set expandtab (maybe see :set softtabstop).
I've written a simple func for it. Anyway it will work only for 4-space tab.
fu! Fixspaces()
while search('^\t* \{4}') != 0
execute ':%s/^\t*\zs \{4}/\t/g'
endwhile
endfu
You can suggest better solution, if exists, and I will use it with pleasure.
The issue is that this func replaces spaces in strings as well.
David's response is very elegant but it doesn't address leading whitespace that has a mixture of tabs and spaces. For example to convert a line like:
<SPACE><SPACE><TAB>something...
you have to know the position of the tab to determine the number of spaces needed to replace the <TAB> and reach the next tabstop. My solution below, although not as compact as David's, addresses this. It also allows me to select which way to use leading whitespace without depending upon &expandtab. I would appreciate any suggestions to improve my code...
function! TabsToSpaces(...)
let ts = &tabstop
let pos = getpos('.')
while search('^ *\zs\t', "w") != 0
let l:curpos = getcharpos('.')
" The number of spaces needed depends upon the position of the <TAB>
let numsp = 1 + ts - ( curpos[2] % ts )
if numsp == 9
let numsp = 1
endif
silent execute ':s/^ *\zs\t/'.repeat(' ', numsp).'/'
endwhile
if a:0 == 0
echo 'Changed leading tabs to spaces'
endif
call setpos('.', pos)
endfunction
function! SpacesToTabs(...)
let ts = &tabstop
let pos = getpos('.')
" First normalize all tabs to spaces
call TabsToSpaces("quiet")
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
if a:0 == 0
echo 'Changed leading spaces to tabs'
endif
call setpos('.', pos)
endfunction
" Some keystrokes to implement the spaces/tabs functions
nmap <Leader>st :execute SpacesToTabs()<CR>
nmap <Leader>ts :execute TabsToSpaces()<CR>
I took Martin's answer and improved on it a bit if anyone's interested:
function Fixspaces()
let ts = &tabstop
let pos = getpos('.')
if &expandtab
while search('^ *\t') != 0
silent execute ':%s/^ *\zs\t/'.repeat(' ', ts).'/g'
endwhile
echo 'Changed tabs to spaces'
else
while search('^\t* \{'.ts.'}') != 0
silent execute ':%s/^\t*\zs \{'.ts.'}/\t/g'
endwhile
echo 'Changed spaces to tabs'
endif
call setpos('.', pos)
endfunction
This function does the appropriate thing depending on the values of the expandtab and tabstop settings and also remembers where the cursor is.

Resources