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
pull/9354/head
parent
27b0978cd5
commit
a82ad89ef3
|
|
@ -94,7 +94,8 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dir: [c-vt, c-vt-key-encode, c-vt-paste, zig-vt, zig-vt-stream]
|
dir:
|
||||||
|
[c-vt, c-vt-key-encode, c-vt-paste, c-vt-sgr, zig-vt, zig-vt-stream]
|
||||||
name: Example ${{ matrix.dir }}
|
name: Example ${{ matrix.dir }}
|
||||||
runs-on: namespace-profile-ghostty-sm
|
runs-on: namespace-profile-ghostty-sm
|
||||||
needs: test
|
needs: test
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Example: `ghostty-vt` SGR Parser
|
||||||
|
|
||||||
|
This contains a simple example of how to use the `ghostty-vt` SGR parser
|
||||||
|
to parse terminal styling sequences and extract text attributes.
|
||||||
|
|
||||||
|
This example demonstrates parsing a complex SGR sequence from Kakoune that
|
||||||
|
includes curly underline, RGB foreground/background colors, and RGB underline
|
||||||
|
color with mixed semicolon and colon separators.
|
||||||
|
|
||||||
|
This uses a `build.zig` and `Zig` to build the C program so that we
|
||||||
|
can reuse a lot of our build logic and depend directly on our source
|
||||||
|
tree, but Ghostty emits a standard C library that can be used with any
|
||||||
|
C tooling.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run the program:
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
zig build run
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
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 exe_mod = b.createModule(.{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe_mod.addCSourceFiles(.{
|
||||||
|
.root = b.path("src"),
|
||||||
|
.files = &.{"main.c"},
|
||||||
|
});
|
||||||
|
|
||||||
|
// You'll want to use a lazy dependency here so that ghostty is only
|
||||||
|
// downloaded if you actually need it.
|
||||||
|
if (b.lazyDependency("ghostty", .{
|
||||||
|
// Setting simd to false will force a pure static build that
|
||||||
|
// doesn't even require libc, but it has a significant performance
|
||||||
|
// penalty. If your embedding app requires libc anyway, you should
|
||||||
|
// always keep simd enabled.
|
||||||
|
// .simd = false,
|
||||||
|
})) |dep| {
|
||||||
|
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exe
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "c_vt_sgr",
|
||||||
|
.root_module = exe_mod,
|
||||||
|
});
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
// Run
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
.{
|
||||||
|
.name = .c_vt_sgr,
|
||||||
|
.version = "0.0.0",
|
||||||
|
.fingerprint = 0x6e9c6d318e59c268,
|
||||||
|
.minimum_zig_version = "0.15.1",
|
||||||
|
.dependencies = .{
|
||||||
|
// Ghostty dependency. In reality, you'd probably use a URL-based
|
||||||
|
// dependency like the one showed (and commented out) below this one.
|
||||||
|
// We use a path dependency here for simplicity and to ensure our
|
||||||
|
// examples always test against the source they're bundled with.
|
||||||
|
.ghostty = .{ .path = "../../" },
|
||||||
|
|
||||||
|
// Example of what a URL-based dependency looks like:
|
||||||
|
// .ghostty = .{
|
||||||
|
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
|
||||||
|
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ghostty/vt.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Create parser
|
||||||
|
GhosttySgrParser parser;
|
||||||
|
GhosttyResult result = ghostty_sgr_new(NULL, &parser);
|
||||||
|
assert(result == GHOSTTY_SUCCESS);
|
||||||
|
|
||||||
|
// Parse a complex SGR sequence from Kakoune
|
||||||
|
// This corresponds to the escape sequence:
|
||||||
|
// ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m
|
||||||
|
//
|
||||||
|
// Breaking down the sequence:
|
||||||
|
// - 4:3 = curly underline (colon-separated sub-parameters)
|
||||||
|
// - 38;2;51;51;51 = foreground RGB color (51, 51, 51) - dark gray
|
||||||
|
// - 48;2;170;170;170 = background RGB color (170, 170, 170) - light gray
|
||||||
|
// - 58;2;255;97;136 = underline RGB color (255, 97, 136) - pink
|
||||||
|
uint16_t params[] = {4, 3, 38, 2, 51, 51, 51, 48, 2, 170, 170, 170, 58, 2, 255, 97, 136};
|
||||||
|
|
||||||
|
// Separator array: ':' at position 0 (between 4 and 3), ';' elsewhere
|
||||||
|
char separators[] = ";;;;;;;;;;;;;;;;";
|
||||||
|
separators[0] = ':';
|
||||||
|
|
||||||
|
result = ghostty_sgr_set_params(parser, params, separators, sizeof(params) / sizeof(params[0]));
|
||||||
|
assert(result == GHOSTTY_SUCCESS);
|
||||||
|
|
||||||
|
printf("Parsing Kakoune SGR sequence:\n");
|
||||||
|
printf("ESC[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136m\n\n");
|
||||||
|
|
||||||
|
// Iterate through attributes
|
||||||
|
GhosttySgrAttribute attr;
|
||||||
|
int count = 0;
|
||||||
|
while (ghostty_sgr_next(parser, &attr)) {
|
||||||
|
count++;
|
||||||
|
printf("Attribute %d: ", count);
|
||||||
|
|
||||||
|
switch (attr.tag) {
|
||||||
|
case GHOSTTY_SGR_ATTR_UNDERLINE:
|
||||||
|
printf("Underline style = ");
|
||||||
|
switch (attr.value.underline) {
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_NONE:
|
||||||
|
printf("none\n");
|
||||||
|
break;
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_SINGLE:
|
||||||
|
printf("single\n");
|
||||||
|
break;
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_DOUBLE:
|
||||||
|
printf("double\n");
|
||||||
|
break;
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_CURLY:
|
||||||
|
printf("curly\n");
|
||||||
|
break;
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_DOTTED:
|
||||||
|
printf("dotted\n");
|
||||||
|
break;
|
||||||
|
case GHOSTTY_SGR_UNDERLINE_DASHED:
|
||||||
|
printf("dashed\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("unknown (%d)\n", attr.value.underline);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_DIRECT_COLOR_FG:
|
||||||
|
printf("Foreground RGB = (%d, %d, %d)\n",
|
||||||
|
attr.value.direct_color_fg.r,
|
||||||
|
attr.value.direct_color_fg.g,
|
||||||
|
attr.value.direct_color_fg.b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_DIRECT_COLOR_BG:
|
||||||
|
printf("Background RGB = (%d, %d, %d)\n",
|
||||||
|
attr.value.direct_color_bg.r,
|
||||||
|
attr.value.direct_color_bg.g,
|
||||||
|
attr.value.direct_color_bg.b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_UNDERLINE_COLOR:
|
||||||
|
printf("Underline color RGB = (%d, %d, %d)\n",
|
||||||
|
attr.value.underline_color.r,
|
||||||
|
attr.value.underline_color.g,
|
||||||
|
attr.value.underline_color.b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_FG_8:
|
||||||
|
printf("Foreground 8-color = %d\n", attr.value.fg_8);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_BG_8:
|
||||||
|
printf("Background 8-color = %d\n", attr.value.bg_8);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_FG_256:
|
||||||
|
printf("Foreground 256-color = %d\n", attr.value.fg_256);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_BG_256:
|
||||||
|
printf("Background 256-color = %d\n", attr.value.bg_256);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_BOLD:
|
||||||
|
printf("Bold\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_ITALIC:
|
||||||
|
printf("Italic\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_UNSET:
|
||||||
|
printf("Reset all attributes\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GHOSTTY_SGR_ATTR_UNKNOWN:
|
||||||
|
printf("Unknown attribute\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("Other attribute (tag=%d)\n", attr.tag);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nTotal attributes parsed: %d\n", count);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
ghostty_sgr_free(parser);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
* The API is organized into the following groups:
|
* The API is organized into the following groups:
|
||||||
* - @ref key "Key Encoding" - Encode key events into terminal sequences
|
* - @ref key "Key Encoding" - Encode key events into terminal sequences
|
||||||
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
|
* - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences
|
||||||
|
* - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences
|
||||||
* - @ref paste "Paste Utilities" - Validate paste data safety
|
* - @ref paste "Paste Utilities" - Validate paste data safety
|
||||||
* - @ref allocator "Memory Management" - Memory management and custom allocators
|
* - @ref allocator "Memory Management" - Memory management and custom allocators
|
||||||
* - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
|
* - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions
|
||||||
|
|
@ -40,6 +41,7 @@
|
||||||
* - @ref c-vt/src/main.c - OSC parser example
|
* - @ref c-vt/src/main.c - OSC parser example
|
||||||
* - @ref c-vt-key-encode/src/main.c - Key encoding example
|
* - @ref c-vt-key-encode/src/main.c - Key encoding example
|
||||||
* - @ref c-vt-paste/src/main.c - Paste safety check example
|
* - @ref c-vt-paste/src/main.c - Paste safety check example
|
||||||
|
* - @ref c-vt-sgr/src/main.c - SGR parser example
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -58,6 +60,11 @@
|
||||||
* paste data is safe before sending it to the terminal.
|
* paste data is safe before sending it to the terminal.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** @example c-vt-sgr/src/main.c
|
||||||
|
* This example demonstrates how to use the SGR parser to parse terminal
|
||||||
|
* styling sequences and extract text attributes like colors and underline styles.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef GHOSTTY_VT_H
|
#ifndef GHOSTTY_VT_H
|
||||||
#define GHOSTTY_VT_H
|
#define GHOSTTY_VT_H
|
||||||
|
|
||||||
|
|
@ -68,6 +75,7 @@ extern "C" {
|
||||||
#include <ghostty/vt/result.h>
|
#include <ghostty/vt/result.h>
|
||||||
#include <ghostty/vt/allocator.h>
|
#include <ghostty/vt/allocator.h>
|
||||||
#include <ghostty/vt/osc.h>
|
#include <ghostty/vt/osc.h>
|
||||||
|
#include <ghostty/vt/sgr.h>
|
||||||
#include <ghostty/vt/key.h>
|
#include <ghostty/vt/key.h>
|
||||||
#include <ghostty/vt/paste.h>
|
#include <ghostty/vt/paste.h>
|
||||||
#include <ghostty/vt/wasm.h>
|
#include <ghostty/vt/wasm.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* @file color.h
|
||||||
|
*
|
||||||
|
* Color types and utilities.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GHOSTTY_VT_COLOR_H
|
||||||
|
#define GHOSTTY_VT_COLOR_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RGB color value.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t r; /**< Red component (0-255) */
|
||||||
|
uint8_t g; /**< Green component (0-255) */
|
||||||
|
uint8_t b; /**< Blue component (0-255) */
|
||||||
|
} GhosttyColorRgb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Palette color index (0-255).
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef uint8_t GhosttyColorPaletteIndex;
|
||||||
|
|
||||||
|
/** @addtogroup sgr
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Black color (0) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BLACK 0
|
||||||
|
/** Red color (1) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_RED 1
|
||||||
|
/** Green color (2) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_GREEN 2
|
||||||
|
/** Yellow color (3) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_YELLOW 3
|
||||||
|
/** Blue color (4) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BLUE 4
|
||||||
|
/** Magenta color (5) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_MAGENTA 5
|
||||||
|
/** Cyan color (6) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_CYAN 6
|
||||||
|
/** White color (7) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_WHITE 7
|
||||||
|
/** Bright black color (8) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLACK 8
|
||||||
|
/** Bright red color (9) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_RED 9
|
||||||
|
/** Bright green color (10) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_GREEN 10
|
||||||
|
/** Bright yellow color (11) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_YELLOW 11
|
||||||
|
/** Bright blue color (12) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_BLUE 12
|
||||||
|
/** Bright magenta color (13) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_MAGENTA 13
|
||||||
|
/** Bright cyan color (14) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_CYAN 14
|
||||||
|
/** Bright white color (15) @ingroup sgr */
|
||||||
|
#define GHOSTTY_COLOR_NAMED_BRIGHT_WHITE 15
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* GHOSTTY_VT_COLOR_H */
|
||||||
|
|
@ -15,6 +15,8 @@ typedef enum {
|
||||||
GHOSTTY_SUCCESS = 0,
|
GHOSTTY_SUCCESS = 0,
|
||||||
/** Operation failed due to failed allocation */
|
/** Operation failed due to failed allocation */
|
||||||
GHOSTTY_OUT_OF_MEMORY = -1,
|
GHOSTTY_OUT_OF_MEMORY = -1,
|
||||||
|
/** Operation failed due to invalid value */
|
||||||
|
GHOSTTY_INVALID_VALUE = -2,
|
||||||
} GhosttyResult;
|
} GhosttyResult;
|
||||||
|
|
||||||
#endif /* GHOSTTY_VT_RESULT_H */
|
#endif /* GHOSTTY_VT_RESULT_H */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
/**
|
||||||
|
* @file sgr.h
|
||||||
|
*
|
||||||
|
* SGR (Select Graphic Rendition) attribute parsing and handling.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GHOSTTY_VT_SGR_H
|
||||||
|
#define GHOSTTY_VT_SGR_H
|
||||||
|
|
||||||
|
/** @defgroup sgr SGR Parser
|
||||||
|
*
|
||||||
|
* SGR (Select Graphic Rendition) attribute parser.
|
||||||
|
*
|
||||||
|
* SGR sequences are the syntax used to set styling attributes such as
|
||||||
|
* bold, italic, underline, and colors for text in terminal emulators.
|
||||||
|
* For example, you may be familiar with sequences like `ESC[1;31m`. The
|
||||||
|
* `1;31` is the SGR attribute list.
|
||||||
|
*
|
||||||
|
* The parser processes SGR parameters from CSI sequences (e.g., `ESC[1;31m`)
|
||||||
|
* and returns individual text attributes like bold, italic, colors, etc.
|
||||||
|
* It supports both semicolon (`;`) and colon (`:`) separators, possibly mixed,
|
||||||
|
* and handles various color formats including 8-color, 16-color, 256-color,
|
||||||
|
* X11 named colors, and RGB in multiple formats.
|
||||||
|
*
|
||||||
|
* ## Basic Usage
|
||||||
|
*
|
||||||
|
* 1. Create a parser instance with ghostty_sgr_new()
|
||||||
|
* 2. Set SGR parameters with ghostty_sgr_set_params()
|
||||||
|
* 3. Iterate through attributes using ghostty_sgr_next()
|
||||||
|
* 4. Free the parser with ghostty_sgr_free() when done
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* @code{.c}
|
||||||
|
* #include <assert.h>
|
||||||
|
* #include <stdio.h>
|
||||||
|
* #include <ghostty/vt.h>
|
||||||
|
*
|
||||||
|
* int main() {
|
||||||
|
* // Create parser
|
||||||
|
* GhosttySgrParser parser;
|
||||||
|
* GhosttyResult result = ghostty_sgr_new(NULL, &parser);
|
||||||
|
* assert(result == GHOSTTY_SUCCESS);
|
||||||
|
*
|
||||||
|
* // Parse "bold, red foreground" sequence: ESC[1;31m
|
||||||
|
* uint16_t params[] = {1, 31};
|
||||||
|
* result = ghostty_sgr_set_params(parser, params, NULL, 2);
|
||||||
|
* assert(result == GHOSTTY_SUCCESS);
|
||||||
|
*
|
||||||
|
* // 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\n", attr.value.fg_8);
|
||||||
|
* break;
|
||||||
|
* default:
|
||||||
|
* break;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Cleanup
|
||||||
|
* ghostty_sgr_free(parser);
|
||||||
|
* return 0;
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ghostty/vt/result.h>
|
||||||
|
#include <ghostty/vt/allocator.h>
|
||||||
|
#include <ghostty/vt/color.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque handle to an SGR parser instance.
|
||||||
|
*
|
||||||
|
* This handle represents an SGR (Select Graphic Rendition) parser that can
|
||||||
|
* be used to parse SGR sequences and extract individual text attributes.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef struct GhosttySgrParser *GhosttySgrParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SGR attribute tags.
|
||||||
|
*
|
||||||
|
* These values identify the type of an SGR attribute in a tagged union.
|
||||||
|
* Use the tag to determine which field in the attribute value union to access.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_SGR_ATTR_UNSET = 0,
|
||||||
|
GHOSTTY_SGR_ATTR_UNKNOWN = 1,
|
||||||
|
GHOSTTY_SGR_ATTR_BOLD = 2,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_BOLD = 3,
|
||||||
|
GHOSTTY_SGR_ATTR_ITALIC = 4,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_ITALIC = 5,
|
||||||
|
GHOSTTY_SGR_ATTR_FAINT = 6,
|
||||||
|
GHOSTTY_SGR_ATTR_UNDERLINE = 7,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_UNDERLINE = 8,
|
||||||
|
GHOSTTY_SGR_ATTR_UNDERLINE_COLOR = 9,
|
||||||
|
GHOSTTY_SGR_ATTR_UNDERLINE_COLOR_256 = 10,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_UNDERLINE_COLOR = 11,
|
||||||
|
GHOSTTY_SGR_ATTR_OVERLINE = 12,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_OVERLINE = 13,
|
||||||
|
GHOSTTY_SGR_ATTR_BLINK = 14,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_BLINK = 15,
|
||||||
|
GHOSTTY_SGR_ATTR_INVERSE = 16,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_INVERSE = 17,
|
||||||
|
GHOSTTY_SGR_ATTR_INVISIBLE = 18,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_INVISIBLE = 19,
|
||||||
|
GHOSTTY_SGR_ATTR_STRIKETHROUGH = 20,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_STRIKETHROUGH = 21,
|
||||||
|
GHOSTTY_SGR_ATTR_DIRECT_COLOR_FG = 22,
|
||||||
|
GHOSTTY_SGR_ATTR_DIRECT_COLOR_BG = 23,
|
||||||
|
GHOSTTY_SGR_ATTR_BG_8 = 24,
|
||||||
|
GHOSTTY_SGR_ATTR_FG_8 = 25,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_FG = 26,
|
||||||
|
GHOSTTY_SGR_ATTR_RESET_BG = 27,
|
||||||
|
GHOSTTY_SGR_ATTR_BRIGHT_BG_8 = 28,
|
||||||
|
GHOSTTY_SGR_ATTR_BRIGHT_FG_8 = 29,
|
||||||
|
GHOSTTY_SGR_ATTR_BG_256 = 30,
|
||||||
|
GHOSTTY_SGR_ATTR_FG_256 = 31,
|
||||||
|
} GhosttySgrAttributeTag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Underline style types.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_SGR_UNDERLINE_NONE = 0,
|
||||||
|
GHOSTTY_SGR_UNDERLINE_SINGLE = 1,
|
||||||
|
GHOSTTY_SGR_UNDERLINE_DOUBLE = 2,
|
||||||
|
GHOSTTY_SGR_UNDERLINE_CURLY = 3,
|
||||||
|
GHOSTTY_SGR_UNDERLINE_DOTTED = 4,
|
||||||
|
GHOSTTY_SGR_UNDERLINE_DASHED = 5,
|
||||||
|
} GhosttySgrUnderline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unknown SGR attribute data.
|
||||||
|
*
|
||||||
|
* Contains the full parameter list and the partial list where parsing
|
||||||
|
* encountered an unknown or invalid sequence.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
const uint16_t *full_ptr;
|
||||||
|
size_t full_len;
|
||||||
|
const uint16_t *partial_ptr;
|
||||||
|
size_t partial_len;
|
||||||
|
} GhosttySgrUnknown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SGR attribute value union.
|
||||||
|
*
|
||||||
|
* This union contains all possible attribute values. Use the tag field
|
||||||
|
* to determine which union member is active. Attributes without associated
|
||||||
|
* data (like bold, italic) don't use the union value.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef union {
|
||||||
|
GhosttySgrUnknown unknown;
|
||||||
|
GhosttySgrUnderline underline;
|
||||||
|
GhosttyColorRgb underline_color;
|
||||||
|
GhosttyColorPaletteIndex underline_color_256;
|
||||||
|
GhosttyColorRgb direct_color_fg;
|
||||||
|
GhosttyColorRgb direct_color_bg;
|
||||||
|
GhosttyColorPaletteIndex bg_8;
|
||||||
|
GhosttyColorPaletteIndex fg_8;
|
||||||
|
GhosttyColorPaletteIndex bright_bg_8;
|
||||||
|
GhosttyColorPaletteIndex bright_fg_8;
|
||||||
|
GhosttyColorPaletteIndex bg_256;
|
||||||
|
GhosttyColorPaletteIndex fg_256;
|
||||||
|
uint64_t _padding[8];
|
||||||
|
} GhosttySgrAttributeValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SGR attribute (tagged union).
|
||||||
|
*
|
||||||
|
* A complete SGR attribute with both its type tag and associated value.
|
||||||
|
* Always check the tag field to determine which value union member is valid.
|
||||||
|
*
|
||||||
|
* Attributes without associated data (e.g., GHOSTTY_SGR_ATTR_BOLD) can be
|
||||||
|
* identified by tag alone; the value union is not used for these and
|
||||||
|
* the memory in the value field is undefined.
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
GhosttySgrAttributeTag tag;
|
||||||
|
GhosttySgrAttributeValue value;
|
||||||
|
} GhosttySgrAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new SGR parser instance.
|
||||||
|
*
|
||||||
|
* Creates a new SGR (Select Graphic Rendition) parser using the provided
|
||||||
|
* allocator. The parser must be freed using ghostty_sgr_free() when
|
||||||
|
* no longer needed.
|
||||||
|
*
|
||||||
|
* @param allocator Pointer to the allocator to use for memory management, or NULL to use the default allocator
|
||||||
|
* @param parser Pointer to store the created parser handle
|
||||||
|
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
GhosttyResult ghostty_sgr_new(const GhosttyAllocator *allocator, GhosttySgrParser *parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Free an SGR parser instance.
|
||||||
|
*
|
||||||
|
* Releases all resources associated with the SGR parser. After this call,
|
||||||
|
* the parser handle becomes invalid and must not be used. This includes
|
||||||
|
* any attributes previously returned by ghostty_sgr_next().
|
||||||
|
*
|
||||||
|
* @param parser The parser handle to free (may be NULL)
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
void ghostty_sgr_free(GhosttySgrParser parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset an SGR parser instance to the beginning of the parameter list.
|
||||||
|
*
|
||||||
|
* Resets the parser's iteration state without clearing the parameters.
|
||||||
|
* After calling this, ghostty_sgr_next() will start from the beginning
|
||||||
|
* of the parameter list again.
|
||||||
|
*
|
||||||
|
* @param parser The parser handle to reset, must not be NULL
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
void ghostty_sgr_reset(GhosttySgrParser parser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set SGR parameters for parsing.
|
||||||
|
*
|
||||||
|
* Sets the SGR parameter list to parse. Parameters are the numeric values
|
||||||
|
* from a CSI SGR sequence (e.g., for `ESC[1;31m`, params would be {1, 31}).
|
||||||
|
*
|
||||||
|
* The separators array optionally specifies the separator type for each
|
||||||
|
* parameter position. Each byte should be either ';' for semicolon or ':'
|
||||||
|
* for colon. This is needed for certain color formats that use colon
|
||||||
|
* separators (e.g., `ESC[4:3m` for curly underline). Any invalid separator
|
||||||
|
* values are treated as semicolons. The separators array must have the same
|
||||||
|
* length as the params array, if it is not NULL.
|
||||||
|
*
|
||||||
|
* If separators is NULL, all parameters are assumed to be semicolon-separated.
|
||||||
|
*
|
||||||
|
* This function makes an internal copy of the parameter and separator data,
|
||||||
|
* so the caller can safely free or modify the input arrays after this call.
|
||||||
|
*
|
||||||
|
* After calling this function, the parser is automatically reset and ready
|
||||||
|
* to iterate from the beginning.
|
||||||
|
*
|
||||||
|
* @param parser The parser handle, must not be NULL
|
||||||
|
* @param params Array of SGR parameter values
|
||||||
|
* @param separators Optional array of separator characters (';' or ':'), or NULL
|
||||||
|
* @param len Number of parameters (and separators if provided)
|
||||||
|
* @return GHOSTTY_SUCCESS on success, or an error code on failure
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
GhosttyResult ghostty_sgr_set_params(
|
||||||
|
GhosttySgrParser parser,
|
||||||
|
const uint16_t *params,
|
||||||
|
const char *separators,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next SGR attribute.
|
||||||
|
*
|
||||||
|
* Parses and returns the next attribute from the parameter list.
|
||||||
|
* Call this function repeatedly until it returns false to process
|
||||||
|
* all attributes in the sequence.
|
||||||
|
*
|
||||||
|
* @param parser The parser handle, must not be NULL
|
||||||
|
* @param attr Pointer to store the next attribute
|
||||||
|
* @return true if an attribute was returned, false if no more attributes
|
||||||
|
*
|
||||||
|
* @ingroup sgr
|
||||||
|
*/
|
||||||
|
bool ghostty_sgr_next(GhosttySgrParser parser, GhosttySgrAttribute *attr);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
||||||
|
#endif /* GHOSTTY_VT_SGR_H */
|
||||||
|
|
@ -98,13 +98,6 @@ comptime {
|
||||||
// we want to reference the C API so that it gets exported.
|
// we want to reference the C API so that it gets exported.
|
||||||
if (@import("root") == lib) {
|
if (@import("root") == lib) {
|
||||||
const c = terminal.c_api;
|
const c = terminal.c_api;
|
||||||
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
|
|
||||||
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
|
|
||||||
@export(&c.osc_next, .{ .name = "ghostty_osc_next" });
|
|
||||||
@export(&c.osc_reset, .{ .name = "ghostty_osc_reset" });
|
|
||||||
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
|
||||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
|
||||||
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
|
||||||
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
|
@export(&c.key_event_new, .{ .name = "ghostty_key_event_new" });
|
||||||
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });
|
@export(&c.key_event_free, .{ .name = "ghostty_key_event_free" });
|
||||||
@export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" });
|
@export(&c.key_event_set_action, .{ .name = "ghostty_key_event_set_action" });
|
||||||
|
|
@ -125,7 +118,19 @@ comptime {
|
||||||
@export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" });
|
@export(&c.key_encoder_free, .{ .name = "ghostty_key_encoder_free" });
|
||||||
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
@export(&c.key_encoder_setopt, .{ .name = "ghostty_key_encoder_setopt" });
|
||||||
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
@export(&c.key_encoder_encode, .{ .name = "ghostty_key_encoder_encode" });
|
||||||
|
@export(&c.osc_new, .{ .name = "ghostty_osc_new" });
|
||||||
|
@export(&c.osc_free, .{ .name = "ghostty_osc_free" });
|
||||||
|
@export(&c.osc_next, .{ .name = "ghostty_osc_next" });
|
||||||
|
@export(&c.osc_reset, .{ .name = "ghostty_osc_reset" });
|
||||||
|
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
||||||
|
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||||
|
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||||
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
@export(&c.paste_is_safe, .{ .name = "ghostty_paste_is_safe" });
|
||||||
|
@export(&c.sgr_new, .{ .name = "ghostty_sgr_new" });
|
||||||
|
@export(&c.sgr_free, .{ .name = "ghostty_sgr_free" });
|
||||||
|
@export(&c.sgr_reset, .{ .name = "ghostty_sgr_reset" });
|
||||||
|
@export(&c.sgr_set_params, .{ .name = "ghostty_sgr_set_params" });
|
||||||
|
@export(&c.sgr_next, .{ .name = "ghostty_sgr_next" });
|
||||||
|
|
||||||
// On Wasm we need to export our allocator convenience functions.
|
// On Wasm we need to export our allocator convenience functions.
|
||||||
if (builtin.target.cpu.arch.isWasm()) {
|
if (builtin.target.cpu.arch.isWasm()) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ pub const osc = @import("osc.zig");
|
||||||
pub const key_event = @import("key_event.zig");
|
pub const key_event = @import("key_event.zig");
|
||||||
pub const key_encode = @import("key_encode.zig");
|
pub const key_encode = @import("key_encode.zig");
|
||||||
pub const paste = @import("paste.zig");
|
pub const paste = @import("paste.zig");
|
||||||
|
pub const sgr = @import("sgr.zig");
|
||||||
|
|
||||||
// The full C API, unexported.
|
// The full C API, unexported.
|
||||||
pub const osc_new = osc.new;
|
pub const osc_new = osc.new;
|
||||||
|
|
@ -12,6 +13,12 @@ pub const osc_end = osc.end;
|
||||||
pub const osc_command_type = osc.commandType;
|
pub const osc_command_type = osc.commandType;
|
||||||
pub const osc_command_data = osc.commandData;
|
pub const osc_command_data = osc.commandData;
|
||||||
|
|
||||||
|
pub const sgr_new = sgr.new;
|
||||||
|
pub const sgr_free = sgr.free;
|
||||||
|
pub const sgr_reset = sgr.reset;
|
||||||
|
pub const sgr_set_params = sgr.setParams;
|
||||||
|
pub const sgr_next = sgr.next;
|
||||||
|
|
||||||
pub const key_event_new = key_event.new;
|
pub const key_event_new = key_event.new;
|
||||||
pub const key_event_free = key_event.free;
|
pub const key_event_free = key_event.free;
|
||||||
pub const key_event_set_action = key_event.set_action;
|
pub const key_event_set_action = key_event.set_action;
|
||||||
|
|
@ -41,6 +48,7 @@ test {
|
||||||
_ = key_event;
|
_ = key_event;
|
||||||
_ = key_encode;
|
_ = key_encode;
|
||||||
_ = paste;
|
_ = paste;
|
||||||
|
_ = sgr;
|
||||||
|
|
||||||
// We want to make sure we run the tests for the C allocator interface.
|
// We want to make sure we run the tests for the C allocator interface.
|
||||||
_ = @import("../../lib/allocator.zig");
|
_ = @import("../../lib/allocator.zig");
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
pub const Result = enum(c_int) {
|
pub const Result = enum(c_int) {
|
||||||
success = 0,
|
success = 0,
|
||||||
out_of_memory = -1,
|
out_of_memory = -1,
|
||||||
|
invalid_value = -2,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const testing = std.testing;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const lib_alloc = @import("../../lib/allocator.zig");
|
||||||
|
const CAllocator = lib_alloc.Allocator;
|
||||||
|
const sgr = @import("../sgr.zig");
|
||||||
|
const Result = @import("result.zig").Result;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.sgr);
|
||||||
|
|
||||||
|
/// Wrapper around parser that tracks the allocator for C API usage.
|
||||||
|
const ParserWrapper = struct {
|
||||||
|
parser: sgr.Parser,
|
||||||
|
alloc: Allocator,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// C: GhosttySgrParser
|
||||||
|
pub const Parser = ?*ParserWrapper;
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
alloc_: ?*const CAllocator,
|
||||||
|
result: *Parser,
|
||||||
|
) callconv(.c) Result {
|
||||||
|
const alloc = lib_alloc.default(alloc_);
|
||||||
|
const ptr = alloc.create(ParserWrapper) catch
|
||||||
|
return .out_of_memory;
|
||||||
|
ptr.* = .{
|
||||||
|
.parser = .empty,
|
||||||
|
.alloc = alloc,
|
||||||
|
};
|
||||||
|
result.* = ptr;
|
||||||
|
return .success;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(parser_: Parser) callconv(.c) void {
|
||||||
|
const wrapper = parser_ orelse return;
|
||||||
|
const alloc = wrapper.alloc;
|
||||||
|
const parser: *sgr.Parser = &wrapper.parser;
|
||||||
|
if (parser.params.len > 0) alloc.free(parser.params);
|
||||||
|
alloc.destroy(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(parser_: Parser) callconv(.c) void {
|
||||||
|
const wrapper = parser_ orelse return;
|
||||||
|
const parser: *sgr.Parser = &wrapper.parser;
|
||||||
|
parser.idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setParams(
|
||||||
|
parser_: Parser,
|
||||||
|
params: [*]const u16,
|
||||||
|
seps_: ?[*]const u8,
|
||||||
|
len: usize,
|
||||||
|
) callconv(.c) Result {
|
||||||
|
const wrapper = parser_ orelse return .invalid_value;
|
||||||
|
const alloc = wrapper.alloc;
|
||||||
|
const parser: *sgr.Parser = &wrapper.parser;
|
||||||
|
|
||||||
|
// Copy our new parameters
|
||||||
|
const params_slice = alloc.dupe(u16, params[0..len]) catch
|
||||||
|
return .out_of_memory;
|
||||||
|
if (parser.params.len > 0) alloc.free(parser.params);
|
||||||
|
parser.params = params_slice;
|
||||||
|
|
||||||
|
// If we have separators, set that state too.
|
||||||
|
parser.params_sep = .initEmpty();
|
||||||
|
if (seps_) |seps| {
|
||||||
|
if (len > @TypeOf(parser.params_sep).bit_length) {
|
||||||
|
log.warn("ghostty_sgr_set_params: separators length {} exceeds max supported length {}", .{
|
||||||
|
len,
|
||||||
|
@TypeOf(parser.params_sep).bit_length,
|
||||||
|
});
|
||||||
|
return .invalid_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (seps[0..len], 0..) |sep, i| {
|
||||||
|
if (sep == ':') parser.params_sep.set(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset our parsing state
|
||||||
|
parser.idx = 0;
|
||||||
|
|
||||||
|
return .success;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(
|
||||||
|
parser_: Parser,
|
||||||
|
result: *sgr.Attribute.C,
|
||||||
|
) callconv(.c) bool {
|
||||||
|
const wrapper = parser_ orelse return false;
|
||||||
|
const parser: *sgr.Parser = &wrapper.parser;
|
||||||
|
if (parser.next()) |attr| {
|
||||||
|
result.* = attr.cval();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "alloc" {
|
||||||
|
var p: Parser = undefined;
|
||||||
|
try testing.expectEqual(Result.success, new(
|
||||||
|
&lib_alloc.test_allocator,
|
||||||
|
&p,
|
||||||
|
));
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "simple params, no seps" {
|
||||||
|
var p: Parser = undefined;
|
||||||
|
try testing.expectEqual(Result.success, new(
|
||||||
|
&lib_alloc.test_allocator,
|
||||||
|
&p,
|
||||||
|
));
|
||||||
|
defer free(p);
|
||||||
|
|
||||||
|
try testing.expectEqual(Result.success, setParams(
|
||||||
|
p,
|
||||||
|
&.{1},
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Set it twice on purpose to make sure we don't leak.
|
||||||
|
try testing.expectEqual(Result.success, setParams(
|
||||||
|
p,
|
||||||
|
&.{1},
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
));
|
||||||
|
|
||||||
|
// Verify we get bold
|
||||||
|
var attr: sgr.Attribute.C = undefined;
|
||||||
|
try testing.expect(next(p, &attr));
|
||||||
|
try testing.expectEqual(.bold, attr.tag);
|
||||||
|
|
||||||
|
// Nothing else
|
||||||
|
try testing.expect(!next(p, &attr));
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,6 @@ const stream = @import("stream.zig");
|
||||||
const ansi = @import("ansi.zig");
|
const ansi = @import("ansi.zig");
|
||||||
const csi = @import("csi.zig");
|
const csi = @import("csi.zig");
|
||||||
const hyperlink = @import("hyperlink.zig");
|
const hyperlink = @import("hyperlink.zig");
|
||||||
const sgr = @import("sgr.zig");
|
|
||||||
const stream_readonly = @import("stream_readonly.zig");
|
const stream_readonly = @import("stream_readonly.zig");
|
||||||
const style = @import("style.zig");
|
const style = @import("style.zig");
|
||||||
pub const apc = @import("apc.zig");
|
pub const apc = @import("apc.zig");
|
||||||
|
|
@ -19,6 +18,7 @@ pub const modes = @import("modes.zig");
|
||||||
pub const page = @import("page.zig");
|
pub const page = @import("page.zig");
|
||||||
pub const parse_table = @import("parse_table.zig");
|
pub const parse_table = @import("parse_table.zig");
|
||||||
pub const search = @import("search.zig");
|
pub const search = @import("search.zig");
|
||||||
|
pub const sgr = @import("sgr.zig");
|
||||||
pub const size = @import("size.zig");
|
pub const size = @import("size.zig");
|
||||||
pub const tmux = if (options.tmux_control_mode) @import("tmux.zig") else struct {};
|
pub const tmux = if (options.tmux_control_mode) @import("tmux.zig") else struct {};
|
||||||
pub const x11_color = @import("x11_color.zig");
|
pub const x11_color = @import("x11_color.zig");
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ pub const Attribute = union(Tag) {
|
||||||
dotted = 4,
|
dotted = 4,
|
||||||
dashed = 5,
|
dashed = 5,
|
||||||
|
|
||||||
pub const C = u8;
|
pub const C = c_int;
|
||||||
|
|
||||||
pub fn cval(self: Underline) Underline.C {
|
pub fn cval(self: Underline) Underline.C {
|
||||||
return @intFromEnum(self);
|
return @intFromEnum(self);
|
||||||
|
|
@ -176,10 +176,13 @@ pub const Attribute = union(Tag) {
|
||||||
|
|
||||||
/// Parser parses the attributes from a list of SGR parameters.
|
/// Parser parses the attributes from a list of SGR parameters.
|
||||||
pub const Parser = struct {
|
pub const Parser = struct {
|
||||||
params: []const u16,
|
params: []const u16 = &.{},
|
||||||
params_sep: SepList = .initEmpty(),
|
params_sep: SepList = .initEmpty(),
|
||||||
idx: usize = 0,
|
idx: usize = 0,
|
||||||
|
|
||||||
|
/// Empty state parser.
|
||||||
|
pub const empty: Parser = .{};
|
||||||
|
|
||||||
/// Next returns the next attribute or null if there are no more attributes.
|
/// Next returns the next attribute or null if there are no more attributes.
|
||||||
pub fn next(self: *Parser) ?Attribute {
|
pub fn next(self: *Parser) ?Attribute {
|
||||||
if (self.idx >= self.params.len) {
|
if (self.idx >= self.params.len) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue