Skip to content

Commit

Permalink
Merge pull request #220 from gnikit/gnikit/issue219
Browse files Browse the repository at this point in the history
feat: Change autocomplete to use MarkupContent
  • Loading branch information
gnikit authored Nov 9, 2022
2 parents a98765a + 4462a76 commit 513391e
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 39 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@

### Changed

- Changed the completion signature to include the full Markdown documentation
for the completion item.
([#219](https://github.com/gnikit/fortls/issues/219))
- Changed hover messages and signature help to use Markdown
([#45](https://github.com/gnikit/fortls/issues/45))

Expand Down
10 changes: 4 additions & 6 deletions fortls/helper_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,17 +582,15 @@ def get_var_stack(line: str) -> list[str]:
return None


def fortran_md(code: str, docs: str | None, highlight: bool, langid: str = "fortran90"):
def fortran_md(code: str, docs: str | None, langid: str = "fortran90"):
"""Convert Fortran code to markdown
Parameters
----------
code : str
Fortran code
docs : str | None
Documentation string, only makes sense if ``highlight`` is ``True``
highlight : bool
Whether to highlight the code
Documentation string
langid : str, optional
Language ID, by default 'fortran90'
Expand All @@ -601,8 +599,8 @@ def fortran_md(code: str, docs: str | None, highlight: bool, langid: str = "fort
str
Markdown string
"""
msg = code
if highlight:
msg = ""
if code:
msg = f"```{langid}\n{code}\n```"
# Add documentation
if docs: # if docs is not None or ""
Expand Down
9 changes: 7 additions & 2 deletions fortls/intrinsics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import os

from fortls.helper_functions import map_keywords
from fortls.helper_functions import fortran_md, map_keywords
from fortls.objects import (
FortranAST,
FortranObj,
Expand Down Expand Up @@ -71,7 +71,12 @@ def get_signature(self):
return call_sig, self.doc_str, arg_sigs

def get_hover(self, long=False):
return self.doc_str, None, False
return None, self.doc_str

def get_hover_md(self, long=False):
msg, docs = self.get_hover(long)
msg = msg if msg else ""
return fortran_md(msg, docs)

def is_callable(self):
if self.type == 2:
Expand Down
25 changes: 14 additions & 11 deletions fortls/langserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,15 @@ def build_comp(
comp_obj["kind"] = map_types(candidate.get_type())
if is_member and (comp_obj["kind"] == 3):
comp_obj["kind"] = 2
# Detail label shown above documentation, also shown when
# documentation is collapsed i.e. short form completions
comp_obj["detail"] = candidate.get_desc()
if call_sig is not None:
comp_obj["detail"] += " " + call_sig
# TODO: doc_str should probably be appended, see LSP standard
hover_msg, doc_str, _ = candidate.get_hover()
if hover_msg is not None:
# Use the full markdown documentation
hover_msg = candidate.get_hover_md(long=True)
if hover_msg:
hover_msg = {"kind": "markdown", "value": hover_msg}
comp_obj["documentation"] = hover_msg
return comp_obj

Expand Down Expand Up @@ -1037,10 +1040,10 @@ def serve_definition(self, request: dict):
return None

def serve_hover(self, request: dict):
def create_hover(string: str, docs: str | None, fortran: bool):
def create_hover(string: str, docs: str | None):
# This does not account for Fixed Form Fortran, but it should be
# okay for 99% of cases
return fortran_md(string, docs, fortran, self.hover_language)
return fortran_md(string, docs, self.hover_language)

# Get parameters from request
params: dict = request["params"]
Expand All @@ -1062,24 +1065,24 @@ def create_hover(string: str, docs: str | None, fortran: bool):
hover_array.append(var_obj.get_hover_md(long=True))
elif var_type == INTERFACE_TYPE_ID:
for member in var_obj.mems:
hover_str, docs, highlight = member.get_hover(long=True)
hover_str, docs = member.get_hover(long=True)
if hover_str is not None:
hover_array.append(create_hover(hover_str, docs, highlight))
hover_array.append(create_hover(hover_str, docs))
elif var_type == VAR_TYPE_ID:
# Unless we have a Fortran literal include the desc in the hover msg
# See get_definition for an explanation about this default name
if not var_obj.desc.startswith(FORTRAN_LITERAL):
hover_array.append(var_obj.get_hover_md(long=True))
# Hover for Literal variables
elif var_obj.desc.endswith("REAL"):
hover_array.append(create_hover("REAL", None, True))
hover_array.append(create_hover("REAL", None))
elif var_obj.desc.endswith("INTEGER"):
hover_array.append(create_hover("INTEGER", None, True))
hover_array.append(create_hover("INTEGER", None))
elif var_obj.desc.endswith("LOGICAL"):
hover_array.append(create_hover("LOGICAL", None, True))
hover_array.append(create_hover("LOGICAL", None))
elif var_obj.desc.endswith("STRING"):
hover_str = f"CHARACTER(LEN={len(var_obj.name)-2})"
hover_array.append(create_hover(hover_str, None, True))
hover_array.append(create_hover(hover_str, None))

if len(hover_array) > 0:
return {"contents": {"kind": "markdown", "value": "\n".join(hover_array)}}
Expand Down
36 changes: 16 additions & 20 deletions fortls/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,11 +414,12 @@ def get_placeholders(arg_list: list[str]):
def get_documentation(self):
return self.doc_str

def get_hover(self, long=False, drop_arg=-1) -> tuple[str | None, str | None, bool]:
return None, None, False
def get_hover(self, long=False, drop_arg=-1) -> tuple[str | None, str | None]:
return None, None

def get_hover_md(self, long=False, drop_arg=-1) -> str:
return ""
msg, docs = self.get_hover(long, drop_arg)
return fortran_md(msg, docs)

def get_signature(self, drop_arg=-1):
return None, None, None
Expand Down Expand Up @@ -934,7 +935,7 @@ def get_hover(self, long=False, drop_arg=-1):
keyword_list.append(f"{self.get_desc()} ")
hover_array = [" ".join(keyword_list) + sub_sig]
hover_array, docs = self.get_docs_full(hover_array, long, drop_arg)
return "\n ".join(hover_array), " \n".join(docs), long
return "\n ".join(hover_array), " \n".join(docs)

def get_hover_md(self, long=False, drop_arg=-1):
return fortran_md(*self.get_hover(long, drop_arg))
Expand Down Expand Up @@ -969,7 +970,7 @@ def get_docs_full(
for i, arg_obj in enumerate(self.arg_objs):
if arg_obj is None or i == drop_arg:
continue
arg, doc_str, _ = arg_obj.get_hover()
arg, doc_str = arg_obj.get_hover()
hover_array.append(arg)
if doc_str: # If doc_str is not None or ""
if has_args:
Expand Down Expand Up @@ -1007,7 +1008,7 @@ def get_interface_array(
for i, arg_obj in enumerate(self.arg_objs):
if arg_obj is None:
return None
arg_doc, docs, _ = arg_obj.get_hover()
arg_doc, docs = arg_obj.get_hover()
if i == change_arg:
i0 = arg_doc.lower().find(change_strings[0].lower())
if i0 >= 0:
Expand Down Expand Up @@ -1122,9 +1123,7 @@ def get_desc(self):
def is_callable(self):
return False

def get_hover(
self, long: bool = False, drop_arg: int = -1
) -> tuple[str, str, bool]:
def get_hover(self, long: bool = False, drop_arg: int = -1) -> tuple[str, str]:
"""Construct the hover message for a FUNCTION.
Two forms are produced here the `long` i.e. the normal for hover requests
Expand Down Expand Up @@ -1162,15 +1161,15 @@ def get_hover(
# Only append the return value if using long form
if self.result_obj and long:
# Parse the documentation from the result variable
arg_doc, doc_str, _ = self.result_obj.get_hover()
arg_doc, doc_str = self.result_obj.get_hover()
if doc_str is not None:
docs.append(f"\n**Return:** \n`{self.result_obj.name}`{doc_str}")
hover_array.append(arg_doc)
# intrinsic functions, where the return type is missing but can be inferred
elif self.result_type and long:
# prepend type to function signature
hover_array[0] = f"{self.result_type} {hover_array[0]}"
return "\n ".join(hover_array), " \n".join(docs), long
return "\n ".join(hover_array), " \n".join(docs)

# TODO: fix this
def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
Expand All @@ -1187,7 +1186,7 @@ def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
keyword_list, fun_sig, change_arg, change_strings
)
if self.result_obj is not None:
arg_doc, docs, _ = self.result_obj.get_hover()
arg_doc, docs = self.result_obj.get_hover()
interface_array.append(f"{arg_doc} :: {self.result_obj.name}")
name = self.name
if name_replace is not None:
Expand Down Expand Up @@ -1696,7 +1695,7 @@ def get_snippet(self, name_replace=None, drop_arg=-1):
# Normal variable
return None, None

def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str]:
doc_str = self.get_documentation()
# In associated blocks we need to fetch the desc and keywords of the
# linked object
Expand All @@ -1706,7 +1705,7 @@ def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
hover_str += f" :: {self.name}"
if self.is_parameter() and self.param_val:
hover_str += f" = {self.param_val}"
return hover_str, doc_str, True
return hover_str, doc_str

def get_hover_md(self, long=False, drop_arg=-1):
return fortran_md(*self.get_hover(long, drop_arg))
Expand Down Expand Up @@ -1845,17 +1844,14 @@ def get_documentation(self):
return self.link_obj.get_documentation()
return self.doc_str

def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str]:
docs = self.get_documentation()
if not long:
hover_str = ", ".join([self.desc] + get_keywords(self.keywords))
return hover_str, docs, True
# Long hover message
if self.link_obj is None:
sub_sig, _ = self.get_snippet()
hover_str = f"{self.get_desc()} {sub_sig}"
else:
link_msg, link_docs, _ = self.link_obj.get_hover(
link_msg, link_docs = self.link_obj.get_hover(
long=True, drop_arg=self.drop_arg
)
# Replace the name of the linked object with the name of this object
Expand All @@ -1874,7 +1870,7 @@ def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
if docs is None:
docs = ""
docs += " \n" + link_docs
return hover_str, docs, True
return hover_str, docs

def get_signature(self, drop_arg=-1):
if self.link_obj is not None:
Expand Down
56 changes: 56 additions & 0 deletions test/test_server_completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,59 @@ def test_comp_fixed():
assert len(exp_results) == len(results) - 1
for i, ref in enumerate(exp_results):
validate_comp(results[i + 1], ref)


def test_comp_documentation():
"""Test that "documentation" is returned for autocomplete results."""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir)})
file_path = test_dir / "subdir" / "test_free.f90"
string += comp_request(file_path, 21, 37)
errcode, results = run_request(
string,
)
assert errcode == 0

exp_results = [
{
"label": "scaled_vector_set",
"kind": 3,
"detail": "SUBROUTINE",
"documentation": {
"kind": "markdown",
"value": (
"```fortran90\n"
"SUBROUTINE scaled_vector_set(self, scale)\n"
" CLASS(scaled_vector), INTENT(INOUT) :: self\n"
" REAL(8), INTENT(IN) :: scale\n"
"```\n"
"-----\n"
"Doc 7 \n\n"
"**Parameters:** \n"
"`scale` Doc 8"
),
},
},
{
"label": "scaled_vector_norm",
"kind": 3,
"detail": "REAL(8) FUNCTION",
"documentation": {
"kind": "markdown",
"value": (
"```fortran90\n"
"FUNCTION scaled_vector_norm(self) RESULT(norm)\n"
" CLASS(scaled_vector), INTENT(IN) :: self\n"
" REAL(8) :: norm\n"
"```\n"
"-----\n"
"Top level docstring \n\n"
"**Parameters:** \n"
"`self` self value docstring \n\n"
"**Return:** \n"
"`norm`return value docstring"
),
},
},
]
assert len(exp_results) == len(results[1])
assert exp_results == results[1]
14 changes: 14 additions & 0 deletions test/test_server_hover.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,3 +522,17 @@ def test_multiline_func_args():
"```fortran90\nREAL :: val4\n```",
]
validate_hover(results, ref_results)


def test_intrinsics():
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "hover")})
file_path = test_dir / "hover" / "functions.f90"
string += hover_req(file_path, 39, 23)
errcode, results = run_request(string, fortls_args=["-n", "1"])
assert errcode == 0
ref_results = [
"\n-----\nSIZE(ARRAY,DIM=dim,KIND=kind) determines the extent of ARRAY along a"
" specified dimension DIM, or the total number of elements in ARRAY if DIM is"
" absent."
]
validate_hover(results, ref_results)

0 comments on commit 513391e

Please sign in to comment.