summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-06-21 17:33:23 -0300
committerGabriel Schneider <[email protected]>2026-06-21 18:35:52 -0300
commit48cdaa01f2024e92109daa0bfbf0653588215c5f (patch)
treedc000961442208052f9688d380c1c10a17589e66
parent1a4733704b5a6d62ff87074c3027cf6942730925 (diff)
downloadpardes-48cdaa01f2024e92109daa0bfbf0653588215c5f.tar.gz
pardes-48cdaa01f2024e92109daa0bfbf0653588215c5f.zip
vibes mouse+tty is a good prototype
-rw-r--r--main.zig265
1 files changed, 240 insertions, 25 deletions
diff --git a/main.zig b/main.zig
index 60bc1290..d2d9a7af 100644
--- a/main.zig
+++ b/main.zig
@@ -41,7 +41,11 @@ pub const Command = struct {
};
const Loop = vaxis.Loop(@TypeOf(Command.value));
-const MAX_FILES: usize = 0x200;
+const MAX_LINES: usize = 0x1000;
+const MAX_LINE_LEN: usize = 0x200;
+const MAX_HISTORY: usize = 0x100;
+const FILE_PANE: usize = 0;
+const TTY_PANE: usize = 1;
fn ticker(io: std.Io, loop: *Loop) void {
while (true) {
@@ -79,12 +83,104 @@ fn readPty(io: std.Io, gpa: std.mem.Allocator, pty: std.Io.File, loop: *Loop) !v
try loop.postEvent(.quit);
}
+fn putLine(lines: *[MAX_LINES][MAX_LINE_LEN]u8, idx: usize, text: []const u8) void {
+ const len = @min(text.len, MAX_LINE_LEN - 1);
+ @memcpy(lines[idx][0..len], text[0..len]);
+ lines[idx][len] = 0;
+}
+
+fn putString(buf: *[MAX_LINE_LEN]u8, text: []const u8) usize {
+ const len = @min(text.len, MAX_LINE_LEN - 1);
+ @memcpy(buf[0..len], text[0..len]);
+ buf[len] = 0;
+ return len;
+}
+
+fn pathFrom(base: []const u8, name: []const u8, buf: *[MAX_LINE_LEN]u8) []const u8 {
+ if (std.fs.path.isAbsolute(name)) {
+ const len = putString(buf, name);
+ return buf[0..len];
+ }
+
+ return std.fmt.bufPrint(buf, "{s}/{s}", .{ base, name }) catch blk: {
+ const len = putString(buf, name);
+ break :blk buf[0..len];
+ };
+}
+
+fn splitLines(lines: *[MAX_LINES][MAX_LINE_LEN]u8, text: []const u8) usize {
+ var iter = std.mem.splitScalar(u8, text, '\n');
+ var i: usize = 0;
+ while (iter.next()) |line| : (i += 1) {
+ if (i >= MAX_LINES) break;
+ putLine(lines, i, line);
+ }
+ if (i == 0) {
+ putLine(lines, 0, "");
+ i = 1;
+ }
+ return i;
+}
+
+fn extractWord(str: []const u8, i: usize) []const u8 {
+ if (str.len == 0) return "";
+ var l = @min(i, str.len - 1);
+ var r = @min(i, str.len);
+
+ while (l > 0 and !std.ascii.isWhitespace(str[l - 1])) : (l -= 1) {}
+ while (r < str.len and !std.ascii.isWhitespace(str[r])) : (r += 1) {}
+
+ var word = str[l..r];
+ word = std.mem.trim(u8, word, " \t\r\n\"'`()[]{}<>;,");
+ if (std.mem.indexOfScalar(u8, word, ':')) |colon| {
+ word = word[0..colon];
+ }
+ return word;
+}
+
+fn loadFile(
+ io: std.Io,
+ gpa: std.mem.Allocator,
+ path: []const u8,
+ lines: *[MAX_LINES][MAX_LINE_LEN]u8,
+ rendered_lines: *usize,
+ current_path: *[MAX_LINE_LEN]u8,
+ current_path_len: *usize,
+) !void {
+ const path_len = @min(path.len, MAX_LINE_LEN - 1);
+ @memcpy(current_path[0..path_len], path[0..path_len]);
+ current_path[path_len] = 0;
+ current_path_len.* = path_len;
+
+ var file = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .read_only }) catch |err| {
+ const msg = try std.fmt.bufPrint(&lines[0], "could not open {s}: {}", .{ path, err });
+ lines[0][msg.len] = 0;
+ rendered_lines.* = 1;
+ return;
+ };
+ defer file.close(io);
+
+ var read_buf: [0x1000]u8 = undefined;
+ const flen = try file.length(io);
+ var reader = file.reader(io, &read_buf);
+ const text = reader.interface.readAlloc(gpa, @min(flen, 0x100000)) catch |err| {
+ const msg = try std.fmt.bufPrint(&lines[0], "could not read {s}: {}", .{ path, err });
+ lines[0][msg.len] = 0;
+ rendered_lines.* = 1;
+ return;
+ };
+ defer gpa.free(text);
+
+ rendered_lines.* = splitLines(lines, text);
+}
+
const nrows = 64;
const ncols = 100;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const gpa = init.gpa;
+ const args = try init.minimal.args.toSlice(init.arena.allocator());
var term: ghostty_vt.Terminal = try .init(gpa, .{
.cols = ncols,
@@ -95,6 +191,26 @@ pub fn main(init: std.process.Init) !void {
defer term_stream.deinit();
var tty_buf: [0x10000]u8 = undefined;
+ var display_lines: [2][MAX_LINES][MAX_LINE_LEN]u8 = undefined;
+ var rendered_lines = [_]usize{ 0, 0 };
+ var line_nums: [MAX_LINES][8]u8 = undefined;
+ var file_scroll: usize = 0;
+ var current_path: [MAX_LINE_LEN]u8 = undefined;
+ var current_path_len: usize = 0;
+ var term_cwd: [MAX_LINE_LEN]u8 = undefined;
+ var term_cwd_len = putString(&term_cwd, init.environ_map.get("PWD") orelse ".");
+ var cwd_history: [MAX_HISTORY][MAX_LINE_LEN]u8 = undefined;
+ var cwd_history_lens: [MAX_HISTORY]usize = undefined;
+ var cwd_history_len: usize = 0;
+ try loadFile(
+ io,
+ gpa,
+ if (args.len > 1) args[1] else "main.zig",
+ &display_lines[FILE_PANE],
+ &rendered_lines[FILE_PANE],
+ &current_path,
+ &current_path_len,
+ );
var tty = try vaxis.Tty.init(io, &tty_buf);
@@ -125,19 +241,99 @@ pub fn main(init: std.process.Init) !void {
var pty_reader = try io.concurrent(readPty, .{ io, gpa, pty_master, &loop });
defer _ = pty_reader.cancel(io) catch {};
defer pty_master.close(io);
- var dbg_print_buf: [0x1000]u8 = undefined;
while (true) {
const event = try loop.nextEvent();
const win = vx.window();
+ const left_outer_width: u16 = @max(1, win.width / 2);
+ const right_x: i16 = @intCast(left_outer_width);
switch (event) {
.mouse => |mouse| {
+ const mouse_on_left = mouse.col < right_x;
+
if (mouse.button == .wheel_down) {
- term.screens.active.scroll(.{ .delta_row = 1 });
+ if (mouse_on_left) {
+ if (file_scroll + 1 < rendered_lines[FILE_PANE]) file_scroll += 1;
+ } else {
+ term.screens.active.scroll(.{ .delta_row = 1 });
+ }
}
if (mouse.button == .wheel_up) {
- term.screens.active.scroll(.{ .delta_row = -1 });
+ if (mouse_on_left) {
+ if (file_scroll > 0) file_scroll -= 1;
+ } else {
+ term.screens.active.scroll(.{ .delta_row = -1 });
+ }
+ }
+
+ if (mouse.type == .press and (mouse.button == .left or mouse.button == .right)) {
+ var selected_word: ?[]const u8 = null;
+
+ if (mouse_on_left) {
+ const line_idx = @as(usize, @intCast(@max(mouse.row, 0))) + file_scroll;
+ if (line_idx < rendered_lines[FILE_PANE] and mouse.col >= 5) {
+ const len = std.mem.findSentinel(u8, 0, @ptrCast(&display_lines[FILE_PANE][line_idx]));
+ const selected_col: usize = @intCast(mouse.col - 5);
+ const selected_line = display_lines[FILE_PANE][line_idx][0..len];
+ selected_word = extractWord(selected_line, selected_col);
+ }
+ } else {
+ const line_idx: usize = @intCast(@max(mouse.row, 0));
+ if (line_idx < rendered_lines[TTY_PANE]) {
+ const len = std.mem.findSentinel(u8, 0, @ptrCast(&display_lines[TTY_PANE][line_idx]));
+ const selected_col: usize = @intCast(@max(mouse.col - right_x, 0));
+ const selected_line = display_lines[TTY_PANE][line_idx][0..len];
+ selected_word = extractWord(selected_line, selected_col);
+ }
+ }
+
+ if (selected_word) |word| {
+ if (word.len > 0) {
+ var found_path_buf: [MAX_LINE_LEN]u8 = undefined;
+ var candidate_buf: [MAX_LINE_LEN]u8 = undefined;
+ var found_path: []const u8 = "";
+ var found_kind: ?std.Io.File.Kind = null;
+
+ const current_candidate = pathFrom(term_cwd[0..term_cwd_len], word, &candidate_buf);
+ if (std.Io.Dir.cwd().statFile(io, current_candidate, .{})) |stat| {
+ const found_len = putString(&found_path_buf, current_candidate);
+ found_path = found_path_buf[0..found_len];
+ found_kind = stat.kind;
+ } else |_| {}
+
+ var h = cwd_history_len;
+ while (found_kind == null and h > 0) {
+ h -= 1;
+ const old_cwd = cwd_history[h][0..cwd_history_lens[h]];
+ const candidate = pathFrom(old_cwd, word, &candidate_buf);
+ if (std.Io.Dir.cwd().statFile(io, candidate, .{})) |stat| {
+ const found_len = putString(&found_path_buf, candidate);
+ found_path = found_path_buf[0..found_len];
+ found_kind = stat.kind;
+ } else |_| {}
+ }
+
+ if (found_kind) |kind| {
+ if (kind == .directory) {
+ if (cwd_history_len < MAX_HISTORY) {
+ cwd_history_lens[cwd_history_len] = putString(&cwd_history[cwd_history_len], term_cwd[0..term_cwd_len]);
+ cwd_history_len += 1;
+ }
+ term_cwd_len = putString(&term_cwd, found_path);
+
+ try writer.interface.writeAll("cd ");
+ try writer.interface.writeAll(found_path);
+ try writer.interface.writeAll(" && ls\r");
+ try writer.flush();
+ if (!mouse_on_left) term.scrollViewport(.bottom);
+ } else {
+ loadFile(io, gpa, found_path, &display_lines[FILE_PANE], &rendered_lines[FILE_PANE], &current_path, &current_path_len) catch {};
+ file_scroll = 0;
+ }
+ }
+ }
+ }
}
},
.key_press => |key| {
@@ -188,32 +384,51 @@ pub fn main(init: std.process.Init) !void {
// term.scrollViewport(.bottom);
const tty_str = try term.plainString(gpa);
defer gpa.free(tty_str);
+ rendered_lines[TTY_PANE] = splitLines(&display_lines[TTY_PANE], tty_str);
- const win_child = win.child(.{
- .border = .{ .where = .bottom },
- .height = nrows + 2,
- .width = ncols + 2,
+ const win_file = win.child(.{
+ .border = .{ .where = .right },
+ .width = left_outer_width,
});
- const win_dbg = win.child(.{
- .border = .{ .where = .all },
- .x_off = ncols + 2,
- .height = nrows + 2,
- .width = ncols + 2,
+ const win_term = win.child(.{
+ .x_off = @intCast(left_outer_width),
+ .width = win.width -| left_outer_width,
});
- win_child.clear();
- _ = win_child.printSegment(.{ .text = tty_str }, .{});
- win_child.showCursor(term.screens.active.cursor.x, term.screens.active.cursor.y);
+ win.clear();
+ _ = win_file.printSegment(.{ .text = current_path[0..current_path_len] }, .{
+ .row_offset = 0,
+ .col_offset = 5,
+ .wrap = .none,
+ });
- const dbg_str = try std.fmt.bufPrintSentinel(
- &dbg_print_buf,
- "c {} {}\npty pid {}\nwow it's working! debugging things will show here",
- .{
- pty_master.handle, term.screens.active.cursor.x, term.screens.active.cursor.y,
- },
- 0,
- );
- _ = win_dbg.printSegment(.{ .text = dbg_str }, .{});
+ const visible_lines = if (win_file.height > 1) win_file.height - 1 else 0;
+ var i: usize = 0;
+ while (i < visible_lines and i + file_scroll < rendered_lines[FILE_PANE]) : (i += 1) {
+ const line_idx = i + file_scroll;
+ const len = std.mem.findSentinel(u8, 0, @ptrCast(&display_lines[FILE_PANE][line_idx]));
+ const num = try std.fmt.bufPrint(&line_nums[i], "{d:0>4}", .{line_idx + 1});
+ _ = win_file.printSegment(.{ .text = num }, .{
+ .row_offset = @intCast(i + 1),
+ .col_offset = 0,
+ .wrap = .none,
+ });
+ _ = win_file.printSegment(.{ .text = display_lines[FILE_PANE][line_idx][0..len] }, .{
+ .row_offset = @intCast(i + 1),
+ .col_offset = 5,
+ .wrap = .none,
+ });
+ }
+
+ i = 0;
+ while (i < win_term.height and i < rendered_lines[TTY_PANE]) : (i += 1) {
+ const len = std.mem.findSentinel(u8, 0, @ptrCast(&display_lines[TTY_PANE][i]));
+ _ = win_term.printSegment(.{ .text = display_lines[TTY_PANE][i][0..len] }, .{
+ .row_offset = @intCast(i),
+ .wrap = .none,
+ });
+ }
+ win_term.showCursor(term.screens.active.cursor.x, term.screens.active.cursor.y);
try vx.render(tty.writer());
}