New modules (13): - src/resize.zig: SIGWINCH terminal resize detection - src/drag.zig: Mouse drag state and Splitter panels - src/diagnostic.zig: Elm-style error messages with code snippets - src/debug.zig: Debug overlay (FPS, timing, widget count) - src/profile.zig: Performance profiling with scoped timers - src/sixel.zig: Sixel graphics encoding for terminal images - src/async_loop.zig: epoll-based async event loop with timers - src/compose.zig: Widget composition utilities - src/shortcuts.zig: Keyboard shortcut registry - src/widgets/logo.zig: ASCII art logo widget Enhanced modules: - src/layout.zig: Added Constraint.ratio(num, denom) - src/terminal.zig: Integrated resize handling - src/root.zig: Re-exports all new modules New examples (9): - resize_demo, splitter_demo, dirtree_demo - help_demo, markdown_demo, progress_demo - spinner_demo, syntax_demo, viewport_demo Package manager: - build.zig.zon: Zig package manager support Stats: 60+ source files, 186+ tests, 20 executables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
126 lines
4.2 KiB
Zig
126 lines
4.2 KiB
Zig
//! Directory Tree Demo - File browser widget
|
|
//!
|
|
//! Run with: zig build dirtree-demo
|
|
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
const Terminal = zcatui.Terminal;
|
|
const Rect = zcatui.Rect;
|
|
const Buffer = zcatui.Buffer;
|
|
const Style = zcatui.Style;
|
|
const Color = zcatui.Color;
|
|
const Block = zcatui.widgets.Block;
|
|
const Borders = zcatui.widgets.Borders;
|
|
const DirectoryTree = zcatui.widgets.DirectoryTree;
|
|
// TreeSymbols is in dirtree_mod, not exported directly
|
|
const DirTreeSymbols = zcatui.widgets.dirtree_mod.TreeSymbols;
|
|
|
|
/// State for the demo
|
|
const State = struct {
|
|
tree: *DirectoryTree,
|
|
use_ascii: bool = false,
|
|
running: bool = true,
|
|
path: []const u8,
|
|
};
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const allocator = gpa.allocator();
|
|
|
|
var term = try Terminal.init(allocator);
|
|
defer term.deinit();
|
|
|
|
// Get current directory
|
|
const start_path = std.fs.cwd().realpathAlloc(allocator, ".") catch "/tmp";
|
|
defer allocator.free(start_path);
|
|
|
|
// Create directory tree
|
|
var tree = DirectoryTree.init(allocator, start_path) catch |err| {
|
|
std.debug.print("Failed to open directory: {}\n", .{err});
|
|
return;
|
|
};
|
|
defer tree.deinit();
|
|
|
|
var state = State{
|
|
.tree = &tree,
|
|
.path = start_path,
|
|
};
|
|
|
|
while (state.running) {
|
|
try term.drawWithContext(&state, render);
|
|
|
|
if (try term.pollEvent(100)) |event| {
|
|
switch (event) {
|
|
.key => |key| {
|
|
switch (key.code) {
|
|
.char => |c| {
|
|
if (c == 'q') state.running = false;
|
|
if (c == 'j') state.tree.moveDown();
|
|
if (c == 'k') state.tree.moveUp();
|
|
if (c == 'l' or c == 'o') state.tree.toggleExpand() catch {};
|
|
if (c == 'h') state.tree.collapse() catch {};
|
|
if (c == '.') state.tree.toggleHidden() catch {};
|
|
if (c == 'i') state.use_ascii = !state.use_ascii;
|
|
if (c == 'g') state.tree.goToTop();
|
|
if (c == 'G') state.tree.goToBottom();
|
|
},
|
|
.up => state.tree.moveUp(),
|
|
.down => state.tree.moveDown(),
|
|
.left => state.tree.collapse() catch {},
|
|
.right => state.tree.toggleExpand() catch {},
|
|
.enter => state.tree.toggleExpand() catch {},
|
|
.home => state.tree.goToTop(),
|
|
.end => state.tree.goToBottom(),
|
|
.esc => state.running = false,
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn render(state: *State, area: Rect, buf: *Buffer) void {
|
|
// Main border
|
|
const block = Block.init()
|
|
.title(" Directory Tree ")
|
|
.setBorders(Borders.all)
|
|
.borderStyle((Style{}).fg(Color.cyan));
|
|
block.render(area, buf);
|
|
|
|
// Path display
|
|
var path_buf: [256]u8 = undefined;
|
|
const current_path = state.tree.getSelectedPath() orelse state.path;
|
|
const path_display = std.fmt.bufPrint(&path_buf, " {s} ", .{current_path}) catch "...";
|
|
_ = buf.setString(2, 1, path_display, (Style{}).fg(Color.yellow));
|
|
|
|
// Tree view
|
|
const tree_area = Rect.init(1, 2, area.width -| 2, area.height -| 4);
|
|
|
|
// Configure symbols based on preference
|
|
if (state.use_ascii) {
|
|
var configured = state.tree.setSymbols(DirTreeSymbols.ascii);
|
|
configured.render(tree_area, buf);
|
|
} else {
|
|
state.tree.render(tree_area, buf);
|
|
}
|
|
|
|
// File info panel
|
|
if (state.tree.getSelected()) |node| {
|
|
var info_buf: [128]u8 = undefined;
|
|
const info = std.fmt.bufPrint(&info_buf, "Selected: {s}", .{node.name}) catch "...";
|
|
_ = buf.setString(2, area.height -| 2, info, (Style{}).fg(Color.white));
|
|
}
|
|
|
|
// Footer
|
|
const footer = "j/k nav | l expand | h collapse | . hidden | i ascii | g/G top/bottom | q quit";
|
|
_ = buf.setString(
|
|
2,
|
|
area.height -| 1,
|
|
footer,
|
|
(Style{}).fg(Color.rgb(100, 100, 100)),
|
|
);
|
|
}
|