home
tpb

repository View

refactor: split OSC52 off into different interface

change ID
mwlqoqomsvxkkrvyxvmlllxkzqxrsuzp
git commit
d7feea3f86358bea2fc3c2c15875706183530d58
author
Grace Yoder <grace_yoder@outlook.com> - 2026-06-24T17:48:16-06:00
committer
Grace Yoder <grace_yoder@outlook.com> - 2026-06-25T16:42:38-06:00
parents
kvsvtrnmqzlt
refs
signature
Present (not validated)
refactor: split OSC52 off into different interface

tree

type name size
file .gitignore 354 bytes
file LICENSE 1073 bytes
file README.md 564 bytes
file build.zig 680 bytes
file build.zig.zon 247 bytes
file src/Clip/OSC52.zig 2746 bytes
file src/Clip.zig 45 bytes
file src/main.zig 1331 bytes

diff

src/Clip/OSC52.zig

added (absent → file)

--- a/src/Clip/OSC52.zig
+++ b/src/Clip/OSC52.zig
@@ -0,0 +1,89 @@
+/// OSC52 Clipboard backend
+/// Fullfils `Clip` interface
+pub const OSC52 = @This();
+
+const std = @import("std");
+const Io = std.Io;
+
+alloc: std.mem.Allocator,
+io: std.Io,
+
+base64buffer: std.ArrayList(u8),
+
+leftover_bytes: [3]u8,
+leftover_count: u8,
+
+const base64encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null);
+
+pub fn init(alloc: std.mem.Allocator, io: Io, buf_size: usize) !OSC52 {
+    return .{
+        .alloc = alloc,
+        .io = io,
+        .base64buffer = try std.ArrayList(u8).initCapacity(alloc, buf_size),
+        .leftover_bytes = undefined,
+        .leftover_count = 0,
+    };
+}
+
+pub fn writeCopyBuffer(self: *OSC52, buf: []u8) !void {
+    if (buf.len == 0) return;
+
+    // Start of buffer after dealing with leftover bytes
+    const buf_idx = 3 - self.leftover_count;
+
+    if (buf.len + self.leftover_count < 3) {
+        const len = buf.len + self.leftover_count;
+        @memcpy(self.leftover_bytes[self.leftover_count..len], buf[0..buf.len]);
+        self.leftover_count = @intCast(len);
+        return;
+    }
+
+    if (self.leftover_count != 0) {
+        @memcpy(self.leftover_bytes[self.leftover_count..3], buf[0..buf_idx]);
+        var temp: [5]u8 = undefined;
+        const s = OSC52.base64encoder.encode(&temp, &self.leftover_bytes);
+        try self.base64buffer.appendSlice(self.alloc, s);
+    }
+
+    const chunkable_length = buf.len - ((buf.len - buf_idx) % 3);
+    var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3);
+    while (chunker.next()) |chunk| {
+        var temp: [5]u8 = undefined;
+        const s = OSC52.base64encoder.encode(&temp, chunk);
+        try self.base64buffer.appendSlice(self.alloc, s);
+    }
+
+    self.leftover_count = @intCast(buf.len - chunkable_length);
+    if (self.leftover_count != 0) {
+        @memmove(self.leftover_bytes[0..self.leftover_count], buf[chunkable_length..]);
+    }
+}
+
+pub fn writePasteboard(self: *OSC52) !void {
+
+    // https://ghostty.org/docs/vt/osc/52
+    const osc_header = &[_]u8{
+        0x1b, // ESC
+        0x5d, // ']'
+        0x35, 0x32, // Code 52
+        0x3b, // ';'
+        // 0x70, // 'p' Primary Clipboard
+        0x63, // 'c' Standard Clipboard
+        0x3b, // ';'
+    };
+    const osc_footer = &[_]u8{
+        0x1b, // ESC
+        0x5c, // '\'
+    };
+
+    // Write leftover bytes
+    var temp: [5]u8 = undefined;
+    const s = OSC52.base64encoder.encode(&temp, self.leftover_bytes[0..self.leftover_count]);
+    try self.base64buffer.appendSlice(self.alloc, s);
+
+    // Write data to stdout
+    const stdout = Io.File.stdout();
+    try stdout.writeStreamingAll(self.io, osc_header);
+    try stdout.writeStreamingAll(self.io, self.base64buffer.items);
+    try stdout.writeStreamingAll(self.io, osc_footer);
+}

