mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-19 22:10:29 +00:00
terminal: add ReadonlyStream that updates terminal state (#9346)
This adds a new stream handler implementation that updates terminal state in reaction to VT sequences, but doesn't perform any of the actions that would require responses (e.g. queries). This is exposed in two ways: first, as a standalone `ReadonlyStream` and `ReadonlyHandler` type that contains all the implementation. Second, as a convenience func on `Terminal` as `vtStream` and `vtHandler` which return their respective types preconfigured to update the calling terminal state. This dramatically simplifies libghostty-vt usage from Zig (and will eventually be exposed to C, too) since a Terminal on its own is ready to go as a full VT parser and state machine without needing to build any custom types! There's a second big bonus here which is that our `stream_readonly.zig` tests are true end-to-end tests for raw bytes to terminal state. This will let us test a wider variety of situations more broadly. To start, there are only a handful of tests implemented here. **AI disclosure:** Amp wrote basically this whole thing, but I reviewed it. https://ampcode.com/threads/T-3490efd2-1137-4112-96f6-4bf8a0141ff5
This commit is contained in:
committed by
GitHub
parent
186b91ef84
commit
580262c96f
33
example/zig-vt-stream/README.md
Normal file
33
example/zig-vt-stream/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Example: `vtStream` API for Parsing Terminal Streams
|
||||
|
||||
This example demonstrates how to use the `vtStream` API to parse and process
|
||||
VT sequences. The `vtStream` API is ideal for read-only terminal applications
|
||||
that need to parse terminal output without responding to queries, such as:
|
||||
|
||||
- Replay tooling
|
||||
- CI log viewers
|
||||
- PaaS builder output
|
||||
- etc.
|
||||
|
||||
The stream processes VT escape sequences and updates terminal state, while
|
||||
ignoring sequences that require responses (like device status queries).
|
||||
|
||||
Requires the Zig version stated in the `build.zig.zon` file.
|
||||
|
||||
## Usage
|
||||
|
||||
Run the program:
|
||||
|
||||
```shell-session
|
||||
zig build run
|
||||
```
|
||||
|
||||
The example will process various VT sequences including:
|
||||
|
||||
- Plain text output
|
||||
- ANSI color codes
|
||||
- Cursor positioning
|
||||
- Line clearing
|
||||
- Multiple line handling
|
||||
|
||||
And display the final terminal state after processing all sequences.
|
||||
39
example/zig-vt-stream/build.zig
Normal file
39
example/zig-vt-stream/build.zig
Normal file
@@ -0,0 +1,39 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
if (b.lazyDependency("ghostty", .{})) |dep| {
|
||||
exe_mod.addImport(
|
||||
"ghostty-vt",
|
||||
dep.module("ghostty-vt"),
|
||||
);
|
||||
}
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "zig_vt_stream",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| run_cmd.addArgs(args);
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
const exe_unit_tests = b.addTest(.{
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||
test_step.dependOn(&run_exe_unit_tests.step);
|
||||
}
|
||||
14
example/zig-vt-stream/build.zig.zon
Normal file
14
example/zig-vt-stream/build.zig.zon
Normal file
@@ -0,0 +1,14 @@
|
||||
.{
|
||||
.name = .zig_vt_stream,
|
||||
.version = "0.0.0",
|
||||
.fingerprint = 0x34c1f71303690b3f,
|
||||
.minimum_zig_version = "0.15.1",
|
||||
.dependencies = .{
|
||||
.ghostty = .{ .path = "../../" },
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
},
|
||||
}
|
||||
40
example/zig-vt-stream/src/main.zig
Normal file
40
example/zig-vt-stream/src/main.zig
Normal file
@@ -0,0 +1,40 @@
|
||||
const std = @import("std");
|
||||
const ghostty_vt = @import("ghostty-vt");
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||
defer _ = gpa.deinit();
|
||||
const alloc = gpa.allocator();
|
||||
|
||||
var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 80, .rows = 24 });
|
||||
defer t.deinit(alloc);
|
||||
|
||||
// Create a read-only VT stream for parsing terminal sequences
|
||||
var stream = t.vtStream();
|
||||
defer stream.deinit();
|
||||
|
||||
// Basic text with newline
|
||||
try stream.nextSlice("Hello, World!\r\n");
|
||||
|
||||
// ANSI color codes: ESC[1;32m = bold green, ESC[0m = reset
|
||||
try stream.nextSlice("\x1b[1;32mGreen Text\x1b[0m\r\n");
|
||||
|
||||
// Cursor positioning: ESC[1;1H = move to row 1, column 1
|
||||
try stream.nextSlice("\x1b[1;1HTop-left corner\r\n");
|
||||
|
||||
// Cursor movement: ESC[5B = move down 5 lines
|
||||
try stream.nextSlice("\x1b[5B");
|
||||
try stream.nextSlice("Moved down!\r\n");
|
||||
|
||||
// Erase line: ESC[2K = clear entire line
|
||||
try stream.nextSlice("\x1b[2K");
|
||||
try stream.nextSlice("New content\r\n");
|
||||
|
||||
// Multiple lines
|
||||
try stream.nextSlice("Line A\r\nLine B\r\nLine C\r\n");
|
||||
|
||||
// Get the final terminal state as a plain string
|
||||
const str = try t.plainString(alloc);
|
||||
defer alloc.free(str);
|
||||
std.debug.print("{s}\n", .{str});
|
||||
}
|
||||
Reference in New Issue
Block a user