Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version to test #416

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ An example for a Configuration file is given below
"incremental_sync": true,
"lowercase_intrinsics": true,
"hover_signature": true,
"folding_range": true,
"use_signature_help": true,
"excl_paths": ["tests/**", "tools/**"],
"excl_suffixes": ["_skip.f90"],
Expand Down
2 changes: 2 additions & 0 deletions docs/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ All the ``fortls`` settings with their default arguments can be found below
"hover_signature": false,
"hover_language": "fortran90",

"folding_range": false

"max_line_length": -1,
"max_comment_line_length": -1,
"disable_diagnostics": false,
Expand Down
6 changes: 6 additions & 0 deletions fortls/fortls.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@
"title": "Hover Language",
"type": "string"
},
"folding_range": {
"title": "Folding Range",
"description": "Fold editor based on language keywords",
"default": false,
"type": "boolean"
},
"max_line_length": {
"default": -1,
"description": "Maximum line length (default: -1)",
Expand Down
7 changes: 7 additions & 0 deletions fortls/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,13 @@ def cli(name: str = "fortls") -> argparse.ArgumentParser:
),
)

# Folding range ------------------------------------------------------------
group.add_argument(
"--folding_range",
action="store_true",
help="Fold editor based on language keywords",
)

# Diagnostic options -------------------------------------------------------
group = parser.add_argument_group("Diagnostic options (error swigles)")
group.add_argument(
Expand Down
55 changes: 55 additions & 0 deletions fortls/langserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def noop(request: dict):
"textDocument/hover": self.serve_hover,
"textDocument/implementation": self.serve_implementation,
"textDocument/rename": self.serve_rename,
"textDocument/foldingRange": self.serve_folding_range,
"textDocument/didOpen": self.serve_onOpen,
"textDocument/didSave": self.serve_onSave,
"textDocument/didClose": self.serve_onClose,
Expand Down Expand Up @@ -226,6 +227,7 @@ def serve_initialize(self, request: dict):
"renameProvider": True,
"workspaceSymbolProvider": True,
"textDocumentSync": self.sync_type,
"foldingRangeProvider": True,
}
if self.use_signature_help:
server_capabilities["signatureHelpProvider"] = {
Expand Down Expand Up @@ -1224,6 +1226,56 @@ def serve_rename(self, request: dict):
)
return {"changes": changes}

def serve_folding_range(self, request: dict):
# Get parameters from request
params: dict = request["params"]
uri: str = params["textDocument"]["uri"]
path = path_from_uri(uri)
# Find object
file_obj = self.workspace.get(path)
if file_obj is None:
return None
if file_obj.ast is None:
return None
else:
folding_start = file_obj.ast.folding_start
folding_end = file_obj.ast.folding_end
if (
folding_start is None
or folding_end is None
or len(folding_start) != len(folding_end)
):
return None
# Construct folding_rage list
folding_ranges = []
# First treating scope objects...
for scope in file_obj.ast.scope_list:
n_mlines = len(scope.mlines)
# ...with intermediate folding lines (if, select)...
if n_mlines > 0:
self.add_range(folding_ranges, scope.sline - 1, scope.mlines[0] - 2)
for i in range(1, n_mlines):
self.add_range(
folding_ranges, scope.mlines[i - 1] - 1, scope.mlines[i] - 2
)
self.add_range(folding_ranges, scope.mlines[-1] - 1, scope.eline - 2)
# ...and without
else:
self.add_range(folding_ranges, scope.sline - 1, scope.eline - 2)
# Then treat comment blocks
folds = len(folding_start)
for i in range(0, folds):
self.add_range(folding_ranges, folding_start[i] - 1, folding_end[i] - 1)

return folding_ranges

def add_range(self, folding_ranges: list, start: int, end: int):
folding_range = {
"startLine": start,
"endLine": end,
}
folding_ranges.append(folding_range)

def serve_codeActions(self, request: dict):
params: dict = request["params"]
uri: str = params["textDocument"]["uri"]
Expand Down Expand Up @@ -1621,6 +1673,9 @@ def _load_config_file_general(self, config_dict: dict) -> None:
self.hover_signature = config_dict.get("hover_signature", self.hover_signature)
self.hover_language = config_dict.get("hover_language", self.hover_language)

# Folding range --------------------------------------------------------
self.folding_range = config_dict.get("folding_range", self.folding_range)

