Expand relative path wrt containing file - vim

I am reading a config file, foo.yml with Vimscript. This file contains a parameter that is the relative path to a directory. This path is relative to the foo.yml file, not my current working directory.
I need to expand this relative path to an absolute path.
I have tried using fnamemodify(path, ':p') and expand(path) without luck. I think these functions seem to get confused because from the current working directory the relative path is invalid. So it keeps the path as is.
Is there a way to make Vim use the foo.yml as the point-of-reference when resolving relative paths? Or any other function that can do the same?
Thanks for your help.

In order to expand relative to the file's directory, it's easiest to temporarily :cd into that directory. Here's some sample code that does this for the current file (%); you have to adapt this to work with a passed filespec.
if expand('%:h') !=# '.'
" Need to change into the file's directory first to get glob results
" relative to the file.
let l:save_cwd = getcwd()
let l:chdirCommand = (haslocaldir() ? 'lchdir!' : 'chdir!')
execute l:chdirCommand '%:p:h'
endif
try
" Get the full path to a:filespec, relative to the current file's directory.
let l:absoluteFilespec = fnamemodify(a:filespec, ':p')
finally
if exists('l:save_cwd')
execute l:chdirCommand fnameescape(l:save_cwd)
endif
endtry

How about
:let dir = expand('%:p:h')
:let absolute_path = dir . '/' . path
You will have to work harder if you want it to work on Windows, too.
:help expand()
:help filename-modifiers
:help file-functions

Related

How can I source files relative to file?

