I have the file /path/a.md which has many sections as follows:

# abc
this this the content of section abc
# def
this this the content of section abc
## defg
this this the content of subsection defg of def

And in another file /path/b.md, I have the following:

I want to switch to def section by this link [section def in a.md](./a.md#def), 
yeh the file name and the section name are separated by "#" 
and this kind of link is also possible:[subsection defg in a.md](./a.md##defg)
  • 2
    On my setup gf didn't opened the file because # was in isfname, so using set isfname-=# allowed gf to jump to the file. Now to go to a particular section I don't think there is a built-in way to do so but developing a function to override gf should not be too hard.
    – statox
    Aug 25, 2016 at 11:35

2 Answers 2


This function is not thoroughly tested but it should provide a good enough bootstrap for your own experiments.

In ~/.vim/after/ftplugin/markdown.vim:

function! s:MDGoToSection()
    let raw_filename = expand('<cfile>')
    let arg = substitute(raw_filename, '\([^#]*\)\(#\{1,6\}\)\([^#]*\)', '+\/\2\\\\s\3 \1', 'g')
    execute "edit" arg

nnoremap <buffer> <key> :call <SID>MDGoToSection()<CR>



The filename under the cursor is split into three groups:

\([^#]*\)......................... everything before the first #              
         \(#\{1,6\}\)............. 1 to 6 #
                     \([^#]*\).... everything after #######

and reordered into a proper argument for :edit:

+\/\2\\\\s\3 \1

which should split ./foo.md##bar into ./foo.md, ##, and bar, and finally pass +/##\\sbar ./foo.md to :edit:

:edit +/##\\sbar ./foo.md
  • Damn you was a few second faster and your solution is much more elegant, I'm a little bit jealous ;)
    – statox
    Aug 25, 2016 at 11:57
  • @statox, hehe… check out the edited version for even more élégance.
    – romainl
    Aug 25, 2016 at 11:59
  • Ahah well done!
    – statox
    Aug 25, 2016 at 12:03

As I said in the comments on my setup gf didn't opened the file because # was in isfname, so using set isfname-=# allowed gf to jump to the file.

As I also said I think the feature to go to a section of the file is not built in so you can try this function:

function! MarkdownGF()
    " Get the filename under the cursor
    let cfile=expand('<cfile>')
    " Separate the filename from the section
    let parts=split(cfile, '#')

    " No section marked
    if (len(parts) == 1)
        execute "normal! gf"
    " There was a subsection in the file name
        execute "e " . parts[0]
        let pattern = "^" . repeat('\#',len(parts)-1) . "\\\s*" . parts[len(parts)-1] . "$"
        call search(pattern, 'w')

The function is to be called when the cursor is on the file you want to open. It will go to the file and if a subsection is specified it will search for it.

To make it easier to call you could remap the function only on markdown buffers:

autocmd! Filetype markdown nnoremap <buffer> gf :call MarkdownGF()<CR>

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.