const std = @import("std"); const vaxis = @import("vaxis"); // basic file navigation // basic shell io // basic layouting: for now following acme and ad // * /home/goblin/05-genizah/plan9port/src/cmd/acme // * /home/goblin/05-genizah/ad // * /usr/lib/zig // * ../03-plano // right click and enter on lines to open files at specified lines pub const Command = struct { prev: ?*Command = null, pub var value: union(enum) { close, ping, nop, quit, tick, key_press: vaxis.Key, winsize: vaxis.Winsize, mouse: vaxis.Mouse, } = .nop; }; const Loop = vaxis.Loop(@TypeOf(Command.value)); const MAX_FILES: usize = 0x200; var rendered_lines: usize = 8; fn ticker(io: std.Io, loop: *Loop) void { while (true) { io.sleep(.fromMilliseconds(200), .awake) catch return; _ = loop.tryPostEvent(.tick) catch return; } } fn extract_word(str: []u8, i: usize) []u8 { std.debug.assert(i < str.len); var l = i; var r = i; while (l > 0 and (!std.ascii.isWhitespace(str[l - 1]))) : (l -= 1) {} while (r < str.len and (!std.ascii.isWhitespace(str[r]))) : (r += 1) {} return str[l..r]; } pub fn main(init: std.process.Init) !void { std.debug.print("hello!\n", .{}); const io = init.io; const gpa = init.gpa; var scroll_off: u32 = 0; const MAX_LEN: usize = 0x100; var tty_buf: [0x10000]u8 = undefined; var lines: [0x1000][MAX_LEN]u8 = undefined; const lstart = 5; const colstart = 4; const dir = std.Io.Dir.cwd(); var cwd = try std.Io.Dir.openDir(dir, io, ".", .{ .iterate = true }); const cwd_len = try cwd.realPath(io, &lines[2]); @memcpy(lines[0][0..1], "."); lines[0][1] = 0; @memcpy(lines[1][0..2], ".."); lines[1][2] = 0; lines[2][cwd_len] = 0; lines[3][0] = 0; lines[4][0] = 0; { var i: usize = 0; var walk = try cwd.walkSelectively(gpa); defer walk.deinit(); while (try walk.next(io)) |e| : (i += 1) { if (i > MAX_FILES) break; @memcpy(lines[i + lstart][0..e.path.len], e.path); lines[i + lstart][e.path.len] = 0; } rendered_lines = i; } var tty = try vaxis.Tty.init(io, &tty_buf); var vx = try vaxis.init(io, gpa, init.environ_map, .{}); defer vx.deinit(gpa, tty.writer()); try vx.enterAltScreen(tty.writer()); defer vx.exitAltScreen(tty.writer()) catch {}; try vx.setMouseMode(tty.writer(), true); var loop: Loop = .init(io, &tty, &vx); try loop.start(); defer loop.stop(); var tick_task = try io.concurrent(ticker, .{ io, &loop }); defer tick_task.cancel(io); var idk: u64 = 0; var last_scroll = io.vtable.now(@ptrCast(&idk), .real); while (true) { const event = try loop.nextEvent(); const win = vx.window(); var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); switch (event) { .key_press => |key| { if (key.matches('c', .{ .ctrl = true }) or key.matches(vaxis.Key.escape, .{})) { break; } }, .mouse => |m| { if (m.type == .press and (m.button == .wheel_down or m.button == .wheel_up)) { const delta = last_scroll.untilNow(io, .real); // basic debouncing if (delta.toMilliseconds() < 30) continue; last_scroll = io.vtable.now(@ptrCast(&idk), .real); if (m.type == .press and m.button == .wheel_down) { scroll_off += 1; win.clear(); } if (m.type == .press and m.button == .wheel_up) { if (scroll_off > 0) { scroll_off -= 1; win.clear(); } continue; } } _ = try std.fmt.bufPrintSentinel(&lines[3], "{any}", .{m}, 0); if (m.type == .press and (m.button == .right or m.button == .left)) { // const txt = try std.fmt.allocPrint(gpa, "col={} row={} xo={} yo={}", .{ m.col, m.row, m.xoffset, m.yoffset }); const line_idx = @as(usize, @intCast(m.row)) + scroll_off; const len = std.mem.findSentinel(u8, 0, @ptrCast(&lines[line_idx])); const selected_col = @as(usize, @intCast(m.col)) - colstart; const selected_line = lines[line_idx][0..len]; const next = extract_word(selected_line, selected_col); if (cwd.openDir(io, next, .{ .iterate = true })) |d| { cwd = d; var walk = try cwd.walkSelectively(gpa); defer walk.deinit(); var i: usize = 0; while (try walk.next(io)) |e| : (i += 1) { if (i > MAX_FILES) break; @memcpy(lines[i + lstart][0..e.path.len], e.path); lines[i + lstart][e.path.len] = 0; } rendered_lines = i; win.clear(); // if it's not a dir maybe it's a file } else |_| { if (cwd.openFile(io, next, .{ .mode = .read_only })) |f| { var buf: [0x100]u8 = undefined; const flen = try f.length(io); var reader = f.reader(io, &buf); const str = reader.interface.readAlloc(gpa, flen) catch "empty"; var iter = std.mem.splitAny(u8, str, "\n"); var i: usize = 0; while (iter.next()) |line| : (i += 1) { if (i > MAX_FILES) break; @memcpy(lines[i + lstart][0..line.len], line); lines[i + lstart][line.len] = 0; } rendered_lines = i; win.clear(); } else |_| {} } scroll_off = 0; } }, .winsize => |ws| try vx.resize(gpa, tty.writer(), ws), else => {}, } for (0..@min(MAX_FILES, rendered_lines + lstart)) |i| { const len = @min(std.mem.findSentinel(u8, 0, @ptrCast(&lines[i + scroll_off])), MAX_LEN - 1); _ = win.printSegment(.{ .text = lines[i + scroll_off][0..len] }, .{ .row_offset = @intCast(i), .col_offset = colstart, .wrap = .none, }); const num = try std.fmt.allocPrintSentinel(arena.allocator(), "{:02}", .{i + scroll_off}, 0); _ = win.printSegment(.{ .text = num }, .{ .row_offset = @intCast(i), .col_offset = 0, .wrap = .none, }); } try vx.render(tty.writer()); } }