Skip to content

Commit bc04518

Browse files
authored
Merge branch 'master' into pre-commit-ci-update-config
2 parents 02d8ef9 + 08c20b9 commit bc04518

File tree

9 files changed

+143
-41
lines changed

9 files changed

+143
-41
lines changed

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,9 @@ updates:
44
directory: "/"
55
schedule:
66
interval: "daily"
7+
8+
# Check for updates Python updates via pip in case we pin a dependency
9+
- package-ecosystem: "pip"
10+
directory: "/"
11+
schedule:
12+
interval: "weekly"

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@
66

77
### Added
88

9+
- Added dependabot alers for PyPi
10+
([#222](https://github.com/gnikit/fortls/issues/222))
911
- Added `CITATION.cff` file to project roots
1012

1113
### Changed
1214

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

1621
### Fixed
1722

23+
- Fixed debug interface parser not loading all configuration files
24+
([#221](https://github.com/gnikit/fortls/issues/221))
1825
- Fixed name mangling of type-bound procedure pointers while hovering
1926
([#214](https://github.com/gnikit/fortls/issues/214))
2027
- Fixed parsing start of multilines into AST

fortls/__init__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,22 @@ def debug_server_parser(args):
451451
args : Namespace
452452
The arguments parsed from the `ArgumentParser`
453453
"""
454+
455+
def locate_config(root: str) -> str | None:
456+
default_conf_files = [args.config, ".fortlsrc", ".fortls.json", ".fortls"]
457+
present_conf_files = [
458+
os.path.isfile(os.path.join(root, f)) for f in default_conf_files
459+
]
460+
if not any(present_conf_files):
461+
return None
462+
463+
# Load the first config file found
464+
for f, present in zip(default_conf_files, present_conf_files):
465+
if not present:
466+
continue
467+
config_path = os.path.join(root, f)
468+
return config_path
469+
454470
if args.debug_filepath is None:
455471
error_exit("'debug_filepath' not specified for parsing test")
456472
file_exists = os.path.isfile(args.debug_filepath)
@@ -461,7 +477,8 @@ def debug_server_parser(args):
461477
pp_defs = {}
462478
include_dirs = set()
463479
if args.debug_rootpath:
464-
config_path = os.path.join(args.debug_rootpath, args.config)
480+
# Check for config files
481+
config_path = locate_config(args.debug_rootpath)
465482
config_exists = os.path.isfile(config_path)
466483
if config_exists:
467484
try:
@@ -479,7 +496,7 @@ def debug_server_parser(args):
479496
pp_defs = {key: "" for key in pp_defs}
480497
except:
481498
print(f"Error while parsing '{args.config}' settings file")
482-
#
499+
483500
print("\nTesting parser")
484501
print(' File = "{}"'.format(args.debug_filepath))
485502
file_obj = FortranFile(args.debug_filepath, pp_suffixes)

fortls/helper_functions.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -582,17 +582,15 @@ def get_var_stack(line: str) -> list[str]:
582582
return None
583583

584584

585-
def fortran_md(code: str, docs: str | None, highlight: bool, langid: str = "fortran90"):
585+
def fortran_md(code: str, docs: str | None, langid: str = "fortran90"):
586586
"""Convert Fortran code to markdown
587587
588588
Parameters
589589
----------
590590
code : str
591591
Fortran code
592592
docs : str | None
593-
Documentation string, only makes sense if ``highlight`` is ``True``
594-
highlight : bool
595-
Whether to highlight the code
593+
Documentation string
596594
langid : str, optional
597595
Language ID, by default 'fortran90'
598596
@@ -601,8 +599,8 @@ def fortran_md(code: str, docs: str | None, highlight: bool, langid: str = "fort
601599
str
602600
Markdown string
603601
"""
604-
msg = code
605-
if highlight:
602+
msg = ""
603+
if code:
606604
msg = f"```{langid}\n{code}\n```"
607605
# Add documentation
608606
if docs: # if docs is not None or ""

fortls/intrinsics.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import os
33

4-
from fortls.helper_functions import map_keywords
4+
from fortls.helper_functions import fortran_md, map_keywords
55
from fortls.objects import (
66
FortranAST,
77
FortranObj,
@@ -71,7 +71,12 @@ def get_signature(self):
7171
return call_sig, self.doc_str, arg_sigs
7272

7373
def get_hover(self, long=False):
74-
return self.doc_str, None, False
74+
return None, self.doc_str
75+
76+
def get_hover_md(self, long=False):
77+
msg, docs = self.get_hover(long)
78+
msg = msg if msg else ""
79+
return fortran_md(msg, docs)
7580

7681
def is_callable(self):
7782
if self.type == 2:

fortls/langserver.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -488,12 +488,15 @@ def build_comp(
488488
comp_obj["kind"] = map_types(candidate.get_type())
489489
if is_member and (comp_obj["kind"] == 3):
490490
comp_obj["kind"] = 2
491+
# Detail label shown above documentation, also shown when
492+
# documentation is collapsed i.e. short form completions
491493
comp_obj["detail"] = candidate.get_desc()
492494
if call_sig is not None:
493495
comp_obj["detail"] += " " + call_sig
494-
# TODO: doc_str should probably be appended, see LSP standard
495-
hover_msg, doc_str, _ = candidate.get_hover()
496-
if hover_msg is not None:
496+
# Use the full markdown documentation
497+
hover_msg = candidate.get_hover_md(long=True)
498+
if hover_msg:
499+
hover_msg = {"kind": "markdown", "value": hover_msg}
497500
comp_obj["documentation"] = hover_msg
498501
return comp_obj
499502

@@ -1037,10 +1040,10 @@ def serve_definition(self, request: dict):
10371040
return None
10381041

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

10451048
# Get parameters from request
10461049
params: dict = request["params"]
@@ -1062,24 +1065,24 @@ def create_hover(string: str, docs: str | None, fortran: bool):
10621065
hover_array.append(var_obj.get_hover_md(long=True))
10631066
elif var_type == INTERFACE_TYPE_ID:
10641067
for member in var_obj.mems:
1065-
hover_str, docs, highlight = member.get_hover(long=True)
1068+
hover_str, docs = member.get_hover(long=True)
10661069
if hover_str is not None:
1067-
hover_array.append(create_hover(hover_str, docs, highlight))
1070+
hover_array.append(create_hover(hover_str, docs))
10681071
elif var_type == VAR_TYPE_ID:
10691072
# Unless we have a Fortran literal include the desc in the hover msg
10701073
# See get_definition for an explanation about this default name
10711074
if not var_obj.desc.startswith(FORTRAN_LITERAL):
10721075
hover_array.append(var_obj.get_hover_md(long=True))
10731076
# Hover for Literal variables
10741077
elif var_obj.desc.endswith("REAL"):
1075-
hover_array.append(create_hover("REAL", None, True))
1078+
hover_array.append(create_hover("REAL", None))
10761079
elif var_obj.desc.endswith("INTEGER"):
1077-
hover_array.append(create_hover("INTEGER", None, True))
1080+
hover_array.append(create_hover("INTEGER", None))
10781081
elif var_obj.desc.endswith("LOGICAL"):
1079-
hover_array.append(create_hover("LOGICAL", None, True))
1082+
hover_array.append(create_hover("LOGICAL", None))
10801083
elif var_obj.desc.endswith("STRING"):
10811084
hover_str = f"CHARACTER(LEN={len(var_obj.name)-2})"
1082-
hover_array.append(create_hover(hover_str, None, True))
1085+
hover_array.append(create_hover(hover_str, None))
10831086

10841087
if len(hover_array) > 0:
10851088
return {"contents": {"kind": "markdown", "value": "\n".join(hover_array)}}

fortls/objects.py

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -414,11 +414,12 @@ def get_placeholders(arg_list: list[str]):
414414
def get_documentation(self):
415415
return self.doc_str
416416

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

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

423424
def get_signature(self, drop_arg=-1):
424425
return None, None, None
@@ -934,7 +935,7 @@ def get_hover(self, long=False, drop_arg=-1):
934935
keyword_list.append(f"{self.get_desc()} ")
935936
hover_array = [" ".join(keyword_list) + sub_sig]
936937
hover_array, docs = self.get_docs_full(hover_array, long, drop_arg)
937-
return "\n ".join(hover_array), " \n".join(docs), long
938+
return "\n ".join(hover_array), " \n".join(docs)
938939

939940
def get_hover_md(self, long=False, drop_arg=-1):
940941
return fortran_md(*self.get_hover(long, drop_arg))
@@ -969,7 +970,7 @@ def get_docs_full(
969970
for i, arg_obj in enumerate(self.arg_objs):
970971
if arg_obj is None or i == drop_arg:
971972
continue
972-
arg, doc_str, _ = arg_obj.get_hover()
973+
arg, doc_str = arg_obj.get_hover()
973974
hover_array.append(arg)
974975
if doc_str: # If doc_str is not None or ""
975976
if has_args:
@@ -1007,7 +1008,7 @@ def get_interface_array(
10071008
for i, arg_obj in enumerate(self.arg_objs):
10081009
if arg_obj is None:
10091010
return None
1010-
arg_doc, docs, _ = arg_obj.get_hover()
1011+
arg_doc, docs = arg_obj.get_hover()
10111012
if i == change_arg:
10121013
i0 = arg_doc.lower().find(change_strings[0].lower())
10131014
if i0 >= 0:
@@ -1122,9 +1123,7 @@ def get_desc(self):
11221123
def is_callable(self):
11231124
return False
11241125

1125-
def get_hover(
1126-
self, long: bool = False, drop_arg: int = -1
1127-
) -> tuple[str, str, bool]:
1126+
def get_hover(self, long: bool = False, drop_arg: int = -1) -> tuple[str, str]:
11281127
"""Construct the hover message for a FUNCTION.
11291128
Two forms are produced here the `long` i.e. the normal for hover requests
11301129
@@ -1162,15 +1161,15 @@ def get_hover(
11621161
# Only append the return value if using long form
11631162
if self.result_obj and long:
11641163
# Parse the documentation from the result variable
1165-
arg_doc, doc_str, _ = self.result_obj.get_hover()
1164+
arg_doc, doc_str = self.result_obj.get_hover()
11661165
if doc_str is not None:
11671166
docs.append(f"\n**Return:** \n`{self.result_obj.name}`{doc_str}")
11681167
hover_array.append(arg_doc)
11691168
# intrinsic functions, where the return type is missing but can be inferred
11701169
elif self.result_type and long:
11711170
# prepend type to function signature
11721171
hover_array[0] = f"{self.result_type} {hover_array[0]}"
1173-
return "\n ".join(hover_array), " \n".join(docs), long
1172+
return "\n ".join(hover_array), " \n".join(docs)
11741173

11751174
# TODO: fix this
11761175
def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
@@ -1187,7 +1186,7 @@ def get_interface(self, name_replace=None, change_arg=-1, change_strings=None):
11871186
keyword_list, fun_sig, change_arg, change_strings
11881187
)
11891188
if self.result_obj is not None:
1190-
arg_doc, docs, _ = self.result_obj.get_hover()
1189+
arg_doc, docs = self.result_obj.get_hover()
11911190
interface_array.append(f"{arg_doc} :: {self.result_obj.name}")
11921191
name = self.name
11931192
if name_replace is not None:
@@ -1696,7 +1695,7 @@ def get_snippet(self, name_replace=None, drop_arg=-1):
16961695
# Normal variable
16971696
return None, None
16981697

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

17111710
def get_hover_md(self, long=False, drop_arg=-1):
17121711
return fortran_md(*self.get_hover(long, drop_arg))
@@ -1845,17 +1844,14 @@ def get_documentation(self):
18451844
return self.link_obj.get_documentation()
18461845
return self.doc_str
18471846

1848-
def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
1847+
def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str]:
18491848
docs = self.get_documentation()
1850-
if not long:
1851-
hover_str = ", ".join([self.desc] + get_keywords(self.keywords))
1852-
return hover_str, docs, True
18531849
# Long hover message
18541850
if self.link_obj is None:
18551851
sub_sig, _ = self.get_snippet()
18561852
hover_str = f"{self.get_desc()} {sub_sig}"
18571853
else:
1858-
link_msg, link_docs, _ = self.link_obj.get_hover(
1854+
link_msg, link_docs = self.link_obj.get_hover(
18591855
long=True, drop_arg=self.drop_arg
18601856
)
18611857
# Replace the name of the linked object with the name of this object
@@ -1874,7 +1870,7 @@ def get_hover(self, long=False, drop_arg=-1) -> tuple[str, str, bool]:
18741870
if docs is None:
18751871
docs = ""
18761872
docs += " \n" + link_docs
1877-
return hover_str, docs, True
1873+
return hover_str, docs
18781874

18791875
def get_signature(self, drop_arg=-1):
18801876
if self.link_obj is not None:

test/test_server_completion.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,59 @@ def test_comp_fixed():
321321
assert len(exp_results) == len(results) - 1
322322
for i, ref in enumerate(exp_results):
323323
validate_comp(results[i + 1], ref)
324+
325+
326+
def test_comp_documentation():
327+
"""Test that "documentation" is returned for autocomplete results."""
328+
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir)})
329+
file_path = test_dir / "subdir" / "test_free.f90"
330+
string += comp_request(file_path, 21, 37)
331+
errcode, results = run_request(
332+
string,
333+
)
334+
assert errcode == 0
335+
336+
exp_results = [
337+
{
338+
"label": "scaled_vector_set",
339+
"kind": 3,
340+
"detail": "SUBROUTINE",
341+
"documentation": {
342+
"kind": "markdown",
343+
"value": (
344+
"```fortran90\n"
345+
"SUBROUTINE scaled_vector_set(self, scale)\n"
346+
" CLASS(scaled_vector), INTENT(INOUT) :: self\n"
347+
" REAL(8), INTENT(IN) :: scale\n"
348+
"```\n"
349+
"-----\n"
350+
"Doc 7 \n\n"
351+
"**Parameters:** \n"
352+
"`scale` Doc 8"
353+
),
354+
},
355+
},
356+
{
357+
"label": "scaled_vector_norm",
358+
"kind": 3,
359+
"detail": "REAL(8) FUNCTION",
360+
"documentation": {
361+
"kind": "markdown",
362+
"value": (
363+
"```fortran90\n"
364+
"FUNCTION scaled_vector_norm(self) RESULT(norm)\n"
365+
" CLASS(scaled_vector), INTENT(IN) :: self\n"
366+
" REAL(8) :: norm\n"
367+
"```\n"
368+
"-----\n"
369+
"Top level docstring \n\n"
370+
"**Parameters:** \n"
371+
"`self` self value docstring \n\n"
372+
"**Return:** \n"
373+
"`norm`return value docstring"
374+
),
375+
},
376+
},
377+
]
378+
assert len(exp_results) == len(results[1])
379+
assert exp_results == results[1]

test/test_server_hover.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,17 @@ def test_multiline_func_args():
522522
"```fortran90\nREAL :: val4\n```",
523523
]
524524
validate_hover(results, ref_results)
525+
526+
527+
def test_intrinsics():
528+
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "hover")})
529+
file_path = test_dir / "hover" / "functions.f90"
530+
string += hover_req(file_path, 39, 23)
531+
errcode, results = run_request(string, fortls_args=["-n", "1"])
532+
assert errcode == 0
533+
ref_results = [
534+
"\n-----\nSIZE(ARRAY,DIM=dim,KIND=kind) determines the extent of ARRAY along a"
535+
" specified dimension DIM, or the total number of elements in ARRAY if DIM is"
536+
" absent."
537+
]
538+
validate_hover(results, ref_results)

0 commit comments

Comments
 (0)