Example #1
0
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
Example #2
0
    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
Example #3
0
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
Example #4
0
 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}"/>',
     )
Example #5
0
 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')
                           ])
     ]
Example #6
0
 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,
     )
Example #7
0
 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
Example #8
0
 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,
     )
Example #9
0
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)
        ]
Example #11
0
    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
Example #12
0
 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,
     )
Example #13
0
 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
Example #14
0
    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
Example #15
0
 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
Example #16
0
    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
Example #17
0
    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