diff --git a/src/features/document_symbol.zig b/src/features/document_symbol.zig index 2edfab1a7..df691921f 100644 --- a/src/features/document_symbol.zig +++ b/src/features/document_symbol.zig @@ -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, @@ -198,25 +186,12 @@ 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; } @@ -224,7 +199,7 @@ fn convertSymbols( 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; @@ -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 }, diff --git a/src/features/folding_range.zig b/src/features/folding_range.zig index 5dbe76e69..f56651310 100644 --- a/src/features/folding_range.zig +++ b/src/features/folding_range.zig @@ -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; diff --git a/src/features/inlay_hints.zig b/src/features/inlay_hints.zig index 4f07f253c..19cb4f3ee 100644 --- a/src/features/inlay_hints.zig +++ b/src/features/inlay_hints.zig @@ -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, @@ -207,6 +208,7 @@ const Builder = struct { .paddingRight = hint.kind == .Parameter, }; } + return converted_hints; } }; diff --git a/src/offsets.zig b/src/offsets.zig index 39db5988a..2fea8294d 100644 --- a/src/offsets.zig +++ b/src/offsets.zig @@ -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