""" Jedi is an autocompletion library for Python. It offers additonal services such as goto / get_definition / pydoc support / get_in_function_call / related names. To give you a simple exmple how you can use the jedi library, here is an exmple for the autocompletion feature: >>> import jedi >>> source = '''import json; json.l''' >>> script = jedi.Script(source, 1, 19, '') >>> script >>> completions = script.complete() >>> completions [, ] >>> completions[0].complete 'oad' >>> completions[0].word 'load' As you see Jedi is pretty simple and allows you to concentrate writing a good text editor, while still having very good IDE features for Python. """ from __future__ import with_statement __all__ = ['Script', 'NotFoundError', 'set_debug_function'] import re import parsing import dynamic import imports import evaluate import modules import debug import settings import keywords import helpers import builtin import api_classes from _compatibility import next, unicode class NotFoundError(Exception): """ A custom error to avoid catching the wrong exceptions """ pass class Script(object): """ A Script is the base for a completion, goto or whatever call. :param source: The source code of the current file :type source: string :param line: The line to complete in. :type line: int :param col: The column to complete in. :type col: int :param source_path: The path in the os, the current module is in. :type source_path: string or None :param source_encoding: encoding for decoding `source`, when it is not a `unicode` object. :type source_encoding: string """ def __init__(self, source, line, column, source_path, source_encoding='utf-8'): debug.reset_time() try: source = unicode(source, source_encoding, 'replace') # Use 'replace' over 'ignore' to hold code structure. except TypeError: # `source` is already a unicode object pass self.pos = line, column self.module = modules.ModuleWithCursor(source_path, source=source, position=self.pos) self.source_path = source_path debug.speed('init') @property def parser(self): """ The lazy parser """ return self.module.parser def complete(self): """ An auto completer for python files. :return: list of Completion objects, sorted by name and __ comes last. :rtype: list """ def follow_imports_if_possible(name): # TODO remove this, or move to another place (not used) par = name.parent if isinstance(par, parsing.Import) and not \ isinstance(self.parser.user_stmt, parsing.Import): new = imports.ImportPath(par).follow(is_goto=True) # Only remove the old entry if a new one has been found. #print par, new, par.parent if new: try: return new except AttributeError: # .name undefined pass return [name] debug.speed('complete start') path = self.module.get_path_until_cursor() path, dot, like = self._get_completion_parts(path) try: scopes = list(self._prepare_goto(path, True)) except NotFoundError: scopes = [] scope_generator = evaluate.get_names_for_scope( self.parser.user_scope, self.pos) completions = [] for scope, name_list in scope_generator: for c in name_list: completions.append((c, scope)) else: completions = [] debug.dbg('possible scopes', scopes) for s in scopes: if s.isinstance(evaluate.Function): names = s.get_magic_method_names() else: if isinstance(s, imports.ImportPath): if like == 'import': l = self.module.get_line(self.pos[0])[:self.pos[1]] if not l.endswith('import import'): continue names = s.get_defined_names(on_import_stmt=True) else: names = s.get_defined_names() for c in names: completions.append((c, s)) if not dot: # named_params have no dots call_def = self.get_in_function_call() if call_def: if not call_def.module.is_builtin(): for p in call_def.params: completions.append((p.get_name(), p)) # Do the completion if there is no path before and no import stmt. if (not scopes or not isinstance(scopes[0], imports.ImportPath)) \ and not path: # add keywords bs = builtin.Builtin.scope completions += ((k, bs) for k in keywords.get_keywords( all=True)) needs_dot = not dot and path comps = [] for c, s in set(completions): n = c.names[-1] if settings.case_insensitive_completion \ and n.lower().startswith(like.lower()) \ or n.startswith(like): if not evaluate.filter_private_variable(s, self.parser.user_stmt, n): new = api_classes.Completion(c, needs_dot, len(like), s) comps.append(new) debug.speed('complete end') return sorted(comps, key=lambda x: (x.word.startswith('__'), x.word.startswith('_'), x.word.lower())) def _prepare_goto(self, goto_path, is_like_search=False): """ Base for complete, goto and get_definition. Basically it returns the resolved scopes under cursor. """ debug.dbg('start: %s in %s' % (goto_path, self.parser.scope)) user_stmt = self.parser.user_stmt debug.speed('parsed') if not user_stmt and len(goto_path.split('\n')) > 1: # If the user_stmt is not defined and the goto_path is multi line, # something's strange. Most probably the backwards tokenizer # matched to much. return [] if isinstance(user_stmt, parsing.Import): scopes = [self._get_on_import_stmt(is_like_search)[0]] else: # just parse one statement, take it and evaluate it stmt = self._get_under_cursor_stmt(goto_path) scopes = evaluate.follow_statement(stmt) return scopes def _get_under_cursor_stmt(self, cursor_txt): r = parsing.PyFuzzyParser(cursor_txt, self.source_path, no_docstr=True) try: stmt = r.module.statements[0] except IndexError: raise NotFoundError() stmt.start_pos = self.pos stmt.parent = self.parser.user_scope return stmt def get_definition(self): """ Returns the definitions of a the path under the cursor. This is not a goto function! This follows complicated paths and returns the end, not the first definition. The big difference of goto and get_definition is that goto doesn't follow imports and statements. Multiple objects may be returned, because Python itself is a dynamic language, which means depending on an option you can have two different versions of a function. :return: list of Definition objects, which are basically scopes. :rtype: list """ def resolve_import_paths(scopes): for s in scopes.copy(): if isinstance(s, imports.ImportPath): scopes.remove(s) scopes.update(resolve_import_paths(set(s.follow()))) return scopes goto_path = self.module.get_path_under_cursor() context = self.module.get_context() if next(context) in ('class', 'def'): scopes = set([self.module.parser.user_scope]) elif not goto_path: op = self.module.get_operator_under_cursor() scopes = set([keywords.get_operator(op, self.pos)] if op else []) else: scopes = set(self._prepare_goto(goto_path)) scopes = resolve_import_paths(scopes) # add keywords scopes |= keywords.get_keywords(string=goto_path, pos=self.pos) d = set([api_classes.Definition(s) for s in scopes if not isinstance(s, imports.ImportPath._GlobalNamespace)]) return sorted(d, key=lambda x: (x.module_path, x.start_pos)) def goto(self): """ Returns the first definition found by goto. This means: It doesn't follow imports and statements. Multiple objects may be returned, because Python itself is a dynamic language, which means depending on an option you can have two different versions of a function. :return: list of Definition objects, which are basically scopes. """ d = [api_classes.Definition(d) for d in set(self._goto()[0])] return sorted(d, key=lambda x: (x.module_path, x.start_pos)) def _goto(self, add_import_name=False): """ Used for goto and related_names. :param add_import_name: TODO add description """ def follow_inexistent_imports(defs): """ Imports can be generated, e.g. following `multiprocessing.dummy` generates an import dummy in the multiprocessing module. The Import doesn't exist -> follow. """ definitions = set(defs) for d in defs: if isinstance(d.parent, parsing.Import) \ and d.start_pos == (0, 0): i = imports.ImportPath(d.parent).follow(is_goto=True) definitions.remove(d) definitions |= follow_inexistent_imports(i) return definitions goto_path = self.module.get_path_under_cursor() context = self.module.get_context() if next(context) in ('class', 'def'): user_scope = self.parser.user_scope definitions = set([user_scope.name]) search_name = str(user_scope.name) elif isinstance(self.parser.user_stmt, parsing.Import): s, name_part = self._get_on_import_stmt() try: definitions = [s.follow(is_goto=True)[0]] except IndexError: definitions = [] search_name = str(name_part) if add_import_name: import_name = self.parser.user_stmt.get_defined_names() # imports have only one name if name_part == import_name[0].names[-1]: definitions.append(import_name[0]) else: stmt = self._get_under_cursor_stmt(goto_path) defs, search_name = evaluate.goto(stmt) definitions = follow_inexistent_imports(defs) return definitions, search_name def related_names(self, additional_module_paths=[]): """ Returns `dynamic.RelatedName` objects, which contain all names, that are defined by the same variable, function, class or import. This function can be used either to show all the usages of a variable or for renaming purposes. TODO implement additional_module_paths """ user_stmt = self.parser.user_stmt definitions, search_name = self._goto(add_import_name=True) if isinstance(user_stmt, parsing.Statement) \ and self.pos < user_stmt.get_assignment_calls().start_pos: # the search_name might be before `=` definitions = [v for v in user_stmt.set_vars if str(v) == search_name] if not isinstance(user_stmt, parsing.Import): # import case is looked at with add_import_name option definitions = dynamic.related_name_add_import_modules(definitions, search_name) module = set([d.get_parent_until() for d in definitions]) module.add(self.parser.module) names = dynamic.related_names(definitions, search_name, module) for d in set(definitions): if isinstance(d, parsing.Module): names.append(api_classes.RelatedName(d, d)) else: names.append(api_classes.RelatedName(d.names[0], d)) return sorted(set(names), key=lambda x: (x.module_path, x.start_pos), reverse=True) def get_in_function_call(self): """ Return the function, that the cursor is in, e.g.: >>> isinstance(| # | <-- cursor is here This would return the `isinstance` function. In contrary: >>> isinstance()| # | <-- cursor is here This would return `None`. """ def check_user_stmt(user_stmt): if user_stmt is None \ or not isinstance(user_stmt, parsing.Statement): return None, 0 ass = helpers.fast_parent_copy(user_stmt.get_assignment_calls()) call, index, stop = helpers.scan_array_for_pos(ass, self.pos) return call, index def check_cache(): """ Do the parsing with a part parser, therefore reduce ressource costs. TODO this is not working with multi-line docstrings, improve. """ if self.source_path is None: return None, 0 try: timestamp, parser = builtin.CachedModule.cache[ self.source_path] except KeyError: return None, 0 part_parser = self.module.get_part_parser() user_stmt = part_parser.user_stmt call, index = check_user_stmt(user_stmt) if call: old_stmt = parser.module.get_statement_for_position(self.pos) if old_stmt is None: return None, 0 old_call, old_index = check_user_stmt(old_stmt) if old_call: # compare repr because that should definitely be the same. # Otherwise the whole thing is out of sync. if repr(old_call) == repr(call): # return the index of the part_parser return old_call, index return None, 0 else: raise NotFoundError() debug.speed('func_call start') try: call, index = check_cache() except NotFoundError: return None debug.speed('func_call parsed') if call is None: # This is a backup, if the above is not successful. user_stmt = self.parser.user_stmt call, index = check_user_stmt(user_stmt) if call is None: return None debug.speed('func_call user_stmt') with helpers.scale_speed_settings(settings.scale_get_in_function_call): origins = evaluate.follow_call(call) debug.speed('func_call followed') if len(origins) == 0: return None # just take entry zero, because we need just one. executable = origins[0] return api_classes.CallDef(executable, index, call) def _get_on_import_stmt(self, is_like_search=False): """ Resolve the user statement, if it is an import. Only resolve the parts until the user position. """ user_stmt = self.parser.user_stmt import_names = user_stmt.get_all_import_names() kill_count = -1 cur_name_part = None for i in import_names: if user_stmt.alias == i: continue for name_part in i.names: if name_part.end_pos >= self.pos: if not cur_name_part: cur_name_part = name_part kill_count += 1 i = imports.ImportPath(user_stmt, is_like_search, kill_count=kill_count, direct_resolve=True) return i, cur_name_part def _get_completion_parts(self, path): """ Returns the parts for the completion :return: tuple - (path, dot, like) """ match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S) return match.groups() def __del__(self): evaluate.clear_caches() def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, notices=True, speed=True): """ You can define a callback debug function to get all the debug messages. :param func_cb: The callback function for debug messages, with n params. """ debug.debug_function = func_cb debug.enable_warning = warnings debug.enable_notice = notices debug.enable_speed = speed