diff --git a/src/hooks/usePasteHandler.test.ts b/src/hooks/usePasteHandler.test.ts index 22207e2a..4e071f9b 100644 --- a/src/hooks/usePasteHandler.test.ts +++ b/src/hooks/usePasteHandler.test.ts @@ -1,5 +1,8 @@ import { expect, test } from 'bun:test' -import { supportsClipboardImageFallback } from './usePasteHandler.ts' +import { + shouldHandleInputAsPaste, + supportsClipboardImageFallback, +} from './usePasteHandler.ts' test('supports clipboard image fallback on Windows', () => { expect(supportsClipboardImageFallback('windows')).toBe(true) @@ -20,3 +23,42 @@ test('does not support clipboard image fallback on WSL', () => { test('does not support clipboard image fallback on unknown platforms', () => { expect(supportsClipboardImageFallback('unknown')).toBe(false) }) + +test('does not treat a bracketed paste as pending when no paste handlers are provided', () => { + expect( + shouldHandleInputAsPaste({ + hasTextPasteHandler: false, + hasImagePasteHandler: false, + inputLength: 'kimi-k2.5'.length, + pastePending: false, + hasImageFilePath: false, + isFromPaste: true, + }), + ).toBe(false) +}) + +test('treats bracketed text paste as pending when a text paste handler exists', () => { + expect( + shouldHandleInputAsPaste({ + hasTextPasteHandler: true, + hasImagePasteHandler: false, + inputLength: 'kimi-k2.5'.length, + pastePending: false, + hasImageFilePath: false, + isFromPaste: true, + }), + ).toBe(true) +}) + +test('treats image path paste as pending when only an image handler exists', () => { + expect( + shouldHandleInputAsPaste({ + hasTextPasteHandler: false, + hasImagePasteHandler: true, + inputLength: 'C:\\Users\\jat\\image.png'.length, + pastePending: false, + hasImageFilePath: true, + isFromPaste: false, + }), + ).toBe(true) +}) diff --git a/src/hooks/usePasteHandler.ts b/src/hooks/usePasteHandler.ts index a5fc6a96..f768e8aa 100644 --- a/src/hooks/usePasteHandler.ts +++ b/src/hooks/usePasteHandler.ts @@ -35,6 +35,24 @@ type PasteHandlerProps = { ) => void } +export function shouldHandleInputAsPaste(options: { + hasTextPasteHandler: boolean + hasImagePasteHandler: boolean + inputLength: number + pastePending: boolean + hasImageFilePath: boolean + isFromPaste: boolean +}): boolean { + return ( + (options.hasTextPasteHandler && + (options.inputLength > PASTE_THRESHOLD || + options.pastePending || + options.hasImageFilePath || + options.isFromPaste)) || + (options.hasImagePasteHandler && options.hasImageFilePath) + ) +} + export function usePasteHandler({ onPaste, onInput, @@ -236,11 +254,6 @@ export function usePasteHandler({ // The keypress parser sets isPasted=true for content within bracketed paste. const isFromPaste = event.keypress.isPasted - // If this is pasted content, set isPasting state for UI feedback - if (isFromPaste) { - setIsPasting(true) - } - // Handle large pastes (>PASTE_THRESHOLD chars) // Usually we get one or two input characters at a time. If we // get more than the threshold, the user has probably pasted. @@ -268,6 +281,7 @@ export function usePasteHandler({ canFallbackToClipboardImage && onImagePaste ) { + setIsPasting(true) checkClipboardForImage() // Reset isPasting since there's no text content to process setIsPasting(false) @@ -275,14 +289,17 @@ export function usePasteHandler({ } // Check if we should handle as paste (from bracketed paste, large input, or continuation) - const shouldHandleAsPaste = - onPaste && - (input.length > PASTE_THRESHOLD || - pastePendingRef.current || - hasImageFilePath || - isFromPaste) + const shouldHandleAsPaste = shouldHandleInputAsPaste({ + hasTextPasteHandler: Boolean(onPaste), + hasImagePasteHandler: Boolean(onImagePaste), + inputLength: input.length, + pastePending: pastePendingRef.current, + hasImageFilePath, + isFromPaste, + }) if (shouldHandleAsPaste) { + setIsPasting(true) pastePendingRef.current = true setPasteState(({ chunks, timeoutId }) => { return {