From a7efd086147894f5007ea0d92ddcd2b7a11d3fd9 Mon Sep 17 00:00:00 2001 From: gnikit Date: Wed, 24 Apr 2024 22:29:59 +0100 Subject: [PATCH 01/17] refactor: debug CLI to smaller functinos --- fortls/__init__.py | 749 +++++++++++++++++++++++---------------------- 1 file changed, 379 insertions(+), 370 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index faebde88..273fb5b5 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -25,26 +25,11 @@ def main(): freeze_support() args = cli(__name__).parse_args() - debug_server = ( - args.debug_diagnostics - or args.debug_symbols - or args.debug_completion - or args.debug_signature - or args.debug_definition - or args.debug_hover - or args.debug_implementation - or args.debug_references - or args.debug_rename - or args.debug_actions - or args.debug_rootpath - or args.debug_workspace_symbols - ) - if args.debug_parser: debug_server_parser(args) - elif debug_server: - debug_server_general(args, vars(args)) + elif is_debug_mode(args): + debug_lsp(args, vars(args)) else: stdin, stdout = sys.stdin.buffer, sys.stdout.buffer @@ -54,388 +39,412 @@ def main(): ).run() -def debug_server_general(args, settings): - """Outputs debug information about the server. - Triggers with any option under the DEBUG group except debug parser. - For the parser see `debug_server_parser` +def is_debug_mode(args): + debug_flags = [ + "debug_diagnostics", + "debug_symbols", + "debug_completion", + "debug_signature", + "debug_definition", + "debug_hover", + "debug_implementation", + "debug_references", + "debug_rename", + "debug_actions", + "debug_rootpath", + "debug_workspace_symbols", + ] + return any(getattr(args, flag, False) for flag in debug_flags) + + +def debug_lsp(args, settings): + debug_functions = { + "debug_rootpath": debug_rootpath, + "debug_diagnostics": debug_diagnostics, + "debug_symbols": debug_symbols, + "debug_workspace_symbols": debug_workspace_symbols, + "debug_completion": debug_completion, + "debug_hover": debug_hover, + "debug_signature": debug_signature, + "debug_definition": debug_definition, + "debug_references": debug_references, + "debug_implementation": debug_implementation, + "debug_rename": debug_rename, + "debug_actions": debug_actions, + } - Parameters - ---------- - args : Namespace - The arguments parsed from the `ArgumentParser` - settings : dict - Language server settings - """ prb, pwb = os.pipe() - tmpin = os.fdopen(prb, "rb") - tmpout = os.fdopen(pwb, "wb") - s = LangServer( - conn=JSONRPC2Connection(ReadWriter(tmpin, tmpout)), - settings=settings, + with os.fdopen(prb, "rb") as tmpin, os.fdopen(pwb, "wb") as tmpout: + server = LangServer( + conn=JSONRPC2Connection(ReadWriter(tmpin, tmpout)), + settings=settings, + ) + for flag, function in debug_functions.items(): + if getattr(args, flag, False): + function(args, server) + + +def debug_rootpath(args, server): + if not os.path.isdir(args.debug_rootpath): + error_exit("'debug_rootpath' not specified for debug request") + print('\nTesting "initialize" request:') + print(f' Root = "{args.debug_rootpath}"') + server.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) + if len(server.post_messages) == 0: + print(" Successful!") + else: + print(" Successful with errors:") + for message in server.post_messages: + print(f" {message[1]}") + print("\n Source directories:") + for source_dir in server.source_dirs: + print(f" {source_dir}") + + +def debug_diagnostics(args, server): + print('\nTesting "textDocument/publishDiagnostics" notification:') + check_request_params(args, loc_needed=False) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results, _ = server.get_diagnostics(args.debug_filepath) + if results is not None: + if args.debug_full_result: + print(json.dumps(results, indent=2)) + else: + sev_map = ["ERROR", "WARNING", "INFO"] + if len(results) == 0: + print("\nNo errors or warnings") + else: + print("\nReported errors or warnings:") + for diag in results: + sline = diag["range"]["start"]["line"] + message = diag["message"] + sev = sev_map[diag["severity"] - 1] + print(f' {sline:5d}:{sev} "{message}"') + + +def debug_symbols(args, server): + print('\nTesting "textDocument/documentSymbol" request:') + check_request_params(args, loc_needed=False) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_document_symbols( + {"params": {"textDocument": {"uri": args.debug_filepath}}} ) - # - if args.debug_rootpath: - dir_exists = os.path.isdir(args.debug_rootpath) - if dir_exists is False: - error_exit( - "Specified 'debug_rootpath' does not exist or is not a directory" + if args.debug_full_result: + print(json.dumps(results, indent=2)) + else: + for symbol in results: + sline = symbol["location"]["range"]["start"]["line"] + if "containerName" in symbol: + parent = symbol["containerName"] + else: + parent = "null" + print( + f" line {sline:5d} symbol -> " + f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" ) - print('\nTesting "initialize" request:') - print(' Root = "{}"'.format(args.debug_rootpath)) - s.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) - if len(s.post_messages) == 0: - print(" Successful!") - else: - print(" Successful with errors:") - for message in s.post_messages: - print(" {}".format(message[1])) - # Print module directories - print("\n Source directories:") - for source_dir in s.source_dirs: - print(" {}".format(source_dir)) - # - if args.debug_diagnostics: - print('\nTesting "textDocument/publishDiagnostics" notification:') - check_request_params(args, loc_needed=False) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - diag_results, _ = s.get_diagnostics(args.debug_filepath) - if diag_results is not None: - if args.debug_full_result: - print(json.dumps(diag_results, indent=2)) + + +def debug_workspace_symbols(args, server): + print('\nTesting "workspace/symbol" request:') + if args.debug_rootpath is None: + error_exit("'debug_rootpath' not specified for debug request") + results = server.serve_workspace_symbol( + {"params": {"query": args.debug_workspace_symbols}} + ) + if args.debug_full_result: + print(json.dumps(results, indent=2)) + else: + for symbol in results: + path = path_from_uri(symbol["location"]["uri"]) + sline = symbol["location"]["range"]["start"]["line"] + if "containerName" in symbol: + parent = symbol["containerName"] else: - sev_map = ["ERROR", "WARNING", "INFO"] - if len(diag_results) == 0: - print("\nNo errors or warnings") - else: - print("\nReported errors or warnings:") - for diag in diag_results: - sline = diag["range"]["start"]["line"] - message = diag["message"] - sev = sev_map[diag["severity"] - 1] - print(' {:5d}:{} "{}"'.format(sline, sev, message)) - # - if args.debug_symbols: - print('\nTesting "textDocument/documentSymbol" request:') - check_request_params(args, loc_needed=False) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - symbol_results = s.serve_document_symbols( - {"params": {"textDocument": {"uri": args.debug_filepath}}} - ) + parent = "null" + print( + f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " + f"{os.path.relpath(path, args.debug_rootpath)}" + ) + + +def debug_completion(args, server): + print('\nTesting "textDocument/completion" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_autocomplete( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + if results is None: + print(" No results!") + else: + print(" Results:") if args.debug_full_result: - print(json.dumps(symbol_results, indent=2)) + print(json.dumps(results, indent=2)) else: - for symbol in symbol_results: - sline = symbol["location"]["range"]["start"]["line"] - if "containerName" in symbol: - parent = symbol["containerName"] - else: - parent = "null" - print( - " line {2:5d} symbol -> {1:3d}:{0:30} parent = {3}".format( - symbol["name"], symbol["kind"], sline, parent - ) - ) - # - if args.debug_workspace_symbols is not None: - print('\nTesting "workspace/symbol" request:') - if args.debug_rootpath is None: - error_exit("'debug_rootpath' not specified for debug request") - symbol_results = s.serve_workspace_symbol( - {"params": {"query": args.debug_workspace_symbols}} - ) + for obj in results: + print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") + + +def debug_hover(args, server): + print('\nTesting "textDocument/hover" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_hover( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + print(" Result:") + if results is None: + print(" No result found!") + else: if args.debug_full_result: - print(json.dumps(symbol_results, indent=2)) + print(json.dumps(results, indent=2)) else: - for symbol in symbol_results: - path = path_from_uri(symbol["location"]["uri"]) - sline = symbol["location"]["range"]["start"]["line"] - if "containerName" in symbol: - parent = symbol["containerName"] - else: - parent = "null" - print( - " {2}::{3:d} symbol -> {1:3d}:{0:30} parent = {4}".format( - symbol["name"], - symbol["kind"], - os.path.relpath(path, args.debug_rootpath), - sline, - parent, - ) - ) - # - if args.debug_completion: - print('\nTesting "textDocument/completion" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - completion_results = s.serve_autocomplete( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } + contents = results["contents"] + print("=======") + if isinstance(contents, dict): + print(contents["value"]) + else: + print(contents) + print("=======") + + +def debug_signature(args, server): + print('\nTesting "textDocument/signatureHelp" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_signature( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, } - ) - if completion_results is None: - print(" No results!") + } + ) + print(" Result:") + if results is None: + print(" No Results!") + else: + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) else: - print(" Results:") - if args.debug_full_result: - print(json.dumps(completion_results, indent=2)) - else: - for obj in completion_results: - print( - " {}: {} -> {}".format( - obj["kind"], obj["label"], obj["detail"] - ) - ) - # - if args.debug_signature: - print('\nTesting "textDocument/signatureHelp" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - signature_results = s.serve_signature( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } + active_param = results.get("activeParameter", 0) + print(f" Active param = {active_param}") + active_signature = results.get("activeSignature", 0) + print(f" Active sig = {active_signature}") + for i, signature in enumerate(results["signatures"]): + print(f" {signature['label']}") + for j, obj in enumerate(signature["parameters"]): + if (i == active_signature) and (j == active_param): + active_mark = "*" + else: + active_mark = " " + arg_desc = obj.get("documentation") + if arg_desc is not None: + print(f"{active_mark} {arg_desc} :: {obj['label']}") + else: + print(f"{active_mark} {obj['label']}") + + +def debug_definition(args, server): + print('\nTesting "textDocument/definition" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_definition( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, } - ) - if signature_results is None: - print(" No Results!") + } + ) + print(" Result:") + if results is None: + print(" No result found!") + else: + if args.debug_full_result: + print(json.dumps(results, indent=2)) else: - print(" Results:") - if args.debug_full_result: - print(json.dumps(signature_results, indent=2)) - else: - active_param = signature_results.get("activeParameter", 0) - print(" Active param = {}".format(active_param)) - active_signature = signature_results.get("activeSignature", 0) - print(" Active sig = {}".format(active_signature)) - for i, signature in enumerate(signature_results["signatures"]): - print(" {}".format(signature["label"])) - for j, obj in enumerate(signature["parameters"]): - if (i == active_signature) and (j == active_param): - active_mark = "*" - else: - active_mark = " " - arg_desc = obj.get("documentation") - if arg_desc is not None: - print( - "{2} {0} :: {1}".format( - arg_desc, obj["label"], active_mark - ) - ) - else: - print("{1} {0}".format(obj["label"], active_mark)) - # - if args.debug_definition or args.debug_implementation: - if args.debug_definition: - print('\nTesting "textDocument/definition" request:') - elif args.debug_implementation: - print('\nTesting "textDocument/implementation" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - if args.debug_definition: - definition_results = s.serve_definition( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - elif args.debug_implementation: - definition_results = s.serve_implementation( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - print(" Result:") - if definition_results is None: - print(" No result found!") + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') + + +def debug_references(args, server): + print('\nTesting "textDocument/references" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_references( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + print(" Result:") + if results is None: + print(" No result found!") + else: + if args.debug_full_result: + print(json.dumps(results, indent=2)) else: - if args.debug_full_result: - print(json.dumps(definition_results, indent=2)) - else: - print(' URI = "{}"'.format(definition_results["uri"])) - print( - " Line = {}".format( - definition_results["range"]["start"]["line"] + 1 - ) - ) + print("=======") + for result in results: print( - " Char = {}".format( - definition_results["range"]["start"]["character"] + 1 - ) + f" {result['uri']} ({result['range']['start']['line'] + 1}" + f", {result['range']['start']['character'] + 1})" ) - # - if args.debug_hover: - print('\nTesting "textDocument/hover" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - hover_results = s.serve_hover( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } + print("=======") + + +def debug_implementation(args, server): + print('\nTesting "textDocument/implementation" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_implementation( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, } - ) - print(" Result:") - if hover_results is None: - print(" No result found!") + } + ) + print(" Result:") + if results is None: + print(" No result found!") + else: + if args.debug_full_result: + print(json.dumps(results, indent=2)) else: - if args.debug_full_result: - print(json.dumps(hover_results, indent=2)) - else: - contents = hover_results["contents"] - print("=======") - if isinstance(contents, dict): - print(contents["value"]) + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') + + +def debug_rename(args, server): + print('\nTesting "textDocument/rename" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_rename( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + "newName": args.debug_rename, + } + } + ) + print(" Result:") + if results is None: + print(" No changes found!") + else: + if args.debug_full_result: + print(json.dumps(results, indent=2)) + else: + print("=======") + for uri, changes in results["changes"].items(): + path = path_from_uri(uri) + file_obj = server.workspace.get(path) + if file_obj is not None: + file_contents = file_obj.contents_split + process_file_changes(path, changes, file_contents) else: - print(contents) - print("=======") - # - if args.debug_references: - print('\nTesting "textDocument/references" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - ref_results = s.serve_references( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { + print(f'Unknown file: "{path}"') + print("=======") + + +def process_file_changes(file_path, changes, file_contents): + print(f'File: "{file_path}"') + for change in changes: + start_line = change["range"]["start"]["line"] + end_line = change["range"]["end"]["line"] + start_col = change["range"]["start"]["character"] + end_col = change["range"]["end"]["character"] + print(f" {start_line + 1}, {end_line + 1}") + new_contents = [] + for i in range(start_line, end_line + 1): + line = file_contents[i] + print(f" - {line}") + line_content = line + if i == start_line: + line_content = line[:start_col] + change["newText"] + if i == end_line: + line_content += line[end_col:] + new_contents.append(line_content) + for line in new_contents: + print(f" + {line}") + print() + + +def debug_actions(args, server): + print('\nTesting "textDocument/getActions" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_codeActions( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "range": { + "start": { "line": args.debug_line - 1, "character": args.debug_char - 1, }, - } - } - ) - print(" Result:") - if ref_results is None: - print(" No result found!") - else: - if args.debug_full_result: - print(json.dumps(ref_results, indent=2)) - else: - print("=======") - for result in ref_results: - print( - " {} ({}, {})".format( - result["uri"], - result["range"]["start"]["line"] + 1, - result["range"]["start"]["character"] + 1, - ) - ) - print("=======") - # - if args.debug_rename is not None: - print('\nTesting "textDocument/rename" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - ref_results = s.serve_rename( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { + "end": { "line": args.debug_line - 1, "character": args.debug_char - 1, }, - "newName": args.debug_rename, - } + }, } - ) - print(" Result:") - if ref_results is None: - print(" No changes found!") - else: - if args.debug_full_result: - print(json.dumps(ref_results, indent=2)) - else: - print("=======") - for uri, result in ref_results["changes"].items(): - path = path_from_uri(uri) - print('File: "{}"'.format(path)) - file_obj = s.workspace.get(path) - if file_obj is not None: - file_contents = file_obj.contents_split - for change in result: - start_line = change["range"]["start"]["line"] - end_line = change["range"]["end"]["line"] - start_col = change["range"]["start"]["character"] - end_col = change["range"]["end"]["character"] - print(" {}, {}".format(start_line + 1, end_line + 1)) - new_contents = [] - for i in range(start_line, end_line + 1): - line = file_contents[i] - print(" - {}".format(line)) - if i == start_line: - new_contents.append( - line[:start_col] + change["newText"] - ) - if i == end_line: - new_contents[-1] += line[end_col:] - for line in new_contents: - print(" + {}".format(line)) - print() - else: - print('Unknown file: "{}"'.format(path)) - print("=======") - # - if args.debug_actions: - pp = pprint.PrettyPrinter(indent=2, width=120) - print('\nTesting "textDocument/getActions" request:') - check_request_params(args) - s.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - action_results = s.serve_codeActions( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "range": { - "start": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - "end": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - }, - } - } - ) + } + ) + print(" Result:") + pp = pprint.PrettyPrinter(indent=2, width=120) + if results is None: + print(" No actions found!") + else: + print("=======") if args.debug_full_result: - print(json.dumps(action_results, indent=2)) + print(json.dumps(results, indent=2)) else: - for result in action_results: - print( - "Kind = '{}', Title = '{}'".format(result["kind"], result["title"]) - ) - for editUri, editChange in result["edit"]["changes"].items(): - print("\nChange: URI = '{}'".format(editUri)) - pp.pprint(editChange) + for result in results: + print(f"Kind = '{result['kind']}', Title = '{result['title']}'") + for edit_uri, edit_change in result["edit"]["changes"].items(): + print(f"\nChange: URI = '{edit_uri}'") + pp.pprint(edit_change) print() - tmpout.close() - tmpin.close() + print("=======") def debug_server_parser(args): From fdf52983297c3d6e03cc95e407e1ae16ab2aad93 Mon Sep 17 00:00:00 2001 From: gnikit Date: Wed, 24 Apr 2024 23:14:01 +0100 Subject: [PATCH 02/17] refactor: convert format to f-strings --- fortls/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 273fb5b5..0e5f00b1 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -526,17 +526,17 @@ def check_request_params(args, loc_needed=True): file_exists = os.path.isfile(args.debug_filepath) if file_exists is False: error_exit("Specified 'debug_filepath' does not exist") - print(' File = "{}"'.format(args.debug_filepath)) + print(f' File = "{args.debug_filepath}"') if loc_needed: if args.debug_line is None: error_exit("'debug_line' not specified for debug request") - print(" Line = {}".format(args.debug_line)) + print(f" Line = {args.debug_line}") if args.debug_char is None: error_exit("'debug_char' not specified for debug request") - print(" Char = {}\n".format(args.debug_char)) + print(f" Char = {args.debug_char}\n") def print_children(obj, indent=""): for child in obj.get_children(): - print(" {}{}: {}".format(indent, child.get_type(), child.FQSN)) + print(f" {indent}{child.get_type()}: {child.FQSN}") print_children(child, indent + " ") From e11a799ebeed82d681e249882ba8cd4256b891a3 Mon Sep 17 00:00:00 2001 From: gnikit Date: Wed, 24 Apr 2024 23:21:24 +0100 Subject: [PATCH 03/17] refactor: made functions to ensure debug_filepath exists and is accessible --- fortls/__init__.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 0e5f00b1..d16d66d2 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -472,11 +472,7 @@ def locate_config(root: str) -> str | None: config_path = os.path.join(root, f) return config_path - if args.debug_filepath is None: - error_exit("'debug_filepath' not specified for parsing test") - file_exists = os.path.isfile(args.debug_filepath) - if file_exists is False: - error_exit("Specified 'debug_filepath' does not exist") + ensure_file_accessible(args.debug_filepath) # Get preprocessor definitions from config file pp_suffixes = None pp_defs = {} @@ -520,13 +516,15 @@ def locate_config(root: str) -> str | None: print("{}: {}".format(obj.get_type(), obj.FQSN)) +def ensure_file_accessible(filepath: str): + """Ensure the file exists and is accessible, raising an error if not.""" + if not os.path.isfile(filepath): + error_exit(f"File '{filepath}' does not exist or is not accessible") + print(f' File = "{filepath}"') + + def check_request_params(args, loc_needed=True): - if args.debug_filepath is None: - error_exit("'debug_filepath' not specified for debug request") - file_exists = os.path.isfile(args.debug_filepath) - if file_exists is False: - error_exit("Specified 'debug_filepath' does not exist") - print(f' File = "{args.debug_filepath}"') + ensure_file_accessible(args.debug_filepath) if loc_needed: if args.debug_line is None: error_exit("'debug_line' not specified for debug request") From 570b52cb720cd20dbc56f2b946e8641a5d007b6c Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 19:13:36 +0100 Subject: [PATCH 04/17] refactor: renamed variables --- fortls/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index d16d66d2..ceaf097c 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -73,10 +73,10 @@ def debug_lsp(args, settings): "debug_actions": debug_actions, } - prb, pwb = os.pipe() - with os.fdopen(prb, "rb") as tmpin, os.fdopen(pwb, "wb") as tmpout: + r, w = os.pipe() + with os.fdopen(r, "rb") as buffer_in, os.fdopen(w, "wb") as buffer_out: server = LangServer( - conn=JSONRPC2Connection(ReadWriter(tmpin, tmpout)), + conn=JSONRPC2Connection(ReadWriter(buffer_in, buffer_out)), settings=settings, ) for flag, function in debug_functions.items(): From 862f43c3a9638254bc590792cb5ae87a4ca75e76 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 19:15:38 +0100 Subject: [PATCH 05/17] refactor: chang --- fortls/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index ceaf097c..02bba9d8 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -114,7 +114,7 @@ def debug_diagnostics(args, server): if len(results) == 0: print("\nNo errors or warnings") else: - print("\nReported errors or warnings:") + print("\nReported Diagnostics:") for diag in results: sline = diag["range"]["start"]["line"] message = diag["message"] From 2d50cd2f76e958c88e1c6b863837bb4db66c3113 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 19:16:50 +0100 Subject: [PATCH 06/17] refactor: minor conditional in workspace symbols --- fortls/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 02bba9d8..d9b22dd8 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -157,10 +157,9 @@ def debug_workspace_symbols(args, server): for symbol in results: path = path_from_uri(symbol["location"]["uri"]) sline = symbol["location"]["range"]["start"]["line"] + parent = "null" if "containerName" in symbol: parent = symbol["containerName"] - else: - parent = "null" print( f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " f"{os.path.relpath(path, args.debug_rootpath)}" From e753056de75cae5be5cd6307cba930be4dab89b7 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 19:33:09 +0100 Subject: [PATCH 07/17] chore: reformated debug_diagnostics message --- fortls/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index d9b22dd8..65e06ed1 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -102,7 +102,7 @@ def debug_rootpath(args, server): def debug_diagnostics(args, server): - print('\nTesting "textDocument/publishDiagnostics" notification:') + print('\nTesting "textDocument/publishDiagnostics" request:') check_request_params(args, loc_needed=False) server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) results, _ = server.get_diagnostics(args.debug_filepath) From 18b8dc167e9443fd0deae5ea36be9e7beed8bf94 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 19:34:14 +0100 Subject: [PATCH 08/17] feat: add debug exceptions --- fortls/__init__.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 65e06ed1..65c63d1d 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -16,27 +16,34 @@ __all__ = ["__version__"] -def error_exit(error_str: str): - print(f"ERROR: {error_str}") - sys.exit(-1) +class DebugError(Exception): + """Base class for debug CLI.""" + + +class ParameterError(DebugError): + """Exception raised for errors in the parameters.""" def main(): freeze_support() args = cli(__name__).parse_args() - if args.debug_parser: - debug_server_parser(args) + try: + if args.debug_parser: + debug_server_parser(args) - elif is_debug_mode(args): - debug_lsp(args, vars(args)) + elif is_debug_mode(args): + debug_lsp(args, vars(args)) - else: - stdin, stdout = sys.stdin.buffer, sys.stdout.buffer - LangServer( - conn=JSONRPC2Connection(ReadWriter(stdin, stdout)), - settings=vars(args), - ).run() + else: + stdin, stdout = sys.stdin.buffer, sys.stdout.buffer + LangServer( + conn=JSONRPC2Connection(ReadWriter(stdin, stdout)), + settings=vars(args), + ).run() + except DebugError as e: + print(f"ERROR: {e}") + sys.exit(-1) def is_debug_mode(args): @@ -86,7 +93,7 @@ def debug_lsp(args, settings): def debug_rootpath(args, server): if not os.path.isdir(args.debug_rootpath): - error_exit("'debug_rootpath' not specified for debug request") + raise DebugError("'debug_rootpath' not specified for debug request") print('\nTesting "initialize" request:') print(f' Root = "{args.debug_rootpath}"') server.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) @@ -147,7 +154,7 @@ def debug_symbols(args, server): def debug_workspace_symbols(args, server): print('\nTesting "workspace/symbol" request:') if args.debug_rootpath is None: - error_exit("'debug_rootpath' not specified for debug request") + raise DebugError("'debug_rootpath' not specified for debug request") results = server.serve_workspace_symbol( {"params": {"query": args.debug_workspace_symbols}} ) @@ -502,7 +509,7 @@ def locate_config(root: str) -> str | None: file_obj = FortranFile(args.debug_filepath, pp_suffixes) err_str, _ = file_obj.load_from_disk() if err_str: - error_exit(f"Reading file failed: {err_str}") + raise DebugError(f"Reading file failed: {err_str}") print(f" Detected format: {'fixed' if file_obj.fixed else 'free'}") print("\n=========\nParser Output\n=========\n") file_ast = file_obj.parse(debug=True, pp_defs=pp_defs, include_dirs=include_dirs) @@ -518,7 +525,7 @@ def locate_config(root: str) -> str | None: def ensure_file_accessible(filepath: str): """Ensure the file exists and is accessible, raising an error if not.""" if not os.path.isfile(filepath): - error_exit(f"File '{filepath}' does not exist or is not accessible") + raise DebugError(f"File '{filepath}' does not exist or is not accessible") print(f' File = "{filepath}"') @@ -526,10 +533,10 @@ def check_request_params(args, loc_needed=True): ensure_file_accessible(args.debug_filepath) if loc_needed: if args.debug_line is None: - error_exit("'debug_line' not specified for debug request") + raise ParameterError("'debug_line' not specified for debug request") print(f" Line = {args.debug_line}") if args.debug_char is None: - error_exit("'debug_char' not specified for debug request") + raise ParameterError("'debug_char' not specified for debug request") print(f" Char = {args.debug_char}\n") From 37451e0b9c55e9524d18a8c6bcae2d891008cb57 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 22:07:24 +0100 Subject: [PATCH 09/17] refactor: removed unnecessary levels of intendation Also, made formatting consistent throughout the debug calls --- fortls/__init__.py | 316 ++++++++++++++++++++++++++------------------- 1 file changed, 185 insertions(+), 131 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 65c63d1d..9b6165f2 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -89,6 +89,7 @@ def debug_lsp(args, settings): for flag, function in debug_functions.items(): if getattr(args, flag, False): function(args, server) + separator() def debug_rootpath(args, server): @@ -97,6 +98,8 @@ def debug_rootpath(args, server): print('\nTesting "initialize" request:') print(f' Root = "{args.debug_rootpath}"') server.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) + + separator() if len(server.post_messages) == 0: print(" Successful!") else: @@ -113,20 +116,28 @@ def debug_diagnostics(args, server): check_request_params(args, loc_needed=False) server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) results, _ = server.get_diagnostics(args.debug_filepath) - if results is not None: - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - sev_map = ["ERROR", "WARNING", "INFO"] - if len(results) == 0: - print("\nNo errors or warnings") - else: - print("\nReported Diagnostics:") - for diag in results: - sline = diag["range"]["start"]["line"] - message = diag["message"] - sev = sev_map[diag["severity"] - 1] - print(f' {sline:5d}:{sev} "{message}"') + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + separator() + return + + sev_map = ["ERROR", "WARNING", "INFO"] + if len(results) == 0: + print("No errors or warnings") + else: + print("Reported Diagnostics:") + for diag in results: + sline = diag["range"]["start"]["line"] + message = diag["message"] + sev = sev_map[diag["severity"] - 1] + print(f' {sline:5d}:{sev} "{message}"') def debug_symbols(args, server): @@ -136,19 +147,27 @@ def debug_symbols(args, server): results = server.serve_document_symbols( {"params": {"textDocument": {"uri": args.debug_filepath}}} ) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") if args.debug_full_result: print(json.dumps(results, indent=2)) - else: - for symbol in results: - sline = symbol["location"]["range"]["start"]["line"] - if "containerName" in symbol: - parent = symbol["containerName"] - else: - parent = "null" - print( - f" line {sline:5d} symbol -> " - f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" - ) + return + + for symbol in results: + sline = symbol["location"]["range"]["start"]["line"] + if "containerName" in symbol: + parent = symbol["containerName"] + else: + parent = "null" + print( + f" line {sline:5d} symbol -> " + f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" + ) def debug_workspace_symbols(args, server): @@ -158,19 +177,27 @@ def debug_workspace_symbols(args, server): results = server.serve_workspace_symbol( {"params": {"query": args.debug_workspace_symbols}} ) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") if args.debug_full_result: print(json.dumps(results, indent=2)) - else: - for symbol in results: - path = path_from_uri(symbol["location"]["uri"]) - sline = symbol["location"]["range"]["start"]["line"] - parent = "null" - if "containerName" in symbol: - parent = symbol["containerName"] - print( - f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " - f"{os.path.relpath(path, args.debug_rootpath)}" - ) + return + + for symbol in results: + path = path_from_uri(symbol["location"]["uri"]) + sline = symbol["location"]["range"]["start"]["line"] + parent = "null" + if "containerName" in symbol: + parent = symbol["containerName"] + print( + f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " + f"{os.path.relpath(path, args.debug_rootpath)}" + ) def debug_completion(args, server): @@ -188,15 +215,19 @@ def debug_completion(args, server): } } ) + + separator() if results is None: print(" No results!") - else: - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - for obj in results: - print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for obj in results: + print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") def debug_hover(args, server): @@ -214,20 +245,22 @@ def debug_hover(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No result found!") + return + + print(" Result:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + contents = results["contents"] + if isinstance(contents, dict): + print(contents["value"]) else: - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - contents = results["contents"] - print("=======") - if isinstance(contents, dict): - print(contents["value"]) - else: - print(contents) - print("=======") + print(contents) def debug_signature(args, server): @@ -245,30 +278,33 @@ def debug_signature(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No Results!") - else: - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - active_param = results.get("activeParameter", 0) - print(f" Active param = {active_param}") - active_signature = results.get("activeSignature", 0) - print(f" Active sig = {active_signature}") - for i, signature in enumerate(results["signatures"]): - print(f" {signature['label']}") - for j, obj in enumerate(signature["parameters"]): - if (i == active_signature) and (j == active_param): - active_mark = "*" - else: - active_mark = " " - arg_desc = obj.get("documentation") - if arg_desc is not None: - print(f"{active_mark} {arg_desc} :: {obj['label']}") - else: - print(f"{active_mark} {obj['label']}") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + active_param = results.get("activeParameter", 0) + print(f" Active param = {active_param}") + active_signature = results.get("activeSignature", 0) + print(f" Active sig = {active_signature}") + for i, signature in enumerate(results["signatures"]): + print(f" {signature['label']}") + for j, obj in enumerate(signature["parameters"]): + if (i == active_signature) and (j == active_param): + active_mark = "*" + else: + active_mark = " " + arg_desc = obj.get("documentation") + if arg_desc is not None: + print(f"{active_mark} {arg_desc} :: {obj['label']}") + else: + print(f"{active_mark} {obj['label']}") def debug_definition(args, server): @@ -286,16 +322,20 @@ def debug_definition(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No result found!") - else: - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') def debug_references(args, server): @@ -313,20 +353,22 @@ def debug_references(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No result found!") - else: - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - print("=======") - for result in results: - print( - f" {result['uri']} ({result['range']['start']['line'] + 1}" - f", {result['range']['start']['character'] + 1})" - ) - print("=======") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for result in results: + print( + f" {result['uri']} ({result['range']['start']['line'] + 1}" + f", {result['range']['start']['character'] + 1})" + ) def debug_implementation(args, server): @@ -344,16 +386,20 @@ def debug_implementation(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No result found!") - else: - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') def debug_rename(args, server): @@ -372,23 +418,25 @@ def debug_rename(args, server): } } ) - print(" Result:") + + separator() if results is None: print(" No changes found!") - else: - if args.debug_full_result: - print(json.dumps(results, indent=2)) + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for uri, changes in results["changes"].items(): + path = path_from_uri(uri) + file_obj = server.workspace.get(path) + if file_obj is not None: + file_contents = file_obj.contents_split + process_file_changes(path, changes, file_contents) else: - print("=======") - for uri, changes in results["changes"].items(): - path = path_from_uri(uri) - file_obj = server.workspace.get(path) - if file_obj is not None: - file_contents = file_obj.contents_split - process_file_changes(path, changes, file_contents) - else: - print(f'Unknown file: "{path}"') - print("=======") + print(f'Unknown file: "{path}"') def process_file_changes(file_path, changes, file_contents): @@ -435,22 +483,24 @@ def debug_actions(args, server): } } ) - print(" Result:") + + separator() pp = pprint.PrettyPrinter(indent=2, width=120) if results is None: print(" No actions found!") - else: - print("=======") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - else: - for result in results: - print(f"Kind = '{result['kind']}', Title = '{result['title']}'") - for edit_uri, edit_change in result["edit"]["changes"].items(): - print(f"\nChange: URI = '{edit_uri}'") - pp.pprint(edit_change) - print() - print("=======") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for result in results: + print(f"Kind = '{result['kind']}', Title = '{result['title']}'") + for edit_uri, edit_change in result["edit"]["changes"].items(): + print(f"\nChange: URI = '{edit_uri}'") + pp.pprint(edit_change) + print() def debug_server_parser(args): @@ -544,3 +594,7 @@ def print_children(obj, indent=""): for child in obj.get_children(): print(f" {indent}{child.get_type()}: {child.FQSN}") print_children(child, indent + " ") + + +def separator(): + print("=" * 80) From b563412ddcc7b40f7dae31252da26dcbb08c5c63 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 22:10:50 +0100 Subject: [PATCH 10/17] refactor: add utf-8 encoding to file open in debug_server_parser --- fortls/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 9b6165f2..167fb3f2 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -539,7 +539,7 @@ def locate_config(root: str) -> str | None: config_exists = os.path.isfile(config_path) if config_exists: try: - with open(config_path) as fhandle: + with open(config_path, encoding="utf-8") as fhandle: config_dict = json.load(fhandle) pp_suffixes = config_dict.get("pp_suffixes", None) pp_defs = config_dict.get("pp_defs", {}) From 8faa7309b7bf9319f2536524daaa02864b5334e9 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 23:04:07 +0100 Subject: [PATCH 11/17] refactor: remove unnecessary code in debug_server_parser --- fortls/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 167fb3f2..838d41f1 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -543,7 +543,6 @@ def locate_config(root: str) -> str | None: config_dict = json.load(fhandle) pp_suffixes = config_dict.get("pp_suffixes", None) pp_defs = config_dict.get("pp_defs", {}) - include_dirs = set() for path in config_dict.get("include_dirs", set()): include_dirs.update( only_dirs(resolve_globs(path, args.debug_rootpath)) @@ -551,8 +550,8 @@ def locate_config(root: str) -> str | None: if isinstance(pp_defs, list): pp_defs = {key: "" for key in pp_defs} - except: - print(f"Error while parsing '{args.config}' settings file") + except ValueError as e: + print(f"Error {e} while parsing '{args.config}' settings file") print("\nTesting parser") print(' File = "{}"'.format(args.debug_filepath)) From abbe44016864c7c539bf6b48f82d9ddd3971d763 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 23:11:38 +0100 Subject: [PATCH 12/17] refactor: improve debug_server_parser code readability and consistency --- fortls/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 838d41f1..1d7e7944 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -554,21 +554,23 @@ def locate_config(root: str) -> str | None: print(f"Error {e} while parsing '{args.config}' settings file") print("\nTesting parser") - print(' File = "{}"'.format(args.debug_filepath)) + separator() + print(f' File = "{args.debug_filepath}"') file_obj = FortranFile(args.debug_filepath, pp_suffixes) err_str, _ = file_obj.load_from_disk() if err_str: raise DebugError(f"Reading file failed: {err_str}") print(f" Detected format: {'fixed' if file_obj.fixed else 'free'}") - print("\n=========\nParser Output\n=========\n") + print("\n" + "=" * 80 + "\nParser Output\n" + "=" * 80 + "\n") file_ast = file_obj.parse(debug=True, pp_defs=pp_defs, include_dirs=include_dirs) - print("\n=========\nObject Tree\n=========\n") + print("\n" + "=" * 80 + "\nObject Tree\n" + "=" * 80 + "\n") for obj in file_ast.get_scopes(): - print("{}: {}".format(obj.get_type(), obj.FQSN)) + print(f"{obj.get_type()}: {obj.FQSN}") print_children(obj) - print("\n=========\nExportable Objects\n=========\n") + print("\n" + "=" * 80 + "\nExportable Objects\n" + "=" * 80 + "\n") for _, obj in file_ast.global_dict.items(): - print("{}: {}".format(obj.get_type(), obj.FQSN)) + print(f"{obj.get_type()}: {obj.FQSN}") + separator() def ensure_file_accessible(filepath: str): From cb00637239e787b2f1a8f95b6d18ff0716197291 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 23:37:07 +0100 Subject: [PATCH 13/17] refactor: move config file read to inner method for debug_server_parser --- fortls/__init__.py | 52 ++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 1d7e7944..cf7ba8a9 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -528,33 +528,39 @@ def locate_config(root: str) -> str | None: config_path = os.path.join(root, f) return config_path - ensure_file_accessible(args.debug_filepath) - # Get preprocessor definitions from config file - pp_suffixes = None - pp_defs = {} - include_dirs = set() - if args.debug_rootpath: + def read_config(root: str | None): + pp_suffixes = None + pp_defs = {} + include_dirs = set() + if root is None: + return pp_suffixes, pp_defs, include_dirs + # Check for config files - config_path = locate_config(args.debug_rootpath) - config_exists = os.path.isfile(config_path) - if config_exists: - try: - with open(config_path, encoding="utf-8") as fhandle: - config_dict = json.load(fhandle) - pp_suffixes = config_dict.get("pp_suffixes", None) - pp_defs = config_dict.get("pp_defs", {}) - for path in config_dict.get("include_dirs", set()): - include_dirs.update( - only_dirs(resolve_globs(path, args.debug_rootpath)) - ) - - if isinstance(pp_defs, list): - pp_defs = {key: "" for key in pp_defs} - except ValueError as e: - print(f"Error {e} while parsing '{args.config}' settings file") + config_path = locate_config(root) + if not os.path.isfile(config_path): + return pp_suffixes, pp_defs, include_dirs + + try: + with open(config_path, encoding="utf-8") as fhandle: + config_dict = json.load(fhandle) + pp_suffixes = config_dict.get("pp_suffixes", None) + pp_defs = config_dict.get("pp_defs", {}) + for path in config_dict.get("include_dirs", set()): + include_dirs.update(only_dirs(resolve_globs(path, root))) + + if isinstance(pp_defs, list): + pp_defs = {key: "" for key in pp_defs} + except ValueError as e: + print(f"Error {e} while parsing '{config_path}' settings file") + + return pp_suffixes, pp_defs, include_dirs print("\nTesting parser") separator() + + ensure_file_accessible(args.debug_filepath) + pp_suffixes, pp_defs, include_dirs = read_config(args.debug_rootpath) + print(f' File = "{args.debug_filepath}"') file_obj = FortranFile(args.debug_filepath, pp_suffixes) err_str, _ = file_obj.load_from_disk() From a34428f92fbb8a14f93929f66fddb5dcbea39f3e Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 23:48:37 +0100 Subject: [PATCH 14/17] refactor: moved debug CLI code to a dedicated module --- .coveragerc | 1 + fortls/__init__.py | 577 +------------------------------------------- fortls/debug.py | 579 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 582 insertions(+), 575 deletions(-) create mode 100644 fortls/debug.py diff --git a/.coveragerc b/.coveragerc index 01ef38ab..cd2ccb7b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] omit = fortls/__init__.py + fortls/debug.py fortls/version.py fortls/schema.py concurrency = multiprocessing diff --git a/fortls/__init__.py b/fortls/__init__.py index cf7ba8a9..584025d1 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -1,29 +1,17 @@ from __future__ import annotations -import json -import os -import pprint import sys from multiprocessing import freeze_support -from .helper_functions import only_dirs, resolve_globs +from .debug import DebugError, debug_lsp, debug_server_parser, is_debug_mode from .interface import cli -from .jsonrpc import JSONRPC2Connection, ReadWriter, path_from_uri +from .jsonrpc import JSONRPC2Connection, ReadWriter from .langserver import LangServer -from .parsers.internal.parser import FortranFile from .version import __version__ __all__ = ["__version__"] -class DebugError(Exception): - """Base class for debug CLI.""" - - -class ParameterError(DebugError): - """Exception raised for errors in the parameters.""" - - def main(): freeze_support() args = cli(__name__).parse_args() @@ -44,564 +32,3 @@ def main(): except DebugError as e: print(f"ERROR: {e}") sys.exit(-1) - - -def is_debug_mode(args): - debug_flags = [ - "debug_diagnostics", - "debug_symbols", - "debug_completion", - "debug_signature", - "debug_definition", - "debug_hover", - "debug_implementation", - "debug_references", - "debug_rename", - "debug_actions", - "debug_rootpath", - "debug_workspace_symbols", - ] - return any(getattr(args, flag, False) for flag in debug_flags) - - -def debug_lsp(args, settings): - debug_functions = { - "debug_rootpath": debug_rootpath, - "debug_diagnostics": debug_diagnostics, - "debug_symbols": debug_symbols, - "debug_workspace_symbols": debug_workspace_symbols, - "debug_completion": debug_completion, - "debug_hover": debug_hover, - "debug_signature": debug_signature, - "debug_definition": debug_definition, - "debug_references": debug_references, - "debug_implementation": debug_implementation, - "debug_rename": debug_rename, - "debug_actions": debug_actions, - } - - r, w = os.pipe() - with os.fdopen(r, "rb") as buffer_in, os.fdopen(w, "wb") as buffer_out: - server = LangServer( - conn=JSONRPC2Connection(ReadWriter(buffer_in, buffer_out)), - settings=settings, - ) - for flag, function in debug_functions.items(): - if getattr(args, flag, False): - function(args, server) - separator() - - -def debug_rootpath(args, server): - if not os.path.isdir(args.debug_rootpath): - raise DebugError("'debug_rootpath' not specified for debug request") - print('\nTesting "initialize" request:') - print(f' Root = "{args.debug_rootpath}"') - server.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) - - separator() - if len(server.post_messages) == 0: - print(" Successful!") - else: - print(" Successful with errors:") - for message in server.post_messages: - print(f" {message[1]}") - print("\n Source directories:") - for source_dir in server.source_dirs: - print(f" {source_dir}") - - -def debug_diagnostics(args, server): - print('\nTesting "textDocument/publishDiagnostics" request:') - check_request_params(args, loc_needed=False) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results, _ = server.get_diagnostics(args.debug_filepath) - - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - separator() - return - - sev_map = ["ERROR", "WARNING", "INFO"] - if len(results) == 0: - print("No errors or warnings") - else: - print("Reported Diagnostics:") - for diag in results: - sline = diag["range"]["start"]["line"] - message = diag["message"] - sev = sev_map[diag["severity"] - 1] - print(f' {sline:5d}:{sev} "{message}"') - - -def debug_symbols(args, server): - print('\nTesting "textDocument/documentSymbol" request:') - check_request_params(args, loc_needed=False) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_document_symbols( - {"params": {"textDocument": {"uri": args.debug_filepath}}} - ) - - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for symbol in results: - sline = symbol["location"]["range"]["start"]["line"] - if "containerName" in symbol: - parent = symbol["containerName"] - else: - parent = "null" - print( - f" line {sline:5d} symbol -> " - f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" - ) - - -def debug_workspace_symbols(args, server): - print('\nTesting "workspace/symbol" request:') - if args.debug_rootpath is None: - raise DebugError("'debug_rootpath' not specified for debug request") - results = server.serve_workspace_symbol( - {"params": {"query": args.debug_workspace_symbols}} - ) - - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for symbol in results: - path = path_from_uri(symbol["location"]["uri"]) - sline = symbol["location"]["range"]["start"]["line"] - parent = "null" - if "containerName" in symbol: - parent = symbol["containerName"] - print( - f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " - f"{os.path.relpath(path, args.debug_rootpath)}" - ) - - -def debug_completion(args, server): - print('\nTesting "textDocument/completion" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_autocomplete( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for obj in results: - print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") - - -def debug_hover(args, server): - print('\nTesting "textDocument/hover" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_hover( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No result found!") - return - - print(" Result:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - contents = results["contents"] - if isinstance(contents, dict): - print(contents["value"]) - else: - print(contents) - - -def debug_signature(args, server): - print('\nTesting "textDocument/signatureHelp" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_signature( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No Results!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - active_param = results.get("activeParameter", 0) - print(f" Active param = {active_param}") - active_signature = results.get("activeSignature", 0) - print(f" Active sig = {active_signature}") - for i, signature in enumerate(results["signatures"]): - print(f" {signature['label']}") - for j, obj in enumerate(signature["parameters"]): - if (i == active_signature) and (j == active_param): - active_mark = "*" - else: - active_mark = " " - arg_desc = obj.get("documentation") - if arg_desc is not None: - print(f"{active_mark} {arg_desc} :: {obj['label']}") - else: - print(f"{active_mark} {obj['label']}") - - -def debug_definition(args, server): - print('\nTesting "textDocument/definition" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_definition( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No result found!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') - - -def debug_references(args, server): - print('\nTesting "textDocument/references" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_references( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No result found!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for result in results: - print( - f" {result['uri']} ({result['range']['start']['line'] + 1}" - f", {result['range']['start']['character'] + 1})" - ) - - -def debug_implementation(args, server): - print('\nTesting "textDocument/implementation" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_implementation( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - } - } - ) - - separator() - if results is None: - print(" No result found!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') - - -def debug_rename(args, server): - print('\nTesting "textDocument/rename" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_rename( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - "newName": args.debug_rename, - } - } - ) - - separator() - if results is None: - print(" No changes found!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for uri, changes in results["changes"].items(): - path = path_from_uri(uri) - file_obj = server.workspace.get(path) - if file_obj is not None: - file_contents = file_obj.contents_split - process_file_changes(path, changes, file_contents) - else: - print(f'Unknown file: "{path}"') - - -def process_file_changes(file_path, changes, file_contents): - print(f'File: "{file_path}"') - for change in changes: - start_line = change["range"]["start"]["line"] - end_line = change["range"]["end"]["line"] - start_col = change["range"]["start"]["character"] - end_col = change["range"]["end"]["character"] - print(f" {start_line + 1}, {end_line + 1}") - new_contents = [] - for i in range(start_line, end_line + 1): - line = file_contents[i] - print(f" - {line}") - line_content = line - if i == start_line: - line_content = line[:start_col] + change["newText"] - if i == end_line: - line_content += line[end_col:] - new_contents.append(line_content) - for line in new_contents: - print(f" + {line}") - print() - - -def debug_actions(args, server): - print('\nTesting "textDocument/getActions" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_codeActions( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "range": { - "start": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - "end": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - }, - } - } - ) - - separator() - pp = pprint.PrettyPrinter(indent=2, width=120) - if results is None: - print(" No actions found!") - return - - print(" Results:") - if args.debug_full_result: - print(json.dumps(results, indent=2)) - return - - for result in results: - print(f"Kind = '{result['kind']}', Title = '{result['title']}'") - for edit_uri, edit_change in result["edit"]["changes"].items(): - print(f"\nChange: URI = '{edit_uri}'") - pp.pprint(edit_change) - print() - - -def debug_server_parser(args): - """Debug the parser of the Language Server - Triggered by `--debug_parser` option. - - Parameters - ---------- - args : Namespace - The arguments parsed from the `ArgumentParser` - """ - - def locate_config(root: str) -> str | None: - default_conf_files = [args.config, ".fortlsrc", ".fortls.json", ".fortls"] - present_conf_files = [ - os.path.isfile(os.path.join(root, f)) for f in default_conf_files - ] - if not any(present_conf_files): - return None - - # Load the first config file found - for f, present in zip(default_conf_files, present_conf_files): - if not present: - continue - config_path = os.path.join(root, f) - return config_path - - def read_config(root: str | None): - pp_suffixes = None - pp_defs = {} - include_dirs = set() - if root is None: - return pp_suffixes, pp_defs, include_dirs - - # Check for config files - config_path = locate_config(root) - if not os.path.isfile(config_path): - return pp_suffixes, pp_defs, include_dirs - - try: - with open(config_path, encoding="utf-8") as fhandle: - config_dict = json.load(fhandle) - pp_suffixes = config_dict.get("pp_suffixes", None) - pp_defs = config_dict.get("pp_defs", {}) - for path in config_dict.get("include_dirs", set()): - include_dirs.update(only_dirs(resolve_globs(path, root))) - - if isinstance(pp_defs, list): - pp_defs = {key: "" for key in pp_defs} - except ValueError as e: - print(f"Error {e} while parsing '{config_path}' settings file") - - return pp_suffixes, pp_defs, include_dirs - - print("\nTesting parser") - separator() - - ensure_file_accessible(args.debug_filepath) - pp_suffixes, pp_defs, include_dirs = read_config(args.debug_rootpath) - - print(f' File = "{args.debug_filepath}"') - file_obj = FortranFile(args.debug_filepath, pp_suffixes) - err_str, _ = file_obj.load_from_disk() - if err_str: - raise DebugError(f"Reading file failed: {err_str}") - print(f" Detected format: {'fixed' if file_obj.fixed else 'free'}") - print("\n" + "=" * 80 + "\nParser Output\n" + "=" * 80 + "\n") - file_ast = file_obj.parse(debug=True, pp_defs=pp_defs, include_dirs=include_dirs) - print("\n" + "=" * 80 + "\nObject Tree\n" + "=" * 80 + "\n") - for obj in file_ast.get_scopes(): - print(f"{obj.get_type()}: {obj.FQSN}") - print_children(obj) - print("\n" + "=" * 80 + "\nExportable Objects\n" + "=" * 80 + "\n") - for _, obj in file_ast.global_dict.items(): - print(f"{obj.get_type()}: {obj.FQSN}") - separator() - - -def ensure_file_accessible(filepath: str): - """Ensure the file exists and is accessible, raising an error if not.""" - if not os.path.isfile(filepath): - raise DebugError(f"File '{filepath}' does not exist or is not accessible") - print(f' File = "{filepath}"') - - -def check_request_params(args, loc_needed=True): - ensure_file_accessible(args.debug_filepath) - if loc_needed: - if args.debug_line is None: - raise ParameterError("'debug_line' not specified for debug request") - print(f" Line = {args.debug_line}") - if args.debug_char is None: - raise ParameterError("'debug_char' not specified for debug request") - print(f" Char = {args.debug_char}\n") - - -def print_children(obj, indent=""): - for child in obj.get_children(): - print(f" {indent}{child.get_type()}: {child.FQSN}") - print_children(child, indent + " ") - - -def separator(): - print("=" * 80) diff --git a/fortls/debug.py b/fortls/debug.py new file mode 100644 index 00000000..121d47e0 --- /dev/null +++ b/fortls/debug.py @@ -0,0 +1,579 @@ +from __future__ import annotations + +import json +import os +import pprint + +from .helper_functions import only_dirs, resolve_globs +from .jsonrpc import JSONRPC2Connection, ReadWriter, path_from_uri +from .langserver import LangServer +from .parsers.internal.parser import FortranFile + + +class DebugError(Exception): + """Base class for debug CLI.""" + + +class ParameterError(DebugError): + """Exception raised for errors in the parameters.""" + + +def is_debug_mode(args): + debug_flags = [ + "debug_diagnostics", + "debug_symbols", + "debug_completion", + "debug_signature", + "debug_definition", + "debug_hover", + "debug_implementation", + "debug_references", + "debug_rename", + "debug_actions", + "debug_rootpath", + "debug_workspace_symbols", + ] + return any(getattr(args, flag, False) for flag in debug_flags) + + +def debug_lsp(args, settings): + debug_functions = { + "debug_rootpath": debug_rootpath, + "debug_diagnostics": debug_diagnostics, + "debug_symbols": debug_symbols, + "debug_workspace_symbols": debug_workspace_symbols, + "debug_completion": debug_completion, + "debug_hover": debug_hover, + "debug_signature": debug_signature, + "debug_definition": debug_definition, + "debug_references": debug_references, + "debug_implementation": debug_implementation, + "debug_rename": debug_rename, + "debug_actions": debug_actions, + } + + r, w = os.pipe() + with os.fdopen(r, "rb") as buffer_in, os.fdopen(w, "wb") as buffer_out: + server = LangServer( + conn=JSONRPC2Connection(ReadWriter(buffer_in, buffer_out)), + settings=settings, + ) + for flag, function in debug_functions.items(): + if getattr(args, flag, False): + function(args, server) + separator() + + +def debug_rootpath(args, server): + if not os.path.isdir(args.debug_rootpath): + raise DebugError("'debug_rootpath' not specified for debug request") + print('\nTesting "initialize" request:') + print(f' Root = "{args.debug_rootpath}"') + server.serve_initialize({"params": {"rootPath": args.debug_rootpath}}) + + separator() + if len(server.post_messages) == 0: + print(" Successful!") + else: + print(" Successful with errors:") + for message in server.post_messages: + print(f" {message[1]}") + print("\n Source directories:") + for source_dir in server.source_dirs: + print(f" {source_dir}") + + +def debug_diagnostics(args, server): + print('\nTesting "textDocument/publishDiagnostics" request:') + check_request_params(args, loc_needed=False) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results, _ = server.get_diagnostics(args.debug_filepath) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + separator() + return + + sev_map = ["ERROR", "WARNING", "INFO"] + if len(results) == 0: + print("No errors or warnings") + else: + print("Reported Diagnostics:") + for diag in results: + sline = diag["range"]["start"]["line"] + message = diag["message"] + sev = sev_map[diag["severity"] - 1] + print(f' {sline:5d}:{sev} "{message}"') + + +def debug_symbols(args, server): + print('\nTesting "textDocument/documentSymbol" request:') + check_request_params(args, loc_needed=False) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_document_symbols( + {"params": {"textDocument": {"uri": args.debug_filepath}}} + ) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for symbol in results: + sline = symbol["location"]["range"]["start"]["line"] + if "containerName" in symbol: + parent = symbol["containerName"] + else: + parent = "null" + print( + f" line {sline:5d} symbol -> " + f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" + ) + + +def debug_workspace_symbols(args, server): + print('\nTesting "workspace/symbol" request:') + if args.debug_rootpath is None: + raise DebugError("'debug_rootpath' not specified for debug request") + results = server.serve_workspace_symbol( + {"params": {"query": args.debug_workspace_symbols}} + ) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for symbol in results: + path = path_from_uri(symbol["location"]["uri"]) + sline = symbol["location"]["range"]["start"]["line"] + parent = "null" + if "containerName" in symbol: + parent = symbol["containerName"] + print( + f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " + f"{os.path.relpath(path, args.debug_rootpath)}" + ) + + +def debug_completion(args, server): + print('\nTesting "textDocument/completion" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_autocomplete( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for obj in results: + print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") + + +def debug_hover(args, server): + print('\nTesting "textDocument/hover" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_hover( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No result found!") + return + + print(" Result:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + contents = results["contents"] + if isinstance(contents, dict): + print(contents["value"]) + else: + print(contents) + + +def debug_signature(args, server): + print('\nTesting "textDocument/signatureHelp" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_signature( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No Results!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + active_param = results.get("activeParameter", 0) + print(f" Active param = {active_param}") + active_signature = results.get("activeSignature", 0) + print(f" Active sig = {active_signature}") + for i, signature in enumerate(results["signatures"]): + print(f" {signature['label']}") + for j, obj in enumerate(signature["parameters"]): + if (i == active_signature) and (j == active_param): + active_mark = "*" + else: + active_mark = " " + arg_desc = obj.get("documentation") + if arg_desc is not None: + print(f"{active_mark} {arg_desc} :: {obj['label']}") + else: + print(f"{active_mark} {obj['label']}") + + +def debug_definition(args, server): + print('\nTesting "textDocument/definition" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_definition( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No result found!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') + + +def debug_references(args, server): + print('\nTesting "textDocument/references" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_references( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No result found!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for result in results: + print( + f" {result['uri']} ({result['range']['start']['line'] + 1}" + f", {result['range']['start']['character'] + 1})" + ) + + +def debug_implementation(args, server): + print('\nTesting "textDocument/implementation" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_implementation( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } + } + ) + + separator() + if results is None: + print(" No result found!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') + + +def debug_rename(args, server): + print('\nTesting "textDocument/rename" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_rename( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + "newName": args.debug_rename, + } + } + ) + + separator() + if results is None: + print(" No changes found!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for uri, changes in results["changes"].items(): + path = path_from_uri(uri) + file_obj = server.workspace.get(path) + if file_obj is not None: + file_contents = file_obj.contents_split + process_file_changes(path, changes, file_contents) + else: + print(f'Unknown file: "{path}"') + + +def process_file_changes(file_path, changes, file_contents): + print(f'File: "{file_path}"') + for change in changes: + start_line = change["range"]["start"]["line"] + end_line = change["range"]["end"]["line"] + start_col = change["range"]["start"]["character"] + end_col = change["range"]["end"]["character"] + print(f" {start_line + 1}, {end_line + 1}") + new_contents = [] + for i in range(start_line, end_line + 1): + line = file_contents[i] + print(f" - {line}") + line_content = line + if i == start_line: + line_content = line[:start_col] + change["newText"] + if i == end_line: + line_content += line[end_col:] + new_contents.append(line_content) + for line in new_contents: + print(f" + {line}") + print() + + +def debug_actions(args, server): + print('\nTesting "textDocument/getActions" request:') + check_request_params(args) + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results = server.serve_codeActions( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "range": { + "start": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + "end": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + }, + } + } + ) + + separator() + pp = pprint.PrettyPrinter(indent=2, width=120) + if results is None: + print(" No actions found!") + return + + print(" Results:") + if args.debug_full_result: + print(json.dumps(results, indent=2)) + return + + for result in results: + print(f"Kind = '{result['kind']}', Title = '{result['title']}'") + for edit_uri, edit_change in result["edit"]["changes"].items(): + print(f"\nChange: URI = '{edit_uri}'") + pp.pprint(edit_change) + print() + + +def debug_server_parser(args): + """Debug the parser of the Language Server + Triggered by `--debug_parser` option. + + Parameters + ---------- + args : Namespace + The arguments parsed from the `ArgumentParser` + """ + + def locate_config(root: str) -> str | None: + default_conf_files = [args.config, ".fortlsrc", ".fortls.json", ".fortls"] + present_conf_files = [ + os.path.isfile(os.path.join(root, f)) for f in default_conf_files + ] + if not any(present_conf_files): + return None + + # Load the first config file found + for f, present in zip(default_conf_files, present_conf_files): + if not present: + continue + config_path = os.path.join(root, f) + return config_path + + def read_config(root: str | None): + pp_suffixes = None + pp_defs = {} + include_dirs = set() + if root is None: + return pp_suffixes, pp_defs, include_dirs + + # Check for config files + config_path = locate_config(root) + if not os.path.isfile(config_path): + return pp_suffixes, pp_defs, include_dirs + + try: + with open(config_path, encoding="utf-8") as fhandle: + config_dict = json.load(fhandle) + pp_suffixes = config_dict.get("pp_suffixes", None) + pp_defs = config_dict.get("pp_defs", {}) + for path in config_dict.get("include_dirs", set()): + include_dirs.update(only_dirs(resolve_globs(path, root))) + + if isinstance(pp_defs, list): + pp_defs = {key: "" for key in pp_defs} + except ValueError as e: + print(f"Error {e} while parsing '{config_path}' settings file") + + return pp_suffixes, pp_defs, include_dirs + + print("\nTesting parser") + separator() + + ensure_file_accessible(args.debug_filepath) + pp_suffixes, pp_defs, include_dirs = read_config(args.debug_rootpath) + + print(f' File = "{args.debug_filepath}"') + file_obj = FortranFile(args.debug_filepath, pp_suffixes) + err_str, _ = file_obj.load_from_disk() + if err_str: + raise DebugError(f"Reading file failed: {err_str}") + print(f" Detected format: {'fixed' if file_obj.fixed else 'free'}") + print("\n" + "=" * 80 + "\nParser Output\n" + "=" * 80 + "\n") + file_ast = file_obj.parse(debug=True, pp_defs=pp_defs, include_dirs=include_dirs) + print("\n" + "=" * 80 + "\nObject Tree\n" + "=" * 80 + "\n") + for obj in file_ast.get_scopes(): + print(f"{obj.get_type()}: {obj.FQSN}") + print_children(obj) + print("\n" + "=" * 80 + "\nExportable Objects\n" + "=" * 80 + "\n") + for _, obj in file_ast.global_dict.items(): + print(f"{obj.get_type()}: {obj.FQSN}") + separator() + + +def ensure_file_accessible(filepath: str): + """Ensure the file exists and is accessible, raising an error if not.""" + if not os.path.isfile(filepath): + raise DebugError(f"File '{filepath}' does not exist or is not accessible") + print(f' File = "{filepath}"') + + +def check_request_params(args, loc_needed=True): + ensure_file_accessible(args.debug_filepath) + if loc_needed: + if args.debug_line is None: + raise ParameterError("'debug_line' not specified for debug request") + print(f" Line = {args.debug_line}") + if args.debug_char is None: + raise ParameterError("'debug_char' not specified for debug request") + print(f" Char = {args.debug_char}\n") + + +def print_children(obj, indent=""): + for child in obj.get_children(): + print(f" {indent}{child.get_type()}: {child.FQSN}") + print_children(child, indent + " ") + + +def separator(): + print("=" * 80) From bf25f51708e410ee10353733d5b4b63816d92351 Mon Sep 17 00:00:00 2001 From: gnikit Date: Thu, 25 Apr 2024 23:50:33 +0100 Subject: [PATCH 15/17] refactor: swap `json` with `json5` package for debugging --- fortls/debug.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/fortls/debug.py b/fortls/debug.py index 121d47e0..9bf547ae 100644 --- a/fortls/debug.py +++ b/fortls/debug.py @@ -1,9 +1,10 @@ from __future__ import annotations -import json import os import pprint +import json5 + from .helper_functions import only_dirs, resolve_globs from .jsonrpc import JSONRPC2Connection, ReadWriter, path_from_uri from .langserver import LangServer @@ -96,7 +97,7 @@ def debug_diagnostics(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) separator() return @@ -127,7 +128,7 @@ def debug_symbols(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for symbol in results: @@ -157,7 +158,7 @@ def debug_workspace_symbols(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for symbol in results: @@ -195,7 +196,7 @@ def debug_completion(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for obj in results: @@ -225,7 +226,7 @@ def debug_hover(args, server): print(" Result:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return contents = results["contents"] @@ -258,7 +259,7 @@ def debug_signature(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return active_param = results.get("activeParameter", 0) @@ -302,7 +303,7 @@ def debug_definition(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return print(f' URI = "{results["uri"]}"') @@ -333,7 +334,7 @@ def debug_references(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for result in results: @@ -366,7 +367,7 @@ def debug_implementation(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return print(f' URI = "{results["uri"]}"') @@ -398,7 +399,7 @@ def debug_rename(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for uri, changes in results["changes"].items(): @@ -464,7 +465,7 @@ def debug_actions(args, server): print(" Results:") if args.debug_full_result: - print(json.dumps(results, indent=2)) + print(json5.dumps(results, indent=2)) return for result in results: @@ -486,7 +487,7 @@ def debug_server_parser(args): """ def locate_config(root: str) -> str | None: - default_conf_files = [args.config, ".fortlsrc", ".fortls.json", ".fortls"] + default_conf_files = [args.config, ".fortlsrc", ".fortls.json5", ".fortls"] present_conf_files = [ os.path.isfile(os.path.join(root, f)) for f in default_conf_files ] @@ -514,7 +515,7 @@ def read_config(root: str | None): try: with open(config_path, encoding="utf-8") as fhandle: - config_dict = json.load(fhandle) + config_dict = json5.load(fhandle) pp_suffixes = config_dict.get("pp_suffixes", None) pp_defs = config_dict.get("pp_defs", {}) for path in config_dict.get("include_dirs", set()): From 2c819194e44d1a8630dd4457a22de887e345516f Mon Sep 17 00:00:00 2001 From: gnikit Date: Fri, 26 Apr 2024 01:33:08 +0100 Subject: [PATCH 16/17] refactor: improve code reusability in debug module Now LSP requests reuse the core code for debugging, while only feeding the request itself and the its results' formatting. --- fortls/debug.py | 578 ++++++++++++++++++++++-------------------------- 1 file changed, 264 insertions(+), 314 deletions(-) diff --git a/fortls/debug.py b/fortls/debug.py index 9bf547ae..eefecd38 100644 --- a/fortls/debug.py +++ b/fortls/debug.py @@ -85,331 +85,301 @@ def debug_rootpath(args, server): def debug_diagnostics(args, server): - print('\nTesting "textDocument/publishDiagnostics" request:') - check_request_params(args, loc_needed=False) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results, _ = server.get_diagnostics(args.debug_filepath) - - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - separator() - return - - sev_map = ["ERROR", "WARNING", "INFO"] - if len(results) == 0: - print("No errors or warnings") - else: - print("Reported Diagnostics:") - for diag in results: - sline = diag["range"]["start"]["line"] - message = diag["message"] - sev = sev_map[diag["severity"] - 1] - print(f' {sline:5d}:{sev} "{message}"') - - -def debug_symbols(args, server): - print('\nTesting "textDocument/documentSymbol" request:') - check_request_params(args, loc_needed=False) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_document_symbols( - {"params": {"textDocument": {"uri": args.debug_filepath}}} + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + results, _ = server.get_diagnostics(args.debug_filepath) + return results + + def format_results(results, _): + sev_map = ["ERROR", "WARNING", "INFO"] + if len(results) == 0: + print("No errors or warnings") + else: + print("Reported Diagnostics:") + for diag in results: + sline = diag["range"]["start"]["line"] + message = diag["message"] + sev = sev_map[diag["severity"] - 1] + print(f' {sline:5d}:{sev} "{message}"') + + debug_generic( + args, + "textDocument/publishDiagnostics", + lsp_request, + format_results, + loc_needed=False, ) - separator() - if results is None: - print(" No results!") - return - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return - - for symbol in results: - sline = symbol["location"]["range"]["start"]["line"] - if "containerName" in symbol: - parent = symbol["containerName"] - else: - parent = "null" - print( - f" line {sline:5d} symbol -> " - f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" +def debug_symbols(args, server): + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_document_symbols( + {"params": {"textDocument": {"uri": args.debug_filepath}}} ) - -def debug_workspace_symbols(args, server): - print('\nTesting "workspace/symbol" request:') - if args.debug_rootpath is None: - raise DebugError("'debug_rootpath' not specified for debug request") - results = server.serve_workspace_symbol( - {"params": {"query": args.debug_workspace_symbols}} + def format_results(results, _): + for symbol in results: + sline = symbol["location"]["range"]["start"]["line"] + parent = "null" + if "containerName" in symbol: + parent = symbol["containerName"] + print( + f" line {sline:5d} symbol -> " + f"{symbol['kind']:3d}:{symbol['name']:30} parent = {parent}" + ) + + debug_generic( + args, + "textDocument/documentSymbol", + lsp_request, + format_results, + loc_needed=False, ) - separator() - if results is None: - print(" No results!") - return - - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return - for symbol in results: - path = path_from_uri(symbol["location"]["uri"]) - sline = symbol["location"]["range"]["start"]["line"] - parent = "null" - if "containerName" in symbol: - parent = symbol["containerName"] - print( - f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " - f"{os.path.relpath(path, args.debug_rootpath)}" +def debug_workspace_symbols(args, server): + def lsp_request(): + if args.debug_rootpath is None: + raise DebugError("'debug_rootpath' not specified for debug request") + return server.serve_workspace_symbol( + {"params": {"query": args.debug_workspace_symbols}} ) + def format_results(results, args): + for symbol in results: + path = path_from_uri(symbol["location"]["uri"]) + sline = symbol["location"]["range"]["start"]["line"] + parent = "null" + if "containerName" in symbol: + parent = symbol["containerName"] + print( + f" {parent}::{sline} symbol -> {symbol['name']:30} parent = " + f"{os.path.relpath(path, args.debug_rootpath)}" + ) + + debug_generic( + args, + "workspace/symbol", + lsp_request, + format_results, + loc_needed=False, + ) + def debug_completion(args, server): - print('\nTesting "textDocument/completion" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_autocomplete( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_autocomplete( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) - - separator() - if results is None: - print(" No results!") - return + ) - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + def format_results(results, _): + for obj in results: + print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") - for obj in results: - print(f" {obj['kind']}: {obj['label']} -> {obj['detail']}") + debug_generic(args, "textDocument/completion", lsp_request, format_results) def debug_hover(args, server): - print('\nTesting "textDocument/hover" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_hover( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_hover( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) - - separator() - if results is None: - print(" No result found!") - return + ) - print(" Result:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + def format_results(results, _): + contents = results["contents"] + if isinstance(contents, dict): + print(contents["value"]) + else: + print(contents) - contents = results["contents"] - if isinstance(contents, dict): - print(contents["value"]) - else: - print(contents) + debug_generic(args, "textDocument/hover", lsp_request, format_results) def debug_signature(args, server): - print('\nTesting "textDocument/signatureHelp" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_signature( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_signature( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) - - separator() - if results is None: - print(" No Results!") - return - - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + ) - active_param = results.get("activeParameter", 0) - print(f" Active param = {active_param}") - active_signature = results.get("activeSignature", 0) - print(f" Active sig = {active_signature}") - for i, signature in enumerate(results["signatures"]): - print(f" {signature['label']}") - for j, obj in enumerate(signature["parameters"]): - if (i == active_signature) and (j == active_param): - active_mark = "*" - else: + def format_results(results, _): + active_param = results.get("activeParameter", 0) + print(f" Active param = {active_param}") + active_signature = results.get("activeSignature", 0) + print(f" Active sig = {active_signature}") + for i, signature in enumerate(results["signatures"]): + print(f" {signature['label']}") + for j, obj in enumerate(signature["parameters"]): active_mark = " " - arg_desc = obj.get("documentation") - if arg_desc is not None: - print(f"{active_mark} {arg_desc} :: {obj['label']}") - else: - print(f"{active_mark} {obj['label']}") + if (i == active_signature) and (j == active_param): + active_mark = "*" + arg_desc = obj.get("documentation") + if arg_desc is not None: + print(f"{active_mark} {arg_desc} :: {obj['label']}") + else: + print(f"{active_mark} {obj['label']}") + + debug_generic(args, "textDocument/signatureHelp", lsp_request, format_results) def debug_definition(args, server): - print('\nTesting "textDocument/definition" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_definition( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_definition( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) - - separator() - if results is None: - print(" No result found!") - return + ) - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + def format_results(results, _): + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') + debug_generic(args, "textDocument/definition", lsp_request, format_results) def debug_references(args, server): - print('\nTesting "textDocument/references" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_references( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_references( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) + ) - separator() - if results is None: - print(" No result found!") - return + def format_results(results, _): + for result in results: + print( + f" {result['uri']} ({result['range']['start']['line'] + 1}" + f", {result['range']['start']['character'] + 1})" + ) - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return - - for result in results: - print( - f" {result['uri']} ({result['range']['start']['line'] + 1}" - f", {result['range']['start']['character'] + 1})" - ) + debug_generic(args, "textDocument/references", lsp_request, format_results) def debug_implementation(args, server): - print('\nTesting "textDocument/implementation" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_implementation( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_implementation( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + } } - } - ) - - separator() - if results is None: - print(" No result found!") - return + ) - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + def format_results(results, _): + print(f' URI = "{results["uri"]}"') + print(f' Line = {results["range"]["start"]["line"] + 1}') + print(f' Char = {results["range"]["start"]["character"] + 1}') - print(f' URI = "{results["uri"]}"') - print(f' Line = {results["range"]["start"]["line"] + 1}') - print(f' Char = {results["range"]["start"]["character"] + 1}') + debug_generic(args, "textDocument/implementation", lsp_request, format_results) def debug_rename(args, server): - print('\nTesting "textDocument/rename" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_rename( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "position": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - "newName": args.debug_rename, + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_rename( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "position": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + "newName": args.debug_rename, + } } - } - ) + ) - separator() - if results is None: - print(" No changes found!") - return + def format_results(results, _): + for uri, changes in results["changes"].items(): + path = path_from_uri(uri) + file_obj = server.workspace.get(path) + if file_obj is not None: + file_contents = file_obj.contents_split + process_file_changes(path, changes, file_contents) + else: + print(f'Unknown file: "{path}"') - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return + debug_generic(args, "textDocument/rename", lsp_request, format_results) - for uri, changes in results["changes"].items(): - path = path_from_uri(uri) - file_obj = server.workspace.get(path) - if file_obj is not None: - file_contents = file_obj.contents_split - process_file_changes(path, changes, file_contents) - else: - print(f'Unknown file: "{path}"') + +def debug_actions(args, server): + def lsp_request(): + server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) + return server.serve_codeActions( + { + "params": { + "textDocument": {"uri": args.debug_filepath}, + "range": { + "start": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + "end": { + "line": args.debug_line - 1, + "character": args.debug_char - 1, + }, + }, + } + } + ) + + def process_results(results, _): + pp = pprint.PrettyPrinter(indent=2, width=120) + for result in results: + print(f"Kind = '{result['kind']}', Title = '{result['title']}'") + for edit_uri, edit_change in result["edit"]["changes"].items(): + print(f"\nChange: URI = '{edit_uri}'") + pp.pprint(edit_change) + print() + + debug_generic(args, "textDocument/getActions", lsp_request, process_results) def process_file_changes(file_path, changes, file_contents): @@ -435,47 +405,6 @@ def process_file_changes(file_path, changes, file_contents): print() -def debug_actions(args, server): - print('\nTesting "textDocument/getActions" request:') - check_request_params(args) - server.serve_onSave({"params": {"textDocument": {"uri": args.debug_filepath}}}) - results = server.serve_codeActions( - { - "params": { - "textDocument": {"uri": args.debug_filepath}, - "range": { - "start": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - "end": { - "line": args.debug_line - 1, - "character": args.debug_char - 1, - }, - }, - } - } - ) - - separator() - pp = pprint.PrettyPrinter(indent=2, width=120) - if results is None: - print(" No actions found!") - return - - print(" Results:") - if args.debug_full_result: - print(json5.dumps(results, indent=2)) - return - - for result in results: - print(f"Kind = '{result['kind']}', Title = '{result['title']}'") - for edit_uri, edit_change in result["edit"]["changes"].items(): - print(f"\nChange: URI = '{edit_uri}'") - pp.pprint(edit_change) - print() - - def debug_server_parser(args): """Debug the parser of the Language Server Triggered by `--debug_parser` option. @@ -570,6 +499,27 @@ def check_request_params(args, loc_needed=True): print(f" Char = {args.debug_char}\n") +def debug_generic(args, test_label, lsp_request, format_results, loc_needed=True): + print(f'\nTesting "{test_label}" request:') + check_request_params(args, loc_needed) + results = lsp_request() + separator() + print_results(results, format_results, args) + + +def print_results(results, format_results, args): + """Helper function to print results based on detail level requested.""" + if results is None: + print(" No result found!") + return + + if args.debug_full_result: + print(json5.dumps(results, indent=2)) + return + + format_results(results, args) + + def print_children(obj, indent=""): for child in obj.get_children(): print(f" {indent}{child.get_type()}: {child.FQSN}") From 13abbcec4637d6ca8cc3196548027d6823eb2da4 Mon Sep 17 00:00:00 2001 From: gnikit Date: Fri, 26 Apr 2024 01:35:46 +0100 Subject: [PATCH 17/17] refactor: rename debug_server_parser to debug_parser --- fortls/__init__.py | 4 ++-- fortls/debug.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fortls/__init__.py b/fortls/__init__.py index 584025d1..4405fd3f 100644 --- a/fortls/__init__.py +++ b/fortls/__init__.py @@ -3,7 +3,7 @@ import sys from multiprocessing import freeze_support -from .debug import DebugError, debug_lsp, debug_server_parser, is_debug_mode +from .debug import DebugError, debug_lsp, debug_parser, is_debug_mode from .interface import cli from .jsonrpc import JSONRPC2Connection, ReadWriter from .langserver import LangServer @@ -18,7 +18,7 @@ def main(): try: if args.debug_parser: - debug_server_parser(args) + debug_parser(args) elif is_debug_mode(args): debug_lsp(args, vars(args)) diff --git a/fortls/debug.py b/fortls/debug.py index eefecd38..b23a57e1 100644 --- a/fortls/debug.py +++ b/fortls/debug.py @@ -405,7 +405,7 @@ def process_file_changes(file_path, changes, file_contents): print() -def debug_server_parser(args): +def debug_parser(args): """Debug the parser of the Language Server Triggered by `--debug_parser` option.