WIP: first working version
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 55s
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 55s
It still has a minor memory leak and has at least two hacks implemented that I would like to improve on.
This commit is contained in:
269
src/elements.zig
Normal file
269
src/elements.zig
Normal file
@@ -0,0 +1,269 @@
|
||||
//! Applications `Element` implementations to be used in *zterm* `Container`'s
|
||||
|
||||
pub fn Root(App: type) type {
|
||||
return struct {
|
||||
gpa: Allocator,
|
||||
diffs: std.ArrayListUnmanaged(App.Scrollable) = .empty,
|
||||
container: *App.Container = undefined,
|
||||
|
||||
pub fn init(gpa: Allocator) @This() {
|
||||
return .{ .gpa = gpa };
|
||||
}
|
||||
|
||||
fn deinit(ctx: *anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
for (this.diffs.items) |*scrollable| scrollable.element().deinit();
|
||||
this.diffs.deinit(this.gpa);
|
||||
// the container is the root container, which is calling this function, do not deinitialize it!
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) App.Element {
|
||||
return .{
|
||||
.ptr = this,
|
||||
.vtable = &.{
|
||||
.deinit = deinit,
|
||||
.handle = handle,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn handle(ctx: *anyopaque, _: *App.Model, event: App.Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
// NOTE assume that there is at least one diff
|
||||
.init => this.container.elements.items[this.container.elements.items.len - 1].element = this.diffs.items[0].element(),
|
||||
.file => |idx| this.container.elements.items[this.container.elements.items.len - 1].element = this.diffs.items[idx].element(),
|
||||
.quit => this.container.elements.items[this.container.elements.items.len - 1].element = .{},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// NOTE base contents from the model!
|
||||
// -> assume that the model does not change! it is created initially and then never changed!
|
||||
pub fn Tree(App: type) type {
|
||||
return struct {
|
||||
scrollback: usize = 0, // scrollback index for the changed files
|
||||
idx: usize = 0, // index for the currently selected entry
|
||||
len: usize = 0, // total amount of changed files
|
||||
size: Point = .{}, // current available size for the `Element`
|
||||
queue: *App.Queue,
|
||||
|
||||
pub fn init(queue: *App.Queue) @This() {
|
||||
return .{ .queue = queue };
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) App.Element {
|
||||
return .{
|
||||
.ptr = this,
|
||||
.vtable = &.{
|
||||
.resize = resize,
|
||||
.handle = handle,
|
||||
.content = content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn resize(ctx: *anyopaque, _: *const App.Model, size: Point) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
fn handle(ctx: *anyopaque, model: *App.Model, event: App.Event) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
switch (event) {
|
||||
.init => {
|
||||
this.len = model.changes.keys().len;
|
||||
this.scrollback = 0;
|
||||
this.idx = 0;
|
||||
},
|
||||
// TODO also support key inputs to change the current file?
|
||||
.mouse => |mouse| if (this.len > 0) switch (mouse.button) {
|
||||
.left => if (mouse.y + this.scrollback < this.len) {
|
||||
this.idx = mouse.y + this.scrollback;
|
||||
this.queue.push(.{ .file = this.idx });
|
||||
},
|
||||
.wheel_up => this.scrollback -|= 3,
|
||||
.wheel_down => this.scrollback = @min(this.len - 1, this.scrollback + 3),
|
||||
else => {},
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, model: *const App.Model, cells: []Cell, size: Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
if (this.len == 0) return;
|
||||
|
||||
var cell_idx: usize = 0;
|
||||
const changes: []const Model.Index = model.changes.keys();
|
||||
const files = changes[this.scrollback..];
|
||||
|
||||
for (0..size.y) |row| {
|
||||
if (row >= files.len) break;
|
||||
const idx = this.scrollback + row;
|
||||
|
||||
const file = files[row];
|
||||
const value = model.content[file.idx .. file.idx + file.len];
|
||||
var value_idx: usize = 0;
|
||||
if (value_idx >= value.len) break;
|
||||
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;
|
||||
|
||||
if (cell_idx >= cells.len) break;
|
||||
switch (cp) {
|
||||
'\n' => cell_idx += 1,
|
||||
'\t' => cell_idx += 2,
|
||||
else => {},
|
||||
}
|
||||
cells[cell_idx].cp = switch (cp) {
|
||||
'\n', '\t' => continue,
|
||||
else => cp,
|
||||
};
|
||||
cells[cell_idx].style.fg = row_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Individual `Change` associated to a parsed diff header.
|
||||
pub fn Change(App: type) type {
|
||||
return struct {
|
||||
change: Model.Index,
|
||||
// how does an individual `Change` knows it relation to the `Model` containing it's contents?
|
||||
pub fn init(change: Model.Index) @This() {
|
||||
return .{ .change = change };
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) App.Element {
|
||||
return .{
|
||||
.ptr = this,
|
||||
.vtable = &.{
|
||||
.minSize = minSize,
|
||||
.content = content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn minSize(ctx: *anyopaque, model: *const App.Model, size: Point) Point {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
// TODO handle `\n`, '\t', etc. correctly in the fill for the `cells`
|
||||
switch (model.render_mode) {
|
||||
.side_by_side => {},
|
||||
.stacked => {},
|
||||
}
|
||||
var y: u16 = 0;
|
||||
const value = model.content[this.change.idx .. this.change.idx + this.change.len];
|
||||
if (this.change.idx + this.change.len == model.content.len) y += 1;
|
||||
for (value) |cp| if (cp == '\n') {
|
||||
y += 1;
|
||||
};
|
||||
return .{ .x = size.x, .y = y };
|
||||
}
|
||||
|
||||
fn content(ctx: *anyopaque, model: *const App.Model, cells: []Cell, size: Point) !void {
|
||||
const this: *@This() = @ptrCast(@alignCast(ctx));
|
||||
assert(cells.len == @as(usize, size.x) * @as(usize, size.y));
|
||||
// TODO render different modes accordingly
|
||||
var cell_idx: usize = 0;
|
||||
const value = model.content[this.change.idx .. this.change.idx + this.change.len];
|
||||
var value_idx: usize = 0;
|
||||
switch (model.render_mode) {
|
||||
.side_by_side => for (0..size.y) |row| {
|
||||
if (value_idx >= value.len) break;
|
||||
const row_color: zterm.Color = switch (value[value_idx]) {
|
||||
'+' => .green,
|
||||
'-' => .red,
|
||||
'@' => .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;
|
||||
|
||||
if (cell_idx >= cells.len) break;
|
||||
switch (cp) {
|
||||
'\n' => {},
|
||||
'\t' => cell_idx += 2,
|
||||
else => {},
|
||||
}
|
||||
cells[cell_idx].cp = switch (cp) {
|
||||
'\n' => break,
|
||||
'\t' => continue,
|
||||
else => cp,
|
||||
};
|
||||
cells[cell_idx].style.fg = row_color;
|
||||
}
|
||||
},
|
||||
.stacked => for (0..size.y) |row| {
|
||||
if (value_idx >= value.len) break;
|
||||
const row_color: zterm.Color = switch (value[value_idx]) {
|
||||
'+' => .green,
|
||||
'-' => .red,
|
||||
'@' => .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;
|
||||
|
||||
if (cell_idx >= cells.len) break;
|
||||
switch (cp) {
|
||||
'\n' => {},
|
||||
'\t' => cell_idx += 2,
|
||||
else => {},
|
||||
}
|
||||
cells[cell_idx].cp = switch (cp) {
|
||||
'\n' => break,
|
||||
'\t' => continue,
|
||||
else => cp,
|
||||
};
|
||||
cells[cell_idx].style.fg = row_color;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// `Diff` describes a collection of `Change`'s (i.e. through a root `Container` inside an `App.Scrollable`)
|
||||
pub fn Diff(App: type, gpa: Allocator, changes: []Change(App)) !App.Scrollable {
|
||||
var diff: App.Container = try .init(gpa, .{
|
||||
.layout = .{
|
||||
.direction = .vertical,
|
||||
.separator = .{
|
||||
.enabled = true,
|
||||
},
|
||||
},
|
||||
}, .{});
|
||||
for (changes) |*change| try diff.append(try .init(gpa, .{}, change.element()));
|
||||
return .init(diff, .disabled);
|
||||
}
|
||||
|
||||
var random_alg = std.Random.DefaultCsprng.init(@splat(1));
|
||||
var random = random_alg.random();
|
||||
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const zterm = @import("zterm");
|
||||
const Model = @import("model.zig");
|
||||
|
||||
const Cell = zterm.Cell;
|
||||
const Point = zterm.Point;
|
||||
Reference in New Issue
Block a user