Rainbow
[my-vim-dotfolder.git] / plugin / fswitch.vim
blob3929a695938c6c4ea69b5a89e61827aa958a012b
1 " ============================================================================
2 " File:        fswitch.vim
4 " Description: Vim global plugin that provides decent companion source file
5 "              switching
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")
19     finish
20 endif
22 if v:version < 700
23   echoerr "FSwitch requires Vim 7.0 or higher!"
24   finish
25 endif
27 " Version
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
37 " s:SetVariables
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
48     endif
49     if !exists("b:fswitchlocs")
50         let b:fswitchlocs = a:locs
51     endif
52 endfunction
55 " s:FSGetLocations
57 " Return the list of possible locations
59 function! s:FSGetLocations()
60     let locations = []
61     if exists("b:fswitchlocs")
62         let locations = split(b:fswitchlocs, ',')
63     endif
64     if !exists("b:fsdisablegloc") || b:fsdisablegloc == 0
65         let locations += split(s:fswitch_global_locs, ',')
66     endif
68     return locations
69 endfunction
72 " s:FSGetExtensions
74 " Return the list of destination extensions
76 function! s:FSGetExtensions()
77     return split(b:fswitchdst, ',')
78 endfunction
81 " s:FSGetMustMatch
83 " Return a boolean on whether or not the regex must match
85 function! s:FSGetMustMatch()
86     let mustmatch = 1
87     if exists("b:fsneednomatch") && b:fsneednomatch != 0
88         let mustmatch = 0
89     endif
91     return mustmatch
92 endfunction
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')
101 endfunction
104 " s:FSGetFileExtension
106 " Given the filename, returns the extension
108 function! s:FSGetFileExtension(filename)
109     return expand(a:filename . ':e')
110 endfunction
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')
119 endfunction
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, ':')
132     let cmd = 'rel'
133     let directive = parts[0]
134     if len(parts) == 2
135         let cmd = parts[0]
136         let directive = parts[1]
137     endif
138     if cmd == 'reg' || cmd == 'ifrel' || cmd == 'ifabs'
139         if strlen(directive) < 3
140             throw 'Bad directive "' . a:location . '".'
141         else
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 . '".'
146             else
147                 let part1 = dirparts[0]
148                 let part2 = dirparts[1]
149                 let flags = ''
150                 if len(dirparts) == 3
151                     let flags = dirparts[2]
152                 endif
153                 if cmd == 'reg'
154                     if a:mustmatch == 1 && match(a:filepath, part1) == -1
155                         let path = ""
156                     else
157                         let path = substitute(a:filepath, part1, part2, flags) . s:os_slash .
158                                     \ a:filename . '.' . a:newextension
159                     endif
160                 elseif cmd == 'ifrel'
161                     if match(a:filepath, part1) == -1
162                         let path = ""
163                     else
164                         let path = a:filepath . s:os_slash . part2 . 
165                                      \ s:os_slash . a:filename . '.' . a:newextension
166                     endif
167                 elseif cmd == 'ifabs'
168                     if match(a:filepath, part1) == -1
169                         let path = ""
170                     else
171                         let path = part2 . s:os_slash . a:filename . '.' . a:newextension
172                     endif
173                 endif
174             endif
175         endif
176     elseif cmd == 'rel'
177         let path = a:filepath . s:os_slash . directive . s:os_slash . a:filename . '.' . a:newextension
178     elseif cmd == 'abs'
179         let path = directive . s:os_slash . a:filename . '.' . a:newextension
180     endif
182     return simplify(path)
183 endfunction
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()
200     let newpath = ''
201     for currentExt in extensions
202         for loc in locations
203             let newpath = s:FSGetAlternateFilename(fullpath, justfile, currentExt, loc, mustmatch)
204             if a:mustBeReadable == 0 && newpath != ''
205                 return newpath
206             elseif a:mustBeReadable == 1
207                 let newpath = glob(newpath)
208                 if filereadable(newpath)
209                     return newpath
210                 endif
211             endif
212         endfor
213     endfor
215     return newpath
216 endfunction
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
223 " is readable.
225 function! FSReturnReadableCompanionFilename(filename)
226     return s:FSReturnCompanionFilename(a:filename, 1)
227 endfunction
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)
238 endfunction
241 " FSwitch
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'
249     endif
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)"
253     endif
254     let newpath = FSReturnReadableCompanionFilename(a:filename)
255     let openfile = 1
256     if !filereadable(newpath)
257         if exists("b:fsnonewfiles") || exists("g:fsnonewfiles")
258             let openfile = 0
259         else
260             let newpath = FSReturnCompanionFilenameString(a:filename)
261         endif
262     endif
263     if openfile == 1
264         if newpath != ''
265             if strlen(a:precmd) != 0
266                 execute a:precmd
267             endif
268             execute 'edit ' . fnameescape(newpath)
269         else
270             echoerr "Alternate has evaluated to nothing.  See :h fswitch-empty for more info."
271         endif
272     else
273         echoerr "No alternate file found.  'fsnonewfiles' is set which denies creation."
274     endif
275 endfunction
278 " The autocmds we set up to set up the buffer variables for us.
280 augroup fswitch_au_group
281     au!
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|')
284 augroup END
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')