Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/malls/lsp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2679,3 +2679,11 @@ class CompletionContext:
trigger_character: str | None = None

model_config = base_config


class HoverParams(TextDocumentPositionParams, WorkDoneProgressParams):
"""
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#hoverParams
"""

model_config = base_config
62 changes: 61 additions & 1 deletion src/malls/lsp/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

import tree_sitter_mal as ts_mal
from pylsp_jsonrpc.endpoint import Endpoint
from tree_sitter import Language, Parser
from tree_sitter import Language, Node, Parser
from uritools import urisplit

from ..ts.utils import (
INCLUDED_FILES_QUERY,
find_comments_function,
find_meta_comment_function,
find_symbols_in_current_scope,
lsp_to_tree_sitter_position,
query_for_error_nodes,
Expand Down Expand Up @@ -124,3 +126,61 @@ def get_completion_list(doc: Document, pos: Position) -> list:
]

return completion_list


def build_markdown_meta_comments(meta_comments: list[Node]):
markdown = ""
for meta_comment in meta_comments:
meta_id = meta_comment.child_by_field_name("id").text.decode()
meta_info = meta_comment.child_by_field_name("info").text.decode()
markdown += f"- **{meta_id}**: {meta_info}\n"
return markdown


def sanitize_comment(comment: str):
sanitized_comment = ""
for line in comment:
line = line.lstrip("/*")
line = line.lstrip("*")
line = line.lstrip("//")
line = line.rstrip("*/")
sanitized_comment += line if line != "\n" else ""
return sanitized_comment


def build_markdown_comments(comments: list[Node]):
markdown = ""
for comment in comments:
markdown += f"{sanitize_comment(comment.text.decode())}\n"
return markdown


def get_hover_info(doc: Document, pos: Position, storage: dict) -> str:
# convert LSP position to TS point
point = lsp_to_tree_sitter_position(doc.text, pos)

# get the symbol in that position
cursor = doc.tree.walk()
while cursor.goto_first_child_for_point(point) is not None:
continue

node = cursor.node
if node.type != "identifier":
return "" # we can only find comments for identifiers

# TODO write better hover info

# get meta comments
meta_title = "## **Meta comments**\n"
meta_comments = find_meta_comment_function(node, node.text, doc.uri, storage)
meta_markdown = build_markdown_meta_comments(meta_comments)

# get regular comments
comments_title = "## **Comments**\n"
comments = find_comments_function(node, node.text, doc.uri, storage)
comments_markdown = build_markdown_comments(comments)

if meta_markdown and comments_markdown:
return meta_title + meta_markdown + "---\n" + comments_title + comments_markdown

return meta_title + meta_markdown if meta_markdown else comments_title + comments_markdown
24 changes: 23 additions & 1 deletion src/malls/mal_lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

from .lsp import enums, models
from .lsp.classes import Document
from .lsp.enums import ErrorCodes, PositionEncodingKind, TraceValue
from .lsp.enums import ErrorCodes, MarkupKind, PositionEncodingKind, TraceValue
from .lsp.fsm import LifecycleFSM
from .lsp.utils import (
get_completion_list,
get_hover_info,
path_to_uri,
recursive_parsing,
send_diagnostics,
Expand Down Expand Up @@ -108,6 +109,7 @@ def capabilities(self, client_capabilities: models.ClientCapabilities | None = N
},
"definitionProvider": True,
"completionProvider": {},
"hoverProvider": True,
}

log.debug("Server capabilities: %s", capabilities)
Expand Down Expand Up @@ -417,3 +419,23 @@ def m_text_document__completion(self, **params: dict | None) -> None:
# `If a CompletionItem[] is provided it is interpreted to
# be complete. So it is the same as { isIncomplete: false, items }`
return completion_list

def m_text_document__hover(self, **params: dict | None) -> None | dict:
# validate parameters
hover = models.HoverParams(**params) if params else None
if hover is None:
return None # parameters are wrong

# obtain relevant parameters
doc_uri = uri_to_path(hover.text_document.uri)
position = hover.position

# get completion list
hover_content = get_hover_info(self.__files[doc_uri], position, self.__files)

