Skip to content

Commit

Permalink
Merge pull request #247 from galaxyproject/add_symbols_provider
Browse files Browse the repository at this point in the history
Add symbols provider
  • Loading branch information
davelopez committed Oct 14, 2023
2 parents de74190 + 3ee9c85 commit 46398a2
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
13 changes: 13 additions & 0 deletions server/galaxyls/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
DocumentFormattingParams,
DocumentLink,
DocumentLinkParams,
DocumentSymbol,
DocumentSymbolParams,
Hover,
INITIALIZED,
InitializeParams,
Expand All @@ -34,6 +36,7 @@
TEXT_DOCUMENT_DID_OPEN,
TEXT_DOCUMENT_DID_SAVE,
TEXT_DOCUMENT_DOCUMENT_LINK,
TEXT_DOCUMENT_DOCUMENT_SYMBOL,
TEXT_DOCUMENT_FORMATTING,
TEXT_DOCUMENT_HOVER,
TextDocumentIdentifier,
Expand Down Expand Up @@ -198,6 +201,16 @@ def process_code_actions(server: GalaxyToolsLanguageServer, params: CodeActionPa
return server.service.get_available_refactoring_actions(xml_document, params)


@language_server.feature(TEXT_DOCUMENT_DOCUMENT_SYMBOL)
def document_symbol(server: GalaxyToolsLanguageServer, params: DocumentSymbolParams) -> Optional[List[DocumentSymbol]]:
"""Returns a list of symbols defined in the document."""
document = _get_valid_document(server, params.text_document.uri)
if document:
xml_document = _get_xml_document(document)
return server.service.symbols_provider.get_document_symbols(xml_document)
return None


@language_server.command(Commands.AUTO_CLOSE_TAGS)
def auto_close_tag(server: GalaxyToolsLanguageServer, parameters: CommandParameters) -> Optional[AutoCloseTagResult]:
"""Responds to a close tag request to close the currently opened node."""
Expand Down
2 changes: 2 additions & 0 deletions server/galaxyls/services/language.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from galaxyls.services.definitions import DocumentDefinitionsProvider
from galaxyls.services.links import DocumentLinksProvider
from galaxyls.services.macros import MacroExpanderService
from galaxyls.services.symbols import DocumentSymbolsProvider
from galaxyls.services.tools.common import (
TestsDiscoveryService,
ToolParamAttributeSorter,
Expand Down Expand Up @@ -75,6 +76,7 @@ def __init__(self) -> None:
self.linter = GalaxyToolLinter()
self.definitions_provider: Optional[DocumentDefinitionsProvider] = None
self.link_provider = DocumentLinksProvider()
self.symbols_provider = DocumentSymbolsProvider()

def set_workspace(self, workspace: Workspace) -> None:
macro_definitions_provider = MacroDefinitionsProvider(workspace)
Expand Down
73 changes: 73 additions & 0 deletions server/galaxyls/services/symbols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from typing import (
List,
Optional,
)

from lsprotocol.types import (
DocumentSymbol,
SymbolKind,
)

from galaxyls.services.xml.document import XmlDocument
from galaxyls.services.xml.nodes import (
XmlAttribute,
XmlElement,
XmlSyntaxNode,
)
from galaxyls.services.xml.utils import convert_document_offsets_to_range


class DocumentSymbolsProvider:
"""Provides symbols defined in the tool document."""

def get_document_symbols(self, xml_document: XmlDocument) -> List[DocumentSymbol]:
"""Gets all symbols defined in the tool document in a hierarchical structure."""
if xml_document.root is None:
return []
return [self._get_element_symbol_definition(xml_document, xml_document.root)]

def _get_element_children_symbols(self, element: XmlElement, xml_document: XmlDocument) -> List[DocumentSymbol]:
result: List[DocumentSymbol] = []
for child in element.children:
if isinstance(child, XmlAttribute):
result.append(self._get_attribute_symbol_definition(xml_document, child))
if isinstance(child, XmlElement):
result.append(self._get_element_symbol_definition(xml_document, child))
return result

def _get_element_symbol_definition(self, xml_document: XmlDocument, element: XmlElement) -> DocumentSymbol:
element_range = convert_document_offsets_to_range(xml_document.document, element.start, element.end)
return DocumentSymbol(
name=self._get_node_name(element),
kind=SymbolKind.Field,
detail=self._get_element_symbol_detail(element, xml_document),
range=element_range,
selection_range=element_range,
children=self._get_element_children_symbols(element, xml_document),
)

def _get_attribute_symbol_definition(self, xml_document: XmlDocument, attribute: XmlAttribute) -> DocumentSymbol:
attribute_range = convert_document_offsets_to_range(xml_document.document, attribute.start, attribute.end)
return DocumentSymbol(
name=self._get_node_name(attribute),
kind=SymbolKind.Property,
detail=attribute.value.unquoted if attribute.value else None,
range=attribute_range,
selection_range=attribute_range,
)

def _get_node_name(self, node: XmlSyntaxNode) -> str:
return node.name or ""

def _get_element_symbol_detail(self, element: XmlElement, xml_document: XmlDocument) -> Optional[str]:
if element.name in ["option", "when", "add", "remove"]:
detail = element.get_attribute_value("value")
elif element.name in ["citation", "validator"]:
detail = element.get_attribute_value("type")
elif element.name in ["requirement", "import"]:
detail = element.get_content(xml_document.document.source)
elif element.name in ["expand"]:
detail = element.get_attribute_value("macro")
else:
detail = element.get_attribute_value("id") or element.get_attribute_value("name")
return detail
44 changes: 44 additions & 0 deletions server/galaxyls/tests/unit/test_symbols.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from galaxyls.services.symbols import DocumentSymbolsProvider
from galaxyls.tests.unit.utils import TestUtils

FIELD_SYMBOL_KIND = 8 # SymbolKind.Field
PROPERTY_SYMBOL_KIND = 7 # SymbolKind.Property


def test_get_document_symbols():
xml_document = TestUtils.from_source_to_xml_document("<tool></tool>")
provider = DocumentSymbolsProvider()
symbols = provider.get_document_symbols(xml_document)
assert len(symbols) == 1
assert symbols[0].name == "tool"
assert symbols[0].kind == FIELD_SYMBOL_KIND
assert len(symbols[0].children) == 0


def test_get_element_children_symbols():
xml_document = TestUtils.from_source_to_xml_document("<tool><command></command></tool>")
provider = DocumentSymbolsProvider()
symbols = provider.get_document_symbols(xml_document)
element_symbol = symbols[0]
children_symbols = element_symbol.children
assert len(children_symbols) == 1
assert children_symbols[0].name == "command"
assert children_symbols[0].kind == FIELD_SYMBOL_KIND


def test_get_attribute_symbol_definition():
xml_document = TestUtils.from_source_to_xml_document("<tool><command executable='true'></command></tool>")
provider = DocumentSymbolsProvider()
symbols = provider.get_document_symbols(xml_document)
attribute_symbol = symbols[0].children[0].children[0]
assert attribute_symbol.name == "executable"
assert attribute_symbol.kind == PROPERTY_SYMBOL_KIND


def test_get_element_symbol_detail():
xml_document = TestUtils.from_source_to_xml_document('<tool id="TEST"></tool>')
provider = DocumentSymbolsProvider()
symbols = provider.get_document_symbols(xml_document)
assert len(symbols) == 1
element_symbol = symbols[0]
assert element_symbol.detail == "TEST"

0 comments on commit 46398a2

Please sign in to comment.