Skip to content

Commit 2f87177

Browse files
authored
checker,pref: implement hover method for vls (#26821)
* checker,pref: implement hover method for vls * review
1 parent 78ef964 commit 2f87177

5 files changed

Lines changed: 261 additions & 1 deletion

File tree

vlib/v/builder/builder.v

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ pub fn (mut b Builder) print_warnings_and_errors() {
577577
}
578578
}
579579
if b.pref.json_errors {
580-
if !b.pref.is_vls || b.pref.linfo.method !in [.definition, .completion, .signature_help] {
580+
if !b.pref.is_vls
581+
|| b.pref.linfo.method !in [.definition, .completion, .signature_help, .hover] {
581582
util.print_json_errors(json_errors)
582583
}
583584
// eprintln(json2.encode_pretty(json_errors))

vlib/v/checker/autocomplete.v

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,221 @@ pub fn (mut c Checker) autocomplete_for_fn_call_expr(node ast.CallExpr) {
111111
exit(0)
112112
}
113113

114+
fn (mut c Checker) ident_hover(node_ ast.Expr) {
115+
if c.pref.linfo.method != .hover {
116+
return
117+
}
118+
if !c.vls_is_the_node(node_.pos()) {
119+
return
120+
}
121+
mut node := unsafe { node_ }
122+
mut declaration := ''
123+
mut doc := ''
124+
match mut node {
125+
ast.CallExpr {
126+
if !c.vls_is_the_node(node.name_pos) {
127+
return
128+
}
129+
f := c.get_fn_from_call_expr(node) or { return }
130+
fn_name := f.name.all_after_last('.')
131+
mut params := []string{cap: f.params.len}
132+
for i, param in f.params {
133+
if f.is_method && i == 0 {
134+
continue
135+
}
136+
params << '${param.name} ${c.table.type_to_str(param.typ)}'
137+
}
138+
ret_str := if f.return_type != ast.no_type && f.return_type != ast.void_type {
139+
' ' + c.table.type_to_str(f.return_type)
140+
} else {
141+
''
142+
}
143+
declaration = 'fn ${fn_name}(${params.join(', ')})${ret_str}'
144+
receiver := if f.is_method {
145+
c.table.sym(f.receiver_type).name.all_after_last('.')
146+
} else {
147+
''
148+
}
149+
if info := c.table.vls_info['fn_${f.mod}[${receiver}]${fn_name}'] {
150+
doc = info.doc
151+
}
152+
}
153+
ast.Ident {
154+
for _, obj in c.table.global_scope.objects {
155+
if obj is ast.ConstField && obj.name == node.name {
156+
type_str := c.table.type_to_str(obj.typ)
157+
declaration = 'const ${node.name.all_after_last('.')} ${type_str}'
158+
if info := c.table.vls_info['const_${obj.name}'] {
159+
doc = info.doc
160+
}
161+
break
162+
} else if obj is ast.GlobalField && obj.name == node.name {
163+
type_str := c.table.type_to_str(obj.typ)
164+
declaration = '__global ${node.name.all_after_last('.')} ${type_str}'
165+
break
166+
}
167+
}
168+
if declaration == '' && !isnil(c.fn_scope) {
169+
if obj := c.fn_scope.find_var(node.name) {
170+
type_str := c.table.type_to_str(obj.typ)
171+
declaration = '${node.name} ${type_str}'
172+
}
173+
}
174+
if declaration == '' {
175+
full_name := if node.mod != '' { '${node.mod}.${node.name}' } else { node.name }
176+
idx := c.table.find_type_idx(full_name)
177+
if idx > 0 {
178+
sym := c.table.type_symbols[idx]
179+
declaration = c.vls_hover_type_declaration(sym)
180+
key := c.vls_hover_type_info_key(sym)
181+
if info := c.table.vls_info['${key}_${sym.name}'] {
182+
doc = info.doc
183+
}
184+
}
185+
}
186+
}
187+
ast.SelectorExpr {
188+
sym := c.table.sym(node.expr_type)
189+
if field := c.table.find_field_with_embeds(sym, node.field_name) {
190+
type_str := c.table.type_to_str(field.typ)
191+
declaration = '${node.field_name} ${type_str}'
192+
} else if method := c.table.find_method(sym, node.field_name) {
193+
fn_name := method.name.all_after_last('.')
194+
mut params := []string{cap: method.params.len}
195+
for i, param in method.params {
196+
if method.is_method && i == 0 {
197+
continue
198+
}
199+
params << '${param.name} ${c.table.type_to_str(param.typ)}'
200+
}
201+
ret_str := if method.return_type != ast.no_type
202+
&& method.return_type != ast.void_type {
203+
' ' + c.table.type_to_str(method.return_type)
204+
} else {
205+
''
206+
}
207+
declaration = 'fn ${fn_name}(${params.join(', ')})${ret_str}'
208+
receiver_name := sym.name.all_after_last('.')
209+
if info := c.table.vls_info['fn_${method.mod}[${receiver_name}]${fn_name}'] {
210+
doc = info.doc
211+
}
212+
} else {
213+
return
214+
}
215+
}
216+
ast.StructInit {
217+
if c.vls_is_the_node(node.name_pos) {
218+
sym := c.table.sym(node.typ)
219+
declaration = c.vls_hover_type_declaration(sym)
220+
key := c.vls_hover_type_info_key(sym)
221+
if info := c.table.vls_info['${key}_${sym.name}'] {
222+
doc = info.doc
223+
}
224+
} else {
225+
for field in node.init_fields {
226+
if c.vls_is_the_node(field.name_pos) {
227+
sym := c.table.sym(node.typ)
228+
if struct_field := c.table.find_field_with_embeds(sym, field.name) {
229+
type_str := c.table.type_to_str(struct_field.typ)
230+
declaration = '${field.name} ${type_str}'
231+
}
232+
break
233+
}
234+
}
235+
}
236+
}
237+
ast.EnumVal {
238+
enum_name := if node.enum_name == '' && node.typ != ast.void_type && node.typ != 0 {
239+
c.table.sym(node.typ).name
240+
} else {
241+
node.enum_name
242+
}
243+
declaration = '${enum_name.all_after_last('.')}.${node.val}'
244+
}
245+
ast.TypeNode {
246+
typ_str := c.table.type_to_str(node.typ)
247+
idx := c.table.find_type_idx(typ_str)
248+
if idx > 0 {
249+
sym := c.table.type_symbols[idx]
250+
declaration = c.vls_hover_type_declaration(sym)
251+
key := c.vls_hover_type_info_key(sym)
252+
if info := c.table.vls_info['${key}_${sym.name}'] {
253+
doc = info.doc
254+
}
255+
}
256+
}
257+
ast.CastExpr {
258+
typ_str := if node.typname != '' {
259+
node.typname
260+
} else {
261+
c.table.type_to_str(node.typ)
262+
}
263+
idx := c.table.find_type_idx(typ_str)
264+
if idx > 0 {
265+
sym := c.table.type_symbols[idx]
266+
declaration = c.vls_hover_type_declaration(sym)
267+
key := c.vls_hover_type_info_key(sym)
268+
if info := c.table.vls_info['${key}_${sym.name}'] {
269+
doc = info.doc
270+
}
271+
}
272+
}
273+
else {}
274+
}
275+
if declaration == '' {
276+
exit(0)
277+
}
278+
c.vls_write_hover(declaration, doc)
279+
exit(0)
280+
}
281+
282+
fn (c &Checker) vls_hover_type_declaration(sym ast.TypeSymbol) string {
283+
name := sym.name.all_after_last('.')
284+
match sym.kind {
285+
.struct {
286+
return 'struct ${name}'
287+
}
288+
.enum {
289+
return 'enum ${name}'
290+
}
291+
.interface {
292+
return 'interface ${name}'
293+
}
294+
.alias {
295+
alias_info := sym.info as ast.Alias
296+
parent_str := c.table.type_to_str(alias_info.parent_type)
297+
return 'type ${name} = ${parent_str}'
298+
}
299+
.sum_type {
300+
sum_info := sym.info as ast.SumType
301+
variants := sum_info.variants.map(c.table.type_to_str(it)).join(' | ')
302+
return 'type ${name} = ${variants}'
303+
}
304+
else {
305+
return name
306+
}
307+
}
308+
}
309+
310+
fn (c &Checker) vls_hover_type_info_key(sym ast.TypeSymbol) string {
311+
return match sym.kind {
312+
.alias { 'aliastype' }
313+
.sum_type { 'sumtype' }
314+
else { '${sym.kind}' }
315+
}
316+
}
317+
318+
fn (c &Checker) vls_write_hover(declaration string, doc string) {
319+
mut lines := ['```v', declaration, '```']
320+
if doc.len > 0 {
321+
lines << ''
322+
lines << doc
323+
}
324+
value := lines.join('\n').replace('\\', '\\\\').replace('"', '\\"').replace('\n',
325+
'\\n')
326+
println('{"contents":{"kind":"markdown","value":"${value}"}}')
327+
}
328+
114329
fn (mut c Checker) name_pos_gotodef(name string) ?token.Pos {
115330
idx := c.table.find_type_idx(name)
116331
if idx > 0 {

vlib/v/checker/checker.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3984,6 +3984,7 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type {
39843984
defer {
39853985
if c.pref.is_vls {
39863986
c.ident_gotodef(node)
3987+
c.ident_hover(node)
39873988
}
39883989
}
39893990
match mut node {

vlib/v/pref/line_info.v

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub enum Method {
1212
definition @['textDocument/definition']
1313
completion @['textDocument/completion']
1414
signature_help @['textDocument/signatureHelp']
15+
hover @['textDocument/hover']
1516
set_trace @['$/setTrace']
1617
cancel_request @['$/cancelRequest']
1718
shutdown @['shutdown']
@@ -51,6 +52,9 @@ fn (mut p Preferences) parse_line_info(line string) {
5152
} else if third.starts_with('gd^') {
5253
col = third[3..].int() - 1
5354
Method.definition
55+
} else if third.starts_with('hv^') {
56+
col = third[3..].int() - 1
57+
Method.hover
5458
} else if third[0].is_digit() {
5559
col = third.int() - 1
5660
Method.completion

vlib/v/tests/vls/autocomplete_module_test.v

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ const autocomplete_info_for_mod_struct = '{"details": [
3636
{"kind":2,"label":"add","detail":"void","declaration":"","documentation":""}
3737
]}'
3838

39+
const hover_info_for_public_fn1 = '{"contents":{"kind":"markdown","value":"```v\\nfn public_fn1(val int) string\\n```"}}'
40+
41+
const hover_info_for_public_struct1 = '{"contents":{"kind":"markdown","value":"```v\\nstruct PublicStruct1\\n```"}}'
42+
3943
const fn_signature_info_for_all_before_last = '{
4044
"signatures":[{
4145
"label":"all_before_last(sub string) string",
@@ -57,6 +61,7 @@ enum Method {
5761
definition @['textDocument/definition']
5862
completion @['textDocument/completion']
5963
signature_help @['textDocument/signatureHelp']
64+
hover @['textDocument/hover']
6065
set_trace @['$/setTrace']
6166
cancel_request @['$/cancelRequest']
6267
shutdown @['shutdown']
@@ -110,6 +115,16 @@ const test_data = [
110115
cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:28:9" ${os.quoted_path(text_file)}'
111116
output: ''
112117
},
118+
TestData{
119+
method: .hover
120+
cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:30:hv^10" ${os.quoted_path(text_file)}'
121+
output: hover_info_for_public_fn1
122+
},
123+
TestData{
124+
method: .hover
125+
cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:31:hv^12" ${os.quoted_path(text_file)}'
126+
output: hover_info_for_public_struct1
127+
},
113128
TestData{
114129
method: .definition
115130
cmd: 'v -w -check -json-errors -nocolor -vls-mode -line-info "${text_file}:30:gd^10" ${os.quoted_path(text_file)}'
@@ -352,6 +367,9 @@ fn test_main() {
352367
.signature_help {
353368
check_valid_fn_signature(t.output)!
354369
}
370+
.hover {
371+
check_valid_hover(t.output)!
372+
}
355373
else {}
356374
}
357375
}
@@ -410,6 +428,27 @@ fn check_valid_json_errors(message string) ! {
410428
}
411429
}
412430

431+
struct HoverContents {
432+
kind string
433+
value string
434+
}
435+
436+
struct HoverResult {
437+
contents HoverContents
438+
}
439+
440+
fn check_valid_hover(message string) ! {
441+
result := json.decode(HoverResult, message) or {
442+
return error('hover: fail to json decode: ${err}')
443+
}
444+
if result.contents.kind != 'markdown' {
445+
return error('hover: contents.kind should be "markdown": ${result.contents.kind}')
446+
}
447+
if result.contents.value.len == 0 {
448+
return error('hover: contents.value should not be empty')
449+
}
450+
}
451+
413452
fn check_valid_fn_signature(message string) ! {
414453
result := json.decode(SignatureHelp, message) or {
415454
return error('fn_signature: fail to json decode')

0 commit comments

Comments
 (0)