Skip to content

Commit a82ad89

Browse files
authored
lib-vt: C API for SGR parser (#9352)
This exposes the SGR parser to the C and Wasm APIs. An example is shown in c-vt-sgr. Compressed example: ```c #include <assert.h> #include <stdio.h> #include <ghostty/vt.h> int main() { // Create parser GhosttySgrParser parser; assert(ghostty_sgr_new(NULL, &parser) == GHOSTTY_SUCCESS); // Parse: ESC[1;31m (bold + red foreground) uint16_t params[] = {1, 31}; assert(ghostty_sgr_set_params(parser, params, NULL, 2) == GHOSTTY_SUCCESS); printf("Parsing: ESC[1;31m\n\n"); // Iterate through attributes GhosttySgrAttribute attr; while (ghostty_sgr_next(parser, &attr)) { switch (attr.tag) { case GHOSTTY_SGR_ATTR_BOLD: printf("✓ Bold enabled\n"); break; case GHOSTTY_SGR_ATTR_FG_8: printf("✓ Foreground color: %d (red)\n", attr.value.fg_8); break; default: break; } } ghostty_sgr_free(parser); return 0; } ``` **AI disclosure:** Amp wrote most of the C headers, but I verified it all. https://ampcode.com/threads/T-d9f145cb-e6ef-48a8-ad63-e5fc85c0d43e
1 parent 27b0978 commit a82ad89

File tree

15 files changed

+782
-11
lines changed

15 files changed

+782
-11
lines changed

‎.github/workflows/test.yml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ jobs:
9494
strategy:
9595
fail-fast: false
9696
matrix:
97-
dir: [c-vt, c-vt-key-encode, c-vt-paste, zig-vt, zig-vt-stream]
97+
dir:
98+
[c-vt, c-vt-key-encode, c-vt-paste, c-vt-sgr, zig-vt, zig-vt-stream]
9899
name: Example ${{ matrix.dir }}
99100
runs-on: namespace-profile-ghostty-sm
100101
needs: test

‎example/c-vt-sgr/README.md‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Example: `ghostty-vt` SGR Parser
2+
3+
This contains a simple example of how to use the `ghostty-vt` SGR parser
4+
to parse terminal styling sequences and extract text attributes.
5+
6+
This example demonstrates parsing a complex SGR sequence from Kakoune that
7+
includes curly underline, RGB foreground/background colors, and RGB underline
8+
color with mixed semicolon and colon separators.
9+
10+
This uses a `build.zig` and `Zig` to build the C program so that we
11+
can reuse a lot of our build logic and depend directly on our source
12+
tree, but Ghostty emits a standard C library that can be used with any
13+
C tooling.
14+
15+
## Usage
16+
17+
Run the program:
18+
19+
```shell-session
20+
zig build run
21+
```

‎example/c-vt-sgr/build.zig‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
const run_step = b.step("run", "Run the app");
8+
9+
const exe_mod = b.createModule(.{
10+
.target = target,
11+
.optimize = optimize,
12+
});
13+
exe_mod.addCSourceFiles(.{
14+
.root = b.path("src"),
15+
.files = &.{"main.c"},
16+
});
17+
18+
// You'll want to use a lazy dependency here so that ghostty is only
19+
// downloaded if you actually need it.
20+
if (b.lazyDependency("ghostty", .{
21+
// Setting simd to false will force a pure static build that
22+
// doesn't even require libc, but it has a significant performance
23+
// penalty. If your embedding app requires libc anyway, you should
24+
// always keep simd enabled.
25+
// .simd = false,
26+
})) |dep| {
27+
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
28+
}
29+
30+
// Exe
31+
const exe = b.addExecutable(.{
32+
.name = "c_vt_sgr",
33+
.root_module = exe_mod,
34+
});
35+
b.installArtifact(exe);
36+
37+
// Run
38+
const run_cmd = b.addRunArtifact(exe);
39+
run_cmd.step.dependOn(b.getInstallStep());
40+
if (b.args) |args| run_cmd.addArgs(args);
41+
run_step.dependOn(&run_cmd.step);
42+
}

‎example/c-vt-sgr/build.zig.zon‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.{
2+
.name = .c_vt_sgr,
3+
.version = "0.0.0",
4+
.fingerprint = 0x6e9c6d318e59c268,
5+
.minimum_zig_version = "0.15.1",
6+
.dependencies = .{
7+
// Ghostty dependency. In reality, you'd probably use a URL-based
8+
// dependency like the one showed (and commented out) below this one.
9+
// We use a path dependency here for simplicity and to ensure our
10+
// examples always test against the source they're bundled with.
11+
.ghostty = .{ .path = "../../" },
12+
13+
// Example of what a URL-based dependency looks like:
14+
// .ghostty = .{
15+
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
16+
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
17+
// },
18+
},
19+
.paths = .{
20+
"build.zig",
21+
"build.zig.zon",
22+
"src",
23+
},
24+
}

