diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 9c7c5edea..2859320b2 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -190,6 +190,8 @@ extension Ghostty { // This is set to non-null during keyDown to accumulate insertText contents private var keyTextAccumulator: [String]? + /// Temporary lead surrogate that's waiting for the trail + private var leadSurrogate: LeadSurrogate? // True when we've consumed a left mouse-down only to move focus and // should suppress the matching mouse-up from being reported. @@ -2044,8 +2046,21 @@ extension Ghostty.SurfaceView: NSTextInputClient { switch string { case let v as NSAttributedString: chars = v.string - case let v as String: - chars = v + case let v as NSString: + if let leadSurrogate = LeadSurrogate(v) { + self.leadSurrogate = leadSurrogate + chars = "" + } else if let trail = TrailSurrogate(v) { + // We ignore trail surrogate without a lead like Terminal.app. + chars = leadSurrogate?.encode(trail: trail) ?? "" + leadSurrogate = nil + } else { + chars = v as String + // Clear whenever other text got inserted. + // Ideally we should encode any adjacent lead and trail surrogate into one, + // but getting the cursor position and reading could be rather expensive to do. + leadSurrogate = nil + } default: return } @@ -2430,3 +2445,41 @@ class CachedValue { expiryTask = nil } } + +/// Check if a UTF16 text is a single lead surrogate character +struct LeadSurrogate { + let char: UTF16Char + + init?(_ text: NSString) { + guard text.length == 1 else { + return nil + } + let char = text.character(at: 0) + if UTF16.isLeadSurrogate(char) { + self.char = char + } else { + return nil + } + } + + func encode(trail: TrailSurrogate) -> String { + String(decoding: [char, trail.char], as: UTF16.self) + } +} + +/// Check if a UTF16 text is a single trail surrogate character +struct TrailSurrogate { + let char: UTF16Char + + init?(_ text: NSString) { + guard text.length == 1 else { + return nil + } + let char = text.character(at: 0) + if UTF16.isTrailSurrogate(char) { + self.char = char + } else { + return nil + } + } +}