async def lsp_completion( ls: LanguageServer, params: CompletionParams, ) -> Optional[List[CompletionItem]]: await asyncio.sleep(DEBOUNCE_DELAY) items: List[CompletionItem] = [] ast = await buildout.parse(ls, params.text_document.uri, True) for line in ast.lines: pos = params.position (var_name, var_type, value) = line ci = CompletionItem(label=var_name, text_edit=TextEdit( range=Range( start=Position(line=pos.line, character=pos.character), end=Position(line=pos.line, character=pos.character + len(var_name))), new_text=var_name, ), kind=CompletionItemKind.Variable, documentation=MarkupContent( kind=MarkupKind.Markdown, value=f"{var_name} : {var_type} = {value}", )) items.append(ci) return items
def _calculate_external_changes_for_macro( self, tool: GalaxyToolXmlDocument, macro_file_definition: ImportedMacrosFile, macro: MacroData, params: CodeActionParams, ) -> Dict[str, List[TextEdit]]: """Returns a dictionary with the document uri and the collection of TextEdit operations for that particular document. The edits will add the macro definition to the given imported macros file and replace the refactored macro with the corresponding <expand> element in the tool wrapper.""" macros_xml_doc = macro_file_definition.document if macro_file_definition.file_uri is None or macros_xml_doc is None or macros_xml_doc.root is None: return {} macros_root = macros_xml_doc.root insert_position = macros_xml_doc.get_position_after_last_child( macros_root) insert_range = Range(start=insert_position, end=insert_position) macro_xml = f'<xml name="{macro.name}">\n{macro.content}\n</xml>' final_macro_xml = self._adapt_format(macros_xml_doc, insert_range, macro_xml) external_edit = TextEdit( range=insert_range, new_text=final_macro_xml, ) changes = { params.text_document.uri: [ self._edit_replace_range_with_macro_expand( tool, macro, params.range) ], macro_file_definition.file_uri: [external_edit], } return changes
def lsp_text_edits( document: Document, 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. Additionally, makes sure returned code is syntactically valid Python. """ new_code = changed_file.get_new_code() if not is_valid_python(new_code): return [] old_code = document.source position_lookup = PositionLookup(old_code) text_edits = [] for opcode in get_opcodes(old_code, new_code): if opcode.op in _OPCODES_CHANGE: start = position_lookup.get(opcode.old_start) end = position_lookup.get(opcode.old_end) new_text = new_code[opcode.new_start : opcode.new_end] text_edits.append( TextEdit( range=Range(start=start, end=end), new_text=new_text, ) ) return text_edits
def _edit_replace_range_with_macro_expand(self, tool: GalaxyToolXmlDocument, macro: MacroData, range: Range) -> TextEdit: """Returns the TextEdit operation that will replace the refactored macro with the corresponding <expand> element.""" indentation = tool.xml_document.get_line_indentation(range.start.line) return TextEdit( range=self._get_range_from_line_start(range), new_text=f'{indentation}<expand macro="{macro.name}"/>', )
def f(params: ColorPresentationParams) -> List[ColorPresentation]: return [ ColorPresentation(label='label1', text_edit=TextEdit(range=Range( start=Position(line=0, character=0), end=Position(line=1, character=1), ), new_text='te'), additional_text_edits=[ TextEdit(range=Range( start=Position(line=1, character=1), end=Position(line=2, character=2), ), new_text='ate1'), TextEdit(range=Range( start=Position(line=2, character=2), end=Position(line=3, character=3), ), new_text='ate2') ]) ]
def _edit_create_with_macros_section(self, tool: GalaxyToolXmlDocument, macro: MacroData) -> TextEdit: """Returns the TextEdit operation that will add a local <macros> section inside the tool wrapper along with the macro provided.""" insert_position = self._find_macros_insert_position(tool) insert_range = Range(start=insert_position, end=insert_position) macro_xml = f'<macros>\n<xml name="{macro.name}">\n{macro.content}\n</xml>\n</macros>' final_macro_xml = self._adapt_format(tool.xml_document, insert_range, macro_xml) return TextEdit( range=insert_range, new_text=final_macro_xml, )
def f(params: DocumentFormattingParams) -> Optional[List[TextEdit]]: if params.text_document.uri == 'file://return.list': return [ TextEdit( range=Range( start=Position(line=0, character=0), end=Position(line=1, character=1), ), new_text='text', ) ] else: return None
def _edit_add_macro_to_macros_section(self, tool: GalaxyToolXmlDocument, macros_element: XmlElement, macro: MacroData) -> TextEdit: """Returns the TextEdit operation that will add a macro definition to the <macros> section of a tool wrapper.""" insert_position = tool.get_position_after_last_child(macros_element) insert_range = Range(start=insert_position, end=insert_position) macro_xml = f'<xml name="{macro.name}">\n{macro.content}\n</xml>' final_macro_xml = self._adapt_format(tool.xml_document, insert_range, macro_xml) return TextEdit( range=insert_range, new_text=final_macro_xml, )
def path_to_completion_item(context: CompletionContext, path: pathlib.Path) -> CompletionItem: """Create the ``CompletionItem`` for the given path. In the case where there are multiple filepath components, this function needs to provide an appropriate ``TextEdit`` so that the most recent entry in the path can be easily edited - without clobbering the existing path. Also bear in mind that this function must play nice with both role target and directive argument completions. """ new_text = f"{path.name}" kind = CompletionItemKind.Folder if path.is_dir( ) else CompletionItemKind.File # If we can't find the '/' we may as well not bother with a `TextEdit` and let the # `Roles` feature provide the default handling. start = _find_start_char(context) if start == -1: insert_text = new_text filter_text = None text_edit = None else: start += 1 _, end = context.match.span() prefix = context.match.group(0)[start:] insert_text = None filter_text = ( f"{prefix}{new_text}" # Needed so VSCode will actually show the results. ) text_edit = TextEdit( range=Range( start=Position(line=context.position.line, character=start), end=Position(line=context.position.line, character=end), ), new_text=new_text, ) return CompletionItem( label=new_text, kind=kind, insert_text=insert_text, filter_text=filter_text, text_edit=text_edit, )
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_content(content, params.options.tab_size) lines = content.count("\n") start = Position(line=0, character=0) end = Position(line=lines + 1, character=0) return [ TextEdit(range=Range(start=start, end=end), new_text=formatted_result) ]
def complete_options(self, context: CompletionContext) -> List[CompletionItem]: surrounding_directive = self.get_surrounding_directive(context) if not surrounding_directive: return [] domain = "" if surrounding_directive.group("domain"): domain = f'{surrounding_directive.group("domain")}:' name = f"{domain}{surrounding_directive.group('name')}" directive = self.rst.get_directives().get(name, None) if not directive: return [] items = [] match = context.match groups = match.groupdict() option = groups["option"] start = match.span()[0] + match.group(0).find(option) end = start + len(option) range_ = Range( start=Position(line=context.position.line, character=start), end=Position(line=context.position.line, character=end), ) for option in self.rst.get_directive_options(name): insert_text = f":{option}:" items.append( CompletionItem( label=option, detail= f"{directive.__module__}.{directive.__name__}:{option}", kind=CompletionItemKind.Field, filter_text=insert_text, text_edit=TextEdit(range=range_, new_text=insert_text), data={ "completion_type": "directive_option", "for_directive": name }, )) return items
def _edit_create_import_macros_section(self, tool: GalaxyToolXmlDocument, macros_file_name: str) -> TextEdit: """Returns the TextEdit operation that will add a macros file <import> definition to the existing <macros> section of a tool wrapper or also create the <macros> section if it doesn't exists.""" macros_element = tool.find_element(MACROS) if macros_element: insert_position = tool.get_position_before_first_child( macros_element) macro_xml = f"<import>{macros_file_name}</import>" else: insert_position = self._find_macros_insert_position(tool) macro_xml = f"<macros>\n<import>{macros_file_name}</import>\n</macros>" insert_range = Range(start=insert_position, end=insert_position) final_macro_xml = self._adapt_format(tool.xml_document, insert_range, macro_xml) return TextEdit( range=insert_range, new_text=final_macro_xml, )
def _calculate_external_changes_for_macro_in_new_file( self, tool: GalaxyToolXmlDocument, new_file_name: str, macro: MacroData, params: CodeActionParams ) -> List[Union[CreateFile, TextDocumentEdit]]: """Returns a list of workspace document changes that will create a new macros.xml file with the given macro definition inside and also import the newly created file in the tool wrapper.""" base_path = Path(urlparse(tool.xml_document.document.uri).path).parent new_file_uri = (base_path / new_file_name).as_uri() xml_content = f'<macros>\n<xml name="{macro.name}">\n{macro.content}\n</xml>\n</macros>' final_xml_content = self.format_service.format_content(xml_content) new_doc_insert_position = Position(line=0, character=0) tool_document = self.workspace.get_document(params.text_document.uri) changes: List[Union[CreateFile, TextDocumentEdit]] = [ CreateFile(uri=new_file_uri, kind=ResourceOperationKind.Create), TextDocumentEdit( text_document=VersionedTextDocumentIdentifier( uri=new_file_uri, version=0, ), edits=[ TextEdit( range=Range(start=new_doc_insert_position, end=new_doc_insert_position), new_text=final_xml_content, ), ], ), TextDocumentEdit( text_document=VersionedTextDocumentIdentifier( uri=tool_document.uri, version=tool_document.version, ), edits=[ self._edit_create_import_macros_section( tool, DEFAULT_MACROS_FILENAME), self._edit_replace_range_with_macro_expand( tool, macro, params.range), ], ), ] return changes
def complete_roles(self, context: CompletionContext) -> List[CompletionItem]: match = context.match groups = match.groupdict() domain = groups["domain"] or "" items = [] # Insert text starting from the starting ':' character of the role. start = match.span()[0] + match.group(0).find(":") end = start + len(groups["role"]) range_ = Range( start=Position(line=context.position.line, character=start), end=Position(line=context.position.line, character=end), ) for name, role in self.rst.get_roles().items(): if not name.startswith(domain): continue try: dotted_name = f"{role.__module__}.{role.__name__}" except AttributeError: dotted_name = f"{role.__module__}.{role.__class__.__name__}" insert_text = f":{name}:" item = CompletionItem( label=name, kind=CompletionItemKind.Function, detail=f"{dotted_name}", filter_text=insert_text, text_edit=TextEdit(range=range_, new_text=insert_text), data={"completion_type": "role"}, ) items.append(item) return items
def f(params: RenameParams) -> Optional[WorkspaceEdit]: if params.text_document.uri == 'file://return.workspace_edit': return WorkspaceEdit( changes={ 'uri1': [ TextEdit( range=Range( start=Position(line=0, character=0), end=Position(line=1, character=1), ), new_text='text1', ), TextEdit( range=Range( start=Position(line=1, character=1), end=Position(line=2, character=2), ), new_text='text2', ), ], }, document_changes=[ TextDocumentEdit( text_document= OptionalVersionedTextDocumentIdentifier( uri='uri', version=3, ), edits=[ TextEdit( range=Range( start=Position(line=2, character=2), end=Position(line=3, character=3), ), new_text='text3', ), ]), CreateFile( kind=ResourceOperationKind.Create, uri='create file', options=CreateFileOptions( overwrite=True, ignore_if_exists=True, ), ), RenameFile(kind=ResourceOperationKind.Rename, old_uri='rename old uri', new_uri='rename new uri', options=RenameFileOptions( overwrite=True, ignore_if_exists=True, )), DeleteFile( kind=ResourceOperationKind.Delete, uri='delete file', options=DeleteFileOptions( recursive=True, ignore_if_exists=True, ), ), ]) else: return None
def complete_directives( self, context: CompletionContext) -> List[CompletionItem]: self.logger.debug("Completing directives") items = [] match = context.match groups = match.groupdict() domain = "" if groups["domain"]: domain = f'{groups["domain"]}:' # Calculate the range of text the CompletionItems should edit. # If there is an existing argument to the directive, we should leave it untouched # otherwise, edit the whole line to insert any required arguments. start = match.span()[0] + match.group(0).find(".") include_argument = context.snippet_support end = match.span()[1] if groups["argument"]: include_argument = False end = match.span()[0] + match.group(0).find("::") + 2 range_ = Range( start=Position(line=context.position.line, character=start), end=Position(line=context.position.line, character=end), ) for name, directive in self.rst.get_directives().items(): if not name.startswith(domain): continue # TODO: Give better names to arguments based on what they represent. if include_argument: insert_format = InsertTextFormat.Snippet args = " " + " ".join( "${{{0}:arg{0}}}".format(i) for i in range(1, directive.required_arguments + 1)) else: args = "" insert_format = InsertTextFormat.PlainText try: dotted_name = f"{directive.__module__}.{directive.__name__}" except AttributeError: dotted_name = f"{directive.__module__}.{directive.__class__.__name__}" insert_text = f".. {name}::{args}" items.append( CompletionItem( label=name, kind=CompletionItemKind.Class, detail=dotted_name, filter_text=insert_text, text_edit=TextEdit(range=range_, new_text=insert_text), insert_text_format=insert_format, data={"completion_type": "directive"}, )) return items
def complete_targets(self, context: CompletionContext) -> List[CompletionItem]: """Generate the list of role target completion suggestions.""" groups = context.match.groupdict() # Handle the default role case. if "role" not in groups: domain, name = self.rst.get_default_role() if not name: return [] else: name = groups["name"] domain = groups["domain"] domain = domain or "" name = name or "" # Only generate suggestions for "aliased" targets if the request comes from # within the <> chars. if groups["alias"]: text = context.match.group(0) start = context.match.span()[0] + text.find(groups["alias"]) end = start + len(groups["alias"]) if start <= context.position.character <= end: return [] targets = [] startchar = "<" if "<" in groups["target"] else "`" endchars = ">`" if "<" in groups["target"] else "`" start, end = context.match.span() start += context.match.group(0).index(startchar) + 1 range_ = Range( start=Position(line=context.position.line, character=start), end=Position(line=context.position.line, character=end), ) prefix = context.match.group(0)[start:] modifier = groups["modifier"] or "" for provide in self._target_completion_providers: candidates = provide.complete_targets(context, name, domain) or [] for candidate in candidates: # Don't interfere with items that already carry a `text_edit`, allowing # some providers (like filepaths) to do something special. if not candidate.text_edit: new_text = candidate.insert_text or candidate.label # This is rather annoying, but `filter_text` needs to start with # the text we are going to replace, otherwise VSCode won't show our # suggestions! candidate.filter_text = f"{prefix}{new_text}" candidate.text_edit = TextEdit( range=range_, new_text=f"{modifier}{new_text}") candidate.insert_text = None if not candidate.text_edit.new_text.endswith(endchars): candidate.text_edit.new_text += endchars targets.append(candidate) return targets