1 " ============================================================================
4 " Description: Vim global plugin that provides decent companion source file
7 " Maintainer: Derek Wyatt <derek at myfirstnamemylastname dot org>
9 " Last Change: March 23rd 2009
11 " License: This program is free software. It comes without any warranty,
12 " to the extent permitted by applicable law. You can redistribute
13 " it and/or modify it under the terms of the Do What The Fuck You
14 " Want To Public License, Version 2, as published by Sam Hocevar.
15 " See http://sam.zoy.org/wtfpl/COPYING for more details.
16 " ============================================================================
18 if exists("g:disable_fswitch")
23 echoerr "FSwitch requires Vim 7.0 or higher!"
28 let s:fswitch_version = '0.9.3'
30 " Get the path separator right
31 let s:os_slash = &ssl == 0 && (has("win16") || has("win32") || has("win64")) ? '\' : '/'
33 " Default locations - appended to buffer locations unless otherwise specified
34 let s:fswitch_global_locs = '.' . s:os_slash
39 " There are two variables that need to be set in the buffer in order for things
40 " to work correctly. Because we're using an autocmd to set things up we need to
41 " be sure that the user hasn't already set them for us explicitly so we have
42 " this function just to check and make sure. If the user's autocmd runs after
43 " ours then they will override the value anyway.
45 function! s:SetVariables(dst, locs)
46 if !exists("b:fswitchdst")
47 let b:fswitchdst = a:dst
49 if !exists("b:fswitchlocs")
50 let b:fswitchlocs = a:locs
57 " Return the list of possible locations
59 function! s:FSGetLocations()
61 if exists("b:fswitchlocs")
62 let locations = split(b:fswitchlocs, ',')
64 if !exists("b:fsdisablegloc") || b:fsdisablegloc == 0
65 let locations += split(s:fswitch_global_locs, ',')
74 " Return the list of destination extensions
76 function! s:FSGetExtensions()
77 return split(b:fswitchdst, ',')
83 " Return a boolean on whether or not the regex must match
85 function! s:FSGetMustMatch()
87 if exists("b:fsneednomatch") && b:fsneednomatch != 0
95 " s:FSGetFullPathToDirectory
97 " Given the filename, return the fully qualified directory portion
99 function! s:FSGetFullPathToDirectory(filename)
100 return expand(a:filename . ':p:h')
104 " s:FSGetFileExtension
106 " Given the filename, returns the extension
108 function! s:FSGetFileExtension(filename)
109 return expand(a:filename . ':e')
113 " s:FSGetFileNameWithoutExtension
115 " Given the filename, returns just the file name without the path or extension
117 function! s:FSGetFileNameWithoutExtension(filename)
118 return expand(a:filename . ':t:r')
122 " s:FSGetAlternateFilename
124 " Takes the path, name and extension of the file in the current buffer and
125 " applies the location to it. If the location is a regular expression pattern
126 " then it will split that up and apply it accordingly. If the location pattern
127 " is actually an explicit relative path or an implicit one (default) then it
128 " will simply apply that to the file directly.
130 function! s:FSGetAlternateFilename(filepath, filename, newextension, location, mustmatch)
131 let parts = split(a:location, ':')
133 let directive = parts[0]
136 let directive = parts[1]
138 if cmd == 'reg' || cmd == 'ifrel' || cmd == 'ifabs'
139 if strlen(directive) < 3
140 throw 'Bad directive "' . a:location . '".'
142 let separator = strpart(directive, 0, 1)
143 let dirparts = split(strpart(directive, 1), separator)
144 if len(dirparts) < 2 || len(dirparts) > 3
145 throw 'Bad directive "' . a:location . '".'
147 let part1 = dirparts[0]
148 let part2 = dirparts[1]
150 if len(dirparts) == 3
151 let flags = dirparts[2]
154 if a:mustmatch == 1 && match(a:filepath, part1) == -1
157 let path = substitute(a:filepath, part1, part2, flags) . s:os_slash .
158 \ a:filename . '.' . a:newextension
160 elseif cmd == 'ifrel'
161 if match(a:filepath, part1) == -1
164 let path = a:filepath . s:os_slash . part2 .
165 \ s:os_slash . a:filename . '.' . a:newextension
167 elseif cmd == 'ifabs'
168 if match(a:filepath, part1) == -1
171 let path = part2 . s:os_slash . a:filename . '.' . a:newextension
177 let path = a:filepath . s:os_slash . directive . s:os_slash . a:filename . '.' . a:newextension
179 let path = directive . s:os_slash . a:filename . '.' . a:newextension
182 return simplify(path)
186 " s:FSReturnCompanionFilename
188 " This function will return a path that is the best candidate for the companion
189 " file to switch to. If mustBeReadable == 1 when then the companion file will
190 " only be returned if it is readable on the filesystem, otherwise it will be
191 " returned so long as it is non-empty.
193 function! s:FSReturnCompanionFilename(filename, mustBeReadable)
194 let fullpath = s:FSGetFullPathToDirectory(a:filename)
195 let ext = s:FSGetFileExtension(a:filename)
196 let justfile = s:FSGetFileNameWithoutExtension(a:filename)
197 let extensions = s:FSGetExtensions()
198 let locations = s:FSGetLocations()
199 let mustmatch = s:FSGetMustMatch()
201 for currentExt in extensions
203 let newpath = s:FSGetAlternateFilename(fullpath, justfile, currentExt, loc, mustmatch)
204 if a:mustBeReadable == 0 && newpath != ''
206 elseif a:mustBeReadable == 1
207 let newpath = glob(newpath)
208 if filereadable(newpath)
219 " FSReturnReadableCompanionFilename
221 " This function will return a path that is the best candidate for the companion
222 " file to switch to, so long as that file actually exists on the filesystem and
225 function! FSReturnReadableCompanionFilename(filename)
226 return s:FSReturnCompanionFilename(a:filename, 1)
230 " FSReturnCompanionFilenameString
232 " This function will return a path that is the best candidate for the companion
233 " file to switch to. The file does not need to actually exist on the
234 " filesystem in order to qualify as a proper companion.
236 function! FSReturnCompanionFilenameString(filename)
237 return s:FSReturnCompanionFilename(a:filename, 0)
243 " This is the only externally accessible function and is what we use to switch
244 " to the alternate file.
246 function! FSwitch(filename, precmd)
247 if !exists("b:fswitchdst") || strlen(b:fswitchdst) == 0
248 throw 'b:fswitchdst not set - read :help fswitch'
250 if (!exists("b:fswitchlocs") || strlen(b:fswitchlocs) == 0) &&
251 \ (!exists("b:fsdisablegloc") || b:fsdisablegloc == 0)
252 throw "There are no locations defined (see :h fswitchlocs and :h fsdisablegloc)"
254 let newpath = FSReturnReadableCompanionFilename(a:filename)
256 if !filereadable(newpath)
257 if exists("b:fsnonewfiles") || exists("g:fsnonewfiles")
260 let newpath = FSReturnCompanionFilenameString(a:filename)
265 if strlen(a:precmd) != 0
268 execute 'edit ' . fnameescape(newpath)
270 echoerr "Alternate has evaluated to nothing. See :h fswitch-empty for more info."
273 echoerr "No alternate file found. 'fsnonewfiles' is set which denies creation."
278 " The autocmds we set up to set up the buffer variables for us.
280 augroup fswitch_au_group
282 au BufEnter *.h call s:SetVariables('cpp,c', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|')
283 au BufEnter *.c,*.cpp call s:SetVariables('h', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|')
287 " The mappings used to do the good work
289 com! FSHere :call FSwitch('%', '')
290 com! FSRight :call FSwitch('%', 'wincmd l')
291 com! FSSplitRight :call FSwitch('%', 'vsplit | wincmd l')
292 com! FSLeft :call FSwitch('%', 'wincmd h')
293 com! FSSplitLeft :call FSwitch('%', 'vsplit | wincmd h')
294 com! FSAbove :call FSwitch('%', 'wincmd k')
295 com! FSSplitAbove :call FSwitch('%', 'split | wincmd k')
296 com! FSBelow :call FSwitch('%', 'wincmd j')
297 com! FSSplitBelow :call FSwitch('%', 'split | wincmd j')