src/Clip.zig

added (absent → file)

--- a/src/Clip.zig
+++ b/src/Clip.zig
@@ -0,0 +1 @@
+pub const OSC52 = @import("Clip/OSC52.zig");

src/main.zig

modified (file → file)

--- a/src/main.zig
+++ b/src/main.zig
@@ -1,5 +1,6 @@
 const std = @import("std");
 const File = std.Io.File;
+const Clip = @import("Clip.zig");
 
 pub fn main(init: std.process.Init) !void {
     const io = init.io;
@@ -14,67 +15,26 @@
 
     // We want to avoid doing as many allocations as possible so having raw
     // pages with it being larger should be totally fine
-    const alloc = std.heap.page_allocator;
+    var clip = try Clip.OSC52.init(std.heap.page_allocator, init.io, std.heap.pageSize());
 
     var buf: [4096]u8 = undefined;
-    var copy_index: u8 = 0;
-
-    var encoded_buffer = try std.ArrayList(u8).initCapacity(alloc, std.heap.pageSize());
-    var encoder = std.base64.Base64Encoder.init(std.base64.standard_alphabet_chars, null);
-
     while (true) {
         // Read as much of stdin there is
-        const bytes = File.stdin().readStreaming(io, &.{buf[copy_index..]}) catch |err| {
+        const bytes = File.stdin().readStreaming(io, &.{&buf}) catch |err| {
             if (err == File.ReadStreamingError.EndOfStream) {
                 break;
             } else {
                 return err;
             }
         };
-        const length = copy_index + bytes;
-
         // Passthrough to stdout
-        _ = try File.stdout().writeStreamingAll(io, buf[copy_index..length]);
-
-        // Base64 encode
-        const chunkable_length = length - (length % 3);
-        var chunker = std.mem.window(u8, buf[0..chunkable_length], 3, 3);
-        while (chunker.next()) |chunk| {
-            var temp: [5]u8 = undefined;
-            const s = encoder.encode(&temp, chunk);
-            try encoded_buffer.appendSlice(alloc, s);
-        }
+        _ = try File.stdout().writeStreamingAll(io, buf[0..bytes]);
 
-        // Move left over bits to the start of the array
-        copy_index = @intCast(length - chunkable_length);
-        if (copy_index != 0) {
-            @memmove(buf[0..copy_index], buf[chunkable_length..length]);
-        }
+        // Write to clip
+        try clip.writeCopyBuffer(buf[0..bytes]);
     }
-    // Encode last bytes
-    var temp: [5]u8 = undefined;
-    const s = encoder.encode(&temp, buf[0..copy_index]);
-    try encoded_buffer.appendSlice(alloc, s);
 
-    // OSC 52 Copying — written as one contiguous buffer so the terminal
-    // receives the complete sequence in a single write.
-    // https://ghostty.org/docs/vt/osc/52
-    const osc_header = &[_]u8{
-        0x1b, // ESC
-        0x5d, // ']'
-        0x35, 0x32, // Code 52
-        0x3b, // ';'
-        // 0x70, // 'p' Primary Clipboard
-        0x63, // 'c' Standard Clipboard
-        0x3b, // ';'
-    };
-    const osc_footer = &[_]u8{
-        0x1b, // ESC
-        0x5c, // '\'
-    };
+    try clip.writePasteboard();
 
-    _ = try File.stdout().writeStreamingAll(io, osc_header);
-    _ = try File.stdout().writeStreamingAll(io, encoded_buffer.items);
-    _ = try File.stdout().writeStreamingAll(io, osc_footer);
     std.debug.print("\x1b[0;32mCopied To Pasteboard!\x1b[0m\n", .{});
 }