‎example/c-vt-sgr/src/main.c‎

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
#include <ghostty/vt.h>
4+
5+
int main() {
6+
// Create parser
7+
GhosttySgrParser parser;
8+
GhosttyResult result = ghostty_sgr_new(NULL, &parser);
9+
assert(result == GHOSTTY_SUCCESS);
10+
11+
// Parse a complex SGR sequence from Kakoune
12+
// This corresponds to the escape sequence:
13+
// ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m
14+
//
15+
// Breaking down the sequence:
16+
// - 4:3 = curly underline (colon-separated sub-parameters)
17+
// - 38;2;51;51;51 = foreground RGB color (51, 51, 51) - dark gray
18+
// - 48;2;170;170;170 = background RGB color (170, 170, 170) - light gray
19+
// - 58;2;255;97;136 = underline RGB color (255, 97, 136) - pink
20+
uint16_t params[] = {4, 3, 38, 2, 51, 51, 51, 48, 2, 170, 170, 170, 58, 2, 255, 97, 136};
21+
22+
// Separator array: ':' at position 0 (between 4 and 3), ';' elsewhere
23+
char separators[] = ";;;;;;;;;;;;;;;;";
24+
separators[0] = ':';
25+
26+
result = ghostty_sgr_set_params(parser, params, separators, sizeof(params) / sizeof(params[0]));
27+
assert(result == GHOSTTY_SUCCESS);
28+
29+
printf("Parsing Kakoune SGR sequence:\n");
30+
printf("ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m\n\n");
31+
32+
// Iterate through attributes
33+
GhosttySgrAttribute attr;
34+
int count = 0;
35+
while (ghostty_sgr_next(parser, &attr)) {
36+
count++;
37+
printf("Attribute %d: ", count);
38+
39+
switch (attr.tag) {
40+
case GHOSTTY_SGR_ATTR_UNDERLINE:
41+
printf("Underline style = ");
42+
switch (attr.value.underline) {
43+
case GHOSTTY_SGR_UNDERLINE_NONE:
44+
printf("none\n");
45+
break;
46+
case GHOSTTY_SGR_UNDERLINE_SINGLE:
47+
printf("single\n");
48+
break;
49+
case GHOSTTY_SGR_UNDERLINE_DOUBLE:
50+
printf("double\n");
51+
break;
52+
case GHOSTTY_SGR_UNDERLINE_CURLY:
53+
printf("curly\n");
54+
break;
55+
case GHOSTTY_SGR_UNDERLINE_DOTTED:
56+
printf("dotted\n");
57+
break;
58+
case GHOSTTY_SGR_UNDERLINE_DASHED:
59+
printf("dashed\n");
60+
break;
61+
default:
62+
printf("unknown (%d)\n", attr.value.underline);
63+
break;
64+
}
65+
break;
66+
67+
case GHOSTTY_SGR_ATTR_DIRECT_COLOR_FG:
68+
printf("Foreground RGB = (%d, %d, %d)\n",
69+
attr.value.direct_color_fg.r,
70+
attr.value.direct_color_fg.g,
71+
attr.value.direct_color_fg.b);
72+
break;
73+
74+
case GHOSTTY_SGR_ATTR_DIRECT_COLOR_BG:
75+
printf("Background RGB = (%d, %d, %d)\n",
76+
attr.value.direct_color_bg.r,
77+
attr.value.direct_color_bg.g,
78+
attr.value.direct_color_bg.b);
79+
break;
80+
81+
case GHOSTTY_SGR_ATTR_UNDERLINE_COLOR:
82+
printf("Underline color RGB = (%d, %d, %d)\n",
83+
attr.value.underline_color.r,
84+
attr.value.underline_color.g,
85+
attr.value.underline_color.b);
86+
break;
87+
88+
case GHOSTTY_SGR_ATTR_FG_8:
89+
printf("Foreground 8-color = %d\n", attr.value.fg_8);
90+
break;
91+
92+
case GHOSTTY_SGR_ATTR_BG_8:
93+
printf("Background 8-color = %d\n", attr.value.bg_8);
94+
break;
95+
96+
case GHOSTTY_SGR_ATTR_FG_256:
97+
printf("Foreground 256-color = %d\n", attr.value.fg_256);
98+
break;
99+
100+
case GHOSTTY_SGR_ATTR_BG_256:
101+
printf("Background 256-color = %d\n", attr.value.bg_256);
102+
break;
103+
104+
case GHOSTTY_SGR_ATTR_BOLD:
105+
printf("Bold\n");
106+
break;
107+
108+
case GHOSTTY_SGR_ATTR_ITALIC:
109+
printf("Italic\n");
110+
break;
111+
112+
case GHOSTTY_SGR_ATTR_UNSET:
113+
printf("Reset all attributes\n");
114+
break;
115+
116+
case GHOSTTY_SGR_ATTR_UNKNOWN:
117+
printf("Unknown attribute\n");
118+
break;
119+
120+
default:
121+
printf("Other attribute (tag=%d)\n", attr.tag);
122+
break;
123+
}
124+
}
125+
126+
printf("\nTotal attributes parsed: %d\n", count);
127+
128+
// Cleanup
129+
ghostty_sgr_free(parser);
130+
return 0;
131+
}

