v2 / vlib / v / checker / autocomplete.v
786 lines · 759 sloc · 19.93 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
3module checker
4
5import strings
6import v.ast
7import os
8import token
9
10enum DetailKind {
11 text = 1
12 method = 2
13 function = 3
14 constructor = 4
15 field = 5
16 variable = 6
17 class = 7
18 interface = 8
19 module = 9
20 property = 10
21 unit = 11
22 value = 12
23 enum = 13
24 keyword = 14
25 snippet = 15
26 color = 16
27 file = 17
28 reference = 18
29 folder = 19
30 enum_member = 20
31 const = 21
32 struct = 22
33 event = 23
34 operator = 24
35 type_parameter = 25
36}
37
38struct Detail {
39 kind DetailKind // The type of item (e.g., Method, Function, Field)
40 label string // The name of the completion item
41 detail string // Additional info like the function signature or return type
42 declaration string // Full fn declaration, e.g. "fn greet(name string) string"
43 documentation string // The documentation for the item
44 insert_text ?string
45 insert_text_format ?int // 1 for PlainText, 2 for Snippet
46}
47
48fn (mut c Checker) get_fn_from_call_expr(node ast.CallExpr) !ast.Fn {
49 fn_name := node.name
50 return if node.is_method {
51 left_sym := c.table.sym(c.unwrap_generic(node.left_type))
52 c.table.find_method(left_sym, fn_name) or {
53 return error('failed to find method "${fn_name}"')
54 }
55 } else {
56 c.table.find_fn(fn_name) or { return error('failed to find fn "${fn_name}"') }
57 }
58}
59
60// Autocomplete for function parameters `os.write_bytes(**path string, bytes []u8***)` etc
61pub 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 }
99 if c.pref.linfo.method != .signature_help {
100 return
101 }
102 if !c.vls_is_the_node(node.name_pos) {
103 return
104 }
105 f := c.get_fn_from_call_expr(node) or {
106 println(err)
107 exit(1)
108 }
109 res := c.build_fn_summary(f)
110 println(res)
111 exit(0)
112}
113
114fn (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
276 if declaration == '' {
277 exit(0)
278 }
279 c.vls_write_hover(declaration, doc)
280 exit(0)
281}
282
283fn (c &Checker) vls_hover_type_declaration(sym ast.TypeSymbol) string {
284 name := sym.name.all_after_last('.')
285 match sym.kind {
286 .struct {
287 return 'struct ${name}'
288 }
289 .enum {
290 return 'enum ${name}'
291 }
292 .interface {
293 return 'interface ${name}'
294 }
295 .alias {
296 alias_info := sym.info as ast.Alias
297 parent_str := c.table.type_to_str(alias_info.parent_type)
298 return 'type ${name} = ${parent_str}'
299 }
300 .sum_type {
301 sum_info := sym.info as ast.SumType
302 variants := sum_info.variants.map(c.table.type_to_str(it)).join(' | ')
303 return 'type ${name} = ${variants}'
304 }
305 else {
306 return name
307 }
308 }
309}
310
311fn (c &Checker) vls_hover_type_info_key(sym ast.TypeSymbol) string {
312 return match sym.kind {
313 .alias { 'aliastype' }
314 .sum_type { 'sumtype' }
315 else { '${sym.kind}' }
316 }
317}
318
319fn (c &Checker) vls_write_hover(declaration string, doc string) {
320 mut lines := ['```v', declaration, '```']
321 if doc.len > 0 {
322 lines << ''
323 lines << doc
324 }
325 value := lines.join('\n').replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n')
326 println('{"contents":{"kind":"markdown","value":"${value}"}}')
327}
328
329fn (mut c Checker) name_pos_gotodef(name string) ?token.Pos {
330 idx := c.table.find_type_idx(name)
331 if idx > 0 {
332 sym := c.table.type_symbols[idx]
333 return sym.info.get_name_pos()
334 }
335 return none
336}
337
338fn (mut c Checker) ident_gotodef(node_ ast.Expr) {
339 if c.pref.linfo.method != .definition {
340 return
341 }
342 if !c.vls_is_the_node(node_.pos()) {
343 return
344 }
345 mut node := unsafe { node_ }
346 mut pos := token.Pos{}
347 match mut node {
348 ast.CallExpr {
349 if !c.vls_is_the_node(node.name_pos) {
350 return
351 }
352 f := c.get_fn_from_call_expr(node) or {
353 println(err)
354 exit(1)
355 }
356 pos = f.name_pos
357 }
358 ast.Ident {
359 // global objects
360 for _, obj in c.table.global_scope.objects {
361 if obj is ast.ConstField && obj.name == node.name {
362 pos = obj.pos
363 break
364 } else if obj is ast.GlobalField && obj.name == node.name {
365 pos = obj.pos
366 break
367 } else if obj is ast.Var && obj.name == node.name {
368 pos = obj.pos
369 break
370 }
371 }
372 // local objects
373 if pos == token.Pos{} && !isnil(c.fn_scope) {
374 if obj := c.fn_scope.find_var(node.name) {
375 pos = obj.pos
376 }
377 }
378 // If not found as a variable/const, try as a type
379 if pos == token.Pos{} {
380 full_name := if node.mod != '' { '${node.mod}.${node.name}' } else { node.name }
381 if np := c.name_pos_gotodef(full_name) {
382 pos = np
383 }
384 }
385 }
386 ast.StructInit {
387 // Check if clicking on a field name in struct init
388 for field in node.init_fields {
389 if c.vls_is_the_node(field.name_pos) {
390 sym := c.table.sym(node.typ)
391 if struct_field := c.table.find_field_with_embeds(sym, field.name) {
392 pos = struct_field.pos
393 break
394 }
395 }
396 }
397 if pos == token.Pos{} && c.vls_is_the_node(node.name_pos) {
398 info := c.table.sym(node.typ).info
399 if np := info.get_name_pos() {
400 pos = np
401 }
402 }
403 if pos == token.Pos{} {
404 return
405 }
406 }
407 ast.SelectorExpr {
408 // Check if clicking on the field name or method
409 sym := c.table.sym(node.expr_type)
410 if field := c.table.find_field_with_embeds(sym, node.field_name) {
411 pos = field.pos
412 } else {
413 if method := c.table.find_method(sym, node.field_name) {
414 pos = method.name_pos
415 } else {
416 println('failed to find field or method "${node.field_name}"')
417 exit(1)
418 }
419 }
420 }
421 ast.EnumVal {
422 // Go to enum field definition
423 mut enum_name := if node.enum_name == '' && node.typ != ast.void_type && node.typ != 0 {
424 c.table.sym(node.typ).name
425 } else {
426 node.enum_name
427 }
428 if enum_decl := c.table.enum_decls[enum_name] {
429 for field in enum_decl.fields {
430 if field.name == node.val {
431 pos = field.pos
432 break
433 }
434 }
435 }
436 }
437 ast.TypeNode {
438 // Go to type definition
439 typ_str := c.table.type_to_str(node.typ)
440 if np := c.name_pos_gotodef(typ_str) {
441 pos = np
442 }
443 }
444 ast.CastExpr {
445 // Go to type definition in cast expr
446 if node.typname != '' {
447 if np := c.name_pos_gotodef(node.typname) {
448 pos = np
449 }
450 }
451 if pos == token.Pos{} && node.typ != ast.void_type {
452 typ_str := c.table.type_to_str(node.typ)
453 if np := c.name_pos_gotodef(typ_str) {
454 pos = np
455 }
456 }
457 }
458 else {}
459 }
460
461 if pos.file_idx != -1 {
462 println('${c.table.filelist[pos.file_idx]}:${pos.line_nr + 1}:${pos.col}')
463 }
464 exit(0)
465}
466
467// Autocomplete for `myvar. ...`, `os. ...`
468fn (mut c Checker) ident_autocomplete(node ast.Ident) {
469 if c.pref.linfo.method != .completion {
470 return
471 }
472 // Mini LS hack (v -line-info "a.v:16")
473 if c.pref.is_verbose {
474 println(
475 'checker.ident_autocomplete() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' +
476 ' node.col=${node.pos.col} pwd="${os.getwd()}" file="${c.file.path}", ' +
477 ' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" node.mod="${node.mod}" col="${c.pref.linfo.col}"')
478 }
479 if node.mod == 'builtin' {
480 // User can't type in `builtin.func(` at all
481 return
482 }
483 if !c.vls_is_the_node(node.pos) {
484 return
485 }
486 check_name := if c.pref.linfo.col == node.pos.col {
487 if node.name == '' {
488 // for `os` in middle of text, followed by something else in next line:
489 // `os.
490 // something`
491 node.mod
492 } else {
493 ''
494 }
495 } else if c.pref.linfo.col == node.pos.col + node.pos.len {
496 // for `os` at end of scope :
497 // `os. }`
498 node.name
499 } else {
500 ''
501 }
502 if check_name.len == 0 {
503 return
504 }
505 // Module autocomplete
506 // `os. ...`
507 mod_name := c.try_resolve_to_import_mod_name(check_name)
508 if mod_name.len > 0 {
509 c.module_autocomplete(mod_name)
510 exit(0)
511 }
512 if node.kind == .unresolved || node.obj.typ == ast.no_type {
513 exit(0)
514 }
515 sym := c.table.sym(c.unwrap_generic(node.obj.typ))
516 mut details := []Detail{cap: 10}
517 c.vls_gen_type_details(mut details, sym)
518 c.vls_write_details(details)
519 exit(0)
520}
521
522fn (mut c Checker) module_autocomplete(mod string) {
523 mut details := []Detail{cap: 128}
524 c.vls_gen_mod_funcs_details(mut details, mod)
525 c.vls_gen_mod_type_details(mut details, mod, .alias, .interface, .enum, .sum_type, .struct)
526 c.vls_gen_mod_consts_details(mut details, mod)
527 c.vls_write_details(details)
528}
529
530fn (c &Checker) build_fn_summary(func ast.Fn) string {
531 mut sb := strings.new_builder(128)
532 fn_name := func.name.all_after_last('.')
533 sb.writeln('{\n"signatures":[{')
534 sb.write_string('\t"label":"${fn_name}(')
535 mut params := []string{cap: func.params.len}
536 for i, param in func.params {
537 if func.is_method && i == 0 {
538 // skip receiver
539 continue
540 }
541 params << '${param.name} ${c.table.type_to_str(param.typ)}'
542 }
543 sb.write_string(params.join(', '))
544 sb.write_string(')')
545 if func.return_type != ast.void_type {
546 sb.write_string(' ')
547 sb.write_string(c.table.type_to_str(func.return_type))
548 }
549 sb.writeln('",\n\t"parameters":[{')
550 for i, p in params {
551 sb.write_string('\t\t"label":"${p}"')
552 if i < params.len - 1 {
553 sb.write_string(',')
554 }
555 sb.writeln('')
556 }
557 sb.writeln('\t}]')
558 sb.writeln('}],')
559 sb.writeln('"activeSignature":0,')
560 sb.writeln('"activeParameter":0')
561 sb.writeln('}')
562 return sb.str()
563}
564
565fn (c &Checker) try_resolve_to_import_mod_name(name string) string {
566 for imp in c.file.imports {
567 if (imp.alias == name || imp.mod == name) && imp.alias in c.file.used_imports {
568 return imp.mod
569 }
570 }
571 return ''
572}
573
574fn (c &Checker) vls_gen_mod_funcs_details(mut details []Detail, mod string) {
575 for _, f in c.table.fns {
576 mut name := f.name
577 if f.is_pub && !f.is_method && !f.is_static_type_method && name.all_before_last('.') == mod {
578 name =
579 name.all_after_last('.') // The user already typed `mod.`, so suggest the name without module
580 type_string := if f.return_type != ast.no_type {
581 c.table.type_to_str(f.return_type)
582 } else {
583 ''
584 }
585 mut doc := ''
586 if info := c.table.vls_info['fn_${mod}[]${name}'] {
587 doc = info.doc
588 }
589 // Build full fn declaration for hover display, e.g. "fn add(a int, b int) int"
590 mut params := []string{cap: f.params.len}
591 for param in f.params {
592 params << '${param.name} ${c.table.type_to_str(param.typ)}'
593 }
594 ret_str := if f.return_type != ast.no_type && f.return_type != ast.void_type {
595 ' ' + c.table.type_to_str(f.return_type)
596 } else {
597 ''
598 }
599 declaration := 'fn ${name}(${params.join(', ')})${ret_str}'
600 details << Detail{
601 kind: .function
602 label: name
603 detail: type_string
604 declaration: declaration
605 documentation: doc
606 }
607 }
608 }
609}
610
611fn (c &Checker) vls_gen_mod_type_details(mut details []Detail, mod string, kinds ...ast.Kind) {
612 for sym in c.table.type_symbols {
613 if sym.is_pub && sym.kind in kinds {
614 if sym.name.all_before_last('.') == mod {
615 mut doc := ''
616 key := match sym.kind {
617 .alias {
618 'aliastype'
619 }
620 .interface {
621 'interface'
622 }
623 .enum {
624 'enum'
625 }
626 .sum_type {
627 'sumtype'
628 }
629 .struct {
630 'struct'
631 }
632 else {
633 '${sym.kind}'
634 }
635 }
636
637 if info := c.table.vls_info['${key}_${sym.name}'] {
638 doc = info.doc
639 }
640 details << Detail{
641 kind: c.vls_map_v_kind_to_lsp_kind(sym.kind)
642 label: sym.name.all_after_last('.')
643 documentation: doc
644 }
645 }
646 }
647 }
648}
649
650fn (c &Checker) vls_gen_mod_consts_details(mut details []Detail, mod string) {
651 for _, obj in c.table.global_scope.objects {
652 if obj is ast.ConstField && obj.is_pub {
653 if obj.name.all_before_last('.') == mod {
654 mut doc := ''
655 if info := c.table.vls_info['const_${obj.name}'] {
656 doc = info.doc
657 }
658 details << Detail{
659 kind: .const
660 label: obj.name.all_after_last('.')
661 documentation: doc
662 }
663 }
664 }
665 }
666}
667
668fn (c &Checker) vls_gen_type_details(mut details []Detail, sym ast.TypeSymbol) {
669 match sym.kind {
670 .struct {
671 struct_info := sym.info as ast.Struct
672 for field in struct_info.fields {
673 field_sym := c.table.sym(field.typ)
674 details << Detail{
675 kind: .field
676 label: field.name
677 detail: field_sym.name
678 }
679 }
680 }
681 .array {
682 if sym.info is ast.Aggregate {
683 } else if sym.info is ast.Array {
684 details << Detail{
685 kind: .property
686 label: 'len'
687 detail: 'int'
688 }
689 details << Detail{
690 kind: .property // use class icon
691 label: 'cap'
692 detail: 'int'
693 }
694 }
695 }
696 .string {
697 details << Detail{
698 kind: .property // use class icon
699 label: 'len'
700 detail: 'int'
701 }
702 }
703 else {}
704 }
705
706 // Aliases and other types can have methods, add them
707 for method in sym.methods {
708 method_ret_type := c.table.sym(method.return_type)
709 details << Detail{
710 kind: .method
711 label: method.name
712 detail: method_ret_type.name
713 }
714 }
715}
716
717fn (c &Checker) vls_write_details(details []Detail) {
718 mut sb := strings.new_builder(details.len * 32)
719 sb.writeln('{"details": [')
720 for detail in details {
721 sb.write_string('{"kind":${int(detail.kind)},')
722 sb.write_string('"label":"${detail.label}",')
723 sb.write_string('"detail":"${detail.detail}",')
724 sb.write_string('"declaration":"${detail.declaration}",')
725 sb.write_string('"documentation":"${detail.documentation}",')
726 if insert_text := detail.insert_text {
727 sb.write_string('"insert_text":"${insert_text}",')
728 }
729 if insert_text_format := detail.insert_text_format {
730 sb.write_string('"insert_text_format":${insert_text_format},')
731 }
732 sb.go_back(1)
733 sb.writeln('},')
734 }
735 if details.len > 0 {
736 sb.go_back(2)
737 }
738 sb.write_string('\n]}')
739 print(sb.str())
740}
741
742fn (c &Checker) vls_map_v_kind_to_lsp_kind(kind ast.Kind) DetailKind {
743 match kind {
744 .alias, .sum_type {
745 return .class
746 }
747 .function {
748 return .function
749 }
750 .interface {
751 return .interface
752 }
753 .enum {
754 return .enum
755 }
756 .struct {
757 return .struct
758 }
759 else {
760 return .text
761 }
762 }
763
764 return .text
765}
766
767fn (c &Checker) vls_is_the_node(pos token.Pos) bool {
768 // Make sure this ident is on the same line and same file as request
769 same_line := c.pref.linfo.line_nr == pos.line_nr
770 if !same_line {
771 return false
772 }
773 if pos.file_idx < 0 {
774 return false
775 }
776 // Normalize paths for comparison to handle directory compilation
777 linfo_path := os.real_path(c.pref.linfo.path)
778 file_path := os.real_path(c.table.filelist[pos.file_idx])
779 if linfo_path != file_path {
780 return false
781 }
782 if c.pref.linfo.col > pos.col + pos.len || c.pref.linfo.col < pos.col {
783 return false
784 }
785 return true
786}
787