summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig118
1 files changed, 110 insertions, 8 deletions
diff --git a/src/main.zig b/src/main.zig
index cb9dfff..7e1d335 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -5,6 +5,54 @@ const builtins = @import("builtins.zig");
const BUILTINS = [_][]const u8{ "echo", "exit" };
+fn findAllMatches(allocator: std.mem.Allocator, partial: []const u8) !std.ArrayList([]const u8) {
+ var matches = std.ArrayList([]const u8){};
+
+ for (BUILTINS) |builtin| {
+ if (std.mem.startsWith(u8, builtin, partial)) {
+ const duped = try allocator.dupe(u8, builtin);
+ try matches.append(allocator, duped);
+ }
+ }
+
+ const path_env = std.posix.getenv("PATH") orelse return matches;
+
+ var path_iter = std.mem.splitScalar(u8, path_env, ':');
+ while (path_iter.next()) |dir_path| {
+ if (dir_path.len == 0) continue;
+
+ var dir = std.fs.openDirAbsolute(dir_path, .{ .iterate = true }) catch continue;
+ defer dir.close();
+
+ var iter = dir.iterate();
+ while (iter.next() catch continue) |entry| {
+ if (entry.kind == .file or entry.kind == .sym_link) {
+ if (std.mem.startsWith(u8, entry.name, partial)) {
+ const stat = dir.statFile(entry.name) catch continue;
+ if (stat.mode & 0o111 != 0) {
+ var already_exists = false;
+ for (matches.items) |existing| {
+ if (std.mem.eql(u8, existing, entry.name)) {
+ already_exists = true;
+ break;
+ }
+ }
+ if (!already_exists) {
+ const duped = allocator.dupe(u8, entry.name) catch continue;
+ matches.append(allocator, duped) catch {
+ allocator.free(duped);
+ continue;
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return matches;
+}
+
fn tryComplete(allocator: std.mem.Allocator, partial: []const u8) ?[]const u8 {
var matches: usize = 0;
var match: ?[]const u8 = null;
@@ -108,6 +156,8 @@ fn readCommand(allocator: std.mem.Allocator) !?[]const u8 {
defer buffer.deinit(allocator);
var byte: [1]u8 = undefined;
+ var last_tab_partial: ?[]const u8 = null;
+ var last_was_tab = false;
while (true) {
const bytes_read = try stdin.read(&byte);
@@ -116,19 +166,61 @@ fn readCommand(allocator: std.mem.Allocator) !?[]const u8 {
const c = byte[0];
if (c == '\n' or c == '\r') {
+ if (last_tab_partial) |p| allocator.free(p);
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(allocator, partial)) |completion| {
- defer allocator.free(completion);
- const remaining = completion[partial.len..];
- try stdout.writeAll(remaining);
- try stdout.writeAll(" ");
- try buffer.appendSlice(allocator, remaining);
- try buffer.append(allocator, ' ');
+ const is_double_tab = last_was_tab and last_tab_partial != null and std.mem.eql(u8, last_tab_partial.?, partial);
+
+ if (is_double_tab) {
+ var matches = try findAllMatches(allocator, partial);
+ defer {
+ for (matches.items) |match| allocator.free(match);
+ matches.deinit(allocator);
+ }
+
+ if (matches.items.len > 1) {
+ std.mem.sort([]const u8, matches.items, {}, struct {
+ fn lessThan(_: void, a: []const u8, b: []const u8) bool {
+ return std.mem.order(u8, a, b) == .lt;
+ }
+ }.lessThan);
+
+ try stdout.writeAll("\n");
+ for (matches.items, 0..) |match, i| {
+ try stdout.writeAll(match);
+ if (i < matches.items.len - 1) {
+ try stdout.writeAll(" ");
+ }
+ }
+ try stdout.writeAll("\n$ ");
+ try stdout.writeAll(partial);
+ }
+
+ last_was_tab = false;
+ if (last_tab_partial) |p| {
+ allocator.free(p);
+ last_tab_partial = null;
+ }
} else {
- try stdout.writeAll("\x07");
+ if (tryComplete(allocator, partial)) |completion| {
+ defer allocator.free(completion);
+ const remaining = completion[partial.len..];
+ try stdout.writeAll(remaining);
+ try stdout.writeAll(" ");
+ try buffer.appendSlice(allocator, remaining);
+ try buffer.append(allocator, ' ');
+ last_was_tab = false;
+ if (last_tab_partial) |p| allocator.free(p);
+ last_tab_partial = null;
+ } else {
+ // No unique completion - ring bell and remember this for double-tab
+ try stdout.writeAll("\x07");
+ last_was_tab = true;
+ if (last_tab_partial) |p| allocator.free(p);
+ last_tab_partial = try allocator.dupe(u8, partial);
+ }
}
}
} else if ((c == 127 or c == 8) and is_tty) {
@@ -136,11 +228,21 @@ fn readCommand(allocator: std.mem.Allocator) !?[]const u8 {
_ = buffer.pop();
try stdout.writeAll("\x08 \x08");
}
+ last_was_tab = false;
+ if (last_tab_partial) |p| {
+ allocator.free(p);
+ last_tab_partial = null;
+ }
} else if (c >= 32 and c < 127) {
try buffer.append(allocator, c);
if (is_tty) {
try stdout.writeAll(&[_]u8{c});
}
+ last_was_tab = false;
+ if (last_tab_partial) |p| {
+ allocator.free(p);
+ last_tab_partial = null;
+ }
} else if (c == '\t' and !is_tty) {
try buffer.append(allocator, c);
}