From 5695def739c2d5705503cfcc6a9d9dfd845bb212 Mon Sep 17 00:00:00 2001 From: RiskyMH Date: Tue, 11 Feb 2025 15:22:35 +0000 Subject: [PATCH] some --- src/cli/init_command.zig | 223 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 217 insertions(+), 6 deletions(-) diff --git a/src/cli/init_command.zig b/src/cli/init_command.zig index 9506ef0b55..54ad5d651f 100644 --- a/src/cli/init_command.zig +++ b/src/cli/init_command.zig @@ -24,6 +24,41 @@ const JSPrinter = bun.js_printer; const exists = bun.sys.exists; const existsZ = bun.sys.existsZ; +// makes read return error.WouldBlock instead of blocking if no input is available +// posix only +fn setNonblock(b: bool) !void { + var flags: std.posix.O = @bitCast(@as(u32, @intCast(try std.posix.fcntl(std.posix.STDIN_FILENO, std.posix.F.GETFL, 0)))); + flags.NONBLOCK = b; + _ = try std.posix.fcntl(std.posix.STDIN_FILENO, std.posix.F.SETFL, @as(u32, @bitCast(flags))); +} + +// makes it so that no newline character is required for forwarding the input +// also makes it so that input characters are not printed to the console +fn setRawInput(b: bool) !void { + if (comptime Environment.isWindows) { + const ENABLE_ECHO_INPUT: u32 = 0x0004; + const ENABLE_LINE_INPUT: u32 = 0x0002; + + const handle = std.io.getStdIn().handle; + var flags: u32 = undefined; + if (std.windows.kernel32.GetConsoleMode(handle, &flags) == 0) return error.NotATerminal; + if (b) { + flags &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT); + } else { + flags |= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT; + } + + std.debug.assert(std.windows.kernel32.SetConsoleMode(handle, flags) != 0); + } else { + var t: std.posix.termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO); + + t.lflag.ECHO = !b; + t.lflag.ICANON = !b; + t.lflag.FLUSHO = true; + try std.posix.tcsetattr(std.posix.STDIN_FILENO, .NOW, t); + } +} + pub const InitCommand = struct { pub fn prompt( alloc: std.mem.Allocator, @@ -59,6 +94,148 @@ pub const InitCommand = struct { } } + pub fn promptBool( + _: std.mem.Allocator, + comptime label: []const u8, + default: bool, + ) !bool { + Output.prettyln(label, .{}); + // Output.pretty("\n", .{}); + // Output.pretty(" (y/n): \n", .{}); + Output.flush(); + + const original_mode: if (Environment.isWindows) ?bun.windows.DWORD else void = if (comptime Environment.isWindows) + bun.win32.unsetStdioModeFlags(0, bun.windows.ENABLE_VIRTUAL_TERMINAL_INPUT) catch null; + + defer if (comptime Environment.isWindows) { + if (original_mode) |mode| { + _ = bun.windows.SetConsoleMode(bun.win32.STDIN_FD.cast(), mode); + } + }; + + Output.flush(); + try setRawInput(true); + + const reader = std.io.getStdIn().reader(); + const val: bool = brk: { + var selected = default; + + while (true) { + Output.pretty("\r\x1B[K", .{}); + if (selected) { + Output.pretty(" Yes / ○ No", .{}); + } else { + Output.pretty("○ Yes / No", .{}); + } + Output.pretty("\x1B", .{}); + + Output.flush(); + const byte = try reader.readByte(); + + switch (byte) { + 'y', 'Y' => break :brk true, + 'n', 'N' => break :brk false, + '\r', '\n' => break :brk selected, + '\x1b' => { + const seq_byte = try reader.readByte(); + if (seq_byte != '[') continue; + const arrow = try reader.readByte(); + switch (arrow) { + 'C' => selected = false, // left arrow + 'D' => selected = true, // right arrow + else => {}, + } + }, + + else => continue, + } + Output.flush(); + } + }; + setRawInput(false) catch unreachable; + + Output.pretty("\r\x1B[A\r\x1B[K", .{}); + Output.pretty(label, .{}); + Output.pretty(" {s}\n", .{if (val) "yes" else "no"}); + Output.flush(); + + return val; + } + + pub fn promptSelect( + comptime label: []const u8, + opts: []const []const u8, + default: usize, + ) !usize { + Output.prettyln(label, .{}); + Output.flush(); + + const original_mode: if (Environment.isWindows) ?bun.windows.DWORD else void = if (comptime Environment.isWindows) + bun.win32.unsetStdioModeFlags(0, bun.windows.ENABLE_VIRTUAL_TERMINAL_INPUT) catch null; + + defer if (comptime Environment.isWindows) { + if (original_mode) |mode| { + _ = bun.windows.SetConsoleMode(bun.win32.STDIN_FD.cast(), mode); + } + }; + + Output.flush(); + try setRawInput(true); + + const reader = std.io.getStdIn().reader(); + const val: usize = brk: { + var selected = default; + + while (true) { + for (opts, 0..) |option, i| { + if (i == selected) { + Output.prettyln(" {s}", .{option}); + } else { + Output.prettyln("○ {s}", .{option}); + } + } + Output.pretty("\x1B[J", .{}); + Output.flush(); + + const byte = try reader.readByte(); + + switch (byte) { + '\r', '\n' => break :brk selected, + '\x1b' => { + const seq_byte = try reader.readByte(); + if (seq_byte != '[') continue; + const arrow = try reader.readByte(); + switch (arrow) { + 'A' => { // Up arrow + if (selected > 0) selected -= 1; + }, + 'B' => { // Down arrow + if (selected < opts.len - 1) selected += 1; + }, + else => continue, + } + }, + else => continue, + } + + for (0..opts.len) |_| { + Output.pretty("\x1B[A", .{}); + } + } + }; + setRawInput(false) catch unreachable; + + for (0..opts.len + 2) |_| { + Output.pretty("\x1B[A", .{}); + } + Output.pretty(label, .{}); + Output.prettyln(" {s}", .{opts[val]}); + Output.prettyln("\x1B[J", .{}); + Output.flush(); + + return val; + } + const Assets = struct { // "known" assets const @".gitignore" = @embedFile("init/gitignore.default"); @@ -300,19 +477,51 @@ pub const InitCommand = struct { fields.name = try normalizePackageName(alloc, name); - fields.entry_point = prompt( - alloc, - "entry point ", - fields.entry_point, - ) catch |err| { + const is_fullstack = promptBool(alloc, "do you want to create a fullstack app?", false) catch |err| { if (err == error.EndOfStream) return; return err; }; + if (is_fullstack) { + const template_options = [_][]const u8{ + "TypeScript (default)", + "React + TypeScript", + "React + TypeScript + Tailwind + shadcn/ui", + }; + + const selected = promptSelect("select fullstack template:", &template_options, 0) catch |err| { + if (err == error.EndOfStream) return; + return err; + }; + + // // Set appropriate entry point based on selection + // const fullstack_template = switch (selected) { + // 0 => .base_typescript, + // 1 => .react_typescript, + // 2 => .react_typescript_tailwind_shadcn, + // else => unreachable, + // }; + + // todo: + Output.prettyln("Setting up project with {s} template...", .{template_options[selected]}); + Output.flush(); + + // try SourceFileProjectGenerator.generate(); + } else { + fields.entry_point = prompt( + alloc, + "entry point ", + fields.entry_point, + ) catch |err| { + if (err == error.EndOfStream) return; + return err; + }; + } + try Output.writer().writeAll("\n"); Output.flush(); } else { - Output.prettyln("A package.json was found here. Would you like to configure", .{}); + Output.prettyln("A package.json was found here.", .{}); } } @@ -490,3 +699,5 @@ pub const InitCommand = struct { } } }; + +const SourceFileProjectGenerator = @import("../create/SourceFileProjectGenerator.zig");