Compare commits

...

3 Commits

Author SHA1 Message Date
6d48af9fca doc: testing string for bigger diff example to test
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 42s
Maybe this input string can be re-used for a potential integration-test?
2026-01-08 23:21:11 +01:00
3e9fc4ce9b fix: memory leak; add: keybindings J and K for file navigation 2026-01-08 23:20:33 +01:00
8d98d3226a feat(tree): make left side of the tree only take as much space as necessary 2026-01-08 23:19:44 +01:00
4 changed files with 146 additions and 7 deletions

View File

@@ -3,8 +3,8 @@
.version = "0.0.1",
.dependencies = .{
.zterm = .{
.url = "git+https://gitea.yves-biener.de/yves-biener/zterm#e972a2ea0f7a9f8caffd439ef206474b46475f91",
.hash = "zterm-0.3.0-1xmmENkhHAB2rmNJFH-9rRqiRLnT673xwuMrqLwOnlT_",
.url = "git+https://gitea.yves-biener.de/yves-biener/zterm#b1a0d60ae379bb91b862d7c4a8a2210cd74c4387",
.hash = "zterm-0.3.0-1xmmEMotHACXUXNtsX1P2iz3XQgabNz1PfF7oazYtNam",
},
},
.minimum_zig_version = "0.16.0-dev.1254+bf15c791f",

View File

