I'm trying to make a function in vimscript that converts markdown to html files using pandoc and putting that converted file in a specified directory. Currently i am putting the file in a directory relative to the original markdown file's path using expand('%:p:h') and appending a new directory

So if my markdown file is in /home/user/notes/subjects/science the converted html file will be in /home/user/notes/subjects/science/convertedfile_dir

Since having an extra directory for my converted files in each and every subdirectory of my markdown notes can be inconvenient, i want to have a 'convertedfile_dir' in my outermost 'notes' directory, like this /home/user/notes/convertedfile_dir.

So basically how do i manipulate the output of expand() function, so that i can add convertedfile_dir in the middle of the outputted path, like this:

markdown file


converted file output /home/user/notes/convertedfile_dir/subjects/science/ecosystem.html

Here is the function that i currently use to convert markdown to html using pandoc to a directory (named _html) that resides in the same directory as the original markdown file

function! ConvertHTMLPandoc()
    let realfile = expand('%:p')
    let filedir = expand('%:p:h') . '/_html/'
    let file = expand('%:t:r')
    let convertedfile = filedir . file . '.html'
    echo 'deleting old file '. convertedfile
    execute "!rm -f " . convertedfile
    echo 'converting to html'
    execute "!pandoc " . realfile . " -o " . convertedfile . " --template=uikit.html --toc"

Making the 'convertedfile_dir' to have the exact same directory structure as my main notes directory is really important since i'm going to be working with images that are going to be placed in a directory of it's own in the main 'notes' directory, so that the images can still be loaded in the converted file. Really trying to write only once, and never have to re-edit the files after it is converted. (especially messed up paths).

Thank you in advance for helping me.

2 Answers 2


Rather than take a direct approach to "inserting text in the middle of a string" I'd suggest something a bit more robust (and a bit more complex). It allows you to, say, change the root directory path or the converted file directory name in a straightforward way if you need to in the future. With a straight string manipulation you'd have to redo the logic.

First let's talk about matchlist(). matchlist() in its simplest form takes a string and regular expression and returns a list containing any sub-matches found in the string. Sub-matches or "capture groups" are designated in a regular expression by surrounding appropriate parts of the pattern with parentheses.

(Note that I'm using "very magic" mode in my pattern to reduce the number of escapes. Without it the pattern would be something like /home/user/notes\(/.\+\)\(/[^/]\+\)\.md$. Note that capture groups look like \(pattern\) normally, i.e. the parentheses are escaped.)

The idea is to parse the path currently stored in realfile with matchlist(). We apply a pattern to the path and use a couple capture groups to extract the sub-paths we want. From those sub-paths the target directory and filename are built. First crack at it looks like...

let m = matchlist(realfile, '\v/home/user/notes(/.+)(/[^/]+)\.md$')
let convertedfile = "/home/user/notes/convertedfile_dir" . m[1] . m[2]  . ".html"

So if realfile contains /home/user/notes/foo/bar/baz.md then sub-match 1 would be the directory path under /home/user/notes (/foo/bar) [see note below] and sub-match 2 would be the base file name (baz)

The "production" version would be more like...

let rootdir = "/home/user/notes"
let converteddir = rootdir . "/convertedfile_dir"

let matched = matchlist(expand("%:p"), '\v' . rootdir . '(/.+)(/[^/]+)\.md$')
" length of list and string elements 1 and 2 should all be > 0
if len(matched) && strlen(matched[1]) && strlen(matched[2])
    let convertedfile = converteddir . matched[1] . matched[2]  . ".html"
    " bad match...handle as you best see fit

If the original file was /home/user/notes/foo/bar/baz.md then convertedfile in this script would be /home/user/notes/convertedfile_dir/foo/bar/baz.html

Side note Regarding the pattern in the first capture group (/.+) I'm assuming *nix filesystem where all (single-byte) characters are valid in filenames except slash (/) and Nul (\0). Since the capture should match multiple dirs / has to be allowed. As for Nul if that appears in this context there are probably larger issues so I'm not even going to worry about it here.

  • 1
    I can confirm this works! i just had to remove the if statement because the length of matched is actually 10 in my case. :)
    – millenito
    Aug 17, 2019 at 15:41
  • That's great. I just noticed an issue with the 'if' statement, though. I've updated the script with a corrected conditional.
    – B Layer
    Aug 17, 2019 at 16:13
  • Heh. I guess you edited your comment...we both noticed the 'if' problem. :) (I was wondering how it worked for you....until I saw your updated comment.) Anyways, you can add it back now using the new version. BTW, if nothing else is there I'd at least put an echoerr statement in the else.
    – B Layer
    Aug 17, 2019 at 16:20
  • One last thing. If you don't need anything else and are satisfied with the solution please don't forget to communicate the question's resolution to the community by accepting my answer. Thanks.
    – B Layer
    Aug 17, 2019 at 16:30
  • Oops sorry, didn't see that check mark, i thought all you had to do was upvoting, will accept the answer and thankyou for the help!
    – millenito
    Aug 17, 2019 at 17:52

in the middle of the outputted path

That depends upon what you think as "a middle". But, in general, you can simply repeat ":h" a few times. For example,

let realfile = expand('%:p')
let depth = count(realfile, '/') / 2
let filedir = expand('%:p' . repeat(':h', depth)) . '/_html/'

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.