Something went wrong. Try again.
A charm-like tui library
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179const std = @import("std");const Writer = std.Io.Writer;const Cmd = @import("Cmd.zig");const blush = @import("blush");
const cmd_style = blush.new().fg(.cyan).attr(.bold, true);const param_style = blush.new().fg(.cyan).attr(.bold, true);const title_style = blush.new().fg(.magenta).attr(.bold, true);const dim_style = blush.new().fg(.bright_black);const err_style = blush.new().fg(.red).attr(.bold, true);const label_style = blush.new().fg(.yellow).attr(.bold, true);
const cmd_ul_style = cmd_style.ul(.single);const param_ul_style = param_style.ul(.single);const dim_ul_style = dim_style.ul(.single);
pub fn printGroup( writer: *Writer, root_description: []const u8, group: ?Cmd, cmds: []const Cmd,) !void { if (group) |g| { if (g.description.len > 0) { try title_style.render(writer, g.key); try writer.writeAll(": "); try writer.print("{s}\n\n", .{g.description}); } } else if (root_description.len > 0) { try writer.print("{s}\n\n", .{root_description}); }
if (cmds.len == 0) return;
var max_key: usize = 0; for (cmds) |cmd| max_key = @max(max_key, cmd.key.len);
try writer.writeAll(" "); try label_style.render(writer, "Commands"); try writer.writeByte('\n');
for (cmds) |cmd| { const desc = switch (cmd.value) { .cmd => firstLine(cmd.description), .list => firstLine(cmd.description), }; try writer.writeAll(" "); try renderMinPrefix(cmd_ul_style, cmd_style, writer, cmd.key, cmds, "key"); try pad(writer, cmd.key.len, max_key + 4); try writer.print("{s}\n", .{desc}); }}
pub fn printCommand( writer: *Writer, name: []const u8, description: []const u8, params: []const Cmd.Command.Param,) !void { if (description.len > 0) { try title_style.render(writer, name); try writer.writeAll(": "); try writer.print("{s}\n\n", .{description}); }
if (params.len == 0) return;
var max_name: usize = 0; var max_type: usize = 0; for (params) |p| { max_name = @max(max_name, p.name.len); const display = if (p.values.len > 0) p.values else p.type_name; max_type = @max(max_type, display.len); }
try writer.writeAll(" "); try label_style.render(writer, "Arguments"); try writer.writeByte('\n');
for (params) |p| { const display = if (p.values.len > 0) p.values else p.type_name; try writer.writeAll(" "); try renderMinPrefix(param_ul_style, param_style, writer, p.name, params, "name"); try pad(writer, p.name.len, max_name + 4); if (p.values.len > 0) { try renderValues(writer, p.values); } else { try dim_style.render(writer, display); } try pad(writer, display.len, max_type + 4); try writer.print("{s}", .{firstLine(p.description)}); if (!p.required) { try writer.writeAll(" "); try dim_style.render(writer, "[optional]"); } try writer.writeByte('\n'); }}
pub fn printError(writer: *Writer, msg: []const u8) !void { try err_style.render(writer, "error:"); try writer.writeByte(' '); try writer.writeAll(msg);}
fn firstLine(s: []const u8) []const u8 { return if (std.mem.findScalar(u8, s, '\n')) |n| s[0..n] else s;}
/// Renders `name` with the shortest prefix that uniquely identifies it among/// `items` (by field `field_name`) underlined.fn renderMinPrefix( ul_style: blush.Style, base_style: blush.Style, writer: *Writer, name: []const u8, items: anytype, comptime field_name: []const u8,) !void { const n = minPrefixLen(name, items, field_name); try ul_style.render(writer, name[0..n]); if (n < name.len) try base_style.render(writer, name[n..]);}
fn minPrefixLen(name: []const u8, items: anytype, comptime field_name: []const u8) usize { var best: usize = 1; for (items) |item| { const other = @field(item, field_name); if (std.mem.eql(u8, other, name)) continue; const n = @min(name.len, other.len); var lcp: usize = 0; while (lcp < n and name[lcp] == other[lcp]) : (lcp += 1) {} best = @max(best, @min(lcp + 1, name.len)); } return best;}
/// Renders a values string like "[installed, all, updates]" with the minimum/// unique prefix of each value underlined.fn renderValues(writer: *Writer, values: []const u8) !void { if (values.len < 2 or values[0] != '[' or values[values.len - 1] != ']') { try dim_style.render(writer, values); return; } const inner = values[1 .. values.len - 1]; try dim_style.render(writer, "["); var first = true; var it = std.mem.splitScalar(u8, inner, ','); while (it.next()) |raw| { const seg = std.mem.trim(u8, raw, " "); if (seg.len == 0) continue; if (!first) try dim_style.render(writer, ", "); first = false; const n = valueMinPrefixLen(seg, inner); try dim_ul_style.render(writer, seg[0..n]); if (n < seg.len) try dim_style.render(writer, seg[n..]); } try dim_style.render(writer, "]");}
fn valueMinPrefixLen(seg: []const u8, inner: []const u8) usize { var best: usize = 1; var it = std.mem.splitScalar(u8, inner, ','); while (it.next()) |raw| { const other = std.mem.trim(u8, raw, " "); if (std.mem.eql(u8, other, seg)) continue; const n = @min(seg.len, other.len); var lcp: usize = 0; while (lcp < n and seg[lcp] == other[lcp]) : (lcp += 1) {} best = @max(best, @min(lcp + 1, seg.len)); } return best;}
fn pad(writer: *Writer, current: usize, target: usize) !void { if (current >= target) return; try writer.splatByteAll(' ', target - current);}