New features: - Copy text to system clipboard (Ctrl+V paste) - Copy to X11 primary selection (middle-click paste) - Copy to both selections simultaneously - Clear clipboard contents - Query clipboard (limited terminal support) - Parse OSC 52 responses API: - copy(allocator, writer, text) - allocating version - copySmall(writer, text) - stack-based for text <= 1024 bytes - copyTo/copySmallTo - select target (clipboard/primary/both) - clear(writer) - clear clipboard - parseResponse(allocator, response) - decode clipboard responses Example: clipboard_demo.zig with interactive testing Tests: 8 clipboard tests, all pass Terminal support: xterm, iTerm2, kitty, alacritty, WezTerm, foot Note: tmux requires 'set -g set-clipboard on' 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
192 lines
6 KiB
Zig
192 lines
6 KiB
Zig
//! Clipboard demo for zcatui.
|
|
//!
|
|
//! Demonstrates OSC 52 clipboard support:
|
|
//! - Copy text to system clipboard
|
|
//! - Copy to primary selection (X11)
|
|
//! - Clear clipboard
|
|
//!
|
|
//! Run with: zig build clipboard-demo
|
|
//!
|
|
//! Note: OSC 52 support varies by terminal. If clipboard operations
|
|
//! don't work, check your terminal's documentation.
|
|
|
|
const std = @import("std");
|
|
const zcatui = @import("zcatui");
|
|
|
|
const Terminal = zcatui.Terminal;
|
|
const Buffer = zcatui.Buffer;
|
|
const Rect = zcatui.Rect;
|
|
const Style = zcatui.Style;
|
|
const Color = zcatui.Color;
|
|
const Event = zcatui.Event;
|
|
const KeyCode = zcatui.KeyCode;
|
|
const Layout = zcatui.Layout;
|
|
const Constraint = zcatui.Constraint;
|
|
const Block = zcatui.widgets.Block;
|
|
const Borders = zcatui.widgets.Borders;
|
|
const Paragraph = zcatui.widgets.Paragraph;
|
|
const Clipboard = zcatui.clipboard;
|
|
|
|
const AppState = struct {
|
|
running: bool = true,
|
|
last_action: []const u8 = "Press a key to test clipboard",
|
|
copy_count: u32 = 0,
|
|
};
|
|
|
|
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();
|
|
|
|
var state = AppState{};
|
|
|
|
while (state.running) {
|
|
try term.drawWithContext(&state, render);
|
|
|
|
if (try term.pollEvent(100)) |event| {
|
|
try handleEvent(&state, &term, allocator, event);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn handleEvent(state: *AppState, term: *Terminal, allocator: std.mem.Allocator, event: Event) !void {
|
|
const writer = term.backend.stdout.deprecatedWriter();
|
|
switch (event) {
|
|
.key => |key| {
|
|
switch (key.code) {
|
|
.esc => state.running = false,
|
|
.char => |c| {
|
|
switch (c) {
|
|
'q', 'Q' => state.running = false,
|
|
'1' => {
|
|
state.copy_count += 1;
|
|
var buf: [64]u8 = undefined;
|
|
const text = std.fmt.bufPrint(&buf, "Hello from zcatui #{d}!", .{state.copy_count}) catch "Hello!";
|
|
try Clipboard.copy(allocator, writer, text);
|
|
state.last_action = "Copied text to clipboard (Ctrl+V to paste)";
|
|
},
|
|
'2' => {
|
|
try Clipboard.copySmallTo(writer, "Primary selection text", .primary);
|
|
state.last_action = "Copied to primary (middle-click to paste)";
|
|
},
|
|
'3' => {
|
|
try Clipboard.copySmallTo(writer, "Both clipboard and primary!", .both);
|
|
state.last_action = "Copied to both clipboard and primary";
|
|
},
|
|
'c', 'C' => {
|
|
try Clipboard.clear(writer);
|
|
state.last_action = "Cleared clipboard";
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
fn render(state: *AppState, area: Rect, buf: *Buffer) void {
|
|
// Main layout
|
|
const chunks = Layout.vertical(&.{
|
|
Constraint.length(3), // Title
|
|
Constraint.length(8), // Actions
|
|
Constraint.length(5), // Status
|
|
Constraint.min(0), // Help
|
|
}).split(area);
|
|
|
|
// Title
|
|
renderTitle(chunks.get(0), buf);
|
|
|
|
// Actions
|
|
renderActions(chunks.get(1), buf);
|
|
|
|
// Status
|
|
renderStatus(state, chunks.get(2), buf);
|
|
|
|
// Help
|
|
renderHelp(chunks.get(3), buf);
|
|
}
|
|
|
|
fn renderTitle(area: Rect, buf: *Buffer) void {
|
|
const block = Block.init()
|
|
.title(" Clipboard Demo (OSC 52) ")
|
|
.setBorders(Borders.all)
|
|
.style(Style.default.fg(Color.cyan));
|
|
block.render(area, buf);
|
|
|
|
const inner = block.inner(area);
|
|
_ = buf.setString(inner.left(), inner.top(), "Test clipboard operations in your terminal", Style.default);
|
|
}
|
|
|
|
fn renderActions(area: Rect, buf: *Buffer) void {
|
|
const block = Block.init()
|
|
.title(" Available Actions ")
|
|
.setBorders(Borders.all)
|
|
.style(Style.default.fg(Color.yellow));
|
|
block.render(area, buf);
|
|
|
|
const inner = block.inner(area);
|
|
var y = inner.top();
|
|
|
|
const actions = [_][]const u8{
|
|
"1 - Copy text to clipboard",
|
|
"2 - Copy to primary selection (X11)",
|
|
"3 - Copy to both clipboard and primary",
|
|
"c - Clear clipboard",
|
|
"q - Quit",
|
|
};
|
|
|
|
for (actions) |action| {
|
|
if (y < inner.bottom()) {
|
|
_ = buf.setString(inner.left(), y, action, Style.default);
|
|
y += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn renderStatus(state: *AppState, area: Rect, buf: *Buffer) void {
|
|
const block = Block.init()
|
|
.title(" Last Action ")
|
|
.setBorders(Borders.all)
|
|
.style(Style.default.fg(Color.green));
|
|
block.render(area, buf);
|
|
|
|
const inner = block.inner(area);
|
|
_ = buf.setString(inner.left(), inner.top(), state.last_action, Style.default.fg(Color.white).bold());
|
|
|
|
if (state.copy_count > 0) {
|
|
var count_buf: [32]u8 = undefined;
|
|
const count_text = std.fmt.bufPrint(&count_buf, "Total copies: {d}", .{state.copy_count}) catch "?";
|
|
_ = buf.setString(inner.left(), inner.top() + 1, count_text, Style.default.dim());
|
|
}
|
|
}
|
|
|
|
fn renderHelp(area: Rect, buf: *Buffer) void {
|
|
const block = Block.init()
|
|
.title(" Notes ")
|
|
.setBorders(Borders.all)
|
|
.style(Style.default.fg(Color.blue));
|
|
block.render(area, buf);
|
|
|
|
const inner = block.inner(area);
|
|
var y = inner.top();
|
|
|
|
const notes = [_][]const u8{
|
|
"OSC 52 clipboard support varies by terminal.",
|
|
"Supported: xterm, iTerm2, kitty, alacritty, WezTerm, foot",
|
|
"tmux: enable with 'set -g set-clipboard on'",
|
|
"Some terminals disable OSC 52 by default for security.",
|
|
};
|
|
|
|
for (notes) |note| {
|
|
if (y < inner.bottom()) {
|
|
_ = buf.setString(inner.left(), y, note, Style.default.dim());
|
|
y += 1;
|
|
}
|
|
}
|
|
}
|