Skip to content

Commit 3817d1e

Browse files
TechatrixxdBronch
andcommitted
don't replace non symbol characters in code completions
Co-Authored-By: xdBronch <[email protected]>
1 parent 9284a3e commit 3817d1e

File tree

3 files changed

+75
-7
lines changed

3 files changed

+75
-7
lines changed

src/ast.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,24 @@ fn findMatchingRBrace(tree: *const Ast, start: Ast.TokenIndex) ?Ast.TokenIndex {
400400
return if (std.mem.findScalarPos(std.zig.Token.Tag, tree.tokens.items(.tag), start, .r_brace)) |index| @intCast(index) else null;
401401
}
402402

403+
pub fn isKeyword(tag: std.zig.Token.Tag) bool {
404+
const tags = std.zig.Token.keywords.values();
405+
var first_keyword: std.meta.Tag(std.zig.Token.Tag) = @intFromEnum(tags[0]);
406+
var last_keyword: std.meta.Tag(std.zig.Token.Tag) = @intFromEnum(tags[tags.len - 1]);
407+
for (std.zig.Token.keywords.values()) |token_tag| {
408+
first_keyword = @min(@intFromEnum(token_tag), first_keyword);
409+
last_keyword = @max(@intFromEnum(token_tag), last_keyword);
410+
}
411+
return first_keyword <= @intFromEnum(tag) and @intFromEnum(tag) <= last_keyword;
412+
}
413+
414+
test isKeyword {
415+
try std.testing.expect(!isKeyword(.invalid));
416+
try std.testing.expect(!isKeyword(.container_doc_comment));
417+
try std.testing.expect(isKeyword(.keyword_addrspace));
418+
try std.testing.expect(isKeyword(.keyword_while));
419+
}
420+
403421
/// Similar to `std.zig.Ast.lastToken` but also handles ASTs with syntax errors.
404422
pub fn lastToken(tree: *const Ast, node: Node.Index) Ast.TokenIndex {
405423
var n = node;

src/features/completions.zig

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,10 @@ fn prepareCompletionLoc(tree: *const Ast, source_index: usize) offsets.Loc {
487487
const token = switch (offsets.sourceIndexToTokenIndex(tree, source_index)) {
488488
.none => return fallback_loc,
489489
.one => |token| token,
490-
.between => |data| data.left,
490+
.between => |data| switch (tree.tokenTag(data.left)) {
491+
.identifier, .builtin, .invalid => data.left,
492+
else => |tag| if (ast.isKeyword(tag)) data.left else data.right,
493+
},
491494
};
492495
switch (tree.tokenTag(token)) {
493496
.identifier, .builtin => |tag| {
@@ -496,21 +499,20 @@ fn prepareCompletionLoc(tree: *const Ast, source_index: usize) offsets.Loc {
496499
std.debug.assert(token_loc.start <= source_index and source_index <= token_loc.end);
497500
return offsets.identifierIndexToLoc(tree.source, token_loc.start, if (tag == .builtin) .name else .full);
498501
},
499-
.colon => return fallback_loc,
500-
else => {
501-
const token_start = tree.tokenStart(token);
502+
else => |token_tag| {
503+
if (token_tag != .invalid and !ast.isKeyword(token_tag))
504+
return fallback_loc;
502505

503-
var start: usize, var end: usize = start: {
506+
const start: usize, var end: usize = start: {
507+
const token_start = tree.tokenStart(token);
504508
if (std.mem.startsWith(u8, tree.source[token_start..], "@\"")) {
505509
break :start .{ token_start, token_start + 2 };
506510
} else if (std.mem.startsWith(u8, tree.source[token_start..], "@") or std.mem.startsWith(u8, tree.source[token_start..], ".")) {
507-
if (token_start + 1 < source_index) return fallback_loc;
508511
break :start .{ token_start + 1, token_start + 1 };
509512
} else {
510513
break :start .{ token_start, token_start };
511514
}
512515
};
513-
start = @min(start, source_index);
514516
end = @max(end, source_index);
515517

516518
while (end < tree.source.len and offsets.isSymbolChar(tree.source[end])) {

tests/lsp_features/completion.zig

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4295,6 +4295,54 @@ test "insert replace behaviour - file system completions" {
42954295
// zig fmt: on
42964296
}
42974297

4298+
test "insert replace behaviour - expression inside parens/braces/brackets" {
4299+
try testCompletionTextEdit(.{
4300+
.source =
4301+
\\const foo = 5;
4302+
\\const bar = foo(<cursor>);
4303+
,
4304+
.label = "foo",
4305+
.expected_insert_line = "const bar = foo(foo);",
4306+
.expected_replace_line = "const bar = foo(foo);",
4307+
});
4308+
try testCompletionTextEdit(.{
4309+
.source =
4310+
\\const foo = 5;
4311+
\\const bar = foo(f<cursor>oo);
4312+
,
4313+
.label = "foo",
4314+
.expected_insert_line = "const bar = foo(foooo);",
4315+
.expected_replace_line = "const bar = foo(foo);",
4316+
});
4317+
try testCompletionTextEdit(.{
4318+
.source =
4319+
\\const foo = 5;
4320+
\\const bar = foo(<cursor>foo);
4321+
,
4322+
.label = "foo",
4323+
.expected_insert_line = "const bar = foo(foofoo);",
4324+
.expected_replace_line = "const bar = foo(foo);",
4325+
});
4326+
try testCompletionTextEdit(.{
4327+
.source =
4328+
\\const foo = 5;
4329+
\\const bar = foo{<cursor>};
4330+
,
4331+
.label = "foo",
4332+
.expected_insert_line = "const bar = foo{foo};",
4333+
.expected_replace_line = "const bar = foo{foo};",
4334+
});
4335+
try testCompletionTextEdit(.{
4336+
.source =
4337+
\\const foo = 5;
4338+
\\const bar = foo[<cursor>];
4339+
,
4340+
.label = "foo",
4341+
.expected_insert_line = "const bar = foo[foo];",
4342+
.expected_replace_line = "const bar = foo[foo];",
4343+
});
4344+
}
4345+
42984346
test "generic function with @This() as self param" {
42994347
try testCompletion(
43004348
\\const Foo = struct {

0 commit comments

Comments
 (0)