terminal/osc: boilerplate new OSC 133 parsing

This commit is contained in:
Mitchell Hashimoto
2026-01-23 13:01:20 -08:00
parent 5de4ff3b98
commit d040c935e2
5 changed files with 104 additions and 16 deletions

View File

@@ -41,6 +41,9 @@ pub const Command = union(Key) {
/// in the log.
change_window_icon: [:0]const u8,
/// Semantic prompt command: https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
semantic_prompt: parsers.semantic_prompt2.Command,
/// 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
/// prompt string (as if followed by OSC 133;P;k=i\007). Note: I've noticed
@@ -225,6 +228,7 @@ pub const Command = union(Key) {
"invalid",
"change_window_title",
"change_window_icon",
"semantic_prompt",
"prompt_start",
"prompt_end",
"end_of_input",
@@ -469,6 +473,7 @@ pub const Parser = struct {
.prompt_end,
.prompt_start,
.report_pwd,
.semantic_prompt,
.show_desktop_notification,
.kitty_text_sizing,
=> {},
@@ -751,7 +756,7 @@ pub const Parser = struct {
.@"77" => null,
.@"133" => parsers.semantic_prompt.parse(self, terminator_ch),
.@"133" => parsers.semantic_prompt2.parse(self, terminator_ch),
.@"777" => parsers.rxvt_extension.parse(self, terminator_ch),

View File

@@ -13,19 +13,8 @@ pub const osc9 = @import("parsers/osc9.zig");
pub const report_pwd = @import("parsers/report_pwd.zig");
pub const rxvt_extension = @import("parsers/rxvt_extension.zig");
pub const semantic_prompt = @import("parsers/semantic_prompt.zig");
pub const semantic_prompt2 = @import("parsers/semantic_prompt2.zig");
test {
_ = change_window_icon;
_ = change_window_title;
_ = clipboard_operation;
_ = color;
_ = hyperlink;
_ = iterm2;
_ = kitty_color;
_ = kitty_text_sizing;
_ = mouse_shape;
_ = osc9;
_ = report_pwd;
_ = rxvt_extension;
_ = semantic_prompt;
std.testing.refAllDecls(@This());
}

View File

@@ -1,7 +1,6 @@
//! https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
const std = @import("std");
const string_encoding = @import("../../../os/string_encoding.zig");
const Parser = @import("../../osc.zig").Parser;
const Command = @import("../../osc.zig").Command;

View File

@@ -0,0 +1,92 @@
//! https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md
const std = @import("std");
const Parser = @import("../../osc.zig").Parser;
const OSCCommand = @import("../../osc.zig").Command;
const log = std.log.scoped(.osc_semantic_prompt);
pub const Command = union(enum) {
fresh_line,
fresh_line_new_prompt: Options,
};
pub const Options = struct {
aid: ?[:0]const u8,
cl: ?Click,
// TODO: more
pub const init: Options = .{
.aid = null,
.click = null,
};
};
pub const Click = enum {
line,
multiple,
conservative_vertical,
smart_vertical,
};
/// Parse OSC 133, semantic prompts
pub fn parse(parser: *Parser, _: ?u8) ?*OSCCommand {
const writer = parser.writer orelse {
parser.state = .invalid;
return null;
};
const data = writer.buffered();
if (data.len == 0) {
parser.state = .invalid;
return null;
}
parser.command = command: {
parse: switch (data[0]) {
'L' => {
if (data.len > 1) break :parse;
break :command .{ .semantic_prompt = .fresh_line };
},
else => {},
}
// Any fallthroughs are invalid
parser.state = .invalid;
return null;
};
return &parser.command;
}
test "OSC 133: fresh_line" {
const testing = std.testing;
var p: Parser = .init(null);
const input = "133;L";
for (input) |ch| p.next(ch);
const cmd = p.end(null).?.*;
try testing.expect(cmd == .semantic_prompt);
try testing.expect(cmd.semantic_prompt == .fresh_line);
}
test "OSC 133: fresh_line extra contents" {
const testing = std.testing;
// Random
{
var p: Parser = .init(null);
const input = "133;Lol";
for (input) |ch| p.next(ch);
try testing.expect(p.end(null) == null);
}
// Options
{
var p: Parser = .init(null);
const input = "133;L;aid=foo";
for (input) |ch| p.next(ch);
try testing.expect(p.end(null) == null);
}
}

View File

@@ -2003,6 +2003,9 @@ pub fn Stream(comptime Handler: type) type {
// ref: https://github.com/qwerasd205/asciinema-stats
switch (cmd) {
// TODO
.semantic_prompt => {},
.change_window_title => |title| {
@branchHint(.likely);
if (!std.unicode.utf8ValidateSlice(title)) {