def get_diff(self): old_lines = split_lines(self._module_node.get_code(), keepends=True) new_lines = split_lines(self.get_new_code(), keepends=True) # Add a newline at the end if it's missing. Otherwise the diff will be # very weird. A `diff -u file1 file2` would show the string: # # \ No newline at end of file # # This is not necessary IMO, because Jedi does not really play with # newlines and the ending newline does not really matter in Python # files. ~dave if old_lines[-1] != '': old_lines[-1] += '\n' if new_lines[-1] != '': new_lines[-1] += '\n' project_path = self._inference_state.project.path if self._from_path is None: from_p = '' else: from_p = relpath(self._from_path, project_path) if self._to_path is None: to_p = '' else: to_p = relpath(self._to_path, project_path) diff = difflib.unified_diff( old_lines, new_lines, fromfile=from_p, tofile=to_p, ) # Apparently there's a space at the end of the diff - for whatever # reason. return ''.join(diff).rstrip(' ')
def get_completions(self, info): '''Get Python completions''' # https://github.com/davidhalter/jedi/blob/master/jedi/utils.py if jedi is None: return [] text = info['code'] position = (info['line_num'], info['column']) interpreter = Interpreter(text, [self.env]) if jedi.__version__ >= LooseVersion('0.12.0'): lines = split_lines(text) name = get_on_completion_name(interpreter._module_node, lines, position) before = text[:len(text) - len(name)] elif jedi.__version__ >= LooseVersion('0.10.0'): lines = split_lines(text) name = get_on_completion_name(interpreter._get_module_node(), lines, position) before = text[:len(text) - len(name)] else: path = UserContext(text, position).get_path_until_cursor() path, dot, like = completion_parts(path) before = text[:len(text) - len(like)] completions = interpreter.completions() completions = [before + c.name_with_symbols for c in completions] self.kernel.log.error(completions) return [c[info['start']:] for c in completions]
def get_diff(self): old_lines = split_lines(self._module_node.get_code(), keepends=True) new_lines = split_lines(self.get_new_code(), keepends=True) project_path = self._inference_state.project._path diff = difflib.unified_diff( old_lines, new_lines, fromfile=relpath(self._from_path, project_path), tofile=relpath(self._to_path, project_path), ) # Apparently there's a space at the end of the diff - for whatever # reason. return ''.join(diff).rstrip(' ')
def get_completions(self, info): '''Get Python completions''' # https://github.com/davidhalter/jedi/blob/master/jedi/utils.py if jedi is None: return [] text = info['code'] position = (info['line_num'], info['column']) interpreter = Interpreter(text, [self.env]) lines = split_lines(text) name = get_on_completion_name( interpreter._module_node, lines, position ) before = text[:len(text) - len(name)] try: completions = interpreter.complete() except AttributeError: completions = interpreter.completions() completions = [before + c.name_with_symbols for c in completions] self.kernel.log.error(completions) return [c[info['start']:] for c in completions]
def extract(script, new_name): """ The `args` / `kwargs` params are the same as in `api.Script`. :param operation: The refactoring operation to execute. :type operation: str :type source: str :return: list of changed lines/changed files """ new_lines = split_lines(python_bytes_to_unicode(script.source)) old_lines = new_lines[:] user_stmt = script._parser.user_stmt() # TODO care for multiline extracts dct = {} if user_stmt: pos = script._pos line_index = pos[0] - 1 arr, index = helpers.array_for_pos(user_stmt, pos) if arr is not None: start_pos = arr[index].start_pos end_pos = arr[index].end_pos # take full line if the start line is different from end line e = end_pos[1] if end_pos[0] == start_pos[0] else None start_line = new_lines[start_pos[0] - 1] text = start_line[start_pos[1]:e] for l in range(start_pos[0], end_pos[0] - 1): text += '\n' + l if e is None: end_line = new_lines[end_pos[0] - 1] text += '\n' + end_line[:end_pos[1]] # remove code from new lines t = text.lstrip() del_start = start_pos[1] + len(text) - len(t) text = t.rstrip() del_end = len(t) - len(text) if e is None: new_lines[end_pos[0] - 1] = end_line[end_pos[1] - del_end:] e = len(start_line) else: e = e - del_end start_line = start_line[:del_start] + new_name + start_line[e:] new_lines[start_pos[0] - 1] = start_line new_lines[start_pos[0]:end_pos[0] - 1] = [] # add parentheses in multiline case open_brackets = ['(', '[', '{'] close_brackets = [')', ']', '}'] if '\n' in text and not (text[0] in open_brackets and text[-1] == close_brackets[open_brackets.index(text[0])]): text = '(%s)' % text # add new line before statement indent = user_stmt.start_pos[1] new = "%s%s = %s" % (' ' * indent, new_name, text) new_lines.insert(line_index, new) dct[script.path] = script.path, old_lines, new_lines return Refactoring(dct)
def _complete_in_string(self, start_leaf, string): """ To make it possible for people to have completions in doctests or generally in "Python" code in docstrings, we use the following heuristic: - Having an indented block of code - Having some doctest code that starts with `>>>` - Having backticks that doesn't have whitespace inside it """ def iter_relevant_lines(lines): include_next_line = False for l in code_lines: if include_next_line or l.startswith('>>>') or l.startswith( ' '): yield re.sub(r'^( *>>> ?| +)', '', l) else: yield None include_next_line = bool(re.match(' *>>>', l)) string = dedent(string) code_lines = split_lines(string, keepends=True) relevant_code_lines = list(iter_relevant_lines(code_lines)) if relevant_code_lines[-1] is not None: # Some code lines might be None, therefore get rid of that. relevant_code_lines = [c or '\n' for c in relevant_code_lines] return self._complete_code_lines(relevant_code_lines) match = re.search(r'`([^`\s]+)', code_lines[-1]) if match: return self._complete_code_lines([match.group(1)]) return []
def _replace(nodes, expression_replacement, extracted, pos, insert_before_leaf=None, remaining_prefix=None): # Now try to replace the nodes found with a variable and move the code # before the current statement. definition = _get_parent_definition(nodes[0]) if insert_before_leaf is None: insert_before_leaf = definition.get_first_leaf() first_node_leaf = nodes[0].get_first_leaf() lines = split_lines(insert_before_leaf.prefix, keepends=True) if first_node_leaf is insert_before_leaf: if remaining_prefix is not None: # The remaining prefix has already been calculated. lines[:-1] = remaining_prefix lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n'] extracted_prefix = ''.join(lines) replacement_dct = {} if first_node_leaf is insert_before_leaf: replacement_dct[nodes[0]] = extracted_prefix + expression_replacement else: if remaining_prefix is None: p = first_node_leaf.prefix else: p = remaining_prefix + _get_indentation(nodes[0]) replacement_dct[nodes[0]] = p + expression_replacement replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value for node in nodes[1:]: replacement_dct[node] = '' return replacement_dct
def _rename(names, replace_str): """ For both rename and inline. """ order = sorted(names, key=lambda x: (x.module_path, x.line, x.column), reverse=True) def process(path, old_lines, new_lines): if new_lines is not None: # goto next file, save last dct[path] = path, old_lines, new_lines dct = {} current_path = object() new_lines = old_lines = None for name in order: if name.in_builtin_module(): continue if current_path != name.module_path: current_path = name.module_path process(current_path, old_lines, new_lines) if current_path is not None: # None means take the source that is a normal param. with open(current_path) as f: source = f.read() new_lines = split_lines(python_bytes_to_unicode(source)) old_lines = new_lines[:] nr, indent = name.line, name.column line = new_lines[nr - 1] new_lines[nr - 1] = line[:indent] + replace_str + \ line[indent + len(name.name):] process(current_path, old_lines, new_lines) return dct
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None, environment=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() if sys_path is not None and not is_py3: sys_path = list(map(force_unicode, sys_path)) # Load the Python grammar of the current interpreter. project = get_default_project( os.path.dirname(self.path)if path else os.getcwd() ) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path self._evaluator = Evaluator( project, environment=environment, script_path=self.path ) debug.speed('init') self._module_node, source = self._evaluator.parse_and_get_code( code=source, path=self.path, encoding=encoding, cache=False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, ) debug.speed('parsed') self._code_lines = parso.split_lines(source, keepends=True) self._code = source line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_string = self._code_lines[line - 1] line_len = len(line_string) if line_string.endswith('\r\n'): line_len -= 1 if line_string.endswith('\n'): line_len -= 1 column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter (%d) is not in a valid range ' '(0-%d) for line %d (%r).' % ( column, line_len, line, line_string)) self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time()
def collections_namedtuple(obj, arguments, callback): """ Implementation of the namedtuple function. This has to be done by processing the namedtuple class template and evaluating the result. """ evaluator = obj.evaluator # Process arguments name = u'jedi_unknown_namedtuple' for c in _follow_param(evaluator, arguments, 0): x = get_str_or_none(c) if x is not None: name = force_unicode(x) break # TODO here we only use one of the types, we should use all. param_contexts = _follow_param(evaluator, arguments, 1) if not param_contexts: return NO_CONTEXTS _fields = list(param_contexts)[0] string = get_str_or_none(_fields) if string is not None: fields = force_unicode(string).replace(',', ' ').split() elif isinstance(_fields, iterable.Sequence): fields = [ force_unicode(get_str_or_none(v)) for lazy_context in _fields.py__iter__() for v in lazy_context.infer() ] fields = [f for f in fields if f is not None] else: return NO_CONTEXTS # Build source code code = _NAMEDTUPLE_CLASS_TEMPLATE.format( typename=name, field_names=tuple(fields), num_fields=len(fields), arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1], repr_fmt='', field_defs='\n'.join( _NAMEDTUPLE_FIELD_TEMPLATE.format(index=index, name=name) for index, name in enumerate(fields))) # Parse source code module = evaluator.grammar.parse(code) generated_class = next(module.iter_classdefs()) parent_context = ModuleContext( evaluator, module, file_io=None, string_names=None, code_lines=parso.split_lines(code, keepends=True), ) return ContextSet( [ClassContext(evaluator, parent_context, generated_class)])
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # TODO do we really want that? self._source = python_bytes_to_unicode(source, encoding, errors='replace') self._code_lines = split_lines(self._source) line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_len = len(self._code_lines[line - 1]) column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter is not in a valid range.') self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() project = Project(sys_path=sys_path) self._evaluator = Evaluator(self._grammar, project) project.add_script_path(self.path) debug.speed('init')
def __init__(self, code=None, *, path=None, environment=None, project=None): self._orig_path = path if isinstance(path, str): path = Path(path) self.path = path.absolute() if path else None if code is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: code = f.read() if project is None: # Load the Python grammar of the current interpreter. project = get_default_project(None if self.path is None else self.path.parent) self._inference_state = InferenceState( project, environment=environment, script_path=self.path ) debug.speed('init') self._module_node, code = self._inference_state.parse_and_get_code( code=code, path=self.path, use_latest_grammar=path and path.suffix == '.pyi', cache=False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, ) debug.speed('parsed') self._code_lines = parso.split_lines(code, keepends=True) self._code = code cache.clear_time_caches() debug.reset_time()
def collections_namedtuple(evaluator, obj, arguments): """ Implementation of the namedtuple function. This has to be done by processing the namedtuple class template and evaluating the result. """ collections_context = obj.parent_context _class_template_set = collections_context.py__getattribute__( u'_class_template') if not _class_template_set: # Namedtuples are not supported on Python 2.6, early 2.7, because the # _class_template variable is not defined, there. return NO_CONTEXTS # Process arguments # TODO here we only use one of the types, we should use all. # TODO this is buggy, doesn't need to be a string name = list(_follow_param(evaluator, arguments, 0))[0].get_safe_value() _fields = list(_follow_param(evaluator, arguments, 1))[0] if isinstance(_fields, compiled.CompiledObject): fields = _fields.get_safe_value().replace(',', ' ').split() elif isinstance(_fields, iterable.Sequence): fields = [ v.get_safe_value() for lazy_context in _fields.py__iter__() for v in lazy_context.infer() if is_string(v) ] else: return NO_CONTEXTS def get_var(name): x, = collections_context.py__getattribute__(name) return x.get_safe_value() base = next(iter(_class_template_set)).get_safe_value() base += _NAMEDTUPLE_INIT # Build source code code = base.format( typename=name, field_names=tuple(fields), num_fields=len(fields), arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1], repr_fmt=', '.join( get_var(u'_repr_template').format(name=name) for name in fields), field_defs='\n'.join( get_var(u'_field_template').format(index=index, name=name) for index, name in enumerate(fields))) # Parse source code module = evaluator.grammar.parse(code) generated_class = next(module.iter_classdefs()) parent_context = ModuleContext( evaluator, module, None, code_lines=parso.split_lines(code, keepends=True), ) return ContextSet(ClassContext(evaluator, parent_context, generated_class))
def collections_namedtuple(value, arguments, callback): """ Implementation of the namedtuple function. This has to be done by processing the namedtuple class template and inferring the result. """ inference_state = value.inference_state # Process arguments name = 'jedi_unknown_namedtuple' for c in _follow_param(inference_state, arguments, 0): x = get_str_or_none(c) if x is not None: name = x break # TODO here we only use one of the types, we should use all. param_values = _follow_param(inference_state, arguments, 1) if not param_values: return NO_VALUES _fields = list(param_values)[0] string = get_str_or_none(_fields) if string is not None: fields = string.replace(',', ' ').split() elif isinstance(_fields, iterable.Sequence): fields = [ get_str_or_none(v) for lazy_value in _fields.py__iter__() for v in lazy_value.infer() ] fields = [f for f in fields if f is not None] else: return NO_VALUES # Build source code code = _NAMEDTUPLE_CLASS_TEMPLATE.format( typename=name, field_names=tuple(fields), num_fields=len(fields), arg_list=repr(tuple(fields)).replace("'", "")[1:-1], repr_fmt='', field_defs='\n'.join( _NAMEDTUPLE_FIELD_TEMPLATE.format(index=index, name=name) for index, name in enumerate(fields))) # Parse source code module = inference_state.grammar.parse(code) generated_class = next(module.iter_classdefs()) parent_context = ModuleValue( inference_state, module, code_lines=parso.split_lines(code, keepends=True), ).as_context() return ValueSet( [ClassValue(inference_state, parent_context, generated_class)])
def _split_prefix_at(leaf, until_line): """ Returns a tuple of the leaf's prefix, split at the until_line position. """ # second means the second returned part second_line_count = leaf.start_pos[0] - until_line lines = split_lines(leaf.prefix, keepends=True) return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
def collections_namedtuple(evaluator, obj, arguments): """ Implementation of the namedtuple function. This has to be done by processing the namedtuple class template and evaluating the result. """ collections_context = obj.parent_context _class_template_set = collections_context.py__getattribute__(u'_class_template') if not _class_template_set: # Namedtuples are not supported on Python 2.6, early 2.7, because the # _class_template variable is not defined, there. return NO_CONTEXTS # Process arguments # TODO here we only use one of the types, we should use all. # TODO this is buggy, doesn't need to be a string name = list(_follow_param(evaluator, arguments, 0))[0].get_safe_value() _fields = list(_follow_param(evaluator, arguments, 1))[0] if isinstance(_fields, compiled.CompiledObject): fields = _fields.get_safe_value().replace(',', ' ').split() elif isinstance(_fields, iterable.Sequence): fields = [ v.get_safe_value() for lazy_context in _fields.py__iter__() for v in lazy_context.infer() if is_string(v) ] else: return NO_CONTEXTS def get_var(name): x, = collections_context.py__getattribute__(name) return x.get_safe_value() base = next(iter(_class_template_set)).get_safe_value() base += _NAMEDTUPLE_INIT # Build source code code = base.format( typename=name, field_names=tuple(fields), num_fields=len(fields), arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1], repr_fmt=', '.join(get_var(u'_repr_template').format(name=name) for name in fields), field_defs='\n'.join(get_var(u'_field_template').format(index=index, name=name) for index, name in enumerate(fields)) ) # Parse source code module = evaluator.grammar.parse(code) generated_class = next(module.iter_classdefs()) parent_context = ModuleContext( evaluator, module, None, code_lines=parso.split_lines(code, keepends=True), ) return ContextSet(ClassContext(evaluator, parent_context, generated_class))
def cut_value_at_position(leaf, position): """ Cuts of the value of the leaf at position """ lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1] column = position[1] if leaf.line == position[0]: column -= leaf.column lines[-1] = lines[-1][:column] return ''.join(lines)
def get_desired_result(self): if platform.system().lower() == 'windows' and self.type == 'diff': # Windows uses backslashes to separate paths. lines = split_lines(self._desired_result, keepends=True) for i, line in enumerate(lines): if re.search(' import_tree/', line): lines[i] = line.replace('/', '\\') return ''.join(lines) return self._desired_result
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None, environment=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() if sys_path is not None and not is_py3: sys_path = list(map(force_unicode, sys_path)) # Load the Python grammar of the current interpreter. project = get_default_project(self.path or os.getcwd()) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path self._evaluator = Evaluator(project, environment=environment, script_path=self.path) self._project = project debug.speed('init') self._module_node, source = self._evaluator.parse_and_get_code( code=source, path=self.path, cache= False, # No disk cache, because the current script often changes. diff_cache=True, cache_path=settings.cache_directory) debug.speed('parsed') self._code_lines = parso.split_lines(source) line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_len = len(self._code_lines[line - 1]) column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter is not in a valid range.') self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time()
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None, environment=None, _project=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() if sys_path is not None and not is_py3: sys_path = list(map(force_unicode, sys_path)) project = _project if project is None: # Load the Python grammar of the current interpreter. project = get_default_project( os.path.dirname(self.path) if path else os.getcwd()) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path self._inference_state = InferenceState(project, environment=environment, script_path=self.path) debug.speed('init') self._module_node, source = self._inference_state.parse_and_get_code( code=source, path=self.path, encoding=encoding, use_latest_grammar=path and path.endswith('.pyi'), cache= False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, ) debug.speed('parsed') self._code_lines = parso.split_lines(source, keepends=True) self._code = source self._pos = line, column cache.clear_time_caches() debug.reset_time()
def _is_on_comment(leaf, position): comment_lines = split_lines(leaf.prefix) difference = leaf.start_pos[0] - position[0] prefix_start_pos = leaf.get_start_pos_of_prefix() if difference == 0: indent = leaf.start_pos[1] elif position[0] == prefix_start_pos[0]: indent = prefix_start_pos[1] else: indent = 0 line = comment_lines[-difference - 1][:position[1] - indent] return '#' in line
def _get_text_edits(changes: ChangedFile) -> List[types.TextEdit]: result = [] old_lines = split_lines(changes._module_node.get_code(), keepends=True) new_lines = split_lines(changes.get_new_code(), keepends=True) line_number = 0 start = None replace_lines = False lines: List[str] = [] def _append(): if replace_lines: end = types.Position(line_number) else: end = start result.append(types.TextEdit(types.Range(start, end), ''.join(lines))) for line in differ.compare(old_lines, new_lines): kind = line[0] if kind == '?': continue if kind == '-': if not start: start = types.Position(line_number) replace_lines = True line_number += 1 continue if kind == '+': if not start: start = types.Position(line_number) lines.append(line[2:]) continue if start: _append() start = None replace_lines = False lines = [] line_number += 1 if start: _append() return result
def get_completions(self, info): self._create_context() text = info["code"] lines = split_lines(text) position = (info["line_num"], info["column"]) code = COMPLETION_TEMPLATE % (text, str(lines), str(position)) response = self.command.execute(code) result = self._strip_out(response[1]) before, _, completions = eval(result) completions = [before + c for c in completions] return [c[info["start"]:] for c in completions]
def _get_typing_replacement_module(grammar): """ The idea is to return our jedi replacement for the PEP-0484 typing module as discussed at https://github.com/davidhalter/jedi/issues/663 """ global _typing_module, _typing_module_code_lines if _typing_module is None: typing_path = \ os.path.abspath(os.path.join(__file__, "../jedi_typing.py")) with open(typing_path) as f: code = unicode(f.read()) _typing_module = grammar.parse(code) _typing_module_code_lines = split_lines(code, keepends=True) return _typing_module, _typing_module_code_lines
def get_completions(self, info): '''Get Python completions''' # https://github.com/davidhalter/jedi/blob/master/jedi/utils.py if jedi is None: return [] text = info['code'] position = (info['line_num'], info['column']) interpreter = Interpreter(text, [self.env]) if jedi.__version__ >= LooseVersion('0.12.0'): lines = split_lines(text) name = get_on_completion_name( interpreter._module_node, lines, position ) before = text[:len(text) - len(name)] elif jedi.__version__ >= LooseVersion('0.10.0'): lines = split_lines(text) name = get_on_completion_name( interpreter._get_module_node(), lines, position ) before = text[:len(text) - len(name)] else: path = UserContext(text, position).get_path_until_cursor() path, dot, like = completion_parts(path) before = text[:len(text) - len(like)] completions = interpreter.completions() completions = [before + c.name_with_symbols for c in completions] self.kernel.log.error(completions) return [c[info['start']:] for c in completions]
def inline(script): """ :type script: api.Script """ new_lines = split_lines(python_bytes_to_unicode(script.source)) dct = {} definitions = script.goto_assignments() assert len(definitions) == 1 stmt = definitions[0]._definition usages = script.usages() inlines = [ r for r in usages if not stmt.start_pos <= (r.line, r.column) <= stmt.end_pos ] inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column), reverse=True) expression_list = stmt.expression_list() # don't allow multi-line refactorings for now. assert stmt.start_pos[0] == stmt.end_pos[0] index = stmt.start_pos[0] - 1 line = new_lines[index] replace_str = line[expression_list[0].start_pos[1]:stmt.end_pos[1] + 1] replace_str = replace_str.strip() # tuples need parentheses if expression_list and isinstance(expression_list[0], pr.Array): arr = expression_list[0] if replace_str[0] not in ['(', '[', '{'] and len(arr) > 1: replace_str = '(%s)' % replace_str # if it's the only assignment, remove the statement if len(stmt.get_defined_names()) == 1: line = line[:stmt.start_pos[1]] + line[stmt.end_pos[1]:] dct = _rename(inlines, replace_str) # remove the empty line new_lines = dct[script.path][2] if line.strip(): new_lines[index] = line else: new_lines.pop(index) return Refactoring(dct)
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() project = Project(sys_path=sys_path) self._evaluator = Evaluator(self._grammar, project) self._module_node, source = self._evaluator.parse_and_get_code( code=source, path=self.path, cache= False, # No disk cache, because the current script often changes. diff_cache=True, cache_path=settings.cache_directory) self._code_lines = split_lines(source) line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_len = len(self._code_lines[line - 1]) column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter is not in a valid range.') self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time() project.add_script_path(self.path) debug.speed('init')
def inline(script): """ :type script: api.Script """ new_lines = split_lines(python_bytes_to_unicode(script.source)) dct = {} definitions = script.goto_assignments() with common.ignored(AssertionError): assert len(definitions) == 1 stmt = definitions[0]._definition usages = script.usages() inlines = [r for r in usages if not stmt.start_pos <= (r.line, r.column) <= stmt.end_pos] inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column), reverse=True) expression_list = stmt.expression_list() # don't allow multiline refactorings for now. assert stmt.start_pos[0] == stmt.end_pos[0] index = stmt.start_pos[0] - 1 line = new_lines[index] replace_str = line[expression_list[0].start_pos[1]:stmt.end_pos[1] + 1] replace_str = replace_str.strip() # tuples need parentheses if expression_list and isinstance(expression_list[0], pr.Array): arr = expression_list[0] if replace_str[0] not in ['(', '[', '{'] and len(arr) > 1: replace_str = '(%s)' % replace_str # if it's the only assignment, remove the statement if len(stmt.get_defined_names()) == 1: line = line[:stmt.start_pos[1]] + line[stmt.end_pos[1]:] dct = _rename(inlines, replace_str) # remove the empty line new_lines = dct[script.path][2] if line.strip(): new_lines[index] = line else: new_lines.pop(index) return Refactoring(dct)
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # TODO do we really want that? self._source = python_bytes_to_unicode(source, encoding, errors='replace') self._code_lines = split_lines(self._source) line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_len = len(self._code_lines[line - 1]) column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter is not in a valid range.') self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() if sys_path is None: venv = os.getenv('VIRTUAL_ENV') if venv: sys_path = list(get_venv_path(venv)) self._evaluator = Evaluator(self._grammar, sys_path=sys_path) debug.speed('init')
def complete(self, text, state): """ This complete stuff is pretty weird, a generator would make a lot more sense, but probably due to backwards compatibility this is still the way how it works. The only important part is stuff in the ``state == 0`` flow, everything else has been copied from the ``rlcompleter`` std. library module. """ if state == 0: sys.path.insert(0, os.getcwd()) # Calling python doesn't have a path, so add to sys.path. try: logging.debug("Start REPL completion: " + repr(text)) interpreter = Interpreter(text, [namespace_module.__dict__]) lines = split_lines(text) position = (len(lines), len(lines[-1])) name = get_on_completion_name(interpreter._module_node, lines, position) before = text[:len(text) - len(name)] completions = interpreter.completions() logging.debug("REPL completions: %s", completions) except: logging.error("REPL Completion error:\n" + traceback.format_exc()) raise finally: sys.path.pop(0) self.matches = [ before + c.name_with_symbols for c in completions ] try: return self.matches[state] except IndexError: return None
def complete(self, text, state): """ This complete stuff is pretty weird, a generator would make a lot more sense, but probably due to backwards compatibility this is still the way how it works. The only important part is stuff in the ``state == 0`` flow, everything else has been copied from the ``rlcompleter`` std. library module. """ if state == 0: sys.path.insert(0, os.getcwd()) # Calling python doesn't have a path, so add to sys.path. try: logging.debug("Start REPL completion: " + repr(text)) interpreter = Interpreter(text, [namespace_module.__dict__]) lines = split_lines(text) position = (len(lines), len(lines[-1])) name = get_on_completion_name( interpreter._module_node, lines, position ) before = text[:len(text) - len(name)] completions = interpreter.completions() logging.debug("REPL completions: %s", completions) except: logging.error("REPL Completion error:\n" + traceback.format_exc()) raise finally: sys.path.pop(0) self.matches = [before + c.name_with_symbols for c in completions] try: return self.matches[state] except IndexError: return None
def __init__(self, code=None, line=None, column=None, path=None, encoding=None, sys_path=None, environment=None, project=None, source=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if encoding is None: encoding = 'utf-8' else: warnings.warn( "Deprecated since version 0.17.0. You should cast to valid " "unicode yourself, especially if you are not using utf-8.", DeprecationWarning, stacklevel=2) if line is not None: warnings.warn( "Providing the line is now done in the functions themselves " "like `Script(...).complete(line, column)`", DeprecationWarning, stacklevel=2) if column is not None: warnings.warn( "Providing the column is now done in the functions themselves " "like `Script(...).complete(line, column)`", DeprecationWarning, stacklevel=2) if source is not None: code = source warnings.warn("Use the code keyword argument instead.", DeprecationWarning, stacklevel=2) if code is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: code = f.read() if sys_path is not None and not is_py3: sys_path = list(map(force_unicode, sys_path)) if project is None: # Load the Python grammar of the current interpreter. project = get_default_project( os.path.dirname(self.path) if path else None) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path warnings.warn( "Deprecated since version 0.17.0. Use the project API instead, " "which means Script(project=Project(dir, sys_path=sys_path)) instead.", DeprecationWarning, stacklevel=2) self._inference_state = InferenceState(project, environment=environment, script_path=self.path) debug.speed('init') self._module_node, code = self._inference_state.parse_and_get_code( code=code, path=self.path, encoding=encoding, use_latest_grammar=path and path.endswith('.pyi'), cache= False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, ) debug.speed('parsed') self._code_lines = parso.split_lines(code, keepends=True) self._code = code self._pos = line, column cache.clear_time_caches() debug.reset_time()
def __init__(self, source=None, line=None, column=None, path=None, encoding='utf-8', sys_path=None, environment=None): self._orig_path = path # An empty path (also empty string) should always result in no path. self.path = os.path.abspath(path) if path else None if source is None: # TODO add a better warning than the traceback! with open(path, 'rb') as f: source = f.read() # Load the Python grammar of the current interpreter. self._grammar = parso.load_grammar() if sys_path is not None and not is_py3: sys_path = list(map(force_unicode, sys_path)) # Load the Python grammar of the current interpreter. project = get_default_project( # iOS: getcwd() creates problems. We force it to ~/Documents # os.path.dirname(self.path)if path else os.getcwd() os.path.dirname(self.path) if path else os.path. join(os.getenv("HOME"), 'Library/lib/python3.7/')) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path self._evaluator = Evaluator(project, environment=environment, script_path=self.path) self._project = project debug.speed('init') self._module_node, source = self._evaluator.parse_and_get_code( code=source, path=self.path, encoding=encoding, cache= False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, ) debug.speed('parsed') self._code_lines = parso.split_lines(source, keepends=True) self._code = source line = max(len(self._code_lines), 1) if line is None else line if not (0 < line <= len(self._code_lines)): raise ValueError('`line` parameter is not in a valid range.') line_string = self._code_lines[line - 1] line_len = len(line_string) if line_string.endswith('\r\n'): line_len -= 1 if line_string.endswith('\n'): line_len -= 1 column = line_len if column is None else column if not (0 <= column <= line_len): raise ValueError('`column` parameter (%d) is not in a valid range ' '(0-%d) for line %d (%r).' % (column, line_len, line, line_string)) self._pos = line, column self._path = path cache.clear_time_caches() debug.reset_time()
def _remove_indent_of_prefix(prefix): r""" Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n". """ return ''.join(split_lines(prefix, keepends=True)[:-1])
def _get_indentation(node): return split_lines(node.get_first_leaf().prefix)[-1]