Skip to content

Commit 4605263

Browse files
xdBronchTechatrix
andauthored
highlight matching control flow keywords (#2323)
Co-authored-by: Techatrix <[email protected]>
1 parent 3b9aa69 commit 4605263

File tree

3 files changed

+489
-66
lines changed

3 files changed

+489
-66
lines changed

src/analysis.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4605,12 +4605,13 @@ pub fn getPositionContext(
46054605
return .{ .char_literal = tok.loc };
46064606
}
46074607
},
4608+
.keyword_addrspace,
46084609
.keyword_break,
4609-
.keyword_continue,
46104610
.keyword_callconv,
4611-
.keyword_addrspace,
4611+
.keyword_continue,
46124612
.keyword_for,
46134613
.keyword_if,
4614+
.keyword_switch,
46144615
.keyword_while,
46154616
=> curr_ctx.ctx = .{ .keyword = tok.tag },
46164617
.doc_comment, .container_doc_comment => curr_ctx.ctx = .comment,

src/features/references.zig

Lines changed: 196 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,158 @@ fn symbolReferences(
289289
return builder.locations;
290290
}
291291

292+
const ControlFlowBuilder = struct {
293+
const Error = error{OutOfMemory};
294+
locations: std.ArrayListUnmanaged(types.Location) = .empty,
295+
encoding: offsets.Encoding,
296+
token_handle: Analyser.TokenWithHandle,
297+
allocator: std.mem.Allocator,
298+
label: ?[]const u8 = null,
299+
last_loop: Ast.TokenIndex,
300+
nodes: []const Ast.Node.Index,
301+
fn iter(builder: *ControlFlowBuilder, tree: Ast, node: Ast.Node.Index) Error!void {
302+
const main_token = tree.nodeMainToken(node);
303+
switch (tree.nodeTag(node)) {
304+
.@"break", .@"continue" => {
305+
if (tree.nodeData(node).opt_token_and_opt_node[0].unwrap()) |label_token| {
306+
const loop_or_switch_label = builder.label orelse return;
307+
const label = offsets.identifierTokenToNameSlice(tree, label_token);
308+
if (std.mem.eql(u8, loop_or_switch_label, label)) {
309+
try builder.add(main_token);
310+
}
311+
} else for (builder.nodes) |n| switch (tree.nodeTag(n)) {
312+
.for_simple,
313+
.@"for",
314+
.while_cont,
315+
.while_simple,
316+
.@"while",
317+
=> if (tree.nodeMainToken(n) == builder.last_loop)
318+
try builder.add(main_token),
319+
// break/continue on a switch must be labeled
320+
.@"switch",
321+
.switch_comma,
322+
=> {},
323+
else => {},
324+
};
325+
},
326+
327+
.@"while",
328+
.while_simple,
329+
.while_cont,
330+
.@"for",
331+
.for_simple,
332+
=> {
333+
const last_loop = builder.last_loop;
334+
defer builder.last_loop = last_loop;
335+
builder.last_loop = main_token;
336+
try ast.iterateChildren(tree, node, builder, Error, iter);
337+
},
338+
else => try ast.iterateChildren(tree, node, builder, Error, iter),
339+
}
340+
}
341+
342+
fn add(builder: *ControlFlowBuilder, token_index: Ast.TokenIndex) Error!void {
343+
const handle = builder.token_handle.handle;
344+
try builder.locations.append(builder.allocator, .{
345+
.uri = handle.uri,
346+
.range = offsets.tokenToRange(handle.tree, token_index, builder.encoding),
347+
});
348+
}
349+
350+
fn deinit(builder: *ControlFlowBuilder) void {
351+
builder.locations.deinit(builder.allocator);
352+
}
353+
};
354+
355+
fn controlFlowReferences(
356+
allocator: std.mem.Allocator,
357+
token_handle: Analyser.TokenWithHandle,
358+
encoding: offsets.Encoding,
359+
include_decl: bool,
360+
) error{OutOfMemory}!std.ArrayListUnmanaged(types.Location) {
361+
const handle = token_handle.handle;
362+
const tree = handle.tree;
363+
const kw_token = token_handle.token;
364+
365+
const source_index = handle.tree.tokenStart(kw_token);
366+
const nodes = try ast.nodesOverlappingIndex(allocator, tree, source_index);
367+
defer allocator.free(nodes);
368+
369+
var builder: ControlFlowBuilder = .{
370+
.allocator = allocator,
371+
.token_handle = token_handle,
372+
.encoding = encoding,
373+
.last_loop = kw_token,
374+
.nodes = nodes,
375+
};
376+
defer builder.deinit();
377+
378+
if (include_decl) {
379+
try builder.add(kw_token);
380+
}
381+
382+
switch (tree.tokenTag(kw_token)) {
383+
.keyword_continue,
384+
.keyword_break,
385+
=> {
386+
const maybe_label = blk: {
387+
if (kw_token + 2 >= tree.tokens.len) break :blk null;
388+
if (tree.tokenTag(kw_token + 1) != .colon) break :blk null;
389+
if (tree.tokenTag(kw_token + 2) != .identifier) break :blk null;
390+
break :blk offsets.identifierTokenToNameSlice(tree, kw_token + 2);
391+
};
392+
for (nodes) |node| switch (tree.nodeTag(node)) {
393+
.for_simple,
394+
.@"for",
395+
.while_cont,
396+
.while_simple,
397+
.@"while",
398+
=> {
399+
// if the break/continue is unlabeled it must belong to the first loop we encounter
400+
const main_token = tree.nodeMainToken(node);
401+
const label = maybe_label orelse break try builder.add(main_token);
402+
const loop_label = if (tree.isTokenPrecededByTags(main_token, &.{ .identifier, .colon }))
403+
offsets.identifierTokenToNameSlice(tree, main_token - 2)
404+
else
405+
continue;
406+
if (std.mem.eql(u8, label, loop_label)) {
407+
try builder.add(main_token);
408+
}
409+
},
410+
.switch_comma,
411+
.@"switch",
412+
=> {
413+
const label = maybe_label orelse continue;
414+
const main_token = tree.nodeMainToken(node);
415+
const switch_label = if (tree.tokenTag(main_token) == .identifier)
416+
offsets.identifierTokenToNameSlice(tree, main_token)
417+
else
418+
continue;
419+
if (std.mem.eql(u8, label, switch_label)) {
420+
try builder.add(
421+
// we already know the switch is labeled so we can just offset
422+
main_token + 2,
423+
);
424+
}
425+
},
426+
else => {},
427+
};
428+
},
429+
.keyword_for,
430+
.keyword_while,
431+
.keyword_switch,
432+
=> {
433+
if (tree.isTokenPrecededByTags(kw_token, &.{ .identifier, .colon }))
434+
builder.label = tree.tokenSlice(kw_token - 2);
435+
try ast.iterateChildren(tree, nodes[0], &builder, ControlFlowBuilder.Error, ControlFlowBuilder.iter);
436+
},
437+
else => {},
438+
}
439+
440+
defer builder.locations = .empty;
441+
return builder.locations;
442+
}
443+
292444
pub const Callsite = struct {
293445
uri: []const u8,
294446
call_node: Ast.Node.Index,
@@ -446,47 +598,60 @@ pub fn referencesHandler(server: *Server, arena: std.mem.Allocator, request: Gen
446598
if (handle.tree.mode == .zon) return null;
447599

448600
const source_index = offsets.positionToIndex(handle.tree.source, request.position(), server.offset_encoding);
449-
const name_loc = Analyser.identifierLocFromIndex(handle.tree, source_index) orelse return null;
450-
const name = offsets.locToSlice(handle.tree.source, name_loc);
451601
const pos_context = try Analyser.getPositionContext(server.allocator, handle.tree, source_index, true);
452602

453603
var analyser = server.initAnalyser(arena, handle);
454604
defer analyser.deinit();
455605

456-
// TODO: Make this work with branching types
457-
const decl = switch (pos_context) {
458-
.var_access => try analyser.lookupSymbolGlobal(handle, name, source_index),
459-
.field_access => |loc| z: {
460-
const held_loc = offsets.locMerge(loc, name_loc);
461-
const a = try analyser.getSymbolFieldAccesses(arena, handle, source_index, held_loc, name);
462-
if (a) |b| {
463-
if (b.len != 0) break :z b[0];
464-
}
465-
466-
break :z null;
467-
},
468-
.label_access, .label_decl => try Analyser.lookupLabel(handle, name, source_index),
469-
.enum_literal => try analyser.getSymbolEnumLiteral(handle, source_index, name),
470-
else => null,
471-
} orelse return null;
472-
473606
const include_decl = switch (request) {
474607
.references => |ref| ref.context.includeDeclaration,
475608
else => true,
476609
};
477610

478-
const locations = if (decl.decl == .label)
479-
try labelReferences(arena, decl, server.offset_encoding, include_decl)
480-
else
481-
try symbolReferences(
482-
arena,
483-
&analyser,
484-
request,
485-
decl,
486-
server.offset_encoding,
487-
include_decl,
488-
server.config.skip_std_references,
489-
);
611+
// TODO: Make this work with branching types
612+
const locations = locs: {
613+
if (pos_context == .keyword and request != .rename) {
614+
break :locs try controlFlowReferences(
615+
arena,
616+
.{ .token = offsets.sourceIndexToTokenIndex(handle.tree, source_index).preferLeft(), .handle = handle },
617+
server.offset_encoding,
618+
include_decl,
619+
);
620+
}
621+
622+
const name_loc = Analyser.identifierLocFromIndex(handle.tree, source_index) orelse return null;
623+
const name = offsets.locToSlice(handle.tree.source, name_loc);
624+
625+
const decl = switch (pos_context) {
626+
.var_access => try analyser.lookupSymbolGlobal(handle, name, source_index),
627+
.field_access => |loc| z: {
628+
const held_loc = offsets.locMerge(loc, name_loc);
629+
const a = try analyser.getSymbolFieldAccesses(arena, handle, source_index, held_loc, name);
630+
if (a) |b| {
631+
if (b.len != 0) break :z b[0];
632+
}
633+
634+
break :z null;
635+
},
636+
.label_access, .label_decl => try Analyser.lookupLabel(handle, name, source_index),
637+
.enum_literal => try analyser.getSymbolEnumLiteral(handle, source_index, name),
638+
.keyword => null,
639+
else => null,
640+
} orelse return null;
641+
642+
break :locs switch (decl.decl) {
643+
.label => try labelReferences(arena, decl, server.offset_encoding, include_decl),
644+
else => try symbolReferences(
645+
arena,
646+
&analyser,
647+
request,
648+
decl,
649+
server.offset_encoding,
650+
include_decl,
651+
server.config.skip_std_references,
652+
),
653+
};
654+
};
490655

491656
switch (request) {
492657
.rename => |rename| {

0 commit comments

Comments
 (0)