Fix IME firstRect preedit anchor sizing (https://github.com/manaflow-ai/cmux/issues/265)
This commit is contained in:
parent
d7ceebaff1
commit
ba818deb44
2 changed files with 92 additions and 3 deletions
|
|
@ -2115,6 +2115,17 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations {
|
|||
var keyTextAccumulatorForTesting: [String]? {
|
||||
keyTextAccumulator
|
||||
}
|
||||
|
||||
// Test-only IME point override so firstRect behavior can be regression tested.
|
||||
private var imePointOverrideForTesting: (x: Double, y: Double, width: Double, height: Double)?
|
||||
|
||||
func setIMEPointForTesting(x: Double, y: Double, width: Double, height: Double) {
|
||||
imePointOverrideForTesting = (x, y, width, height)
|
||||
}
|
||||
|
||||
func clearIMEPointForTesting() {
|
||||
imePointOverrideForTesting = nil
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
|
|
@ -4204,17 +4215,28 @@ extension GhosttyNSView: NSTextInputClient {
|
|||
// Use Ghostty's IME point API for accurate cursor position if available.
|
||||
var x: Double = 0
|
||||
var y: Double = 0
|
||||
var w: Double = 0
|
||||
var h: Double = 0
|
||||
var w: Double = cellSize.width
|
||||
var h: Double = cellSize.height
|
||||
#if DEBUG
|
||||
if let override = imePointOverrideForTesting {
|
||||
x = override.x
|
||||
y = override.y
|
||||
w = override.width
|
||||
h = override.height
|
||||
} else if let surface = surface {
|
||||
ghostty_surface_ime_point(surface, &x, &y, &w, &h)
|
||||
}
|
||||
#else
|
||||
if let surface = surface {
|
||||
ghostty_surface_ime_point(surface, &x, &y, &w, &h)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ghostty coordinates are top-left origin; AppKit expects bottom-left.
|
||||
let viewRect = NSRect(
|
||||
x: x,
|
||||
y: frame.size.height - y,
|
||||
width: 0,
|
||||
width: w,
|
||||
height: max(h, cellSize.height)
|
||||
)
|
||||
let winRect = convert(viewRect, to: nil)
|
||||
|
|
|
|||
|
|
@ -642,6 +642,73 @@ final class CJKIMECompositionSequenceTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - IME firstRect placement and sizing
|
||||
|
||||
/// Regression tests for IME candidate/preedit anchor rectangle reporting.
|
||||
/// If width/height are discarded here, macOS can place preedit UI incorrectly.
|
||||
final class CJKIMEFirstRectTests: XCTestCase {
|
||||
|
||||
func testFirstRectUsesIMEProvidedWidthAndHeight() {
|
||||
let frame = NSRect(x: 0, y: 0, width: 800, height: 600)
|
||||
let view = GhosttyNSView(frame: frame)
|
||||
view.cellSize = CGSize(width: 10, height: 20)
|
||||
view.setIMEPointForTesting(x: 120, y: 240, width: 64, height: 26)
|
||||
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 100, y: 100, width: 800, height: 600),
|
||||
styleMask: [.titled],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
let content = NSView(frame: frame)
|
||||
window.contentView = content
|
||||
content.addSubview(view)
|
||||
view.frame = frame
|
||||
|
||||
defer {
|
||||
view.clearIMEPointForTesting()
|
||||
window.orderOut(nil)
|
||||
}
|
||||
|
||||
let rect = view.firstRect(forCharacterRange: NSRange(location: 0, length: 1), actualRange: nil)
|
||||
|
||||
let expectedViewRect = NSRect(x: 120, y: frame.height - 240, width: 64, height: 26)
|
||||
let expectedScreenRect = window.convertToScreen(view.convert(expectedViewRect, to: nil))
|
||||
|
||||
XCTAssertEqual(rect.origin.x, expectedScreenRect.origin.x, accuracy: 0.001)
|
||||
XCTAssertEqual(rect.origin.y, expectedScreenRect.origin.y, accuracy: 0.001)
|
||||
XCTAssertEqual(rect.width, 64, accuracy: 0.001)
|
||||
XCTAssertEqual(rect.height, 26, accuracy: 0.001)
|
||||
}
|
||||
|
||||
func testFirstRectFallsBackToCellHeightWhenIMEHeightIsZero() {
|
||||
let frame = NSRect(x: 0, y: 0, width: 640, height: 480)
|
||||
let view = GhosttyNSView(frame: frame)
|
||||
view.cellSize = CGSize(width: 9, height: 18)
|
||||
view.setIMEPointForTesting(x: 80, y: 120, width: 36, height: 0)
|
||||
|
||||
let window = NSWindow(
|
||||
contentRect: NSRect(x: 40, y: 40, width: 640, height: 480),
|
||||
styleMask: [.titled],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
let content = NSView(frame: frame)
|
||||
window.contentView = content
|
||||
content.addSubview(view)
|
||||
view.frame = frame
|
||||
|
||||
defer {
|
||||
view.clearIMEPointForTesting()
|
||||
window.orderOut(nil)
|
||||
}
|
||||
|
||||
let rect = view.firstRect(forCharacterRange: NSRange(location: 0, length: 1), actualRange: nil)
|
||||
XCTAssertEqual(rect.width, 36, accuracy: 0.001)
|
||||
XCTAssertEqual(rect.height, 18, accuracy: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Key text accumulator during CJK IME composition
|
||||
|
||||
/// Tests that the keyTextAccumulator correctly manages text during the keyDown
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue