*fswitch.txt* For Vim version 7.2 and above Last change: 2009 Mar 23 --------------- File Switcher --------------- Author: Derek Wyatt (derek at myfirstnamemylastname dot org) *fswitch-copyright* Copyright: The VIM LICENSE applies to fswitch.vim, and fswitch.txt (see |copyright|) except use "fswitch" instead of "Vim". No warranty, express or implied. Use At-Your-Own-Risk! ============================================================================== *fswitch* *fswitch-contents* 1. Contents~ 1. Contents .............................: |fswitch-contents| 2. About ................................: |fswitch-about| 3. Features .............................: |fswitch-features| 4. Setup ................................: |fswitch-setup| 5. Configuration ........................: |fswitch-configure| 6. "Creating" the Alternate File ........: |fswitch-altcreate| 7. Useful Mappings ......................: |fswitch-mappings| 8. FSwitch() ............................: |fswitch-function| 9. FSReturnCompanionFilenameString().....: |fswitch-getcompanion| 10. FSReturnReadableCompanionFilename()...: |fswitch-getreadablecomp| 11. The Default Settings .................: |fswitch-defaults| 12. Examples .............................: |fswitch-examples| 13. Troubleshooting ......................: |fswitch-trouble| A. Change History .......................: |fswitch-changes| ============================================================================== *fswitch-about* 2. About~ FSwitch is designed to allow you to switch between companion files of source code (e.g. "cpp" files and their corresponding "h" files). The source for this came from a home-grown script that was influenced later by the "Alternate" (a.vim) script. The original intention was to modify the existing a.vim script to do what the home-grown version could do (choose to open the other file in an existing window) but it was a rather complex script and modification looked difficult so the choice was made to simply move the home-grown script forward a couple of notches and produce a new plugin. This doc file is twice the length of the actual code at this point :) ============================================================================== *fswitch-features* 3. Features~ FSwitch has the following features: - Switches between a file and its companion file - Ability to create a new file using a preferential location - Simple configuration using buffer-local variables - It's got a really long doc file (... seriously, why is this thing so bloody long?) - Umm... other stuff? ============================================================================== *fswitch-setup* 4. Setup~ Most of the behaviour of FSwitch is customized via buffer-local variables. You set up the variables with auto commands: > au! BufEnter *.cpp let b:fswitchdst = 'hpp,h' | let b:fswitchlocs = '../inc' < That |:autocmd| will set the 'fswitchdst' and 'fswitchlocs' variables when the |BufEnter| event takes place on a file whose name matches {*.cpp} (e.g. when you enter the buffer containing the {MyFile.cpp} file). The variables above state that the alternate file to {MyFile.cpp} are {MyFile.hpp} and {MyFile.h} preferred in that order, and located in the {inc} directory at the same level as the current directory. That should get you there but there's more capability here if you want. To get that move on to |fswitch-configure|. ============================================================================== *fswitch-configure* 5. Configuration~ *'fswitchdst'* 'fswitchdst' string (default depends on file in current buffer) local to buffer The 'fswitchdst' variable denotes the file extension that is the target extension of the current file's companion file. For example: > :let b:fswitchdst = 'cpp,cxx,C' < The above specifies that the current buffer's file has a companion filename which can be found by replacing the current extension with {cpp}, {cxx} or {C}. The extensions will be tried in this order and the first match wins. 'fswitchdst' is taken relative to directories that are found in the 'fswitchlocs' variable. *'fswitchlocs'* 'fswitchlocs' string (default depends on file in current buffer) local to buffer The 'fswitchlocs' variable contains a set of directives that indicate directory names that should be formulated when trying to find the alternate file. For example: > " Create the destination path by substituting any " 'include' string from the pathname with 'src' :let b:fswitchlocs = 'reg:/include/src/' " First try adding the relative path '../src' to the path " in which the file in the buffer exists and if that fails " then try using 'source' instead :let b:fswitchlocs = 'rel:../src,source' " Same as above but leaving off the optional 'rel:' :let b:fswitchlocs = '../src,../source' < The following types of directives are understood: *fswitch_reg* reg:~ A regular expression. The regular expression takes the form: > {delim}{pat}{delim}{globsub}{delim} < Where: {delim} is something that doesn't appear in {pat} or {globsub} used to delimit the {pat} and {globsub} {pat} is a standard pattern to search on {globsub} is a substitution string that will be run through the |glob()| function. *fswitch_rel* rel:~ A relative path. The {rel:} is actually optional. If you leave this off, then FSwitch will assume that the string is denoting a relative path. *fswitch_ifrel* ifrel:~ Takes the same form as {:reg} but the {globsub} part of the directive is a relative path. The relative path is only used if the {pat} matches the existing path of the buffer. *fswitch_abs* abs:~ An absolute path. I have no idea why you'd ever want to do this, but it's there if you want it. *fswitch_ifabs* ifabs:~ Takes the same form as {:reg} but the {globsub} part of the directive is an absolute path. The absolute path is only used if the {pat} matches the existing path of the buffer. Why use the "if" variants? Here's the situation: You've got the following file: > ~/code/MyFile.h < And you've set the following locations: > For .h -> reg:/include/src/,../src,./ For .cpp -> reg:/src/include/,../include,./ < Here's what happens when run the following commands: > FSwitch('%') # Creates a new file ~/src/MyFile.cpp due to the first # relative path in the list for .h FSwitch('%') # Creates a new file ~/include/MyFile.h due to the first # regular expression in the list for .cpp < The problem is that you've unconditionally said you want to use {../src} for the alternate file but in reality you probably wanted to use {./}. If you use {:ifrel} instead then you can say that you only want to use {../src} if the path to the current buffer contains {/include/} or something like that. If you did this FSwitch would not have taken {../src} for the new file but would have chosen {./} So the "right" setup is: > For .h -> reg:/include/src/,ifrel:|/include/|../src|,./ For .cpp -> reg:/src/include/,ifrel:|/src/|../include|,./ < *'fswitchdisablegloc'* 'fsdisablegloc' string (default off) local to buffer Disables the appending of the default global locations to the local buffer definition. Normally when processing alternate file locations FSwitch will append some default values to the list of locations. If you define this variable then this will not happen. The default locations are currently set to "./" or ".\" depending on what slash your configuration evaluates to. *'fswitchfnames'* 'fswitchfnames' string (default depends on file in current buffer) local to buffer The 'fswitchfnames' variable contains a comma-separated list of possible substitution patterns over the base filename (without path and extension) that should be formulated when trying to find the alternate file. The format of the substitution pattern is the same as |fswitch-reg|. This may be useful when the companion file may can be formulated by adding a suffix to the base filename. For instance, when the companion of MyClass.java is MyClassTest.java. For example: > " Create the destination filename by appending 'Test' " to the filename :let b:fswitchfnames = '/$/Test/' " Create the destination filename by removing 'Test' " from the end of the filename. :let b:fswitchfnames = '/Test$//' < *'fswitchnonewfiles'* 'fsnonewfiles' string (default off) local to buffer and global This variable is both global and local. If you want to disable the creation of the alternate file when it doesn't already exist you can choose to do this on a per-extension basis or globally. Set the global one to shut it off all the time and use the buffer version to shut it off locally. *'fsneednomatch'* 'fsneednomatch' string (default off) local to buffer and global Normally when doing a regular expression alteration of the path (see {reg:} in 'fswitchdst' the pattern you're going to substitute the value with must actually match in the string. When it doesn't matter whether or not that the match actually takes place, you can set this value. If you do set this then the failure to match actually results in nothing happening at all. So if the right filename exists in the same directory as the one you're switching from then that's the one that will be switched to. Example: > If the b:fswitchlocs is set to reg:/src/include/,include and # This is the file we're editing ~/code/program/myfile.c # These choices exist for the header file ~/code/program/myfile.h ~/code/program/include/myfile.h < Then the first substitution will result in the first header file being chosen, not the second. ============================================================================== *fswitch-altcreate* 6. "Creating" the Alternate File~ If the file being switched to does not exist, and 'fsnonewfiles' has not been set, then it will be created as a new, unwritten buffer. If there are multiple possibilities here, FSwitch prefers the first possible match. For example if the current buffer has a filename called {/code/src/a/b/MyFile.cpp} and has the following set: > let b:fswitchdst = 'h,hpp' let b:fswitchlocs = 'reg:/src/include/,../include,../inc' < then the created filename will be {/code/include/a/b/MyFile.cpp}. As stated, this file hasn't actually been written to yet so you could easily delete the buffer and there's no harm done but you also may not be able to write the buffer very easily if the directory hierarchy doesn't yet exist. In this case, it's quite helpful to define a mapping for easily creating the directory for you: > nmap md :!mkdir -p %:p:h < Then it's pretty easy to create the directory before writing the file. ============================================================================== *fswitch-mappings* 7. Useful Mappings~ I didn't bother putting mappings into the script directly as this might have caused conflicts and I don't know how to avoid those. I use the following mappings myself: - Switch to the file and load it into the current window > nmap of :FSHere < - Switch to the file and load it into the window on the right > nmap ol :FSRight < - Switch to the file and load it into a new window split on the right > nmap oL :FSSplitRight < - Switch to the file and load it into the window on the left > nmap oh :FSLeft < - Switch to the file and load it into a new window split on the left > nmap oH :FSSplitLeft < - Switch to the file and load it into the window above > nmap ok :FSAbove < - Switch to the file and load it into a new window split above > nmap oK :FSSplitAbove < - Switch to the file and load it into the window below > nmap oj :FSBelow < - Switch to the file and load it into a new window split below > nmap oJ :FSSplitBelow < ============================================================================== *FSwitch()* 8. FSwitch()~ The main work is done by the FSwitch() function. The reason it's documented here is because you can use it to do something more interesting if you wish. As it stands now, you get the "Split Above and Switch" functionality by calling FSwitch() like this: > FSwitch('%', 'split \| wincmd k') < There's probably not much to stop anyone from doing something more interesting in the second argument. If this string is non-empty then it will be run through an |:execute| call. ============================================================================== *fswitch-getcompanion* *FSReturnCompanionFilenameString()* 9. FSReturnCompanionFilenameString()~ This function is used by |FSwitch()| to return the pathname to the preferred companion file. In this case, the file need not actually exist on the filesystem but would be the one created if you chose to do so. As an example: > let path = FSReturnCompanionFilenameString('%') < The resultant path string contains the preferred companion file or nothing if no preferred file could be discovered. ============================================================================== *fswitch-getreadablecomp* *FSReturnReadableCompanionFilename()* 10. FSReturnReadableCompanionFilename()~ This function returns the companion file, but the companion file must be readable on the filesystem for it to be successfully returned. > let path = FSReturnReadableCompanionFilename('%') < The resultant path string contains the preferred companion file or nothing if no preferred file could be found on the filesystem. In order to see what created the need for this function, see |fswitch-example3|. ============================================================================== *fswitch-defaults* 11. The Default Settings~ By default FSwitch handles {c}, {cc}, {cpp}, {cxx} and {C}. Note that the difference between {c} and {C} is only case sensitivity. This may mean that weird stuff happens if your OS is case insensitive. Also NOTE that when you use {cxx/hxx} or {C/H}, god kills a puppy. Consider that next time you want to do such a silly thing. For *.h files: > let b:fswitchdst = 'c,cpp' let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' < For *.hpp files: > let b:fswitchdst = 'cpp' let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' < For *.hxx files: > let b:fswitchdst = 'cxx' let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' < For *.H files: > let b:fswitchdst = 'C' let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' < For *.c > let b:fswitchdst = 'h' let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' < For *.cpp > let b:fswitchdst = 'hpp,h' let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' < For *.cxx > let b:fswitchdst = 'hxx' let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' < For *.C > let b:fswitchdst = 'H' let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' < ============================================================================== *fswitch-examples* 12. Examples~ *fswitch-example1* Let's say you have a C++ codebase and it has the following properties (this level of insanity is a bit high but versions that are only slightly saner exist in real life): - Source files with {.cpp}, {.cc} and {.C} extensions - Header files with {.h} extensions - Source files and header files in the same directory - Source files in the {src} directory and include files in the {include} directory - Source files in the {src} directory and include files in the {include/name/space} directory (i.e. subdirectories denoted by the namespace). - Source files in {src/name/space} and header files in {include/name/space} (i.e. subdirectories denoted by the namespace). As a final part to this, the "new" way of doing things in this source tree is to put header files in a directory noted by namespace and to do the same with source files and to name source files with a {cpp} extension. In order to switch between files organized like this, you could specify the following: > augroup mycppfiles au! au BufEnter *.h let b:fswitchdst = 'cpp,cc,C' au BufEnter *.h let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/' augroup END < Here the setting of b:fswitchdst to {cpp,cc,C} handles the different C++ extensions, and prefers to use {cpp} and will create new files with that extension. The fswitchlocs setting allows for the following: reg:/include/src/~ Take the pathname to the file in the current buffer and substitute "src" for "include". This handles the following possibilities: - Files are in {include} and {src} respectively - Files are in {include/name/space} and {src/name/space} respectively reg:/include.*/src/~ Take the pathname to the file in the current buffer and substitute "src" for "include.*". This handles the following possibility: - Files are in {include/name/space} and {src} respectively ./~ This one's a hiddden option. The default location is the current directory already so we don't explicitly have to state this, but it is the last possibility: - Files are in the same directory *fswitch-example2* Here we'll just show a quick example of making use of the globbing aspect of the system. Let's say you're working on a {cpp} file and you want to find the matching header file, and you have your destination and locations set to the following: > let b:fswitchdst = 'h' let b:fswitchlocs = 'reg:|src|include/**|' > then if you have the a file {src/MyFile.cpp} then this will find the file {include/name/space/MyFile.h}. *fswitch-example3* At work I'm a Windows C++ programmer and at home I'm a OS X Objective-C programmer. There's a problem with this... C++ and Objective-C both use the same extension for header files ({.h}). At home I want to be able to use the XCode command line builder in the 'makeprg' setting when I'm working on the code. I would like this to be set when I am on a {.m} file or its companion {.h} file. This is done with the following function: > function! SetMakeForXCode(filename) let isObjCFile = 0 let ext = expand(a:filename . ":e") if ext == 'm' || ext == 'mm' let isObjCFile = 1 elseif ext == 'h' " Find the companion file let companionfile = FSReturnReadableCompanionFilename('%') " For some reason expand() doesn't work on the next line let companionext = substitute(companionfile, '.*\.', '', '') if companionext == 'm' || companionext == 'mm' let isObjCFile = 1 endif endif if isObjCFile == 1 setl makeprg=xcodebuild\ -configuration\ Debug endif endfunction < Yup, this could have been easier by using the 'filetype' or using some sort of |grep| call but I wanted to use this particular hammer. :) I'll probably end up switching it to use the 'filetype' instead in the end... ============================================================================== *fswitch-trouble* 13. TroubleShooting~ *fswitch-empty* You may get the following error: > Alternate has evaluated to nothing. See :h fswitch-empty for more info. < It can happen... This is probably due to the fact that you've got a nicely strict set of rules for your locations. With |fswitch-reg| and |fswitch-ifrel| and |fswitch-ifabs| you can get rather specific about whether or not anything actually happens. If you aren't letting anything really happen, it's not going to happen and you're going to end up with an empty path. ============================================================================== *fswitch-changes* A. Change History~ 0.9.5 - Modified the autocommands to handle the myriad different formulations of C++ files per Hong Xu's request. See: https://github.com/derekwyatt/vim-fswitch/pull/3 0.9.4 - Added fixes from Alexey Radkov to handle the 'sb' and 'spr' option settings as well as the 'switchbuf' option. See: https://github.com/derekwyatt/vim-fswitch/pull/2 - Made small fix to Alexey's fix to keep the 'sb' and 'spr' shadow variable local to the buffer and unlet them at the end. - I made a quick regression check on this update but did not test Alexey's features directly - I trust that he did that :) 0.9.3 - Made sure that there's a check for 7.0 (Thanks Timon Kelter) 0.9.2 - Fix for the splitting commands (Thanks Michael Henry) 0.9.1 - Added :ifrel (|fswitch_ifrel|) - Added :ifabs (|fswitch_ifabs|) - Added |FSReturnReadableCompanionFilename()| - Added |FSReturnCompanionFilenameString()| - Changed default settings for .h to use :ifrel instead of :rel - Changed default settings for .c and .cpp to use :ifrel instead of :rel 0.9.0 - Initial release vim:tw=78:sts=8:ts=8:sw=8:noet:ft=help: