add(zmd): parse markdown file contents and display the parsed contents
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 44s

Not everything can be parsed and displayed correctly yet, however this
will the default file format to use for adding page contents (i.e.
normal pages, blog entries, etc.)
This commit is contained in:
2024-10-13 12:57:44 +02:00
parent 2db1d55fcf
commit a01619911d
6 changed files with 184 additions and 50 deletions

View File

@@ -33,18 +33,6 @@ pub fn deinit(this: *@This()) void {
fn fillView(this: *@This()) void {
this.view.?.clear();
if (this.path) |path| {
for (0..path.len, this.view.?.screen.width / 2 - path.len / 2..) |i, col| {
const cell: vaxis.Cell = .{
.char = .{ .grapheme = path[i .. i + 1] },
.style = .{
.ul_style = .single,
},
};
this.view.?.writeCell(col, 0, cell);
}
}
const msg = "Yves Biener";
for (msg, 0..) |_, i| {
const cell: vaxis.Cell = .{
@@ -58,8 +46,22 @@ fn fillView(this: *@This()) void {
.bold = true,
},
};
this.view.?.writeCell(this.view.?.screen.width - msg.len - 3 + i, 0, cell);
this.view.?.writeCell(i + 1, 0, cell);
}
if (this.path) |path| {
for (0..path.len, this.view.?.screen.width / 2 - path.len / 2..) |i, col| {
const cell: vaxis.Cell = .{
.char = .{ .grapheme = path[i .. i + 1] },
.style = .{
.ul_style = .single,
},
};
this.view.?.writeCell(col, 0, cell);
}
}
// TODO: github icon, discord icon, mail icon with corresponding links to contact me
}
/// Update loop for a given widget to react to the provided `Event`. It may

View File

@@ -2,7 +2,9 @@
const std = @import("std");
const vaxis = @import("vaxis");
const Zmd = @import("zmd").Zmd;
const node2buffer = @import("node2buffer.zig");
const widget = @import("../widget.zig");
const Event = widget.Event;
@@ -51,23 +53,21 @@ pub fn draw(this: *@This(), win: vaxis.Window) void {
const msg =
\\# Goto
\\
\\*a* about
\\*h* home
\\**a** about
\\**h** home
;
var zmd = Zmd.init(this.allocator);
defer zmd.deinit();
var cells = std.ArrayList(vaxis.Cell).init(this.allocator);
defer cells.deinit();
zmd.parse(msg) catch @panic("failed to parse markdown file");
node2buffer.toBuffer(zmd.nodes.items[0], this.allocator, msg, &cells, .{}) catch @panic("failed to transform to cell array");
var col: usize = 0;
var row: usize = 0;
for (msg, 0..) |_, i| {
const cell: vaxis.Cell = .{
// each cell takes a _grapheme_ as opposed to a single
// codepoint. This allows Vaxis to handle emoji properly,
// particularly with terminals that the Unicode Core extension
// (IE Mode 2027)
.char = .{ .grapheme = msg[i .. i + 1] },
.style = .{
.fg = .{ .index = 6 },
.bold = true,
},
};
for (cells.items) |cell| {
view.writeCell(col, row, cell);
if (std.mem.eql(u8, cell.char.grapheme, "\n")) {
col = 0;

View File

@@ -2,27 +2,34 @@
const std = @import("std");
const vaxis = @import("vaxis");
const Zmd = @import("zmd").Zmd;
const node2buffer = @import("node2buffer.zig");
const widget = @import("../widget.zig");
const Event = widget.Event;
allocator: std.mem.Allocator = undefined,
unicode: *const vaxis.Unicode = undefined,
buffer: vaxis.widgets.TextView.Buffer = undefined,
contents: ?[]const u8 = null,
buffer: std.ArrayList(vaxis.Cell) = undefined,
view: vaxis.widgets.ScrollView = undefined,
pub fn init(allocator: std.mem.Allocator, unicode: *const vaxis.Unicode) @This() {
return .{
.allocator = allocator,
.unicode = unicode,
.buffer = .{},
.contents = null,
.buffer = std.ArrayList(vaxis.Cell).init(allocator),
.view = .{ .vertical_scrollbar = null },
};
}
pub fn deinit(this: *@This()) void {
this.buffer.deinit(this.allocator);
if (this.contents) |*content| {
this.allocator.free(content.*);
}
this.buffer.deinit();
this.* = undefined;
}
@@ -60,10 +67,17 @@ pub fn update(this: *@This(), event: Event) void {
return;
};
defer file.close();
if (this.contents) |*content| {
this.allocator.free(content.*);
}
this.contents = file.readToEndAlloc(this.allocator, 4096) catch @panic("could not read to end");
this.buffer.clear(this.allocator);
const writer = this.buffer.writer(this.allocator, &this.unicode.grapheme_data, &this.unicode.width_data);
file.reader().streamUntilDelimiter(writer, 0, null) catch {};
var zmd = Zmd.init(this.allocator);
defer zmd.deinit();
this.buffer.clearRetainingCapacity();
zmd.parse(this.contents.?) catch @panic("failed to parse markdown contents");
node2buffer.toBuffer(zmd.nodes.items[0], this.allocator, this.contents.?, &this.buffer, .{}) catch @panic("failed to transform to cell array");
},
else => {},
}
@@ -73,30 +87,22 @@ pub fn update(this: *@This(), event: Event) void {
/// the dimension one widget may take on the screen. The widget itself has no
/// control over this.
pub fn draw(this: *@This(), win: vaxis.Window) void {
this.view.draw(win, .{ .cols = this.buffer.cols, .rows = this.buffer.rows });
// FIXME: the current node2buffer implementation cannot determine how many rows and columns there are from the read file contents
// this.view.draw(win, .{ .cols = this.buffer.cols, .rows = this.buffer.rows }); // needed for scroll bar scaling and display
const Pos = struct { x: usize = 0, y: usize = 0 };
var pos: Pos = .{};
const bounds = this.view.bounds(win);
for (this.buffer.grapheme.items(.len), this.buffer.grapheme.items(.offset), 0..) |g_len, g_offset, index| {
for (this.buffer.items) |cell| {
if (bounds.above(pos.y)) break;
// if (!bounds.colInside(pos.x)) continue; // FIX: how can I prevent writes out of bounce of the viewport?
const cluster = this.buffer.content.items[g_offset..][0..g_len];
if (std.mem.eql(u8, cluster, "\n")) {
if (index == this.buffer.grapheme.len - 1) break;
pos.y +|= 1;
this.view.writeCell(win, pos.x, pos.y, cell);
if (std.mem.eql(u8, cell.char.grapheme, "\n")) {
pos.x = 0;
continue;
} else if (bounds.below(pos.y)) {
continue;
pos.y += 1;
} else {
pos.x += 1;
}
const width = win.gwidth(cluster);
defer pos.x +|= width;
if (!bounds.colInside(pos.x)) continue;
this.view.writeCell(win, pos.x, pos.y, .{ .char = .{ .grapheme = cluster, .width = width }, .style = .{} });
}
}

117
src/widget/node2buffer.zig Normal file
View File

@@ -0,0 +1,117 @@
///! Transform a given `zmd.Note` into a buffer which can be used by any `vaxis.widgets.View`
const std = @import("std");
const vaxis = @import("vaxis");
const zmd = @import("zmd");
pub fn toBuffer(node: *zmd.Node, allocator: std.mem.Allocator, input: []const u8, array: *std.ArrayList(vaxis.Cell), s: vaxis.Cell.Style) !void {
const content = switch (node.token.element.type) {
.text => input[node.token.start..node.token.end],
.code, .block => node.content,
else => "",
};
var style = s;
// determine general styling changes
switch (node.token.element.type) {
.bold => {
style.bold = true;
},
.bold_close => {
style.bold = false;
},
.italic => {
style.italic = true;
},
.italic_close => {
style.italic = false;
},
.block => {
// TODO: what should I do with blocks?
},
.block_close => {
// TODO: what should I do with blocks?
},
.code => {
style.dim = true;
},
.code_close => {
style.dim = false;
},
else => {},
}
switch (node.token.element.type) {
.root, .none, .eof => {},
.linebreak, .paragraph => {
try array.append(.{
.char = .{ .grapheme = "\n" },
.style = style,
});
style.ul_style = .off;
},
.h1 => {
style.ul_style = .single;
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
},
.h2 => {
style.ul_style = .single;
for (0..2) |_| {
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
}
},
.h3 => {
style.ul_style = .single;
for (0..3) |_| {
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
}
},
.h4 => {
style.ul_style = .single;
for (0..4) |_| {
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
}
},
.h5 => {
style.ul_style = .single;
for (0..5) |_| {
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
}
},
.h6 => {
style.ul_style = .single;
for (0..6) |_| {
try array.append(.{
.char = .{ .grapheme = "#" },
.style = style,
});
}
},
else => {
for (content, 0..) |_, i| {
try array.append(.{
.char = .{ .grapheme = content[i .. i + 1] },
.style = style,
});
}
},
}
for (node.children.items) |child_node| {
try toBuffer(child_node, allocator, input, array, style);
}
}