| Tor Norbye | 3a2425a | 2013-11-04 10:16:08 -0800 | [diff] [blame^] | 1 | import os.path |
| 2 | import inspect |
| 3 | import sys |
| 4 | |
| 5 | from _tipper_common import DoFind |
| 6 | |
| 7 | |
| 8 | #completion types. |
| 9 | TYPE_IMPORT = '0' |
| 10 | TYPE_CLASS = '1' |
| 11 | TYPE_FUNCTION = '2' |
| 12 | TYPE_ATTR = '3' |
| 13 | TYPE_BUILTIN = '4' |
| 14 | TYPE_PARAM = '5' |
| 15 | |
| 16 | def _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 | |
| 37 | IS_IPY = False |
| 38 | if 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 | |
| 61 | def 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 | |
| 75 | def 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 | |
| 109 | def 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 | |
| 123 | def 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 | |
| 134 | def CheckChar(c): |
| 135 | if c == '-' or c == '.': |
| 136 | return '_' |
| 137 | return c |
| 138 | |
| 139 | def 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 | |