From 9f1091637cf587f0b2b0a248198d230ee146c753 Mon Sep 17 00:00:00 2001 From: metif12 Date: Sat, 3 Oct 2026 11:59:29 +0330 Subject: [PATCH 1/6] fix: make index_max_file_bytes usable as a string repeat count PR #521 changed index_max_file_bytes from int to u64 so it compares directly against os.file_size. string.repeat still takes an int, so the three oversized-file tests stopped compiling and the whole suite failed to build on every platform. Convert at the call site and keep the constant u64, which is what index.v compares against. --- handlers_test.v | 2 +- index_test.v | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/handlers_test.v b/handlers_test.v index c5a8cdaf..2f0f9ede 100644 --- a/handlers_test.v +++ b/handlers_test.v @@ -3765,7 +3765,7 @@ fn test_handle_rename_refuses_incomplete_oversized_sibling_index() { test_file := os.join_path(test_dir, 'main.v') content := 'module main\n\nfn target() {\n\ttarget()\n}\n' must_write_file(test_file, content) - must_write_file(os.join_path(test_dir, 'oversized.v'), 'x'.repeat(index_max_file_bytes + 1)) + must_write_file(os.join_path(test_dir, 'oversized.v'), 'x'.repeat(int(index_max_file_bytes) + 1)) uri := path_to_uri(test_file) app.open_files[uri] = content diff --git a/index_test.v b/index_test.v index 3f459705..d78354b0 100644 --- a/index_test.v +++ b/index_test.v @@ -114,7 +114,7 @@ fn test_watched_file_reindex_drops_oversized_disk_entry() { app.occurrences_for(uri) assert uri in app.ref_occurrences - os.write_file(path, 'x'.repeat(index_max_file_bytes + 1)) or { + os.write_file(path, 'x'.repeat(int(index_max_file_bytes) + 1)) or { assert false, 'grow watched file failed: ${err}' return } @@ -816,7 +816,7 @@ fn test_index_completeness_is_scoped_to_relevant_project() { return } path_b := os.join_path(root_b, 'oversized.v') - os.write_file(path_b, 'x'.repeat(index_max_file_bytes + 1)) or { + os.write_file(path_b, 'x'.repeat(int(index_max_file_bytes) + 1)) or { assert false, 'write oversized root_b file failed: ${err}' return } From 7bc840f2ed13e90058d66b98ce9b7be0d607a3a0 Mon Sep 17 00:00:00 2001 From: metif12 Date: Sat, 3 Oct 2026 12:05:01 +0330 Subject: [PATCH 2/6] fix: escape interpolated paths in the Sublime Text handshake test test_integration_sublime_text_lsp_handshake hand-writes the initialize payload to mimic a real client, then interpolates the native project path straight into the rootPath JSON string. On Windows that path contains backslashes, and \U is an unknown JSON escape, so json2.decode of the params fails and on_initialize returns InvalidParams. received_initialize never becomes true and the test fails on every Windows run. Real clients escape those separators, so escape them here too. The workspace_roots assertion now compares against the URI's own path form, because roots are resolved from the folder URI and carry '/' separators rather than the native backslashes. --- integration_test.v | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/integration_test.v b/integration_test.v index d3410bdf..6a832b76 100644 --- a/integration_test.v +++ b/integration_test.v @@ -2787,19 +2787,33 @@ fn integration_run_frames(mut app App, project_dir string, name string, payloads return app.captured_output } +// integration_test_json_string escapes a value for embedding inside a JSON string +// literal. A native Windows path contains backslashes, and a raw one makes the +// whole payload undecodable: `\U` is an unknown escape sequence, so +// json2.decode fails and the server rejects `initialize` with InvalidParams. +// Real clients percent- or backslash-escape these, so the test must too. +fn integration_test_json_string(value string) string { + encoded := json2.encode(value) + return encoded[1..encoded.len - 1] +} + fn test_integration_sublime_text_lsp_handshake() { mut app, project_dir := create_integration_test_env() defer { cleanup_integration_test_env(app, project_dir) } root_uri := path_to_uri(project_dir) - initialize := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":42,"clientInfo":{"name":"Sublime Text LSP","version":"2.13.0"},"locale":"en","rootUri":"${root_uri}","rootPath":"${project_dir}","workspaceFolders":[{"uri":"${root_uri}","name":"test_project"}],"capabilities":{"general":{"positionEncodings":["utf-16"]},"workspace":{"workspaceFolders":true,"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"publishDiagnostics":{"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":{}}}' + root_path := integration_test_json_string(project_dir) + initialize := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":42,"clientInfo":{"name":"Sublime Text LSP","version":"2.13.0"},"locale":"en","rootUri":"${root_uri}","rootPath":"${root_path}","workspaceFolders":[{"uri":"${root_uri}","name":"test_project"}],"capabilities":{"general":{"positionEncodings":["utf-16"]},"workspace":{"workspaceFolders":true,"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"publishDiagnostics":{"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":{}}}' initialized := '{"jsonrpc":"2.0","method":"initialized","params":{}}' output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [initialize, initialized]) assert app.received_initialize - assert app.workspace_roots == [project_dir] + // Workspace roots are resolved from the folder URI, so they carry the '/' + // separators a file URI uses. Compare against the URI's own path form rather + // than the native one, which is backslash-separated on Windows. + assert app.workspace_roots == [uri_to_path(root_uri)] assert app.position_encoding == .utf16 assert app.supports_dynamic_watched_files_registration assert app.supports_work_done_progress From d38a9395130f069be80e6118706bc2e9dc275f5c Mon Sep 17 00:00:00 2001 From: metif12 Date: Sat, 3 Oct 2026 12:06:04 +0330 Subject: [PATCH 3/6] chore: restore v fmt compliance on master --- call_hierarchy.v | 58 ++-- compilation_test.v | 2 +- diagnostics_scheduler.v | 52 +-- document_highlight.v | 24 +- folding_range.v | 30 +- handlers.v | 499 ++++++++++++++-------------- handlers_test.v | 702 ++++++++++++++++++++-------------------- index.v | 46 +-- index_test.v | 12 +- integration_test.v | 260 +++++++-------- interop.v | 51 +-- interop_test.v | 180 +++++------ lsp.v | 84 ++--- lsp_test.v | 217 +++++++------ main.v | 239 +++++++------- run_command.v | 18 +- semantic_tokens.v | 58 ++-- 17 files changed, 1273 insertions(+), 1259 deletions(-) diff --git a/call_hierarchy.v b/call_hierarchy.v index 9b5026f9..8b5a18bf 100644 --- a/call_hierarchy.v +++ b/call_hierarchy.v @@ -14,7 +14,7 @@ fn (mut app App) handle_prepare_call_hierarchy(request Request) Response { log('Failed to decode PrepareCallHierarchyParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -23,7 +23,7 @@ fn (mut app App) handle_prepare_call_hierarchy(request Request) Response { lines := content.split_into_lines() if params.position.line < 0 || params.position.line >= lines.len { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -31,14 +31,14 @@ fn (mut app App) handle_prepare_call_hierarchy(request Request) Response { start, end := find_word_bounds_at_col(line_text, params.position.char, app.position_encoding) if start < 0 || end <= start { return Response{ - id: request.id + id: request.id result: 'null' } } word := substr_by_char_bounds(line_text, start, end, app.position_encoding) if word == '' { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -51,12 +51,12 @@ fn (mut app App) handle_prepare_call_hierarchy(request Request) Response { } if item.name == '' { return Response{ - id: request.id + id: request.id result: 'null' } } return Response{ - id: request.id + id: request.id result: [item] } } @@ -70,14 +70,14 @@ fn (mut app App) handle_call_hierarchy_incoming(request Request) Response { log('Failed to decode CallHierarchyIncomingCallsParams: ${err}') } return Response{ - id: request.id + id: request.id result: []CallHierarchyIncomingCall{} } } fn_name := extract_simple_fn_name(params.item.name) if fn_name == '' { return Response{ - id: request.id + id: request.id result: []CallHierarchyIncomingCall{} } } @@ -90,7 +90,7 @@ fn (mut app App) handle_call_hierarchy_incoming(request Request) Response { for uri, fc in app.open_files { if request.id in app.cancelled_requests { return Response{ - id: request.id + id: request.id result: results } } @@ -108,7 +108,7 @@ fn (mut app App) handle_call_hierarchy_incoming(request Request) Response { for f in os.walk_ext(dir, '.v') { if request.id in app.cancelled_requests { return Response{ - id: request.id + id: request.id result: results } } @@ -125,7 +125,7 @@ fn (mut app App) handle_call_hierarchy_incoming(request Request) Response { } } return Response{ - id: request.id + id: request.id result: results } } @@ -138,7 +138,7 @@ fn (mut app App) handle_call_hierarchy_outgoing(request Request) Response { log('Failed to decode CallHierarchyOutgoingCallsParams: ${err}') } return Response{ - id: request.id + id: request.id result: []CallHierarchyOutgoingCall{} } } @@ -174,12 +174,12 @@ fn (mut app App) handle_call_hierarchy_outgoing(request Request) Response { continue } results << CallHierarchyOutgoingCall{ - to: callee + to: callee from_ranges: call_ranges } } return Response{ - id: request.id + id: request.id result: results } } @@ -208,10 +208,10 @@ fn find_fn_in_content(fn_name string, content string, uri string, enc PositionEn } if extract_simple_fn_name(sym.name) == fn_name { return CallHierarchyItem{ - name: sym.name - kind: sym.kind - uri: uri - range: sym.range + name: sym.name + kind: sym.kind + uri: uri + range: sym.range selection_range: sym.selection_range } } @@ -235,10 +235,10 @@ fn (mut app App) find_fn_declaration(fn_name string, search_dirs []string, inclu for dirs in dir_passes { if uri, sym := app.find_indexed_fn(fn_name, include_tests, dirs) { return CallHierarchyItem{ - name: sym.name - kind: sym.kind - uri: uri - range: sym.range + name: sym.name + kind: sym.kind + uri: uri + range: sym.range selection_range: sym.selection_range } } @@ -305,7 +305,7 @@ fn scan_for_callers(fn_name string, file_uri string, file_content string, enc Po line: li char: start_char } - end: Position{ + end: Position{ line: li char: end_char } @@ -315,11 +315,11 @@ fn scan_for_callers(fn_name string, file_uri string, file_content string, enc Po } if call_ranges.len > 0 { results << CallHierarchyIncomingCall{ - from: CallHierarchyItem{ - name: sym.name - kind: sym.kind - uri: file_uri - range: sym.range + from: CallHierarchyItem{ + name: sym.name + kind: sym.kind + uri: file_uri + range: sym.range selection_range: sym.selection_range } from_ranges: call_ranges @@ -380,7 +380,7 @@ fn find_fn_calls_in_line(line string, line_idx int, enc PositionEncoding, mut ca line: line_idx char: start_char } - end: Position{ + end: Position{ line: line_idx char: end_char } diff --git a/compilation_test.v b/compilation_test.v index 51ef750c..deb3e07c 100644 --- a/compilation_test.v +++ b/compilation_test.v @@ -16,7 +16,7 @@ fn test_smoke_incremental_edit_is_lossless() { line: 0 char: 1 } - end: Position{ + end: Position{ line: 0 char: 1 } diff --git a/diagnostics_scheduler.v b/diagnostics_scheduler.v index fbc884cc..fdb3117c 100644 --- a/diagnostics_scheduler.v +++ b/diagnostics_scheduler.v @@ -56,9 +56,9 @@ mut: fn new_diagnostics_scheduler() &DiagnosticsScheduler { return &DiagnosticsScheduler{ - generations: map[string]u64{} + generations: map[string]u64{} project_generations: map[string]u64{} - pending_jobs: map[string]DiagnosticsJob{} + pending_jobs: map[string]DiagnosticsJob{} } } @@ -107,9 +107,9 @@ fn (mut scheduler DiagnosticsScheduler) begin_project_mutation(project_key strin for affected_uri, _ in affected { scheduler.generations[affected_uri] = scheduler.generations[affected_uri] + 1 tickets << DiagnosticsTicket{ - uri: affected_uri - global_generation: scheduler.global_generation - generation: scheduler.generations[affected_uri] + uri: affected_uri + global_generation: scheduler.global_generation + generation: scheduler.generations[affected_uri] project_generation: project_generation } } @@ -225,7 +225,7 @@ fn (mut app App) begin_diagnostics_project_schedule(uri string) DiagnosticsProje project_key := app.generation_key(uri) return DiagnosticsProjectMutation{ project_key: project_key - tickets: scheduler.begin_project_schedule(uri, project_key) + tickets: scheduler.begin_project_schedule(uri, project_key) } } return DiagnosticsProjectMutation{} @@ -249,7 +249,7 @@ fn (mut app App) begin_diagnostics_project_mutation(uri string) DiagnosticsProje project_key := app.generation_key(uri) return DiagnosticsProjectMutation{ project_key: project_key - tickets: scheduler.begin_project_mutation(project_key, '') + tickets: scheduler.begin_project_mutation(project_key, '') } } return DiagnosticsProjectMutation{} @@ -283,19 +283,19 @@ fn (mut app App) enqueue_diagnostics_tickets(mut scheduler DiagnosticsScheduler, version = current_version } job := DiagnosticsJob{ - uri: ticket.uri - content: job_content - version: version - project_key: project_key - project_generation: ticket.project_generation - position_encoding: app.position_encoding - open_files: app.open_files.clone() + uri: ticket.uri + content: job_content + version: version + project_key: project_key + project_generation: ticket.project_generation + position_encoding: app.position_encoding + open_files: app.open_files.clone() project_generations: app.project_generations.clone() - write_mutex: app.write_mutex - tcp_conn: app.tcp_conn - global_generation: ticket.global_generation - generation: ticket.generation - ready_at: ready_at + write_mutex: app.write_mutex + tcp_conn: app.tcp_conn + global_generation: ticket.global_generation + generation: ticket.generation + ready_at: ready_at } if scheduler.enqueue(job) { should_start = true @@ -346,16 +346,16 @@ fn run_diagnostics_job(mut scheduler DiagnosticsScheduler, job DiagnosticsJob) { versions[job.uri] = version } mut worker := App{ - text: job.content - open_files: job.open_files + text: job.content + open_files: job.open_files open_files_versions: versions - temp_dir: temp_dir + temp_dir: temp_dir diagnostics_enabled: true - diag_cache: map[string]DiagCacheEntry{} + diag_cache: map[string]DiagCacheEntry{} project_generations: job.project_generations - position_encoding: job.position_encoding - write_mutex: job.write_mutex - tcp_conn: job.tcp_conn + position_encoding: job.position_encoding + write_mutex: job.write_mutex + tcp_conn: job.tcp_conn } notification := worker.build_diagnostics_notification(job.uri, job.content) scheduler.publish_if_current(mut worker, job, notification) diff --git a/document_highlight.v b/document_highlight.v index 49993502..d64f0b3f 100644 --- a/document_highlight.v +++ b/document_highlight.v @@ -165,9 +165,9 @@ fn collect_document_highlight_candidates(content string, lines []string, symbol } line := lines[position.line] candidates << DocumentHighlightCandidate{ - line_idx: position.line + line_idx: position.line start_byte: encoded_col_to_byte(line, position.start_char, enc) - end_byte: encoded_col_to_byte(line, position.end_char, enc) + end_byte: encoded_col_to_byte(line, position.end_char, enc) } } return candidates @@ -182,7 +182,7 @@ fn (mut app App) handle_document_highlight(request Request) Response { log('Failed to decode DocumentHighlightParams: ${err}') } return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } @@ -190,14 +190,14 @@ fn (mut app App) handle_document_highlight(request Request) Response { content := app.open_files[uri] or { os.read_file(uri_to_path(uri)) or { '' } } if content == '' { return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } lines := content.split_into_lines() if params.position.line < 0 || params.position.line >= lines.len { return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } @@ -205,21 +205,21 @@ fn (mut app App) handle_document_highlight(request Request) Response { start, end := find_word_bounds_at_col(line_text, params.position.char, app.position_encoding) if start < 0 || end <= start { return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } symbol := substr_by_char_bounds(line_text, start, end, app.position_encoding) if symbol == '' { return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } candidates := collect_document_highlight_candidates(content, lines, symbol, app.position_encoding) if candidates.len > document_highlight_semantic_max_candidates { return Response{ - id: request.id + id: request.id result: []DocumentHighlight{} } } @@ -246,7 +246,7 @@ fn (mut app App) handle_document_highlight(request Request) Response { mut kind := classify_highlight_kind(line, candidate.start_byte, candidate.end_byte) if a := anchor { occurrence := Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: candidate.line_idx @@ -264,16 +264,16 @@ fn (mut app App) handle_document_highlight(request Request) Response { line: candidate.line_idx char: start_char } - end: Position{ + end: Position{ line: candidate.line_idx char: end_char } } - kind: kind // Read/Write (P2-03) + kind: kind // Read/Write (P2-03) } } return Response{ - id: request.id + id: request.id result: highlights } } diff --git a/folding_range.v b/folding_range.v index a1ddd45e..a40741c7 100644 --- a/folding_range.v +++ b/folding_range.v @@ -13,7 +13,7 @@ fn (mut app App) handle_folding_range(request Request) Response { log('Failed to decode FoldingRangeParams: ${err}') } return Response{ - id: request.id + id: request.id result: []FoldingRange{} } } @@ -21,12 +21,12 @@ fn (mut app App) handle_folding_range(request Request) Response { content := app.open_files[uri] or { os.read_file(uri_to_path(uri)) or { '' } } if content == '' { return Response{ - id: request.id + id: request.id result: []FoldingRange{} } } return Response{ - id: request.id + id: request.id result: compute_folding_ranges(content) } } @@ -57,8 +57,8 @@ fn compute_folding_ranges(content string) []FoldingRange { if i > block_comment_start { ranges << FoldingRange{ start_line: block_comment_start - end_line: i - kind: 'comment' + end_line: i + kind: 'comment' } } block_comment_start = -1 @@ -83,8 +83,8 @@ fn compute_folding_ranges(content string) []FoldingRange { if import_start >= 0 && import_end > import_start { ranges << FoldingRange{ start_line: import_start - end_line: import_end - kind: 'imports' + end_line: import_end + kind: 'imports' } } import_start = -1 @@ -101,8 +101,8 @@ fn compute_folding_ranges(content string) []FoldingRange { if comment_start >= 0 && comment_end > comment_start { ranges << FoldingRange{ start_line: comment_start - end_line: comment_end - kind: 'comment' + end_line: comment_end + kind: 'comment' } } comment_start = -1 @@ -122,8 +122,8 @@ fn compute_folding_ranges(content string) []FoldingRange { if i > start { ranges << FoldingRange{ start_line: start - end_line: i - kind: 'region' + end_line: i + kind: 'region' } } } @@ -135,15 +135,15 @@ fn compute_folding_ranges(content string) []FoldingRange { if import_start >= 0 && import_end > import_start { ranges << FoldingRange{ start_line: import_start - end_line: import_end - kind: 'imports' + end_line: import_end + kind: 'imports' } } if comment_start >= 0 && comment_end > comment_start { ranges << FoldingRange{ start_line: comment_start - end_line: comment_end - kind: 'comment' + end_line: comment_end + kind: 'comment' } } diff --git a/handlers.v b/handlers.v index 23ecedf0..3dec3f8b 100644 --- a/handlers.v +++ b/handlers.v @@ -8,17 +8,17 @@ import time import v.pref const v_keywords = ['asm', 'as', 'assert', 'atomic', 'break', 'const', 'continue', 'defer', 'dump', - 'else', 'enum', 'false', 'fn', 'for', 'go', 'goto', 'if', 'ilike', 'implements', 'import', 'in', - 'interface', 'is', 'isreftype', 'like', 'lock', 'match', 'module', 'mut', 'nil', 'none', 'or', - 'pub', 'return', 'rlock', 'select', 'shared', 'sizeof', 'spawn', 'static', 'struct', 'true', - 'type', 'typeof', 'union', 'unsafe', 'volatile']! + 'else', 'enum', 'false', 'fn', 'for', 'go', 'goto', 'if', 'ilike', 'implements', 'import', + 'in', 'interface', 'is', 'isreftype', 'like', 'lock', 'match', 'module', 'mut', 'nil', 'none', + 'or', 'pub', 'return', 'rlock', 'select', 'shared', 'sizeof', 'spawn', 'static', 'struct', + 'true', 'type', 'typeof', 'union', 'unsafe', 'volatile']! const v_builtins = ['close', 'copy', 'eprintln', 'eprint', 'error', 'error_with_code', 'exit', 'flush_stderr', 'flush_stdout', 'free', 'isnil', 'panic', 'print', 'println']! -const v_builtin_types = ['any', 'array', 'bool', 'byte', 'byteptr', 'chan', 'char', 'charptr', 'f32', - 'f64', 'i8', 'i16', 'i32', 'i64', 'int', 'isize', 'IError', 'map', 'rune', 'string', 'thread', - 'u8', 'u16', 'u32', 'u64', 'usize', 'void', 'voidptr']! +const v_builtin_types = ['any', 'array', 'bool', 'byte', 'byteptr', 'chan', 'char', 'charptr', + 'f32', 'f64', 'i8', 'i16', 'i32', 'i64', 'int', 'isize', 'IError', 'map', 'rune', 'string', + 'thread', 'u8', 'u16', 'u32', 'u64', 'usize', 'void', 'voidptr']! struct IndexedCompletionResult { items []Detail @@ -140,7 +140,7 @@ fn source_call_target(content string, cursor Position, enc PositionEncoding) ?So name_start_in_line } return SourceCallTarget{ - position: Position{ + position: Position{ line: target_line char: byte_to_encoded_col(target_line_text, probe_byte, enc) } @@ -373,7 +373,7 @@ fn (mut app App) hover_with_written_declaration(uri string, position Position, r } return Hover{ contents: MarkupContent{ - kind: hover.contents.kind + kind: hover.contents.kind value: value[..body_start] + declaration + '\n' + value[body_start + close_offset..] } } @@ -389,7 +389,7 @@ fn (mut app App) source_hover_fallback(uri string, position Position) ?Hover { } return Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: '```v\n${declaration}\n```' } } @@ -697,7 +697,7 @@ fn (mut app App) local_binding_hover(uri string, position Position) ?Hover { if declared != '' { return Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: '```v\n${name} ${declared}\n```' } } @@ -708,7 +708,7 @@ fn (mut app App) local_binding_hover(uri string, position Position) ?Hover { } return Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: '```v\n${name} ${typ}\n```' } } @@ -734,9 +734,9 @@ fn (mut app App) source_signature_fallback(uri string, position Position) ?Signa } parameters := signature_parameters(label) return SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: label + label: label parameters: parameters }, ] @@ -751,7 +751,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { log('Failed to decode TextDocumentPositionParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -760,7 +760,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { log('operation_at_pos: missing textDocument.uri') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -768,7 +768,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { // than indexing arrays with negative values (P1-09). if params.position.line < 0 || params.position.char < 0 { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -794,18 +794,18 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { } items := merge_completion_items(indexed.items, compiler_items) return Response{ - id: request.id + id: request.id result: CompletionList{ is_incomplete: false - items: items + items: items } } } return Response{ - id: request.id + id: request.id result: CompletionList{ is_incomplete: false - items: indexed.items + items: indexed.items } } } @@ -818,7 +818,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { if method in [.definition, .declaration, .type_definition, .implementation] { if location := app.resolve_indexed_definition(path, params.position) { return Response{ - id: request.id + id: request.id result: location } } @@ -827,7 +827,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { if method == .hover { if binding := app.local_binding_hover(path, params.position) { return Response{ - id: request.id + id: request.id result: binding } } @@ -837,7 +837,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { // the documentation with its answer. if member := app.member_selector_hover(path, params.position) { return Response{ - id: request.id + id: request.id result: member } } @@ -877,7 +877,7 @@ fn (mut app App) operation_at_pos(method Method, request Request) Response { log(result.str()) } return Response{ - id: request.id + id: request.id result: result } } @@ -922,7 +922,7 @@ fn (mut app App) indexed_completions(uri string, position Position) IndexedCompl if !has_local_binding { module_result := app.get_imported_module_member_completions(module_path, os.dir(uri_to_path(uri))) return IndexedCompletionResult{ - items: module_result.items + items: module_result.items use_compiler: module_result.use_compiler } } @@ -952,7 +952,7 @@ fn (mut app App) indexed_completions(uri string, position Position) IndexedCompl if struct_type != '' { field_result := app.indexed_struct_field_completions(uri, content, struct_type) return IndexedCompletionResult{ - items: field_result.items + items: field_result.items use_compiler: field_result.use_compiler || field_result.items.len == 0 } } @@ -972,8 +972,8 @@ fn (mut app App) indexed_completions(uri string, position Position) IndexedCompl for binding in parse_import_bindings(content) { if binding.alias != '' && binding.alias !in seen_labels { details << Detail{ - kind: 9 // CompletionItemKind.Module - label: binding.alias + kind: 9 // CompletionItemKind.Module + label: binding.alias detail: binding.module_path } seen_labels[binding.alias] = true @@ -992,7 +992,7 @@ fn (mut app App) indexed_completions(uri string, position Position) IndexedCompl } } return IndexedCompletionResult{ - items: details + items: details use_compiler: use_compiler } } @@ -1076,8 +1076,8 @@ fn binding_scope_header_starts_literal(source string) bool { } struct AnonymousFunctionHeader { - found bool - complete bool + found bool + complete bool parameter_names []string parameter_types map[string]string } @@ -1144,8 +1144,8 @@ fn anonymous_function_header(source string) AnonymousFunctionHeader { } } return AnonymousFunctionHeader{ - found: true - complete: true + found: true + complete: true parameter_names: names parameter_types: types } @@ -1156,8 +1156,8 @@ fn expression_line_is_continued(line string) bool { if trimmed == '' { return false } - return trimmed[trimmed.len - 1] in [`.`, `,`, `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `=`, `!`, - `<`, `>`, `?`, `:`] + return trimmed[trimmed.len - 1] in [`.`, `,`, `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `=`, + `!`, `<`, `>`, `?`, `:`] } fn struct_literal_cursor_is_at_field(prefix string, open_brace int, raw_lines []string) bool { @@ -1671,8 +1671,8 @@ fn (app &App) local_scope_bindings(content string, position Position) []LocalBin mut parameter_bindings := []LocalBinding{} for name in parameter_names { parameter_bindings << LocalBinding{ - name: name - line: function_start + name: name + line: function_start column: -1 } } @@ -1764,8 +1764,8 @@ fn (app &App) local_scope_bindings(content string, position Position) []LocalBin for name in outer_segment_names { if !scopes.last().any(it.name == name) { scopes[scopes.len - 1] << LocalBinding{ - name: name - line: line_idx + name: name + line: line_idx column: local_binding_column(segment, segment_start, name) } } @@ -1778,14 +1778,14 @@ fn (app &App) local_scope_bindings(content string, position Position) []LocalBin for name in block_names { if !scopes.last().any(it.name == name) { scopes[scopes.len - 1] << LocalBinding{ - name: name - line: block_line + name: name + line: block_line column: if binding_scope_header { local_binding_column(segment, segment_start, name) } else { pending_block_columns[name] or { -1 } } - typ: if closure_header.complete { + typ: if closure_header.complete { closure_header.parameter_types[name] or { '' } } else { '' @@ -1829,8 +1829,8 @@ fn (app &App) local_scope_bindings(content string, position Position) []LocalBin for name in tail_names { if !scopes.last().any(it.name == name) { scopes[scopes.len - 1] << LocalBinding{ - name: name - line: line_idx + name: name + line: line_idx column: local_binding_column(tail, segment_start, name) } } @@ -1863,8 +1863,8 @@ fn (app &App) local_scope_bindings(content string, position Position) []LocalBin if has_implicit_it_scope_at_cursor(active_code_lines.join('\n')) && scopes.len > 0 && !has_explicit_it { scopes[scopes.len - 1] << LocalBinding{ - name: 'it' - line: position.line + name: 'it' + line: position.line column: -1 } } @@ -1885,8 +1885,8 @@ fn (app &App) local_scope_completions(content string, position Position) []Detai } } return names.filter(it != '').map(Detail{ - kind: 6 // CompletionItemKind.Variable - label: it + kind: 6 // CompletionItemKind.Variable + label: it detail: 'local binding' }) } @@ -2014,8 +2014,8 @@ fn receiver_rhs_needs_continuation(rhs string, has_expression bool, scan_state & if trimmed == '' { return false } - return trimmed[trimmed.len - 1] in [`.`, `,`, `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `=`, `!`, - `<`, `>`, `?`, `:`] + return trimmed[trimmed.len - 1] in [`.`, `,`, `+`, `-`, `*`, `/`, `%`, `&`, `|`, `^`, `=`, + `!`, `<`, `>`, `?`, `:`] } struct ReceiverDeclaration { @@ -2054,9 +2054,9 @@ fn receiver_declaration_on_line(code string, receiver string, active_columns []i continue } latest = ReceiverDeclaration{ - rhs: raw_statement[assign_idx + 2..].trim_space() - binding_index: binding_index - binding_count: bindings.len + rhs: raw_statement[assign_idx + 2..].trim_space() + binding_index: binding_index + binding_count: bindings.len binding_column: statement_start + lhs_start + receiver_column assignment_end: statement_start + assign_idx + 2 } @@ -2421,10 +2421,10 @@ fn method_completion_from_lines(lines []string, symbol DocumentSymbol) ?Detail { } insert := build_fn_snippet(name, after_receiver[paren_idx..]) return Detail{ - kind: 2 - label: name - detail: '${if trimmed.starts_with('pub ') { 'pub ' } else { '' }}fn ${after_fn}'.all_before('{').trim_space() - insert_text: insert + kind: 2 + label: name + detail: '${if trimmed.starts_with('pub ') { 'pub ' } else { '' }}fn ${after_fn}'.all_before('{').trim_space() + insert_text: insert insert_text_format: if insert.contains('\$') { 2 } else { 1 } } } @@ -2518,13 +2518,13 @@ fn (mut app App) indexed_method_symbols(uri string, content string, receiver_typ } } matches << Location{ - uri: indexed_uri + uri: indexed_uri range: LSPRange{ start: Position{ line: symbol.range.start.line char: symbol.selection_range.start.char } - end: Position{ + end: Position{ line: symbol.range.start.line char: symbol.selection_range.end.char } @@ -2533,8 +2533,8 @@ fn (mut app App) indexed_method_symbols(uri string, content string, receiver_typ } } return IndexedMethodSymbolResult{ - locations: matches - items: items + locations: matches + items: items use_compiler: has_conditional } } @@ -2570,8 +2570,8 @@ fn field_completion_from_symbol(lines []string, code_lines []string, symbol Docu return none } return Detail{ - kind: 5 // CompletionItemKind.Field - label: symbol.name + kind: 5 // CompletionItemKind.Field + label: symbol.name detail: lines[line_idx].trim_space() } } @@ -2760,12 +2760,12 @@ fn (mut app App) indexed_struct_field_completions_visited(uri string, content st } } return IndexedCompletionResult{ - items: items - use_compiler: has_conditional || has_unresolved_embedded - embedded_types: embedded_types - field_types: field_types + items: items + use_compiler: has_conditional || has_unresolved_embedded + embedded_types: embedded_types + field_types: field_types field_declared_types: field_declared_types - resolved_type: resolved_type + resolved_type: resolved_type } } @@ -2854,10 +2854,10 @@ fn thread_wait_completion(receiver_type string) ?Detail { ' ${payload}' } return Detail{ - kind: 2 - label: 'wait' - detail: 'fn (${receiver}) wait()${result}' - insert_text: 'wait()' + kind: 2 + label: 'wait' + detail: 'fn (${receiver}) wait()${result}' + insert_text: 'wait()' insert_text_format: 1 } } @@ -2969,8 +2969,8 @@ fn (mut app App) indexed_enum_members(uri string, content string, type_name stri for symbol in entry.doc_symbols { if symbol.kind == sym_kind_enum && symbol.name == name { return symbol.children.filter(it.kind == sym_kind_enum_member).map(Detail{ - kind: 20 // CompletionItemKind.EnumMember - label: it.name + kind: 20 // CompletionItemKind.EnumMember + label: it.name detail: type_name }) } @@ -3025,7 +3025,8 @@ fn (mut app App) expected_enum_type(uri string, content string, lines []string, } for op in ['==', '!=', '='] { if before.ends_with(op) { - if op == '=' && before.len > 1 && before[before.len - 2] in [`:`, `<`, `>`, `+`, `-`, `*`, `/`, `%`, `|`, `&`, `^`] { + if op == '=' && before.len > 1 && before[before.len - 2] in [`:`, `<`, `>`, `+`, `-`, + `*`, `/`, `%`, `|`, `&`, `^`] { break } left := trailing_selector(before[..before.len - op.len].trim_right(' \t')) @@ -3314,11 +3315,11 @@ fn (mut app App) callback_argument_completions(uri string, content string, lines label, skeleton := callback_skeleton(fn_type, generic_default) or { return [] } return [ Detail{ - kind: 15 // CompletionItemKind.Snippet - label: label - detail: 'function literal' - sort_text: '0' - insert_text: skeleton + kind: 15 // CompletionItemKind.Snippet + label: label + detail: 'function literal' + sort_text: '0' + insert_text: skeleton insert_text_format: 2 }, ] @@ -3510,7 +3511,7 @@ fn parse_import_binding(text string) ?ImportedModuleBinding { return none } return ImportedModuleBinding{ - alias: alias + alias: alias module_path: module_path } } @@ -3586,7 +3587,7 @@ fn (mut app App) get_imported_module_member_completions(module_path string, work } } return IndexedModuleCompletionResult{ - items: items + items: items use_compiler: has_conditional || items.len == 0 } } @@ -3816,8 +3817,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := first_word(trimmed) if is_valid_v_identifier_name(name) { items << Detail{ - kind: 6 // CompletionItemKind.Variable - label: name + kind: 6 // CompletionItemKind.Variable + label: name detail: '__global' } } @@ -3841,8 +3842,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := const_block_assignment_name(trimmed) if name != '' && (!public_only || const_block_public) { items << Detail{ - kind: 21 // CompletionItemKind.Constant - label: name + kind: 21 // CompletionItemKind.Constant + label: name detail: if const_block_public { 'pub const' } else { 'const' } } } @@ -3870,10 +3871,10 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines detail_str := '${if is_public { 'pub ' } else { '' }}${complete_declaration}'.all_before('{').trim_space() insert := build_fn_snippet(fn_name, after_fn[paren_idx..]) items << Detail{ - kind: 3 // CompletionItemKind.Function - label: fn_name - detail: detail_str - insert_text: insert + kind: 3 // CompletionItemKind.Function + label: fn_name + detail: detail_str + insert_text: insert insert_text_format: if insert.contains('\$') { 2 } else { 1 } } continue @@ -3882,8 +3883,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := extract_const_name(declaration[6..]) if name != '' { items << Detail{ - kind: 21 - label: name + kind: 21 + label: name detail: trimmed.all_before('=').trim_space() } } @@ -3893,8 +3894,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := module_type_completion_name(declaration[7..]) if name != '' { items << Detail{ - kind: 22 // CompletionItemKind.Struct - label: name + kind: 22 // CompletionItemKind.Struct + label: name detail: trimmed.all_before('{').trim_space() } } @@ -3904,8 +3905,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := module_type_completion_name(declaration[6..]) if name != '' { items << Detail{ - kind: 22 // CompletionItemKind.Struct - label: name + kind: 22 // CompletionItemKind.Struct + label: name detail: trimmed.all_before('{').trim_space() } } @@ -3915,8 +3916,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := module_type_completion_name(declaration[5..]) if name != '' { items << Detail{ - kind: 13 // CompletionItemKind.Enum - label: name + kind: 13 // CompletionItemKind.Enum + label: name detail: trimmed.all_before('{').trim_space() } } @@ -3926,8 +3927,8 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := module_type_completion_name(declaration[10..]) if name != '' { items << Detail{ - kind: 8 // CompletionItemKind.Interface - label: name + kind: 8 // CompletionItemKind.Interface + label: name detail: trimmed.all_before('{').trim_space() } } @@ -3937,15 +3938,15 @@ fn parse_module_member_completions_from_lines(lines []string, conditional_lines name := module_type_completion_name(declaration[5..]) if name != '' { items << Detail{ - kind: 7 // CompletionItemKind.Class - label: name + kind: 7 // CompletionItemKind.Class + label: name detail: trimmed.all_before('=').trim_space() } } } } return ParsedModuleCompletionIndex{ - items: items + items: items has_conditional: has_conditional } } @@ -4035,8 +4036,8 @@ fn (mut app App) build_diagnostics_notification(uri string, content string) Noti return Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: uri - version: if uri in app.open_files_versions { + uri: uri + version: if uri in app.open_files_versions { ?i64(app.open_files_versions[uri]) } else { none @@ -4064,8 +4065,8 @@ fn (mut app App) build_diagnostics_notification(uri string, content string) Noti diagnostics << app.encode_diagnostic_range(v_error_to_lsp_diagnostic(v_err), lines) } pd_params := PublishDiagnosticsParams{ - uri: uri - version: if uri in app.open_files_versions { + uri: uri + version: if uri in app.open_files_versions { ?i64(app.open_files_versions[uri]) } else { none @@ -4176,7 +4177,7 @@ fn (app &App) encode_diagnostic_range(diag LSPDiagnostic, lines []string) LSPDia line: start_line char: start_char } - end: Position{ + end: Position{ line: end_line char: end_char } @@ -4245,14 +4246,14 @@ fn (mut app App) on_will_save_wait_until(request Request) Response { log('Failed to decode WillSaveTextDocumentParams: ${err}') } return Response{ - id: request.id + id: request.id result: []TextEdit{} } } uri := params.text_document.uri content := app.open_files[uri] or { return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -4262,7 +4263,7 @@ fn (mut app App) on_will_save_wait_until(request Request) Response { // would desynchronize the server from the editor (P0-07 item 7). edits, _ := app.format_content(uri, content) return Response{ - id: request.id + id: request.id result: edits } } @@ -4276,7 +4277,7 @@ fn (mut app App) handle_prepare_rename(request Request) Response { log('Failed to decode TextDocumentPositionParams for prepareRename: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4285,7 +4286,7 @@ fn (mut app App) handle_prepare_rename(request Request) Response { lines := content.split_into_lines() if params.position.line < 0 || params.position.line >= lines.len { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4293,14 +4294,14 @@ fn (mut app App) handle_prepare_rename(request Request) Response { start, end := find_word_bounds_at_col(line_text, params.position.char, app.position_encoding) if start < 0 || end <= start { return Response{ - id: request.id + id: request.id result: 'null' } } symbol := substr_by_char_bounds(line_text, start, end, app.position_encoding) if symbol == '' { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4308,26 +4309,26 @@ fn (mut app App) handle_prepare_rename(request Request) Response { first := symbol[0] if !is_ident_start(first) { return Response{ - id: request.id + id: request.id result: 'null' } } // Reject V keywords and built-in function names — they cannot be renamed. if symbol in v_keywords || symbol in v_builtins { return Response{ - id: request.id + id: request.id result: 'null' } } return Response{ - id: request.id + id: request.id result: PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: params.position.line char: start } - end: Position{ + end: Position{ line: params.position.line char: end } @@ -4345,10 +4346,10 @@ fn add_workspace_symbol(mut results []WorkspaceSymbol, mut seen_symbols map[stri } seen_symbols[key] = true results << WorkspaceSymbol{ - name: name - kind: kind + name: name + kind: kind location: Location{ - uri: uri + uri: uri range: rng } } @@ -4410,7 +4411,7 @@ fn (mut app App) handle_workspace_symbol(request Request) Response { log('Failed to decode WorkspaceSymbolParams: ${err}') } return Response{ - id: request.id + id: request.id result: []WorkspaceSymbol{} } } @@ -4424,7 +4425,7 @@ fn (mut app App) handle_workspace_symbol(request Request) Response { results := app.query_workspace_symbols(query) app.end_progress(token, '') return Response{ - id: request.id + id: request.id result: results } } @@ -4556,7 +4557,7 @@ fn (mut app App) find_references(request Request) Response { log('Failed to decode ReferenceParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4568,7 +4569,7 @@ fn (mut app App) find_references(request Request) Response { symbol := app.get_word_at_position(path, line, col) if symbol == '' { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4598,13 +4599,13 @@ fn (mut app App) find_references(request Request) Response { } if locations.len == 0 { return Response{ - id: request.id + id: request.id result: 'null' } } return Response{ - id: request.id + id: request.id result: locations } } @@ -4616,7 +4617,7 @@ fn (mut app App) handle_rename(request Request) Response { log('Failed to decode RenameParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4629,7 +4630,7 @@ fn (mut app App) handle_rename(request Request) Response { symbol := app.get_word_at_position(path, line, col) if symbol == '' { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4642,7 +4643,7 @@ fn (mut app App) handle_rename(request Request) Response { if !app.index_is_complete_for_scope(scope) { log('rename: source index is incomplete; refusing a partial workspace edit') return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4655,7 +4656,7 @@ fn (mut app App) handle_rename(request Request) Response { anchor := app.resolve_symbol_anchor(path, line, col) or { log('rename: could not resolve a semantic anchor for "${symbol}"; refusing lexical rename (P1-04)') return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4666,7 +4667,7 @@ fn (mut app App) handle_rename(request Request) Response { if locations.len == 0 { log('rename: no scope-safe occurrences for "${symbol}" (unresolved or above candidate cap); refusing') return Response{ - id: request.id + id: request.id result: 'null' } } @@ -4681,9 +4682,9 @@ fn (mut app App) handle_rename(request Request) Response { loc.range.start.char + byte_to_encoded_col(symbol, symbol.len, app.position_encoding) } edit := TextEdit{ - range: LSPRange{ + range: LSPRange{ start: loc.range.start - end: Position{ + end: Position{ line: loc.range.start.line char: end_char } @@ -4704,17 +4705,17 @@ fn (mut app App) handle_rename(request Request) Response { } doc_changes << TextDocumentEdit{ text_document: OptionalVersionedTextDocumentIdentifier{ - uri: uri + uri: uri version: version } - edits: edits + edits: edits } } return Response{ - id: request.id + id: request.id result: WorkspaceEdit{ - changes: changes + changes: changes document_changes: doc_changes } } @@ -5728,7 +5729,7 @@ fn (mut app App) find_indexed_source_definition(dir string, symbol string, activ continue } matches << Location{ - uri: uri + uri: uri range: sym.selection_range } } @@ -5984,9 +5985,9 @@ fn get_import_completions(line string, work_dir string) []Detail { continue } results << Detail{ - kind: 9 // CompletionItemKind.Module - label: entry - detail: 'V stdlib module' + kind: 9 // CompletionItemKind.Module + label: entry + detail: 'V stdlib module' insert_text: entry } } @@ -6009,9 +6010,9 @@ fn get_import_completions(line string, work_dir string) []Detail { continue } results << Detail{ - kind: 9 - label: entry - detail: 'Local module' + kind: 9 + label: entry + detail: 'Local module' insert_text: entry } } @@ -6212,12 +6213,12 @@ fn (mut app App) format_content(uri string, content string) ([]TextEdit, string) end_char := byte_to_encoded_col(final_segment, final_segment.len, app.position_encoding) edit := TextEdit{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: end_line char: end_char } @@ -6232,7 +6233,7 @@ fn (mut app App) handle_formatting(request Request) Response { params := json2.decode[DocumentFormattingParams](request.params) or { log('Failed to decode DocumentFormattingParams: ${err}') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -6243,7 +6244,7 @@ fn (mut app App) handle_formatting(request Request) Response { os.read_file(real_path) or { log('Failed to read file for formatting: ${err}') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -6251,7 +6252,7 @@ fn (mut app App) handle_formatting(request Request) Response { edits, _ := app.format_content(path, content) return Response{ - id: request.id + id: request.id result: edits } } @@ -6261,7 +6262,7 @@ fn (mut app App) handle_document_symbols(request Request) Response { params := json2.decode[DocumentSymbolParams](request.params) or { log('Failed to decode DocumentSymbolParams: ${err}') return Response{ - id: request.id + id: request.id result: []DocumentSymbol{} } } @@ -6271,13 +6272,13 @@ fn (mut app App) handle_document_symbols(request Request) Response { app.reindex_uri(uri) if entry := app.symbol_index[uri] { return Response{ - id: request.id + id: request.id result: entry.doc_symbols } } content := app.open_files[uri] or { '' } return Response{ - id: request.id + id: request.id result: encode_document_symbols(parse_document_symbols(content), content.split_into_lines(), app.position_encoding) } } @@ -6286,14 +6287,14 @@ fn (mut app App) handle_document_symbols(request Request) Response { fn (mut app App) handle_inlay_hints(request Request) Response { if !app.inlay_hints_enabled { return Response{ - id: request.id + id: request.id result: []InlayHint{} } } params := json2.decode[InlayHintParams](request.params) or { log('Failed to decode InlayHintParams: ${err}') return Response{ - id: request.id + id: request.id result: []InlayHint{} } } @@ -6420,18 +6421,18 @@ fn (mut app App) handle_inlay_hints(request Request) Response { // byte offset is re-encoded into the client's encoding (P0-01/P2-07). name_col := raw.index(var_name) or { continue } hints << InlayHint{ - position: Position{ + position: Position{ line: line_idx char: byte_to_encoded_col(raw, name_col + var_name.len, app.position_encoding) } - label: ': ${inferred}' - kind: inlay_hint_kind_type + label: ': ${inferred}' + kind: inlay_hint_kind_type padding_left: false } } return Response{ - id: request.id + id: request.id result: hints } } @@ -6771,7 +6772,7 @@ fn make_symbol(name string, kind int, line_idx int, raw_line string) DocumentSym line: line_idx char: 0 } - end: Position{ + end: Position{ line: line_idx char: raw_line.len } @@ -6781,17 +6782,17 @@ fn make_symbol(name string, kind int, line_idx int, raw_line string) DocumentSym line: line_idx char: col_start } - end: Position{ + end: Position{ line: line_idx char: col_end } } return DocumentSymbol{ - name: name - kind: kind - range: line_range + name: name + kind: kind + range: line_range selection_range: sel_range - children: []DocumentSymbol{} + children: []DocumentSymbol{} } } @@ -6879,13 +6880,13 @@ fn (mut app App) search_symbol_in_dirs(symbol string, request_id int) []Location positions := occ[symbol] or { continue } for p in positions { locations << Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: p.line char: p.start_char } - end: Position{ + end: Position{ line: p.line char: p.end_char } @@ -6988,13 +6989,13 @@ fn (mut app App) collect_semantic_candidates(symbol string, scope IndexScope) [] positions := occ[symbol] or { continue } for p in positions { candidates << Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: p.line char: p.start_char } - end: Position{ + end: Position{ line: p.line char: p.end_char } @@ -7061,7 +7062,7 @@ fn (mut app App) handle_code_action(request Request) Response { log('Failed to decode CodeActionParams: ${err}') } return Response{ - id: request.id + id: request.id result: []CodeAction{} } } @@ -7103,12 +7104,12 @@ fn (mut app App) handle_code_action(request Request) Response { changes: { uri: [ TextEdit{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: line_nr char: 0 } - end: end_pos + end: end_pos } new_text: '' }, @@ -7116,11 +7117,11 @@ fn (mut app App) handle_code_action(request Request) Response { } } actions << CodeAction{ - title: 'Remove unknown import' - kind: code_action_kind_quickfix + title: 'Remove unknown import' + kind: code_action_kind_quickfix is_preferred: true - edit: edit - diagnostics: [diag] + edit: edit + diagnostics: [diag] } } } @@ -7137,7 +7138,7 @@ fn (mut app App) handle_code_action(request Request) Response { } return Response{ - id: request.id + id: request.id result: actions } } @@ -7208,12 +7209,12 @@ fn build_safe_organize_imports_action(uri string, content string, lines []string changes: { uri: [ TextEdit{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: first char: 0 } - end: Position{ + end: Position{ line: last char: byte_to_encoded_col(lines[last], lines[last].len, enc) } @@ -7225,8 +7226,8 @@ fn build_safe_organize_imports_action(uri string, content string, lines []string } return CodeAction{ title: 'Organize Imports' - kind: code_action_kind_source_organize_imports - edit: edit + kind: code_action_kind_source_organize_imports + edit: edit } } @@ -7303,7 +7304,7 @@ fn (mut app App) collect_module_completions(current_file_uri string, working_dir items << entry.module_completions } return IndexedModuleCompletionResult{ - items: items + items: items use_compiler: has_conditional } } @@ -7350,22 +7351,22 @@ fn make_keyword_completions() []Detail { mut items := []Detail{} for kw in v_keywords { items << Detail{ - kind: 14 // Keyword - label: kw + kind: 14 // Keyword + label: kw detail: kw } } for b in v_builtins { items << Detail{ - kind: 3 // Function - label: b + kind: 3 // Function + label: b detail: b } } for builtin_type in v_builtin_types { items << Detail{ - kind: 7 // Class - label: builtin_type + kind: 7 // Class + label: builtin_type detail: 'builtin type' } } @@ -7378,7 +7379,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { params := json2.decode[DocumentRangeFormattingParams](request.params) or { log('Failed to decode DocumentRangeFormattingParams: ${err}') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7388,7 +7389,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { os.read_file(real_path) or { log('Failed to read file for range formatting: ${err}') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7401,7 +7402,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { os.write_file(temp_file, content) or { log('Failed to write temp file for range formatting: ${err}') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7410,7 +7411,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { formatted := os.read_file(temp_file) or { os.rm(temp_file) or {} return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7421,7 +7422,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { } if result.exit_code != 0 || formatted == '' || formatted == content { return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7434,7 +7435,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { } if req_start >= original_lines.len || req_start > req_end { return Response{ - id: request.id + id: request.id result: []TextEdit{} } } @@ -7456,19 +7457,19 @@ fn (mut app App) handle_range_formatting(request Request) Response { if orig_hunk_start < req_start || orig_hunk_end - 1 > req_end { log('range formatting: changed hunk [${orig_hunk_start}..${orig_hunk_end}) outside requested range [${req_start}..${req_end}]; returning no edits') return Response{ - id: request.id + id: request.id result: []TextEdit{} } } fmt_hunk_end := formatted_lines.len - suf // exclusive new_text := formatted_lines[orig_hunk_start..fmt_hunk_end].join('\n') + '\n' edit := TextEdit{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: orig_hunk_start char: 0 } - end: Position{ + end: Position{ line: orig_hunk_end char: 0 } @@ -7476,7 +7477,7 @@ fn (mut app App) handle_range_formatting(request Request) Response { new_text: new_text } return Response{ - id: request.id + id: request.id result: [edit] } } @@ -7491,7 +7492,7 @@ fn (mut app App) handle_selection_range(request Request) Response { log('Failed to decode SelectionRangeParams: ${err}') } return Response{ - id: request.id + id: request.id result: []SelectionRange{} } } @@ -7504,7 +7505,7 @@ fn (mut app App) handle_selection_range(request Request) Response { results << SelectionRange{ range: LSPRange{ start: pos - end: pos + end: pos } } continue @@ -7517,7 +7518,7 @@ fn (mut app App) handle_selection_range(request Request) Response { line: pos.line char: 0 } - end: Position{ + end: Position{ line: pos.line char: byte_to_encoded_col(line_text, line_text.len, app.position_encoding) } @@ -7535,7 +7536,7 @@ fn (mut app App) handle_selection_range(request Request) Response { line: pos.line char: start } - end: Position{ + end: Position{ line: pos.line char: end } @@ -7544,12 +7545,12 @@ fn (mut app App) handle_selection_range(request Request) Response { range: line_range } results << SelectionRange{ - range: word_range + range: word_range parent: line_parent } } return Response{ - id: request.id + id: request.id result: results } } @@ -7891,7 +7892,7 @@ fn code_lens_range(line int, raw_line string, encoding PositionEncoding) LSPRang line: line char: 0 } - end: Position{ + end: Position{ line: line char: byte_to_encoded_col(raw_line, raw_line.len, encoding) } @@ -7906,7 +7907,7 @@ fn (mut app App) handle_code_lens(request Request) Response { log('Failed to decode CodeLensParams: ${err}') } return Response{ - id: request.id + id: request.id result: []CodeLens{} } } @@ -7921,35 +7922,35 @@ fn (mut app App) handle_code_lens(request Request) Response { fn_name := code_lens_fn_name(code) if fn_name == 'main' { lenses << CodeLens{ - range: code_lens_range(i, raw_line, app.position_encoding) + range: code_lens_range(i, raw_line, app.position_encoding) command: Command{ - title: 'Run Main' - command: 'vls.runFile' + title: 'Run Main' + command: 'vls.runFile' arguments: [uri] } } } if is_test_file && fn_name.starts_with('test_') { lenses << CodeLens{ - range: code_lens_range(i, raw_line, app.position_encoding) + range: code_lens_range(i, raw_line, app.position_encoding) command: Command{ - title: 'Run File' - command: 'vls.runTests' + title: 'Run File' + command: 'vls.runTests' arguments: [uri] } } lenses << CodeLens{ - range: code_lens_range(i, raw_line, app.position_encoding) + range: code_lens_range(i, raw_line, app.position_encoding) command: Command{ - title: 'Run Test' - command: 'vls.runTests' + title: 'Run Test' + command: 'vls.runTests' arguments: [uri, fn_name] } } } } return Response{ - id: request.id + id: request.id result: lenses } } @@ -7962,12 +7963,12 @@ fn (mut app App) handle_code_lens_resolve(request Request) Response { log('Failed to decode CodeLens for resolve: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } return Response{ - id: request.id + id: request.id result: lens } } @@ -8009,7 +8010,7 @@ fn (mut app App) handle_execute_command(request Request) Response { log('Failed to decode ExecuteCommandParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -8023,13 +8024,13 @@ fn (mut app App) handle_execute_command(request Request) Response { app.send_show_message('vls: the V compiler (`v`) was not found on PATH.', 1) } else { app.start_code_lens_run(CodeLensRunJob{ - kind: .main - title: 'Run Main' - uri: uri - path: path - open_files: app.open_files.clone() - write_mutex: app.write_mutex - tcp_conn: app.tcp_conn + kind: .main + title: 'Run Main' + uri: uri + path: path + open_files: app.open_files.clone() + write_mutex: app.write_mutex + tcp_conn: app.tcp_conn capture_output: app.capture_output }) } @@ -8057,14 +8058,14 @@ fn (mut app App) handle_execute_command(request Request) Response { app.send_show_message('vls: the V compiler (`v`) was not found on PATH.', 1) } else { app.start_code_lens_run(CodeLensRunJob{ - kind: kind - title: title - uri: uri - path: path - fn_name: fn_name - open_files: app.open_files.clone() - write_mutex: app.write_mutex - tcp_conn: app.tcp_conn + kind: kind + title: title + uri: uri + path: path + fn_name: fn_name + open_files: app.open_files.clone() + write_mutex: app.write_mutex + tcp_conn: app.tcp_conn capture_output: app.capture_output }) } @@ -8077,7 +8078,7 @@ fn (mut app App) handle_execute_command(request Request) Response { } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -8090,7 +8091,7 @@ fn (mut app App) handle_inline_value(request Request) Response { log('Failed to decode InlineValueParams: ${err}') } return Response{ - id: request.id + id: request.id result: []InlineValueText{} } } @@ -8124,16 +8125,16 @@ fn (mut app App) handle_inline_value(request Request) Response { line: i char: col_start } - end: Position{ + end: Position{ line: i char: col_start + var_name.len } } - text: ': ${inferred}' + text: ': ${inferred}' } } return Response{ - id: request.id + id: request.id result: values } } @@ -8147,7 +8148,7 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { log('Failed to decode TextDocumentPositionParams for linkedEditingRange: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -8156,7 +8157,7 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { lines := content.split_into_lines() if params.position.line < 0 || params.position.line >= lines.len { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -8164,7 +8165,7 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { start, end := find_word_bounds_at_col(line_text, params.position.char, app.position_encoding) if start < 0 || end <= start { return Response{ - id: request.id + id: request.id result: 'null' } } @@ -8185,7 +8186,7 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { line: params.position.line char: sc } - end: Position{ + end: Position{ line: params.position.line char: ec } @@ -8195,12 +8196,12 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { } if ranges.len == 0 { return Response{ - id: request.id + id: request.id result: 'null' } } return Response{ - id: request.id + id: request.id result: LinkedEditingRanges{ ranges: ranges } @@ -8211,7 +8212,7 @@ fn (mut app App) handle_linked_editing_range(request Request) Response { // For now it returns empty edits — triggering v fmt on every keystroke would be too expensive. fn (mut app App) handle_on_type_formatting(request Request) Response { return Response{ - id: request.id + id: request.id result: []TextEdit{} } } diff --git a/handlers_test.v b/handlers_test.v index c5a8cdaf..ef4044f9 100644 --- a/handlers_test.v +++ b/handlers_test.v @@ -25,15 +25,15 @@ fn create_test_app() &App { os.mkdir_all(temp_dir) or { assert false, 'Failed to create test temp dir: ${err}' return &App{ - text: '' + text: '' open_files: map[string]string{} - temp_dir: temp_dir + temp_dir: temp_dir } } return &App{ - text: '' + text: '' open_files: map[string]string{} - temp_dir: temp_dir + temp_dir: temp_dir } } @@ -56,10 +56,10 @@ fn test_on_did_open_tracks_file() { uri := path_to_uri(test_file) request := Request{ - id: 1 - method: 'textDocument/didOpen' + id: 1 + method: 'textDocument/didOpen' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -201,7 +201,7 @@ fn test_on_did_open_uses_text_document_payload() { app.on_did_open(Request{ params: json2.encode(DidOpenTextDocumentParams{ text_document: DidOpenTextDocumentItem{ - uri: uri + uri: uri text: content } }, @@ -224,7 +224,7 @@ fn test_on_did_open_uses_empty_text_payload_without_disk_fallback() { app.on_did_open(Request{ params: json2.encode(DidOpenTextDocumentParams{ text_document: DidOpenTextDocumentItem{ - uri: uri + uri: uri text: '' } }, @@ -335,11 +335,11 @@ fn test_on_did_change_updates_content() { // Then change it new_content := 'module main\n\nfn main() {\n\tprintln("changed")\n}' request := Request{ - id: 2 - method: 'textDocument/didChange' + id: 2 + method: 'textDocument/didChange' jsonrpc: '2.0' - params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + params: json2.encode(Params{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -427,7 +427,7 @@ fn test_on_did_change_returns_notification() { request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -461,8 +461,8 @@ fn test_on_did_change_schedules_diagnostics_without_blocking() { result := app.on_did_change(Request{ params: json2.encode(DidChangeTextDocumentParams{ - text_document: VersionedTextDocumentIdentifier{ - uri: uri + text_document: VersionedTextDocumentIdentifier{ + uri: uri version: 2 } content_changes: [ContentChange{ @@ -502,22 +502,22 @@ fn test_diagnostics_scheduler_coalesces_pending_jobs() { uri := 'file:///pending.v' global_first, generation_first := scheduler.next_generation(uri) should_start := scheduler.enqueue(DiagnosticsJob{ - uri: uri - content: 'first' + uri: uri + content: 'first' global_generation: global_first - generation: generation_first - ready_at: 100 - write_mutex: app.write_mutex + generation: generation_first + ready_at: 100 + write_mutex: app.write_mutex }) assert should_start global_latest, generation_latest := scheduler.next_generation(uri) should_restart := scheduler.enqueue(DiagnosticsJob{ - uri: uri - content: 'latest' + uri: uri + content: 'latest' global_generation: global_latest - generation: generation_latest - ready_at: 100 - write_mutex: app.write_mutex + generation: generation_latest + ready_at: 100 + write_mutex: app.write_mutex }) assert !should_restart @@ -558,8 +558,8 @@ fn test_diagnostics_scheduler_requeues_pending_sibling_with_latest_buffers() { result := app.on_did_change(Request{ params: json2.encode(DidChangeTextDocumentParams{ - text_document: VersionedTextDocumentIdentifier{ - uri: uri_b + text_document: VersionedTextDocumentIdentifier{ + uri: uri_b version: 2 } content_changes: [ContentChange{ @@ -604,7 +604,7 @@ fn test_diagnostics_scheduler_requeues_sibling_after_open() { assert app.on_did_open(Request{ params: json2.encode(DidOpenTextDocumentParams{ text_document: DidOpenTextDocumentItem{ - uri: uri_b + uri: uri_b text: content_b } }, @@ -649,7 +649,7 @@ fn test_diagnostics_scheduler_requeues_sibling_after_save_text() { text_document: TextDocumentIdentifier{ uri: uri_b } - text: new_content_b + text: new_content_b }, escape_unicode: true ) @@ -739,7 +739,7 @@ fn test_diagnostics_scheduler_requeues_job_after_watched_file_change() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: uri_b + uri: uri_b event_type: 2 }] }) @@ -766,13 +766,13 @@ fn test_diagnostics_scheduler_requeues_active_sibling() { tickets_a := scheduler.begin_project_schedule(uri_a, project_key) assert tickets_a.len == 1 active_job := DiagnosticsJob{ - uri: uri_a - project_key: project_key + uri: uri_a + project_key: project_key project_generation: tickets_a[0].project_generation - global_generation: tickets_a[0].global_generation - generation: tickets_a[0].generation - ready_at: 0 - write_mutex: app.write_mutex + global_generation: tickets_a[0].global_generation + generation: tickets_a[0].generation + ready_at: 0 + write_mutex: app.write_mutex } assert scheduler.enqueue(active_job) jobs, should_stop := scheduler.take_ready_jobs(0) @@ -807,10 +807,10 @@ fn test_diagnostics_scheduler_checks_staleness_while_publishing() { uri := 'file:///publish.v' global_generation, generation := scheduler.next_generation(uri) job := DiagnosticsJob{ - uri: uri + uri: uri global_generation: global_generation - generation: generation - write_mutex: app.write_mutex + generation: generation + write_mutex: app.write_mutex } notification := Notification{ method: 'textDocument/publishDiagnostics' @@ -858,7 +858,7 @@ fn test_on_did_change_multiple_changes() { for change in changes { request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -905,7 +905,7 @@ fn test_on_did_change_updates_tracked_file() { new_content := 'modified content' app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -928,7 +928,7 @@ fn test_apply_incremental_change_handles_utf8_columns() { line: 0 char: 1 } - end: Position{ + end: Position{ line: 0 char: 2 } @@ -947,7 +947,7 @@ fn test_apply_incremental_change_preserves_crlf() { line: 1 char: 0 } - end: Position{ + end: Position{ line: 1 char: 3 } @@ -963,7 +963,7 @@ fn test_apply_incremental_change_rejects_reversed_range() { line: 0 char: 4 } - end: Position{ + end: Position{ line: 0 char: 2 } @@ -982,7 +982,7 @@ fn test_incremental_change_is_valid_rejects_lines_past_eof() { line: 5 char: 0 } - end: Position{ + end: Position{ line: 6 char: 0 } @@ -994,7 +994,7 @@ fn test_incremental_change_is_valid_rejects_lines_past_eof() { line: 1 char: 0 } - end: Position{ + end: Position{ line: 9 char: 0 } @@ -1006,7 +1006,7 @@ fn test_incremental_change_is_valid_rejects_lines_past_eof() { line: 0 char: 1 } - end: Position{ + end: Position{ line: 1 char: 2 } @@ -1057,7 +1057,7 @@ fn test_semantic_candidate_cap_ignores_unrelated_workspace_root() { app.ensure_dirs_indexed(app.index_query_dirs()) current_scope := IndexScope{ - dir: '/root_a' + dir: '/root_a' recursive: true } candidates := app.collect_semantic_candidates('unique', current_scope) @@ -1076,7 +1076,7 @@ fn test_incremental_change_is_valid_rejects_char_past_line() { line: 0 char: 9 } - end: Position{ + end: Position{ line: 1 char: 1 } @@ -1087,7 +1087,7 @@ fn test_incremental_change_is_valid_rejects_char_past_line() { line: 0 char: 1 } - end: Position{ + end: Position{ line: 1 char: 9 } @@ -1100,7 +1100,7 @@ fn test_incremental_change_is_valid_rejects_char_past_line() { line: 0 char: 3 } - end: Position{ + end: Position{ line: 0 char: 3 } @@ -1115,7 +1115,7 @@ fn test_apply_incremental_change_handles_multiline_ranges() { line: 0 char: 1 } - end: Position{ + end: Position{ line: 1 char: 2 } @@ -1141,13 +1141,13 @@ fn test_operation_at_pos_completion_line_info() { app.open_files[uri] = content request := Request{ - id: 1 + id: 1 method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 4 } @@ -1177,13 +1177,13 @@ fn test_operation_at_pos_definition_line_info() { app.open_files[uri] = content request := Request{ - id: 2 + id: 2 method: 'textDocument/definition' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -2238,8 +2238,8 @@ fn test_active_indexed_source_file_names_applies_compiler_build_rules() { must_mkdir_all(test_dir) inactive_os := $if windows { 'linux' } $else { 'windows' } source := 'module main\n\nfn helper() {}\n' - for name in ['main.v', 'plain_${inactive_os}.v', 'gated_d_somefeature.v', - 'gated_notd_somefeature.v', 'main_test.v', 'sibling_${inactive_os}_test.v'] { + for name in ['main.v', 'plain_${inactive_os}.v', 'gated_d_somefeature.v', 'gated_notd_somefeature.v', + 'main_test.v', 'sibling_${inactive_os}_test.v'] { must_write_file(os.join_path(test_dir, name), source) } // A file the client created but has not saved yet is not on disk, so the @@ -2716,7 +2716,7 @@ fn test_source_declaration_at_stops_non_braced_declarations() { app.open_files[uri] = content constant := app.source_declaration_at(Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: 3 @@ -2726,7 +2726,7 @@ fn test_source_declaration_at_stops_non_braced_declarations() { assert constant == 'answer = 42' alias := app.source_declaration_at(Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: 7 @@ -2736,7 +2736,7 @@ fn test_source_declaration_at_stops_non_braced_declarations() { assert alias == 'type Alias = int' function_alias := app.source_declaration_at(Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: 8 @@ -2746,7 +2746,7 @@ fn test_source_declaration_at_stops_non_braced_declarations() { assert function_alias == 'type Handler = fn (int) bool' function := app.source_declaration_at(Location{ - uri: uri + uri: uri range: LSPRange{ start: Position{ line: 12 @@ -2839,13 +2839,13 @@ fn test_operation_at_pos_signature_help_line_info() { app.open_files[uri] = content request := Request{ - id: 3 + id: 3 method: 'textDocument/signatureHelp' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } @@ -2878,12 +2878,12 @@ fn test_operation_at_pos_preserves_request_id() { test_ids := [0, 1, 42, 999, 12345] for id in test_ids { request := Request{ - id: id + id: id params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -2898,7 +2898,7 @@ fn test_operation_at_pos_preserves_request_id() { fn test_json_encode_response() { response := Response{ - id: 1 + id: 1 result: 'null' } encoded := json2.encode(response, escape_unicode: true) @@ -2908,20 +2908,20 @@ fn test_json_encode_response() { fn test_json_encode_capabilities_response() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } } @@ -2934,20 +2934,20 @@ fn test_json_encode_capabilities_response() { fn test_json_encode_completion_response() { details := [ Detail{ - kind: 6 - label: 'println' - detail: 'fn println(s string)' + kind: 6 + label: 'println' + detail: 'fn println(s string)' documentation: 'Prints to stdout' }, Detail{ - kind: 6 - label: 'print' - detail: 'fn print(s string)' + kind: 6 + label: 'print' + detail: 'fn print(s string)' documentation: 'Prints without newline' }, ] response := Response{ - id: 2 + id: 2 result: details } encoded := json2.encode(response, escape_unicode: true) @@ -2957,15 +2957,15 @@ fn test_json_encode_completion_response() { fn test_json_encode_location_response() { response := Response{ - id: 3 + id: 3 result: Location{ - uri: 'file:///test/main.v' + uri: 'file:///test/main.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -2979,11 +2979,11 @@ fn test_json_encode_location_response() { fn test_json_encode_signature_help_response() { response := Response{ - id: 4 + id: 4 result: SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -3008,20 +3008,20 @@ fn test_json_encode_notification() { notification := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 10 } } - message: 'undefined identifier' + message: 'undefined identifier' severity: 1 }, ] @@ -3107,17 +3107,17 @@ fn test_diagnostics_deduplication() { errors := [ JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'error 1' }, JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'error 2' }, // duplicate position JsonError{ line_nr: 6 - col: 5 + col: 5 message: 'error 3' }, ] @@ -3141,17 +3141,17 @@ fn test_diagnostics_deduplication_same_line_different_col() { errors := [ JsonError{ line_nr: 5 - col: 1 + col: 1 message: 'error 1' }, JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'error 2' }, JsonError{ line_nr: 5 - col: 20 + col: 20 message: 'error 3' }, ] @@ -3198,7 +3198,7 @@ fn test_response_result_string() { fn test_response_result_details() { details := [ Detail{ - kind: 6 + kind: 6 label: 'test' }, ] @@ -3273,11 +3273,11 @@ fn test_app_exit_flag_default() { fn test_v_error_to_lsp_diagnostic_basic() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'undefined identifier `foo`' line_nr: 10 - col: 5 - len: 3 + col: 5 + len: 3 } diag := v_error_to_lsp_diagnostic(v_err) @@ -3292,11 +3292,11 @@ fn test_v_error_to_lsp_diagnostic_basic() { fn test_v_error_to_lsp_diagnostic_first_line() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'syntax error' line_nr: 1 - col: 1 - len: 1 + col: 1 + len: 1 } diag := v_error_to_lsp_diagnostic(v_err) @@ -3307,11 +3307,11 @@ fn test_v_error_to_lsp_diagnostic_first_line() { fn test_v_error_to_lsp_diagnostic_long_error() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'unexpected token' line_nr: 100 - col: 50 - len: 20 + col: 50 + len: 20 } diag := v_error_to_lsp_diagnostic(v_err) @@ -3322,11 +3322,11 @@ fn test_v_error_to_lsp_diagnostic_long_error() { fn test_v_error_to_lsp_diagnostic_zero_length() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'error at position' line_nr: 5 - col: 10 - len: 0 + col: 10 + len: 0 } diag := v_error_to_lsp_diagnostic(v_err) @@ -3347,8 +3347,8 @@ fn test_v_error_to_lsp_diagnostic_preserves_message() { v_err := JsonError{ message: msg line_nr: 1 - col: 1 - len: 1 + col: 1 + len: 1 } diag := v_error_to_lsp_diagnostic(v_err) assert diag.message == msg @@ -3357,11 +3357,11 @@ fn test_v_error_to_lsp_diagnostic_preserves_message() { fn test_v_error_to_lsp_diagnostic_always_error_severity() { v_err := JsonError{ - path: '/test.v' + path: '/test.v' message: 'any error' line_nr: 1 - col: 1 - len: 1 + col: 1 + len: 1 } diag := v_error_to_lsp_diagnostic(v_err) assert diag.severity == 1 // Always Error severity @@ -3438,7 +3438,7 @@ fn test_multifile_change_single_file() { new_content := 'module main\n\nfn main() { changed }' app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: main_uri } content_changes: [ContentChange{ @@ -3472,10 +3472,10 @@ fn test_handle_formatting_formats_code() { app.open_files[uri] = unformatted request := Request{ - id: 1 - method: 'textDocument/formatting' + id: 1 + method: 'textDocument/formatting' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -3519,10 +3519,10 @@ fn test_handle_formatting_already_formatted() { app.open_files[uri] = formatted request := Request{ - id: 2 - method: 'textDocument/formatting' + id: 2 + method: 'textDocument/formatting' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -3552,10 +3552,10 @@ fn test_handle_formatting_nonexistent_file() { uri := path_to_uri(nonexistent) request := Request{ - id: 3 - method: 'textDocument/formatting' + id: 3 + method: 'textDocument/formatting' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -3592,10 +3592,10 @@ fn test_handle_formatting_uses_open_file_content() { app.open_files[uri] = 'module main\n\nfn new( ) {}' request := Request{ - id: 4 - method: 'textDocument/formatting' + id: 4 + method: 'textDocument/formatting' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -3633,17 +3633,17 @@ fn test_find_references_returns_null_when_no_symbol_at_position() { app.open_files[uri] = content resp := app.find_references(Request{ - id: 901 + id: 901 method: 'textDocument/references' params: json2.encode(ReferenceParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 1 char: 0 } - context: ReferenceContext{ + context: ReferenceContext{ include_declaration: true } }, @@ -3672,17 +3672,17 @@ fn test_handle_rename_returns_null_when_no_symbol_at_position() { app.open_files[uri] = content resp := app.handle_rename(Request{ - id: 902 + id: 902 method: 'textDocument/rename' params: json2.encode(RenameParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 1 char: 0 } - new_name: 'renamed' + new_name: 'renamed' }, escape_unicode: true ) @@ -3740,7 +3740,7 @@ fn test_did_close_reindexes_noncanonical_uri_under_disk_uri() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: disk_uri + uri: disk_uri event_type: 2 }] }) @@ -3770,17 +3770,17 @@ fn test_handle_rename_refuses_incomplete_oversized_sibling_index() { app.open_files[uri] = content resp := app.handle_rename(Request{ - id: 903 + id: 903 method: 'textDocument/rename' params: json2.encode(RenameParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 4 } - new_name: 'renamed' + new_name: 'renamed' }, escape_unicode: true ) @@ -4043,7 +4043,7 @@ fn test_handle_document_symbols_empty_file() { app.open_files[uri] = '' request := Request{ - id: 10 + id: 10 method: 'textDocument/documentSymbol' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ @@ -4071,7 +4071,7 @@ fn test_handle_document_symbols_no_tracked_file() { // URI not in open_files — should still return an empty symbol list, not crash request := Request{ - id: 11 + id: 11 method: 'textDocument/documentSymbol' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ @@ -4101,7 +4101,7 @@ fn test_handle_document_symbols_returns_correct_symbols() { app.open_files[uri] = 'module main\n\nfn hello() {}\n\nstruct Config {}\n\nenum Mode { on off }\n\nconst version = 1\n' request := Request{ - id: 12 + id: 12 method: 'textDocument/documentSymbol' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ @@ -4138,7 +4138,7 @@ fn test_handle_document_symbols_preserves_request_id() { for id in [1, 99, 1000, 0] { request := Request{ - id: id + id: id method: 'textDocument/documentSymbol' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ @@ -4176,7 +4176,7 @@ const my_const = 42 ' request := Request{ - id: 20 + id: 20 method: 'textDocument/documentSymbol' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ @@ -4513,13 +4513,13 @@ fn main() { char: start_col + 1 } response := app.operation_at_pos(.hover, Request{ - id: 904 + id: 904 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: position + position: position }, escape_unicode: true ) @@ -4690,18 +4690,18 @@ obj := MyStruct{} app.open_files[uri] = content request := Request{ - id: 30 + id: 30 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 9 char: 0 } @@ -4741,18 +4741,18 @@ x := 99 app.open_files[uri] = content request := Request{ - id: 31 + id: 31 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 4 char: 0 } @@ -4788,18 +4788,18 @@ fn test_handle_inlay_hints_empty_file() { app.open_files[uri] = '' request := Request{ - id: 32 + id: 32 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 0 } @@ -4831,18 +4831,18 @@ mut count := 0 app.open_files[uri] = content request := Request{ - id: 33 + id: 33 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 2 char: 0 } @@ -4880,18 +4880,18 @@ const is_debug = false app.open_files[uri] = content request := Request{ - id: 34 + id: 34 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 7 char: 0 } @@ -4935,18 +4935,18 @@ enabled = true app.open_files[uri] = content request := Request{ - id: 35 + id: 35 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 9 char: 0 } @@ -4986,18 +4986,18 @@ fn test_handle_inlay_hints_local_fn_call() { app.open_files[uri] = 'module main\n\nfn main() {\n\tmsg := get_greeting()\n}\n' request := Request{ - id: 40 + id: 40 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 5 char: 0 } @@ -5031,18 +5031,18 @@ fn test_handle_inlay_hints_error_result_fn() { app.open_files[uri] = 'module main\n\nfn main() {\n\tdata := read_data() or { return }\n}\n' request := Request{ - id: 41 + id: 41 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 5 char: 0 } @@ -5081,18 +5081,18 @@ greeting := get_greeting() app.open_files[uri] = content request := Request{ - id: 50 + id: 50 method: 'textDocument/inlayHint' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 9 char: 0 } @@ -5657,13 +5657,13 @@ fn test_operation_at_pos_completion_includes_current_file_fns() { app.text = content request := Request{ - id: 1 + id: 1 method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 4 } @@ -5703,13 +5703,13 @@ fn test_operation_at_pos_dot_completion_includes_imported_module_members() { app.text = content response := app.operation_at_pos(.completion, Request{ - id: 9001 + id: 9001 method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 8 } @@ -5751,13 +5751,13 @@ fn test_operation_at_pos_dot_completion_includes_aliased_import_module_members() app.text = content response := app.operation_at_pos(.completion, Request{ - id: 9002 + id: 9002 method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 4 } @@ -5897,13 +5897,13 @@ fn test_operation_at_pos_completion_and_definition_resolve_cross_file_receiver_m assert show_col >= 0 completion := app.operation_at_pos(.completion, Request{ - id: 9100 + id: 9100 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: show_col } @@ -5916,13 +5916,13 @@ fn test_operation_at_pos_completion_and_definition_resolve_cross_file_receiver_m assert completion_items.any(it.label == 'show' && it.kind == 2) definition := app.operation_at_pos(.definition, Request{ - id: 9101 + id: 9101 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: show_col + 2 } @@ -5956,13 +5956,13 @@ fn test_operation_at_pos_completion_includes_indexed_struct_fields() { completion_line := lines.index('\tuser.') assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9200 + id: 9200 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6001,13 +6001,13 @@ fn test_receiver_inference_does_not_reuse_declaration_from_earlier_function() { assert beta_col >= 0 completion := app.operation_at_pos(.completion, Request{ - id: 9201 + id: 9201 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: call_line char: dot_col + 1 } @@ -6021,13 +6021,13 @@ fn test_receiver_inference_does_not_reuse_declaration_from_earlier_function() { assert !items.any(it.label == 'alpha') definition := app.operation_at_pos(.definition, Request{ - id: 9202 + id: 9202 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: call_line char: beta_col + 2 } @@ -6066,13 +6066,13 @@ fn test_imported_module_completion_resolves_from_project_root() { completion_line := lines.index('\tmylib.') assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9203 + id: 9203 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6104,13 +6104,13 @@ fn test_bare_completion_includes_local_and_top_level_scope_symbols() { completion_line := lines.index('\tlocal_') assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9300 + id: 9300 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6162,13 +6162,13 @@ fn test_literal_and_container_receiver_completion_falls_back_to_compiler() { expected_member := if case_idx == 0 { 'after' } else { 'filter' } assert indexed.items.any(it.label == expected_member), completion_case.str() response := app.operation_at_pos(.completion, Request{ - id: 9301 + case_idx + id: 9301 + case_idx method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6211,13 +6211,13 @@ fn test_typed_container_receiver_does_not_infer_nested_struct_type() { assert !indexed.use_compiler, declaration assert !indexed.items.any(it.label in ['name', 'save']), declaration response := app.operation_at_pos(.completion, Request{ - id: 9350 + id: 9350 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6252,13 +6252,13 @@ fn test_receiver_completion_honors_local_binding_that_shadows_import() { completion_line := lines.index('\tclock.') assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9303 + id: 9303 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6297,13 +6297,13 @@ fn test_imported_module_completion_uses_unsaved_open_buffer() { assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9304 + id: 9304 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6343,13 +6343,13 @@ fn test_member_completion_recognizes_typed_prefix() { completion_line := lines.index(source_line) assert completion_line >= 0 response := app.operation_at_pos(.completion, Request{ - id: 9400 + completion_line + id: 9400 + completion_line method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -6387,13 +6387,13 @@ fn test_local_scope_completion_drops_bindings_after_nested_block() { assert !app.local_scope_completions(content, position).any(it.label == 'clock') response := app.operation_at_pos(.completion, Request{ - id: 9401 + id: 9401 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: position + position: position }, escape_unicode: true ) @@ -6526,13 +6526,13 @@ fn test_hover_prefers_shadowing_closure_parameter_type() { x_col := lines[line].index('x') or { -1 } assert x_col >= 0 response := app.operation_at_pos(.hover, Request{ - id: 9501 + id: 9501 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: line char: x_col + 1 } @@ -6573,13 +6573,13 @@ fn test_hover_does_not_treat_member_selector_as_local_binding() { char: field_col }) == none field_response := app.operation_at_pos(.hover, Request{ - id: 9531 + id: 9531 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: line char: field_col } @@ -6729,13 +6729,13 @@ fn test_hover_keeps_reference_and_option_parameter_types() { // uses, and returns the text of the answer. fn public_hover_text(mut app App, uri string, line int, character int) string { response := app.operation_at_pos(.hover, Request{ - id: 9700 + line + id: 9700 + line method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: line char: character } @@ -6877,13 +6877,13 @@ fn test_hover_on_a_call_keeps_the_declaration_as_written() { // The compiler re-prints a function type without its parameter names, so the // declaration written in the source is the better answer. response := app.operation_at_pos(.hover, Request{ - id: 9601 + id: 9601 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: line char: col + 2 } @@ -6902,7 +6902,7 @@ fn test_hover_on_a_field_of_a_chain_answers_for_that_field() { } test_dir := os.join_path(app.temp_dir, 'chain_field_hover') must_mkdir_all(test_dir) - content := "module main\n\nstruct Child {\n\tvalue int\n}\n\nstruct Node {\n\tchild Child\n}\n\nstruct Listener {\n\tnode Node\n}\n\nfn main() {\n\tlistener := Listener{}\n\tprintln(listener.node.child.value)\n}\n" + content := 'module main\n\nstruct Child {\n\tvalue int\n}\n\nstruct Node {\n\tchild Child\n}\n\nstruct Listener {\n\tnode Node\n}\n\nfn main() {\n\tlistener := Listener{}\n\tprintln(listener.node.child.value)\n}\n' main_file := os.join_path(test_dir, 'main.v') must_write_file(main_file, content) uri := path_to_uri(main_file) @@ -6933,7 +6933,7 @@ fn test_hover_on_a_deep_chain_inside_nested_closures() { } test_dir := os.join_path(app.temp_dir, 'nested_chain_hover') must_mkdir_all(test_dir) - content := "module main\n\nstruct Leaf {\n\tflag bool\n}\n\nstruct Child {\n\tleaf Leaf\n}\n\nstruct Node {\n\tchild Child\n}\n\nstruct Listener {\n\tnode Node\n}\n\nfn main() {\n\tlisteners := []Listener{}\n\touter := fn (x Listener) bool {\n\t\tinner := fn (y Listener) bool {\n\t\t\treturn y.node.child.leaf.flag\n\t\t}\n\t\treturn inner(x) && x.node.child.leaf.flag\n\t}\n\tprintln(listeners.filter(outer))\n}\n" + content := 'module main\n\nstruct Leaf {\n\tflag bool\n}\n\nstruct Child {\n\tleaf Leaf\n}\n\nstruct Node {\n\tchild Child\n}\n\nstruct Listener {\n\tnode Node\n}\n\nfn main() {\n\tlisteners := []Listener{}\n\touter := fn (x Listener) bool {\n\t\tinner := fn (y Listener) bool {\n\t\t\treturn y.node.child.leaf.flag\n\t\t}\n\t\treturn inner(x) && x.node.child.leaf.flag\n\t}\n\tprintln(listeners.filter(outer))\n}\n' main_file := os.join_path(test_dir, 'main.v') must_write_file(main_file, content) uri := path_to_uri(main_file) @@ -7028,10 +7028,10 @@ fn test_hover_on_nested_closure_parameters_keeps_each_type() { // Three parameters of the same name, one inside the other: each hover has to // answer with the type written next to that one. for source_line, expected in { - '\touter := fn (x Listener) bool {': 'x Listener' - '\t\tinner := fn (x Child) bool {': 'x Child' - '\t\t\treturn x.value == 1': 'x Child' - '\t\treturn inner(x.node.child)': 'x Listener' + '\touter := fn (x Listener) bool {': 'x Listener' + '\t\tinner := fn (x Child) bool {': 'x Child' + '\t\t\treturn x.value == 1': 'x Child' + '\t\treturn inner(x.node.child)': 'x Listener' } { line := lines.index(source_line) assert line >= 0, source_line @@ -7065,9 +7065,9 @@ fn test_hover_types_a_binding_holding_a_function_literal() { // A function literal writes its own type down: the signature, without the // capture list and without the body. for name, expected in { - 'f': 'f fn (a int)' - 'g': 'g fn (a int, b string) !int' - 'h': 'h fn ()' + 'f': 'f fn (a int)' + 'g': 'g fn (a int, b string) !int' + 'h': 'h fn ()' 'c': 'c fn (a int) int' 'cb': 'cb fn (int) int' } { @@ -7097,7 +7097,7 @@ fn test_hover_types_bindings_whose_value_names_no_type() { } test_dir := os.join_path(app.temp_dir, 'inferred_binding_hover') must_mkdir_all(test_dir) - content := "module main\n\nfn make_int() !int {\n\treturn 3\n}\n\nfn work() int {\n\treturn 4\n}\n\nfn main() {\n\tres := make_int() or {\n\t\tprintln(err)\n\t\t0\n\t}\n\tth := spawn work()\n\tif v := make_int() {\n\t\tprintln(v)\n\t}\n\tprintln(res)\n\tprintln(th.wait())\n}\n" + content := 'module main\n\nfn make_int() !int {\n\treturn 3\n}\n\nfn work() int {\n\treturn 4\n}\n\nfn main() {\n\tres := make_int() or {\n\t\tprintln(err)\n\t\t0\n\t}\n\tth := spawn work()\n\tif v := make_int() {\n\t\tprintln(v)\n\t}\n\tprintln(res)\n\tprintln(th.wait())\n}\n' main_file := os.join_path(test_dir, 'main.v') must_write_file(main_file, content) uri := path_to_uri(main_file) @@ -7433,13 +7433,13 @@ fn test_embedded_struct_receiver_completion_includes_promoted_members() { assert !indexed.items.any(it.label == 'Base') response := app.operation_at_pos(.completion, Request{ - id: 9700 + id: 9700 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: position + position: position }, escape_unicode: true ) @@ -7961,13 +7961,13 @@ fn test_conditional_bare_completion_requests_compiler_fallback() { assert !indexed.items.any(it.label == 'platform_only') response := app.operation_at_pos(.completion, Request{ - id: 9600 + id: 9600 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: position + position: position }, escape_unicode: true ) @@ -8032,13 +8032,13 @@ fn test_conditional_methods_request_receiver_completion_fallback() { assert indexed.items.any(it.label == 'start') assert !indexed.items.any(it.label == 'reload') response := app.operation_at_pos(.completion, Request{ - id: 9601 + id: 9601 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: completion_line char: lines[completion_line].len } @@ -8163,13 +8163,13 @@ fn test_chained_definition_resolves_nested_receiver_not_import_alias() { assert indexed_location.uri == uri assert indexed_location.range.start.line == lines.index('fn (timer Timer) start() {}') definition := app.operation_at_pos(.definition, Request{ - id: 9602 + id: 9602 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: position + position: position }, escape_unicode: true ) @@ -8441,7 +8441,7 @@ fn test_semantic_tokens_returns_data_for_known_content() { app.open_files[uri] = content resp := app.handle_semantic_tokens(Request{ - id: 800 + id: 800 method: 'textDocument/semanticTokens/full' params: json2.encode(SemanticTokensParams{ text_document: TextDocumentIdentifier{ @@ -8531,7 +8531,7 @@ fn test_semantic_tokens_returns_empty_object_for_empty_file() { app.open_files[uri] = '' resp := app.handle_semantic_tokens(Request{ - id: 801 + id: 801 method: 'textDocument/semanticTokens/full' params: json2.encode(SemanticTokensParams{ text_document: TextDocumentIdentifier{ @@ -8556,7 +8556,7 @@ fn test_semantic_tokens_range_returns_empty_for_missing_document() { } resp := app.handle_semantic_tokens_range(Request{ - id: 802 + id: 802 method: 'textDocument/semanticTokens/range' params: '{}' }) @@ -8582,12 +8582,12 @@ fn test_semantic_tokens_range_filters_by_character() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 50 } @@ -8596,7 +8596,7 @@ fn test_semantic_tokens_range_filters_by_character() { escape_unicode: true ) full := app.handle_semantic_tokens_range(Request{ - id: 1 + id: 1 params: full_params }) ftok := full.result as SemanticTokens @@ -8612,12 +8612,12 @@ fn test_semantic_tokens_range_filters_by_character() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 8 } - end: Position{ + end: Position{ line: 0 char: 50 } @@ -8626,7 +8626,7 @@ fn test_semantic_tokens_range_filters_by_character() { escape_unicode: true ) narrow := app.handle_semantic_tokens_range(Request{ - id: 2 + id: 2 params: narrow_params }) ntok := narrow.result as SemanticTokens @@ -8648,7 +8648,7 @@ fn test_code_lens_returns_run_lens_for_main() { app.open_files[uri] = content resp := app.handle_code_lens(Request{ - id: 810 + id: 810 method: 'textDocument/codeLens' params: json2.encode(CodeLensParams{ text_document: TextDocumentIdentifier{ @@ -8681,7 +8681,7 @@ fn test_code_lens_range_uses_negotiated_position_encoding() { uri := 'file:///tmp/codelens_unicode.v' app.open_files[uri] = 'module main\n\nfn main() {} // 🚀\n' request := Request{ - id: 814 + id: 814 method: 'textDocument/codeLens' params: json2.encode(CodeLensParams{ text_document: TextDocumentIdentifier{ @@ -8724,7 +8724,7 @@ fn test_code_lens_returns_test_lens_for_test_fn() { app.open_files[uri] = content resp := app.handle_code_lens(Request{ - id: 811 + id: 811 method: 'textDocument/codeLens' params: json2.encode(CodeLensParams{ text_document: TextDocumentIdentifier{ @@ -8766,7 +8766,7 @@ fn test_code_lens_ignores_declarations_in_comments_and_non_test_files() { app.open_files[uri] = 'module main\n\n/*\nfn main() {}\nfn test_hidden() {}\n*/\nfn helper() {}\n' resp := app.handle_code_lens(Request{ - id: 813 + id: 813 method: 'textDocument/codeLens' params: json2.encode(CodeLensParams{ text_document: TextDocumentIdentifier{ @@ -8787,25 +8787,25 @@ fn test_code_lens_resolve_returns_same_lens() { cleanup_test_app(app) } lens := CodeLens{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 10 } } command: Command{ - title: '▶ Run' - command: 'vls.runFile' + title: '▶ Run' + command: 'vls.runFile' arguments: ['file:///tmp/a.v'] } } resp := app.handle_code_lens_resolve(Request{ - id: 812 + id: 812 method: 'codeLens/resolve' params: json2.encode(lens, escape_unicode: true) }) @@ -8826,7 +8826,7 @@ fn test_execute_command_returns_null_result() { app.capture_output = true resp := app.handle_execute_command(Request{ - id: 820 + id: 820 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ command: 'vls.runFile' @@ -8866,10 +8866,10 @@ fn test_execute_run_file_invokes_compiler() { app.execute_commands_synchronously = true resp := app.handle_execute_command(Request{ - id: 822 + id: 822 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runFile' + command: 'vls.runFile' arguments: [uri] }, escape_unicode: true @@ -8886,8 +8886,7 @@ fn test_execute_run_file_invokes_compiler() { main_column := main_source.split_into_lines()[7].index('@COLUMN') or { 0 } expected_paths := [os.real_path(project_dir), os.real_path(helper_path), 'helper.v:8', '${os.real_path(helper_path)}:8, main.code_lens_sibling_paths', (helper_column + 1).str(), - os.real_path(path), 'main.v:8', '${os.real_path(path)}:8, main.main', - (main_column + 1).str()] + os.real_path(path), 'main.v:8', '${os.real_path(path)}:8, main.main', (main_column + 1).str()] assert (os.read_file(compile_time_output_path) or { '' }) == expected_paths.join('\n') assert (os.read_file(vmod_output_path) or { '' }) == vmod_source assert (os.read_file(helper_path) or { '' }) == helper_source @@ -8907,10 +8906,10 @@ fn test_execute_run_file_materializes_new_unsaved_buffer() { app.execute_commands_synchronously = true resp := app.handle_execute_command(Request{ - id: 824 + id: 824 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runFile' + command: 'vls.runFile' arguments: [uri] }, escape_unicode: true @@ -8937,10 +8936,10 @@ fn test_execute_run_file_returns_before_long_running_program_finishes() { started_at := time.now().unix_milli() resp := app.handle_execute_command(Request{ - id: 825 + id: 825 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runFile' + command: 'vls.runFile' arguments: [path_to_uri(path)] }, escape_unicode: true @@ -8979,10 +8978,10 @@ fn test_execute_run_file_replaces_active_target() { app.capture_output = true first_resp := app.handle_execute_command(Request{ - id: 826 + id: 826 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runFile' + command: 'vls.runFile' arguments: [uri] }, escape_unicode: true @@ -9001,10 +9000,10 @@ fn test_execute_run_file_replaces_active_target() { app.open_files[uri] = 'module main\n\nimport os\nimport time\n\nfn main() {\n\tos.write_file(${marker_literal}, "second") or { return }\n\ttime.sleep(5 * time.second)\n}\n' second_resp := app.handle_execute_command(Request{ - id: 827 + id: 827 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runFile' + command: 'vls.runFile' arguments: [uri] }, escape_unicode: true @@ -9095,10 +9094,10 @@ fn test_execute_run_test_selects_one_function() { app.execute_commands_synchronously = true resp := app.handle_execute_command(Request{ - id: 823 + id: 823 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ - command: 'vls.runTests' + command: 'vls.runTests' arguments: [uri, 'test_selected'] }, escape_unicode: true @@ -9118,7 +9117,7 @@ fn test_execute_command_unknown_still_returns_null() { } resp := app.handle_execute_command(Request{ - id: 821 + id: 821 method: 'workspace/executeCommand' params: json2.encode(ExecuteCommandParams{ command: 'unknownCommand' @@ -9144,18 +9143,18 @@ fn test_inline_value_returns_values_for_simple_assignment() { app.open_files[uri] = content resp := app.handle_inline_value(Request{ - id: 830 + id: 830 method: 'textDocument/inlineValue' params: json2.encode(InlineValueParams{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 5 char: 0 } @@ -9181,18 +9180,18 @@ fn test_inline_value_returns_empty_for_no_assignments() { app.open_files[uri] = 'module main\n\nfn main() {}\n' resp := app.handle_inline_value(Request{ - id: 831 + id: 831 method: 'textDocument/inlineValue' params: json2.encode(InlineValueParams{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 2 char: 0 } @@ -9221,13 +9220,13 @@ fn test_linked_editing_range_returns_ranges_for_identifier() { app.open_files[uri] = content resp := app.handle_linked_editing_range(Request{ - id: 840 + id: 840 method: 'textDocument/linkedEditingRange' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -9253,13 +9252,13 @@ fn test_linked_editing_range_returns_null_when_not_on_identifier() { // Position on an empty line resp := app.handle_linked_editing_range(Request{ - id: 841 + id: 841 method: 'textDocument/linkedEditingRange' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 1 char: 0 } @@ -9285,13 +9284,13 @@ fn test_selection_range_returns_one_entry_per_position() { app.open_files[uri] = content resp := app.handle_selection_range(Request{ - id: 850 + id: 850 method: 'textDocument/selectionRange' params: json2.encode(SelectionRangeParams{ text_document: TextDocumentIdentifier{ uri: uri } - positions: [Position{ + positions: [Position{ line: 3 char: 2 }, Position{ @@ -9319,13 +9318,13 @@ fn test_selection_range_word_range_has_parent_line_range() { app.open_files[uri] = content resp := app.handle_selection_range(Request{ - id: 851 + id: 851 method: 'textDocument/selectionRange' params: json2.encode(SelectionRangeParams{ text_document: TextDocumentIdentifier{ uri: uri } - positions: [Position{ + positions: [Position{ line: 3 char: 2 }] @@ -9353,17 +9352,17 @@ fn test_on_type_formatting_returns_empty_edits() { } resp := app.handle_on_type_formatting(Request{ - id: 860 + id: 860 method: 'textDocument/onTypeFormatting' params: json2.encode(OnTypeFormattingParams{ text_document: TextDocumentIdentifier{ uri: 'file:///tmp/fmt.v' } - position: Position{ + position: Position{ line: 3 char: 0 } - ch: '}' + ch: '}' }, escape_unicode: true ) @@ -9393,19 +9392,19 @@ fn test_call_hierarchy_outgoing_returns_callees() { app.workspace_roots = [root] resp := app.handle_call_hierarchy_outgoing(Request{ - id: 870 + id: 870 method: 'callHierarchy/outgoingCalls' params: json2.encode(CallHierarchyOutgoingCallsParams{ item: CallHierarchyItem{ - name: 'main' - kind: sym_kind_function - uri: uri - range: LSPRange{ + name: 'main' + kind: sym_kind_function + uri: uri + range: LSPRange{ start: Position{ line: 4 char: 0 } - end: Position{ + end: Position{ line: 6 char: 1 } @@ -9415,7 +9414,7 @@ fn test_call_hierarchy_outgoing_returns_callees() { line: 4 char: 3 } - end: Position{ + end: Position{ line: 4 char: 7 } @@ -9448,19 +9447,19 @@ fn test_call_hierarchy_incoming_returns_callers() { app.workspace_roots = [root] resp := app.handle_call_hierarchy_incoming(Request{ - id: 871 + id: 871 method: 'callHierarchy/incomingCalls' params: json2.encode(CallHierarchyIncomingCallsParams{ item: CallHierarchyItem{ - name: 'helper' - kind: sym_kind_function - uri: uri - range: LSPRange{ + name: 'helper' + kind: sym_kind_function + uri: uri + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 15 } @@ -9470,7 +9469,7 @@ fn test_call_hierarchy_incoming_returns_callers() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 9 } @@ -9501,11 +9500,11 @@ fn test_organize_imports_refuses_non_contiguous_block() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{} - context: CodeActionContext{} + range: LSPRange{} + context: CodeActionContext{} } resp := app.handle_code_action(Request{ - id: 1 + id: 1 params: json2.encode(params, escape_unicode: true) }) assert resp.result is []CodeAction @@ -9526,11 +9525,11 @@ fn test_organize_imports_sorts_contiguous_block() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{} - context: CodeActionContext{} + range: LSPRange{} + context: CodeActionContext{} } resp := app.handle_code_action(Request{ - id: 2 + id: 2 params: json2.encode(params, escape_unicode: true) }) assert resp.result is []CodeAction @@ -9557,13 +9556,13 @@ fn test_organize_imports_preserves_crlf_line_endings() { uri := 'file:///tmp/oi_crlf.v' app.open_files[uri] = 'module main\r\n\r\nimport time\r\nimport os\r\n\r\nfn main() {}\r\n' resp := app.handle_code_action(Request{ - id: 3 + id: 3 params: json2.encode(CodeActionParams{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{} - context: CodeActionContext{} + range: LSPRange{} + context: CodeActionContext{} }, escape_unicode: true ) @@ -9599,12 +9598,12 @@ fn test_remove_unknown_import_range_at_eof_without_newline() { app.open_files[uri] = 'module main\nimport foo' diag := LSPDiagnostic{ message: 'cannot import module "foo" (not found)' - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 0 } - end: Position{ + end: Position{ line: 1 char: 10 } @@ -9614,13 +9613,13 @@ fn test_remove_unknown_import_range_at_eof_without_newline() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{} - context: CodeActionContext{ + range: LSPRange{} + context: CodeActionContext{ diagnostics: [diag] } } resp := app.handle_code_action(Request{ - id: 1 + id: 1 params: json2.encode(params, escape_unicode: true) }) actions := resp.result as []CodeAction @@ -9651,12 +9650,12 @@ fn test_remove_unknown_import_range_with_trailing_newline() { app.open_files[uri] = 'import foo\nmodule main\n' diag := LSPDiagnostic{ message: 'unknown module `foo`' - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 10 } @@ -9666,13 +9665,13 @@ fn test_remove_unknown_import_range_with_trailing_newline() { text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{} - context: CodeActionContext{ + range: LSPRange{} + context: CodeActionContext{ diagnostics: [diag] } } resp := app.handle_code_action(Request{ - id: 1 + id: 1 params: json2.encode(params, escape_unicode: true) }) actions := resp.result as []CodeAction @@ -9780,7 +9779,7 @@ fn test_apply_incremental_change_non_bmp_utf16() { line: 0 char: 3 // after 🚀 in UTF-16 units (a=1, 🚀=2) } - end: Position{ + end: Position{ line: 0 char: 4 } @@ -9876,13 +9875,13 @@ fn test_document_highlight_returns_empty_over_semantic_cap() { app.open_files[uri] = content response := app.handle_document_highlight(Request{ - id: 900 + id: 900 method: 'textDocument/documentHighlight' params: json2.encode(DocumentHighlightParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 5 } @@ -9908,19 +9907,19 @@ fn test_on_did_change_invalid_range_does_not_advance_version() { // must NOT advance (P0-07). app.on_did_change(Request{ params: json2.encode(DidChangeTextDocumentParams{ - text_document: VersionedTextDocumentIdentifier{ - uri: uri + text_document: VersionedTextDocumentIdentifier{ + uri: uri version: 2 } content_changes: [ ContentChange{ - text: 'X' + text: 'X' range: LSPRange{ start: Position{ line: 0 char: 5 } - end: Position{ + end: Position{ line: 0 char: 2 } @@ -9968,13 +9967,13 @@ fn test_operation_at_pos_hover_returns_symbol_information() { app.text = content response := app.operation_at_pos(.hover, Request{ - id: 901 + id: 901 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 8 char: 13 } @@ -10047,13 +10046,13 @@ fn main() { } response := app.operation_at_pos(.hover, Request{ - id: 902 + id: 902 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: call_line char: new_col + 1 } @@ -10077,13 +10076,13 @@ fn main() { return } imported_response := app.operation_at_pos(.hover, Request{ - id: 903 + id: 903 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: imported_line char: imported_col + 1 } @@ -10114,17 +10113,17 @@ fn test_find_references_returns_declaration_and_calls() { app.workspace_roots = [test_dir] response := app.find_references(Request{ - id: 902 + id: 902 method: 'textDocument/references' params: json2.encode(ReferenceParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 7 char: 10 } - context: ReferenceContext{ + context: ReferenceContext{ include_declaration: true } }, @@ -10158,17 +10157,17 @@ fn test_handle_rename_returns_complete_workspace_edit() { app.workspace_roots = [test_dir] response := app.handle_rename(Request{ - id: 903 + id: 903 method: 'textDocument/rename' params: json2.encode(RenameParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 7 char: 11 } - new_name: 'renamed_value' + new_name: 'renamed_value' }, escape_unicode: true ) @@ -10200,7 +10199,7 @@ fn test_folding_range_covers_imports_comments_and_code_blocks() { app.open_files[uri] = 'module main\n\nimport os\nimport time\n\n// first line\n// second line\n\nfn main() {\n\tprintln(os.args)\n}\n' response := app.handle_folding_range(Request{ - id: 904 + id: 904 method: 'textDocument/foldingRange' params: json2.encode(FoldingRangeParams{ text_document: TextDocumentIdentifier{ @@ -10233,13 +10232,13 @@ fn test_document_highlight_returns_reads_and_writes() { app.open_files[uri] = content response := app.handle_document_highlight(Request{ - id: 905 + id: 905 method: 'textDocument/documentHighlight' params: json2.encode(DocumentHighlightParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -10273,15 +10272,15 @@ fn test_workspace_configuration_toggles_feature_behavior() { assert !app.diagnostics_enabled hint_response := app.handle_inlay_hints(Request{ - id: 906 + id: 906 method: 'textDocument/inlayHint' params: json2.encode(InlayHintParams{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{} - end: Position{ + end: Position{ line: 5 } } @@ -10364,13 +10363,13 @@ fn test_will_save_wait_until_formats_without_mutating_open_document() { app.open_files[uri] = content response := app.on_will_save_wait_until(Request{ - id: 907 + id: 907 method: 'textDocument/willSaveWaitUntil' params: json2.encode(WillSaveTextDocumentParams{ text_document: TextDocumentIdentifier{ uri: uri } - reason: 1 + reason: 1 }, escape_unicode: true ) @@ -10398,22 +10397,22 @@ fn test_range_formatting_returns_only_contained_changed_hunk() { app.open_files[uri] = content response := app.handle_range_formatting(Request{ - id: 908 + id: 908 method: 'textDocument/rangeFormatting' params: json2.encode(DocumentRangeFormattingParams{ text_document: TextDocumentIdentifier{ uri: uri } - range: LSPRange{ + range: LSPRange{ start: Position{ line: 3 } - end: Position{ + end: Position{ line: 3 char: 4 } } - options: FormattingOptions{ + options: FormattingOptions{ tab_size: 4 } }, @@ -10439,13 +10438,13 @@ fn test_prepare_call_hierarchy_returns_function_item() { app.open_files[uri] = 'module main\n\nfn helper() {}\n\nfn main() {\n\thelper()\n}\n' response := app.handle_prepare_call_hierarchy(Request{ - id: 909 + id: 909 method: 'textDocument/prepareCallHierarchy' params: json2.encode(PrepareCallHierarchyParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -10538,12 +10537,12 @@ fn test_unary_ampersand_operand_is_guarded() { uri := path_to_uri(os.join_path(app.temp_dir, 'guarded_ampersand.v')) content := 'module main\n\nfn main() {}\n' app.open_files[uri] = content - assert app.expression_type(uri, content, '&', Position{line: 0, char: 0}) == '' - assert app.expression_type(uri, content, '(&)', Position{line: 0, char: 0}) == '' + assert app.expression_type(uri, content, '&', Position{ line: 0, char: 0 }) == '' + assert app.expression_type(uri, content, '(&)', Position{ line: 0, char: 0 }) == '' } fn test_index_key_with_dotdot_in_string_literal_is_not_treated_as_slice() { - result := indexed_completions_at_line_end('map_key_dotdot_completion', 'module main\n\nstruct Point {\n\tx int\n}\n\nfn main() {\n\tm := map[string]Point{}\n\tm[\'a..b\'].\n}\n', '\tm[\'a..b\'].') + result := indexed_completions_at_line_end('map_key_dotdot_completion', "module main\n\nstruct Point {\n\tx int\n}\n\nfn main() {\n\tm := map[string]Point{}\n\tm['a..b'].\n}\n", "\tm['a..b'].") labels := result.items.map(it.label) assert 'x' in labels, labels.str() assert 'keys' !in labels, labels.str() @@ -11081,8 +11080,9 @@ fn array_completion_items(dir_name string, decl string) []Detail { fn test_array_receivers_complete_their_builtin_methods() { ints := array_completion_items('array_int_literal', 'arr := [3, 1, 2]') int_labels := ints.map(it.label) - for name in ['len', 'cap', 'filter', 'map', 'sort', 'sorted', 'contains', 'index', 'first', 'last', - 'pop', 'insert', 'prepend', 'delete', 'clear', 'reverse', 'clone', 'any', 'all', 'count', 'trim'] { + for name in ['len', 'cap', 'filter', 'map', 'sort', 'sorted', 'contains', 'index', 'first', + 'last', 'pop', 'insert', 'prepend', 'delete', 'clear', 'reverse', 'clone', 'any', 'all', + 'count', 'trim'] { assert name in int_labels, '${name} missing: ${int_labels}' } assert 'join' !in int_labels diff --git a/index.v b/index.v index 81456c67..3218418b 100644 --- a/index.v +++ b/index.v @@ -20,11 +20,11 @@ import time struct IndexEntry { fingerprint int // content.hash(); used to skip re-parsing unchanged files module_name string - doc_symbols []DocumentSymbol // hierarchical symbols (as parse_document_symbols returns) + doc_symbols []DocumentSymbol // hierarchical symbols (as parse_document_symbols returns) docs map[string]string // simple symbol name -> leading vdoc comment - fn_completions []Detail // free-function completion items for this file - module_completions []Detail // all same-module top-level completion items - public_module_completions []Detail // exported completion items for imported modules + fn_completions []Detail // free-function completion items for this file + module_completions []Detail // all same-module top-level completion items + public_module_completions []Detail // exported completion items for imported modules has_conditional_module_completions bool has_conditional_public_completions bool conditional_lines []bool // declarations guarded by $if/$else or @[if] @@ -52,16 +52,16 @@ fn build_index_entry(content string, enc PositionEncoding) IndexEntry { } } return IndexEntry{ - fingerprint: content.hash() - module_name: get_module_name(content) - doc_symbols: doc_syms - docs: docs - fn_completions: module_completion_index.items.filter(it.kind == 3) - module_completions: module_completion_index.items - public_module_completions: public_module_completion_index.items + fingerprint: content.hash() + module_name: get_module_name(content) + doc_symbols: doc_syms + docs: docs + fn_completions: module_completion_index.items.filter(it.kind == 3) + module_completions: module_completion_index.items + public_module_completions: public_module_completion_index.items has_conditional_module_completions: module_completion_index.has_conditional has_conditional_public_completions: public_module_completion_index.has_conditional - conditional_lines: conditional_lines + conditional_lines: conditional_lines } } @@ -75,12 +75,12 @@ fn encode_document_symbols(syms []DocumentSymbol, lines []string, enc PositionEn mut out := []DocumentSymbol{cap: syms.len} for sym in syms { out << DocumentSymbol{ - name: sym.name - kind: sym.kind - tags: sym.tags - range: encode_range_chars(sym.range, lines, enc) + name: sym.name + kind: sym.kind + tags: sym.tags + range: encode_range_chars(sym.range, lines, enc) selection_range: encode_range_chars(sym.selection_range, lines, enc) - children: encode_document_symbols(sym.children, lines, enc) + children: encode_document_symbols(sym.children, lines, enc) } } return out @@ -104,7 +104,7 @@ fn encode_range_chars(r LSPRange, lines []string, enc PositionEncoding) LSPRange line: r.start.line char: byte_to_encoded_col(start_line, r.start.char, enc) } - end: Position{ + end: Position{ line: r.end.line char: byte_to_encoded_col(end_line, r.end.char, enc) } @@ -156,9 +156,9 @@ fn add_identifier_occurrence(line_text string, line_idx int, start int, end int, } name := line_text[start..end] occ[name] << TokenOccurrence{ - line: line_idx + line: line_idx start_char: byte_to_encoded_col(line_text, start, enc) - end_char: byte_to_encoded_col(line_text, end, enc) + end_char: byte_to_encoded_col(line_text, end, enc) } } @@ -305,7 +305,7 @@ fn (mut app App) occurrences_for(uri string) map[string][]TokenOccurrence { occ := extract_identifier_occurrences(content, app.position_encoding) app.ref_occurrences[uri] = OccEntry{ fingerprint: fp - occ: occ + occ: occ } return occ } @@ -763,13 +763,13 @@ fn (app &App) index_scope_for_uri(uri string) IndexScope { if project_root != '' && project_root != '/' && (workspace_root == '' || path_is_within(project_root, workspace_root)) { return IndexScope{ - dir: project_root + dir: project_root recursive: true } } if workspace_root != '' { return IndexScope{ - dir: workspace_root + dir: workspace_root recursive: true } } diff --git a/index_test.v b/index_test.v index 3f459705..2afa8fc1 100644 --- a/index_test.v +++ b/index_test.v @@ -9,7 +9,7 @@ import time fn index_test_app() &App { return &App{ open_files: map[string]string{} - temp_dir: os.temp_dir() + temp_dir: os.temp_dir() } } @@ -105,7 +105,7 @@ fn test_watched_file_reindex_drops_oversized_disk_entry() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: uri + uri: uri event_type: 1 }] }) @@ -121,7 +121,7 @@ fn test_watched_file_reindex_drops_oversized_disk_entry() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: uri + uri: uri event_type: 2 }] }) @@ -148,7 +148,7 @@ fn test_watched_file_reindex_obeys_total_entry_limit() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: uri + uri: uri event_type: 1 }] }) @@ -177,7 +177,7 @@ fn test_watched_file_reuses_equivalent_open_document_uri() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: event_uri + uri: event_uri event_type: 2 }] }) @@ -1027,7 +1027,7 @@ fn test_index_large_multifile_project_stays_complete_and_incremental() { app.on_did_change_watched_files(Request{ params: json2.encode(DidChangeWatchedFilesParams{ changes: [FileEvent{ - uri: changed_uri + uri: changed_uri event_type: 2 }] }) diff --git a/integration_test.v b/integration_test.v index d3410bdf..c89df040 100644 --- a/integration_test.v +++ b/integration_test.v @@ -59,9 +59,9 @@ fn create_integration_test_env() (&App, string) { integration_test_must_mkdir_all(project_dir) app := &App{ - text: '' + text: '' open_files: map[string]string{} - temp_dir: temp_dir + temp_dir: temp_dir } return app, project_dir } @@ -165,20 +165,20 @@ fn test_integration_stdio_initialize_completion_and_hover() { fn test_integration_initialize_capabilities() { // Simulate what the server returns for initialize response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } } @@ -199,7 +199,7 @@ fn test_integration_initialize_capabilities() { fn test_integration_initialize_response_structure() { // Verify response has proper JSON-RPC structure response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -217,7 +217,7 @@ fn test_integration_initialize_response_structure() { fn test_integration_initialize_does_not_advertise_client_snippet_support() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ completion_provider: CompletionProvider{ @@ -233,14 +233,14 @@ fn test_integration_initialize_does_not_advertise_client_snippet_support() { fn test_integration_initialize_workspace_capabilities() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ execute_command_provider: ExecuteCommandOptions{ commands: ['vls.runFile', 'vls.runTests'] } - workspace: WorkspaceCapability{ - file_operations: WorkspaceFileOperations{ + workspace: WorkspaceCapability{ + file_operations: WorkspaceFileOperations{ will_create: FileOperationRegistrationOptions{ filters: [ FileOperationFilter{ @@ -270,7 +270,7 @@ fn test_integration_initialize_workspace_capabilities() { } } workspace_folders: WorkspaceFoldersServerCapability{ - supported: true + supported: true change_notifications: true } } @@ -301,10 +301,10 @@ fn test_integration_document_lifecycle() { // 1. Open document open_request := Request{ - id: 1 - method: 'textDocument/didOpen' + id: 1 + method: 'textDocument/didOpen' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -320,11 +320,11 @@ fn test_integration_document_lifecycle() { // 2. Change document new_content := "module main\n\nfn main() {\n\tprintln('world')\n}\n" change_request := Request{ - id: 2 - method: 'textDocument/didChange' + id: 2 + method: 'textDocument/didChange' jsonrpc: '2.0' - params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + params: json2.encode(Params{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -539,7 +539,7 @@ fn test_integration_diagnostics_syntax_error() { // Trigger change to get diagnostics change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -585,7 +585,7 @@ fn test_integration_diagnostics_valid_code() { change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -639,17 +639,17 @@ fn test_integration_diagnostics_deduplication() { errors := [ JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'first error' }, JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'duplicate error' }, // Same position JsonError{ line_nr: 6 - col: 1 + col: 1 message: 'different position' }, ] @@ -691,7 +691,7 @@ fn test_integration_diagnostics_empty_file() { // Empty content should be processed and return diagnostics for the empty file result := app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -736,14 +736,14 @@ fn test_integration_completion_request() { // Request completion at the position after "os." request := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 4 } // After "os." @@ -774,12 +774,12 @@ fn test_integration_completion_request_id_preserved() { // Test with various IDs for id in [1, 42, 100, 999] { request := Request{ - id: id + id: id params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -807,12 +807,12 @@ fn test_integration_completion_at_function_call() { app.open_files[uri] = content request := Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 9 } @@ -851,14 +851,14 @@ fn test_integration_definition_request() { // Request definition at the call site of helper() request := Request{ - id: 2 - method: 'textDocument/definition' + id: 2 + method: 'textDocument/definition' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } // At "helper()" @@ -920,12 +920,12 @@ fn test_integration_definition_multifile() { // Request definition from main file request := Request{ - id: 3 + id: 3 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -973,13 +973,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { app.text = open_content definition := app.operation_at_pos(.definition, Request{ - id: 31 + id: 31 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -993,13 +993,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert definition_location.range.start.line == 2 hover := app.operation_at_pos(.hover, Request{ - id: 32 + id: 32 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -1011,13 +1011,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert (hover.result as Hover).contents.value.contains('answer') signature := app.operation_at_pos(.signature_help, Request{ - id: 33 + id: 33 method: 'textDocument/signatureHelp' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 25 } @@ -1030,13 +1030,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert signature_help.signatures.any(it.label.contains('answer')) completion := app.operation_at_pos(.completion, Request{ - id: 34 + id: 34 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 18 } @@ -1070,17 +1070,17 @@ fn (a VlangVCallAnchor) decl_path(root string) string { // vlib/v, `builder.compile` before that. const vlang_v_module_call_anchors = [ VlangVCallAnchor{ - call: 'driver.run(' + call: 'driver.run(' qualifier: 'driver.' - name: 'run' - decl_rel: 'vlib/v/driver/driver.v' + name: 'run' + decl_rel: 'vlib/v/driver/driver.v' decl_text: 'fn run(' }, VlangVCallAnchor{ - call: "builder.compile('build'" + call: "builder.compile('build'" qualifier: 'builder.' - name: 'compile' - decl_rel: 'vlib/v/builder/compile.v' + name: 'compile' + decl_rel: 'vlib/v/builder/compile.v' decl_text: 'fn compile(' }, ] @@ -1088,17 +1088,17 @@ const vlang_v_module_call_anchors = [ // Method calls on a local variable, for receiver-type inference. const vlang_v_method_call_anchors = [ VlangVCallAnchor{ - call: 'process.set_args(' + call: 'process.set_args(' qualifier: 'process.' - name: 'set_args' - decl_rel: 'vlib/os/process.v' + name: 'set_args' + decl_rel: 'vlib/os/process.v' decl_text: 'fn (mut p Process) set_args(' }, VlangVCallAnchor{ - call: "timers.show('v start'" + call: "timers.show('v start'" qualifier: 'timers.' - name: 'show' - decl_rel: 'vlib/v/util/timers.v' + name: 'show' + decl_rel: 'vlib/v/util/timers.v' decl_text: 'fn (mut t Timers) show(' }, ] @@ -1171,12 +1171,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { for i, method in [Method.definition, .declaration, .type_definition, .implementation] { response := app.operation_at_pos(method, Request{ - id: 40 + i + id: 40 + i params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1191,12 +1191,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { } hover := app.operation_at_pos(.hover, Request{ - id: 44 + id: 44 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1211,12 +1211,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { open_paren_col := lines[call_line].index(call_text) or { -1 } assert open_paren_col >= 0 signature := app.operation_at_pos(.signature_help, Request{ - id: 45 + id: 45 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: open_paren_col + call_text.len } @@ -1235,12 +1235,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { app.open_files[main_uri] = completion_content app.text = completion_content completion := app.operation_at_pos(.completion, Request{ - id: 46 + id: 46 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: dot_col + anchor.qualifier.len } @@ -1288,13 +1288,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( app.workspace_roots = [root] module_completion := app.operation_at_pos(.completion, Request{ - id: 47 + id: 47 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: compile_line char: compile_dot_col } @@ -1306,13 +1306,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (module_completion.result as CompletionList).items.any(it.label == module_anchor.name) receiver_completion := app.operation_at_pos(.completion, Request{ - id: 48 + id: 48 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: timer_dot_col } @@ -1324,13 +1324,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (receiver_completion.result as CompletionList).items.any(it.label == method_anchor.name) definition := app.operation_at_pos(.definition, Request{ - id: 49 + id: 49 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: show_col + 2 } @@ -1374,14 +1374,14 @@ fn test_integration_signature_help_request() { // Request signature help after opening paren request := Request{ - id: 3 - method: 'textDocument/signatureHelp' + id: 3 + method: 'textDocument/signatureHelp' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } // After "greet(" @@ -1415,12 +1415,12 @@ fn test_integration_signature_help_with_params() { // At second parameter position request := Request{ - id: 4 + id: 4 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } @@ -1584,7 +1584,7 @@ fn test_integration_json_error_with_special_chars() { fn test_integration_response_encoding() { response := Response{ - id: 42 + id: 42 result: 'null' } @@ -1600,20 +1600,20 @@ fn test_integration_notification_encoding() { notification := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } } - message: 'test error' + message: 'test error' severity: 1 }, ] @@ -1663,19 +1663,19 @@ fn test_integration_begin_progress_with_client_support_emits_create_and_begin() fn test_integration_completion_response_encoding() { details := [ Detail{ - kind: 6 - label: 'println' + kind: 6 + label: 'println' detail: 'fn println(s string)' }, Detail{ - kind: 6 - label: 'print' + kind: 6 + label: 'print' detail: 'fn print(s string)' }, ] response := Response{ - id: 1 + id: 1 result: details } @@ -1686,15 +1686,15 @@ fn test_integration_completion_response_encoding() { fn test_integration_location_response_encoding() { response := Response{ - id: 1 + id: 1 result: Location{ - uri: 'file:///test/main.v' + uri: 'file:///test/main.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1709,11 +1709,11 @@ fn test_integration_location_response_encoding() { fn test_integration_signature_help_response_encoding() { response := Response{ - id: 1 + id: 1 result: SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1752,13 +1752,13 @@ fn test_integration_request_id_preserved() { // Test with different request IDs for id in [1, 42, 999, 0] { request := Request{ - id: id + id: id method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -1871,17 +1871,17 @@ fn test_integration_full_lifecycle() { // 2. Simulate initialize (verify capabilities) caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1902,7 +1902,7 @@ fn test_integration_full_lifecycle() { modified_content := 'module main\n\nfn helper() {}\n\nfn main() {\n\thelper()\n}\n' app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -1916,12 +1916,12 @@ fn test_integration_full_lifecycle() { // 5. Request completion comp_response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1933,12 +1933,12 @@ fn test_integration_full_lifecycle() { // 6. Request definition def_response := app.operation_at_pos(.definition, Request{ - id: 2 + id: 2 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1955,7 +1955,7 @@ fn test_integration_full_lifecycle() { fn test_integration_shutdown_response() { // Verify shutdown response structure shutdown_resp := Response{ - id: 1 + id: 1 result: 'null' } @@ -2429,12 +2429,12 @@ fn test_integration_completion_includes_sibling_pub_fn() { // Request completion at `helper` on line 3, col 1 (not after '.') response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 1 } @@ -2475,12 +2475,12 @@ fn test_integration_completion_includes_private_sibling_fn() { app.text = main_content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -2514,12 +2514,12 @@ fn test_integration_completion_includes_current_file_fns() { app.text = content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 // inside fn main, after `he` char: 2 } @@ -2614,13 +2614,13 @@ fn test_integration_prepare_rename_returns_symbol_range() { app.open_files[uri] = content response := app.handle_prepare_rename(Request{ - id: 301 + id: 301 method: 'textDocument/prepareRename' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 4 char: 12 } @@ -2649,7 +2649,7 @@ fn test_integration_workspace_symbol_query_matches() { app.open_files[uri] = content response := app.handle_workspace_symbol(Request{ - id: 302 + id: 302 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'name' @@ -2682,7 +2682,7 @@ fn test_integration_workspace_symbol_indexes_loose_module_sibling() { assert find_project_root(project_dir) == '' response := app.handle_workspace_symbol(Request{ - id: 303 + id: 303 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'unopened_loose' @@ -2714,13 +2714,13 @@ fn test_integration_alias_navigation_methods_preserve_id() { mut request_id := 410 for m in methods { resp := app.operation_at_pos(m, Request{ - id: request_id + id: request_id method: m.str() params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -2735,17 +2735,17 @@ fn test_integration_alias_navigation_methods_preserve_id() { fn test_integration_capability_flags_for_new_features() { caps := Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 2 - save: SaveOptions{ + change: 2 + save: SaveOptions{ include_text: true } } - declaration_provider: true - type_definition_provider: true - implementation_provider: true - rename_provider: RenameOptions{ + declaration_provider: true + type_definition_provider: true + implementation_provider: true + rename_provider: RenameOptions{ prepare_provider: true } workspace_symbol_provider: true @@ -2795,8 +2795,10 @@ fn test_integration_sublime_text_lsp_handshake() { root_uri := path_to_uri(project_dir) initialize := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":42,"clientInfo":{"name":"Sublime Text LSP","version":"2.13.0"},"locale":"en","rootUri":"${root_uri}","rootPath":"${project_dir}","workspaceFolders":[{"uri":"${root_uri}","name":"test_project"}],"capabilities":{"general":{"positionEncodings":["utf-16"]},"workspace":{"workspaceFolders":true,"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"publishDiagnostics":{"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":{}}}' initialized := '{"jsonrpc":"2.0","method":"initialized","params":{}}' - output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [initialize, - initialized]) + output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [ + initialize, + initialized, + ]) assert app.received_initialize assert app.workspace_roots == [project_dir] diff --git a/interop.v b/interop.v index 0aad112f..3a91ac2c 100644 --- a/interop.v +++ b/interop.v @@ -267,7 +267,8 @@ fn build_v_check_args_multifile(is_library_module bool) []string { } fn build_v_line_info_args_multifile(rel_file string, line_info string) []string { - return ['-w', '-check', '-nocolor', '-vls-mode', '-line-info', '${rel_file}:${line_info}', '.'] + return ['-w', '-check', '-nocolor', '-vls-mode', '-line-info', '${rel_file}:${line_info}', + '.'] } fn build_v_line_info_args_single(file_to_check string, line_info string, compile_target string) []string { @@ -284,8 +285,8 @@ fn build_v_line_info_args_single(file_to_check string, line_info string, compile // than assumed from a version number. enum LineInfoMode { unknown // not probed yet — try the options as-is - direct // the compiler answers `-vls-mode` / `-line-info` itself - compat // reaching the checker needs `-old-compiler` + direct // the compiler answers `-vls-mode` / `-line-info` itself + compat // reaching the checker needs `-old-compiler` missing // no compatibility compiler either — answer from VLS's own index } @@ -465,11 +466,11 @@ fn parse_v_check_diagnostic_header(line string, source_dir string) ?JsonError { if marker_idx > best_marker_idx { best_marker_idx = marker_idx best = JsonError{ - path: path + path: path message: line[marker_idx + marker.len..] line_nr: line_nr_text.int() - col: col_text.int() - level: if level.contains('error') { 'error' } else { level } + col: col_text.int() + level: if level.contains('error') { 'error' } else { level } } } break @@ -517,8 +518,8 @@ fn (mut app App) cache_v_check_result(path string, content_hash int, generation } app.diag_cache[path] = DiagCacheEntry{ content_hash: content_hash - generation: generation - errors: errors + generation: generation + errors: errors } } @@ -553,7 +554,7 @@ fn run_v_argv(args []string, work_folder string) os.Result { log(msg) return os.Result{ exit_code: 1 - output: msg + output: msg } } v_exe := resolve_v_compiler_exe() @@ -615,12 +616,12 @@ fn run_v_argv(args []string, work_folder string) os.Result { if timed_out { return os.Result{ exit_code: compiler_exit_timeout - output: '' + output: '' } } return os.Result{ exit_code: code - output: out.str() + output: out.str() } } @@ -728,12 +729,12 @@ fn (mut app App) prepare_compilation_overlay(real_path string) !CompilationOverl } } return CompilationOverlay{ - source_root: source_root + source_root: source_root source_display_root: source_display_root - temp_root: temp_root - source_work_dir: source_work_dir - temp_work_dir: temp_work_dir - temp_source_file: os.join_path(temp_root, file_rel) + temp_root: temp_root + source_work_dir: source_work_dir + temp_work_dir: temp_work_dir + temp_source_file: os.join_path(temp_root, file_rel) } } @@ -840,12 +841,12 @@ fn (mut app App) run_v_check(path string, text string) []JsonError { err_file := source_path_from_overlay(err.path, overlay) if normalized_index_path(err_file) == normalized_index_path(real_path) { updated_err := JsonError{ - path: real_path + path: real_path message: err.message line_nr: err.line_nr - col: err.col - len: err.len - level: err.level + col: err.col + len: err.len + level: err.level } filtered_errors << updated_err log('INCLUDING ERROR from err_file=${err_file}: ${err.message}') @@ -1513,7 +1514,7 @@ fn (mut app App) line_info_unavailable_result(method Method, path string, line_i } return Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: doc } } @@ -1696,7 +1697,7 @@ fn (mut app App) run_v_line_info(method Method, path string, line_info string) R } result = Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: value } } @@ -1704,7 +1705,7 @@ fn (mut app App) run_v_line_info(method Method, path string, line_info string) R // Compiler returned no info but we found a vdoc comment result = Hover{ contents: MarkupContent{ - kind: 'markdown' + kind: 'markdown' value: doc } } @@ -1749,13 +1750,13 @@ fn (app &App) compiler_location(path string, line int, byte_col int) Location { target_uri := index_uri_for_path(path, app.open_index_uris_by_path()) client_col := app.byte_col_to_client_col(target_uri, line, byte_col) return Location{ - uri: target_uri + uri: target_uri range: LSPRange{ start: Position{ line: line char: client_col } - end: Position{ + end: Position{ line: line char: client_col } diff --git a/interop_test.v b/interop_test.v index 3f03a75a..3e2a86ee 100644 --- a/interop_test.v +++ b/interop_test.v @@ -164,8 +164,8 @@ fn test_normalize_overlay_path_preserves_posix_backslashes() { fn test_source_path_from_overlay_normalizes_windows_relative_join() { overlay := CompilationOverlay{ source_display_root: r'C:\repo' - temp_root: r'C:\temp\overlay' - temp_work_dir: r'C:\temp\overlay\src' + temp_root: r'C:\temp\overlay' + temp_work_dir: r'C:\temp\overlay\src' } mapped := source_path_from_overlay_with_windows_rules('./main.v', overlay, true) assert mapped == 'C:/repo/src/main.v' @@ -257,7 +257,7 @@ fn test_compiler_location_reuses_equivalent_open_uri() { canonical_uri := path_to_uri(path) open_uri := canonical_uri.replace_once('file:///', 'file://localhost/') mut app := &App{ - open_files: map[string]string{} + open_files: map[string]string{} position_encoding: .utf16 } app.open_files[open_uri] = '🚀 target\n' @@ -641,12 +641,12 @@ fn test_parse_v_check_diagnostics_reads_v3_output() { diagnostics := parse_v_check_diagnostics(output, '') assert diagnostics.len == 2 assert diagnostics[0] == JsonError{ - path: '/tmp/main.v' + path: '/tmp/main.v' message: 'undefined variable: `missing_name`' line_nr: 4 - col: 7 - len: 12 - level: 'error' + col: 7 + len: 12 + level: 'error' } assert diagnostics[1].level == 'warning' assert diagnostics[1].line_nr == 8 @@ -661,12 +661,12 @@ fn test_parse_v_check_diagnostics_maps_v3_builder_error_to_error() { ' diagnostics := parse_v_check_diagnostics(output, '') assert diagnostics == [JsonError{ - path: '/tmp/main.v' + path: '/tmp/main.v' message: 'cannot import module "missing" (not found)' line_nr: 3 - col: 1 - len: 14 - level: 'error' + col: 1 + len: 14 + level: 'error' }] } @@ -743,8 +743,8 @@ fn test_cache_v_check_result_retries_failure_without_diagnostics() { path := 'file:///tmp/main.v' app.diag_cache[path] = DiagCacheEntry{ content_hash: 1 - generation: 1 - errors: [] + generation: 1 + errors: [] } app.cache_v_check_result(path, 2, 2, [], compiler_exit_timeout, 0) assert path !in app.diag_cache @@ -755,16 +755,16 @@ fn test_cache_v_check_result_retries_timeout_with_partial_diagnostics() { path := 'file:///tmp/main.v' app.diag_cache[path] = DiagCacheEntry{ content_hash: 1 - generation: 1 - errors: [] + generation: 1 + errors: [] } partial_errors := [ JsonError{ - path: '/tmp/main.v' + path: '/tmp/main.v' message: 'partial compiler output' line_nr: 1 - col: 1 - level: 'error' + col: 1 + level: 'error' }, ] app.cache_v_check_result(path, 2, 2, partial_errors, compiler_exit_timeout, partial_errors.len) @@ -799,11 +799,11 @@ fn test_run_v_argv_reports_missing_working_dir() { fn test_v_error_to_lsp_diagnostic_basic() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'undefined identifier `foo`' line_nr: 10 - col: 5 - len: 3 + col: 5 + len: 3 } diag := v_error_to_lsp_diagnostic(v_err) @@ -818,11 +818,11 @@ fn test_v_error_to_lsp_diagnostic_basic() { fn test_v_error_to_lsp_diagnostic_first_line() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'syntax error' line_nr: 1 - col: 1 - len: 1 + col: 1 + len: 1 } diag := v_error_to_lsp_diagnostic(v_err) @@ -833,11 +833,11 @@ fn test_v_error_to_lsp_diagnostic_first_line() { fn test_v_error_to_lsp_diagnostic_long_error() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'unexpected token' line_nr: 100 - col: 50 - len: 20 + col: 50 + len: 20 } diag := v_error_to_lsp_diagnostic(v_err) @@ -848,11 +848,11 @@ fn test_v_error_to_lsp_diagnostic_long_error() { fn test_v_error_to_lsp_diagnostic_zero_length() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'error at position' line_nr: 5 - col: 10 - len: 0 + col: 10 + len: 0 } diag := v_error_to_lsp_diagnostic(v_err) @@ -862,11 +862,11 @@ fn test_v_error_to_lsp_diagnostic_zero_length() { fn test_v_error_to_lsp_diagnostic_large_line_numbers() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'error in large file' line_nr: 10000 - col: 200 - len: 50 + col: 200 + len: 50 } diag := v_error_to_lsp_diagnostic(v_err) @@ -877,11 +877,11 @@ fn test_v_error_to_lsp_diagnostic_large_line_numbers() { fn test_v_error_to_lsp_diagnostic_column_one() { v_err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'error at start of line' line_nr: 5 - col: 1 - len: 5 + col: 1 + len: 5 } diag := v_error_to_lsp_diagnostic(v_err) @@ -917,7 +917,7 @@ fn test_lsp_range_struct() { line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 10 } @@ -932,7 +932,7 @@ fn test_lsp_range_multiline() { line: 5 char: 10 } - end: Position{ + end: Position{ line: 10 char: 5 } @@ -942,17 +942,17 @@ fn test_lsp_range_multiline() { fn test_lsp_diagnostic_struct() { diag := LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 10 } } - message: 'test error' + message: 'test error' severity: 1 } assert diag.message == 'test error' @@ -965,8 +965,8 @@ fn test_lsp_diagnostic_severities() { severities := [1, 2, 3, 4] // Error, Warning, Information, Hint for sev in severities { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'test' + range: LSPRange{} + message: 'test' severity: sev } assert diag.severity == sev @@ -975,13 +975,13 @@ fn test_lsp_diagnostic_severities() { fn test_location_struct() { loc := Location{ - uri: 'file:///test/file.v' + uri: 'file:///test/file.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -999,9 +999,9 @@ fn test_location_empty() { fn test_detail_struct() { detail := Detail{ - kind: 6 // Function - label: 'my_function' - detail: 'fn my_function() string' + kind: 6 // Function + label: 'my_function' + detail: 'fn my_function() string' documentation: 'A helper function' } assert detail.kind == 6 @@ -1014,7 +1014,7 @@ fn test_detail_kinds() { kinds := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // Text, Method, Function, etc. for k in kinds { detail := Detail{ - kind: k + kind: k label: 'test' } assert detail.kind == k @@ -1023,11 +1023,11 @@ fn test_detail_kinds() { fn test_detail_struct_with_snippet() { detail := Detail{ - kind: 6 - label: 'println' - detail: 'fn println(s string)' - documentation: 'Prints a string' - insert_text: 'println(\${1:s})' + kind: 6 + label: 'println' + detail: 'fn println(s string)' + documentation: 'Prints a string' + insert_text: 'println(\${1:s})' insert_text_format: 2 // Snippet format } assert detail.insert_text? == 'println(\${1:s})' @@ -1036,7 +1036,7 @@ fn test_detail_struct_with_snippet() { fn test_detail_without_snippet() { detail := Detail{ - kind: 6 + kind: 6 label: 'println' } assert detail.insert_text == none @@ -1045,9 +1045,9 @@ fn test_detail_without_snippet() { fn test_signature_help_struct() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn my_func(a int, b string) bool' + label: 'fn my_func(a int, b string) bool' parameters: [ ParameterInformation{ label: 'a int' @@ -1068,7 +1068,7 @@ fn test_signature_help_struct() { fn test_signature_help_multiple_signatures() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ label: 'fn overload1(a int)' }, @@ -1096,17 +1096,17 @@ fn test_signature_help_empty() { fn test_capabilities_struct() { cap := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert cap.capabilities.definition_provider == true @@ -1127,11 +1127,11 @@ fn test_capabilities_minimal() { fn test_request_struct() { req := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' - params: json2.encode(Params{ - position: Position{ + params: json2.encode(Params{ + position: Position{ line: 5 char: 10 } @@ -1162,7 +1162,7 @@ fn test_request_params_decode_malformed_returns_error() { fn test_response_struct() { resp := Response{ - id: 1 + id: 1 result: 'null' } assert resp.id == 1 @@ -1171,7 +1171,7 @@ fn test_response_struct() { fn test_response_with_capabilities() { resp := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -1188,7 +1188,7 @@ fn test_notification_struct() { notif := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } } @@ -1200,16 +1200,16 @@ fn test_notification_with_diagnostics() { notif := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{} - message: 'error 1' + range: LSPRange{} + message: 'error 1' severity: 1 }, LSPDiagnostic{ - range: LSPRange{} - message: 'error 2' + range: LSPRange{} + message: 'error 2' severity: 1 }, ] @@ -1252,7 +1252,7 @@ fn test_write_tracked_files_to_temp_single_file() { interop_test_must_write_file(test_file, 'module main') mut app := &App{ - temp_dir: temp_dir + temp_dir: temp_dir open_files: map[string]string{} } @@ -1290,7 +1290,7 @@ fn test_write_tracked_files_to_temp_multiple_files() { } mut app := &App{ - temp_dir: temp_dir + temp_dir: temp_dir open_files: map[string]string{} } @@ -1331,7 +1331,7 @@ fn test_write_tracked_files_to_temp_nested_directories() { interop_test_must_write_file(nested_file, 'module internal') mut app := &App{ - temp_dir: temp_dir + temp_dir: temp_dir open_files: map[string]string{} } @@ -1371,7 +1371,7 @@ fn test_prepare_compilation_overlay_preserves_nested_symlink_layout() { main_uri := path_to_uri(main_file) unsaved_content := 'module main\n\nfn unsaved() {}\n' mut app := &App{ - temp_dir: app_temp_dir + temp_dir: app_temp_dir open_files: { main_uri: unsaved_content } @@ -1412,7 +1412,7 @@ fn test_prepare_compilation_overlay_preserves_posix_backslashes() { assert uri_to_path(main_uri) == main_file unsaved_content := 'module main\n\nfn unsaved() {}\n' mut app := &App{ - temp_dir: app_temp_dir + temp_dir: app_temp_dir open_files: { main_uri: unsaved_content } @@ -1450,7 +1450,7 @@ fn test_write_tracked_files_skips_files_outside_working_dir() { interop_test_must_write_file(other_file, 'module other') mut app := &App{ - temp_dir: temp_dir + temp_dir: temp_dir open_files: map[string]string{} } @@ -1911,8 +1911,8 @@ fn test_json_error_negative_values() { // to 0 rather than emitted as negative positions (P1-09). err := JsonError{ line_nr: -1 - col: -1 - len: -1 + col: -1 + len: -1 } diag := v_error_to_lsp_diagnostic(err) assert diag.severity == 1 @@ -1939,11 +1939,11 @@ fn test_params_struct_complete() { content_changes: [ContentChange{ text: 'test' }] - position: Position{ + position: Position{ line: 5 char: 10 } - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: 'file:///test.v' } } @@ -1971,7 +1971,7 @@ fn test_signature_help_options_triggers() { fn test_text_document_sync_options() { sync := TextDocumentSyncOptions{ open_close: true - change: 1 // Full sync + change: 1 // Full sync } assert sync.open_close == true assert sync.change == 1 @@ -1980,7 +1980,7 @@ fn test_text_document_sync_options() { fn test_text_document_sync_incremental() { sync := TextDocumentSyncOptions{ open_close: true - change: 2 // Incremental sync + change: 2 // Incremental sync } assert sync.change == 2 } @@ -1994,7 +1994,7 @@ fn test_parameter_information() { fn test_signature_information_with_params() { sig := SignatureInformation{ - label: 'fn test(a int, b string, c bool)' + label: 'fn test(a int, b string, c bool)' parameters: [ ParameterInformation{ label: 'a int' @@ -2013,11 +2013,11 @@ fn test_signature_information_with_params() { fn test_publish_diagnostics_params() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{} - message: 'error' + range: LSPRange{} + message: 'error' severity: 1 }, ] diff --git a/lsp.v b/lsp.v index dc1fd27b..600824f8 100644 --- a/lsp.v +++ b/lsp.v @@ -39,7 +39,7 @@ struct Params { position Position range LSPRange text_document TextDocumentIdentifier @[json: 'textDocument'] - new_name string @[json: 'newName'] + new_name string @[json: 'newName'] } // Optional JSON integers use i64 because V3 emits a generic option ABI for struct fields, while @@ -118,7 +118,7 @@ type ResponseResult = string struct WorkspaceSymbol { name string kind int - tags ?[]int @[json: 'tags'] + tags ?[]int @[json: 'tags'] container_name ?string @[json: 'containerName'] location Location } @@ -176,8 +176,8 @@ struct LSPDiagnostic { message string severity int source ?string @[json: 'source'] // diagnostic source identifier, e.g. 'vlang' - code ?string @[json: 'code'] // optional diagnostic code, e.g. 'unused_variable' - tags ?[]int @[json: 'tags'] // 1 = unnecessary, 2 = deprecated + code ?string @[json: 'code'] // optional diagnostic code, e.g. 'unused_variable' + tags ?[]int @[json: 'tags'] // 1 = unnecessary, 2 = deprecated } // LSPRange represents a range in a text document. @@ -188,17 +188,17 @@ struct LSPRange { // Detail represents a completion or symbol detail item. struct Detail { - kind int // The type of item (e.g., Method, Function, Field) + kind int // 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 declaration string // Full fn declaration, e.g. "fn greet(name string) string" documentation string // The documentation for the item - sort_text ?string @[json: 'sortText'] // sort key, defaults to label + sort_text ?string @[json: 'sortText'] // sort key, defaults to label filter_text ?string @[json: 'filterText'] // filter key, defaults to label insert_text ?string @[json: 'insertText'] - insert_text_format ?i64 @[json: 'insertTextFormat'] // 1 for PlainText, 2 for Snippet - tags ?[]int @[json: 'tags'] // 1 = deprecated - deprecated ?bool @[json: 'deprecated'] // legacy deprecated flag + insert_text_format ?i64 @[json: 'insertTextFormat'] // 1 for PlainText, 2 for Snippet + tags ?[]int @[json: 'tags'] // 1 = deprecated + deprecated ?bool @[json: 'deprecated'] // legacy deprecated flag } // Capabilities describes the server's capabilities. @@ -256,7 +256,7 @@ struct WorkspaceFoldersServerCapability { // WorkspaceCapability advertises workspace-level server features. struct WorkspaceCapability { - file_operations ?WorkspaceFileOperations @[json: 'fileOperations'] + file_operations ?WorkspaceFileOperations @[json: 'fileOperations'] workspace_folders ?WorkspaceFoldersServerCapability @[json: 'workspaceFolders'] } @@ -267,39 +267,39 @@ struct CodeLensOptions { // Capability lists supported LSP features for the server. struct Capability { - completion_provider CompletionProvider @[json: 'completionProvider'] - text_document_sync TextDocumentSyncOptions @[json: 'textDocumentSync'] - signature_help_provider SignatureHelpOptions @[json: 'signatureHelpProvider'] - definition_provider bool @[json: 'definitionProvider'] - declaration_provider bool @[json: 'declarationProvider'] - type_definition_provider bool @[json: 'typeDefinitionProvider'] - implementation_provider bool @[json: 'implementationProvider'] - hover_provider bool @[json: 'hoverProvider'] - references_provider bool @[json: 'referencesProvider'] - rename_provider RenameOptions @[json: 'renameProvider'] - execute_command_provider ?ExecuteCommandOptions @[json: 'executeCommandProvider'] - document_formatting_provider bool @[json: 'documentFormattingProvider'] - document_range_formatting_provider bool @[json: 'documentRangeFormattingProvider'] - document_symbol_provider bool @[json: 'documentSymbolProvider'] - workspace_symbol_provider bool @[json: 'workspaceSymbolProvider'] - inlay_hint_provider bool @[json: 'inlayHintProvider'] - code_action_provider bool @[json: 'codeActionProvider'] - code_lens_provider ?CodeLensOptions @[json: 'codeLensProvider'] - inline_value_provider bool @[json: 'inlineValueProvider'] - linked_editing_range_provider bool @[json: 'linkedEditingRangeProvider'] + completion_provider CompletionProvider @[json: 'completionProvider'] + text_document_sync TextDocumentSyncOptions @[json: 'textDocumentSync'] + signature_help_provider SignatureHelpOptions @[json: 'signatureHelpProvider'] + definition_provider bool @[json: 'definitionProvider'] + declaration_provider bool @[json: 'declarationProvider'] + type_definition_provider bool @[json: 'typeDefinitionProvider'] + implementation_provider bool @[json: 'implementationProvider'] + hover_provider bool @[json: 'hoverProvider'] + references_provider bool @[json: 'referencesProvider'] + rename_provider RenameOptions @[json: 'renameProvider'] + execute_command_provider ?ExecuteCommandOptions @[json: 'executeCommandProvider'] + document_formatting_provider bool @[json: 'documentFormattingProvider'] + document_range_formatting_provider bool @[json: 'documentRangeFormattingProvider'] + document_symbol_provider bool @[json: 'documentSymbolProvider'] + workspace_symbol_provider bool @[json: 'workspaceSymbolProvider'] + inlay_hint_provider bool @[json: 'inlayHintProvider'] + code_action_provider bool @[json: 'codeActionProvider'] + code_lens_provider ?CodeLensOptions @[json: 'codeLensProvider'] + inline_value_provider bool @[json: 'inlineValueProvider'] + linked_editing_range_provider bool @[json: 'linkedEditingRangeProvider'] on_type_formatting_provider ?OnTypeFormattingOptions @[json: 'documentOnTypeFormattingProvider'] - semantic_tokens_provider SemanticTokensOptions @[json: 'semanticTokensProvider'] - folding_range_provider bool @[json: 'foldingRangeProvider'] - call_hierarchy_provider bool @[json: 'callHierarchyProvider'] - document_highlight_provider bool @[json: 'documentHighlightProvider'] - selection_range_provider bool @[json: 'selectionRangeProvider'] + semantic_tokens_provider SemanticTokensOptions @[json: 'semanticTokensProvider'] + folding_range_provider bool @[json: 'foldingRangeProvider'] + call_hierarchy_provider bool @[json: 'callHierarchyProvider'] + document_highlight_provider bool @[json: 'documentHighlightProvider'] + selection_range_provider bool @[json: 'selectionRangeProvider'] workspace WorkspaceCapability position_encoding ?string @[json: 'positionEncoding'] } // OnTypeFormattingOptions describes the triggers for on-type formatting. struct OnTypeFormattingOptions { - first_trigger_character string @[json: 'firstTriggerCharacter'] + first_trigger_character string @[json: 'firstTriggerCharacter'] more_trigger_characters []string @[json: 'moreTriggerCharacters'] } @@ -676,8 +676,8 @@ struct OnTypeFormattingParams { // InitializeParams holds client startup parameters relevant to server workspace scope. struct InitializeParams { - root_uri ?string @[json: 'rootUri'] - root_path ?string @[json: 'rootPath'] + root_uri ?string @[json: 'rootUri'] + root_path ?string @[json: 'rootPath'] workspace_folders ?[]WorkspaceFolder @[json: 'workspaceFolders'] capabilities ?ClientCapabilities } @@ -706,7 +706,7 @@ struct SaveOptions { // TextDocumentSyncOptions describes document synchronization options. struct TextDocumentSyncOptions { open_close bool @[json: 'openClose'] - change int // 1 for Full, 2 for Incremental + change int // 1 for Full, 2 for Incremental save SaveOptions // emit {"includeText":true} to receive text in didSave will_save bool @[json: 'willSave'] will_save_wait_until bool @[json: 'willSaveWaitUntil'] @@ -785,7 +785,7 @@ const inlay_hint_kind_type = 1 struct InlayHint { position Position label string - kind int @[json: 'kind'] + kind int @[json: 'kind'] padding_left bool @[json: 'paddingLeft'] } @@ -964,7 +964,7 @@ struct DidOpenTextDocumentParams { // DidChangeTextDocumentParams for didChange struct DidChangeTextDocumentParams { text_document VersionedTextDocumentIdentifier @[json: 'textDocument'] - content_changes []ContentChange @[json: 'contentChanges'] + content_changes []ContentChange @[json: 'contentChanges'] } // DidCloseTextDocumentParams for didClose @@ -1007,7 +1007,7 @@ struct RenameParams { // FormattingOptions carries client formatting preferences (LSP §3.17). // VLS ignores these and always delegates to `v fmt`. struct FormattingOptions { - tab_size int @[json: 'tabSize'] + tab_size int @[json: 'tabSize'] insert_spaces bool @[json: 'insertSpaces'] } diff --git a/lsp_test.v b/lsp_test.v index 0949ec75..dbc5d5f4 100644 --- a/lsp_test.v +++ b/lsp_test.v @@ -242,9 +242,8 @@ fn test_method_roundtrip_all_values() { .workspace_did_change_configuration, .workspace_did_change_workspace_folders, .document_highlight, .selection_range, .semantic_tokens_range, .range_formatting, .will_save, .will_save_wait_until, .did_change_watched_files, .code_lens, .code_lens_resolve, - .execute_command, .inline_value, .linked_editing_range, .will_create_files, - .will_rename_files, .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, - .shutdown, .exit] + .execute_command, .inline_value, .linked_editing_range, .will_create_files, .will_rename_files, + .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, .shutdown, .exit] for m in methods { assert Method.from_string(m.str()) == m } @@ -472,7 +471,7 @@ fn test_lsp_range_same_line() { line: 5 char: 10 } - end: Position{ + end: Position{ line: 5 char: 20 } @@ -487,7 +486,7 @@ fn test_lsp_range_multi_line() { line: 5 char: 0 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -501,7 +500,7 @@ fn test_lsp_range_json_encoding() { line: 1 char: 2 } - end: Position{ + end: Position{ line: 3 char: 4 } @@ -593,8 +592,8 @@ fn test_request_default_values() { fn test_request_with_values() { req := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' } assert req.id == 1 @@ -644,7 +643,7 @@ fn test_request_json_decoding_initialize() { fn test_response_default_jsonrpc() { resp := Response{ - id: 1 + id: 1 result: 'null' } assert resp.jsonrpc == '2.0' @@ -652,7 +651,7 @@ fn test_response_default_jsonrpc() { fn test_response_json_encoding() { resp := Response{ - id: 42 + id: 42 result: 'null' } encoded := json2.encode(resp, escape_unicode: true) @@ -662,7 +661,7 @@ fn test_response_json_encoding() { fn test_encode_response_payload_uses_json_null_for_null_result() { resp := Response{ - id: 2 + id: 2 result: 'null' } encoded := encode_response_payload(resp) @@ -672,7 +671,7 @@ fn test_encode_response_payload_uses_json_null_for_null_result() { fn test_encode_response_payload_preserves_non_null_results() { resp := Response{ - id: 3 + id: 3 result: []TextEdit{} } encoded := encode_response_payload(resp) @@ -681,7 +680,7 @@ fn test_encode_response_payload_preserves_non_null_results() { fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { resp := Response{ - id: 4 + id: 4 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -695,14 +694,14 @@ fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { resp := Response{ - id: 5 + id: 5 result: PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 7 } @@ -717,15 +716,15 @@ fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { fn test_workspace_edit_closed_document_has_explicit_null_version() { resp := Response{ - id: 6 + id: 6 result: WorkspaceEdit{ document_changes: [TextDocumentEdit{ text_document: OptionalVersionedTextDocumentIdentifier{ - uri: 'file:///tmp/closed.v' + uri: 'file:///tmp/closed.v' version: json2.null } - edits: [TextEdit{ - range: LSPRange{} + edits: [TextEdit{ + range: LSPRange{} new_text: 'renamed' }] }] @@ -924,7 +923,7 @@ fn test_notification_json_encoding() { notif := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } } @@ -935,8 +934,8 @@ fn test_notification_json_encoding() { fn test_lsp_diagnostic_error_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'error message' + range: LSPRange{} + message: 'error message' severity: 1 } assert diag.severity == 1 // Error @@ -945,8 +944,8 @@ fn test_lsp_diagnostic_error_severity() { fn test_lsp_diagnostic_warning_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'warning message' + range: LSPRange{} + message: 'warning message' severity: 2 } assert diag.severity == 2 // Warning @@ -954,17 +953,17 @@ fn test_lsp_diagnostic_warning_severity() { fn test_lsp_diagnostic_json_encoding() { diag := LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 10 } } - message: 'undefined identifier' + message: 'undefined identifier' severity: 1 } encoded := json2.encode(diag, escape_unicode: true) @@ -974,9 +973,9 @@ fn test_lsp_diagnostic_json_encoding() { fn test_detail_function_kind() { detail := Detail{ - kind: 6 // Function - label: 'my_function' - detail: 'fn my_function() string' + kind: 6 // Function + label: 'my_function' + detail: 'fn my_function() string' documentation: 'A helper function' } assert detail.kind == 6 @@ -985,9 +984,9 @@ fn test_detail_function_kind() { fn test_detail_variable_kind() { detail := Detail{ - kind: 6 - label: 'my_var' - detail: 'int' + kind: 6 + label: 'my_var' + detail: 'int' documentation: 'A variable' } assert detail.label == 'my_var' @@ -995,10 +994,10 @@ fn test_detail_variable_kind() { fn test_detail_with_snippet() { detail := Detail{ - kind: 6 - label: 'println' - detail: 'fn println(s string)' - insert_text: 'println(\${1:s})' + kind: 6 + label: 'println' + detail: 'fn println(s string)' + insert_text: 'println(\${1:s})' insert_text_format: 2 // Snippet } assert detail.insert_text? == 'println(\${1:s})' @@ -1007,7 +1006,7 @@ fn test_detail_with_snippet() { fn test_detail_json_encoding() { detail := Detail{ - kind: 6 + kind: 6 label: 'test_fn' } encoded := json2.encode(detail, escape_unicode: true) @@ -1017,13 +1016,13 @@ fn test_detail_json_encoding() { fn test_location_basic() { loc := Location{ - uri: 'file:///test/file.v' + uri: 'file:///test/file.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1035,13 +1034,13 @@ fn test_location_basic() { fn test_location_json_encoding() { loc := Location{ - uri: 'file:///path/to/file.v' + uri: 'file:///path/to/file.v' range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } @@ -1061,9 +1060,9 @@ fn test_signature_help_empty() { fn test_signature_help_with_signature() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1084,7 +1083,7 @@ fn test_signature_help_with_signature() { fn test_signature_help_json_encoding() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ label: 'fn example()' }, @@ -1100,17 +1099,17 @@ fn test_signature_help_json_encoding() { fn test_capabilities_full() { caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1147,7 +1146,7 @@ fn test_completion_item_capability_snippet_support() { fn test_text_document_sync_full() { sync := TextDocumentSyncOptions{ open_close: true - change: 1 // Full + change: 1 // Full } assert sync.open_close == true assert sync.change == 1 @@ -1156,7 +1155,7 @@ fn test_text_document_sync_full() { fn test_text_document_sync_incremental() { sync := TextDocumentSyncOptions{ open_close: true - change: 2 // Incremental + change: 2 // Incremental } assert sync.change == 2 } @@ -1181,7 +1180,7 @@ fn test_response_result_string() { fn test_response_result_details() { details := [ Detail{ - kind: 6 + kind: 6 label: 'test' }, ] @@ -1234,7 +1233,7 @@ fn test_response_result_location() { fn test_publish_diagnostics_params_empty() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } assert params.uri == 'file:///test.v' @@ -1243,16 +1242,16 @@ fn test_publish_diagnostics_params_empty() { fn test_publish_diagnostics_params_with_diagnostics() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{} - message: 'error 1' + range: LSPRange{} + message: 'error 1' severity: 1 }, LSPDiagnostic{ - range: LSPRange{} - message: 'error 2' + range: LSPRange{} + message: 'error 2' severity: 1 }, ] @@ -1262,11 +1261,11 @@ fn test_publish_diagnostics_params_with_diagnostics() { fn test_json_error_struct() { err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'undefined identifier' line_nr: 10 - col: 5 - len: 3 + col: 5 + len: 3 } assert err.path == '/test/file.v' assert err.message == 'undefined identifier' @@ -1305,11 +1304,11 @@ fn test_json_var_ac_with_details() { ac := JsonVarAC{ details: [ Detail{ - kind: 6 + kind: 6 label: 'fn1' }, Detail{ - kind: 6 + kind: 6 label: 'fn2' }, ] @@ -1340,14 +1339,14 @@ fn test_document_symbol_default_values() { fn test_document_symbol_with_values() { sym := DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 20 } @@ -1357,12 +1356,12 @@ fn test_document_symbol_with_values() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } assert sym.name == 'greet' assert sym.kind == sym_kind_function @@ -1372,14 +1371,14 @@ fn test_document_symbol_with_values() { fn test_document_symbol_json_encoding() { sym := DocumentSymbol{ - name: 'Person' - kind: sym_kind_struct - range: LSPRange{ + name: 'Person' + kind: sym_kind_struct + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 14 } @@ -1389,12 +1388,12 @@ fn test_document_symbol_json_encoding() { line: 5 char: 7 } - end: Position{ + end: Position{ line: 5 char: 13 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } encoded := json2.encode(sym, escape_unicode: true) assert encoded.contains('"name":"Person"') @@ -1417,17 +1416,17 @@ fn test_document_symbol_json_decoding() { fn test_document_symbol_with_children() { sym := DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: [ + children: [ DocumentSymbol{ - name: 'run' - kind: sym_kind_method - range: LSPRange{} + name: 'run' + kind: sym_kind_method + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] } @@ -1508,10 +1507,10 @@ fn test_method_roundtrip_new_methods() { fn test_response_result_workspace_symbols() { result := ResponseResult([ WorkspaceSymbol{ - name: 'main' - kind: sym_kind_function + name: 'main' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/main.v' + uri: 'file:///tmp/main.v' range: LSPRange{} } }, @@ -1526,12 +1525,12 @@ fn test_response_result_workspace_symbols() { fn test_response_result_prepare_rename_result() { result := ResponseResult(PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 5 } @@ -1559,18 +1558,18 @@ fn test_response_result_document_symbols_empty() { fn test_response_result_document_symbols_with_data() { syms := [ DocumentSymbol{ - name: 'main' - kind: sym_kind_function - range: LSPRange{} + name: 'main' + kind: sym_kind_function + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] result := ResponseResult(syms) @@ -1588,14 +1587,14 @@ fn test_response_result_document_symbols_with_data() { fn test_response_with_document_symbols_json_encoding() { syms := [ DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 25 } @@ -1605,16 +1604,16 @@ fn test_response_with_document_symbols_json_encoding() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] resp := Response{ - id: 7 + id: 7 result: syms } encoded := json2.encode(resp, escape_unicode: true) @@ -1640,7 +1639,7 @@ fn test_capability_document_symbol_provider_json_encoding() { caps := Capabilities{ capabilities: Capability{ document_symbol_provider: true - definition_provider: true + definition_provider: true } } encoded := json2.encode(caps, escape_unicode: true) @@ -1802,13 +1801,13 @@ fn test_encode_response_payload_strips_type_from_array_variant() { // Array-of-struct result variants (e.g. []WorkspaceSymbol) must also have // their per-element `_type` discriminators stripped (P1-10). resp := Response{ - id: 9 + id: 9 result: [ WorkspaceSymbol{ - name: 'helper_fn' - kind: sym_kind_function + name: 'helper_fn' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/lib.v' + uri: 'file:///tmp/lib.v' range: LSPRange{} } }, diff --git a/main.v b/main.v index 23ab684e..c5bb4fe5 100644 --- a/main.v +++ b/main.v @@ -12,47 +12,47 @@ import io // App represents the context of the server during its lifetime. pub struct App { cur_mod string = 'main' - exit bool = os.args.contains('exit') + exit bool = os.args.contains('exit') mut: - text string // Current file content + text string // Current file content open_files map[string]string // Map of file URI to file content - open_files_versions map[string]i64 // Per-URI document version from the client - temp_dir string // Temporary directory for multi-file compilation - workspace_roots []string // Workspace root directories from initialize - removed_workspace_roots []string // Roots explicitly removed by the client - capture_output bool // Test hook: capture outbound transport messages instead of writing - captured_output []string // Test hook buffer for outbound transport messages - supports_dynamic_watched_files_registration bool // Client supports dynamic workspace watcher registration - supports_work_done_progress bool // Client supports window/workDoneProgress + $/progress - sent_watched_files_registration bool // client/registerCapability watcher registration was sent - watched_files_registration_id string // Raw id of the watcher registration request, to match its response - watched_files_active bool // True once the client acknowledged watcher registration (not rejected) + open_files_versions map[string]i64 // Per-URI document version from the client + temp_dir string // Temporary directory for multi-file compilation + workspace_roots []string // Workspace root directories from initialize + removed_workspace_roots []string // Roots explicitly removed by the client + capture_output bool // Test hook: capture outbound transport messages instead of writing + captured_output []string // Test hook buffer for outbound transport messages + supports_dynamic_watched_files_registration bool // Client supports dynamic workspace watcher registration + supports_work_done_progress bool // Client supports window/workDoneProgress + $/progress + sent_watched_files_registration bool // client/registerCapability watcher registration was sent + watched_files_registration_id string // Raw id of the watcher registration request, to match its response + watched_files_active bool // True once the client acknowledged watcher registration (not rejected) inlay_hints_enabled bool = true // toggled via workspace/didChangeConfiguration diagnostics_enabled bool = true // toggled via workspace/didChangeConfiguration diag_cache map[string]DiagCacheEntry // Per-URI cached diagnostics - open_files_generation int // Incremented on every workspace file mutation - project_generations map[string]int // Per-project-dir revision, for scoped cache invalidation - cancelled_requests map[int]bool // Request ids cancelled via $/cancelRequest - cancelled_raw_ids map[string]bool // String/raw request ids cancelled via $/cancelRequest - current_request_raw_id string // Raw JSON id of the request being processed (echoed verbatim) + open_files_generation int // Incremented on every workspace file mutation + project_generations map[string]int // Per-project-dir revision, for scoped cache invalidation + cancelled_requests map[int]bool // Request ids cancelled via $/cancelRequest + cancelled_raw_ids map[string]bool // String/raw request ids cancelled via $/cancelRequest + current_request_raw_id string // Raw JSON id of the request being processed (echoed verbatim) position_encoding PositionEncoding = .utf16 // Negotiated LSP position encoding (default UTF-16) - symbol_index map[string]IndexEntry // Persistent per-URI symbol index (see index.v) - indexed_dirs map[string]bool // Project dirs already walked into the index - indexed_dir_walk_ms map[string]i64 // Last walk time per dir, for watcher-less refresh - ref_occurrences map[string]OccEntry // Per-URI identifier occurrences for references (see index.v) - index_skipped_uris map[string]bool // Disk files omitted from the bounded index - index_incomplete_scopes map[string]bool // Index walks that could not finish + symbol_index map[string]IndexEntry // Persistent per-URI symbol index (see index.v) + indexed_dirs map[string]bool // Project dirs already walked into the index + indexed_dir_walk_ms map[string]i64 // Last walk time per dir, for watcher-less refresh + ref_occurrences map[string]OccEntry // Per-URI identifier occurrences for references (see index.v) + index_skipped_uris map[string]bool // Disk files omitted from the bounded index + index_incomplete_scopes map[string]bool // Index walks that could not finish vlib_fn_cache map[string]map[string]string // Per-vlib-module fn→return-type index (immutable during a session) - expression_type_depth int // Nesting of expression_type, which a binding's declaration re-enters - line_info_mode LineInfoMode // How the configured `v` reaches the `-line-info` checker (see interop.v) + expression_type_depth int // Nesting of expression_type, which a binding's declaration re-enters + line_info_mode LineInfoMode // How the configured `v` reaches the `-line-info` checker (see interop.v) tcp_conn ?&net.TcpConn // Non-nil when serving a TCP client - is_shutdown bool // True after shutdown request was acknowledged - exit_was_requested bool // True when the exit notification was received - received_initialize bool // True after initialize request was processed + is_shutdown bool // True after shutdown request was acknowledged + exit_was_requested bool // True when the exit notification was received + received_initialize bool // True after initialize request was processed next_request_id int = 1 // Counter for server-initiated request ids diagnostics_scheduler ?&DiagnosticsScheduler // Production-only async diagnostics - run_command_manager ?&RunCommandManager // Async code-lens process lifecycle - execute_commands_synchronously bool // Test hook for deterministic command assertions + run_command_manager ?&RunCommandManager // Async code-lens process lifecycle + execute_commands_synchronously bool // Test hook for deterministic command assertions write_mutex &sync.Mutex = sync.new_mutex() // Serializes worker and request-loop writes } @@ -203,7 +203,7 @@ fn (mut reader StdinBufferedReader) read_line(config io.BufferedReadLineConfig) fn new_stdin_buffered_reader_for_fd(fd int, cap int) &StdinBufferedReader { return &StdinBufferedReader{ - fd: fd + fd: fd buf: []u8{len: cap} } } @@ -254,9 +254,9 @@ fn main() { return } mut app := &App{ - text: '' - open_files: map[string]string{} - temp_dir: temp_dir + text: '' + open_files: map[string]string{} + temp_dir: temp_dir diagnostics_scheduler: new_diagnostics_scheduler() } // os.File.read uses C fread, which waits for the entire buffer on an open @@ -330,10 +330,10 @@ fn handle_tcp_client(mut conn net.TcpConn) { return } mut app := &App{ - text: '' - open_files: map[string]string{} - temp_dir: temp_dir - tcp_conn: &conn + text: '' + open_files: map[string]string{} + temp_dir: temp_dir + tcp_conn: &conn diagnostics_scheduler: new_diagnostics_scheduler() } mut reader := io.new_buffered_reader(reader: conn, cap: transport_buffer_cap) @@ -617,10 +617,10 @@ fn (mut app App) handle_requests[T](mut reader T) { continue } lsp_request := Request{ - id: raw_id_to_int(app.current_request_raw_id) - method: body.method + id: raw_id_to_int(app.current_request_raw_id) + method: body.method jsonrpc: body.jsonrpc - params: body.params + params: body.params } log('\n\nRECV (pretty): ${content}') method := Method.from_string(lsp_request.method) @@ -670,7 +670,8 @@ fn (mut app App) handle_requests[T](mut reader T) { } } match method { - .completion, .signature_help, .definition, .hover, .declaration, .type_definition, .implementation { + .completion, .signature_help, .definition, .hover, .declaration, .type_definition, + .implementation { resp := app.operation_at_pos(method, lsp_request) app.write_response_or_cancelled(lsp_request.id, resp) } @@ -718,7 +719,7 @@ fn (mut app App) handle_requests[T](mut reader T) { } // Return all supported capabilities, matching the LSP spec and what is implemented. response := Response{ - id: lsp_request.id + id: lsp_request.id result: Capabilities{ capabilities: Capability{ // NOTE: Placeholder/stub capabilities are intentionally NOT @@ -727,66 +728,66 @@ fn (mut app App) handle_requests[T](mut reader T) { // (wrong abstraction), file-operation hooks (no-ops), and // willSave (never dispatched). Advertising only working // features gives a better editor experience than broken UI. - text_document_sync: TextDocumentSyncOptions{ - open_close: true - change: 2 // Incremental - save: SaveOptions{ + text_document_sync: TextDocumentSyncOptions{ + open_close: true + change: 2 // Incremental + save: SaveOptions{ include_text: true } - will_save: false + will_save: false will_save_wait_until: true } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } - signature_help_provider: SignatureHelpOptions{ + signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true - declaration_provider: true - type_definition_provider: true - implementation_provider: true - hover_provider: true - references_provider: true - rename_provider: RenameOptions{ + definition_provider: true + declaration_provider: true + type_definition_provider: true + implementation_provider: true + hover_provider: true + references_provider: true + rename_provider: RenameOptions{ prepare_provider: true } - document_formatting_provider: true - document_symbol_provider: true - workspace_symbol_provider: true - inlay_hint_provider: true - code_action_provider: true - execute_command_provider: ExecuteCommandOptions{ + document_formatting_provider: true + document_symbol_provider: true + workspace_symbol_provider: true + inlay_hint_provider: true + code_action_provider: true + execute_command_provider: ExecuteCommandOptions{ commands: ['vls.runFile', 'vls.runTests'] } - code_lens_provider: CodeLensOptions{} - semantic_tokens_provider: SemanticTokensOptions{ + code_lens_provider: CodeLensOptions{} + semantic_tokens_provider: SemanticTokensOptions{ legend: SemanticTokensLegend{ - token_types: semantic_token_types() + token_types: semantic_token_types() token_modifiers: semantic_token_modifiers() } - full: true - range: true + full: true + range: true } - folding_range_provider: true - call_hierarchy_provider: true - document_highlight_provider: true - selection_range_provider: true + folding_range_provider: true + call_hierarchy_provider: true + document_highlight_provider: true + selection_range_provider: true // Range formatting is NOT advertised: v fmt only formats whole // files, so a correct range implementation needs a // character-accurate, EOL-preserving diff restricted to the // requested range, which is not yet implemented (P0-08). document_range_formatting_provider: false - position_encoding: position_encoding_string(app.position_encoding) - workspace: WorkspaceCapability{ + position_encoding: position_encoding_string(app.position_encoding) + workspace: WorkspaceCapability{ workspace_folders: WorkspaceFoldersServerCapability{ - supported: true + supported: true change_notifications: true } } } - server_info: ServerInfo{ - name: 'vls' + server_info: ServerInfo{ + name: 'vls' version: '0.0.2' } } @@ -816,7 +817,7 @@ fn (mut app App) handle_requests[T](mut reader T) { app.write_notification(Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: params.text_document.uri + uri: params.text_document.uri diagnostics: [] } }) @@ -920,7 +921,7 @@ fn (mut app App) handle_requests[T](mut reader T) { .will_create_files, .will_rename_files, .will_delete_files { // Return null — vls has no pre-operation file mutations to apply. app.write_response(Response{ - id: lsp_request.id + id: lsp_request.id result: 'null' }) } @@ -1312,7 +1313,7 @@ fn (mut app App) accept_shutdown(id int) { app.stop_run_commands() app.is_shutdown = true app.write_response(Response{ - id: id + id: id result: 'null' }) } @@ -1354,17 +1355,17 @@ fn v_error_to_lsp_diagnostic(e JsonError) LSPDiagnostic { code, tags := derive_diagnostic_code_and_tags(e.message) return LSPDiagnostic{ - message: e.message + message: e.message severity: severity - source: 'vlang' - code: code - tags: tags - range: LSPRange{ + source: 'vlang' + code: code + tags: tags + range: LSPRange{ start: Position{ line: start_line char: start_char } - end: Position{ + end: Position{ line: start_line char: end_char } @@ -1399,7 +1400,14 @@ fn derive_diagnostic_code_and_tags(message string) (?string, ?[]int) { fn method_requires_response(method Method) bool { return match method { - .initialize, .completion, .signature_help, .definition, .hover, .declaration, .type_definition, .implementation, .references, .rename, .prepare_rename, .workspace_symbol, .formatting, .document_symbols, .inlay_hint, .shutdown, .code_action, .semantic_tokens, .folding_range, .callhierarchy_prepare, .callhierarchy_incoming, .callhierarchy_outgoing, .document_highlight, .selection_range, .semantic_tokens_range, .range_formatting, .code_lens, .code_lens_resolve, .execute_command, .inline_value, .linked_editing_range, .will_create_files, .will_rename_files, .will_delete_files, .on_type_formatting { + .initialize, .completion, .signature_help, .definition, .hover, .declaration, + .type_definition, .implementation, .references, .rename, .prepare_rename, + .workspace_symbol, .formatting, .document_symbols, .inlay_hint, .shutdown, .code_action, + .semantic_tokens, .folding_range, .callhierarchy_prepare, .callhierarchy_incoming, + .callhierarchy_outgoing, .document_highlight, .selection_range, .semantic_tokens_range, + .range_formatting, .code_lens, .code_lens_resolve, .execute_command, .inline_value, + .linked_editing_range, .will_create_files, .will_rename_files, .will_delete_files, + .on_type_formatting { true } else { @@ -1414,7 +1422,9 @@ fn method_requires_response(method Method) bool { // regardless of whether a client mistakenly attaches an id. fn method_is_notification_only(method Method) bool { return match method { - .initialized, .did_open, .did_change, .did_close, .did_save, .did_change_watched_files, .workspace_did_change_configuration, .workspace_did_change_workspace_folders, .set_trace, .cancel_request, .will_save { + .initialized, .did_open, .did_change, .did_close, .did_save, .did_change_watched_files, + .workspace_did_change_configuration, .workspace_did_change_workspace_folders, .set_trace, + .cancel_request, .will_save { true } else { @@ -1437,9 +1447,9 @@ fn (app &App) request_is_cancelled(id int) bool { fn make_invalid_request_error_response(id int, message string) ErrorResponse { msg := if message != '' { message } else { 'Invalid request' } return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_invalid_request + code: jsonrpc_err_invalid_request message: msg } } @@ -1447,9 +1457,9 @@ fn make_invalid_request_error_response(id int, message string) ErrorResponse { fn make_server_not_initialized_error_response(id int) ErrorResponse { return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_server_not_initialized + code: jsonrpc_err_server_not_initialized message: 'Server not yet initialized' } } @@ -1457,9 +1467,9 @@ fn make_server_not_initialized_error_response(id int) ErrorResponse { fn make_server_already_initialized_error_response(id int) ErrorResponse { return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_invalid_request + code: jsonrpc_err_invalid_request message: 'Server already initialized' } } @@ -1467,9 +1477,9 @@ fn make_server_already_initialized_error_response(id int) ErrorResponse { fn make_server_shutdown_error_response(id int) ErrorResponse { return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_invalid_request + code: jsonrpc_err_invalid_request message: 'Server has been shut down' } } @@ -1477,9 +1487,9 @@ fn make_server_shutdown_error_response(id int) ErrorResponse { fn make_cancelled_error_response(id int) ErrorResponse { return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_request_cancelled + code: jsonrpc_err_request_cancelled message: 'Request cancelled' } } @@ -1488,9 +1498,9 @@ fn make_cancelled_error_response(id int) ErrorResponse { fn make_parse_error_response(message string) ErrorResponse { msg := if message != '' { message } else { 'Invalid JSON' } return ErrorResponse{ - id: 0 + id: 0 error: ResponseError{ - code: jsonrpc_err_parse_error + code: jsonrpc_err_parse_error message: msg } } @@ -1498,9 +1508,9 @@ fn make_parse_error_response(message string) ErrorResponse { fn make_method_not_found_error_response(id int, method string) ErrorResponse { return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_method_not_found + code: jsonrpc_err_method_not_found message: 'Method not found: ${method}' } } @@ -1509,9 +1519,9 @@ fn make_method_not_found_error_response(id int, method string) ErrorResponse { fn make_invalid_params_error_response(id int, message string) ErrorResponse { msg := if message != '' { message } else { 'Invalid params' } return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_invalid_params + code: jsonrpc_err_invalid_params message: msg } } @@ -1520,9 +1530,9 @@ fn make_invalid_params_error_response(id int, message string) ErrorResponse { fn make_internal_error_response(id int, message string) ErrorResponse { msg := if message != '' { message } else { 'Internal error' } return ErrorResponse{ - id: id + id: id error: ResponseError{ - code: jsonrpc_err_internal_error + code: jsonrpc_err_internal_error message: msg } } @@ -1542,7 +1552,8 @@ fn validate_request_params(method Method, params_json string) ?string { } } } - .completion, .signature_help, .definition, .hover, .declaration, .type_definition, .implementation, .prepare_rename, .document_highlight { + .completion, .signature_help, .definition, .hover, .declaration, .type_definition, + .implementation, .prepare_rename, .document_highlight { params := json2.decode[TextDocumentPositionParams](params_json) or { return 'Invalid textDocument/position params: ${err.msg()}' } @@ -1805,7 +1816,7 @@ fn (mut app App) write_raw_request(id int, method string, params_json string) { // level: 1=Error, 2=Warning, 3=Info, 4=Log. fn (mut app App) send_show_message(msg string, level int) { params := ShowMessageParams{ - type_: level + type_: level message: msg } app.write_raw_notification('window/showMessage', json2.encode(params, escape_unicode: true)) @@ -1815,7 +1826,7 @@ fn (mut app App) send_show_message(msg string, level int) { // level: 1=Error, 2=Warning, 3=Info, 4=Log. fn (mut app App) send_log_message(msg string, level int) { params := LogMessageParams{ - type_: level + type_: level message: msg } app.write_raw_notification('window/logMessage', json2.encode(params, escape_unicode: true)) @@ -1853,7 +1864,7 @@ fn (mut app App) report_progress(token string, message string, percentage int) { return } report := WorkDoneProgressReport{ - message: message + message: message percentage: percentage } progress_json := '{"token":"${token}","value":${json2.encode(report, escape_unicode: true)}}' @@ -1885,12 +1896,12 @@ fn (mut app App) on_initialized(_ Request) { } reg_id := app.next_request_id reg := RegisterCapabilityRequest{ - id: reg_id + id: reg_id params: WatcherRegistrationParams{ registrations: [ WatcherRegistration{ - id: 'vls-file-watcher' - method: 'workspace/didChangeWatchedFiles' + id: 'vls-file-watcher' + method: 'workspace/didChangeWatchedFiles' register_options: WatcherRegisterOptions{ watchers: [ FileSystemWatcher{ diff --git a/run_command.v b/run_command.v index 1acde127..a4c5905b 100644 --- a/run_command.v +++ b/run_command.v @@ -365,8 +365,8 @@ mut: fn new_run_command_manager() &RunCommandManager { return &RunCommandManager{ - workers: sync.new_waitgroup() - processes: map[u64]&os.Process{} + workers: sync.new_waitgroup() + processes: map[u64]&os.Process{} active_targets: map[string]u64{} } } @@ -506,7 +506,7 @@ fn run_managed_process(mut manager RunCommandManager, id u64, target string, exe return ManagedRunResult{ result: os.Result{ exit_code: 1 - output: 'Working dir does not exist: ${work_folder}' + output: 'Working dir does not exist: ${work_folder}' } } } @@ -548,9 +548,9 @@ fn run_managed_process(mut manager RunCommandManager, id u64, target string, exe exit_code := process.code process.close() return ManagedRunResult{ - result: os.Result{ + result: os.Result{ exit_code: exit_code - output: output.str() + output: output.str() } cancelled: !registered || manager.job_is_cancelled(id, target) } @@ -598,11 +598,11 @@ fn run_code_lens_job(mut manager RunCommandManager, id u64, target string, } temp_dir := os.join_path(os.temp_dir(), 'vls_run_${os.getpid()}_${id}_${time.now().unix_nano()}') mut worker := App{ - open_files: job.open_files - temp_dir: temp_dir + open_files: job.open_files + temp_dir: temp_dir capture_output: job.capture_output - write_mutex: job.write_mutex - tcp_conn: job.tcp_conn + write_mutex: job.write_mutex + tcp_conn: job.tcp_conn } os.mkdir_all(temp_dir) or { worker.send_show_message('vls: ${job.title} could not create a temporary directory: ${err}', 1) diff --git a/semantic_tokens.v b/semantic_tokens.v index ace6b3e1..28ae36c6 100644 --- a/semantic_tokens.v +++ b/semantic_tokens.v @@ -119,9 +119,9 @@ fn tokenize_v_line(line string, line_idx int, mut state TokenizeState, mut token } if col > start { tokens << SemToken{ - line: line_idx - start: start - length: col - start + line: line_idx + start: start + length: col - start type_idx: sem_tok_comment } } @@ -163,9 +163,9 @@ fn tokenize_v_code(line string, from int, to int, line_idx int, mut state Tokeni state.in_block_comment = true } tokens << SemToken{ - line: line_idx - start: start - length: col - start + line: line_idx + start: start + length: col - start type_idx: sem_tok_comment } continue @@ -174,9 +174,9 @@ fn tokenize_v_code(line string, from int, to int, line_idx int, mut state Tokeni // Line comment: // … if col + 1 < n && c == `/` && line[col + 1] == `/` { tokens << SemToken{ - line: line_idx - start: col - length: n - col + line: line_idx + start: col + length: n - col type_idx: sem_tok_comment } return @@ -200,9 +200,9 @@ fn tokenize_v_code(line string, from int, to int, line_idx int, mut state Tokeni col++ } tokens << SemToken{ - line: line_idx - start: start - length: col - start + line: line_idx + start: start + length: col - start type_idx: sem_tok_string } continue @@ -226,9 +226,9 @@ fn tokenize_v_code(line string, from int, to int, line_idx int, mut state Tokeni } } tokens << SemToken{ - line: line_idx - start: start - length: col - start + line: line_idx + start: start + length: col - start type_idx: sem_tok_number } continue @@ -249,13 +249,13 @@ fn tokenize_v_code(line string, from int, to int, line_idx int, mut state Tokeni tok_type := classify_v_identifier_at(line, start, col, word, state.import_aliases) if tok_type >= 0 { tokens << SemToken{ - line: line_idx - start: start - length: col - start + line: line_idx + start: start + length: col - start type_idx: tok_type mod_bits: if tok_type == sem_tok_variable && (variable_binding_key(state.variable_scope, word) in state.readonly_variables - || variable_binding_key(0, word) in state.readonly_variables) { + || variable_binding_key(0, word) in state.readonly_variables) { sem_mod_readonly } else { 0 @@ -505,9 +505,9 @@ fn tokenize_v_string(line string, start int, to int, line_idx int, mut state Tok fn append_string_token(mut tokens []SemToken, line_idx int, start int, end int) { if end > start { tokens << SemToken{ - line: line_idx - start: start - length: end - start + line: line_idx + start: start + length: end - start type_idx: sem_tok_string } } @@ -579,7 +579,7 @@ fn convert_tokens_to_encoding(tokens []SemToken, lines []string, enc PositionEnc enc_end := byte_to_encoded_col(line, tok.start + tok.length, enc) out << SemToken{ ...tok - start: enc_start + start: enc_start length: enc_end - enc_start } } @@ -615,7 +615,7 @@ fn (mut app App) handle_semantic_tokens(request Request) Response { log('Failed to decode SemanticTokensParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -624,7 +624,7 @@ fn (mut app App) handle_semantic_tokens(request Request) Response { if content == '' { // An empty document has an empty token set, not a null result (P2-01). return Response{ - id: request.id + id: request.id result: SemanticTokens{ data: [] } @@ -634,7 +634,7 @@ fn (mut app App) handle_semantic_tokens(request Request) Response { raw_tokens := convert_tokens_to_encoding(tokenize_v_source(content), lines, app.position_encoding) encoded := encode_semantic_tokens(raw_tokens) return Response{ - id: request.id + id: request.id result: SemanticTokens{ data: encoded } @@ -650,7 +650,7 @@ fn (mut app App) handle_semantic_tokens_range(request Request) Response { log('Failed to decode SemanticTokensRangeParams: ${err}') } return Response{ - id: request.id + id: request.id result: 'null' } } @@ -658,7 +658,7 @@ fn (mut app App) handle_semantic_tokens_range(request Request) Response { content := app.open_files[uri] or { os.read_file(uri_to_path(uri)) or { '' } } if content == '' { return Response{ - id: request.id + id: request.id result: SemanticTokens{ data: [] } @@ -679,7 +679,7 @@ fn (mut app App) handle_semantic_tokens_range(request Request) Response { && (it.line < end_line || (it.line == end_line && it.start < end_char))) encoded := encode_semantic_tokens(range_tokens) return Response{ - id: request.id + id: request.id result: SemanticTokens{ data: encoded } From 9f1572184e165920635b93b01d5214f2a730479b Mon Sep 17 00:00:00 2001 From: metif12 Date: Sun, 4 Oct 2026 09:58:37 +0330 Subject: [PATCH 4/6] cgen: keep side effects out of `assert` conditions in stdio tests `-prod` removes assert statements whole, as documented in doc/docs.md and implemented in `vlib/v/gen/c/stmt.v`: ```v .assert_stmt { if g.is_prod { return } } ``` Two tests here used a side-effecting call *as* the assertion's condition: ```v assert os.fd_dup2(transport.read_fd, 0) >= 0 ``` That dup2 is the operation the test depends on, not a check on it. Under `-prod` the statement disappears, fd 0 keeps pointing at the real stdin, and the read that follows blocks on a descriptor nobody is going to write to. So: - `v -prod test lsp_test.v` never terminated (>40 min for a file that takes 25s) - `v -prod test integration_test.v` likewise Perform the call outside the assert, matching how the neighbouring code already does it (`os.fd_close(transport.write_fd)` a few lines above has no check either). V's behaviour here is correct and is not changed by this commit; the defect was in the tests. Verified on Windows with V 0137eb5: - `v -prod test lsp_test.v` -> OK, 53s (was hanging) - `v -prod test integration_test.v` -> OK, 62s (was hanging) - `v -prod test .` -> completes in 76s, 4/6; the two failures are the unrelated `index_max_file_bytes` compile error that #526 fixes. Applying that fix on top gives `6 passed, 6 total` under `-prod`. - Normal builds unchanged: `v test lsp_test.v` OK, and `integration_test.v` fails only on the pre-existing Sublime Text handshake bug that #527 fixes. Reported upstream as vlang/v#29426, where the `-prod` codegen evidence is included. That issue is closed as not-a-bug: the behaviour is documented and matches C's `assert` under `NDEBUG`. --- integration_test.v | 264 +++++++++++++++++++++++---------------------- lsp_test.v | 223 +++++++++++++++++++------------------- 2 files changed, 247 insertions(+), 240 deletions(-) diff --git a/integration_test.v b/integration_test.v index d3410bdf..9535ca54 100644 --- a/integration_test.v +++ b/integration_test.v @@ -59,9 +59,9 @@ fn create_integration_test_env() (&App, string) { integration_test_must_mkdir_all(project_dir) app := &App{ - text: '' + text: '' open_files: map[string]string{} - temp_dir: temp_dir + temp_dir: temp_dir } return app, project_dir } @@ -115,7 +115,9 @@ fn test_integration_stdio_initialize_completion_and_hover() { } os.fd_close(transport.write_fd) transport.write_fd = -1 - assert os.fd_dup2(transport.read_fd, 0) >= 0 + // Keep this outside `assert`: `-prod` removes assert statements whole, so a dup2 + // hidden in one never runs and the read below waits on the real stdin forever. + os.fd_dup2(transport.read_fd, 0) mut capture := os.stdio_capture() or { assert false, 'failed to capture stdio test output: ${err}' @@ -165,20 +167,20 @@ fn test_integration_stdio_initialize_completion_and_hover() { fn test_integration_initialize_capabilities() { // Simulate what the server returns for initialize response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } } @@ -199,7 +201,7 @@ fn test_integration_initialize_capabilities() { fn test_integration_initialize_response_structure() { // Verify response has proper JSON-RPC structure response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -217,7 +219,7 @@ fn test_integration_initialize_response_structure() { fn test_integration_initialize_does_not_advertise_client_snippet_support() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ completion_provider: CompletionProvider{ @@ -233,14 +235,14 @@ fn test_integration_initialize_does_not_advertise_client_snippet_support() { fn test_integration_initialize_workspace_capabilities() { response := Response{ - id: 0 + id: 0 result: Capabilities{ capabilities: Capability{ execute_command_provider: ExecuteCommandOptions{ commands: ['vls.runFile', 'vls.runTests'] } - workspace: WorkspaceCapability{ - file_operations: WorkspaceFileOperations{ + workspace: WorkspaceCapability{ + file_operations: WorkspaceFileOperations{ will_create: FileOperationRegistrationOptions{ filters: [ FileOperationFilter{ @@ -270,7 +272,7 @@ fn test_integration_initialize_workspace_capabilities() { } } workspace_folders: WorkspaceFoldersServerCapability{ - supported: true + supported: true change_notifications: true } } @@ -301,10 +303,10 @@ fn test_integration_document_lifecycle() { // 1. Open document open_request := Request{ - id: 1 - method: 'textDocument/didOpen' + id: 1 + method: 'textDocument/didOpen' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } @@ -320,11 +322,11 @@ fn test_integration_document_lifecycle() { // 2. Change document new_content := "module main\n\nfn main() {\n\tprintln('world')\n}\n" change_request := Request{ - id: 2 - method: 'textDocument/didChange' + id: 2 + method: 'textDocument/didChange' jsonrpc: '2.0' - params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + params: json2.encode(Params{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -539,7 +541,7 @@ fn test_integration_diagnostics_syntax_error() { // Trigger change to get diagnostics change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -585,7 +587,7 @@ fn test_integration_diagnostics_valid_code() { change_request := Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -639,17 +641,17 @@ fn test_integration_diagnostics_deduplication() { errors := [ JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'first error' }, JsonError{ line_nr: 5 - col: 10 + col: 10 message: 'duplicate error' }, // Same position JsonError{ line_nr: 6 - col: 1 + col: 1 message: 'different position' }, ] @@ -691,7 +693,7 @@ fn test_integration_diagnostics_empty_file() { // Empty content should be processed and return diagnostics for the empty file result := app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -736,14 +738,14 @@ fn test_integration_completion_request() { // Request completion at the position after "os." request := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 4 } // After "os." @@ -774,12 +776,12 @@ fn test_integration_completion_request_id_preserved() { // Test with various IDs for id in [1, 42, 100, 999] { request := Request{ - id: id + id: id params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -807,12 +809,12 @@ fn test_integration_completion_at_function_call() { app.open_files[uri] = content request := Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 3 char: 9 } @@ -851,14 +853,14 @@ fn test_integration_definition_request() { // Request definition at the call site of helper() request := Request{ - id: 2 - method: 'textDocument/definition' + id: 2 + method: 'textDocument/definition' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } // At "helper()" @@ -920,12 +922,12 @@ fn test_integration_definition_multifile() { // Request definition from main file request := Request{ - id: 3 + id: 3 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -973,13 +975,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { app.text = open_content definition := app.operation_at_pos(.definition, Request{ - id: 31 + id: 31 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -993,13 +995,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert definition_location.range.start.line == 2 hover := app.operation_at_pos(.hover, Request{ - id: 32 + id: 32 method: 'textDocument/hover' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 20 } @@ -1011,13 +1013,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert (hover.result as Hover).contents.value.contains('answer') signature := app.operation_at_pos(.signature_help, Request{ - id: 33 + id: 33 method: 'textDocument/signatureHelp' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 25 } @@ -1030,13 +1032,13 @@ fn test_integration_cross_module_features_use_unsaved_project_overlay() { assert signature_help.signatures.any(it.label.contains('answer')) completion := app.operation_at_pos(.completion, Request{ - id: 34 + id: 34 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 5 char: 18 } @@ -1070,17 +1072,17 @@ fn (a VlangVCallAnchor) decl_path(root string) string { // vlib/v, `builder.compile` before that. const vlang_v_module_call_anchors = [ VlangVCallAnchor{ - call: 'driver.run(' + call: 'driver.run(' qualifier: 'driver.' - name: 'run' - decl_rel: 'vlib/v/driver/driver.v' + name: 'run' + decl_rel: 'vlib/v/driver/driver.v' decl_text: 'fn run(' }, VlangVCallAnchor{ - call: "builder.compile('build'" + call: "builder.compile('build'" qualifier: 'builder.' - name: 'compile' - decl_rel: 'vlib/v/builder/compile.v' + name: 'compile' + decl_rel: 'vlib/v/builder/compile.v' decl_text: 'fn compile(' }, ] @@ -1088,17 +1090,17 @@ const vlang_v_module_call_anchors = [ // Method calls on a local variable, for receiver-type inference. const vlang_v_method_call_anchors = [ VlangVCallAnchor{ - call: 'process.set_args(' + call: 'process.set_args(' qualifier: 'process.' - name: 'set_args' - decl_rel: 'vlib/os/process.v' + name: 'set_args' + decl_rel: 'vlib/os/process.v' decl_text: 'fn (mut p Process) set_args(' }, VlangVCallAnchor{ - call: "timers.show('v start'" + call: "timers.show('v start'" qualifier: 'timers.' - name: 'show' - decl_rel: 'vlib/v/util/timers.v' + name: 'show' + decl_rel: 'vlib/v/util/timers.v' decl_text: 'fn (mut t Timers) show(' }, ] @@ -1171,12 +1173,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { for i, method in [Method.definition, .declaration, .type_definition, .implementation] { response := app.operation_at_pos(method, Request{ - id: 40 + i + id: 40 + i params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1191,12 +1193,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { } hover := app.operation_at_pos(.hover, Request{ - id: 44 + id: 44 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: compile_col + 2 } @@ -1211,12 +1213,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { open_paren_col := lines[call_line].index(call_text) or { -1 } assert open_paren_col >= 0 signature := app.operation_at_pos(.signature_help, Request{ - id: 45 + id: 45 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: open_paren_col + call_text.len } @@ -1235,12 +1237,12 @@ fn test_integration_vlang_v_cross_module_features_from_env() { app.open_files[main_uri] = completion_content app.text = completion_content completion := app.operation_at_pos(.completion, Request{ - id: 46 + id: 46 params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: call_line char: dot_col + anchor.qualifier.len } @@ -1288,13 +1290,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( app.workspace_roots = [root] module_completion := app.operation_at_pos(.completion, Request{ - id: 47 + id: 47 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: compile_line char: compile_dot_col } @@ -1306,13 +1308,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (module_completion.result as CompletionList).items.any(it.label == module_anchor.name) receiver_completion := app.operation_at_pos(.completion, Request{ - id: 48 + id: 48 method: 'textDocument/completion' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: timer_dot_col } @@ -1324,13 +1326,13 @@ fn test_integration_vlang_v_indexed_completion_and_receiver_definition_from_env( assert (receiver_completion.result as CompletionList).items.any(it.label == method_anchor.name) definition := app.operation_at_pos(.definition, Request{ - id: 49 + id: 49 method: 'textDocument/definition' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: timer_line char: show_col + 2 } @@ -1374,14 +1376,14 @@ fn test_integration_signature_help_request() { // Request signature help after opening paren request := Request{ - id: 3 - method: 'textDocument/signatureHelp' + id: 3 + method: 'textDocument/signatureHelp' jsonrpc: '2.0' - params: json2.encode(Params{ + params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } // After "greet(" @@ -1415,12 +1417,12 @@ fn test_integration_signature_help_with_params() { // At second parameter position request := Request{ - id: 4 + id: 4 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 7 } @@ -1584,7 +1586,7 @@ fn test_integration_json_error_with_special_chars() { fn test_integration_response_encoding() { response := Response{ - id: 42 + id: 42 result: 'null' } @@ -1600,20 +1602,20 @@ fn test_integration_notification_encoding() { notification := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } } - message: 'test error' + message: 'test error' severity: 1 }, ] @@ -1663,19 +1665,19 @@ fn test_integration_begin_progress_with_client_support_emits_create_and_begin() fn test_integration_completion_response_encoding() { details := [ Detail{ - kind: 6 - label: 'println' + kind: 6 + label: 'println' detail: 'fn println(s string)' }, Detail{ - kind: 6 - label: 'print' + kind: 6 + label: 'print' detail: 'fn print(s string)' }, ] response := Response{ - id: 1 + id: 1 result: details } @@ -1686,15 +1688,15 @@ fn test_integration_completion_response_encoding() { fn test_integration_location_response_encoding() { response := Response{ - id: 1 + id: 1 result: Location{ - uri: 'file:///test/main.v' + uri: 'file:///test/main.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1709,11 +1711,11 @@ fn test_integration_location_response_encoding() { fn test_integration_signature_help_response_encoding() { response := Response{ - id: 1 + id: 1 result: SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1752,13 +1754,13 @@ fn test_integration_request_id_preserved() { // Test with different request IDs for id in [1, 42, 999, 0] { request := Request{ - id: id + id: id method: 'textDocument/completion' params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 2 char: 0 } @@ -1871,17 +1873,17 @@ fn test_integration_full_lifecycle() { // 2. Simulate initialize (verify capabilities) caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1902,7 +1904,7 @@ fn test_integration_full_lifecycle() { modified_content := 'module main\n\nfn helper() {}\n\nfn main() {\n\thelper()\n}\n' app.on_did_change(Request{ params: json2.encode(Params{ - text_document: TextDocumentIdentifier{ + text_document: TextDocumentIdentifier{ uri: uri } content_changes: [ContentChange{ @@ -1916,12 +1918,12 @@ fn test_integration_full_lifecycle() { // 5. Request completion comp_response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1933,12 +1935,12 @@ fn test_integration_full_lifecycle() { // 6. Request definition def_response := app.operation_at_pos(.definition, Request{ - id: 2 + id: 2 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -1955,7 +1957,7 @@ fn test_integration_full_lifecycle() { fn test_integration_shutdown_response() { // Verify shutdown response structure shutdown_resp := Response{ - id: 1 + id: 1 result: 'null' } @@ -2429,12 +2431,12 @@ fn test_integration_completion_includes_sibling_pub_fn() { // Request completion at `helper` on line 3, col 1 (not after '.') response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 1 } @@ -2475,12 +2477,12 @@ fn test_integration_completion_includes_private_sibling_fn() { app.text = main_content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: main_uri } - position: Position{ + position: Position{ line: 3 char: 2 } @@ -2514,12 +2516,12 @@ fn test_integration_completion_includes_current_file_fns() { app.text = content response := app.operation_at_pos(.completion, Request{ - id: 1 + id: 1 params: json2.encode(Params{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 // inside fn main, after `he` char: 2 } @@ -2614,13 +2616,13 @@ fn test_integration_prepare_rename_returns_symbol_range() { app.open_files[uri] = content response := app.handle_prepare_rename(Request{ - id: 301 + id: 301 method: 'textDocument/prepareRename' params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 4 char: 12 } @@ -2649,7 +2651,7 @@ fn test_integration_workspace_symbol_query_matches() { app.open_files[uri] = content response := app.handle_workspace_symbol(Request{ - id: 302 + id: 302 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'name' @@ -2682,7 +2684,7 @@ fn test_integration_workspace_symbol_indexes_loose_module_sibling() { assert find_project_root(project_dir) == '' response := app.handle_workspace_symbol(Request{ - id: 303 + id: 303 method: 'workspace/symbol' params: json2.encode(WorkspaceSymbolParams{ query: 'unopened_loose' @@ -2714,13 +2716,13 @@ fn test_integration_alias_navigation_methods_preserve_id() { mut request_id := 410 for m in methods { resp := app.operation_at_pos(m, Request{ - id: request_id + id: request_id method: m.str() params: json2.encode(TextDocumentPositionParams{ text_document: TextDocumentIdentifier{ uri: uri } - position: Position{ + position: Position{ line: 5 char: 2 } @@ -2735,17 +2737,17 @@ fn test_integration_alias_navigation_methods_preserve_id() { fn test_integration_capability_flags_for_new_features() { caps := Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 2 - save: SaveOptions{ + change: 2 + save: SaveOptions{ include_text: true } } - declaration_provider: true - type_definition_provider: true - implementation_provider: true - rename_provider: RenameOptions{ + declaration_provider: true + type_definition_provider: true + implementation_provider: true + rename_provider: RenameOptions{ prepare_provider: true } workspace_symbol_provider: true @@ -2795,8 +2797,10 @@ fn test_integration_sublime_text_lsp_handshake() { root_uri := path_to_uri(project_dir) initialize := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":42,"clientInfo":{"name":"Sublime Text LSP","version":"2.13.0"},"locale":"en","rootUri":"${root_uri}","rootPath":"${project_dir}","workspaceFolders":[{"uri":"${root_uri}","name":"test_project"}],"capabilities":{"general":{"positionEncodings":["utf-16"]},"workspace":{"workspaceFolders":true,"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true}},"textDocument":{"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"completionItem":{"snippetSupport":true,"documentationFormat":["markdown","plaintext"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"publishDiagnostics":{"versionSupport":true}},"window":{"workDoneProgress":true}},"initializationOptions":{}}}' initialized := '{"jsonrpc":"2.0","method":"initialized","params":{}}' - output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [initialize, - initialized]) + output := integration_run_frames(mut app, project_dir, 'sublime_handshake', [ + initialize, + initialized, + ]) assert app.received_initialize assert app.workspace_roots == [project_dir] diff --git a/lsp_test.v b/lsp_test.v index 0949ec75..abd19455 100644 --- a/lsp_test.v +++ b/lsp_test.v @@ -19,7 +19,11 @@ fn test_stdio_reader_processes_frame_before_eof() { transport.close() } - assert os.fd_dup2(transport.read_fd, 0) >= 0 + // This dup2 is the operation the test depends on, not a check on it, so it + // must not live inside `assert`: `-prod` removes assert statements whole, + // which left fd 0 pointing at the real stdin and made the read below block + // forever instead of failing. + os.fd_dup2(transport.read_fd, 0) payload := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' frame := 'Content-Length: ${payload.len}\r\n\r\n${payload}' transport.write(frame.bytes()) or { @@ -242,9 +246,8 @@ fn test_method_roundtrip_all_values() { .workspace_did_change_configuration, .workspace_did_change_workspace_folders, .document_highlight, .selection_range, .semantic_tokens_range, .range_formatting, .will_save, .will_save_wait_until, .did_change_watched_files, .code_lens, .code_lens_resolve, - .execute_command, .inline_value, .linked_editing_range, .will_create_files, - .will_rename_files, .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, - .shutdown, .exit] + .execute_command, .inline_value, .linked_editing_range, .will_create_files, .will_rename_files, + .will_delete_files, .on_type_formatting, .set_trace, .cancel_request, .shutdown, .exit] for m in methods { assert Method.from_string(m.str()) == m } @@ -472,7 +475,7 @@ fn test_lsp_range_same_line() { line: 5 char: 10 } - end: Position{ + end: Position{ line: 5 char: 20 } @@ -487,7 +490,7 @@ fn test_lsp_range_multi_line() { line: 5 char: 0 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -501,7 +504,7 @@ fn test_lsp_range_json_encoding() { line: 1 char: 2 } - end: Position{ + end: Position{ line: 3 char: 4 } @@ -593,8 +596,8 @@ fn test_request_default_values() { fn test_request_with_values() { req := Request{ - id: 1 - method: 'textDocument/completion' + id: 1 + method: 'textDocument/completion' jsonrpc: '2.0' } assert req.id == 1 @@ -644,7 +647,7 @@ fn test_request_json_decoding_initialize() { fn test_response_default_jsonrpc() { resp := Response{ - id: 1 + id: 1 result: 'null' } assert resp.jsonrpc == '2.0' @@ -652,7 +655,7 @@ fn test_response_default_jsonrpc() { fn test_response_json_encoding() { resp := Response{ - id: 42 + id: 42 result: 'null' } encoded := json2.encode(resp, escape_unicode: true) @@ -662,7 +665,7 @@ fn test_response_json_encoding() { fn test_encode_response_payload_uses_json_null_for_null_result() { resp := Response{ - id: 2 + id: 2 result: 'null' } encoded := encode_response_payload(resp) @@ -672,7 +675,7 @@ fn test_encode_response_payload_uses_json_null_for_null_result() { fn test_encode_response_payload_preserves_non_null_results() { resp := Response{ - id: 3 + id: 3 result: []TextEdit{} } encoded := encode_response_payload(resp) @@ -681,7 +684,7 @@ fn test_encode_response_payload_preserves_non_null_results() { fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { resp := Response{ - id: 4 + id: 4 result: Capabilities{ capabilities: Capability{ definition_provider: true @@ -695,14 +698,14 @@ fn test_encode_response_payload_strips_sum_type_tag_from_capabilities() { fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { resp := Response{ - id: 5 + id: 5 result: PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 7 } @@ -717,15 +720,15 @@ fn test_encode_response_payload_strips_sum_type_tag_from_prepare_rename() { fn test_workspace_edit_closed_document_has_explicit_null_version() { resp := Response{ - id: 6 + id: 6 result: WorkspaceEdit{ document_changes: [TextDocumentEdit{ text_document: OptionalVersionedTextDocumentIdentifier{ - uri: 'file:///tmp/closed.v' + uri: 'file:///tmp/closed.v' version: json2.null } - edits: [TextEdit{ - range: LSPRange{} + edits: [TextEdit{ + range: LSPRange{} new_text: 'renamed' }] }] @@ -924,7 +927,7 @@ fn test_notification_json_encoding() { notif := Notification{ method: 'textDocument/publishDiagnostics' params: PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } } @@ -935,8 +938,8 @@ fn test_notification_json_encoding() { fn test_lsp_diagnostic_error_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'error message' + range: LSPRange{} + message: 'error message' severity: 1 } assert diag.severity == 1 // Error @@ -945,8 +948,8 @@ fn test_lsp_diagnostic_error_severity() { fn test_lsp_diagnostic_warning_severity() { diag := LSPDiagnostic{ - range: LSPRange{} - message: 'warning message' + range: LSPRange{} + message: 'warning message' severity: 2 } assert diag.severity == 2 // Warning @@ -954,17 +957,17 @@ fn test_lsp_diagnostic_warning_severity() { fn test_lsp_diagnostic_json_encoding() { diag := LSPDiagnostic{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 10 } } - message: 'undefined identifier' + message: 'undefined identifier' severity: 1 } encoded := json2.encode(diag, escape_unicode: true) @@ -974,9 +977,9 @@ fn test_lsp_diagnostic_json_encoding() { fn test_detail_function_kind() { detail := Detail{ - kind: 6 // Function - label: 'my_function' - detail: 'fn my_function() string' + kind: 6 // Function + label: 'my_function' + detail: 'fn my_function() string' documentation: 'A helper function' } assert detail.kind == 6 @@ -985,9 +988,9 @@ fn test_detail_function_kind() { fn test_detail_variable_kind() { detail := Detail{ - kind: 6 - label: 'my_var' - detail: 'int' + kind: 6 + label: 'my_var' + detail: 'int' documentation: 'A variable' } assert detail.label == 'my_var' @@ -995,10 +998,10 @@ fn test_detail_variable_kind() { fn test_detail_with_snippet() { detail := Detail{ - kind: 6 - label: 'println' - detail: 'fn println(s string)' - insert_text: 'println(\${1:s})' + kind: 6 + label: 'println' + detail: 'fn println(s string)' + insert_text: 'println(\${1:s})' insert_text_format: 2 // Snippet } assert detail.insert_text? == 'println(\${1:s})' @@ -1007,7 +1010,7 @@ fn test_detail_with_snippet() { fn test_detail_json_encoding() { detail := Detail{ - kind: 6 + kind: 6 label: 'test_fn' } encoded := json2.encode(detail, escape_unicode: true) @@ -1017,13 +1020,13 @@ fn test_detail_json_encoding() { fn test_location_basic() { loc := Location{ - uri: 'file:///test/file.v' + uri: 'file:///test/file.v' range: LSPRange{ start: Position{ line: 10 char: 5 } - end: Position{ + end: Position{ line: 10 char: 15 } @@ -1035,13 +1038,13 @@ fn test_location_basic() { fn test_location_json_encoding() { loc := Location{ - uri: 'file:///path/to/file.v' + uri: 'file:///path/to/file.v' range: LSPRange{ start: Position{ line: 0 char: 0 } - end: Position{ + end: Position{ line: 0 char: 5 } @@ -1061,9 +1064,9 @@ fn test_signature_help_empty() { fn test_signature_help_with_signature() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ - label: 'fn test(a int, b string)' + label: 'fn test(a int, b string)' parameters: [ ParameterInformation{ label: 'a int' @@ -1084,7 +1087,7 @@ fn test_signature_help_with_signature() { fn test_signature_help_json_encoding() { sig := SignatureHelp{ - signatures: [ + signatures: [ SignatureInformation{ label: 'fn example()' }, @@ -1100,17 +1103,17 @@ fn test_signature_help_json_encoding() { fn test_capabilities_full() { caps := Capabilities{ capabilities: Capability{ - text_document_sync: TextDocumentSyncOptions{ + text_document_sync: TextDocumentSyncOptions{ open_close: true - change: 1 + change: 1 } - completion_provider: CompletionProvider{ + completion_provider: CompletionProvider{ trigger_characters: ['.'] } signature_help_provider: SignatureHelpOptions{ trigger_characters: ['(', ','] } - definition_provider: true + definition_provider: true } } assert caps.capabilities.definition_provider == true @@ -1147,7 +1150,7 @@ fn test_completion_item_capability_snippet_support() { fn test_text_document_sync_full() { sync := TextDocumentSyncOptions{ open_close: true - change: 1 // Full + change: 1 // Full } assert sync.open_close == true assert sync.change == 1 @@ -1156,7 +1159,7 @@ fn test_text_document_sync_full() { fn test_text_document_sync_incremental() { sync := TextDocumentSyncOptions{ open_close: true - change: 2 // Incremental + change: 2 // Incremental } assert sync.change == 2 } @@ -1181,7 +1184,7 @@ fn test_response_result_string() { fn test_response_result_details() { details := [ Detail{ - kind: 6 + kind: 6 label: 'test' }, ] @@ -1234,7 +1237,7 @@ fn test_response_result_location() { fn test_publish_diagnostics_params_empty() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [] } assert params.uri == 'file:///test.v' @@ -1243,16 +1246,16 @@ fn test_publish_diagnostics_params_empty() { fn test_publish_diagnostics_params_with_diagnostics() { params := PublishDiagnosticsParams{ - uri: 'file:///test.v' + uri: 'file:///test.v' diagnostics: [ LSPDiagnostic{ - range: LSPRange{} - message: 'error 1' + range: LSPRange{} + message: 'error 1' severity: 1 }, LSPDiagnostic{ - range: LSPRange{} - message: 'error 2' + range: LSPRange{} + message: 'error 2' severity: 1 }, ] @@ -1262,11 +1265,11 @@ fn test_publish_diagnostics_params_with_diagnostics() { fn test_json_error_struct() { err := JsonError{ - path: '/test/file.v' + path: '/test/file.v' message: 'undefined identifier' line_nr: 10 - col: 5 - len: 3 + col: 5 + len: 3 } assert err.path == '/test/file.v' assert err.message == 'undefined identifier' @@ -1305,11 +1308,11 @@ fn test_json_var_ac_with_details() { ac := JsonVarAC{ details: [ Detail{ - kind: 6 + kind: 6 label: 'fn1' }, Detail{ - kind: 6 + kind: 6 label: 'fn2' }, ] @@ -1340,14 +1343,14 @@ fn test_document_symbol_default_values() { fn test_document_symbol_with_values() { sym := DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 20 } @@ -1357,12 +1360,12 @@ fn test_document_symbol_with_values() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } assert sym.name == 'greet' assert sym.kind == sym_kind_function @@ -1372,14 +1375,14 @@ fn test_document_symbol_with_values() { fn test_document_symbol_json_encoding() { sym := DocumentSymbol{ - name: 'Person' - kind: sym_kind_struct - range: LSPRange{ + name: 'Person' + kind: sym_kind_struct + range: LSPRange{ start: Position{ line: 5 char: 0 } - end: Position{ + end: Position{ line: 5 char: 14 } @@ -1389,12 +1392,12 @@ fn test_document_symbol_json_encoding() { line: 5 char: 7 } - end: Position{ + end: Position{ line: 5 char: 13 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} } encoded := json2.encode(sym, escape_unicode: true) assert encoded.contains('"name":"Person"') @@ -1417,17 +1420,17 @@ fn test_document_symbol_json_decoding() { fn test_document_symbol_with_children() { sym := DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: [ + children: [ DocumentSymbol{ - name: 'run' - kind: sym_kind_method - range: LSPRange{} + name: 'run' + kind: sym_kind_method + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] } @@ -1508,10 +1511,10 @@ fn test_method_roundtrip_new_methods() { fn test_response_result_workspace_symbols() { result := ResponseResult([ WorkspaceSymbol{ - name: 'main' - kind: sym_kind_function + name: 'main' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/main.v' + uri: 'file:///tmp/main.v' range: LSPRange{} } }, @@ -1526,12 +1529,12 @@ fn test_response_result_workspace_symbols() { fn test_response_result_prepare_rename_result() { result := ResponseResult(PrepareRenameResult{ - range: LSPRange{ + range: LSPRange{ start: Position{ line: 1 char: 2 } - end: Position{ + end: Position{ line: 1 char: 5 } @@ -1559,18 +1562,18 @@ fn test_response_result_document_symbols_empty() { fn test_response_result_document_symbols_with_data() { syms := [ DocumentSymbol{ - name: 'main' - kind: sym_kind_function - range: LSPRange{} + name: 'main' + kind: sym_kind_function + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, DocumentSymbol{ - name: 'App' - kind: sym_kind_struct - range: LSPRange{} + name: 'App' + kind: sym_kind_struct + range: LSPRange{} selection_range: LSPRange{} - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] result := ResponseResult(syms) @@ -1588,14 +1591,14 @@ fn test_response_result_document_symbols_with_data() { fn test_response_with_document_symbols_json_encoding() { syms := [ DocumentSymbol{ - name: 'greet' - kind: sym_kind_function - range: LSPRange{ + name: 'greet' + kind: sym_kind_function + range: LSPRange{ start: Position{ line: 2 char: 0 } - end: Position{ + end: Position{ line: 2 char: 25 } @@ -1605,16 +1608,16 @@ fn test_response_with_document_symbols_json_encoding() { line: 2 char: 3 } - end: Position{ + end: Position{ line: 2 char: 8 } } - children: []DocumentSymbol{} + children: []DocumentSymbol{} }, ] resp := Response{ - id: 7 + id: 7 result: syms } encoded := json2.encode(resp, escape_unicode: true) @@ -1640,7 +1643,7 @@ fn test_capability_document_symbol_provider_json_encoding() { caps := Capabilities{ capabilities: Capability{ document_symbol_provider: true - definition_provider: true + definition_provider: true } } encoded := json2.encode(caps, escape_unicode: true) @@ -1802,13 +1805,13 @@ fn test_encode_response_payload_strips_type_from_array_variant() { // Array-of-struct result variants (e.g. []WorkspaceSymbol) must also have // their per-element `_type` discriminators stripped (P1-10). resp := Response{ - id: 9 + id: 9 result: [ WorkspaceSymbol{ - name: 'helper_fn' - kind: sym_kind_function + name: 'helper_fn' + kind: sym_kind_function location: Location{ - uri: 'file:///tmp/lib.v' + uri: 'file:///tmp/lib.v' range: LSPRange{} } }, From fe086a747765603138cc1a05642cdb543467eba4 Mon Sep 17 00:00:00 2001 From: metif12 Date: Mon, 5 Oct 2026 17:44:29 +0330 Subject: [PATCH 5/6] fix: resolve vlib through a compiler wrapper script find_v_dir trusted the resolved compiler executable directory and returned it unconditionally. V's own Windows launcher is a .bat wrapper kept in .bin/ that forwards to the real v.exe one level up, so that directory contains no vlib at all. Every caller builds /vlib, so all of them silently resolved to nothing: import completions returned an empty list, and hover and go-to-definition stopped resolving vlib symbols. find_v_dir_from_exe now walks up from the executable until it finds a directory that actually contains vlib, bounded to eight levels. A directory with no vlib anywhere on the way up now returns an empty string instead of a path whose vlib does not exist. Measured on Windows with v resolved to the wrapper: 7 of 454 handlers_test cases failed before this change and all 454 pass after it, under identical conditions. The same run also fixes 2 of the 3 failing integration cases that a stdio test would otherwise have masked. --- main.v | 31 +++++++++++++-- main_test.v | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 main_test.v diff --git a/main.v b/main.v index c5bb4fe5..39dc7f6f 100644 --- a/main.v +++ b/main.v @@ -78,14 +78,37 @@ struct DiagCacheEntry { // Keep runtime-derived settings behind functions. Function-call module constants can crash V3's // parallel constant precomputation while compiling VLS. -// find_v_dir resolves the V home directory by finding the V executable and -// returning its parent directory. +// find_v_dir resolves the V home directory, meaning the directory that holds `vlib`. fn find_v_dir() string { - v_exe := resolve_v_compiler_exe() + return find_v_dir_from_exe(resolve_v_compiler_exe()) +} + +// find_v_dir_from_exe locates the directory holding `vlib` for a given compiler +// executable. Trusting the executable's own directory is not enough: V's Windows +// launcher is a `.bat` wrapper in `.bin/` that forwards to the real `v.exe` one +// level up, so the executable's directory has no `vlib` at all. Every vlib lookup +// then silently resolves to nothing, which empties import completions and breaks +// hover and go-to-definition for vlib symbols. Walk up until a directory that +// actually contains `vlib` is found, so both layouts work. +fn find_v_dir_from_exe(v_exe string) string { if v_exe == 'v' || !os.is_file(v_exe) { return '' } - return os.dir(os.real_path(v_exe)) + mut dir := os.dir(os.real_path(v_exe)) + // A V checkout nests vlib directly under its root, so the root is at or above + // the executable. The bound stops a pathological layout from walking to the + // filesystem root one component at a time. + for _ in 0 .. 8 { + if os.is_dir(os.join_path(dir, 'vlib')) { + return dir + } + parent := os.dir(dir) + if parent == dir || parent == '' { + break + } + dir = parent + } + return '' } // logging_enabled gates all diagnostic logging. Logging is OFF by default: diff --git a/main_test.v b/main_test.v new file mode 100644 index 00000000..39a096a9 --- /dev/null +++ b/main_test.v @@ -0,0 +1,106 @@ +// Copyright (c) 2025 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by a GPL license that can be found in the LICENSE file. +module main + +import os +import time + +fn main_test_temp_dir(tag string) string { + dir := os.join_path(os.temp_dir(), 'vls_find_v_dir_${tag}_${os.getpid()}_${time.now().unix_nano()}') + os.mkdir_all(dir) or { + assert false, 'Failed to create ${dir}: ${err}' + return dir + } + return dir +} + +fn main_test_write(path string, content string) { + os.write_file(path, content) or { + assert false, 'Failed to write ${path}: ${err}' + } +} + +// The plain case: the executable sits directly beside vlib. +fn test_find_v_dir_from_exe_beside_vlib() { + root := main_test_temp_dir('beside') + defer { + os.rmdir_all(root) or {} + } + main_test_write(os.join_path(root, 'v.exe'), 'binary') + os.mkdir_all(os.join_path(root, 'vlib')) or { + assert false, 'Failed to create vlib: ${err}' + return + } + + assert find_v_dir_from_exe(os.join_path(root, 'v.exe')) == root +} + +// The regression case. V's Windows launcher is a `.bat` wrapper kept in `.bin/` +// that forwards to the real `v.exe` in the parent, so the executable's own +// directory has no vlib. Trusting it made every vlib lookup resolve to nothing. +fn test_find_v_dir_from_exe_wrapper_script_in_bin_dir() { + root := main_test_temp_dir('wrapper') + defer { + os.rmdir_all(root) or {} + } + main_test_write(os.join_path(root, 'v.exe'), 'binary') + os.mkdir_all(os.join_path(root, 'vlib')) or { + assert false, 'Failed to create vlib: ${err}' + return + } + bin_dir := os.join_path(root, '.bin') + os.mkdir_all(bin_dir) or { + assert false, 'Failed to create .bin: ${err}' + return + } + wrapper := os.join_path(bin_dir, 'v.bat') + // The wrapper's own contents do not matter, only that it is a file that is + // not the real compiler. + main_test_write(wrapper, '@echo off\r\ncall v.exe %*\r\n') + + assert find_v_dir_from_exe(wrapper) == root +} + +// A wrapper nested one level deeper still resolves, because the walk is bounded +// by the number of levels between the launcher and the V root rather than by one. +fn test_find_v_dir_from_exe_deeply_nested_wrapper() { + root := main_test_temp_dir('nested') + defer { + os.rmdir_all(root) or {} + } + main_test_write(os.join_path(root, 'v.exe'), 'binary') + os.mkdir_all(os.join_path(root, 'vlib')) or { + assert false, 'Failed to create vlib: ${err}' + return + } + nested := os.join_path(root, '.bin', 'shims') + os.mkdir_all(nested) or { + assert false, 'Failed to create nested shim dir: ${err}' + return + } + wrapper := os.join_path(nested, 'v.bat') + main_test_write(wrapper, '@echo off\r\n') + + assert find_v_dir_from_exe(wrapper) == root +} + +// A directory that holds no vlib anywhere on the way up reports nothing found, +// instead of returning a directory whose vlib path does not exist. +fn test_find_v_dir_from_exe_without_any_vlib() { + root := main_test_temp_dir('novlib') + defer { + os.rmdir_all(root) or {} + } + exe := os.join_path(root, 'v.exe') + main_test_write(exe, 'binary') + + assert find_v_dir_from_exe(exe) == '' +} + +fn test_find_v_dir_from_exe_rejects_unresolved_name() { + assert find_v_dir_from_exe('v') == '' +} + +fn test_find_v_dir_from_exe_rejects_missing_file() { + assert find_v_dir_from_exe(os.join_path(os.temp_dir(), 'vls_no_such_v_exe_9f3a')) == '' +} From 6596485895077914fa732f5f76b6be1a697a44ec Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 5 Oct 2026 18:19:04 +0300 Subject: [PATCH 6/6] test: canonicalize vlib discovery temporary roots --- main_test.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main_test.v b/main_test.v index 39a096a9..3df98203 100644 --- a/main_test.v +++ b/main_test.v @@ -11,7 +11,7 @@ fn main_test_temp_dir(tag string) string { assert false, 'Failed to create ${dir}: ${err}' return dir } - return dir + return os.real_path(dir) } fn main_test_write(path string, content string) {