vls: improve and simplify autocomplete; remove the scanner hack
Some checks are pending
Graphics CI / gg-regressions (push) Waiting to run
vlib modules CI / build-module-docs (push) Waiting to run
native backend CI / native-backend-ubuntu (push) Waiting to run
native backend CI / native-backend-windows (push) Waiting to run
Shy and PV CI / v-compiles-puzzle-vibes (push) Waiting to run
Sanitized CI / sanitize-undefined-clang (push) Waiting to run
Sanitized CI / sanitize-undefined-gcc (push) Waiting to run
Sanitized CI / tests-sanitize-address-clang (push) Waiting to run
Sanitized CI / sanitize-address-msvc (push) Waiting to run
Sanitized CI / sanitize-address-gcc (push) Waiting to run
Sanitized CI / sanitize-memory-clang (push) Waiting to run
sdl CI / v-compiles-sdl-examples (push) Waiting to run
Time CI / time-linux (push) Waiting to run
Time CI / time-macos (push) Waiting to run
Time CI / time-windows (push) Waiting to run
toml CI / toml-module-pass-external-test-suites (push) Waiting to run
Tools CI / tools-linux (tcc) (push) Waiting to run
Tools CI / tools-linux (clang) (push) Waiting to run
Tools CI / tools-linux (gcc) (push) Waiting to run
Tools CI / tools-macos (clang) (push) Waiting to run
Tools CI / tools-windows (gcc) (push) Waiting to run
Tools CI / tools-windows (msvc) (push) Waiting to run
Tools CI / tools-windows (tcc) (push) Waiting to run
Tools CI / tools-docker-ubuntu-musl (push) Waiting to run
vab CI / vab-compiles-v-examples (push) Waiting to run
vab CI / v-compiles-os-android (push) Waiting to run
wasm backend CI / wasm-backend (ubuntu-22.04) (push) Waiting to run
wasm backend CI / wasm-backend (windows-2022) (push) Waiting to run

This commit is contained in:
Alexander Medvednikov 2025-08-12 01:37:32 +03:00
parent 963e3e8ae2
commit 9391b7c234
9 changed files with 86 additions and 40 deletions

View file

