make diff.edits
memory safe (#1026)
This commit is contained in:
parent
ed908a2511
commit
89ab9fdf70
32
src/diff.zig
32
src/diff.zig
@ -7,7 +7,7 @@ const dmp = DiffMatchPatch{
|
|||||||
.diff_timeout = 250,
|
.diff_timeout = 250,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Error = error{ OutOfMemory, InvalidRange, UnknownError };
|
pub const Error = error{OutOfMemory};
|
||||||
|
|
||||||
pub fn edits(
|
pub fn edits(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
@ -15,8 +15,25 @@ pub fn edits(
|
|||||||
after: []const u8,
|
after: []const u8,
|
||||||
encoding: offsets.Encoding,
|
encoding: offsets.Encoding,
|
||||||
) Error!std.ArrayListUnmanaged(types.TextEdit) {
|
) Error!std.ArrayListUnmanaged(types.TextEdit) {
|
||||||
var diffs = try dmp.diff(allocator, before, after, true);
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
var diffs = try dmp.diff(arena.allocator(), before, after, true);
|
||||||
|
|
||||||
|
var edit_count: usize = 0;
|
||||||
|
for (diffs.items) |diff| {
|
||||||
|
switch (diff.operation) {
|
||||||
|
.delete => edit_count += 1,
|
||||||
|
.equal => continue,
|
||||||
|
.insert => edit_count += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var eds = std.ArrayListUnmanaged(types.TextEdit){};
|
var eds = std.ArrayListUnmanaged(types.TextEdit){};
|
||||||
|
try eds.ensureTotalCapacity(allocator, edit_count);
|
||||||
|
errdefer {
|
||||||
|
for (eds.items) |edit| allocator.free(edit.newText);
|
||||||
|
eds.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
var offset: usize = 0;
|
var offset: usize = 0;
|
||||||
for (diffs.items) |diff| {
|
for (diffs.items) |diff| {
|
||||||
@ -24,13 +41,19 @@ pub fn edits(
|
|||||||
switch (diff.operation) {
|
switch (diff.operation) {
|
||||||
.delete => {
|
.delete => {
|
||||||
offset += diff.text.len;
|
offset += diff.text.len;
|
||||||
try eds.append(allocator, .{ .range = offsets.locToRange(before, .{ .start = start, .end = offset }, encoding), .newText = "" });
|
eds.appendAssumeCapacity(.{
|
||||||
|
.range = offsets.locToRange(before, .{ .start = start, .end = offset }, encoding),
|
||||||
|
.newText = "",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
.equal => {
|
.equal => {
|
||||||
offset += diff.text.len;
|
offset += diff.text.len;
|
||||||
},
|
},
|
||||||
.insert => {
|
.insert => {
|
||||||
try eds.append(allocator, .{ .range = offsets.locToRange(before, .{ .start = start, .end = start }, encoding), .newText = diff.text });
|
eds.appendAssumeCapacity(.{
|
||||||
|
.range = offsets.locToRange(before, .{ .start = start, .end = start }, encoding),
|
||||||
|
.newText = try allocator.dupe(u8, diff.text),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -95,6 +118,7 @@ pub fn applyTextEdits(
|
|||||||
std.sort.sort(types.TextEdit, text_edits_sortable, {}, textEditLessThan);
|
std.sort.sort(types.TextEdit, text_edits_sortable, {}, textEditLessThan);
|
||||||
|
|
||||||
var final_text = std.ArrayListUnmanaged(u8){};
|
var final_text = std.ArrayListUnmanaged(u8){};
|
||||||
|
errdefer final_text.deinit(allocator);
|
||||||
|
|
||||||
var last: usize = 0;
|
var last: usize = 0;
|
||||||
for (text_edits_sortable) |te| {
|
for (text_edits_sortable) |te| {
|
||||||
|
@ -1,30 +1,37 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zls = @import("zls");
|
const zls = @import("zls");
|
||||||
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
|
|
||||||
fn gen(alloc: std.mem.Allocator, rand: std.rand.Random) ![]const u8 {
|
fn gen(alloc: std.mem.Allocator, rand: std.rand.Random) ![]const u8 {
|
||||||
var buffer = try alloc.alloc(u8, rand.intRangeAtMost(usize, 16, 1024));
|
var buffer = try alloc.alloc(u8, rand.intRangeAtMost(usize, 0, 256));
|
||||||
for (buffer) |*b| b.* = rand.intRangeAtMost(u8, 32, 126);
|
for (buffer) |*b| b.* = rand.intRangeAtMost(u8, ' ', '~');
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
test "diff - random" {
|
test "diff - random" {
|
||||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
const allocator = std.testing.allocator;
|
||||||
defer arena.deinit();
|
try std.testing.checkAllAllocationFailures(allocator, testDiff, .{ 0, .@"utf-8" });
|
||||||
|
for (0..30) |i| {
|
||||||
var rand = std.rand.DefaultPrng.init(0);
|
try testDiff(allocator, i, .@"utf-8");
|
||||||
|
try testDiff(allocator, i, .@"utf-16");
|
||||||
var index: usize = 0;
|
try testDiff(allocator, i, .@"utf-32");
|
||||||
|
|
||||||
while (index < 100) : (index += 1) {
|
|
||||||
defer _ = arena.reset(.retain_capacity);
|
|
||||||
|
|
||||||
const pre = try gen(arena.allocator(), rand.random());
|
|
||||||
const post = try gen(arena.allocator(), rand.random());
|
|
||||||
|
|
||||||
var edits = try zls.diff.edits(arena.allocator(), pre, post, .@"utf-8");
|
|
||||||
const applied = try zls.diff.applyTextEdits(arena.allocator(), pre, edits.items, .@"utf-8");
|
|
||||||
try std.testing.expectEqualStrings(post, applied);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn testDiff(allocator: std.mem.Allocator, seed: u64, encoding: zls.offsets.Encoding) !void {
|
||||||
|
var rand = std.rand.DefaultPrng.init(seed);
|
||||||
|
const before = try gen(allocator, rand.random());
|
||||||
|
defer allocator.free(before);
|
||||||
|
const after = try gen(allocator, rand.random());
|
||||||
|
defer allocator.free(after);
|
||||||
|
|
||||||
|
var edits = try zls.diff.edits(allocator, before, after, encoding);
|
||||||
|
defer {
|
||||||
|
for (edits.items) |edit| allocator.free(edit.newText);
|
||||||
|
edits.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const applied = try zls.diff.applyTextEdits(allocator, before, edits.items, encoding);
|
||||||
|
defer allocator.free(applied);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(after, applied);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user