Skip to content

Commit e3e23de

Browse files
committed
rework code action test fn to take code action kind, fix string literal code action handlers
1 parent 8edf260 commit e3e23de

File tree

3 files changed

+106
-61
lines changed

3 files changed

+106
-61
lines changed

src/Server.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ fn showMessage(
321321
}
322322
}
323323

324-
fn initAnalyser(server: *Server, handle: ?*DocumentStore.Handle) Analyser {
324+
pub fn initAnalyser(server: *Server, handle: ?*DocumentStore.Handle) Analyser {
325325
return Analyser.init(
326326
server.allocator,
327327
&server.document_store,

src/features/code_actions.zig

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -388,25 +388,45 @@ fn handleStringLiteralToMultiline(builder: *Builder, actions: *std.ArrayListUnma
388388
const str_tok_idx = offsets.sourceIndexToTokenIndex(builder.handle.tree, loc.start);
389389
if (tokens.items(.tag)[str_tok_idx] != .string_literal) return;
390390
const token_src = builder.handle.tree.tokenSlice(str_tok_idx);
391-
const str_conts = token_src[1 .. token_src.len - 1]; // Omit leading and trailing '"'
392391
const edit_loc_start = builder.handle.tree.tokenLocation(tokens.items(.start)[str_tok_idx], str_tok_idx).line_start;
393392

394-
var multiline = std.ArrayList(u8).init(builder.arena);
395-
const writer = multiline.writer();
393+
var parsed = std.ArrayList(u8).init(builder.arena);
394+
defer parsed.deinit();
395+
const writer = parsed.writer();
396396

397-
if (builder.handle.tree.tokensOnSameLine(str_tok_idx -| 1, str_tok_idx)) {
398-
try writer.writeByte('\n');
397+
switch (try std.zig.string_literal.parseWrite(writer, token_src)) {
398+
.failure => return,
399+
.success => {},
399400
}
401+
// carriage returns are not allowed in multiline string literals
402+
if (std.mem.containsAtLeast(u8, parsed.items, 1, "\r")) return;
400403

401-
var iter = std.mem.splitSequence(u8, str_conts, "\\n");
402-
while (iter.next()) |line| {
403-
try writer.print("\\\\{s}\n", .{line});
404-
}
404+
const next_token = @min(str_tok_idx + 1, builder.handle.tree.tokens.len - 1);
405+
const leading_nl = builder.handle.tree.tokensOnSameLine(str_tok_idx -| 1, str_tok_idx);
406+
const trailing_nl = builder.handle.tree.tokensOnSameLine(str_tok_idx, @intCast(next_token));
407+
408+
const len = blk: {
409+
var tot: usize = 0;
410+
if (leading_nl) tot += 1;
411+
tot += std.mem.replacementSize(u8, writer.context.items, "\n", "\n\\\\") + "\\\\".len;
412+
if (trailing_nl) tot += 1;
413+
414+
break :blk tot;
415+
};
416+
var buf = try std.ArrayList(u8).initCapacity(builder.arena, len);
417+
errdefer buf.deinit();
405418

406-
// remove trailing newline in cases where it's not needed
407-
if (str_tok_idx + 1 < tokens.len and !builder.handle.tree.tokensOnSameLine(str_tok_idx, str_tok_idx + 1)) {
408-
_ = multiline.pop();
419+
var start_idx: usize = 0;
420+
if (leading_nl) {
421+
buf.appendAssumeCapacity('\n');
422+
start_idx += 1;
409423
}
424+
buf.appendSliceAssumeCapacity("\\\\");
425+
start_idx += 2;
426+
try if (trailing_nl) buf.resize(len - 1) else buf.resize(len);
427+
428+
_ = std.mem.replace(u8, parsed.items, "\n", "\n\\\\", buf.items[start_idx..]);
429+
if (trailing_nl) buf.appendAssumeCapacity('\n');
410430

411431
try actions.append(builder.arena, .{
412432
.title = "string literal to multiline string",
@@ -418,7 +438,7 @@ fn handleStringLiteralToMultiline(builder: *Builder, actions: *std.ArrayListUnma
418438
.start = edit_loc_start,
419439
.end = edit_loc_start + token_src.len,
420440
},
421-
multiline.items,
441+
buf.items,
422442
),
423443
}),
424444
});
@@ -430,15 +450,18 @@ fn handleMultilineStringToLiteral(builder: *Builder, actions: *std.ArrayListUnma
430450

431451
var multiline_tok_idx = offsets.sourceIndexToTokenIndex(builder.handle.tree, loc.start);
432452
if (token_tags[multiline_tok_idx] != .multiline_string_literal_line) return;
453+
multiline_tok_idx -|= 1;
433454

434455
// walk up to the first multiline string literal
435456
const start_tok_idx = blk: {
436-
while (multiline_tok_idx > 0) : (multiline_tok_idx -= 1) {
457+
while (true) : (multiline_tok_idx -|= 1) {
437458
if (token_tags[multiline_tok_idx] != .multiline_string_literal_line) {
438459
break :blk multiline_tok_idx + 1;
460+
} else if (multiline_tok_idx == 0) {
461+
break :blk multiline_tok_idx;
439462
}
440463
}
441-
break :blk multiline_tok_idx;
464+
unreachable;
442465
};
443466

444467
var str_literal = std.ArrayList(u8).init(builder.arena);
@@ -464,16 +487,14 @@ fn handleMultilineStringToLiteral(builder: *Builder, actions: *std.ArrayListUnma
464487
try writer.writeAll("\\n");
465488
}
466489
const line = builder.handle.tree.tokenSlice(curr_tok_idx);
467-
const end = if (line[line.len - 1] == '\n')
468-
line.len - 1
469-
else
470-
line.len;
471-
try writer.writeAll(line[2..end]); // Omit the leading '\\', trailing '\n' (if it's there)
490+
std.debug.assert(line.len >= 2);
491+
const end = if (line[line.len - 1] == '\n') line.len - 1 else line.len;
492+
// Omit the leading "\\", trailing '\n' (if it's there)
493+
try std.zig.stringEscape(line[2..end], "", .{}, writer);
472494
edit_loc_end = builder.handle.tree.tokenLocation(token_starts[curr_tok_idx], curr_tok_idx).line_end;
473495
}
474496

475497
try writer.writeByte('\"');
476-
477498
// bring up the semicolon from the next line, if it's there
478499
if (curr_tok_idx < token_tags.len and token_tags[curr_tok_idx] == .semicolon) {
479500
try writer.writeByte(';');
@@ -705,7 +726,7 @@ const DiagnosticKind = union(enum) {
705726
}
706727
};
707728

708-
const UserActionKind = union(enum) {
729+
pub const UserActionKind = union(enum) {
709730
str_kind_conv: StrCat,
710731

711732
const StrCat = enum {

tests/lsp_features/code_actions.zig

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ test "ignore autofix comment whitespace" {
364364
}
365365

366366
test "string literal to multiline string literal" {
367-
try testUserCodeAction(
367+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
368368
\\const foo = <cursor>"line one\nline two\nline three";
369369
,
370370
\\const foo =
@@ -373,15 +373,15 @@ test "string literal to multiline string literal" {
373373
\\\\line three
374374
\\;
375375
);
376-
try testUserCodeAction(
376+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
377377
\\const foo = "Hello, <cursor>World!\n";
378378
,
379379
\\const foo =
380380
\\\\Hello, World!
381381
\\\\
382382
\\;
383383
);
384-
try testUserCodeAction(
384+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
385385
\\std.debug.print(<cursor>"Hi\nHey\nHello\n", .{});
386386
,
387387
\\std.debug.print(
@@ -391,18 +391,44 @@ test "string literal to multiline string literal" {
391391
\\\\
392392
\\, .{});
393393
);
394-
try testUserCodeAction(
394+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
395395
\\const blank = <cursor>""
396396
\\;
397397
,
398398
\\const blank =
399399
\\\\
400400
\\;
401401
);
402+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
403+
\\for (0..42) |idx| {
404+
\\ std.debug.print("{}: {}\n<cursor>", .{ idx, my_foos[idx] });
405+
\\}
406+
,
407+
\\for (0..42) |idx| {
408+
\\ std.debug.print(
409+
\\\\{}: {}
410+
\\\\
411+
\\, .{ idx, my_foos[idx] });
412+
\\}
413+
);
414+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
415+
\\const s1 = <cursor>"\t";
416+
,
417+
\\const s1 =
418+
\\\\
419+
\\;
420+
);
421+
try testUserCodeAction(.{ .str_kind_conv = .@"string literal to multiline string" },
422+
\\const s1 = <cursor>"pre text\tpost text";
423+
,
424+
\\const s1 =
425+
\\\\pre text post text
426+
\\;
427+
);
402428
}
403429

404430
test "multiline string literal to string literal" {
405-
try testUserCodeAction(
431+
try testUserCodeAction(.{ .str_kind_conv = .@"multiline string to string literal" },
406432
\\const bleh =
407433
\\ \\hello
408434
\\ \\world<cursor>
@@ -415,7 +441,7 @@ test "multiline string literal to string literal" {
415441
\\ \\oh?
416442
\\;
417443
);
418-
try testUserCodeAction(
444+
try testUserCodeAction(.{ .str_kind_conv = .@"multiline string to string literal" },
419445
\\std.debug.print(
420446
\\\\Hi<cursor>
421447
\\\\Hey
@@ -427,7 +453,7 @@ test "multiline string literal to string literal" {
427453
\\"Hi\nHey\nHello\n"
428454
\\, .{});
429455
);
430-
try testUserCodeAction(
456+
try testUserCodeAction(.{ .str_kind_conv = .@"multiline string to string literal" },
431457
\\const nums =
432458
\\ \\123
433459
\\ \\456<cursor>
@@ -436,17 +462,19 @@ test "multiline string literal to string literal" {
436462
,
437463
\\const nums = "123\n456\n789";
438464
);
439-
try testUserCodeAction(
440-
\\for (0..42) |idx| {
441-
\\ std.debug.print("{}: {}\n<cursor>", .{ idx, my_foos[idx] });
442-
\\}
465+
try testUserCodeAction(.{ .str_kind_conv = .@"multiline string to string literal" },
466+
\\const s3 =
467+
\\ <cursor>\\"
468+
\\;
443469
,
444-
\\for (0..42) |idx| {
445-
\\ std.debug.print(
446-
\\\\{}: {}
447-
\\\\
448-
\\, .{ idx, my_foos[idx] });
449-
\\}
470+
\\const s3 = "\"";
471+
);
472+
try testUserCodeAction(.{ .str_kind_conv = .@"multiline string to string literal" },
473+
\\const s3 =
474+
\\ <cursor>\\\
475+
\\;
476+
,
477+
\\const s3 = "\\";
450478
);
451479
}
452480

@@ -503,7 +531,7 @@ fn testAutofixOptions(before: []const u8, after: []const u8, want_zir: bool) !vo
503531
try std.testing.expectEqualStrings(after, handle.tree.source);
504532
}
505533

506-
fn testUserCodeAction(source: []const u8, expected: []const u8) !void {
534+
fn testUserCodeAction(action_kind: zls.code_actions.UserActionKind, source: []const u8, expected: []const u8) !void {
507535
var ctx = try Context.init();
508536
defer ctx.deinit();
509537

@@ -514,7 +542,6 @@ fn testUserCodeAction(source: []const u8, expected: []const u8) !void {
514542
const uri = try ctx.addDocument(text);
515543
const handle = ctx.server.document_store.getHandle(uri).?;
516544
const pos = offsets.indexToPosition(text, cursor_idx, ctx.server.offset_encoding);
517-
518545
const params = types.CodeActionParams{
519546
.textDocument = .{ .uri = uri },
520547
.range = .{
@@ -524,29 +551,26 @@ fn testUserCodeAction(source: []const u8, expected: []const u8) !void {
524551
.context = .{ .diagnostics = &[_]zls.types.Diagnostic{} },
525552
};
526553

527-
@setEvalBranchQuota(5000);
528-
const response = try ctx.server.sendRequestSync(ctx.arena.allocator(), "textDocument/codeAction", params) orelse {
529-
std.debug.print("Server returned `null` as the result\n", .{});
530-
return error.InvalidResponse;
554+
var analyser = ctx.server.initAnalyser(handle);
555+
defer analyser.deinit();
556+
var builder = zls.code_actions.Builder{
557+
.arena = ctx.arena.allocator(),
558+
.analyser = &analyser,
559+
.handle = handle,
560+
.offset_encoding = ctx.server.offset_encoding,
531561
};
562+
var actions = std.ArrayListUnmanaged(types.CodeAction){};
532563

533-
var text_edits: std.ArrayListUnmanaged(types.TextEdit) = .{};
534-
defer text_edits.deinit(allocator);
535-
536-
for (response) |action| {
537-
const code_action = action.CodeAction;
538-
if (code_action.kind.? == .@"source.fixAll") continue;
539-
const workspace_edit = code_action.edit.?;
540-
const changes = workspace_edit.changes.?.map;
541-
try std.testing.expectEqual(@as(usize, 1), changes.count());
542-
try std.testing.expect(changes.contains(uri));
543-
544-
try text_edits.appendSlice(allocator, changes.get(uri).?);
545-
}
564+
try builder.addCodeAction(action_kind, params, &actions);
565+
try std.testing.expect(actions.items.len == 1);
566+
const code_action = actions.items[0];
567+
const workspace_edit = code_action.edit.?;
568+
const changes = workspace_edit.changes.?.map;
569+
try std.testing.expectEqual(@as(usize, 1), changes.count());
570+
try std.testing.expect(changes.contains(uri));
546571

547-
const actual = try zls.diff.applyTextEdits(allocator, text, text_edits.items, ctx.server.offset_encoding);
572+
const actual = try zls.diff.applyTextEdits(allocator, text, changes.get(uri).?, ctx.server.offset_encoding);
548573
defer allocator.free(actual);
549574
try ctx.server.document_store.refreshDocument(uri, try allocator.dupeZ(u8, actual));
550-
551575
try std.testing.expectEqualStrings(expected, handle.tree.source);
552576
}

0 commit comments

Comments
 (0)