mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-02 16:08:39 +00:00
lib-vt: OSC data extraction boilerplate
This also changes OSC strings to be null-terminated to ease lib-vt integration. This shouldn't have any practical effect on terminal performance, but it does lower the maximum length of OSC strings by 1 since we always reserve space for the null terminator.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
#include <ghostty/vt.h>
|
#include <ghostty/vt.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -8,10 +9,13 @@ int main() {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup change window title command to change the title to "a"
|
// Setup change window title command to change the title to "hello"
|
||||||
ghostty_osc_next(parser, '0');
|
ghostty_osc_next(parser, '0');
|
||||||
ghostty_osc_next(parser, ';');
|
ghostty_osc_next(parser, ';');
|
||||||
ghostty_osc_next(parser, 'a');
|
const char *title = "hello";
|
||||||
|
for (size_t i = 0; i < strlen(title); i++) {
|
||||||
|
ghostty_osc_next(parser, title[i]);
|
||||||
|
}
|
||||||
|
|
||||||
// End parsing and get command
|
// End parsing and get command
|
||||||
GhosttyOscCommand command = ghostty_osc_end(parser, 0);
|
GhosttyOscCommand command = ghostty_osc_end(parser, 0);
|
||||||
@@ -20,6 +24,13 @@ int main() {
|
|||||||
GhosttyOscCommandType type = ghostty_osc_command_type(command);
|
GhosttyOscCommandType type = ghostty_osc_command_type(command);
|
||||||
printf("Command type: %d\n", type);
|
printf("Command type: %d\n", type);
|
||||||
|
|
||||||
|
// Extract and print the title
|
||||||
|
if (ghostty_osc_command_data(command, GHOSTTY_OSC_DATA_CHANGE_WINDOW_TITLE_STR, &title)) {
|
||||||
|
printf("Extracted title: %s\n", title);
|
||||||
|
} else {
|
||||||
|
printf("Failed to extract title\n");
|
||||||
|
}
|
||||||
|
|
||||||
ghostty_osc_free(parser);
|
ghostty_osc_free(parser);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,8 @@ extern "C" {
|
|||||||
* be used to parse the contents of OSC sequences. This isn't a full VT
|
* be used to parse the contents of OSC sequences. This isn't a full VT
|
||||||
* parser; it is only the OSC parser component. This is useful if you have
|
* parser; it is only the OSC parser component. This is useful if you have
|
||||||
* a parser already and want to only extract and handle OSC sequences.
|
* a parser already and want to only extract and handle OSC sequences.
|
||||||
|
*
|
||||||
|
* @ingroup osc
|
||||||
*/
|
*/
|
||||||
typedef struct GhosttyOscParser *GhosttyOscParser;
|
typedef struct GhosttyOscParser *GhosttyOscParser;
|
||||||
|
|
||||||
@@ -41,6 +43,8 @@ typedef struct GhosttyOscParser *GhosttyOscParser;
|
|||||||
* This handle represents a parsed OSC (Operating System Command) command.
|
* This handle represents a parsed OSC (Operating System Command) command.
|
||||||
* The command can be queried for its type and associated data using
|
* The command can be queried for its type and associated data using
|
||||||
* `ghostty_osc_command_type` and `ghostty_osc_command_data`.
|
* `ghostty_osc_command_type` and `ghostty_osc_command_data`.
|
||||||
|
*
|
||||||
|
* @ingroup osc
|
||||||
*/
|
*/
|
||||||
typedef struct GhosttyOscCommand *GhosttyOscCommand;
|
typedef struct GhosttyOscCommand *GhosttyOscCommand;
|
||||||
|
|
||||||
@@ -56,6 +60,8 @@ typedef enum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* OSC command types.
|
* OSC command types.
|
||||||
|
*
|
||||||
|
* @ingroup osc
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_OSC_COMMAND_INVALID = 0,
|
GHOSTTY_OSC_COMMAND_INVALID = 0,
|
||||||
@@ -81,6 +87,31 @@ typedef enum {
|
|||||||
GHOSTTY_OSC_COMMAND_CONEMU_GUIMACRO = 20,
|
GHOSTTY_OSC_COMMAND_CONEMU_GUIMACRO = 20,
|
||||||
} GhosttyOscCommandType;
|
} GhosttyOscCommandType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSC command data types.
|
||||||
|
*
|
||||||
|
* These values specify what type of data to extract from an OSC command
|
||||||
|
* using `ghostty_osc_command_data`.
|
||||||
|
*
|
||||||
|
* @ingroup osc
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
/** Invalid data type. Never results in any data extraction. */
|
||||||
|
GHOSTTY_OSC_DATA_INVALID = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window title string data.
|
||||||
|
*
|
||||||
|
* Valid for: GHOSTTY_OSC_COMMAND_CHANGE_WINDOW_TITLE
|
||||||
|
*
|
||||||
|
* Output type: const char ** (pointer to null-terminated string)
|
||||||
|
*
|
||||||
|
* Lifetime: Valid until the next call to any ghostty_osc_* function with
|
||||||
|
* the same parser instance. Memory is owned by the parser.
|
||||||
|
*/
|
||||||
|
GHOSTTY_OSC_DATA_CHANGE_WINDOW_TITLE_STR = 1,
|
||||||
|
} GhosttyOscCommandData;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Allocator Interface
|
// Allocator Interface
|
||||||
|
|
||||||
@@ -227,6 +258,27 @@ typedef struct {
|
|||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
// Functions
|
// Functions
|
||||||
|
|
||||||
|
/** @defgroup osc OSC Parser
|
||||||
|
*
|
||||||
|
* OSC (Operating System Command) sequence parser and command handling.
|
||||||
|
*
|
||||||
|
* The parser operates in a streaming fashion, processing input byte-by-byte
|
||||||
|
* to handle OSC sequences that may arrive in fragments across multiple reads.
|
||||||
|
* This interface makes it easy to integrate into most environments and avoids
|
||||||
|
* over-allocating buffers.
|
||||||
|
*
|
||||||
|
* ## Basic Usage
|
||||||
|
*
|
||||||
|
* 1. Create a parser instance with ghostty_osc_new()
|
||||||
|
* 2. Feed bytes to the parser using ghostty_osc_next()
|
||||||
|
* 3. Finalize parsing with ghostty_osc_end() to get the command
|
||||||
|
* 4. Query command type and extract data using ghostty_osc_command_type()
|
||||||
|
* and ghostty_osc_command_data()
|
||||||
|
* 5. Free the parser with ghostty_osc_free() when done
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new OSC parser instance.
|
* Create a new OSC parser instance.
|
||||||
*
|
*
|
||||||
@@ -316,6 +368,23 @@ GhosttyOscCommand ghostty_osc_end(GhosttyOscParser parser, uint8_t terminator);
|
|||||||
*/
|
*/
|
||||||
GhosttyOscCommandType ghostty_osc_command_type(GhosttyOscCommand command);
|
GhosttyOscCommandType ghostty_osc_command_type(GhosttyOscCommand command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract data from an OSC command.
|
||||||
|
*
|
||||||
|
* Extracts typed data from the given OSC command based on the specified
|
||||||
|
* data type. The output pointer must be of the appropriate type for the
|
||||||
|
* requested data kind. Valid command types, output types, and memory
|
||||||
|
* safety information are documented in the `GhosttyOscCommandData` enum.
|
||||||
|
*
|
||||||
|
* @param command The OSC command handle to query (may be NULL)
|
||||||
|
* @param data The type of data to extract
|
||||||
|
* @param out Pointer to store the extracted data (type depends on data parameter)
|
||||||
|
* @return true if data extraction was successful, false otherwise
|
||||||
|
*/
|
||||||
|
bool ghostty_osc_command_data(GhosttyOscCommand command, GhosttyOscCommandData data, void *out);
|
||||||
|
|
||||||
|
/** @} */ // end of osc group
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@@ -197,7 +197,9 @@ pub const VTEvent = struct {
|
|||||||
) !void {
|
) !void {
|
||||||
switch (@TypeOf(v)) {
|
switch (@TypeOf(v)) {
|
||||||
void => {},
|
void => {},
|
||||||
[]const u8 => try md.put("data", try alloc.dupeZ(u8, v)),
|
[]const u8,
|
||||||
|
[:0]const u8,
|
||||||
|
=> try md.put("data", try alloc.dupeZ(u8, v)),
|
||||||
else => |T| switch (@typeInfo(T)) {
|
else => |T| switch (@typeInfo(T)) {
|
||||||
.@"struct" => |info| inline for (info.fields) |field| {
|
.@"struct" => |info| inline for (info.fields) |field| {
|
||||||
try encodeMetadataSingle(
|
try encodeMetadataSingle(
|
||||||
@@ -284,7 +286,9 @@ pub const VTEvent = struct {
|
|||||||
try std.fmt.allocPrintZ(alloc, "{}", .{value}),
|
try std.fmt.allocPrintZ(alloc, "{}", .{value}),
|
||||||
),
|
),
|
||||||
|
|
||||||
[]const u8 => try md.put(key, try alloc.dupeZ(u8, value)),
|
[]const u8,
|
||||||
|
[:0]const u8,
|
||||||
|
=> try md.put(key, try alloc.dupeZ(u8, value)),
|
||||||
|
|
||||||
else => |T| {
|
else => |T| {
|
||||||
@compileLog(T);
|
@compileLog(T);
|
||||||
|
@@ -77,6 +77,7 @@ comptime {
|
|||||||
@export(&c.osc_reset, .{ .name = "ghostty_osc_reset" });
|
@export(&c.osc_reset, .{ .name = "ghostty_osc_reset" });
|
||||||
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
@export(&c.osc_end, .{ .name = "ghostty_osc_end" });
|
||||||
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
@export(&c.osc_command_type, .{ .name = "ghostty_osc_command_type" });
|
||||||
|
@export(&c.osc_command_data, .{ .name = "ghostty_osc_command_data" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ pub const osc_reset = osc.reset;
|
|||||||
pub const osc_next = osc.next;
|
pub const osc_next = osc.next;
|
||||||
pub const osc_end = osc.end;
|
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;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = osc;
|
_ = osc;
|
||||||
|
@@ -49,6 +49,51 @@ pub fn commandType(command_: Command) callconv(.c) osc.Command.Key {
|
|||||||
return command.*;
|
return command.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// C: GhosttyOscCommandData
|
||||||
|
pub const CommandData = enum(c_int) {
|
||||||
|
invalid = 0,
|
||||||
|
change_window_title_str = 1,
|
||||||
|
|
||||||
|
/// Output type expected for querying the data of the given kind.
|
||||||
|
pub fn OutType(comptime self: CommandData) type {
|
||||||
|
return switch (self) {
|
||||||
|
.invalid => void,
|
||||||
|
.change_window_title_str => [*:0]const u8,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn commandData(
|
||||||
|
command_: Command,
|
||||||
|
data: CommandData,
|
||||||
|
out: ?*anyopaque,
|
||||||
|
) callconv(.c) bool {
|
||||||
|
return switch (data) {
|
||||||
|
inline else => |comptime_data| commandDataTyped(
|
||||||
|
command_,
|
||||||
|
comptime_data,
|
||||||
|
@ptrCast(@alignCast(out)),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commandDataTyped(
|
||||||
|
command_: Command,
|
||||||
|
comptime data: CommandData,
|
||||||
|
out: *data.OutType(),
|
||||||
|
) bool {
|
||||||
|
const command = command_.?;
|
||||||
|
switch (data) {
|
||||||
|
.invalid => return false,
|
||||||
|
.change_window_title_str => switch (command.*) {
|
||||||
|
.change_window_title => |v| out.* = v.ptr,
|
||||||
|
else => return false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
test "alloc" {
|
test "alloc" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var p: Parser = undefined;
|
var p: Parser = undefined;
|
||||||
@@ -64,7 +109,7 @@ test "command type null" {
|
|||||||
try testing.expectEqual(.invalid, commandType(null));
|
try testing.expectEqual(.invalid, commandType(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
test "command type" {
|
test "change window title" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var p: Parser = undefined;
|
var p: Parser = undefined;
|
||||||
try testing.expectEqual(Result.success, new(
|
try testing.expectEqual(Result.success, new(
|
||||||
@@ -73,9 +118,15 @@ test "command type" {
|
|||||||
));
|
));
|
||||||
defer free(p);
|
defer free(p);
|
||||||
|
|
||||||
|
// Parse it
|
||||||
next(p, '0');
|
next(p, '0');
|
||||||
next(p, ';');
|
next(p, ';');
|
||||||
next(p, 'a');
|
next(p, 'a');
|
||||||
const cmd = end(p, 0);
|
const cmd = end(p, 0);
|
||||||
try testing.expectEqual(.change_window_title, commandType(cmd));
|
try testing.expectEqual(.change_window_title, commandType(cmd));
|
||||||
|
|
||||||
|
// Extract the title
|
||||||
|
var title: [*:0]const u8 = undefined;
|
||||||
|
try testing.expect(commandData(cmd, .change_window_title_str, @ptrCast(&title)));
|
||||||
|
try testing.expectEqualStrings("a", std.mem.span(title));
|
||||||
}
|
}
|
||||||
|
@@ -26,19 +26,19 @@ pub const Command = union(Key) {
|
|||||||
|
|
||||||
/// Set the window title of the terminal
|
/// Set the window title of the terminal
|
||||||
///
|
///
|
||||||
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
|
/// If title mode 0 is set text is expect to be hex encoded (i.e. utf-8
|
||||||
/// with each code unit further encoded with two hex digits).
|
/// with each code unit further encoded with two hex digits).
|
||||||
///
|
///
|
||||||
/// If title mode 2 is set or the terminal is setup for unconditional
|
/// If title mode 2 is set or the terminal is setup for unconditional
|
||||||
/// utf-8 titles text is interpreted as utf-8. Else text is interpreted
|
/// utf-8 titles text is interpreted as utf-8. Else text is interpreted
|
||||||
/// as latin1.
|
/// as latin1.
|
||||||
change_window_title: []const u8,
|
change_window_title: [:0]const u8,
|
||||||
|
|
||||||
/// Set the icon of the terminal window. The name of the icon is not
|
/// Set the icon of the terminal window. The name of the icon is not
|
||||||
/// well defined, so this is currently ignored by Ghostty at the time
|
/// well defined, so this is currently ignored by Ghostty at the time
|
||||||
/// of writing this. We just parse it so that we don't get parse errors
|
/// of writing this. We just parse it so that we don't get parse errors
|
||||||
/// in the log.
|
/// in the log.
|
||||||
change_window_icon: []const u8,
|
change_window_icon: [:0]const u8,
|
||||||
|
|
||||||
/// First do a fresh-line. Then start a new command, and enter prompt mode:
|
/// First do a fresh-line. Then start a new command, and enter prompt mode:
|
||||||
/// Subsequent text (until a OSC "133;B" or OSC "133;I" command) is a
|
/// Subsequent text (until a OSC "133;B" or OSC "133;I" command) is a
|
||||||
@@ -54,7 +54,7 @@ pub const Command = union(Key) {
|
|||||||
/// - secondary: a non-editable continuation line
|
/// - secondary: a non-editable continuation line
|
||||||
/// - right: a right-aligned prompt that may need adjustment during reflow
|
/// - right: a right-aligned prompt that may need adjustment during reflow
|
||||||
prompt_start: struct {
|
prompt_start: struct {
|
||||||
aid: ?[]const u8 = null,
|
aid: ?[:0]const u8 = null,
|
||||||
kind: enum { primary, continuation, secondary, right } = .primary,
|
kind: enum { primary, continuation, secondary, right } = .primary,
|
||||||
redraw: bool = true,
|
redraw: bool = true,
|
||||||
},
|
},
|
||||||
@@ -96,7 +96,7 @@ pub const Command = union(Key) {
|
|||||||
/// contents is set on the clipboard.
|
/// contents is set on the clipboard.
|
||||||
clipboard_contents: struct {
|
clipboard_contents: struct {
|
||||||
kind: u8,
|
kind: u8,
|
||||||
data: []const u8,
|
data: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// OSC 7. Reports the current working directory of the shell. This is
|
/// OSC 7. Reports the current working directory of the shell. This is
|
||||||
@@ -106,7 +106,7 @@ pub const Command = union(Key) {
|
|||||||
report_pwd: struct {
|
report_pwd: struct {
|
||||||
/// The reported pwd value. This is not checked for validity. It should
|
/// The reported pwd value. This is not checked for validity. It should
|
||||||
/// be a file URL but it is up to the caller to utilize this value.
|
/// be a file URL but it is up to the caller to utilize this value.
|
||||||
value: []const u8,
|
value: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// OSC 22. Set the mouse shape. There doesn't seem to be a standard
|
/// OSC 22. Set the mouse shape. There doesn't seem to be a standard
|
||||||
@@ -114,7 +114,7 @@ pub const Command = union(Key) {
|
|||||||
/// are moving towards using the W3C CSS cursor names. For OSC parsing,
|
/// are moving towards using the W3C CSS cursor names. For OSC parsing,
|
||||||
/// we just parse whatever string is given.
|
/// we just parse whatever string is given.
|
||||||
mouse_shape: struct {
|
mouse_shape: struct {
|
||||||
value: []const u8,
|
value: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// OSC color operations to set, reset, or report color settings. Some OSCs
|
/// OSC color operations to set, reset, or report color settings. Some OSCs
|
||||||
@@ -138,14 +138,14 @@ pub const Command = union(Key) {
|
|||||||
|
|
||||||
/// Show a desktop notification (OSC 9 or OSC 777)
|
/// Show a desktop notification (OSC 9 or OSC 777)
|
||||||
show_desktop_notification: struct {
|
show_desktop_notification: struct {
|
||||||
title: []const u8,
|
title: [:0]const u8,
|
||||||
body: []const u8,
|
body: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Start a hyperlink (OSC 8)
|
/// Start a hyperlink (OSC 8)
|
||||||
hyperlink_start: struct {
|
hyperlink_start: struct {
|
||||||
id: ?[]const u8 = null,
|
id: ?[:0]const u8 = null,
|
||||||
uri: []const u8,
|
uri: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// End a hyperlink (OSC 8)
|
/// End a hyperlink (OSC 8)
|
||||||
@@ -157,12 +157,12 @@ pub const Command = union(Key) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/// ConEmu show GUI message box (OSC 9;2)
|
/// ConEmu show GUI message box (OSC 9;2)
|
||||||
conemu_show_message_box: []const u8,
|
conemu_show_message_box: [:0]const u8,
|
||||||
|
|
||||||
/// ConEmu change tab title (OSC 9;3)
|
/// ConEmu change tab title (OSC 9;3)
|
||||||
conemu_change_tab_title: union(enum) {
|
conemu_change_tab_title: union(enum) {
|
||||||
reset,
|
reset,
|
||||||
value: []const u8,
|
value: [:0]const u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// ConEmu progress report (OSC 9;4)
|
/// ConEmu progress report (OSC 9;4)
|
||||||
@@ -172,7 +172,7 @@ pub const Command = union(Key) {
|
|||||||
conemu_wait_input,
|
conemu_wait_input,
|
||||||
|
|
||||||
/// ConEmu GUI macro (OSC 9;6)
|
/// ConEmu GUI macro (OSC 9;6)
|
||||||
conemu_guimacro: []const u8,
|
conemu_guimacro: [:0]const u8,
|
||||||
|
|
||||||
pub const Key = LibEnum(
|
pub const Key = LibEnum(
|
||||||
if (build_options.c_abi) .c else .zig,
|
if (build_options.c_abi) .c else .zig,
|
||||||
@@ -305,7 +305,7 @@ pub const Parser = struct {
|
|||||||
/// Temporary state that is dependent on the current state.
|
/// Temporary state that is dependent on the current state.
|
||||||
temp_state: union {
|
temp_state: union {
|
||||||
/// Current string parameter being populated
|
/// Current string parameter being populated
|
||||||
str: *[]const u8,
|
str: *[:0]const u8,
|
||||||
|
|
||||||
/// Current numeric parameter being populated
|
/// Current numeric parameter being populated
|
||||||
num: u16,
|
num: u16,
|
||||||
@@ -498,7 +498,10 @@ pub const Parser = struct {
|
|||||||
// If our buffer is full then we're invalid, so we set our state
|
// If our buffer is full then we're invalid, so we set our state
|
||||||
// accordingly and indicate the sequence is incomplete so that we
|
// accordingly and indicate the sequence is incomplete so that we
|
||||||
// don't accidentally issue a command when ending.
|
// don't accidentally issue a command when ending.
|
||||||
if (self.buf_idx >= self.buf.len) {
|
//
|
||||||
|
// We always keep space for 1 byte at the end to null-terminate
|
||||||
|
// values.
|
||||||
|
if (self.buf_idx >= self.buf.len - 1) {
|
||||||
if (self.state != .invalid) {
|
if (self.state != .invalid) {
|
||||||
log.warn(
|
log.warn(
|
||||||
"OSC sequence too long (> {d}), ignoring. state={}",
|
"OSC sequence too long (> {d}), ignoring. state={}",
|
||||||
@@ -1037,7 +1040,8 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
.notification_title => switch (c) {
|
.notification_title => switch (c) {
|
||||||
';' => {
|
';' => {
|
||||||
self.command.show_desktop_notification.title = self.buf[self.buf_start .. self.buf_idx - 1];
|
self.buf[self.buf_idx - 1] = 0;
|
||||||
|
self.command.show_desktop_notification.title = self.buf[self.buf_start .. self.buf_idx - 1 :0];
|
||||||
self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
|
self.temp_state = .{ .str = &self.command.show_desktop_notification.body };
|
||||||
self.buf_start = self.buf_idx;
|
self.buf_start = self.buf_idx;
|
||||||
self.state = .string;
|
self.state = .string;
|
||||||
@@ -1406,7 +1410,8 @@ pub const Parser = struct {
|
|||||||
fn endHyperlink(self: *Parser) void {
|
fn endHyperlink(self: *Parser) void {
|
||||||
switch (self.command) {
|
switch (self.command) {
|
||||||
.hyperlink_start => |*v| {
|
.hyperlink_start => |*v| {
|
||||||
const value = self.buf[self.buf_start..self.buf_idx];
|
self.buf[self.buf_idx] = 0;
|
||||||
|
const value = self.buf[self.buf_start..self.buf_idx :0];
|
||||||
if (v.id == null and value.len == 0) {
|
if (v.id == null and value.len == 0) {
|
||||||
self.command = .{ .hyperlink_end = {} };
|
self.command = .{ .hyperlink_end = {} };
|
||||||
return;
|
return;
|
||||||
@@ -1420,10 +1425,12 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn endHyperlinkOptionValue(self: *Parser) void {
|
fn endHyperlinkOptionValue(self: *Parser) void {
|
||||||
const value = if (self.buf_start == self.buf_idx)
|
const value: [:0]const u8 = if (self.buf_start == self.buf_idx)
|
||||||
""
|
""
|
||||||
else
|
else buf: {
|
||||||
self.buf[self.buf_start .. self.buf_idx - 1];
|
self.buf[self.buf_idx - 1] = 0;
|
||||||
|
break :buf self.buf[self.buf_start .. self.buf_idx - 1 :0];
|
||||||
|
};
|
||||||
|
|
||||||
if (mem.eql(u8, self.temp_state.key, "id")) {
|
if (mem.eql(u8, self.temp_state.key, "id")) {
|
||||||
switch (self.command) {
|
switch (self.command) {
|
||||||
@@ -1438,7 +1445,11 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn endSemanticOptionValue(self: *Parser) void {
|
fn endSemanticOptionValue(self: *Parser) void {
|
||||||
const value = self.buf[self.buf_start..self.buf_idx];
|
const value = value: {
|
||||||
|
self.buf[self.buf_idx] = 0;
|
||||||
|
defer self.buf_idx += 1;
|
||||||
|
break :value self.buf[self.buf_start..self.buf_idx :0];
|
||||||
|
};
|
||||||
|
|
||||||
if (mem.eql(u8, self.temp_state.key, "aid")) {
|
if (mem.eql(u8, self.temp_state.key, "aid")) {
|
||||||
switch (self.command) {
|
switch (self.command) {
|
||||||
@@ -1495,7 +1506,9 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn endString(self: *Parser) void {
|
fn endString(self: *Parser) void {
|
||||||
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx];
|
self.buf[self.buf_idx] = 0;
|
||||||
|
defer self.buf_idx += 1;
|
||||||
|
self.temp_state.str.* = self.buf[self.buf_start..self.buf_idx :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endConEmuSleepValue(self: *Parser) void {
|
fn endConEmuSleepValue(self: *Parser) void {
|
||||||
@@ -1589,8 +1602,15 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn endAllocableString(self: *Parser) void {
|
fn endAllocableString(self: *Parser) void {
|
||||||
|
const alloc = self.alloc.?;
|
||||||
const list = self.buf_dynamic.?;
|
const list = self.buf_dynamic.?;
|
||||||
self.temp_state.str.* = list.items;
|
list.append(alloc, 0) catch {
|
||||||
|
log.warn("allocation failed on allocable string termination", .{});
|
||||||
|
self.temp_state.str.* = "";
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.temp_state.str.* = list.items[0 .. list.items.len - 1 :0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// End the sequence and return the command, if any. If the return value
|
/// End the sequence and return the command, if any. If the return value
|
||||||
@@ -1976,6 +1996,36 @@ test "OSC: longer than buffer" {
|
|||||||
try testing.expect(p.complete == false);
|
try testing.expect(p.complete == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "OSC: one shorter than buffer length" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .init();
|
||||||
|
|
||||||
|
const prefix = "0;";
|
||||||
|
const title = "a" ** (Parser.MAX_BUF - prefix.len - 1);
|
||||||
|
const input = prefix ++ title;
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
const cmd = p.end(null).?.*;
|
||||||
|
try testing.expect(cmd == .change_window_title);
|
||||||
|
try testing.expectEqualStrings(title, cmd.change_window_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "OSC: exactly at buffer length" {
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
var p: Parser = .init();
|
||||||
|
|
||||||
|
const prefix = "0;";
|
||||||
|
const title = "a" ** (Parser.MAX_BUF - prefix.len);
|
||||||
|
const input = prefix ++ title;
|
||||||
|
for (input) |ch| p.next(ch);
|
||||||
|
|
||||||
|
// This should be null because we always reserve space for a null terminator.
|
||||||
|
try testing.expect(p.end(null) == null);
|
||||||
|
try testing.expect(p.complete == false);
|
||||||
|
}
|
||||||
|
|
||||||
test "OSC: OSC 9;1 ConEmu sleep" {
|
test "OSC: OSC 9;1 ConEmu sleep" {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user