def lsp_text_edits(changed_file: ChangedFile) -> List[TextEdit]: """Take a jedi `ChangedFile` and convert to list of text edits. Handles inserts, replaces, and deletions within a text file """ old_code = ( changed_file._module_node.get_code() # pylint: disable=protected-access ) new_code = changed_file.get_new_code() opcode_position_lookup_old = get_opcode_position_lookup(old_code) text_edits = [] for opcode in get_opcodes(old_code, new_code): if opcode.op in _OPCODES_CHANGE: start = opcode_position_lookup_old[opcode.old_start] end = opcode_position_lookup_old[opcode.old_end] start_char = opcode.old_start - start.range_start end_char = opcode.old_end - end.range_start new_text = new_code[opcode.new_start:opcode.new_end] text_edits.append( TextEdit( range=Range( start=Position(line=start.line, character=start_char), end=Position(line=end.line, character=end_char), ), new_text=new_text, )) return text_edits
def _get_macros_range(self, document: Document, xml_tree: etree.ElementTree) -> Optional[Range]: """Given a XML document and its corresponding ElementTree, finds the first macro import element and returns its Range position inside the document. Args: document (Document): The XML tool document. xml_tree (etree.ElementTree): The corresponding ElementTree of the document. Returns: Optional[Range]: The Range position of the import file if it exists. """ try: import_element = xml_tree.find("macros//import") line_number = import_element.sourceline - 1 filename = import_element.text start = document.lines[line_number].find(filename) end = start + len(filename) return Range( Position(line_number, start), Position(line_number, end), ) except BaseException: return None
def _make_diagnostic(linter, node, message): start_pos = Position(node.parse_tree.loc) end_pos = Position(node.parse_tree.loc + 1) return Diagnostic(Range(start_pos, end_pos), message=message, code=linter.code, severity=linter.severity)
def _validate(ls, params): """ Validate a document and publish diagnostics """ text_doc = ls.workspace.get_document(params.textDocument.uri) source = text_doc.source diagnostics = [] line_index = -1 # test comment for line in source.splitlines(): logging.debug("*** %s", line) line_index += 1 col_index = -1 while True: if col_index == -1: col_index = str.find(line, "e") else: col_index = str.find(line, "e", col_index + 1) if col_index == -1: logging.debug("The remainder of line %s is efree", line_index) break msg = "Character e at column {}".format(col_index) d = Diagnostic(Range(Position(line_index, col_index), Position(line_index, col_index + 1)), msg, source=type(server).__name__) diagnostics.append(d) logging.debug("e at line %s, col %s", line_index, col_index) ls.publish_diagnostics(text_doc.uri, diagnostics)
def _build_diagnostics( self, error_log: etree._ListErrorLog, xml_tree: Optional[etree.ElementTree] = None) -> List[Diagnostic]: """Gets a list of diagnostics from the XSD validation error log. Args: error_log (etree._ListErrorLog): The error log generated after XSD schema validation. xml_tree (Optional[etree.ElementTree], optional): The element tree associated with the error log. Defaults to None. Raises: ExpandMacrosFoundException: This is raised when a macro ``expand`` element is found in the error log. The ``expand`` element is not part of the XSD so, when this exception is raised, we know that the document must expand the macros before validation. Returns: List[Diagnostic]: The list of resulting diagnostic items found in the error log. """ diagnostics = [] for error in error_log.filter_from_errors(): if "expand" in error.message: raise ExpandMacrosFoundException(xml_tree) result = Diagnostic( Range( Position(error.line - 1, error.column), Position(error.line - 1, error.column), ), error.message, source=self.server_name, ) diagnostics.append(result) return diagnostics
def test_position(): assert Position(1, 2) == Position(1, 2) assert Position(1, 2) != Position(2, 2) assert Position(1, 2) <= Position(2, 2) assert Position(2, 2) >= Position(2, 0) assert Position(1, 2) != 'something else' assert "1:2" == repr(Position(1, 2))
def visit_symbol(self, ctx: antlr4.ParserRuleContext, kind:int=SymbolKind.Variable, visit_children:bool=True): id_ctx = ctx.getChild(0, Parser.IdentifierContext) symbol = DocumentSymbol( name=id_ctx.start.text, kind=kind, range=Range( start=Position(ctx.start.line-1, ctx.start.column), end=Position(ctx.stop.line-1, ctx.stop.column) ), selection_range=Range( start=Position(id_ctx.start.line-1, id_ctx.start.column), end=Position(id_ctx.stop.line-1, id_ctx.stop.column) ), detail="", children=[], deprecated=False ) logging.debug(f"found symbol: {symbol.name}") self.scope.append(symbol) if visit_children: logging.debug(f"Visiting children of symbol: {symbol.name}") prev_scope = self.scope self.scope = symbol.children res = self.visitChildren(ctx) self.scope = prev_scope return res
def test_invalid_textxfile_diagnostics_on_doc_change(client_server): client, server = client_server doc = doc_from_path(TEXTXFILE_WITH_ERROR_PATH, 'textxfile') content = doc.text doc.text = '' # Add doc to server's workspace directly server.workspace.put_document(doc) versioned_doc = VersionedTextDocumentIdentifier(doc.uri, doc.version + 1) content_change = TextDocumentContentChangeEvent( Range(Position(0, 0), Position(0, 0)), 0, content ) send_text_doc_did_change_request(client, versioned_doc, [content_change]) params = builtin_notifications.get(timeout=CALL_TIMEOUT) assert params.uri == doc.uri d = params.diagnostics[0] r = d.range assert d.message == "Expected ',' or ']'" assert r.start.line == 0 assert r.start.character == 0 assert r.end.line == 0 assert r.end.character == 500 # Remove all docs from the workspace server.workspace._docs = {}
def write(self, line): """This method lets us catch output from Sphinx.""" match = PROBLEM_PATTERN.match(line) if match: filepath = match.group("file") severity = PROBLEM_SEVERITY.get(match.group("type"), PROBLEM_SEVERITY["ERROR"]) diagnostics = self.diagnostics.get(filepath, None) if diagnostics is None: diagnostics = DiagnosticList() try: line_number = int(match.group("line")) except (TypeError, ValueError) as exc: self.logger.debug("Unable to parse line number: '%s'", match.group("line")) self.logger.debug(exc) line_number = 1 range_ = Range(Position(line_number - 1, 0), Position(line_number, 0)) diagnostics.append( Diagnostic(range_, match.group("message"), severity=severity, source="sphinx")) self.diagnostics[filepath] = diagnostics self.sphinx_log.info(line)
def test_document_multiline_edit(): old = ["def hello(a, b):\n", " print a\n", " print b\n"] doc = Document('file:///uri', u''.join(old)) change = TextDocumentContentChangeEvent( Range(Position(1, 4), Position(2, 11)), 0, u'print a, b') doc.apply_change(change) assert doc.lines == ["def hello(a, b):\n", " print a, b\n"]
def __init__(self, filename, ctx, kind=None): super().__init__( name=ctx.getChild(0, Parser.IdentifierContext).start.text, kind=kind, location=Location( filename, Range(start=Position(ctx.start.line, ctx.start.column), end=Position(ctx.stop.line, ctx.stop.column))))
def test_word_at_position(doc): """ Return the position under the cursor (or last in line if past the end) """ assert doc.word_at_position(Position(0, 8)) == 'document' assert doc.word_at_position(Position(0, 1000)) == 'document' assert doc.word_at_position(Position(1, 5)) == 'for' assert doc.word_at_position(Position(2, 0)) == 'testing' assert doc.word_at_position(Position(4, 0)) == ''
def _validate(ls, params): ls.show_message_log("Validating CP2K input...") diagnostics = [] text_doc = ls.workspace.get_document(params.textDocument.uri) parser = CP2KInputParser(DEFAULT_CP2K_INPUT_XML) with open(text_doc.path, "r") as fhandle: try: parser.parse(fhandle) except (TokenizerError, ParserError) as exc: ctx = exc.args[1] line = ctx["line"].rstrip() msg = f"Syntax error: {exc.args[0]}" if exc.__cause__: msg += f"({exc.__cause__})" linenr = ctx["linenr"] - 1 colnr = ctx["colnr"] if colnr is not None: count = 0 # number of underline chars after (positiv) or before (negative) the marker if ref_colnr given nchars = colnr # relevant line length if ctx["ref_colnr"] is not None: count = ctx["ref_colnr"] - ctx["colnr"] nchars = min(ctx["ref_colnr"], ctx["colnr"]) # correct if ref comes before if ctx["colnrs"] is not None: # shift by the number of left-stripped ws # ctx["colnrs"] contains the left shift for each possibly continued line nchars += ctx["colnrs"][ 0] # assume no line-continuation for now # at least do one context count = max(1, count) erange = Range(Position(linenr, colnr + 1 - count), Position(linenr, colnr + 1)) else: erange = Range(Position(linenr, 1), Position(linenr, len(line))) diagnostics += [ Diagnostic(erange, msg, source=type(cp2k_inp_server).__name__, related_information=[]) ] ls.publish_diagnostics(text_doc.uri, diagnostics)
def test_document_full_edit(): old = ["def hello(a, b):\n", " print a\n", " print b\n"] doc = Document('file:///uri', u''.join(old), sync_kind=TextDocumentSyncKind.FULL) change = TextDocumentContentChangeEvent( Range(Position(1, 4), Position(2, 11)), 0, u'print a, b') doc.apply_change(change) assert doc.lines == ["print a, b"]
def format(self, content: str, params: DocumentFormattingParams) -> List[TextEdit]: """Given the document contents returns the list of TextEdits needed to properly layout the document. """ formatted_result = self._format_document(content, params.options.tabSize) lines = content.count("\n") start = Position(0, 0) end = Position(lines + 1, 0) return [TextEdit(Range(start, end), formatted_result)]
def lsp_diagnostic(error: jedi.api.errors.SyntaxError) -> Diagnostic: """Get LSP Diagnostic from Jedi SyntaxError""" return Diagnostic( range=Range( start=Position(line=error.line - 1, character=error.column), end=Position(line=error.line - 1, character=error.column), ), message=str(error).strip("<>"), severity=DiagnosticSeverity.Error, source="jedi", )
def _get_range(p: SourcePosition = None): if p is None: return Range( Position(), Position(0, sys.maxsize), ) else: return Range( Position(p.line - 1, p.column - 1), Position(p.end_line - 1, p.end_column - 1), )
def _diagnostic(msg: str, line = 1, col = 1, end_line = None, end_col = sys.maxsize, severity = DiagnosticSeverity.Error): if end_line is None: end_line = line return Diagnostic( Range( Position(line - 1, col - 1), Position(end_line - 1, end_col - 1), ), msg, severity=severity, )
def formatting(params: DocumentFormattingParams): doc = self.workspace.get_document(params.textDocument.uri) content = doc.source tokens, remain = self._parser.parse(content) if remain: self.show_message("CMake parser failed") return None formatted = Formatter().format(tokens) lines = content.count("\n") return [TextEdit(Range(Position(0, 0), Position(lines + 1, 0)), formatted)]
def jump_to_definition(ls, c: TextDocumentPositionParams) -> List[Location]: doc = ls.workspace.get_document(c.textDocument.uri) items = find_definition(doc.uri, doc.source, c.position.line + 1, c.position.character) return [ Location( path_to_uri(item.module_path), Range( Position(item.line - 1, item.column), Position(item.line - 1, item.column + len(item.name)), ), ) for item in items ]
def test_document_end_of_file_edit(): old = ["print 'a'\n", "print 'b'\n"] doc = Document('file:///uri', u''.join(old)) change = TextDocumentContentChangeEvent( Range(Position(2, 0), Position(2, 0)), 0, u'o') doc.apply_change(change) assert doc.lines == [ "print 'a'\n", "print 'b'\n", "o", ]
def test_offset_at_position(doc): assert doc.offset_at_position(Position(0, 8)) == 8 assert doc.offset_at_position(Position(1, 5)) == 14 assert doc.offset_at_position(Position(2, 0)) == 13 assert doc.offset_at_position(Position(2, 4)) == 17 assert doc.offset_at_position(Position(3, 6)) == 27 assert doc.offset_at_position(Position(3, 7)) == 27 assert doc.offset_at_position(Position(3, 8)) == 28 assert doc.offset_at_position(Position(4, 0)) == 39 assert doc.offset_at_position(Position(5, 0)) == 39
def _cursor_word(self, uri: str, position: Position, include_all: bool = True) -> Optional[Tuple[str, Range]]: line = self._cursor_line(uri, position) cursor = position.character for m in re.finditer(r'\w+', line): end = m.end() if include_all else cursor if m.start() <= cursor <= m.end(): word = (line[m.start():end], Range(Position(position.line, m.start()), Position(position.line, end))) return word return None
def lsp_range(name: Name) -> Range: """Get LSP range from Jedi definition. - jedi is 1-indexed for lines and 0-indexed for columns - LSP is 0-indexed for lines and 0-indexed for columns - Therefore, subtract 1 from Jedi's definition line """ return Range( start=Position(line=name.line - 1, character=name.column), end=Position( line=name.line - 1, character=name.column + len(name.name), ), )
def _get_diagnostic_range(err: TextXError) -> Range: """Prettifies error message. Args: err: an instance of TextXError (syntax or semantic) Returns: A range which should be highlighted Raises: None """ # Mark a whole line ( 500 for now, should be len(doc.lines[line]) ) line = 0 if err.line - 1 < 0 else err.line - 1 return Range(Position(line, 0), Position(line, 500))
def validate(self, source): self.itpr = check(source) diagnostics = [] logging.debug(f'itpr errors: {self.itpr.errors}') for item in self.itpr.errors: l1 = item['lineno'] - 1 c1 = item['col_offset'] l2 = item['end_lineno'] - 1 c2 = item['end_col_offset'] msg = item['error'].message diagnostics.append( Diagnostic(range=Range(Position(l1, c1), Position(l2, c2)), message=msg, source="PDChecker")) return diagnostics
def role_to_completion_item( self, name: str, role, match: "re.Match", position: Position ) -> CompletionItem: """Convert an rst role to its CompletionItem representation. With domain support it's necessary to compute the CompletionItem representation specifically for each completion site. See :meth:`~esbonio.lsp.directives.Directives.directive_to_completion_item` for more historical information. For some reason, even though these completion items are constructed in the same manner as the ones for directives using them in VSCode does not feel as nice.... Parameters ---------- name: The name of the role as a user would type into an reStructuredText document. role: The implementation of the role. match: The regular expression match object that represents the line we are providing the autocomplete suggestions for. position: The position in the source code where the autocompletion request was sent from. """ groups = match.groupdict() line = position.line start = position.character - len(groups["role"]) end = position.character insert_text = f":{name}:" item = CompletionItem( name, kind=CompletionItemKind.Function, filter_text=insert_text, detail="role", text_edit=TextEdit( range=Range(Position(line, start), Position(line, end)), new_text=insert_text, ), ) self.logger.debug("Item %s", dump(item)) return item
def char_pos_to_position(buf, position): seekpos = 0 position = position - 1 for lineno, line in enumerate(buf.split('\n')): if seekpos + (len(line) + 1) > position: return Position(lineno, position - seekpos) seekpos += len(line) + 1
def test_completions(): """An example test which does very little""" completion_list = server.completions( fake_server, CompletionParams(TextDocumentIdentifier('file://fake_doc.txt'), Position(0, 2), None)) # test the list is empty for now assert completion_list.items == []
def _find_snippet_insert_position(self) -> Union[Position, Range]: """Returns the position inside the document where command section can be inserted. If the <command> section does not exists in the file, the best aproximate position where the section should be inserted is returned (acording to the IUC best practices tag order). Returns: Position: The position where the command section can be inserted in the document. """ tool = self.tool_document section = tool.find_element(COMMAND) if section: content_range = tool.get_element_content_range(section) if content_range: return content_range.end else: # is self closed <tests/> start = tool.get_position_before(section) end = tool.get_position_after(section) return Range(start, end) else: section = tool.find_element(ENV_VARIABLES) if section: return tool.get_position_before(section) section = tool.find_element(CONFIGFILES) if section: return tool.get_position_before(section) section = tool.find_element(INPUTS) if section: return tool.get_position_before(section) section = tool.find_element(TOOL) if section: return tool.get_element_content_range(section).end return Position()