Skip to content

Commit

Permalink
move multi Position/Range conversion functions into offsets.zig
Browse files Browse the repository at this point in the history
  • Loading branch information
Techatrix committed Oct 26, 2024
1 parent 6018f84 commit 383aedf
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 91 deletions.
33 changes: 4 additions & 29 deletions src/features/document_symbol.zig
Original file line number Diff line number Diff line change
Expand Up @@ -169,18 +169,6 @@ fn callback(ctx: *Context, tree: Ast, node: Ast.Node.Index) error{OutOfMemory}!v
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,
Expand All @@ -198,33 +186,20 @@ fn convertSymbols(
// 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){};
var mappings = std.ArrayListUnmanaged(offsets.multiple.IndexToPositionMapping){};
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;
}
offsets.multiple.indexToPositionWithMappings(tree.source, mappings.items, encoding);

return result;
}

fn convertSymbolsInternal(
from: []const Symbol,
symbol_buffer: *std.ArrayListUnmanaged(types.DocumentSymbol),
mappings: *std.ArrayListUnmanaged(IndexToPositionEntry),
mappings: *std.ArrayListUnmanaged(offsets.multiple.IndexToPositionMapping),
) []types.DocumentSymbol {
// acquire storage for exactly `from.len` symbols
const prev_len = symbol_buffer.items.len;
Expand All @@ -241,7 +216,7 @@ fn convertSymbolsInternal(
.selectionRange = undefined,
.children = convertSymbolsInternal(symbol.children.items, symbol_buffer, mappings),
};
mappings.appendSliceAssumeCapacity(&[4]IndexToPositionEntry{
mappings.appendSliceAssumeCapacity(&[4]offsets.multiple.IndexToPositionMapping{
.{ .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 },
Expand Down
67 changes: 19 additions & 48 deletions src/features/folding_range.zig
Original file line number Diff line number Diff line change
Expand Up @@ -74,60 +74,31 @@ const Builder = struct {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();

const result_locations = try builder.allocator.alloc(types.FoldingRange, builder.locations.items.len);
errdefer builder.allocator.free(result_locations);

for (builder.locations.items, result_locations) |folding_range, *result| {
result.* = .{
.startLine = undefined,
.endLine = undefined,
.kind = folding_range.kind,
};
}

// a mapping from a source index to a line character pair
const IndexToPositionEntry = struct {
output: *types.FoldingRange,
source_index: usize,
where: enum { start, end },

const Self = @This();

fn lessThan(_: void, lhs: Self, rhs: Self) bool {
return lhs.source_index < rhs.source_index;
}
};
const result_ranges = try builder.allocator.alloc(types.Range, builder.locations.items.len);
errdefer builder.allocator.free(result_ranges);

// one mapping for every start and end position
var mappings = try builder.allocator.alloc(IndexToPositionEntry, builder.locations.items.len * 2);
var mappings = try builder.allocator.alloc(offsets.multiple.IndexToPositionMapping, builder.locations.items.len * 2);
defer builder.allocator.free(mappings);

for (builder.locations.items, result_locations, 0..) |*folding_range, *result, i| {
mappings[2 * i + 0] = .{ .output = result, .source_index = folding_range.loc.start, .where = .start };
mappings[2 * i + 1] = .{ .output = result, .source_index = folding_range.loc.end, .where = .end };
for (builder.locations.items, result_ranges, 0..) |*folding_range, *result, i| {
mappings[2 * i + 0] = .{ .output = &result.start, .source_index = folding_range.loc.start };
mappings[2 * i + 1] = .{ .output = &result.end, .source_index = folding_range.loc.end };
}

// sort mappings based on their source index
std.mem.sort(IndexToPositionEntry, mappings, {}, IndexToPositionEntry.lessThan);

var last_index: usize = 0;
var last_position: types.Position = .{ .line = 0, .character = 0 };
for (mappings) |mapping| {
const index = mapping.source_index;
const position = offsets.advancePosition(builder.tree.source, last_position, last_index, index, builder.encoding);
defer last_index = index;
defer last_position = position;

switch (mapping.where) {
.start => {
mapping.output.startLine = position.line;
mapping.output.startCharacter = position.character;
},
.end => {
mapping.output.endLine = position.line;
mapping.output.endCharacter = position.character;
},
}
offsets.multiple.indexToPositionWithMappings(builder.tree.source, mappings, builder.encoding);

const result_locations = try builder.allocator.alloc(types.FoldingRange, builder.locations.items.len);
errdefer builder.allocator.free(result_locations);

for (builder.locations.items, result_ranges, result_locations) |folding_range, range, *result| {
result.* = .{
.startLine = range.start.line,
.startCharacter = range.start.character,
.endLine = range.end.line,
.endCharacter = range.end.character,
.kind = folding_range.kind,
};
}

return result_locations;
Expand Down
30 changes: 16 additions & 14 deletions src/features/inlay_hints.zig
Original file line number Diff line number Diff line change
Expand Up @@ -182,23 +182,24 @@ const Builder = struct {
}

fn getInlayHints(self: *Builder, offset_encoding: offsets.Encoding) error{OutOfMemory}![]types.InlayHint {
std.mem.sort(InlayHint, self.hints.items, {}, InlayHint.lessThan);
const source_indices = try self.arena.alloc(usize, self.hints.items.len);
for (source_indices, self.hints.items) |*index, hint| {
index.* = hint.index;
}

const positions = try self.arena.alloc(types.Position, self.hints.items.len);

var last_index: usize = 0;
var last_position: types.Position = .{ .line = 0, .character = 0 };
try offsets.multiple.indexToPosition(
self.arena,
self.handle.tree.source,
source_indices,
positions,
offset_encoding,
);

const converted_hints = try self.arena.alloc(types.InlayHint, self.hints.items.len);
for (converted_hints, self.hints.items) |*converted_hint, hint| {
const position = offsets.advancePosition(
self.handle.tree.source,
last_position,
last_index,
hint.index,
offset_encoding,
);
defer last_index = hint.index;
defer last_position = position;
converted_hint.* = types.InlayHint{
for (converted_hints, self.hints.items, positions) |*converted_hint, hint, position| {
converted_hint.* = .{
.position = position,
.label = .{ .string = hint.label },
.kind = hint.kind,
Expand All @@ -207,6 +208,7 @@ const Builder = struct {
.paddingRight = hint.kind == .Parameter,
};
}

return converted_hints;
}
};
Expand Down
114 changes: 114 additions & 0 deletions src/offsets.zig
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,120 @@ test positionInsideRange {
try std.testing.expect(!positionInsideRange(.{ .line = 3, .character = 6 }, range));
}

/// More efficient conversion functions that operate on multiple elements.
pub const multiple = struct {
/// a mapping from a source index to a line character pair
pub const IndexToPositionMapping = struct {
output: *types.Position,
source_index: usize,

fn lessThan(_: void, lhs: IndexToPositionMapping, rhs: IndexToPositionMapping) bool {
return lhs.source_index < rhs.source_index;
}
};

pub fn indexToPositionWithMappings(
text: []const u8,
mappings: []IndexToPositionMapping,
encoding: Encoding,
) void {
std.mem.sort(IndexToPositionMapping, mappings, {}, IndexToPositionMapping.lessThan);

var last_index: usize = 0;
var last_position: types.Position = .{ .line = 0, .character = 0 };
for (mappings) |mapping| {
const index = mapping.source_index;
const position = advancePosition(text, last_position, last_index, index, encoding);
defer last_index = index;
defer last_position = position;

mapping.output.* = position;
}
}

pub fn indexToPosition(
allocator: std.mem.Allocator,
text: []const u8,
source_indices: []const usize,
result_positions: []types.Position,
encoding: Encoding,
) error{OutOfMemory}!void {
std.debug.assert(source_indices.len == result_positions.len);

// one mapping for every start and end position
const mappings = try allocator.alloc(IndexToPositionMapping, source_indices.len);
defer allocator.free(mappings);

for (mappings, source_indices, result_positions) |*mapping, index, *position| {
mapping.* = .{ .output = position, .source_index = index };
}

indexToPositionWithMappings(text, mappings, encoding);
}

test "indexToPosition" {
const text =
\\hello
\\world
;

const source_indices: []const usize = &.{ 3, 9, 6, 0 };
var result_positions: [4]types.Position = undefined;
try multiple.indexToPosition(std.testing.allocator, text, source_indices, &result_positions, .@"utf-16");

try std.testing.expectEqualSlices(types.Position, &.{
.{ .line = 0, .character = 3 },
.{ .line = 1, .character = 3 },
.{ .line = 1, .character = 0 },
.{ .line = 0, .character = 0 },
}, &result_positions);
}

pub fn locToRange(
allocator: std.mem.Allocator,
text: []const u8,
locs: []const Loc,
ranges: []types.Range,
encoding: Encoding,
) error{OutOfMemory}!void {
std.debug.assert(locs.len == ranges.len);

// one mapping for every start and end position
var mappings = try allocator.alloc(IndexToPositionMapping, locs.len * 2);
defer allocator.free(mappings);

for (locs, ranges, 0..) |loc, *range, i| {
mappings[2 * i + 0] = .{ .output = &range.start, .source_index = loc.start };
mappings[2 * i + 1] = .{ .output = &range.end, .source_index = loc.end };
}

indexToPositionWithMappings(text, mappings, encoding);
}

test "locToRange" {
const text =
\\hello
\\world
;

const locs: []const Loc = &.{
.{ .start = 3, .end = 9 },
.{ .start = 6, .end = 0 },
};
var result_ranges: [2]types.Range = undefined;
try multiple.locToRange(std.testing.allocator, text, locs, &result_ranges, .@"utf-16");

try std.testing.expectEqualSlices(types.Range, &.{
.{ .start = .{ .line = 0, .character = 3 }, .end = .{ .line = 1, .character = 3 } },
.{ .start = .{ .line = 1, .character = 0 }, .end = .{ .line = 0, .character = 0 } },
}, &result_ranges);
}
};

comptime {
std.testing.refAllDecls(multiple);
}

// Helper functions

/// advance `position` which starts at `from_index` to `to_index` accounting for line breaks
Expand Down

0 comments on commit 383aedf

Please sign in to comment.