@@ -135,6 +135,19 @@ final class ClearlyTextView: PersistentTextCheckingTextView {
135135
136136 // MARK: - Paste
137137
138+ /// NSTextView's default validation rejects `paste:` when the clipboard
139+ /// has no string/RTF data (e.g., a screenshot put TIFF on the clipboard
140+ /// via Cmd+Shift+Ctrl+4). The Edit > Paste menu item gets disabled and
141+ /// Cmd+V silently no-ops before our `paste(_:)` ever runs. Force-allow
142+ /// `paste:` so our override sees every Cmd+V regardless of clipboard
143+ /// contents — we route image data ourselves in `handleIncomingPasteboard`.
144+ override func validateMenuItem( _ menuItem: NSMenuItem ) -> Bool {
145+ if menuItem. action == #selector( NSText . paste ( _: ) ) {
146+ return true
147+ }
148+ return super. validateMenuItem ( menuItem)
149+ }
150+
138151 override func paste( _ sender: Any ? ) {
139152 let pasteboard = NSPasteboard . general
140153 if handleIncomingPasteboard ( pasteboard) { return }
@@ -173,10 +186,34 @@ final class ClearlyTextView: PersistentTextCheckingTextView {
173186 }
174187 }
175188
189+ // Preserve-format types: write original bytes with matching
190+ // extension so animations / quality / metadata survive. JPEG / GIF
191+ // / HEIC don't have built-in NSPasteboard.PasteboardType constants
192+ // on macOS, so use the UTI strings directly.
193+ let preserveFormats : [ ( NSPasteboard . PasteboardType , String ) ] = [
194+ ( . png, " png " ) ,
195+ ( NSPasteboard . PasteboardType ( " public.jpeg " ) , " jpg " ) ,
196+ ( NSPasteboard . PasteboardType ( " com.compuserve.gif " ) , " gif " ) ,
197+ ( NSPasteboard . PasteboardType ( " public.heic " ) , " heic " ) ,
198+ ]
199+ for (type, ext) in preserveFormats {
200+ if let data = pasteboard. data ( forType: type) {
201+ return insertPastedImage ( data, ext: ext)
202+ }
203+ }
204+
205+ // TIFF is macOS's generic image container — Cmd+Shift+Ctrl+4
206+ // screenshots land here. Decode + re-encode to PNG so we don't
207+ // drop a multi-MB uncompressed sibling next to the document.
208+ if let tiff = pasteboard. data ( forType: . tiff) ,
209+ let png = Self . pngData ( from: tiff) {
210+ return insertPastedImage ( png, ext: " png " )
211+ }
212+
176213 if pasteboard. canReadObject ( forClasses: [ NSImage . self] , options: nil ) ,
177214 let image = NSImage ( pasteboard: pasteboard) ,
178215 let png = Self . pngData ( from: image) {
179- return insertPastedPNG ( png)
216+ return insertPastedImage ( png, ext : " png " )
180217 }
181218
182219 if let text = pasteboard. string ( forType: . string) ? . trimmingCharacters ( in: . whitespacesAndNewlines) ,
@@ -221,14 +258,14 @@ final class ClearlyTextView: PersistentTextCheckingTextView {
221258 }
222259
223260 @discardableResult
224- private func insertPastedPNG ( _ png : Data ) -> Bool {
261+ private func insertPastedImage ( _ data : Data , ext : String ) -> Bool {
225262 guard let docURL = resolveDocumentURLForPaste ( ) else { return false }
226263 do {
227- let result = try ImagePasteService . writePNG ( png , besidesDocumentAt: docURL, presenter: nil )
264+ let result = try ImagePasteService . writeImageData ( data , ext : ext , besidesDocumentAt: docURL, presenter: nil )
228265 insertText ( result. markdown, replacementRange: selectedRange ( ) )
229266 return true
230267 } catch {
231- DiagnosticLog . log ( " Paste: failed to write sibling PNG : \( error. localizedDescription) " )
268+ DiagnosticLog . log ( " Paste: failed to write sibling image ( \( ext ) ) : \( error. localizedDescription) " )
232269 return false
233270 }
234271 }
0 commit comments