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

@@ -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);
}