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);
|
|
}
|