core, renderer: handle wide preedit chars

Fixes #855
This commit is contained in:
Mitchell Hashimoto
2023-11-10 17:26:36 -08:00
parent a8614815d6
commit ce4541dd61
3 changed files with 34 additions and 10 deletions

View File

@ -20,6 +20,7 @@ const builtin = @import("builtin");
const assert = std.debug.assert; const assert = std.debug.assert;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
const ziglyph = @import("ziglyph");
const renderer = @import("renderer.zig"); const renderer = @import("renderer.zig");
const termio = @import("termio.zig"); const termio = @import("termio.zig");
const objc = @import("objc"); const objc = @import("objc");
@ -1020,12 +1021,24 @@ fn resize(self: *Surface, size: renderer.ScreenSize) !void {
/// The core surface will NOT reset the preedit state on charCallback or /// The core surface will NOT reset the preedit state on charCallback or
/// keyCallback and we rely completely on the apprt implementation to track /// keyCallback and we rely completely on the apprt implementation to track
/// the preedit state correctly. /// the preedit state correctly.
pub fn preeditCallback(self: *Surface, preedit: ?u21) !void { pub fn preeditCallback(self: *Surface, preedit_: ?u21) !void {
const preedit: ?renderer.State.Preedit = if (preedit_) |cp| preedit: {
const width = ziglyph.display_width.codePointWidth(cp, .half);
// This shouldn't ever happen in well-behaved programs because
// preedit text must be visible, but we want to protect against it
// at this point.
if (width <= 0) break :preedit null;
break :preedit .{
.codepoint = cp,
.wide = width >= 2,
};
} else null;
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
defer self.renderer_state.mutex.unlock(); defer self.renderer_state.mutex.unlock();
self.renderer_state.preedit = if (preedit) |v| .{ self.renderer_state.preedit = preedit;
.codepoint = v,
} else null;
try self.queueRender(); try self.queueRender();
} }

View File

@ -1203,7 +1203,7 @@ fn rebuildCells(
// a cursor cell then we invert the colors on that and add it in so // a cursor cell then we invert the colors on that and add it in so
// that we can always see it. // that we can always see it.
if (cursor_style_) |cursor_style| { if (cursor_style_) |cursor_style| {
const real_cursor_cell = self.addCursor(screen, cursor_style); const real_cursor_cell = self.addCursor(screen, cursor_style, preedit);
// If we have a preedit, we try to render the preedit text on top // If we have a preedit, we try to render the preedit text on top
// of the cursor. // of the cursor.
@ -1427,10 +1427,18 @@ fn addCursor(
self: *Metal, self: *Metal,
screen: *terminal.Screen, screen: *terminal.Screen,
cursor_style: renderer.CursorStyle, cursor_style: renderer.CursorStyle,
preedit: ?renderer.State.Preedit,
) ?*const mtl_shaders.Cell { ) ?*const mtl_shaders.Cell {
// Add the cursor. We render the cursor over the wide character if // Add the cursor. We render the cursor over the wide character if
// we're on the wide characer tail. // we're on the wide characer tail.
const cell, const x = cell: { const wide, const x = cell: {
// If we have preedit text, our width is based on that.
if (preedit) |p| {
if (p.codepoint > 0) {
break :cell .{ p.wide, screen.cursor.x };
}
}
// The cursor goes over the screen cursor position. // The cursor goes over the screen cursor position.
const cell = screen.getCell( const cell = screen.getCell(
.active, .active,
@ -1438,7 +1446,7 @@ fn addCursor(
screen.cursor.x, screen.cursor.x,
); );
if (!cell.attrs.wide_spacer_tail or screen.cursor.x == 0) if (!cell.attrs.wide_spacer_tail or screen.cursor.x == 0)
break :cell .{ cell, screen.cursor.x }; 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.
@ -1446,7 +1454,7 @@ fn addCursor(
.active, .active,
screen.cursor.y, screen.cursor.y,
screen.cursor.x - 1, screen.cursor.x - 1,
), 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;
@ -1466,7 +1474,7 @@ fn addCursor(
self.alloc, self.alloc,
font.sprite_index, font.sprite_index,
@intFromEnum(sprite), @intFromEnum(sprite),
.{ .cell_width = if (cell.attrs.wide) 2 else 1 }, .{ .cell_width = if (wide) 2 else 1 },
) catch |err| { ) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err}); log.warn("error rendering cursor glyph err={}", .{err});
return null; return null;
@ -1478,7 +1486,7 @@ fn addCursor(
@as(f32, @floatFromInt(x)), @as(f32, @floatFromInt(x)),
@as(f32, @floatFromInt(screen.cursor.y)), @as(f32, @floatFromInt(screen.cursor.y)),
}, },
.cell_width = if (cell.attrs.wide) 2 else 1, .cell_width = if (wide) 2 else 1,
.color = .{ color.r, color.g, color.b, alpha }, .color = .{ color.r, color.g, color.b, alpha },
.glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y },
.glyph_size = .{ glyph.width, glyph.height }, .glyph_size = .{ glyph.width, glyph.height },

View File

@ -34,4 +34,7 @@ pub const Preedit = struct {
/// This can also be "0" in which case we can know we're in a preedit /// This can also be "0" in which case we can know we're in a preedit
/// mode but we don't have any preedit text to render. /// mode but we don't have any preedit text to render.
codepoint: u21 = 0, codepoint: u21 = 0,
/// True if the preedit text should be rendered "wide" (two cells)
wide: bool = false,
}; };