return {
"contents": {
"kind": MarkupKind.Markdown,
"value": hover_content,
}
}
20 changes: 10 additions & 10 deletions src/malls/ts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,7 +1162,7 @@ def find_meta_comment_category_declaration(node: Node) -> list:
"""
meta_info = []
for children in node.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand All @@ -1174,7 +1174,7 @@ def find_meta_comment_asset_declaration(node: Node) -> list:
"""
meta_info = []
for children in node.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand All @@ -1186,7 +1186,7 @@ def find_meta_comment_attack_step(node: Node) -> list:
"""
meta_info = []
for children in node.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand All @@ -1209,7 +1209,7 @@ def find_meta_comment_asset_variable(

meta_info = []
for children in asset.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand Down Expand Up @@ -1252,7 +1252,7 @@ def find_meta_comment_asset_variable_subsitution(
# otherwise get the meta corresponding to that asset
meta_info = []
for children in asset.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand All @@ -1275,7 +1275,7 @@ def find_meta_comment_asset_expr(
# otherwise get the meta corresponding to that asset
meta_info = []
for children in asset.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand All @@ -1299,7 +1299,7 @@ def find_meta_comment_association(
# otherwise get the meta corresponding to that asset
meta_info = []
for children in result_node.children_by_field_name("meta"):
meta_info.append(children.child_by_field_name("info").text.strip(b'"'))
meta_info.append(children)

return meta_info

Expand Down Expand Up @@ -1380,7 +1380,7 @@ def find_comments_function(
key=lambda item: item.start_point.row,
)

comments = [sorted_comments[0].text]
comments = [sorted_comments[0]]
previous_row = sorted_comments[0].end_point.row

for comment_node in sorted_comments[1:]:
Expand All @@ -1389,11 +1389,11 @@ def find_comments_function(
# if the comment is in a consecutive row,
# we keep it
if current_row == previous_row + 1:
comments.append(comment_node.text)
comments.append(comment_node)
previous_row = current_row # update row
else:
# otherwise, restart the count
comments = [comment_node.text]
comments = [comment_node]
previous_row = comment_node.end_point.row

return comments if previous_row == start_row - 1 else []
1 change: 1 addition & 0 deletions tests/fixtures/lsp/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def initalize_response(
"textDocumentSync": {"openClose": True, "change": 1},
"definitionProvider": True,
"completionProvider": {},
"hoverProvider": True,
},
# TODO: Replace with values from an actual server instance (e.g. via instance.server_info()) # noqa: E501
"serverInfo": {"name": "mal-ls"},
Expand Down
77 changes: 77 additions & 0 deletions tests/fixtures/mal/hover_document.mal
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#id: "org.mal-lang.testAnalyzer"
#version:"0.0.0"

// should be ignored

// category comment
category Example
developer info: "dev cat"
modeler info: "mod cat"
{
//should be ignored

// asset1 comment
abstract asset Asset1
developer info: "dev asset"
modeler info: "mod asset"
{
let var = c.b
// attack_step comment
| compromise
developer info: "dev attack_step"
modeler info: "mod attack_step"
-> var().destroy

// attack_step comment2
| attack
-> c.b.h.attack4,
c.b.h[Asset5].attack5
}

// should be ignored
abstract asset
// asset3 comment
Asset3
developer info: "dev asset3"
modeler info: "mod asset3"
{

}

// asset2 comment
asset Asset2 extends Asset3
{
| destroy
}

asset Asset4
developer info: "dev asset4"
modeler info: "mod asset4"
{
& attack4
}

/*
* MULTI-LINE
*/
asset Asset5 extends Asset4
developer info: "dev asset5"
modeler info: "mod asset5"
{
// attack_step comment3
& attack5
developer info: "dev attack_step_5"
modeler info: "mod attack_step_5"
// should be ignored
}

}
associations
{
// association1 comment
Asset1 [a] * <-- L --> * [c] Asset2 developer info: "some info"
// association2 comment
Asset2 [d] 1 <-- M --> 1 [e] Asset2
Asset3 [b] 1 <-- N --> 1 [f] Asset2
Asset3 [g] 1 <-- O --> 1 [h] Asset4
}
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_asset_1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev asset"
- **modeler**: "mod asset"
---
## **Comments**
asset1 comment
2 changes: 2 additions & 0 deletions tests/fixtures/markdown/comments_in_asset_2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## **Comments**
asset2 comment
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_asset_3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev asset3"
- **modeler**: "mod asset3"
---
## **Comments**
asset3 comment
3 changes: 3 additions & 0 deletions tests/fixtures/markdown/comments_in_asset_4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## **Meta comments**
- **developer**: "dev asset4"
- **modeler**: "mod asset4"
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_asset_5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev asset5"
- **modeler**: "mod asset5"
---
## **Comments**
MULTI-LINE
5 changes: 5 additions & 0 deletions tests/fixtures/markdown/comments_in_association.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## **Meta comments**
- **developer**: "some info"
---
## **Comments**
association1 comment
2 changes: 2 additions & 0 deletions tests/fixtures/markdown/comments_in_association2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## **Comments**
association2 comment
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_attack_step1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev attack_step"
- **modeler**: "mod attack_step"
---
## **Comments**
attack_step comment
2 changes: 2 additions & 0 deletions tests/fixtures/markdown/comments_in_attack_step2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## **Comments**
attack_step comment2
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_attack_step3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev attack_step_5"
- **modeler**: "mod attack_step_5"
---
## **Comments**
attack_step comment3
6 changes: 6 additions & 0 deletions tests/fixtures/markdown/comments_in_category.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
## **Meta comments**
- **developer**: "dev cat"
- **modeler**: "mod cat"
---
## **Comments**
category comment
Loading
Loading