]> git.madduck.net Git - etc/vim.git/blob - autoload/ale/path.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/ale/' content from commit 22185c4c
[etc/vim.git] / autoload / ale / path.vim
1 " Author: w0rp <devw0rp@gmail.com>
2 " Description: Functions for working with paths in the filesystem.
3
4 " simplify a path, and fix annoying issues with paths on Windows.
5 "
6 " Forward slashes are changed to back slashes so path equality works better
7 " on Windows. Back slashes are changed to forward slashes on Unix.
8 "
9 " Unix paths can technically contain back slashes, but in practice no path
10 " should, and replacing back slashes with forward slashes makes linters work
11 " in environments like MSYS.
12 "
13 " Paths starting with more than one forward slash are changed to only one
14 " forward slash, to prevent the paths being treated as special MSYS paths.
15 function! ale#path#Simplify(path) abort
16     if has('unix')
17         let l:unix_path = substitute(a:path, '\\', '/', 'g')
18
19         return substitute(simplify(l:unix_path), '^//\+', '/', 'g') " no-custom-checks
20     endif
21
22     let l:win_path = substitute(a:path, '/', '\\', 'g')
23
24     return substitute(simplify(l:win_path), '^\\\+', '\', 'g') " no-custom-checks
25 endfunction
26
27 " Simplify a path without a Windows drive letter.
28 " This function can be used for checking if paths are equal.
29 function! ale#path#RemoveDriveLetter(path) abort
30     return has('win32') && a:path[1:2] is# ':\'
31     \   ? ale#path#Simplify(a:path[2:])
32     \   : ale#path#Simplify(a:path)
33 endfunction
34
35 " Given a buffer and a filename, find the nearest file by searching upwards
36 " through the paths relative to the given buffer.
37 function! ale#path#FindNearestFile(buffer, filename) abort
38     let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p')
39     let l:buffer_filename = fnameescape(l:buffer_filename)
40
41     let l:relative_path = findfile(a:filename, l:buffer_filename . ';')
42
43     if !empty(l:relative_path)
44         return fnamemodify(l:relative_path, ':p')
45     endif
46
47     return ''
48 endfunction
49
50 " Given a buffer and a directory name, find the nearest directory by searching upwards
51 " through the paths relative to the given buffer.
52 function! ale#path#FindNearestDirectory(buffer, directory_name) abort
53     let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p')
54     let l:buffer_filename = fnameescape(l:buffer_filename)
55
56     let l:relative_path = finddir(a:directory_name, l:buffer_filename . ';')
57
58     if !empty(l:relative_path)
59         return fnamemodify(l:relative_path, ':p')
60     endif
61
62     return ''
63 endfunction
64
65 " Given a buffer, a string to search for, and a global fallback for when
66 " the search fails, look for a file in parent paths, and if that fails,
67 " use the global fallback path instead.
68 function! ale#path#ResolveLocalPath(buffer, search_string, global_fallback) abort
69     " Search for a locally installed file first.
70     let l:path = ale#path#FindNearestFile(a:buffer, a:search_string)
71
72     " If the search fails, try the global executable instead.
73     if empty(l:path)
74         let l:path = a:global_fallback
75     endif
76
77     return l:path
78 endfunction
79
80 " Given a buffer number, a base variable name, and a list of paths to search
81 " for in ancestor directories, detect the executable path for a program.
82 function! ale#path#FindNearestExecutable(buffer, path_list) abort
83     for l:path in a:path_list
84         if ale#path#IsAbsolute(l:path)
85             let l:executable = filereadable(l:path) ? l:path : ''
86         else
87             let l:executable = ale#path#FindNearestFile(a:buffer, l:path)
88         endif
89
90         if !empty(l:executable)
91             return l:executable
92         endif
93     endfor
94
95     return ''
96 endfunction
97
98 " Given a buffer number, a base variable name, and a list of paths to search
99 " for in ancestor directories, detect the executable path for a program.
100 "
101 " The use_global and executable options for the relevant program will be used.
102 function! ale#path#FindExecutable(buffer, base_var_name, path_list) abort
103     if ale#Var(a:buffer, a:base_var_name . '_use_global')
104         return ale#Var(a:buffer, a:base_var_name . '_executable')
105     endif
106
107     let l:nearest = ale#path#FindNearestExecutable(a:buffer, a:path_list)
108
109     if !empty(l:nearest)
110         return l:nearest
111     endif
112
113     return ale#Var(a:buffer, a:base_var_name . '_executable')
114 endfunction
115
116 " Return 1 if a path is an absolute path.
117 function! ale#path#IsAbsolute(filename) abort
118     if has('win32') && a:filename[:0] is# '\'
119         return 1
120     endif
121
122     " Check for /foo and C:\foo, etc.
123     return a:filename[:0] is# '/' || a:filename[1:2] is# ':\'
124 endfunction
125
126 let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h'))
127 let s:resolved_temp_dir = resolve(s:temp_dir)
128
129 " Given a filename, return 1 if the file represents some temporary file
130 " created by Vim. If the temporary location is symlinked (e.g. macOS), some
131 " linters may report the resolved version of the path, so both are checked.
132 function! ale#path#IsTempName(filename) abort
133     let l:filename = ale#path#Simplify(a:filename)
134
135     return l:filename[:len(s:temp_dir) - 1] is# s:temp_dir
136     \|| l:filename[:len(s:resolved_temp_dir) - 1] is# s:resolved_temp_dir
137 endfunction
138
139 " Given a base directory, which must not have a trailing slash, and a
140 " filename, which may have an absolute path a path relative to the base
141 " directory, return the absolute path to the file.
142 function! ale#path#GetAbsPath(base_directory, filename) abort
143     if ale#path#IsAbsolute(a:filename)
144         return ale#path#Simplify(a:filename)
145     endif
146
147     let l:sep = has('win32') ? '\' : '/'
148
149     return ale#path#Simplify(a:base_directory . l:sep . a:filename)
150 endfunction
151
152 " Given a path, return the directory name for that path, with no trailing
153 " slashes. If the argument is empty(), return an empty string.
154 function! ale#path#Dirname(path) abort
155     if empty(a:path)
156         return ''
157     endif
158
159     " For /foo/bar/ we need :h:h to get /foo
160     if a:path[-1:] is# '/' || (has('win32') && a:path[-1:] is# '\')
161         return fnamemodify(a:path, ':h:h')
162     endif
163
164     return fnamemodify(a:path, ':h')
165 endfunction
166
167 " Given a buffer number and a relative or absolute path, return 1 if the
168 " two paths represent the same file on disk.
169 function! ale#path#IsBufferPath(buffer, complex_filename) abort
170     " If the path is one of many different names for stdin, we have a match.
171     if a:complex_filename is# '-'
172     \|| a:complex_filename is# 'stdin'
173     \|| a:complex_filename[:0] is# '<'
174         return 1
175     endif
176
177     let l:test_filename = ale#path#Simplify(a:complex_filename)
178
179     if l:test_filename[:1] is# './'
180         let l:test_filename = l:test_filename[2:]
181     endif
182
183     if l:test_filename[:1] is# '..'
184         " Remove ../../ etc. from the front of the path.
185         let l:test_filename = substitute(l:test_filename, '\v^(\.\.[/\\])+', '/', '')
186     endif
187
188     " Use the basename for temporary files, as they are likely our files.
189     if ale#path#IsTempName(l:test_filename)
190         let l:test_filename = fnamemodify(l:test_filename, ':t')
191     endif
192
193     let l:buffer_filename = expand('#' . a:buffer . ':p')
194
195     return l:buffer_filename is# l:test_filename
196     \   || l:buffer_filename[-len(l:test_filename):] is# l:test_filename
197 endfunction
198
199 " Given a path, return every component of the path, moving upwards.
200 function! ale#path#Upwards(path) abort
201     let l:pattern = has('win32') ? '\v/+|\\+' : '\v/+'
202     let l:sep = has('win32') ? '\' : '/'
203     let l:parts = split(ale#path#Simplify(a:path), l:pattern)
204     let l:path_list = []
205
206     while !empty(l:parts)
207         call add(l:path_list, join(l:parts, l:sep))
208         let l:parts = l:parts[:-2]
209     endwhile
210
211     if has('win32') && a:path =~# '^[a-zA-z]:\'
212         " Add \ to C: for C:\, etc.
213         let l:path_list[-1] .= '\'
214     elseif a:path[0] is# '/'
215         " If the path starts with /, even on Windows, add / and / to all paths.
216         call map(l:path_list, '''/'' . v:val')
217         call add(l:path_list, '/')
218     endif
219
220     return l:path_list
221 endfunction
222
223 " Convert a filesystem path to a file:// URI
224 " relatives paths will not be prefixed with the protocol.
225 " For Windows paths, the `:` in C:\ etc. will not be percent-encoded.
226 function! ale#path#ToFileURI(path) abort
227     let l:has_drive_letter = a:path[1:2] is# ':\'
228
229     return substitute(
230     \   ((l:has_drive_letter || a:path[:0] is# '/') ? 'file://' : '')
231     \       . (l:has_drive_letter ? '/' . a:path[:2] : '')
232     \       . ale#uri#Encode(l:has_drive_letter ? a:path[3:] : a:path),
233     \   '\\',
234     \   '/',
235     \   'g',
236     \)
237 endfunction
238
239 function! ale#path#FromFileURI(uri) abort
240     if a:uri[:6] is? 'file://'
241         let l:encoded_path = a:uri[7:]
242     elseif a:uri[:4] is? 'file:'
243         let l:encoded_path = a:uri[5:]
244     else
245         let l:encoded_path = a:uri
246     endif
247
248     let l:path = ale#uri#Decode(l:encoded_path)
249
250     " If the path is like /C:/foo/bar, it should be C:\foo\bar instead.
251     if has('win32') && l:path =~# '^/[a-zA-Z][:|]'
252         let l:path = substitute(l:path[1:], '/', '\\', 'g')
253         let l:path = l:path[0] . ':' . l:path[2:]
254     endif
255
256     return l:path
257 endfunction