feat: add cross file location history support
Also, this improves the location history storage to be simpler and provide much more intuitive navigation.
This commit is contained in:
parent
2fafceab83
commit
695b0dbff5
2 changed files with 75 additions and 46 deletions
|
@ -32,14 +32,16 @@ pub fn deinit(self: *Self) void {
|
||||||
const Process = struct {
|
const Process = struct {
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
a: std.mem.Allocator,
|
a: std.mem.Allocator,
|
||||||
pos: usize = 0,
|
backwards: std.ArrayList(Entry),
|
||||||
records: std.ArrayList(Entry),
|
current: ?Entry = null,
|
||||||
|
forwards: std.ArrayList(Entry),
|
||||||
receiver: Receiver,
|
receiver: Receiver,
|
||||||
|
|
||||||
const Receiver = tp.Receiver(*Process);
|
const Receiver = tp.Receiver(*Process);
|
||||||
const outer_a = std.heap.page_allocator;
|
const outer_a = std.heap.page_allocator;
|
||||||
|
|
||||||
const Entry = struct {
|
const Entry = struct {
|
||||||
|
file_path: []const u8,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
selection: ?Selection = null,
|
selection: ?Selection = null,
|
||||||
};
|
};
|
||||||
|
@ -49,7 +51,8 @@ const Process = struct {
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.arena = std.heap.ArenaAllocator.init(outer_a),
|
.arena = std.heap.ArenaAllocator.init(outer_a),
|
||||||
.a = self.arena.allocator(),
|
.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),
|
.receiver = Receiver.init(Process.receive, self),
|
||||||
};
|
};
|
||||||
return tp.spawn_link(self.a, self, Process.start, module_name) catch |e| tp.exit_error(e);
|
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 {
|
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();
|
self.arena.deinit();
|
||||||
outer_a.destroy(self);
|
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 {
|
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
|
||||||
errdefer self.deinit();
|
errdefer self.deinit();
|
||||||
|
|
||||||
var c: Cursor = .{};
|
var c: Cursor = .{};
|
||||||
var s: Selection = .{};
|
var s: Selection = .{};
|
||||||
var cb: usize = 0;
|
var cb: usize = 0;
|
||||||
|
var file_path: []const u8 = undefined;
|
||||||
|
|
||||||
return if (try m.match(.{ "A", tp.extract(&c.col), tp.extract(&c.row) }))
|
return if (try m.match(.{ "U", tp.extract(&file_path), tp.extract(&c.col), tp.extract(&c.row) }))
|
||||||
self.add(.{ .cursor = c })
|
self.update(.{ .file_path = file_path, .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) }))
|
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.add(.{ .cursor = c, .selection = s })
|
self.update(.{ .file_path = file_path, .cursor = c, .selection = s })
|
||||||
else if (try m.match(.{ "B", tp.extract(&cb) }))
|
else if (try m.match(.{ "B", tp.extract(&cb) }))
|
||||||
self.back(from, cb)
|
self.back(from, cb)
|
||||||
else if (try m.match(.{ "F", tp.extract(&cb) }))
|
else if (try m.match(.{ "F", tp.extract(&cb) }))
|
||||||
|
@ -85,58 +106,62 @@ const Process = struct {
|
||||||
tp.exit_normal();
|
tp.exit_normal();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(self: *Process, entry: Entry) tp.result {
|
fn update(self: *Process, entry_: Entry) tp.result {
|
||||||
if (self.records.items.len == 0)
|
const entry: Entry = .{
|
||||||
return self.records.append(entry) catch |e| tp.exit_error(e);
|
.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) {
|
if (isdupe(self.current, entry))
|
||||||
self.records.items[self.pos] = entry;
|
return self.a.free(self.current.?.file_path);
|
||||||
return;
|
|
||||||
|
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) {
|
fn isdupe(a_: ?Entry, b: Entry) bool {
|
||||||
if (entry.cursor.row == self.records.items[self.pos + 1].cursor.row)
|
return if (a_) |a| std.mem.eql(u8, a.file_path, b.file_path) and a.cursor.row == b.cursor.row else false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.pos > 0) {
|
fn back(self: *const Process, from: tp.pid_ref, cb_addr: usize) void {
|
||||||
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 {
|
|
||||||
const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr);
|
const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr);
|
||||||
if (self.pos == 0)
|
if (self.backwards.getLastOrNull()) |entry|
|
||||||
return;
|
cb(from, entry.file_path, entry.cursor, entry.selection);
|
||||||
self.pos -= 1;
|
|
||||||
const entry = self.records.items[self.pos];
|
|
||||||
cb(from, entry.cursor, entry.selection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn forward(self: *Process, from: tp.pid_ref, cb_addr: usize) void {
|
fn forward(self: *Process, from: tp.pid_ref, cb_addr: usize) void {
|
||||||
const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr);
|
const cb: *CallBack = if (cb_addr == 0) return else @ptrFromInt(cb_addr);
|
||||||
if (self.records.items.len == 0) return;
|
if (self.forwards.getLastOrNull()) |entry|
|
||||||
if (self.pos == self.records.items.len - 1) return;
|
cb(from, entry.file_path, entry.cursor, entry.selection);
|
||||||
self.pos += 1;
|
|
||||||
const entry = self.records.items[self.pos];
|
|
||||||
cb(from, 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 (self.pid) |pid| {
|
||||||
if (selection) |sel|
|
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
|
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 {
|
pub fn back(self: Self, cb: *const CallBack) tp.result {
|
||||||
if (self.pid) |pid| try pid.send(.{ "B", @intFromPtr(cb) });
|
if (self.pid) |pid| try pid.send(.{ "B", @intFromPtr(cb) });
|
||||||
|
|
|
@ -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 {
|
pub fn location_update(self: *Self, m: tp.message) tp.result {
|
||||||
var row: usize = 0;
|
var row: usize = 0;
|
||||||
var col: 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) }))
|
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 (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 = .{};
|
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) }))
|
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|
|
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
|
from.send(.{ "cmd", "goto", .{ cursor.row, cursor.col, sel.begin.row, sel.begin.col, sel.end.row, sel.end.col } }) catch return
|
||||||
else
|
else
|
||||||
|
|
Loading…
Add table
Reference in a new issue