Skip to content

Commit c15a1be

Browse files
author
Dionysios Grapsas
committed
black formatting
1 parent 0a00200 commit c15a1be

16 files changed

+258
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ An example for a Configuration file is given below
123123
"lowercase_intrinsics": true,
124124
"hover_signature": true,
125125
"use_signature_help": true,
126+
"folding_range": true,
126127
"excl_paths": ["tests/**", "tools/**"],
127128
"excl_suffixes": ["_skip.f90"],
128129
"include_dirs": ["include/**"],

docs/options.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ All the ``fortls`` settings with their default arguments can be found below
6666
"hover_signature": false,
6767
"hover_language": "fortran90",
6868
69+
"folding_range": false,
70+
6971
"max_line_length": -1,
7072
"max_comment_line_length": -1,
7173
"disable_diagnostics": false,

fortls/fortls.schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@
135135
"title": "Hover Language",
136136
"type": "string"
137137
},
138+
"folding_range": {
139+
"default": false,
140+
"description": "Fold editor based on language keywords",
141+
"title": "Folding Range",
142+
"type": "boolean"
143+
},
138144
"max_line_length": {
139145
"default": -1,
140146
"description": "Maximum line length (default: -1)",

fortls/interface.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,13 @@ def cli(name: str = "fortls") -> argparse.ArgumentParser:
210210
),
211211
)
212212

