blob: 97fc76a91b3eb49d5de34c443b22f3439aaff384 [file] [log] [blame]
Tor Norbye3a2425a2013-11-04 10:16:08 -08001import os.path
2import inspect
3import sys
4
5from _tipper_common import DoFind
6
7
8#completion types.
9TYPE_IMPORT = '0'
10TYPE_CLASS = '1'
11TYPE_FUNCTION = '2'
12TYPE_ATTR = '3'
13TYPE_BUILTIN = '4'
14TYPE_PARAM = '5'
15
16def _imp(name, log=None):
17 try:
18 return __import__(name)
19 except:
20 if '.' in name:
21 sub = name[0:name.rfind('.')]
22
23 if log is not None:
24 log.AddContent('Unable to import', name, 'trying with', sub)
25 log.AddException()
26
27 return _imp(sub, log)
28 else:
29 s = 'Unable to import module: %s - sys.path: %s' % (str(name), sys.path)
30 if log is not None:
31 log.AddContent(s)
32 log.AddException()
33
34 raise ImportError(s)
35
36
37IS_IPY = False
38if sys.platform == 'cli':
39 IS_IPY = True
40 _old_imp = _imp
41 def _imp(name, log=None):
42 #We must add a reference in clr for .Net
43 import clr #@UnresolvedImport
44 initial_name = name
45 while '.' in name:
46 try:
47 clr.AddReference(name)
48 break #If it worked, that's OK.
49 except:
50 name = name[0:name.rfind('.')]
51 else:
52 try:
53 clr.AddReference(name)
54 except:
55 pass #That's OK (not dot net module).
56
57 return _old_imp(initial_name, log)
58
59
60
61def GetFile(mod):
62 f = None
63 try:
64 f = inspect.getsourcefile(mod) or inspect.getfile(mod)
65 except:
66 if hasattr(mod, '__file__'):
67 f = mod.__file__
68 if f.lower(f[-4:]) in ['.pyc', '.pyo']:
69 filename = f[:-4] + '.py'
70 if os.path.exists(filename):
71 f = filename
72
73 return f
74
75def Find(name, log=None):
76 f = None
77
78 mod = _imp(name, log)
79 parent = mod
80 foundAs = ''
81
82 if inspect.ismodule(mod):
83 f = GetFile(mod)
84
85 components = name.split('.')
86
87 old_comp = None
88 for comp in components[1:]:
89 try:
90 #this happens in the following case:
91 #we have mx.DateTime.mxDateTime.mxDateTime.pyd
92 #but after importing it, mx.DateTime.mxDateTime shadows access to mxDateTime.pyd
93 mod = getattr(mod, comp)
94 except AttributeError:
95 if old_comp != comp:
96 raise
97
98 if inspect.ismodule(mod):
99 f = GetFile(mod)
100 else:
101 if len(foundAs) > 0:
102 foundAs = foundAs + '.'
103 foundAs = foundAs + comp
104
105 old_comp = comp
106
107 return f, mod, parent, foundAs
108
109def Search(data):
110 '''@return file, line, col
111 '''
112
113 data = data.replace('\n', '')
114 if data.endswith('.'):
115 data = data.rstrip('.')
116 f, mod, parent, foundAs = Find(data)
117 try:
118 return DoFind(f, mod), foundAs
119 except:
120 return DoFind(f, parent), foundAs
121
122
123def GenerateTip(data, log=None):
124 data = data.replace('\n', '')
125 if data.endswith('.'):
126 data = data.rstrip('.')
127
128 f, mod, parent, foundAs = Find(data, log)
129 #print_ >> open('temp.txt', 'w'), f
130 tips = GenerateImportsTipForModule(mod)
131 return f, tips
132
133
134def CheckChar(c):
135 if c == '-' or c == '.':
136 return '_'
137 return c
138
139def GenerateImportsTipForModule(obj_to_complete, dirComps=None, getattr=getattr, filter=lambda name:True):
140 '''
141 @param obj_to_complete: the object from where we should get the completions
142 @param dirComps: if passed, we should not 'dir' the object and should just iterate those passed as a parameter
143 @param getattr: the way to get a given object from the obj_to_complete (used for the completer)
144 @param filter: a callable that receives the name and decides if it should be appended or not to the results
145 @return: list of tuples, so that each tuple represents a completion with:
146 name, doc, args, type (from the TYPE_* constants)
147 '''
148 ret = []
149
150 if dirComps is None:
151 dirComps = dir(obj_to_complete)
152 if hasattr(obj_to_complete, '__dict__'):
153 dirComps.append('__dict__')
154
155 getCompleteInfo = True
156
157 if len(dirComps) > 1000:
158 #ok, we don't want to let our users wait forever...
159 #no complete info for you...
160
161 getCompleteInfo = False
162
163 dontGetDocsOn = (float, int, str, tuple, list)
164 for d in dirComps:
165
166 if d is None:
167 continue
168
169 if not filter(d):
170 continue
171
172 args = ''
173
174 try:
175 obj = getattr(obj_to_complete, d)
176 except: #just ignore and get it without aditional info
177 ret.append((d, '', args, TYPE_BUILTIN))
178 else:
179
180 if getCompleteInfo:
181 try:
182 retType = TYPE_BUILTIN
183
184 #check if we have to get docs
185 getDoc = True
186 for class_ in dontGetDocsOn:
187
188 if isinstance(obj, class_):
189 getDoc = False
190 break
191
192 doc = ''
193 if getDoc:
194 #no need to get this info... too many constants are defined and
195 #makes things much slower (passing all that through sockets takes quite some time)
196 try:
197 doc = inspect.getdoc(obj)
198 if doc is None:
199 doc = ''
200 except: #may happen on jython when checking java classes (so, just ignore it)
201 doc = ''
202
203
204 if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
205 try:
206 args, vargs, kwargs, defaults = inspect.getargspec(obj)
207
208 r = ''
209 for a in (args):
210 if len(r) > 0:
211 r = r + ', '
212 r = r + str(a)
213 args = '(%s)' % (r)
214 except TypeError:
215 #ok, let's see if we can get the arguments from the doc
216 args = '()'
217 try:
218 found = False
219 if len(doc) > 0:
220 if IS_IPY:
221 #Handle case where we have the situation below
222 #sort(self, object cmp, object key)
223 #sort(self, object cmp, object key, bool reverse)
224 #sort(self)
225 #sort(self, object cmp)
226
227 #Or: sort(self: list, cmp: object, key: object)
228 #sort(self: list, cmp: object, key: object, reverse: bool)
229 #sort(self: list)
230 #sort(self: list, cmp: object)
231 if hasattr(obj, '__name__'):
232 name = obj.__name__+'('
233
234
235 #Fix issue where it was appearing sort(aa)sort(bb)sort(cc) in the same line.
236 lines = doc.splitlines()
237 if len(lines) == 1:
238 c = doc.count(name)
239 if c > 1:
240 doc = ('\n'+name).join(doc.split(name))
241
242
243 major = ''
244 for line in doc.splitlines():
245 if line.startswith(name) and line.endswith(')'):
246 if len(line) > len(major):
247 major = line
248 if major:
249 args = major[major.index('('):]
250 found = True
251
252
253 if not found:
254 i = doc.find('->')
255 if i < 0:
256 i = doc.find('--')
257 if i < 0:
258 i = doc.find('\n')
259 if i < 0:
260 i = doc.find('\r')
261
262
263 if i > 0:
264 s = doc[0:i]
265 s = s.strip()
266
267 #let's see if we have a docstring in the first line
268 if s[-1] == ')':
269 start = s.find('(')
270 if start >= 0:
271 end = s.find('[')
272 if end <= 0:
273 end = s.find(')')
274 if end <= 0:
275 end = len(s)
276
277 args = s[start:end]
278 if not args[-1] == ')':
279 args = args + ')'
280
281
282 #now, get rid of unwanted chars
283 l = len(args) - 1
284 r = []
285 for i in range(len(args)):
286 if i == 0 or i == l:
287 r.append(args[i])
288 else:
289 r.append(CheckChar(args[i]))
290
291 args = ''.join(r)
292
293 if IS_IPY:
294 if args.startswith('(self:'):
295 i = args.find(',')
296 if i >= 0:
297 args = '(self'+args[i:]
298 else:
299 args = '(self)'
300 i = args.find(')')
301 if i > 0:
302 args = args[:i+1]
303
304 except:
305 pass
306
307 retType = TYPE_FUNCTION
308
309 elif inspect.isclass(obj):
310 retType = TYPE_CLASS
311
312 elif inspect.ismodule(obj):
313 retType = TYPE_IMPORT
314
315 else:
316 retType = TYPE_ATTR
317
318
319 #add token and doc to return - assure only strings.
320 ret.append((d, doc, args, retType))
321
322 except: #just ignore and get it without aditional info
323 ret.append((d, '', args, TYPE_BUILTIN))
324
325 else: #getCompleteInfo == False
326 if inspect.ismethod(obj) or inspect.isbuiltin(obj) or inspect.isfunction(obj) or inspect.isroutine(obj):
327 retType = TYPE_FUNCTION
328
329 elif inspect.isclass(obj):
330 retType = TYPE_CLASS
331
332 elif inspect.ismodule(obj):
333 retType = TYPE_IMPORT
334
335 else:
336 retType = TYPE_ATTR
337 #ok, no complete info, let's try to do this as fast and clean as possible
338 #so, no docs for this kind of information, only the signatures
339 ret.append((d, '', str(args), retType))
340
341 return ret
342
343
344
345