feat(terminal/osc12): define cursor color through style of cell that describes the cursor position
Some checks failed
Zig Project Action / Lint, Spell-check and test zig project (push) Failing after 1m33s

The `.default` color will reset the cursor color to the terminal's default color
This commit is contained in:
2025-11-19 18:42:13 +01:00
parent 28c733352e
commit 424740d350
13 changed files with 55 additions and 30 deletions

View File

@@ -156,7 +156,7 @@ pub fn main() !void {
defer container.deinit();
try container.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
.size = .{
.grow = .horizontal,
.dim = .{ .y = 10 },
@@ -164,7 +164,7 @@ pub fn main() !void {
}, input_field.element()));
const nested_container: App.Container = try .init(allocator, .{
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
}, spinner.element());
try container.append(nested_container);

View File

@@ -58,13 +58,13 @@ pub fn main() !void {
},
}, .{});
try box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
}, .{}));
try box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
}, .{}));
try box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
}, .{}));
defer box.deinit();
@@ -93,7 +93,7 @@ pub fn main() !void {
.direction = .vertical,
},
.border = .{
.color = .light_blue,
.color = .lightblue,
.sides = .all,
},
}, .{});

View File

@@ -102,7 +102,7 @@ pub fn main() !void {
}, quit_text.element());
defer container.deinit();
try container.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .light_grey } }, element));
try container.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .lightgrey } }, element));
try container.append(try App.Container.init(allocator, .{ .rectangle = .{ .fill = .black } }, button.element()));
try app.start();

View File

