ae38f777
Render actual cell colors
a73x 2026-04-08 11:53
Commit message
src/main.zig
| Old | New | ||
|---|---|---|---|
| @@ -198,13 +198,15 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 198 | const term_rows = term.render_state.row_data.items(.cells); | 198 | const term_rows = term.render_state.row_data.items(.cells); |
| 199 | var row_idx: u32 = 0; | 199 | var row_idx: u32 = 0; |
| 200 | while (row_idx < term_rows.len) : (row_idx += 1) { | 200 | while (row_idx < term_rows.len) : (row_idx += 1) { |
| 201 | const raw_cells = term_rows[row_idx].items(.raw); | 201 | const row_cells = term_rows[row_idx]; |
| 202 | const raw_cells = row_cells.items(.raw); | ||
| 202 | var col_idx: u32 = 0; | 203 | var col_idx: u32 = 0; |
| 203 | while (col_idx < raw_cells.len) : (col_idx += 1) { | 204 | while (col_idx < raw_cells.len) : (col_idx += 1) { |
| 204 | const cp = raw_cells[col_idx].codepoint(); | 205 | const cp = raw_cells[col_idx].codepoint(); |
| 205 | if (cp == 0 or cp == ' ') continue; | 206 | if (cp == 0 or cp == ' ') continue; |
| 206 | 207 | ||
| 207 | const uv = atlas.getOrInsert(&face, @intCast(cp)) catch continue; | 208 | const uv = atlas.getOrInsert(&face, @intCast(cp)) catch continue; |
| 209 | const colors = term.cellColors(row_cells.get(col_idx)); | ||
| 208 | 210 | ||
| 209 | try instances.append(alloc, .{ | 211 | try instances.append(alloc, .{ |
| 210 | .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) }, | 212 | .cell_pos = .{ @floatFromInt(col_idx), @floatFromInt(row_idx) }, |
| @@ -216,8 +218,8 @@ fn runTerminal(alloc: std.mem.Allocator) !void { | |||
| 216 | @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)), | 218 | @as(f32, @floatFromInt(cell_h)) - @as(f32, @floatFromInt(uv.bearing_y)), |
| 217 | }, | 219 | }, |
| 218 | .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 }, | 220 | .uv_rect = .{ uv.u0, uv.v0, uv.u1, uv.v1 }, |
| 219 | .fg = .{ 0.9, 0.9, 0.9, 1.0 }, | 221 | .fg = colors.fg, |
| 220 | .bg = .{ 0.08, 0.08, 0.08, 1.0 }, | 222 | .bg = colors.bg, |
| 221 | }); | 223 | }); |
| 222 | } | 224 | } |
| 223 | } | 225 | } |
src/vt.zig
| Old | New | ||
|---|---|---|---|
| @@ -53,6 +53,10 @@ const ghostty_vt = @import("ghostty-vt"); | |||
| 53 | pub const InputAction = ghostty_vt.input.KeyAction; | 53 | pub const InputAction = ghostty_vt.input.KeyAction; |
| 54 | pub const InputKey = ghostty_vt.input.Key; | 54 | pub const InputKey = ghostty_vt.input.Key; |
| 55 | pub const InputMods = ghostty_vt.input.KeyMods; | 55 | pub const InputMods = ghostty_vt.input.KeyMods; |
| 56 | pub const CellColors = struct { | ||
| 57 | fg: [4]f32, | ||
| 58 | bg: [4]f32, | ||
| 59 | }; | ||
| 56 | 60 | ||
| 57 | pub const Terminal = struct { | 61 | pub const Terminal = struct { |
| 58 | alloc: std.mem.Allocator, | 62 | alloc: std.mem.Allocator, |
| @@ -126,6 +130,26 @@ pub const Terminal = struct { | |||
| 126 | return writer.buffered(); | 130 | return writer.buffered(); |
| 127 | } | 131 | } |
| 128 | 132 | ||
| 133 | pub fn cellColors( | ||
| 134 | self: *const Terminal, | ||
| 135 | cell: ghostty_vt.RenderState.Cell, | ||
| 136 | ) CellColors { | ||
| 137 | const style: ghostty_vt.Style = switch (cell.raw.content_tag) { | ||
| 138 | .bg_color_palette, .bg_color_rgb => cell.style, | ||
| 139 | else => if (cell.raw.style_id != 0) cell.style else .{}, | ||
| 140 | }; | ||
| 141 | const colors = self.render_state.colors; | ||
| 142 | const fg = style.fg(.{ | ||
| 143 | .default = colors.foreground, | ||
| 144 | .palette = &colors.palette, | ||
| 145 | }); | ||
| 146 | const bg = style.bg(&cell.raw, &colors.palette) orelse colors.background; | ||
| 147 | return .{ | ||
| 148 | .fg = rgbToFloat4(fg), | ||
| 149 | .bg = rgbToFloat4(bg), | ||
| 150 | }; | ||
| 151 | } | ||
| 152 | |||
| 129 | pub fn resize(self: *Terminal, new_cols: u16, new_rows: u16) !void { | 153 | pub fn resize(self: *Terminal, new_cols: u16, new_rows: u16) !void { |
| 130 | try self.inner.resize(self.alloc, @intCast(new_cols), @intCast(new_rows)); | 154 | try self.inner.resize(self.alloc, @intCast(new_cols), @intCast(new_rows)); |
| 131 | } | 155 | } |
| @@ -145,6 +169,15 @@ pub const Terminal = struct { | |||
| 145 | } | 169 | } |
| 146 | }; | 170 | }; |
| 147 | 171 | ||
| 172 | fn rgbToFloat4(rgb: ghostty_vt.color.RGB) [4]f32 { | ||
| 173 | return .{ | ||
| 174 | @as(f32, @floatFromInt(rgb.r)) / 255.0, | ||
| 175 | @as(f32, @floatFromInt(rgb.g)) / 255.0, | ||
| 176 | @as(f32, @floatFromInt(rgb.b)) / 255.0, | ||
| 177 | 1.0, | ||
| 178 | }; | ||
| 179 | } | ||
| 180 | |||
| 148 | test "Terminal init/deinit" { | 181 | test "Terminal init/deinit" { |
| 149 | var term = try Terminal.init(std.testing.allocator, .{ | 182 | var term = try Terminal.init(std.testing.allocator, .{ |
| 150 | .cols = 80, | 183 | .cols = 80, |
| @@ -191,3 +224,21 @@ test "Terminal encodes non-text arrow keys" { | |||
| 191 | const encoded = try term.encodeKey(.arrow_left, .{}, .press, &buf); | 224 | const encoded = try term.encodeKey(.arrow_left, .{}, .press, &buf); |
| 192 | try std.testing.expectEqualStrings("\x1b[D", encoded); | 225 | try std.testing.expectEqualStrings("\x1b[D", encoded); |
| 193 | } | 226 | } |
| 227 | |||
| 228 | test "Terminal resolves ANSI fg and bg colors from render state" { | ||
| 229 | var term = try Terminal.init(std.testing.allocator, .{ | ||
| 230 | .cols = 80, | ||
| 231 | .rows = 24, | ||
| 232 | }); | ||
| 233 | defer term.deinit(); | ||
| 234 | |||
| 235 | term.write("\x1b[31;44mA\x1b[0m"); | ||
| 236 | try term.snapshot(); | ||
| 237 | |||
| 238 | const cell = term.render_state.row_data.get(0).cells.get(0); | ||
| 239 | const colors = term.cellColors(cell); | ||
| 240 | const palette = term.render_state.colors.palette; | ||
| 241 | |||
| 242 | try std.testing.expectEqualDeep(rgbToFloat4(palette[1]), colors.fg); | ||
| 243 | try std.testing.expectEqualDeep(rgbToFloat4(palette[4]), colors.bg); | ||
| 244 | } | ||