From 679b6178586d63e0cee7616f6b7c00d8af63c209 Mon Sep 17 00:00:00 2001 From: luca-chen198 Date: Thu, 14 May 2026 12:03:48 +0200 Subject: [PATCH 1/2] Restyle marked text outside heading lines so dead keys in code blocks keep their background AppKit doesn't fire textDidChange for setMarkedText mutations, so the inserted character keeps base typingAttributes. The previous override only restyled when the line started with `#`, which fixed heading inline-prediction but left code-block (and inline-code/LaTeX) lines without their background while dead keys like `, ^ were composing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../TextView/NativeTextView/NativeTextView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView.swift b/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView.swift index 4ab2978..59ad8da 100644 --- a/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView.swift +++ b/Sources/MarkdownEngine/TextView/NativeTextView/NativeTextView.swift @@ -58,7 +58,7 @@ final class NativeTextView: NSTextView { } } - // AppKit doesn't fire textDidChange for setMarkedText mutations, so Apple's inline-prediction inserts the completion with base typingAttributes and heading lines flicker to body font; restyle the paragraph here to reapply heading font. + // AppKit skips textDidChange for setMarkedText, so markdown attrs (heading font, code-block bg) don't reach the marked range — restyle the affected paragraph to fix it. override func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) { super.setMarkedText(string, selectedRange: selectedRange, replacementRange: replacementRange) guard hasMarkedText(), @@ -67,8 +67,6 @@ final class NativeTextView: NSTextView { guard marked.location != NSNotFound, marked.length > 0 else { return } let nsText = self.string as NSString let paragraph = nsText.paragraphRange(for: marked) - let line = nsText.substring(with: nsText.lineRange(for: NSRange(location: paragraph.location, length: 0))) - guard line.hasPrefix("#") else { return } coord.restyleParagraphs([paragraph], in: self) } From 94bf769bc19049ee9c7e8b0f97ed6abc4541a6bc Mon Sep 17 00:00:00 2001 From: luca-chen198 Date: Thu, 14 May 2026 12:03:56 +0200 Subject: [PATCH 2/2] Skip codeblock-button update during pending edit to avoid stale Y flash textViewDidChangeSelection fires before textDidChange when typing, so viewRect returned the pre-restyle layout and the overlay button briefly rendered at the old Y before settling. textDidChange already runs the update after restyle, so the selection-change pass can skip it when a pending edit is in flight (same guard the restyle path uses). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../NativeTextViewCoordinator+TextDelegate.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/MarkdownEngine/TextView/Coordinator/NativeTextViewCoordinator+TextDelegate.swift b/Sources/MarkdownEngine/TextView/Coordinator/NativeTextViewCoordinator+TextDelegate.swift index dfd5855..5a68e32 100644 --- a/Sources/MarkdownEngine/TextView/Coordinator/NativeTextViewCoordinator+TextDelegate.swift +++ b/Sources/MarkdownEngine/TextView/Coordinator/NativeTextViewCoordinator+TextDelegate.swift @@ -299,8 +299,10 @@ extension NativeTextViewCoordinator { self.previousActiveTokenIndices = self.activeTokenIndices self.previousCaretLocation = caretLoc - // Track code blocks for button overlay (reuse tokens) - updateCodeBlockSelection(textView: tv, tokens: tokens) + // Skip during a pending edit — viewRect is stale until textDidChange's restyle runs; otherwise the overlay flashes to the old Y before settling. + if !shouldSkipSelectionRestyle { + updateCodeBlockSelection(textView: tv, tokens: tokens) + } } public func textView(_ textView: NSTextView, shouldChangeTextIn affectedCharRange: NSRange, replacementString: String?) -> Bool {