/
emacsCppComplete.py
288 lines (272 loc) · 9.76 KB
/
emacsCppComplete.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
from Pymacs import lisp
import re
import CppObject as cpp
import time
import os
import os.path
import traceback
#matches a.b.c->d
# ^ ^ <>
_re_conn = re.compile("(\.|->)")
#matches a "namespace definition", if I may say so
_re_namespace = re.compile("namespace\W+(?P<nspace>[a-zA-Z0-9]+)\W*")
def parse_lemacs(path):
"""
parse a .lemacs file and grab the information within
"""
data = file(path,'r').read()
r = {'include':[]}
current_class = ""
cwd = os.path.split(path)[0]
for l in data.splitlines():
if l.startswith("[") and l.endswith("]"):
current_class=l[1:-1]
continue
if l.startswith("#"):continue
if r.has_key(current_class):
if current_class=="include" and l.startswith("."):
r[current_class].append(cwd+"/"+l[1:])
# since os.path.join doesnt seem to be working???
continue
r[current_class].append(l)
else:
r[current_class]=[l]
return r
def update_state():
"""
Updates the current state by looking for included files and parsing them
"""
lisp.message("Loading... this might take a while.")
this_text = lisp.buffer_string()
cwd = os.path.dirname(lisp.buffer_file_name())
includes = []
j = cwd
while j!="/":
p = os.path.join(j,".lemacs")
if os.path.isfile(p):
includes+=parse_lemacs(p)['include']
j = os.path.join(os.path.split(j)[:-1])[0]
cpp.parse_from_includes(this_text,cwd = cwd,inclDIRS = includes)
lisp.message("Loaded auto-completion data!")
def command():
c = lisp.completing_read("Enter command:",["update","reload","complete"])
lisp.message(str(c))
if c=="update" or c=="reload":
update_state()
elif c=="complete":
complete_type()
command.interaction = ''
def rmargs(s):
"""
hack to transform "foo(bar)" into "foo"
not really classy, just kills after the first parenthesis "("
"""
if '(' in s:
return s[:s.index('(')]
return s
def get_last_word(gp):
"""
grab the last word from a group of words
basically to tranform "bar(Vector foo,Matrix " into "Matrix "
doesn't strip or anything, so beware :P
"""
w = ""
k = set(",=(")
for i in gp[::-1]:
if i not in k:
w = i+w
else:
break
return w
def try_cn_match(l,var):
"""
Try to match a classname inside a line which would define 'var'
say var is "toto"
SomeFunction(SomeClass* toto);
should return ["SomeClass"]
"""
indexes = [i for i in range(len(l)) if l[i:].startswith(var)]
cnames = []
for i in indexes:
k = list(l[:i])
k.reverse()
k = "".join(k)
cname = ""
for char in k:
if char in ".:,;()":
break
cname=char+cname
cnames.append(cname.replace("const","").replace("*","").replace("&","").strip())
return cnames
def make_help_message(completions,help_str=""):
"""
makes a "help message" from a list of CppObject's
"""
slist = []
for i in completions:
slist.append(i.prettynames())# (attr,name,type)
max1 = str(max([len(i[1]) for i in slist]))
max2 = str(max([len(i[2]) for i in slist]))
hlist = []
s = "%3s %-"+max1+"s %"+max2+"s"
for i in slist:
print i
hlist.append(s%i)
hlist.sort()
return help_str+"\n".join(hlist)
def check_word():
"""
Check the current word and try to push a help message and a completion
"""
line = lisp.buffer_substring(lisp.line_beginning_position(),lisp.point())
lines= lisp.buffer_substring(1,lisp.point()).splitlines()
lines.reverse()
d = find_completion(line,lines)
if d[0]:
f,completions,target = d
#lisp.message("Found %d completions\n%s"%(len(completions),curType))
help_str = make_help_message(completions)
# make sure we're probably running X:
if lisp.window_system() is not None and \
lisp.pos_tip_show is not None: # make sure extension is installed
lisp.pos_tip_show(help_str,None,None,None,0)
else:
lisp.message(help_str) # just use the emacs mini-buffer
if len(completions)==1:#if there's only one completion, just insert it
d = completions[0].data['name'][len(target):]
if len(d):lisp.insert(d)
else:
lisp.message(d[1])
def find_completion(line,buffer_lines):
"""
the big function, returns a list of completions, or an error message ;)
"""
word = line
# split "a.b->c"
words = [i.strip() for i in _re_conn.split(word) if i not in ['','.','->']]
if word.endswith(".") or word.endswith("->"):#e.g. "a.b."
words.append('') # if the last element is 'nothing' then
# we append an empty element which matches all completions
if len(words)<=1:
return (False,"Nothing to complete")
#get the actual variable name, which should be the last "word" of the
#first part of the previous split.
#example, line is: "foo(a.bar"
# words should be: ["foo(a","bar"]
# we therefore want "a" from "foo(a"
var = get_last_word(words[0]).strip()
print "base var is:",var
# the target, "bar", which is what we're ultimately trying to complete.
target = words[-1]
curType = None# should also check for static class access
potentialClassnames = []
for l in buffer_lines:
#we're looking for "Type var"
if var in l:
for classname in try_cn_match(l,var):
if len(classname)==0:continue
try:
#then check if it exists
cls = cpp.CppObject.getByName(classname)
curType = classname
break
except KeyError:
potentialClassnames.append(classname)
# there might be an associated namespace
# here we're looking, for something like
# namespace NameSpace{
# then we're gonna look for NameSpace::Type
# which we might have found earlier
for L in buffer_lines:
defs = _re_namespace.search(L)
if defs:
nspace = defs.group('nspace')+"::"
try:
cls = cpp.CppObject.getByName(nspace+classname)
curType = nspace+classname
break
except:
pass
except Exception,e:
print "##",e
if curType is not None:
break
if curType is None: #nothing found, send a small error message:
rlcls = [] #candidates
for cls in potentialClassnames:
cdts = cpp.CppObject.getClassCandidates(cls)
rlcls.append([cdts,cls])
s = "No definition of "+var+" found. (did you update?)"
if len(rlcls):
s+="\n Potential candidates:\n"
for i in rlcls:
for j in i[0]:
s+="\n "+j+" for "+i[1]
return (False,s)
lastWord = ""
def clean(s):
return s.replace("*","").replace("[","").replace("]","").replace("&","").strip()
try:
curClass = cpp.CppObject.getByName(clean(curType))
except KeyError,e:
if curType!="!NoMatch":
return (False,"Cannot complete type '"+curType+"' from "+lastWord)
else:
return (False,"No match for property "+lastWord)
for i in words[1:]:
#here we run through the stack of symbols, a.b->c.target
#to find the final symbol's type (c's type in example above)
try:
curClass = cpp.CppObject.getByName(clean(curType))
if i==target:
break
except KeyError,e:
if curType!="!NoMatch":
return (False,"Cannot complete type '"+curType+"' from "+lastWord)
else:
return (False,"No match for property "+lastWord)
# we found a class
lastType = curType
curType = "!NoMatch"
i = rmargs(i)
lastWord = i
# we're trying to find a children of this class(method or field)
# which has the name of the next word
# hopefully, there's only one, but in overloading
# cases, it probably just won't work.
# We'd need much better parsing than now.
try:
wordobj = curClass.getChildrenByWeakName(i)[0]
except:
return (False,"Cannot expand type '"+curType+"'")
curType = wordobj.getType().fullname()
# we ran through all the symbols, curClass is now "c"'s [return] type
# and we can list all completions which start with "target"
# (the original last word)
try:
completions = curClass.getChildrenByWeakName(target)
except Exception,e:
return (False,"Cannot expand type '"+curType+"' "+str(e))
#return all of em
if len(completions):
return (True,completions,target)
else:
s = "No completions for %s.%s*, from %s"%(curType,target,str(curClass))
return (False,s)
check_word.interaction = ''
def complete_type():
"""
UI attempt to grab completions for a class
"""
c = lisp.completing_read("Type to complete:",cpp.CppObject.classes)
lisp.message("Looking for:"+str(c))
try:
t = cpp.CppObject.getByName(c)
completions = t.getChildrenByWeakName("")
lisp.pos_tip_show(make_help_message(completions),None,None,None,0)
except:
lisp.message("Couldn't find class "+c)
complete_type.interaction = ''
def ask(s):
lisp.message("got:"+str(s))
ask.interaction = "s Enter str:"