240 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
			
		
		
	
	
			240 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			Zig
		
	
	
	
	
	
const std = @import("std");
 | 
						|
const Ast = std.zig.Ast;
 | 
						|
const log = std.log.scoped(.zls_document_symbol);
 | 
						|
 | 
						|
const types = @import("../lsp.zig");
 | 
						|
const offsets = @import("../offsets.zig");
 | 
						|
const ast = @import("../ast.zig");
 | 
						|
const analysis = @import("../analysis.zig");
 | 
						|
const tracy = @import("../tracy.zig");
 | 
						|
 | 
						|
const Symbol = struct {
 | 
						|
    name: []const u8,
 | 
						|
    detail: ?[]const u8 = null,
 | 
						|
    kind: types.SymbolKind,
 | 
						|
    loc: offsets.Loc,
 | 
						|
    selection_loc: offsets.Loc,
 | 
						|
    children: std.ArrayListUnmanaged(Symbol),
 | 
						|
};
 | 
						|
 | 
						|
pub const Context = struct {
 | 
						|
    arena: std.mem.Allocator,
 | 
						|
    last_var_decl_name: ?[]const u8,
 | 
						|
    parent_node: Ast.Node.Index,
 | 
						|
    parent_symbols: *std.ArrayListUnmanaged(Symbol),
 | 
						|
    total_symbol_count: *usize,
 | 
						|
};
 | 
						|
 | 
						|