@ -11,19 +11,31 @@ struct ACFieldMethod {
typ string typ string
} }
fn abs(a int) int {
if a < 0 {
return -a
}
return a
}
fn (mut c Checker) ident_autocomplete(node ast.Ident) { fn (mut c Checker) ident_autocomplete(node ast.Ident) {
// Mini LS hack (v -line-info "a.v:16") // Mini LS hack (v -line-info "a.v:16")
println( if c.pref.is_verbose {
'checker.ident() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' + println(
' pwd="${os.getwd()}" file="${c.file.path}", ' + 'checker.ident_autocomplete() info.line_nr=${c.pref.linfo.line_nr} node.line_nr=${node.pos.line_nr} ' +
' pref.linfo.path="${c.pref.linfo.path}" node.name="${node.name}" expr="${c.pref.linfo.expr}"') ' node.col=${node.pos.col} pwd="${os.getwd()}" file="${c.file.path}", ' +
//' 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}" 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 as requeste, in the same file, and has the same name
same_line := node.pos.line_nr in [c.pref.linfo.line_nr - 1, c.pref.linfo.line_nr + 1, c.pref.linfo.line_nr] same_line := node.pos.line_nr in [c.pref.linfo.line_nr - 1, c.pref.linfo.line_nr + 1, c.pref.linfo.line_nr]
if !same_line { if !same_line {
return return
} }
same_name := c.pref.linfo.expr == node.name // same_name := c.pref.linfo.expr == node.name
if !same_name { same_col := abs(c.pref.linfo.col - node.pos.col) < 3
// if !same_name {
if !same_col {
return return
} }
abs_path := os.join_path(os.getwd(), c.file.path) abs_path := os.join_path(os.getwd(), c.file.path)
@ -39,9 +51,14 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) {
sym := c.table.sym(c.unwrap_generic(node.obj.typ)) sym := c.table.sym(c.unwrap_generic(node.obj.typ))
// sb.writeln('VAR ${node.name}:${sym.name} ${node.pos.line_nr}') // sb.writeln('VAR ${node.name}:${sym.name} ${node.pos.line_nr}')
nt := '${node.name}:${sym.name}' nt := '${node.name}:${sym.name}'
sb.writeln('{')
if !c.pref.linfo.vars_printed[nt] { // avoid dups if !c.pref.linfo.vars_printed[nt] { // avoid dups
sb.writeln('===') // sb.writeln('===')
sb.writeln('VAR ${nt}') //${node.name}:${sym.name}') // 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() // print_backtrace()
/* /*
if sym.kind == .alias { if sym.kind == .alias {
@ -75,9 +92,14 @@ fn (mut c Checker) ident_autocomplete(node ast.Ident) {
fields << ACFieldMethod{build_method_summary(method), method_ret_type.name} fields << ACFieldMethod{build_method_summary(method), method_ret_type.name}
} }
fields.sort(a.name < b.name) fields.sort(a.name < b.name)
for field in fields { for i, field in fields {
sb.writeln('${field.name}:${field.typ}') // 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]\n}')
res := sb.str().trim_space() res := sb.str().trim_space()
if res != '' { if res != '' {
println(res) println(res)

View file

@ -430,12 +430,13 @@ pub fn (mut c Checker) check_files(ast_files []&ast.File) {
// After the main checker run, run the line info check, print line info, and exit (if it's present) // After the main checker run, run the line info check, print line info, and exit (if it's present)
if !c.pref.linfo.is_running && c.pref.line_info != '' { //'' && c.pref.linfo.line_nr == 0 { if !c.pref.linfo.is_running && c.pref.line_info != '' { //'' && c.pref.linfo.line_nr == 0 {
// c.do_line_info(c.pref.line_info, ast_files) // c.do_line_info(c.pref.line_info, ast_files)
println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd()) // println('setting is_running=true, pref.path=${c.pref.linfo.path} curdir' + os.getwd())
c.pref.linfo.is_running = true c.pref.linfo.is_running = true
// println('linfo path=${c.pref.linfo.path}')
for i, file in ast_files { for i, file in ast_files {
// println(file.path) // println(file.path)
if file.path == c.pref.linfo.path { if file.path == c.pref.linfo.path {
println('running c.check_files') // println('running c.check_files')
c.check_files([ast_files[i]]) c.check_files([ast_files[i]])
exit(0) exit(0)
} else if file.path.starts_with('./') { } else if file.path.starts_with('./') {

View file

@ -2635,6 +2635,13 @@ fn (mut p Parser) type_decl() ast.TypeDecl {
if p.disallow_declarations_in_script_mode() { if p.disallow_declarations_in_script_mode() {
return ast.SumTypeDecl{} return ast.SumTypeDecl{}
} }
if p.is_vls && p.tok.is_key() {
// End parsing after `type ` in vls mode to avoid lots of junk errors
// If next token is a key, the type wasn't finished
p.error('expecting type name')
p.should_abort = true
return ast.AliasTypeDecl{}
}
mut name := p.check_name() mut name := p.check_name()
mut language := ast.Language.v mut language := ast.Language.v
if name.len == 1 && name[0].is_capital() { if name.len == 1 && name[0].is_capital() {

View file

@ -125,6 +125,16 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
} }
} }
p.check(.lcbr) p.check(.lcbr)
// if p.is_vls && p.tok.kind == .key_struct { // p.tok.is_key() {
if p.is_vls && p.tok.is_key() {
// End parsing after `struct Foo {` in vls mode to avoid lots of junk errors
// If next token after { is a key, the struct wasn't finished
p.error('expected `}` to finish a struct definition')
p.should_abort = true
return ast.StructDecl{
name: name
}
}
pre_comments << p.eat_comments() pre_comments << p.eat_comments()
mut i := 0 mut i := 0
for p.tok.kind != .rcbr { for p.tok.kind != .rcbr {

View file

@ -4,7 +4,7 @@ module pref
fn (mut p Preferences) parse_line_info(line string) { fn (mut p Preferences) parse_line_info(line string) {
// println("parse_line_info '${line}'") // println("parse_line_info '${line}'")
format_err := 'wrong format, use `-line-info "file.v:24:expr_to_look_up"' format_err := 'wrong format, use `-line-info "file.v:24:7"'
vals := line.split(':') vals := line.split(':')
if vals.len != 3 { if vals.len != 3 {
eprintln(format_err) eprintln(format_err)
@ -12,7 +12,8 @@ fn (mut p Preferences) parse_line_info(line string) {
} }
file_name := vals[0] file_name := vals[0]
line_nr := vals[1].int() - 1 line_nr := vals[1].int() - 1
expr := vals[2] col := vals[2].int() - 1
// expr := vals[2]
if !file_name.ends_with('.v') || line_nr == -1 { if !file_name.ends_with('.v') || line_nr == -1 {
eprintln(format_err) eprintln(format_err)
return return
@ -20,20 +21,7 @@ fn (mut p Preferences) parse_line_info(line string) {
p.linfo = LineInfo{ p.linfo = LineInfo{
line_nr: line_nr line_nr: line_nr
path: file_name path: file_name
expr: expr // expr: expr
col: col
} }
} }
pub fn add_line_info_expr_to_program_text(raw_text string, linfo LineInfo) string {
lines := raw_text.split('\n')
lines_before := lines[..linfo.line_nr].join('\n')
mut expr := linfo.expr
if !expr.contains('.') {
// Single variable, `foo` => `foo.xx`
// expr += '.xxx'
// expr = '_ = ' + expr
expr = 'println(' + expr + ')'
}
lines_after := lines[linfo.line_nr..].join('\n')
return lines_before + '\n' + expr + '\n' + lines_after
}

View file

@ -264,9 +264,10 @@ pub mut:
pub struct LineInfo { pub struct LineInfo {
pub mut: pub mut:
line_nr int // a quick single file run when called with v -line-info (contains line nr to inspect) line_nr int // a quick single file run when called with v -line-info (contains line nr to inspect)
path string // same, but stores the path being parsed path string // same, but stores the path being parsed
expr string // "foo" or "foo.bar" V code (expression) which needs autocomplete // expr string // "foo" or "foo.bar" V code (expression) which needs autocomplete
col int
is_running bool // so that line info is fetched only on the second checker run is_running bool // so that line info is fetched only on the second checker run
vars_printed map[string]bool // to avoid dups vars_printed map[string]bool // to avoid dups
} }

View file

@ -111,14 +111,7 @@ pub fn new_scanner_file(file_path string, comments_mode CommentsMode, pref_ &pre
if !os.is_file(file_path) { if !os.is_file(file_path) {
return error('${file_path} is not a .v file') return error('${file_path} is not a .v file')
} }
mut raw_text := util.read_file(file_path) or { return err } raw_text := util.read_file(file_path) or { return err }
if pref_.line_info != '' {
// Add line info expr to the scanner text
abs_path := os.join_path(os.getwd(), file_path)
if pref_.linfo.path in [file_path, abs_path] {
raw_text = pref.add_line_info_expr_to_program_text(raw_text, pref_.linfo)
}
}
mut s := &Scanner{ mut s := &Scanner{
pref: pref_ pref: pref_
text: raw_text text: raw_text

View file

@ -390,6 +390,10 @@ pub fn (t Token) is_next_to(pre_token Token) bool {
return t.pos - pre_token.pos == pre_token.len return t.pos - pre_token.pos == pre_token.len
} }
pub fn (t Token) is_key() bool {
return int(t.kind) > int(Kind.keyword_beg) && int(t.kind) < int(Kind.keyword_end)
}
pub fn (t Token) str() string { pub fn (t Token) str() string {
mut s := t.kind.str() mut s := t.kind.str()
if s.len == 0 { if s.len == 0 {

20
vlib/v/token/token_test.v Normal file
View file

@ -0,0 +1,20 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module token
fn test_is_key() {
// Test that tokens with keyword kinds return true.
assert Token{
kind: .key_as
}.is_key()
assert Token{
kind: .key_fn
}.is_key()
assert Token{
kind: .key_mut
}.is_key()
assert !Token{
kind: .name
}.is_key()
}