From 1a4cf3b7486cbdc1e7845474bc7efe2147cab71a Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sat, 18 Oct 2025 00:31:27 +0800 Subject: [PATCH] parser,checker: autocomplete cleanup (#25524) --- vlib/v/checker/autocomplete.v | 412 ++++++++++---------- vlib/v/checker/checker.v | 1 - vlib/v/parser/enum.v | 4 +- vlib/v/parser/fn.v | 4 +- vlib/v/parser/parser.v | 22 +- vlib/v/parser/struct.v | 14 +- vlib/v/pref/line_info.v | 13 +- vlib/v/tests/vls/autocomplete_module_test.v | 70 ++++ vlib/v/tests/vls/sample_mod1/sample.v | 100 +++++ vlib/v/tests/vls/sample_mod2/sample.v | 55 +++ vlib/v/tests/vls/sample_text.vv | 22 ++ 11 files changed, 475 insertions(+), 242 deletions(-) create mode 100644 vlib/v/tests/vls/autocomplete_module_test.v create mode 100644 vlib/v/tests/vls/sample_mod1/sample.v create mode 100644 vlib/v/tests/vls/sample_mod2/sample.v create mode 100644 vlib/v/tests/vls/sample_text.vv diff --git a/vlib/v/checker/autocomplete.v b/vlib/v/checker/autocomplete.v index 155d44aae..7afdc277e 100644 --- a/vlib/v/checker/autocomplete.v +++ b/vlib/v/checker/autocomplete.v @@ -6,17 +6,45 @@ import strings import v.ast import os -struct ACFieldMethod { - name string - typ string +enum DetailKind { + text = 1 + method = 2 + function = 3 + constructor = 4 + field = 5 + variable = 6 + class = 7 + interface = 8 + module = 9 + property = 10 + unit = 11 + value = 12 + enum = 13 + keyword = 14 + snippet = 15 + color = 16 + file = 17 + reference = 18 + folder = 19 + enum_member = 20 + const = 21 + struct = 22 + event = 23 + operator = 24 + type_parameter = 25 } -pub fn (mut c Checker) run_ac(ast_file &ast.File) { +struct Detail { + kind DetailKind // The type of item (e.g., Method, Function, Field) + label string // The name of the completion item + detail string // Additional info like the function signature or return type + documentation string // The documentation for the item + insert_text ?string + insert_text_format ?int // 1 for PlainText, 2 for Snippet } // Autocomplete for function parameters `os.write_bytes(**path string, bytes []u8***)` etc pub fn (mut c Checker) autocomplete_for_fn_call_expr() { - // println(c.pref.linfo.expr) mut fn_name := if c.pref.linfo.expr.ends_with('(') { c.pref.linfo.expr[..c.pref.linfo.expr.len - 1].trim_space() } else { @@ -46,29 +74,6 @@ fn (mut c Checker) ident_gotodef() { println('${f.file}:${f.pos.line_nr}:${f.pos.col}') return } - // TODO: As EnumDecl has no `file` field, we can't goto enum right now - // for name,val in c.table.enum_decls { - // if val.is_pub && name == ident_name { - // println('${val.file}:${val.pos.line_nr}:${val.pos.col}') - // return - // } - //} - - // TODO: we need other TypeDecl information here - // for mut sym in c.table.type_symbols { - // if sym.is_pub && sym.name == ident_name { - // match mut sym.info { - // ast.Alias { - // } - // ast.Interface { - // } - // ast.Struct { - // } - // else { - // } - // } - // } - //} } // Autocomplete for `myvar. ...`, `os. ...` @@ -81,7 +86,7 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { //' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" expr="${c.pref.linfo.expr}"') ' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" node.mod="${node.mod}" col="${c.pref.linfo.col}"') } - // Make sure this ident is on the same line as requeste, in the same file, and has the same name + // Make sure this ident is on the same line and same file as request same_line := c.pref.linfo.line_nr == node.pos.line_nr if !same_line { return @@ -90,8 +95,10 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { if !same_col { return } - abs_path := os.join_path(os.getwd(), c.file.path) - if c.pref.linfo.path !in [c.file.path, abs_path] { + if node.pos.file_idx < 0 { + return + } + if c.pref.linfo.path != c.table.filelist[node.pos.file_idx] { return } // Module autocomplete @@ -103,9 +110,8 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { } exit(0) } - mut sb := strings.new_builder(10) + if node.kind == .unresolved { - // println(node) eprintln('unresolved type, maybe "${node.name}" was not defined. otherwise this is a bug, should never happen; please report') exit(1) } @@ -113,110 +119,38 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) { exit(0) } sym := c.table.sym(c.unwrap_generic(node.obj.typ)) - // sb.writeln('VAR ${node.name}:${sym.name} ${node.pos.line_nr}') - nt := '${node.name}:${sym.name}' - sb.writeln('{') - if !c.pref.linfo.vars_printed[nt] { // avoid dups - // sb.writeln('===') - // sb.writeln('VAR ${nt}') //${node.name}:${sym.name}') - sb.writeln('\t"name":"${node.name}",') - sb.writeln('\t"type":"${sym.name}",') - sb.writeln('\t"fields":[') - - // print_backtrace() - /* - if sym.kind == .alias { - parent_sym := c.table.sym(sym.parent_type) - } - */ - - mut fields := []ACFieldMethod{cap: 10} - mut methods := []ACFieldMethod{cap: 10} - if sym.kind == .struct { - // Add fields, but only if it's a struct. - struct_info := sym.info as ast.Struct - // match struct_info { - // ast.Struct - //} - for field in struct_info.fields { - field_sym := c.table.sym(field.typ) - fields << ACFieldMethod{field.name, field_sym.name} - } - } else if sym.kind == .array { - // t := typeof(sym.info).name - if sym.info is ast.Aggregate { - } else if sym.info is ast.Array { - fields << ACFieldMethod{'len', 'int'} - fields << ACFieldMethod{'cap', 'int'} - } - // array_info := sym.info as ast.Array - } else if sym.kind == .string { - fields << ACFieldMethod{'len', 'int'} - } - // Aliases and other types can have methods, add them - for method in sym.methods { - method_ret_type := c.table.sym(method.return_type) - methods << ACFieldMethod{build_method_summary(method), method_ret_type.name} - } - fields.sort(a.name < b.name) - for i, field in fields { - // sb.writeln('${field.name}:${field.typ}') - sb.write_string('\t\t"${field.name}:${field.typ}"') - if i < fields.len - 1 { - sb.writeln(', ') - } - } - sb.writeln('\n\t], "methods":[') - - for i, method in methods { - sb.write_string('\t\t"${method.name}:${method.typ}"') - if i < methods.len - 1 { - sb.writeln(', ') - } - } - sb.writeln('\n\t]\n}') - res := sb.str().trim_space() - if res != '' { - println(res) - c.pref.linfo.vars_printed[nt] = true - } - } + mut details := []Detail{cap: 10} + c.vls_gen_type_details(mut details, sym) + c.vls_write_details(details) } fn (mut c Checker) module_autocomplete(mod string) { - println('{') - c.write_mod_funcs(mod) - c.write_mod_type_alias(mod) - c.write_mod_interfaces(mod) - c.write_mod_enums(mod) - c.write_mod_consts(mod) - c.write_mod_structs(mod) - println('}') + mut details := []Detail{cap: 128} + c.vls_gen_mod_funcs_details(mut details, mod) + c.vls_gen_mod_type_details(mut details, mod, .alias, .interface, .enum, .sum_type, + .struct) + c.vls_gen_mod_consts_details(mut details, mod) + c.vls_write_details(details) } -fn build_method_summary(method ast.Fn) string { - mut s := method.name + '(' +fn (c &Checker) build_fn_summary(method ast.Fn) string { + mut sb := strings.new_builder(64) + sb.write_string(method.name.all_after_last('.')) + sb.write_string('(') for i, param in method.params { - if i == 0 { + if method.is_method && i == 0 { + // skip receiver continue } - s += param.name + sb.write_string(param.name) + sb.write_string(' ') + sb.write_string(c.table.type_to_str(param.typ)) if i < method.params.len - 1 { - s += ', ' + sb.write_string(', ') } } - return s + ')' -} - -fn (c &Checker) build_fn_summary(method ast.Fn) string { - mut s := method.name + '(' - for i, param in method.params { - s += param.name + ' ' + c.table.type_to_str(param.typ) - if i < method.params.len - 1 { - s += ', ' - } - } - return s + ')' + sb.write_string(')') + return sb.str() } fn (c &Checker) try_resolve_to_import_mod_name(name string) string { @@ -228,126 +162,178 @@ fn (c &Checker) try_resolve_to_import_mod_name(name string) string { return '' } -fn (c &Checker) write_mod_funcs(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"functions":[') - mut empty := true +fn (c &Checker) vls_gen_mod_funcs_details(mut details []Detail, mod string) { for _, f in c.table.fns { mut name := f.name - if f.is_pub && name.all_before_last('.') == mod { - empty = false - if name.contains('__static__') { - name = name.replace('__static__', '.') - } + if f.is_pub && !f.is_method && !f.is_static_type_method && name.all_before_last('.') == mod { name = name.all_after_last('.') // The user already typed `mod.`, so suggest the name without module type_string := if f.return_type != ast.no_type { c.table.type_to_str(f.return_type) } else { - 'int' + '' + } + mut doc := '' + if info := c.table.vls_info['fn_${mod}[]${name}'] { + doc = info.doc + } + details << Detail{ + kind: .function + label: name + detail: type_string + documentation: doc } - sb.writeln('"${name}:${type_string}" ,') } } - if !empty { - sb.go_back(2) // remove final , - } - sb.writeln('],') - println(sb.str().trim_space()) } -fn (c &Checker) write_mod_type_alias(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"type_alias":[') - mut empty := true +fn (c &Checker) vls_gen_mod_type_details(mut details []Detail, mod string, kinds ...ast.Kind) { for sym in c.table.type_symbols { - if sym.is_pub && sym.info is ast.Alias { + if sym.is_pub && sym.kind in kinds { if sym.name.all_before_last('.') == mod { - empty = false - sb.writeln('"${sym.name.all_after_last('.')}" ,') + mut doc := '' + key := match sym.kind { + .alias { + 'aliastype' + } + .interface { + 'interface' + } + .enum { + 'enum' + } + .sum_type { + 'sumtype' + } + .struct { + 'struct' + } + else { + '${sym.kind}' + } + } + if info := c.table.vls_info['${key}_${sym.name}'] { + doc = info.doc + } + details << Detail{ + kind: c.vls_map_v_kind_to_lsp_kind(sym.kind) + label: sym.name.all_after_last('.') + documentation: doc + } } } } - if !empty { - sb.go_back(2) // remove final , - } - sb.writeln('],') - println(sb.str().trim_space()) } -fn (c &Checker) write_mod_interfaces(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"interfaces":[') - mut empty := true - for sym in c.table.type_symbols { - if sym.is_pub && sym.info is ast.Interface { - if sym.name.all_before_last('.') == mod { - empty = false - sb.writeln('"${sym.name.all_after_last('.')}" ,') +fn (c &Checker) vls_gen_mod_consts_details(mut details []Detail, mod string) { + for _, obj in c.table.global_scope.objects { + if obj is ast.ConstField && obj.is_pub { + if obj.name.all_before_last('.') == mod { + mut doc := '' + if info := c.table.vls_info['const_${obj.name}'] { + doc = info.doc + } + details << Detail{ + kind: .const + label: obj.name.all_after_last('.') + documentation: doc + } } } } - if !empty { - sb.go_back(2) // remove final , - } - sb.writeln('],') - println(sb.str().trim_space()) } -fn (c &Checker) write_mod_enums(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"enums":[') - mut empty := true - for name, val in c.table.enum_decls { - if val.is_pub && name.all_before_last('.') == mod { - empty = false - sb.writeln('"${name.all_after_last('.')}" ,') +fn (c &Checker) vls_gen_type_details(mut details []Detail, sym ast.TypeSymbol) { + match sym.kind { + .struct { + struct_info := sym.info as ast.Struct + for field in struct_info.fields { + field_sym := c.table.sym(field.typ) + details << Detail{ + kind: .field + label: field.name + detail: field_sym.name + } + } + } + .array { + if sym.info is ast.Aggregate { + } else if sym.info is ast.Array { + details << Detail{ + kind: .property + label: 'len' + detail: 'int' + } + details << Detail{ + kind: .property // use class icon + label: 'cap' + detail: 'int' + } + } + } + .string { + details << Detail{ + kind: .property // use class icon + label: 'len' + detail: 'int' + } } + else {} } - if !empty { - sb.go_back(2) // remove final , + // Aliases and other types can have methods, add them + for method in sym.methods { + method_ret_type := c.table.sym(method.return_type) + details << Detail{ + kind: .method + label: c.build_fn_summary(method) + detail: method_ret_type.name + } } - sb.writeln('],') - println(sb.str().trim_space()) } -fn (c &Checker) write_mod_consts(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"constants":[') - mut empty := true - for _, obj in c.table.global_scope.objects { - if obj is ast.ConstField && obj.is_pub { - if obj.name.all_before_last('.') == mod { - empty = false - if obj.typ != ast.no_type { - sb.writeln('"${obj.name.all_after_last('.')}:${c.table.type_to_str(obj.typ)}" ,') - } else { - sb.writeln('"${obj.name.all_after_last('.')}:int" ,') - } - } +fn (c &Checker) vls_write_details(details []Detail) { + mut sb := strings.new_builder(details.len * 32) + sb.writeln('{"details" : [') + for detail in details { + sb.write_string('{"kind":${int(detail.kind)},') + sb.write_string('"label":"${detail.label}",') + sb.write_string('"detail":"${detail.detail}",') + sb.write_string('"documentation":"${detail.documentation}",') + if insert_text := detail.insert_text { + sb.write_string('"insert_text":"${insert_text}",') + } + if insert_text_format := detail.insert_text_format { + sb.write_string('"insert_text_format":${insert_text_format},') } + sb.go_back(1) + sb.writeln('},') } - if !empty { - sb.go_back(2) // remove final , + if details.len > 0 { + sb.go_back(2) } - sb.writeln('],') - println(sb.str().trim_space()) + sb.write_string('\n]}') + print(sb.str()) } -fn (c &Checker) write_mod_structs(mod string) { - mut sb := strings.new_builder(128) - sb.writeln('"structs":[') - mut empty := true - for sym in c.table.type_symbols { - if sym.is_pub && sym.info is ast.Struct { - if sym.name.all_before_last('.') == mod { - empty = false - sb.writeln('"${sym.name.all_after_last('.')}" ,') - } +fn (c &Checker) vls_map_v_kind_to_lsp_kind(kind ast.Kind) DetailKind { + match kind { + .alias, .sum_type { + return .class + } + .function { + return .function + } + .interface { + return .interface + } + .enum { + return .enum + } + .struct { + return .struct + } + else { + return .text } } - if !empty { - sb.go_back(2) // remove final , - } - sb.writeln(']') - println(sb.str().trim_space()) + return .text } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index b2be48961..c4a732392 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -464,7 +464,6 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) { abs_path := os.join_path(os.getwd(), file.path).replace('/./', '/') // TODO: join_path shouldn't have /./ if abs_path == c.pref.linfo.path { c.check_files([ast_files[i]]) - c.run_ac(ast_files[i]) exit(0) } } diff --git a/vlib/v/parser/enum.v b/vlib/v/parser/enum.v index c33dd6947..89050930f 100644 --- a/vlib/v/parser/enum.v +++ b/vlib/v/parser/enum.v @@ -48,7 +48,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { } p.check(.key_enum) end_pos := p.tok.pos() - mut comments_before_key_enum := if p.is_vls { + mut comments_before_key_enum := if p.pref.is_vls { p.cur_comments.clone() } else { [] @@ -258,7 +258,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl { if !already_exists { p.table.register_enum_decl(enum_decl) - if p.is_vls { + if p.pref.is_vls { key := 'enum_${name}' mut has_decl_end_comment := false if enum_decl.comments.len > 0 diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 3d31284e8..4971efd86 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -301,7 +301,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { p.next() } p.check(.key_fn) - mut comments_before_key_fn := if p.is_vls { + mut comments_before_key_fn := if p.pref.is_vls { p.cur_comments.clone() } else { [] @@ -758,7 +758,7 @@ run them via `v file.v` instead', p.table.register_fn_generic_types(fn_decl.fkey()) } p.label_names = [] - if p.is_vls { + if p.pref.is_vls { type_str := if (is_method || is_static_type_method) && rec.typ != ast.no_type { p.table.sym(rec.typ.idx_type()).name.all_after_last('.') } else { diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 05b1a394b..5ff45d861 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -649,7 +649,7 @@ fn (mut p Parser) top_stmt() ast.Stmt { mut keep_cur_comments := false defer { // clear `cur_comments` after each statement, except a comment stmt - if !keep_cur_comments && p.is_vls { + if !keep_cur_comments && p.pref.is_vls { p.cur_comments.clear() } } @@ -794,7 +794,7 @@ fn (mut p Parser) top_stmt() ast.Stmt { } } // clear `cur_comments` after each statement, except a comment stmt - if !keep_cur_comments && p.is_vls { + if !keep_cur_comments && p.pref.is_vls { p.cur_comments.clear() } if p.should_abort { @@ -891,7 +891,7 @@ fn (mut p Parser) comment() ast.Comment { fn (mut p Parser) comment_stmt() ast.ExprStmt { comment := p.comment() - if p.is_vls { + if p.pref.is_vls { p.cur_comments << comment } return ast.ExprStmt{ @@ -942,7 +942,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { mut keep_cur_comments := false defer { - if !keep_cur_comments && p.is_vls { + if !keep_cur_comments && p.pref.is_vls { p.cur_comments.clear() } } @@ -2563,7 +2563,7 @@ fn (mut p Parser) const_decl() ast.ConstDecl { p.inside_assign_rhs = old_inside_assign_rhs } // we need `end_comments` when in `vls` mode too - if is_block || p.is_vls { + if is_block || p.pref.is_vls { end_comments << p.eat_comments(same_line: true) } mut field := ast.ConstField{ @@ -2584,7 +2584,7 @@ fn (mut p Parser) const_decl() ast.ConstDecl { } fields << field p.table.global_scope.register(field) - if p.is_vls { + if p.pref.is_vls { key := 'const_${full_name}' // Fixme: because ConstDecl has no name, we can't access ConstDecl via name // So the comment before the `const` keyword will be set to the first field's comment @@ -2786,7 +2786,7 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { is_block: is_block attrs: attrs } - if p.is_vls { + if p.pref.is_vls { for i, f in fields { mut key := 'global_${f.name}' // Fixme: because GlobalDecl has no name, we can't access GlobalDecl via name @@ -2826,7 +2826,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { p.next() } p.check(.key_type) - mut comments_before_key_type := if p.is_vls { + mut comments_before_key_type := if p.pref.is_vls { p.cur_comments.clone() } else { [] @@ -2888,7 +2888,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { attrs: attrs is_markused: attrs.contains('markused') } - if p.is_vls { + if p.pref.is_vls { key := 'fntype_${fn_name}' val := ast.VlsInfo{ pos: decl_pos @@ -2946,7 +2946,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { is_markused: attrs.contains('markused') } p.table.register_sumtype(node) - if p.is_vls { + if p.pref.is_vls { key := 'sumtype_${p.prepend_mod(name)}' val := ast.VlsInfo{ pos: node.pos @@ -3009,7 +3009,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { is_markused: attrs.contains('markused') attrs: attrs } - if p.is_vls { + if p.pref.is_vls { key := 'aliastype_${p.prepend_mod(name)}' val := ast.VlsInfo{ pos: alias_type_decl.pos diff --git a/vlib/v/parser/struct.v b/vlib/v/parser/struct.v index b0d8508ef..c06ca1140 100644 --- a/vlib/v/parser/struct.v +++ b/vlib/v/parser/struct.v @@ -64,7 +64,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { } generic_types, _ := p.parse_generic_types() mut pre_comments := p.eat_comments() - mut comments_before_key_struct := if p.is_vls { + mut comments_before_key_struct := if p.pref.is_vls { p.cur_comments.clone() } else { [] @@ -495,7 +495,7 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl { is_implements: is_implements implements_types: implements_types } - if p.is_vls { + if p.pref.is_vls { key := 'struct_${name}' mut has_decl_end_comment := false if struct_decl.pre_comments.len > 0 @@ -677,7 +677,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { p.next() // `interface` language := p.parse_language() name_pos := p.tok.pos() - mut comments_before_key_interface := if p.is_vls { + mut comments_before_key_interface := if p.pref.is_vls { p.cur_comments.clone() } else { [] @@ -707,7 +707,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { mut pre_comments := p.eat_comments() p.check(.lcbr) pre_comments << p.eat_comments() - if p.is_vls { + if p.pref.is_vls { pre_comment_string = if pre_comments.len > 0 && pre_comments[0].pos.line_nr == pos.line_nr { // interface MyInterface { // end_comment p.comments_to_string(pre_comments[1..]) @@ -890,7 +890,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { ts.register_method(tmethod) info.methods << tmethod - if p.is_vls { + if p.pref.is_vls { f_key := 'fn_${p.mod}[${modless_name}]${name}' f_val := ast.VlsInfo{ pos: method.pos @@ -928,7 +928,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { has_prev_newline: has_prev_newline has_break_line: has_break_line } - if p.is_vls { + if p.pref.is_vls { // split comments into f_end_comment and f_nxt_comment first mut f_end_comment := ast.Comment{} mut f_nxt_comment := []ast.Comment{} @@ -970,7 +970,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl { name_pos: name_pos } p.table.register_interface(res) - if p.is_vls { + if p.pref.is_vls { key := 'interface_${interface_name}' if res.pre_comments.len > 0 && res.pre_comments[0].pos.line_nr == res.pos.line_nr { // interface MyInterface { // MyInterface end_comment1 diff --git a/vlib/v/pref/line_info.v b/vlib/v/pref/line_info.v index b7a00ae27..f8987a69c 100644 --- a/vlib/v/pref/line_info.v +++ b/vlib/v/pref/line_info.v @@ -14,14 +14,15 @@ pub mut: fn (mut p Preferences) parse_line_info(line string) { // println("parse_line_info '${line}'") + // Note: windows C:\Users\DDT\AppData\Local\Temp\sample_text.v:18:3 format_err := 'wrong format, use `-line-info "file.v:24:7"' vals := line.split(':') - if vals.len != 3 { + if vals.len < 3 { eprintln(format_err) return } - file_name := vals[0] - line_nr := vals[1].int() - 1 + file_name := vals[..vals.len - 2].join(':') + line_nr := vals[vals.len - 2].int() - 1 if !file_name.ends_with('.v') || line_nr == -1 { eprintln(format_err) @@ -29,16 +30,16 @@ fn (mut p Preferences) parse_line_info(line string) { } // Third value can be a column or expression for autocomplete like `os.create()` - third := vals[2] + third := vals[vals.len - 1] if third[0].is_digit() { - col := vals[2].int() - 1 + col := third.int() - 1 p.linfo = LineInfo{ line_nr: line_nr path: file_name col: col } } else { - expr := vals[2] + expr := third p.linfo = LineInfo{ line_nr: line_nr path: file_name diff --git a/vlib/v/tests/vls/autocomplete_module_test.v b/vlib/v/tests/vls/autocomplete_module_test.v new file mode 100644 index 000000000..a7a058783 --- /dev/null +++ b/vlib/v/tests/vls/autocomplete_module_test.v @@ -0,0 +1,70 @@ +import os +import term +import v.util.diff + +const vroot = os.real_path(@VMODROOT) + +const text_file_orig = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'sample_text.vv') +const text_file = os.join_path(os.temp_dir(), 'sample_text.v') + +fn testsuite_begin() { + eprintln('testsuite_begin, text_file = ${text_file}') + os.cp(text_file_orig, text_file) or { panic(err) } +} + +fn testsuite_end() { +} + +struct TestData { + cmd string + output string +} + +const test_data = [ + TestData{ + cmd: 'v -check -json-errors -nocolor -vls-mode -line-info "${text_file}:18:3" ${os.quoted_path(text_file)}' + output: '{"details" : [ +{"kind":3,"label":"public_fn1","detail":"string","documentation":""}, +{"kind":22,"label":"PublicStruct1","detail":"","documentation":""}, +{"kind":13,"label":"PublicEnum1","detail":"","documentation":""}, +{"kind":8,"label":"PublicInterface1","detail":"","documentation":""}, +{"kind":7,"label":"PublicAlias1_1","detail":"","documentation":""}, +{"kind":7,"label":"PublicAlias1_2","detail":"","documentation":""}, +{"kind":21,"label":"public_const1","detail":"","documentation":""} +]}' + }, +] + +fn test_main() { + mut total_errors := 0 + + for t in test_data { + res := os.execute(t.cmd) + if res.exit_code < 0 { + println('fail execute ${t.cmd}') + panic(res.output) + } + res_output := $if windows { + res.output.replace('\r\n', '\n') + } $else { + res.output + } + if t.output != res_output { + println('${term.red('FAIL')} ${t.cmd}') + if diff_ := diff.compare_text(t.output, res_output) { + println(term.header('difference:', '-')) + println(diff_) + } else { + println(term.header('expected:', '-')) + println(t.output) + println(term.header('found:', '-')) + println(res_output) + } + println(term.h_divider('-')) + total_errors++ + } else { + println('${term.green('OK ')} ${t.cmd}') + } + } + assert total_errors == 0 +} diff --git a/vlib/v/tests/vls/sample_mod1/sample.v b/vlib/v/tests/vls/sample_mod1/sample.v new file mode 100644 index 000000000..b13a086bd --- /dev/null +++ b/vlib/v/tests/vls/sample_mod1/sample.v @@ -0,0 +1,100 @@ +// for vls module test +module sample_mod1 + +// public + +// This line is not the PublicStruct1's comment +// PublicStruct1 is a public struct +pub struct PublicStruct1 { // And PublicStruct1 should init here + // data_int is a int data + data_int int + // this is a string + data_string string // note: init = '' + // and this is a u8 + data_u8 u8 +} + +// This line is not the public_const1's comment +// public_const1 is a public const +pub const public_const1 = 'a public const' // public_const1 is a string + +// This line is not the PublicEnum1's comment +// PublicEnum1 contains `a`,`b` and `c` +pub enum PublicEnum1 { + // a is a PublicEnum1 data + a + // b is a PublicEnum1 data + b + // c is a PublicEnum1 data + c +} + +// PublicInterface1 is a public interface, has fields and methods +pub interface PublicInterface1 { + // val is a int type data field + val int + // method is a void func return string + method() string +} + +// PublicAlias1_1 is a sumtype of `u8`,`u16` and `int` +pub type PublicAlias1_1 = u8 | u16 | int + +// PublicAlias1_2 is a alias of `PublicStruct1` +pub type PublicAlias1_2 = PublicStruct1 + +// PublicCB is a funtion type +pub type PublicCB = fn (msg &char, arg usize) + +// public_fn1 is a public function, return string +pub fn public_fn1(val int) string { + return '${val}' +} + +// public is a static method of `PublicStruct1` +pub fn PublicStruct1.public() PublicStruct1 { + return PublicStruct1{} +} + +// new is a method of `PublicStruct1` +pub fn (mut p PublicStruct1) new() PublicStruct1 { + return PublicStruct1{} +} + +// private + +struct PrivateStruct1 { + data int + data_string string +} + +const private_const1 = 'a private const' + +enum PrivateEnum1 { + a + b + c +} + +interface PrivateInterface1 { + val int + method() string +} + +type PrivateAlias1_1 = u8 | u16 | int + +type PrivateAlias1_2 = PublicStruct1 + +type PrivateCB = fn (msg &char, arg usize) + +fn private_fn1(val int) string { + return '${val}' +} + +fn PrivateStruct1.new() PrivateStruct1 { + return PrivateStruct1{} +} + +fn PublicStruct1.private() PublicStruct1 { + return PublicStruct1{} +} diff --git a/vlib/v/tests/vls/sample_mod2/sample.v b/vlib/v/tests/vls/sample_mod2/sample.v new file mode 100644 index 000000000..048c607a1 --- /dev/null +++ b/vlib/v/tests/vls/sample_mod2/sample.v @@ -0,0 +1,55 @@ +// for vls module test +module sample_mod2 + +// public + +pub struct PublicStruct2 { + data_int int + data_string string + data_u8 u8 +} + +pub const public_const2 = 'a public const' + +pub enum PublicEnum2 { + a + b + c +} + +pub interface PublicInterface2 { + val int + method() string +} + +pub type PublicAlias2 = u8 | u16 | int + +pub fn public_fn2(val int) string { + return '${val}' +} + +// private + +struct PrivateStruct2 { + data int + data_string string +} + +const private_const2 = 'a private const' + +enum PrivateEnum2 { + a + b + c +} + +interface PrivateInterface2 { + val int + method() string +} + +type PrivateAlias2 = u8 | u16 | int + +fn private_fn2(val int) string { + return '${val}' +} diff --git a/vlib/v/tests/vls/sample_text.vv b/vlib/v/tests/vls/sample_text.vv new file mode 100644 index 000000000..5c406a656 --- /dev/null +++ b/vlib/v/tests/vls/sample_text.vv @@ -0,0 +1,22 @@ +// This file is used as a text file +module main + +import v.tests.vls.sample_mod1 as s +import v.tests.vls.sample_mod2 + +struct MyS{ + b int + a string +} + +// add add `val` to `a` +fn (mut m MyS) add(val int) { + m.a += val +} + +fn main() { + s. + //sample_mod2. + //mut k := MyS{} + //k. +} -- 2.39.5