fn callback(ctx: *Context, tree: Ast, node: Ast.Node.Index) error{OutOfMemory}!void {
 | 
						|
    if (node == 0) return;
 | 
						|
 | 
						|
    const node_tags = tree.nodes.items(.tag);
 | 
						|
    const main_tokens = tree.nodes.items(.main_token);
 | 
						|
    const token_tags = tree.tokens.items(.tag);
 | 
						|
 | 
						|
    const decl_name_token = analysis.getDeclNameToken(tree, node);
 | 
						|
    const decl_name = analysis.getDeclName(tree, node);
 | 
						|
 | 
						|
    var new_ctx = ctx.*;
 | 
						|
    const maybe_symbol: ?Symbol = switch (node_tags[node]) {
 | 
						|
        .global_var_decl,
 | 
						|
        .local_var_decl,
 | 
						|
        .simple_var_decl,
 | 
						|
        .aligned_var_decl,
 | 
						|
        => blk: {
 | 
						|
            new_ctx.last_var_decl_name = decl_name;
 | 
						|
            if (!ast.isContainer(tree, ctx.parent_node)) break :blk null;
 | 
						|
 | 
						|
            const kind: types.SymbolKind = switch (token_tags[main_tokens[node]]) {
 | 
						|
                .keyword_var => .Variable,
 | 
						|
                .keyword_const => .Constant,
 | 
						|
                else => unreachable,
 | 
						|
            };
 | 
						|
 | 
						|
            break :blk .{
 | 
						|
                .name = decl_name.?,
 | 
						|
                .detail = null,
 | 
						|
                .kind = kind,
 | 
						|
                .loc = offsets.nodeToLoc(tree, node),
 | 
						|
                .selection_loc = offsets.tokenToLoc(tree, decl_name_token.?),
 | 
						|
                .children = .{},
 | 
						|
            };
 | 
						|
        },
 | 
						|
 | 
						|
        .test_decl,
 | 
						|
        .fn_decl,
 | 
						|
        => |tag| blk: {
 | 
						|
            const kind: types.SymbolKind = switch (tag) {
 | 
						|
                .test_decl => .Method, // there is no SymbolKind that represents a tests
 | 
						|
                .fn_decl => .Function,
 | 
						|
                else => unreachable,
 | 
						|
            };
 | 
						|
 | 
						|
            var buffer: [1]Ast.Node.Index = undefined;
 | 
						|
            const detail = if (tree.fullFnProto(&buffer, node)) |fn_info| analysis.getFunctionSignature(tree, fn_info) else null;
 | 
						|
 | 
						|
            break :blk .{
 | 
						|
                .name = decl_name orelse break :blk null,
 | 
						|
                .detail = detail,
 | 
						|
                .kind = kind,
 | 
						|
                .loc = offsets.nodeToLoc(tree, node),
 | 
						|
                .selection_loc = offsets.tokenToLoc(tree, decl_name_token.?),
 | 
						|
                .children = .{},
 | 
						|
            };
 | 
						|
        },
 | 
						|
 | 
						|
        .container_field_init,
 | 
						|
        .container_field_align,
 | 
						|
        .container_field,
 | 
						|
        => blk: {
 | 
						|
            const kind: types.SymbolKind = switch (node_tags[ctx.parent_node]) {
 | 
						|
                .root => .Field,
 | 
						|
                .container_decl,
 | 
						|
                .container_decl_trailing,
 | 
						|
                .container_decl_arg,
 | 
						|
                .container_decl_arg_trailing,
 | 
						|
                .container_decl_two,
 | 
						|
                .container_decl_two_trailing,
 | 
						|
                => switch (token_tags[main_tokens[ctx.parent_node]]) {
 | 
						|
                    .keyword_struct => .Field,
 | 
						|
                    .keyword_union => .Field,
 | 
						|
                    .keyword_enum => .EnumMember,
 | 
						|
                    .keyword_opaque => break :blk null,
 | 
						|
                    else => unreachable,
 | 
						|
                },
 | 
						|
                .tagged_union,
 | 
						|
                .tagged_union_trailing,
 | 
						|
                .tagged_union_enum_tag,
 | 
						|
                .tagged_union_enum_tag_trailing,
 | 
						|
                .tagged_union_two,
 | 
						|
                .tagged_union_two_trailing,
 | 
						|
                => .Field,
 | 
						|
                else => unreachable,
 | 
						|
            };
 | 
						|
 | 
						|
            break :blk .{
 | 
						|
                .name = decl_name.?,
 | 
						|
                .detail = ctx.last_var_decl_name,
 | 
						|
                .kind = kind,
 | 
						|
                .loc = offsets.nodeToLoc(tree, node),
 | 
						|
                .selection_loc = offsets.tokenToLoc(tree, decl_name_token.?),
 | 
						|
                .children = .{},
 | 
						|
            };
 | 
						|
        },
 | 
						|
        else => null,
 | 
						|
    };
 | 
						|
 | 
						|
    new_ctx.parent_node = node;
 | 
						|
    if (maybe_symbol) |symbol| {
 | 
						|
        var symbol_ptr = try ctx.parent_symbols.addOne(ctx.arena);
 | 
						|
        symbol_ptr.* = symbol;
 | 
						|
        new_ctx.parent_symbols = &symbol_ptr.children;
 | 
						|
        ctx.total_symbol_count.* += 1;
 | 
						|
    }
 | 
						|
 | 
						|
    try ast.iterateChildren(tree, node, &new_ctx, error{OutOfMemory}, callback);
 | 
						|
}
 | 
						|
 | 
						|
/// a mapping from a source index to a line character pair
 | 
						|
