Whenever I need to write something I use VIM. However most of my work requires that I use Windows OS. I have WAMP server running and I used to edit PHP files just in VIM in WSL2. I then thought why not to set up a debugger and take advantage of it in VIM.

I managed to set everything up (surprisingly, VIM listens to port 9000 in WSL2 and establishes communication with XDebug from native Windows WAMP installation). However, when I hit a breakpoint VIM (VDebug to be exact) tries to open file and here comes a problem:

XDebug passes file path as D:\something\htdocs\somefile.php but in WSL this file has path /mnt/d/something/htdocs/somefile.php

As a result I have an empty tab in VIM.

Does anyone know a workaround for that? I almost got it working (staying in linux terminal and using VIM would be a dream)...

P.S. The obvious workaround is to set up XAMP in WSL but this is too messy. I already have tuned WAMP setup and want to stick to it.

Edit 1:

Using path_maps option in VDebug I managed

let g:vdebug_options = { 'path_maps': { 'D:': '/mnt/d' } }

to get almost the correct paths. The problem is that windows paths use \ and WSL recognizes \ as escape character. So I need to convert \ to /, but that seems to be outside the scope of VDebug path_maps option. Investigating...

  • If you can hook into the part where vim gets a file from XDebug, it should be straightforward to make the substitution (something like tr(xdebug_path, '\', '/')->substitute('^\(\u\):\(.*\)', '/mnt/\l\1\2', '') worked on your example for me)
    – D. Ben Knoble
    Apr 3, 2021 at 14:59
  • I don't know anything XDebug but if you can find some way to filter or pre-process paths then you could use wslpath which translates between WSL and Win paths, either direction. (It is almost identical to Cygwin's cygpath, if you're familiar with that. Same flags and everything.)
    – B Layer
    Apr 3, 2021 at 15:02

1 Answer 1


The solution was path_maps option in vdebug plugin:

let g:vdebug_options = { 'path_maps': { 'D:\Dropbox': '/mnt/d/Dropbox' } }

The code of how path_maps are parsed is located in util.py file of the plugin and reads (at the time of writing this post):

  219         if opts.Options.isset('path_maps'):
    1             sorted_path_maps = sorted(
    2                 opts.Options.get('path_maps', dict).items(),
    3                 key=lambda l: len(l[0]), reverse=True)
    4             for remote, local in sorted_path_maps:
    5                 if remote in ret:
    6                     log.Log("Replacing remote path (%s) with local path (%s)"
    7                             % (remote, local), log.Logger.DEBUG)
    8                     if not local.endswith('/') and remote.endswith('/'):
    9                         local = local+'/'
   10                     elif local.endswith('/') and not remote.endswith('/'):
   11                         local = local[:-1]
   12                     ret = ret.replace(remote, local, 1)
   14                     # determine remote path separator and replace by local
   15                     local_sep = self._findSeparator(local)
   16                     remote_sep = self._findSeparator(remote)
   17                     if local_sep and remote_sep and remote_sep != local_sep:
   18                         ret = ret.replace(remote_sep, local_sep)
   19                     break
   21         return ret

As you can see it tries to determine the local and remote separator.

The only caveat is that 'D:\' : '/mnt/d/' won't work since it removes the trailing separator in remote path, so "D:\Dropbox" will become "/mnt/dDropbox/".

I therefore had to add one folder deep into the path explicitly.

Thank you everyone who commented - these comments led to the correct solution.

P.S. One could modify the substitution in util.py as suggested, but I decided to use more robust solution, even though it won't work for files in "D:" root folder (don't need that anyway).

  • 1
    That's a pretty brittle routine they have there. I wouldn't be able to resist extending it so local can be a "filter" script/executable which takes a remote-type path as it's only param. Specify { 'filter': 'filterprog' } (or whatever notation would be safe). Then with { 'filter': 'wslpath' } and ret value D:\foo\bar, wslpath 'D:\foo\bar' would be run and you'd get '/mnt/d/foo/bar'...and it would work for any value of ret. But I've been known to get carried away. ;)
    – B Layer
    Apr 4, 2021 at 5:20
  • 1
    And this could be used by Cygwin users, too, with "filter" cygpath. :) (I'm a heavy user of both WSL and Cyg.)
    – B Layer
    Apr 4, 2021 at 5:26

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.