@@ -72,7 +72,7 @@ pub fn main() !void {
var renderer = zterm.Renderer.Buffered.init(allocator);
defer renderer.deinit();
var input_field: App.Input(.accept) = .init(allocator, &app.queue, .init(.black));
var input_field: App.Input(.accept) = .init(allocator, &app.queue, .init(.black, .blue));
defer input_field.deinit();
var mouse_draw: MouseDraw = .{};
@@ -89,7 +89,7 @@ pub fn main() !void {
defer container.deinit();
try container.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
.size = .{
.grow = .horizontal,
.dim = .{ .y = 1 },
@@ -101,7 +101,7 @@ pub fn main() !void {
.sides = .all,
.color = .black,
},
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
.layout = .{
.separator = .{
.enabled = true,
@@ -110,10 +110,10 @@ pub fn main() !void {
},
}, .{});
try nested_container.append(try .init(allocator, .{
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
}, mouse_draw.element()));
try nested_container.append(try .init(allocator, .{
.rectangle = .{ .fill = .light_grey },
.rectangle = .{ .fill = .lightgrey },
}, second_mouse_draw.element()));
try container.append(nested_container);

View File

@@ -89,19 +89,19 @@ pub fn main() !void {
},
}, .{});
try top_box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
.size = .{
.dim = .{ .y = 30 },
},
}, .{}));
try top_box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
.size = .{
.dim = .{ .y = 5 },
},
}, element));
try top_box.append(try App.Container.init(allocator, .{
.rectangle = .{ .fill = .light_green },
.rectangle = .{ .fill = .lightgreen },
.size = .{
.dim = .{ .y = 2 },
},

View File

@@ -93,6 +93,7 @@ pub fn App(comptime M: type, comptime E: type) type {
if (this.termios) |termios| {
try terminal.disableMouseSupport();
try terminal.showCursor();
try terminal.resetCursorColor();
try terminal.restoreScreen();
try terminal.disableRawMode(&termios);
try terminal.exitAltScreen();
@@ -426,6 +427,7 @@ pub fn App(comptime M: type, comptime E: type) type {
pub fn panic_handler(msg: []const u8, _: ?*std.builtin.StackTrace, ret_addr: ?usize) noreturn {
terminal.disableMouseSupport() catch {};
terminal.showCursor() catch {};
terminal.resetCursorColor() catch {};
terminal.restoreScreen() catch {};
terminal.disableRawMode(&.{
.iflag = .{},

View File

@@ -25,7 +25,7 @@ test "ascii styled text" {
.{ .cp = 'Y', .style = .{ .fg = .green, .bg = .grey, .emphasis = &.{} } },
.{ .cp = 'v', .style = .{ .emphasis = &.{ .bold, .underline } } },
.{ .cp = 'e', .style = .{ .emphasis = &.{.italic} } },
.{ .cp = 's', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } },
.{ .cp = 's', .style = .{ .fg = .lightgreen, .bg = .black, .emphasis = &.{.underline} } },
};
var writer = std.Io.Writer.Allocating.init(std.testing.allocator);
@@ -45,7 +45,7 @@ test "utf-8 styled text" {
.{ .cp = '╭', .style = .{ .fg = .green, .bg = .grey, .emphasis = &.{} } },
.{ .cp = '─', .style = .{ .emphasis = &.{} } },
.{ .cp = '┄', .style = .{ .emphasis = &.{} } },
.{ .cp = '┘', .style = .{ .fg = .light_green, .bg = .black, .emphasis = &.{.underline} } },
.{ .cp = '┘', .style = .{ .fg = .lightgreen, .bg = .black, .emphasis = &.{.underline} } },
};
var writer = std.Io.Writer.Allocating.init(std.testing.allocator);

View File

@@ -1,13 +1,13 @@
pub const Color = enum(u8) {
default = 0,
black = 16,
light_red = 1,
light_green,
light_yellow,
light_blue,
light_magenta,
light_cyan,
light_grey,
lightred = 1,
lightgreen,
lightyellow,
lightblue,
lightmagenta,
lightcyan,
lightgrey,
grey,
red,
green,

View File

@@ -139,5 +139,6 @@ pub const osc10_reset = "\x1b]110\x1b\\"; // reset fg to terminal default
pub const osc11_query = "\x1b]11;?\x1b\\"; // bg
pub const osc11_set = "\x1b]11;rgb:{x:0>2}{x:0>2}/{x:0>2}{x:0>2}/{x:0>2}{x:0>2}\x1b\\"; // set default terminal bg
pub const osc11_reset = "\x1b]111\x1b\\"; // reset bg to terminal default
pub const osc12_set = "\x1b]12;{s}\x1b\\"; // set the cursor color through the name of the 8 base colors!
pub const osc12_query = "\x1b]12;?\x1b\\"; // cursor color
pub const osc12_reset = "\x1b]112\x1b\\"; // reset cursor to terminal default

View File

@@ -519,9 +519,13 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t
/// Configuration for InputField's.
pub const Configuration = packed struct {
color: Color,
cursor: Color,
pub fn init(color: Color) @This() {
return .{ .color = color };
pub fn init(color: Color, cursor: Color) @This() {
return .{
.color = color,
.cursor = cursor,
};
}
};
@@ -680,10 +684,13 @@ pub fn Input(Model: type, Event: type, Queue: type) fn (meta.FieldEnum(Event)) t
break;
}
}
if (this.input.items.len < cells.len)
cells[this.input.items.len - this.cursor_offset].style.cursor = true
else
if (this.input.items.len < cells.len) {
cells[this.input.items.len - this.cursor_offset].style.cursor = true;
cells[this.input.items.len - this.cursor_offset].style.cursor_color = this.configuration.cursor;
} else {
cells[this.input.items.len - offset - this.cursor_offset].style.cursor = true;
cells[this.input.items.len - offset - this.cursor_offset].style.cursor_color = this.configuration.cursor;
}
}
};
}
@@ -1615,7 +1622,7 @@ test "input element" {
};
var queue: Queue = .{};
var input_element: Input(Model, Event, Queue)(.accept) = .init(allocator, &queue, .init(.black));
var input_element: Input(Model, Event, Queue)(.accept) = .init(allocator, &queue, .init(.black, .default));
defer input_element.deinit();
const input_container: Container(Model, Event) = try .init(allocator, .{

View File

@@ -104,6 +104,7 @@ pub const Buffered = struct {
.x = @truncate(col),
.y = @truncate(row),
};
try cvs.style.set_cursor_color(&writer);
}
if (cs.eql(cvs)) continue;

View File

@@ -12,6 +12,7 @@ fg: Color = .default,
bg: Color = .default,
ul: Color = .default,
cursor: bool = false,
cursor_color: Color = .default,
ul_style: Underline = .off,
emphasis: []const Emphasis,
@@ -40,6 +41,14 @@ pub fn eql(this: Style, other: Style) bool {
return meta.eql(this, other);
}
pub fn set_cursor_color(this: Style, writer: *std.Io.Writer) !void {
if (!this.cursor) return;
switch (this.cursor_color) {
.default => try writer.print(ctlseqs.osc12_reset, .{}),
else => try writer.print(ctlseqs.osc12_set, .{@tagName(this.cursor_color)}),
}
}
pub fn value(this: Style, writer: *std.Io.Writer, cp: u21) !void {
var buffer: [4]u8 = undefined;
const bytes = try unicode.utf8Encode(cp, &buffer);
@@ -72,4 +81,5 @@ const unicode = std.unicode;
const meta = std.meta;
const assert = std.debug.assert;
const Color = @import("color.zig").Color;
const ctlseqs = @import("ctlseqs.zig");
const Style = @This();

View File

@@ -42,6 +42,10 @@ pub fn showCursor() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.show_cursor);
}
pub fn resetCursorColor() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.osc12_reset);
}
pub fn setCursorPositionHome() !void {
_ = try posix.write(posix.STDIN_FILENO, ctlseqs.home);
}