flow/src/diff.zig

164 lines
5.6 KiB
Zig

const std = @import("std");
const tp = @import("thespian");
const dizzy = @import("dizzy");
const Buffer = @import("Buffer");
const tracy = @import("tracy");
const cbor = @import("cbor");
const Self = @This();
const module_name = @typeName(Self);
pub const Kind = enum { insert, delete };
pub const Edit = struct {
kind: Kind,
line: usize,
offset: usize,
bytes: []const u8,
};
pid: ?tp.pid,
pub fn create() !Self {
return .{ .pid = try Process.create() };
}
pub fn deinit(self: *Self) void {
if (self.pid) |pid| {
pid.send(.{"shutdown"}) catch {};
pid.deinit();
self.pid = null;
}
}
const Process = struct {
arena: std.heap.ArenaAllocator,
a: std.mem.Allocator,
receiver: Receiver,
const Receiver = tp.Receiver(*Process);
const outer_a = std.heap.page_allocator;
pub fn create() !tp.pid {
const self = try outer_a.create(Process);
self.* = .{
.arena = std.heap.ArenaAllocator.init(outer_a),
.a = self.arena.allocator(),
.receiver = Receiver.init(Process.receive, self),
};
return tp.spawn_link(self.a, self, Process.start, module_name);
}
fn start(self: *Process) tp.result {
errdefer self.deinit();
tp.receive(&self.receiver);
}
fn deinit(self: *Process) void {
self.arena.deinit();
outer_a.destroy(self);
}
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
errdefer self.deinit();
var cb: usize = 0;
var root_dst: usize = 0;
var root_src: usize = 0;
return if (try m.match(.{ "D", tp.extract(&cb), tp.extract(&root_dst), tp.extract(&root_src) }))
self.diff(from, cb, root_dst, root_src) catch |e| tp.exit_error(e, @errorReturnTrace())
else if (try m.match(.{"shutdown"}))
tp.exit_normal();
}
fn diff(self: *Process, from: tp.pid_ref, cb_addr: usize, root_new_addr: usize, root_old_addr: usize) !void {
const frame = tracy.initZone(@src(), .{ .name = "diff" });
defer frame.deinit();
const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr);
const root_dst: Buffer.Root = if (root_new_addr == 0) return else @ptrFromInt(root_new_addr);
const root_src: Buffer.Root = if (root_old_addr == 0) return else @ptrFromInt(root_old_addr);
var dizzy_edits = std.ArrayListUnmanaged(dizzy.Edit){};
var dst = std.ArrayList(u8).init(self.a);
var src = std.ArrayList(u8).init(self.a);
var scratch = std.ArrayListUnmanaged(u32){};
var edits = std.ArrayList(Edit).init(self.a);
defer {
dst.deinit();
src.deinit();
scratch.deinit(self.a);
dizzy_edits.deinit(self.a);
}
try root_dst.store(dst.writer());
try root_src.store(src.writer());
const scratch_len = 4 * (dst.items.len + src.items.len) + 2;
try scratch.ensureTotalCapacity(self.a, scratch_len);
scratch.items.len = scratch_len;
try dizzy.PrimitiveSliceDiffer(u8).diff(self.a, &dizzy_edits, src.items, dst.items, scratch.items);
if (dizzy_edits.items.len > 2)
try edits.ensureTotalCapacity((dizzy_edits.items.len - 1) / 2);
var lines_dst: usize = 0;
var pos_src: usize = 0;
var pos_dst: usize = 0;
var last_offset: usize = 0;
for (dizzy_edits.items) |dizzy_edit| {
switch (dizzy_edit.kind) {
.equal => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += dist;
pos_dst += dist;
scan_char(src.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', &last_offset);
},
.insert => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += 0;
pos_dst += dist;
const line_start_dst: usize = lines_dst;
scan_char(dst.items[dizzy_edit.range.start..dizzy_edit.range.end], &lines_dst, '\n', null);
(try edits.addOne()).* = .{
.kind = .insert,
.line = line_start_dst,
.offset = last_offset,
.bytes = dst.items[dizzy_edit.range.start..dizzy_edit.range.end],
};
},
.delete => {
const dist = dizzy_edit.range.end - dizzy_edit.range.start;
pos_src += dist;
pos_dst += 0;
(try edits.addOne()).* = .{
.kind = .delete,
.line = lines_dst,
.offset = last_offset,
.bytes = src.items[dizzy_edit.range.start..dizzy_edit.range.end],
};
},
}
}
cb(from, edits.items);
}
fn scan_char(chars: []const u8, lines: *usize, char: u8, last_offset: ?*usize) void {
var pos = chars;
while (pos.len > 0) {
if (pos[0] == char) {
if (last_offset) |off| off.* = pos.len - 1;
lines.* += 1;
}
pos = pos[1..];
}
}
};
pub const CallBack = fn (from: tp.pid_ref, edits: []Edit) void;
pub fn diff(self: Self, cb: *const CallBack, root_dst: Buffer.Root, root_src: Buffer.Root) tp.result {
if (self.pid) |pid| try pid.send(.{ "D", @intFromPtr(cb), @intFromPtr(root_dst), @intFromPtr(root_src) });
}