Skip to content

Commit 7f15a22

Browse files
committed
Adds partial support for literals when hovering
Supports logicals, integer and real numbers but not copmlex. Strings but only when hovering over the " or ' because of how `get_line_prefix` discards incomplete strigs as prefixes. Does not support any type of arrays e.g. [] or (//) Fixes Hover over numbers when passed as arguments does not show #188
1 parent ad18b7f commit 7f15a22

File tree

3 files changed

+82
-28
lines changed

3 files changed

+82
-28
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# CHANGELONG
22

3+
### 1.13.0
4+
5+
### Fixes
6+
7+
* Fixes (partially) Fortran literal variable hover
8+
([#188](https://github.com/hansec/fortran-language-server/issues/188))
9+
310
## 1.12.1
411

512
### Fixes

fortls/langserver.py

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@
3030
climb_type_tree,
3131
find_in_scope,
3232
find_in_workspace,
33+
fortran_var,
3334
get_use_tree,
3435
get_var_stack,
3536
set_keyword_ordering,
3637
)
3738
from fortls.parse_fortran import (
39+
DQ_STRING_REGEX,
40+
LOGICAL_REGEX,
41+
NUMBER_REGEX,
42+
SQ_STRING_REGEX,
3843
expand_name,
3944
fortran_ast,
4045
fortran_file,
@@ -847,7 +852,7 @@ def build_comp(
847852
)
848853
return item_list
849854

850-
def get_definition(self, def_file, def_line, def_char):
855+
def get_definition(self, def_file, def_line, def_char, hover_req=False):
851856
# Get full line (and possible continuations) from file
852857
pre_lines, curr_line, _ = def_file.get_code_line(
853858
def_line, forward=False, strip_comment=True
@@ -901,6 +906,32 @@ def get_definition(self, def_file, def_line, def_char):
901906
for obj in self.intrinsic_funs:
902907
if obj.name.lower() == key:
903908
return obj
909+
910+
# If we have a Fortran literal constant e.g. 100, .false., etc.
911+
# Return a dummy object with the correct type & position in the doc
912+
if (
913+
hover_req
914+
and curr_scope
915+
and (
916+
NUMBER_REGEX.match(def_name)
917+
or LOGICAL_REGEX.match(def_name)
918+
or SQ_STRING_REGEX.match(def_name)
919+
or DQ_STRING_REGEX.match(def_name)
920+
)
921+
):
922+
# The description name chosen is non-ambiguous and cannot naturally
923+
# occur in Fortran (with/out C preproc) code
924+
# It is invalid syntax to define a type starting with numerics
925+
# it cannot also be a comment that requires !, c, d
926+
# and ^= (xor_eq) operator is invalid in Fortran C++ preproc
927+
var_obj = fortran_var(
928+
curr_scope.file_ast,
929+
def_line + 1,
930+
def_name,
931+
"0^=__LITERAL_INTERNAL_DUMMY_VAR_",
932+
curr_scope.keywords,
933+
)
934+
return var_obj
904935
else:
905936
return var_obj
906937
return None
@@ -1141,6 +1172,26 @@ def create_hover(string, highlight):
11411172
else:
11421173
return string
11431174

1175+
def create_signature_hover():
1176+
sig_request = request.copy()
1177+
sig_result = self.serve_signature(sig_request)
1178+
try:
1179+
arg_id = sig_result.get("activeParameter")
1180+
if arg_id is not None:
1181+
arg_info = sig_result["signatures"][0]["parameters"][arg_id]
1182+
arg_doc = arg_info["documentation"]
1183+
doc_split = arg_doc.find("\n !!")
1184+
if doc_split < 0:
1185+
arg_string = f"{arg_doc} :: {arg_info['label']}"
1186+
else:
1187+
arg_string = (
1188+
f"{arg_doc[:doc_split]} :: "
1189+
f"{arg_info['label']}{arg_doc[doc_split:]}"
1190+
)
1191+
return create_hover(arg_string, True)
1192+
except:
1193+
pass
1194+
11441195
# Get parameters from request
11451196
params = request["params"]
11461197
uri = params["textDocument"]["uri"]
@@ -1151,7 +1202,7 @@ def create_hover(string, highlight):
11511202
if file_obj is None:
11521203
return None
11531204
# Find object
1154-
var_obj = self.get_definition(file_obj, def_line, def_char)
1205+
var_obj = self.get_definition(file_obj, def_line, def_char, hover_req=True)
11551206
if var_obj is None:
11561207
return None
11571208
# Construct hover information
@@ -1165,30 +1216,17 @@ def create_hover(string, highlight):
11651216
hover_str, highlight = member.get_hover(long=True)
11661217
if hover_str is not None:
11671218
hover_array.append(create_hover(hover_str, highlight))
1168-
return {"contents": hover_array}
1169-
elif self.variable_hover and (var_type == 6):
1170-
hover_str, highlight = var_obj.get_hover()
1171-
hover_array.append(create_hover(hover_str, highlight))
1219+
elif self.variable_hover and (var_type == VAR_TYPE_ID):
1220+
# Unless we have a Fortran literal include the desc in the hover msg
1221+
# See get_definition for an explanaiton about this default name
1222+
if var_obj.desc != "0^=__LITERAL_INTERNAL_DUMMY_VAR_":
1223+
hover_str, highlight = var_obj.get_hover()
1224+
hover_array.append(create_hover(hover_str, highlight))
1225+
# Include the signature if one is present e.g. if in an argument list
11721226
if self.hover_signature:
1173-
sig_request = request.copy()
1174-
sig_result = self.serve_signature(sig_request)
1175-
try:
1176-
arg_id = sig_result.get("activeParameter")
1177-
if arg_id is not None:
1178-
arg_info = sig_result["signatures"][0]["parameters"][arg_id]
1179-
arg_doc = arg_info["documentation"]
1180-
doc_split = arg_doc.find("\n !!")
1181-
if doc_split < 0:
1182-
arg_string = "{0} :: {1}".format(arg_doc, arg_info["label"])
1183-
else:
1184-
arg_string = "{0} :: {1}{2}".format(
1185-
arg_doc[:doc_split],
1186-
arg_info["label"],
1187-
arg_doc[doc_split:],
1188-
)
1189-
hover_array.append(create_hover(arg_string, True))
1190-
except:
1191-
pass
1227+
hover_str = create_signature_hover()
1228+
if hover_str is not None:
1229+
hover_array.append(hover_str)
11921230
#
11931231
if len(hover_array) > 0:
11941232
return {"contents": hover_array}

fortls/parse_fortran.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@
112112
)
113113
VIS_REGEX = re.compile(r"[ ]*(PUBLIC|PRIVATE)[ :]", re.I)
114114
WORD_REGEX = re.compile(r"[a-z_][a-z0-9_]*", re.I)
115+
NUMBER_REGEX = re.compile(
116+
r"[\+\-]?(\b\d+\.?\d*|\.\d+)(_\w+|d[\+\-]?\d+|e[\+\-]?\d+(_\w+)?)?(?![a-z_])",
117+
re.I,
118+
)
119+
LOGICAL_REGEX = re.compile(r".true.|.false.", re.I)
115120
SUB_PAREN_MATCH = re.compile(r"\([a-z0-9_, ]*\)", re.I)
116121
KIND_SPEC_MATCH = re.compile(r"\([a-z0-9_, =*]*\)", re.I)
117122
SQ_STRING_REGEX = re.compile(r"\'[^\']*\'", re.I)
@@ -164,9 +169,13 @@
164169

165170
def expand_name(line, char_poss):
166171
"""Get full word containing given cursor position"""
167-
for word_match in WORD_REGEX.finditer(line):
168-
if word_match.start(0) <= char_poss and word_match.end(0) >= char_poss:
169-
return word_match.group(0)
172+
# The order here is important.
173+
# WORD will capture substrings in logical and strings
174+
regexs = [LOGICAL_REGEX, SQ_STRING_REGEX, DQ_STRING_REGEX, WORD_REGEX, NUMBER_REGEX]
175+
for r in regexs:
176+
for num_match in r.finditer(line):
177+
if num_match.start(0) <= char_poss and num_match.end(0) >= char_poss:
178+
return num_match.group(0)
170179
return ""
171180

172181

0 commit comments

Comments
 (0)