227 lines
8.3 KiB
Zig
227 lines
8.3 KiB
Zig
const std = @import("std");
|
|
const tp = @import("thespian");
|
|
const diffz = @import("diffz");
|
|
const Buffer = @import("Buffer");
|
|
const tracy = @import("tracy");
|
|
|
|
const module_name = @typeName(@This());
|
|
|
|
const diff_ = @import("diff.zig");
|
|
const Diff = diff_.LineDiff;
|
|
const Kind = diff_.LineDiffKind;
|
|
|
|
pub fn create() !AsyncDiffer {
|
|
return .{ .pid = try Process.create() };
|
|
}
|
|
|
|
pub const AsyncDiffer = struct {
|
|
pid: ?tp.pid,
|
|
|
|
pub fn deinit(self: *@This()) void {
|
|
if (self.pid) |pid| {
|
|
pid.send(.{"shutdown"}) catch {};
|
|
pid.deinit();
|
|
self.pid = null;
|
|
}
|
|
}
|
|
|
|
pub const CallBack = fn (from: tp.pid_ref, data: usize, edits: []Diff) void;
|
|
|
|
pub fn diff_buffer(self: @This(), cb: *const CallBack, cb_data: usize, buffer: *Buffer) tp.result {
|
|
const allocator = std.heap.c_allocator;
|
|
const eol_mode = buffer.file_eol_mode;
|
|
const text_dst = allocator.dupe(u8, buffer.store_to_string_cached(buffer.root, eol_mode)) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
|
errdefer allocator.free(text_dst);
|
|
const text_src = allocator.dupe(
|
|
u8,
|
|
if (buffer.get_vcs_content()) |vcs_content|
|
|
vcs_content
|
|
else
|
|
buffer.store_last_save_to_string_cached(eol_mode) orelse return,
|
|
) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
|
errdefer allocator.free(text_src);
|
|
const text_dst_ptr: usize = if (text_dst.len > 0) @intFromPtr(text_dst.ptr) else 0;
|
|
const text_src_ptr: usize = if (text_src.len > 0) @intFromPtr(text_src.ptr) else 0;
|
|
if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), cb_data, text_dst_ptr, text_dst.len, text_src_ptr, text_src.len });
|
|
}
|
|
};
|
|
|
|
const Process = struct {
|
|
receiver: Receiver,
|
|
|
|
const Receiver = tp.Receiver(*Process);
|
|
const allocator = std.heap.c_allocator;
|
|
|
|
pub fn create() !tp.pid {
|
|
const self = try allocator.create(Process);
|
|
errdefer allocator.destroy(self);
|
|
self.* = .{
|
|
.receiver = Receiver.init(Process.receive, self),
|
|
};
|
|
return tp.spawn_link(allocator, self, Process.start, module_name);
|
|
}
|
|
|
|
fn start(self: *Process) tp.result {
|
|
errdefer self.deinit();
|
|
tp.receive(&self.receiver);
|
|
}
|
|
|
|
fn deinit(self: *Process) void {
|
|
allocator.destroy(self);
|
|
}
|
|
|
|
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
|
|
errdefer self.deinit();
|
|
|
|
var cb: usize = 0;
|
|
var cb_data: usize = 0;
|
|
var text_dst_ptr: usize = 0;
|
|
var text_dst_len: usize = 0;
|
|
var text_src_ptr: usize = 0;
|
|
var text_src_len: usize = 0;
|
|
|
|
return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&cb_data), tp.extract(&text_dst_ptr), tp.extract(&text_dst_len), tp.extract(&text_src_ptr), tp.extract(&text_src_len) })) blk: {
|
|
const text_dst = if (text_dst_len > 0) @as([*]const u8, @ptrFromInt(text_dst_ptr))[0..text_dst_len] else "";
|
|
const text_src = if (text_src_len > 0) @as([*]const u8, @ptrFromInt(text_src_ptr))[0..text_src_len] else "";
|
|
const cb_: *AsyncDiffer.CallBack = if (cb == 0) return else @ptrFromInt(cb);
|
|
break :blk do_diff_async(from, cb_, cb_data, text_dst, text_src) catch |e| {
|
|
cb_(from, cb_data, &.{});
|
|
break :blk tp.exit_error(e, @errorReturnTrace());
|
|
};
|
|
} else if (try m.match(.{"shutdown"}))
|
|
tp.exit_normal();
|
|
}
|
|
|
|
fn do_diff_async(from: tp.pid_ref, cb: *AsyncDiffer.CallBack, cb_data: usize, text_dst: []const u8, text_src: []const u8) !void {
|
|
defer std.heap.c_allocator.free(text_dst);
|
|
defer std.heap.c_allocator.free(text_src);
|
|
|
|
var arena_ = std.heap.ArenaAllocator.init(allocator);
|
|
defer arena_.deinit();
|
|
const arena = arena_.allocator();
|
|
|
|
const edits = try diff(arena, text_dst, text_src);
|
|
cb(from, cb_data, edits);
|
|
}
|
|
};
|
|
|
|
pub fn diff(allocator: std.mem.Allocator, dst: []const u8, src: []const u8) error{OutOfMemory}![]Diff {
|
|
var arena_ = std.heap.ArenaAllocator.init(allocator);
|
|
defer arena_.deinit();
|
|
const arena = arena_.allocator();
|
|
const frame = tracy.initZone(@src(), .{ .name = "diff" });
|
|
defer frame.deinit();
|
|
|
|
var diffs: std.ArrayList(Diff) = .empty;
|
|
errdefer diffs.deinit(allocator);
|
|
|
|
const dmp = diffz.default;
|
|
var diff_list = try diffz.diff(&dmp, arena, src, dst, false);
|
|
try diffz.diffCleanupSemanticLossless(arena, &diff_list);
|
|
|
|
if (diff_list.items.len > 2)
|
|
try diffs.ensureTotalCapacity(allocator, (diff_list.items.len - 1) / 2);
|
|
|
|
var lines_dst: usize = 0;
|
|
var pos_dst: usize = 0;
|
|
var last_offset: usize = 0;
|
|
var last_equal_char: u8 = '\n';
|
|
for (diff_list.items) |diffz_diff| {
|
|
switch (diffz_diff.operation) {
|
|
.equal => {
|
|
const dist = diffz_diff.text.len;
|
|
pos_dst += dist;
|
|
scan_eol(diffz_diff.text, &lines_dst, &last_offset);
|
|
last_equal_char = diffz_diff.text[diffz_diff.text.len - 1];
|
|
},
|
|
.insert => {
|
|
const dist = diffz_diff.text.len;
|
|
pos_dst += dist;
|
|
var line_start_dst: usize = lines_dst;
|
|
scan_eol(diffz_diff.text, &lines_dst, &last_offset);
|
|
const n_lines = lines_dst - line_start_dst;
|
|
|
|
if (n_lines == 0) {
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
} else {
|
|
if (diffz_diff.text[0] != '\n') {
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
line_start_dst += 1;
|
|
}
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .insert,
|
|
.line = line_start_dst,
|
|
.lines = n_lines,
|
|
};
|
|
line_start_dst += n_lines;
|
|
|
|
if (last_offset > 0) {
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
}
|
|
}
|
|
},
|
|
.delete => {
|
|
pos_dst += 0;
|
|
var lines: usize = 0;
|
|
var diff_offset: usize = 0;
|
|
var line_start_dst: usize = lines_dst;
|
|
scan_eol(diffz_diff.text, &lines, &diff_offset);
|
|
if (lines == 0) {
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
} else {
|
|
if (last_equal_char != '\n') {
|
|
// Deleting starts in previous line
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
line_start_dst += 1;
|
|
}
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .delete,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
|
|
if (diff_offset > 0 and last_equal_char == '\n') {
|
|
(try diffs.addOne(allocator)).* = .{
|
|
.kind = .modify,
|
|
.line = line_start_dst,
|
|
.lines = 1,
|
|
};
|
|
}
|
|
}
|
|
},
|
|
}
|
|
}
|
|
return diffs.toOwnedSlice(allocator);
|
|
}
|
|
|
|
fn scan_eol(chars: []const u8, lines: *usize, remain: *usize) void {
|
|
var pos = chars;
|
|
remain.* += pos.len;
|
|
while (pos.len > 0) {
|
|
if (pos[0] == '\n') {
|
|
remain.* = pos.len - 1;
|
|
lines.* += 1;
|
|
}
|
|
pos = pos[1..];
|
|
}
|
|
}
|