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..b8da7475d 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 }; } @@ -810,10 +811,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; @@ -1312,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 ebf530f91..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" { @@ -3571,6 +3574,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 { @@ -3832,6 +3842,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 =