initial ctags/etags support for C

--HG--
branch : pmacs2
This commit is contained in:
Erik Osheim 2010-08-16 23:45:33 -04:00
parent 990414dc34
commit 2f67f708cd
6 changed files with 311 additions and 2 deletions

View File

@ -152,7 +152,7 @@ class Application(object):
'method', 'method.svn', 'method.cvs', 'method.search', 'method', 'method.svn', 'method.cvs', 'method.search',
'method.buffers', 'method.move', 'method.shell', 'method.buffers', 'method.move', 'method.shell',
'method.introspect', 'method.help', 'method.numbers', 'method.introspect', 'method.help', 'method.numbers',
'method.spell', 'method.hg', 'method.utf8', 'method.spell', 'method.hg', 'method.utf8', 'method.tags',
) )
for name in names: for name in names:
exec("import %s" % name) exec("import %s" % name)
@ -231,6 +231,15 @@ class Application(object):
self.registers = {} self.registers = {}
self.arg_history = {'default': []} self.arg_history = {'default': []}
# this is used to maintain state about various things (ctags, version
# control, etc) which is shared across several different buffers.
# this is so it will be correctly inherited by new buffers, and also
# so that when buffers are closed this state won't be lost.
self.state = {
'tags': {},
'vc': {},
}
# initialize tab handlers # initialize tab handlers
completer.set_completer('path', completer.FileCompleter(self)) completer.set_completer('path', completer.FileCompleter(self))
completer.set_completer('buffer', completer.BufferCompleter(self)) completer.set_completer('buffer', completer.BufferCompleter(self))

View File

@ -15,6 +15,18 @@ def last_buffer(w):
def current_buffer(w): def current_buffer(w):
return w.buffer.name() return w.buffer.name()
def current_word(w):
return w.get_word() or ''
def current_token(w):
if w.mode.name not in w.buffer.highlights:
return ''
token = w.get_token()
if token:
return token.string
else:
return ''
def last_replace_before(w): def last_replace_before(w):
a = w.application a = w.application
if a.config.get('use_last_replace') and a.last_replace_before: if a.config.get('use_last_replace') and a.last_replace_before:

186
etags.py Normal file
View File

