From c62fc6fb43223fc1db8a8af194367de3ecd93628 Mon Sep 17 00:00:00 2001 From: Yves Biener Date: Fri, 1 Nov 2024 20:46:27 +0100 Subject: [PATCH] WIP: transformations --- README.md | 15 + src/widget/PopupMenu.zig | 9 +- src/widget/ViewPort.zig | 14 +- src/widget/node2buffer.zig | 544 ++++++++++++++++++++----------------- 4 files changed, 314 insertions(+), 268 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..859e9a7 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Tui-Website + +This is my terminal based website. It is served as a tui application via ssh and as a simple html page via https. + +It contains information about me and my projects as well as blog entries about something I feel like writing something about. + +## Open tasks + +- [ ] BUG: when served via `wish-serve` the corresponding outputs are not pushed through the ssh connection + - they are instead showed locally, which might cause issues with the docker container running in the background + - very likely it is `tui-website` which causes this issue + - not entirely as inputs are not passed through correctly to the below running application (i.e. `diffnav` via `serve git diff`) +- [ ] Improve navigation +- [ ] Have clickable/navigatable links inside of the tui application +- [ ] Launch simple http server alongside tui application diff --git a/src/widget/PopupMenu.zig b/src/widget/PopupMenu.zig index 27d4bac..3643033 100644 --- a/src/widget/PopupMenu.zig +++ b/src/widget/PopupMenu.zig @@ -2,9 +2,8 @@ const std = @import("std"); const vaxis = @import("vaxis"); -const Zmd = @import("zmd").Zmd; -const node2buffer = @import("node2buffer.zig"); +const converter = @import("node2buffer.zig").Markdown; const widget = @import("../widget.zig"); const Event = widget.Event; @@ -61,14 +60,10 @@ pub fn draw(this: *@This(), win: vaxis.Window) void { \\**h** home \\**t** test ; - 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, .{}, null) catch @panic("failed to transform to cell array"); + converter.toBuffer(msg, this.allocator, &cells); var col: usize = 0; var row: usize = 0; diff --git a/src/widget/ViewPort.zig b/src/widget/ViewPort.zig index 8699bd6..05e7f54 100644 --- a/src/widget/ViewPort.zig +++ b/src/widget/ViewPort.zig @@ -4,7 +4,7 @@ const std = @import("std"); const vaxis = @import("vaxis"); const Zmd = @import("zmd").Zmd; -const node2buffer = @import("node2buffer.zig"); +const converter = @import("node2buffer.zig").Markdown; const widget = @import("../widget.zig"); const Event = widget.Event; @@ -67,19 +67,9 @@ pub fn update(this: *@This(), event: Event) void { // TODO: support typst files as parser and display driver -> as I'll be using this file format anyway with my personal note system // - I should leverage the typst compiler! (i.e. maybe use the html export, once that is available?) - 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, - .{}, - null, - ) catch @panic("failed to transform to cell array"); + converter.toBuffer(this.contents.?, this.allocator, &this.buffer); }, else => {}, } diff --git a/src/widget/node2buffer.zig b/src/widget/node2buffer.zig index fcee259..dfa225c 100644 --- a/src/widget/node2buffer.zig +++ b/src/widget/node2buffer.zig @@ -1,268 +1,314 @@ -///! Transform a given `zmd.Node` into a buffer which can be used by any `vaxis.widgets.View` +///! Transform a given file type into a buffer which can be used by any `vaxis.widgets.View` +///! +///! Use the `toBuffer` method of any struct to convert the file type described by the +///! struct to convert the file contents accordingly. const std = @import("std"); const vaxis = @import("vaxis"); -const zmd = @import("zmd"); -const digits = "0123456789"; +/// Markdown tronsformation to convert a markdown file as a `std.ArrayList(vaxis.cell)` +pub const Markdown = struct { + const zmd = @import("zmd"); -pub fn toBuffer( - node: *zmd.Node, - allocator: std.mem.Allocator, - input: []const u8, - array: *std.ArrayList(vaxis.Cell), - sty: vaxis.Cell.Style, - start: ?usize, -) !void { - var next_start: ?usize = start; - var style = sty; + const digits = "0123456789"; - // determine general styling changes - switch (node.token.element.type) { - .bold => { - style.bold = true; - next_start = node.token.start; - style.fg = .{ .index = 5 }; - }, - .italic => { - style.italic = true; - next_start = node.token.start; - style.fg = .{ .index = 2 }; - }, - .block => { - style.fg = .{ .index = 252 }; - style.dim = true; - next_start = node.token.start; - }, - .code => { - next_start = node.token.start; - style.fg = .{ .index = 6 }; - }, - .href => { - next_start = node.token.start; - style.fg = .{ .index = 8 }; - }, - .link => { - style.fg = .{ .index = 3 }; - style.ul_style = .single; - }, - .list_item => { - style.fg = .{ .index = 1 }; - }, - else => {}, + pub fn toBuffer( + input: []const u8, + allocator: std.mem.Allocator, + array: *std.ArrayList(vaxis.Cell), + ) void { + var z = zmd.Zmd.init(allocator); + defer z.deinit(); + + z.parse(input) catch @panic("failed to parse markdown contents"); + convert( + z.nodes.items[0], + allocator, + input, + array, + .{}, + null, + ) catch @panic("failed to transform parsed markdown to cell array"); } - // determine content that needs to be displayed - const content = value: { + fn convert( + node: *zmd.Node, + allocator: std.mem.Allocator, + input: []const u8, + array: *std.ArrayList(vaxis.Cell), + sty: vaxis.Cell.Style, + start: ?usize, + ) !void { + var next_start: ?usize = start; + var style = sty; + + // determine general styling changes switch (node.token.element.type) { - // NOTE: do not support ordered lists? (as it only accepts `1.` and not `2.`, etc.) - .text, .list_item => break :value input[node.token.start..node.token.end], - .link => break :value input[node.token.start + 1 .. node.token.start + 1 + node.title.?.len], - .code_close => { - if (next_start) |s| { - next_start = null; - break :value input[s + 1 .. node.token.end - 1]; - } - break :value ""; + .bold => { + style.bold = true; + next_start = node.token.start; + style.fg = .{ .index = 5 }; }, - .bold_close, .italic_close, .block_close, .title_close, .href_close => { - if (next_start) |s| { - next_start = null; - break :value input[s..node.token.end]; + .italic => { + style.italic = true; + next_start = node.token.start; + style.fg = .{ .index = 2 }; + }, + .block => { + style.fg = .{ .index = 252 }; + style.dim = true; + next_start = node.token.start; + }, + .code => { + next_start = node.token.start; + style.fg = .{ .index = 6 }; + }, + .href => { + next_start = node.token.start; + style.fg = .{ .index = 8 }; + }, + .link => { + style.fg = .{ .index = 3 }; + style.ul_style = .single; + }, + .list_item => { + style.fg = .{ .index = 1 }; + }, + else => {}, + } + + // determine content that needs to be displayed + const content = value: { + switch (node.token.element.type) { + // NOTE: do not support ordered lists? (as it only accepts `1.` and not `2.`, etc.) + .text, .list_item => break :value input[node.token.start..node.token.end], + .link => break :value input[node.token.start + 1 .. node.token.start + 1 + node.title.?.len], + .code_close => { + if (next_start) |s| { + next_start = null; + break :value input[s + 1 .. node.token.end - 1]; + } + break :value ""; + }, + .bold_close, .italic_close, .block_close, .title_close, .href_close => { + if (next_start) |s| { + next_start = null; + break :value input[s..node.token.end]; + } + break :value ""; + }, + else => { + break :value ""; + }, + } + }; + + // display content + switch (node.token.element.type) { + .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, + }); + } + }, + .link => { + const uri = input[node.token.start + 1 + node.title.?.len + 2 .. node.token.start + 1 + node.title.?.len + 2 + node.href.?.len]; + for (content, 0..) |_, i| { + try array.append(.{ + .link = .{ .uri = uri }, + .char = .{ .grapheme = content[i .. i + 1] }, + .style = style, + }); + } + }, + .block_close => { + // generate a `block` i.e. + // 01 | ... + // 02 | ... + // 03 | ... + // ... + // 10 | ... + try array.append(.{ + .char = .{ .grapheme = "\n" }, + }); + var rows: usize = 0; + var c: usize = 0; + // TODO: would be cool to not have to re-iterate over the contents + for (content, 0..) |char, i| { + if (char == '\n') { + // NOTE: start after the ``` + if (c == 0) { + c = i + 1; + } + rows += 1; + } + } + rows = rows -| 1; + const pad = vaxis.widgets.LineNumbers.numDigits(rows); + for (1..rows + 1) |r| { + try array.append(.{ + .char = .{ .grapheme = " " }, + .style = style, + }); + try array.append(.{ + .char = .{ .grapheme = " " }, + .style = style, + }); + for (1..pad + 1) |i| { + const digit = vaxis.widgets.LineNumbers.extractDigit(r, pad - i); + try array.append(.{ + .char = .{ .grapheme = digits[digit .. digit + 1] }, + .style = style, + }); + } + try array.append(.{ + .char = .{ .grapheme = " " }, + .style = style, + }); + try array.append(.{ + .char = .{ .grapheme = "│" }, + .style = style, + }); + try array.append(.{ + .char = .{ .grapheme = " " }, + .style = style, + }); + for (c..content.len) |c_i| { + if (r == rows and content[c_i] == '\n') { + break; + } + try array.append(.{ + .char = .{ .grapheme = content[c_i .. c_i + 1] }, + .style = style, + }); + if (content[c_i] == '\n') { + c = c_i + 1; + break; + } + } + } + }, + .list_item => { + try array.append(.{ + .char = .{ .grapheme = "\n" }, + }); + for (content, 0..) |_, i| { + try array.append(.{ + .char = .{ .grapheme = content[i .. i + 1] }, + .style = style, + }); } - break :value ""; }, else => { - break :value ""; + for (content, 0..) |_, i| { + try array.append(.{ + .char = .{ .grapheme = content[i .. i + 1] }, + .style = style, + }); + } }, } - }; - // display content - switch (node.token.element.type) { - .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, - }); - } - }, - .link => { - const uri = input[node.token.start + 1 + node.title.?.len + 2 .. node.token.start + 1 + node.title.?.len + 2 + node.href.?.len]; - for (content, 0..) |_, i| { - try array.append(.{ - .link = .{ .uri = uri }, - .char = .{ .grapheme = content[i .. i + 1] }, - .style = style, - }); - } - }, - .block_close => { - // generate a `block` i.e. - // 01 | ... - // 02 | ... - // 03 | ... - // ... - // 10 | ... - try array.append(.{ - .char = .{ .grapheme = "\n" }, - }); - var rows: usize = 0; - var c: usize = 0; - // TODO: would be cool to not have to re-iterate over the contents - for (content, 0..) |char, i| { - if (char == '\n') { - // NOTE: start after the ``` - if (c == 0) { - c = i + 1; - } - rows += 1; - } - } - rows = rows -| 1; - const pad = vaxis.widgets.LineNumbers.numDigits(rows); - for (1..rows + 1) |r| { - try array.append(.{ - .char = .{ .grapheme = " " }, - .style = style, - }); - try array.append(.{ - .char = .{ .grapheme = " " }, - .style = style, - }); - for (1..pad + 1) |i| { - const digit = vaxis.widgets.LineNumbers.extractDigit(r, pad - i); - try array.append(.{ - .char = .{ .grapheme = digits[digit .. digit + 1] }, - .style = style, - }); - } - try array.append(.{ - .char = .{ .grapheme = " " }, - .style = style, - }); - try array.append(.{ - .char = .{ .grapheme = "│" }, - .style = style, - }); - try array.append(.{ - .char = .{ .grapheme = " " }, - .style = style, - }); - for (c..content.len) |c_i| { - if (r == rows and content[c_i] == '\n') { - break; - } - try array.append(.{ - .char = .{ .grapheme = content[c_i .. c_i + 1] }, - .style = style, - }); - if (content[c_i] == '\n') { - c = c_i + 1; - break; - } - } - } - }, - .list_item => { - try array.append(.{ - .char = .{ .grapheme = "\n" }, - }); - for (content, 0..) |_, i| { - try array.append(.{ - .char = .{ .grapheme = content[i .. i + 1] }, - .style = style, - }); - } - }, - else => { - for (content, 0..) |_, i| { - try array.append(.{ - .char = .{ .grapheme = content[i .. i + 1] }, - .style = style, - }); - } - }, - } + // close styling after creating the corresponding cells + switch (node.token.element.type) { + .bold_close => { + style.bold = false; + style.fg = .default; + }, + .italic_close => { + style.italic = false; + style.fg = .default; + }, + .block_close => { + style.fg = .default; + }, + .code_close => { + style.fg = .default; + }, + .href_close => { + style.fg = .default; + }, + .list_item => { + style.fg = .default; + }, + else => {}, + } - // close styling after creating the corresponding cells - switch (node.token.element.type) { - .bold_close => { - style.bold = false; - style.fg = .default; - }, - .italic_close => { - style.italic = false; - style.fg = .default; - }, - .block_close => { - style.fg = .default; - }, - .code_close => { - style.fg = .default; - }, - .href_close => { - style.fg = .default; - }, - .list_item => { - style.fg = .default; - }, - else => {}, + // run conversion for all childrens + for (node.children.items) |child_node| { + try convert(child_node, allocator, input, array, style, next_start); + } } +}; - // run conversion for all childrens - for (node.children.items) |child_node| { - try toBuffer(child_node, allocator, input, array, style, next_start); +pub const Typst = struct { + pub fn toBuffer( + input: []const u8, + allocator: std.mem.Allocator, + array: *std.ArrayList(vaxis.Cell), + ) void { + // TODO: leverage the compiler to create corresponding file? + // -> would enable functions to be executed, however this is not possible currently + // -> this would only work if I serve the corresponding pdf's (maybe I can have a download server for these?) + // NOTE: currently the typst compiler does not allow such a usage from the outside + // -> I would need to wait for html export I guess + + // TODO: use pret parsing to parse typst file contents and transform them into `vaxis.Cell`s accordingly + _ = input; + _ = allocator; + _ = array; + @panic("Typst parsing not yet implemented"); } -} +};