213+
# Folding range ------------------------------------------------------------
214+
group.add_argument(
215+
"--folding_range",
216+
action="store_true",
217+
help="Fold editor based on language keywords",
218+
)
219+
213220
# Diagnostic options -------------------------------------------------------
214221
group = parser.add_argument_group("Diagnostic options (error swigles)")
215222
group.add_argument(

fortls/langserver.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ def noop(request: dict):
151151
"textDocument/didClose": self.serve_onClose,
152152
"textDocument/didChange": self.serve_onChange,
153153
"textDocument/codeAction": self.serve_codeActions,
154+
"textDocument/foldingRange": self.serve_folding_range,
154155
"initialized": noop,
155156
"workspace/didChangeWatchedFiles": noop,
156157
"workspace/didChangeConfiguration": noop,
@@ -226,6 +227,7 @@ def serve_initialize(self, request: dict):
226227
"renameProvider": True,
227228
"workspaceSymbolProvider": True,
228229
"textDocumentSync": self.sync_type,
230+
"foldingRangeProvider": True,
229231
}
230232
if self.use_signature_help:
231233
server_capabilities["signatureHelpProvider"] = {
@@ -1250,6 +1252,56 @@ def serve_codeActions(self, request: dict):
12501252
action["diagnostics"] = new_diags
12511253
return action_list
12521254

1255+
def serve_folding_range(self, request: dict):
1256+
# Get parameters from request
1257+
params: dict = request["params"]
1258+
uri: str = params["textDocument"]["uri"]
1259+
path = path_from_uri(uri)
1260+
# Find object
1261+
file_obj = self.workspace.get(path)
1262+
if file_obj is None:
1263+
return None
1264+
if file_obj.ast is None:
1265+
return None
1266+
else:
1267+
folding_start = file_obj.ast.folding_start
1268+
folding_end = file_obj.ast.folding_end
1269+
if (
1270+
folding_start is None
1271+
or folding_end is None
1272+
or len(folding_start) != len(folding_end)
1273+
):
1274+
return None
1275+
# Construct folding_rage list
1276+
folding_ranges = []
1277+
# First treating scope objects...
1278+
for scope in file_obj.ast.scope_list:
1279+
n_mlines = len(scope.mlines)
1280+
# ...with intermediate folding lines (if, select)...
1281+
if n_mlines > 0:
1282+
self.add_range(folding_ranges, scope.sline - 1, scope.mlines[0] - 2)
1283+
for i in range(1, n_mlines):
1284+
self.add_range(
1285+
folding_ranges, scope.mlines[i - 1] - 1, scope.mlines[i] - 2
1286+
)
1287+
self.add_range(folding_ranges, scope.mlines[-1] - 1, scope.eline - 2)
1288+
# ...and without
1289+
else:
1290+
self.add_range(folding_ranges, scope.sline - 1, scope.eline - 2)
1291+
# Then treat comment blocks
1292+
folds = len(folding_start)
1293+
for i in range(0, folds):
1294+
self.add_range(folding_ranges, folding_start[i] - 1, folding_end[i] - 1)
1295+
1296+
return folding_ranges
1297+
1298+
def add_range(self, folding_ranges: list, start: int, end: int):
1299+
folding_range = {
1300+
"startLine": start,
1301+
"endLine": end,
1302+
}
1303+
folding_ranges.append(folding_range)
1304+
12531305
def send_diagnostics(self, uri: str):
12541306
diag_results, diag_exp = self.get_diagnostics(uri)
12551307
if diag_results is not None:
@@ -1621,6 +1673,9 @@ def _load_config_file_general(self, config_dict: dict) -> None:
16211673
self.hover_signature = config_dict.get("hover_signature", self.hover_signature)
16221674
self.hover_language = config_dict.get("hover_language", self.hover_language)
16231675

1676+
# Folding range --------------------------------------------------------
1677+
self.folding_range = config_dict.get("folding_range", self.folding_range)
1678+
16241679
# Diagnostic options ---------------------------------------------------
16251680
self.max_line_length = config_dict.get("max_line_length", self.max_line_length)
16261681
self.max_comment_line_length = config_dict.get(

fortls/parsers/internal/ast.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def __init__(self, file_obj=None):
3434
self.inherit_objs: list = []
3535
self.linkable_objs: list = []
3636
self.external_objs: list = []
37+
self.folding_start: list = []
38+
self.folding_end: list = []
39+
self.comment_block_start = 0
40+
self.comment_block_end = 0
3741
self.none_scope = None
3842
self.inc_scope = None
3943
self.current_scope = None

fortls/parsers/internal/parser.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,20 @@ def parse(
13051305
line = multi_lines.pop()
13061306
get_full = False
13071307

1308+
# Add comment blocks to folding patterns
1309+
if FRegex.FREE_COMMENT.match(line) is not None:
1310+
if file_ast.comment_block_start == 0:
1311+
file_ast.comment_block_start = line_no
1312+
else:
1313+
file_ast.comment_block_end = line_no
1314+
elif file_ast.comment_block_start != 0:
1315+
# Only fold consecutive comment lines
1316+
if file_ast.comment_block_end > file_ast.comment_block_start + 1:
1317+
file_ast.folding_start.append(file_ast.comment_block_start)
1318+
file_ast.folding_end.append(line_no - 1)
1319+
file_ast.comment_block_end = 0
1320+
file_ast.comment_block_start = 0
1321+
13081322
if line == "":
13091323
continue # Skip empty lines
13101324

@@ -1335,6 +1349,10 @@ def parse(
13351349
# Need to keep the line number for registering start of Scopes
13361350
line_no_end += len(post_lines)
13371351
line = "".join([line] + post_lines)
1352+
# Add multilines to folding blocks
1353+
if line_no != line_no_end:
1354+
file_ast.folding_start.append(line_no)
1355+
file_ast.folding_end.append(line_no_end)
13381356
line, line_label = strip_line_label(line)
13391357
line_stripped = strip_strings(line, maintain_len=True)
13401358
# Find trailing comments
@@ -1353,6 +1371,22 @@ def parse(
13531371
line_no_comment = line
13541372
# Test for scope end
13551373
if file_ast.end_scope_regex is not None:
1374+
# treat intermediate folding lines in scopes if they exist
1375+
if (
1376+
file_ast.end_scope_regex == FRegex.END_IF
1377+
and FRegex.ELSE_IF.match(line_no_comment) is not None
1378+
):
1379+
self.update_scope_mlist(file_ast, "#IF", line_no)
1380+
elif (
1381+
file_ast.end_scope_regex == FRegex.END_SELECT
1382+
and (
1383+
FRegex.SELECT_CASE.match(line_no_comment)
1384+
or FRegex.SELECT_DEFAULT.match(line_no_comment)
1385+
)
1386+
is not None
1387+
):
1388+
self.update_scope_mlist(file_ast, "#SELECT", line_no)
1389+
13561390
match = FRegex.END_WORD.match(line_no_comment)
13571391
# Handle end statement
13581392
if self.parse_end_scope_word(line_no_comment, line_no, file_ast, match):
@@ -1488,6 +1522,8 @@ def parse(
14881522
keywords=keywords,
14891523
)
14901524
file_ast.add_scope(new_sub, FRegex.END_SUB)
1525+
if line_no != line_no_end:
1526+
file_ast.scope_list[-1].mlines.append(line_no_end)
14911527
log.debug("%s !!! SUBROUTINE - Ln:%d", line, line_no)
14921528

14931529
elif obj_type == "fun":
@@ -1684,6 +1720,20 @@ def parse(
16841720
log.debug("%s: %s", error["range"], error["message"])
16851721
return file_ast
16861722

1723+
def update_scope_mlist(
1724+
self, file_ast: FortranAST, scope_name_prefix: str, line_no: int
1725+
):
1726+
"""Find the last unclosed scope (eline == sline) containing the
1727+
scope_name_prefix and add update its nb of intermediate lines (mlines)"""
1728+
1729+
i = 1
1730+
while True:
1731+
scope = file_ast.scope_list[-i]
1732+
if (scope_name_prefix in scope.name) and (scope.eline == scope.sline):
1733+
scope.mlines.append(line_no)
1734+
return
1735+
i += 1
1736+
16871737
def parse_imp_dim(self, line: str):
16881738
"""Parse the implicit dimension of an array e.g.
16891739
var(3,4), var_name(size(val,1)*10)

fortls/parsers/internal/scope.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(
3838
self.file_ast: FortranAST = file_ast
3939
self.sline: int = line_number
4040
self.eline: int = line_number
41+
self.mlines: list = []
4142
self.name: str = name
4243
self.children: list[T[Scope]] = []
4344
self.members: list = []

fortls/regex_patterns.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ class FortranRegularExpressions:
146146
r" |MODULE|PROGRAM|SUBROUTINE|FUNCTION|PROCEDURE|TYPE|DO|IF|SELECT)?",
147147
I,
148148
)
149+
ELSE_IF: Pattern = compile(r"(^|.*\s)(ELSE$|ELSE(\s)|ELSEIF(\s*\())", I)
150+
SELECT_CASE: Pattern = compile(r"((^|\s*\s)(CASE)(\s*\())", I)
149151
# Object regex patterns
150152
CLASS_VAR: Pattern = compile(r"(TYPE|CLASS)[ ]*\(", I)
151153
DEF_KIND: Pattern = compile(r"(\w*)[ ]*\((?:KIND|LEN)?[ =]*(\w*)", I)

test/test_server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def check_return(result_array):
181181
["test_free", 2, 0],
182182
["test_gen_type", 5, 1],
183183
["test_generic", 2, 0],
184+
["test_if_folding", 2, 0],
184185
["test_inherit", 2, 0],
185186
["test_int", 2, 0],
186187
["test_mod", 2, 0],

0 commit comments

Comments
 (0)