I'm trying to split my vimrc up into multiple files - init.vim, keybindings.vim, ui.vim, etc. - but I can't get Vim to source files relative to init.vim (it instead sources relative to where I launch Vim from.
This is what I've got at the top of init.vim:
source keybindings.vim
source ui.vim
If I run vim from the same directory as those files, it works fine; if I run it from any other directory, I get the following errors:
Error detected while processing /path/to/vimrc:
line 1:
E484: Can't open file keybindings.vim
line 2:
E484: Can't open file ui.vim
Press ENTER or type command to continue
Edit: It's worth noting that I'm using NixOS, so I don't know what the absolute paths will be, nor if they would be constant if I found out.
I think you can use
runtime keybindings.vim
Source needs the full path, you can however simplify it using something like this :
let path = expand('%:p:h')
exec 'source' path . '/keybindings.vim'
You can have a look at mine here - https://github.com/dhruvasagar/dotfiles/blob/master/vim/vimrc for reference.
If the order is not important, you can just put your scripts into ~/.vim/plugin/, and they will be sourced after ~/.vimrc. You can check :scriptnames output to see what gets sourced when.
You can influence the ordering somewhat via the plugin filenames. For example, I have a ~/.vim/plugin/00plugin-configuration.vim that configures Vim plugins; the 00... ensures this is sourced first.
To get finer control, I would instead put the scripts into ~/.vim/. Vim will ignore them there, but they can easily be addressed via :runtime, which looks in all runtimepaths, and ~/.vim/ typically is included in 'runtimepath':
# .vimrc
runtime init.vim
runtime keybindings.vim
...
Relevant help pages: :help .vimrc and :help load-plugins.
Building on Dhruva's answer, you can make a function to help out with this
function! SourceLocal(relativePath)
let root = expand('%:p:h')
let fullPath = root . '/'. a:relativePath
exec 'source ' . fullPath
endfunction
You then use it like
call SourceLocal ("yourScript.vim")
I have met exactly the same issue with you in Neovim. I split my large init.vim file into several small vim scripts and I want to source them inside init.vim.
This is what I get finally based on #Dhruva Sagar's links:
let g:nvim_config_root = stdpath('config')
let g:config_file_list = ['variables.vim',
\ 'options.vim',
\ 'autocommands.vim',
\ 'mappings.vim',
\ 'plugins.vim',
\ 'ui.vim'
\ ]
for f in g:config_file_list
execute 'source ' . g:nvim_config_root . '/' . f
endfor
As none of the solutions works as a real substitute for source working globally (on any script, even sourced from vimrc), I ended up with this solution and decided to share here, which can be used as a substitute for source with relative support, as simple as:
Rsource /home/me/.vim/your/file/path
Rsource $HOME/.vim/your/file/path
Rsource your/file/path
Rsource ../your/file/path
To use it, this must be defined on your vimrc or any file sourced by it before you can use Rsource:
if !exists('g:RelativeSource')
function! g:RelativeSource(file)
let file = expand(a:file)
" if file is a root path, just source it
if stridx(file, '/') == 0
exec 'source ' . file
return
endif
let sfile = expand('<sfile>:p:h')
" If this is called outside this script, it will contains this script
" name, this function name, a script_marker then the executing script name
" In this case we extract just the last part, the script name which called
" the this function
let script_marker = '..script '
let path_index = strridx(sfile, script_marker)
if path_index == -1
let path_index = 0
else
let path_index += len(script_marker)
endif
let path = strpart(sfile,path_index)
let absolute_path = resolve(path . '/'. file)
exec 'source ' . absolute_path
endfunction
command! -nargs=1 Rsource :call g:RelativeSource(<q-args>)
endif
This is safe to be used in any script or plugin.
These are all great solutions, and this is what I ended up using.
let home = expand('~')
exec 'source' home . '/.config/nvim/prettierConfig.vim'

Displaying the relative path of the currently edited file in Vim's statusline?

:help statusline claims %f will render as:
Path to the file in the buffer, as typed or relative to current directory.
When I set statusline=%f, the path in the statusline is sometimes relative, but often absolute.
Is there a way to make sure the path displayed is always relative?
There may be a better way, but you could try this:
set stl+=%{expand('%:~:.')}
The expression inside %{} should be evaluated and added to your statusline. Here the expression is:
expand('%:~:.')
... which expands the name of the current file, but prevents the expansion of the tilde (:~), and makes the path relative to the current working directory (:.).
Just below the explanation of %f, you will find explanation of F. Using %F instead of %f will give you the desired display.
item meaning ~
f S Path to the file in the buffer, as typed or relative to current
directory.
F S Full path to the file in the buffer.
you can type :!echo % to view path in shell
and :pwd shows the current working directory of vim

Find command in vim gets incomplete path

In vim when I'm using :find to open another file, it misses the first component of the relative path.
For example, if I'm looking for a file that's in:
./foo/bar/file.txt
I'll type
:find **/file.txt
It finds the file but then tries to open
bar/file.txt
It works correctly if I type
./**/file.txt
But I'm lazy and don't want to type that much. Is there some config I'm missing that will correctly locate and open this path?
My Solution
I simply appended the main source code dir to my path
exec "set path^=src/**"
Is your 'path' set? That (IMO) is a pretty handy way to keep from even typing the **/ bit.
In my setup, there's an environment variable that defines which project I'm currently in so I use that and construct a path with that as the root. In a nutshell:
let s:rootdir = $PROJECT_DIR
let s:path = 'src/**;' . s:rootdir . ',scripts/**;' . s:rootdir
execute "set path=" . s:path
Then I can just :find a_file.txt and it searches my src hierarchy then my scripts hierarchy for the file.

Getting relative paths in Vim

Say I am running Vim and pwd returns
/home/rafid/myproject
And say I am currently editing the file
/home/rafid/myproject/website/editpage.php
Is there any command that returns this for me?
website/editpage.php
That is, the path of the file relative to the current folder.
Although expand('%') often works, there are rare occasions where it does not. But you can force Vim to always present the relative path by calling fnamemodify:
:echo fnamemodify(expand("%"), ":~:.")
From the manual:
:. Reduce file name to be relative to current directory, if
possible. File name is unmodified if it is not below the
current directory.
For maximum shortness, use ":~:.".
The :~ is optional. It will reduce the path relative to your home folder if possible (~/...). (Unfortunately that only works on your home; it won't turn /home/fred into ~fred if you aren't logged in as fred.)
As Adam pointed out the comments, this can be shortened to:
:echo expand("%:~:.")
Reference: :h expand<Tab> and :h fnamem<Tab>
If you are limited for space (e.g. using this in your statusline), and can manage with "fuzzy" information about where the file is located, then check out pathshorten() which compresses folder names down to one character:
:echo pathshorten('~/.vim/autoload/myfile.vim')
~/.v/a/myfile.vim
Reference: :h pathsh<Tab>
Another option would be to write a vim function. Here's my humble attempt:
function! Relpath(filename)
let cwd = getcwd()
let s = substitute(a:filename, l:cwd . "/" , "", "")
return s
endfunction
You call Relpath with any full path name, and it will strip the current directory name from its argument.
For example, try :echo Relpath(expand("%:p")) (the :p modifier asks Vim to return the full path). Obviously, this is not necessary in your case, since % by itself returns relative path. However, it might come in handy in other cases.
This works for me :
:echo expand("%")
if you use autocmd to always set the current directory of the buffer that you are working on ( cd %:p:h ) then you can just type :cd
Blockquote
This works for me :
:echo expand("%")
This is only working if you opened that file with a relative file:
for vi ./foo, expand("%") will be ./foo
but
for vi /tmp/foo expand("%") will be /tmp/foo
Yes, you can use
:args
This will give you the filename of the current file, for informational purposes.
A workaround can be :cd . which seems to re-evaluate the path relative-ness. I agree this is very annoying though.

How can I expand the full path of the current file to pass to a command in Vim?

When I go to command mode and type
:!mycommand %
I get my command executed on the current file (% is expanded to the current file name).
Is there a similar construct that expands the full file name (with the full path)?
I am using Windows.
:!mycommand %:p
Related:
:!cd %:p:h
The other two answers didn’t work for me (for some reason). However, I found that this combo displays the full path when typed in Normal mode:
Press 1 then CtrlG
Source: “Get the name of the current file” on the Vim Tips Wiki. See also the {count}CTRL-G section of :help CTRL-G.
Append :p, e.g.
:!mycommand %:p
And %:p:h will give you the path of the directory that the file resides in.
To print out the current vim filename:
:help expand
:echo expand("%:p") " absolute path
:echo expand("%:p:h") " absolute path dirname
:echo expand("%:p:h:h")" absolute path dirname dirname
:echo expand("%:.") " relative path
:echo expand("%:.:h") " relative path dirname
:echo expand("%:.:h:h")" relative path dirname dirname
:echo expand("<sfile>:p") " absolute path to [this] vimscript
:help filename-modifiers
For example (with a vim function), to resolve() and expand() any symlinks to the absolute path to the current script <sfile>:p (instead of %:p), and then exec to source the fnameescape-ed filename contained in a function-local vim variable l:vimrcfilename:
" g:__sfile__dirname -- directory containing this vimrc script
" after symlinks
" ~dirname(abspath(realpath(__file__)))
let g:__sfile__dirname=fnamemodify(resolve(expand("<sfile>:p")), ":h")
" Source_dotvim(filename) -- source dirname(this_vimrc)/filename
function Source_dotvim(filename)
let l:vimrcfilename=g:__sfile__dirname . "/" . a:filename
if filereadable(l:vimrcfilename) && !empty(l:vimrcfilename)
"source s:vimrcfilename "this doesn't work, so exec:
exec "source " . fnameescape(l:vimrcfilename)
else
echo l:vimrcfilename . " empty or not found."
endif
endfunction
call Source_dotvim("vimrc.local.01-env.vimrc")
Notes:
:help fnamemodify
"current file": %:p (buffer file) / <sfile>:p (script file)
expand('<sfile>') does not work from within a function
source l:varname does not work; so, exec - string concatenation - and fnameescape
:messages prints the most-recent 200 vim [error,] messages
References
annika-backstrom's answer
umber-ferrule's answer
tito-11's answer
Get the name of the current file
http://vim.wikia.com/wiki/Get_the_name_of_the_current_file
Set_working_directory_to_the_current_file
http://vim.wikia.com/wiki/Set_working_directory_to_the_current_file
If you want to use the full path in your vimrc, you can use something like this:
let vimFiles = '$HOME/.vim'
let absPath = expand(vimFiles . '/subDir')
This will give you a path with backslashes on Windows.

Resources