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