const IndexToPositionEntry = struct {
 | 
						|
    output: *types.Position,
 | 
						|
    source_index: usize,
 | 
						|
 | 
						|
    const Self = @This();
 | 
						|
 | 
						|
    fn lessThan(_: void, lhs: Self, rhs: Self) bool {
 | 
						|
        return lhs.source_index < rhs.source_index;
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
/// converts `Symbol` to `types.DocumentSymbol`
 | 
						|
fn convertSymbols(
 | 
						|
    arena: std.mem.Allocator,
 | 
						|
    tree: Ast,
 | 
						|
    from: []const Symbol,
 | 
						|
    total_symbol_count: usize,
 | 
						|
    encoding: offsets.Encoding,
 | 
						|
) error{OutOfMemory}![]types.DocumentSymbol {
 | 
						|
    const tracy_zone = tracy.trace(@src());
 | 
						|
    defer tracy_zone.end();
 | 
						|
 | 
						|
    var symbol_buffer = std.ArrayListUnmanaged(types.DocumentSymbol){};
 | 
						|
    try symbol_buffer.ensureTotalCapacityPrecise(arena, total_symbol_count);
 | 
						|
 | 
						|
    // instead of converting every `offsets.Loc` to `types.Range` by calling `offsets.locToRange`
 | 
						|
    // we instead store a mapping from source indices to their desired position, sort them by their source index
 | 
						|
    // and then iterate through them which avoids having to re-iterate through the source file to find out the line number
 | 
						|
    // this reduces algorithmic complexity from `O(file_size*symbol_count)` to `O(symbol_count*log(symbol_count))`
 | 
						|
    var mappings = std.ArrayListUnmanaged(IndexToPositionEntry){};
 | 
						|
    try mappings.ensureTotalCapacityPrecise(arena, total_symbol_count * 4);
 | 
						|
 | 
						|
    const result = convertSymbolsInternal(from, &symbol_buffer, &mappings);
 | 
						|
 | 
						|
    // sort items based on their source position
 | 
						|
    std.mem.sort(IndexToPositionEntry, mappings.items, {}, IndexToPositionEntry.lessThan);
 | 
						|
 | 
						|
    var last_index: usize = 0;
 | 
						|
    var last_position: types.Position = .{ .line = 0, .character = 0 };
 | 
						|
    for (mappings.items) |mapping| {
 | 
						|
        const index = mapping.source_index;
 | 
						|
        const position = offsets.advancePosition(tree.source, last_position, last_index, index, encoding);
 | 
						|
        defer last_index = index;
 | 
						|
        defer last_position = position;
 | 
						|
 | 
						|
        mapping.output.* = position;
 | 
						|
    }
 | 
						|
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
fn convertSymbolsInternal(
 | 
						|
    from: []const Symbol,
 | 
						|
    symbol_buffer: *std.ArrayListUnmanaged(types.DocumentSymbol),
 | 
						|
    mappings: *std.ArrayListUnmanaged(IndexToPositionEntry),
 | 
						|
) []types.DocumentSymbol {
 | 
						|
    // acquire storage for exactly `from.len` symbols
 | 
						|
    const prev_len = symbol_buffer.items.len;
 | 
						|
    symbol_buffer.items.len += from.len;
 | 
						|
    const to: []types.DocumentSymbol = symbol_buffer.items[prev_len..];
 | 
						|
 | 
						|
    for (from, to) |symbol, *out| {
 | 
						|
        out.* = .{
 | 
						|
            .name = symbol.name,
 | 
						|
            .detail = symbol.detail,
 | 
						|
            .kind = symbol.kind,
 | 
						|
            // will be set later through the mapping below
 | 
						|
            .range = undefined,
 | 
						|
            .selectionRange = undefined,
 | 
						|
            .children = convertSymbolsInternal(symbol.children.items, symbol_buffer, mappings),
 | 
						|
        };
 | 
						|
        mappings.appendSliceAssumeCapacity(&[4]IndexToPositionEntry{
 | 
						|
            .{ .output = &out.range.start, .source_index = symbol.loc.start },
 | 
						|
            .{ .output = &out.selectionRange.start, .source_index = symbol.selection_loc.start },
 | 
						|
            .{ .output = &out.selectionRange.end, .source_index = symbol.selection_loc.end },
 | 
						|
            .{ .output = &out.range.end, .source_index = symbol.loc.end },
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    return to;
 | 
						|
}
 | 
						|
 | 
						|
pub fn getDocumentSymbols(
 | 
						|
    arena: std.mem.Allocator,
 | 
						|
    tree: Ast,
 | 
						|
    encoding: offsets.Encoding,
 | 
						|
) error{OutOfMemory}![]types.DocumentSymbol {
 | 
						|
    var root_symbols = std.ArrayListUnmanaged(Symbol){};
 | 
						|
    var total_symbol_count: usize = 0;
 | 
						|
 | 
						|
    var ctx = Context{
 | 
						|
        .arena = arena,
 | 
						|
        .last_var_decl_name = null,
 | 
						|
        .parent_node = 0, // root-node
 | 
						|
        .parent_symbols = &root_symbols,
 | 
						|
        .total_symbol_count = &total_symbol_count,
 | 
						|
    };
 | 
						|
    try ast.iterateChildren(tree, 0, &ctx, error{OutOfMemory}, callback);
 | 
						|
 | 
						|
    return try convertSymbols(arena, tree, root_symbols.items, ctx.total_symbol_count.*, encoding);
 | 
						|
}
 |