@ -0,0 +1,186 @@
"""
Etags parser
:author: Dan Williams
"""
import os
import re
from stat import ST_MTIME
from subprocess import Popen, PIPE, STDOUT
class EtagsRunError(Exception): pass
class TagManager(object):
lang = None
prune = ('SCCS', 'RCS', 'CVS', '.svn', '.hg', '.git', '.bzr')
exts = set()
def __init__(self, base='.'):
self.etags = None
self.base = base
self.path = os.path.join(self.base, 'TAGS')
self.update()
def has(self, name):
return name in self.etags.tag_map
def get(self, name):
return self.etags.tag_map.get(name, [])
def update(self):
if self.is_outdated():
self.run()
self.etags = Etags(self.path)
self.etags.parse()
def run(self):
lf = '--language-force=%s' % self.lang
args = ['ctags', '-e', '-f', self.path, lf, '-L-']
pipe = Popen(args, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
indata = '\n'.join(self.get_paths()) + '\n'
outdata = pipe.communicate(indata)
if pipe.returncode != 0:
raise EtagsRunError(outdata)
def get_paths(self):
return list(self._walk(mtime=0))
def is_outdated(self):
if not os.path.exists(self.path):
return True
mtime = os.stat(self.path)[ST_MTIME]
itr = self._walk(mtime)
try:
itr.next()
return True
except StopIteration:
return False
def _walk(self, mtime=0):
paths = []
for root, dirs, files in os.walk(self.base):
for d in dirs:
if d in self.prune:
dirs.remove(d)
for f in files:
path = os.path.join(root, f)
if not self._match(path):
continue
elif os.stat(path)[ST_MTIME] < mtime:
continue
else:
yield os.path.join(root, f)
raise StopIteration
def _match(self, path):
_, ext = os.path.splitext(path)
return ext in self.exts
class Etags(object):
def __init__(self, fname=None):
self.fname = fname
self.rawdata = None
self.record_list = []
self.tag_map = {}
def _load(self):
fd = file(self.fname, 'r')
self.rawdata = fd.read()
fd.close()
def lookup(self, tag):
return self.tag_map[tag]
def __getitem__(self, tag):
return self.lookup(tag)
def parse(self, fname=None):
"""
Parser is based on the little info found in
Wikipedia: http://en.wikipedia.org/wiki/Ctags
"""
if fname:
self.fname = fname
self._load()
i = 0
data_len = len(self.rawdata)
data = self.rawdata
while i < data_len:
if ord(data[i]) == 0xc:
i = self._parse_block(data, i+2)
def _add_record(self, record):
self.record_list.append(record)
name = record.name
if name is None:
return
self.tag_map.setdefault(name, [])
self.tag_map[name].append(record)
def _parse_block(self, data, i):
n = data[i:].find('\n') + i
l = data[i:n]
try:
filename, size = l.split(',')
except ValueError:
print i
raise
size = int(size)
subblock = data[n+1:n+size+1]
# ...
for lineitem in subblock.split('\n'):
if len(lineitem) == 0:
continue
record = self._parse_record(lineitem, filename)
self._add_record(record)
return n+size+1
def _parse_record(self, lineitem, filename):
try:
defn, rest = lineitem.split(chr(0x7f))
except ValueError:
print lineitem
raise
name = None
if chr(0x01) in rest:
name, rest = rest.split(chr(0x01))
else:
txt = defn.strip()
sp = re.split('[ ,;*()\t&=]', txt)
sp = [x for x in sp if x != '']
if len(sp):
name = sp[-1]
tokens = rest.split(',')
line = int(tokens[0])
byte = int(tokens[1])
record = EtagRecord(path=filename, defn=defn, name=name, line=line, byte=byte)
return record
class EtagRecord(object):
def __init__(self, **kwargs):
self.path = None
self.defn = None
self.name = None
self.line = -1
self.byte = None
self.__dict__.update(kwargs)
def __repr__(self):
return "%s [%s:%d]" % (self.name, self.path, self.line)
if __name__ == '__main__':
import sys
from pprint import pprint
etags = Etags(sys.argv[1])
etags.parse()
if len(sys.argv) > 2:
print etags[sys.argv[2]]
else:
pprint(etags.record_list)

91
method/tags.py Normal file
View File

@ -0,0 +1,91 @@
import os.path
from method import Method, arg
import completer
from etags import TagManager
import default
class FindTag(Method):
args = [arg('tag', p='Tag name: ', dv=default.current_token, ld=True, h='Search for a tag')]
def _execute(self, w, **vargs):
tag = vargs['tag']
b = w.buffer
a = w.application
InitTags().execute(w)
base = b.settings[w.mode.name].get('tag-base')
m = a.state['tags'][base]
records = m.get(tag)
if not records:
w.set_error('tag %r was not found' % tag)
return
if len(records) == 1:
b = a.open_path(records[0].path)
a.switch_buffer(b)
b.windows[0].goto_line(records[0].line)
w.set_error('found one record for tag %r' % tag)
return
cwd = os.getcwd()
if not cwd.endswith('/'):
cwd += '/'
tpls = [(r.path.replace(cwd, ''), r.line, r.defn) for r in records]
data = '\n'.join(['%s:%d:%s' % tpl for tpl in tpls]) + '\n'
a.data_buffer("*Tag-Records*", data, switch_to=True, modename='error')
w.set_error('found %d records for tag %r' % (len(records), tag))
class InitTags(Method):
manager_cls = None
def _save_manager(self, w, base):
m = w.mode.tagcls(base)
w.application.state['tags'][base] = m
w.buffer.settings[w.mode.name]['tag-base'] = base
ntag, nrec = len(m.etags.tag_map), len(m.etags.record_list)
w.set_error('%s: loaded %d names (%d records)' % (m.path, ntag, nrec))
def _execute(self, w, **vargs):
b = w.buffer
a = w.application
a.state.setdefault('tags', {})
if not b.path:
raise Exception('Buffer %r has no path' % b.name())
t = b.settings['C'].get('tag-base')
if t and t in a.state['tags']:
m = a.state['tags'][t]
if m.is_outdated():
m.update()
ntag, nrec = len(m.etags.tag_map), len(m.etags.record_list)
fmt = '%s: updated %d names (%d records)'
w.set_error(fmt % (m.path, ntag, nrec))
else:
w.set_error('%s: is up-to-date' % m.path)
return
base = b.path
while 'tag-base' not in b.settings['C']:
base, tail = os.path.split(base)
if not tail:
break
if base in a.state['tags']:
return self._save_manager(w, base)
elif os.path.exists(os.path.join(base, 'TAGS')):
return self._save_manager(w, base)
if 'tag-base' not in b.settings['C']:
self._old_window = w
self._prompt = "Enter source directory: "
c = completer.get_completer('path')
d = os.path.dirname(w.buffer.path)
a.open_mini_buffer(self._prompt, self._cb, tabber=c, startvalue=d)
def _cb(self, v):
w = self._old_window
w.application.close_mini_buffer()
self._save_manager(w, v)

View File

@ -261,9 +261,9 @@ class Fundamental(Handler):
self.add_bindings('insert-multiline-text', ('C-c m',)) self.add_bindings('insert-multiline-text', ('C-c m',))
self.add_bindings('increment', ('M-+', 'M-=')) self.add_bindings('increment', ('M-+', 'M-='))
self.add_bindings('decrement', ('M--',)) self.add_bindings('decrement', ('M--',))
self.add_bindings('uppercase-word', ('M-u',)) self.add_bindings('uppercase-word', ('M-u',))
self.add_bindings('lowercase-word', ('M-l',)) self.add_bindings('lowercase-word', ('M-l',))
self.add_bindings('find-tag', ('M-.',))
# used for all word operations # used for all word operations
if not self.word_letters: if not self.word_letters:

View File

@ -1,10 +1,14 @@
import os.path import os.path
from subprocess import Popen, PIPE, STDOUT from subprocess import Popen, PIPE, STDOUT
from method import Method, arg
from method.shell import Exec from method.shell import Exec
from method.tags import InitTags
from mode import Fundamental from mode import Fundamental
import tab import tab
import completer
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule, OverridePatternRule from lex import Grammar, PatternRule, RegionRule, PatternMatchRule, OverridePatternRule
from mode.python import StringGrammar2 from mode.python import StringGrammar2
from etags import TagManager
class CommentGrammar(Grammar): class CommentGrammar(Grammar):
rules = [ rules = [
@ -148,6 +152,11 @@ class CTabber2(tab.StackTabber2):
return t.fqisa('spaces', 'eol', 'c.comment', 'c.comment.start', return t.fqisa('spaces', 'eol', 'c.comment', 'c.comment.start',
'c.comment.data', 'c.comment.null', 'c.comment.end') 'c.comment.data', 'c.comment.null', 'c.comment.end')
class CTagManager(TagManager):
lang = 'C'
exts = set(('.c', '.h'))
class CCheckSyntax(Exec): class CCheckSyntax(Exec):
'''Build this C program (using the mode's make cmd)''' '''Build this C program (using the mode's make cmd)'''
show_success = False show_success = False
@ -207,11 +216,13 @@ class C(Fundamental):
name = 'C' name = 'C'
extensions = ['.c', '.h', '.cpp'] extensions = ['.c', '.h', '.cpp']
tabbercls = CTabber2 tabbercls = CTabber2
tagcls = CTagManager
grammar = CGrammar grammar = CGrammar
opentokens = ('delimiter',) opentokens = ('delimiter',)
opentags = {'(': ')', '[': ']', '{': '}'} opentags = {'(': ')', '[': ']', '{': '}'}
closetokens = ('delimiter',) closetokens = ('delimiter',)
closetags = {')': '(', ']': '[', '}': '{'} closetags = {')': '(', ']': '[', '}': '{'}
#actions = [CCheckSyntax, CMake, CInitTags]
actions = [CCheckSyntax, CMake] actions = [CCheckSyntax, CMake]
format = "%(flag)s %(bname)s (%(mname)s) %(indent)s %(cursor)s %(perc)s [%(func)s] %(vc-info)s" format = "%(flag)s %(bname)s (%(mname)s) %(indent)s %(cursor)s %(perc)s [%(func)s] %(vc-info)s"
commentc = '//' commentc = '//'