Skip to content

Commit a2feac7

Browse files
committed
Moves functions to helper_functions
1 parent ca11365 commit a2feac7

File tree

6 files changed

+234
-220
lines changed

6 files changed

+234
-220
lines changed

fortls/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
PY3K = sys.version_info >= (3, 0)
44

5+
# Global variables
6+
sort_keywords = True
7+
58
# Keyword identifiers
69
KEYWORD_LIST = [
710
"pointer",

fortls/helper_functions.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import logging
2+
import os
3+
from pathlib import Path
4+
5+
from fortls.constants import KEYWORD_ID_DICT, KEYWORD_LIST, sort_keywords
16
from fortls.regex_patterns import (
27
DQ_STRING_REGEX,
38
FIXED_COMMENT_LINE_MATCH,
@@ -6,10 +11,13 @@
611
LOGICAL_REGEX,
712
NAT_VAR_REGEX,
813
NUMBER_REGEX,
14+
OBJBREAK_REGEX,
915
SQ_STRING_REGEX,
1016
WORD_REGEX,
1117
)
1218

19+
log = logging.getLogger(__name__)
20+
1321

1422
def expand_name(line, char_poss):
1523
"""Get full word containing given cursor position"""
@@ -124,3 +132,211 @@ def find_paren_match(test_str):
124132
if paren_count == 0:
125133
return i
126134
return ind
135+
136+
137+
def get_line_prefix(pre_lines: list, curr_line: str, col: int, qs: bool = True) -> str:
138+
"""Get code line prefix from current line and preceding continuation lines
139+
140+
Parameters
141+
----------
142+
pre_lines : list
143+
for multiline cases get all the previous, relevant lines
144+
curr_line : str
145+
the current line
146+
col : int
147+
column index of the current line
148+
qs : bool, optional
149+
strip quotes i.e. string literals from `curr_line` and `pre_lines`.
150+
Need this disable when hovering over string literals, by default True
151+
152+
Returns
153+
-------
154+
str
155+
part of the line including any relevant line continuations before `col`
156+
"""
157+
if (curr_line is None) or (col > len(curr_line)) or (curr_line.startswith("#")):
158+
return None
159+
prepend_string = "".join(pre_lines)
160+
curr_line = prepend_string + curr_line
161+
col += len(prepend_string)
162+
line_prefix = curr_line[:col].lower()
163+
# Ignore string literals
164+
if qs:
165+
if (line_prefix.find("'") > -1) or (line_prefix.find('"') > -1):
166+
sq_count = 0
167+
dq_count = 0
168+
for char in line_prefix:
169+
if (char == "'") and (dq_count % 2 == 0):
170+
sq_count += 1
171+
elif (char == '"') and (sq_count % 2 == 0):
172+
dq_count += 1
173+
if (dq_count % 2 == 1) or (sq_count % 2 == 1):
174+
return None
175+
return line_prefix
176+
177+
178+
def resolve_globs(glob_path: str, root_path: str = None) -> list[str]:
179+
"""Resolve glob patterns
180+
181+
Parameters
182+
----------
183+
glob_path : str
184+
Path containing the glob pattern follows
185+
`fnmatch` glob pattern, can include relative paths, etc.
186+
see fnmatch: https://docs.python.org/3/library/fnmatch.html#module-fnmatch
187+
188+
root_path : str, optional
189+
root path to start glob search. If left empty the root_path will be
190+
extracted from the glob_path, by default None
191+
192+
Returns
193+
-------
194+
list[str]
195+
Expanded glob patterns with absolute paths.
196+
Absolute paths are used to resolve any potential ambiguity
197+
"""
198+
# Path.glob returns a generator, we then cast the Path obj to a str
199+
# alternatively use p.as_posix()
200+
if root_path:
201+
return [str(p) for p in Path(root_path).resolve().glob(glob_path)]
202+
# Attempt to extract the root and glob pattern from the glob_path
203+
# This is substantially less robust that then above
204+
else:
205+
p = Path(glob_path).expanduser()
206+
parts = p.parts[p.is_absolute() :]
207+
return [str(i) for i in Path(p.root).resolve().glob(str(Path(*parts)))]
208+
209+
210+
def only_dirs(paths: list[str], err_msg: list = []) -> list[str]:
211+
dirs: list[str] = []
212+
for p in paths:
213+
if os.path.isdir(p):
214+
dirs.append(p)
215+
elif os.path.isfile(p):
216+
continue
217+
else:
218+
msg: str = (
219+
f"Directory '{p}' specified in Configuration settings file does not"
220+
" exist"
221+
)
222+
if err_msg:
223+
err_msg.append([2, msg])
224+
else:
225+
log.warning(msg)
226+
return dirs
227+
228+
229+
def set_keyword_ordering(sorted):
230+
global sort_keywords
231+
sort_keywords = sorted
232+
233+
234+
def map_keywords(keywords):
235+
mapped_keywords = []
236+
keyword_info = {}
237+
for keyword in keywords:
238+
keyword_prefix = keyword.split("(")[0].lower()
239+
keyword_ind = KEYWORD_ID_DICT.get(keyword_prefix)
240+
if keyword_ind is not None:
241+
mapped_keywords.append(keyword_ind)
242+
if keyword_prefix in ("intent", "dimension", "pass"):
243+
keyword_substring = get_paren_substring(keyword)
244+
if keyword_substring is not None:
245+
keyword_info[keyword_prefix] = keyword_substring
246+
if sort_keywords:
247+
mapped_keywords.sort()
248+
return mapped_keywords, keyword_info
249+
250+
251+
def get_keywords(keywords, keyword_info={}):
252+
keyword_strings = []
253+
for keyword_id in keywords:
254+
string_rep = KEYWORD_LIST[keyword_id]
255+
addl_info = keyword_info.get(string_rep)
256+
string_rep = string_rep.upper()
257+
if addl_info is not None:
258+
string_rep += "({0})".format(addl_info)
259+
keyword_strings.append(string_rep)
260+
return keyword_strings
261+
262+
263+
def get_paren_substring(test_str):
264+
i1 = test_str.find("(")
265+
i2 = test_str.rfind(")")
266+
if i1 > -1 and i2 > i1:
267+
return test_str[i1 + 1 : i2]
268+
else:
269+
return None
270+
271+
272+
def get_paren_level(line):
273+
"""Get sub-string corresponding to a single parenthesis level,
274+
via backward search up through the line.
275+
276+
Examples:
277+
"CALL sub1(arg1,arg2" -> ("arg1,arg2", [[10, 19]])
278+
"CALL sub1(arg1(i),arg2" -> ("arg1,arg2", [[10, 14], [17, 22]])
279+
"""
280+
if line == "":
281+
return "", [[0, 0]]
282+
level = 0
283+
in_string = False
284+
string_char = ""
285+
i1 = len(line)
286+
sections = []
287+
for i in range(len(line) - 1, -1, -1):
288+
char = line[i]
289+
if in_string:
290+
if char == string_char:
291+
in_string = False
292+
continue
293+
if (char == "(") or (char == "["):
294+
level -= 1
295+
if level == 0:
296+
i1 = i
297+
elif level < 0:
298+
sections.append([i + 1, i1])
299+
break
300+
elif (char == ")") or (char == "]"):
301+
level += 1
302+
if level == 1:
303+
sections.append([i + 1, i1])
304+
elif (char == "'") or (char == '"'):
305+
in_string = True
306+
string_char = char
307+
if level == 0:
308+
sections.append([i, i1])
309+
sections.reverse()
310+
out_string = ""
311+
for section in sections:
312+
out_string += line[section[0] : section[1]]
313+
return out_string, sections
314+
315+
316+
def get_var_stack(line):
317+
"""Get user-defined type field sequence terminating the given line
318+
319+
Examples:
320+
"myvar%foo%bar" -> ["myvar", "foo", "bar"]
321+
"myarray(i)%foo%bar" -> ["myarray", "foo", "bar"]
322+
"CALL self%method(this%foo" -> ["this", "foo"]
323+
"""
324+
if len(line) == 0:
325+
return [""]
326+
final_var, sections = get_paren_level(line)
327+
if final_var == "":
328+
return [""]
329+
# Continuation of variable after paren requires '%' character
330+
iLast = 0
331+
for (i, section) in enumerate(sections):
332+
if not line[section[0] : section[1]].startswith("%"):
333+
iLast = i
334+
final_var = ""
335+
for section in sections[iLast:]:
336+
final_var += line[section[0] : section[1]]
337+
#
338+
if final_var is not None:
339+
final_op_split = OBJBREAK_REGEX.split(final_var)
340+
return final_op_split[-1].split("%")
341+
else:
342+
return None

fortls/intrinsics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import os
33

4+
from fortls.helper_functions import map_keywords
45
from fortls.objects import (
56
fortran_ast,
67
fortran_function,
@@ -9,7 +10,6 @@
910
fortran_subroutine,
1011
fortran_type,
1112
fortran_var,
12-
map_keywords,
1313
)
1414

1515
none_ast = fortran_ast()

fortls/langserver.py

Lines changed: 9 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@
2323
SUBROUTINE_TYPE_ID,
2424
VAR_TYPE_ID,
2525
)
26-
from fortls.helper_functions import expand_name
26+
from fortls.helper_functions import (
27+
expand_name,
28+
get_line_prefix,
29+
get_paren_level,
30+
get_var_stack,
31+
only_dirs,
32+
resolve_globs,
33+
set_keyword_ordering,
34+
)
2735
from fortls.intrinsics import (
2836
get_intrinsic_keywords,
2937
load_intrinsics,
@@ -36,10 +44,7 @@
3644
find_in_workspace,
3745
fortran_ast,
3846
fortran_var,
39-
get_paren_level,
4047
get_use_tree,
41-
get_var_stack,
42-
set_keyword_ordering,
4348
)
4449
from fortls.parse_fortran import fortran_file, get_line_context, process_file
4550
from fortls.regex_patterns import (
@@ -81,98 +86,6 @@ def init_file(filepath, pp_defs, pp_suffixes, include_dirs):
8186
return file_obj, None
8287

8388

84-
def get_line_prefix(pre_lines: list, curr_line: str, col: int, qs: bool = True) -> str:
85-
"""Get code line prefix from current line and preceding continuation lines
86-
87-
Parameters
88-
----------
89-
pre_lines : list
90-
for multiline cases get all the previous, relevant lines
91-
curr_line : str
92-
the current line
93-
col : int
94-
column index of the current line
95-
qs : bool, optional
96-
strip quotes i.e. string literals from `curr_line` and `pre_lines`.
97-
Need this disable when hovering over string literals, by default True
98-
99-
Returns
100-
-------
101-
str
102-
part of the line including any relevant line continuations before `col`
103-
"""
104-
if (curr_line is None) or (col > len(curr_line)) or (curr_line.startswith("#")):
105-
return None
106-
prepend_string = "".join(pre_lines)
107-
curr_line = prepend_string + curr_line
108-
col += len(prepend_string)
109-
line_prefix = curr_line[:col].lower()
110-
# Ignore string literals
111-
if qs:
112-
if (line_prefix.find("'") > -1) or (line_prefix.find('"') > -1):
113-
sq_count = 0
114-
dq_count = 0
115-
for char in line_prefix:
116-
if (char == "'") and (dq_count % 2 == 0):
117-
sq_count += 1
118-
elif (char == '"') and (sq_count % 2 == 0):
119-
dq_count += 1
120-
if (dq_count % 2 == 1) or (sq_count % 2 == 1):
121-
return None
122-
return line_prefix
123-
124-
125-
def resolve_globs(glob_path: str, root_path: str = None) -> list[str]:
126-
"""Resolve glob patterns
127-
128-
Parameters
129-
----------
130-
glob_path : str
131-
Path containing the glob pattern follows
132-
`fnmatch` glob pattern, can include relative paths, etc.
133-
see fnmatch: https://docs.python.org/3/library/fnmatch.html#module-fnmatch
134-
135-
root_path : str, optional
136-
root path to start glob search. If left empty the root_path will be
137-
extracted from the glob_path, by default None
138-
139-
Returns
140-
-------
141-
list[str]
142-
Expanded glob patterns with absolute paths.
143-
Absolute paths are used to resolve any potential ambiguity
144-
"""
145-
# Path.glob returns a generator, we then cast the Path obj to a str
146-
# alternatively use p.as_posix()
147-
if root_path:
148-
return [str(p) for p in Path(root_path).resolve().glob(glob_path)]
149-
# Attempt to extract the root and glob pattern from the glob_path
150-
# This is substantially less robust that then above
151-
else:
152-
p = Path(glob_path).expanduser()
153-
parts = p.parts[p.is_absolute() :]
154-
return [str(i) for i in Path(p.root).resolve().glob(str(Path(*parts)))]
155-
156-
157-
def only_dirs(paths: list[str], err_msg: list = []) -> list[str]:
158-
dirs: list[str] = []
159-
for p in paths:
160-
if os.path.isdir(p):
161-
dirs.append(p)
162-
elif os.path.isfile(p):
163-
continue
164-
else:
165-
msg: str = (
166-
f"Directory '{p}' specified in Configuration settings file does not"
167-
" exist"
168-
)
169-
if err_msg:
170-
err_msg.append([2, msg])
171-
else:
172-
log.warning(msg)
173-
return dirs
174-
175-
17689
class LangServer:
17790
def __init__(self, conn, debug_log=False, settings={}):
17891
self.conn = conn

0 commit comments

Comments
 (0)