1

Suppose I have a filename f that may or may not be absolute. And I have a directory d that f is in, that isn't (or at least might not be) the current working directory, or the home directory.

What is a good way to get the relative path of f with respect to d.

Since I need it to work with an arbitrary directory, fnamemodify (and expand) don't really work.

I'd ideally like a solution that doesn't require a plugin.

I use neovim, so neovim specific solutions are acceptable.

3
  • What's wrong with :help fnamemodify()? Commented Apr 11 at 19:20
  • @romainl, fnamemodify only allows you to get a relative path with respect to the working directory or the home directory, but I need this to user-specified directory that might be neither of those. Commented Apr 15 at 4:12
  • I addressed that point in my answer. Commented Apr 15 at 5:40

3 Answers 3

2

Most of file system related functions are just wrappers over stdlib / OS specific APIs. And the one you want does not really belong to any standard API, AFAIK. Therefore, you have to implement it yourself, as with any other exotic.

The easiest way would be, of course, to change current working directory temporarily, use appropriate standard functions and go back. But you're free to do it any other way you like.

Sign up to request clarification or add additional context in comments.

Comments

2

Assuming the following:

  • f is the name of a buffer loaded in Vim, in which case it is either an absolute path or a path relative to the working directory,
  • d is not simply a directory "name", but its path,
  • those are stored in variables d and f,

you can get the absolute path of directory d with:

let absolute_path_of_d = fnamemodify(d, ':p')
echo absolute_path_of_d
/a/b/c/d/

and the absolute path of file f with:

let absolute_path_of_f = fnamemodify(f, ':p')
echo absolute_path_of_f
/a/b/c/d/e/f

From there, getting the path of f relative to d is just a matter of removing the path of d from the path of f:

let path_of_f_relative_to_d = substitute(absolute_path_of_f, absolute_path_of_d, '', '')
echo path_of_f_relative_to_d
e/f

See :help fnamemodify() and :help filename-mofifiers.

Now, this won't work if directory d and filename f are really just arbitrary strings d and f, because fnamemodify() only deals with paths in the abstract, never checking if d and f actually exist or not. Moreover, if you don't use a full path, d and f will be assumed to be relative to the working directory, which is probably why you wrote:

fnamemodify (and expand) don't really work.

Vimscript is only really meant to work with Vim… "objects", for lack of a better word. If you really only have the strings d and f to work with, then you will have to go low-level, and find the paths of directory d and file f relative to file system root.

Vimscript's :help finddir() and :help findfile() (or whatever Lua library you can find) can be used, here, but there's a catch: there might be any number of directories named d and files named f on your filesystem. I have 4104 files named index.js under directories named src in my $HOME, so which src and which index.js is it? Is it acceptable to return all of them? Only the first?

Comments

2

You can also rely on realpath from GNU coreutils. The only limitation is that both paths should exist on the file system.

function! GetRelPath(file, base)
  return system($"realpath --relative-base='{a:base}' '{a:file}'")
endfunction

echo GetRelPath(f->fnamemodify(':p'), d->fnamemodify(':p'))
echo GetRelPath('/hello/world/foo/bar', '/hello/world') " prints 'foo/bar'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.