Skip to content

Commit 3005a2a

Browse files
committed
config: simplify shell feature parsing and formatting
Use std.mem.cutPrefix for feature prefixes and iterate fields in declaration order instead of sorting copied metadata. Use the Writer buffered accessor and narrow the formatter error set to the current API.
1 parent ec7eb87 commit 3005a2a

2 files changed

Lines changed: 32 additions & 52 deletions

File tree

src/config/shell.zig

Lines changed: 29 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -61,31 +61,27 @@ pub const ShellIntegrationFeatures = struct {
6161
const trimmed = std.mem.trim(u8, part_raw, cli.args.whitespace);
6262

6363
// Handle cursor[:shape[:style]] syntax
64-
if (std.mem.eql(u8, trimmed, "cursor") or
65-
std.mem.startsWith(u8, trimmed, "cursor:"))
66-
{
67-
var cursor_iter = std.mem.splitScalar(u8, trimmed, ':');
68-
_ = cursor_iter.next(); // skip "cursor"
69-
70-
// Parse shape (if present)
71-
if (cursor_iter.next()) |shape| {
72-
// For convenience, "blink" or "steady" alone implies bar (default)
73-
if (std.mem.eql(u8, shape, "blink")) {
74-
result.cursor.shape = .bar;
75-
result.cursor.style = .blink;
76-
} else if (std.mem.eql(u8, shape, "steady")) {
77-
result.cursor.shape = .bar;
78-
result.cursor.style = .steady;
79-
} else {
80-
result.cursor.shape = std.meta.stringToEnum(Cursor.Shape, shape) orelse return error.InvalidValue;
81-
}
82-
83-
// Parse style (if present)
84-
if (cursor_iter.next()) |style| {
85-
result.cursor.style = std.meta.stringToEnum(Cursor.Style, style) orelse return error.InvalidValue;
86-
}
64+
if (std.mem.eql(u8, trimmed, "cursor")) {
65+
result.cursor = .{};
66+
continue;
67+
} else if (std.mem.cutPrefix(u8, trimmed, "cursor:")) |cursor_value| {
68+
var cursor_iter = std.mem.splitScalar(u8, cursor_value, ':');
69+
const shape = cursor_iter.next().?;
70+
71+
// For convenience, "blink" or "steady" alone implies bar (default)
72+
if (std.mem.eql(u8, shape, "blink")) {
73+
result.cursor.shape = .bar;
74+
result.cursor.style = .blink;
75+
} else if (std.mem.eql(u8, shape, "steady")) {
76+
result.cursor.shape = .bar;
77+
result.cursor.style = .steady;
8778
} else {
88-
result.cursor = .{};
79+
result.cursor.shape = std.meta.stringToEnum(Cursor.Shape, shape) orelse return error.InvalidValue;
80+
}
81+
82+
// Parse style (if present)
83+
if (cursor_iter.next()) |style| {
84+
result.cursor.style = std.meta.stringToEnum(Cursor.Style, style) orelse return error.InvalidValue;
8985
}
9086
if (cursor_iter.next() != null) return error.InvalidValue;
9187
continue;
@@ -94,15 +90,10 @@ pub const ShellIntegrationFeatures = struct {
9490
continue;
9591
}
9692

97-
const name, const value = part: {
98-
const negation_prefix = "no-";
99-
const trimmed_name = std.mem.trim(u8, part_raw, cli.args.whitespace);
100-
if (std.mem.startsWith(u8, trimmed, negation_prefix)) {
101-
break :part .{ trimmed_name[negation_prefix.len..], false };
102-
} else {
103-
break :part .{ trimmed_name, true };
104-
}
105-
};
93+
const name, const value = if (std.mem.cutPrefix(u8, trimmed, "no-")) |name|
94+
.{ name, false }
95+
else
96+
.{ trimmed, true };
10697

10798
inline for (@typeInfo(ShellIntegrationFeatures).@"struct".fields) |field| {
10899
if (field.type == bool and std.mem.eql(u8, field.name, name)) {
@@ -125,27 +116,15 @@ pub const ShellIntegrationFeatures = struct {
125116
env,
126117
};
127118

128-
pub fn format(self: ShellIntegrationFeatures, writer: *std.Io.Writer, mode: FormatMode) anyerror!void {
129-
const fields = comptime fields: {
130-
const all_fields = @typeInfo(ShellIntegrationFeatures).@"struct".fields;
131-
var sorted: [all_fields.len]std.builtin.Type.StructField = all_fields[0..].*;
132-
const SortContext = struct {
133-
fn lessThan(_: @This(), a: std.builtin.Type.StructField, b: std.builtin.Type.StructField) bool {
134-
return std.ascii.orderIgnoreCase(a.name, b.name) == .lt;
135-
}
136-
};
137-
std.mem.sortUnstable(std.builtin.Type.StructField, &sorted, SortContext{}, SortContext.lessThan);
138-
break :fields sorted;
139-
};
140-
141-
inline for (fields) |field| {
119+
pub fn format(self: ShellIntegrationFeatures, writer: *std.Io.Writer, mode: FormatMode) std.Io.Writer.Error!void {
120+
inline for (std.meta.fields(ShellIntegrationFeatures)) |field| {
142121
const enabled = switch (field.type) {
143122
bool => @field(self, field.name),
144123
Cursor => @field(self, field.name).shape != .disabled,
145124
else => @compileError("unexpected field type in ShellIntegrationFeatures"),
146125
};
147126
if (enabled or mode == .config) {
148-
if (writer.end > 0) try writer.writeByte(',');
127+
if (writer.buffered().len > 0) try writer.writeByte(',');
149128
if (mode == .config and !enabled) try writer.writeAll("no-");
150129
try writer.writeAll(field.name);
151130

@@ -196,7 +175,7 @@ pub const ShellIntegrationFeatures = struct {
196175
var buf: [128]u8 = undefined;
197176
var writer: std.Io.Writer = .fixed(&buf);
198177
try self.format(&writer, .config);
199-
try formatter.formatEntry([]const u8, buf[0..writer.end]);
178+
try formatter.formatEntry([]const u8, writer.buffered());
200179
}
201180

202181
pub fn clone(self: ShellIntegrationFeatures, _: Allocator) error{}!ShellIntegrationFeatures {
@@ -310,7 +289,7 @@ pub const ShellIntegrationFeatures = struct {
310289
var buf: [128]u8 = undefined;
311290
var writer: std.Io.Writer = .fixed(&buf);
312291
try features.format(&writer, mode);
313-
try testing.expectEqualStrings(expected, buf[0..writer.end]);
292+
try testing.expectEqualStrings(expected, writer.buffered());
314293
}
315294
}.f;
316295

src/termio/shell_integration.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ pub fn setupFeatures(
200200
}
201201
try resolved.format(&writer, .env);
202202

203-
if (writer.end > 0) {
204-
try env.put("GHOSTTY_SHELL_FEATURES", buf[0..writer.end]);
203+
const value = writer.buffered();
204+
if (value.len > 0) {
205+
try env.put("GHOSTTY_SHELL_FEATURES", value);
205206
}
206207
}
207208

0 commit comments

Comments
 (0)