@@ -778,10 +778,10 @@ export function Prompt(props: PromptProps) {
778778 e . preventDefault ( )
779779 return
780780 }
781- // Handle clipboard paste (Ctrl+V) - check for images first on Windows
782- // This is needed because Windows terminal doesn't properly send image data
783- // through bracketed paste, so we need to intercept the keypress and
784- // directly read from clipboard before the terminal handles it
781+ // Handle clipboard paste (Ctrl+V) - IMAGES ONLY
782+ // Windows terminal doesn't send image data through bracketed paste,
783+ // so we intercept Ctrl+V and read directly from clipboard for images.
784+ // Text is handled by onPaste (which always calls preventDefault first).
785785 if ( keybind . match ( "input_paste" , e ) ) {
786786 const content = await Clipboard . read ( )
787787 if ( content ?. mime . startsWith ( "image/" ) ) {
@@ -793,7 +793,7 @@ export function Prompt(props: PromptProps) {
793793 } )
794794 return
795795 }
796- // If no image, let the default paste behavior continue
796+ // Text: let onPaste handle it (don't insert here)
797797 }
798798 if ( keybind . match ( "input_clear" , e ) && store . prompt . input !== "" ) {
799799 input . clear ( )
@@ -853,70 +853,62 @@ export function Prompt(props: PromptProps) {
853853 } }
854854 onSubmit = { submit }
855855 onPaste = { async ( event : PasteEvent ) => {
856- if ( props . disabled ) {
857- event . preventDefault ( )
858- return
859- }
856+ // ALWAYS preventDefault FIRST to avoid double-paste on WSL2/ConPTY
857+ event . preventDefault ( )
858+ if ( props . disabled ) return
860859
861- // Normalize line endings at the boundary
862- // Windows ConPTY/Terminal often sends CR-only newlines in bracketed paste
863- // Replace CRLF first, then any remaining CR
860+ // Normalize line endings
864861 const normalizedText = event . text . replace ( / \r \n / g, "\n" ) . replace ( / \r / g, "\n" )
865862 const pastedContent = normalizedText . trim ( )
866- if ( ! pastedContent ) {
867- command . trigger ( "prompt.paste" )
868- return
869- }
863+ if ( ! pastedContent ) return
870864
871- // trim ' from the beginning and end of the pasted content. just
872- // ' and nothing else
865+ // Check if pasted content is a file path
873866 const filepath = pastedContent . replace ( / ^ ' + | ' + $ / g, "" ) . replace ( / \\ / g, " " )
874867 const isUrl = / ^ ( h t t p s ? ) : \/ \/ / . test ( filepath )
875868 if ( ! isUrl ) {
876869 try {
877870 const file = Bun . file ( filepath )
878- // Handle SVG as raw text content, not as base64 image
879- if ( file . type === "image/svg+xml" ) {
880- event . preventDefault ( )
881- const content = await file . text ( ) . catch ( ( ) => { } )
882- if ( content ) {
883- pasteText ( content , `[SVG: ${ file . name ?? "image" } ]` )
884- return
871+ const fileExists = await file . exists ( )
872+ if ( fileExists ) {
873+ // Handle SVG as raw text content
874+ if ( file . type === "image/svg+xml" ) {
875+ const content = await file . text ( ) . catch ( ( ) => { } )
876+ if ( content ) {
877+ pasteText ( content , `[SVG: ${ file . name ?? "image" } ]` )
878+ return
879+ }
885880 }
886- }
887- if ( file . type . startsWith ( "image/" ) ) {
888- event . preventDefault ( )
889- const content = await file
890- . arrayBuffer ( )
891- . then ( ( buffer ) => Buffer . from ( buffer ) . toString ( "base64" ) )
892- . catch ( ( ) => { } )
893- if ( content ) {
894- await pasteImage ( {
895- filename : file . name ,
896- mime : file . type ,
897- content ,
898- } )
899- return
881+ // Handle images
882+ if ( file . type . startsWith ( "image/" ) ) {
883+ const content = await file
884+ . arrayBuffer ( )
885+ . then ( ( buffer ) => Buffer . from ( buffer ) . toString ( "base64" ) )
886+ . catch ( ( ) => { } )
887+ if ( content ) {
888+ await pasteImage ( {
889+ filename : file . name ,
890+ mime : file . type ,
891+ content ,
892+ } )
893+ return
894+ }
900895 }
901896 }
902897 } catch { }
903898 }
904899
900+ // Large paste: show summary
905901 const lineCount = ( pastedContent . match ( / \n / g) ?. length ?? 0 ) + 1
906902 if (
907903 ( lineCount >= 3 || pastedContent . length > 150 ) &&
908904 ! sync . data . config . experimental ?. disable_paste_summary
909905 ) {
910- event . preventDefault ( )
911906 pasteText ( pastedContent , `[Pasted ~${ lineCount } lines]` )
912907 return
913908 }
914909
915- // Force layout update and render for the pasted content
916- setTimeout ( ( ) => {
917- input . getLayoutNode ( ) . markDirty ( )
918- renderer . requestRender ( )
919- } , 0 )
910+ // Small paste: direct insert
911+ input . insertText ( normalizedText )
920912 } }
921913 ref = { ( r : TextareaRenderable ) => {
922914 input = r
0 commit comments