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..e3cf2ae9e 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. @@ -1215,6 +1215,7 @@ fn rebuildCells( var cell: mtl_shaders.Cell = cursor_cell orelse (real_cursor_cell orelse break :preedit).*; cell.color = .{ 0, 0, 0, 255 }; + cell.cell_width = if (preedit_v.wide) 2 else 1; // If preedit rendering succeeded then we don't want to // re-render the underlying cell fg @@ -1427,10 +1428,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 +1447,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 +1455,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 +1475,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 +1487,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/OpenGL.zig b/src/renderer/OpenGL.zig index bc8982b37..c699572c9 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -817,7 +817,7 @@ pub 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. @@ -832,6 +832,7 @@ pub fn rebuildCells( cell.fg_g = 0; cell.fg_b = 0; cell.fg_a = 255; + cell.grid_width = if (preedit_v.wide) 2 else 1; // If preedit rendering succeeded then we don't want to // re-render the underlying cell fg @@ -871,10 +872,18 @@ fn addCursor( self: *OpenGL, screen: *terminal.Screen, cursor_style: renderer.CursorStyle, + preedit: ?renderer.State.Preedit, ) ?*const GPUCell { // 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, @@ -882,7 +891,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. @@ -890,7 +899,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; @@ -910,7 +919,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; @@ -920,7 +929,7 @@ fn addCursor( .mode = .fg, .grid_col = @intCast(x), .grid_row = @intCast(screen.cursor.y), - .grid_width = if (cell.attrs.wide) 2 else 1, + .grid_width = if (wide) 2 else 1, .fg_r = color.r, .fg_g = color.g, .fg_b = color.b, 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, };