ls but with io_uring

add #1: support tree output

authored by

theMackabu and committed by rockorager.dev cb16ce70 429eaeff

+151 -2
+151 -2
src/main.zig
··· 26 26 \\ -l, --long Display extended file metadata 27 27 \\ -r, --reverse Reverse the sort order 28 28 \\ -t, --time Sort the entries by modification time, most recent first 29 + \\ --tree[=DEPTH] Display entries in a tree format (optional limit depth) 29 30 \\ 30 31 ; 31 32 ··· 42 43 long: bool = false, 43 44 sort_by_mod_time: bool = false, 44 45 reverse_sort: bool = false, 46 + tree: bool = false, 47 + tree_depth: ?usize = null, 45 48 46 49 directories: std.ArrayListUnmanaged([:0]const u8) = .empty, 47 50 file: ?[]const u8 = null, ··· 241 244 try stderr.print("Invalid boolean: '{s}'", .{val}); 242 245 std.process.exit(1); 243 246 }; 247 + } else if (eql(opt, "tree")) { 248 + if (val.len == 0) { 249 + cmd.opts.tree = true; 250 + cmd.opts.tree_depth = null; // unlimited depth 251 + } else { 252 + cmd.opts.tree = true; 253 + cmd.opts.tree_depth = std.fmt.parseInt(usize, val, 10) catch { 254 + try stderr.print("Invalid tree depth: '{s}'", .{val}); 255 + std.process.exit(1); 256 + }; 257 + } 244 258 } else if (eql(opt, "help")) { 245 259 return stderr.writeAll(usage); 246 260 } else if (eql(opt, "version")) { ··· 320 334 std.mem.reverse(Entry, cmd.entries); 321 335 } 322 336 323 - if (multiple_dirs) { 337 + if (multiple_dirs and !cmd.opts.tree) { 324 338 if (dir_idx > 0) try bw.writer().writeAll("\r\n"); 325 339 try bw.writer().print("{s}:\r\n", .{directory}); 326 340 } 327 341 328 - if (cmd.opts.long) { 342 + if (cmd.opts.tree) { 343 + if (multiple_dirs and dir_idx > 0) try bw.writer().writeAll("\r\n"); 344 + try printTree(cmd, bw.writer()); 345 + } else if (cmd.opts.long) { 329 346 try printLong(cmd, bw.writer()); 330 347 } else switch (cmd.opts.shortview) { 331 348 .columns => try printShortColumns(cmd, bw.writer()), ··· 470 487 for (cmd.entries) |entry| { 471 488 try printShortEntry(entry, cmd, writer); 472 489 try writer.writeAll("\r\n"); 490 + } 491 + } 492 + 493 + fn drawTreePrefix(writer: anytype, prefix_list: []const bool, is_last: bool) !void { 494 + for (prefix_list) |is_last_at_level| { 495 + if (is_last_at_level) { 496 + try writer.writeAll(" "); 497 + } else { 498 + try writer.writeAll("│ "); 499 + } 500 + } 501 + 502 + if (is_last) { 503 + try writer.writeAll("└── "); 504 + } else { 505 + try writer.writeAll("├── "); 506 + } 507 + } 508 + 509 + fn printTree(cmd: Command, writer: anytype) !void { 510 + const dir_name = if (std.mem.eql(u8, cmd.current_directory, ".")) blk: { 511 + var buf: [std.fs.max_path_bytes]u8 = undefined; 512 + const cwd = try std.process.getCwd(&buf); 513 + break :blk std.fs.path.basename(cwd); 514 + } else std.fs.path.basename(cmd.current_directory); 515 + 516 + try writer.print("{s}\n", .{dir_name}); 517 + 518 + const max_depth = cmd.opts.tree_depth orelse std.math.maxInt(usize); 519 + var prefix_list = std.ArrayList(bool).init(cmd.arena); 520 + 521 + for (cmd.entries, 0..) |entry, i| { 522 + const is_last = i == cmd.entries.len - 1; 523 + 524 + try drawTreePrefix(writer, prefix_list.items, is_last); 525 + try printShortEntry(entry, cmd, writer); 526 + try writer.writeAll("\r\n"); 527 + 528 + if (entry.kind == .directory and max_depth > 0) { 529 + const full_path = try std.fs.path.joinZ(cmd.arena, &.{ cmd.current_directory, entry.name }); 530 + 531 + try prefix_list.append(is_last); 532 + try recurseTree(cmd, writer, full_path, &prefix_list, 1, max_depth); 533 + 534 + _ = prefix_list.pop(); 535 + } 536 + } 537 + } 538 + 539 + fn recurseTree(cmd: Command, writer: anytype, dir_path: [:0]const u8, prefix_list: *std.ArrayList(bool), depth: usize, max_depth: usize) !void { 540 + var dir = std.fs.cwd().openDir(dir_path, .{ .iterate = true }) catch { return; }; 541 + defer dir.close(); 542 + 543 + var entries = std.ArrayList(Entry).init(cmd.arena); 544 + var iter = dir.iterate(); 545 + 546 + while (try iter.next()) |dirent| { 547 + if (!cmd.opts.showDotfiles() and std.mem.startsWith(u8, dirent.name, ".")) continue; 548 + 549 + const nameZ = try cmd.arena.dupeZ(u8, dirent.name); 550 + try entries.append(.{ 551 + .name = nameZ, 552 + .kind = dirent.kind, 553 + .statx = undefined, 554 + }); 555 + } 556 + 557 + std.sort.pdq(Entry, entries.items, cmd.opts, Entry.lessThan); 558 + 559 + if (cmd.opts.reverse_sort) { 560 + std.mem.reverse(Entry, entries.items); 561 + } 562 + 563 + for (entries.items, 0..) |entry, i| { 564 + const is_last = i == entries.items.len - 1; 565 + 566 + try drawTreePrefix(writer, prefix_list.items, is_last); 567 + try printTreeEntry(entry, cmd, writer, dir_path); 568 + try writer.writeAll("\r\n"); 569 + 570 + if (entry.kind == .directory and depth < max_depth) { 571 + const full_path = try std.fs.path.joinZ(cmd.arena, &.{ dir_path, entry.name }); 572 + 573 + try prefix_list.append(is_last); 574 + try recurseTree(cmd, writer, full_path, prefix_list, depth + 1, max_depth); 575 + 576 + _ = prefix_list.pop(); 577 + } 578 + } 579 + } 580 + 581 + fn printTreeEntry(entry: Entry, cmd: Command, writer: anytype, dir_path: [:0]const u8) !void { 582 + const opts = cmd.opts; 583 + const colors = opts.colors; 584 + 585 + if (opts.useIcons()) { 586 + const icon = Icon.get(entry); 587 + 588 + if (opts.useColor()) { 589 + try writer.writeAll(icon.color); 590 + try writer.writeAll(icon.icon); 591 + try writer.writeAll(colors.reset); 592 + } else { 593 + try writer.writeAll(icon.icon); 594 + } 595 + 596 + try writer.writeByte(' '); 597 + } 598 + 599 + switch (entry.kind) { 600 + .directory => try writer.writeAll(colors.dir), 601 + .sym_link => try writer.writeAll(colors.symlink), 602 + else => { 603 + const full_path = try std.fs.path.join(cmd.arena, &.{ dir_path, entry.name }); 604 + const stat_result = std.fs.cwd().statFile(full_path) catch null; 605 + if (stat_result) |stat| { 606 + if (stat.mode & (std.posix.S.IXUSR | std.posix.S.IXGRP | std.posix.S.IXOTH) != 0) { 607 + try writer.writeAll(colors.executable); 608 + } 609 + } 610 + }, 611 + } 612 + 613 + if (opts.useHyperlinks()) { 614 + const path = try std.fs.path.join(cmd.arena, &.{ dir_path, entry.name }); 615 + try writer.print("\x1b]8;;file://{s}\x1b\\", .{path}); 616 + try writer.writeAll(entry.name); 617 + try writer.writeAll("\x1b]8;;\x1b\\"); 618 + try writer.writeAll(colors.reset); 619 + } else { 620 + try writer.writeAll(entry.name); 621 + try writer.writeAll(colors.reset); 473 622 } 474 623 } 475 624