From 2914f1bb821da9739981bc924d3d1b04e91aca03 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 26 Feb 2026 07:42:14 +0100 Subject: [PATCH 1/3] fix import completions after unterminted string literal with newline --- src/analysis.zig | 2 ++ src/features/completions.zig | 5 +---- tests/lsp_features/completion.zig | 7 +++++++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index a51b06c9b..3a30e45c1 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -4806,8 +4806,10 @@ pub const PositionContext = union(enum) { if (std.mem.endsWith(u8, string_literal_slice[1..], "\"")) { location.end -= 1; } + location.end = std.mem.findAnyPos(u8, source, location.start, &.{ '\n', '"' }) orelse source.len; } else if (std.mem.startsWith(u8, string_literal_slice, "\\")) { location.start += 2; + location.end = std.mem.findScalarPos(u8, source, location.start, '\n') orelse source.len; } return location; } diff --git a/src/features/completions.zig b/src/features/completions.zig index 3f8c906f0..552109b4b 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -810,10 +810,7 @@ fn completeFileSystemStringLiteral(builder: *Builder, pos_context: Analyser.Posi if (pos_context == .string_literal and !DocumentStore.isBuildFile(builder.orig_handle.uri)) return; - var string_content_loc = pos_context.stringLiteralContentLoc(source); - - // the position context is without lookahead so we have to do it ourself - string_content_loc.end = std.mem.findAnyPos(u8, source, string_content_loc.end, &.{ 0, '\n', '\r', '"' }) orelse source.len; + const string_content_loc = pos_context.stringLiteralContentLoc(source); if (builder.source_index < string_content_loc.start or string_content_loc.end < builder.source_index) return; diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index ebf530f91..2eb717f74 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -3571,6 +3571,13 @@ test "filesystem string literal ends with non ASCII symbol" { }); } +test "filesystem unterminated string literal with newline" { + try testCompletion( + \\const foo = @import(" + \\ + , &.{}); +} + test "label details disabled" { try testCompletionWithOptions( \\const S = struct { From c37e84466cea34ff776cc4b28f62d87b6160f33f Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 26 Feb 2026 09:33:55 +0100 Subject: [PATCH 2/3] fix completion when the cursor is positioned before a lone `@` character --- src/features/completions.zig | 3 ++- tests/lsp_features/completion.zig | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/features/completions.zig b/src/features/completions.zig index 552109b4b..caab835d3 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -508,7 +508,8 @@ fn prepareCompletionLoc(tree: *const Ast, source_index: usize) offsets.Loc { if (std.mem.startsWith(u8, tree.source[token_start..], "@\"")) { break :start .{ token_start, token_start + 2 }; } else if (std.mem.startsWith(u8, tree.source[token_start..], "@") or std.mem.startsWith(u8, tree.source[token_start..], ".")) { - break :start .{ token_start + 1, token_start + 1 }; + const start = @min(token_start + 1, source_index); + break :start .{ start, start }; } else { break :start .{ token_start, token_start }; } diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 2eb717f74..91a04439d 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -3839,6 +3839,24 @@ test "insert replace behaviour - builtin with partial argument placeholders" { }); } +test "insert replace behaviour - prepend on builtin or enum literal" { + try testCompletionTextEdit(.{ + .source = "const foo = @", + .label = "comptime_int", + .expected_insert_line = "const foo = comptime_int@", + .expected_replace_line = "const foo = comptime_int@", + }); + try testCompletionTextEdit(.{ + .source = + \\const E = enum{ A, B }; + \\const foo: E = . + , + .label = "comptime_int", + .expected_insert_line = "const foo: E = comptime_int.", + .expected_replace_line = "const foo: E = comptime_int.", + }); +} + test "insert replace behaviour - function" { try testCompletionTextEdit(.{ .source = From 5efb45b2be80dddf76850f17051c314b41427f93 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Thu, 26 Feb 2026 09:51:49 +0100 Subject: [PATCH 3/3] fix enum completion on out of bound token index --- src/features/completions.zig | 1 + tests/lsp_features/completion.zig | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/features/completions.zig b/src/features/completions.zig index caab835d3..b8da7475d 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -1310,6 +1310,7 @@ fn getSwitchOrStructInitContext( // The opening brace is preceded by a r_paren => evaluate .r_paren => { need_ret_type = true; + if (upper_index < 1) return null; var token_index = upper_index - 1; // if `switch` we need the last token of the condition parens_depth = even; // Walk backwards counting parens until one_opening then check the preceding token's tag diff --git a/tests/lsp_features/completion.zig b/tests/lsp_features/completion.zig index 91a04439d..185e24d31 100644 --- a/tests/lsp_features/completion.zig +++ b/tests/lsp_features/completion.zig @@ -3485,6 +3485,9 @@ test "enum completion on out of bound token index" { try testCompletion( \\ = 1. , &.{}); + try testCompletion( + \\) { . + , &.{}); } test "combine doc comments of declaration and definition" {