1 *fswitch.txt* For Vim version 7.2 and above Last change: 2009 Mar 23
7 Author: Derek Wyatt (derek at myfirstnamemylastname dot org)
10 Copyright: The VIM LICENSE applies to fswitch.vim, and fswitch.txt
11 (see |copyright|) except use "fswitch" instead of "Vim".
12 No warranty, express or implied.
15 ==============================================================================
16 *fswitch* *fswitch-contents*
19 1. Contents .............................: |fswitch-contents|
20 2. About ................................: |fswitch-about|
21 3. Features .............................: |fswitch-features|
22 4. Setup ................................: |fswitch-setup|
23 5. Configuration ........................: |fswitch-configure|
24 6. "Creating" the Alternate File ........: |fswitch-altcreate|
25 7. Useful Mappings ......................: |fswitch-mappings|
26 8. FSwitch() ............................: |fswitch-function|
27 9. FSReturnCompanionFilenameString().....: |fswitch-getcompanion|
28 10. FSReturnReadableCompanionFilename()...: |fswitch-getreadablecomp|
29 11. The Default Settings .................: |fswitch-defaults|
30 12. Examples .............................: |fswitch-examples|
31 13. Troubleshooting ......................: |fswitch-trouble|
32 A. Change History .......................: |fswitch-changes|
34 ==============================================================================
38 FSwitch is designed to allow you to switch between companion files of source
39 code (e.g. "cpp" files and their corresponding "h" files). The source for
40 this came from a home-grown script that was influenced later by the
41 "Alternate" (a.vim) script.
43 The original intention was to modify the existing a.vim script to do what the
44 home-grown version could do (choose to open the other file in an existing
45 window) but it was a rather complex script and modification looked difficult
46 so the choice was made to simply move the home-grown script forward a couple
47 of notches and produce a new plugin. This doc file is twice the length of the
48 actual code at this point :)
50 ==============================================================================
54 FSwitch has the following features:
56 - Switches between a file and its companion file
57 - Ability to create a new file using a preferential location
58 - Simple configuration using buffer-local variables
59 - It's got a really long doc file (... seriously, why is this thing so
63 ==============================================================================
67 Most of the behaviour of FSwitch is customized via buffer-local variables.
68 You set up the variables with auto commands:
70 au! BufEnter *.cpp let b:fswitchdst = 'hpp,h' | let b:fswitchlocs = '../inc'
72 That |:autocmd| will set the 'fswitchdst' and 'fswitchlocs' variables when the
73 |BufEnter| event takes place on a file whose name matches {*.cpp} (e.g. when
74 you enter the buffer containing the {MyFile.cpp} file).
76 The variables above state that the alternate file to {MyFile.cpp} are
77 {MyFile.hpp} and {MyFile.h} preferred in that order, and located in the {inc}
78 directory at the same level as the current directory.
80 That should get you there but there's more capability here if you want. To
81 get that move on to |fswitch-configure|.
83 ==============================================================================
89 'fswitchdst' string (default depends on file in current buffer)
92 The 'fswitchdst' variable denotes the file extension that is the
93 target extension of the current file's companion file. For example:
95 :let b:fswitchdst = 'cpp,cxx,C'
97 The above specifies that the current buffer's file has a companion
98 filename which can be found by replacing the current extension with
99 {cpp}, {cxx} or {C}. The extensions will be tried in this order and
100 the first match wins.
102 'fswitchdst' is taken relative to directories that are found in the
103 'fswitchlocs' variable.
106 'fswitchlocs' string (default depends on filen in current buffer)
109 The 'fswitchlocs' variable contains a set of directives that indicate
110 directory names that should be formulated when trying to find the
111 alternate file. For example:
113 " Create the destination path by substituting any
114 " 'include' string from the pathname with 'src'
115 :let b:fswitchlocs = 'reg:/include/src/'
117 " First try adding the relative path '../src' to the path
118 " in which the file in the buffer exists and if that fails
119 " then try using 'source' instead
120 :let b:fswitchlocs = 'rel:../src,source'
122 " Same as above but leaving off the optional 'rel:'
123 :let b:fswitchlocs = '../src,../source'
125 The following types of directives are understood:
129 A regular expression. The regular expression takes the form:
131 {delim}{pat}{delim}{globsub}{delim}
135 {delim} is something that doesn't appear in {pat} or
136 {globsub} used to delimit the {pat} and {globsub}
138 {pat} is a standard pattern to search on
140 {globsub} is a substitution string that will be run through
141 the |glob()| function.
145 A relative path. The {rel:} is actually optional. If you
146 leave this off, then FSwitch will assume that the string is
147 denoting a relative path.
151 Takes the same form as {:reg} but the {globsub} part of the
152 directive is a relative path. The relative path is only used
153 if the {pat} matches the existing path of the buffer.
157 An absolute path. I have no idea why you'd ever want to do
158 this, but it's there if you want it.
162 Takes the same form as {:reg} but the {globsub} part of the
163 directive is an absolute path. The absolute path is only used
164 if the {pat} matches the existing path of the buffer.
166 Why use the "if" variants?
168 Here's the situation: You've got the following file:
172 And you've set the following locations:
174 For .h -> reg:/include/src/,../src,./
175 For .cpp -> reg:/src/include/,../include,./
177 Here's what happens when run the following commands:
180 # Creates a new file ~/src/MyFile.cpp due to the first
181 # relative path in the list for .h
183 # Creates a new file ~/include/MyFile.h due to the first
184 # regular expression in the list for .cpp
186 The problem is that you've unconditionally said you want to use
187 {../src} for the alternate file but in reality you probably wanted to
188 use {./}. If you use {:ifrel} instead then you can say that you only
189 want to use {../src} if the path to the current buffer contains
190 {/include/} or something like that. If you did this FSwitch would not
191 have taken {../src} for the new file but would have chosen {./}
193 So the "right" setup is:
195 For .h -> reg:/include/src/,ifrel:|/include/|../src|,./
196 For .cpp -> reg:/src/include/,ifrel:|/src/|../include|,./
198 *'fswitchdisablegloc'*
203 Disables the appending of the default global locations to the local
204 buffer definition. Normally when processing alternate file locations
205 FSwitch will append some default values to the list of locations. If
206 you define this variable then this will not happen.
208 The default locations are currently set to "./" or ".\" depending on
209 what slash your configuration evaluates to.
211 *'fswitchnonewfiles'*
214 local to buffer and global
216 This variable is both global and local. If you want to disable the
217 creation of the alternate file when it doesn't already exist you can
218 choose to do this on a per-extension basis or globally. Set the
219 global one to shut it off all the time and use the buffer version to
225 local to buffer and global
227 Normally when doing a regular expression alteration of the path (see
228 {reg:} in 'fswitchdst' the pattern you're going to substitute the
229 value with must actually match in the string. When it doesn't matter
230 whether or not that the match actually takes place, you can set this
233 If you do set this then the failure to match actually results in
234 nothing happening at all. So if the right filename exists in the same
235 directory as the one you're switching from then that's the one that
241 If the b:fswitchlocs is set to
243 reg:/src/include/,include
247 # This is the file we're editing
248 ~/code/program/myfile.c
250 # These choices exist for the header file
251 ~/code/program/myfile.h
252 ~/code/program/include/myfile.h
254 Then the first substitution will result in the first header file being
255 chosen, not the second.
257 ==============================================================================
259 6. "Creating" the Alternate File~
261 If the file being switched to does not exist, and 'fsnonewfiles' has not been
262 set, then it will be created as a new, unwritten buffer. If there are
263 multiple possibilities here, FSwitch prefers the first possible match. For
264 example if the current buffer has a filename called {/code/src/a/b/MyFile.cpp}
265 and has the following set:
267 let b:fswitchdst = 'h,hpp'
268 let b:fswitchlocs = 'reg:/src/include/,../include,../inc'
270 then the created filename will be {/code/include/a/b/MyFile.cpp}.
272 As stated, this file hasn't actually been written to yet so you could easily
273 delete the buffer and there's no harm done but you also may not be able to
274 write the buffer very easily if the directory hierarchy doesn't yet exist. In
275 this case, it's quite helpful to define a mapping for easily creating the
278 nmap <Leader>md :!mkdir -p %:p:h<cr>
280 Then it's pretty easy to create the directory before writing the file.
282 ==============================================================================
286 I didn't bother putting mappings into the script directly as this might have
287 caused conflicts and I don't know how to avoid those. I use the following
290 - Switch to the file and load it into the current window >
291 nmap <silent> <Leader>of :FSHere<cr>
293 - Switch to the file and load it into the window on the right >
294 nmap <silent> <Leader>ol :FSRight<cr>
296 - Switch to the file and load it into a new window split on the right >
297 nmap <silent> <Leader>oL :FSSplitRight<cr>
299 - Switch to the file and load it into the window on the left >
300 nmap <silent> <Leader>oh :FSLeft<cr>
302 - Switch to the file and load it into a new window split on the left >
303 nmap <silent> <Leader>oH :FSSplitLeft<cr>
305 - Switch to the file and load it into the window above >
306 nmap <silent> <Leader>ok :FSAbove<cr>
308 - Switch to the file and load it into a new window split above >
309 nmap <silent> <Leader>oK :FSSplitAbove<cr>
311 - Switch to the file and load it into the window below >
312 nmap <silent> <Leader>oj :FSBelow<cr>
314 - Switch to the file and load it into a new window split below >
315 nmap <silent> <Leader>oJ :FSSplitBelow<cr>
317 ==============================================================================
321 The main work is done by the FSwitch() function. The reason it's documented
322 here is because you can use it to do something more interesting if you wish.
323 As it stands now, you get the "Split Above and Switch" functionality by
324 calling FSwitch() like this:
326 FSwitch('%', 'split \| wincmd k')
328 There's probably not much to stop anyone from doing something more interesting
329 in the second argument. If this string is non-empty then it will be run
330 through an |:execute| call.
332 ==============================================================================
333 *fswitch-getcompanion* *FSReturnCompanionFilenameString()*
335 9. FSReturnCompanionFilenameString()~
337 This function is used by |FSwitch()| to return the pathname to the preferred
338 companion file. In this case, the file need not actually exist on the
339 filesystem but would be the one created if you chose to do so. As an
342 let path = FSReturnCompanionFilenameString('%')
344 The resultant path string contains the preferred companion file or nothing if
345 no preferred file could be discovered.
347 ==============================================================================
348 *fswitch-getreadablecomp* *FSReturnReadableCompanionFilename()*
350 10. FSReturnReadableCompanionFilename()~
352 This function returns the companion file, but the companion file must be
353 readable on the filesystem for it to be successfully returned.
355 let path = FSReturnReadableCompanionFilename('%')
357 The resultant path string contains the preferred companion file or nothing if
358 no preferred file could be found on the filesystem.
360 In order to see what created the need for this function, see
363 ==============================================================================
365 11. The Default Settings~
367 By default FSwitch handles {c} and {cpp} files, favouring {cpp}.
371 let b:fswitchdst = 'cpp,c'
372 let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src'
376 let b:fswitchdst = 'h'
377 let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include'
382 let b:fswitchdst = 'h'
383 let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include'
386 ==============================================================================
390 Let's say you have a C++ codebase and it has the following properties (this
391 level of insanity is a bit high but versions that are only slightly saner
394 - Source files with {.cpp}, {.cc} and {.C} extensions
395 - Header files with {.h} extensions
396 - Source files and header files in the same directory
397 - Source files in the {src} directory and include files in the
399 - Source files in the {src} directory and include files in the
400 {include/name/space} directory (i.e. subdirectories denoted by the
402 - Source files in {src/name/space} and header files in
403 {include/name/space} (i.e. subdirectories denoted by the namespace).
405 As a final part to this, the "new" way of doing things in this source tree is
406 to put header files in a directory noted by namespace and to do the same with
407 source files and to name source files with a {cpp} extension.
409 In order to switch between files organized like this, you could specify the
414 au BufEnter *.h let b:fswitchdst = 'cpp,cc,C'
415 au BufEnter *.h let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/'
418 Here the setting of b:fswitchdst to {cpp,cc,C} handles the different C++
419 extensions, and prefers to use {cpp} and will create new files with that
422 The fswitchlocs setting allows for the following:
426 Take the pathname to the file in the current buffer and
427 substitute "src" for "include". This handles the following
430 - Files are in {include} and {src} respectively
431 - Files are in {include/name/space} and {src/name/space}
436 Take the pathname to the file in the current buffer and
437 substitute "src" for "include.*". This handles the following
440 - Files are in {include/name/space} and {src} respectively
443 This one's a hiddden option. The default location is the
444 current directory already so we don't explicitly have to state
445 this, but it is the last possibility:
447 - Files are in the same directory
450 Here we'll just show a quick example of making use of the globbing aspect of
451 the system. Let's say you're working on a {cpp} file and you want to find the
452 matching header file, and you have your destination and locations set to the
455 let b:fswitchdst = 'h'
456 let b:fswitchlocs = 'reg:|src|include/**|'
458 then if you have the a file {src/MyFile.cpp} then this will find the file
459 {include/name/space/MyFile.h}.
462 At work I'm a Windows C++ programmer and at home I'm a OS X Objective-C
463 programmer. There's a problem with this... C++ and Objective-C both use the
464 same extension for header files ({.h}).
466 At home I want to be able to use the XCode command line builder in the
467 'makeprg' setting when I'm working on the code. I would like this to be set
468 when I am on a {.m} file or its companion {.h} file. This is done with the
471 function! SetMakeForXCode(filename)
473 let ext = expand(a:filename . ":e")
474 if ext == 'm' || ext == 'mm'
477 " Find the companion file
478 let companionfile = FSReturnReadableCompanionFilename('%')
479 " For some reason expand() doesn't work on the next line
480 let companionext = substitute(companionfile, '.*\.', '', '')
481 if companionext == 'm' || companionext == 'mm'
486 setl makeprg=xcodebuild\ -configuration\ Debug
490 Yup, this could have been easier by using the 'filetype' or using some sort of
491 |grep| call but I wanted to use this particular hammer. :) I'll probably end
492 up switching it to use the 'filetype' instead in the end...
494 ==============================================================================
498 You may get the following error:
500 Alternate has evaluated to nothing. See :h fswitch-empty for more info.
502 It can happen... This is probably due to the fact that you've got a nicely
503 strict set of rules for your locations. With |fswitch-reg| and
504 |fswitch-ifrel| and |fswitch-ifabs| you can get rather specific about whether
505 or not anything actually happens. If you aren't letting anything really
506 happen, it's not going to happen and you're going to end up with an empty
509 ==============================================================================
514 - Made sure that there's a check for 7.0 (Thanks Timon Kelter)
517 - Fix for the splitting commands (Thanks Michael Henry)
520 - Added :ifrel (|fswitch_ifrel|)
521 - Added :ifabs (|fswitch_ifabs|)
522 - Added |FSReturnReadableCompanionFilename()|
523 - Added |FSReturnCompanionFilenameString()|
524 - Changed default settings for .h to use :ifrel instead of :rel
525 - Changed default settings for .c and .cpp to use :ifrel instead of
531 vim:tw=78:sts=8:ts=8:sw=8:noet:ft=help: