From ce4541dd6145fbd070a0c811e8553ed750322dbd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 10 Nov 2023 17:26:36 -0800 Subject: [PATCH] core, renderer: handle wide preedit chars Fixes #855 --- src/Surface.zig | 21 +++++++++++++++++---- src/renderer/Metal.zig | 20 ++++++++++++++------ src/renderer/State.zig | 3 +++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 0187481d4..86d6310e3 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -20,6 +20,7 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; +const ziglyph = @import("ziglyph"); const renderer = @import("renderer.zig"); const termio = @import("termio.zig"); 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 /// keyCallback and we rely completely on the apprt implementation to track /// 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(); defer self.renderer_state.mutex.unlock(); - self.renderer_state.preedit = if (preedit) |v| .{ - .codepoint = v, - } else null; + self.renderer_state.preedit = preedit; try self.queueRender(); } diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 6d75400ba..43ce4cd61 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -1203,7 +1203,7 @@ fn rebuildCells( // a cursor cell then we invert the colors on that and add it in so // that we can always see it. 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 // of the cursor. @@ -1427,10 +1427,18 @@ fn addCursor( self: *Metal, screen: *terminal.Screen, cursor_style: renderer.CursorStyle, + preedit: ?renderer.State.Preedit, ) ?*const mtl_shaders.Cell { // Add the cursor. We render the cursor over the wide character if // 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. const cell = screen.getCell( .active, @@ -1438,7 +1446,7 @@ fn addCursor( screen.cursor.x, ); 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 // the actual character. @@ -1446,7 +1454,7 @@ fn addCursor( .active, screen.cursor.y, screen.cursor.x - 1, - ), screen.cursor.x - 1 }; + ).attrs.wide, screen.cursor.x - 1 }; }; const color = self.cursor_color orelse self.foreground_color; @@ -1466,7 +1474,7 @@ fn addCursor( self.alloc, font.sprite_index, @intFromEnum(sprite), - .{ .cell_width = if (cell.attrs.wide) 2 else 1 }, + .{ .cell_width = if (wide) 2 else 1 }, ) catch |err| { log.warn("error rendering cursor glyph err={}", .{err}); return null; @@ -1478,7 +1486,7 @@ fn addCursor( @as(f32, @floatFromInt(x)), @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 }, .glyph_pos = .{ glyph.atlas_x, glyph.atlas_y }, .glyph_size = .{ glyph.width, glyph.height }, diff --git a/src/renderer/State.zig b/src/renderer/State.zig index aebc43fce..a675c4fac 100644 --- a/src/renderer/State.zig +++ b/src/renderer/State.zig @@ -34,4 +34,7 @@ pub const Preedit = struct { /// 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. codepoint: u21 = 0, + + /// True if the preedit text should be rendered "wide" (two cells) + wide: bool = false, };