parent
990414dc34
commit
2f67f708cd
|
@ -152,7 +152,7 @@ class Application(object):
|
|||
'method', 'method.svn', 'method.cvs', 'method.search',
|
||||
'method.buffers', 'method.move', 'method.shell',
|
||||
'method.introspect', 'method.help', 'method.numbers',
|
||||
'method.spell', 'method.hg', 'method.utf8',
|
||||
'method.spell', 'method.hg', 'method.utf8', 'method.tags',
|
||||
)
|
||||
for name in names:
|
||||
exec("import %s" % name)
|
||||
|
@ -231,6 +231,15 @@ class Application(object):
|
|||
self.registers = {}
|
||||
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
|
||||
completer.set_completer('path', completer.FileCompleter(self))
|
||||
completer.set_completer('buffer', completer.BufferCompleter(self))
|
||||
|
|
12
default.py
12
default.py
|
@ -15,6 +15,18 @@ def last_buffer(w):
|
|||
def current_buffer(w):
|
||||
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):
|
||||
a = w.application
|
||||
if a.config.get('use_last_replace') and a.last_replace_before:
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -261,9 +261,9 @@ class Fundamental(Handler):
|
|||
self.add_bindings('insert-multiline-text', ('C-c m',))
|
||||
self.add_bindings('increment', ('M-+', 'M-='))
|
||||
self.add_bindings('decrement', ('M--',))
|
||||
|
||||
self.add_bindings('uppercase-word', ('M-u',))
|
||||
self.add_bindings('lowercase-word', ('M-l',))
|
||||
self.add_bindings('find-tag', ('M-.',))
|
||||
|
||||
# used for all word operations
|
||||
if not self.word_letters:
|
||||
|
|
11
mode/c.py
11
mode/c.py
|
@ -1,10 +1,14 @@
|
|||
import os.path
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
from method import Method, arg
|
||||
from method.shell import Exec
|
||||
from method.tags import InitTags
|
||||
from mode import Fundamental
|
||||
import tab
|
||||
import completer
|
||||
from lex import Grammar, PatternRule, RegionRule, PatternMatchRule, OverridePatternRule
|
||||
from mode.python import StringGrammar2
|
||||
from etags import TagManager
|
||||
|
||||
class CommentGrammar(Grammar):
|
||||
rules = [
|
||||
|
@ -148,6 +152,11 @@ class CTabber2(tab.StackTabber2):
|
|||
return t.fqisa('spaces', 'eol', 'c.comment', 'c.comment.start',
|
||||
'c.comment.data', 'c.comment.null', 'c.comment.end')
|
||||
|
||||
class CTagManager(TagManager):
|
||||
lang = 'C'
|
||||
exts = set(('.c', '.h'))
|
||||
|
||||
|
||||
class CCheckSyntax(Exec):
|
||||
'''Build this C program (using the mode's make cmd)'''
|
||||
show_success = False
|
||||
|
@ -207,11 +216,13 @@ class C(Fundamental):
|
|||
name = 'C'
|
||||
extensions = ['.c', '.h', '.cpp']
|
||||
tabbercls = CTabber2
|
||||
tagcls = CTagManager
|
||||
grammar = CGrammar
|
||||
opentokens = ('delimiter',)
|
||||
opentags = {'(': ')', '[': ']', '{': '}'}
|
||||
closetokens = ('delimiter',)
|
||||
closetags = {')': '(', ']': '[', '}': '{'}
|
||||
#actions = [CCheckSyntax, CMake, CInitTags]
|
||||
actions = [CCheckSyntax, CMake]
|
||||
format = "%(flag)s %(bname)s (%(mname)s) %(indent)s %(cursor)s %(perc)s [%(func)s] %(vc-info)s"
|
||||
commentc = '//'
|
||||
|
|
Loading…
Reference in New Issue