ls but with io_uring

feat: add OSC 8 hyperlinks

Fixes: https://tangled.sh/@rockorager.dev/lsr/issues/2

rockorager.dev b4b166f6 8e3319ec

verified
+52 -7
+52 -7
src/main.zig
··· 33 33 color: When = .auto, 34 34 shortview: enum { columns, oneline } = .oneline, 35 35 @"group-directories-first": bool = true, 36 + hyperlinks: When = .auto, 36 37 icons: When = .auto, 37 38 long: bool = false, 38 39 ··· 101 102 } 102 103 } 103 104 105 + fn useHyperlinks(self: Options) bool { 106 + switch (self.hyperlinks) { 107 + .never => return false, 108 + .always => return true, 109 + .auto => return self.isatty(), 110 + } 111 + } 112 + 104 113 fn isatty(self: Options) bool { 105 114 return self.winsize != null; 106 115 } ··· 182 191 } else if (eql(opt, "color")) { 183 192 cmd.opts.color = std.meta.stringToEnum(Options.When, val) orelse { 184 193 try stderr.print("Invalid color option: '{s}'", .{val}); 194 + std.process.exit(1); 195 + }; 196 + } else if (eql(opt, "hyperlinks")) { 197 + cmd.opts.hyperlinks = std.meta.stringToEnum(Options.When, val) orelse { 198 + try stderr.print("Invalid hyperlinks option: '{s}'", .{val}); 185 199 std.process.exit(1); 186 200 }; 187 201 } else if (eql(opt, "icons")) { ··· 332 346 for (columns.items, 0..) |column, i| { 333 347 if (row >= column.entries.len) continue; 334 348 const entry = column.entries[row]; 335 - try printShortEntry(column.entries[row], cmd.opts, writer); 349 + try printShortEntry(column.entries[row], cmd, writer); 336 350 337 351 if (i < columns.items.len - 1) { 338 352 const spaces = column.width - (icon_width + entry.name.len); ··· 347 361 return idx + n_short_cols >= n_cols; 348 362 } 349 363 350 - fn printShortEntry(entry: Entry, opts: Options, writer: anytype) !void { 364 + fn printShortEntry(entry: Entry, cmd: Command, writer: anytype) !void { 365 + const opts = cmd.opts; 351 366 const colors = opts.colors; 352 367 if (opts.useIcons()) { 353 368 const icon = Icon.get(entry, opts); ··· 371 386 } 372 387 }, 373 388 } 374 - try writer.writeAll(entry.name); 375 - try writer.writeAll(colors.reset); 389 + 390 + if (opts.useHyperlinks()) { 391 + const path = try std.fs.path.join(cmd.arena, &.{ opts.directory, entry.name }); 392 + try writer.print("\x1b]8;;file://{s}\x1b\\", .{path}); 393 + try writer.writeAll(entry.name); 394 + try writer.writeAll("\x1b]8;;\x1b\\"); 395 + try writer.writeAll(colors.reset); 396 + } else { 397 + try writer.writeAll(entry.name); 398 + try writer.writeAll(colors.reset); 399 + } 376 400 } 377 401 378 402 fn printShortOneRow(cmd: Command, writer: anytype) !void { ··· 385 409 386 410 fn printShortOnePerLine(cmd: Command, writer: anytype) !void { 387 411 for (cmd.entries) |entry| { 388 - try printShortEntry(entry, cmd.opts, writer); 412 + try printShortEntry(entry, cmd, writer); 389 413 try writer.writeAll("\r\n"); 390 414 } 391 415 } ··· 504 528 } 505 529 }, 506 530 } 507 - try writer.writeAll(entry.name); 531 + 532 + if (cmd.opts.useHyperlinks()) { 533 + const path = try std.fs.path.join(cmd.arena, &.{ cmd.opts.directory, entry.name }); 534 + try writer.print("\x1b]8;;file://{s}\x1b\\", .{path}); 535 + try writer.writeAll(entry.name); 536 + try writer.writeAll("\x1b]8;;\x1b\\"); 537 + } else { 538 + try writer.writeAll(entry.name); 539 + } 508 540 try writer.writeAll(colors.reset); 509 541 510 542 if (entry.kind == .sym_link) { ··· 513 545 colors.symlink_missing 514 546 else 515 547 colors.symlink_target; 548 + 516 549 try writer.writeAll(color); 517 - try writer.writeAll(entry.link_name); 550 + if (cmd.opts.useHyperlinks()) { 551 + try writer.print("\x1b]8;;file://{s}\x1b\\", .{entry.link_name}); 552 + try writer.writeAll(entry.link_name); 553 + try writer.writeAll("\x1b]8;;\x1b\\"); 554 + } else { 555 + try writer.writeAll(entry.link_name); 556 + } 518 557 try writer.writeAll(colors.reset); 519 558 } 520 559 ··· 682 721 // we are async, no need to defer! 683 722 _ = try io.close(fd, .{}); 684 723 const dir: std.fs.Dir = .{ .fd = fd }; 724 + 725 + if (cmd.opts.useHyperlinks()) { 726 + var buf: [std.fs.max_path_bytes]u8 = undefined; 727 + const cwd = try std.os.getFdPath(fd, &buf); 728 + cmd.opts.directory = try cmd.arena.dupeZ(u8, cwd); 729 + } 685 730 686 731 var temp_results: std.ArrayListUnmanaged(MinimalEntry) = .empty; 687 732