# Diagnostic options ---------------------------------------------------
self.max_line_length = config_dict.get("max_line_length", self.max_line_length)
self.max_comment_line_length = config_dict.get(
Expand Down
4 changes: 4 additions & 0 deletions fortls/parsers/internal/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ def __init__(self, file_obj=None):
self.inherit_objs: list = []
self.linkable_objs: list = []
self.external_objs: list = []
self.folding_start: list = []
self.folding_end: list = []
self.comment_block_start = 0
self.comment_block_end = 0
self.none_scope = None
self.inc_scope = None
self.current_scope = None
Expand Down
402 changes: 201 additions & 201 deletions fortls/parsers/internal/intrinsic.procedures.markdown.json

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions fortls/parsers/internal/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,20 @@ def parse(
line = multi_lines.pop()
get_full = False

# Add comment blocks to folding patterns
if FRegex.FREE_COMMENT.match(line) is not None:
if file_ast.comment_block_start == 0:
file_ast.comment_block_start = line_no
else:
file_ast.comment_block_end = line_no
elif file_ast.comment_block_start != 0:
# Only fold consecutive comment lines
if file_ast.comment_block_end > file_ast.comment_block_start + 1:
file_ast.folding_start.append(file_ast.comment_block_start)
file_ast.folding_end.append(line_no - 1)
file_ast.comment_block_end = 0
file_ast.comment_block_start = 0

if line == "":
continue # Skip empty lines

Expand Down Expand Up @@ -1335,6 +1349,10 @@ def parse(
# Need to keep the line number for registering start of Scopes
line_no_end += len(post_lines)
line = "".join([line] + post_lines)
# Add multilines to folding blocks
if line_no != line_no_end:
file_ast.folding_start.append(line_no)
file_ast.folding_end.append(line_no_end)
line, line_label = strip_line_label(line)
line_stripped = strip_strings(line, maintain_len=True)
# Find trailing comments
Expand All @@ -1353,6 +1371,18 @@ def parse(
line_no_comment = line
# Test for scope end
if file_ast.end_scope_regex is not None:
# treat intermediate folding lines in scopes if they exist
if (
file_ast.end_scope_regex == FRegex.END_IF
and FRegex.ELSE_IF.match(line_no_comment) is not None
):
self.update_scope_mlist(file_ast, "#IF", line_no)
elif (
file_ast.end_scope_regex == FRegex.END_SELECT
and FRegex.SELECT_CASE.match(line_no_comment) is not None
):
self.update_scope_mlist(file_ast, "#SELECT", line_no)

match = FRegex.END_WORD.match(line_no_comment)
# Handle end statement
if self.parse_end_scope_word(line_no_comment, line_no, file_ast, match):
Expand Down Expand Up @@ -1488,6 +1518,8 @@ def parse(
keywords=keywords,
)
file_ast.add_scope(new_sub, FRegex.END_SUB)
if line_no != line_no_end:
file_ast.scope_list[-1].mlines.append(line_no_end)
log.debug("%s !!! SUBROUTINE - Ln:%d", line, line_no)

elif obj_type == "fun":
Expand Down Expand Up @@ -1684,6 +1716,20 @@ def parse(
log.debug("%s: %s", error["range"], error["message"])
return file_ast

def update_scope_mlist(
self, file_ast: FortranAST, scope_name_prefix: str, line_no: int
):
"""Find the last unclosed scope (eline == sline) containing the
scope_name_prefix and add update its mlines"""

i = 1
while True:
scope = file_ast.scope_list[-i]
if (scope_name_prefix in scope.name) and (scope.eline == scope.sline):
scope.mlines.append(line_no)
return
i += 1

def parse_imp_dim(self, line: str):
"""Parse the implicit dimension of an array e.g.
var(3,4), var_name(size(val,1)*10)
Expand Down
1 change: 1 addition & 0 deletions fortls/parsers/internal/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def __init__(
keywords = []
self.file_ast: FortranAST = file_ast
self.sline: int = line_number
self.mlines: list = []
self.eline: int = line_number
self.name: str = name
self.children: list[T[Scope]] = []
Expand Down
4 changes: 4 additions & 0 deletions fortls/regex_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ class FortranRegularExpressions:
r" |MODULE|PROGRAM|SUBROUTINE|FUNCTION|PROCEDURE|TYPE|DO|IF|SELECT)?",
I,
)

ELSE_IF: Pattern = compile(r"(^|.*\s)(ELSE$|ELSE(\s)|ELSEIF(\s*\())", I)
SELECT_CASE: Pattern = compile(r"((^|\s*\s)(CASE)(\s*\())", I)

# Object regex patterns
CLASS_VAR: Pattern = compile(r"(TYPE|CLASS)[ ]*\(", I)
DEF_KIND: Pattern = compile(r"(\w*)[ ]*\((?:KIND|LEN)?[ =]*(\w*)", I)
Expand Down
1 change: 1 addition & 0 deletions test/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ def check_return(result_array):
["test_free", 2, 0],
["test_gen_type", 5, 1],
["test_generic", 2, 0],
["test_if_folding", 2, 0],
["test_inherit", 2, 0],
["test_int", 2, 0],
["test_mod", 2, 0],
Expand Down
57 changes: 57 additions & 0 deletions test/test_server_folding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from setup_tests import Path, run_request, test_dir, write_rpc_request


def folding_req(file_path: Path) -> str:
return write_rpc_request(
1,
"textDocument/foldingRange",
{"textDocument": {"uri": str(file_path)}},
)


def validate_folding(results: list, ref: list):
assert len(results) == len(ref)
for i in range(0, len(results)):
assert results[i] == ref[i]


def test_if_folding():
"""Test the ranges for several blocks are correct"""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir)})
file_path = test_dir / "subdir" / "test_if_folding.f90"
string += folding_req(file_path)
errcode, results = run_request(string)
assert errcode == 0
ref = [
{"startLine": 0, "endLine": 42},
{"startLine": 10, "endLine": 20},
{"startLine": 21, "endLine": 22},
{"startLine": 11, "endLine": 19},
{"startLine": 13, "endLine": 14},
{"startLine": 15, "endLine": 16},
{"startLine": 17, "endLine": 18},
{"startLine": 30, "endLine": 34},
{"startLine": 35, "endLine": 38},
{"startLine": 39, "endLine": 40},
{"startLine": 2, "endLine": 5},
{"startLine": 25, "endLine": 27},
{"startLine": 31, "endLine": 33},
]
validate_folding(results[1], ref)


def test_mline_sub_folding():
"""Test the ranges for several blocks are correct"""
string = write_rpc_request(1, "initialize", {"rootPath": str(test_dir)})
file_path = test_dir / "subdir" / "test_mline_sub_folding.f90"
string += folding_req(file_path)
errcode, results = run_request(string)
assert errcode == 0
ref = [
{"startLine": 0, "endLine": 3},
{"startLine": 4, "endLine": 14},
{"startLine": 0, "endLine": 4},
{"startLine": 6, "endLine": 9},
{"startLine": 12, "endLine": 13},
]
validate_folding(results[1], ref)
2 changes: 2 additions & 0 deletions test/test_source/f90_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"hover_signature": true,
"hover_language": "FortranFreeForm",

"folding_range": true,

"max_line_length": 80,
"max_comment_line_length": 80,
"disable_diagnostics": true,
Expand Down
1 change: 1 addition & 0 deletions test/test_source/pp/.pp_conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"use_signature_help": true,
"variable_hover": true,
"hover_signature": true,
"folding_range": true,
"enable_code_actions": true,
"pp_suffixes": [".h", ".F90"],
"incl_suffixes": [".h"],
Expand Down
44 changes: 44 additions & 0 deletions test/test_source/subdir/test_if_folding.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
program test_if_folding

! adding some comment lines
! to check is comment folding
! works
! as expected

implicit none
integer :: i, j

if (i > 0 .and. j > 0) then
if (i > j) then
j = j + 1
if (mod(j,100) == 0) then
print*, "j = ", j
else if (mod(j,100) < 50) then
print*, "j = ", j
else
print*, "j = ", j
end if
end if
else
print*, i-j
end if

if (i==0) &
i = 1; &
j = 2


if (j == i) then
! testing some
! comment lines
! right here
print*, "well done"
else if(.true.) then
print*, "missed something..."
print*, "something more"
! random comment here
else
print*, "something else"
end if

end program test_if_folding
16 changes: 16 additions & 0 deletions test/test_source/subdir/test_mline_sub_folding.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
subroutine too_many_args(one, two, &
three, &
four, &
five, &
six )

integer, intent(in) :: one, two,&
three, &
four, &
five
integer, intent(out) :: six

six = five + one + four - two + &
2*three

end subroutine too_many_args
Loading