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
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:
@@ -25,6 +25,10 @@ pub fn build(b: *std.Build) void {
|
||||
.target = target,
|
||||
.timestamp = true,
|
||||
});
|
||||
const zmd_dep = b.dependency("zmd", .{
|
||||
.optimize = optimize,
|
||||
.target = target,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "tui-website",
|
||||
@@ -34,6 +38,7 @@ pub fn build(b: *std.Build) void {
|
||||
});
|
||||
exe.root_module.addImport("vaxis", vaxis_dep.module("vaxis"));
|
||||
exe.root_module.addImport("zlog", zlog_dep.module("zlog"));
|
||||
exe.root_module.addImport("zmd", zmd_dep.module("zmd"));
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
.url = "git+https://github.com/rockorager/libvaxis#e3062d62b60bf8a351f267a77e1a94e2614394aa",
|
||||
.hash = "1220479d09031168a100748e103ef5a8ffec5d4b0ff74a7e45cf2a9b09f82342b91f",
|
||||
},
|
||||
.zmd = .{
|
||||
.url = "git+https://github.com/jetzig-framework/zmd#90e52429cacd4fdc90fd615596fe584ae40ec8e9",
|
||||
.hash = "12202a4edefedd52478223a44cdc9a3b41d4bc5cf3670497a48377f45ff16a5e3363",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
117
src/widget/node2buffer.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user