Skip to content

Commit aa3390d

Browse files
authored
checker: added support for hover function declaration (#26789)
* feat: add support for hover fn declaration * feat: implement hover functionality for function declarations * fix: adjust column calculation for hover functionality in function declarations * fix: enhance fn_decl to ensure correct file context for hover functionality * fix: update function call autocomplete to include receiver type for hover details * fix formatting * fix tests
1 parent 1ad6961 commit aa3390d

3 files changed

Lines changed: 102 additions & 16 deletions

File tree

‎vlib/v/checker/autocomplete.v‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ struct Detail {
3939
kind DetailKind // The type of item (e.g., Method, Function, Field)
4040
label string // The name of the completion item
4141
detail string // Additional info like the function signature or return type
42+
declaration string // Full fn declaration, e.g. "fn greet(name string) string"
4243
documentation string // The documentation for the item
4344
insert_text ?string
4445
insert_text_format ?int // 1 for PlainText, 2 for Snippet
@@ -58,6 +59,43 @@ fn (mut c Checker) get_fn_from_call_expr(node ast.CallExpr) !ast.Fn {
5859

5960
// Autocomplete for function parameters `os.write_bytes(**path string, bytes []u8***)` etc
6061
pub fn (mut c Checker) autocomplete_for_fn_call_expr(node ast.CallExpr) {
62+
// Hover over a function call: cursor is on the function name, method is .completion.
63+
// Output the full fn declaration as a single Detail so VLS can display it.
64+
if c.pref.linfo.method == .completion && c.vls_is_the_node(node.name_pos) {
65+
f := c.get_fn_from_call_expr(node) or { return }
66+
fn_name := f.name.all_after_last('.')
67+
mut params := []string{cap: f.params.len}
68+
for i, param in f.params {
69+
if f.is_method && i == 0 {
70+
continue // skip receiver
71+
}
72+
params << '${param.name} ${c.table.type_to_str(param.typ)}'
73+
}
74+
ret_str := if f.return_type != ast.no_type && f.return_type != ast.void_type {
75+
' ' + c.table.type_to_str(f.return_type)
76+
} else {
77+
''
78+
}
79+
declaration := 'fn ${fn_name}(${params.join(', ')})${ret_str}'
80+
mut doc := ''
81+
receiver := if f.is_method {
82+
c.table.sym(f.receiver_type).name.all_after_last('.')
83+
} else {
84+
''
85+
}
86+
if info := c.table.vls_info['fn_${f.mod}[${receiver}]${fn_name}'] {
87+
doc = info.doc
88+
}
89+
c.vls_write_details([
90+
Detail{
91+
kind: .function
92+
label: fn_name
93+
declaration: declaration
94+
documentation: doc
95+
},
96+
])
97+
exit(0)
98+
}
6199
if c.pref.linfo.method != .signature_help {
62100
return
63101
}
@@ -332,10 +370,22 @@ fn (c &Checker) vls_gen_mod_funcs_details(mut details []Detail, mod string) {
332370
if info := c.table.vls_info['fn_${mod}[]${name}'] {
333371
doc = info.doc
334372
}
373+
// Build full fn declaration for hover display, e.g. "fn add(a int, b int) int"
374+
mut params := []string{cap: f.params.len}
375+
for param in f.params {
376+
params << '${param.name} ${c.table.type_to_str(param.typ)}'
377+
}
378+
ret_str := if f.return_type != ast.no_type && f.return_type != ast.void_type {
379+
' ' + c.table.type_to_str(f.return_type)
380+
} else {
381+
''
382+
}
383+
declaration := 'fn ${name}(${params.join(', ')})${ret_str}'
335384
details << Detail{
336385
kind: .function
337386
label: name
338387
detail: type_string
388+
declaration: declaration
339389
documentation: doc
340390
}
341391
}
@@ -453,6 +503,7 @@ fn (c &Checker) vls_write_details(details []Detail) {
453503
sb.write_string('{"kind":${int(detail.kind)},')
454504
sb.write_string('"label":"${detail.label}",')
455505
sb.write_string('"detail":"${detail.detail}",')
506+
sb.write_string('"declaration":"${detail.declaration}",')
456507
sb.write_string('"documentation":"${detail.documentation}",')
457508
if insert_text := detail.insert_text {
458509
sb.write_string('"insert_text":"${insert_text}",')

‎vlib/v/checker/fn.v‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,41 @@ const print_everything_fns = ['println', 'print', 'eprintln', 'eprint', 'panic']
1111
fn (mut c Checker) fn_decl(mut node ast.FnDecl) {
1212
// handle vls go to definition for method receiver types
1313
if c.pref.is_vls {
14+
// Hover over fn keyword or function name on the declaration line — show full declaration.
15+
on_fn_name := c.vls_is_the_node(node.name_pos)
16+
on_fn_keyword := c.pref.linfo.line_nr == node.name_pos.line_nr
17+
&& c.pref.linfo.col >= int(node.pos.col) - 1 && c.pref.linfo.col < node.name_pos.col
18+
&& node.name_pos.file_idx >= 0
19+
&& os.real_path(c.pref.linfo.path) == os.real_path(c.table.filelist[node.name_pos.file_idx])
20+
if c.pref.linfo.method == .completion && (on_fn_name || on_fn_keyword) {
21+
mut params := []string{cap: node.params.len}
22+
for param in node.params {
23+
if param.is_hidden {
24+
continue
25+
}
26+
params << '${param.name} ${c.table.type_to_str(param.typ)}'
27+
}
28+
ret_str := if node.return_type != ast.no_type && node.return_type != ast.void_type {
29+
' ' + c.table.type_to_str(node.return_type)
30+
} else {
31+
''
32+
}
33+
declaration := 'fn ${node.short_name}(${params.join(', ')})${ret_str}'
34+
mut doc := ''
35+
mod := node.name.all_before_last('.')
36+
if info := c.table.vls_info['fn_${mod}[]${node.short_name}'] {
37+
doc = info.doc
38+
}
39+
c.vls_write_details([
40+
Detail{
41+
kind: .function
42+
label: node.short_name
43+
declaration: declaration
44+
documentation: doc
45+
},
46+
])
47+
exit(0)
48+
}
1449
if node.is_method && node.receiver.type_pos.line_nr > 0 {
1550
if c.vls_is_the_node(node.receiver.type_pos) {
1651
typ_str := c.table.type_to_str(node.receiver.typ)

‎vlib/v/tests/vls/autocomplete_module_test.v‎

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,28 @@ const mod1_text_file = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'sample_
1212
'sample.v')
1313

1414
const autocomplete_info_for_mod_sample_mod1 = '{"details": [
15-
{"kind":3,"label":"public_fn1","detail":"string","documentation":""},
16-
{"kind":22,"label":"PublicStruct1","detail":"","documentation":""},
17-
{"kind":7,"label":"PublicAlias1_1","detail":"","documentation":""},
18-
{"kind":7,"label":"PublicAlias1_2","detail":"","documentation":""},
19-
{"kind":13,"label":"PublicEnum1","detail":"","documentation":""},
20-
{"kind":8,"label":"PublicInterface1","detail":"","documentation":""},
21-
{"kind":21,"label":"public_const1","detail":"","documentation":""}
15+
{"kind":3,"label":"public_fn1","detail":"string","declaration":"fn public_fn1(val int) string","documentation":""},
16+
{"kind":22,"label":"PublicStruct1","detail":"","declaration":"","documentation":""},
17+
{"kind":7,"label":"PublicAlias1_1","detail":"","declaration":"","documentation":""},
18+
{"kind":7,"label":"PublicAlias1_2","detail":"","declaration":"","documentation":""},
19+
{"kind":13,"label":"PublicEnum1","detail":"","declaration":"","documentation":""},
20+
{"kind":8,"label":"PublicInterface1","detail":"","declaration":"","documentation":""},
21+
{"kind":21,"label":"public_const1","detail":"","declaration":"","documentation":""}
2222
]}'
2323

2424
const autocomplete_info_for_mod_sample_mod2 = '{"details": [
25-
{"kind":3,"label":"public_fn2","detail":"string","documentation":""},
26-
{"kind":22,"label":"PublicStruct2","detail":"","documentation":""},
27-
{"kind":13,"label":"PublicEnum2","detail":"","documentation":""},
28-
{"kind":8,"label":"PublicInterface2","detail":"","documentation":""},
29-
{"kind":7,"label":"PublicAlias2","detail":"","documentation":""},
30-
{"kind":21,"label":"public_const2","detail":"","documentation":""}
25+
{"kind":3,"label":"public_fn2","detail":"string","declaration":"fn public_fn2(val int) string","documentation":""},
26+
{"kind":22,"label":"PublicStruct2","detail":"","declaration":"","documentation":""},
27+
{"kind":13,"label":"PublicEnum2","detail":"","declaration":"","documentation":""},
28+
{"kind":8,"label":"PublicInterface2","detail":"","declaration":"","documentation":""},
29+
{"kind":7,"label":"PublicAlias2","detail":"","declaration":"","documentation":""},
30+
{"kind":21,"label":"public_const2","detail":"","declaration":"","documentation":""}
3131
]}'
3232

3333
const autocomplete_info_for_mod_struct = '{"details": [
34-
{"kind":5,"label":"a","detail":"int","documentation":""},
35-
{"kind":5,"label":"b","detail":"string","documentation":""},
36-
{"kind":2,"label":"add","detail":"void","documentation":""}
34+
{"kind":5,"label":"a","detail":"int","declaration":"","documentation":""},
35+
{"kind":5,"label":"b","detail":"string","declaration":"","documentation":""},
36+
{"kind":2,"label":"add","detail":"void","declaration":"","documentation":""}
3737
]}'
3838

3939
const fn_signature_info_for_all_before_last = '{

0 commit comments

Comments
 (0)