diff --git a/include/ghostty.h b/include/ghostty.h index c871dd593..7888b380c 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -964,7 +964,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t, double, ghostty_input_scroll_mods_t); void ghostty_surface_mouse_pressure(ghostty_surface_t, uint32_t, double); -void ghostty_surface_ime_point(ghostty_surface_t, double*, double*); +void ghostty_surface_ime_point(ghostty_surface_t, double*, double*, double*, double*); void ghostty_surface_request_close(ghostty_surface_t); void ghostty_surface_split(ghostty_surface_t, ghostty_action_split_direction_e); void ghostty_surface_split_focus(ghostty_surface_t, diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index d88201eaf..eef4bccb3 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -1683,8 +1683,10 @@ extension Ghostty.SurfaceView: NSTextInputClient { } // Ghostty will tell us where it thinks an IME keyboard should render. - var x: Double = 0; - var y: Double = 0; + var x: Double = 0 + var y: Double = 0 + var width: Double = cellSize.width + var height: Double = cellSize.height // QuickLook never gives us a matching range to our selection so if we detect // this then we return the top-left selection point rather than the cursor point. @@ -1702,15 +1704,19 @@ extension Ghostty.SurfaceView: NSTextInputClient { // Free our text ghostty_surface_free_text(surface, &text) } else { - ghostty_surface_ime_point(surface, &x, &y) + ghostty_surface_ime_point(surface, &x, &y, &width, &height) } } else { - ghostty_surface_ime_point(surface, &x, &y) + ghostty_surface_ime_point(surface, &x, &y, &width, &height) } // Ghostty coordinates are in top-left (0, 0) so we have to convert to // bottom-left since that is what UIKit expects - let viewRect = NSMakeRect(x, frame.size.height - y, cellSize.width, cellSize.height) + let viewRect = NSMakeRect( + x, + frame.size.height - y, + max(width, cellSize.width), + max(height, cellSize.height)) // Convert the point to the window coordinates let winRect = self.convert(viewRect, to: nil) diff --git a/src/Surface.zig b/src/Surface.zig index 330d25102..f7a961b62 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1730,6 +1730,7 @@ pub fn pwd( pub fn imePoint(self: *const Surface) apprt.IMEPos { self.renderer_state.mutex.lock(); const cursor = self.renderer_state.terminal.screen.cursor; + const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0; self.renderer_state.mutex.unlock(); // TODO: need to handle when scrolling and the cursor is not @@ -1764,7 +1765,38 @@ pub fn imePoint(self: *const Surface) apprt.IMEPos { break :y y; }; - return .{ .x = x, .y = y }; + // Our height for now is always just the cell height because our preedit + // rendering only renders in a single line. + const height: f64 = height: { + var height: f64 = @floatFromInt(self.size.cell.height); + height /= content_scale.y; + break :height height; + }; + const width: f64 = width: { + var width: f64 = @floatFromInt(preedit_width * self.size.cell.width); + + // Our max width is the remaining screen width after the cursor. + // We don't have to deal with wrapping because the preedit doesn't + // wrap right now. + const screen_width: f64 = @floatFromInt(self.size.terminal().width); + const x_offset: f64 = @floatFromInt((cursor.x + 1) * self.size.cell.width); + const max = screen_width - x_offset; + width = @min(width, max); + + // Note: we don't apply content scale here because it looks like + // for some reason in macOS its already scaled. I'm not sure why + // that is so I'm going to just leave this comment here so its known + // that I left this out on purpose pending more investigation. + + break :width width; + }; + + return .{ + .x = x, + .y = y, + .width = width, + .height = height, + }; } fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) !void { diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index e4961ac49..5f43e1659 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1822,10 +1822,18 @@ pub const CAPI = struct { surface.mousePressureCallback(stage, pressure); } - export fn ghostty_surface_ime_point(surface: *Surface, x: *f64, y: *f64) void { + export fn ghostty_surface_ime_point( + surface: *Surface, + x: *f64, + y: *f64, + width: *f64, + height: *f64, + ) void { const pos = surface.core_surface.imePoint(); x.* = pos.x; y.* = pos.y; + width.* = pos.width; + height.* = pos.height; } /// Request that the surface become closed. This will go through the diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index c9948f3ee..b9e93e9ba 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -24,6 +24,8 @@ pub const CursorPos = struct { pub const IMEPos = struct { x: f64, y: f64, + width: f64, + height: f64, }; /// The clipboard type.