# Copyright 2008 Edd Dawson. # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE_1_0.txt or copy at # http://www.boost.org/LICENSE_1_0.txt) __all__ = ['find_counterpart', 'edit_counterpart', 'edit_counterpart_split'] from os.path import * import os, sys import vim # My build of vim uses Python 2.3.5, which doesn't have a built-in set type yet try: s = set([1, 2, 3]) except: import sets set = sets.Set source_extensions = ['c', 'cpp', 'cxx', 'cc', 'CC', 'C'] header_extensions = ['h', 'hpp', 'hxx', 'hh', 'HH', 'H'] # We create an extensions dictionary, for use by this module. # It maps filename extensions commonly used for C and C++ headers and source # files to sequences of extensions that might be used for a counterpart file extensions = { } for s, h in zip(source_extensions, header_extensions): extensions[s] = set([h, 'h']) extensions[h] = set([s]) extensions['h'].update(source_extensions) # TODO: maybe order is important? def subdirs(path, depth = 1): """ subdirs(path, depth = 1) -> iterator This is a generator that may be used to iterate over all subdirectories of path up to the defined depth. """ if depth < 2: for d in os.listdir(path): full = join(path, d) if os.path.isdir(full): yield full else: for d in subdirs(path): yield d for subd in subdirs(d, depth - 1): yield subd def search_paths(source_path, sub, nup, ndescend): """ search_paths(source_path, sub, nup, ndescend) -> iterator This generator is designed to provide a means to iterate over directories rooted around the source_path directory to find counterpart files (i.e. headers for source files and vice versa). The directories that the associated iterator yields are: * source_path itself * subdirectories of source_path up to a depth of ndescend. * os.path.join(source_path, '../' * i) for i in [1, nup] and * subdirectories of those up to a depth of ndescend * join(d, sub) where d is any of the directories listed above Note that the paths returned by the iterator may not exist. """ yield source_path yield join(source_path, sub) for d in subdirs(source_path, ndescend): yield d yield join(d, sub) for i in range(1, nup + 1): up = join(source_path, '../' * i) yield up yield join(up, sub) for d in subdirs(up, ndescend): if abspath(d) != abspath(source_path): yield d yield join(d, sub) def header_search_paths(source_path, nup, ndescend, sub = 'include'): """ header_search_paths(source_path, nup, ndescend, sub = 'include') -> iterator This generator is a wrapper around search_paths whose associated iterator returns places in which to look for headers. """ for d in search_paths(source_path, sub, nup, ndescend): if exists(d) and isdir(d): yield d def source_search_paths(header_path, nup, ndescend, sub = 'src'): """ source_search_paths(source_path, nup, ndescend, sub = 'src') -> iterator This generator is a wrapper around search_paths whose associated iterator returns places in which to look for source files. """ for d in search_paths(header_path, sub, nup, ndescend): if exists(d) and isdir(d): yield d # Here we create a dictionary that maps filename extensions to a callable object # that generates a sequence of paths in which to look for a counterpart file. For # example, the following snippet prints all paths in which you would expect to find # counterparts for the file '~/code/blah.cpp': # for p in paths['cpp']('~/code/blah.cpp'): print p paths = {} for ext in source_extensions: paths[ext] = lambda p: header_search_paths(p, 5, 5) for ext in header_extensions: paths[ext] = lambda p: source_search_paths(p, 5, 5) def find_counterpart(path): """ find_counterpart(path) -> string Given the filename of a C(++) source or header file, this function will return either the counterpart file, or None if a counterpart wasn't found """ startloc, filename = split(path) preext, ext = splitext(filename) ext = ext[1:] gen = paths.get(ext) seek_exts = extensions.get(ext) if not gen or not seek_exts: return None for d in gen(startloc): for e in seek_exts: full = join(d, preext + '.' + e) if exists(full): return normpath(full) # ========================================================== # Vim-specific stuff follows # ========================================================== def edit_counterpart(cmd = 'edit'): """ edit_counterpart(cmd = 'edit') -> None Looks for the counterpart of the file in the current buffer. If found it is opened for editing using cmd. If the counterpart is already in another vim buffer, the buffer is made current rather than opening another copy. """ current = vim.current.buffer.name if not current: sys.stderr.write('No counterpart found for current buffer: buffer has no name\n') return cp = find_counterpart(current) if cp: escaped_cp = cp.replace(' ', r'\ ') # Look in the existing buffers first, to avoid opening copies for b in vim.buffers: if abspath(b.name) == abspath(cp): vim.command('b ' + escaped_cp) break else: vim.command(cmd + ' ' + escaped_cp) else: sys.stderr.write('No counterpart found for current buffer\n') def edit_counterpart_split(direction = 'up'): """ edit_counterpart_split(direction = 'up') -> None Looks for the counterpart of the file in the current buffer. If it is found it is opened in a split window. The file will be opened in a split in the given direction. For example, if direction is 'up' the counterpart will be opened in a split above the current window. Other valid directions are 'down', 'left', 'right', '<', '>', '^', 'v' If a buffer for the counterpart is already open, then the split window will simply change to the existing buffer rather than opening another copy. If the counterpart is already in another window, then the cursor will move to that window rather than creating another window with the same buffer. """ d = direction[0].lower() if d in 'ud^v': cmd = 'split' else: cmd = 'vsplit' # Save values of splitrigt and splitbelow so we can restore them sr = vim.eval('&splitright') sb = vim.eval('&splitbelow') # Make sure we split in the direction that the user intended if d in 'r>': vim.command('set splitright') elif d in 'bv': vim.command('set splitbelow') elif d in 'u^': vim.command('set nosplitbelow') else: vim.command('set nosplitright') ret = edit_counterpart_split_cmd(cmd) # Restore values of splitright and splitbelow if sr: vim.command('set splitright') if sb: vim.command('set splitbelow') return ret def edit_counterpart_split_cmd(cmd): """ edit_counterpart_split(cmd) -> None This is a utility function used to implement edit_counterpart_split. Looks for the counterpart of the file in the current buffer. If it is found it is opened in a split window using the given command ('split' or 'vsplit'). If a buffer for the counterpart is already open, then the split window will simply change to the existing buffer rather than opening another copy. If the counterpart is already in another window, then the cursor will move to that window rather than creating another window with the same buffer. """ current = vim.current.buffer.name if not current: sys.stderr.write('No counterpart found for current buffer: buffer has no name\n') return cp = find_counterpart(current) if cp: escaped_cp = cp.replace(' ', r'\ ') i = 1 for w in vim.windows: if abspath(w.buffer.name) == abspath(cp): # Move cursor to the other window vim.command('exe %d . "wincmd W"' % i) return i += 1 # The counterpart is not open in an exitsing window # but it may already be loaded in to a buffer. for b in vim.buffers: if abspath(b.name) == abspath(cp): if len(vim.windows) == 1: vim.command(cmd) # split else: vim.command('wincmd w') # go to next window vim.command('b ' + escaped_cp) # open existing buffer return # Counterpart is not in an existing window or an existing buffer vim.command(cmd + ' ' + escaped_cp) else: sys.stderr.write('No counterpart found for current buffer\n')