diff --git a/src/location_history.zig b/src/location_history.zig index 6a90240..a6a0ee3 100644 --- a/src/location_history.zig +++ b/src/location_history.zig @@ -32,14 +32,16 @@ pub fn deinit(self: *Self) void { const Process = struct { arena: std.heap.ArenaAllocator, a: std.mem.Allocator, - pos: usize = 0, - records: std.ArrayList(Entry), + backwards: std.ArrayList(Entry), + current: ?Entry = null, + forwards: std.ArrayList(Entry), receiver: Receiver, const Receiver = tp.Receiver(*Process); const outer_a = std.heap.page_allocator; const Entry = struct { + file_path: []const u8, cursor: Cursor, selection: ?Selection = null, }; @@ -49,7 +51,8 @@ const Process = struct { self.* = .{ .arena = std.heap.ArenaAllocator.init(outer_a), .a = self.arena.allocator(), - .records = std.ArrayList(Entry).init(self.a), + .backwards = std.ArrayList(Entry).init(self.a), + .forwards = std.ArrayList(Entry).init(self.a), .receiver = Receiver.init(Process.receive, self), }; return tp.spawn_link(self.a, self, Process.start, module_name) catch |e| tp.exit_error(e); @@ -61,22 +64,40 @@ const Process = struct { } fn deinit(self: *Process) void { - self.records.deinit(); + self.clear_backwards(); + self.clear_forwards(); + self.backwards.deinit(); + self.forwards.deinit(); + if (self.current) |entry| self.a.free(entry.file_path); self.arena.deinit(); outer_a.destroy(self); } + fn clear_backwards(self: *Process) void { + return self.clear_table(&self.backwards); + } + + fn clear_forwards(self: *Process) void { + return self.clear_table(&self.forwards); + } + + fn clear_table(self: *Process, table: *std.ArrayList(Entry)) void { + for (table.items) |entry| self.a.free(entry.file_path); + table.clearAndFree(); + } + fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result { errdefer self.deinit(); var c: Cursor = .{}; var s: Selection = .{}; var cb: usize = 0; + var file_path: []const u8 = undefined; - return if (try m.match(.{ "A", tp.extract(&c.col), tp.extract(&c.row) })) - self.add(.{ .cursor = c }) - else if (try m.match(.{ "A", tp.extract(&c.col), tp.extract(&c.row), tp.extract(&s.begin.row), tp.extract(&s.begin.col), tp.extract(&s.end.row), tp.extract(&s.end.col) })) - self.add(.{ .cursor = c, .selection = s }) + return if (try m.match(.{ "U", tp.extract(&file_path), tp.extract(&c.col), tp.extract(&c.row) })) + self.update(.{ .file_path = file_path, .cursor = c }) + else if (try m.match(.{ "U", tp.extract(&file_path), tp.extract(&c.col), tp.extract(&c.row), tp.extract(&s.begin.row), tp.extract(&s.begin.col), tp.extract(&s.end.row), tp.extract(&s.end.col) })) + self.update(.{ .file_path = file_path, .cursor = c, .selection = s }) else if (try m.match(.{ "B", tp.extract(&cb) })) self.back(from, cb) else if (try m.match(.{ "F", tp.extract(&cb) })) @@ -85,58 +106,62 @@ const Process = struct { tp.exit_normal(); } - fn add(self: *Process, entry: Entry) tp.result { - if (self.records.items.len == 0) - return self.records.append(entry) catch |e| tp.exit_error(e); + fn update(self: *Process, entry_: Entry) tp.result { + const entry: Entry = .{ + .file_path = self.a.dupe(u8, entry_.file_path) catch |e| return tp.exit_error(e), + .cursor = entry_.cursor, + .selection = entry_.selection, + }; + errdefer self.a.free(entry.file_path); + defer self.current = entry; - if (entry.cursor.row == self.records.items[self.pos].cursor.row) { - self.records.items[self.pos] = entry; - return; + if (isdupe(self.current, entry)) + return self.a.free(self.current.?.file_path); + + if (isdupe(self.backwards.getLastOrNull(), entry)) { + if (self.current) |current| self.forwards.append(current) catch {}; + const top = self.backwards.pop(); + self.a.free(top.file_path); + tp.trace(tp.channel.all, tp.message.fmt(.{ "location", "back", entry.file_path, entry.cursor.row, entry.cursor.col, self.backwards.items.len, self.forwards.items.len })); + } else if (isdupe(self.forwards.getLastOrNull(), entry)) { + if (self.current) |current| self.backwards.append(current) catch {}; + const top = self.forwards.pop(); + self.a.free(top.file_path); + tp.trace(tp.channel.all, tp.message.fmt(.{ "location", "forward", entry.file_path, entry.cursor.row, entry.cursor.col, self.backwards.items.len, self.forwards.items.len })); + } else if (self.current) |current| { + self.backwards.append(current) catch |e| return tp.exit_error(e); + tp.trace(tp.channel.all, tp.message.fmt(.{ "location", "new", current.file_path, current.cursor.row, current.cursor.col, self.backwards.items.len, self.forwards.items.len })); + self.clear_forwards(); } - - if (self.records.items.len > self.pos + 1) { - if (entry.cursor.row == self.records.items[self.pos + 1].cursor.row) - return; - } - - if (self.pos > 0) { - if (entry.cursor.row == self.records.items[self.pos - 1].cursor.row) - return; - } - - self.records.append(entry) catch |e| return tp.exit_error(e); - self.pos = self.records.items.len - 1; } - fn back(self: *Process, from: tp.pid_ref, cb_addr: usize) void { + fn isdupe(a_: ?Entry, b: Entry) bool { + return if (a_) |a| std.mem.eql(u8, a.file_path, b.file_path) and a.cursor.row == b.cursor.row else false; + } + + fn back(self: *const Process, from: tp.pid_ref, cb_addr: usize) void { const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr); - if (self.pos == 0) - return; - self.pos -= 1; - const entry = self.records.items[self.pos]; - cb(from, entry.cursor, entry.selection); + if (self.backwards.getLastOrNull()) |entry| + cb(from, entry.file_path, entry.cursor, entry.selection); } fn forward(self: *Process, from: tp.pid_ref, cb_addr: usize) void { const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr); - if (self.records.items.len == 0) return; - if (self.pos == self.records.items.len - 1) return; - self.pos += 1; - const entry = self.records.items[self.pos]; - cb(from, entry.cursor, entry.selection); + if (self.forwards.getLastOrNull()) |entry| + cb(from, entry.file_path, entry.cursor, entry.selection); } }; -pub fn add(self: Self, cursor: Cursor, selection: ?Selection) void { +pub fn update(self: Self, file_path: []const u8, cursor: Cursor, selection: ?Selection) void { if (self.pid) |pid| { if (selection) |sel| - pid.send(.{ "A", cursor.col, cursor.row, sel.begin.row, sel.begin.col, sel.end.row, sel.end.col }) catch {} + pid.send(.{ "U", file_path, cursor.col, cursor.row, sel.begin.row, sel.begin.col, sel.end.row, sel.end.col }) catch {} else - pid.send(.{ "A", cursor.col, cursor.row }) catch {}; + pid.send(.{ "U", file_path, cursor.col, cursor.row }) catch {}; } } -pub const CallBack = fn (from: tp.pid_ref, cursor: Cursor, selection: ?Selection) void; +pub const CallBack = fn (from: tp.pid_ref, file_path: []const u8, cursor: Cursor, selection: ?Selection) void; pub fn back(self: Self, cb: *const CallBack) tp.result { if (self.pid) |pid| try pid.send(.{ "B", @intFromPtr(cb) }); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 711b7ca..4187833 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -296,16 +296,20 @@ pub fn handle_editor_event(self: *Self, _: tp.pid_ref, m: tp.message) tp.result pub fn location_update(self: *Self, m: tp.message) tp.result { var row: usize = 0; var col: usize = 0; + const file_path = (self.editor orelse return).file_path orelse return; - if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col) })) - return self.location_history.add(.{ .row = row + 1, .col = col + 1 }, null); + if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col) })) { + if (row == 0 and col == 0) return; + return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, null); + } var sel: location_history.Selection = .{}; if (try m.match(.{ tp.any, tp.any, tp.any, tp.extract(&row), tp.extract(&col), tp.extract(&sel.begin.row), tp.extract(&sel.begin.col), tp.extract(&sel.end.row), tp.extract(&sel.end.col) })) - return self.location_history.add(.{ .row = row + 1, .col = col + 1 }, sel); + return self.location_history.update(file_path, .{ .row = row + 1, .col = col + 1 }, sel); } -fn location_jump(from: tp.pid_ref, cursor: location_history.Cursor, selection: ?location_history.Selection) void { +fn location_jump(from: tp.pid_ref, file_path: []const u8, cursor: location_history.Cursor, selection: ?location_history.Selection) void { + from.send(.{ "cmd", "navigate", .{ .file = file_path } }) catch return; if (selection) |sel| from.send(.{ "cmd", "goto", .{ cursor.row, cursor.col, sel.begin.row, sel.begin.col, sel.end.row, sel.end.col } }) catch return else