‎include/ghostty/vt.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* The API is organized into the following groups:
3131
* - @ref key "Key Encoding" - Encode key events into terminal sequences
3232
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
33+
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
3334
* - @ref paste "Paste Utilities" - Validate paste data safety
3435
* - @ref allocator "Memory Management" - Memory management and custom allocators
3536
* - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
@@ -40,6 +41,7 @@
4041
* - @ref c-vt/src/main.c - OSC parser example
4142
* - @ref c-vt-key-encode/src/main.c - Key encoding example
4243
* - @ref c-vt-paste/src/main.c - Paste safety check example
44+
* - @ref c-vt-sgr/src/main.c - SGR parser example
4345
*
4446
*/
4547

@@ -58,6 +60,11 @@
5860
* paste data is safe before sending it to the terminal.
5961
*/
6062

63+
/** @example c-vt-sgr/src/main.c
64+
* This example demonstrates how to use the SGR parser to parse terminal
65+
* styling sequences and extract text attributes like colors and underline styles.
66+
*/
67+
6168
#ifndef GHOSTTY_VT_H
6269
#define GHOSTTY_VT_H
6370

@@ -68,6 +75,7 @@ extern "C" {
6875
#include <ghostty/vt/result.h>
6976
#include <ghostty/vt/allocator.h>
7077
#include <ghostty/vt/osc.h>
78+
#include <ghostty/vt/sgr.h>
7179
#include <ghostty/vt/key.h>
7280
#include <ghostty/vt/paste.h>
7381
#include <ghostty/vt/wasm.h>

‎include/ghostty/vt/color.h‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* @file color.h
3+
*
4+
* Color types and utilities.
5+
*/
6+
7+
#ifndef GHOSTTY_VT_COLOR_H
8+
#define GHOSTTY_VT_COLOR_H
9+
10+
#include <stdint.h>
11+
12+
#ifdef __cplusplus
13+
extern "C" {
14+
#endif
15+
16+
/**
17+
* RGB color value.
18+
*
19+
* @ingroup sgr
20+
*/
21+
typedef struct {
22+
uint8_t r; /**< Red component (0-255) */
23+
uint8_t g; /**< Green component (0-255) */
24+
uint8_t b; /**< Blue component (0-255) */
25+
} GhosttyColorRgb;
26+
27+
/**
28+
* Palette color index (0-255).
29+
*
30+
* @ingroup sgr
31+
*/
32+
typedef uint8_t GhosttyColorPaletteIndex;
33+
34+
/** @addtogroup sgr
35+
* @{
36+
*/
37+
38+
/** Black color (0) @ingroup sgr */
39+
#define GHOSTTY_COLOR_NAMED_BLACK 0
40+
/** Red color (1) @ingroup sgr */
41+
#define GHOSTTY_COLOR_NAMED_RED 1
42+
/** Green color (2) @ingroup sgr */
43+
#define GHOSTTY_COLOR_NAMED_GREEN 2
44+
/** Yellow color (3) @ingroup sgr */
45+
#define GHOSTTY_COLOR_NAMED_YELLOW 3
46+
/** Blue color (4) @ingroup sgr */
47+
#define GHOSTTY_COLOR_NAMED_BLUE 4
48+
/** Magenta color (5) @ingroup sgr */
49+
#define GHOSTTY_COLOR_NAMED_MAGENTA 5
50+
/** Cyan color (6) @ingroup sgr */
51+
#define GHOSTTY_COLOR_NAMED_CYAN 6
52+
/** White color (7) @ingroup sgr */
53+
#define GHOSTTY_COLOR_NAMED_WHITE 7
54+
/** Bright black color (8) @ingroup sgr */
55+
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8
56+
/** Bright red color (9) @ingroup sgr */
57+
#define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9
58+
/** Bright green color (10) @ingroup sgr */
59+
#define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10
60+
/** Bright yellow color (11) @ingroup sgr */
61+
#define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11
62+
/** Bright blue color (12) @ingroup sgr */
63+
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12
64+
/** Bright magenta color (13) @ingroup sgr */
65+
#define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13
66+
/** Bright cyan color (14) @ingroup sgr */
67+
#define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14
68+
/** Bright white color (15) @ingroup sgr */
69+
#define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15
70+
71+
/** @} */
72+
73+
#ifdef __cplusplus
74+
}
75+
#endif
76+
77+
#endif /* GHOSTTY_VT_COLOR_H */

‎include/ghostty/vt/result.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ typedef enum {
1515
GHOSTTY_SUCCESS = 0,
1616
/** Operation failed due to failed allocation */
1717
GHOSTTY_OUT_OF_MEMORY = -1,
18+
/** Operation failed due to invalid value */
19+
GHOSTTY_INVALID_VALUE = -2,
1820
} GhosttyResult;
1921

2022
#endif /* GHOSTTY_VT_RESULT_H */

0 commit comments

Comments
 (0)