summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig108
1 files changed, 100 insertions, 8 deletions
diff --git a/src/main.zig b/src/main.zig
index b45cb7c..6922764 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -1,26 +1,118 @@
const std = @import("std");
const parser = @import("parser.zig");
const shell = @import("shell.zig");
+const builtins = @import("builtins.zig");
-var stdin_buffer: [4096]u8 = undefined;
-var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
-const stdin = &stdin_reader.interface;
+const BUILTINS = [_][]const u8{ "echo", "exit" };
-var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
-const stdout = &stdout_writer.interface;
+fn tryComplete(partial: []const u8) ?[]const u8 {
+ var matches: usize = 0;
+ var match: []const u8 = "";
+ for (BUILTINS) |builtin| {
+ if (std.mem.startsWith(u8, builtin, partial)) {
+ matches += 1;
+ match = builtin;
+ if (matches > 1) return null;
+ }
+ }
+
+ if (matches == 1) return match;
+ return null;
+}
+
+fn enableRawMode(fd: std.posix.fd_t) !std.posix.termios {
+ const original = try std.posix.tcgetattr(fd);
+ var raw = original;
+
+ // Disable canonical mode and echo
+ raw.lflag.ICANON = false;
+ raw.lflag.ECHO = false;
+
+ // Set minimum bytes and timeout for read
+ raw.cc[@intFromEnum(std.posix.V.MIN)] = 1;
+ raw.cc[@intFromEnum(std.posix.V.TIME)] = 0;
+
+ try std.posix.tcsetattr(fd, .FLUSH, raw);
+ return original;
+}
+
+fn disableRawMode(fd: std.posix.fd_t, original: std.posix.termios) !void {
+ try std.posix.tcsetattr(fd, .FLUSH, original);
+}
+
+fn readCommand(allocator: std.mem.Allocator) !?[]const u8 {
+ const stdin = std.fs.File.stdin();
+ const stdout = std.fs.File.stdout();
+
+ const stdin_fd = stdin.handle;
+
+ // Only use raw mode if stdin is a terminal
+ const is_tty = std.posix.isatty(stdin_fd);
+ const original_termios = if (is_tty) try enableRawMode(stdin_fd) else null;
+ defer if (original_termios) |orig| disableRawMode(stdin_fd, orig) catch {};
+
+ var buffer = std.ArrayList(u8){};
+ defer buffer.deinit(allocator);
+
+ var byte: [1]u8 = undefined;
+
+ while (true) {
+ const bytes_read = try stdin.read(&byte);
+ if (bytes_read == 0) return null;
+
+ const c = byte[0];
+
+ if (c == '\n' or c == '\r') {
+ return try buffer.toOwnedSlice(allocator);
+ } else if (c == '\t' and is_tty) {
+ const partial = buffer.items;
+ if (partial.len > 0 and std.mem.indexOf(u8, partial, " ") == null) {
+ if (tryComplete(partial)) |completion| {
+ const remaining = completion[partial.len..];
+ try stdout.writeAll(remaining);
+ try stdout.writeAll(" ");
+ try buffer.appendSlice(allocator, remaining);
+ try buffer.append(allocator, ' ');
+ }
+ }
+ } else if ((c == 127 or c == 8) and is_tty) {
+ if (buffer.items.len > 0) {
+ _ = buffer.pop();
+ try stdout.writeAll("\x08 \x08");
+ }
+ } else if (c >= 32 and c < 127) {
+ try buffer.append(allocator, c);
+ if (is_tty) {
+ try stdout.writeAll(&[_]u8{c});
+ }
+ } else if (c == '\t' and !is_tty) {
+ // When not a TTY, tab is part of the input (for testing)
+ try buffer.append(allocator, c);
+ }
+ }
+}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
+ const stdout = std.fs.File.stdout();
+
while (true) {
- try stdout.print("$ ", .{});
+ try stdout.writeAll("$ ");
- const command = try stdin.takeDelimiter('\n');
+ const command = try readCommand(allocator);
if (command) |cmd| {
+ defer allocator.free(cmd);
+ try stdout.writeAll("\n");
+
const parsed = parser.parseCommand(cmd);
- const result = try shell.executeCommand(allocator, stdout, parsed.name, parsed.args, parsed.output_redirect, parsed.error_redirect, parsed.append_output, parsed.append_error);
+
+ var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
+ const stdout_iface = &stdout_writer.interface;
+
+ const result = try shell.executeCommand(allocator, stdout_iface, parsed.name, parsed.args, parsed.output_redirect, parsed.error_redirect, parsed.append_output, parsed.append_error);
if (result == .exit_shell) break;
} else {