--- /dev/null
+*.pyc
+*~
+*.swp
--- /dev/null
+Copyright (c) 2013 Ross Younger
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+find-definition
+===============
+
+This is a script I lashed together to open up a new editor directly
+at the definition of a type, structure, function or anything else that
+ctags reports.
+
+I use it with vim. With other editors you either need to have ctags
+support (contributions welcome!) or be prepared to use vi as an external
+source browser.
+
+
+Usage
+-----
+
+`$ find_definition.py fooBar`
+
+... and vim opens up in read-only mode, right at the definition of fooBar.
+(I keep a symlink to the main script on my PATH.)
+If the definition isn't found in the tags file, the script apologises.
+
+But, of course, this only works because I already have a tags file in
+place. Read on...
+
+
+Limitations
+-----------
+
+In the words of A.A. Milne, this script is a bear of little brain.
+It takes you only to the first match for the given tag.
+
+If there are multiple matches in the tags file, you need to drive
+your source browser to walk them.
+
+In vim:
+
+1. Put the cursor on the keyword
+2. Ctrl-] to do a tags search
+3. Use command :tn for next, :tp for previous.
+
+Users of other editors are welcome to contribute instructions!
+
+There is no tab-completion at present; tag matching is whole-string only.
+Contributions welcome (though I might just do it myself one day).
+
+My tags file at work is 78M but the searching is blink-of-an-eye fast.
+
+
+Setting up ctags
+----------------
+
+Install the exuberant-ctags package, if you don't already have it.
+
+`$ sudo apt-get install exuberant-ctags`
+
+You need to run ctags regularly to keep the tags file up to date.
+
+I have set up a personal cron job on my workstation to do this, here's the crontab line:
+
+ 0 6 * * * /home/younger/bin/update-ctags
+
+The `update-ctags` script looks like this (redacted; edit to suit):
+
+ #!/bin/sh
+
+ cd /home/younger/Work/Mainline
+ ctags -R --c++-kinds=+p --fields=+iaS --extra=+q SourceDir* AutoGenDir
+
+
+Configuration
+-------------
+
+By default, the script reads `~/Work/Mainline/tags` and uses my `ViewInvocation` class (which opens vim in read-only mode).
+
+You can change this behaviour by creating a config file `~/.find_definition`:
+
+ [DEFAULT]
+ tagsfile=/some/where/tags
+ invocation=MyClass
+
+`invocation` is the name of a class.
+If it's not within the main script then the script will attempt to import
+the named class (so you might want to set your `PYTHONPATH` suitably)
+and then construct it with no parameters.
+
+
+Invocation Classes
+------------------
+
+`ViewInvocation` is the default. It opens up the source in `view` (which is a read-only vim), in the current shell.
+
+`GViewInvocation` opens up the source in `gview` which spawns a new graphical vim, read-only mode.
+
+Contributions welcome!
+
+
+Writing your own Invocation class
+---------------------------------
+
+It's probably easiest to crib from one of the existing invocation classes.
+You are given the filename and the ex-command (vim-style regex search)
+that will take you to the tag.
+
+If your editor can't cope with regexp-style ex-commands, you might care to look
+into driving ctags with `--excmd=number` to have it use line numbers
+instead; see the ctags man page for a discussion of the advantages and
+disadvantages.
+
--- /dev/null
+import re
+
+class TagsResponse:
+ def __init__(self, filename, pattern):
+ self.filename = filename
+ self.pattern = pattern
+ def __str__(self):
+ return '[filename=%s pattern=%s]' % (self.filename, self.pattern)
+
+def quote_pattern(pat):
+ pat = re.sub('~', '\~', pat)
+ pat = re.sub('\*', '\\\*', pat)
+ return pat
+
+class CTag:
+ def __init__(self, line):
+ self.fields = line.split('\t')
+ def to_response(self):
+ return TagsResponse(self.filename(), self.pattern())
+ def tag(self):
+ return self.fields[0]
+ def filename(self):
+ return self.fields[1]
+ def pattern(self):
+ return quote_pattern(self.fields[2])
+
+class AbstractTagsSearcher:
+ '''
+ Tags searching, using an unspecified algorithm.
+ '''
+ def __init__(self, tagsfile):
+ self.tagsfile = tagsfile
+
+ def find(self, tag):
+ '''
+ Searches for a tag. Returns a TagsResponse, or None if not found.
+ NOTE: Only returns the first match it finds.
+ '''
+ raise 'Abstract interface!'
+
+class LinearTagsSearcher(AbstractTagsSearcher):
+ ''' Crungey old lsearch. '''
+ def find(self,tag):
+ f = open(self.tagsfile, 'r')
+ needle = tag + '\t'
+ for line in f:
+ if line.startswith(needle):
+ return CTag(line).to_response()
+ return None
+
+class BinaryTagsSearcher(AbstractTagsSearcher):
+ ''' bsearch is faster, particularly on large files '''
+ def find(self,needle):
+ f = open(self.tagsfile, 'r')
+ f.seek(0,2)
+ size = f.tell()
+
+ TOP = 0
+ BOTTOM = size
+ while True:
+ MID = (TOP+BOTTOM)/2
+ #print "Top %d Mid %d Bottom %d"%(TOP,MID,BOTTOM)
+ f.seek(MID,0)
+ f.readline() # throw away the partial line
+ MID = f.tell()
+ if MID>=BOTTOM:
+ # Endgame... naive binary search fails to take account of line lengths, so use TOP as a false pivot
+ MID = TOP
+ f.seek(TOP,0)
+ f.readline()
+ MID=f.tell()
+ if MID>=BOTTOM:
+ return None # No?
+ line = f.readline()
+ ctag = CTag(line)
+ tag = ctag.tag()
+ #print "..-> %s"%tag
+
+ if tag == needle:
+ return ctag.to_response() # Jackpot!
+ elif tag < needle:
+ TOP = f.tell()-1 # end of the rejected mid-record
+ #print "<<<"
+ else: # needle > tag
+ BOTTOM = MID # beginning of the rejected mid-record
+ #print ">>>"
+
+ if TOP>=BOTTOM:
+ return None # Simple termination
+
+class TagsSearcherFactory:
+ def get(self, tagsfile):
+ return BinaryTagsSearcher(tagsfile)
+
--- /dev/null
+#!/usr/bin/env python
+
+import sys, os, subprocess, ConfigParser
+
+from TagsSearch import *
+
+CONFIG = os.path.expanduser('~/.find_definition')
+CONFIG_SECTION = 'DEFAULT'
+KEY_TAGS = 'tagsfile'
+KEY_INVOCATION = 'invocation'
+
+class AbstractInvocation:
+ def invoke(self, filename, expattern):
+ raise TypeError,'Abstract interface!'
+
+class ViewInvocation(AbstractInvocation):
+ ''' view FILENAME -c PATTERN - give it control '''
+ def __init__(self):
+ self.CMD='view'
+ self.waitFor=True
+ def invoke(self, filename, expattern):
+ args = [self.CMD, filename, '-c', expattern, '-c', 'redraw']
+ # -c redraw => avoids that pesky "Press Enter to continue"
+ proc = subprocess.Popen(args, executable=self.CMD)
+ # let it inherit from parent
+ if self.waitFor:
+ proc.wait()
+ if proc.returncode != 0:
+ raise subprocess.CalledProcessError(proc.returncode)
+
+class GViewInvocation(ViewInvocation):
+ ''' gview FILENAME -c PATTERN - launch it and leave it '''
+ def __init__(self):
+ self.CMD='gview'
+ self.waitFor=False
+
+if __name__ == '__main__':
+ helpRequested = False
+ if len(sys.argv) > 1 and sys.argv[1]=='--help':
+ helpRequested = True
+ if len(sys.argv) != 2 or helpRequested:
+ print "Usage: %s TermToSearchFor"%sys.argv[0]
+ if helpRequested: sys.exit(0)
+ sys.exit(1)
+
+ config = ConfigParser.SafeConfigParser({
+ KEY_TAGS : os.path.expanduser('~/Work/Mainline/tags'),
+ KEY_INVOCATION : 'ViewInvocation',
+ })
+ if os.path.exists(CONFIG):
+ config.read(CONFIG)
+
+ needle = sys.argv[1]
+
+ invokeme = config.get(CONFIG_SECTION, KEY_INVOCATION)
+ if invokeme in globals().keys():
+ invocation = globals()[invokeme]()
+ else:
+ # Not in scope, so we'll try the equivalent of:
+ # import CLASS
+ # invocation = CLASS()
+ module = __import__(invokeme)
+ try:
+ invclass = getattr(module,invokeme) # raises if not found
+ except AttributeError,e:
+ raise AttributeError,('Source module %s does not contain a class %s? (from %s)'%(invokeme,invokeme, e.args))
+ try:
+ invocation = invclass()
+ except TypeError,t:
+ raise TypeError,('Class %s does not have a no-parameter constructor? (from %s)'%(invokeme, t.args))
+
+ tag = TagsSearcherFactory().get(config.get(CONFIG_SECTION, KEY_TAGS)).find(needle)
+ if tag is None:
+ print "Not found, sorry"
+ sys.exit(1)
+ else:
+ invocation.invoke(tag.filename, tag.pattern)
+