Skip to content

Commit

Permalink
✨ Support full features of LSP
Browse files Browse the repository at this point in the history
  • Loading branch information
Freed-Wu committed Oct 19, 2023
1 parent fe7eeb6 commit 368a12e
Show file tree
Hide file tree
Showing 7 changed files with 323 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ Features:
- [x] diagnostic: only support `Makefile`
- [x] document hover
- [x] completion
- [ ] go to definition
- [ ] go to references
- [x] go to definitions: only support `Makefile`
- [x] go to references: only support `Makefile`

![diagnostic](https://github.com/Freed-Wu/autotools-language-server/assets/32936898/a1b35e66-7046-42e0-8db8-b636e711764d)

![document hover](https://github.com/SchemaStore/schemastore/assets/32936898/d8a2cdf1-d62b-46f4-87a9-12701ab660a6)
![document hover](https://github.com/Freed-Wu/autotools-language-server/assets/32936898/d553d812-5978-45c4-ae8b-9703021da18a)

![completion](https://github.com/SchemaStore/schemastore/assets/32936898/fa0c523d-cb51-4870-92a4-07d64c624221)

Expand Down
181 changes: 181 additions & 0 deletions src/autotools_language_server/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,184 @@ def filter(self, uni: UNI) -> bool:
and not (text.startswith(".") and text.isupper())
)
return False


# https://github.com/alemuller/tree-sitter-make/issues/22
class DefinitionFinder(RepeatedTargetFinder):
r"""Definitionfinder."""

def __init__(self, node: Node) -> None:
r"""Init.
:param node:
:type node: Node
:rtype: None
"""
super().__init__()
self.name = UNI.node2text(node)
parent = node.parent
if parent is None:
raise TypeError
if parent.type == "arguments":
self.is_define = self.is_function_define
# https://github.com/alemuller/tree-sitter-make/issues/8
self.name = UNI.node2text(node).split(",")[0]
elif parent.type == "variable_reference":
self.is_define = self.is_variable_define
elif parent.type == "prerequisites":
self.is_define = self.is_target_define
else:
raise NotImplementedError

def is_function_define(self, uni: UNI) -> bool:
r"""Is function define.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return (
parent.type == "define_directive"
and uni.get_text() == self.name
and node == parent.children[1]
)

def is_variable_define(self, uni: UNI) -> bool:
r"""Is variable define.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return (
parent.type == "variable_assignment"
and uni.get_text() == self.name
and node == parent.children[0]
)

def is_target_define(self, uni: UNI) -> bool:
r"""Is target define.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return parent.type == "targets" and uni.get_text() == self.name

def __call__(self, uni: UNI) -> bool:
r"""Call.
:param uni:
:type uni: UNI
:rtype: bool
"""
return self.is_define(uni)

@staticmethod
def uni2document(uni: UNI) -> str:
r"""Uni2document.
:param uni:
:type uni: UNI
:rtype: str
"""
node = uni.node
parent = node.parent
if parent is None:
raise TypeError
if parent.type == "targets":
parent = parent.parent
if parent is None:
raise TypeError
return uni.uri + "\n" + UNI.node2text(parent)


class ReferenceFinder(RepeatedTargetFinder):
r"""Referencefinder."""

def __init__(self, node: Node) -> None:
r"""Init.
:param node:
:type node: Node
:rtype: None
"""
super().__init__()
self.name = UNI.node2text(node)
parent = node.parent
if parent is None:
raise TypeError
if parent.type == "define_directive":
self.is_reference = self.is_function_reference
elif parent.type == "variable_assignment":
self.is_reference = self.is_variable_reference
elif parent.type == "prerequisites":
self.is_reference = self.is_target_reference
else:
raise NotImplementedError

def is_function_reference(self, uni: UNI) -> bool:
r"""Is function reference.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return (
parent.type == "arguments"
# https://github.com/alemuller/tree-sitter-make/issues/8
and self.name in UNI.node2text(node).split(",")
)

def is_variable_reference(self, uni: UNI) -> bool:
r"""Is variable reference.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return (
parent.type == "variable_reference" and uni.get_text() == self.name
)

def is_target_reference(self, uni: UNI) -> bool:
r"""Is target reference.
:param uni:
:type uni: UNI
:rtype: bool
"""
node = uni.node
parent = node.parent
if parent is None:
return False
return parent.type == "targets" and uni.get_text() == self.name

def __call__(self, uni: UNI) -> bool:
r"""Call.
:param uni:
:type uni: UNI
:rtype: bool
"""
return self.is_reference(uni)
70 changes: 68 additions & 2 deletions src/autotools_language_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@
"""
import re
from typing import Any
from urllib.parse import unquote, urlparse

from lsprotocol.types import (
INITIALIZE,
TEXT_DOCUMENT_COMPLETION,
TEXT_DOCUMENT_DEFINITION,
TEXT_DOCUMENT_DID_CHANGE,
TEXT_DOCUMENT_DID_OPEN,
TEXT_DOCUMENT_HOVER,
TEXT_DOCUMENT_REFERENCES,
CompletionItem,
CompletionItemKind,
CompletionList,
CompletionParams,
DidChangeTextDocumentParams,
Hover,
InitializeParams,
Location,
MarkupContent,
MarkupKind,
Position,
Expand All @@ -27,9 +29,11 @@
from pygls.server import LanguageServer

from .documents import get_document, get_filetype
from .finders import DefinitionFinder, ReferenceFinder
from .parser import parse
from .tree_sitter_lsp import UNI
from .tree_sitter_lsp.diagnose import get_diagnostics
from .tree_sitter_lsp.finders import PositionFinder, TypeFinder
from .tree_sitter_lsp.finders import PositionFinder
from .utils import DIAGNOSTICS_FINDERS


Expand Down Expand Up @@ -73,6 +77,52 @@ def did_change(params: DidChangeTextDocumentParams) -> None:
)
self.publish_diagnostics(params.text_document.uri, diagnostics)

@self.feature(TEXT_DOCUMENT_DEFINITION)
def definition(params: TextDocumentPositionParams) -> list[Location]:
r"""Get definition.
:param params:
:type params: TextDocumentPositionParams
:rtype: list[Location]
"""
if get_filetype(params.text_document.uri) != "make":
return []
document = self.workspace.get_document(params.text_document.uri)
uni = PositionFinder(params.position).find(
document.uri, self.trees[document.uri]
)
if uni is None:
return []
return [
uni.get_location()
for uni in DefinitionFinder(uni.node).find_all(
document.uri, self.trees[document.uri]
)
]

@self.feature(TEXT_DOCUMENT_REFERENCES)
def references(params: TextDocumentPositionParams) -> list[Location]:
r"""Get references.
:param params:
:type params: TextDocumentPositionParams
:rtype: list[Location]
"""
if get_filetype(params.text_document.uri) != "make":
return []
document = self.workspace.get_document(params.text_document.uri)
uni = PositionFinder(params.position).find(
document.uri, self.trees[document.uri]
)
if uni is None:
return []
return [
uni.get_location()
for uni in ReferenceFinder(uni.node).find_all(
document.uri, self.trees[document.uri]
)
]

@self.feature(TEXT_DOCUMENT_HOVER)
def hover(params: TextDocumentPositionParams) -> Hover | None:
r"""Hover.
Expand All @@ -96,6 +146,22 @@ def hover(params: TextDocumentPositionParams) -> Hover | None:
parent = uni.node.parent
if parent is None:
return None
if parent.type in [
"prerequisites",
"variable_reference",
"function_call",
]:
result = "\n".join(
DefinitionFinder.uni2document(uni)
for uni in DefinitionFinder(uni.node).find_all(
document.uri, self.trees[document.uri]
)
)
if result != "":
return Hover(
MarkupContent(MarkupKind.PlainText, result),
_range,
)
if parent.type not in [
"variable_reference",
"function_call",
Expand Down
Loading

0 comments on commit 368a12e

Please sign in to comment.