1const std = @import("std");
2const ansi = @import("ansi");
3
4pub fn answer(out: anytype, comptime prompt: []const u8, comptime T: type, comptime valfmt: []const u8, value: T) !T {
5 try out.print(comptime ansi.color.Fg(.Green, "? "), .{});
6 try out.print(comptime ansi.color.Bold(prompt ++ " "), .{});
7 try out.print(comptime ansi.color.Fg(.Cyan, valfmt ++ "\n"), .{value});
8 return value;
9}
10
11const PromptRet = struct {
12 n: usize,
13 value: []const u8,
14};
15
16fn doprompt(out: anytype, in: anytype, alloc: std.mem.Allocator, default: ?[]const u8) !PromptRet {
17 var n: usize = 1;
18 var value: []const u8 = undefined;
19 while (true) : (n += 1) {
20 try out.print(comptime ansi.color.Faint("> "), .{});
21 var input: []const u8 = try in.readUntilDelimiterAlloc(alloc, '\n', 1024);
22 input = std.mem.trimEnd(u8, input, "\r\n");
23
24 if (input.len == 0) if (default) |d| {
25 value = d;
26 break;
27 };
28 if (input.len > 0) {
29 value = input;
30 break;
31 }
32 }
33 return PromptRet{ .n = n, .value = value };
34}
35
36fn clean(out: anytype, n: usize) !void {
37 for (0..n) |_| {
38 try out.print(comptime ansi.csi.CursorUp(1), .{});
39 try out.print(comptime ansi.csi.EraseInLine(0), .{});
40 }
41}
42
43pub fn forEnum(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator, comptime E: type, default: ?E) !E {
44 comptime std.debug.assert(@typeInfo(E) == .@"enum");
45 const def: ?[]const u8 = if (default) |d| @tagName(d) else null;
46
47 try out.print(comptime ansi.color.Fg(.Green, "? "), .{});
48 try out.print(comptime ansi.color.Bold(prompt ++ " "), .{});
49
50 try out.print(ansi.style.Faint ++ "(", .{});
51 inline for (std.meta.fields(E), 0..) |f, i| {
52 if (i != 0) try out.print("/", .{});
53 if (std.mem.eql(u8, f.name, def orelse "")) {
54 try out.print(ansi.style.ResetIntensity, .{});
55 try out.print(comptime ansi.color.Fg(.Cyan, f.name), .{});
56 try out.print(ansi.style.Faint, .{});
57 } else try out.print(f.name, .{});
58 }
59 try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{});
60
61 var value: E = undefined;
62 var i: usize = 0;
63 while (true) {
64 const p = try doprompt(out, in, alloc, def);
65 defer if (!std.mem.eql(u8, p.value, def orelse "")) alloc.free(p.value);
66
67 i += p.n;
68 if (std.meta.stringToEnum(E, p.value)) |cap| {
69 value = cap;
70 break;
71 }
72 }
73 try clean(out, i);
74 _ = try answer(out, prompt, []const u8, "{s}", @tagName(value));
75
76 return value;
77}
78
79pub fn forString(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator, default: ?[]const u8) ![]const u8 {
80 try out.print(comptime ansi.color.Fg(.Green, "? "), .{});
81 try out.print(comptime ansi.color.Bold(prompt ++ " "), .{});
82
83 if (default != null and default.?.len > 0) {
84 try out.print(ansi.style.Faint ++ "(", .{});
85 try out.print("{s}", .{default.?});
86 try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{});
87 }
88
89 const p = try doprompt(out, in, alloc, default);
90 try clean(out, p.n);
91 return try answer(out, prompt, []const u8, "{s}", p.value);
92}
93
94pub fn forConfirm(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator) !bool {
95 return (try forEnum(out, in, prompt, alloc, enum { y, n }, .y)) == .y;
96}
97
98pub fn forStruct(out: anytype, in: anytype, comptime prompt: []const u8, comptime S: type, comptime name: std.meta.FieldEnum(S), items: []const S) !usize {
99 comptime std.debug.assert(@typeInfo(S) == .@"struct");
100 std.debug.assert(items.len > 0);
101
102 try out.print(comptime ansi.color.Fg(.Green, "? "), .{});
103 try out.print(comptime ansi.color.Bold(prompt ++ "\n"), .{});
104
105 const STDIN_FILENO = 0;
106 const TCSAFLUSH = 2;
107 const ECHO: std.os.linux.tcflag_t = @bitCast(std.os.linux.tc_lflag_t{ .ECHO = true });
108 const ICANON: std.os.linux.tcflag_t = @bitCast(std.os.linux.tc_lflag_t{ .ICANON = true });
109
110 var orig_termios: struct_termios = undefined;
111 _ = tcgetattr(STDIN_FILENO, &orig_termios);
112 defer _ = tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
113
114 var raw_termios: struct_termios = orig_termios;
115 raw_termios.c_lflag &= ~(ECHO | ICANON);
116 _ = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_termios);
117
118 var r: usize = 0;
119 _ = &r;
120 while (true) {
121 for (items, 0..) |itm, i| {
122 if (i == r) {
123 try out.print(comptime ansi.color.Fg(.Cyan, "> {s}\n"), .{@field(itm, @tagName(name))});
124 } else {
125 try out.print(" {s}\n", .{@field(itm, @tagName(name))});
126 }
127 }
128 switch (@as(ansi.ascii, @enumFromInt(try in.readByte()))) {
129 .LF => {
130 break;
131 },
132 .ESC => {
133 std.debug.assert(try in.readByte() == '[');
134 switch (try in.readByte()) {
135 'A' => { //up
136 r = if (r > 0) r - 1 else items.len - 1;
137 },
138 'B' => { //down
139 r += 1;
140 if (r == items.len) r = 0;
141 },
142 else => {},
143 }
144 },
145 else => {},
146 }
147 try clean(out, items.len);
148 }
149 try clean(out, items.len + 1);
150 _ = try answer(out, prompt, []const u8, "{s}", @field(items[r], @tagName(name)));
151 return r;
152}
153
154// pub fn forNumber(comptime prompt: []const u8, alloc: *std.mem.Allocator, comptime T: type, default: ?T) !T {
155// try out.print(comptime ansi.color.Fg(.Green, "? "), .{});
156// try out.print(comptime ansi.color.Bold(prompt ++ " "), .{});
157
158// if (default != null) {
159// try out.print(ansi.style.Faint ++ "(", .{});
160// try out.print("{d}", .{default.?});
161// try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{});
162// }
163
164// var value: T = undefined;
165// var i: usize = 0;
166// while (true) {
167// const n = if (default != null) try std.fmt.allocPrint(alloc, "{d}", .{default}) else null;
168// defer if (n != null) alloc.free(n.?);
169// const p = try doprompt(stdin, alloc, if (default != null) n else null);
170// defer alloc.free(p.value);
171// i += p.n;
172// switch (@typeInfo(T)) {
173// .Int => {
174// if (std.fmt.parseInt(T, p.value, 10) catch null) |cap| {
175// value = cap;
176// break;
177// }
178// },
179// .Float => {
180// if (std.fmt.parseFloat(T, p.value) catch null) |cap| {
181// value = cap;
182// break;
183// }
184// },
185// else => @compileError("expected number type instead got: " ++ @typeName(T)),
186// }
187// }
188// clean(i);
189// _ = answer(prompt, T, "{d}", value);
190
191// return value;
192// }
193
194extern fn tcgetattr(fd: c_int, termios_p: *struct_termios) c_int;
195extern fn tcsetattr(fd: c_int, optional_actions: c_int, termios_p: *const struct_termios) c_int;
196
197const impdef = @cImport({
198 @cInclude("termios.h");
199});
200const struct_termios = impdef.struct_termios;