renderer/metal: port

This commit is contained in:
Mitchell Hashimoto
2024-03-08 21:33:10 -08:00
parent d966e74f45
commit c61de49082
5 changed files with 181 additions and 139 deletions

View File

@@ -623,7 +623,6 @@ pub fn updateFrame(
// Data we extract out of the critical area. // Data we extract out of the critical area.
const Critical = struct { const Critical = struct {
bg: terminal.color.RGB, bg: terminal.color.RGB,
selection: ?terminal.Selection,
screen: terminal.Screen, screen: terminal.Screen,
mouse: renderer.State.Mouse, mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit, preedit: ?renderer.State.Preedit,
@@ -657,25 +656,13 @@ pub fn updateFrame(
// We used to share terminal state, but we've since learned through // We used to share terminal state, but we've since learned through
// analysis that it is faster to copy the terminal state than to // analysis that it is faster to copy the terminal state than to
// hold the lock while rebuilding GPU cells. // hold the lock while rebuilding GPU cells.
const viewport_bottom = state.terminal.screen.viewportIsBottom(); var screen_copy = try state.terminal.screen.clone(
var screen_copy = if (viewport_bottom) try state.terminal.screen.clone(
self.alloc, self.alloc,
.{ .active = 0 }, .{ .viewport = .{} },
.{ .active = state.terminal.rows - 1 }, null,
) else try state.terminal.screen.clone(
self.alloc,
.{ .viewport = 0 },
.{ .viewport = state.terminal.rows - 1 },
); );
errdefer screen_copy.deinit(); errdefer screen_copy.deinit();
// Convert our selection to viewport points because we copy only
// the viewport above.
const selection: ?terminal.Selection = if (state.terminal.screen.selection) |sel|
sel.toViewport(&state.terminal.screen)
else
null;
// Whether to draw our cursor or not. // Whether to draw our cursor or not.
const cursor_style = renderer.cursorStyle( const cursor_style = renderer.cursorStyle(
state, state,
@@ -694,13 +681,15 @@ pub fn updateFrame(
// If we have Kitty graphics data, we enter a SLOW SLOW SLOW path. // If we have Kitty graphics data, we enter a SLOW SLOW SLOW path.
// We only do this if the Kitty image state is dirty meaning only if // We only do this if the Kitty image state is dirty meaning only if
// it changes. // it changes.
if (state.terminal.screen.kitty_images.dirty) { // TODO(paged-terminal)
try self.prepKittyGraphics(state.terminal); if (false) {
if (state.terminal.screen.kitty_images.dirty) {
try self.prepKittyGraphics(state.terminal);
}
} }
break :critical .{ break :critical .{
.bg = self.background_color, .bg = self.background_color,
.selection = selection,
.screen = screen_copy, .screen = screen_copy,
.mouse = state.mouse, .mouse = state.mouse,
.preedit = preedit, .preedit = preedit,
@@ -715,7 +704,6 @@ pub fn updateFrame(
// Build our GPU cells // Build our GPU cells
try self.rebuildCells( try self.rebuildCells(
critical.selection,
&critical.screen, &critical.screen,
critical.mouse, critical.mouse,
critical.preedit, critical.preedit,
@@ -1536,16 +1524,21 @@ pub fn setScreenSize(
/// down to the GPU yet. /// down to the GPU yet.
fn rebuildCells( fn rebuildCells(
self: *Metal, self: *Metal,
term_selection: ?terminal.Selection,
screen: *terminal.Screen, screen: *terminal.Screen,
mouse: renderer.State.Mouse, mouse: renderer.State.Mouse,
preedit: ?renderer.State.Preedit, preedit: ?renderer.State.Preedit,
cursor_style_: ?renderer.CursorStyle, cursor_style_: ?renderer.CursorStyle,
color_palette: *const terminal.color.Palette, color_palette: *const terminal.color.Palette,
) !void { ) !void {
const rows_usize: usize = @intCast(screen.pages.rows);
const cols_usize: usize = @intCast(screen.pages.cols);
// Bg cells at most will need space for the visible screen size // Bg cells at most will need space for the visible screen size
self.cells_bg.clearRetainingCapacity(); self.cells_bg.clearRetainingCapacity();
try self.cells_bg.ensureTotalCapacity(self.alloc, screen.rows * screen.cols); try self.cells_bg.ensureTotalCapacity(
self.alloc,
rows_usize * cols_usize,
);
// Over-allocate just to ensure we don't allocate again during loops. // Over-allocate just to ensure we don't allocate again during loops.
self.cells.clearRetainingCapacity(); self.cells.clearRetainingCapacity();
@@ -1554,21 +1547,24 @@ fn rebuildCells(
// * 3 for glyph + underline + strikethrough for each cell // * 3 for glyph + underline + strikethrough for each cell
// + 1 for cursor // + 1 for cursor
(screen.rows * screen.cols * 3) + 1, (rows_usize * cols_usize * 3) + 1,
); );
// Create an arena for all our temporary allocations while rebuilding // Create an arena for all our temporary allocations while rebuilding
var arena = ArenaAllocator.init(self.alloc); var arena = ArenaAllocator.init(self.alloc);
defer arena.deinit(); defer arena.deinit();
const arena_alloc = arena.allocator(); const arena_alloc = arena.allocator();
_ = arena_alloc;
_ = mouse;
// Create our match set for the links. // Create our match set for the links.
var link_match_set: link.MatchSet = if (mouse.point) |mouse_pt| try self.config.links.matchSet( // TODO(paged-terminal)
arena_alloc, // var link_match_set: link.MatchSet = if (mouse.point) |mouse_pt| try self.config.links.matchSet(
screen, // arena_alloc,
mouse_pt, // screen,
mouse.mods, // mouse_pt,
) else .{}; // mouse.mods,
// ) else .{};
// Determine our x/y range for preedit. We don't want to render anything // Determine our x/y range for preedit. We don't want to render anything
// here because we will render the preedit separately. // here because we will render the preedit separately.
@@ -1577,7 +1573,7 @@ fn rebuildCells(
x: [2]usize, x: [2]usize,
cp_offset: usize, cp_offset: usize,
} = if (preedit) |preedit_v| preedit: { } = if (preedit) |preedit_v| preedit: {
const range = preedit_v.range(screen.cursor.x, screen.cols - 1); const range = preedit_v.range(screen.cursor.x, screen.pages.cols - 1);
break :preedit .{ break :preedit .{
.y = screen.cursor.y, .y = screen.cursor.y,
.x = .{ range.start, range.end }, .x = .{ range.start, range.end },
@@ -1591,9 +1587,9 @@ fn rebuildCells(
var cursor_cell: ?mtl_shaders.Cell = null; var cursor_cell: ?mtl_shaders.Cell = null;
// Build each cell // Build each cell
var rowIter = screen.rowIterator(.viewport); var row_it = screen.pages.rowIterator(.right_down, .{ .viewport = .{} }, null);
var y: usize = 0; var y: usize = 0;
while (rowIter.next()) |row| { while (row_it.next()) |row| {
defer y += 1; defer y += 1;
// True if this is the row with our cursor. There are a lot of conditions // True if this is the row with our cursor. There are a lot of conditions
@@ -1628,8 +1624,8 @@ fn rebuildCells(
defer if (cursor_row) { defer if (cursor_row) {
// If we're on a wide spacer tail, then we want to look for // If we're on a wide spacer tail, then we want to look for
// the previous cell. // the previous cell.
const screen_cell = row.getCell(screen.cursor.x); const screen_cell = row.cells(.all)[screen.cursor.x];
const x = screen.cursor.x - @intFromBool(screen_cell.attrs.wide_spacer_tail); const x = screen.cursor.x - @intFromBool(screen_cell.wide == .spacer_tail);
for (self.cells.items[start_i..]) |cell| { for (self.cells.items[start_i..]) |cell| {
if (cell.grid_pos[0] == @as(f32, @floatFromInt(x)) and if (cell.grid_pos[0] == @as(f32, @floatFromInt(x)) and
(cell.mode == .fg or cell.mode == .fg_color)) (cell.mode == .fg or cell.mode == .fg_color))
@@ -1643,15 +1639,16 @@ fn rebuildCells(
// We need to get this row's selection if there is one for proper // We need to get this row's selection if there is one for proper
// run splitting. // run splitting.
const row_selection = sel: { const row_selection = sel: {
if (term_selection) |sel| { // TODO(paged-terminal)
const screen_point = (terminal.point.Viewport{ // if (screen.selection) |sel| {
.x = 0, // const screen_point = (terminal.point.Viewport{
.y = y, // .x = 0,
}).toScreen(screen); // .y = y,
if (sel.containedRow(screen, screen_point)) |row_sel| { // }).toScreen(screen);
break :sel row_sel; // if (sel.containedRow(screen, screen_point)) |row_sel| {
} // break :sel row_sel;
} // }
// }
break :sel null; break :sel null;
}; };
@@ -1659,6 +1656,7 @@ fn rebuildCells(
// Split our row into runs and shape each one. // Split our row into runs and shape each one.
var iter = self.font_shaper.runIterator( var iter = self.font_shaper.runIterator(
self.font_group, self.font_group,
screen,
row, row,
row_selection, row_selection,
if (shape_cursor) screen.cursor.x else null, if (shape_cursor) screen.cursor.x else null,
@@ -1679,24 +1677,23 @@ fn rebuildCells(
// It this cell is within our hint range then we need to // It this cell is within our hint range then we need to
// underline it. // underline it.
const cell: terminal.Screen.Cell = cell: { const cell: terminal.Pin = cell: {
var cell = row.getCell(shaper_cell.x); var copy = row;
copy.x = shaper_cell.x;
break :cell copy;
// If our links contain this cell then we want to // TODO(paged-terminal)
// underline it. // // If our links contain this cell then we want to
if (link_match_set.orderedContains(.{ // // underline it.
.x = shaper_cell.x, // if (link_match_set.orderedContains(.{
.y = y, // .x = shaper_cell.x,
})) { // .y = y,
cell.attrs.underline = .single; // })) {
} // cell.attrs.underline = .single;
// }
break :cell cell;
}; };
if (self.updateCell( if (self.updateCell(
term_selection,
screen,
cell, cell,
color_palette, color_palette,
shaper_cell, shaper_cell,
@@ -1714,9 +1711,6 @@ fn rebuildCells(
} }
} }
} }
// Set row is not dirty anymore
row.setDirty(false);
} }
// Add the cursor at the end so that it overlays everything. If we have // Add the cursor at the end so that it overlays everything. If we have
@@ -1744,7 +1738,8 @@ fn rebuildCells(
break :cursor_style; break :cursor_style;
} }
_ = self.addCursor(screen, cursor_style); _ = cursor_style;
//_ = self.addCursor(screen, cursor_style);
if (cursor_cell) |*cell| { if (cursor_cell) |*cell| {
if (cell.mode == .fg) { if (cell.mode == .fg) {
cell.color = if (self.config.cursor_text) |txt| cell.color = if (self.config.cursor_text) |txt|
@@ -1766,9 +1761,7 @@ fn rebuildCells(
fn updateCell( fn updateCell(
self: *Metal, self: *Metal,
selection: ?terminal.Selection, cell_pin: terminal.Pin,
screen: *terminal.Screen,
cell: terminal.Screen.Cell,
palette: *const terminal.color.Palette, palette: *const terminal.color.Palette,
shaper_cell: font.shape.Cell, shaper_cell: font.shape.Cell,
shaper_run: font.shape.TextRun, shaper_run: font.shape.TextRun,
@@ -1790,45 +1783,35 @@ fn updateCell(
// True if this cell is selected // True if this cell is selected
// TODO(perf): we can check in advance if selection is in // TODO(perf): we can check in advance if selection is in
// our viewport at all and not run this on every point. // our viewport at all and not run this on every point.
const selected: bool = if (selection) |sel| selected: { const selected = false;
const screen_point = (terminal.point.Viewport{ // TODO(paged-terminal)
.x = x, // const selected: bool = if (screen.selection) |sel| selected: {
.y = y, // const screen_point = (terminal.point.Viewport{
}).toScreen(screen); // .x = x,
// .y = y,
// }).toScreen(screen);
//
// break :selected sel.contains(screen_point);
// } else false;
break :selected sel.contains(screen_point); const rac = cell_pin.rowAndCell();
} else false; const cell = rac.cell;
const style = cell_pin.style(cell);
// The colors for the cell. // The colors for the cell.
const colors: BgFg = colors: { const colors: BgFg = colors: {
// The normal cell result // The normal cell result
const cell_res: BgFg = if (!cell.attrs.inverse) .{ const cell_res: BgFg = if (!style.flags.inverse) .{
// In normal mode, background and fg match the cell. We // In normal mode, background and fg match the cell. We
// un-optionalize the fg by defaulting to our fg color. // un-optionalize the fg by defaulting to our fg color.
.bg = switch (cell.bg) { .bg = style.bg(cell, palette),
.none => null, .fg = style.fg(palette) orelse self.foreground_color,
.indexed => |i| palette[i],
.rgb => |rgb| rgb,
},
.fg = switch (cell.fg) {
.none => self.foreground_color,
.indexed => |i| palette[i],
.rgb => |rgb| rgb,
},
} else .{ } else .{
// In inverted mode, the background MUST be set to something // In inverted mode, the background MUST be set to something
// (is never null) so it is either the fg or default fg. The // (is never null) so it is either the fg or default fg. The
// fg is either the bg or default background. // fg is either the bg or default background.
.bg = switch (cell.fg) { .bg = style.fg(palette) orelse self.foreground_color,
.none => self.foreground_color, .fg = style.bg(cell, palette) orelse self.background_color,
.indexed => |i| palette[i],
.rgb => |rgb| rgb,
},
.fg = switch (cell.bg) {
.none => self.background_color,
.indexed => |i| palette[i],
.rgb => |rgb| rgb,
},
}; };
// If we are selected, we our colors are just inverted fg/bg // If we are selected, we our colors are just inverted fg/bg
@@ -1846,7 +1829,7 @@ fn updateCell(
// If the cell is "invisible" then we just make fg = bg so that // If the cell is "invisible" then we just make fg = bg so that
// the cell is transparent but still copy-able. // the cell is transparent but still copy-able.
const res: BgFg = selection_res orelse cell_res; const res: BgFg = selection_res orelse cell_res;
if (cell.attrs.invisible) { if (style.flags.invisible) {
break :colors BgFg{ break :colors BgFg{
.bg = res.bg, .bg = res.bg,
.fg = res.bg orelse self.background_color, .fg = res.bg orelse self.background_color,
@@ -1857,7 +1840,7 @@ fn updateCell(
}; };
// Alpha multiplier // Alpha multiplier
const alpha: u8 = if (cell.attrs.faint) 175 else 255; const alpha: u8 = if (style.flags.faint) 175 else 255;
// If the cell has a background, we always draw it. // If the cell has a background, we always draw it.
const bg: [4]u8 = if (colors.bg) |rgb| bg: { const bg: [4]u8 = if (colors.bg) |rgb| bg: {
@@ -1874,11 +1857,11 @@ fn updateCell(
if (selected) break :bg_alpha default; if (selected) break :bg_alpha default;
// If we're reversed, do not apply background opacity // If we're reversed, do not apply background opacity
if (cell.attrs.inverse) break :bg_alpha default; if (style.flags.inverse) break :bg_alpha default;
// If we have a background and its not the default background // If we have a background and its not the default background
// then we apply background opacity // then we apply background opacity
if (cell.bg != .none and !rgb.eql(self.background_color)) { if (style.bg(cell, palette) != null and !rgb.eql(self.background_color)) {
break :bg_alpha default; break :bg_alpha default;
} }
@@ -1892,7 +1875,7 @@ fn updateCell(
self.cells_bg.appendAssumeCapacity(.{ self.cells_bg.appendAssumeCapacity(.{
.mode = .bg, .mode = .bg,
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) }, .grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
.cell_width = cell.widthLegacy(), .cell_width = cell.gridWidth(),
.color = .{ rgb.r, rgb.g, rgb.b, bg_alpha }, .color = .{ rgb.r, rgb.g, rgb.b, bg_alpha },
.bg_color = .{ 0, 0, 0, 0 }, .bg_color = .{ 0, 0, 0, 0 },
}); });
@@ -1906,7 +1889,7 @@ fn updateCell(
}; };
// If the cell has a character, draw it // If the cell has a character, draw it
if (cell.char > 0) fg: { if (cell.hasText()) fg: {
// Render // Render
const glyph = try self.font_group.renderGlyph( const glyph = try self.font_group.renderGlyph(
self.alloc, self.alloc,
@@ -1920,11 +1903,8 @@ fn updateCell(
const mode: mtl_shaders.Cell.Mode = switch (try fgMode( const mode: mtl_shaders.Cell.Mode = switch (try fgMode(
&self.font_group.group, &self.font_group.group,
screen, cell_pin,
cell,
shaper_run, shaper_run,
x,
y,
)) { )) {
.normal => .fg, .normal => .fg,
.color => .fg_color, .color => .fg_color,
@@ -1934,7 +1914,7 @@ fn updateCell(
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = mode, .mode = mode,
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) }, .grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
.cell_width = cell.widthLegacy(), .cell_width = cell.gridWidth(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.bg_color = bg, .bg_color = bg,
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
@@ -1946,8 +1926,8 @@ fn updateCell(
}); });
} }
if (cell.attrs.underline != .none) { if (style.flags.underline != .none) {
const sprite: font.Sprite = switch (cell.attrs.underline) { const sprite: font.Sprite = switch (style.flags.underline) {
.none => unreachable, .none => unreachable,
.single => .underline, .single => .underline,
.double => .underline_double, .double => .underline_double,
@@ -1961,17 +1941,17 @@ fn updateCell(
font.sprite_index, font.sprite_index,
@intFromEnum(sprite), @intFromEnum(sprite),
.{ .{
.cell_width = if (cell.attrs.wide) 2 else 1, .cell_width = if (cell.wide == .wide) 2 else 1,
.grid_metrics = self.grid_metrics, .grid_metrics = self.grid_metrics,
}, },
); );
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg; const color = style.underlineColor(palette) orelse colors.fg;
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = .fg, .mode = .fg,
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) }, .grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
.cell_width = cell.widthLegacy(), .cell_width = cell.gridWidth(),
.color = .{ color.r, color.g, color.b, alpha }, .color = .{ color.r, color.g, color.b, alpha },
.bg_color = bg, .bg_color = bg,
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
@@ -1980,11 +1960,11 @@ fn updateCell(
}); });
} }
if (cell.attrs.strikethrough) { if (style.flags.strikethrough) {
self.cells.appendAssumeCapacity(.{ self.cells.appendAssumeCapacity(.{
.mode = .strikethrough, .mode = .strikethrough,
.grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) }, .grid_pos = .{ @as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(y)) },
.cell_width = cell.widthLegacy(), .cell_width = cell.gridWidth(),
.color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha }, .color = .{ colors.fg.r, colors.fg.g, colors.fg.b, alpha },
.bg_color = bg, .bg_color = bg,
}); });
@@ -2002,21 +1982,14 @@ fn addCursor(
// we're on the wide characer tail. // we're on the wide characer tail.
const wide, const x = cell: { const wide, const x = cell: {
// The cursor goes over the screen cursor position. // The cursor goes over the screen cursor position.
const cell = screen.getCell( const cell = screen.cursor.page_cell;
.active, if (cell.wide != .spacer_tail or screen.cursor.x == 0)
screen.cursor.y, break :cell .{ cell.wide == .wide, screen.cursor.x };
screen.cursor.x,
);
if (!cell.attrs.wide_spacer_tail or screen.cursor.x == 0)
break :cell .{ cell.attrs.wide, screen.cursor.x };
// If we're part of a wide character, we move the cursor back to // If we're part of a wide character, we move the cursor back to
// the actual character. // the actual character.
break :cell .{ screen.getCell( const prev_cell = screen.cursorCellLeft(1);
.active, break :cell .{ prev_cell.wide == .wide, screen.cursor.x - 1 };
screen.cursor.y,
screen.cursor.x - 1,
).attrs.wide, screen.cursor.x - 1 };
}; };
const color = self.cursor_color orelse self.foreground_color; const color = self.cursor_color orelse self.foreground_color;

View File

@@ -21,11 +21,8 @@ pub const FgMode = enum {
/// renderer. /// renderer.
pub fn fgMode( pub fn fgMode(
group: *font.Group, group: *font.Group,
screen: *terminal.Screen, cell_pin: terminal.Pin,
cell: terminal.Screen.Cell,
shaper_run: font.shape.TextRun, shaper_run: font.shape.TextRun,
x: usize,
y: usize,
) !FgMode { ) !FgMode {
const presentation = try group.presentationFromIndex(shaper_run.font_index); const presentation = try group.presentationFromIndex(shaper_run.font_index);
return switch (presentation) { return switch (presentation) {
@@ -41,42 +38,55 @@ pub fn fgMode(
// the subsequent character is empty, then we allow it to use // the subsequent character is empty, then we allow it to use
// the full glyph size. See #1071. // the full glyph size. See #1071.
.text => text: { .text => text: {
if (!ziglyph.general_category.isPrivateUse(@intCast(cell.char)) and const cell = cell_pin.rowAndCell().cell;
!ziglyph.blocks.isDingbats(@intCast(cell.char))) const cp = cell.codepoint();
if (!ziglyph.general_category.isPrivateUse(cp) and
!ziglyph.blocks.isDingbats(cp))
{ {
break :text .normal; break :text .normal;
} }
// We exempt the Powerline range from this since they exhibit // We exempt the Powerline range from this since they exhibit
// box-drawing behavior and should not be constrained. // box-drawing behavior and should not be constrained.
if (isPowerline(cell.char)) { if (isPowerline(cp)) {
break :text .normal; break :text .normal;
} }
// If we are at the end of the screen its definitely constrained // If we are at the end of the screen its definitely constrained
if (x == screen.cols - 1) break :text .constrained; if (cell_pin.x == cell_pin.page.data.size.cols - 1) break :text .constrained;
// If we have a previous cell and it was PUA then we need to // If we have a previous cell and it was PUA then we need to
// also constrain. This is so that multiple PUA glyphs align. // also constrain. This is so that multiple PUA glyphs align.
// As an exception, we ignore powerline glyphs since they are // As an exception, we ignore powerline glyphs since they are
// used for box drawing and we consider them whitespace. // used for box drawing and we consider them whitespace.
if (x > 0) prev: { if (cell_pin.x > 0) prev: {
const prev_cell = screen.getCell(.active, y, x - 1); const prev_cp = prev_cp: {
var copy = cell_pin;
copy.x -= 1;
const prev_cell = copy.rowAndCell().cell;
break :prev_cp prev_cell.codepoint();
};
// Powerline is whitespace // Powerline is whitespace
if (isPowerline(prev_cell.char)) break :prev; if (isPowerline(prev_cp)) break :prev;
if (ziglyph.general_category.isPrivateUse(@intCast(prev_cell.char))) { if (ziglyph.general_category.isPrivateUse(prev_cp)) {
break :text .constrained; break :text .constrained;
} }
} }
// If the next cell is empty, then we allow it to use the // If the next cell is empty, then we allow it to use the
// full glyph size. // full glyph size.
const next_cell = screen.getCell(.active, y, x + 1); const next_cp = next_cp: {
if (next_cell.char == 0 or var copy = cell_pin;
next_cell.char == ' ' or copy.x += 1;
isPowerline(next_cell.char)) const next_cell = copy.rowAndCell().cell;
break :next_cp next_cell.codepoint();
};
if (next_cp == 0 or
next_cp == ' ' or
isPowerline(next_cp))
{ {
break :text .normal; break :text .normal;
} }
@@ -88,7 +98,7 @@ pub fn fgMode(
} }
// Returns true if the codepoint is a part of the Powerline range. // Returns true if the codepoint is a part of the Powerline range.
fn isPowerline(char: u32) bool { fn isPowerline(char: u21) bool {
return switch (char) { return switch (char) {
0xE0B0...0xE0C8, 0xE0CA, 0xE0CC...0xE0D2, 0xE0D4 => true, 0xE0B0...0xE0C8, 0xE0CA, 0xE0CC...0xE0D2, 0xE0D4 => true,
else => false, else => false,

View File

@@ -227,6 +227,7 @@ pub fn clonePool(
.pages = pages, .pages = pages,
.no_scrollback = self.no_scrollback, .no_scrollback = self.no_scrollback,
// TODO: selection
// TODO: let's make this reasonble // TODO: let's make this reasonble
.cursor = undefined, .cursor = undefined,
}; };

View File

@@ -779,6 +779,14 @@ pub const Cell = packed struct(u64) {
}; };
} }
/// The width in grid cells that this cell takes up.
pub fn gridWidth(self: Cell) u2 {
return switch (self.wide) {
.narrow, .spacer_head, .spacer_tail => 1,
.wide => 2,
};
}
pub fn hasStyling(self: Cell) bool { pub fn hasStyling(self: Cell) bool {
return self.style_id != style.default_id; return self.style_id != style.default_id;
} }

View File

@@ -51,6 +51,56 @@ pub const Style = struct {
return std.mem.eql(u8, std.mem.asBytes(&self), def); return std.mem.eql(u8, std.mem.asBytes(&self), def);
} }
/// Returns the bg color for a cell with this style given the cell
/// that has this style and the palette to use.
///
/// Note that generally if a cell is a color-only cell, it SHOULD
/// only have the default style, but this is meant to work with the
/// default style as well.
pub fn bg(
self: Style,
cell: *const page.Cell,
palette: *const color.Palette,
) ?color.RGB {
return switch (cell.content_tag) {
.bg_color_palette => palette[cell.content.color_palette],
.bg_color_rgb => rgb: {
const rgb = cell.content.color_rgb;
break :rgb .{ .r = rgb.r, .g = rgb.g, .b = rgb.b };
},
else => switch (self.bg_color) {
.none => null,
.palette => |idx| palette[idx],
.rgb => |rgb| rgb,
},
};
}
/// Returns the fg color for a cell with this style given the palette.
pub fn fg(
self: Style,
palette: *const color.Palette,
) ?color.RGB {
return switch (self.fg_color) {
.none => null,
.palette => |idx| palette[idx],
.rgb => |rgb| rgb,
};
}
/// Returns the underline color for this style.
pub fn underlineColor(
self: Style,
palette: *const color.Palette,
) ?color.RGB {
return switch (self.underline_color) {
.none => null,
.palette => |idx| palette[idx],
.rgb => |rgb| rgb,
};
}
/// Returns a bg-color only cell from this style, if it exists. /// Returns a bg-color only cell from this style, if it exists.
pub fn bgCell(self: Style) ?page.Cell { pub fn bgCell(self: Style) ?page.Cell {
return switch (self.bg_color) { return switch (self.bg_color) {