@@ -59,6 +59,7 @@ pub fn Tree(App: type) type {
.ptr = this,
.vtable = &.{
.resize = resize,
.minSize = minSize,
.handle = handle,
.content = content,
},
@@ -70,6 +71,19 @@ pub fn Tree(App: type) type {
this.size = size;
}
fn minSize(ctx: *anyopaque, model: *const App.Model, _: Point) Point {
// NOTE as we assume the model contents do not change we could calculate the
// maximum width required for this `Element` and return that, instead of
// calculating the width every time anew. For now this works fine.
const this: *@This() = @ptrCast(@alignCast(ctx));
const changes: []const Model.Index = model.changes.keys();
const files = changes[this.scrollback..];
var width: u16 = 0;
for (files) |file| width = @max(@as(u16, @intCast(file.len)), width);
return .{ .x = width };
}
fn handle(ctx: *anyopaque, model: *App.Model, event: App.Event) !void {
const this: *@This() = @ptrCast(@alignCast(ctx));
switch (event) {
@@ -78,7 +92,17 @@ pub fn Tree(App: type) type {
this.scrollback = 0;
this.idx = 0;
},
// TODO also support key inputs to change the current file?
.key => |key| {
if (key.eql(.{ .cp = 'J' }) and this.idx < this.len - 1) {
this.idx += 1;
this.queue.push(.{ .file = this.idx });
}
if (key.eql(.{ .cp = 'K' }) and this.idx > 0) {
this.idx -= 1;
this.queue.push(.{ .file = this.idx });
}
},
.mouse => |mouse| if (this.len > 0) switch (mouse.button) {
.left => if (mouse.y + this.scrollback < this.len) {
this.idx = mouse.y + this.scrollback;
@@ -112,7 +136,6 @@ pub fn Tree(App: type) type {
const row_color: zterm.Color = if (this.idx == idx) .blue else .default;
cell_idx = row * size.x;
for (0..size.x) |_| {
cell_idx += 1;
if (value_idx >= value.len) break;
const cp = value[value_idx];
defer value_idx += 1;
@@ -128,6 +151,7 @@ pub fn Tree(App: type) type {
else => cp,
};
cells[cell_idx].style.fg = row_color;
cell_idx += 1;
}
}
}

View File

@@ -2,7 +2,108 @@
// FIX known issues:
const diff = "";
const diff =
\\diff --git a/build.zig.zon b/build.zig.zon
\\index a039487..944fc49 100644
\\--- a/build.zig.zon
\\+++ b/build.zig.zon
\\@@ -3,8 +3,8 @@
\\ .version = "0.0.1",
\\ .dependencies = .{
\\ .zterm = .{
\\- .url = "git+https://gitea.yves-biener.de/yves-biener/zterm#e972a2ea0f7a9f8caffd439ef206474b46475f91",
\\- .hash = "zterm-0.3.0-1xmmENkhHAB2rmNJFH-9rRqiRLnT673xwuMrqLwOnlT_",
\\+ .url = "git+https://gitea.yves-biener.de/yves-biener/zterm#b1a0d60ae379bb91b862d7c4a8a2210cd74c4387",
\\+ .hash = "zterm-0.3.0-1xmmEMotHACXUXNtsX1P2iz3XQgabNz1PfF7oazYtNam",
\\ },
\\ },
\\ .minimum_zig_version = "0.16.0-dev.1254+bf15c791f",
\\diff --git a/src/elements.zig b/src/elements.zig
\\index c0225ae..fb1ad68 100644
\\--- a/src/elements.zig
\\+++ b/src/elements.zig
\\@@ -59,6 +59,7 @@ pub fn Tree(App: type) type {
\\ .ptr = this,
\\ .vtable = &.{
\\ .resize = resize,
\\+ .minSize = minSize,
\\ .handle = handle,
\\ .content = content,
\\ },
\\@@ -70,6 +71,19 @@ pub fn Tree(App: type) type {
\\ this.size = size;
\\ }
\\
\\+ fn minSize(ctx: *anyopaque, model: *const App.Model, _: Point) Point {
\\+ // NOTE as we assume the model contents do not change we could calculate the
\\+ // maximum width required for this `Element` and return that, instead of
\\+ // calculating the width every time anew. For now this works fine.
\\+ const this: *@This() = @ptrCast(@alignCast(ctx));
\\+ const changes: []const Model.Index = model.changes.keys();
\\+ const files = changes[this.scrollback..];
\\+ var width: u16 = 0;
\\+ for (files) |file| width = @max(@as(u16, @intCast(file.len)), width);
\\+ return .{ .x = width };
\\+ }
\\+
\\+
\\ fn handle(ctx: *anyopaque, model: *App.Model, event: App.Event) !void {
\\ const this: *@This() = @ptrCast(@alignCast(ctx));
\\ switch (event) {
\\@@ -78,7 +92,17 @@ pub fn Tree(App: type) type {
\\ this.scrollback = 0;
\\ this.idx = 0;
\\ },
\\- // TODO also support key inputs to change the current file?
\\+ .key => |key| {
\\+ if (key.eql(.{ .cp = 'J' }) and this.idx < this.len - 1) {
\\+ this.idx += 1;
\\+ this.queue.push(.{ .file = this.idx });
\\+ }
\\+
\\+ if (key.eql(.{ .cp = 'K' }) and this.idx > 0) {
\\+ this.idx -= 1;
\\+ this.queue.push(.{ .file = this.idx });
\\+ }
\\+ },
\\ .mouse => |mouse| if (this.len > 0) switch (mouse.button) {
\\ .left => if (mouse.y + this.scrollback < this.len) {
\\ this.idx = mouse.y + this.scrollback;
\\@@ -112,7 +136,6 @@ pub fn Tree(App: type) type {
\\ const row_color: zterm.Color = if (this.idx == idx) .blue else .default;
\\ cell_idx = row * size.x;
\\ for (0..size.x) |_| {
\\- cell_idx += 1;
\\ if (value_idx >= value.len) break;
\\ const cp = value[value_idx];
\\ defer value_idx += 1;
\\@@ -128,6 +151,7 @@ pub fn Tree(App: type) type {
\\ else => cp,
\\ };
\\ cells[cell_idx].style.fg = row_color;
\\+ cell_idx += 1;
\\ }
\\ }
\\ }
\\diff --git a/src/root.zig b/src/root.zig
\\index 29aa832..54d3631 100644
\\--- a/src/root.zig
\\+++ b/src/root.zig
\\@@ -17,7 +17,11 @@ pub fn Container(App: type, gpa: Allocator, element: *elements.Root(App), tree:
\\ },
\\ },
\\ }, element.element());
\\- try root.append(try .init(gpa, .{}, tree.element()));
\\+ try root.append(try .init(gpa, .{
\\+ .size = .{
\\+ .grow = .vertical,
\\+ },
\\+ }, tree.element()));
\\ try root.append(try .init(gpa, .{}, .{})); // empty container holding the scrollable element for the diff of each file
\\ return root;
\\ }
\\
;
pub fn main() !void {
// argument handling
@@ -33,7 +134,7 @@ pub fn main() !void {
// tui creation
errdefer |err| log.err("Application Error: {any}", .{err});
var threaded_io: std.Io.Threaded = .init(allocator);
var threaded_io: std.Io.Threaded = .init(allocator, .{});
errdefer threaded_io.deinit();
const io = threaded_io.io();
@@ -46,13 +147,23 @@ pub fn main() !void {
var element_root: tui_diff.elements.Root(App) = .init(allocator);
var element_tree: tui_diff.elements.Tree(App) = .init(&app.queue);
// NOTE hold change set as entries of the changes for each file of in the `App.Model.changes`
var change_set: std.ArrayList([]tui_diff.elements.Change(App)) = .empty;
defer {
for (change_set.items) |change| allocator.free(change);
change_set.deinit(allocator);
}
var iter = app.model.changes.iterator();
var entry = iter.next();
while (entry != null) : (entry = iter.next()) if (entry) |e| {
var changes = try allocator.alloc(tui_diff.elements.Change(App), e.value_ptr.items.len);
for (0.., e.value_ptr.items) |i, diff_index|
changes[i] = .init(diff_index);
// `tui_diff.elements.Diff` does not own the changes array because it only passes the
// corresponding `Element` instances for the `Container` tree generation
try element_root.diffs.append(allocator, try tui_diff.elements.Diff(App, allocator, changes));
try change_set.append(allocator, changes);
};
var root = try tui_diff.Container(App, allocator, &element_root, &element_tree);

View File

@@ -17,7 +17,11 @@ pub fn Container(App: type, gpa: Allocator, element: *elements.Root(App), tree:
},
},
}, element.element());
try root.append(try .init(gpa, .{}, tree.element()));
try root.append(try .init(gpa, .{
.size = .{
.grow = .vertical,
},
}, tree.element()));
try root.append(try .init(gpa, .{}, .{})); // empty container holding the scrollable element for the diff of each file
return root;
}