From 05beb717772267690eeee6fd0a350f1ffbc2fa67 Mon Sep 17 00:00:00 2001 From: gnikit Date: Tue, 16 May 2023 08:41:51 +0100 Subject: [PATCH 1/3] fix: evaluates langid only at LSP creation We do a lazy evaluation of the `langid`, only replacing it with a value at the LSP creation stage. This allows us to better isolate the LSP and the AST parts of the server. Fixes #257 --- CHANGELOG.md | 3 +++ fortls/helper_functions.py | 8 +++----- fortls/langserver.py | 38 +++++++++++++++++++++++++++++++++----- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b5bb28..8cdfbe1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ ### Fixed +- Fixed bug where the `langid` was not propagated correctly from the user + settings to the LSP creation stage for all types of requests. + ([#257](https://github.com/fortran-lang/fortls/issues/257)) - Fixed end of scope for `CRITICAL` keyword blocks ([#255](https://github.com/fortran-lang/fortls/issues/255)) - Fixed bug where completion of interfaces in USE ONLY would produce the snippet diff --git a/fortls/helper_functions.py b/fortls/helper_functions.py index 87799125..4121fa4a 100644 --- a/fortls/helper_functions.py +++ b/fortls/helper_functions.py @@ -582,7 +582,7 @@ def get_var_stack(line: str) -> list[str]: return None -def fortran_md(code: str, docs: str | None, langid: str = "fortran90"): +def fortran_md(code: str, docs: str | None): """Convert Fortran code to markdown Parameters @@ -591,9 +591,6 @@ def fortran_md(code: str, docs: str | None, langid: str = "fortran90"): Fortran code docs : str | None Documentation string - langid : str, optional - Language ID, by default 'fortran90' - Returns ------- str @@ -601,7 +598,8 @@ def fortran_md(code: str, docs: str | None, langid: str = "fortran90"): """ msg = "" if code: - msg = f"```{langid}\n{code}\n```" + msg = "```{langid}\n" # This gets inserted later + msg += f"{code}\n```" # Add documentation if docs: # if docs is not None or "" msg += f"\n-----\n{docs}" diff --git a/fortls/langserver.py b/fortls/langserver.py index 8f003b87..14e2c7a9 100644 --- a/fortls/langserver.py +++ b/fortls/langserver.py @@ -514,9 +514,14 @@ def build_comp( if call_sig is not None: comp_obj["detail"] += " " + call_sig # Use the full markdown documentation - hover_msg = candidate.get_hover_md(long=True) + hover_msg: str = candidate.get_hover_md(long=True) if hover_msg: - hover_msg = {"kind": "markdown", "value": hover_msg} + hover_msg: dict = { + "kind": "markdown", + "value": hover_msg.replace( + "```{langid}", f"```{self.hover_language}", 1 + ), + } comp_obj["documentation"] = hover_msg return comp_obj @@ -837,6 +842,17 @@ def check_optional(arg, params: dict): return i return None + def replace_langid(params: list[dict]) -> list[dict]: + new_params = params[:] + for param in new_params: + if "documentation" not in param: + continue + # Replace the first value of langid, when starting a code block + param["documentation"]["value"] = param["documentation"][ + "value" + ].replace("```{langid}", f"```{self.hover_language}", 1) + return params + # Get parameters from request params: dict = request["params"] uri: str = params["textDocument"]["uri"] @@ -904,6 +920,9 @@ def check_optional(arg, params: dict): label, doc_str, params = var_obj.get_signature() if label is None: return None + # Replace placeholder language id with Fortran ID + params = replace_langid(params) + # Find current parameter by index or by # looking at last arg with optional name param_num = len(arg_strings) - 1 @@ -917,6 +936,7 @@ def check_optional(arg, params: dict): param_num = opt_num signature = {"label": label, "parameters": params} if doc_str is not None: + doc_str = doc_str.format(langid=self.hover_language) signature["documentation"] = {"kind": "markdown", "value": doc_str} req_dict = {"signatures": [signature], "activeParameter": param_num} return req_dict @@ -1063,7 +1083,7 @@ def serve_hover(self, request: dict): 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, self.hover_language) + return fortran_md(string, docs).format(langid=self.hover_language) # Get parameters from request params: dict = request["params"] @@ -1087,7 +1107,11 @@ def create_hover(string: str, docs: str | None): MODULE_TYPE_ID, CLASS_TYPE_ID, ): - hover_array.append(var_obj.get_hover_md(long=True)) + hover_array.append( + var_obj.get_hover_md(long=True).replace( + "```{langid}", f"```{self.hover_language}", 1 + ) + ) elif var_type == INTERFACE_TYPE_ID: for member in var_obj.mems: hover_str, docs = member.get_hover(long=True) @@ -1097,7 +1121,11 @@ def create_hover(string: str, docs: str | None): # 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_array.append( + var_obj.get_hover_md(long=True).replace( + "```{langid}", f"```{self.hover_language}", 1 + ) + ) # Hover for Literal variables elif var_obj.desc.endswith("REAL"): hover_array.append(create_hover("REAL", None)) From 107c8e8a440e54200df362704c6ddaa0589543eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 07:44:46 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- fortls/helper_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fortls/helper_functions.py b/fortls/helper_functions.py index 4121fa4a..469aa0dc 100644 --- a/fortls/helper_functions.py +++ b/fortls/helper_functions.py @@ -598,7 +598,7 @@ def fortran_md(code: str, docs: str | None): """ msg = "" if code: - msg = "```{langid}\n" # This gets inserted later + msg = "```{langid}\n" # This gets inserted later msg += f"{code}\n```" # Add documentation if docs: # if docs is not None or "" From 59f440267a2b97170d4384e5f84300ef434db384 Mon Sep 17 00:00:00 2001 From: gnikit Date: Wed, 17 May 2023 00:15:13 +0100 Subject: [PATCH 3/3] fix: associate block from Function result I think the signature is still not ideal i.e. `LOGICAL FUNCTION` but I chose it since it indicates that the associate block variable is the result of a function. Fixes #269 --- CHANGELOG.md | 3 +++ fortls/objects.py | 5 +++++ fortls/parse_fortran.py | 3 ++- test/test_server_hover.py | 14 ++++++++++++++ test/test_source/hover/associate_block_2.f90 | 10 ++++++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 test/test_source/hover/associate_block_2.f90 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdfbe1e..935d35c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ ### Fixed +- Fixed bug where `associate` blocks for variables pointing to function results + where not properly resolved + ([#269](https://github.com/fortran-lang/fortls/issues/269)) - Fixed bug where the `langid` was not propagated correctly from the user settings to the LSP creation stage for all types of requests. ([#257](https://github.com/fortran-lang/fortls/issues/257)) diff --git a/fortls/objects.py b/fortls/objects.py index b44d1362..4daf7b91 100644 --- a/fortls/objects.py +++ b/fortls/objects.py @@ -1160,6 +1160,7 @@ def __init__( args: str = "", mod_flag: bool = False, keywords: list = None, + keyword_info: dict = None, result_type: str = None, result_name: str = None, ): @@ -1173,9 +1174,13 @@ def __init__( self.result_name: str = result_name self.result_type: str = result_type self.result_obj: Variable = None + self.keyword_info: dict = keyword_info # Set the implicit result() name to be the function name if self.result_name is None: self.result_name = self.name + # Used in Associated blocks + if self.keyword_info is None: + self.keyword_info = {} def copy_interface(self, copy_source: Function): # Call the parent class method diff --git a/fortls/parse_fortran.py b/fortls/parse_fortran.py index 9c47c3b6..fa1d9dfe 100644 --- a/fortls/parse_fortran.py +++ b/fortls/parse_fortran.py @@ -1493,7 +1493,7 @@ def parse( log.debug("%s !!! SUBROUTINE - Ln:%d", line, line_no) elif obj_type == "fun": - keywords, _ = map_keywords(obj_info.keywords) + keywords, keyword_info = map_keywords(obj_info.keywords) new_fun = Function( file_ast, line_no, @@ -1501,6 +1501,7 @@ def parse( args=obj_info.args, mod_flag=obj_info.mod_flag, keywords=keywords, + keyword_info=keyword_info, result_type=obj_info.result.type, result_name=obj_info.result.name, ) diff --git a/test/test_server_hover.py b/test/test_server_hover.py index ba52dbe3..f1a79147 100644 --- a/test/test_server_hover.py +++ b/test/test_server_hover.py @@ -366,6 +366,20 @@ def test_hover_block(): validate_hover(results, ref_results) +def test_associate_block_func_result(): + string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir / "hover")}) + file_path = test_dir / "hover" / "associate_block_2.f90" + string += hover_req(file_path, 2, 14) + string += hover_req(file_path, 3, 9) + errorcode, results = run_request(string, fortls_args=["--sort_keywords", "-n", "1"]) + assert errorcode == 0 + ref_results = [ + "```fortran90\nLOGICAL FUNCTION :: hi\n```", + "```fortran90\nLOGICAL FUNCTION :: hi\n```", + ] + validate_hover(results, ref_results) + + def test_hover_submodule_procedure(): """Test that submodule procedures and functions with modifier keywords are correctly displayed when hovering. diff --git a/test/test_source/hover/associate_block_2.f90 b/test/test_source/hover/associate_block_2.f90 new file mode 100644 index 00000000..3649f18f --- /dev/null +++ b/test/test_source/hover/associate_block_2.f90 @@ -0,0 +1,10 @@ +program associate_block_2 + implicit none + associate (hi => say_hi()) + if (hi) print *, 'Bye' + end associate +contains + logical function say_hi() + say_hi = .true. + end +end program